diff --git a/docs/api/backend.md b/docs/api/backend.md index b3efa8b04..724149574 100644 --- a/docs/api/backend.md +++ b/docs/api/backend.md @@ -251,6 +251,15 @@ backend.use(action, middleware) > A [middleware]({{ site.baseurl }}{% link middleware/index.md %}) function +`order` -- number + +Optional +{: .label .label-grey } + +> Default: `0` + +> The order to run this middleware relative to other middlewares. Middleware with higher `order` will run later. Ties are broken on registration order. + ### addProjection() Defines a [projection]({{ site.baseurl }}{% link projections.md %}). diff --git a/lib/backend.js b/lib/backend.js index dcc95aa33..c3d409bd9 100644 --- a/lib/backend.js +++ b/lib/backend.js @@ -183,15 +183,19 @@ Backend.prototype.addProjection = function(name, collection, fields) { /** * Add middleware to an action or array of actions */ -Backend.prototype.use = function(action, fn) { +Backend.prototype.use = function(action, fn, order) { if (Array.isArray(action)) { for (var i = 0; i < action.length; i++) { - this.use(action[i], fn); + this.use(action[i], fn, order); } return this; } + fn.__order = order || 0; var fns = this.middleware[action] || (this.middleware[action] = []); fns.push(fn); + fns.sort(function(a, b) { + return a.__order - b.__order; + }); return this; }; diff --git a/test/middleware.js b/test/middleware.js index 66e50b2fa..7c73c2a65 100644 --- a/test/middleware.js +++ b/test/middleware.js @@ -6,6 +6,7 @@ var errorHandler = util.errorHandler; var ShareDBError = require('../lib/error'); var sinon = require('sinon'); var ACTIONS = require('../lib/message-actions').ACTIONS; +var async = require('async'); var ERROR_CODE = ShareDBError.CODES; @@ -35,6 +36,124 @@ describe('middleware', function() { var response = this.backend.use(['submit', 'connect'], function() {}); expect(response).equal(this.backend); }); + + describe('sorting', function() { + var calls; + + beforeEach(function() { + calls = []; + }); + + it('defaults to insertion order', function(done) { + var a = makeMiddleware(); + var b = makeMiddleware(); + var c = makeMiddleware(); + + this.backend.use('readSnapshots', a); + this.backend.use('readSnapshots', b); + this.backend.use('readSnapshots', c); + + var connection = this.backend.connect(); + var doc = connection.get('dogs', 'fido'); + + async.series([ + doc.fetch.bind(doc), + function(next) { + expect(calls).to.eql([a, b, c]); + next(); + } + ], done); + }); + + it('sorts middleware by order', function(done) { + var a = makeMiddleware(); + var b = makeMiddleware(); + var c = makeMiddleware(); + + this.backend.use('readSnapshots', a, 2); + this.backend.use('readSnapshots', b, 1); + this.backend.use('readSnapshots', c, 3); + + var connection = this.backend.connect(); + var doc = connection.get('dogs', 'fido'); + + async.series([ + doc.fetch.bind(doc), + function(next) { + expect(calls).to.eql([b, a, c]); + next(); + } + ], done); + }); + + it('defaults order to 0', function(done) { + var a = makeMiddleware(); + var b = makeMiddleware(); + var c = makeMiddleware(); + + this.backend.use('readSnapshots', a); + this.backend.use('readSnapshots', b, 1); + this.backend.use('readSnapshots', c, -1); + + var connection = this.backend.connect(); + var doc = connection.get('dogs', 'fido'); + + async.series([ + doc.fetch.bind(doc), + function(next) { + expect(calls).to.eql([c, a, b]); + next(); + } + ], done); + }); + + it('can sort using MAX_SAFE_INTEGER and MIN_SAFE_INTEGER', function(done) { + var a = makeMiddleware(); + var b = makeMiddleware(); + + this.backend.use('readSnapshots', a, Number.MAX_SAFE_INTEGER); + this.backend.use('readSnapshots', b, Number.MIN_SAFE_INTEGER); + + var connection = this.backend.connect(); + var doc = connection.get('dogs', 'fido'); + + async.series([ + doc.fetch.bind(doc), + function(next) { + expect(calls).to.eql([b, a]); + next(); + } + ], done); + }); + + + it('can sort using MAX_VALUE', function(done) { + var a = makeMiddleware(); + var b = makeMiddleware(); + + this.backend.use('readSnapshots', a, Number.MAX_VALUE); + this.backend.use('readSnapshots', b, -Number.MAX_VALUE); + + var connection = this.backend.connect(); + var doc = connection.get('dogs', 'fido'); + + async.series([ + doc.fetch.bind(doc), + function(next) { + expect(calls).to.eql([b, a]); + next(); + } + ], done); + }); + + function makeMiddleware() { + var fn = function(context, next) { + calls.push(fn); + next(); + }; + return fn; + } + }); }); describe('connect', function() {