From 9e36abdd5da77157a668542ff4c460a3addf838b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Rebours?= Date: Wed, 10 Jan 2024 16:59:31 +0100 Subject: [PATCH] add compaction.failed event --- API.md | 13 ++++++++++- CHANGELOG.md | 3 +++ lib/datastore.js | 14 ++++++++++++ lib/persistence.js | 42 +++++++++++++++++++--------------- test/persistence.async.test.js | 16 +++++++++++++ 5 files changed, 68 insertions(+), 20 deletions(-) diff --git a/API.md b/API.md index fe318e5..d5f1bea 100644 --- a/API.md +++ b/API.md @@ -241,7 +241,7 @@ Will return pointers to matched elements (shallow copies), returning full copies **Kind**: global class **Extends**: [EventEmitter](http://nodejs.org/api/events.html) -**Emits**: Datastore#event:"compaction.done" +**Emits**: Datastore#event:"compaction.done", Datastore#event:"compaction.failed" * [Datastore](#Datastore) ⇐ [EventEmitter](http://nodejs.org/api/events.html) * [new Datastore(options)](#new_Datastore_new) @@ -282,6 +282,7 @@ Will return pointers to matched elements (shallow copies), returning full copies * [.remove(query, [options], [cb])](#Datastore+remove) * [.removeAsync(query, [options])](#Datastore+removeAsync) ⇒ Promise.<number> * ["event:compaction.done"](#Datastore+event_compaction.done) + * ["event:compaction.failed"](#Datastore+event_compaction.failed) * _inner_ * [~countCallback](#Datastore..countCallback) : function * [~findOneCallback](#Datastore..findOneCallback) : function @@ -743,6 +744,16 @@ if the update did not actually modify them.

Compaction event. Happens when the Datastore's Persistence has been compacted. It happens when calling [compactDatafileAsync](#Datastore+compactDatafileAsync), which is called periodically if you have called [setAutocompactionInterval](#Datastore+setAutocompactionInterval).

+

In case of failure, it emits [Datastore#event:"compaction.failed"](Datastore#event:"compaction.failed") instead.

+ +**Kind**: event emitted by [Datastore](#Datastore) + + +### "event:compaction.failed" +

Compaction event. Happens when the Datastore's Persistence compaction task has failed. +It may happen when calling [compactDatafileAsync](#Datastore+compactDatafileAsync), which is called periodically if you have called +[setAutocompactionInterval](#Datastore+setAutocompactionInterval).

+

In case of success, it emits [Datastore#event:"compaction.done"](Datastore#event:"compaction.done") instead.

**Kind**: event emitted by [Datastore](#Datastore) diff --git a/CHANGELOG.md b/CHANGELOG.md index a696c7e..600fc0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Explicitly import `buffer` [#34](https://github.com/seald/nedb/pull/34). - Fix `Cursor`'s typings [#45](https://github.com/seald/nedb/issues/45) +### Added +- Added a `compaction.failed` event [#28](https://github.com/seald/nedb/issues/28) + ## [4.0.3] - 2023-12-13 ### Fixed - Fixed EPERM Exception when datastore is at the root of a disk on Windows [#48](https://github.com/seald/nedb/issues/48) diff --git a/lib/datastore.js b/lib/datastore.js index 740abf8..12de523 100755 --- a/lib/datastore.js +++ b/lib/datastore.js @@ -60,10 +60,23 @@ const { isDate, pick, filterIndexNames } = require('./utils.js') * It happens when calling {@link Datastore#compactDatafileAsync}, which is called periodically if you have called * {@link Datastore#setAutocompactionInterval}. * + * In case of failure, it emits {@link Datastore#event:"compaction.failed"} instead. + * * @event Datastore#event:"compaction.done" * @type {undefined} */ +/** + * Compaction event. Happens when the Datastore's Persistence compaction task has failed. + * It may happen when calling {@link Datastore#compactDatafileAsync}, which is called periodically if you have called + * {@link Datastore#setAutocompactionInterval}. + * + * In case of success, it emits {@link Datastore#event:"compaction.done"} instead. + * + * @event Datastore#event:"compaction.failed" + * @type {Error} + */ + /** * Generic document in NeDB. * It consists of an Object with anything you want inside. @@ -143,6 +156,7 @@ const { isDate, pick, filterIndexNames } = require('./utils.js') * @classdesc The `Datastore` class is the main class of NeDB. * @extends external:EventEmitter * @emits Datastore#event:"compaction.done" + * @emits Datastore#event:"compaction.failed" * @typicalname NeDB */ class Datastore extends EventEmitter { diff --git a/lib/persistence.js b/lib/persistence.js index ba359a5..ee1e64f 100755 --- a/lib/persistence.js +++ b/lib/persistence.js @@ -96,27 +96,31 @@ class Persistence { * @private */ async persistCachedDatabaseAsync () { - const lines = [] - - if (this.inMemoryOnly) return + try { + const lines = [] - this.db.getAllData().forEach(doc => { - lines.push(this.afterSerialization(model.serialize(doc))) - }) - Object.keys(this.db.indexes).forEach(fieldName => { - if (fieldName !== '_id') { // The special _id index is managed by datastore.js, the others need to be persisted - lines.push(this.afterSerialization(model.serialize({ - $$indexCreated: { - fieldName: this.db.indexes[fieldName].fieldName, - unique: this.db.indexes[fieldName].unique, - sparse: this.db.indexes[fieldName].sparse - } - }))) - } - }) + if (this.inMemoryOnly) return + this.db.getAllData().forEach(doc => { + lines.push(this.afterSerialization(model.serialize(doc))) + }) + Object.keys(this.db.indexes).forEach(fieldName => { + if (fieldName !== '_id') { // The special _id index is managed by datastore.js, the others need to be persisted + lines.push(this.afterSerialization(model.serialize({ + $$indexCreated: { + fieldName: this.db.indexes[fieldName].fieldName, + unique: this.db.indexes[fieldName].unique, + sparse: this.db.indexes[fieldName].sparse + } + }))) + } + }) - await storage.crashSafeWriteFileLinesAsync(this.filename, lines, this.modes) - this.db.emit('compaction.done') + await storage.crashSafeWriteFileLinesAsync(this.filename, lines, this.modes) + this.db.emit('compaction.done') + } catch (error) { + this.db.emit('compaction.failed', error) + throw error + } } /** diff --git a/test/persistence.async.test.js b/test/persistence.async.test.js index 241147b..a3179b4 100755 --- a/test/persistence.async.test.js +++ b/test/persistence.async.test.js @@ -368,6 +368,7 @@ describe('Persistence async', function () { }) it('Can listen to compaction events', async () => { + d.insertAsync({ a: 2 }) const compacted = new Promise(resolve => { d.once('compaction.done', function () { resolve() @@ -375,6 +376,21 @@ describe('Persistence async', function () { }) await d.compactDatafileAsync() await compacted // should already be resolved when the function returns, but still awaiting for it + + const failure = new Promise(resolve => { + d.once('compaction.failed', function (error) { + resolve(error) + }) + }) + + const afterSerializationOriginal = d.persistence.afterSerialization + d.persistence.afterSerialization = () => { throw new Error('synthetic error') } + await d.compactDatafileAsync().then(() => { throw new Error('should have failed') }, error => { + if (!error || !(error instanceof Error) || error.message !== 'synthetic error') throw error + }) + const error = await failure + if (!error || !(error instanceof Error) || error.message !== 'synthetic error') throw error + d.persistence.afterSerialization = afterSerializationOriginal }) it('setAutocompaction fails gracefully when passed a NaN', async () => {