dexie-cloud-addon 4.0.8 → 4.1.0-alpha.10
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/dist/modern/DexieCloudOptions.d.ts +1 -0
- package/dist/modern/WSObservable.d.ts +9 -6
- package/dist/modern/db/DexieCloudDB.d.ts +2 -0
- package/dist/modern/db/entities/PersistedSyncState.d.ts +7 -0
- package/dist/modern/dexie-cloud-addon.js +1731 -62
- package/dist/modern/dexie-cloud-addon.js.map +1 -1
- package/dist/modern/dexie-cloud-addon.min.js +1 -1
- package/dist/modern/dexie-cloud-addon.min.js.map +1 -1
- package/dist/modern/service-worker.js +1731 -62
- package/dist/modern/service-worker.js.map +1 -1
- package/dist/modern/service-worker.min.js +1 -1
- package/dist/modern/service-worker.min.js.map +1 -1
- package/dist/modern/sync/DEXIE_CLOUD_SYNCER_ID.d.ts +1 -0
- package/dist/modern/sync/syncWithServer.d.ts +2 -2
- package/dist/modern/yjs/Y.d.ts +3 -0
- package/dist/modern/yjs/YDexieCloudSyncState.d.ts +4 -0
- package/dist/modern/yjs/YTable.d.ts +2 -0
- package/dist/modern/yjs/applyYMessages.d.ts +5 -0
- package/dist/modern/yjs/awareness.d.ts +4 -0
- package/dist/modern/yjs/createYClientUpdateObservable.d.ts +4 -0
- package/dist/modern/yjs/createYHandler.d.ts +5 -0
- package/dist/modern/yjs/downloadYDocsFromServer.d.ts +3 -0
- package/dist/modern/yjs/getUpdatesTable.d.ts +3 -0
- package/dist/modern/yjs/listUpdatesSince.d.ts +2 -0
- package/dist/modern/yjs/listYClientMessagesAndStateVector.d.ts +26 -0
- package/dist/modern/yjs/updateYSyncStates.d.ts +6 -0
- package/dist/umd/DexieCloudOptions.d.ts +1 -0
- package/dist/umd/WSObservable.d.ts +9 -6
- package/dist/umd/db/DexieCloudDB.d.ts +2 -0
- package/dist/umd/db/entities/PersistedSyncState.d.ts +7 -0
- package/dist/umd/dexie-cloud-addon.js +1729 -60
- package/dist/umd/dexie-cloud-addon.js.map +1 -1
- package/dist/umd/dexie-cloud-addon.min.js +1 -1
- package/dist/umd/dexie-cloud-addon.min.js.map +1 -1
- package/dist/umd/service-worker.js +1729 -60
- package/dist/umd/service-worker.js.map +1 -1
- package/dist/umd/service-worker.min.js +1 -1
- package/dist/umd/service-worker.min.js.map +1 -1
- package/dist/umd/sync/DEXIE_CLOUD_SYNCER_ID.d.ts +1 -0
- package/dist/umd/sync/syncWithServer.d.ts +2 -2
- package/dist/umd/yjs/Y.d.ts +3 -0
- package/dist/umd/yjs/YDexieCloudSyncState.d.ts +4 -0
- package/dist/umd/yjs/YTable.d.ts +2 -0
- package/dist/umd/yjs/applyYMessages.d.ts +5 -0
- package/dist/umd/yjs/awareness.d.ts +4 -0
- package/dist/umd/yjs/createYClientUpdateObservable.d.ts +4 -0
- package/dist/umd/yjs/createYHandler.d.ts +5 -0
- package/dist/umd/yjs/downloadYDocsFromServer.d.ts +3 -0
- package/dist/umd/yjs/getUpdatesTable.d.ts +3 -0
- package/dist/umd/yjs/listUpdatesSince.d.ts +2 -0
- package/dist/umd/yjs/listYClientMessagesAndStateVector.d.ts +26 -0
- package/dist/umd/yjs/updateYSyncStates.d.ts +6 -0
- package/package.json +5 -4
- package/dist/modern/helpers/dbOnClosed.d.ts +0 -2
- package/dist/umd/helpers/dbOnClosed.d.ts +0 -2
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* ==========================================================================
|
|
10
10
|
*
|
|
11
|
-
* Version 4.0.
|
|
11
|
+
* Version 4.1.0-alpha.10, Wed Oct 16 2024
|
|
12
12
|
*
|
|
13
13
|
* https://dexie.org
|
|
14
14
|
*
|
|
@@ -470,6 +470,1075 @@
|
|
|
470
470
|
: url.pathname.split('/')[1];
|
|
471
471
|
}
|
|
472
472
|
|
|
473
|
+
/**
|
|
474
|
+
* Common Math expressions.
|
|
475
|
+
*
|
|
476
|
+
* @module math
|
|
477
|
+
*/
|
|
478
|
+
|
|
479
|
+
const floor = Math.floor;
|
|
480
|
+
const abs = Math.abs;
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* @function
|
|
484
|
+
* @param {number} a
|
|
485
|
+
* @param {number} b
|
|
486
|
+
* @return {number} The smaller element of a and b
|
|
487
|
+
*/
|
|
488
|
+
const min = (a, b) => a < b ? a : b;
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* @function
|
|
492
|
+
* @param {number} a
|
|
493
|
+
* @param {number} b
|
|
494
|
+
* @return {number} The bigger element of a and b
|
|
495
|
+
*/
|
|
496
|
+
const max = (a, b) => a > b ? a : b;
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* @param {number} n
|
|
500
|
+
* @return {boolean} Wether n is negative. This function also differentiates between -0 and +0
|
|
501
|
+
*/
|
|
502
|
+
const isNegativeZero = n => n !== 0 ? n < 0 : 1 / n < 0;
|
|
503
|
+
|
|
504
|
+
/* eslint-env browser */
|
|
505
|
+
|
|
506
|
+
const BIT7 = 64;
|
|
507
|
+
const BIT8 = 128;
|
|
508
|
+
const BITS6 = 63;
|
|
509
|
+
const BITS7 = 127;
|
|
510
|
+
/**
|
|
511
|
+
* @type {number}
|
|
512
|
+
*/
|
|
513
|
+
const BITS31 = 0x7FFFFFFF;
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Utility helpers for working with numbers.
|
|
517
|
+
*
|
|
518
|
+
* @module number
|
|
519
|
+
*/
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
|
|
523
|
+
|
|
524
|
+
/* c8 ignore next */
|
|
525
|
+
const isInteger = Number.isInteger || (num => typeof num === 'number' && isFinite(num) && floor(num) === num);
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Utility module to work with Arrays.
|
|
529
|
+
*
|
|
530
|
+
* @module array
|
|
531
|
+
*/
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
const isArray = Array.isArray;
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* @param {string} str
|
|
538
|
+
* @return {Uint8Array}
|
|
539
|
+
*/
|
|
540
|
+
const _encodeUtf8Polyfill = str => {
|
|
541
|
+
const encodedString = unescape(encodeURIComponent(str));
|
|
542
|
+
const len = encodedString.length;
|
|
543
|
+
const buf = new Uint8Array(len);
|
|
544
|
+
for (let i = 0; i < len; i++) {
|
|
545
|
+
buf[i] = /** @type {number} */ (encodedString.codePointAt(i));
|
|
546
|
+
}
|
|
547
|
+
return buf
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
/* c8 ignore next */
|
|
551
|
+
const utf8TextEncoder = /** @type {TextEncoder} */ (typeof TextEncoder !== 'undefined' ? new TextEncoder() : null);
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* @param {string} str
|
|
555
|
+
* @return {Uint8Array}
|
|
556
|
+
*/
|
|
557
|
+
const _encodeUtf8Native = str => utf8TextEncoder.encode(str);
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* @param {string} str
|
|
561
|
+
* @return {Uint8Array}
|
|
562
|
+
*/
|
|
563
|
+
/* c8 ignore next */
|
|
564
|
+
const encodeUtf8 = utf8TextEncoder ? _encodeUtf8Native : _encodeUtf8Polyfill;
|
|
565
|
+
|
|
566
|
+
/* c8 ignore next */
|
|
567
|
+
let utf8TextDecoder = typeof TextDecoder === 'undefined' ? null : new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
|
|
568
|
+
|
|
569
|
+
/* c8 ignore start */
|
|
570
|
+
if (utf8TextDecoder && utf8TextDecoder.decode(new Uint8Array()).length === 1) {
|
|
571
|
+
// Safari doesn't handle BOM correctly.
|
|
572
|
+
// This fixes a bug in Safari 13.0.5 where it produces a BOM the first time it is called.
|
|
573
|
+
// utf8TextDecoder.decode(new Uint8Array()).length === 1 on the first call and
|
|
574
|
+
// utf8TextDecoder.decode(new Uint8Array()).length === 1 on the second call
|
|
575
|
+
// Another issue is that from then on no BOM chars are recognized anymore
|
|
576
|
+
/* c8 ignore next */
|
|
577
|
+
utf8TextDecoder = null;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Efficient schema-less binary encoding with support for variable length encoding.
|
|
582
|
+
*
|
|
583
|
+
* Use [lib0/encoding] with [lib0/decoding]. Every encoding function has a corresponding decoding function.
|
|
584
|
+
*
|
|
585
|
+
* Encodes numbers in little-endian order (least to most significant byte order)
|
|
586
|
+
* and is compatible with Golang's binary encoding (https://golang.org/pkg/encoding/binary/)
|
|
587
|
+
* which is also used in Protocol Buffers.
|
|
588
|
+
*
|
|
589
|
+
* ```js
|
|
590
|
+
* // encoding step
|
|
591
|
+
* const encoder = encoding.createEncoder()
|
|
592
|
+
* encoding.writeVarUint(encoder, 256)
|
|
593
|
+
* encoding.writeVarString(encoder, 'Hello world!')
|
|
594
|
+
* const buf = encoding.toUint8Array(encoder)
|
|
595
|
+
* ```
|
|
596
|
+
*
|
|
597
|
+
* ```js
|
|
598
|
+
* // decoding step
|
|
599
|
+
* const decoder = decoding.createDecoder(buf)
|
|
600
|
+
* decoding.readVarUint(decoder) // => 256
|
|
601
|
+
* decoding.readVarString(decoder) // => 'Hello world!'
|
|
602
|
+
* decoding.hasContent(decoder) // => false - all data is read
|
|
603
|
+
* ```
|
|
604
|
+
*
|
|
605
|
+
* @module encoding
|
|
606
|
+
*/
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* A BinaryEncoder handles the encoding to an Uint8Array.
|
|
611
|
+
*/
|
|
612
|
+
class Encoder {
|
|
613
|
+
constructor () {
|
|
614
|
+
this.cpos = 0;
|
|
615
|
+
this.cbuf = new Uint8Array(100);
|
|
616
|
+
/**
|
|
617
|
+
* @type {Array<Uint8Array>}
|
|
618
|
+
*/
|
|
619
|
+
this.bufs = [];
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* The current length of the encoded data.
|
|
625
|
+
*
|
|
626
|
+
* @function
|
|
627
|
+
* @param {Encoder} encoder
|
|
628
|
+
* @return {number}
|
|
629
|
+
*/
|
|
630
|
+
const length = encoder => {
|
|
631
|
+
let len = encoder.cpos;
|
|
632
|
+
for (let i = 0; i < encoder.bufs.length; i++) {
|
|
633
|
+
len += encoder.bufs[i].length;
|
|
634
|
+
}
|
|
635
|
+
return len
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Transform to Uint8Array.
|
|
640
|
+
*
|
|
641
|
+
* @function
|
|
642
|
+
* @param {Encoder} encoder
|
|
643
|
+
* @return {Uint8Array} The created ArrayBuffer.
|
|
644
|
+
*/
|
|
645
|
+
const toUint8Array = encoder => {
|
|
646
|
+
const uint8arr = new Uint8Array(length(encoder));
|
|
647
|
+
let curPos = 0;
|
|
648
|
+
for (let i = 0; i < encoder.bufs.length; i++) {
|
|
649
|
+
const d = encoder.bufs[i];
|
|
650
|
+
uint8arr.set(d, curPos);
|
|
651
|
+
curPos += d.length;
|
|
652
|
+
}
|
|
653
|
+
uint8arr.set(new Uint8Array(encoder.cbuf.buffer, 0, encoder.cpos), curPos);
|
|
654
|
+
return uint8arr
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Verify that it is possible to write `len` bytes wtihout checking. If
|
|
659
|
+
* necessary, a new Buffer with the required length is attached.
|
|
660
|
+
*
|
|
661
|
+
* @param {Encoder} encoder
|
|
662
|
+
* @param {number} len
|
|
663
|
+
*/
|
|
664
|
+
const verifyLen = (encoder, len) => {
|
|
665
|
+
const bufferLen = encoder.cbuf.length;
|
|
666
|
+
if (bufferLen - encoder.cpos < len) {
|
|
667
|
+
encoder.bufs.push(new Uint8Array(encoder.cbuf.buffer, 0, encoder.cpos));
|
|
668
|
+
encoder.cbuf = new Uint8Array(max(bufferLen, len) * 2);
|
|
669
|
+
encoder.cpos = 0;
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Write one byte to the encoder.
|
|
675
|
+
*
|
|
676
|
+
* @function
|
|
677
|
+
* @param {Encoder} encoder
|
|
678
|
+
* @param {number} num The byte that is to be encoded.
|
|
679
|
+
*/
|
|
680
|
+
const write = (encoder, num) => {
|
|
681
|
+
const bufferLen = encoder.cbuf.length;
|
|
682
|
+
if (encoder.cpos === bufferLen) {
|
|
683
|
+
encoder.bufs.push(encoder.cbuf);
|
|
684
|
+
encoder.cbuf = new Uint8Array(bufferLen * 2);
|
|
685
|
+
encoder.cpos = 0;
|
|
686
|
+
}
|
|
687
|
+
encoder.cbuf[encoder.cpos++] = num;
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Write a variable length unsigned integer. Max encodable integer is 2^53.
|
|
692
|
+
*
|
|
693
|
+
* @function
|
|
694
|
+
* @param {Encoder} encoder
|
|
695
|
+
* @param {number} num The number that is to be encoded.
|
|
696
|
+
*/
|
|
697
|
+
const writeVarUint = (encoder, num) => {
|
|
698
|
+
while (num > BITS7) {
|
|
699
|
+
write(encoder, BIT8 | (BITS7 & num));
|
|
700
|
+
num = floor(num / 128); // shift >>> 7
|
|
701
|
+
}
|
|
702
|
+
write(encoder, BITS7 & num);
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Write a variable length integer.
|
|
707
|
+
*
|
|
708
|
+
* We use the 7th bit instead for signaling that this is a negative number.
|
|
709
|
+
*
|
|
710
|
+
* @function
|
|
711
|
+
* @param {Encoder} encoder
|
|
712
|
+
* @param {number} num The number that is to be encoded.
|
|
713
|
+
*/
|
|
714
|
+
const writeVarInt = (encoder, num) => {
|
|
715
|
+
const isNegative = isNegativeZero(num);
|
|
716
|
+
if (isNegative) {
|
|
717
|
+
num = -num;
|
|
718
|
+
}
|
|
719
|
+
// |- whether to continue reading |- whether is negative |- number
|
|
720
|
+
write(encoder, (num > BITS6 ? BIT8 : 0) | (isNegative ? BIT7 : 0) | (BITS6 & num));
|
|
721
|
+
num = floor(num / 64); // shift >>> 6
|
|
722
|
+
// We don't need to consider the case of num === 0 so we can use a different
|
|
723
|
+
// pattern here than above.
|
|
724
|
+
while (num > 0) {
|
|
725
|
+
write(encoder, (num > BITS7 ? BIT8 : 0) | (BITS7 & num));
|
|
726
|
+
num = floor(num / 128); // shift >>> 7
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* A cache to store strings temporarily
|
|
732
|
+
*/
|
|
733
|
+
const _strBuffer = new Uint8Array(30000);
|
|
734
|
+
const _maxStrBSize = _strBuffer.length / 3;
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Write a variable length string.
|
|
738
|
+
*
|
|
739
|
+
* @function
|
|
740
|
+
* @param {Encoder} encoder
|
|
741
|
+
* @param {String} str The string that is to be encoded.
|
|
742
|
+
*/
|
|
743
|
+
const _writeVarStringNative = (encoder, str) => {
|
|
744
|
+
if (str.length < _maxStrBSize) {
|
|
745
|
+
// We can encode the string into the existing buffer
|
|
746
|
+
/* c8 ignore next */
|
|
747
|
+
const written = utf8TextEncoder.encodeInto(str, _strBuffer).written || 0;
|
|
748
|
+
writeVarUint(encoder, written);
|
|
749
|
+
for (let i = 0; i < written; i++) {
|
|
750
|
+
write(encoder, _strBuffer[i]);
|
|
751
|
+
}
|
|
752
|
+
} else {
|
|
753
|
+
writeVarUint8Array(encoder, encodeUtf8(str));
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Write a variable length string.
|
|
759
|
+
*
|
|
760
|
+
* @function
|
|
761
|
+
* @param {Encoder} encoder
|
|
762
|
+
* @param {String} str The string that is to be encoded.
|
|
763
|
+
*/
|
|
764
|
+
const _writeVarStringPolyfill = (encoder, str) => {
|
|
765
|
+
const encodedString = unescape(encodeURIComponent(str));
|
|
766
|
+
const len = encodedString.length;
|
|
767
|
+
writeVarUint(encoder, len);
|
|
768
|
+
for (let i = 0; i < len; i++) {
|
|
769
|
+
write(encoder, /** @type {number} */ (encodedString.codePointAt(i)));
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Write a variable length string.
|
|
775
|
+
*
|
|
776
|
+
* @function
|
|
777
|
+
* @param {Encoder} encoder
|
|
778
|
+
* @param {String} str The string that is to be encoded.
|
|
779
|
+
*/
|
|
780
|
+
/* c8 ignore next */
|
|
781
|
+
const writeVarString = (utf8TextEncoder && /** @type {any} */ (utf8TextEncoder).encodeInto) ? _writeVarStringNative : _writeVarStringPolyfill;
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Append fixed-length Uint8Array to the encoder.
|
|
785
|
+
*
|
|
786
|
+
* @function
|
|
787
|
+
* @param {Encoder} encoder
|
|
788
|
+
* @param {Uint8Array} uint8Array
|
|
789
|
+
*/
|
|
790
|
+
const writeUint8Array = (encoder, uint8Array) => {
|
|
791
|
+
const bufferLen = encoder.cbuf.length;
|
|
792
|
+
const cpos = encoder.cpos;
|
|
793
|
+
const leftCopyLen = min(bufferLen - cpos, uint8Array.length);
|
|
794
|
+
const rightCopyLen = uint8Array.length - leftCopyLen;
|
|
795
|
+
encoder.cbuf.set(uint8Array.subarray(0, leftCopyLen), cpos);
|
|
796
|
+
encoder.cpos += leftCopyLen;
|
|
797
|
+
if (rightCopyLen > 0) {
|
|
798
|
+
// Still something to write, write right half..
|
|
799
|
+
// Append new buffer
|
|
800
|
+
encoder.bufs.push(encoder.cbuf);
|
|
801
|
+
// must have at least size of remaining buffer
|
|
802
|
+
encoder.cbuf = new Uint8Array(max(bufferLen * 2, rightCopyLen));
|
|
803
|
+
// copy array
|
|
804
|
+
encoder.cbuf.set(uint8Array.subarray(leftCopyLen));
|
|
805
|
+
encoder.cpos = rightCopyLen;
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Append an Uint8Array to Encoder.
|
|
811
|
+
*
|
|
812
|
+
* @function
|
|
813
|
+
* @param {Encoder} encoder
|
|
814
|
+
* @param {Uint8Array} uint8Array
|
|
815
|
+
*/
|
|
816
|
+
const writeVarUint8Array = (encoder, uint8Array) => {
|
|
817
|
+
writeVarUint(encoder, uint8Array.byteLength);
|
|
818
|
+
writeUint8Array(encoder, uint8Array);
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Create an DataView of the next `len` bytes. Use it to write data after
|
|
823
|
+
* calling this function.
|
|
824
|
+
*
|
|
825
|
+
* ```js
|
|
826
|
+
* // write float32 using DataView
|
|
827
|
+
* const dv = writeOnDataView(encoder, 4)
|
|
828
|
+
* dv.setFloat32(0, 1.1)
|
|
829
|
+
* // read float32 using DataView
|
|
830
|
+
* const dv = readFromDataView(encoder, 4)
|
|
831
|
+
* dv.getFloat32(0) // => 1.100000023841858 (leaving it to the reader to find out why this is the correct result)
|
|
832
|
+
* ```
|
|
833
|
+
*
|
|
834
|
+
* @param {Encoder} encoder
|
|
835
|
+
* @param {number} len
|
|
836
|
+
* @return {DataView}
|
|
837
|
+
*/
|
|
838
|
+
const writeOnDataView = (encoder, len) => {
|
|
839
|
+
verifyLen(encoder, len);
|
|
840
|
+
const dview = new DataView(encoder.cbuf.buffer, encoder.cpos, len);
|
|
841
|
+
encoder.cpos += len;
|
|
842
|
+
return dview
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* @param {Encoder} encoder
|
|
847
|
+
* @param {number} num
|
|
848
|
+
*/
|
|
849
|
+
const writeFloat32 = (encoder, num) => writeOnDataView(encoder, 4).setFloat32(0, num, false);
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* @param {Encoder} encoder
|
|
853
|
+
* @param {number} num
|
|
854
|
+
*/
|
|
855
|
+
const writeFloat64 = (encoder, num) => writeOnDataView(encoder, 8).setFloat64(0, num, false);
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* @param {Encoder} encoder
|
|
859
|
+
* @param {bigint} num
|
|
860
|
+
*/
|
|
861
|
+
const writeBigInt64 = (encoder, num) => /** @type {any} */ (writeOnDataView(encoder, 8)).setBigInt64(0, num, false);
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* @param {Encoder} encoder
|
|
865
|
+
* @param {bigint} num
|
|
866
|
+
*/
|
|
867
|
+
const writeBigUint64 = (encoder, num) => /** @type {any} */ (writeOnDataView(encoder, 8)).setBigUint64(0, num, false);
|
|
868
|
+
|
|
869
|
+
const floatTestBed = new DataView(new ArrayBuffer(4));
|
|
870
|
+
/**
|
|
871
|
+
* Check if a number can be encoded as a 32 bit float.
|
|
872
|
+
*
|
|
873
|
+
* @param {number} num
|
|
874
|
+
* @return {boolean}
|
|
875
|
+
*/
|
|
876
|
+
const isFloat32 = num => {
|
|
877
|
+
floatTestBed.setFloat32(0, num);
|
|
878
|
+
return floatTestBed.getFloat32(0) === num
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Encode data with efficient binary format.
|
|
883
|
+
*
|
|
884
|
+
* Differences to JSON:
|
|
885
|
+
* • Transforms data to a binary format (not to a string)
|
|
886
|
+
* • Encodes undefined, NaN, and ArrayBuffer (these can't be represented in JSON)
|
|
887
|
+
* • Numbers are efficiently encoded either as a variable length integer, as a
|
|
888
|
+
* 32 bit float, as a 64 bit float, or as a 64 bit bigint.
|
|
889
|
+
*
|
|
890
|
+
* Encoding table:
|
|
891
|
+
*
|
|
892
|
+
* | Data Type | Prefix | Encoding Method | Comment |
|
|
893
|
+
* | ------------------- | -------- | ------------------ | ------- |
|
|
894
|
+
* | undefined | 127 | | Functions, symbol, and everything that cannot be identified is encoded as undefined |
|
|
895
|
+
* | null | 126 | | |
|
|
896
|
+
* | integer | 125 | writeVarInt | Only encodes 32 bit signed integers |
|
|
897
|
+
* | float32 | 124 | writeFloat32 | |
|
|
898
|
+
* | float64 | 123 | writeFloat64 | |
|
|
899
|
+
* | bigint | 122 | writeBigInt64 | |
|
|
900
|
+
* | boolean (false) | 121 | | True and false are different data types so we save the following byte |
|
|
901
|
+
* | boolean (true) | 120 | | - 0b01111000 so the last bit determines whether true or false |
|
|
902
|
+
* | string | 119 | writeVarString | |
|
|
903
|
+
* | object<string,any> | 118 | custom | Writes {length} then {length} key-value pairs |
|
|
904
|
+
* | array<any> | 117 | custom | Writes {length} then {length} json values |
|
|
905
|
+
* | Uint8Array | 116 | writeVarUint8Array | We use Uint8Array for any kind of binary data |
|
|
906
|
+
*
|
|
907
|
+
* Reasons for the decreasing prefix:
|
|
908
|
+
* We need the first bit for extendability (later we may want to encode the
|
|
909
|
+
* prefix with writeVarUint). The remaining 7 bits are divided as follows:
|
|
910
|
+
* [0-30] the beginning of the data range is used for custom purposes
|
|
911
|
+
* (defined by the function that uses this library)
|
|
912
|
+
* [31-127] the end of the data range is used for data encoding by
|
|
913
|
+
* lib0/encoding.js
|
|
914
|
+
*
|
|
915
|
+
* @param {Encoder} encoder
|
|
916
|
+
* @param {undefined|null|number|bigint|boolean|string|Object<string,any>|Array<any>|Uint8Array} data
|
|
917
|
+
*/
|
|
918
|
+
const writeAny = (encoder, data) => {
|
|
919
|
+
switch (typeof data) {
|
|
920
|
+
case 'string':
|
|
921
|
+
// TYPE 119: STRING
|
|
922
|
+
write(encoder, 119);
|
|
923
|
+
writeVarString(encoder, data);
|
|
924
|
+
break
|
|
925
|
+
case 'number':
|
|
926
|
+
if (isInteger(data) && abs(data) <= BITS31) {
|
|
927
|
+
// TYPE 125: INTEGER
|
|
928
|
+
write(encoder, 125);
|
|
929
|
+
writeVarInt(encoder, data);
|
|
930
|
+
} else if (isFloat32(data)) {
|
|
931
|
+
// TYPE 124: FLOAT32
|
|
932
|
+
write(encoder, 124);
|
|
933
|
+
writeFloat32(encoder, data);
|
|
934
|
+
} else {
|
|
935
|
+
// TYPE 123: FLOAT64
|
|
936
|
+
write(encoder, 123);
|
|
937
|
+
writeFloat64(encoder, data);
|
|
938
|
+
}
|
|
939
|
+
break
|
|
940
|
+
case 'bigint':
|
|
941
|
+
// TYPE 122: BigInt
|
|
942
|
+
write(encoder, 122);
|
|
943
|
+
writeBigInt64(encoder, data);
|
|
944
|
+
break
|
|
945
|
+
case 'object':
|
|
946
|
+
if (data === null) {
|
|
947
|
+
// TYPE 126: null
|
|
948
|
+
write(encoder, 126);
|
|
949
|
+
} else if (isArray(data)) {
|
|
950
|
+
// TYPE 117: Array
|
|
951
|
+
write(encoder, 117);
|
|
952
|
+
writeVarUint(encoder, data.length);
|
|
953
|
+
for (let i = 0; i < data.length; i++) {
|
|
954
|
+
writeAny(encoder, data[i]);
|
|
955
|
+
}
|
|
956
|
+
} else if (data instanceof Uint8Array) {
|
|
957
|
+
// TYPE 116: ArrayBuffer
|
|
958
|
+
write(encoder, 116);
|
|
959
|
+
writeVarUint8Array(encoder, data);
|
|
960
|
+
} else {
|
|
961
|
+
// TYPE 118: Object
|
|
962
|
+
write(encoder, 118);
|
|
963
|
+
const keys = Object.keys(data);
|
|
964
|
+
writeVarUint(encoder, keys.length);
|
|
965
|
+
for (let i = 0; i < keys.length; i++) {
|
|
966
|
+
const key = keys[i];
|
|
967
|
+
writeVarString(encoder, key);
|
|
968
|
+
writeAny(encoder, data[key]);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
break
|
|
972
|
+
case 'boolean':
|
|
973
|
+
// TYPE 120/121: boolean (true/false)
|
|
974
|
+
write(encoder, data ? 120 : 121);
|
|
975
|
+
break
|
|
976
|
+
default:
|
|
977
|
+
// TYPE 127: undefined
|
|
978
|
+
write(encoder, 127);
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
|
|
982
|
+
function encodeYMessage(msg) {
|
|
983
|
+
const encoder = new Encoder();
|
|
984
|
+
writeVarString(encoder, msg.type);
|
|
985
|
+
writeVarString(encoder, msg.table);
|
|
986
|
+
writeVarString(encoder, msg.prop);
|
|
987
|
+
switch (msg.type) {
|
|
988
|
+
case 'u-ack':
|
|
989
|
+
case 'u-reject':
|
|
990
|
+
writeBigUint64(encoder, BigInt(msg.i));
|
|
991
|
+
break;
|
|
992
|
+
default:
|
|
993
|
+
writeAny(encoder, msg.k);
|
|
994
|
+
switch (msg.type) {
|
|
995
|
+
case 'aware':
|
|
996
|
+
writeVarUint8Array(encoder, msg.u);
|
|
997
|
+
break;
|
|
998
|
+
case 'doc-open':
|
|
999
|
+
writeAny(encoder, msg.serverRev);
|
|
1000
|
+
writeAny(encoder, msg.sv);
|
|
1001
|
+
break;
|
|
1002
|
+
case 'doc-close':
|
|
1003
|
+
break;
|
|
1004
|
+
case 'sv':
|
|
1005
|
+
writeVarUint8Array(encoder, msg.sv);
|
|
1006
|
+
break;
|
|
1007
|
+
case 'u-c':
|
|
1008
|
+
writeVarUint8Array(encoder, msg.u);
|
|
1009
|
+
writeBigUint64(encoder, BigInt(msg.i));
|
|
1010
|
+
break;
|
|
1011
|
+
case 'u-s':
|
|
1012
|
+
writeVarUint8Array(encoder, msg.u);
|
|
1013
|
+
break;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
return toUint8Array(encoder);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* Error helpers.
|
|
1021
|
+
*
|
|
1022
|
+
* @module error
|
|
1023
|
+
*/
|
|
1024
|
+
|
|
1025
|
+
/**
|
|
1026
|
+
* @param {string} s
|
|
1027
|
+
* @return {Error}
|
|
1028
|
+
*/
|
|
1029
|
+
/* c8 ignore next */
|
|
1030
|
+
const create = s => new Error(s);
|
|
1031
|
+
|
|
1032
|
+
/**
|
|
1033
|
+
* Efficient schema-less binary decoding with support for variable length encoding.
|
|
1034
|
+
*
|
|
1035
|
+
* Use [lib0/decoding] with [lib0/encoding]. Every encoding function has a corresponding decoding function.
|
|
1036
|
+
*
|
|
1037
|
+
* Encodes numbers in little-endian order (least to most significant byte order)
|
|
1038
|
+
* and is compatible with Golang's binary encoding (https://golang.org/pkg/encoding/binary/)
|
|
1039
|
+
* which is also used in Protocol Buffers.
|
|
1040
|
+
*
|
|
1041
|
+
* ```js
|
|
1042
|
+
* // encoding step
|
|
1043
|
+
* const encoder = encoding.createEncoder()
|
|
1044
|
+
* encoding.writeVarUint(encoder, 256)
|
|
1045
|
+
* encoding.writeVarString(encoder, 'Hello world!')
|
|
1046
|
+
* const buf = encoding.toUint8Array(encoder)
|
|
1047
|
+
* ```
|
|
1048
|
+
*
|
|
1049
|
+
* ```js
|
|
1050
|
+
* // decoding step
|
|
1051
|
+
* const decoder = decoding.createDecoder(buf)
|
|
1052
|
+
* decoding.readVarUint(decoder) // => 256
|
|
1053
|
+
* decoding.readVarString(decoder) // => 'Hello world!'
|
|
1054
|
+
* decoding.hasContent(decoder) // => false - all data is read
|
|
1055
|
+
* ```
|
|
1056
|
+
*
|
|
1057
|
+
* @module decoding
|
|
1058
|
+
*/
|
|
1059
|
+
|
|
1060
|
+
|
|
1061
|
+
const errorUnexpectedEndOfArray = create('Unexpected end of array');
|
|
1062
|
+
const errorIntegerOutOfRange = create('Integer out of Range');
|
|
1063
|
+
|
|
1064
|
+
/**
|
|
1065
|
+
* A Decoder handles the decoding of an Uint8Array.
|
|
1066
|
+
*/
|
|
1067
|
+
class Decoder {
|
|
1068
|
+
/**
|
|
1069
|
+
* @param {Uint8Array} uint8Array Binary data to decode
|
|
1070
|
+
*/
|
|
1071
|
+
constructor (uint8Array) {
|
|
1072
|
+
/**
|
|
1073
|
+
* Decoding target.
|
|
1074
|
+
*
|
|
1075
|
+
* @type {Uint8Array}
|
|
1076
|
+
*/
|
|
1077
|
+
this.arr = uint8Array;
|
|
1078
|
+
/**
|
|
1079
|
+
* Current decoding position.
|
|
1080
|
+
*
|
|
1081
|
+
* @type {number}
|
|
1082
|
+
*/
|
|
1083
|
+
this.pos = 0;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* @function
|
|
1089
|
+
* @param {Decoder} decoder
|
|
1090
|
+
* @return {boolean}
|
|
1091
|
+
*/
|
|
1092
|
+
const hasContent = decoder => decoder.pos !== decoder.arr.length;
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* Create an Uint8Array view of the next `len` bytes and advance the position by `len`.
|
|
1096
|
+
*
|
|
1097
|
+
* Important: The Uint8Array still points to the underlying ArrayBuffer. Make sure to discard the result as soon as possible to prevent any memory leaks.
|
|
1098
|
+
* Use `buffer.copyUint8Array` to copy the result into a new Uint8Array.
|
|
1099
|
+
*
|
|
1100
|
+
* @function
|
|
1101
|
+
* @param {Decoder} decoder The decoder instance
|
|
1102
|
+
* @param {number} len The length of bytes to read
|
|
1103
|
+
* @return {Uint8Array}
|
|
1104
|
+
*/
|
|
1105
|
+
const readUint8Array = (decoder, len) => {
|
|
1106
|
+
const view = new Uint8Array(decoder.arr.buffer, decoder.pos + decoder.arr.byteOffset, len);
|
|
1107
|
+
decoder.pos += len;
|
|
1108
|
+
return view
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* Read variable length Uint8Array.
|
|
1113
|
+
*
|
|
1114
|
+
* Important: The Uint8Array still points to the underlying ArrayBuffer. Make sure to discard the result as soon as possible to prevent any memory leaks.
|
|
1115
|
+
* Use `buffer.copyUint8Array` to copy the result into a new Uint8Array.
|
|
1116
|
+
*
|
|
1117
|
+
* @function
|
|
1118
|
+
* @param {Decoder} decoder
|
|
1119
|
+
* @return {Uint8Array}
|
|
1120
|
+
*/
|
|
1121
|
+
const readVarUint8Array = decoder => readUint8Array(decoder, readVarUint(decoder));
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Read one byte as unsigned integer.
|
|
1125
|
+
* @function
|
|
1126
|
+
* @param {Decoder} decoder The decoder instance
|
|
1127
|
+
* @return {number} Unsigned 8-bit integer
|
|
1128
|
+
*/
|
|
1129
|
+
const readUint8 = decoder => decoder.arr[decoder.pos++];
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* Read unsigned integer (32bit) with variable length.
|
|
1133
|
+
* 1/8th of the storage is used as encoding overhead.
|
|
1134
|
+
* * numbers < 2^7 is stored in one bytlength
|
|
1135
|
+
* * numbers < 2^14 is stored in two bylength
|
|
1136
|
+
*
|
|
1137
|
+
* @function
|
|
1138
|
+
* @param {Decoder} decoder
|
|
1139
|
+
* @return {number} An unsigned integer.length
|
|
1140
|
+
*/
|
|
1141
|
+
const readVarUint = decoder => {
|
|
1142
|
+
let num = 0;
|
|
1143
|
+
let mult = 1;
|
|
1144
|
+
const len = decoder.arr.length;
|
|
1145
|
+
while (decoder.pos < len) {
|
|
1146
|
+
const r = decoder.arr[decoder.pos++];
|
|
1147
|
+
// num = num | ((r & binary.BITS7) << len)
|
|
1148
|
+
num = num + (r & BITS7) * mult; // shift $r << (7*#iterations) and add it to num
|
|
1149
|
+
mult *= 128; // next iteration, shift 7 "more" to the left
|
|
1150
|
+
if (r < BIT8) {
|
|
1151
|
+
return num
|
|
1152
|
+
}
|
|
1153
|
+
/* c8 ignore start */
|
|
1154
|
+
if (num > MAX_SAFE_INTEGER) {
|
|
1155
|
+
throw errorIntegerOutOfRange
|
|
1156
|
+
}
|
|
1157
|
+
/* c8 ignore stop */
|
|
1158
|
+
}
|
|
1159
|
+
throw errorUnexpectedEndOfArray
|
|
1160
|
+
};
|
|
1161
|
+
|
|
1162
|
+
/**
|
|
1163
|
+
* Read signed integer (32bit) with variable length.
|
|
1164
|
+
* 1/8th of the storage is used as encoding overhead.
|
|
1165
|
+
* * numbers < 2^7 is stored in one bytlength
|
|
1166
|
+
* * numbers < 2^14 is stored in two bylength
|
|
1167
|
+
* @todo This should probably create the inverse ~num if number is negative - but this would be a breaking change.
|
|
1168
|
+
*
|
|
1169
|
+
* @function
|
|
1170
|
+
* @param {Decoder} decoder
|
|
1171
|
+
* @return {number} An unsigned integer.length
|
|
1172
|
+
*/
|
|
1173
|
+
const readVarInt = decoder => {
|
|
1174
|
+
let r = decoder.arr[decoder.pos++];
|
|
1175
|
+
let num = r & BITS6;
|
|
1176
|
+
let mult = 64;
|
|
1177
|
+
const sign = (r & BIT7) > 0 ? -1 : 1;
|
|
1178
|
+
if ((r & BIT8) === 0) {
|
|
1179
|
+
// don't continue reading
|
|
1180
|
+
return sign * num
|
|
1181
|
+
}
|
|
1182
|
+
const len = decoder.arr.length;
|
|
1183
|
+
while (decoder.pos < len) {
|
|
1184
|
+
r = decoder.arr[decoder.pos++];
|
|
1185
|
+
// num = num | ((r & binary.BITS7) << len)
|
|
1186
|
+
num = num + (r & BITS7) * mult;
|
|
1187
|
+
mult *= 128;
|
|
1188
|
+
if (r < BIT8) {
|
|
1189
|
+
return sign * num
|
|
1190
|
+
}
|
|
1191
|
+
/* c8 ignore start */
|
|
1192
|
+
if (num > MAX_SAFE_INTEGER) {
|
|
1193
|
+
throw errorIntegerOutOfRange
|
|
1194
|
+
}
|
|
1195
|
+
/* c8 ignore stop */
|
|
1196
|
+
}
|
|
1197
|
+
throw errorUnexpectedEndOfArray
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* We don't test this function anymore as we use native decoding/encoding by default now.
|
|
1202
|
+
* Better not modify this anymore..
|
|
1203
|
+
*
|
|
1204
|
+
* Transforming utf8 to a string is pretty expensive. The code performs 10x better
|
|
1205
|
+
* when String.fromCodePoint is fed with all characters as arguments.
|
|
1206
|
+
* But most environments have a maximum number of arguments per functions.
|
|
1207
|
+
* For effiency reasons we apply a maximum of 10000 characters at once.
|
|
1208
|
+
*
|
|
1209
|
+
* @function
|
|
1210
|
+
* @param {Decoder} decoder
|
|
1211
|
+
* @return {String} The read String.
|
|
1212
|
+
*/
|
|
1213
|
+
/* c8 ignore start */
|
|
1214
|
+
const _readVarStringPolyfill = decoder => {
|
|
1215
|
+
let remainingLen = readVarUint(decoder);
|
|
1216
|
+
if (remainingLen === 0) {
|
|
1217
|
+
return ''
|
|
1218
|
+
} else {
|
|
1219
|
+
let encodedString = String.fromCodePoint(readUint8(decoder)); // remember to decrease remainingLen
|
|
1220
|
+
if (--remainingLen < 100) { // do not create a Uint8Array for small strings
|
|
1221
|
+
while (remainingLen--) {
|
|
1222
|
+
encodedString += String.fromCodePoint(readUint8(decoder));
|
|
1223
|
+
}
|
|
1224
|
+
} else {
|
|
1225
|
+
while (remainingLen > 0) {
|
|
1226
|
+
const nextLen = remainingLen < 10000 ? remainingLen : 10000;
|
|
1227
|
+
// this is dangerous, we create a fresh array view from the existing buffer
|
|
1228
|
+
const bytes = decoder.arr.subarray(decoder.pos, decoder.pos + nextLen);
|
|
1229
|
+
decoder.pos += nextLen;
|
|
1230
|
+
// Starting with ES5.1 we can supply a generic array-like object as arguments
|
|
1231
|
+
encodedString += String.fromCodePoint.apply(null, /** @type {any} */ (bytes));
|
|
1232
|
+
remainingLen -= nextLen;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
return decodeURIComponent(escape(encodedString))
|
|
1236
|
+
}
|
|
1237
|
+
};
|
|
1238
|
+
/* c8 ignore stop */
|
|
1239
|
+
|
|
1240
|
+
/**
|
|
1241
|
+
* @function
|
|
1242
|
+
* @param {Decoder} decoder
|
|
1243
|
+
* @return {String} The read String
|
|
1244
|
+
*/
|
|
1245
|
+
const _readVarStringNative = decoder =>
|
|
1246
|
+
/** @type any */ (utf8TextDecoder).decode(readVarUint8Array(decoder));
|
|
1247
|
+
|
|
1248
|
+
/**
|
|
1249
|
+
* Read string of variable length
|
|
1250
|
+
* * varUint is used to store the length of the string
|
|
1251
|
+
*
|
|
1252
|
+
* @function
|
|
1253
|
+
* @param {Decoder} decoder
|
|
1254
|
+
* @return {String} The read String
|
|
1255
|
+
*
|
|
1256
|
+
*/
|
|
1257
|
+
/* c8 ignore next */
|
|
1258
|
+
const readVarString = utf8TextDecoder ? _readVarStringNative : _readVarStringPolyfill;
|
|
1259
|
+
|
|
1260
|
+
/**
|
|
1261
|
+
* @param {Decoder} decoder
|
|
1262
|
+
* @param {number} len
|
|
1263
|
+
* @return {DataView}
|
|
1264
|
+
*/
|
|
1265
|
+
const readFromDataView = (decoder, len) => {
|
|
1266
|
+
const dv = new DataView(decoder.arr.buffer, decoder.arr.byteOffset + decoder.pos, len);
|
|
1267
|
+
decoder.pos += len;
|
|
1268
|
+
return dv
|
|
1269
|
+
};
|
|
1270
|
+
|
|
1271
|
+
/**
|
|
1272
|
+
* @param {Decoder} decoder
|
|
1273
|
+
*/
|
|
1274
|
+
const readFloat32 = decoder => readFromDataView(decoder, 4).getFloat32(0, false);
|
|
1275
|
+
|
|
1276
|
+
/**
|
|
1277
|
+
* @param {Decoder} decoder
|
|
1278
|
+
*/
|
|
1279
|
+
const readFloat64 = decoder => readFromDataView(decoder, 8).getFloat64(0, false);
|
|
1280
|
+
|
|
1281
|
+
/**
|
|
1282
|
+
* @param {Decoder} decoder
|
|
1283
|
+
*/
|
|
1284
|
+
const readBigInt64 = decoder => /** @type {any} */ (readFromDataView(decoder, 8)).getBigInt64(0, false);
|
|
1285
|
+
|
|
1286
|
+
/**
|
|
1287
|
+
* @param {Decoder} decoder
|
|
1288
|
+
*/
|
|
1289
|
+
const readBigUint64 = decoder => /** @type {any} */ (readFromDataView(decoder, 8)).getBigUint64(0, false);
|
|
1290
|
+
|
|
1291
|
+
/**
|
|
1292
|
+
* @type {Array<function(Decoder):any>}
|
|
1293
|
+
*/
|
|
1294
|
+
const readAnyLookupTable = [
|
|
1295
|
+
decoder => undefined, // CASE 127: undefined
|
|
1296
|
+
decoder => null, // CASE 126: null
|
|
1297
|
+
readVarInt, // CASE 125: integer
|
|
1298
|
+
readFloat32, // CASE 124: float32
|
|
1299
|
+
readFloat64, // CASE 123: float64
|
|
1300
|
+
readBigInt64, // CASE 122: bigint
|
|
1301
|
+
decoder => false, // CASE 121: boolean (false)
|
|
1302
|
+
decoder => true, // CASE 120: boolean (true)
|
|
1303
|
+
readVarString, // CASE 119: string
|
|
1304
|
+
decoder => { // CASE 118: object<string,any>
|
|
1305
|
+
const len = readVarUint(decoder);
|
|
1306
|
+
/**
|
|
1307
|
+
* @type {Object<string,any>}
|
|
1308
|
+
*/
|
|
1309
|
+
const obj = {};
|
|
1310
|
+
for (let i = 0; i < len; i++) {
|
|
1311
|
+
const key = readVarString(decoder);
|
|
1312
|
+
obj[key] = readAny(decoder);
|
|
1313
|
+
}
|
|
1314
|
+
return obj
|
|
1315
|
+
},
|
|
1316
|
+
decoder => { // CASE 117: array<any>
|
|
1317
|
+
const len = readVarUint(decoder);
|
|
1318
|
+
const arr = [];
|
|
1319
|
+
for (let i = 0; i < len; i++) {
|
|
1320
|
+
arr.push(readAny(decoder));
|
|
1321
|
+
}
|
|
1322
|
+
return arr
|
|
1323
|
+
},
|
|
1324
|
+
readVarUint8Array // CASE 116: Uint8Array
|
|
1325
|
+
];
|
|
1326
|
+
|
|
1327
|
+
/**
|
|
1328
|
+
* @param {Decoder} decoder
|
|
1329
|
+
*/
|
|
1330
|
+
const readAny = decoder => readAnyLookupTable[127 - readUint8(decoder)](decoder);
|
|
1331
|
+
|
|
1332
|
+
function decodeYMessage(a) {
|
|
1333
|
+
const decoder = new Decoder(a);
|
|
1334
|
+
const type = readVarString(decoder);
|
|
1335
|
+
const table = readVarString(decoder);
|
|
1336
|
+
const prop = readVarString(decoder);
|
|
1337
|
+
switch (type) {
|
|
1338
|
+
case 'u-ack':
|
|
1339
|
+
case 'u-reject':
|
|
1340
|
+
return {
|
|
1341
|
+
type,
|
|
1342
|
+
table,
|
|
1343
|
+
prop,
|
|
1344
|
+
i: Number(readBigUint64(decoder)),
|
|
1345
|
+
};
|
|
1346
|
+
default: {
|
|
1347
|
+
const k = readAny(decoder);
|
|
1348
|
+
switch (type) {
|
|
1349
|
+
case 'in-sync':
|
|
1350
|
+
return { type, table, prop, k };
|
|
1351
|
+
case 'aware':
|
|
1352
|
+
return {
|
|
1353
|
+
type,
|
|
1354
|
+
table,
|
|
1355
|
+
prop,
|
|
1356
|
+
k,
|
|
1357
|
+
u: readVarUint8Array(decoder),
|
|
1358
|
+
};
|
|
1359
|
+
case 'doc-open':
|
|
1360
|
+
return {
|
|
1361
|
+
type,
|
|
1362
|
+
table,
|
|
1363
|
+
prop,
|
|
1364
|
+
k,
|
|
1365
|
+
serverRev: readAny(decoder),
|
|
1366
|
+
sv: readAny(decoder),
|
|
1367
|
+
};
|
|
1368
|
+
case 'doc-close':
|
|
1369
|
+
return { type, table, prop, k };
|
|
1370
|
+
case 'sv':
|
|
1371
|
+
return {
|
|
1372
|
+
type,
|
|
1373
|
+
table,
|
|
1374
|
+
prop,
|
|
1375
|
+
k,
|
|
1376
|
+
sv: readVarUint8Array(decoder),
|
|
1377
|
+
};
|
|
1378
|
+
case 'u-c':
|
|
1379
|
+
return {
|
|
1380
|
+
type,
|
|
1381
|
+
table,
|
|
1382
|
+
prop,
|
|
1383
|
+
k,
|
|
1384
|
+
u: readVarUint8Array(decoder),
|
|
1385
|
+
i: Number(readBigUint64(decoder)),
|
|
1386
|
+
};
|
|
1387
|
+
case 'u-s':
|
|
1388
|
+
return {
|
|
1389
|
+
type,
|
|
1390
|
+
table,
|
|
1391
|
+
prop,
|
|
1392
|
+
k,
|
|
1393
|
+
u: readVarUint8Array(decoder)
|
|
1394
|
+
};
|
|
1395
|
+
default:
|
|
1396
|
+
throw new TypeError(`Unknown message type: ${type}`);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
async function asyncIterablePipeline(source, ...stages) {
|
|
1403
|
+
var _a, e_1, _b, _c;
|
|
1404
|
+
// Chain generators by sending outdata from one to another
|
|
1405
|
+
let result = source(); // Start with the source generator
|
|
1406
|
+
for (let i = 0; i < stages.length; i++) {
|
|
1407
|
+
result = stages[i](result); // Pass on the result to next generator
|
|
1408
|
+
}
|
|
1409
|
+
try {
|
|
1410
|
+
// Start running the machine. If the last stage is a sink, it will consume the data and never emit anything
|
|
1411
|
+
// to us here...
|
|
1412
|
+
for (var _d = true, result_1 = __asyncValues(result), result_1_1; result_1_1 = await result_1.next(), _a = result_1_1.done, !_a; _d = true) {
|
|
1413
|
+
_c = result_1_1.value;
|
|
1414
|
+
_d = false;
|
|
1415
|
+
const chunk = _c;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
1419
|
+
finally {
|
|
1420
|
+
try {
|
|
1421
|
+
if (!_d && !_a && (_b = result_1.return)) await _b.call(result_1);
|
|
1422
|
+
}
|
|
1423
|
+
finally { if (e_1) throw e_1.error; }
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
function consumeChunkedBinaryStream(source) {
|
|
1428
|
+
return __asyncGenerator(this, arguments, function* consumeChunkedBinaryStream_1() {
|
|
1429
|
+
var _a, e_1, _b, _c;
|
|
1430
|
+
let state = 0;
|
|
1431
|
+
let sizeBuf = new Uint8Array(4);
|
|
1432
|
+
let sizeBufPos = 0;
|
|
1433
|
+
let bufs = [];
|
|
1434
|
+
let len = 0;
|
|
1435
|
+
try {
|
|
1436
|
+
for (var _d = true, source_1 = __asyncValues(source), source_1_1; source_1_1 = yield __await(source_1.next()), _a = source_1_1.done, !_a; _d = true) {
|
|
1437
|
+
_c = source_1_1.value;
|
|
1438
|
+
_d = false;
|
|
1439
|
+
const chunk = _c;
|
|
1440
|
+
const dw = new DataView(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
1441
|
+
let pos = 0;
|
|
1442
|
+
while (pos < chunk.byteLength) {
|
|
1443
|
+
switch (state) {
|
|
1444
|
+
case 0:
|
|
1445
|
+
// Beginning of a size header
|
|
1446
|
+
if (pos + 4 > chunk.byteLength) {
|
|
1447
|
+
for (const b of chunk.slice(pos)) {
|
|
1448
|
+
if (sizeBufPos === 4)
|
|
1449
|
+
break;
|
|
1450
|
+
sizeBuf[sizeBufPos++] = b;
|
|
1451
|
+
++pos;
|
|
1452
|
+
}
|
|
1453
|
+
if (sizeBufPos < 4) {
|
|
1454
|
+
// Need more bytes in order to read length.
|
|
1455
|
+
// Will go out from while loop as well because pos is defenitely = chunk.byteLength here.
|
|
1456
|
+
break;
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
else if (sizeBufPos > 0 && sizeBufPos < 4) {
|
|
1460
|
+
for (const b of chunk.slice(pos, pos + 4 - sizeBufPos)) {
|
|
1461
|
+
sizeBuf[sizeBufPos++] = b;
|
|
1462
|
+
++pos;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
// Intentional fall-through...
|
|
1466
|
+
case 1:
|
|
1467
|
+
len =
|
|
1468
|
+
sizeBufPos === 4
|
|
1469
|
+
? new DataView(sizeBuf.buffer, 0, 4).getUint32(0, false)
|
|
1470
|
+
: dw.getUint32(pos, false);
|
|
1471
|
+
if (sizeBufPos)
|
|
1472
|
+
sizeBufPos = 0; // in this case pos is already forwarded
|
|
1473
|
+
else
|
|
1474
|
+
pos += 4; // else pos is not yet forwarded - that's why we do it now
|
|
1475
|
+
// Intentional fall-through...
|
|
1476
|
+
case 2:
|
|
1477
|
+
// Eat the chunk
|
|
1478
|
+
if (pos >= chunk.byteLength) {
|
|
1479
|
+
state = 2;
|
|
1480
|
+
break;
|
|
1481
|
+
}
|
|
1482
|
+
if (pos + len > chunk.byteLength) {
|
|
1483
|
+
bufs.push(chunk.slice(pos));
|
|
1484
|
+
len -= (chunk.byteLength - pos);
|
|
1485
|
+
state = 2;
|
|
1486
|
+
pos = chunk.byteLength; // will break while loop.
|
|
1487
|
+
}
|
|
1488
|
+
else {
|
|
1489
|
+
if (bufs.length > 0) {
|
|
1490
|
+
const concats = new Uint8Array(bufs.reduce((p, c) => p + c.byteLength, len));
|
|
1491
|
+
let p = 0;
|
|
1492
|
+
for (const buf of bufs) {
|
|
1493
|
+
concats.set(buf, p);
|
|
1494
|
+
p += buf.byteLength;
|
|
1495
|
+
}
|
|
1496
|
+
concats.set(chunk.slice(pos, pos + len), p);
|
|
1497
|
+
bufs = [];
|
|
1498
|
+
yield yield __await(concats);
|
|
1499
|
+
}
|
|
1500
|
+
else {
|
|
1501
|
+
yield yield __await(chunk.slice(pos, pos + len));
|
|
1502
|
+
}
|
|
1503
|
+
pos += len;
|
|
1504
|
+
state = 0;
|
|
1505
|
+
}
|
|
1506
|
+
break;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
1512
|
+
finally {
|
|
1513
|
+
try {
|
|
1514
|
+
if (!_d && !_a && (_b = source_1.return)) yield __await(_b.call(source_1));
|
|
1515
|
+
}
|
|
1516
|
+
finally { if (e_1) throw e_1.error; }
|
|
1517
|
+
}
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
function getFetchResponseBodyGenerator(res) {
|
|
1522
|
+
return function () {
|
|
1523
|
+
return __asyncGenerator(this, arguments, function* () {
|
|
1524
|
+
if (!res.body)
|
|
1525
|
+
throw new Error("Response body is not readable");
|
|
1526
|
+
const reader = res.body.getReader();
|
|
1527
|
+
try {
|
|
1528
|
+
while (true) {
|
|
1529
|
+
const { done, value } = yield __await(reader.read());
|
|
1530
|
+
if (done)
|
|
1531
|
+
return yield __await(void 0);
|
|
1532
|
+
yield yield __await(value);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
finally {
|
|
1536
|
+
reader.releaseLock();
|
|
1537
|
+
}
|
|
1538
|
+
});
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
|
|
473
1542
|
function isFunction(value) {
|
|
474
1543
|
return typeof value === 'function';
|
|
475
1544
|
}
|
|
@@ -3617,7 +4686,7 @@
|
|
|
3617
4686
|
}
|
|
3618
4687
|
|
|
3619
4688
|
//import {BisonWebStreamReader} from "dreambase-library/dist/typeson-simplified/BisonWebStreamReader";
|
|
3620
|
-
function syncWithServer(changes, syncState, baseRevs, db, databaseUrl, schema, clientIdentity, currentUser) {
|
|
4689
|
+
function syncWithServer(changes, y, syncState, baseRevs, db, databaseUrl, schema, clientIdentity, currentUser) {
|
|
3621
4690
|
return __awaiter(this, void 0, void 0, function* () {
|
|
3622
4691
|
//
|
|
3623
4692
|
// Push changes to server using fetch
|
|
@@ -3655,6 +4724,7 @@
|
|
|
3655
4724
|
: undefined,
|
|
3656
4725
|
baseRevs,
|
|
3657
4726
|
changes: encodeIdsForServer(db.dx.core.schema, currentUser, changes),
|
|
4727
|
+
y,
|
|
3658
4728
|
};
|
|
3659
4729
|
console.debug('Sync request', syncRequest);
|
|
3660
4730
|
db.syncStateChangedEvent.next({
|
|
@@ -3868,6 +4938,357 @@
|
|
|
3868
4938
|
});
|
|
3869
4939
|
}
|
|
3870
4940
|
|
|
4941
|
+
const DEXIE_CLOUD_SYNCER_ID = 'dexie-cloud-syncer';
|
|
4942
|
+
|
|
4943
|
+
function listUpdatesSince(yTable, sinceIncluding) {
|
|
4944
|
+
return yTable
|
|
4945
|
+
.where('i')
|
|
4946
|
+
.between(sinceIncluding, Infinity, true)
|
|
4947
|
+
.toArray();
|
|
4948
|
+
}
|
|
4949
|
+
|
|
4950
|
+
function $Y(db) {
|
|
4951
|
+
const $Y = db.dx._options.Y;
|
|
4952
|
+
if (!$Y)
|
|
4953
|
+
throw new Error('Y library not supplied to Dexie constructor');
|
|
4954
|
+
return $Y;
|
|
4955
|
+
}
|
|
4956
|
+
|
|
4957
|
+
/** Queries the local database for YMessages to send to server.
|
|
4958
|
+
*
|
|
4959
|
+
* There are 2 messages that this function can provide:
|
|
4960
|
+
* YUpdateFromClientRequest ( for local updates )
|
|
4961
|
+
* YStateVector ( for state vector of foreign updates so that server can reduce the number of udpates to send back )
|
|
4962
|
+
*
|
|
4963
|
+
* Notice that we do not do a step 1 sync phase here to get a state vector from the server. Reason we can avoid
|
|
4964
|
+
* the 2-step sync is that we are client-server and not client-client here and we keep track of the client changes
|
|
4965
|
+
* sent to server by letting server acknowledge them. There is always a chance that some client update has already
|
|
4966
|
+
* been sent and that the client failed to receive the ack. However, if this happens it does not matter - the change
|
|
4967
|
+
* would be sent again and Yjs handles duplicate changes anyway. And it's rare so we earn the cost of roundtrips by
|
|
4968
|
+
* avoiding the step1 sync and instead keep track of this in the `unsentFrom` property of the SyncState.
|
|
4969
|
+
*
|
|
4970
|
+
* @param db
|
|
4971
|
+
* @returns
|
|
4972
|
+
*/
|
|
4973
|
+
function listYClientMessagesAndStateVector(db, tablesToSync) {
|
|
4974
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
4975
|
+
const result = [];
|
|
4976
|
+
const lastUpdateIds = {};
|
|
4977
|
+
for (const table of tablesToSync) {
|
|
4978
|
+
if (table.schema.yProps) {
|
|
4979
|
+
for (const yProp of table.schema.yProps) {
|
|
4980
|
+
const Y = $Y(db); // This is how we retrieve the user-provided Y library
|
|
4981
|
+
const yTable = db.table(yProp.updatesTable); // the updates-table for this combo of table+propName
|
|
4982
|
+
const syncState = (yield yTable.get(DEXIE_CLOUD_SYNCER_ID));
|
|
4983
|
+
// unsentFrom = the `i` value of updates that aren't yet sent to server (or at least not acked by the server yet)
|
|
4984
|
+
const unsentFrom = (syncState === null || syncState === void 0 ? void 0 : syncState.unsentFrom) || 1;
|
|
4985
|
+
// receivedUntil = the `i` value of updates that both we and the server knows we already have (we know it by the outcome from last syncWithServer() because server keep track of its revision numbers
|
|
4986
|
+
const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
|
|
4987
|
+
// Compute the least value of these two (but since receivedUntil is inclusive we need to add +1 to it)
|
|
4988
|
+
const unsyncedFrom = Math.min(unsentFrom, receivedUntil + 1);
|
|
4989
|
+
// Query all these updates for all docs of this table+prop combination
|
|
4990
|
+
const updates = yield listUpdatesSince(yTable, unsyncedFrom);
|
|
4991
|
+
if (updates.length > 0)
|
|
4992
|
+
lastUpdateIds[yTable.name] = updates[updates.length - 1].i;
|
|
4993
|
+
// Now sort them by document and whether they are local or not + ignore local updates already sent:
|
|
4994
|
+
const perDoc = {};
|
|
4995
|
+
for (const update of updates) {
|
|
4996
|
+
// Sort updates into buckets of the doc primary key + the flag (whether it's local or foreign)
|
|
4997
|
+
const isLocal = ((update.f || 0) & 0x01) === 0x01;
|
|
4998
|
+
if (isLocal && update.i < unsentFrom)
|
|
4999
|
+
continue; // This local update has already been sent and acked.
|
|
5000
|
+
const docKey = JSON.stringify(update.k) + '/' + isLocal;
|
|
5001
|
+
let entry = perDoc[docKey];
|
|
5002
|
+
if (!entry) {
|
|
5003
|
+
perDoc[docKey] = entry = {
|
|
5004
|
+
i: update.i,
|
|
5005
|
+
k: update.k,
|
|
5006
|
+
isLocal,
|
|
5007
|
+
u: [],
|
|
5008
|
+
};
|
|
5009
|
+
entry.u.push(update.u);
|
|
5010
|
+
}
|
|
5011
|
+
else {
|
|
5012
|
+
entry.u.push(update.u);
|
|
5013
|
+
entry.i = Math.max(update.i, entry.i);
|
|
5014
|
+
}
|
|
5015
|
+
}
|
|
5016
|
+
// Now, go through all these and:
|
|
5017
|
+
// * For local updates, compute a merged update per document.
|
|
5018
|
+
// * For foreign updates, compute a state vector to pass to server, so that server can
|
|
5019
|
+
// avoid re-sending updates that we already have (they might have been sent of websocket
|
|
5020
|
+
// and when that happens, we do not mark them in any way nor do we update receivedUntil -
|
|
5021
|
+
// we only update receivedUntil after a "full sync" (syncWithServer()))
|
|
5022
|
+
for (const { k, isLocal, u, i } of Object.values(perDoc)) {
|
|
5023
|
+
const mergedUpdate = u.length === 1 ? u[0] : Y.mergeUpdatesV2(u);
|
|
5024
|
+
if (isLocal) {
|
|
5025
|
+
result.push({
|
|
5026
|
+
type: 'u-c',
|
|
5027
|
+
table: table.name,
|
|
5028
|
+
prop: yProp.prop,
|
|
5029
|
+
k,
|
|
5030
|
+
u: mergedUpdate,
|
|
5031
|
+
i,
|
|
5032
|
+
});
|
|
5033
|
+
}
|
|
5034
|
+
else {
|
|
5035
|
+
const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
|
|
5036
|
+
result.push({
|
|
5037
|
+
type: 'sv',
|
|
5038
|
+
table: table.name,
|
|
5039
|
+
prop: yProp.prop,
|
|
5040
|
+
k,
|
|
5041
|
+
sv: stateVector,
|
|
5042
|
+
});
|
|
5043
|
+
}
|
|
5044
|
+
}
|
|
5045
|
+
}
|
|
5046
|
+
}
|
|
5047
|
+
}
|
|
5048
|
+
return {
|
|
5049
|
+
yMessages: result,
|
|
5050
|
+
lastUpdateIds
|
|
5051
|
+
};
|
|
5052
|
+
});
|
|
5053
|
+
}
|
|
5054
|
+
|
|
5055
|
+
function getUpdatesTable(db, table, ydocProp) {
|
|
5056
|
+
var _a, _b, _c;
|
|
5057
|
+
const utbl = (_c = (_b = (_a = db.table(table)) === null || _a === void 0 ? void 0 : _a.schema.yProps) === null || _b === void 0 ? void 0 : _b.find(p => p.prop === ydocProp)) === null || _c === void 0 ? void 0 : _c.updatesTable;
|
|
5058
|
+
if (!utbl)
|
|
5059
|
+
throw new Error(`No updatesTable found for ${table}.${ydocProp}`);
|
|
5060
|
+
return db.table(utbl);
|
|
5061
|
+
}
|
|
5062
|
+
|
|
5063
|
+
function applyYServerMessages(yMessages, db) {
|
|
5064
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
5065
|
+
const result = {};
|
|
5066
|
+
for (const m of yMessages) {
|
|
5067
|
+
switch (m.type) {
|
|
5068
|
+
case 'u-s': {
|
|
5069
|
+
const utbl = getUpdatesTable(db, m.table, m.prop);
|
|
5070
|
+
result[utbl.name] = yield utbl.add({
|
|
5071
|
+
k: m.k,
|
|
5072
|
+
u: m.u,
|
|
5073
|
+
});
|
|
5074
|
+
break;
|
|
5075
|
+
}
|
|
5076
|
+
case 'u-ack': {
|
|
5077
|
+
const utbl = getUpdatesTable(db, m.table, m.prop);
|
|
5078
|
+
yield db.transaction('rw', utbl, (tx) => __awaiter(this, void 0, void 0, function* () {
|
|
5079
|
+
let syncer = (yield tx
|
|
5080
|
+
.table(utbl.name)
|
|
5081
|
+
.get(DEXIE_CLOUD_SYNCER_ID));
|
|
5082
|
+
yield tx.table(utbl.name).put(Object.assign(Object.assign({}, (syncer || { i: DEXIE_CLOUD_SYNCER_ID })), { unsentFrom: Math.max((syncer === null || syncer === void 0 ? void 0 : syncer.unsentFrom) || 1, m.i + 1) }));
|
|
5083
|
+
}));
|
|
5084
|
+
break;
|
|
5085
|
+
}
|
|
5086
|
+
case 'u-reject': {
|
|
5087
|
+
// Acces control or constraint rejected the update.
|
|
5088
|
+
// We delete it. It's not going to be sent again.
|
|
5089
|
+
// What's missing is a way to notify consumers, such as Tiptap editor, that the update was rejected.
|
|
5090
|
+
// This is only an issue when the document is open. We could find the open document and
|
|
5091
|
+
// in a perfect world, we should send a reverse update to the open document to undo the change.
|
|
5092
|
+
// See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
|
|
5093
|
+
console.debug(`Y update rejected. Deleting it.`);
|
|
5094
|
+
const utbl = getUpdatesTable(db, m.table, m.prop);
|
|
5095
|
+
yield utbl.delete(m.i);
|
|
5096
|
+
break;
|
|
5097
|
+
}
|
|
5098
|
+
case 'in-sync': {
|
|
5099
|
+
const doc = Dexie.DexieYProvider.getDocCache(db.dx).find(m.table, m.k, m.prop);
|
|
5100
|
+
if (doc && !doc.isSynced) {
|
|
5101
|
+
doc.emit('sync', [true]);
|
|
5102
|
+
}
|
|
5103
|
+
break;
|
|
5104
|
+
}
|
|
5105
|
+
}
|
|
5106
|
+
}
|
|
5107
|
+
return result;
|
|
5108
|
+
});
|
|
5109
|
+
}
|
|
5110
|
+
|
|
5111
|
+
function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db, serverRevision) {
|
|
5112
|
+
var _a, _b, _c, _d, _e;
|
|
5113
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
5114
|
+
// We want to update unsentFrom for each yTable to the value specified in first argument
|
|
5115
|
+
// because we got those values before we synced with server and here we are back from server
|
|
5116
|
+
// that has successfully received all those messages - no matter if the last update was a client or server update,
|
|
5117
|
+
// we can safely store unsentFrom to a value of the last update + 1 here.
|
|
5118
|
+
// We also want to update receivedUntil for each yTable to the value specified in the second argument,
|
|
5119
|
+
// because that contains the highest resulted id of each update from server after storing it.
|
|
5120
|
+
// We could do these two tasks separately, but that would require two update calls on the same YSyncState, so
|
|
5121
|
+
// to optimize the dexie calls, we merge these two maps into a single one so we can do a single update request
|
|
5122
|
+
// per yTable.
|
|
5123
|
+
const mergedSpec = {};
|
|
5124
|
+
for (const [yTable, lastUpdateId] of Object.entries(lastUpdateIdsBeforeSync)) {
|
|
5125
|
+
(_a = mergedSpec[yTable]) !== null && _a !== void 0 ? _a : (mergedSpec[yTable] = {});
|
|
5126
|
+
mergedSpec[yTable].unsentFrom = lastUpdateId + 1;
|
|
5127
|
+
}
|
|
5128
|
+
for (const [yTable, lastUpdateId] of Object.entries(receivedUntilsAfterSync)) {
|
|
5129
|
+
(_b = mergedSpec[yTable]) !== null && _b !== void 0 ? _b : (mergedSpec[yTable] = {});
|
|
5130
|
+
mergedSpec[yTable].receivedUntil = lastUpdateId;
|
|
5131
|
+
}
|
|
5132
|
+
// Now go through all yTables and update their YSyncStates:
|
|
5133
|
+
const allYTables = Object.values(db.dx._dbSchema)
|
|
5134
|
+
.filter((tblSchema) => tblSchema.yProps)
|
|
5135
|
+
.map((tblSchema) => tblSchema.yProps.map((yProp) => yProp.updatesTable))
|
|
5136
|
+
.flat();
|
|
5137
|
+
for (const yTable of allYTables) {
|
|
5138
|
+
const mergedEntry = mergedSpec[yTable];
|
|
5139
|
+
const unsentFrom = (_c = mergedEntry === null || mergedEntry === void 0 ? void 0 : mergedEntry.unsentFrom) !== null && _c !== void 0 ? _c : 1;
|
|
5140
|
+
const receivedUntil = (_e = (_d = mergedEntry === null || mergedEntry === void 0 ? void 0 : mergedEntry.receivedUntil) !== null && _d !== void 0 ? _d :
|
|
5141
|
+
// from local because we are in the same parent transaction (in sync.ts) that
|
|
5142
|
+
// applied all updates from the server
|
|
5143
|
+
(yield db
|
|
5144
|
+
.table(yTable)
|
|
5145
|
+
.where('i')
|
|
5146
|
+
.between(1, Infinity) // Because i might be string DEXIE_CLOUD_SYNCER_ID if not a number.
|
|
5147
|
+
.reverse()
|
|
5148
|
+
.limit(1)
|
|
5149
|
+
.primaryKeys())[0]) !== null && _e !== void 0 ? _e : 0;
|
|
5150
|
+
// We're already in a transaction, but for the sake of
|
|
5151
|
+
// code readability and correctness, let's launch an atomic sub transaction:
|
|
5152
|
+
yield db.transaction('rw', yTable, () => __awaiter(this, void 0, void 0, function* () {
|
|
5153
|
+
const state = yield db
|
|
5154
|
+
.table(yTable)
|
|
5155
|
+
.get(DEXIE_CLOUD_SYNCER_ID);
|
|
5156
|
+
if (!state) {
|
|
5157
|
+
yield db.table(yTable).add({
|
|
5158
|
+
i: DEXIE_CLOUD_SYNCER_ID,
|
|
5159
|
+
unsentFrom,
|
|
5160
|
+
receivedUntil,
|
|
5161
|
+
serverRev: serverRevision,
|
|
5162
|
+
});
|
|
5163
|
+
}
|
|
5164
|
+
else {
|
|
5165
|
+
state.unsentFrom = Math.max(unsentFrom, state.unsentFrom || 1);
|
|
5166
|
+
state.receivedUntil = Math.max(receivedUntil, state.receivedUntil || 0);
|
|
5167
|
+
state.serverRev = serverRevision;
|
|
5168
|
+
yield db.table(yTable).put(state);
|
|
5169
|
+
}
|
|
5170
|
+
}));
|
|
5171
|
+
}
|
|
5172
|
+
});
|
|
5173
|
+
}
|
|
5174
|
+
|
|
5175
|
+
const BINSTREAM_TYPE_REALMID = 1;
|
|
5176
|
+
const BINSTREAM_TYPE_TABLE_AND_PROP = 2;
|
|
5177
|
+
const BINSTREAM_TYPE_DOCUMENT = 3;
|
|
5178
|
+
function downloadYDocsFromServer(db, databaseUrl, { yDownloadedRealms, realms }) {
|
|
5179
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
5180
|
+
if (yDownloadedRealms &&
|
|
5181
|
+
realms &&
|
|
5182
|
+
realms.every((realmId) => yDownloadedRealms[realmId] === '*')) {
|
|
5183
|
+
return; // Already done!
|
|
5184
|
+
}
|
|
5185
|
+
console.debug('Downloading Y.Docs from added realms');
|
|
5186
|
+
const user = yield loadAccessToken(db);
|
|
5187
|
+
const headers = {
|
|
5188
|
+
'Content-Type': 'application/json',
|
|
5189
|
+
Accept: 'application/octet-stream',
|
|
5190
|
+
};
|
|
5191
|
+
if (user) {
|
|
5192
|
+
headers.Authorization = `Bearer ${user.accessToken}`;
|
|
5193
|
+
}
|
|
5194
|
+
const res = yield fetch(`${databaseUrl}/y/download`, {
|
|
5195
|
+
body: TSON.stringify({ downloadedRealms: yDownloadedRealms || {} }),
|
|
5196
|
+
method: 'POST',
|
|
5197
|
+
headers,
|
|
5198
|
+
credentials: 'include',
|
|
5199
|
+
});
|
|
5200
|
+
if (!res.ok) {
|
|
5201
|
+
throw new Error(`Failed to download Yjs documents from server. Status: ${res.status}`);
|
|
5202
|
+
}
|
|
5203
|
+
yield asyncIterablePipeline(getFetchResponseBodyGenerator(res), consumeChunkedBinaryStream, consumeDownloadChunks);
|
|
5204
|
+
function consumeDownloadChunks(chunks) {
|
|
5205
|
+
return __asyncGenerator(this, arguments, function* consumeDownloadChunks_1() {
|
|
5206
|
+
var _a, e_1, _b, _c;
|
|
5207
|
+
let currentRealmId = null;
|
|
5208
|
+
let currentTable = null;
|
|
5209
|
+
let currentProp = null;
|
|
5210
|
+
let docsToInsert = [];
|
|
5211
|
+
function storeCollectedDocs(completedRealm) {
|
|
5212
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
5213
|
+
const lastDoc = docsToInsert[docsToInsert.length - 1];
|
|
5214
|
+
if (docsToInsert.length > 0) {
|
|
5215
|
+
if (!currentRealmId || !currentTable || !currentProp) {
|
|
5216
|
+
throw new Error(`Protocol error from ${databaseUrl}/y/download`);
|
|
5217
|
+
}
|
|
5218
|
+
const yTable = getUpdatesTable(db, currentTable, currentProp);
|
|
5219
|
+
yield yTable.bulkAdd(docsToInsert);
|
|
5220
|
+
docsToInsert = [];
|
|
5221
|
+
}
|
|
5222
|
+
if (currentRealmId &&
|
|
5223
|
+
((currentTable && currentProp && lastDoc) || completedRealm)) {
|
|
5224
|
+
yield db.$syncState.update('syncState', (syncState) => {
|
|
5225
|
+
const yDownloadedRealms = syncState.yDownloadedRealms || {};
|
|
5226
|
+
yDownloadedRealms[currentRealmId] = completedRealm
|
|
5227
|
+
? '*'
|
|
5228
|
+
: {
|
|
5229
|
+
tbl: currentTable,
|
|
5230
|
+
prop: currentProp,
|
|
5231
|
+
key: lastDoc.k,
|
|
5232
|
+
};
|
|
5233
|
+
syncState.yDownloadedRealms = yDownloadedRealms;
|
|
5234
|
+
});
|
|
5235
|
+
}
|
|
5236
|
+
});
|
|
5237
|
+
}
|
|
5238
|
+
try {
|
|
5239
|
+
try {
|
|
5240
|
+
for (var _d = true, chunks_1 = __asyncValues(chunks), chunks_1_1; chunks_1_1 = yield __await(chunks_1.next()), _a = chunks_1_1.done, !_a; _d = true) {
|
|
5241
|
+
_c = chunks_1_1.value;
|
|
5242
|
+
_d = false;
|
|
5243
|
+
const chunk = _c;
|
|
5244
|
+
const decoder = new Decoder(chunk);
|
|
5245
|
+
while (hasContent(decoder)) {
|
|
5246
|
+
switch (readUint8(decoder)) {
|
|
5247
|
+
case BINSTREAM_TYPE_REALMID:
|
|
5248
|
+
yield __await(storeCollectedDocs(true));
|
|
5249
|
+
currentRealmId = readVarString(decoder);
|
|
5250
|
+
break;
|
|
5251
|
+
case BINSTREAM_TYPE_TABLE_AND_PROP:
|
|
5252
|
+
yield __await(storeCollectedDocs(false)); // still on same realm
|
|
5253
|
+
currentTable = readVarString(decoder);
|
|
5254
|
+
currentProp = readVarString(decoder);
|
|
5255
|
+
break;
|
|
5256
|
+
case BINSTREAM_TYPE_DOCUMENT: {
|
|
5257
|
+
const k = readAny(decoder);
|
|
5258
|
+
const u = readVarUint8Array(decoder);
|
|
5259
|
+
docsToInsert.push({
|
|
5260
|
+
k,
|
|
5261
|
+
u,
|
|
5262
|
+
});
|
|
5263
|
+
break;
|
|
5264
|
+
}
|
|
5265
|
+
}
|
|
5266
|
+
}
|
|
5267
|
+
yield __await(storeCollectedDocs(false)); // Chunk full - migth still be on same realm
|
|
5268
|
+
}
|
|
5269
|
+
}
|
|
5270
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
5271
|
+
finally {
|
|
5272
|
+
try {
|
|
5273
|
+
if (!_d && !_a && (_b = chunks_1.return)) yield __await(_b.call(chunks_1));
|
|
5274
|
+
}
|
|
5275
|
+
finally { if (e_1) throw e_1.error; }
|
|
5276
|
+
}
|
|
5277
|
+
yield __await(storeCollectedDocs(true)); // Everything downloaded - finalize last downloaded realm to "*"
|
|
5278
|
+
}
|
|
5279
|
+
catch (error) {
|
|
5280
|
+
if (!(error instanceof Dexie.DexieError)) {
|
|
5281
|
+
// Network error might have happened.
|
|
5282
|
+
// Store what we've collected so far:
|
|
5283
|
+
yield __await(storeCollectedDocs(false));
|
|
5284
|
+
}
|
|
5285
|
+
throw error;
|
|
5286
|
+
}
|
|
5287
|
+
});
|
|
5288
|
+
}
|
|
5289
|
+
});
|
|
5290
|
+
}
|
|
5291
|
+
|
|
3871
5292
|
const CURRENT_SYNC_WORKER = 'currentSyncWorker';
|
|
3872
5293
|
function sync(db, options, schema, syncOptions) {
|
|
3873
5294
|
return _sync
|
|
@@ -3956,10 +5377,11 @@
|
|
|
3956
5377
|
//
|
|
3957
5378
|
// List changes to sync
|
|
3958
5379
|
//
|
|
3959
|
-
const [clientChangeSet, syncState, baseRevs] = yield db.transaction('r', db.tables, () => __awaiter(this, void 0, void 0, function* () {
|
|
5380
|
+
const [clientChangeSet, syncState, baseRevs, { yMessages, lastUpdateIds }] = yield db.transaction('r', db.tables, () => __awaiter(this, void 0, void 0, function* () {
|
|
3960
5381
|
const syncState = yield db.getPersistedSyncState();
|
|
3961
5382
|
const baseRevs = yield db.$baseRevs.toArray();
|
|
3962
5383
|
let clientChanges = yield listClientChanges(mutationTables);
|
|
5384
|
+
const yResults = yield listYClientMessagesAndStateVector(db, tablesToSync);
|
|
3963
5385
|
throwIfCancelled(cancelToken);
|
|
3964
5386
|
if (doSyncify) {
|
|
3965
5387
|
const alreadySyncedRealms = [
|
|
@@ -3969,11 +5391,11 @@
|
|
|
3969
5391
|
const syncificationInserts = yield listSyncifiedChanges(tablesToSyncify, currentUser, schema, alreadySyncedRealms);
|
|
3970
5392
|
throwIfCancelled(cancelToken);
|
|
3971
5393
|
clientChanges = clientChanges.concat(syncificationInserts);
|
|
3972
|
-
return [clientChanges, syncState, baseRevs];
|
|
5394
|
+
return [clientChanges, syncState, baseRevs, yResults];
|
|
3973
5395
|
}
|
|
3974
|
-
return [clientChanges, syncState, baseRevs];
|
|
5396
|
+
return [clientChanges, syncState, baseRevs, yResults];
|
|
3975
5397
|
}));
|
|
3976
|
-
const pushSyncIsNeeded = clientChangeSet.some((set) => set.muts.some((mut) => mut.keys.length > 0));
|
|
5398
|
+
const pushSyncIsNeeded = clientChangeSet.some((set) => set.muts.some((mut) => mut.keys.length > 0)) || yMessages.some(m => m.type === 'u-c');
|
|
3977
5399
|
if (justCheckIfNeeded) {
|
|
3978
5400
|
console.debug('Sync is needed:', pushSyncIsNeeded);
|
|
3979
5401
|
return pushSyncIsNeeded;
|
|
@@ -3988,12 +5410,12 @@
|
|
|
3988
5410
|
// Push changes to server
|
|
3989
5411
|
//
|
|
3990
5412
|
throwIfCancelled(cancelToken);
|
|
3991
|
-
const res = yield syncWithServer(clientChangeSet, syncState, baseRevs, db, databaseUrl, schema, clientIdentity, currentUser);
|
|
5413
|
+
const res = yield syncWithServer(clientChangeSet, yMessages, syncState, baseRevs, db, databaseUrl, schema, clientIdentity, currentUser);
|
|
3992
5414
|
console.debug('Sync response', res);
|
|
3993
5415
|
//
|
|
3994
5416
|
// Apply changes locally and clear old change entries:
|
|
3995
5417
|
//
|
|
3996
|
-
const done = yield db.transaction('rw', db.tables, (tx) => __awaiter(this, void 0, void 0, function* () {
|
|
5418
|
+
const { done, newSyncState } = yield db.transaction('rw', db.tables, (tx) => __awaiter(this, void 0, void 0, function* () {
|
|
3997
5419
|
// @ts-ignore
|
|
3998
5420
|
tx.idbtrans.disableChangeTracking = true;
|
|
3999
5421
|
// @ts-ignore
|
|
@@ -4085,17 +5507,35 @@
|
|
|
4085
5507
|
// apply server changes
|
|
4086
5508
|
//
|
|
4087
5509
|
yield applyServerChanges(filteredChanges, db);
|
|
5510
|
+
if (res.yMessages) {
|
|
5511
|
+
//
|
|
5512
|
+
// apply yMessages
|
|
5513
|
+
//
|
|
5514
|
+
const receivedUntils = yield applyYServerMessages(res.yMessages, db);
|
|
5515
|
+
//
|
|
5516
|
+
// update Y SyncStates
|
|
5517
|
+
//
|
|
5518
|
+
yield updateYSyncStates(lastUpdateIds, receivedUntils, db, res.serverRevision);
|
|
5519
|
+
}
|
|
4088
5520
|
//
|
|
4089
|
-
// Update syncState
|
|
5521
|
+
// Update regular syncState
|
|
4090
5522
|
//
|
|
4091
5523
|
db.$syncState.put(newSyncState, 'syncState');
|
|
4092
|
-
return
|
|
5524
|
+
return {
|
|
5525
|
+
done: addedClientChanges.length === 0,
|
|
5526
|
+
newSyncState
|
|
5527
|
+
};
|
|
4093
5528
|
}));
|
|
4094
5529
|
if (!done) {
|
|
4095
5530
|
console.debug('MORE SYNC NEEDED. Go for it again!');
|
|
4096
5531
|
yield checkSyncRateLimitDelay(db);
|
|
4097
5532
|
return yield _sync(db, options, schema, { isInitialSync, cancelToken });
|
|
4098
5533
|
}
|
|
5534
|
+
const usingYProps = Object.values(schema).some(tbl => { var _a; return (_a = tbl.yProps) === null || _a === void 0 ? void 0 : _a.length; });
|
|
5535
|
+
const serverSupportsYprops = !!res.yMessages;
|
|
5536
|
+
if (usingYProps && serverSupportsYprops) {
|
|
5537
|
+
yield downloadYDocsFromServer(db, databaseUrl, newSyncState);
|
|
5538
|
+
}
|
|
4099
5539
|
console.debug('SYNC DONE', { isInitialSync });
|
|
4100
5540
|
db.syncCompleteEvent.next();
|
|
4101
5541
|
return false; // Not needed anymore
|
|
@@ -4148,6 +5588,18 @@
|
|
|
4148
5588
|
}
|
|
4149
5589
|
}
|
|
4150
5590
|
}
|
|
5591
|
+
if (rejectedRealms.size > 0) {
|
|
5592
|
+
// Remove rejected/deleted realms from yDownloadedRealms because of the following use case:
|
|
5593
|
+
// 1. User becomes added to the realm
|
|
5594
|
+
// 2. User syncs and all documents of the realm is downloaded (downloadYDocsFromServer.ts)
|
|
5595
|
+
// 3. User leaves the realm and all docs are deleted locally (built-in-trigger of deleting their rows in this file)
|
|
5596
|
+
// 4. User is yet again added to the realm. At this point, we must make sure the docs are not considered already downloaded.
|
|
5597
|
+
const updateSpec = {};
|
|
5598
|
+
for (const realmId of rejectedRealms) {
|
|
5599
|
+
updateSpec[`yDownloadedRealms.${realmId}`] = undefined; // Setting to undefined will delete the property
|
|
5600
|
+
}
|
|
5601
|
+
yield db.$syncState.update('syncState', updateSpec);
|
|
5602
|
+
}
|
|
4151
5603
|
});
|
|
4152
5604
|
}
|
|
4153
5605
|
function filterServerChangesThroughAddedClientChanges(serverChanges, addedClientChanges) {
|
|
@@ -4457,6 +5909,7 @@
|
|
|
4457
5909
|
};
|
|
4458
5910
|
Object.assign(db, helperMethods);
|
|
4459
5911
|
db.messageConsumer = MessagesFromServerConsumer(db);
|
|
5912
|
+
db.messageProducer = new rxjs.Subject();
|
|
4460
5913
|
wm.set(dx.cloud, db);
|
|
4461
5914
|
}
|
|
4462
5915
|
return db;
|
|
@@ -4486,24 +5939,6 @@
|
|
|
4486
5939
|
const DISABLE_SERVICEWORKER_STRATEGY = (isSafari && safariVersion <= 605) || // Disable for Safari for now.
|
|
4487
5940
|
isFirefox; // Disable for Firefox for now. Seems to have a bug in reading CryptoKeys from IDB from service workers
|
|
4488
5941
|
|
|
4489
|
-
/* Helper function to subscribe to database close no matter if it was unexpectedly closed or manually using db.close()
|
|
4490
|
-
*/
|
|
4491
|
-
function dbOnClosed(db, handler) {
|
|
4492
|
-
db.on.close.subscribe(handler);
|
|
4493
|
-
// @ts-ignore
|
|
4494
|
-
const origClose = db._close;
|
|
4495
|
-
// @ts-ignore
|
|
4496
|
-
db._close = function () {
|
|
4497
|
-
origClose.call(this);
|
|
4498
|
-
handler();
|
|
4499
|
-
};
|
|
4500
|
-
return () => {
|
|
4501
|
-
db.on.close.unsubscribe(handler);
|
|
4502
|
-
// @ts-ignore
|
|
4503
|
-
db._close = origClose;
|
|
4504
|
-
};
|
|
4505
|
-
}
|
|
4506
|
-
|
|
4507
5942
|
const IS_SERVICE_WORKER = typeof self !== "undefined" && "clients" in self && !self.document;
|
|
4508
5943
|
|
|
4509
5944
|
function throwVersionIncrementNeeded() {
|
|
@@ -4969,13 +6404,18 @@
|
|
|
4969
6404
|
values = values.filter((_, idx) => !failures[idx]);
|
|
4970
6405
|
}
|
|
4971
6406
|
const ts = Date.now();
|
|
6407
|
+
// Canonicalize req.criteria.index to null if it's on the primary key.
|
|
6408
|
+
const criteria = 'criteria' in req && req.criteria
|
|
6409
|
+
? Object.assign(Object.assign({}, req.criteria), { index: req.criteria.index === schema.primaryKey.keyPath // Use null to inform server that criteria is on primary key
|
|
6410
|
+
? null // This will disable the server from trying to log consistent operations where it shouldnt.
|
|
6411
|
+
: req.criteria.index }) : undefined;
|
|
4972
6412
|
const mut = req.type === 'delete'
|
|
4973
6413
|
? {
|
|
4974
6414
|
type: 'delete',
|
|
4975
6415
|
ts,
|
|
4976
6416
|
opNo,
|
|
4977
6417
|
keys,
|
|
4978
|
-
criteria
|
|
6418
|
+
criteria,
|
|
4979
6419
|
txid,
|
|
4980
6420
|
userId,
|
|
4981
6421
|
}
|
|
@@ -4989,14 +6429,14 @@
|
|
|
4989
6429
|
userId,
|
|
4990
6430
|
values,
|
|
4991
6431
|
}
|
|
4992
|
-
:
|
|
6432
|
+
: criteria && req.changeSpec
|
|
4993
6433
|
? {
|
|
4994
6434
|
// Common changeSpec for all keys
|
|
4995
6435
|
type: 'modify',
|
|
4996
6436
|
ts,
|
|
4997
6437
|
opNo,
|
|
4998
6438
|
keys,
|
|
4999
|
-
criteria
|
|
6439
|
+
criteria,
|
|
5000
6440
|
changeSpec: req.changeSpec,
|
|
5001
6441
|
txid,
|
|
5002
6442
|
userId,
|
|
@@ -5024,7 +6464,7 @@
|
|
|
5024
6464
|
if ('isAdditionalChunk' in req && req.isAdditionalChunk) {
|
|
5025
6465
|
mut.isAdditionalChunk = true;
|
|
5026
6466
|
}
|
|
5027
|
-
return keys.length > 0 ||
|
|
6467
|
+
return keys.length > 0 || criteria
|
|
5028
6468
|
? mutsTable
|
|
5029
6469
|
.mutate({ type: 'add', trans, values: [mut] }) // Log entry
|
|
5030
6470
|
.then(() => res) // Return original response
|
|
@@ -5038,6 +6478,7 @@
|
|
|
5038
6478
|
|
|
5039
6479
|
function overrideParseStoresSpec(origFunc, dexie) {
|
|
5040
6480
|
return function (stores, dbSchema) {
|
|
6481
|
+
var _a;
|
|
5041
6482
|
const storesClone = Object.assign(Object.assign({}, DEXIE_CLOUD_SCHEMA), stores);
|
|
5042
6483
|
// Merge indexes of DEXIE_CLOUD_SCHEMA with stores
|
|
5043
6484
|
Object.keys(DEXIE_CLOUD_SCHEMA).forEach((tableName) => {
|
|
@@ -5098,6 +6539,14 @@
|
|
|
5098
6539
|
}
|
|
5099
6540
|
});
|
|
5100
6541
|
const rv = origFunc.call(this, storesClone, dbSchema);
|
|
6542
|
+
for (const [tableName, spec] of Object.entries(dbSchema)) {
|
|
6543
|
+
if ((_a = spec.yProps) === null || _a === void 0 ? void 0 : _a.length) {
|
|
6544
|
+
const cloudTableSchema = cloudSchema[tableName];
|
|
6545
|
+
if (cloudTableSchema) {
|
|
6546
|
+
cloudTableSchema.yProps = spec.yProps.map((yProp) => yProp.prop);
|
|
6547
|
+
}
|
|
6548
|
+
}
|
|
6549
|
+
}
|
|
5101
6550
|
return rv;
|
|
5102
6551
|
};
|
|
5103
6552
|
}
|
|
@@ -5183,31 +6632,90 @@
|
|
|
5183
6632
|
}
|
|
5184
6633
|
}
|
|
5185
6634
|
|
|
6635
|
+
function createYClientUpdateObservable(db) {
|
|
6636
|
+
const yTableRecords = flatten(db.tables
|
|
6637
|
+
.filter((table) => { var _a, _b; return ((_b = (_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[table.name]) === null || _b === void 0 ? void 0 : _b.markedForSync) && table.schema.yProps; })
|
|
6638
|
+
.map((table) => table.schema.yProps.map((p) => ({
|
|
6639
|
+
table: table.name,
|
|
6640
|
+
ydocProp: p.prop,
|
|
6641
|
+
updatesTable: p.updatesTable,
|
|
6642
|
+
}))));
|
|
6643
|
+
return rxjs.merge(...yTableRecords.map(({ table, ydocProp, updatesTable }) => {
|
|
6644
|
+
// Per updates table (table+prop combo), we first read syncer.unsentFrom,
|
|
6645
|
+
// and then start listening for updates since that number.
|
|
6646
|
+
const yTbl = db.table(updatesTable);
|
|
6647
|
+
return rxjs.from(yTbl.get(DEXIE_CLOUD_SYNCER_ID)).pipe(rxjs.switchMap((syncer) => {
|
|
6648
|
+
let currentUnsentFrom = (syncer === null || syncer === void 0 ? void 0 : syncer.unsentFrom) || 1;
|
|
6649
|
+
return rxjs.from(Dexie.liveQuery(() => __awaiter(this, void 0, void 0, function* () {
|
|
6650
|
+
const addedUpdates = yield listUpdatesSince(yTbl, currentUnsentFrom);
|
|
6651
|
+
return addedUpdates
|
|
6652
|
+
.filter((update) => update.f && update.f & 1) // Only include local updates
|
|
6653
|
+
.map((update) => {
|
|
6654
|
+
return {
|
|
6655
|
+
type: 'u-c',
|
|
6656
|
+
table,
|
|
6657
|
+
prop: ydocProp,
|
|
6658
|
+
k: update.k,
|
|
6659
|
+
u: update.u,
|
|
6660
|
+
i: update.i,
|
|
6661
|
+
};
|
|
6662
|
+
});
|
|
6663
|
+
}))).pipe(rxjs.tap((addedUpdates) => {
|
|
6664
|
+
// Update currentUnsentFrom to only listen for updates that will be newer than the ones we emitted.
|
|
6665
|
+
// (Before, we did this within the liveQuery, but that caused a bug because
|
|
6666
|
+
// a cancelled emittion of a liveQuery would update the currentUnsentFrom without
|
|
6667
|
+
// emitting anything, leading to that we jumped over some updates. Here we update it
|
|
6668
|
+
// after the liveQuery has emitted its updates)
|
|
6669
|
+
if (addedUpdates.length > 0) {
|
|
6670
|
+
currentUnsentFrom = addedUpdates.at(-1).i + 1;
|
|
6671
|
+
}
|
|
6672
|
+
}));
|
|
6673
|
+
}));
|
|
6674
|
+
})).pipe(
|
|
6675
|
+
// Flatten the array of messages.
|
|
6676
|
+
// If messageProducer emits empty array, nothing is emitted
|
|
6677
|
+
// but if messageProducer emits array of messages, they are
|
|
6678
|
+
// emitted one by one.
|
|
6679
|
+
rxjs.mergeMap((messages) => messages), rxjs.tap((message) => {
|
|
6680
|
+
console.debug('dexie-cloud emitting y-c', message);
|
|
6681
|
+
}));
|
|
6682
|
+
}
|
|
6683
|
+
|
|
6684
|
+
function getAwarenessLibrary(db) {
|
|
6685
|
+
var _a, _b;
|
|
6686
|
+
if (!((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.awarenessProtocol)) {
|
|
6687
|
+
throw new Dexie.MissingAPIError('awarenessProtocol was not provided to db.cloud.configure(). Please import * as awarenessProtocol from "y-protocols/awareness".');
|
|
6688
|
+
}
|
|
6689
|
+
return (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.awarenessProtocol;
|
|
6690
|
+
}
|
|
6691
|
+
const awarenessWeakMap = new WeakMap();
|
|
6692
|
+
const getDocAwareness = (doc) => awarenessWeakMap.get(doc);
|
|
6693
|
+
|
|
5186
6694
|
const SERVER_PING_TIMEOUT = 20000;
|
|
5187
6695
|
const CLIENT_PING_INTERVAL = 30000;
|
|
5188
6696
|
const FAIL_RETRY_WAIT_TIME = 60000;
|
|
5189
6697
|
class WSObservable extends rxjs.Observable {
|
|
5190
|
-
constructor(
|
|
5191
|
-
super((subscriber) => new WSConnection(
|
|
6698
|
+
constructor(db, rev, realmSetHash, clientIdentity, messageProducer, webSocketStatus, user) {
|
|
6699
|
+
super((subscriber) => new WSConnection(db, rev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus));
|
|
5192
6700
|
}
|
|
5193
6701
|
}
|
|
5194
6702
|
let counter = 0;
|
|
5195
6703
|
class WSConnection extends rxjs.Subscription {
|
|
5196
|
-
constructor(
|
|
6704
|
+
constructor(db, rev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus) {
|
|
5197
6705
|
super(() => this.teardown());
|
|
5198
6706
|
this.id = ++counter;
|
|
6707
|
+
this.subscriptions = new Set();
|
|
5199
6708
|
this.reconnecting = false;
|
|
5200
|
-
console.debug('New WebSocket Connection', this.id,
|
|
5201
|
-
this.
|
|
6709
|
+
console.debug('New WebSocket Connection', this.id, user.accessToken ? 'authorized' : 'unauthorized');
|
|
6710
|
+
this.db = db;
|
|
6711
|
+
this.databaseUrl = db.cloud.options.databaseUrl;
|
|
5202
6712
|
this.rev = rev;
|
|
5203
6713
|
this.realmSetHash = realmSetHash;
|
|
5204
6714
|
this.clientIdentity = clientIdentity;
|
|
5205
|
-
this.
|
|
5206
|
-
this.tokenExpiration = tokenExpiration;
|
|
6715
|
+
this.user = user;
|
|
5207
6716
|
this.subscriber = subscriber;
|
|
5208
6717
|
this.lastUserActivity = new Date();
|
|
5209
6718
|
this.messageProducer = messageProducer;
|
|
5210
|
-
this.messageProducerSubscription = null;
|
|
5211
6719
|
this.webSocketStatus = webSocketStatus;
|
|
5212
6720
|
this.connect();
|
|
5213
6721
|
}
|
|
@@ -5228,10 +6736,10 @@
|
|
|
5228
6736
|
catch (_a) { }
|
|
5229
6737
|
}
|
|
5230
6738
|
this.ws = null;
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
this.messageProducerSubscription = null;
|
|
6739
|
+
for (const sub of this.subscriptions) {
|
|
6740
|
+
sub.unsubscribe();
|
|
5234
6741
|
}
|
|
6742
|
+
this.subscriptions.clear();
|
|
5235
6743
|
}
|
|
5236
6744
|
reconnect() {
|
|
5237
6745
|
if (this.reconnecting)
|
|
@@ -5264,7 +6772,8 @@
|
|
|
5264
6772
|
//console.debug('SyncStatus: DUBB: Ooops it was closed!');
|
|
5265
6773
|
return;
|
|
5266
6774
|
}
|
|
5267
|
-
|
|
6775
|
+
const tokenExpiration = this.user.accessTokenExpiration;
|
|
6776
|
+
if (tokenExpiration && tokenExpiration < new Date()) {
|
|
5268
6777
|
this.subscriber.error(new TokenExpiredError()); // Will be handled in connectWebSocket.ts.
|
|
5269
6778
|
return;
|
|
5270
6779
|
}
|
|
@@ -5319,13 +6828,13 @@
|
|
|
5319
6828
|
searchParams.set('rev', this.rev);
|
|
5320
6829
|
searchParams.set('realmsHash', this.realmSetHash);
|
|
5321
6830
|
searchParams.set('clientId', this.clientIdentity);
|
|
5322
|
-
if (this.
|
|
5323
|
-
searchParams.set('token', this.
|
|
6831
|
+
if (this.user.accessToken) {
|
|
6832
|
+
searchParams.set('token', this.user.accessToken);
|
|
5324
6833
|
}
|
|
5325
6834
|
// Connect the WebSocket to given url:
|
|
5326
6835
|
console.debug('dexie-cloud WebSocket create');
|
|
5327
6836
|
const ws = (this.ws = new WebSocket(`${wsUrl}/changes?${searchParams}`));
|
|
5328
|
-
|
|
6837
|
+
ws.binaryType = "arraybuffer";
|
|
5329
6838
|
ws.onclose = (event) => {
|
|
5330
6839
|
if (!this.pinger)
|
|
5331
6840
|
return;
|
|
@@ -5335,17 +6844,33 @@
|
|
|
5335
6844
|
ws.onmessage = (event) => {
|
|
5336
6845
|
if (!this.pinger)
|
|
5337
6846
|
return;
|
|
5338
|
-
console.debug('dexie-cloud WebSocket onmessage', event.data);
|
|
5339
6847
|
this.lastServerActivity = new Date();
|
|
5340
6848
|
try {
|
|
5341
|
-
const msg =
|
|
6849
|
+
const msg = typeof event.data === 'string'
|
|
6850
|
+
? TSON.parse(event.data)
|
|
6851
|
+
: decodeYMessage(new Uint8Array(event.data));
|
|
6852
|
+
console.debug('dexie-cloud WebSocket onmessage', msg.type, msg);
|
|
5342
6853
|
if (msg.type === 'error') {
|
|
5343
6854
|
throw new Error(`Error message from dexie-cloud: ${msg.error}`);
|
|
5344
6855
|
}
|
|
5345
|
-
if (msg.type === 'rev') {
|
|
6856
|
+
else if (msg.type === 'rev') {
|
|
5346
6857
|
this.rev = msg.rev; // No meaning but seems reasonable.
|
|
5347
6858
|
}
|
|
5348
|
-
if (msg.type
|
|
6859
|
+
else if (msg.type === 'aware') {
|
|
6860
|
+
const docCache = Dexie.DexieYProvider.getDocCache(this.db.dx);
|
|
6861
|
+
const doc = docCache.find(msg.table, msg.k, msg.prop);
|
|
6862
|
+
if (doc) {
|
|
6863
|
+
const awareness = getDocAwareness(doc);
|
|
6864
|
+
if (awareness) {
|
|
6865
|
+
const awap = getAwarenessLibrary(this.db);
|
|
6866
|
+
awap.applyAwarenessUpdate(awareness, msg.u, 'server');
|
|
6867
|
+
}
|
|
6868
|
+
}
|
|
6869
|
+
}
|
|
6870
|
+
else if (msg.type === 'u-ack' || msg.type === 'u-reject' || msg.type === 'u-s' || msg.type === 'in-sync') {
|
|
6871
|
+
applyYServerMessages([msg], this.db);
|
|
6872
|
+
}
|
|
6873
|
+
else if (msg.type !== 'pong') {
|
|
5349
6874
|
this.subscriber.next(msg);
|
|
5350
6875
|
}
|
|
5351
6876
|
}
|
|
@@ -5373,16 +6898,27 @@
|
|
|
5373
6898
|
}
|
|
5374
6899
|
};
|
|
5375
6900
|
});
|
|
5376
|
-
this.
|
|
5377
|
-
var _a;
|
|
6901
|
+
this.subscriptions.add(this.messageProducer.subscribe((msg) => {
|
|
6902
|
+
var _a, _b;
|
|
5378
6903
|
if (!this.closed) {
|
|
5379
6904
|
if (msg.type === 'ready' &&
|
|
5380
6905
|
this.webSocketStatus.value !== 'connected') {
|
|
5381
6906
|
this.webSocketStatus.next('connected');
|
|
5382
6907
|
}
|
|
5383
|
-
(
|
|
6908
|
+
console.debug('dexie-cloud WebSocket send', msg.type, msg);
|
|
6909
|
+
if (msg.type === 'ready') {
|
|
6910
|
+
(_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(TSON.stringify(msg));
|
|
6911
|
+
}
|
|
6912
|
+
else {
|
|
6913
|
+
// If it's not a "ready" message, it's an YMessage.
|
|
6914
|
+
// YMessages can be sent binary encoded.
|
|
6915
|
+
(_b = this.ws) === null || _b === void 0 ? void 0 : _b.send(encodeYMessage(msg));
|
|
6916
|
+
}
|
|
5384
6917
|
}
|
|
5385
|
-
});
|
|
6918
|
+
}));
|
|
6919
|
+
if (this.user.isLoggedIn && !isEagerSyncDisabled(this.db)) {
|
|
6920
|
+
this.subscriptions.add(createYClientUpdateObservable(this.db).subscribe(this.db.messageProducer));
|
|
6921
|
+
}
|
|
5386
6922
|
}
|
|
5387
6923
|
catch (error) {
|
|
5388
6924
|
this.pauseUntil = new Date(Date.now() + FAIL_RETRY_WAIT_TIME);
|
|
@@ -5424,7 +6960,7 @@
|
|
|
5424
6960
|
if (!((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl)) {
|
|
5425
6961
|
throw new Error(`No database URL to connect WebSocket to`);
|
|
5426
6962
|
}
|
|
5427
|
-
const
|
|
6963
|
+
const readyForChangesMessage = db.messageConsumer.readyToServe.pipe(filter((isReady) => isReady), // When consumer is ready for new messages, produce such a message to inform server about it
|
|
5428
6964
|
switchMap(() => db.getPersistedSyncState()), // We need the info on which server revision we are at:
|
|
5429
6965
|
filter((syncState) => syncState && syncState.serverRevision), // We wont send anything to server before inital sync has taken place
|
|
5430
6966
|
switchMap((syncState) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -5435,6 +6971,7 @@
|
|
|
5435
6971
|
realmSetHash: yield computeRealmSetHash(syncState)
|
|
5436
6972
|
});
|
|
5437
6973
|
})));
|
|
6974
|
+
const messageProducer = rxjs.merge(readyForChangesMessage, db.messageProducer);
|
|
5438
6975
|
function createObservable() {
|
|
5439
6976
|
return db.cloud.persistedSyncState.pipe(filter((syncState) => syncState === null || syncState === void 0 ? void 0 : syncState.serverRevision), // Don't connect before there's no initial sync performed.
|
|
5440
6977
|
take(1), // Don't continue waking up whenever syncState change
|
|
@@ -5461,7 +6998,7 @@
|
|
|
5461
6998
|
// If no new entries, server won't bother the client. If new entries, server sends only those
|
|
5462
6999
|
// and the baseRev of the last from same client-ID.
|
|
5463
7000
|
if (userLogin) {
|
|
5464
|
-
return new WSObservable(db
|
|
7001
|
+
return new WSObservable(db, db.cloud.persistedSyncState.value.serverRevision, realmSetHash, db.cloud.persistedSyncState.value.clientIdentity, messageProducer, db.cloud.webSocketStatus, userLogin);
|
|
5465
7002
|
}
|
|
5466
7003
|
else {
|
|
5467
7004
|
return rxjs.from([]);
|
|
@@ -6267,6 +7804,130 @@
|
|
|
6267
7804
|
})), []);
|
|
6268
7805
|
});
|
|
6269
7806
|
|
|
7807
|
+
function createYHandler(db) {
|
|
7808
|
+
return (provider) => {
|
|
7809
|
+
var _a;
|
|
7810
|
+
const awap = getAwarenessLibrary(db);
|
|
7811
|
+
const doc = provider.doc;
|
|
7812
|
+
const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
|
|
7813
|
+
if (!((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[parentTable].markedForSync)) {
|
|
7814
|
+
return; // The table that holds the doc is not marked for sync - leave it to dexie. No syncing, no awareness.
|
|
7815
|
+
}
|
|
7816
|
+
let awareness = new awap.Awareness(doc);
|
|
7817
|
+
awarenessWeakMap.set(doc, awareness);
|
|
7818
|
+
provider.awareness = awareness;
|
|
7819
|
+
awareness.on('update', ({ added, updated, removed }, origin) => {
|
|
7820
|
+
// Send the update
|
|
7821
|
+
const changedClients = added.concat(updated).concat(removed);
|
|
7822
|
+
const user = db.cloud.currentUser.value;
|
|
7823
|
+
if (origin !== 'server' && user.isLoggedIn && !isEagerSyncDisabled(db)) {
|
|
7824
|
+
const update = awap.encodeAwarenessUpdate(awareness, changedClients);
|
|
7825
|
+
db.messageProducer.next({
|
|
7826
|
+
type: 'aware',
|
|
7827
|
+
table: parentTable,
|
|
7828
|
+
prop: parentProp,
|
|
7829
|
+
k: doc.meta.parentId,
|
|
7830
|
+
u: update,
|
|
7831
|
+
});
|
|
7832
|
+
if (provider.destroyed) {
|
|
7833
|
+
// We're called from awareness.on('destroy') that did
|
|
7834
|
+
// removeAwarenessStates.
|
|
7835
|
+
// It's time to also send the doc-close message that dexie-cloud understands
|
|
7836
|
+
// and uses to stop subscribing for updates and awareness updates and brings
|
|
7837
|
+
// down the cached information in memory on the WS connection for this.
|
|
7838
|
+
db.messageProducer.next({
|
|
7839
|
+
type: 'doc-close',
|
|
7840
|
+
table: parentTable,
|
|
7841
|
+
prop: parentProp,
|
|
7842
|
+
k: doc.meta.parentId
|
|
7843
|
+
});
|
|
7844
|
+
}
|
|
7845
|
+
}
|
|
7846
|
+
});
|
|
7847
|
+
awareness.on('destroy', () => {
|
|
7848
|
+
// Signal to server that this provider is destroyed (the update event will be triggered, which
|
|
7849
|
+
// in turn will trigger db.messageProducer that will send the message to the server if WS is connected)
|
|
7850
|
+
awap.removeAwarenessStates(awareness, [doc.clientID], 'provider destroyed');
|
|
7851
|
+
});
|
|
7852
|
+
// Now wait til document is loaded and then open the document on the server
|
|
7853
|
+
provider.on('load', () => __awaiter(this, void 0, void 0, function* () {
|
|
7854
|
+
if (provider.destroyed)
|
|
7855
|
+
return;
|
|
7856
|
+
let connected = false;
|
|
7857
|
+
let currentFlowId = 1;
|
|
7858
|
+
const subscription = db.cloud.webSocketStatus.subscribe((wsStatus) => {
|
|
7859
|
+
if (provider.destroyed)
|
|
7860
|
+
return;
|
|
7861
|
+
// Keep "connected" state in a variable so we can check it after async operations
|
|
7862
|
+
connected = wsStatus === 'connected';
|
|
7863
|
+
// We are or got connected. Open the document on the server.
|
|
7864
|
+
const user = db.cloud.currentUser.value;
|
|
7865
|
+
if (wsStatus === "connected" && user.isLoggedIn && !isEagerSyncDisabled(db)) {
|
|
7866
|
+
++currentFlowId;
|
|
7867
|
+
openDocumentOnServer().catch(error => {
|
|
7868
|
+
console.warn(`Error catched in createYHandler.ts: ${error}`);
|
|
7869
|
+
});
|
|
7870
|
+
}
|
|
7871
|
+
});
|
|
7872
|
+
// Wait until WebSocket is connected
|
|
7873
|
+
provider.addCleanupHandler(subscription);
|
|
7874
|
+
/** Sends an 'doc-open' message to server whenever websocket becomes
|
|
7875
|
+
* connected, or if it is already connected.
|
|
7876
|
+
* The flow is aborted in case websocket is disconnected while querying
|
|
7877
|
+
* information required to compute the state vector. Flow is also
|
|
7878
|
+
* aborted in case document or provider has been destroyed during
|
|
7879
|
+
* the async parts of the task.
|
|
7880
|
+
*
|
|
7881
|
+
* The state vector is only computed from the updates that have occured
|
|
7882
|
+
* after the last full sync - which could very often be zero - in which
|
|
7883
|
+
* case no state vector is sent (then the server already knows us by
|
|
7884
|
+
* revision)
|
|
7885
|
+
*
|
|
7886
|
+
* When server gets the doc-open message, it will authorized us for
|
|
7887
|
+
* whether we are allowed to read / write to this document, and then
|
|
7888
|
+
* keep the cached information in memory on the WS connection for this
|
|
7889
|
+
* particular document, as well as subscribe to updates and awareness updates
|
|
7890
|
+
* from other clients on the document.
|
|
7891
|
+
*/
|
|
7892
|
+
function openDocumentOnServer(wsStatus) {
|
|
7893
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
7894
|
+
const myFlow = currentFlowId; // So we can abort when a new flow is started
|
|
7895
|
+
const yTbl = db.table(updatesTable);
|
|
7896
|
+
const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
|
|
7897
|
+
// After every await, check if we still should be working on this task.
|
|
7898
|
+
if (provider.destroyed || currentFlowId !== myFlow || !connected)
|
|
7899
|
+
return;
|
|
7900
|
+
const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
|
|
7901
|
+
const docOpenMsg = {
|
|
7902
|
+
type: 'doc-open',
|
|
7903
|
+
table: parentTable,
|
|
7904
|
+
prop: parentProp,
|
|
7905
|
+
k: parentId,
|
|
7906
|
+
serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
|
|
7907
|
+
};
|
|
7908
|
+
const serverUpdatesSinceLastSync = yield yTbl
|
|
7909
|
+
.where('i')
|
|
7910
|
+
.between(receivedUntil, Infinity, false)
|
|
7911
|
+
.filter((update) => Dexie.cmp(update.k, parentId) === 0 && // Only updates for this document
|
|
7912
|
+
((update.f || 0) & 1) === 0 // Don't include local changes
|
|
7913
|
+
)
|
|
7914
|
+
.toArray();
|
|
7915
|
+
// After every await, check if we still should be working on this task.
|
|
7916
|
+
if (provider.destroyed || currentFlowId !== myFlow || !connected)
|
|
7917
|
+
return;
|
|
7918
|
+
if (serverUpdatesSinceLastSync.length > 0) {
|
|
7919
|
+
const Y = $Y(db); // Get the Yjs library from Dexie constructor options
|
|
7920
|
+
const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
|
|
7921
|
+
const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
|
|
7922
|
+
docOpenMsg.sv = stateVector;
|
|
7923
|
+
}
|
|
7924
|
+
db.messageProducer.next(docOpenMsg);
|
|
7925
|
+
});
|
|
7926
|
+
}
|
|
7927
|
+
}));
|
|
7928
|
+
};
|
|
7929
|
+
}
|
|
7930
|
+
|
|
6270
7931
|
function getTiedRealmId(objectId) {
|
|
6271
7932
|
return 'rlm~' + objectId;
|
|
6272
7933
|
}
|
|
@@ -6303,8 +7964,9 @@
|
|
|
6303
7964
|
if (closed)
|
|
6304
7965
|
throw new Dexie.DatabaseClosedError();
|
|
6305
7966
|
}
|
|
6306
|
-
|
|
7967
|
+
dexie.once('close', () => {
|
|
6307
7968
|
subscriptions.forEach((subscription) => subscription.unsubscribe());
|
|
7969
|
+
subscriptions.splice(0, subscriptions.length);
|
|
6308
7970
|
closed = true;
|
|
6309
7971
|
localSyncWorker && localSyncWorker.stop();
|
|
6310
7972
|
localSyncWorker = null;
|
|
@@ -6313,7 +7975,7 @@
|
|
|
6313
7975
|
const syncComplete = new rxjs.Subject();
|
|
6314
7976
|
dexie.cloud = {
|
|
6315
7977
|
// @ts-ignore
|
|
6316
|
-
version: "4.0.
|
|
7978
|
+
version: "4.1.0-alpha.10",
|
|
6317
7979
|
options: Object.assign({}, DEFAULT_OPTIONS),
|
|
6318
7980
|
schema: null,
|
|
6319
7981
|
get currentUserId() {
|
|
@@ -6459,6 +8121,7 @@
|
|
|
6459
8121
|
throw new Error(`Internal error`); // options cannot be null if configuredProgramatically is set.
|
|
6460
8122
|
const newPersistedOptions = Object.assign({}, options);
|
|
6461
8123
|
delete newPersistedOptions.fetchTokens;
|
|
8124
|
+
delete newPersistedOptions.awarenessProtocol;
|
|
6462
8125
|
yield db.$syncState.put(newPersistedOptions, 'options');
|
|
6463
8126
|
}
|
|
6464
8127
|
if (((_h = db.cloud.options) === null || _h === void 0 ? void 0 : _h.tryUseServiceWorker) &&
|
|
@@ -6536,6 +8199,12 @@
|
|
|
6536
8199
|
currentUserEmitter.pipe(skip(1), take(1)),
|
|
6537
8200
|
db.cloud.persistedSyncState.pipe(skip(1), take(1)),
|
|
6538
8201
|
]));
|
|
8202
|
+
const yHandler = createYHandler(db);
|
|
8203
|
+
db.dx.on('y', yHandler);
|
|
8204
|
+
db.dx.once('close', () => {
|
|
8205
|
+
var _a;
|
|
8206
|
+
(_a = db.dx.on.y) === null || _a === void 0 ? void 0 : _a.unsubscribe(yHandler);
|
|
8207
|
+
});
|
|
6539
8208
|
}
|
|
6540
8209
|
// HERE: If requireAuth, do athentication now.
|
|
6541
8210
|
let changedUser = false;
|
|
@@ -6608,7 +8277,7 @@
|
|
|
6608
8277
|
}
|
|
6609
8278
|
}
|
|
6610
8279
|
// @ts-ignore
|
|
6611
|
-
dexieCloud.version = "4.0.
|
|
8280
|
+
dexieCloud.version = "4.1.0-alpha.10";
|
|
6612
8281
|
Dexie.Cloud = dexieCloud;
|
|
6613
8282
|
|
|
6614
8283
|
exports.default = dexieCloud;
|