sharedb 3.2.0 → 3.2.1
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/client/connection.js +9 -2
- package/lib/client/doc.js +3 -2
- package/lib/client/presence/presence.js +16 -2
- package/package.json +1 -1
- package/test/client/doc.js +21 -1
- package/test/client/presence/presence.js +112 -0
- package/CHANGELOG.md +0 -44
package/lib/client/connection.js
CHANGED
|
@@ -521,6 +521,7 @@ Connection.prototype.get = function(collection, id) {
|
|
|
521
521
|
this.emit('doc', doc);
|
|
522
522
|
}
|
|
523
523
|
|
|
524
|
+
doc._wantsDestroy = false;
|
|
524
525
|
return doc;
|
|
525
526
|
};
|
|
526
527
|
|
|
@@ -531,7 +532,9 @@ Connection.prototype.get = function(collection, id) {
|
|
|
531
532
|
* @private
|
|
532
533
|
*/
|
|
533
534
|
Connection.prototype._destroyDoc = function(doc) {
|
|
535
|
+
if (!doc._wantsDestroy) return;
|
|
534
536
|
util.digAndRemove(this.collections, doc.collection, doc.id);
|
|
537
|
+
doc.emit('destroy');
|
|
535
538
|
};
|
|
536
539
|
|
|
537
540
|
Connection.prototype._addDoc = function(doc) {
|
|
@@ -773,17 +776,21 @@ Connection.prototype._initialize = function(message) {
|
|
|
773
776
|
|
|
774
777
|
Connection.prototype.getPresence = function(channel) {
|
|
775
778
|
var connection = this;
|
|
776
|
-
|
|
779
|
+
var presence = util.digOrCreate(this._presences, channel, function() {
|
|
777
780
|
return new Presence(connection, channel);
|
|
778
781
|
});
|
|
782
|
+
presence._wantsDestroy = false;
|
|
783
|
+
return presence;
|
|
779
784
|
};
|
|
780
785
|
|
|
781
786
|
Connection.prototype.getDocPresence = function(collection, id) {
|
|
782
787
|
var channel = DocPresence.channel(collection, id);
|
|
783
788
|
var connection = this;
|
|
784
|
-
|
|
789
|
+
var presence = util.digOrCreate(this._presences, channel, function() {
|
|
785
790
|
return new DocPresence(connection, collection, id);
|
|
786
791
|
});
|
|
792
|
+
presence._wantsDestroy = false;
|
|
793
|
+
return presence;
|
|
787
794
|
};
|
|
788
795
|
|
|
789
796
|
Connection.prototype._sendPresenceAction = function(action, seq, presence) {
|
package/lib/client/doc.js
CHANGED
|
@@ -76,6 +76,8 @@ function Doc(connection, collection, id) {
|
|
|
76
76
|
// Whether to re-establish the subscription on reconnect
|
|
77
77
|
this.wantSubscribe = false;
|
|
78
78
|
|
|
79
|
+
this._wantsDestroy = false;
|
|
80
|
+
|
|
79
81
|
// The op that is currently roundtripping to the server, or null.
|
|
80
82
|
//
|
|
81
83
|
// When the connection reconnects, the inflight op is resubmitted.
|
|
@@ -122,6 +124,7 @@ function Doc(connection, collection, id) {
|
|
|
122
124
|
emitter.mixin(Doc);
|
|
123
125
|
|
|
124
126
|
Doc.prototype.destroy = function(callback) {
|
|
127
|
+
this._wantsDestroy = true;
|
|
125
128
|
var doc = this;
|
|
126
129
|
doc.whenNothingPending(function() {
|
|
127
130
|
if (doc.wantSubscribe) {
|
|
@@ -131,12 +134,10 @@ Doc.prototype.destroy = function(callback) {
|
|
|
131
134
|
return doc.emit('error', err);
|
|
132
135
|
}
|
|
133
136
|
doc.connection._destroyDoc(doc);
|
|
134
|
-
doc.emit('destroy');
|
|
135
137
|
if (callback) callback();
|
|
136
138
|
});
|
|
137
139
|
} else {
|
|
138
140
|
doc.connection._destroyDoc(doc);
|
|
139
|
-
doc.emit('destroy');
|
|
140
141
|
if (callback) callback();
|
|
141
142
|
}
|
|
142
143
|
});
|
|
@@ -24,6 +24,7 @@ function Presence(connection, channel) {
|
|
|
24
24
|
|
|
25
25
|
this._remotePresenceInstances = {};
|
|
26
26
|
this._subscriptionCallbacksBySeq = {};
|
|
27
|
+
this._wantsDestroy = false;
|
|
27
28
|
}
|
|
28
29
|
emitter.mixin(Presence);
|
|
29
30
|
|
|
@@ -36,6 +37,9 @@ Presence.prototype.unsubscribe = function(callback) {
|
|
|
36
37
|
};
|
|
37
38
|
|
|
38
39
|
Presence.prototype.create = function(id) {
|
|
40
|
+
if (this._wantsDestroy) {
|
|
41
|
+
throw new Error('Presence is being destroyed');
|
|
42
|
+
}
|
|
39
43
|
id = id || hat();
|
|
40
44
|
var localPresence = this._createLocalPresence(id);
|
|
41
45
|
this.localPresences[id] = localPresence;
|
|
@@ -43,10 +47,15 @@ Presence.prototype.create = function(id) {
|
|
|
43
47
|
};
|
|
44
48
|
|
|
45
49
|
Presence.prototype.destroy = function(callback) {
|
|
50
|
+
this._wantsDestroy = true;
|
|
46
51
|
var presence = this;
|
|
52
|
+
// Store these at the time of destruction: any LocalPresence on this
|
|
53
|
+
// instance at this time will be destroyed, but if the destroy is
|
|
54
|
+
// cancelled, any future LocalPresence objects will be kept.
|
|
55
|
+
// See: https://github.com/share/sharedb/pull/579
|
|
56
|
+
var localIds = Object.keys(presence.localPresences);
|
|
47
57
|
this.unsubscribe(function(error) {
|
|
48
58
|
if (error) return presence._callbackOrEmit(error, callback);
|
|
49
|
-
var localIds = Object.keys(presence.localPresences);
|
|
50
59
|
var remoteIds = Object.keys(presence._remotePresenceInstances);
|
|
51
60
|
async.parallel(
|
|
52
61
|
[
|
|
@@ -56,13 +65,18 @@ Presence.prototype.destroy = function(callback) {
|
|
|
56
65
|
}, next);
|
|
57
66
|
},
|
|
58
67
|
function(next) {
|
|
68
|
+
// We don't bother stashing the RemotePresence instances because
|
|
69
|
+
// they're not really bound to our local state: if we want to
|
|
70
|
+
// destroy, we destroy them all, but if we cancel the destroy,
|
|
71
|
+
// we'll want to keep them all
|
|
72
|
+
if (!presence._wantsDestroy) return next();
|
|
59
73
|
async.each(remoteIds, function(presenceId, next) {
|
|
60
74
|
presence._remotePresenceInstances[presenceId].destroy(next);
|
|
61
75
|
}, next);
|
|
62
76
|
}
|
|
63
77
|
],
|
|
64
78
|
function(error) {
|
|
65
|
-
delete presence.connection._presences[presence.channel];
|
|
79
|
+
if (presence._wantsDestroy) delete presence.connection._presences[presence.channel];
|
|
66
80
|
presence._callbackOrEmit(error, callback);
|
|
67
81
|
}
|
|
68
82
|
);
|
package/package.json
CHANGED
package/test/client/doc.js
CHANGED
|
@@ -43,11 +43,31 @@ describe('Doc', function() {
|
|
|
43
43
|
if (err) return done(err);
|
|
44
44
|
var doc2 = connection.get('dogs', 'fido');
|
|
45
45
|
expect(doc).not.equal(doc2);
|
|
46
|
-
expect(doc).eql(doc2);
|
|
47
46
|
done();
|
|
48
47
|
});
|
|
49
48
|
});
|
|
50
49
|
|
|
50
|
+
it('destroying then getting synchronously does not destroy the new doc', function(done) {
|
|
51
|
+
var connection = this.connection;
|
|
52
|
+
var doc = connection.get('dogs', 'fido');
|
|
53
|
+
var doc2;
|
|
54
|
+
|
|
55
|
+
doc.create({name: 'fido'}, function(error) {
|
|
56
|
+
if (error) return done(error);
|
|
57
|
+
|
|
58
|
+
doc.destroy(function(error) {
|
|
59
|
+
if (error) return done(error);
|
|
60
|
+
var doc3 = connection.get('dogs', 'fido');
|
|
61
|
+
async.parallel([
|
|
62
|
+
doc2.submitOp.bind(doc2, [{p: ['snacks'], oi: true}]),
|
|
63
|
+
doc3.submitOp.bind(doc3, [{p: ['color'], oi: 'gray'}])
|
|
64
|
+
], done);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
doc2 = connection.get('dogs', 'fido');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
51
71
|
it('doc.destroy() works without a callback', function() {
|
|
52
72
|
var doc = this.connection.get('dogs', 'fido');
|
|
53
73
|
doc.destroy();
|
|
@@ -98,6 +98,118 @@ describe('Presence', function() {
|
|
|
98
98
|
], done);
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
+
it('gets presence during a destroy', function(done) {
|
|
102
|
+
var localPresence1 = presence1.create('presence-1');
|
|
103
|
+
var presence2a;
|
|
104
|
+
|
|
105
|
+
async.series([
|
|
106
|
+
presence2.subscribe.bind(presence2),
|
|
107
|
+
function(next) {
|
|
108
|
+
presence2.destroy(errorHandler(done));
|
|
109
|
+
next();
|
|
110
|
+
},
|
|
111
|
+
function(next) {
|
|
112
|
+
presence2a = connection2.getPresence('test-channel');
|
|
113
|
+
presence2a.subscribe(function(error) {
|
|
114
|
+
next(error);
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
function(next) {
|
|
118
|
+
localPresence1.submit({index: 5}, errorHandler(done));
|
|
119
|
+
presence2a.once('receive', function() {
|
|
120
|
+
next();
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
], done);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('destroys old local presence but keeps new local presence when getting during destroy', function(done) {
|
|
127
|
+
presence2.create('presence-2');
|
|
128
|
+
var presence2a;
|
|
129
|
+
|
|
130
|
+
async.series([
|
|
131
|
+
presence2.subscribe.bind(presence2),
|
|
132
|
+
function(next) {
|
|
133
|
+
presence2.destroy(function() {
|
|
134
|
+
expect(presence2).to.equal(presence2a);
|
|
135
|
+
expect(Object.keys(presence2.localPresences)).to.eql(['presence-2a']);
|
|
136
|
+
done();
|
|
137
|
+
});
|
|
138
|
+
next();
|
|
139
|
+
},
|
|
140
|
+
function(next) {
|
|
141
|
+
presence2a = connection2.getPresence('test-channel');
|
|
142
|
+
presence2a.create('presence-2a');
|
|
143
|
+
presence2a.subscribe(function(error) {
|
|
144
|
+
next(error);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
], errorHandler(done));
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('destroys old local presence but keeps new local presence when getting during destroy', function(done) {
|
|
151
|
+
presence2.create('presence-2');
|
|
152
|
+
var presence2a;
|
|
153
|
+
|
|
154
|
+
async.series([
|
|
155
|
+
presence2.subscribe.bind(presence2),
|
|
156
|
+
function(next) {
|
|
157
|
+
presence2.destroy(function() {
|
|
158
|
+
expect(presence2).to.equal(presence2a);
|
|
159
|
+
expect(Object.keys(presence2.localPresences)).to.eql(['presence-2a']);
|
|
160
|
+
done();
|
|
161
|
+
});
|
|
162
|
+
next();
|
|
163
|
+
},
|
|
164
|
+
function(next) {
|
|
165
|
+
presence2a = connection2.getPresence('test-channel');
|
|
166
|
+
presence2a.create('presence-2a');
|
|
167
|
+
presence2a.subscribe(function(error) {
|
|
168
|
+
next(error);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
], errorHandler(done));
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('throws if trying to create local presence when wanting destroy', function(done) {
|
|
175
|
+
presence2.destroy(errorHandler(done));
|
|
176
|
+
expect(function() {
|
|
177
|
+
presence2.create('presence-2');
|
|
178
|
+
}).to.throw('Presence is being destroyed');
|
|
179
|
+
done();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('gets presence after destroy unsubscribe', function(done) {
|
|
183
|
+
var localPresence2 = presence2.create('presence-2');
|
|
184
|
+
var presence2a;
|
|
185
|
+
|
|
186
|
+
var flushLocalPresence2Destroy;
|
|
187
|
+
sinon.stub(localPresence2, 'destroy').callsFake(function(callback) {
|
|
188
|
+
flushLocalPresence2Destroy = callback;
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
async.series([
|
|
192
|
+
presence2.subscribe.bind(presence2),
|
|
193
|
+
function(next) {
|
|
194
|
+
presence2.destroy(function() {
|
|
195
|
+
expect(connection2.getPresence('test-channel')).to.equal(presence2a);
|
|
196
|
+
done();
|
|
197
|
+
});
|
|
198
|
+
next();
|
|
199
|
+
},
|
|
200
|
+
// Wait for the destroy unsubscribe callback to start, where we check
|
|
201
|
+
// _wantsDestroy for the first time
|
|
202
|
+
presence2.unsubscribe.bind(presence2),
|
|
203
|
+
function(next) {
|
|
204
|
+
presence2a = connection2.getPresence('test-channel');
|
|
205
|
+
presence2a.subscribe(function(error) {
|
|
206
|
+
next(error);
|
|
207
|
+
});
|
|
208
|
+
flushLocalPresence2Destroy();
|
|
209
|
+
}
|
|
210
|
+
], errorHandler(done));
|
|
211
|
+
});
|
|
212
|
+
|
|
101
213
|
it('requests existing presence from other subscribed clients when subscribing', function(done) {
|
|
102
214
|
var localPresence1 = presence1.create('presence-1');
|
|
103
215
|
async.series([
|
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).
|