sharedb-mongo 1.0.0-beta.8 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +49 -0
- package/.github/workflows/test.yml +58 -0
- package/LICENSE +21 -0
- package/README.md +70 -61
- package/index.js +275 -128
- package/mongodb.js +3 -0
- package/op-link-validator.js +8 -8
- package/package.json +25 -12
- package/src/middleware/actions.js +9 -0
- package/src/middleware/middlewareHandler.js +66 -0
- package/test/mocha.opts +1 -0
- package/test/setup.js +10 -0
- package/test/test_get_ops.js +135 -0
- package/test/test_get_ops_without_strict_linking.js +66 -69
- package/test/test_mongo.js +121 -45
- package/test/test_mongo_middleware.js +469 -0
- package/test/test_op_link_validator.js +49 -49
- package/test/test_skip_poll.js +8 -5
- package/.travis.yml +0 -27
package/mongodb.js
ADDED
package/op-link-validator.js
CHANGED
|
@@ -37,17 +37,17 @@ function OpLinkValidator() {
|
|
|
37
37
|
this.oneBeforePreviousOp = undefined;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
OpLinkValidator.prototype.push = function
|
|
40
|
+
OpLinkValidator.prototype.push = function(op) {
|
|
41
41
|
this.oneBeforePreviousOp = this.previousOp;
|
|
42
42
|
this.previousOp = this.currentOp;
|
|
43
43
|
this.currentOp = op;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
-
OpLinkValidator.prototype.opWithUniqueVersion = function
|
|
46
|
+
OpLinkValidator.prototype.opWithUniqueVersion = function() {
|
|
47
47
|
return this._previousVersionWasUnique() ? this.previousOp : null;
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
-
OpLinkValidator.prototype.isAtEndOfList = function
|
|
50
|
+
OpLinkValidator.prototype.isAtEndOfList = function() {
|
|
51
51
|
// We ascribe a special meaning to a current op of null
|
|
52
52
|
// being that we're at the end of the list, because this
|
|
53
53
|
// is the value that the Mongo cursor will return when
|
|
@@ -55,23 +55,23 @@ OpLinkValidator.prototype.isAtEndOfList = function () {
|
|
|
55
55
|
return this.currentOp === null;
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
-
OpLinkValidator.prototype._previousVersionWasUnique = function
|
|
59
|
-
|
|
58
|
+
OpLinkValidator.prototype._previousVersionWasUnique = function() {
|
|
59
|
+
var previousVersion = this._previousVersion();
|
|
60
60
|
|
|
61
61
|
return typeof previousVersion === 'number'
|
|
62
62
|
&& previousVersion !== this._currentVersion()
|
|
63
63
|
&& previousVersion !== this._oneBeforePreviousVersion();
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
-
OpLinkValidator.prototype._currentVersion = function
|
|
66
|
+
OpLinkValidator.prototype._currentVersion = function() {
|
|
67
67
|
return this.currentOp && this.currentOp.v;
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
-
OpLinkValidator.prototype._previousVersion = function
|
|
70
|
+
OpLinkValidator.prototype._previousVersion = function() {
|
|
71
71
|
return this.previousOp && this.previousOp.v;
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
-
OpLinkValidator.prototype._oneBeforePreviousVersion = function
|
|
74
|
+
OpLinkValidator.prototype._oneBeforePreviousVersion = function() {
|
|
75
75
|
return this.oneBeforePreviousOp && this.oneBeforePreviousOp.v;
|
|
76
76
|
};
|
|
77
77
|
|
package/package.json
CHANGED
|
@@ -1,24 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sharedb-mongo",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "MongoDB database adapter for ShareDB",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"async": "^
|
|
8
|
-
"mongodb": "^2.1.2",
|
|
9
|
-
"sharedb": "^1.0.0
|
|
7
|
+
"async": "^2.6.3",
|
|
8
|
+
"mongodb": "^2.1.2 || ^3.1.13 || ^4.0.0",
|
|
9
|
+
"sharedb": "^1.9.1 || ^2.0.0 || ^3.0.0"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
12
|
+
"chai": "^4.2.0",
|
|
13
|
+
"coveralls": "^3.0.7",
|
|
14
|
+
"eslint": "^5.16.0",
|
|
15
|
+
"eslint-config-google": "^0.13.0",
|
|
16
|
+
"mocha": "^6.2.2",
|
|
17
|
+
"mongodb2": "npm:mongodb@^2.1.2",
|
|
18
|
+
"mongodb3": "npm:mongodb@^3.0.0",
|
|
19
|
+
"mongodb4": "npm:mongodb@^4.0.0",
|
|
20
|
+
"nyc": "^14.1.1",
|
|
21
|
+
"ot-json1": "^1.0.1",
|
|
22
|
+
"sharedb-mingo-memory": "^1.1.1",
|
|
23
|
+
"sinon": "^6.1.5",
|
|
24
|
+
"sinon-chai": "^3.7.0"
|
|
18
25
|
},
|
|
19
26
|
"scripts": {
|
|
20
|
-
"
|
|
21
|
-
"
|
|
27
|
+
"lint": "./node_modules/.bin/eslint --ignore-path .gitignore '**/*.js'",
|
|
28
|
+
"lint:fix": "npm run lint -- --fix",
|
|
29
|
+
"test": "mocha",
|
|
30
|
+
"test:mongodb2": "_SHAREDB_MONGODB_DRIVER=mongodb2 npm test",
|
|
31
|
+
"test:mongodb3": "_SHAREDB_MONGODB_DRIVER=mongodb3 npm test",
|
|
32
|
+
"test:mongodb4": "_SHAREDB_MONGODB_DRIVER=mongodb4 npm test",
|
|
33
|
+
"test:all": "npm run test:mongodb2 && npm run test:mongodb3 && npm run test:mongodb4",
|
|
34
|
+
"test-cover": "nyc --temp-dir=coverage -r text -r lcov npm run test:all"
|
|
22
35
|
},
|
|
23
36
|
"repository": "git://github.com/share/sharedb-mongo.git",
|
|
24
37
|
"author": "Nate Smith and Joseph Gentle",
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
// Triggers before the call to write a new document is made
|
|
3
|
+
beforeCreate: 'beforeCreate',
|
|
4
|
+
// Triggers before the call to replace a document is made
|
|
5
|
+
beforeOverwrite: 'beforeOverwrite',
|
|
6
|
+
// Triggers directly before the call to issue a query for snapshots
|
|
7
|
+
// Applies for both a single lookup by ID and bulk lookups by a list of IDs
|
|
8
|
+
beforeSnapshotLookup: 'beforeSnapshotLookup'
|
|
9
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
var MIDDLEWARE_ACTIONS = require('./actions');
|
|
2
|
+
|
|
3
|
+
function MiddlewareHandler() {
|
|
4
|
+
this._middleware = {};
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Add middleware to an action or array of actions
|
|
9
|
+
*
|
|
10
|
+
* @param action The action to use from MIDDLEWARE_ACTIONS (e.g. 'beforeOverwrite')
|
|
11
|
+
* @param fn The function to call when this middleware is triggered
|
|
12
|
+
* The fn receives a request object with information on the triggered action (e.g. the snapshot to write)
|
|
13
|
+
* and a next function to call once the middleware is complete
|
|
14
|
+
*
|
|
15
|
+
* NOTE: It is recommended not to add async or long running tasks to the sharedb-mongo middleware as it will
|
|
16
|
+
* be called very frequently during sensitive operations. It may have a significant performance impact.
|
|
17
|
+
*/
|
|
18
|
+
MiddlewareHandler.prototype.use = function(action, fn) {
|
|
19
|
+
if (Array.isArray(action)) {
|
|
20
|
+
for (var i = 0; i < action.length; i++) {
|
|
21
|
+
this.use(action[i], fn);
|
|
22
|
+
}
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
if (!action) throw new Error('Expected action to be defined');
|
|
26
|
+
if (!fn) throw new Error('Expected fn to be defined');
|
|
27
|
+
if (!Object.values(MIDDLEWARE_ACTIONS).includes(action)) {
|
|
28
|
+
throw new Error('Unrecognized action name ' + action);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
var fns = this._middleware[action] || (this._middleware[action] = []);
|
|
32
|
+
fns.push(fn);
|
|
33
|
+
return this;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Passes request through the middleware stack
|
|
38
|
+
*
|
|
39
|
+
* Middleware may modify the request object. After all middleware have been
|
|
40
|
+
* invoked we call `callback` with `null` and the modified request. If one of
|
|
41
|
+
* the middleware resturns an error the callback is called with that error.
|
|
42
|
+
*
|
|
43
|
+
* @param action The action to trigger from MIDDLEWARE_ACTIONS (e.g. 'beforeOverwrite')
|
|
44
|
+
* @param request Request details such as the snapshot to write, depends on the triggered action
|
|
45
|
+
* @param callback Function to call once the middleware has been processed.
|
|
46
|
+
*/
|
|
47
|
+
MiddlewareHandler.prototype.trigger = function(action, request, callback) {
|
|
48
|
+
request.action = action;
|
|
49
|
+
|
|
50
|
+
var fns = this._middleware[action];
|
|
51
|
+
if (!fns) return callback();
|
|
52
|
+
|
|
53
|
+
// Copying the triggers we'll fire so they don't get edited while we iterate.
|
|
54
|
+
fns = fns.slice();
|
|
55
|
+
var next = function(err) {
|
|
56
|
+
if (err) return callback(err);
|
|
57
|
+
var fn = fns.shift();
|
|
58
|
+
if (!fn) return callback();
|
|
59
|
+
fn(request, next);
|
|
60
|
+
};
|
|
61
|
+
next();
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
MiddlewareHandler.Actions = MIDDLEWARE_ACTIONS;
|
|
65
|
+
|
|
66
|
+
module.exports = MiddlewareHandler;
|
package/test/mocha.opts
CHANGED
package/test/setup.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
var expect = require('chai').expect;
|
|
2
|
+
var ShareDbMongo = require('..');
|
|
3
|
+
var sinon = require('sinon');
|
|
4
|
+
|
|
5
|
+
var mongoUrl = process.env.TEST_MONGO_URL || 'mongodb://localhost:27017/test';
|
|
6
|
+
|
|
7
|
+
function create(options, callback) {
|
|
8
|
+
var opts = Object.assign({
|
|
9
|
+
mongoOptions: {},
|
|
10
|
+
getOpsWithoutStrictLinking: true
|
|
11
|
+
}, options);
|
|
12
|
+
var db = new ShareDbMongo(mongoUrl, opts);
|
|
13
|
+
db.getDbs(function(err, mongo) {
|
|
14
|
+
if (err) return callback(err);
|
|
15
|
+
mongo.dropDatabase(function(err) {
|
|
16
|
+
if (err) return callback(err);
|
|
17
|
+
callback(null, db, mongo);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// loop thru strict linking options
|
|
23
|
+
[true, false].forEach(function(strictLinkingOption) {
|
|
24
|
+
describe('getOps with strict linking ' + strictLinkingOption, function() {
|
|
25
|
+
beforeEach(function(done) {
|
|
26
|
+
var self = this;
|
|
27
|
+
create(
|
|
28
|
+
{getOpsWithoutStrictLinking: strictLinkingOption},
|
|
29
|
+
function(err, db, mongo) {
|
|
30
|
+
if (err) return done(err);
|
|
31
|
+
self.db = db;
|
|
32
|
+
self.mongo = mongo;
|
|
33
|
+
done();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(function(done) {
|
|
38
|
+
this.db.close(done);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('a chain of ops', function() {
|
|
42
|
+
var db;
|
|
43
|
+
var mongo;
|
|
44
|
+
var id;
|
|
45
|
+
var collection;
|
|
46
|
+
|
|
47
|
+
beforeEach(function(done) {
|
|
48
|
+
db = this.db;
|
|
49
|
+
mongo = this.mongo;
|
|
50
|
+
id = 'document1';
|
|
51
|
+
collection = 'testcollection';
|
|
52
|
+
|
|
53
|
+
sinon.spy(db, '_getOps');
|
|
54
|
+
sinon.spy(db, '_getSnapshotOpLink');
|
|
55
|
+
|
|
56
|
+
var ops = [
|
|
57
|
+
{v: 0, create: {}},
|
|
58
|
+
{v: 1, p: ['foo'], oi: 'bar'},
|
|
59
|
+
{v: 2, p: ['foo'], oi: 'baz'},
|
|
60
|
+
{v: 3, p: ['foo'], oi: 'qux'}
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
commitOpChain(db, mongo, collection, id, ops, function(error) {
|
|
64
|
+
if (error) done(error);
|
|
65
|
+
mongo.collection('o_' + collection).deleteOne({v: 1}, done);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('fetches ops 2-3 without fetching all ops', function(done) {
|
|
70
|
+
db.getOps(collection, id, 2, 4, null, function(error, ops) {
|
|
71
|
+
if (error) return done(error);
|
|
72
|
+
expect(ops.length).to.equal(2);
|
|
73
|
+
expect(ops[0].v).to.equal(2);
|
|
74
|
+
expect(ops[1].v).to.equal(3);
|
|
75
|
+
done();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('default option errors when missing ops', function(done) {
|
|
80
|
+
db.getOps(collection, id, 0, 4, null, function(error) {
|
|
81
|
+
expect(error.code).to.equal(5103);
|
|
82
|
+
expect(error.message).to.equal('Missing ops from requested version testcollection.document1 0');
|
|
83
|
+
done();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('ignoreMissingOps option returns ops up to the first missing op', function(done) {
|
|
88
|
+
db.getOps(collection, id, 0, 4, {ignoreMissingOps: true}, function(error, ops) {
|
|
89
|
+
if (error) return done(error);
|
|
90
|
+
expect(ops.length).to.equal(2);
|
|
91
|
+
expect(ops[0].v).to.equal(2);
|
|
92
|
+
expect(ops[1].v).to.equal(3);
|
|
93
|
+
done();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('getOpsToSnapshot ignoreMissingOps option returns ops up to the first missing op', function(done) {
|
|
98
|
+
db.getSnapshot(collection, id, {$submit: true}, null, function(error, snapshot) {
|
|
99
|
+
if (error) done(error);
|
|
100
|
+
db.getOpsToSnapshot(collection, id, 0, snapshot, {ignoreMissingOps: true}, function(error, ops) {
|
|
101
|
+
if (error) return done(error);
|
|
102
|
+
expect(ops.length).to.equal(2);
|
|
103
|
+
expect(ops[0].v).to.equal(2);
|
|
104
|
+
expect(ops[1].v).to.equal(3);
|
|
105
|
+
done();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
function commitOpChain(db, mongo, collection, id, ops, previousOpId, version, callback) {
|
|
114
|
+
if (typeof previousOpId === 'function') {
|
|
115
|
+
callback = previousOpId;
|
|
116
|
+
previousOpId = undefined;
|
|
117
|
+
version = 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
ops = ops.slice();
|
|
121
|
+
var op = ops.shift();
|
|
122
|
+
|
|
123
|
+
if (!op) {
|
|
124
|
+
return callback();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
var snapshot = {id: id, v: version + 1, type: 'json0', data: {}, m: null, _opLink: previousOpId};
|
|
128
|
+
db.commit(collection, id, op, snapshot, null, function(error) {
|
|
129
|
+
if (error) return callback(error);
|
|
130
|
+
mongo.collection('o_' + collection).find({d: id, v: version}).next(function(error, op) {
|
|
131
|
+
if (error) return callback(error);
|
|
132
|
+
commitOpChain(db, mongo, collection, id, ops, (op ? op._id : null), ++version, callback);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
var expect = require('
|
|
2
|
-
var mongodb = require('mongodb');
|
|
1
|
+
var expect = require('chai').expect;
|
|
3
2
|
var ShareDbMongo = require('..');
|
|
4
3
|
var getQuery = require('sharedb-mingo-memory/get-query');
|
|
5
4
|
var sinon = require('sinon');
|
|
@@ -7,27 +6,25 @@ var sinon = require('sinon');
|
|
|
7
6
|
var mongoUrl = process.env.TEST_MONGO_URL || 'mongodb://localhost:27017/test';
|
|
8
7
|
|
|
9
8
|
function create(callback) {
|
|
10
|
-
var db = ShareDbMongo({
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
},
|
|
21
|
-
getOpsWithoutStrictLinking: true,
|
|
9
|
+
var db = new ShareDbMongo(mongoUrl, {
|
|
10
|
+
mongoOptions: {},
|
|
11
|
+
getOpsWithoutStrictLinking: true
|
|
12
|
+
});
|
|
13
|
+
db.getDbs(function(err, mongo) {
|
|
14
|
+
if (err) return callback(err);
|
|
15
|
+
mongo.dropDatabase(function(err) {
|
|
16
|
+
if (err) return callback(err);
|
|
17
|
+
callback(null, db, mongo);
|
|
18
|
+
});
|
|
22
19
|
});
|
|
23
20
|
};
|
|
24
21
|
|
|
25
22
|
require('sharedb/test/db')({create: create, getQuery: getQuery});
|
|
26
23
|
|
|
27
|
-
describe('getOpsWithoutStrictLinking: true', function
|
|
28
|
-
beforeEach(function
|
|
24
|
+
describe('getOpsWithoutStrictLinking: true', function() {
|
|
25
|
+
beforeEach(function(done) {
|
|
29
26
|
var self = this;
|
|
30
|
-
create(function
|
|
27
|
+
create(function(err, db, mongo) {
|
|
31
28
|
if (err) return done(err);
|
|
32
29
|
self.db = db;
|
|
33
30
|
self.mongo = mongo;
|
|
@@ -35,17 +32,17 @@ describe('getOpsWithoutStrictLinking: true', function () {
|
|
|
35
32
|
});
|
|
36
33
|
});
|
|
37
34
|
|
|
38
|
-
afterEach(function
|
|
35
|
+
afterEach(function(done) {
|
|
39
36
|
this.db.close(done);
|
|
40
37
|
});
|
|
41
38
|
|
|
42
|
-
describe('a chain of ops', function
|
|
43
|
-
var db
|
|
39
|
+
describe('a chain of ops', function() {
|
|
40
|
+
var db;
|
|
44
41
|
var mongo;
|
|
45
42
|
var id;
|
|
46
43
|
var collection;
|
|
47
44
|
|
|
48
|
-
beforeEach(function
|
|
45
|
+
beforeEach(function(done) {
|
|
49
46
|
db = this.db;
|
|
50
47
|
mongo = this.mongo;
|
|
51
48
|
id = 'document1';
|
|
@@ -55,93 +52,93 @@ describe('getOpsWithoutStrictLinking: true', function () {
|
|
|
55
52
|
sinon.spy(db, '_getSnapshotOpLink');
|
|
56
53
|
|
|
57
54
|
var ops = [
|
|
58
|
-
{
|
|
59
|
-
{
|
|
60
|
-
{
|
|
61
|
-
{
|
|
55
|
+
{v: 0, create: {}},
|
|
56
|
+
{v: 1, p: ['foo'], oi: 'bar'},
|
|
57
|
+
{v: 2, p: ['foo'], oi: 'baz'},
|
|
58
|
+
{v: 3, p: ['foo'], oi: 'qux'}
|
|
62
59
|
];
|
|
63
60
|
|
|
64
61
|
commitOpChain(db, mongo, collection, id, ops, done);
|
|
65
62
|
});
|
|
66
63
|
|
|
67
|
-
it('fetches ops 0-1 without fetching all ops', function
|
|
68
|
-
db.getOps(collection, id, 0, 2, null, function
|
|
64
|
+
it('fetches ops 0-1 without fetching all ops', function(done) {
|
|
65
|
+
db.getOps(collection, id, 0, 2, null, function(error, ops) {
|
|
69
66
|
if (error) return done(error);
|
|
70
|
-
expect(ops.length).to.
|
|
71
|
-
expect(ops[0].v).to.
|
|
72
|
-
expect(ops[1].v).to.
|
|
73
|
-
expect(db._getSnapshotOpLink.notCalled).to.
|
|
74
|
-
expect(db._getOps.calledOnceWith(collection, id, 0, 2)).to.
|
|
67
|
+
expect(ops.length).to.equal(2);
|
|
68
|
+
expect(ops[0].v).to.equal(0);
|
|
69
|
+
expect(ops[1].v).to.equal(1);
|
|
70
|
+
expect(db._getSnapshotOpLink.notCalled).to.equal(true);
|
|
71
|
+
expect(db._getOps.calledOnceWith(collection, id, 0, 2)).to.equal(true);
|
|
75
72
|
done();
|
|
76
73
|
});
|
|
77
74
|
});
|
|
78
75
|
|
|
79
|
-
it('fetches ops 0-1 when v1 has a spurious duplicate', function
|
|
80
|
-
var spuriousOp = {
|
|
76
|
+
it('fetches ops 0-1 when v1 has a spurious duplicate', function(done) {
|
|
77
|
+
var spuriousOp = {v: 1, d: id, p: ['foo'], oi: 'corrupt', o: null};
|
|
81
78
|
|
|
82
79
|
callInSeries([
|
|
83
|
-
function
|
|
84
|
-
mongo.collection('o_' + collection).
|
|
80
|
+
function(next) {
|
|
81
|
+
mongo.collection('o_' + collection).insertOne(spuriousOp, next);
|
|
85
82
|
},
|
|
86
|
-
function
|
|
83
|
+
function(result, next) {
|
|
87
84
|
db.getOps(collection, id, 0, 2, null, next);
|
|
88
85
|
},
|
|
89
|
-
function
|
|
90
|
-
expect(ops.length).to.
|
|
91
|
-
expect(ops[1].oi).to.
|
|
92
|
-
expect(db._getSnapshotOpLink.notCalled).to.
|
|
93
|
-
expect(db._getOps.calledOnceWith(collection, id, 0, 2)).to.
|
|
86
|
+
function(ops, next) {
|
|
87
|
+
expect(ops.length).to.equal(2);
|
|
88
|
+
expect(ops[1].oi).to.equal('bar');
|
|
89
|
+
expect(db._getSnapshotOpLink.notCalled).to.equal(true);
|
|
90
|
+
expect(db._getOps.calledOnceWith(collection, id, 0, 2)).to.equal(true);
|
|
94
91
|
next();
|
|
95
92
|
},
|
|
96
93
|
done
|
|
97
94
|
]);
|
|
98
95
|
});
|
|
99
96
|
|
|
100
|
-
it('fetches ops 0-1 when the next op v2 has a spurious duplicate', function
|
|
101
|
-
var spuriousOp = {
|
|
97
|
+
it('fetches ops 0-1 when the next op v2 has a spurious duplicate', function(done) {
|
|
98
|
+
var spuriousOp = {v: 2, d: id, p: ['foo'], oi: 'corrupt', o: null};
|
|
102
99
|
|
|
103
100
|
callInSeries([
|
|
104
|
-
function
|
|
105
|
-
mongo.collection('o_' + collection).
|
|
101
|
+
function(next) {
|
|
102
|
+
mongo.collection('o_' + collection).insertOne(spuriousOp, next);
|
|
106
103
|
},
|
|
107
|
-
function
|
|
104
|
+
function(result, next) {
|
|
108
105
|
db.getOps(collection, id, 0, 2, null, next);
|
|
109
106
|
},
|
|
110
|
-
function
|
|
111
|
-
expect(ops.length).to.
|
|
112
|
-
expect(ops[1].oi).to.
|
|
113
|
-
expect(db._getSnapshotOpLink.notCalled).to.
|
|
114
|
-
expect(db._getOps.calledOnceWith(collection, id, 0, 3)).to.
|
|
107
|
+
function(ops, next) {
|
|
108
|
+
expect(ops.length).to.equal(2);
|
|
109
|
+
expect(ops[1].oi).to.equal('bar');
|
|
110
|
+
expect(db._getSnapshotOpLink.notCalled).to.equal(true);
|
|
111
|
+
expect(db._getOps.calledOnceWith(collection, id, 0, 3)).to.equal(true);
|
|
115
112
|
next();
|
|
116
113
|
},
|
|
117
114
|
done
|
|
118
115
|
]);
|
|
119
116
|
});
|
|
120
117
|
|
|
121
|
-
it('fetches ops 0-1 when all the ops have spurious duplicates', function
|
|
118
|
+
it('fetches ops 0-1 when all the ops have spurious duplicates', function(done) {
|
|
122
119
|
var spuriousOps = [
|
|
123
|
-
{
|
|
124
|
-
{
|
|
125
|
-
{
|
|
126
|
-
{
|
|
120
|
+
{v: 0, d: id, p: ['foo'], oi: 'corrupt', o: null},
|
|
121
|
+
{v: 1, d: id, p: ['foo'], oi: 'corrupt', o: null},
|
|
122
|
+
{v: 2, d: id, p: ['foo'], oi: 'corrupt', o: null},
|
|
123
|
+
{v: 3, d: id, p: ['foo'], oi: 'corrupt', o: null}
|
|
127
124
|
];
|
|
128
125
|
|
|
129
126
|
callInSeries([
|
|
130
|
-
function
|
|
127
|
+
function(next) {
|
|
131
128
|
mongo.collection('o_' + collection).insertMany(spuriousOps, next);
|
|
132
129
|
},
|
|
133
|
-
function
|
|
130
|
+
function(result, next) {
|
|
134
131
|
db.getOps(collection, id, 0, 2, null, next);
|
|
135
132
|
},
|
|
136
|
-
function
|
|
137
|
-
expect(ops.length).to.
|
|
133
|
+
function(ops, next) {
|
|
134
|
+
expect(ops.length).to.equal(2);
|
|
138
135
|
expect(ops[0].create).to.eql({});
|
|
139
|
-
expect(ops[1].oi).to.
|
|
140
|
-
expect(db._getSnapshotOpLink.calledOnce).to.
|
|
136
|
+
expect(ops[1].oi).to.equal('bar');
|
|
137
|
+
expect(db._getSnapshotOpLink.calledOnce).to.equal(true);
|
|
141
138
|
next();
|
|
142
139
|
},
|
|
143
|
-
done
|
|
144
|
-
])
|
|
140
|
+
done
|
|
141
|
+
]);
|
|
145
142
|
});
|
|
146
143
|
});
|
|
147
144
|
});
|
|
@@ -160,10 +157,10 @@ function commitOpChain(db, mongo, collection, id, ops, previousOpId, version, ca
|
|
|
160
157
|
return callback();
|
|
161
158
|
}
|
|
162
159
|
|
|
163
|
-
var snapshot = {
|
|
164
|
-
db.commit(collection, id, op, snapshot, null, function
|
|
160
|
+
var snapshot = {id: id, v: version + 1, type: 'json0', data: {}, m: null, _opLink: previousOpId};
|
|
161
|
+
db.commit(collection, id, op, snapshot, null, function(error) {
|
|
165
162
|
if (error) return callback(error);
|
|
166
|
-
mongo.collection('o_' + collection).find({
|
|
163
|
+
mongo.collection('o_' + collection).find({d: id, v: version}).next(function(error, op) {
|
|
167
164
|
if (error) return callback(error);
|
|
168
165
|
commitOpChain(db, mongo, collection, id, ops, op._id, ++version, callback);
|
|
169
166
|
});
|
|
@@ -182,7 +179,7 @@ function callInSeries(callbacks, args) {
|
|
|
182
179
|
|
|
183
180
|
var callback = callbacks.shift();
|
|
184
181
|
if (callbacks.length) {
|
|
185
|
-
args.push(function
|
|
182
|
+
args.push(function() {
|
|
186
183
|
var args = Array.from(arguments);
|
|
187
184
|
callInSeries(callbacks, args);
|
|
188
185
|
});
|