sharedb 5.1.0 → 5.2.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/agent.js +1 -1
- package/lib/client/doc.js +18 -7
- package/lib/error.js +1 -0
- package/package.json +1 -1
- package/test/client/doc.js +19 -0
- package/test/client/query-subscribe.js +23 -0
package/lib/agent.js
CHANGED
|
@@ -569,7 +569,7 @@ Agent.prototype._querySubscribe = function(queryId, collection, query, options,
|
|
|
569
569
|
}
|
|
570
570
|
this.backend.querySubscribe(this, collection, query, options, function(err, emitter, results, extra) {
|
|
571
571
|
if (err) return finish(err);
|
|
572
|
-
if (
|
|
572
|
+
if (agent.closed) return emitter.destroy();
|
|
573
573
|
|
|
574
574
|
agent._subscribeToQuery(emitter, queryId, collection, query);
|
|
575
575
|
// No results are returned when ids are passed in as an option. Instead,
|
package/lib/client/doc.js
CHANGED
|
@@ -61,6 +61,7 @@ function Doc(connection, collection, id) {
|
|
|
61
61
|
this.id = id;
|
|
62
62
|
|
|
63
63
|
this.version = null;
|
|
64
|
+
// The OT type of this document. An uncreated document has type `null`
|
|
64
65
|
this.type = null;
|
|
65
66
|
this.data = undefined;
|
|
66
67
|
|
|
@@ -70,6 +71,8 @@ function Doc(connection, collection, id) {
|
|
|
70
71
|
this.pendingFetch = [];
|
|
71
72
|
this.pendingSubscribe = [];
|
|
72
73
|
|
|
74
|
+
this._isInHardRollback = false;
|
|
75
|
+
|
|
73
76
|
// Whether we think we are subscribed on the server. Synchronously set to
|
|
74
77
|
// false on calls to unsubscribe and disconnect. Should never be true when
|
|
75
78
|
// this.wantSubscribe is false
|
|
@@ -93,9 +96,6 @@ function Doc(connection, collection, id) {
|
|
|
93
96
|
// This is a list of {[create:{...}], [del:true], [op:...], callbacks:[...]}
|
|
94
97
|
this.pendingOps = [];
|
|
95
98
|
|
|
96
|
-
// The OT type of this document. An uncreated document has type `null`
|
|
97
|
-
this.type = null;
|
|
98
|
-
|
|
99
99
|
// The applyStack enables us to track any ops submitted while we are
|
|
100
100
|
// applying an op incrementally. This value is an array when we are
|
|
101
101
|
// performing an incremental apply and null otherwise. When it is an array,
|
|
@@ -748,10 +748,18 @@ Doc.prototype._submit = function(op, source, callback) {
|
|
|
748
748
|
// The op contains either op, create, delete, or none of the above (a no-op).
|
|
749
749
|
if ('op' in op) {
|
|
750
750
|
if (!this.type) {
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
751
|
+
if (this._isInHardRollback) {
|
|
752
|
+
var err = new ShareDBError(
|
|
753
|
+
ERROR_CODE.ERR_DOC_IN_HARD_ROLLBACK,
|
|
754
|
+
'Cannot submit op. Document is performing hard rollback. ' + this.collection + '.' + this.id
|
|
755
|
+
);
|
|
756
|
+
} else {
|
|
757
|
+
var err = new ShareDBError(
|
|
758
|
+
ERROR_CODE.ERR_DOC_DOES_NOT_EXIST,
|
|
759
|
+
'Cannot submit op. Document has not been created. ' + this.collection + '.' + this.id
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
|
|
755
763
|
if (callback) return callback(err);
|
|
756
764
|
return this.emit('error', err);
|
|
757
765
|
}
|
|
@@ -1030,6 +1038,7 @@ Doc.prototype._rollback = function(err) {
|
|
|
1030
1038
|
};
|
|
1031
1039
|
|
|
1032
1040
|
Doc.prototype._hardRollback = function(err) {
|
|
1041
|
+
this._isInHardRollback = true;
|
|
1033
1042
|
// Store pending ops so that we can notify their callbacks of the error.
|
|
1034
1043
|
// We combine the inflight op and the pending ops, because it's possible
|
|
1035
1044
|
// to hit a condition where we have no inflight op, but we do have pending
|
|
@@ -1047,6 +1056,8 @@ Doc.prototype._hardRollback = function(err) {
|
|
|
1047
1056
|
// Fetch the latest version from the server to get us back into a working state
|
|
1048
1057
|
var doc = this;
|
|
1049
1058
|
this._fetch({force: true}, function(fetchError) {
|
|
1059
|
+
doc._isInHardRollback = false;
|
|
1060
|
+
|
|
1050
1061
|
// We want to check that no errors are swallowed, so we check that:
|
|
1051
1062
|
// - there are callbacks to call, and
|
|
1052
1063
|
// - that every single pending op called a callback
|
package/lib/error.js
CHANGED
|
@@ -30,6 +30,7 @@ ShareDBError.CODES = {
|
|
|
30
30
|
ERR_DOC_DOES_NOT_EXIST: 'ERR_DOC_DOES_NOT_EXIST',
|
|
31
31
|
ERR_DOC_TYPE_NOT_RECOGNIZED: 'ERR_DOC_TYPE_NOT_RECOGNIZED',
|
|
32
32
|
ERR_DOC_WAS_DELETED: 'ERR_DOC_WAS_DELETED',
|
|
33
|
+
ERR_DOC_IN_HARD_ROLLBACK: 'ERR_DOC_IN_HARD_ROLLBACK',
|
|
33
34
|
ERR_INFLIGHT_OP_MISSING: 'ERR_INFLIGHT_OP_MISSING',
|
|
34
35
|
ERR_INGESTED_SNAPSHOT_HAS_NO_VERSION: 'ERR_INGESTED_SNAPSHOT_HAS_NO_VERSION',
|
|
35
36
|
ERR_MAX_SUBMIT_RETRIES_EXCEEDED: 'ERR_MAX_SUBMIT_RETRIES_EXCEEDED',
|
package/package.json
CHANGED
package/test/client/doc.js
CHANGED
|
@@ -636,6 +636,25 @@ describe('Doc', function() {
|
|
|
636
636
|
}
|
|
637
637
|
], done);
|
|
638
638
|
});
|
|
639
|
+
|
|
640
|
+
it('rejects ops with ERR_DOC_IN_HARD_ROLLBACK error when in hard rollback', function(done) {
|
|
641
|
+
var backend = this.backend;
|
|
642
|
+
var doc = backend.connect().get('dogs', 'snoopy');
|
|
643
|
+
|
|
644
|
+
async.series([
|
|
645
|
+
doc.create.bind(doc, {color: 'red'}),
|
|
646
|
+
function(next) {
|
|
647
|
+
doc.on('error', function(error) {
|
|
648
|
+
expect(error.code).to.be.equal('TEST_ERROR');
|
|
649
|
+
});
|
|
650
|
+
doc._hardRollback(new ShareDBError('TEST_ERROR'));
|
|
651
|
+
doc.submitOp({p: ['color'], oi: 'blue', od: 'red'}, function(error) {
|
|
652
|
+
expect(error.code).to.be.equal('ERR_DOC_IN_HARD_ROLLBACK');
|
|
653
|
+
next();
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
], done);
|
|
657
|
+
});
|
|
639
658
|
});
|
|
640
659
|
|
|
641
660
|
describe('errors on ops that could cause prototype corruption', function() {
|
|
@@ -729,6 +729,29 @@ function commonTests(options) {
|
|
|
729
729
|
connection.get('dogs', 'fido').on('error', done).create({age: 3});
|
|
730
730
|
});
|
|
731
731
|
|
|
732
|
+
it('does not reply if the agent is closed before the query returns', function(done) {
|
|
733
|
+
var backend = this.backend;
|
|
734
|
+
var connection = backend.connect();
|
|
735
|
+
var agent = connection.agent;
|
|
736
|
+
|
|
737
|
+
backend.use('query', function(request, next) {
|
|
738
|
+
backend.use('reply', function() {
|
|
739
|
+
done(new Error('unexpected reply'));
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
expect(agent.closed).to.be.false;
|
|
743
|
+
agent.stream.on('close', function() {
|
|
744
|
+
expect(agent.closed).to.be.true;
|
|
745
|
+
next();
|
|
746
|
+
done();
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
agent.close();
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
connection.createSubscribeQuery('dogs', {});
|
|
753
|
+
});
|
|
754
|
+
|
|
732
755
|
describe('passing agent.custom to the DB adapter', function() {
|
|
733
756
|
var connection;
|
|
734
757
|
var expectedArg = {
|