sharedb 5.2.2 → 6.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/lib/backend.js +27 -18
- package/package.json +10 -10
- package/test/client/query-subscribe.js +17 -14
- package/test/client/snapshot-timestamp-request.js +18 -2
- package/test/setup.js +4 -1
- package/test/util.js +20 -0
- package/test/.jshintrc +0 -22
package/lib/backend.js
CHANGED
|
@@ -881,29 +881,38 @@ Backend.prototype._fetchSnapshotByTimestamp = function(collection, id, timestamp
|
|
|
881
881
|
var from = 0;
|
|
882
882
|
var to = null;
|
|
883
883
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
});
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
milestoneDb.getMilestoneSnapshotAtOrBeforeTime(collection, id, timestamp, function(error, snapshot) {
|
|
884
|
+
// Always fetch the current snapshot first. We request its metadata so that we
|
|
885
|
+
// can read its mtime, which lets us serve the current snapshot directly when
|
|
886
|
+
// the requested timestamp is after it. This avoids replaying ops when they
|
|
887
|
+
// aren't needed, and - crucially - still works when older ops have been
|
|
888
|
+
// deleted/TTLed and the current version can no longer be rebuilt from ops.
|
|
889
|
+
db.getSnapshot(collection, id, null, {metadata: true}, function(error, currentSnapshot) {
|
|
894
890
|
if (error) return callback(error);
|
|
895
|
-
milestoneSnapshot = snapshot;
|
|
896
|
-
if (snapshot) from = snapshot.v;
|
|
897
891
|
|
|
898
|
-
|
|
892
|
+
var mtime = currentSnapshot.m && currentSnapshot.m.mtime;
|
|
893
|
+
var shouldGetLatestSnapshot = timestamp === null || (mtime != null && timestamp > mtime);
|
|
894
|
+
if (shouldGetLatestSnapshot) {
|
|
895
|
+
// Strip the metadata that we only fetched in order to compare the mtime,
|
|
896
|
+
// so that the returned snapshot is consistent with the op-replayed path.
|
|
897
|
+
currentSnapshot.m = null;
|
|
898
|
+
return callback(null, currentSnapshot);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
milestoneDb.getMilestoneSnapshotAtOrBeforeTime(collection, id, timestamp, function(error, snapshot) {
|
|
899
902
|
if (error) return callback(error);
|
|
900
|
-
|
|
903
|
+
milestoneSnapshot = snapshot;
|
|
904
|
+
if (snapshot) from = snapshot.v;
|
|
901
905
|
|
|
902
|
-
|
|
903
|
-
db.getOps(collection, id, from, to, options, function(error, ops) {
|
|
906
|
+
milestoneDb.getMilestoneSnapshotAtOrAfterTime(collection, id, timestamp, function(error, snapshot) {
|
|
904
907
|
if (error) return callback(error);
|
|
905
|
-
|
|
906
|
-
|
|
908
|
+
if (snapshot) to = snapshot.v;
|
|
909
|
+
|
|
910
|
+
var options = {metadata: true};
|
|
911
|
+
db.getOps(collection, id, from, to, options, function(error, ops) {
|
|
912
|
+
if (error) return callback(error);
|
|
913
|
+
filterOpsInPlaceBeforeTimestamp(ops, timestamp);
|
|
914
|
+
backend._buildSnapshotFromOps(id, milestoneSnapshot, ops, callback);
|
|
915
|
+
});
|
|
907
916
|
});
|
|
908
917
|
});
|
|
909
918
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sharedb",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "JSON OT database backend",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"dependencies": {
|
|
@@ -11,18 +11,18 @@
|
|
|
11
11
|
"ot-json0": "^1.1.0"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"eslint": "^
|
|
14
|
+
"@eslint/js": "^10.0.1",
|
|
15
|
+
"chai": "^6.2.2",
|
|
16
|
+
"eslint": "^10.1.0",
|
|
17
17
|
"eslint-config-google": "^0.14.0",
|
|
18
|
-
"mocha": "^
|
|
19
|
-
"nyc": "^
|
|
18
|
+
"mocha": "^11.7.5",
|
|
19
|
+
"nyc": "^18.0.0",
|
|
20
20
|
"ot-json0-v2": "https://github.com/ottypes/json0#90a3ae26364c4fa3b19b6df34dad46707a704421",
|
|
21
21
|
"ot-json1": "^1.0.2",
|
|
22
22
|
"rich-text": "^4.1.0",
|
|
23
23
|
"sharedb-legacy": "npm:sharedb@1.1.0",
|
|
24
|
-
"sinon": "^
|
|
25
|
-
"sinon-chai": "^
|
|
24
|
+
"sinon": "^21.0.3",
|
|
25
|
+
"sinon-chai": "^4.0.1"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
28
|
"lib/",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"docs:start": "cd docs && bundle exec jekyll serve --livereload",
|
|
35
35
|
"test": "mocha",
|
|
36
36
|
"test-cover": "nyc --temp-dir=coverage -r text -r lcov npm test",
|
|
37
|
-
"lint": "
|
|
38
|
-
"lint:fix": "
|
|
37
|
+
"lint": "eslint",
|
|
38
|
+
"lint:fix": "eslint --fix"
|
|
39
39
|
},
|
|
40
40
|
"repository": {
|
|
41
41
|
"type": "git",
|
|
@@ -13,6 +13,9 @@ module.exports = function(options) {
|
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
afterEach(function() {
|
|
16
|
+
if (sinon.clock) {
|
|
17
|
+
sinon.clock.uninstall();
|
|
18
|
+
}
|
|
16
19
|
sinon.restore();
|
|
17
20
|
});
|
|
18
21
|
|
|
@@ -517,7 +520,7 @@ function commonTests(options) {
|
|
|
517
520
|
});
|
|
518
521
|
|
|
519
522
|
it('pollDebounce option reduces subsequent poll interval', function(done) {
|
|
520
|
-
var clock =
|
|
523
|
+
var clock = util.useFakeTimers();
|
|
521
524
|
var connection = this.backend.connect();
|
|
522
525
|
this.backend.db.canPollDoc = function() {
|
|
523
526
|
return false;
|
|
@@ -539,7 +542,7 @@ function commonTests(options) {
|
|
|
539
542
|
connection.get('items', i.toString()).on('error', done).create({}, function(err) {
|
|
540
543
|
if (err) return done(err);
|
|
541
544
|
counter++;
|
|
542
|
-
if (counter === 9) clock.
|
|
545
|
+
if (counter === 9) clock.tick(10000);
|
|
543
546
|
});
|
|
544
547
|
}
|
|
545
548
|
}
|
|
@@ -561,12 +564,12 @@ function commonTests(options) {
|
|
|
561
564
|
// event firing the first time, while sharedb is definitely
|
|
562
565
|
// debouncing
|
|
563
566
|
connection.get('items', '0').on('error', done).create({}, function() {
|
|
564
|
-
clock.
|
|
567
|
+
clock.tick(3000);
|
|
565
568
|
});
|
|
566
569
|
});
|
|
567
570
|
|
|
568
571
|
it('db.pollDebounce option reduces subsequent poll interval', function(done) {
|
|
569
|
-
var clock =
|
|
572
|
+
var clock = util.useFakeTimers();
|
|
570
573
|
var connection = this.backend.connect();
|
|
571
574
|
this.backend.db.canPollDoc = function() {
|
|
572
575
|
return false;
|
|
@@ -589,7 +592,7 @@ function commonTests(options) {
|
|
|
589
592
|
connection.get('items', i.toString()).on('error', done).create({}, function(err) {
|
|
590
593
|
if (err) return done(err);
|
|
591
594
|
counter++;
|
|
592
|
-
if (counter === 9) clock.
|
|
595
|
+
if (counter === 9) clock.tick(10000);
|
|
593
596
|
});
|
|
594
597
|
}
|
|
595
598
|
}
|
|
@@ -611,12 +614,12 @@ function commonTests(options) {
|
|
|
611
614
|
// event firing the first time, while sharedb is definitely
|
|
612
615
|
// debouncing
|
|
613
616
|
connection.get('items', '0').on('error', done).create({}, function() {
|
|
614
|
-
clock.
|
|
617
|
+
clock.tick(3000);
|
|
615
618
|
});
|
|
616
619
|
});
|
|
617
620
|
|
|
618
621
|
it('pollInterval updates a subscribed query after an unpublished create', function(done) {
|
|
619
|
-
var clock =
|
|
622
|
+
var clock = util.useFakeTimers();
|
|
620
623
|
var connection = this.backend.connect();
|
|
621
624
|
this.backend.suppressPublish = true;
|
|
622
625
|
var query = connection.createSubscribeQuery(
|
|
@@ -626,7 +629,7 @@ function commonTests(options) {
|
|
|
626
629
|
function(err) {
|
|
627
630
|
if (err) return done(err);
|
|
628
631
|
connection.get('dogs', 'fido').on('error', done).create({}, function() {
|
|
629
|
-
clock.
|
|
632
|
+
clock.tick(51);
|
|
630
633
|
});
|
|
631
634
|
}
|
|
632
635
|
);
|
|
@@ -638,7 +641,7 @@ function commonTests(options) {
|
|
|
638
641
|
});
|
|
639
642
|
|
|
640
643
|
it('db.pollInterval updates a subscribed query after an unpublished create', function(done) {
|
|
641
|
-
var clock =
|
|
644
|
+
var clock = util.useFakeTimers();
|
|
642
645
|
var connection = this.backend.connect();
|
|
643
646
|
this.backend.suppressPublish = true;
|
|
644
647
|
this.backend.db.pollDebounce = 0;
|
|
@@ -646,7 +649,7 @@ function commonTests(options) {
|
|
|
646
649
|
var query = connection.createSubscribeQuery('dogs', this.matchAllDbQuery, null, function(err) {
|
|
647
650
|
if (err) return done(err);
|
|
648
651
|
connection.get('dogs', 'fido').on('error', done).create({}, function() {
|
|
649
|
-
clock.
|
|
652
|
+
clock.tick(51);
|
|
650
653
|
});
|
|
651
654
|
});
|
|
652
655
|
query.on('error', done);
|
|
@@ -657,7 +660,7 @@ function commonTests(options) {
|
|
|
657
660
|
});
|
|
658
661
|
|
|
659
662
|
it('pollInterval captures additional unpublished creates', function(done) {
|
|
660
|
-
var clock =
|
|
663
|
+
var clock = util.useFakeTimers();
|
|
661
664
|
var connection = this.backend.connect();
|
|
662
665
|
this.backend.suppressPublish = true;
|
|
663
666
|
var count = 0;
|
|
@@ -667,7 +670,7 @@ function commonTests(options) {
|
|
|
667
670
|
var doc = connection.get('dogs', count.toString()).on('error', done);
|
|
668
671
|
doc.create({}, function(e) {
|
|
669
672
|
if (e) return done(e);
|
|
670
|
-
clock.
|
|
673
|
+
clock.tick(2000);
|
|
671
674
|
});
|
|
672
675
|
});
|
|
673
676
|
query.on('error', done);
|
|
@@ -677,10 +680,10 @@ function commonTests(options) {
|
|
|
677
680
|
var doc = connection.get('dogs', count.toString()).on('error', done);
|
|
678
681
|
doc.create({}, function(e) {
|
|
679
682
|
if (e) return done(e);
|
|
680
|
-
clock.
|
|
683
|
+
clock.tick(10000);
|
|
681
684
|
});
|
|
682
685
|
});
|
|
683
|
-
clock.
|
|
686
|
+
clock.tick(1);
|
|
684
687
|
});
|
|
685
688
|
|
|
686
689
|
it('query extra is returned to client', function(done) {
|
|
@@ -2,6 +2,7 @@ var Backend = require('../../lib/backend');
|
|
|
2
2
|
var expect = require('chai').expect;
|
|
3
3
|
var MemoryDb = require('../../lib/db/memory');
|
|
4
4
|
var MemoryMilestoneDb = require('../../lib/milestone-db/memory');
|
|
5
|
+
var util = require('../util');
|
|
5
6
|
var sinon = require('sinon');
|
|
6
7
|
var async = require('async');
|
|
7
8
|
|
|
@@ -17,12 +18,12 @@ describe('SnapshotTimestampRequest', function() {
|
|
|
17
18
|
var ONE_DAY = 1000 * 60 * 60 * 24;
|
|
18
19
|
|
|
19
20
|
beforeEach(function() {
|
|
20
|
-
clock =
|
|
21
|
+
clock = util.useFakeTimers(day1);
|
|
21
22
|
backend = new Backend();
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
afterEach(function(done) {
|
|
25
|
-
|
|
26
|
+
sinon.restore();
|
|
26
27
|
backend.close(done);
|
|
27
28
|
});
|
|
28
29
|
|
|
@@ -138,6 +139,21 @@ describe('SnapshotTimestampRequest', function() {
|
|
|
138
139
|
], done);
|
|
139
140
|
});
|
|
140
141
|
|
|
142
|
+
it('fetches the current snapshot when ops are missing and the timestamp is after the latest op', function(done) {
|
|
143
|
+
var connection = backend.connect();
|
|
144
|
+
async.series([
|
|
145
|
+
// Simulate ops having been deleted/TTLed so the snapshot can't be rebuilt from ops.
|
|
146
|
+
backend.db.deleteOps.bind(backend.db, 'books', 'time-machine', null, null, null),
|
|
147
|
+
function(next) {
|
|
148
|
+
connection.fetchSnapshotByTimestamp('books', 'time-machine', day4, function(error, snapshot) {
|
|
149
|
+
if (error) return next(error);
|
|
150
|
+
expect(snapshot).to.eql(v3);
|
|
151
|
+
next();
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
], done);
|
|
155
|
+
});
|
|
156
|
+
|
|
141
157
|
it('fetches the most recent version when not specifying a timestamp', function(done) {
|
|
142
158
|
var connection = backend.connect();
|
|
143
159
|
async.waterfall([
|
package/test/setup.js
CHANGED
|
@@ -3,7 +3,7 @@ var sinon = require('sinon');
|
|
|
3
3
|
var sinonChai = require('sinon-chai');
|
|
4
4
|
var chai = require('chai');
|
|
5
5
|
|
|
6
|
-
chai.use(sinonChai);
|
|
6
|
+
chai.use(sinonChai.default);
|
|
7
7
|
|
|
8
8
|
if (process.env.LOGGING !== 'true') {
|
|
9
9
|
// Silence the logger for tests by setting all its methods to no-ops
|
|
@@ -15,5 +15,8 @@ if (process.env.LOGGING !== 'true') {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
afterEach(function() {
|
|
18
|
+
if (sinon.clock) {
|
|
19
|
+
sinon.clock.uninstall();
|
|
20
|
+
}
|
|
18
21
|
sinon.restore();
|
|
19
22
|
});
|
package/test/util.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
var sinon = require('sinon');
|
|
1
2
|
|
|
2
3
|
exports.sortById = function(docs) {
|
|
3
4
|
return docs.slice().sort(function(a, b) {
|
|
@@ -15,6 +16,25 @@ exports.pluck = function(docs, key) {
|
|
|
15
16
|
return values;
|
|
16
17
|
};
|
|
17
18
|
|
|
19
|
+
/**
|
|
20
|
+
* @param {Parameters<typeof sinon.useFakeTimers>[0]} config
|
|
21
|
+
* @see {@link sinon.useFakeTimers}
|
|
22
|
+
* @see {@link https://github.com/sinonjs/fake-timers#clocksettickmodemode}
|
|
23
|
+
*/
|
|
24
|
+
exports.useFakeTimers = function(config) {
|
|
25
|
+
if (config == null) {
|
|
26
|
+
config = {};
|
|
27
|
+
} else if (typeof config !== 'object') {
|
|
28
|
+
// Number or Date
|
|
29
|
+
config = {now: config};
|
|
30
|
+
}
|
|
31
|
+
if (config.toFake == null) {
|
|
32
|
+
config.toFake = ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'Date'];
|
|
33
|
+
}
|
|
34
|
+
var clock = sinon.useFakeTimers(config);
|
|
35
|
+
return clock;
|
|
36
|
+
};
|
|
37
|
+
|
|
18
38
|
// Wrap a done function to call back only after a specified number of calls.
|
|
19
39
|
// For example, `var callbackAfter = callAfter(1, callback)` means that if
|
|
20
40
|
// `callbackAfter` is called once, it won't call back. If it is called twice
|
package/test/.jshintrc
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"node": true,
|
|
3
|
-
"laxcomma": true,
|
|
4
|
-
"eqnull": true,
|
|
5
|
-
"eqeqeq": true,
|
|
6
|
-
"indent": 2,
|
|
7
|
-
"newcap": true,
|
|
8
|
-
"quotmark": "single",
|
|
9
|
-
"undef": true,
|
|
10
|
-
"trailing": true,
|
|
11
|
-
"shadow": true,
|
|
12
|
-
"expr": true,
|
|
13
|
-
"boss": true,
|
|
14
|
-
"globals": {
|
|
15
|
-
"describe": false,
|
|
16
|
-
"it": false,
|
|
17
|
-
"before": false,
|
|
18
|
-
"after": false,
|
|
19
|
-
"beforeEach": false,
|
|
20
|
-
"afterEach": false
|
|
21
|
-
}
|
|
22
|
-
}
|