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 CHANGED
@@ -1,8 +1,8 @@
1
1
  # ShareDB
2
2
 
3
3
  [![NPM Version](https://img.shields.io/npm/v/sharedb.svg)](https://npmjs.org/package/sharedb)
4
- ![Test](https://github.com/share/sharedb/workflows/Test/badge.svg)
5
- [![Coverage Status](https://coveralls.io/repos/github/share/sharedb/badge.svg?branch=master)](https://coveralls.io/github/share/sharedb?branch=master)
4
+ ![Test](https://github.com/share/sharedb/actions/workflows/test.yml/badge.svg?branch=main)
5
+ [![Coverage Status](https://coveralls.io/repos/github/share/sharedb/badge.svg?branch=main)](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
- if (callback) return callback(err);
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
- if (callback) return callback(err);
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
- if (callback) return callback(err);
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
- if (callback) return callback(err);
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
- if (callback) return callback(err);
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;
@@ -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
- if (callback) return callback(err);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sharedb",
3
- "version": "6.0.3",
3
+ "version": "6.0.4",
4
4
  "description": "JSON OT database backend",
5
5
  "main": "lib/index.js",
6
6
  "dependencies": {
@@ -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;
@@ -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
+ });