Skip to content

Instantly share code, notes, and snippets.

@JamesMGreene
Last active June 26, 2021 09:57
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save JamesMGreene/0e0b2506c7bd2a2557f7 to your computer and use it in GitHub Desktop.
Save JamesMGreene/0e0b2506c7bd2a2557f7 to your computer and use it in GitHub Desktop.
Forcibly adding NeDB events by deriving from the Datastore prototype
// IMPORTANT:
// As of nedb@^1.7.0, Datastore now inherits from EventEmitter in the NeDB core module.
// If you need to support older versions of NeDB, please look at the following previous revision
// of this gist:
// https://gist.github.com/JamesMGreene/0e0b2506c7bd2a2557f7/d8b4b1e97bb0d118c509672e3c7276b6dc4ba13a
/*
This gist provides a module which derives from the NeDB Datastore module and extends it to
emit several important events:
- "load": an event that is emitted when a datastore is fully loaded (or errs and fails to load)
- callback: function( err ) { ... }
- "inserted": an event that is emitted once for EACH new document that is inserted into a datastore
- callback: function( newDoc ) { ... }
- "updated": an event that is emitted once for EACH existing document that is updated within a datastore
- callback: function( newDoc, oldDoc ) { ... }
- "removed": an event that is emitted once for EACH existing document that is removed from a datastore
- callback: function( oldDoc ) { ... }
*/
var util = require('util');
var Datastore = require('nedb');
function EventedDatastore( options ) {
if ( !(this instanceof EventedDatastore) ) {
return new EventedDatastore( options );
}
var self = this;
options = options || {};
var autoLoadOption = options.autoload || false;
options.autoload = false;
Datastore.call( self, options );
options.autoload = autoLoadOption;
self.autoload = autoLoadOption;
// Temporary properties used to collect data for event emissions
self.__removedDocs = null;
self.__modifications = null;
if ( self.autoload ) {
// Wait till the next tick to allow time for event listeners to be attached
process.nextTick(function() {
self.loadDatabase( options.onload );
});
}
}
util.inherits( EventedDatastore, Datastore );
EventedDatastore.prototype.loadDatabase = function( cb ) {
var self = this;
var callback = typeof cb === 'function' ? cb : function( err ) {
if ( err ) {
throw err;
}
};
var eventedCallback = function( err ) {
process.nextTick(function() {
self.emit( 'load', err );
callback( err );
});
};
try {
return Datastore.prototype.loadDatabase.call( self, eventedCallback );
}
catch ( err ) {
eventedCallback.call( self, err );
}
};
EventedDatastore.prototype._insert = function( newDoc, cb ) {
var self = this;
var callback = typeof cb === 'function' ? cb : function() {};
var eventedCallback = function( err, newDocs ) {
process.nextTick(function() {
if ( err ) {
return callback( err, newDocs );
}
var newDocsArr = util.isArray( newDocs ) ? newDocs : [ newDocs ];
// Ensure there are listeners registered before making a bunch of unnecessary function calls to `emit`
if ( self.listeners( 'inserted' ).length > 0 ) {
newDocsArr.forEach(function( newDoc ) {
self.emit( 'inserted', newDoc );
});
}
callback( null, newDocs );
});
};
try {
return Datastore.prototype._insert.call( self, newDoc, eventedCallback );
}
catch ( err ) {
eventedCallback.call( self, err );
}
};
EventedDatastore.prototype.updateIndexes = function( modifications ) {
// Add a new temporary property
this.__modifications = modifications;
return Datastore.prototype.updateIndexes.apply( this, arguments );
};
EventedDatastore.prototype._update = function( query, updateQuery, options, cb ) {
var self = this;
if ( typeof options === 'function' ) {
cb = options;
options = {};
}
if ( options == null ) {
options = {};
}
var callback = typeof cb === 'function' ? cb : function() {};
var eventedCallback = function( err, numAffected, newDocOrAffectedDocs ) {
process.nextTick(function() {
if ( err ) {
return callback( err );
}
var modifications = self.__modifications || [];
if ( modifications && modifications.length > 0 ) {
// Remove the temporary property
self.__modifications = null;
// Ensure there are listeners registered before making a bunch of unnecessary function calls to `emit`
if ( self.listeners( 'updated' ).length > 0 ) {
modifications.forEach(function( mod ) {
self.emit( 'updated', mod.newDoc, mod.oldDoc );
});
}
}
var isNewDoc = modifications.length === 0 && numAffected === 1 && !!newDocOrAffectedDocs;
var newDocsArr = util.isArray( newDocOrAffectedDocs ) ? newDocOrAffectedDocs : [ newDocOrAffectedDocs ];
var affectedDocsArr = options.returnUpdatedDocs === true ? modifications.map(function( mod ) { return mod.newDoc; }) : undefined;
var affectedDocs = isNewDoc ? newDocsArr : affectedDocsArr;
callback( null, numAffected, affectedDocs );
});
};
try {
return Datastore.prototype._update.call( self, query, updateQuery, options, eventedCallback );
}
catch ( err ) {
eventedCallback.call( self, err );
}
};
EventedDatastore.prototype._remove = function( query, options, cb ) {
var self = this;
if ( typeof options === 'function' ) {
cb = options;
options = {};
}
var callback = typeof cb === 'function' ? cb : function() {};
var eventedCallback = function( err, numRemoved ) {
process.nextTick(function() {
if ( err ) {
return callback( err );
}
var removedDocs = self.__removedDocs || [];
// Remove the temporary property
self.__removedDocs = null;
if ( removedDocs && removedDocs.length > 0 ) {
// Ensure there are listeners registered before making a bunch of unnecessary function calls to `emit`
if ( self.listeners( 'removed' ).length > 0 ) {
removedDocs.forEach(function( oldDoc ) {
self.emit( 'removed', oldDoc );
});
}
}
callback( null, numRemoved );
});
};
try {
return Datastore.prototype._remove.call( self, query, options, eventedCallback );
}
catch ( err ) {
eventedCallback.call( self, err );
}
};
EventedDatastore.prototype.removeFromIndexes = function( doc /*, cb */ ) {
var self = this;
// Add a new temporary property
if ( !self.__removedDocs ) {
self.__removedDocs = [];
}
self.__removedDocs.push( doc );
return Datastore.prototype.removeFromIndexes.apply( self, arguments );
};
module.exports = EventedDatastore;
@roccomuso
Copy link

work really appreciated ;)

So if I put a listener like this:

db.codes.on('inserted', function(doc){
    console.log('doc inserted:', doc);
});

and I put in the DB an array of doc, the function above will be called for each doc insertion. Is this the intended behaviour right?

@JamesMGreene
Copy link
Author

@roccomuso: Correct, the intended behavior is to emit an event for each doc that is inserted ("newDoc") or removed ("oldDoc"), and a pair of docs ("newDoc", "oldDoc") for each doc that is updated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment