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
|
[](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.
|
|
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.
|
|
222
|
-
this.type.deserialize
|
|
223
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
652
|
-
(this.type.createDeserialized)
|
|
653
|
-
this.type.createDeserialized(op.create.data)
|
|
654
|
-
|
|
655
|
-
|
|
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
|
@@ -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
|
});
|