sharedb 2.2.3 → 2.2.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/lib/backend.js +19 -4
- package/package.json +1 -1
- package/test/client/snapshot-timestamp-request.js +48 -0
- package/test/client/snapshot-version-request.js +54 -0
- package/test/ot.js +225 -93
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/package.json
CHANGED
|
@@ -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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
383
|
-
{
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
396
|
-
var snapshot = {
|
|
397
|
-
type: 'json0',
|
|
398
|
-
data: {title: 'Wee Free Men'}
|
|
399
|
-
};
|
|
431
|
+
var ops = [{v: 1, del: true}];
|
|
400
432
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
-
|
|
451
|
+
ot.applyOps(snapshot, ops, {
|
|
452
|
+
_normalizeLegacyJson0Ops: true
|
|
453
|
+
});
|
|
454
|
+
expect(snapshot.data).to.eql({title: 'Wee Free Men'});
|
|
455
|
+
});
|
|
427
456
|
|
|
428
|
-
|
|
429
|
-
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|
-
|
|
445
|
-
|
|
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
|
-
|
|
449
|
-
var snapshot = {type: 'unknown-type', data: {}};
|
|
450
|
-
var ops = [
|
|
451
|
-
{
|
|
494
|
+
var ops = [{
|
|
452
495
|
v: 1,
|
|
453
|
-
op: [{p: ['title'],
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
-
|
|
487
|
-
|
|
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
|
});
|