Skip to content
This repository was archived by the owner on Apr 2, 2024. It is now read-only.

Commit 39a95c8

Browse files
authored
Merge branch 'master' into warn-about-deprecated-features
2 parents f157131 + 721a0ab commit 39a95c8

File tree

11 files changed

+182
-32
lines changed

11 files changed

+182
-32
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,5 @@ coverage
3030

3131
# Dependency directories
3232
node_modules
33+
package-lock.json
3334
jspm_packages

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ node_js:
33
- "10"
44
- "8"
55
- "6"
6-
script: "ln -s .. node_modules/sharedb; npm run jshint && npm run test-cover"
6+
script: "npm run jshint && npm run test-cover"
77
# Send coverage data to Coveralls
88
after_script: "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js"

lib/client/doc.js

+11-3
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,19 @@ emitter.mixin(Doc);
104104
Doc.prototype.destroy = function(callback) {
105105
var doc = this;
106106
doc.whenNothingPending(function() {
107-
doc.connection._destroyDoc(doc);
108107
if (doc.wantSubscribe) {
109-
return doc.unsubscribe(callback);
108+
doc.unsubscribe(function(err) {
109+
if (err) {
110+
if (callback) return callback(err);
111+
return doc.emit('error', err);
112+
}
113+
doc.connection._destroyDoc(doc);
114+
if (callback) callback();
115+
});
116+
} else {
117+
doc.connection._destroyDoc(doc);
118+
if (callback) callback();
110119
}
111-
if (callback) callback();
112120
});
113121
};
114122

lib/db/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ function DB(options) {
77
}
88
module.exports = DB;
99

10+
// When false, Backend will handle projections instead of DB
1011
DB.prototype.projectsSnapshots = false;
1112
DB.prototype.disableSubscribe = false;
1213

lib/ot.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ exports.apply = function(snapshot, op) {
102102
function applyOpEdit(snapshot, edit) {
103103
if (!snapshot.type) return {code: 4015, message: 'Document does not exist'};
104104

105-
if (typeof edit !== 'object') return {code: 5004, message: 'Missing op'};
105+
if (edit == null) return {code: 5004, message: 'Missing op'};
106106
var type = types[snapshot.type];
107107
if (!type) return {code: 4008, message: 'Unknown type'};
108108

package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616
"expect.js": "^0.3.1",
1717
"istanbul": "^0.4.2",
1818
"jshint": "^2.9.2",
19-
"mocha": "^3.2.0",
20-
"sharedb-mingo-memory": "^1.0.0-beta"
19+
"mocha": "^5.2.0"
2120
},
2221
"scripts": {
2322
"test": "./node_modules/.bin/mocha && npm run jshint",

test/client/number-type.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// A simple number type, where:
2+
//
3+
// - snapshot is an integer
4+
// - operation is an integer
5+
exports.type = {
6+
name: 'number-type',
7+
uri: 'http://sharejs.org/types/number-type',
8+
create: create,
9+
apply: apply,
10+
transform: transform
11+
};
12+
13+
function create(data) {
14+
return data | 0;
15+
}
16+
17+
function apply(snapshot, op) {
18+
return snapshot + op;
19+
}
20+
21+
function transform(op1, op2, side) {
22+
return op1;
23+
}

test/client/query-subscribe.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -413,14 +413,13 @@ describe('client query subscribe', function() {
413413

414414
it('changing a sorted property moves in a subscribed query', function(done) {
415415
var connection = this.backend.connect();
416-
var matchAllDbQuery = this.matchAllDbQuery;
417416

418417
async.parallel([
419418
function(cb) { connection.get('dogs', 'fido').create({age: 3}, cb); },
420419
function(cb) { connection.get('dogs', 'spot').create({age: 5}, cb); }
421420
], function(err) {
422421
if (err) return done(err);
423-
var dbQuery = getQuery({query: matchAllDbQuery, sort: [['age', 1]]});
422+
var dbQuery = getQuery({query: {}, sort: [['age', 1]]});
424423
var query = connection.createSubscribeQuery(
425424
'dogs',
426425
dbQuery,

test/client/submit.js

+57-13
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ var async = require('async');
22
var expect = require('expect.js');
33
var types = require('../../lib/types');
44
var deserializedType = require('./deserialized-type');
5+
var numberType = require('./number-type');
56
types.register(deserializedType.type);
67
types.register(deserializedType.type2);
8+
types.register(numberType.type);
79

810
module.exports = function() {
911
describe('client submit', function() {
@@ -509,25 +511,44 @@ describe('client submit', function() {
509511
});
510512

511513
it('submits fail above the backend.maxSubmitRetries threshold', function(done) {
514+
var backend = this.backend;
512515
this.backend.maxSubmitRetries = 0;
513516
var doc = this.backend.connect().get('dogs', 'fido');
514517
var doc2 = this.backend.connect().get('dogs', 'fido');
515518
doc.create({age: 3}, function(err) {
516519
if (err) return done(err);
517520
doc2.fetch(function(err) {
518521
if (err) return done(err);
519-
var count = 0;
520-
var cb = function(err) {
521-
count++;
522-
if (count === 1) {
523-
if (err) return done(err);
524-
} else {
525-
expect(err).ok();
526-
done();
522+
var docCallback;
523+
var doc2Callback;
524+
// The submit retry happens just after an op is committed. This hook into the middleware
525+
// catches both ops just before they're about to be committed. This ensures that both ops
526+
// are certainly working on the same snapshot (ie one op hasn't been committed before the
527+
// other fetches the snapshot to apply to). By storing the callbacks, we can then
528+
// manually trigger the callbacks, first calling doc, and when we know that's been committed,
529+
// we then commit doc2.
530+
backend.use('commit', function (request, callback) {
531+
if (request.op.op[0].na === 2) docCallback = callback;
532+
if (request.op.op[0].na === 7) doc2Callback = callback;
533+
534+
// Wait until both ops have been applied to the same snapshot and are about to be committed
535+
if (docCallback && doc2Callback) {
536+
// Trigger the first op's commit and then the second one later, which will cause the
537+
// second op to retry
538+
docCallback();
527539
}
528-
};
529-
doc.submitOp({p: ['age'], na: 2}, cb);
530-
doc2.submitOp({p: ['age'], na: 7}, cb);
540+
});
541+
doc.submitOp({p: ['age'], na: 2}, function (error) {
542+
if (error) return done(error);
543+
// When we know the first op has been committed, we try to commit the second op, which will
544+
// fail because it's working on an out-of-date snapshot. It will retry, but exceed the
545+
// maxSubmitRetries limit of 0
546+
doc2Callback();
547+
});
548+
doc2.submitOp({p: ['age'], na: 7}, function (error) {
549+
expect(error).ok();
550+
done();
551+
});
531552
});
532553
});
533554
});
@@ -608,11 +629,16 @@ describe('client submit', function() {
608629
doc2.del(function(err) {
609630
if (err) return done(err);
610631
doc.pause();
632+
var calledBack = false;
633+
doc.on('error', function() {
634+
expect(calledBack).equal(true);
635+
done();
636+
});
611637
doc.submitOp({p: ['age'], na: 1}, function(err) {
612638
expect(err).ok();
613639
expect(doc.version).equal(2);
614640
expect(doc.data).eql(undefined);
615-
done();
641+
calledBack = true;
616642
});
617643
doc.fetch();
618644
});
@@ -632,11 +658,16 @@ describe('client submit', function() {
632658
doc2.create({age: 5}, function(err) {
633659
if (err) return done(err);
634660
doc.pause();
661+
var calledBack = false;
662+
doc.on('error', function() {
663+
expect(calledBack).equal(true);
664+
done();
665+
});
635666
doc.create({age: 9}, function(err) {
636667
expect(err).ok();
637668
expect(doc.version).equal(3);
638669
expect(doc.data).eql({age: 5});
639-
done();
670+
calledBack = true;
640671
});
641672
doc.fetch();
642673
});
@@ -1044,6 +1075,19 @@ describe('client submit', function() {
10441075
});
10451076
});
10461077

1078+
it('allows snapshot and op to be a non-object', function(done) {
1079+
var doc = this.backend.connect().get('dogs', 'fido');
1080+
doc.create(5, numberType.type.uri, function (err) {
1081+
if (err) return done(err);
1082+
expect(doc.data).to.equal(5);
1083+
doc.submitOp(2, function(err) {
1084+
if (err) return done(err);
1085+
expect(doc.data).to.equal(7);
1086+
done();
1087+
});
1088+
});
1089+
});
1090+
10471091
describe('type.deserialize', function() {
10481092
it('can create a new doc', function(done) {
10491093
var doc = this.backend.connect().get('dogs', 'fido');

test/client/subscribe.js

+17-3
Original file line numberDiff line numberDiff line change
@@ -405,23 +405,37 @@ describe('client subscribe', function() {
405405
});
406406

407407
it('doc destroy stops op updates', function(done) {
408-
var doc = this.backend.connect().get('dogs', 'fido');
409-
var doc2 = this.backend.connect().get('dogs', 'fido');
408+
var connection1 = this.backend.connect();
409+
var connection2 = this.backend.connect();
410+
var doc = connection1.get('dogs', 'fido');
411+
var doc2 = connection2.get('dogs', 'fido');
410412
doc.create({age: 3}, function(err) {
411413
if (err) return done(err);
412414
doc2.subscribe(function(err) {
413415
if (err) return done(err);
414416
doc2.on('op', function(op, context) {
415-
done();
417+
done(new Error('Should not get op event'));
416418
});
417419
doc2.destroy(function(err) {
418420
if (err) return done(err);
421+
expect(connection2.getExisting('dogs', 'fido')).equal(undefined);
419422
doc.submitOp({p: ['age'], na: 1}, done);
420423
});
421424
});
422425
});
423426
});
424427

428+
it('doc destroy removes doc from connection when doc is not subscribed', function(done) {
429+
var connection = this.backend.connect();
430+
var doc = connection.get('dogs', 'fido');
431+
expect(connection.getExisting('dogs', 'fido')).equal(doc);
432+
doc.destroy(function(err) {
433+
if (err) return done(err);
434+
expect(connection.getExisting('dogs', 'fido')).equal(undefined);
435+
done();
436+
});
437+
});
438+
425439
it('bulk unsubscribe stops op updates', function(done) {
426440
var connection = this.backend.connect();
427441
var connection2 = this.backend.connect();

test/db-memory.js

+68-7
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@ var expect = require('expect.js');
22
var DB = require('../lib/db');
33
var MemoryDB = require('../lib/db/memory');
44

5-
// Extend from MemoryDB as defined in this package, not the one that
6-
// sharedb-mingo-memory depends on.
7-
var ShareDbMingo = require('sharedb-mingo-memory').extendMemoryDB(MemoryDB);
8-
var getQuery = require('sharedb-mingo-memory/get-query');
9-
105
describe('DB base class', function() {
116
it('can call db.close() without callback', function() {
127
var db = new DB();
@@ -59,10 +54,76 @@ describe('DB base class', function() {
5954
});
6055
});
6156

57+
58+
// Extension of MemoryDB that supports query filters and sorts on simple
59+
// top-level properties, which is enough for the core ShareDB tests on
60+
// query subscription updating.
61+
function BasicQueryableMemoryDB() {
62+
MemoryDB.apply(this, arguments);
63+
}
64+
BasicQueryableMemoryDB.prototype = Object.create(MemoryDB.prototype);
65+
BasicQueryableMemoryDB.prototype.constructor = BasicQueryableMemoryDB;
66+
67+
BasicQueryableMemoryDB.prototype._querySync = function(snapshots, query, options) {
68+
if (query.filter) {
69+
snapshots = snapshots.filter(function(snapshot) {
70+
for (var queryKey in query.filter) {
71+
// This fake only supports simple property equality filters, so
72+
// throw an error on Mongo-like filter properties with dots.
73+
if (queryKey.includes('.')) {
74+
throw new Error('Only simple property filters are supported, got:', queryKey);
75+
}
76+
if (snapshot.data[queryKey] !== query.filter[queryKey]) {
77+
return false;
78+
}
79+
}
80+
return true;
81+
});
82+
}
83+
84+
if (query.sort) {
85+
if (!Array.isArray(query.sort)) {
86+
throw new Error('query.sort must be an array');
87+
}
88+
if (query.sort.length) {
89+
snapshots.sort(snapshotComparator(query.sort));
90+
}
91+
}
92+
93+
return {snapshots: snapshots};
94+
};
95+
96+
// sortProperties is an array whose items are each [propertyName, direction].
97+
function snapshotComparator(sortProperties) {
98+
return function(snapshotA, snapshotB) {
99+
for (var i = 0; i < sortProperties.length; i++) {
100+
var sortProperty = sortProperties[i];
101+
var sortKey = sortProperty[0];
102+
var sortDirection = sortProperty[1];
103+
104+
var aPropVal = snapshotA.data[sortKey];
105+
var bPropVal = snapshotB.data[sortKey];
106+
if (aPropVal < bPropVal) {
107+
return -1 * sortDirection;
108+
} else if (aPropVal > bPropVal) {
109+
return sortDirection;
110+
} else if (aPropVal === bPropVal) {
111+
continue;
112+
} else {
113+
throw new Error('Could not compare ' + aPropVal + ' and ' + bPropVal);
114+
}
115+
}
116+
return 0;
117+
};
118+
}
119+
120+
// Run all the DB-based tests against the BasicQueryableMemoryDB.
62121
require('./db')({
63122
create: function(callback) {
64-
var db = new ShareDbMingo();
123+
var db = new BasicQueryableMemoryDB();
65124
callback(null, db);
66125
},
67-
getQuery: getQuery
126+
getQuery: function(options) {
127+
return {filter: options.query, sort: options.sort};
128+
}
68129
});

0 commit comments

Comments
 (0)