diff --git a/README.md b/README.md index 2c66759..bba1a62 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,7 @@ Register a new middleware. - `action` _(String)_ One of: + - `'beforeCreate'`: directly before the call to write a new document - `'beforeOverwrite'`: directly before the call to replace a document, can include edits as well as deletions - `'beforeSnapshotLookup'`: directly before the call to issue a query for one or more snapshots by ID - `fn` _(Function(context, callback))_ @@ -209,6 +210,9 @@ Register a new middleware. - `action`: The action this middleware is handling - `collectionName`: The collection name being handled - `options`: Original options as they were passed into the relevant function that triggered the action + - `'beforeCreate'` actions have additional context properties: + - `documentToWrite` - The document to be written + - `op` - The op that represents the changes that will be made to the document - `'beforeOverwrite'` actions have additional context properties: - `documentToWrite` - The document to be written - `op` - The op that represents the changes that will be made to the document diff --git a/index.js b/index.js index 0b341fb..2e256fc 100644 --- a/index.js +++ b/index.js @@ -270,16 +270,21 @@ ShareDbMongo.prototype._writeSnapshot = function(request, id, snapshot, opId, ca if (err) return callback(err); request.documentToWrite = castToDoc(id, snapshot, opId); if (request.documentToWrite._v === 1) { - collection.insertOne(request.documentToWrite, function(err) { - if (err) { - // Return non-success instead of duplicate key error, since this is - // expected to occur during simultaneous creates on the same id - if (err.code === 11000 && /\b_id_\b/.test(err.message)) { - return callback(null, false); - } - return callback(err); + self._middleware.trigger(MiddlewareHandler.Actions.beforeCreate, request, function(middlewareErr) { + if (middlewareErr) { + return callback(middlewareErr); } - callback(null, true); + collection.insertOne(request.documentToWrite, function(err) { + if (err) { + // Return non-success instead of duplicate key error, since this is + // expected to occur during simultaneous creates on the same id + if (err.code === 11000 && /\b_id_\b/.test(err.message)) { + return callback(null, false); + } + return callback(err); + } + callback(null, true); + }); }); } else { request.query = {_id: id, _v: request.documentToWrite._v - 1}; diff --git a/src/middleware/actions.js b/src/middleware/actions.js index 3e33c64..34e13b4 100644 --- a/src/middleware/actions.js +++ b/src/middleware/actions.js @@ -1,4 +1,6 @@ module.exports = { + // Triggers before the call to write a new document is made + beforeCreate: 'beforeCreate', // Triggers before the call to replace a document is made beforeOverwrite: 'beforeOverwrite', // Triggers directly before the call to issue a query for snapshots diff --git a/test/test_mongo_middleware.js b/test/test_mongo_middleware.js index 50b624f..f0dc44a 100644 --- a/test/test_mongo_middleware.js +++ b/test/test_mongo_middleware.js @@ -4,6 +4,7 @@ var ShareDbMongo = require('..'); var mongoUrl = process.env.TEST_MONGO_URL || 'mongodb://localhost:27017/test'; var BEFORE_EDIT = ShareDbMongo.MiddlewareActions.beforeOverwrite; +var BEFORE_CREATE = ShareDbMongo.MiddlewareActions.beforeCreate; var BEFORE_SNAPSHOT_LOOKUP = ShareDbMongo.MiddlewareActions.beforeSnapshotLookup; function create(callback) { @@ -155,6 +156,63 @@ describe('mongo db middleware', function() { }); }); + describe(BEFORE_CREATE, function() { + it('has the expected properties on the request object', function(done) { + db.use(BEFORE_CREATE, function(request, next) { + expect(request).to.have.all.keys([ + 'action', + 'collectionName', + 'documentToWrite', + 'op', + 'options' + ]); + expect(request.action).to.equal(BEFORE_CREATE); + expect(request.collectionName).to.equal('testcollection'); + expect(request.documentToWrite.foo).to.equal('bar'); + expect(request.op).to.exist; + expect(request.options.testOptions).to.equal('baz'); + next(); + done(); + }); + + var snapshot = {type: 'json0', id: 'test1', v: 1, data: {foo: 'bar'}}; + + db.commit('testcollection', snapshot.id, {v: 0, create: {}}, snapshot, {testOptions: 'baz'}, function(err) { + if (err) return done(err); + }); + }); + + it('should augment the written document when commit is called', function(done) { + // Change the written value of foo to be `fuzz` + db.use(BEFORE_CREATE, function(request, next) { + request.documentToWrite.foo = 'fuzz'; + next(); + }); + + var snapshot = {type: 'json0', id: 'test1', v: 1, data: {foo: 'bar'}}; + + db.commit('testcollection', snapshot.id, {v: 0, create: {}}, snapshot, null, function(err) { + if (err) return done(err); + expectDocumentToContainFoo('fuzz', done); + }); + }); + + it('returns without writing when there was a middleware error', function(done) { + db.use(BEFORE_CREATE, function(_, next) { + next(new Error('Oh no!')); + }); + + var snapshot = {type: 'json0', id: 'test1', v: 1, data: {foo: 'bar'}}; + + db.commit('testcollection', snapshot.id, {v: 0, create: {}}, snapshot, null, function(err) { + expectDocumentNotToExist(function() { + if (err) return done(); + done('Expected an error, did not get one'); + }); + }); + }); + }); + describe(BEFORE_SNAPSHOT_LOOKUP, function() { it('has the expected properties on the request object before getting a single snapshot', function(done) { db.use(BEFORE_SNAPSHOT_LOOKUP, function(request, next) { @@ -284,4 +342,14 @@ describe('mongo db middleware', function() { cb(); }); }; + + function expectDocumentNotToExist(cb) { + var query = {_id: 'test1'}; + + db.query('testcollection', query, null, null, function(err, results) { + if (err) return cb(err); + expect(results).to.be.empty; + cb(); + }); + }; });