sharedb 2.2.1 → 2.2.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/README.md CHANGED
@@ -1,5 +1,3 @@
1
- _This README is for `sharedb@1.x`. For `sharedb@1.x-beta`, see [the 1.x-beta branch](https://github.com/share/sharedb/tree/1.x-beta). To upgrade, see [the upgrade guide](https://github.com/share/sharedb/wiki/Upgrading-to-sharedb@1.0.0-from-1.0.0-beta)._
2
-
3
1
  # ShareDB
4
2
 
5
3
  [![NPM Version](https://img.shields.io/npm/v/sharedb.svg)](https://npmjs.org/package/sharedb)
package/lib/client/doc.js CHANGED
@@ -114,6 +114,10 @@ function Doc(connection, collection, id) {
114
114
  // ops are still received. Should be toggled through the pause() and
115
115
  // resume() methods to correctly flush on resume.
116
116
  this.paused = false;
117
+
118
+ // Internal counter that gets incremented every time doc.data is updated.
119
+ // Used as a cheap way to check if doc.data has changed.
120
+ this._dataStateVersion = 0;
117
121
  }
118
122
  emitter.mixin(Doc);
119
123
 
@@ -156,13 +160,18 @@ Doc.prototype._setType = function(newType) {
156
160
  } else if (newType === null) {
157
161
  this.type = newType;
158
162
  // If we removed the type from the object, also remove its data
159
- this.data = undefined;
163
+ this._setData(undefined);
160
164
  } else {
161
165
  var err = new ShareDBError(ERROR_CODE.ERR_DOC_TYPE_NOT_RECOGNIZED, 'Missing type ' + newType);
162
166
  return this.emit('error', err);
163
167
  }
164
168
  };
165
169
 
170
+ Doc.prototype._setData = function(data) {
171
+ this.data = data;
172
+ this._dataStateVersion++;
173
+ };
174
+
166
175
  // Ingest snapshot data. This data must include a version, snapshot and type.
167
176
  // This is used both to ingest data that was exported with a webpage and data
168
177
  // that was received from the server during a fetch.
@@ -218,9 +227,11 @@ Doc.prototype.ingestSnapshot = function(snapshot, callback) {
218
227
  this.version = snapshot.v;
219
228
  var type = (snapshot.type === undefined) ? types.defaultType : snapshot.type;
220
229
  this._setType(type);
221
- this.data = (this.type && this.type.deserialize) ?
222
- this.type.deserialize(snapshot.data) :
223
- snapshot.data;
230
+ this._setData(
231
+ (this.type && this.type.deserialize) ?
232
+ this.type.deserialize(snapshot.data) :
233
+ snapshot.data
234
+ );
224
235
  this.emit('load');
225
236
  callback && callback();
226
237
  };
@@ -622,7 +633,7 @@ Doc.prototype._otApply = function(op, source) {
622
633
  }
623
634
  // Apply the individual op component
624
635
  this.emit('before op', componentOp.op, source, op.src);
625
- this.data = this.type.apply(this.data, componentOp.op);
636
+ this._setData(this.type.apply(this.data, componentOp.op));
626
637
  this.emit('op', componentOp.op, source, op.src);
627
638
  }
628
639
  this.emit('op batch', op.op, source);
@@ -635,7 +646,7 @@ Doc.prototype._otApply = function(op, source) {
635
646
  // the snapshot before it gets changed
636
647
  this.emit('before op', op.op, source, op.src);
637
648
  // Apply the operation to the local data, mutating it in place
638
- this.data = this.type.apply(this.data, op.op);
649
+ this._setData(this.type.apply(this.data, op.op));
639
650
  // Emit an 'op' event once the local data includes the changes from the
640
651
  // op. For locally submitted ops, this will be synchronously with
641
652
  // submission and before the server or other clients have received the op.
@@ -648,11 +659,15 @@ Doc.prototype._otApply = function(op, source) {
648
659
 
649
660
  if (op.create) {
650
661
  this._setType(op.create.type);
651
- this.data = (this.type.deserialize) ?
652
- (this.type.createDeserialized) ?
653
- this.type.createDeserialized(op.create.data) :
654
- this.type.deserialize(this.type.create(op.create.data)) :
655
- this.type.create(op.create.data);
662
+ if (this.type.deserialize) {
663
+ if (this.type.createDeserialized) {
664
+ this._setData(this.type.createDeserialized(op.create.data));
665
+ } else {
666
+ this._setData(this.type.deserialize(this.type.create(op.create.data)));
667
+ }
668
+ } else {
669
+ this._setData(this.type.create(op.create.data));
670
+ }
656
671
  this.emit('create', source);
657
672
  return;
658
673
  }
@@ -12,6 +12,7 @@ function LocalDocPresence(presence, presenceId) {
12
12
 
13
13
  this._doc = this.connection.get(this.collection, this.id);
14
14
  this._isSending = false;
15
+ this._docDataVersionByPresenceVersion = {};
15
16
 
16
17
  this._opHandler = this._transformAgainstOp.bind(this);
17
18
  this._createOrDelHandler = this._handleCreateOrDel.bind(this);
@@ -35,6 +36,9 @@ LocalDocPresence.prototype.submit = function(value, callback) {
35
36
  return this._callbackOrEmit(error, callback);
36
37
  };
37
38
 
39
+ // Record the current data state version to check if we need to transform
40
+ // the presence later
41
+ this._docDataVersionByPresenceVersion[this.presenceVersion] = this._doc._dataStateVersion;
38
42
  LocalPresence.prototype.submit.call(this, value, callback);
39
43
  };
40
44
 
@@ -63,6 +67,7 @@ LocalDocPresence.prototype._sendPending = function() {
63
67
  });
64
68
 
65
69
  presence._pendingMessages = [];
70
+ presence._docDataVersionByPresenceVersion = {};
66
71
  });
67
72
  };
68
73
 
@@ -76,9 +81,18 @@ LocalDocPresence.prototype._registerWithDoc = function() {
76
81
 
77
82
  LocalDocPresence.prototype._transformAgainstOp = function(op, source) {
78
83
  var presence = this;
84
+ var docDataVersion = this._doc._dataStateVersion;
85
+
79
86
  this._pendingMessages.forEach(function(message) {
87
+ // Check if the presence needs transforming against the op - this is to check against
88
+ // edge cases where presence is submitted from an 'op' event
89
+ var messageDocDataVersion = presence._docDataVersionByPresenceVersion[message.pv];
90
+ if (messageDocDataVersion >= docDataVersion) return;
80
91
  try {
81
92
  message.p = presence._transformPresence(message.p, op, source);
93
+ // Ensure the presence's data version is kept consistent to deal with "deep" op
94
+ // submissions
95
+ presence._docDataVersionByPresenceVersion[message.pv] = docDataVersion;
82
96
  } catch (error) {
83
97
  var callback = presence._getCallback(message.pv);
84
98
  presence._callbackOrEmit(error, callback);
@@ -103,6 +117,7 @@ LocalDocPresence.prototype._handleCreateOrDel = function() {
103
117
  LocalDocPresence.prototype._handleLoad = function() {
104
118
  this.value = null;
105
119
  this._pendingMessages = [];
120
+ this._docDataVersionByPresenceVersion = {};
106
121
  };
107
122
 
108
123
  LocalDocPresence.prototype._message = function() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sharedb",
3
- "version": "2.2.1",
3
+ "version": "2.2.2",
4
4
  "description": "JSON OT database backend",
5
5
  "main": "lib/index.js",
6
6
  "dependencies": {
@@ -979,4 +979,50 @@ describe('DocPresence', function() {
979
979
  }
980
980
  ], done);
981
981
  });
982
+
983
+ it('does not transform presence submitted in an op event when the presence was created late', function(done) {
984
+ var localPresence1;
985
+
986
+ doc1.on('op', function() {
987
+ localPresence1.submit({index: 7});
988
+ });
989
+
990
+ localPresence1 = presence1.create('presence-1');
991
+
992
+ async.series([
993
+ presence2.subscribe.bind(presence2),
994
+ function(next) {
995
+ doc1.submitOp({index: 5, value: 'ern'});
996
+ presence2.on('receive', function(id, value) {
997
+ expect(value).to.eql({index: 7});
998
+ next();
999
+ });
1000
+ }
1001
+ ], done);
1002
+ });
1003
+
1004
+ it('does transform late-created presence submitted in an op event by a deep second op', function(done) {
1005
+ var localPresence1;
1006
+
1007
+ var submitted = false;
1008
+ doc1.on('op', function() {
1009
+ if (submitted) return;
1010
+ submitted = true;
1011
+ localPresence1.submit({index: 7});
1012
+ doc1.submitOp({index: 5, value: 'akg'});
1013
+ });
1014
+
1015
+ localPresence1 = presence1.create('presence-1');
1016
+
1017
+ async.series([
1018
+ presence2.subscribe.bind(presence2),
1019
+ function(next) {
1020
+ doc1.submitOp({index: 5, value: 'ern'});
1021
+ presence2.on('receive', function(id, value) {
1022
+ expect(value).to.eql({index: 10});
1023
+ next();
1024
+ });
1025
+ }
1026
+ ], done);
1027
+ });
982
1028
  });