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 () => {