sharedb 2.2.3 → 3.0.0

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/backend.js CHANGED
@@ -772,6 +772,16 @@ Backend.prototype._fetchSnapshot = function(collection, id, version, callback) {
772
772
  var db = this.db;
773
773
  var backend = this;
774
774
 
775
+ var shouldGetLatestSnapshot = version === null;
776
+ if (shouldGetLatestSnapshot) {
777
+ return backend.db.getSnapshot(collection, id, null, null, function(error, snapshot) {
778
+ if (error) return callback(error);
779
+
780
+ callback(null, snapshot);
781
+ });
782
+ }
783
+
784
+
775
785
  this.milestoneDb.getMilestoneSnapshot(collection, id, version, function(error, milestoneSnapshot) {
776
786
  if (error) return callback(error);
777
787
 
@@ -833,6 +843,15 @@ Backend.prototype._fetchSnapshotByTimestamp = function(collection, id, timestamp
833
843
  var from = 0;
834
844
  var to = null;
835
845
 
846
+ var shouldGetLatestSnapshot = timestamp === null;
847
+ if (shouldGetLatestSnapshot) {
848
+ return backend.db.getSnapshot(collection, id, null, null, function(error, snapshot) {
849
+ if (error) return callback(error);
850
+
851
+ callback(null, snapshot);
852
+ });
853
+ }
854
+
836
855
  milestoneDb.getMilestoneSnapshotAtOrBeforeTime(collection, id, timestamp, function(error, snapshot) {
837
856
  if (error) return callback(error);
838
857
  milestoneSnapshot = snapshot;
@@ -883,10 +902,6 @@ function pluckIds(snapshots) {
883
902
  }
884
903
 
885
904
  function filterOpsInPlaceBeforeTimestamp(ops, timestamp) {
886
- if (timestamp === null) {
887
- return;
888
- }
889
-
890
905
  for (var i = 0; i < ops.length; i++) {
891
906
  var op = ops[i];
892
907
  var opTimestamp = op.m && op.m.ts;
package/lib/client/doc.js CHANGED
@@ -624,6 +624,8 @@ Doc.prototype._otApply = function(op, source) {
624
624
  for (var i = 0; i < op.op.length; i++) {
625
625
  var component = op.op[i];
626
626
  var componentOp = {op: [component]};
627
+ // Apply the individual op component
628
+ this.emit('before op', componentOp.op, source, op.src);
627
629
  // Transform componentOp against any ops that have been submitted
628
630
  // sychronously inside of an op event handler since we began apply of
629
631
  // our operation
@@ -631,8 +633,6 @@ Doc.prototype._otApply = function(op, source) {
631
633
  var transformErr = transformX(this.applyStack[j], componentOp);
632
634
  if (transformErr) return this._hardRollback(transformErr);
633
635
  }
634
- // Apply the individual op component
635
- this.emit('before op', componentOp.op, source, op.src);
636
636
  this._setData(this.type.apply(this.data, componentOp.op));
637
637
  this.emit('op', componentOp.op, source, op.src);
638
638
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sharedb",
3
- "version": "2.2.3",
3
+ "version": "3.0.0",
4
4
  "description": "JSON OT database backend",
5
5
  "main": "lib/index.js",
6
6
  "dependencies": {
@@ -214,6 +214,36 @@ describe('Doc', function() {
214
214
  });
215
215
  });
216
216
 
217
+ it('remote ops are transformed by ops submitted in `before op` event handlers', function(done) {
218
+ var doc = this.doc;
219
+ var doc2 = this.doc2;
220
+ var doc3 = this.doc3;
221
+ function beforeOpHandler(op, source) {
222
+ if (source) {
223
+ return;
224
+ }
225
+ doc.off('before op', beforeOpHandler);
226
+ doc.submitOp({p: ['list', 0], li: 2}, {source: true});
227
+ }
228
+ function opHandler(op, source) {
229
+ if (source) {
230
+ return;
231
+ }
232
+ doc.off('op', opHandler);
233
+ doc.submitOp({p: ['list', 0], li: 3}, {source: true});
234
+ }
235
+ doc2.submitOp({p: ['list'], oi: []}, function() {
236
+ doc.fetch(function() {
237
+ doc.on('before op', beforeOpHandler);
238
+ doc.on('op', opHandler);
239
+ doc2.submitOp([{p: ['list', 0], li: 1}, {p: ['list', 1], li: 42}], function() {
240
+ doc.fetch();
241
+ verifyConsistency(doc, doc2, doc3, [], done);
242
+ });
243
+ });
244
+ });
245
+ });
246
+
217
247
  it('remote multi component ops are transformed by ops submitted in `op` event handlers', function(done) {
218
248
  var doc = this.doc;
219
249
  var doc2 = this.doc2;
@@ -490,6 +490,54 @@ describe('SnapshotTimestampRequest', function() {
490
490
  done();
491
491
  });
492
492
  });
493
+
494
+ describe('when timestamp is null', function() {
495
+ it('fetches latest snapshot', function(done) {
496
+ sinon.spy(milestoneDb, 'getMilestoneSnapshotAtOrBeforeTime');
497
+ sinon.spy(milestoneDb, 'getMilestoneSnapshotAtOrAfterTime');
498
+ sinon.spy(db, 'getOps');
499
+
500
+ backendWithMilestones.connect()
501
+ .fetchSnapshotByTimestamp('books', 'mocking-bird', null, function(error, snapshot) {
502
+ if (error) return done(error);
503
+
504
+ expect(milestoneDb.getMilestoneSnapshotAtOrBeforeTime.called).to.be.false;
505
+ expect(milestoneDb.getMilestoneSnapshotAtOrAfterTime.called).to.be.false;
506
+ expect(db.getOps.called).to.be.false;
507
+
508
+ expect(snapshot.v).to.equal(5);
509
+ expect(snapshot.data).to.eql({
510
+ title: 'To Kill a Mocking Bird',
511
+ author: 'Harper Lee',
512
+ year: 1960
513
+ });
514
+
515
+ done();
516
+ });
517
+ });
518
+
519
+ it('returns error if getSnapshot fails', function(done) {
520
+ sinon.spy(milestoneDb, 'getMilestoneSnapshotAtOrBeforeTime');
521
+ sinon.spy(milestoneDb, 'getMilestoneSnapshotAtOrAfterTime');
522
+ sinon.spy(db, 'getOps');
523
+
524
+ sinon.stub(db, 'getSnapshot').callsFake(function(_collection, _id, _fields, _options, callback) {
525
+ callback(new Error('TEST_ERROR'));
526
+ });
527
+
528
+ backendWithMilestones.connect()
529
+ .fetchSnapshotByTimestamp('books', 'mocking-bird', null, function(error) {
530
+ expect(error.message).to.be.equal('TEST_ERROR');
531
+
532
+ expect(milestoneDb.getMilestoneSnapshotAtOrBeforeTime.called).to.be.false;
533
+ expect(milestoneDb.getMilestoneSnapshotAtOrAfterTime.called).to.be.false;
534
+ expect(db.getOps.called).to.be.false;
535
+ expect(db.getSnapshot.called).to.be.true;
536
+
537
+ done();
538
+ });
539
+ });
540
+ });
493
541
  });
494
542
  });
495
543
  });
@@ -439,6 +439,60 @@ describe('SnapshotVersionRequest', function() {
439
439
  }
440
440
  ], done);
441
441
  });
442
+
443
+ describe('with version null', function() {
444
+ it('fetches a latest version', function(done) {
445
+ var doc = backendWithMilestones.connect().get('books', 'mocking-bird');
446
+
447
+ async.waterfall([
448
+ doc.create.bind(doc, {title: 'To Kill a Mocking Bird'}),
449
+ doc.submitOp.bind(doc, {p: ['author'], oi: 'Harper Lea'}),
450
+ doc.submitOp.bind(doc, {p: ['author'], od: 'Harper Lea', oi: 'Harper Lee'}),
451
+ function(next) {
452
+ sinon.spy(milestoneDb, 'getMilestoneSnapshot');
453
+ sinon.spy(db, 'getOps');
454
+ backendWithMilestones.connect().fetchSnapshot('books', 'mocking-bird', null, next);
455
+ },
456
+ function(snapshot, next) {
457
+ expect(milestoneDb.getMilestoneSnapshot.called).to.be.false;
458
+ expect(db.getOps.called).to.be.false;
459
+ expect(snapshot.v).to.equal(3);
460
+ expect(snapshot.data).to.eql({
461
+ title: 'To Kill a Mocking Bird',
462
+ author: 'Harper Lee'
463
+ });
464
+ next();
465
+ }
466
+ ], done);
467
+ });
468
+
469
+ it('return error if getSnapshot fails', function(done) {
470
+ var doc = backendWithMilestones.connect().get('books', 'mocking-bird');
471
+
472
+ async.waterfall([
473
+ doc.create.bind(doc, {title: 'To Kill a Mocking Bird'}),
474
+ doc.submitOp.bind(doc, {p: ['author'], oi: 'Harper Lea'}),
475
+ doc.submitOp.bind(doc, {p: ['author'], od: 'Harper Lea', oi: 'Harper Lee'}),
476
+ function(next) {
477
+ sinon.spy(milestoneDb, 'getMilestoneSnapshot');
478
+ sinon.spy(db, 'getOps');
479
+ sinon.stub(db, 'getSnapshot').callsFake(function(_collection, _id, _fields, _options, callback) {
480
+ callback(new Error('TEST_ERROR'));
481
+ });
482
+ backendWithMilestones.connect().fetchSnapshot('books', 'mocking-bird', null, function(error) {
483
+ expect(error.message).to.be.equal('TEST_ERROR');
484
+ next(null, null);
485
+ });
486
+ },
487
+ function(snapshot, next) {
488
+ expect(milestoneDb.getMilestoneSnapshot.called).to.be.false;
489
+ expect(db.getOps.called).to.be.false;
490
+ expect(db.getSnapshot.called).to.be.true;
491
+ next();
492
+ }
493
+ ], done);
494
+ });
495
+ });
442
496
  });
443
497
 
444
498
  describe('invalid json0v2 path', function() {
package/test/ot.js CHANGED
@@ -373,118 +373,250 @@ describe('ot', function() {
373
373
  });
374
374
 
375
375
  describe('applyOps', function() {
376
- it('applies an op to a snapshot', function() {
377
- var snapshot = {
378
- type: 'json0',
379
- data: {title: 'Wee Free Men'}
380
- };
376
+ describe('with normalization turned on', function() {
377
+ it('applies an op to a snapshot', function() {
378
+ var snapshot = {
379
+ type: 'http://sharejs.org/types/JSONv0',
380
+ data: {title: 'Wee Free Men'}
381
+ };
382
+
383
+ var ops = [
384
+ {
385
+ v: 1,
386
+ op: [{p: ['title', 0], si: 'The '}]
387
+ }
388
+ ];
389
+
390
+ var error = ot.applyOps(snapshot, ops, {
391
+ _normalizeLegacyJson0Ops: true
392
+ });
393
+ expect(error).to.be.undefined;
394
+ expect(snapshot.data).to.eql({title: 'The Wee Free Men'});
395
+ expect(snapshot.v).to.equal(2);
396
+ });
381
397
 
382
- var ops = [
383
- {
384
- v: 1,
385
- op: [{p: ['title', 0], si: 'The '}]
386
- }
387
- ];
398
+ it('applies multiple ops', function() {
399
+ var snapshot = {
400
+ type: 'http://sharejs.org/types/JSONv0',
401
+ data: {title: 'Wee Free Men'}
402
+ };
403
+
404
+ var ops = [
405
+ {
406
+ v: 1,
407
+ op: [{p: ['title', 0], si: 'The '}]
408
+ },
409
+ {
410
+ v: 2,
411
+ op: [{p: ['author'], oi: 'Terry Pratchett'}]
412
+ }
413
+ ];
414
+
415
+ ot.applyOps(snapshot, ops, {
416
+ _normalizeLegacyJson0Ops: true
417
+ });
418
+ expect(snapshot.data).to.eql({
419
+ author: 'Terry Pratchett',
420
+ title: 'The Wee Free Men'
421
+ });
422
+ expect(snapshot.v).to.equal(3);
423
+ });
388
424
 
389
- var error = ot.applyOps(snapshot, ops);
390
- expect(error).to.be.undefined;
391
- expect(snapshot.data).to.eql({title: 'The Wee Free Men'});
392
- expect(snapshot.v).to.equal(2);
393
- });
425
+ it('applies a del to a snapshot', function() {
426
+ var snapshot = {
427
+ type: 'http://sharejs.org/types/JSONv0',
428
+ data: {title: 'Wee Free Men'}
429
+ };
394
430
 
395
- it('applies multiple ops', function() {
396
- var snapshot = {
397
- type: 'json0',
398
- data: {title: 'Wee Free Men'}
399
- };
431
+ var ops = [{v: 1, del: true}];
400
432
 
401
- var ops = [
402
- {
403
- v: 1,
404
- op: [{p: ['title', 0], si: 'The '}]
405
- },
406
- {
407
- v: 2,
408
- op: [{p: ['author'], oi: 'Terry Pratchett'}]
409
- }
410
- ];
411
-
412
- ot.applyOps(snapshot, ops);
413
- expect(snapshot.data).to.eql({
414
- author: 'Terry Pratchett',
415
- title: 'The Wee Free Men'
433
+ ot.applyOps(snapshot, ops, {
434
+ _normalizeLegacyJson0Ops: true
435
+ });
436
+ expect(snapshot.data).to.be.undefined;
416
437
  });
417
- expect(snapshot.v).to.equal(3);
418
- });
419
438
 
420
- it('applies a del to a snapshot', function() {
421
- var snapshot = {
422
- type: 'json0',
423
- data: {title: 'Wee Free Men'}
424
- };
439
+ it('applies a create to a snapshot', function() {
440
+ var snapshot = {};
441
+ var ops = [
442
+ {
443
+ v: 1,
444
+ create: {
445
+ type: 'http://sharejs.org/types/JSONv0',
446
+ data: {title: 'Wee Free Men'}
447
+ }
448
+ }
449
+ ];
425
450
 
426
- var ops = [{v: 1, del: true}];
451
+ ot.applyOps(snapshot, ops, {
452
+ _normalizeLegacyJson0Ops: true
453
+ });
454
+ expect(snapshot.data).to.eql({title: 'Wee Free Men'});
455
+ });
427
456
 
428
- ot.applyOps(snapshot, ops);
429
- expect(snapshot.data).to.be.undefined;
430
- });
457
+ it('returns an error if the snapshot has an unknown type', function() {
458
+ var snapshot = {type: 'unknown-type', data: {}};
459
+ var ops = [
460
+ {
461
+ v: 1,
462
+ op: [{p: ['title'], oi: 'Title'}]
463
+ }
464
+ ];
465
+ var error = ot.applyOps(snapshot, ops, {
466
+ _normalizeLegacyJson0Ops: true
467
+ });
468
+ expect(error.code).to.equal(ERROR_CODE.ERR_DOC_TYPE_NOT_RECOGNIZED);
469
+ });
431
470
 
432
- it('applies a create to a snapshot', function() {
433
- var snapshot = {};
434
- var ops = [
435
- {
436
- v: 1,
437
- create: {
438
- type: 'json0',
439
- data: {title: 'Wee Free Men'}
471
+ it('returns an error if a create op has an unknown type', function() {
472
+ var snapshot = {};
473
+ var ops = [
474
+ {
475
+ v: 1,
476
+ create: {
477
+ type: 'unknown-type',
478
+ data: {}
479
+ }
440
480
  }
441
- }
442
- ];
481
+ ];
482
+ var error = ot.applyOps(snapshot, ops, {
483
+ _normalizeLegacyJson0Ops: true
484
+ });
485
+ expect(error.code).to.equal(ERROR_CODE.ERR_DOC_TYPE_NOT_RECOGNIZED);
486
+ });
443
487
 
444
- ot.applyOps(snapshot, ops);
445
- expect(snapshot.data).to.eql({title: 'Wee Free Men'});
446
- });
488
+ it('catches and returns an error thrown by type.apply', function() {
489
+ var snapshot = {
490
+ type: 'http://sharejs.org/types/JSONv0',
491
+ data: {title: 'Wee Free Men'}
492
+ };
447
493
 
448
- it('returns an error if the snapshot has an unknown type', function() {
449
- var snapshot = {type: 'unknown-type', data: {}};
450
- var ops = [
451
- {
494
+ var ops = [{
452
495
  v: 1,
453
- op: [{p: ['title'], oi: 'Title'}]
454
- }
455
- ];
456
- var error = ot.applyOps(snapshot, ops);
457
- expect(error.code).to.equal(ERROR_CODE.ERR_DOC_TYPE_NOT_RECOGNIZED);
496
+ op: [{p: ['title'], li: 'not a list'}]
497
+ }];
498
+
499
+ var error = ot.applyOps(snapshot, ops, {
500
+ _normalizeLegacyJson0Ops: true
501
+ });
502
+ expect(error.code).to.equal(ERROR_CODE.ERR_OT_OP_NOT_APPLIED);
503
+ });
458
504
  });
459
505
 
460
- it('returns an error if a create op has an unknown type', function() {
461
- var snapshot = {};
462
- var ops = [
463
- {
464
- v: 1,
465
- create: {
466
- type: 'unknown-type',
467
- data: {}
506
+ describe('with normalization turned off', function() {
507
+ it('applies an op to a snapshot', function() {
508
+ var snapshot = {
509
+ type: 'json0',
510
+ data: {title: 'Wee Free Men'}
511
+ };
512
+
513
+ var ops = [
514
+ {
515
+ v: 1,
516
+ op: [{p: ['title', 0], si: 'The '}]
468
517
  }
469
- }
470
- ];
471
- var error = ot.applyOps(snapshot, ops);
472
- expect(error.code).to.equal(ERROR_CODE.ERR_DOC_TYPE_NOT_RECOGNIZED);
473
- });
518
+ ];
474
519
 
475
- it('catches and returns an error thrown by type.apply', function() {
476
- var snapshot = {
477
- type: 'json0',
478
- data: {title: 'Wee Free Men'}
479
- };
520
+ var error = ot.applyOps(snapshot, ops);
521
+ expect(error).to.be.undefined;
522
+ expect(snapshot.data).to.eql({title: 'The Wee Free Men'});
523
+ expect(snapshot.v).to.equal(2);
524
+ });
525
+
526
+ it('applies multiple ops', function() {
527
+ var snapshot = {
528
+ type: 'json0',
529
+ data: {title: 'Wee Free Men'}
530
+ };
531
+
532
+ var ops = [
533
+ {
534
+ v: 1,
535
+ op: [{p: ['title', 0], si: 'The '}]
536
+ },
537
+ {
538
+ v: 2,
539
+ op: [{p: ['author'], oi: 'Terry Pratchett'}]
540
+ }
541
+ ];
542
+
543
+ ot.applyOps(snapshot, ops);
544
+ expect(snapshot.data).to.eql({
545
+ author: 'Terry Pratchett',
546
+ title: 'The Wee Free Men'
547
+ });
548
+ expect(snapshot.v).to.equal(3);
549
+ });
480
550
 
481
- var ops = [{
482
- v: 1,
483
- op: [{p: ['title'], li: 'not a list'}]
484
- }];
551
+ it('applies a del to a snapshot', function() {
552
+ var snapshot = {
553
+ type: 'json0',
554
+ data: {title: 'Wee Free Men'}
555
+ };
485
556
 
486
- var error = ot.applyOps(snapshot, ops);
487
- expect(error.code).to.equal(ERROR_CODE.ERR_OT_OP_NOT_APPLIED);
557
+ var ops = [{v: 1, del: true}];
558
+
559
+ ot.applyOps(snapshot, ops);
560
+ expect(snapshot.data).to.be.undefined;
561
+ });
562
+
563
+ it('applies a create to a snapshot', function() {
564
+ var snapshot = {};
565
+ var ops = [
566
+ {
567
+ v: 1,
568
+ create: {
569
+ type: 'json0',
570
+ data: {title: 'Wee Free Men'}
571
+ }
572
+ }
573
+ ];
574
+
575
+ ot.applyOps(snapshot, ops);
576
+ expect(snapshot.data).to.eql({title: 'Wee Free Men'});
577
+ });
578
+
579
+ it('returns an error if the snapshot has an unknown type', function() {
580
+ var snapshot = {type: 'unknown-type', data: {}};
581
+ var ops = [
582
+ {
583
+ v: 1,
584
+ op: [{p: ['title'], oi: 'Title'}]
585
+ }
586
+ ];
587
+ var error = ot.applyOps(snapshot, ops);
588
+ expect(error.code).to.equal(ERROR_CODE.ERR_DOC_TYPE_NOT_RECOGNIZED);
589
+ });
590
+
591
+ it('returns an error if a create op has an unknown type', function() {
592
+ var snapshot = {};
593
+ var ops = [
594
+ {
595
+ v: 1,
596
+ create: {
597
+ type: 'unknown-type',
598
+ data: {}
599
+ }
600
+ }
601
+ ];
602
+ var error = ot.applyOps(snapshot, ops);
603
+ expect(error.code).to.equal(ERROR_CODE.ERR_DOC_TYPE_NOT_RECOGNIZED);
604
+ });
605
+
606
+ it('catches and returns an error thrown by type.apply', function() {
607
+ var snapshot = {
608
+ type: 'json0',
609
+ data: {title: 'Wee Free Men'}
610
+ };
611
+
612
+ var ops = [{
613
+ v: 1,
614
+ op: [{p: ['title'], li: 'not a list'}]
615
+ }];
616
+
617
+ var error = ot.applyOps(snapshot, ops);
618
+ expect(error.code).to.equal(ERROR_CODE.ERR_OT_OP_NOT_APPLIED);
619
+ });
488
620
  });
489
621
  });
490
622
  });