dexie-cloud-addon 4.0.8 → 4.1.0-alpha.12

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.
Files changed (59) hide show
  1. package/dist/modern/DexieCloudOptions.d.ts +1 -0
  2. package/dist/modern/WSObservable.d.ts +9 -6
  3. package/dist/modern/db/DexieCloudDB.d.ts +2 -0
  4. package/dist/modern/db/entities/PersistedSyncState.d.ts +7 -0
  5. package/dist/modern/define-ydoc-trigger.d.ts +2 -0
  6. package/dist/modern/dexie-cloud-addon.d.ts +1 -0
  7. package/dist/modern/dexie-cloud-addon.js +1914 -63
  8. package/dist/modern/dexie-cloud-addon.js.map +1 -1
  9. package/dist/modern/dexie-cloud-addon.min.js +1 -1
  10. package/dist/modern/dexie-cloud-addon.min.js.map +1 -1
  11. package/dist/modern/service-worker.js +1761 -62
  12. package/dist/modern/service-worker.js.map +1 -1
  13. package/dist/modern/service-worker.min.js +1 -1
  14. package/dist/modern/service-worker.min.js.map +1 -1
  15. package/dist/modern/sync/DEXIE_CLOUD_SYNCER_ID.d.ts +1 -0
  16. package/dist/modern/sync/syncWithServer.d.ts +2 -2
  17. package/dist/modern/yjs/Y.d.ts +3 -0
  18. package/dist/modern/yjs/YDexieCloudSyncState.d.ts +4 -0
  19. package/dist/modern/yjs/YTable.d.ts +2 -0
  20. package/dist/modern/yjs/applyYMessages.d.ts +5 -0
  21. package/dist/modern/yjs/awareness.d.ts +4 -0
  22. package/dist/modern/yjs/createYClientUpdateObservable.d.ts +4 -0
  23. package/dist/modern/yjs/createYHandler.d.ts +5 -0
  24. package/dist/modern/yjs/downloadYDocsFromServer.d.ts +3 -0
  25. package/dist/modern/yjs/getUpdatesTable.d.ts +3 -0
  26. package/dist/modern/yjs/listUpdatesSince.d.ts +2 -0
  27. package/dist/modern/yjs/listYClientMessagesAndStateVector.d.ts +26 -0
  28. package/dist/modern/yjs/updateYSyncStates.d.ts +6 -0
  29. package/dist/umd/DexieCloudOptions.d.ts +1 -0
  30. package/dist/umd/WSObservable.d.ts +9 -6
  31. package/dist/umd/db/DexieCloudDB.d.ts +2 -0
  32. package/dist/umd/db/entities/PersistedSyncState.d.ts +7 -0
  33. package/dist/umd/define-ydoc-trigger.d.ts +2 -0
  34. package/dist/umd/dexie-cloud-addon.d.ts +1 -0
  35. package/dist/umd/dexie-cloud-addon.js +1912 -60
  36. package/dist/umd/dexie-cloud-addon.js.map +1 -1
  37. package/dist/umd/dexie-cloud-addon.min.js +1 -1
  38. package/dist/umd/dexie-cloud-addon.min.js.map +1 -1
  39. package/dist/umd/service-worker.js +1759 -60
  40. package/dist/umd/service-worker.js.map +1 -1
  41. package/dist/umd/service-worker.min.js +1 -1
  42. package/dist/umd/service-worker.min.js.map +1 -1
  43. package/dist/umd/sync/DEXIE_CLOUD_SYNCER_ID.d.ts +1 -0
  44. package/dist/umd/sync/syncWithServer.d.ts +2 -2
  45. package/dist/umd/yjs/Y.d.ts +3 -0
  46. package/dist/umd/yjs/YDexieCloudSyncState.d.ts +4 -0
  47. package/dist/umd/yjs/YTable.d.ts +2 -0
  48. package/dist/umd/yjs/applyYMessages.d.ts +5 -0
  49. package/dist/umd/yjs/awareness.d.ts +4 -0
  50. package/dist/umd/yjs/createYClientUpdateObservable.d.ts +4 -0
  51. package/dist/umd/yjs/createYHandler.d.ts +5 -0
  52. package/dist/umd/yjs/downloadYDocsFromServer.d.ts +3 -0
  53. package/dist/umd/yjs/getUpdatesTable.d.ts +3 -0
  54. package/dist/umd/yjs/listUpdatesSince.d.ts +2 -0
  55. package/dist/umd/yjs/listYClientMessagesAndStateVector.d.ts +26 -0
  56. package/dist/umd/yjs/updateYSyncStates.d.ts +6 -0
  57. package/package.json +5 -4
  58. package/dist/modern/helpers/dbOnClosed.d.ts +0 -2
  59. package/dist/umd/helpers/dbOnClosed.d.ts +0 -2
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * ==========================================================================
10
10
  *
11
- * Version 4.0.8, Tue Jun 04 2024
11
+ * Version 4.1.0-alpha.12, Wed Oct 16 2024
12
12
  *
13
13
  * https://dexie.org
14
14
  *
@@ -16,8 +16,8 @@
16
16
  *
17
17
  */
18
18
 
19
- import Dexie, { PropModification, cmp, liveQuery } from 'dexie';
20
- import { firstValueFrom, from as from$1, filter as filter$1, Observable as Observable$1, BehaviorSubject, Subject, fromEvent, of, merge, Subscription as Subscription$1, throwError, combineLatest, map as map$1, share, timer as timer$1 } from 'rxjs';
19
+ import Dexie, { PropModification, cmp, DexieYProvider, liveQuery, RangeSet } from 'dexie';
20
+ import { firstValueFrom, from as from$1, filter as filter$1, Observable as Observable$1, BehaviorSubject, Subject, fromEvent, of, merge, switchMap as switchMap$1, tap as tap$1, mergeMap as mergeMap$1, Subscription as Subscription$1, throwError, combineLatest, map as map$1, share, timer as timer$1 } from 'rxjs';
21
21
 
22
22
  /******************************************************************************
23
23
  Copyright (c) Microsoft Corporation.
@@ -467,6 +467,1075 @@ function getDbNameFromDbUrl(dbUrl) {
467
467
  : url.pathname.split('/')[1];
468
468
  }
469
469
 
470
+ /**
471
+ * Common Math expressions.
472
+ *
473
+ * @module math
474
+ */
475
+
476
+ const floor = Math.floor;
477
+ const abs = Math.abs;
478
+
479
+ /**
480
+ * @function
481
+ * @param {number} a
482
+ * @param {number} b
483
+ * @return {number} The smaller element of a and b
484
+ */
485
+ const min = (a, b) => a < b ? a : b;
486
+
487
+ /**
488
+ * @function
489
+ * @param {number} a
490
+ * @param {number} b
491
+ * @return {number} The bigger element of a and b
492
+ */
493
+ const max = (a, b) => a > b ? a : b;
494
+
495
+ /**
496
+ * @param {number} n
497
+ * @return {boolean} Wether n is negative. This function also differentiates between -0 and +0
498
+ */
499
+ const isNegativeZero = n => n !== 0 ? n < 0 : 1 / n < 0;
500
+
501
+ /* eslint-env browser */
502
+
503
+ const BIT7 = 64;
504
+ const BIT8 = 128;
505
+ const BITS6 = 63;
506
+ const BITS7 = 127;
507
+ /**
508
+ * @type {number}
509
+ */
510
+ const BITS31 = 0x7FFFFFFF;
511
+
512
+ /**
513
+ * Utility helpers for working with numbers.
514
+ *
515
+ * @module number
516
+ */
517
+
518
+
519
+ const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
520
+
521
+ /* c8 ignore next */
522
+ const isInteger = Number.isInteger || (num => typeof num === 'number' && isFinite(num) && floor(num) === num);
523
+
524
+ /**
525
+ * Utility module to work with Arrays.
526
+ *
527
+ * @module array
528
+ */
529
+
530
+
531
+ const isArray = Array.isArray;
532
+
533
+ /**
534
+ * @param {string} str
535
+ * @return {Uint8Array}
536
+ */
537
+ const _encodeUtf8Polyfill = str => {
538
+ const encodedString = unescape(encodeURIComponent(str));
539
+ const len = encodedString.length;
540
+ const buf = new Uint8Array(len);
541
+ for (let i = 0; i < len; i++) {
542
+ buf[i] = /** @type {number} */ (encodedString.codePointAt(i));
543
+ }
544
+ return buf
545
+ };
546
+
547
+ /* c8 ignore next */
548
+ const utf8TextEncoder = /** @type {TextEncoder} */ (typeof TextEncoder !== 'undefined' ? new TextEncoder() : null);
549
+
550
+ /**
551
+ * @param {string} str
552
+ * @return {Uint8Array}
553
+ */
554
+ const _encodeUtf8Native = str => utf8TextEncoder.encode(str);
555
+
556
+ /**
557
+ * @param {string} str
558
+ * @return {Uint8Array}
559
+ */
560
+ /* c8 ignore next */
561
+ const encodeUtf8 = utf8TextEncoder ? _encodeUtf8Native : _encodeUtf8Polyfill;
562
+
563
+ /* c8 ignore next */
564
+ let utf8TextDecoder = typeof TextDecoder === 'undefined' ? null : new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
565
+
566
+ /* c8 ignore start */
567
+ if (utf8TextDecoder && utf8TextDecoder.decode(new Uint8Array()).length === 1) {
568
+ // Safari doesn't handle BOM correctly.
569
+ // This fixes a bug in Safari 13.0.5 where it produces a BOM the first time it is called.
570
+ // utf8TextDecoder.decode(new Uint8Array()).length === 1 on the first call and
571
+ // utf8TextDecoder.decode(new Uint8Array()).length === 1 on the second call
572
+ // Another issue is that from then on no BOM chars are recognized anymore
573
+ /* c8 ignore next */
574
+ utf8TextDecoder = null;
575
+ }
576
+
577
+ /**
578
+ * Efficient schema-less binary encoding with support for variable length encoding.
579
+ *
580
+ * Use [lib0/encoding] with [lib0/decoding]. Every encoding function has a corresponding decoding function.
581
+ *
582
+ * Encodes numbers in little-endian order (least to most significant byte order)
583
+ * and is compatible with Golang's binary encoding (https://golang.org/pkg/encoding/binary/)
584
+ * which is also used in Protocol Buffers.
585
+ *
586
+ * ```js
587
+ * // encoding step
588
+ * const encoder = encoding.createEncoder()
589
+ * encoding.writeVarUint(encoder, 256)
590
+ * encoding.writeVarString(encoder, 'Hello world!')
591
+ * const buf = encoding.toUint8Array(encoder)
592
+ * ```
593
+ *
594
+ * ```js
595
+ * // decoding step
596
+ * const decoder = decoding.createDecoder(buf)
597
+ * decoding.readVarUint(decoder) // => 256
598
+ * decoding.readVarString(decoder) // => 'Hello world!'
599
+ * decoding.hasContent(decoder) // => false - all data is read
600
+ * ```
601
+ *
602
+ * @module encoding
603
+ */
604
+
605
+
606
+ /**
607
+ * A BinaryEncoder handles the encoding to an Uint8Array.
608
+ */
609
+ class Encoder {
610
+ constructor () {
611
+ this.cpos = 0;
612
+ this.cbuf = new Uint8Array(100);
613
+ /**
614
+ * @type {Array<Uint8Array>}
615
+ */
616
+ this.bufs = [];
617
+ }
618
+ }
619
+
620
+ /**
621
+ * The current length of the encoded data.
622
+ *
623
+ * @function
624
+ * @param {Encoder} encoder
625
+ * @return {number}
626
+ */
627
+ const length = encoder => {
628
+ let len = encoder.cpos;
629
+ for (let i = 0; i < encoder.bufs.length; i++) {
630
+ len += encoder.bufs[i].length;
631
+ }
632
+ return len
633
+ };
634
+
635
+ /**
636
+ * Transform to Uint8Array.
637
+ *
638
+ * @function
639
+ * @param {Encoder} encoder
640
+ * @return {Uint8Array} The created ArrayBuffer.
641
+ */
642
+ const toUint8Array = encoder => {
643
+ const uint8arr = new Uint8Array(length(encoder));
644
+ let curPos = 0;
645
+ for (let i = 0; i < encoder.bufs.length; i++) {
646
+ const d = encoder.bufs[i];
647
+ uint8arr.set(d, curPos);
648
+ curPos += d.length;
649
+ }
650
+ uint8arr.set(new Uint8Array(encoder.cbuf.buffer, 0, encoder.cpos), curPos);
651
+ return uint8arr
652
+ };
653
+
654
+ /**
655
+ * Verify that it is possible to write `len` bytes wtihout checking. If
656
+ * necessary, a new Buffer with the required length is attached.
657
+ *
658
+ * @param {Encoder} encoder
659
+ * @param {number} len
660
+ */
661
+ const verifyLen = (encoder, len) => {
662
+ const bufferLen = encoder.cbuf.length;
663
+ if (bufferLen - encoder.cpos < len) {
664
+ encoder.bufs.push(new Uint8Array(encoder.cbuf.buffer, 0, encoder.cpos));
665
+ encoder.cbuf = new Uint8Array(max(bufferLen, len) * 2);
666
+ encoder.cpos = 0;
667
+ }
668
+ };
669
+
670
+ /**
671
+ * Write one byte to the encoder.
672
+ *
673
+ * @function
674
+ * @param {Encoder} encoder
675
+ * @param {number} num The byte that is to be encoded.
676
+ */
677
+ const write = (encoder, num) => {
678
+ const bufferLen = encoder.cbuf.length;
679
+ if (encoder.cpos === bufferLen) {
680
+ encoder.bufs.push(encoder.cbuf);
681
+ encoder.cbuf = new Uint8Array(bufferLen * 2);
682
+ encoder.cpos = 0;
683
+ }
684
+ encoder.cbuf[encoder.cpos++] = num;
685
+ };
686
+
687
+ /**
688
+ * Write a variable length unsigned integer. Max encodable integer is 2^53.
689
+ *
690
+ * @function
691
+ * @param {Encoder} encoder
692
+ * @param {number} num The number that is to be encoded.
693
+ */
694
+ const writeVarUint = (encoder, num) => {
695
+ while (num > BITS7) {
696
+ write(encoder, BIT8 | (BITS7 & num));
697
+ num = floor(num / 128); // shift >>> 7
698
+ }
699
+ write(encoder, BITS7 & num);
700
+ };
701
+
702
+ /**
703
+ * Write a variable length integer.
704
+ *
705
+ * We use the 7th bit instead for signaling that this is a negative number.
706
+ *
707
+ * @function
708
+ * @param {Encoder} encoder
709
+ * @param {number} num The number that is to be encoded.
710
+ */
711
+ const writeVarInt = (encoder, num) => {
712
+ const isNegative = isNegativeZero(num);
713
+ if (isNegative) {
714
+ num = -num;
715
+ }
716
+ // |- whether to continue reading |- whether is negative |- number
717
+ write(encoder, (num > BITS6 ? BIT8 : 0) | (isNegative ? BIT7 : 0) | (BITS6 & num));
718
+ num = floor(num / 64); // shift >>> 6
719
+ // We don't need to consider the case of num === 0 so we can use a different
720
+ // pattern here than above.
721
+ while (num > 0) {
722
+ write(encoder, (num > BITS7 ? BIT8 : 0) | (BITS7 & num));
723
+ num = floor(num / 128); // shift >>> 7
724
+ }
725
+ };
726
+
727
+ /**
728
+ * A cache to store strings temporarily
729
+ */
730
+ const _strBuffer = new Uint8Array(30000);
731
+ const _maxStrBSize = _strBuffer.length / 3;
732
+
733
+ /**
734
+ * Write a variable length string.
735
+ *
736
+ * @function
737
+ * @param {Encoder} encoder
738
+ * @param {String} str The string that is to be encoded.
739
+ */
740
+ const _writeVarStringNative = (encoder, str) => {
741
+ if (str.length < _maxStrBSize) {
742
+ // We can encode the string into the existing buffer
743
+ /* c8 ignore next */
744
+ const written = utf8TextEncoder.encodeInto(str, _strBuffer).written || 0;
745
+ writeVarUint(encoder, written);
746
+ for (let i = 0; i < written; i++) {
747
+ write(encoder, _strBuffer[i]);
748
+ }
749
+ } else {
750
+ writeVarUint8Array(encoder, encodeUtf8(str));
751
+ }
752
+ };
753
+
754
+ /**
755
+ * Write a variable length string.
756
+ *
757
+ * @function
758
+ * @param {Encoder} encoder
759
+ * @param {String} str The string that is to be encoded.
760
+ */
761
+ const _writeVarStringPolyfill = (encoder, str) => {
762
+ const encodedString = unescape(encodeURIComponent(str));
763
+ const len = encodedString.length;
764
+ writeVarUint(encoder, len);
765
+ for (let i = 0; i < len; i++) {
766
+ write(encoder, /** @type {number} */ (encodedString.codePointAt(i)));
767
+ }
768
+ };
769
+
770
+ /**
771
+ * Write a variable length string.
772
+ *
773
+ * @function
774
+ * @param {Encoder} encoder
775
+ * @param {String} str The string that is to be encoded.
776
+ */
777
+ /* c8 ignore next */
778
+ const writeVarString = (utf8TextEncoder && /** @type {any} */ (utf8TextEncoder).encodeInto) ? _writeVarStringNative : _writeVarStringPolyfill;
779
+
780
+ /**
781
+ * Append fixed-length Uint8Array to the encoder.
782
+ *
783
+ * @function
784
+ * @param {Encoder} encoder
785
+ * @param {Uint8Array} uint8Array
786
+ */
787
+ const writeUint8Array = (encoder, uint8Array) => {
788
+ const bufferLen = encoder.cbuf.length;
789
+ const cpos = encoder.cpos;
790
+ const leftCopyLen = min(bufferLen - cpos, uint8Array.length);
791
+ const rightCopyLen = uint8Array.length - leftCopyLen;
792
+ encoder.cbuf.set(uint8Array.subarray(0, leftCopyLen), cpos);
793
+ encoder.cpos += leftCopyLen;
794
+ if (rightCopyLen > 0) {
795
+ // Still something to write, write right half..
796
+ // Append new buffer
797
+ encoder.bufs.push(encoder.cbuf);
798
+ // must have at least size of remaining buffer
799
+ encoder.cbuf = new Uint8Array(max(bufferLen * 2, rightCopyLen));
800
+ // copy array
801
+ encoder.cbuf.set(uint8Array.subarray(leftCopyLen));
802
+ encoder.cpos = rightCopyLen;
803
+ }
804
+ };
805
+
806
+ /**
807
+ * Append an Uint8Array to Encoder.
808
+ *
809
+ * @function
810
+ * @param {Encoder} encoder
811
+ * @param {Uint8Array} uint8Array
812
+ */
813
+ const writeVarUint8Array = (encoder, uint8Array) => {
814
+ writeVarUint(encoder, uint8Array.byteLength);
815
+ writeUint8Array(encoder, uint8Array);
816
+ };
817
+
818
+ /**
819
+ * Create an DataView of the next `len` bytes. Use it to write data after
820
+ * calling this function.
821
+ *
822
+ * ```js
823
+ * // write float32 using DataView
824
+ * const dv = writeOnDataView(encoder, 4)
825
+ * dv.setFloat32(0, 1.1)
826
+ * // read float32 using DataView
827
+ * const dv = readFromDataView(encoder, 4)
828
+ * dv.getFloat32(0) // => 1.100000023841858 (leaving it to the reader to find out why this is the correct result)
829
+ * ```
830
+ *
831
+ * @param {Encoder} encoder
832
+ * @param {number} len
833
+ * @return {DataView}
834
+ */
835
+ const writeOnDataView = (encoder, len) => {
836
+ verifyLen(encoder, len);
837
+ const dview = new DataView(encoder.cbuf.buffer, encoder.cpos, len);
838
+ encoder.cpos += len;
839
+ return dview
840
+ };
841
+
842
+ /**
843
+ * @param {Encoder} encoder
844
+ * @param {number} num
845
+ */
846
+ const writeFloat32 = (encoder, num) => writeOnDataView(encoder, 4).setFloat32(0, num, false);
847
+
848
+ /**
849
+ * @param {Encoder} encoder
850
+ * @param {number} num
851
+ */
852
+ const writeFloat64 = (encoder, num) => writeOnDataView(encoder, 8).setFloat64(0, num, false);
853
+
854
+ /**
855
+ * @param {Encoder} encoder
856
+ * @param {bigint} num
857
+ */
858
+ const writeBigInt64 = (encoder, num) => /** @type {any} */ (writeOnDataView(encoder, 8)).setBigInt64(0, num, false);
859
+
860
+ /**
861
+ * @param {Encoder} encoder
862
+ * @param {bigint} num
863
+ */
864
+ const writeBigUint64 = (encoder, num) => /** @type {any} */ (writeOnDataView(encoder, 8)).setBigUint64(0, num, false);
865
+
866
+ const floatTestBed = new DataView(new ArrayBuffer(4));
867
+ /**
868
+ * Check if a number can be encoded as a 32 bit float.
869
+ *
870
+ * @param {number} num
871
+ * @return {boolean}
872
+ */
873
+ const isFloat32 = num => {
874
+ floatTestBed.setFloat32(0, num);
875
+ return floatTestBed.getFloat32(0) === num
876
+ };
877
+
878
+ /**
879
+ * Encode data with efficient binary format.
880
+ *
881
+ * Differences to JSON:
882
+ * • Transforms data to a binary format (not to a string)
883
+ * • Encodes undefined, NaN, and ArrayBuffer (these can't be represented in JSON)
884
+ * • Numbers are efficiently encoded either as a variable length integer, as a
885
+ * 32 bit float, as a 64 bit float, or as a 64 bit bigint.
886
+ *
887
+ * Encoding table:
888
+ *
889
+ * | Data Type | Prefix | Encoding Method | Comment |
890
+ * | ------------------- | -------- | ------------------ | ------- |
891
+ * | undefined | 127 | | Functions, symbol, and everything that cannot be identified is encoded as undefined |
892
+ * | null | 126 | | |
893
+ * | integer | 125 | writeVarInt | Only encodes 32 bit signed integers |
894
+ * | float32 | 124 | writeFloat32 | |
895
+ * | float64 | 123 | writeFloat64 | |
896
+ * | bigint | 122 | writeBigInt64 | |
897
+ * | boolean (false) | 121 | | True and false are different data types so we save the following byte |
898
+ * | boolean (true) | 120 | | - 0b01111000 so the last bit determines whether true or false |
899
+ * | string | 119 | writeVarString | |
900
+ * | object<string,any> | 118 | custom | Writes {length} then {length} key-value pairs |
901
+ * | array<any> | 117 | custom | Writes {length} then {length} json values |
902
+ * | Uint8Array | 116 | writeVarUint8Array | We use Uint8Array for any kind of binary data |
903
+ *
904
+ * Reasons for the decreasing prefix:
905
+ * We need the first bit for extendability (later we may want to encode the
906
+ * prefix with writeVarUint). The remaining 7 bits are divided as follows:
907
+ * [0-30] the beginning of the data range is used for custom purposes
908
+ * (defined by the function that uses this library)
909
+ * [31-127] the end of the data range is used for data encoding by
910
+ * lib0/encoding.js
911
+ *
912
+ * @param {Encoder} encoder
913
+ * @param {undefined|null|number|bigint|boolean|string|Object<string,any>|Array<any>|Uint8Array} data
914
+ */
915
+ const writeAny = (encoder, data) => {
916
+ switch (typeof data) {
917
+ case 'string':
918
+ // TYPE 119: STRING
919
+ write(encoder, 119);
920
+ writeVarString(encoder, data);
921
+ break
922
+ case 'number':
923
+ if (isInteger(data) && abs(data) <= BITS31) {
924
+ // TYPE 125: INTEGER
925
+ write(encoder, 125);
926
+ writeVarInt(encoder, data);
927
+ } else if (isFloat32(data)) {
928
+ // TYPE 124: FLOAT32
929
+ write(encoder, 124);
930
+ writeFloat32(encoder, data);
931
+ } else {
932
+ // TYPE 123: FLOAT64
933
+ write(encoder, 123);
934
+ writeFloat64(encoder, data);
935
+ }
936
+ break
937
+ case 'bigint':
938
+ // TYPE 122: BigInt
939
+ write(encoder, 122);
940
+ writeBigInt64(encoder, data);
941
+ break
942
+ case 'object':
943
+ if (data === null) {
944
+ // TYPE 126: null
945
+ write(encoder, 126);
946
+ } else if (isArray(data)) {
947
+ // TYPE 117: Array
948
+ write(encoder, 117);
949
+ writeVarUint(encoder, data.length);
950
+ for (let i = 0; i < data.length; i++) {
951
+ writeAny(encoder, data[i]);
952
+ }
953
+ } else if (data instanceof Uint8Array) {
954
+ // TYPE 116: ArrayBuffer
955
+ write(encoder, 116);
956
+ writeVarUint8Array(encoder, data);
957
+ } else {
958
+ // TYPE 118: Object
959
+ write(encoder, 118);
960
+ const keys = Object.keys(data);
961
+ writeVarUint(encoder, keys.length);
962
+ for (let i = 0; i < keys.length; i++) {
963
+ const key = keys[i];
964
+ writeVarString(encoder, key);
965
+ writeAny(encoder, data[key]);
966
+ }
967
+ }
968
+ break
969
+ case 'boolean':
970
+ // TYPE 120/121: boolean (true/false)
971
+ write(encoder, data ? 120 : 121);
972
+ break
973
+ default:
974
+ // TYPE 127: undefined
975
+ write(encoder, 127);
976
+ }
977
+ };
978
+
979
+ function encodeYMessage(msg) {
980
+ const encoder = new Encoder();
981
+ writeVarString(encoder, msg.type);
982
+ writeVarString(encoder, msg.table);
983
+ writeVarString(encoder, msg.prop);
984
+ switch (msg.type) {
985
+ case 'u-ack':
986
+ case 'u-reject':
987
+ writeBigUint64(encoder, BigInt(msg.i));
988
+ break;
989
+ default:
990
+ writeAny(encoder, msg.k);
991
+ switch (msg.type) {
992
+ case 'aware':
993
+ writeVarUint8Array(encoder, msg.u);
994
+ break;
995
+ case 'doc-open':
996
+ writeAny(encoder, msg.serverRev);
997
+ writeAny(encoder, msg.sv);
998
+ break;
999
+ case 'doc-close':
1000
+ break;
1001
+ case 'sv':
1002
+ writeVarUint8Array(encoder, msg.sv);
1003
+ break;
1004
+ case 'u-c':
1005
+ writeVarUint8Array(encoder, msg.u);
1006
+ writeBigUint64(encoder, BigInt(msg.i));
1007
+ break;
1008
+ case 'u-s':
1009
+ writeVarUint8Array(encoder, msg.u);
1010
+ break;
1011
+ }
1012
+ }
1013
+ return toUint8Array(encoder);
1014
+ }
1015
+
1016
+ /**
1017
+ * Error helpers.
1018
+ *
1019
+ * @module error
1020
+ */
1021
+
1022
+ /**
1023
+ * @param {string} s
1024
+ * @return {Error}
1025
+ */
1026
+ /* c8 ignore next */
1027
+ const create = s => new Error(s);
1028
+
1029
+ /**
1030
+ * Efficient schema-less binary decoding with support for variable length encoding.
1031
+ *
1032
+ * Use [lib0/decoding] with [lib0/encoding]. Every encoding function has a corresponding decoding function.
1033
+ *
1034
+ * Encodes numbers in little-endian order (least to most significant byte order)
1035
+ * and is compatible with Golang's binary encoding (https://golang.org/pkg/encoding/binary/)
1036
+ * which is also used in Protocol Buffers.
1037
+ *
1038
+ * ```js
1039
+ * // encoding step
1040
+ * const encoder = encoding.createEncoder()
1041
+ * encoding.writeVarUint(encoder, 256)
1042
+ * encoding.writeVarString(encoder, 'Hello world!')
1043
+ * const buf = encoding.toUint8Array(encoder)
1044
+ * ```
1045
+ *
1046
+ * ```js
1047
+ * // decoding step
1048
+ * const decoder = decoding.createDecoder(buf)
1049
+ * decoding.readVarUint(decoder) // => 256
1050
+ * decoding.readVarString(decoder) // => 'Hello world!'
1051
+ * decoding.hasContent(decoder) // => false - all data is read
1052
+ * ```
1053
+ *
1054
+ * @module decoding
1055
+ */
1056
+
1057
+
1058
+ const errorUnexpectedEndOfArray = create('Unexpected end of array');
1059
+ const errorIntegerOutOfRange = create('Integer out of Range');
1060
+
1061
+ /**
1062
+ * A Decoder handles the decoding of an Uint8Array.
1063
+ */
1064
+ class Decoder {
1065
+ /**
1066
+ * @param {Uint8Array} uint8Array Binary data to decode
1067
+ */
1068
+ constructor (uint8Array) {
1069
+ /**
1070
+ * Decoding target.
1071
+ *
1072
+ * @type {Uint8Array}
1073
+ */
1074
+ this.arr = uint8Array;
1075
+ /**
1076
+ * Current decoding position.
1077
+ *
1078
+ * @type {number}
1079
+ */
1080
+ this.pos = 0;
1081
+ }
1082
+ }
1083
+
1084
+ /**
1085
+ * @function
1086
+ * @param {Decoder} decoder
1087
+ * @return {boolean}
1088
+ */
1089
+ const hasContent = decoder => decoder.pos !== decoder.arr.length;
1090
+
1091
+ /**
1092
+ * Create an Uint8Array view of the next `len` bytes and advance the position by `len`.
1093
+ *
1094
+ * Important: The Uint8Array still points to the underlying ArrayBuffer. Make sure to discard the result as soon as possible to prevent any memory leaks.
1095
+ * Use `buffer.copyUint8Array` to copy the result into a new Uint8Array.
1096
+ *
1097
+ * @function
1098
+ * @param {Decoder} decoder The decoder instance
1099
+ * @param {number} len The length of bytes to read
1100
+ * @return {Uint8Array}
1101
+ */
1102
+ const readUint8Array = (decoder, len) => {
1103
+ const view = new Uint8Array(decoder.arr.buffer, decoder.pos + decoder.arr.byteOffset, len);
1104
+ decoder.pos += len;
1105
+ return view
1106
+ };
1107
+
1108
+ /**
1109
+ * Read variable length Uint8Array.
1110
+ *
1111
+ * Important: The Uint8Array still points to the underlying ArrayBuffer. Make sure to discard the result as soon as possible to prevent any memory leaks.
1112
+ * Use `buffer.copyUint8Array` to copy the result into a new Uint8Array.
1113
+ *
1114
+ * @function
1115
+ * @param {Decoder} decoder
1116
+ * @return {Uint8Array}
1117
+ */
1118
+ const readVarUint8Array = decoder => readUint8Array(decoder, readVarUint(decoder));
1119
+
1120
+ /**
1121
+ * Read one byte as unsigned integer.
1122
+ * @function
1123
+ * @param {Decoder} decoder The decoder instance
1124
+ * @return {number} Unsigned 8-bit integer
1125
+ */
1126
+ const readUint8 = decoder => decoder.arr[decoder.pos++];
1127
+
1128
+ /**
1129
+ * Read unsigned integer (32bit) with variable length.
1130
+ * 1/8th of the storage is used as encoding overhead.
1131
+ * * numbers < 2^7 is stored in one bytlength
1132
+ * * numbers < 2^14 is stored in two bylength
1133
+ *
1134
+ * @function
1135
+ * @param {Decoder} decoder
1136
+ * @return {number} An unsigned integer.length
1137
+ */
1138
+ const readVarUint = decoder => {
1139
+ let num = 0;
1140
+ let mult = 1;
1141
+ const len = decoder.arr.length;
1142
+ while (decoder.pos < len) {
1143
+ const r = decoder.arr[decoder.pos++];
1144
+ // num = num | ((r & binary.BITS7) << len)
1145
+ num = num + (r & BITS7) * mult; // shift $r << (7*#iterations) and add it to num
1146
+ mult *= 128; // next iteration, shift 7 "more" to the left
1147
+ if (r < BIT8) {
1148
+ return num
1149
+ }
1150
+ /* c8 ignore start */
1151
+ if (num > MAX_SAFE_INTEGER) {
1152
+ throw errorIntegerOutOfRange
1153
+ }
1154
+ /* c8 ignore stop */
1155
+ }
1156
+ throw errorUnexpectedEndOfArray
1157
+ };
1158
+
1159
+ /**
1160
+ * Read signed integer (32bit) with variable length.
1161
+ * 1/8th of the storage is used as encoding overhead.
1162
+ * * numbers < 2^7 is stored in one bytlength
1163
+ * * numbers < 2^14 is stored in two bylength
1164
+ * @todo This should probably create the inverse ~num if number is negative - but this would be a breaking change.
1165
+ *
1166
+ * @function
1167
+ * @param {Decoder} decoder
1168
+ * @return {number} An unsigned integer.length
1169
+ */
1170
+ const readVarInt = decoder => {
1171
+ let r = decoder.arr[decoder.pos++];
1172
+ let num = r & BITS6;
1173
+ let mult = 64;
1174
+ const sign = (r & BIT7) > 0 ? -1 : 1;
1175
+ if ((r & BIT8) === 0) {
1176
+ // don't continue reading
1177
+ return sign * num
1178
+ }
1179
+ const len = decoder.arr.length;
1180
+ while (decoder.pos < len) {
1181
+ r = decoder.arr[decoder.pos++];
1182
+ // num = num | ((r & binary.BITS7) << len)
1183
+ num = num + (r & BITS7) * mult;
1184
+ mult *= 128;
1185
+ if (r < BIT8) {
1186
+ return sign * num
1187
+ }
1188
+ /* c8 ignore start */
1189
+ if (num > MAX_SAFE_INTEGER) {
1190
+ throw errorIntegerOutOfRange
1191
+ }
1192
+ /* c8 ignore stop */
1193
+ }
1194
+ throw errorUnexpectedEndOfArray
1195
+ };
1196
+
1197
+ /**
1198
+ * We don't test this function anymore as we use native decoding/encoding by default now.
1199
+ * Better not modify this anymore..
1200
+ *
1201
+ * Transforming utf8 to a string is pretty expensive. The code performs 10x better
1202
+ * when String.fromCodePoint is fed with all characters as arguments.
1203
+ * But most environments have a maximum number of arguments per functions.
1204
+ * For effiency reasons we apply a maximum of 10000 characters at once.
1205
+ *
1206
+ * @function
1207
+ * @param {Decoder} decoder
1208
+ * @return {String} The read String.
1209
+ */
1210
+ /* c8 ignore start */
1211
+ const _readVarStringPolyfill = decoder => {
1212
+ let remainingLen = readVarUint(decoder);
1213
+ if (remainingLen === 0) {
1214
+ return ''
1215
+ } else {
1216
+ let encodedString = String.fromCodePoint(readUint8(decoder)); // remember to decrease remainingLen
1217
+ if (--remainingLen < 100) { // do not create a Uint8Array for small strings
1218
+ while (remainingLen--) {
1219
+ encodedString += String.fromCodePoint(readUint8(decoder));
1220
+ }
1221
+ } else {
1222
+ while (remainingLen > 0) {
1223
+ const nextLen = remainingLen < 10000 ? remainingLen : 10000;
1224
+ // this is dangerous, we create a fresh array view from the existing buffer
1225
+ const bytes = decoder.arr.subarray(decoder.pos, decoder.pos + nextLen);
1226
+ decoder.pos += nextLen;
1227
+ // Starting with ES5.1 we can supply a generic array-like object as arguments
1228
+ encodedString += String.fromCodePoint.apply(null, /** @type {any} */ (bytes));
1229
+ remainingLen -= nextLen;
1230
+ }
1231
+ }
1232
+ return decodeURIComponent(escape(encodedString))
1233
+ }
1234
+ };
1235
+ /* c8 ignore stop */
1236
+
1237
+ /**
1238
+ * @function
1239
+ * @param {Decoder} decoder
1240
+ * @return {String} The read String
1241
+ */
1242
+ const _readVarStringNative = decoder =>
1243
+ /** @type any */ (utf8TextDecoder).decode(readVarUint8Array(decoder));
1244
+
1245
+ /**
1246
+ * Read string of variable length
1247
+ * * varUint is used to store the length of the string
1248
+ *
1249
+ * @function
1250
+ * @param {Decoder} decoder
1251
+ * @return {String} The read String
1252
+ *
1253
+ */
1254
+ /* c8 ignore next */
1255
+ const readVarString = utf8TextDecoder ? _readVarStringNative : _readVarStringPolyfill;
1256
+
1257
+ /**
1258
+ * @param {Decoder} decoder
1259
+ * @param {number} len
1260
+ * @return {DataView}
1261
+ */
1262
+ const readFromDataView = (decoder, len) => {
1263
+ const dv = new DataView(decoder.arr.buffer, decoder.arr.byteOffset + decoder.pos, len);
1264
+ decoder.pos += len;
1265
+ return dv
1266
+ };
1267
+
1268
+ /**
1269
+ * @param {Decoder} decoder
1270
+ */
1271
+ const readFloat32 = decoder => readFromDataView(decoder, 4).getFloat32(0, false);
1272
+
1273
+ /**
1274
+ * @param {Decoder} decoder
1275
+ */
1276
+ const readFloat64 = decoder => readFromDataView(decoder, 8).getFloat64(0, false);
1277
+
1278
+ /**
1279
+ * @param {Decoder} decoder
1280
+ */
1281
+ const readBigInt64 = decoder => /** @type {any} */ (readFromDataView(decoder, 8)).getBigInt64(0, false);
1282
+
1283
+ /**
1284
+ * @param {Decoder} decoder
1285
+ */
1286
+ const readBigUint64 = decoder => /** @type {any} */ (readFromDataView(decoder, 8)).getBigUint64(0, false);
1287
+
1288
+ /**
1289
+ * @type {Array<function(Decoder):any>}
1290
+ */
1291
+ const readAnyLookupTable = [
1292
+ decoder => undefined, // CASE 127: undefined
1293
+ decoder => null, // CASE 126: null
1294
+ readVarInt, // CASE 125: integer
1295
+ readFloat32, // CASE 124: float32
1296
+ readFloat64, // CASE 123: float64
1297
+ readBigInt64, // CASE 122: bigint
1298
+ decoder => false, // CASE 121: boolean (false)
1299
+ decoder => true, // CASE 120: boolean (true)
1300
+ readVarString, // CASE 119: string
1301
+ decoder => { // CASE 118: object<string,any>
1302
+ const len = readVarUint(decoder);
1303
+ /**
1304
+ * @type {Object<string,any>}
1305
+ */
1306
+ const obj = {};
1307
+ for (let i = 0; i < len; i++) {
1308
+ const key = readVarString(decoder);
1309
+ obj[key] = readAny(decoder);
1310
+ }
1311
+ return obj
1312
+ },
1313
+ decoder => { // CASE 117: array<any>
1314
+ const len = readVarUint(decoder);
1315
+ const arr = [];
1316
+ for (let i = 0; i < len; i++) {
1317
+ arr.push(readAny(decoder));
1318
+ }
1319
+ return arr
1320
+ },
1321
+ readVarUint8Array // CASE 116: Uint8Array
1322
+ ];
1323
+
1324
+ /**
1325
+ * @param {Decoder} decoder
1326
+ */
1327
+ const readAny = decoder => readAnyLookupTable[127 - readUint8(decoder)](decoder);
1328
+
1329
+ function decodeYMessage(a) {
1330
+ const decoder = new Decoder(a);
1331
+ const type = readVarString(decoder);
1332
+ const table = readVarString(decoder);
1333
+ const prop = readVarString(decoder);
1334
+ switch (type) {
1335
+ case 'u-ack':
1336
+ case 'u-reject':
1337
+ return {
1338
+ type,
1339
+ table,
1340
+ prop,
1341
+ i: Number(readBigUint64(decoder)),
1342
+ };
1343
+ default: {
1344
+ const k = readAny(decoder);
1345
+ switch (type) {
1346
+ case 'in-sync':
1347
+ return { type, table, prop, k };
1348
+ case 'aware':
1349
+ return {
1350
+ type,
1351
+ table,
1352
+ prop,
1353
+ k,
1354
+ u: readVarUint8Array(decoder),
1355
+ };
1356
+ case 'doc-open':
1357
+ return {
1358
+ type,
1359
+ table,
1360
+ prop,
1361
+ k,
1362
+ serverRev: readAny(decoder),
1363
+ sv: readAny(decoder),
1364
+ };
1365
+ case 'doc-close':
1366
+ return { type, table, prop, k };
1367
+ case 'sv':
1368
+ return {
1369
+ type,
1370
+ table,
1371
+ prop,
1372
+ k,
1373
+ sv: readVarUint8Array(decoder),
1374
+ };
1375
+ case 'u-c':
1376
+ return {
1377
+ type,
1378
+ table,
1379
+ prop,
1380
+ k,
1381
+ u: readVarUint8Array(decoder),
1382
+ i: Number(readBigUint64(decoder)),
1383
+ };
1384
+ case 'u-s':
1385
+ return {
1386
+ type,
1387
+ table,
1388
+ prop,
1389
+ k,
1390
+ u: readVarUint8Array(decoder)
1391
+ };
1392
+ default:
1393
+ throw new TypeError(`Unknown message type: ${type}`);
1394
+ }
1395
+ }
1396
+ }
1397
+ }
1398
+
1399
+ async function asyncIterablePipeline(source, ...stages) {
1400
+ var _a, e_1, _b, _c;
1401
+ // Chain generators by sending outdata from one to another
1402
+ let result = source(); // Start with the source generator
1403
+ for (let i = 0; i < stages.length; i++) {
1404
+ result = stages[i](result); // Pass on the result to next generator
1405
+ }
1406
+ try {
1407
+ // Start running the machine. If the last stage is a sink, it will consume the data and never emit anything
1408
+ // to us here...
1409
+ 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) {
1410
+ _c = result_1_1.value;
1411
+ _d = false;
1412
+ const chunk = _c;
1413
+ }
1414
+ }
1415
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
1416
+ finally {
1417
+ try {
1418
+ if (!_d && !_a && (_b = result_1.return)) await _b.call(result_1);
1419
+ }
1420
+ finally { if (e_1) throw e_1.error; }
1421
+ }
1422
+ }
1423
+
1424
+ function consumeChunkedBinaryStream(source) {
1425
+ return __asyncGenerator(this, arguments, function* consumeChunkedBinaryStream_1() {
1426
+ var _a, e_1, _b, _c;
1427
+ let state = 0;
1428
+ let sizeBuf = new Uint8Array(4);
1429
+ let sizeBufPos = 0;
1430
+ let bufs = [];
1431
+ let len = 0;
1432
+ try {
1433
+ 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) {
1434
+ _c = source_1_1.value;
1435
+ _d = false;
1436
+ const chunk = _c;
1437
+ const dw = new DataView(chunk.buffer, chunk.byteOffset, chunk.byteLength);
1438
+ let pos = 0;
1439
+ while (pos < chunk.byteLength) {
1440
+ switch (state) {
1441
+ case 0:
1442
+ // Beginning of a size header
1443
+ if (pos + 4 > chunk.byteLength) {
1444
+ for (const b of chunk.slice(pos)) {
1445
+ if (sizeBufPos === 4)
1446
+ break;
1447
+ sizeBuf[sizeBufPos++] = b;
1448
+ ++pos;
1449
+ }
1450
+ if (sizeBufPos < 4) {
1451
+ // Need more bytes in order to read length.
1452
+ // Will go out from while loop as well because pos is defenitely = chunk.byteLength here.
1453
+ break;
1454
+ }
1455
+ }
1456
+ else if (sizeBufPos > 0 && sizeBufPos < 4) {
1457
+ for (const b of chunk.slice(pos, pos + 4 - sizeBufPos)) {
1458
+ sizeBuf[sizeBufPos++] = b;
1459
+ ++pos;
1460
+ }
1461
+ }
1462
+ // Intentional fall-through...
1463
+ case 1:
1464
+ len =
1465
+ sizeBufPos === 4
1466
+ ? new DataView(sizeBuf.buffer, 0, 4).getUint32(0, false)
1467
+ : dw.getUint32(pos, false);
1468
+ if (sizeBufPos)
1469
+ sizeBufPos = 0; // in this case pos is already forwarded
1470
+ else
1471
+ pos += 4; // else pos is not yet forwarded - that's why we do it now
1472
+ // Intentional fall-through...
1473
+ case 2:
1474
+ // Eat the chunk
1475
+ if (pos >= chunk.byteLength) {
1476
+ state = 2;
1477
+ break;
1478
+ }
1479
+ if (pos + len > chunk.byteLength) {
1480
+ bufs.push(chunk.slice(pos));
1481
+ len -= (chunk.byteLength - pos);
1482
+ state = 2;
1483
+ pos = chunk.byteLength; // will break while loop.
1484
+ }
1485
+ else {
1486
+ if (bufs.length > 0) {
1487
+ const concats = new Uint8Array(bufs.reduce((p, c) => p + c.byteLength, len));
1488
+ let p = 0;
1489
+ for (const buf of bufs) {
1490
+ concats.set(buf, p);
1491
+ p += buf.byteLength;
1492
+ }
1493
+ concats.set(chunk.slice(pos, pos + len), p);
1494
+ bufs = [];
1495
+ yield yield __await(concats);
1496
+ }
1497
+ else {
1498
+ yield yield __await(chunk.slice(pos, pos + len));
1499
+ }
1500
+ pos += len;
1501
+ state = 0;
1502
+ }
1503
+ break;
1504
+ }
1505
+ }
1506
+ }
1507
+ }
1508
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
1509
+ finally {
1510
+ try {
1511
+ if (!_d && !_a && (_b = source_1.return)) yield __await(_b.call(source_1));
1512
+ }
1513
+ finally { if (e_1) throw e_1.error; }
1514
+ }
1515
+ });
1516
+ }
1517
+
1518
+ function getFetchResponseBodyGenerator(res) {
1519
+ return function () {
1520
+ return __asyncGenerator(this, arguments, function* () {
1521
+ if (!res.body)
1522
+ throw new Error("Response body is not readable");
1523
+ const reader = res.body.getReader();
1524
+ try {
1525
+ while (true) {
1526
+ const { done, value } = yield __await(reader.read());
1527
+ if (done)
1528
+ return yield __await(void 0);
1529
+ yield yield __await(value);
1530
+ }
1531
+ }
1532
+ finally {
1533
+ reader.releaseLock();
1534
+ }
1535
+ });
1536
+ };
1537
+ }
1538
+
470
1539
  function isFunction(value) {
471
1540
  return typeof value === 'function';
472
1541
  }
@@ -3614,7 +4683,7 @@ function updateSyncRateLimitDelays(db, res) {
3614
4683
  }
3615
4684
 
3616
4685
  //import {BisonWebStreamReader} from "dreambase-library/dist/typeson-simplified/BisonWebStreamReader";
3617
- function syncWithServer(changes, syncState, baseRevs, db, databaseUrl, schema, clientIdentity, currentUser) {
4686
+ function syncWithServer(changes, y, syncState, baseRevs, db, databaseUrl, schema, clientIdentity, currentUser) {
3618
4687
  return __awaiter(this, void 0, void 0, function* () {
3619
4688
  //
3620
4689
  // Push changes to server using fetch
@@ -3652,6 +4721,7 @@ function syncWithServer(changes, syncState, baseRevs, db, databaseUrl, schema, c
3652
4721
  : undefined,
3653
4722
  baseRevs,
3654
4723
  changes: encodeIdsForServer(db.dx.core.schema, currentUser, changes),
4724
+ y,
3655
4725
  };
3656
4726
  console.debug('Sync request', syncRequest);
3657
4727
  db.syncStateChangedEvent.next({
@@ -3865,6 +4935,375 @@ function applyServerChanges(changes, db) {
3865
4935
  });
3866
4936
  }
3867
4937
 
4938
+ const DEXIE_CLOUD_SYNCER_ID = 'dexie-cloud-syncer';
4939
+
4940
+ function listUpdatesSince(yTable, sinceIncluding) {
4941
+ return yTable
4942
+ .where('i')
4943
+ .between(sinceIncluding, Infinity, true)
4944
+ .toArray();
4945
+ }
4946
+
4947
+ function $Y$1(db) {
4948
+ const $Y = db.dx._options.Y;
4949
+ if (!$Y)
4950
+ throw new Error('Y library not supplied to Dexie constructor');
4951
+ return $Y;
4952
+ }
4953
+
4954
+ /** Queries the local database for YMessages to send to server.
4955
+ *
4956
+ * There are 2 messages that this function can provide:
4957
+ * YUpdateFromClientRequest ( for local updates )
4958
+ * YStateVector ( for state vector of foreign updates so that server can reduce the number of udpates to send back )
4959
+ *
4960
+ * Notice that we do not do a step 1 sync phase here to get a state vector from the server. Reason we can avoid
4961
+ * the 2-step sync is that we are client-server and not client-client here and we keep track of the client changes
4962
+ * sent to server by letting server acknowledge them. There is always a chance that some client update has already
4963
+ * been sent and that the client failed to receive the ack. However, if this happens it does not matter - the change
4964
+ * would be sent again and Yjs handles duplicate changes anyway. And it's rare so we earn the cost of roundtrips by
4965
+ * avoiding the step1 sync and instead keep track of this in the `unsentFrom` property of the SyncState.
4966
+ *
4967
+ * @param db
4968
+ * @returns
4969
+ */
4970
+ function listYClientMessagesAndStateVector(db, tablesToSync) {
4971
+ return __awaiter(this, void 0, void 0, function* () {
4972
+ const result = [];
4973
+ const lastUpdateIds = {};
4974
+ for (const table of tablesToSync) {
4975
+ if (table.schema.yProps) {
4976
+ for (const yProp of table.schema.yProps) {
4977
+ const Y = $Y$1(db); // This is how we retrieve the user-provided Y library
4978
+ const yTable = db.table(yProp.updatesTable); // the updates-table for this combo of table+propName
4979
+ const syncState = (yield yTable.get(DEXIE_CLOUD_SYNCER_ID));
4980
+ // unsentFrom = the `i` value of updates that aren't yet sent to server (or at least not acked by the server yet)
4981
+ const unsentFrom = (syncState === null || syncState === void 0 ? void 0 : syncState.unsentFrom) || 1;
4982
+ // 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
4983
+ const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
4984
+ // Compute the least value of these two (but since receivedUntil is inclusive we need to add +1 to it)
4985
+ const unsyncedFrom = Math.min(unsentFrom, receivedUntil + 1);
4986
+ // Query all these updates for all docs of this table+prop combination
4987
+ const updates = yield listUpdatesSince(yTable, unsyncedFrom);
4988
+ if (updates.length > 0)
4989
+ lastUpdateIds[yTable.name] = updates[updates.length - 1].i;
4990
+ // Now sort them by document and whether they are local or not + ignore local updates already sent:
4991
+ const perDoc = {};
4992
+ for (const update of updates) {
4993
+ // Sort updates into buckets of the doc primary key + the flag (whether it's local or foreign)
4994
+ const isLocal = ((update.f || 0) & 0x01) === 0x01;
4995
+ if (isLocal && update.i < unsentFrom)
4996
+ continue; // This local update has already been sent and acked.
4997
+ const docKey = JSON.stringify(update.k) + '/' + isLocal;
4998
+ let entry = perDoc[docKey];
4999
+ if (!entry) {
5000
+ perDoc[docKey] = entry = {
5001
+ i: update.i,
5002
+ k: update.k,
5003
+ isLocal,
5004
+ u: [],
5005
+ };
5006
+ entry.u.push(update.u);
5007
+ }
5008
+ else {
5009
+ entry.u.push(update.u);
5010
+ entry.i = Math.max(update.i, entry.i);
5011
+ }
5012
+ }
5013
+ // Now, go through all these and:
5014
+ // * For local updates, compute a merged update per document.
5015
+ // * For foreign updates, compute a state vector to pass to server, so that server can
5016
+ // avoid re-sending updates that we already have (they might have been sent of websocket
5017
+ // and when that happens, we do not mark them in any way nor do we update receivedUntil -
5018
+ // we only update receivedUntil after a "full sync" (syncWithServer()))
5019
+ for (const { k, isLocal, u, i } of Object.values(perDoc)) {
5020
+ const mergedUpdate = u.length === 1 ? u[0] : Y.mergeUpdatesV2(u);
5021
+ if (isLocal) {
5022
+ result.push({
5023
+ type: 'u-c',
5024
+ table: table.name,
5025
+ prop: yProp.prop,
5026
+ k,
5027
+ u: mergedUpdate,
5028
+ i,
5029
+ });
5030
+ }
5031
+ else {
5032
+ const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
5033
+ result.push({
5034
+ type: 'sv',
5035
+ table: table.name,
5036
+ prop: yProp.prop,
5037
+ k,
5038
+ sv: stateVector,
5039
+ });
5040
+ }
5041
+ }
5042
+ }
5043
+ }
5044
+ }
5045
+ return {
5046
+ yMessages: result,
5047
+ lastUpdateIds
5048
+ };
5049
+ });
5050
+ }
5051
+
5052
+ function getUpdatesTable(db, table, ydocProp) {
5053
+ var _a, _b, _c;
5054
+ 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;
5055
+ if (!utbl)
5056
+ throw new Error(`No updatesTable found for ${table}.${ydocProp}`);
5057
+ return db.table(utbl);
5058
+ }
5059
+
5060
+ function applyYServerMessages(yMessages, db) {
5061
+ var _a;
5062
+ return __awaiter(this, void 0, void 0, function* () {
5063
+ const result = {};
5064
+ for (const m of yMessages) {
5065
+ switch (m.type) {
5066
+ case 'u-s': {
5067
+ const utbl = getUpdatesTable(db, m.table, m.prop);
5068
+ result[utbl.name] = yield utbl.add({
5069
+ k: m.k,
5070
+ u: m.u,
5071
+ });
5072
+ break;
5073
+ }
5074
+ case 'u-ack': {
5075
+ const utbl = getUpdatesTable(db, m.table, m.prop);
5076
+ yield db.transaction('rw', utbl, (tx) => __awaiter(this, void 0, void 0, function* () {
5077
+ let syncer = (yield tx
5078
+ .table(utbl.name)
5079
+ .get(DEXIE_CLOUD_SYNCER_ID));
5080
+ 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) }));
5081
+ }));
5082
+ break;
5083
+ }
5084
+ case 'u-reject': {
5085
+ // Acces control or constraint rejected the update.
5086
+ // We delete it. It's not going to be sent again.
5087
+ // What's missing is a way to notify consumers, such as Tiptap editor, that the update was rejected.
5088
+ // This is only an issue when the document is open. We could find the open document and
5089
+ // in a perfect world, we should send a reverse update to the open document to undo the change.
5090
+ // See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
5091
+ console.debug(`Y update rejected. Deleting it.`);
5092
+ const utbl = getUpdatesTable(db, m.table, m.prop);
5093
+ // Delete the rejected update and all local updates since (avoid holes in the CRDT)
5094
+ // and destroy it's open document if there is one.
5095
+ const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
5096
+ if (primaryKey != null) {
5097
+ yield db.transaction('rw', utbl, tx => {
5098
+ // @ts-ignore
5099
+ tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
5100
+ return utbl
5101
+ .where('i')
5102
+ .aboveOrEqual(m.i)
5103
+ .filter(u => cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
5104
+ .delete();
5105
+ });
5106
+ // Destroy active doc
5107
+ const activeDoc = DexieYProvider.getDocCache(db.dx).find(m.table, primaryKey, m.prop);
5108
+ if (activeDoc)
5109
+ activeDoc.destroy(); // Destroy the document so that editors don't continue to work on it
5110
+ }
5111
+ break;
5112
+ }
5113
+ case 'in-sync': {
5114
+ const doc = DexieYProvider.getDocCache(db.dx).find(m.table, m.k, m.prop);
5115
+ if (doc && !doc.isSynced) {
5116
+ doc.emit('sync', [true]);
5117
+ }
5118
+ break;
5119
+ }
5120
+ }
5121
+ }
5122
+ return result;
5123
+ });
5124
+ }
5125
+
5126
+ function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db, serverRevision) {
5127
+ var _a, _b, _c, _d, _e;
5128
+ return __awaiter(this, void 0, void 0, function* () {
5129
+ // We want to update unsentFrom for each yTable to the value specified in first argument
5130
+ // because we got those values before we synced with server and here we are back from server
5131
+ // that has successfully received all those messages - no matter if the last update was a client or server update,
5132
+ // we can safely store unsentFrom to a value of the last update + 1 here.
5133
+ // We also want to update receivedUntil for each yTable to the value specified in the second argument,
5134
+ // because that contains the highest resulted id of each update from server after storing it.
5135
+ // We could do these two tasks separately, but that would require two update calls on the same YSyncState, so
5136
+ // to optimize the dexie calls, we merge these two maps into a single one so we can do a single update request
5137
+ // per yTable.
5138
+ const mergedSpec = {};
5139
+ for (const [yTable, lastUpdateId] of Object.entries(lastUpdateIdsBeforeSync)) {
5140
+ (_a = mergedSpec[yTable]) !== null && _a !== void 0 ? _a : (mergedSpec[yTable] = {});
5141
+ mergedSpec[yTable].unsentFrom = lastUpdateId + 1;
5142
+ }
5143
+ for (const [yTable, lastUpdateId] of Object.entries(receivedUntilsAfterSync)) {
5144
+ (_b = mergedSpec[yTable]) !== null && _b !== void 0 ? _b : (mergedSpec[yTable] = {});
5145
+ mergedSpec[yTable].receivedUntil = lastUpdateId;
5146
+ }
5147
+ // Now go through all yTables and update their YSyncStates:
5148
+ const allYTables = Object.values(db.dx._dbSchema)
5149
+ .filter((tblSchema) => tblSchema.yProps)
5150
+ .map((tblSchema) => tblSchema.yProps.map((yProp) => yProp.updatesTable))
5151
+ .flat();
5152
+ for (const yTable of allYTables) {
5153
+ const mergedEntry = mergedSpec[yTable];
5154
+ const unsentFrom = (_c = mergedEntry === null || mergedEntry === void 0 ? void 0 : mergedEntry.unsentFrom) !== null && _c !== void 0 ? _c : 1;
5155
+ const receivedUntil = (_e = (_d = mergedEntry === null || mergedEntry === void 0 ? void 0 : mergedEntry.receivedUntil) !== null && _d !== void 0 ? _d :
5156
+ // from local because we are in the same parent transaction (in sync.ts) that
5157
+ // applied all updates from the server
5158
+ (yield db
5159
+ .table(yTable)
5160
+ .where('i')
5161
+ .between(1, Infinity) // Because i might be string DEXIE_CLOUD_SYNCER_ID if not a number.
5162
+ .reverse()
5163
+ .limit(1)
5164
+ .primaryKeys())[0]) !== null && _e !== void 0 ? _e : 0;
5165
+ // We're already in a transaction, but for the sake of
5166
+ // code readability and correctness, let's launch an atomic sub transaction:
5167
+ yield db.transaction('rw', yTable, () => __awaiter(this, void 0, void 0, function* () {
5168
+ const state = yield db
5169
+ .table(yTable)
5170
+ .get(DEXIE_CLOUD_SYNCER_ID);
5171
+ if (!state) {
5172
+ yield db.table(yTable).add({
5173
+ i: DEXIE_CLOUD_SYNCER_ID,
5174
+ unsentFrom,
5175
+ receivedUntil,
5176
+ serverRev: serverRevision,
5177
+ });
5178
+ }
5179
+ else {
5180
+ state.unsentFrom = Math.max(unsentFrom, state.unsentFrom || 1);
5181
+ state.receivedUntil = Math.max(receivedUntil, state.receivedUntil || 0);
5182
+ state.serverRev = serverRevision;
5183
+ yield db.table(yTable).put(state);
5184
+ }
5185
+ }));
5186
+ }
5187
+ });
5188
+ }
5189
+
5190
+ const BINSTREAM_TYPE_REALMID = 1;
5191
+ const BINSTREAM_TYPE_TABLE_AND_PROP = 2;
5192
+ const BINSTREAM_TYPE_DOCUMENT = 3;
5193
+ function downloadYDocsFromServer(db, databaseUrl, { yDownloadedRealms, realms }) {
5194
+ return __awaiter(this, void 0, void 0, function* () {
5195
+ if (yDownloadedRealms &&
5196
+ realms &&
5197
+ realms.every((realmId) => yDownloadedRealms[realmId] === '*')) {
5198
+ return; // Already done!
5199
+ }
5200
+ console.debug('Downloading Y.Docs from added realms');
5201
+ const user = yield loadAccessToken(db);
5202
+ const headers = {
5203
+ 'Content-Type': 'application/json',
5204
+ Accept: 'application/octet-stream',
5205
+ };
5206
+ if (user) {
5207
+ headers.Authorization = `Bearer ${user.accessToken}`;
5208
+ }
5209
+ const res = yield fetch(`${databaseUrl}/y/download`, {
5210
+ body: TSON.stringify({ downloadedRealms: yDownloadedRealms || {} }),
5211
+ method: 'POST',
5212
+ headers,
5213
+ credentials: 'include',
5214
+ });
5215
+ if (!res.ok) {
5216
+ throw new Error(`Failed to download Yjs documents from server. Status: ${res.status}`);
5217
+ }
5218
+ yield asyncIterablePipeline(getFetchResponseBodyGenerator(res), consumeChunkedBinaryStream, consumeDownloadChunks);
5219
+ function consumeDownloadChunks(chunks) {
5220
+ return __asyncGenerator(this, arguments, function* consumeDownloadChunks_1() {
5221
+ var _a, e_1, _b, _c;
5222
+ let currentRealmId = null;
5223
+ let currentTable = null;
5224
+ let currentProp = null;
5225
+ let docsToInsert = [];
5226
+ function storeCollectedDocs(completedRealm) {
5227
+ return __awaiter(this, void 0, void 0, function* () {
5228
+ const lastDoc = docsToInsert[docsToInsert.length - 1];
5229
+ if (docsToInsert.length > 0) {
5230
+ if (!currentRealmId || !currentTable || !currentProp) {
5231
+ throw new Error(`Protocol error from ${databaseUrl}/y/download`);
5232
+ }
5233
+ const yTable = getUpdatesTable(db, currentTable, currentProp);
5234
+ yield yTable.bulkAdd(docsToInsert);
5235
+ docsToInsert = [];
5236
+ }
5237
+ if (currentRealmId &&
5238
+ ((currentTable && currentProp && lastDoc) || completedRealm)) {
5239
+ yield db.$syncState.update('syncState', (syncState) => {
5240
+ const yDownloadedRealms = syncState.yDownloadedRealms || {};
5241
+ yDownloadedRealms[currentRealmId] = completedRealm
5242
+ ? '*'
5243
+ : {
5244
+ tbl: currentTable,
5245
+ prop: currentProp,
5246
+ key: lastDoc.k,
5247
+ };
5248
+ syncState.yDownloadedRealms = yDownloadedRealms;
5249
+ });
5250
+ }
5251
+ });
5252
+ }
5253
+ try {
5254
+ try {
5255
+ 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) {
5256
+ _c = chunks_1_1.value;
5257
+ _d = false;
5258
+ const chunk = _c;
5259
+ const decoder = new Decoder(chunk);
5260
+ while (hasContent(decoder)) {
5261
+ switch (readUint8(decoder)) {
5262
+ case BINSTREAM_TYPE_REALMID:
5263
+ yield __await(storeCollectedDocs(true));
5264
+ currentRealmId = readVarString(decoder);
5265
+ break;
5266
+ case BINSTREAM_TYPE_TABLE_AND_PROP:
5267
+ yield __await(storeCollectedDocs(false)); // still on same realm
5268
+ currentTable = readVarString(decoder);
5269
+ currentProp = readVarString(decoder);
5270
+ break;
5271
+ case BINSTREAM_TYPE_DOCUMENT: {
5272
+ const k = readAny(decoder);
5273
+ const u = readVarUint8Array(decoder);
5274
+ docsToInsert.push({
5275
+ k,
5276
+ u,
5277
+ });
5278
+ break;
5279
+ }
5280
+ }
5281
+ }
5282
+ yield __await(storeCollectedDocs(false)); // Chunk full - migth still be on same realm
5283
+ }
5284
+ }
5285
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
5286
+ finally {
5287
+ try {
5288
+ if (!_d && !_a && (_b = chunks_1.return)) yield __await(_b.call(chunks_1));
5289
+ }
5290
+ finally { if (e_1) throw e_1.error; }
5291
+ }
5292
+ yield __await(storeCollectedDocs(true)); // Everything downloaded - finalize last downloaded realm to "*"
5293
+ }
5294
+ catch (error) {
5295
+ if (!(error instanceof Dexie.DexieError)) {
5296
+ // Network error might have happened.
5297
+ // Store what we've collected so far:
5298
+ yield __await(storeCollectedDocs(false));
5299
+ }
5300
+ throw error;
5301
+ }
5302
+ });
5303
+ }
5304
+ });
5305
+ }
5306
+
3868
5307
  const CURRENT_SYNC_WORKER = 'currentSyncWorker';
3869
5308
  function sync(db, options, schema, syncOptions) {
3870
5309
  return _sync
@@ -3953,10 +5392,11 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
3953
5392
  //
3954
5393
  // List changes to sync
3955
5394
  //
3956
- const [clientChangeSet, syncState, baseRevs] = yield db.transaction('r', db.tables, () => __awaiter(this, void 0, void 0, function* () {
5395
+ const [clientChangeSet, syncState, baseRevs, { yMessages, lastUpdateIds }] = yield db.transaction('r', db.tables, () => __awaiter(this, void 0, void 0, function* () {
3957
5396
  const syncState = yield db.getPersistedSyncState();
3958
5397
  const baseRevs = yield db.$baseRevs.toArray();
3959
5398
  let clientChanges = yield listClientChanges(mutationTables);
5399
+ const yResults = yield listYClientMessagesAndStateVector(db, tablesToSync);
3960
5400
  throwIfCancelled(cancelToken);
3961
5401
  if (doSyncify) {
3962
5402
  const alreadySyncedRealms = [
@@ -3966,11 +5406,11 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
3966
5406
  const syncificationInserts = yield listSyncifiedChanges(tablesToSyncify, currentUser, schema, alreadySyncedRealms);
3967
5407
  throwIfCancelled(cancelToken);
3968
5408
  clientChanges = clientChanges.concat(syncificationInserts);
3969
- return [clientChanges, syncState, baseRevs];
5409
+ return [clientChanges, syncState, baseRevs, yResults];
3970
5410
  }
3971
- return [clientChanges, syncState, baseRevs];
5411
+ return [clientChanges, syncState, baseRevs, yResults];
3972
5412
  }));
3973
- const pushSyncIsNeeded = clientChangeSet.some((set) => set.muts.some((mut) => mut.keys.length > 0));
5413
+ const pushSyncIsNeeded = clientChangeSet.some((set) => set.muts.some((mut) => mut.keys.length > 0)) || yMessages.some(m => m.type === 'u-c');
3974
5414
  if (justCheckIfNeeded) {
3975
5415
  console.debug('Sync is needed:', pushSyncIsNeeded);
3976
5416
  return pushSyncIsNeeded;
@@ -3985,12 +5425,12 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
3985
5425
  // Push changes to server
3986
5426
  //
3987
5427
  throwIfCancelled(cancelToken);
3988
- const res = yield syncWithServer(clientChangeSet, syncState, baseRevs, db, databaseUrl, schema, clientIdentity, currentUser);
5428
+ const res = yield syncWithServer(clientChangeSet, yMessages, syncState, baseRevs, db, databaseUrl, schema, clientIdentity, currentUser);
3989
5429
  console.debug('Sync response', res);
3990
5430
  //
3991
5431
  // Apply changes locally and clear old change entries:
3992
5432
  //
3993
- const done = yield db.transaction('rw', db.tables, (tx) => __awaiter(this, void 0, void 0, function* () {
5433
+ const { done, newSyncState } = yield db.transaction('rw', db.tables, (tx) => __awaiter(this, void 0, void 0, function* () {
3994
5434
  // @ts-ignore
3995
5435
  tx.idbtrans.disableChangeTracking = true;
3996
5436
  // @ts-ignore
@@ -4082,17 +5522,35 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
4082
5522
  // apply server changes
4083
5523
  //
4084
5524
  yield applyServerChanges(filteredChanges, db);
5525
+ if (res.yMessages) {
5526
+ //
5527
+ // apply yMessages
5528
+ //
5529
+ const receivedUntils = yield applyYServerMessages(res.yMessages, db);
5530
+ //
5531
+ // update Y SyncStates
5532
+ //
5533
+ yield updateYSyncStates(lastUpdateIds, receivedUntils, db, res.serverRevision);
5534
+ }
4085
5535
  //
4086
- // Update syncState
5536
+ // Update regular syncState
4087
5537
  //
4088
5538
  db.$syncState.put(newSyncState, 'syncState');
4089
- return addedClientChanges.length === 0;
5539
+ return {
5540
+ done: addedClientChanges.length === 0,
5541
+ newSyncState
5542
+ };
4090
5543
  }));
4091
5544
  if (!done) {
4092
5545
  console.debug('MORE SYNC NEEDED. Go for it again!');
4093
5546
  yield checkSyncRateLimitDelay(db);
4094
5547
  return yield _sync(db, options, schema, { isInitialSync, cancelToken });
4095
5548
  }
5549
+ const usingYProps = Object.values(schema).some(tbl => { var _a; return (_a = tbl.yProps) === null || _a === void 0 ? void 0 : _a.length; });
5550
+ const serverSupportsYprops = !!res.yMessages;
5551
+ if (usingYProps && serverSupportsYprops) {
5552
+ yield downloadYDocsFromServer(db, databaseUrl, newSyncState);
5553
+ }
4096
5554
  console.debug('SYNC DONE', { isInitialSync });
4097
5555
  db.syncCompleteEvent.next();
4098
5556
  return false; // Not needed anymore
@@ -4145,6 +5603,18 @@ function deleteObjectsFromRemovedRealms(db, res, prevState) {
4145
5603
  }
4146
5604
  }
4147
5605
  }
5606
+ if (rejectedRealms.size > 0) {
5607
+ // Remove rejected/deleted realms from yDownloadedRealms because of the following use case:
5608
+ // 1. User becomes added to the realm
5609
+ // 2. User syncs and all documents of the realm is downloaded (downloadYDocsFromServer.ts)
5610
+ // 3. User leaves the realm and all docs are deleted locally (built-in-trigger of deleting their rows in this file)
5611
+ // 4. User is yet again added to the realm. At this point, we must make sure the docs are not considered already downloaded.
5612
+ const updateSpec = {};
5613
+ for (const realmId of rejectedRealms) {
5614
+ updateSpec[`yDownloadedRealms.${realmId}`] = undefined; // Setting to undefined will delete the property
5615
+ }
5616
+ yield db.$syncState.update('syncState', updateSpec);
5617
+ }
4148
5618
  });
4149
5619
  }
4150
5620
  function filterServerChangesThroughAddedClientChanges(serverChanges, addedClientChanges) {
@@ -4454,6 +5924,7 @@ function DexieCloudDB(dx) {
4454
5924
  };
4455
5925
  Object.assign(db, helperMethods);
4456
5926
  db.messageConsumer = MessagesFromServerConsumer(db);
5927
+ db.messageProducer = new Subject();
4457
5928
  wm.set(dx.cloud, db);
4458
5929
  }
4459
5930
  return db;
@@ -4483,24 +5954,6 @@ const safariVersion = isSafari
4483
5954
  const DISABLE_SERVICEWORKER_STRATEGY = (isSafari && safariVersion <= 605) || // Disable for Safari for now.
4484
5955
  isFirefox; // Disable for Firefox for now. Seems to have a bug in reading CryptoKeys from IDB from service workers
4485
5956
 
4486
- /* Helper function to subscribe to database close no matter if it was unexpectedly closed or manually using db.close()
4487
- */
4488
- function dbOnClosed(db, handler) {
4489
- db.on.close.subscribe(handler);
4490
- // @ts-ignore
4491
- const origClose = db._close;
4492
- // @ts-ignore
4493
- db._close = function () {
4494
- origClose.call(this);
4495
- handler();
4496
- };
4497
- return () => {
4498
- db.on.close.unsubscribe(handler);
4499
- // @ts-ignore
4500
- db._close = origClose;
4501
- };
4502
- }
4503
-
4504
5957
  const IS_SERVICE_WORKER = typeof self !== "undefined" && "clients" in self && !self.document;
4505
5958
 
4506
5959
  function throwVersionIncrementNeeded() {
@@ -4966,13 +6419,18 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
4966
6419
  values = values.filter((_, idx) => !failures[idx]);
4967
6420
  }
4968
6421
  const ts = Date.now();
6422
+ // Canonicalize req.criteria.index to null if it's on the primary key.
6423
+ const criteria = 'criteria' in req && req.criteria
6424
+ ? Object.assign(Object.assign({}, req.criteria), { index: req.criteria.index === schema.primaryKey.keyPath // Use null to inform server that criteria is on primary key
6425
+ ? null // This will disable the server from trying to log consistent operations where it shouldnt.
6426
+ : req.criteria.index }) : undefined;
4969
6427
  const mut = req.type === 'delete'
4970
6428
  ? {
4971
6429
  type: 'delete',
4972
6430
  ts,
4973
6431
  opNo,
4974
6432
  keys,
4975
- criteria: req.criteria,
6433
+ criteria,
4976
6434
  txid,
4977
6435
  userId,
4978
6436
  }
@@ -4986,14 +6444,14 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
4986
6444
  userId,
4987
6445
  values,
4988
6446
  }
4989
- : req.criteria && req.changeSpec
6447
+ : criteria && req.changeSpec
4990
6448
  ? {
4991
6449
  // Common changeSpec for all keys
4992
6450
  type: 'modify',
4993
6451
  ts,
4994
6452
  opNo,
4995
6453
  keys,
4996
- criteria: req.criteria,
6454
+ criteria,
4997
6455
  changeSpec: req.changeSpec,
4998
6456
  txid,
4999
6457
  userId,
@@ -5021,7 +6479,7 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
5021
6479
  if ('isAdditionalChunk' in req && req.isAdditionalChunk) {
5022
6480
  mut.isAdditionalChunk = true;
5023
6481
  }
5024
- return keys.length > 0 || ('criteria' in req && req.criteria)
6482
+ return keys.length > 0 || criteria
5025
6483
  ? mutsTable
5026
6484
  .mutate({ type: 'add', trans, values: [mut] }) // Log entry
5027
6485
  .then(() => res) // Return original response
@@ -5035,6 +6493,7 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
5035
6493
 
5036
6494
  function overrideParseStoresSpec(origFunc, dexie) {
5037
6495
  return function (stores, dbSchema) {
6496
+ var _a;
5038
6497
  const storesClone = Object.assign(Object.assign({}, DEXIE_CLOUD_SCHEMA), stores);
5039
6498
  // Merge indexes of DEXIE_CLOUD_SCHEMA with stores
5040
6499
  Object.keys(DEXIE_CLOUD_SCHEMA).forEach((tableName) => {
@@ -5095,6 +6554,14 @@ function overrideParseStoresSpec(origFunc, dexie) {
5095
6554
  }
5096
6555
  });
5097
6556
  const rv = origFunc.call(this, storesClone, dbSchema);
6557
+ for (const [tableName, spec] of Object.entries(dbSchema)) {
6558
+ if ((_a = spec.yProps) === null || _a === void 0 ? void 0 : _a.length) {
6559
+ const cloudTableSchema = cloudSchema[tableName];
6560
+ if (cloudTableSchema) {
6561
+ cloudTableSchema.yProps = spec.yProps.map((yProp) => yProp.prop);
6562
+ }
6563
+ }
6564
+ }
5098
6565
  return rv;
5099
6566
  };
5100
6567
  }
@@ -5180,31 +6647,90 @@ class TokenExpiredError extends Error {
5180
6647
  }
5181
6648
  }
5182
6649
 
6650
+ function createYClientUpdateObservable(db) {
6651
+ const yTableRecords = flatten(db.tables
6652
+ .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; })
6653
+ .map((table) => table.schema.yProps.map((p) => ({
6654
+ table: table.name,
6655
+ ydocProp: p.prop,
6656
+ updatesTable: p.updatesTable,
6657
+ }))));
6658
+ return merge(...yTableRecords.map(({ table, ydocProp, updatesTable }) => {
6659
+ // Per updates table (table+prop combo), we first read syncer.unsentFrom,
6660
+ // and then start listening for updates since that number.
6661
+ const yTbl = db.table(updatesTable);
6662
+ return from$1(yTbl.get(DEXIE_CLOUD_SYNCER_ID)).pipe(switchMap$1((syncer) => {
6663
+ let currentUnsentFrom = (syncer === null || syncer === void 0 ? void 0 : syncer.unsentFrom) || 1;
6664
+ return from$1(liveQuery(() => __awaiter(this, void 0, void 0, function* () {
6665
+ const addedUpdates = yield listUpdatesSince(yTbl, currentUnsentFrom);
6666
+ return addedUpdates
6667
+ .filter((update) => update.f && update.f & 1) // Only include local updates
6668
+ .map((update) => {
6669
+ return {
6670
+ type: 'u-c',
6671
+ table,
6672
+ prop: ydocProp,
6673
+ k: update.k,
6674
+ u: update.u,
6675
+ i: update.i,
6676
+ };
6677
+ });
6678
+ }))).pipe(tap$1((addedUpdates) => {
6679
+ // Update currentUnsentFrom to only listen for updates that will be newer than the ones we emitted.
6680
+ // (Before, we did this within the liveQuery, but that caused a bug because
6681
+ // a cancelled emittion of a liveQuery would update the currentUnsentFrom without
6682
+ // emitting anything, leading to that we jumped over some updates. Here we update it
6683
+ // after the liveQuery has emitted its updates)
6684
+ if (addedUpdates.length > 0) {
6685
+ currentUnsentFrom = addedUpdates.at(-1).i + 1;
6686
+ }
6687
+ }));
6688
+ }));
6689
+ })).pipe(
6690
+ // Flatten the array of messages.
6691
+ // If messageProducer emits empty array, nothing is emitted
6692
+ // but if messageProducer emits array of messages, they are
6693
+ // emitted one by one.
6694
+ mergeMap$1((messages) => messages), tap$1((message) => {
6695
+ console.debug('dexie-cloud emitting y-c', message);
6696
+ }));
6697
+ }
6698
+
6699
+ function getAwarenessLibrary(db) {
6700
+ var _a, _b;
6701
+ if (!((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.awarenessProtocol)) {
6702
+ throw new Dexie.MissingAPIError('awarenessProtocol was not provided to db.cloud.configure(). Please import * as awarenessProtocol from "y-protocols/awareness".');
6703
+ }
6704
+ return (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.awarenessProtocol;
6705
+ }
6706
+ const awarenessWeakMap = new WeakMap();
6707
+ const getDocAwareness = (doc) => awarenessWeakMap.get(doc);
6708
+
5183
6709
  const SERVER_PING_TIMEOUT = 20000;
5184
6710
  const CLIENT_PING_INTERVAL = 30000;
5185
6711
  const FAIL_RETRY_WAIT_TIME = 60000;
5186
6712
  class WSObservable extends Observable$1 {
5187
- constructor(databaseUrl, rev, realmSetHash, clientIdentity, messageProducer, webSocketStatus, token, tokenExpiration) {
5188
- super((subscriber) => new WSConnection(databaseUrl, rev, realmSetHash, clientIdentity, token, tokenExpiration, subscriber, messageProducer, webSocketStatus));
6713
+ constructor(db, rev, realmSetHash, clientIdentity, messageProducer, webSocketStatus, user) {
6714
+ super((subscriber) => new WSConnection(db, rev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus));
5189
6715
  }
5190
6716
  }
5191
6717
  let counter = 0;
5192
6718
  class WSConnection extends Subscription$1 {
5193
- constructor(databaseUrl, rev, realmSetHash, clientIdentity, token, tokenExpiration, subscriber, messageProducer, webSocketStatus) {
6719
+ constructor(db, rev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus) {
5194
6720
  super(() => this.teardown());
5195
6721
  this.id = ++counter;
6722
+ this.subscriptions = new Set();
5196
6723
  this.reconnecting = false;
5197
- console.debug('New WebSocket Connection', this.id, token ? 'authorized' : 'unauthorized');
5198
- this.databaseUrl = databaseUrl;
6724
+ console.debug('New WebSocket Connection', this.id, user.accessToken ? 'authorized' : 'unauthorized');
6725
+ this.db = db;
6726
+ this.databaseUrl = db.cloud.options.databaseUrl;
5199
6727
  this.rev = rev;
5200
6728
  this.realmSetHash = realmSetHash;
5201
6729
  this.clientIdentity = clientIdentity;
5202
- this.token = token;
5203
- this.tokenExpiration = tokenExpiration;
6730
+ this.user = user;
5204
6731
  this.subscriber = subscriber;
5205
6732
  this.lastUserActivity = new Date();
5206
6733
  this.messageProducer = messageProducer;
5207
- this.messageProducerSubscription = null;
5208
6734
  this.webSocketStatus = webSocketStatus;
5209
6735
  this.connect();
5210
6736
  }
@@ -5225,10 +6751,10 @@ class WSConnection extends Subscription$1 {
5225
6751
  catch (_a) { }
5226
6752
  }
5227
6753
  this.ws = null;
5228
- if (this.messageProducerSubscription) {
5229
- this.messageProducerSubscription.unsubscribe();
5230
- this.messageProducerSubscription = null;
6754
+ for (const sub of this.subscriptions) {
6755
+ sub.unsubscribe();
5231
6756
  }
6757
+ this.subscriptions.clear();
5232
6758
  }
5233
6759
  reconnect() {
5234
6760
  if (this.reconnecting)
@@ -5261,7 +6787,8 @@ class WSConnection extends Subscription$1 {
5261
6787
  //console.debug('SyncStatus: DUBB: Ooops it was closed!');
5262
6788
  return;
5263
6789
  }
5264
- if (this.tokenExpiration && this.tokenExpiration < new Date()) {
6790
+ const tokenExpiration = this.user.accessTokenExpiration;
6791
+ if (tokenExpiration && tokenExpiration < new Date()) {
5265
6792
  this.subscriber.error(new TokenExpiredError()); // Will be handled in connectWebSocket.ts.
5266
6793
  return;
5267
6794
  }
@@ -5316,13 +6843,13 @@ class WSConnection extends Subscription$1 {
5316
6843
  searchParams.set('rev', this.rev);
5317
6844
  searchParams.set('realmsHash', this.realmSetHash);
5318
6845
  searchParams.set('clientId', this.clientIdentity);
5319
- if (this.token) {
5320
- searchParams.set('token', this.token);
6846
+ if (this.user.accessToken) {
6847
+ searchParams.set('token', this.user.accessToken);
5321
6848
  }
5322
6849
  // Connect the WebSocket to given url:
5323
6850
  console.debug('dexie-cloud WebSocket create');
5324
6851
  const ws = (this.ws = new WebSocket(`${wsUrl}/changes?${searchParams}`));
5325
- //ws.binaryType = "arraybuffer"; // For future when subscribing to actual changes.
6852
+ ws.binaryType = "arraybuffer";
5326
6853
  ws.onclose = (event) => {
5327
6854
  if (!this.pinger)
5328
6855
  return;
@@ -5332,17 +6859,33 @@ class WSConnection extends Subscription$1 {
5332
6859
  ws.onmessage = (event) => {
5333
6860
  if (!this.pinger)
5334
6861
  return;
5335
- console.debug('dexie-cloud WebSocket onmessage', event.data);
5336
6862
  this.lastServerActivity = new Date();
5337
6863
  try {
5338
- const msg = TSON.parse(event.data);
6864
+ const msg = typeof event.data === 'string'
6865
+ ? TSON.parse(event.data)
6866
+ : decodeYMessage(new Uint8Array(event.data));
6867
+ console.debug('dexie-cloud WebSocket onmessage', msg.type, msg);
5339
6868
  if (msg.type === 'error') {
5340
6869
  throw new Error(`Error message from dexie-cloud: ${msg.error}`);
5341
6870
  }
5342
- if (msg.type === 'rev') {
6871
+ else if (msg.type === 'rev') {
5343
6872
  this.rev = msg.rev; // No meaning but seems reasonable.
5344
6873
  }
5345
- if (msg.type !== 'pong') {
6874
+ else if (msg.type === 'aware') {
6875
+ const docCache = DexieYProvider.getDocCache(this.db.dx);
6876
+ const doc = docCache.find(msg.table, msg.k, msg.prop);
6877
+ if (doc) {
6878
+ const awareness = getDocAwareness(doc);
6879
+ if (awareness) {
6880
+ const awap = getAwarenessLibrary(this.db);
6881
+ awap.applyAwarenessUpdate(awareness, msg.u, 'server');
6882
+ }
6883
+ }
6884
+ }
6885
+ else if (msg.type === 'u-ack' || msg.type === 'u-reject' || msg.type === 'u-s' || msg.type === 'in-sync') {
6886
+ applyYServerMessages([msg], this.db);
6887
+ }
6888
+ else if (msg.type !== 'pong') {
5346
6889
  this.subscriber.next(msg);
5347
6890
  }
5348
6891
  }
@@ -5370,16 +6913,27 @@ class WSConnection extends Subscription$1 {
5370
6913
  }
5371
6914
  };
5372
6915
  });
5373
- this.messageProducerSubscription = this.messageProducer.subscribe((msg) => {
5374
- var _a;
6916
+ this.subscriptions.add(this.messageProducer.subscribe((msg) => {
6917
+ var _a, _b;
5375
6918
  if (!this.closed) {
5376
6919
  if (msg.type === 'ready' &&
5377
6920
  this.webSocketStatus.value !== 'connected') {
5378
6921
  this.webSocketStatus.next('connected');
5379
6922
  }
5380
- (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(TSON.stringify(msg));
6923
+ console.debug('dexie-cloud WebSocket send', msg.type, msg);
6924
+ if (msg.type === 'ready') {
6925
+ (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(TSON.stringify(msg));
6926
+ }
6927
+ else {
6928
+ // If it's not a "ready" message, it's an YMessage.
6929
+ // YMessages can be sent binary encoded.
6930
+ (_b = this.ws) === null || _b === void 0 ? void 0 : _b.send(encodeYMessage(msg));
6931
+ }
5381
6932
  }
5382
- });
6933
+ }));
6934
+ if (this.user.isLoggedIn && !isEagerSyncDisabled(this.db)) {
6935
+ this.subscriptions.add(createYClientUpdateObservable(this.db).subscribe(this.db.messageProducer));
6936
+ }
5383
6937
  }
5384
6938
  catch (error) {
5385
6939
  this.pauseUntil = new Date(Date.now() + FAIL_RETRY_WAIT_TIME);
@@ -5421,7 +6975,7 @@ function connectWebSocket(db) {
5421
6975
  if (!((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl)) {
5422
6976
  throw new Error(`No database URL to connect WebSocket to`);
5423
6977
  }
5424
- const messageProducer = db.messageConsumer.readyToServe.pipe(filter((isReady) => isReady), // When consumer is ready for new messages, produce such a message to inform server about it
6978
+ 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
5425
6979
  switchMap(() => db.getPersistedSyncState()), // We need the info on which server revision we are at:
5426
6980
  filter((syncState) => syncState && syncState.serverRevision), // We wont send anything to server before inital sync has taken place
5427
6981
  switchMap((syncState) => __awaiter(this, void 0, void 0, function* () {
@@ -5432,6 +6986,7 @@ function connectWebSocket(db) {
5432
6986
  realmSetHash: yield computeRealmSetHash(syncState)
5433
6987
  });
5434
6988
  })));
6989
+ const messageProducer = merge(readyForChangesMessage, db.messageProducer);
5435
6990
  function createObservable() {
5436
6991
  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.
5437
6992
  take(1), // Don't continue waking up whenever syncState change
@@ -5458,7 +7013,7 @@ function connectWebSocket(db) {
5458
7013
  // If no new entries, server won't bother the client. If new entries, server sends only those
5459
7014
  // and the baseRev of the last from same client-ID.
5460
7015
  if (userLogin) {
5461
- return new WSObservable(db.cloud.options.databaseUrl, db.cloud.persistedSyncState.value.serverRevision, realmSetHash, db.cloud.persistedSyncState.value.clientIdentity, messageProducer, db.cloud.webSocketStatus, userLogin.accessToken, userLogin.accessTokenExpiration);
7016
+ return new WSObservable(db, db.cloud.persistedSyncState.value.serverRevision, realmSetHash, db.cloud.persistedSyncState.value.clientIdentity, messageProducer, db.cloud.webSocketStatus, userLogin);
5462
7017
  }
5463
7018
  else {
5464
7019
  return from$1([]);
@@ -6264,6 +7819,142 @@ const getInvitesObservable = associate((db) => {
6264
7819
  })), []);
6265
7820
  });
6266
7821
 
7822
+ function createYHandler(db) {
7823
+ return (provider) => {
7824
+ var _a;
7825
+ const doc = provider.doc;
7826
+ const { parentTable } = doc.meta || {};
7827
+ if (!((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[parentTable].markedForSync)) {
7828
+ return; // The table that holds the doc is not marked for sync - leave it to dexie. No syncing, no awareness.
7829
+ }
7830
+ let awareness;
7831
+ Object.defineProperty(provider, "awareness", {
7832
+ get() {
7833
+ if (awareness)
7834
+ return awareness;
7835
+ awarenessWeakMap.set(doc, awareness);
7836
+ awareness = createAwareness(db, doc, provider);
7837
+ return awareness;
7838
+ }
7839
+ });
7840
+ };
7841
+ }
7842
+ function createAwareness(db, doc, provider) {
7843
+ const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7844
+ const awap = getAwarenessLibrary(db);
7845
+ const awareness = new awap.Awareness(doc);
7846
+ awareness.on('update', ({ added, updated, removed }, origin) => {
7847
+ // Send the update
7848
+ const changedClients = added.concat(updated).concat(removed);
7849
+ const user = db.cloud.currentUser.value;
7850
+ if (origin !== 'server' && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7851
+ const update = awap.encodeAwarenessUpdate(awareness, changedClients);
7852
+ db.messageProducer.next({
7853
+ type: 'aware',
7854
+ table: parentTable,
7855
+ prop: parentProp,
7856
+ k: doc.meta.parentId,
7857
+ u: update,
7858
+ });
7859
+ if (provider.destroyed) {
7860
+ // We're called from awareness.on('destroy') that did
7861
+ // removeAwarenessStates.
7862
+ // It's time to also send the doc-close message that dexie-cloud understands
7863
+ // and uses to stop subscribing for updates and awareness updates and brings
7864
+ // down the cached information in memory on the WS connection for this.
7865
+ db.messageProducer.next({
7866
+ type: 'doc-close',
7867
+ table: parentTable,
7868
+ prop: parentProp,
7869
+ k: doc.meta.parentId
7870
+ });
7871
+ }
7872
+ }
7873
+ });
7874
+ awareness.on('destroy', () => {
7875
+ // Signal to server that this provider is destroyed (the update event will be triggered, which
7876
+ // in turn will trigger db.messageProducer that will send the message to the server if WS is connected)
7877
+ awap.removeAwarenessStates(awareness, [doc.clientID], 'provider destroyed');
7878
+ });
7879
+ // Open the document on the server
7880
+ (() => __awaiter(this, void 0, void 0, function* () {
7881
+ if (provider.destroyed)
7882
+ return;
7883
+ let connected = false;
7884
+ let currentFlowId = 1;
7885
+ const subscription = db.cloud.webSocketStatus.subscribe((wsStatus) => {
7886
+ if (provider.destroyed)
7887
+ return;
7888
+ // Keep "connected" state in a variable so we can check it after async operations
7889
+ connected = wsStatus === 'connected';
7890
+ // We are or got connected. Open the document on the server.
7891
+ const user = db.cloud.currentUser.value;
7892
+ if (wsStatus === "connected" && user.isLoggedIn && !isEagerSyncDisabled(db)) {
7893
+ ++currentFlowId;
7894
+ openDocumentOnServer().catch(error => {
7895
+ console.warn(`Error catched in createYHandler.ts: ${error}`);
7896
+ });
7897
+ }
7898
+ });
7899
+ // Wait until WebSocket is connected
7900
+ provider.addCleanupHandler(subscription);
7901
+ /** Sends an 'doc-open' message to server whenever websocket becomes
7902
+ * connected, or if it is already connected.
7903
+ * The flow is aborted in case websocket is disconnected while querying
7904
+ * information required to compute the state vector. Flow is also
7905
+ * aborted in case document or provider has been destroyed during
7906
+ * the async parts of the task.
7907
+ *
7908
+ * The state vector is only computed from the updates that have occured
7909
+ * after the last full sync - which could very often be zero - in which
7910
+ * case no state vector is sent (then the server already knows us by
7911
+ * revision)
7912
+ *
7913
+ * When server gets the doc-open message, it will authorized us for
7914
+ * whether we are allowed to read / write to this document, and then
7915
+ * keep the cached information in memory on the WS connection for this
7916
+ * particular document, as well as subscribe to updates and awareness updates
7917
+ * from other clients on the document.
7918
+ */
7919
+ function openDocumentOnServer(wsStatus) {
7920
+ return __awaiter(this, void 0, void 0, function* () {
7921
+ const myFlow = currentFlowId; // So we can abort when a new flow is started
7922
+ const yTbl = db.table(updatesTable);
7923
+ const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
7924
+ // After every await, check if we still should be working on this task.
7925
+ if (provider.destroyed || currentFlowId !== myFlow || !connected)
7926
+ return;
7927
+ const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
7928
+ const docOpenMsg = {
7929
+ type: 'doc-open',
7930
+ table: parentTable,
7931
+ prop: parentProp,
7932
+ k: parentId,
7933
+ serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
7934
+ };
7935
+ const serverUpdatesSinceLastSync = yield yTbl
7936
+ .where('i')
7937
+ .between(receivedUntil, Infinity, false)
7938
+ .filter((update) => cmp(update.k, parentId) === 0 && // Only updates for this document
7939
+ ((update.f || 0) & 1) === 0 // Don't include local changes
7940
+ )
7941
+ .toArray();
7942
+ // After every await, check if we still should be working on this task.
7943
+ if (provider.destroyed || currentFlowId !== myFlow || !connected)
7944
+ return;
7945
+ if (serverUpdatesSinceLastSync.length > 0) {
7946
+ const Y = $Y$1(db); // Get the Yjs library from Dexie constructor options
7947
+ const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
7948
+ const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
7949
+ docOpenMsg.sv = stateVector;
7950
+ }
7951
+ db.messageProducer.next(docOpenMsg);
7952
+ });
7953
+ }
7954
+ }))();
7955
+ return awareness;
7956
+ }
7957
+
6267
7958
  function getTiedRealmId(objectId) {
6268
7959
  return 'rlm~' + objectId;
6269
7960
  }
@@ -6300,8 +7991,9 @@ function dexieCloud(dexie) {
6300
7991
  if (closed)
6301
7992
  throw new Dexie.DatabaseClosedError();
6302
7993
  }
6303
- dbOnClosed(dexie, () => {
7994
+ dexie.once('close', () => {
6304
7995
  subscriptions.forEach((subscription) => subscription.unsubscribe());
7996
+ subscriptions.splice(0, subscriptions.length);
6305
7997
  closed = true;
6306
7998
  localSyncWorker && localSyncWorker.stop();
6307
7999
  localSyncWorker = null;
@@ -6310,7 +8002,7 @@ function dexieCloud(dexie) {
6310
8002
  const syncComplete = new Subject();
6311
8003
  dexie.cloud = {
6312
8004
  // @ts-ignore
6313
- version: "4.0.8",
8005
+ version: "4.1.0-alpha.12",
6314
8006
  options: Object.assign({}, DEFAULT_OPTIONS),
6315
8007
  schema: null,
6316
8008
  get currentUserId() {
@@ -6456,6 +8148,7 @@ function dexieCloud(dexie) {
6456
8148
  throw new Error(`Internal error`); // options cannot be null if configuredProgramatically is set.
6457
8149
  const newPersistedOptions = Object.assign({}, options);
6458
8150
  delete newPersistedOptions.fetchTokens;
8151
+ delete newPersistedOptions.awarenessProtocol;
6459
8152
  yield db.$syncState.put(newPersistedOptions, 'options');
6460
8153
  }
6461
8154
  if (((_h = db.cloud.options) === null || _h === void 0 ? void 0 : _h.tryUseServiceWorker) &&
@@ -6533,6 +8226,12 @@ function dexieCloud(dexie) {
6533
8226
  currentUserEmitter.pipe(skip(1), take(1)),
6534
8227
  db.cloud.persistedSyncState.pipe(skip(1), take(1)),
6535
8228
  ]));
8229
+ const yHandler = createYHandler(db);
8230
+ db.dx.on('y', yHandler);
8231
+ db.dx.once('close', () => {
8232
+ var _a;
8233
+ (_a = db.dx.on.y) === null || _a === void 0 ? void 0 : _a.unsubscribe(yHandler);
8234
+ });
6536
8235
  }
6537
8236
  // HERE: If requireAuth, do athentication now.
6538
8237
  let changedUser = false;
@@ -6605,8 +8304,160 @@ function dexieCloud(dexie) {
6605
8304
  }
6606
8305
  }
6607
8306
  // @ts-ignore
6608
- dexieCloud.version = "4.0.8";
8307
+ dexieCloud.version = "4.1.0-alpha.12";
6609
8308
  Dexie.Cloud = dexieCloud;
6610
8309
 
6611
- export { dexieCloud as default, dexieCloud, getTiedObjectId, getTiedRealmId, resolveText };
8310
+ const ydocTriggers = {};
8311
+ const docIsAlreadyHooked = new WeakSet();
8312
+ const middlewares = new WeakMap();
8313
+ const createMiddleware = (db) => ({
8314
+ stack: 'dbcore',
8315
+ level: 10,
8316
+ name: 'yTriggerMiddleware',
8317
+ create: (down) => {
8318
+ return Object.assign(Object.assign({}, down), { transaction: (stores, mode, options) => {
8319
+ const idbtrans = down.transaction(stores, mode, options);
8320
+ idbtrans.addEventListener('complete', onTransactionCommitted);
8321
+ return idbtrans;
8322
+ }, table: (tblName) => {
8323
+ const coreTable = down.table(tblName);
8324
+ const triggerSpec = ydocTriggers[tblName];
8325
+ if (!triggerSpec)
8326
+ return coreTable;
8327
+ const { trigger, parentTable, prop } = triggerSpec;
8328
+ return Object.assign(Object.assign({}, coreTable), { mutate(req) {
8329
+ switch (req.type) {
8330
+ case 'add': {
8331
+ for (const obj of req.values) {
8332
+ const primaryKey = coreTable.schema.primaryKey.extractKey(obj);
8333
+ const doc = DexieYProvider.getDocCache(db).find(parentTable, primaryKey, prop);
8334
+ if (doc) {
8335
+ if (!docIsAlreadyHooked.has(doc)) {
8336
+ hookToDoc(doc, primaryKey, trigger);
8337
+ docIsAlreadyHooked.add(doc);
8338
+ }
8339
+ }
8340
+ else {
8341
+ enqueueTrigger(tblName, primaryKey, trigger);
8342
+ }
8343
+ }
8344
+ break;
8345
+ }
8346
+ case 'delete':
8347
+ // @ts-ignore
8348
+ if (req.trans._rejecting_y_ypdate) {
8349
+ // The deletion came from a rejection, not garbage collection.
8350
+ // When that happens, let the triggers run to compute new values
8351
+ // based on the deleted updates.
8352
+ coreTable
8353
+ .getMany({
8354
+ keys: req.keys,
8355
+ trans: req.trans,
8356
+ cache: 'immutable',
8357
+ })
8358
+ .then((updates) => {
8359
+ const keySet = new RangeSet();
8360
+ for (const { k } of updates) {
8361
+ keySet.addKey(k);
8362
+ }
8363
+ for (const key of keySet) {
8364
+ enqueueTrigger(tblName, key, trigger);
8365
+ }
8366
+ });
8367
+ }
8368
+ break;
8369
+ }
8370
+ return coreTable.mutate(req);
8371
+ } });
8372
+ } });
8373
+ },
8374
+ });
8375
+ let triggerExecPromise = null;
8376
+ let triggerScheduled = false;
8377
+ let scheduledTriggers = [];
8378
+ function $Y(db) {
8379
+ const $Y = db._options.Y;
8380
+ if (!$Y)
8381
+ throw new Error('Y library not supplied to Dexie constructor');
8382
+ return $Y;
8383
+ }
8384
+ function executeTriggers(triggersToRun) {
8385
+ return __awaiter(this, void 0, void 0, function* () {
8386
+ for (const { db, parentId, trigger, updatesTable } of triggersToRun) {
8387
+ // Load entire document into an Y.Doc instance:
8388
+ const updates = yield db
8389
+ .table(updatesTable)
8390
+ .where({ k: parentId })
8391
+ .toArray();
8392
+ const Y = $Y(db);
8393
+ const yDoc = new Y.Doc();
8394
+ for (const update of updates) {
8395
+ Y.applyUpdateV2(yDoc, update);
8396
+ }
8397
+ try {
8398
+ yield trigger(yDoc, parentId);
8399
+ }
8400
+ catch (error) {
8401
+ console.error(`Error in YDocTrigger ${error}`);
8402
+ }
8403
+ }
8404
+ });
8405
+ }
8406
+ function enqueueTrigger(updatesTable, parentId, trigger) {
8407
+ var _a;
8408
+ ((_a = scheduledTriggers[updatesTable]) !== null && _a !== void 0 ? _a : (scheduledTriggers[updatesTable] = [])).push({
8409
+ parentId,
8410
+ trigger,
8411
+ });
8412
+ }
8413
+ function onTransactionCommitted() {
8414
+ return __awaiter(this, void 0, void 0, function* () {
8415
+ if (!triggerScheduled && scheduledTriggers.length > 0) {
8416
+ triggerScheduled = true;
8417
+ if (triggerExecPromise)
8418
+ yield triggerExecPromise.catch(() => { });
8419
+ setTimeout(() => {
8420
+ // setTimeout() is to escape from Promise.PSD zones and never run within liveQueries or transaction scopes
8421
+ triggerScheduled = false;
8422
+ const triggersToRun = scheduledTriggers;
8423
+ scheduledTriggers = [];
8424
+ triggerExecPromise = executeTriggers(triggersToRun).finally(() => (triggerExecPromise = null));
8425
+ }, 0);
8426
+ }
8427
+ });
8428
+ }
8429
+ function hookToDoc(doc, parentId, trigger) {
8430
+ // From now on, keep listening to doc updates and execute the trigger when it happens there instead
8431
+ doc.on('updateV2', (update, origin) => {
8432
+ //Dexie.ignoreTransaction(()=>{
8433
+ trigger(doc, parentId);
8434
+ //});
8435
+ });
8436
+ /*
8437
+ NOT NEEDED because DexieYProvider's docCache will also listen to destroy and remove it from its cache:
8438
+ doc.on('destroy', ()=>{
8439
+ docIsAlreadyHooked.delete(doc);
8440
+ })
8441
+ */
8442
+ }
8443
+ function defineYDocTrigger(table, prop, trigger) {
8444
+ var _a, _b;
8445
+ const updatesTable = (_b = (_a = table.schema.yProps) === null || _a === void 0 ? void 0 : _a.find((p) => p.prop === prop)) === null || _b === void 0 ? void 0 : _b.updatesTable;
8446
+ if (!updatesTable)
8447
+ throw new Error(`Table ${table.name} does not have a Yjs property named ${prop}`);
8448
+ ydocTriggers[updatesTable] = {
8449
+ trigger,
8450
+ parentTable: table.name,
8451
+ prop,
8452
+ };
8453
+ const db = table.db._novip;
8454
+ let mw = middlewares.get(db);
8455
+ if (!mw) {
8456
+ mw = createMiddleware(db);
8457
+ middlewares.set(db, mw);
8458
+ }
8459
+ db.use(mw);
8460
+ }
8461
+
8462
+ export { dexieCloud as default, defineYDocTrigger, dexieCloud, getTiedObjectId, getTiedRealmId, resolveText };
6612
8463
  //# sourceMappingURL=dexie-cloud-addon.js.map