Skip to content

Commit 5352311

Browse files
committed
✨ Allow sorting middleware
At the moment middleware is executed in the order that it is registered. This is fine if middleware is only registered in a single place, but in distributed setups, there may be multiple places in code (possibly across repos) that consumers may want to register middleware. In order to allow some distributed way of defining an execution order of middlewares, this is a non-breaking change that lets consumers define an `order` for their middleware when registering: ```js backend.use('commit', fn, 10); ``` This `order` will be used to sort the middlewares, such that higher `order` execute after middleware with lower `order`. Ties are broken on registration order (the current behaviour), since JavaScript's `.sort()` [is stable][1]. The default value for `order` is `0`, so if consumers want to run middleware before other middleware that has not specified an `order`, they will need to set a negative `order` (which is legal): ```js backend.use('commit', fn, -10); ``` [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#sort_stability
1 parent a8f997b commit 5352311

File tree

3 files changed

+134
-2
lines changed

3 files changed

+134
-2
lines changed

docs/api/backend.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,15 @@ backend.use(action, middleware)
251251
252252
> A [middleware]({{ site.baseurl }}{% link middleware/index.md %}) function
253253
254+
`order` -- number
255+
256+
Optional
257+
{: .label .label-grey }
258+
259+
> Default: `0`
260+
261+
> The order to run this middleware relative to other middlewares. Middleware with higher `order` will run later. Ties are broken on registration order.
262+
254263
### addProjection()
255264
256265
Defines a [projection]({{ site.baseurl }}{% link projections.md %}).

lib/backend.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,15 +183,19 @@ Backend.prototype.addProjection = function(name, collection, fields) {
183183
/**
184184
* Add middleware to an action or array of actions
185185
*/
186-
Backend.prototype.use = function(action, fn) {
186+
Backend.prototype.use = function(action, fn, order) {
187187
if (Array.isArray(action)) {
188188
for (var i = 0; i < action.length; i++) {
189-
this.use(action[i], fn);
189+
this.use(action[i], fn, order);
190190
}
191191
return this;
192192
}
193+
fn.__order = order || 0;
193194
var fns = this.middleware[action] || (this.middleware[action] = []);
194195
fns.push(fn);
196+
fns.sort(function(a, b) {
197+
return a.__order - b.__order;
198+
});
195199
return this;
196200
};
197201

test/middleware.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ var errorHandler = util.errorHandler;
66
var ShareDBError = require('../lib/error');
77
var sinon = require('sinon');
88
var ACTIONS = require('../lib/message-actions').ACTIONS;
9+
var async = require('async');
910

1011
var ERROR_CODE = ShareDBError.CODES;
1112

@@ -35,6 +36,124 @@ describe('middleware', function() {
3536
var response = this.backend.use(['submit', 'connect'], function() {});
3637
expect(response).equal(this.backend);
3738
});
39+
40+
describe('sorting', function() {
41+
var calls;
42+
43+
beforeEach(function() {
44+
calls = [];
45+
});
46+
47+
it('defaults to insertion order', function(done) {
48+
var a = makeMiddleware();
49+
var b = makeMiddleware();
50+
var c = makeMiddleware();
51+
52+
this.backend.use('readSnapshots', a);
53+
this.backend.use('readSnapshots', b);
54+
this.backend.use('readSnapshots', c);
55+
56+
var connection = this.backend.connect();
57+
var doc = connection.get('dogs', 'fido');
58+
59+
async.series([
60+
doc.fetch.bind(doc),
61+
function(next) {
62+
expect(calls).to.eql([a, b, c]);
63+
next();
64+
}
65+
], done);
66+
});
67+
68+
it('sorts middleware by order', function(done) {
69+
var a = makeMiddleware();
70+
var b = makeMiddleware();
71+
var c = makeMiddleware();
72+
73+
this.backend.use('readSnapshots', a, 2);
74+
this.backend.use('readSnapshots', b, 1);
75+
this.backend.use('readSnapshots', c, 3);
76+
77+
var connection = this.backend.connect();
78+
var doc = connection.get('dogs', 'fido');
79+
80+
async.series([
81+
doc.fetch.bind(doc),
82+
function(next) {
83+
expect(calls).to.eql([b, a, c]);
84+
next();
85+
}
86+
], done);
87+
});
88+
89+
it('defaults order to 0', function(done) {
90+
var a = makeMiddleware();
91+
var b = makeMiddleware();
92+
var c = makeMiddleware();
93+
94+
this.backend.use('readSnapshots', a);
95+
this.backend.use('readSnapshots', b, 1);
96+
this.backend.use('readSnapshots', c, -1);
97+
98+
var connection = this.backend.connect();
99+
var doc = connection.get('dogs', 'fido');
100+
101+
async.series([
102+
doc.fetch.bind(doc),
103+
function(next) {
104+
expect(calls).to.eql([c, a, b]);
105+
next();
106+
}
107+
], done);
108+
});
109+
110+
it('can sort using MAX_SAFE_INTEGER and MIN_SAFE_INTEGER', function(done) {
111+
var a = makeMiddleware();
112+
var b = makeMiddleware();
113+
114+
this.backend.use('readSnapshots', a, Number.MAX_SAFE_INTEGER);
115+
this.backend.use('readSnapshots', b, Number.MIN_SAFE_INTEGER);
116+
117+
var connection = this.backend.connect();
118+
var doc = connection.get('dogs', 'fido');
119+
120+
async.series([
121+
doc.fetch.bind(doc),
122+
function(next) {
123+
expect(calls).to.eql([b, a]);
124+
next();
125+
}
126+
], done);
127+
});
128+
129+
130+
it('can sort using MAX_VALUE', function(done) {
131+
var a = makeMiddleware();
132+
var b = makeMiddleware();
133+
134+
this.backend.use('readSnapshots', a, Number.MAX_VALUE);
135+
this.backend.use('readSnapshots', b, -Number.MAX_VALUE);
136+
137+
var connection = this.backend.connect();
138+
var doc = connection.get('dogs', 'fido');
139+
140+
async.series([
141+
doc.fetch.bind(doc),
142+
function(next) {
143+
expect(calls).to.eql([b, a]);
144+
next();
145+
}
146+
], done);
147+
});
148+
149+
function makeMiddleware() {
150+
var fn = function(context, next) {
151+
calls.push(fn);
152+
next();
153+
}
154+
return fn;
155+
}
156+
});
38157
});
39158

40159
describe('connect', function() {

0 commit comments

Comments
 (0)