diff --git a/lib/client/doc.js b/lib/client/doc.js index acb457374..a7ad4e708 100644 --- a/lib/client/doc.js +++ b/lib/client/doc.js @@ -70,6 +70,8 @@ function Doc(connection, collection, id) { this.pendingFetch = []; this.pendingSubscribe = []; + this._isInHardRollback = false; + // Whether we think we are subscribed on the server. Synchronously set to // false on calls to unsubscribe and disconnect. Should never be true when // this.wantSubscribe is false @@ -748,10 +750,18 @@ Doc.prototype._submit = function(op, source, callback) { // The op contains either op, create, delete, or none of the above (a no-op). if ('op' in op) { if (!this.type) { - var err = new ShareDBError( - ERROR_CODE.ERR_DOC_DOES_NOT_EXIST, - 'Cannot submit op. Document has not been created. ' + this.collection + '.' + this.id - ); + if (this._isInHardRollback) { + var err = new ShareDBError( + ERROR_CODE.ERR_DOC_IN_HARD_ROLLBACK, + 'Cannot submit op. Document is performing hard rollback. ' + this.collection + '.' + this.id + ); + } else { + var err = new ShareDBError( + ERROR_CODE.ERR_DOC_DOES_NOT_EXIST, + 'Cannot submit op. Document has not been created. ' + this.collection + '.' + this.id + ); + } + if (callback) return callback(err); return this.emit('error', err); } @@ -1030,6 +1040,7 @@ Doc.prototype._rollback = function(err) { }; Doc.prototype._hardRollback = function(err) { + this._isInHardRollback = true; // Store pending ops so that we can notify their callbacks of the error. // We combine the inflight op and the pending ops, because it's possible // to hit a condition where we have no inflight op, but we do have pending @@ -1047,6 +1058,8 @@ Doc.prototype._hardRollback = function(err) { // Fetch the latest version from the server to get us back into a working state var doc = this; this._fetch({force: true}, function(fetchError) { + doc._isInHardRollback = false; + // We want to check that no errors are swallowed, so we check that: // - there are callbacks to call, and // - that every single pending op called a callback diff --git a/lib/error.js b/lib/error.js index cde95bdbe..85750104d 100644 --- a/lib/error.js +++ b/lib/error.js @@ -30,6 +30,7 @@ ShareDBError.CODES = { ERR_DOC_DOES_NOT_EXIST: 'ERR_DOC_DOES_NOT_EXIST', ERR_DOC_TYPE_NOT_RECOGNIZED: 'ERR_DOC_TYPE_NOT_RECOGNIZED', ERR_DOC_WAS_DELETED: 'ERR_DOC_WAS_DELETED', + ERR_DOC_IN_HARD_ROLLBACK: 'ERR_DOC_IN_HARD_ROLLBACK', ERR_INFLIGHT_OP_MISSING: 'ERR_INFLIGHT_OP_MISSING', ERR_INGESTED_SNAPSHOT_HAS_NO_VERSION: 'ERR_INGESTED_SNAPSHOT_HAS_NO_VERSION', ERR_MAX_SUBMIT_RETRIES_EXCEEDED: 'ERR_MAX_SUBMIT_RETRIES_EXCEEDED', diff --git a/test/client/doc.js b/test/client/doc.js index 4156ab4a1..3caa5ddfd 100644 --- a/test/client/doc.js +++ b/test/client/doc.js @@ -636,6 +636,25 @@ describe('Doc', function() { } ], done); }); + + it('rejects ops with ERR_DOC_IN_HARD_ROLLBACK error when in hard rollback', function(done) { + var backend = this.backend; + var doc = backend.connect().get('dogs', 'snoopy'); + + async.series([ + doc.create.bind(doc, {color: 'red'}), + function(next) { + doc.on('error', function(error) { + expect(error.code).to.be.equal('TEST_ERROR'); + }); + doc._hardRollback(new ShareDBError('TEST_ERROR')); + doc.submitOp({p: ['color'], oi: 'blue', od: 'red'}, function(error) { + expect(error.code).to.be.equal('ERR_DOC_IN_HARD_ROLLBACK'); + next(); + }); + } + ], done); + }); }); describe('errors on ops that could cause prototype corruption', function() {