sharedb 6.0.3 → 6.0.4
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 +2 -2
- package/lib/agent.js +7 -0
- package/lib/backend.js +1 -4
- package/lib/client/doc.js +7 -18
- package/lib/client/query.js +1 -2
- package/lib/util.js +5 -0
- package/package.json +1 -1
- package/test/client/snapshot-timestamp-request.js +20 -0
- package/test/client/snapshot-version-request.js +20 -0
- package/test/lib-util.js +56 -0
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# ShareDB
|
|
2
2
|
|
|
3
3
|
[](https://npmjs.org/package/sharedb)
|
|
4
|
-

|
|
5
|
+
[](https://coveralls.io/github/share/sharedb?branch=main)
|
|
6
6
|
|
|
7
7
|
ShareDB is a realtime database backend based on [Operational Transformation
|
|
8
8
|
(OT)](https://en.wikipedia.org/wiki/Operational_transformation) of JSON
|
package/lib/agent.js
CHANGED
|
@@ -388,6 +388,8 @@ Agent.prototype._checkRequest = function(request) {
|
|
|
388
388
|
request.a === ACTIONS.fetch ||
|
|
389
389
|
request.a === ACTIONS.subscribe ||
|
|
390
390
|
request.a === ACTIONS.unsubscribe ||
|
|
391
|
+
request.a === ACTIONS.snapshotFetch ||
|
|
392
|
+
request.a === ACTIONS.snapshotFetchByTimestamp ||
|
|
391
393
|
request.a === ACTIONS.presence) {
|
|
392
394
|
// Doc-based request.
|
|
393
395
|
if (request.c != null) {
|
|
@@ -401,6 +403,11 @@ Agent.prototype._checkRequest = function(request) {
|
|
|
401
403
|
}
|
|
402
404
|
}
|
|
403
405
|
|
|
406
|
+
if (request.a === ACTIONS.snapshotFetch || request.a === ACTIONS.snapshotFetchByTimestamp) {
|
|
407
|
+
if (request.c == null) return 'Invalid collection';
|
|
408
|
+
if (request.d == null) return 'Invalid id';
|
|
409
|
+
}
|
|
410
|
+
|
|
404
411
|
if (request.a === ACTIONS.op || request.a === ACTIONS.presence) {
|
|
405
412
|
if (request.v != null && (typeof request.v !== 'number' || request.v < 0)) return 'Invalid version';
|
|
406
413
|
}
|
package/lib/backend.js
CHANGED
|
@@ -106,10 +106,7 @@ Backend.prototype.close = function(callback) {
|
|
|
106
106
|
var wait = 4;
|
|
107
107
|
var backend = this;
|
|
108
108
|
function finish(err) {
|
|
109
|
-
if (err)
|
|
110
|
-
if (callback) return callback(err);
|
|
111
|
-
return backend.emit('error', err);
|
|
112
|
-
}
|
|
109
|
+
if (err) return util.callbackOrEmit(backend, err, callback);
|
|
113
110
|
if (--wait) return;
|
|
114
111
|
if (callback) callback();
|
|
115
112
|
}
|
package/lib/client/doc.js
CHANGED
|
@@ -131,10 +131,7 @@ Doc.prototype.destroy = function(callback) {
|
|
|
131
131
|
doc.whenNothingPending(function() {
|
|
132
132
|
if (doc.wantSubscribe) {
|
|
133
133
|
doc.unsubscribe(function(err) {
|
|
134
|
-
if (err)
|
|
135
|
-
if (callback) return callback(err);
|
|
136
|
-
return doc.emit('error', err);
|
|
137
|
-
}
|
|
134
|
+
if (err) return util.callbackOrEmit(doc, err, callback);
|
|
138
135
|
doc.connection._destroyDoc(doc);
|
|
139
136
|
if (callback) callback();
|
|
140
137
|
});
|
|
@@ -191,8 +188,7 @@ Doc.prototype.ingestSnapshot = function(snapshot, callback) {
|
|
|
191
188
|
ERROR_CODE.ERR_INGESTED_SNAPSHOT_HAS_NO_VERSION,
|
|
192
189
|
'Missing version in ingested snapshot. ' + this.collection + '.' + this.id
|
|
193
190
|
);
|
|
194
|
-
|
|
195
|
-
return this.emit('error', err);
|
|
191
|
+
return util.callbackOrEmit(this, err, callback);
|
|
196
192
|
}
|
|
197
193
|
|
|
198
194
|
// If the doc is already created or there are ops pending, we cannot use the
|
|
@@ -214,8 +210,7 @@ Doc.prototype.ingestSnapshot = function(snapshot, callback) {
|
|
|
214
210
|
ERROR_CODE.ERR_DOC_MISSING_VERSION,
|
|
215
211
|
'Cannot ingest snapshot in doc with null version. ' + this.collection + '.' + this.id
|
|
216
212
|
);
|
|
217
|
-
|
|
218
|
-
return this.emit('error', err);
|
|
213
|
+
return util.callbackOrEmit(this, err, callback);
|
|
219
214
|
}
|
|
220
215
|
// If we got a snapshot for a version further along than the document is
|
|
221
216
|
// currently, issue a fetch to get the latest ops and catch us up
|
|
@@ -764,8 +759,7 @@ Doc.prototype._submit = function(op, source, callback) {
|
|
|
764
759
|
);
|
|
765
760
|
}
|
|
766
761
|
|
|
767
|
-
|
|
768
|
-
return this.emit('error', err);
|
|
762
|
+
return util.callbackOrEmit(this, err, callback);
|
|
769
763
|
}
|
|
770
764
|
// Try to normalize the op. This removes trailing skip:0's and things like that.
|
|
771
765
|
if (this.type.normalize) op.op = this.type.normalize(op.op);
|
|
@@ -773,10 +767,7 @@ Doc.prototype._submit = function(op, source, callback) {
|
|
|
773
767
|
// This has to happen before _pushOp(), because _tryCompose() applies the op
|
|
774
768
|
// to a pending create, well before _otApply() gets a chance to check it
|
|
775
769
|
var pathError = ot.checkOpPathsForType(this.type, op);
|
|
776
|
-
if (pathError)
|
|
777
|
-
if (callback) return callback(pathError);
|
|
778
|
-
return this.emit('error', pathError);
|
|
779
|
-
}
|
|
770
|
+
if (pathError) return util.callbackOrEmit(this, pathError, callback);
|
|
780
771
|
}
|
|
781
772
|
|
|
782
773
|
try {
|
|
@@ -913,8 +904,7 @@ Doc.prototype.create = function(data, type, options, callback) {
|
|
|
913
904
|
}
|
|
914
905
|
if (this.type) {
|
|
915
906
|
var err = new ShareDBError(ERROR_CODE.ERR_DOC_ALREADY_CREATED, 'Document already exists');
|
|
916
|
-
|
|
917
|
-
return this.emit('error', err);
|
|
907
|
+
return util.callbackOrEmit(this, err, callback);
|
|
918
908
|
}
|
|
919
909
|
var op = {create: {type: type, data: data}};
|
|
920
910
|
var source = options && options.source;
|
|
@@ -935,8 +925,7 @@ Doc.prototype.del = function(options, callback) {
|
|
|
935
925
|
}
|
|
936
926
|
if (!this.type) {
|
|
937
927
|
var err = new ShareDBError(ERROR_CODE.ERR_DOC_DOES_NOT_EXIST, 'Document does not exist');
|
|
938
|
-
|
|
939
|
-
return this.emit('error', err);
|
|
928
|
+
return util.callbackOrEmit(this, err, callback);
|
|
940
929
|
}
|
|
941
930
|
var op = {del: true};
|
|
942
931
|
var source = options && options.source;
|
package/lib/client/query.js
CHANGED
|
@@ -149,8 +149,7 @@ Query.prototype._finishResponse = function(err, callback) {
|
|
|
149
149
|
this.ready = true;
|
|
150
150
|
if (err) {
|
|
151
151
|
this.connection._destroyQuery(this);
|
|
152
|
-
|
|
153
|
-
return this.emit('error', err);
|
|
152
|
+
return util.callbackOrEmit(this, err, callback);
|
|
154
153
|
}
|
|
155
154
|
if (callback) callback(null, this.results, this.extra);
|
|
156
155
|
};
|
package/lib/util.js
CHANGED
|
@@ -84,6 +84,11 @@ exports.callEach = function(callbacks, error) {
|
|
|
84
84
|
return called;
|
|
85
85
|
};
|
|
86
86
|
|
|
87
|
+
exports.callbackOrEmit = function(emitter, error, callback) {
|
|
88
|
+
if (callback) return callback(error);
|
|
89
|
+
if (error) emitter.emit('error', error);
|
|
90
|
+
};
|
|
91
|
+
|
|
87
92
|
exports.truthy = function(arg) {
|
|
88
93
|
return !!arg;
|
|
89
94
|
};
|
package/package.json
CHANGED
|
@@ -208,6 +208,26 @@ describe('SnapshotTimestampRequest', function() {
|
|
|
208
208
|
expect(fetch).to.throw(Error);
|
|
209
209
|
});
|
|
210
210
|
|
|
211
|
+
[{}, null, '__proto__'].forEach(function(badValue) {
|
|
212
|
+
it('errors without reading the database if the collection is ' + JSON.stringify(badValue), function(done) {
|
|
213
|
+
var getSnapshot = sinon.spy(MemoryDb.prototype, 'getSnapshot');
|
|
214
|
+
backend.connect().fetchSnapshotByTimestamp(badValue, 'time-machine', null, function(error) {
|
|
215
|
+
expect(error).to.include({code: 'ERR_MESSAGE_BADLY_FORMED', message: 'Invalid collection'});
|
|
216
|
+
expect(getSnapshot).not.to.have.been.called;
|
|
217
|
+
done();
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('errors without reading the database if the id is ' + JSON.stringify(badValue), function(done) {
|
|
222
|
+
var getSnapshot = sinon.spy(MemoryDb.prototype, 'getSnapshot');
|
|
223
|
+
backend.connect().fetchSnapshotByTimestamp('books', badValue, null, function(error) {
|
|
224
|
+
expect(error).to.include({code: 'ERR_MESSAGE_BADLY_FORMED', message: 'Invalid id'});
|
|
225
|
+
expect(getSnapshot).not.to.have.been.called;
|
|
226
|
+
done();
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
211
231
|
it('returns an empty snapshot if trying to fetch a non-existent document', function(done) {
|
|
212
232
|
backend.connect().fetchSnapshotByTimestamp('books', 'does-not-exist', day1, function(error, snapshot) {
|
|
213
233
|
if (error) return done(error);
|
|
@@ -143,6 +143,26 @@ describe('SnapshotVersionRequest', function() {
|
|
|
143
143
|
expect(fetch).to.throw(Error);
|
|
144
144
|
});
|
|
145
145
|
|
|
146
|
+
[{}, null, '__proto__'].forEach(function(badValue) {
|
|
147
|
+
it('errors without reading the database if the collection is ' + JSON.stringify(badValue), function(done) {
|
|
148
|
+
var getSnapshot = sinon.spy(MemoryDb.prototype, 'getSnapshot');
|
|
149
|
+
backend.connect().fetchSnapshot(badValue, 'don-quixote', null, function(error) {
|
|
150
|
+
expect(error).to.include({code: 'ERR_MESSAGE_BADLY_FORMED', message: 'Invalid collection'});
|
|
151
|
+
expect(getSnapshot).not.to.have.been.called;
|
|
152
|
+
done();
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('errors without reading the database if the id is ' + JSON.stringify(badValue), function(done) {
|
|
157
|
+
var getSnapshot = sinon.spy(MemoryDb.prototype, 'getSnapshot');
|
|
158
|
+
backend.connect().fetchSnapshot('books', badValue, null, function(error) {
|
|
159
|
+
expect(error).to.include({code: 'ERR_MESSAGE_BADLY_FORMED', message: 'Invalid id'});
|
|
160
|
+
expect(getSnapshot).not.to.have.been.called;
|
|
161
|
+
done();
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
146
166
|
it('errors if asking for a version that does not exist', function(done) {
|
|
147
167
|
backend.connect().fetchSnapshot('books', 'don-quixote', 4, function(error, snapshot) {
|
|
148
168
|
expect(error).to.be.ok;
|
package/test/lib-util.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
var EventEmitter = require('../lib/emitter').EventEmitter;
|
|
2
|
+
var util = require('../lib/util');
|
|
3
|
+
var expect = require('chai').expect;
|
|
4
|
+
|
|
5
|
+
describe('util.callbackOrEmit', function() {
|
|
6
|
+
var emitter;
|
|
7
|
+
var error;
|
|
8
|
+
|
|
9
|
+
beforeEach(function() {
|
|
10
|
+
emitter = new EventEmitter();
|
|
11
|
+
error = new Error('some error');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('calls back synchronously with the error', function() {
|
|
15
|
+
var calledWith = 'not called';
|
|
16
|
+
util.callbackOrEmit(emitter, error, function(err) {
|
|
17
|
+
calledWith = err;
|
|
18
|
+
});
|
|
19
|
+
expect(calledWith).to.equal(error);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('calls back synchronously when there is no error', function() {
|
|
23
|
+
var calledWith = 'not called';
|
|
24
|
+
util.callbackOrEmit(emitter, null, function(err) {
|
|
25
|
+
calledWith = err;
|
|
26
|
+
});
|
|
27
|
+
expect(calledWith).to.equal(null);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('does not emit when a callback is provided', function() {
|
|
31
|
+
var emitted = false;
|
|
32
|
+
emitter.on('error', function() {
|
|
33
|
+
emitted = true;
|
|
34
|
+
});
|
|
35
|
+
util.callbackOrEmit(emitter, error, function() {});
|
|
36
|
+
expect(emitted).to.equal(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('emits the error when there is no callback', function() {
|
|
40
|
+
var emittedWith = 'not emitted';
|
|
41
|
+
emitter.on('error', function(err) {
|
|
42
|
+
emittedWith = err;
|
|
43
|
+
});
|
|
44
|
+
util.callbackOrEmit(emitter, error);
|
|
45
|
+
expect(emittedWith).to.equal(error);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('does nothing when there is no error and no callback', function() {
|
|
49
|
+
var emitted = false;
|
|
50
|
+
emitter.on('error', function() {
|
|
51
|
+
emitted = true;
|
|
52
|
+
});
|
|
53
|
+
util.callbackOrEmit(emitter, null);
|
|
54
|
+
expect(emitted).to.equal(false);
|
|
55
|
+
});
|
|
56
|
+
});
|