sharedb 6.0.0 → 6.0.2
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/client/doc.js
CHANGED
|
@@ -968,11 +968,11 @@ Doc.prototype._opAcknowledged = function(message) {
|
|
|
968
968
|
}
|
|
969
969
|
|
|
970
970
|
if (message[ACTIONS.fixup]) {
|
|
971
|
-
for (var
|
|
972
|
-
var fixupOp = message[ACTIONS.fixup][
|
|
971
|
+
for (var fixupIndex = 0; fixupIndex < message[ACTIONS.fixup].length; fixupIndex++) {
|
|
972
|
+
var fixupOp = message[ACTIONS.fixup][fixupIndex];
|
|
973
973
|
|
|
974
|
-
for (var
|
|
975
|
-
var transformErr = transformX(this.pendingOps[
|
|
974
|
+
for (var pendingIndex = 0; pendingIndex < this.pendingOps.length; pendingIndex++) {
|
|
975
|
+
var transformErr = transformX(this.pendingOps[pendingIndex], fixupOp);
|
|
976
976
|
if (transformErr) return this._hardRollback(transformErr);
|
|
977
977
|
}
|
|
978
978
|
|
|
@@ -76,12 +76,12 @@ RemoteDocPresence.prototype._handleOp = function(op, source, connectionId) {
|
|
|
76
76
|
this._setPendingPresence();
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
RemoteDocPresence.prototype._handleCreateDel = function() {
|
|
80
80
|
this._cacheOp(null);
|
|
81
81
|
this._setPendingPresence();
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
RemoteDocPresence.prototype._handleLoad = function() {
|
|
85
85
|
this.value = null;
|
|
86
86
|
this._pending = null;
|
|
87
87
|
this._opCache = null;
|
package/lib/submit-request.js
CHANGED
|
@@ -3,6 +3,7 @@ var projections = require('./projections');
|
|
|
3
3
|
var ShareDBError = require('./error');
|
|
4
4
|
var types = require('./types');
|
|
5
5
|
var protocol = require('./protocol');
|
|
6
|
+
var util = require('./util');
|
|
6
7
|
|
|
7
8
|
var ERROR_CODE = ShareDBError.CODES;
|
|
8
9
|
|
|
@@ -43,6 +44,7 @@ function SubmitRequest(backend, agent, index, id, op, options) {
|
|
|
43
44
|
this.ops = [];
|
|
44
45
|
this.channels = null;
|
|
45
46
|
this._fixupOps = [];
|
|
47
|
+
this._opBeforeFixups = null;
|
|
46
48
|
}
|
|
47
49
|
module.exports = SubmitRequest;
|
|
48
50
|
|
|
@@ -70,6 +72,12 @@ SubmitRequest.prototype.$fixup = function(op) {
|
|
|
70
72
|
);
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
if (!this._opBeforeFixups) {
|
|
76
|
+
this._opBeforeFixups = this.op.create ?
|
|
77
|
+
{data: util.clone(this.op.create.data)} :
|
|
78
|
+
{op: util.clone(this.op.op)};
|
|
79
|
+
}
|
|
80
|
+
|
|
73
81
|
if (this.op.create) this.op.create.data = type.apply(this.op.create.data, op);
|
|
74
82
|
else this.op.op = type.compose(this.op.op, op);
|
|
75
83
|
|
|
@@ -186,8 +194,6 @@ SubmitRequest.prototype.apply = function(callback) {
|
|
|
186
194
|
// Always set the channels before each attempt to apply. If the channels are
|
|
187
195
|
// modified in a middleware and we retry, we want to reset to a new array
|
|
188
196
|
this.channels = this.backend.getChannels(this.collection, this.id);
|
|
189
|
-
this._fixupOps = [];
|
|
190
|
-
delete this.op.m.fixup;
|
|
191
197
|
|
|
192
198
|
var request = this;
|
|
193
199
|
this.backend.trigger(this.backend.MIDDLEWARE_ACTIONS.apply, this.agent, this, function(err) {
|
|
@@ -254,10 +260,21 @@ SubmitRequest.prototype.retry = function(callback) {
|
|
|
254
260
|
if (this.maxRetries != null && this.retries > this.maxRetries) {
|
|
255
261
|
return callback(this.maxRetriesError());
|
|
256
262
|
}
|
|
263
|
+
this._resetFixups();
|
|
257
264
|
this.backend.emit('timing', 'submit.retry', Date.now() - this.start, this);
|
|
258
265
|
this.submit(callback);
|
|
259
266
|
};
|
|
260
267
|
|
|
268
|
+
// The fixups must be undone before the op is transformed forward again
|
|
269
|
+
SubmitRequest.prototype._resetFixups = function() {
|
|
270
|
+
this._fixupOps = [];
|
|
271
|
+
delete this.op.m.fixup;
|
|
272
|
+
if (!this._opBeforeFixups) return;
|
|
273
|
+
if (this.op.create) this.op.create.data = this._opBeforeFixups.data;
|
|
274
|
+
else this.op.op = this._opBeforeFixups.op;
|
|
275
|
+
this._opBeforeFixups = null;
|
|
276
|
+
};
|
|
277
|
+
|
|
261
278
|
SubmitRequest.prototype._transformOp = function(ops) {
|
|
262
279
|
var type = this.snapshot.type;
|
|
263
280
|
for (var i = 0; i < ops.length; i++) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sharedb",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.2",
|
|
4
4
|
"description": "JSON OT database backend",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"dependencies": {
|
|
@@ -21,7 +21,7 @@
|
|
|
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": "^
|
|
24
|
+
"sinon": "^22.0.0",
|
|
25
25
|
"sinon-chai": "^4.0.1"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
package/test/middleware.js
CHANGED
|
@@ -565,6 +565,68 @@ describe('middleware', function() {
|
|
|
565
565
|
});
|
|
566
566
|
});
|
|
567
567
|
|
|
568
|
+
it('transforms a pending op by multiple fixups', function(done) {
|
|
569
|
+
var applied = false;
|
|
570
|
+
backend.use('apply', function(request, next) {
|
|
571
|
+
if (applied) return next();
|
|
572
|
+
applied = true;
|
|
573
|
+
request.$fixup([{p: ['tricks', 0], li: 'sit'}]);
|
|
574
|
+
request.$fixup([{p: ['tricks', 0], li: 'stay'}]);
|
|
575
|
+
next();
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
var remoteConnection = backend.connect();
|
|
579
|
+
var remoteDoc = remoteConnection.get('dogs', 'fido');
|
|
580
|
+
|
|
581
|
+
remoteDoc.subscribe(function(error) {
|
|
582
|
+
if (error) return done(error);
|
|
583
|
+
|
|
584
|
+
expect(remoteDoc.data).to.eql({name: 'fido'});
|
|
585
|
+
|
|
586
|
+
remoteDoc.on('op batch', function() {
|
|
587
|
+
if (remoteDoc.version !== 3) return;
|
|
588
|
+
expect(remoteDoc.data.tricks).to.eql(['stay', 'sit', 'fetch', 'lie down']);
|
|
589
|
+
expect(remoteDoc.data).to.eql(doc.data);
|
|
590
|
+
done();
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
doc.preventCompose = true;
|
|
594
|
+
doc.submitOp([{p: ['tricks'], oi: ['fetch']}], errorHandler(done));
|
|
595
|
+
doc.submitOp([{p: ['tricks', 1], li: 'lie down'}], errorHandler(done));
|
|
596
|
+
});
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
it('transforms multiple pending ops by a fixup', function(done) {
|
|
600
|
+
var applied = false;
|
|
601
|
+
backend.use('apply', function(request, next) {
|
|
602
|
+
if (applied) return next();
|
|
603
|
+
applied = true;
|
|
604
|
+
request.$fixup([{p: ['tricks', 0], li: 'stay'}]);
|
|
605
|
+
next();
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
var remoteConnection = backend.connect();
|
|
609
|
+
var remoteDoc = remoteConnection.get('dogs', 'fido');
|
|
610
|
+
|
|
611
|
+
remoteDoc.subscribe(function(error) {
|
|
612
|
+
if (error) return done(error);
|
|
613
|
+
|
|
614
|
+
expect(remoteDoc.data).to.eql({name: 'fido'});
|
|
615
|
+
|
|
616
|
+
remoteDoc.on('op batch', function() {
|
|
617
|
+
if (remoteDoc.version !== 4) return;
|
|
618
|
+
expect(remoteDoc.data.tricks).to.eql(['stay', 'fetch', 'sit', 'roll over']);
|
|
619
|
+
expect(remoteDoc.data).to.eql(doc.data);
|
|
620
|
+
done();
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
doc.preventCompose = true;
|
|
624
|
+
doc.submitOp([{p: ['tricks'], oi: ['fetch']}], errorHandler(done));
|
|
625
|
+
doc.submitOp([{p: ['tricks', 1], li: 'sit'}], errorHandler(done));
|
|
626
|
+
doc.submitOp([{p: ['tricks', 2], li: 'roll over'}], errorHandler(done));
|
|
627
|
+
});
|
|
628
|
+
});
|
|
629
|
+
|
|
568
630
|
it('applies a fixup to a creation op', function(done) {
|
|
569
631
|
backend.use('apply', function(request, next) {
|
|
570
632
|
request.$fixup([{p: ['goodBoy'], oi: true}]);
|
|
@@ -702,6 +764,79 @@ describe('middleware', function() {
|
|
|
702
764
|
});
|
|
703
765
|
});
|
|
704
766
|
|
|
767
|
+
it('applies the fixup once when the submit is retried', function(done) {
|
|
768
|
+
var remoteDoc = backend.connect().get('dogs', 'fido');
|
|
769
|
+
|
|
770
|
+
doc.submitOp([{p: ['fixups'], oi: 0}], function(error) {
|
|
771
|
+
if (error) return done(error);
|
|
772
|
+
remoteDoc.fetch(function(error) {
|
|
773
|
+
if (error) return done(error);
|
|
774
|
+
|
|
775
|
+
backend.use('apply', function(request, next) {
|
|
776
|
+
request.$fixup([{p: ['fixups'], na: 1}]);
|
|
777
|
+
next();
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
var commitDoc;
|
|
781
|
+
var commitRemoteDoc;
|
|
782
|
+
backend.use('commit', function(request, next) {
|
|
783
|
+
// Once we've forced the race, let the retry through untouched
|
|
784
|
+
if (commitDoc && commitRemoteDoc) return next();
|
|
785
|
+
if (request.op.src === doc.connection.id) commitDoc = next;
|
|
786
|
+
else commitRemoteDoc = next;
|
|
787
|
+
// Both ops have now been applied to the same snapshot, so
|
|
788
|
+
// committing one of them will force the other to retry
|
|
789
|
+
if (commitDoc && commitRemoteDoc) commitDoc();
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
doc.submitOp([{p: ['name', 0], si: 'a'}], function(error) {
|
|
793
|
+
if (error) return done(error);
|
|
794
|
+
commitRemoteDoc();
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
remoteDoc.submitOp([{p: ['name', 0], si: 'b'}], function(error) {
|
|
798
|
+
if (error) return done(error);
|
|
799
|
+
backend.db.getSnapshot('dogs', 'fido', null, null, function(error, snapshot) {
|
|
800
|
+
if (error) return done(error);
|
|
801
|
+
expect(snapshot.data.fixups).to.equal(2);
|
|
802
|
+
expect(snapshot.data).to.eql(remoteDoc.data);
|
|
803
|
+
expect(snapshot.v).to.equal(remoteDoc.version);
|
|
804
|
+
done();
|
|
805
|
+
});
|
|
806
|
+
});
|
|
807
|
+
});
|
|
808
|
+
});
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
it('applies the fixup once when a create is retried', function(done) {
|
|
812
|
+
backend.use('apply', function(request, next) {
|
|
813
|
+
request.$fixup([{p: ['fixups'], na: 1}]);
|
|
814
|
+
next();
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
// A create can only be retried if the database reports a failed commit
|
|
818
|
+
// without another op having landed, since a create that loses a genuine
|
|
819
|
+
// race is rejected during transform
|
|
820
|
+
var commit = backend.db.commit;
|
|
821
|
+
var lostRace = false;
|
|
822
|
+
sinon.stub(backend.db, 'commit').callsFake(function(collection, id, op, snapshot, options, callback) {
|
|
823
|
+
if (lostRace) return commit.call(this, collection, id, op, snapshot, options, callback);
|
|
824
|
+
lostRace = true;
|
|
825
|
+
process.nextTick(callback, null, false);
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
doc = connection.get('dogs', 'rover');
|
|
829
|
+
doc.create({name: 'rover', fixups: 0}, function(error) {
|
|
830
|
+
if (error) return done(error);
|
|
831
|
+
backend.db.getSnapshot('dogs', 'rover', null, null, function(error, snapshot) {
|
|
832
|
+
if (error) return done(error);
|
|
833
|
+
expect(snapshot.data.fixups).to.equal(1);
|
|
834
|
+
expect(snapshot.data).to.eql(doc.data);
|
|
835
|
+
done();
|
|
836
|
+
});
|
|
837
|
+
});
|
|
838
|
+
});
|
|
839
|
+
|
|
705
840
|
it('applies two fixups', function(done) {
|
|
706
841
|
backend.use('apply', function(request, next) {
|
|
707
842
|
request.$fixup([{p: ['tricks', 0], li: 'sit'}]);
|