sharedb 4.0.2 → 4.1.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 CHANGED
@@ -776,6 +776,7 @@ Agent.prototype._broadcastPresence = function(presence, callback) {
776
776
  presence: presence,
777
777
  collection: presence.c
778
778
  };
779
+ var start = Date.now();
779
780
  backend.trigger(backend.MIDDLEWARE_ACTIONS.receivePresence, this, context, function(error) {
780
781
  if (error) return callback(error);
781
782
  backend.transformPresenceToLatestVersion(agent, presence, function(error, presence) {
@@ -783,6 +784,7 @@ Agent.prototype._broadcastPresence = function(presence, callback) {
783
784
  var channel = agent._getPresenceChannel(presence.ch);
784
785
  agent.backend.pubsub.publish([channel], presence, function(error) {
785
786
  if (error) return callback(error);
787
+ backend.emit('timing', 'presence.broadcast', Date.now() - start, context);
786
788
  callback(null, presence);
787
789
  });
788
790
  });
@@ -807,6 +809,19 @@ Agent.prototype._createPresence = function(request) {
807
809
 
808
810
  Agent.prototype._subscribePresence = function(channel, seq, callback) {
809
811
  var agent = this;
812
+
813
+ function requestPresence() {
814
+ agent._requestPresence(channel, function(error) {
815
+ callback(error, {ch: channel, seq: seq});
816
+ });
817
+ }
818
+
819
+ var existingStream = agent.subscribedPresences[channel];
820
+ if (existingStream) {
821
+ agent.presenceSubscriptionSeq[channel] = seq;
822
+ return requestPresence();
823
+ }
824
+
810
825
  var presenceChannel = this._getPresenceChannel(channel);
811
826
  this.backend.pubsub.subscribe(presenceChannel, function(error, stream) {
812
827
  if (error) return callback(error);
@@ -817,17 +832,15 @@ Agent.prototype._subscribePresence = function(channel, seq, callback) {
817
832
  agent.presenceSubscriptionSeq[channel] = seq;
818
833
  agent.subscribedPresences[channel] = stream;
819
834
  agent._subscribeToPresenceStream(channel, stream);
820
- agent._requestPresence(channel, function(error) {
821
- callback(error, {ch: channel, seq: seq});
822
- });
835
+ requestPresence();
823
836
  });
824
837
  };
825
838
 
826
839
  Agent.prototype._unsubscribePresence = function(channel, seq, callback) {
827
- if (seq < this.presenceSubscriptionSeq[channel]) return;
828
840
  this.presenceSubscriptionSeq[channel] = seq;
829
841
  var stream = this.subscribedPresences[channel];
830
842
  if (stream) stream.destroy();
843
+ delete this.subscribedPresences[channel];
831
844
  callback(null, {ch: channel, seq: seq});
832
845
  };
833
846
 
package/lib/client/doc.js CHANGED
@@ -329,10 +329,6 @@ Doc.prototype._handleSubscribe = function(error, snapshot) {
329
329
  Doc.prototype._handleOp = function(err, message) {
330
330
  if (err) {
331
331
  if (this.inflightOp) {
332
- // The server has rejected submission of the current operation. If we get
333
- // an "Op submit rejected" error, this was done intentionally
334
- // and we should roll back but not return an error to the user.
335
- if (err.code === ERROR_CODE.ERR_OP_SUBMIT_REJECTED) err = null;
336
332
  return this._rollback(err);
337
333
  }
338
334
  return this.emit('error', err);
@@ -984,38 +980,44 @@ Doc.prototype._rollback = function(err) {
984
980
  // working state, then call back
985
981
  var op = this.inflightOp;
986
982
 
987
- if ('op' in op && op.type.invert) {
988
- try {
989
- op.op = op.type.invert(op.op);
990
- } catch (error) {
991
- // If the op doesn't support `.invert()`, we just reload the doc
992
- // instead of trying to locally revert it.
993
- return this._hardRollback(err);
994
- }
983
+ if (!('op' in op && op.type.invert)) {
984
+ return this._hardRollback(err);
985
+ }
995
986
 
996
- // Transform the undo operation by any pending ops.
997
- for (var i = 0; i < this.pendingOps.length; i++) {
998
- var transformErr = transformX(this.pendingOps[i], op);
999
- if (transformErr) return this._hardRollback(transformErr);
1000
- }
987
+ try {
988
+ op.op = op.type.invert(op.op);
989
+ } catch (error) {
990
+ // If the op doesn't support `.invert()`, we just reload the doc
991
+ // instead of trying to locally revert it.
992
+ return this._hardRollback(err);
993
+ }
1001
994
 
1002
- // ... and apply it locally, reverting the changes.
1003
- //
1004
- // This operation is applied to look like it comes from a remote source.
1005
- // I'm still not 100% sure about this functionality, because its really a
1006
- // local op. Basically, the problem is that if the client's op is rejected
1007
- // by the server, the editor window should update to reflect the undo.
1008
- try {
1009
- this._otApply(op, false);
1010
- } catch (error) {
1011
- return this._hardRollback(error);
1012
- }
995
+ // Transform the undo operation by any pending ops.
996
+ for (var i = 0; i < this.pendingOps.length; i++) {
997
+ var transformErr = transformX(this.pendingOps[i], op);
998
+ if (transformErr) return this._hardRollback(transformErr);
999
+ }
1013
1000
 
1014
- this._clearInflightOp(err);
1015
- return;
1001
+ // ... and apply it locally, reverting the changes.
1002
+ //
1003
+ // This operation is applied to look like it comes from a remote source.
1004
+ // I'm still not 100% sure about this functionality, because its really a
1005
+ // local op. Basically, the problem is that if the client's op is rejected
1006
+ // by the server, the editor window should update to reflect the undo.
1007
+ try {
1008
+ this._otApply(op, false);
1009
+ } catch (error) {
1010
+ return this._hardRollback(error);
1016
1011
  }
1017
1012
 
1018
- this._hardRollback(err);
1013
+ // The server has rejected submission of the current operation. If we get
1014
+ // an "Op submit rejected" error, this was done intentionally
1015
+ // and we should roll back but not return an error to the user.
1016
+ if (err.code === ERROR_CODE.ERR_OP_SUBMIT_REJECTED) {
1017
+ return this._clearInflightOp(null);
1018
+ }
1019
+
1020
+ this._clearInflightOp(err);
1019
1021
  };
1020
1022
 
1021
1023
  Doc.prototype._hardRollback = function(err) {
@@ -1024,9 +1026,8 @@ Doc.prototype._hardRollback = function(err) {
1024
1026
  // to hit a condition where we have no inflight op, but we do have pending
1025
1027
  // ops. This can happen when an invalid op is submitted, which causes us
1026
1028
  // to hard rollback before the pending op was flushed.
1027
- var pendingOps = [];
1028
- if (this.inflightOp) pendingOps.push(this.inflightOp);
1029
- pendingOps = pendingOps.concat(this.pendingOps);
1029
+ var pendingOps = this.pendingOps;
1030
+ var inflightOp = this.inflightOp;
1030
1031
 
1031
1032
  // Cancel all pending ops and reset if we can't invert
1032
1033
  this._setType(null);
@@ -1036,17 +1037,50 @@ Doc.prototype._hardRollback = function(err) {
1036
1037
 
1037
1038
  // Fetch the latest version from the server to get us back into a working state
1038
1039
  var doc = this;
1039
- this._fetch({force: true}, function() {
1040
+ this._fetch({force: true}, function(fetchError) {
1040
1041
  // We want to check that no errors are swallowed, so we check that:
1041
1042
  // - there are callbacks to call, and
1042
1043
  // - that every single pending op called a callback
1043
1044
  // If there are no ops queued, or one of them didn't handle the error,
1044
1045
  // then we emit the error.
1046
+
1047
+ if (fetchError) {
1048
+ // This is critical error as it means that our doc is not in usable state
1049
+ // anymore, we should throw doc error.
1050
+ logger.error('Hard rollback doc fetch failed.', fetchError, inflightOp);
1051
+
1052
+ doc.emit('error', new ShareDBError(
1053
+ ERROR_CODE.ERR_HARD_ROLLBACK_FETCH_FAILED,
1054
+ 'Hard rollback fetch failed: ' + fetchError.message
1055
+ ));
1056
+ }
1057
+
1058
+ if (err.code === ERROR_CODE.ERR_OP_SUBMIT_REJECTED) {
1059
+ /**
1060
+ * Handle special case of ERR_OP_SUBMIT_REJECTED
1061
+ * This ensures that we resolve the main op callback and reject
1062
+ * all the pending ops. This is hard rollback so all the pending ops will be
1063
+ * discarded. This will ensure that the user is at least informed about it.
1064
+ * more info: https://github.com/share/sharedb/pull/626
1065
+ */
1066
+ if (inflightOp) {
1067
+ util.callEach(inflightOp.callbacks);
1068
+ inflightOp = null;
1069
+ }
1070
+
1071
+ if (!pendingOps.length) return;
1072
+ err = new ShareDBError(
1073
+ ERROR_CODE.ERR_PENDING_OP_REMOVED_BY_OP_SUBMIT_REJECTED,
1074
+ 'Discarding pending op because of hard rollback during ERR_OP_SUBMIT_REJECTED'
1075
+ );
1076
+ }
1077
+
1078
+ if (inflightOp) pendingOps.unshift(inflightOp);
1045
1079
  var allOpsHadCallbacks = !!pendingOps.length;
1046
1080
  for (var i = 0; i < pendingOps.length; i++) {
1047
1081
  allOpsHadCallbacks = util.callEach(pendingOps[i].callbacks, err) && allOpsHadCallbacks;
1048
1082
  }
1049
- if (err && !allOpsHadCallbacks) return doc.emit('error', err);
1083
+ if (err && !allOpsHadCallbacks) doc.emit('error', err);
1050
1084
  });
1051
1085
  };
1052
1086
 
@@ -1062,3 +1096,5 @@ Doc.prototype._clearInflightOp = function(err) {
1062
1096
 
1063
1097
  if (err && !called) return this.emit('error', err);
1064
1098
  };
1099
+
1100
+
@@ -28,12 +28,12 @@ function Presence(connection, channel) {
28
28
  }
29
29
  emitter.mixin(Presence);
30
30
 
31
- Presence.prototype.subscribe = function(callback) {
32
- this._sendSubscriptionAction(true, callback);
31
+ Presence.prototype.subscribe = function(options, callback) {
32
+ this._sendSubscriptionAction(true, options, callback);
33
33
  };
34
34
 
35
- Presence.prototype.unsubscribe = function(callback) {
36
- this._sendSubscriptionAction(false, callback);
35
+ Presence.prototype.unsubscribe = function(options, callback) {
36
+ this._sendSubscriptionAction(false, options, callback);
37
37
  };
38
38
 
39
39
  Presence.prototype.create = function(id) {
@@ -83,8 +83,21 @@ Presence.prototype.destroy = function(callback) {
83
83
  });
84
84
  };
85
85
 
86
- Presence.prototype._sendSubscriptionAction = function(wantSubscribe, callback) {
87
- this.wantSubscribe = !!wantSubscribe;
86
+ Presence.prototype._sendSubscriptionAction = function(wantSubscribe, options, callback) {
87
+ if (typeof options === 'function') {
88
+ callback = options;
89
+ options = {};
90
+ }
91
+ options = options || {};
92
+ wantSubscribe = !!wantSubscribe;
93
+ if (!options._force && wantSubscribe === this.wantSubscribe) {
94
+ if (!callback) return;
95
+ if (wantSubscribe === this.subscribed) return util.nextTick(callback);
96
+ if (Object.keys(this._subscriptionCallbacksBySeq).length) {
97
+ return this._combineSubscribeCallbackWithLastAdded(callback);
98
+ }
99
+ }
100
+ this.wantSubscribe = wantSubscribe;
88
101
  var action = this.wantSubscribe ? ACTIONS.presenceSubscribe : ACTIONS.presenceUnsubscribe;
89
102
  var seq = this.connection._presenceSeq++;
90
103
  this._subscriptionCallbacksBySeq[seq] = callback;
@@ -139,7 +152,10 @@ Presence.prototype._removeRemotePresence = function(id) {
139
152
  };
140
153
 
141
154
  Presence.prototype._onConnectionStateChanged = function() {
142
- if (!this.connection.canSend) return;
155
+ if (!this.connection.canSend) {
156
+ this.subscribed = false;
157
+ return;
158
+ }
143
159
  this._resubscribe();
144
160
  for (var id in this.localPresences) {
145
161
  this.localPresences[id]._sendPending();
@@ -184,3 +200,14 @@ Presence.prototype._callEachOrEmit = function(callbacks, error) {
184
200
  var called = util.callEach(callbacks, error);
185
201
  if (!called && error) this.emit('error', error);
186
202
  };
203
+
204
+ Presence.prototype._combineSubscribeCallbackWithLastAdded = function(callback) {
205
+ var seqs = Object.keys(this._subscriptionCallbacksBySeq);
206
+ var lastSeq = seqs[seqs.length - 1];
207
+ var originalCallback = this._subscriptionCallbacksBySeq[lastSeq];
208
+ if (!originalCallback) return this._subscriptionCallbacksBySeq[lastSeq] = callback;
209
+ this._subscriptionCallbacksBySeq[lastSeq] = function(error) {
210
+ originalCallback(error);
211
+ callback(error);
212
+ };
213
+ };
@@ -107,7 +107,7 @@ RemoteDocPresence.prototype._catchUpStalePresence = function() {
107
107
  this._doc.fetch();
108
108
  // We're already subscribed, but we send another subscribe message
109
109
  // to force presence updates from other clients
110
- this.presence.subscribe();
110
+ this.presence.subscribe({_force: true});
111
111
  return false;
112
112
  }
113
113
 
package/lib/error.js CHANGED
@@ -38,6 +38,8 @@ ShareDBError.CODES = {
38
38
  ERR_OP_ALREADY_SUBMITTED: 'ERR_OP_ALREADY_SUBMITTED',
39
39
  ERR_OP_NOT_ALLOWED_IN_PROJECTION: 'ERR_OP_NOT_ALLOWED_IN_PROJECTION',
40
40
  ERR_OP_SUBMIT_REJECTED: 'ERR_OP_SUBMIT_REJECTED',
41
+ ERR_PENDING_OP_REMOVED_BY_OP_SUBMIT_REJECTED: 'ERR_PENDING_OP_REMOVED_BY_OP_SUBMIT_REJECTED',
42
+ ERR_HARD_ROLLBACK_FETCH_FAILED: 'ERR_HARD_ROLLBACK_FETCH_FAILED',
41
43
  ERR_OP_VERSION_MISMATCH_AFTER_TRANSFORM: 'ERR_OP_VERSION_MISMATCH_AFTER_TRANSFORM',
42
44
  ERR_OP_VERSION_MISMATCH_DURING_TRANSFORM: 'ERR_OP_VERSION_MISMATCH_DURING_TRANSFORM',
43
45
  ERR_OP_VERSION_NEWER_THAN_CURRENT_SNAPSHOT: 'ERR_OP_VERSION_NEWER_THAN_CURRENT_SNAPSHOT',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sharedb",
3
- "version": "4.0.2",
3
+ "version": "4.1.0",
4
4
  "description": "JSON OT database backend",
5
5
  "main": "lib/index.js",
6
6
  "dependencies": {
@@ -6,6 +6,7 @@ var richText = require('rich-text').type;
6
6
  var ShareDBError = require('../../lib/error');
7
7
  var errorHandler = require('../util').errorHandler;
8
8
  var sinon = require('sinon');
9
+ var types = require('../../lib/types');
9
10
 
10
11
  describe('Doc', function() {
11
12
  beforeEach(function() {
@@ -514,6 +515,38 @@ describe('Doc', function() {
514
515
  ], done);
515
516
  });
516
517
 
518
+ it('throws an error when hard rollback fetch failed', function(done) {
519
+ var backend = this.backend;
520
+ doc = this.connection.get('dogs', 'scrappy');
521
+ types.register(richText);
522
+
523
+ async.series([
524
+ doc.create.bind(doc, {ops: [{insert: 'Scrappy'}]}, 'rich-text'),
525
+ function(next) {
526
+ backend.use('reply', function(replyContext, cb) {
527
+ if (replyContext.request.a !== 'f') return cb();
528
+ cb({code: 'FETCH_ERROR'});
529
+ });
530
+ backend.use('submit', function(_context, cb) {
531
+ cb(new ShareDBError('SUBMIT_ERROR'));
532
+ });
533
+ var nonInvertibleOp = [{insert: 'e'}];
534
+
535
+ var count = 0;
536
+ function expectError(code) {
537
+ count++;
538
+ return function(error) {
539
+ expect(error.code).to.equal(code);
540
+ count--;
541
+ if (!count) next();
542
+ };
543
+ }
544
+
545
+ doc.on('error', expectError('ERR_HARD_ROLLBACK_FETCH_FAILED'));
546
+ doc.submitOp(nonInvertibleOp, expectError('SUBMIT_ERROR'));
547
+ }
548
+ ], done);
549
+ });
517
550
 
518
551
  it('rescues an irreversible op collision', function(done) {
519
552
  // This test case attempts to reconstruct the following corner case, with
@@ -401,6 +401,62 @@ describe('Presence', function() {
401
401
  ], done);
402
402
  });
403
403
 
404
+ it('does not send another subscribe request if already subscribed', function(done) {
405
+ var sendPresenceAction = sinon.spy(connection1, '_sendPresenceAction');
406
+ async.series([
407
+ presence1.subscribe.bind(presence1),
408
+ presence1.subscribe.bind(presence1),
409
+ function(next) {
410
+ expect(sendPresenceAction).to.have.been.calledOnce;
411
+ next();
412
+ }
413
+ ], done);
414
+ });
415
+
416
+ it('only subscribes once when calling multiple in parallel', function(done) {
417
+ var sendPresenceAction = sinon.spy(connection1, '_sendPresenceAction');
418
+ async.series([
419
+ function(next) {
420
+ async.parallel([
421
+ presence1.subscribe.bind(presence1),
422
+ presence1.subscribe.bind(presence1)
423
+ ], next);
424
+ },
425
+ function(next) {
426
+ expect(sendPresenceAction).to.have.been.calledOnce;
427
+ next();
428
+ }
429
+ ], done);
430
+ });
431
+
432
+ it('subscribes once when calling again after no callback', function(done) {
433
+ var sendPresenceAction = sinon.spy(connection1, '_sendPresenceAction');
434
+ presence1.subscribe(); // no callback
435
+ async.series([
436
+ presence1.subscribe.bind(presence1),
437
+ function(next) {
438
+ expect(sendPresenceAction).to.have.been.calledOnce;
439
+ next();
440
+ }
441
+ ], done);
442
+ });
443
+
444
+ it('does not leak Streams when subscribing the same presence multiple times', function(done) {
445
+ var streamsCount;
446
+ async.series([
447
+ presence1.subscribe.bind(presence1, {force: true}),
448
+ function(next) {
449
+ streamsCount = backend.pubsub.streamsCount;
450
+ next();
451
+ },
452
+ presence1.subscribe.bind(presence1, {force: true}),
453
+ function(next) {
454
+ expect(backend.pubsub.streamsCount).to.equal(streamsCount);
455
+ next();
456
+ }
457
+ ], done);
458
+ });
459
+
404
460
  it('throws an error when trying to create a presence with a non-string ID', function() {
405
461
  expect(function() {
406
462
  presence1.create(123);
@@ -5,9 +5,11 @@ var types = require('../../lib/types');
5
5
  var deserializedType = require('./deserialized-type');
6
6
  var numberType = require('./number-type');
7
7
  var errorHandler = require('../util').errorHandler;
8
+ var richText = require('rich-text');
8
9
  types.register(deserializedType.type);
9
10
  types.register(deserializedType.type2);
10
11
  types.register(numberType.type);
12
+ types.register(richText.type);
11
13
 
12
14
  module.exports = function() {
13
15
  describe('client submit', function() {
@@ -919,6 +921,27 @@ module.exports = function() {
919
921
  ], done);
920
922
  });
921
923
 
924
+
925
+ it('request.rejectedError() soft rejects main op and throws for pending ops on hard rollback', function(done) {
926
+ this.backend.use('submit', function(request, next) {
927
+ if (request.op.create) {
928
+ next(request.rejectedError());
929
+ }
930
+ });
931
+
932
+ var connection = this.backend.connect();
933
+ var doc = connection.get('dogs', 'fido');
934
+ doc.preventCompose = true;
935
+
936
+ doc.create({age: 3}, function(error) {
937
+ if (error) done(error);
938
+ });
939
+ doc.submitOp({p: ['age'], na: 1}, function(err) {
940
+ expect(err.code).to.be.equal('ERR_PENDING_OP_REMOVED_BY_OP_SUBMIT_REJECTED');
941
+ done();
942
+ });
943
+ });
944
+
922
945
  it('request.rejectedError() soft rejects a create', function(done) {
923
946
  this.backend.use('submit', function(request, next) {
924
947
  next(request.rejectedError());
@@ -949,6 +972,29 @@ module.exports = function() {
949
972
  });
950
973
  });
951
974
 
975
+ it(
976
+ 'request.rejectedError() soft rejects main op and throws for pending ops on hard rollback without callback',
977
+ function(done) {
978
+ this.backend.use('submit', function(request, next) {
979
+ if (request.op.create) {
980
+ next(request.rejectedError());
981
+ }
982
+ });
983
+
984
+ var connection = this.backend.connect();
985
+ var doc = connection.get('dogs', 'fido');
986
+ doc.preventCompose = true;
987
+
988
+ doc.create({age: 3});
989
+ doc.submitOp({p: ['age'], na: 1});
990
+
991
+ doc.on('error', function(err) {
992
+ expect(err.code).to.be.equal('ERR_PENDING_OP_REMOVED_BY_OP_SUBMIT_REJECTED');
993
+ done();
994
+ });
995
+ }
996
+ );
997
+
952
998
  it('passing an error in submit middleware rejects an op and calls back with the erorr', function(done) {
953
999
  this.backend.use('submit', function(request, next) {
954
1000
  if ('op' in request.op) return next({message: 'Custom error'});
@@ -1043,6 +1089,65 @@ module.exports = function() {
1043
1089
  });
1044
1090
  });
1045
1091
 
1092
+ it('request.rejectedError() soft rejects main op and pending ops for invertible type', function(done) {
1093
+ var rejectedOnce = false;
1094
+ this.backend.use('submit', function(request, next) {
1095
+ if ('op' in request.op && !rejectedOnce) {
1096
+ rejectedOnce = true;
1097
+ return next(request.rejectedError());
1098
+ }
1099
+ next();
1100
+ });
1101
+ var doc = this.backend.connect().get('dogs', 'fido');
1102
+ doc.preventCompose = true;
1103
+
1104
+ doc.create({age: 3}, function(err) {
1105
+ if (err) return done(err);
1106
+ doc.submitOp({p: ['age'], na: 1}, function(err) {
1107
+ if (err) return done(err);
1108
+ });
1109
+ doc.submitOp({p: ['age'], na: 3}, function(err) {
1110
+ if (err) return done(err);
1111
+ expect(doc.version).equal(2);
1112
+ expect(doc.data).eql({age: 6});
1113
+ done();
1114
+ });
1115
+ expect(doc.version).equal(1);
1116
+ expect(doc.data).eql({age: 7});
1117
+ });
1118
+ });
1119
+
1120
+ it(
1121
+ 'request.rejectedError() soft rejects main op and throws for pending ops for non invertible type',
1122
+ function(done) {
1123
+ var rejectedOnce = false;
1124
+ this.backend.use('submit', function(request, next) {
1125
+ if ('op' in request.op && !rejectedOnce) {
1126
+ rejectedOnce = true;
1127
+ return next(request.rejectedError());
1128
+ }
1129
+ next();
1130
+ });
1131
+ var doc = this.backend.connect().get('dogs', 'fido');
1132
+ doc.preventCompose = true;
1133
+
1134
+ doc.create({ops: [{insert: 'Scrappy'}]}, 'rich-text', function(err) {
1135
+ if (err) return done(err);
1136
+
1137
+ var nonInvertibleOp = [{insert: 'a'}];
1138
+ doc.submitOp(nonInvertibleOp, function(err) {
1139
+ if (err) return done(err);
1140
+ });
1141
+ doc.submitOp([{insert: 'b'}], function(err) {
1142
+ expect(err.code).to.be.equal('ERR_PENDING_OP_REMOVED_BY_OP_SUBMIT_REJECTED');
1143
+ done();
1144
+ });
1145
+ expect(doc.version).equal(1);
1146
+ expect(doc.data.ops).eql([{insert: 'baScrappy'}]);
1147
+ });
1148
+ }
1149
+ );
1150
+
1046
1151
  it('request.rejectedError() soft rejects an op without callback', function(done) {
1047
1152
  this.backend.use('submit', function(request, next) {
1048
1153
  if ('op' in request.op) return next(request.rejectedError());
package/CHANGELOG.md DELETED
@@ -1,44 +0,0 @@
1
- ## v2.0
2
-
3
- ### Breaking changes
4
-
5
- * Drop Node.js v10 support
6
-
7
- ## v1.0-beta
8
-
9
- ### Breaking changes
10
-
11
- * Add options argument to all public database adapter methods that read
12
- or write from snapshots or ops.
13
-
14
- * DB methods that get snapshots or ops no longer return metadata unless
15
- `{metadata: true}` option is passed.
16
-
17
- * Replace `source` argument with `options` in doc methods. Use `options.source`
18
- instead.
19
-
20
- * Backend streams now write objects intead of strings.
21
-
22
- * MemoryDB.prototype._querySync now returns `{snapshots: ..., extra: ...}`
23
- instead of just an array of snapshots.
24
-
25
- ### Non-breaking changes
26
-
27
- * Add options argument to backend.submit.
28
-
29
- * Add error codes to all errors.
30
-
31
- * Add `'updated'` event on queries which fires on all query result changes.
32
-
33
- * In clients, wrap errors in Error objects to they get passed through event
34
- emitters.
35
-
36
- * Sanitize stack traces when sending errors to client, but log them on the
37
- server.
38
-
39
- ## v0.11.37
40
-
41
- Beginning of changelog.
42
-
43
- If you're upgrading from ShareJS 0.7 or earlier,
44
- take a look at the [ShareJS upgrade guide](docs/upgrading-from-sharejs.md).