dexie-cloud-addon 4.0.7 → 4.1.0-alpha.2

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 (57) hide show
  1. package/dist/modern/DexieCloudOptions.d.ts +4 -2
  2. package/dist/modern/WSObservable.d.ts +7 -4
  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/dexie-cloud-addon.js +1709 -72
  6. package/dist/modern/dexie-cloud-addon.js.map +1 -1
  7. package/dist/modern/dexie-cloud-addon.min.js +1 -1
  8. package/dist/modern/dexie-cloud-addon.min.js.map +1 -1
  9. package/dist/modern/service-worker.js +1709 -72
  10. package/dist/modern/service-worker.js.map +1 -1
  11. package/dist/modern/service-worker.min.js +1 -1
  12. package/dist/modern/service-worker.min.js.map +1 -1
  13. package/dist/modern/sync/DEXIE_CLOUD_SYNCER_ID.d.ts +1 -0
  14. package/dist/modern/sync/syncWithServer.d.ts +2 -2
  15. package/dist/modern/yjs/YDexieCloudSyncState.d.ts +4 -0
  16. package/dist/modern/yjs/YTable.d.ts +2 -0
  17. package/dist/modern/yjs/applyYMessages.d.ts +5 -0
  18. package/dist/modern/yjs/awareness.d.ts +4 -0
  19. package/dist/modern/yjs/createYClientUpdateObservable.d.ts +4 -0
  20. package/dist/modern/yjs/createYHandler.d.ts +5 -0
  21. package/dist/modern/yjs/downloadYDocsFromServer.d.ts +3 -0
  22. package/dist/modern/yjs/getUpdatesTable.d.ts +3 -0
  23. package/dist/modern/yjs/listUpdatesSince.d.ts +2 -0
  24. package/dist/modern/yjs/listYClientMessages.d.ts +3 -0
  25. package/dist/modern/yjs/listYClientMessagesAndStateVector.d.ts +24 -0
  26. package/dist/modern/yjs/updateYSyncStates.d.ts +6 -0
  27. package/dist/modern/yjs/y.d.ts +3 -0
  28. package/dist/umd/DexieCloudOptions.d.ts +4 -2
  29. package/dist/umd/WSObservable.d.ts +7 -4
  30. package/dist/umd/db/DexieCloudDB.d.ts +2 -0
  31. package/dist/umd/db/entities/PersistedSyncState.d.ts +7 -0
  32. package/dist/umd/dexie-cloud-addon.js +1707 -70
  33. package/dist/umd/dexie-cloud-addon.js.map +1 -1
  34. package/dist/umd/dexie-cloud-addon.min.js +1 -1
  35. package/dist/umd/dexie-cloud-addon.min.js.map +1 -1
  36. package/dist/umd/service-worker.js +1707 -70
  37. package/dist/umd/service-worker.js.map +1 -1
  38. package/dist/umd/service-worker.min.js +1 -1
  39. package/dist/umd/service-worker.min.js.map +1 -1
  40. package/dist/umd/sync/DEXIE_CLOUD_SYNCER_ID.d.ts +1 -0
  41. package/dist/umd/sync/syncWithServer.d.ts +2 -2
  42. package/dist/umd/yjs/YDexieCloudSyncState.d.ts +4 -0
  43. package/dist/umd/yjs/YTable.d.ts +2 -0
  44. package/dist/umd/yjs/applyYMessages.d.ts +5 -0
  45. package/dist/umd/yjs/awareness.d.ts +4 -0
  46. package/dist/umd/yjs/createYClientUpdateObservable.d.ts +4 -0
  47. package/dist/umd/yjs/createYHandler.d.ts +5 -0
  48. package/dist/umd/yjs/downloadYDocsFromServer.d.ts +3 -0
  49. package/dist/umd/yjs/getUpdatesTable.d.ts +3 -0
  50. package/dist/umd/yjs/listUpdatesSince.d.ts +2 -0
  51. package/dist/umd/yjs/listYClientMessages.d.ts +3 -0
  52. package/dist/umd/yjs/listYClientMessagesAndStateVector.d.ts +24 -0
  53. package/dist/umd/yjs/updateYSyncStates.d.ts +6 -0
  54. package/dist/umd/yjs/y.d.ts +3 -0
  55. package/package.json +5 -4
  56. package/dist/modern/helpers/dbOnClosed.d.ts +0 -2
  57. package/dist/umd/helpers/dbOnClosed.d.ts +0 -2
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * ==========================================================================
10
10
  *
11
- * Version 4.0.7, Sun May 26 2024
11
+ * Version 4.1.0-alpha.2, Mon Oct 07 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 { Observable as Observable$1, BehaviorSubject, firstValueFrom, Subject, from as from$1, filter as filter$1, 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 } from 'dexie';
20
+ import { Observable as Observable$1, BehaviorSubject, firstValueFrom, Subject, from as from$1, filter as filter$1, fromEvent, of, merge, 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.
@@ -2242,6 +2242,1075 @@ function getDbNameFromDbUrl(dbUrl) {
2242
2242
  : url.pathname.split('/')[1];
2243
2243
  }
2244
2244
 
2245
+ /**
2246
+ * Common Math expressions.
2247
+ *
2248
+ * @module math
2249
+ */
2250
+
2251
+ const floor = Math.floor;
2252
+ const abs = Math.abs;
2253
+
2254
+ /**
2255
+ * @function
2256
+ * @param {number} a
2257
+ * @param {number} b
2258
+ * @return {number} The smaller element of a and b
2259
+ */
2260
+ const min = (a, b) => a < b ? a : b;
2261
+
2262
+ /**
2263
+ * @function
2264
+ * @param {number} a
2265
+ * @param {number} b
2266
+ * @return {number} The bigger element of a and b
2267
+ */
2268
+ const max = (a, b) => a > b ? a : b;
2269
+
2270
+ /**
2271
+ * @param {number} n
2272
+ * @return {boolean} Wether n is negative. This function also differentiates between -0 and +0
2273
+ */
2274
+ const isNegativeZero = n => n !== 0 ? n < 0 : 1 / n < 0;
2275
+
2276
+ /* eslint-env browser */
2277
+
2278
+ const BIT7 = 64;
2279
+ const BIT8 = 128;
2280
+ const BITS6 = 63;
2281
+ const BITS7 = 127;
2282
+ /**
2283
+ * @type {number}
2284
+ */
2285
+ const BITS31 = 0x7FFFFFFF;
2286
+
2287
+ /**
2288
+ * Utility helpers for working with numbers.
2289
+ *
2290
+ * @module number
2291
+ */
2292
+
2293
+
2294
+ const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
2295
+
2296
+ /* c8 ignore next */
2297
+ const isInteger = Number.isInteger || (num => typeof num === 'number' && isFinite(num) && floor(num) === num);
2298
+
2299
+ /**
2300
+ * Utility module to work with Arrays.
2301
+ *
2302
+ * @module array
2303
+ */
2304
+
2305
+
2306
+ const isArray = Array.isArray;
2307
+
2308
+ /**
2309
+ * @param {string} str
2310
+ * @return {Uint8Array}
2311
+ */
2312
+ const _encodeUtf8Polyfill = str => {
2313
+ const encodedString = unescape(encodeURIComponent(str));
2314
+ const len = encodedString.length;
2315
+ const buf = new Uint8Array(len);
2316
+ for (let i = 0; i < len; i++) {
2317
+ buf[i] = /** @type {number} */ (encodedString.codePointAt(i));
2318
+ }
2319
+ return buf
2320
+ };
2321
+
2322
+ /* c8 ignore next */
2323
+ const utf8TextEncoder = /** @type {TextEncoder} */ (typeof TextEncoder !== 'undefined' ? new TextEncoder() : null);
2324
+
2325
+ /**
2326
+ * @param {string} str
2327
+ * @return {Uint8Array}
2328
+ */
2329
+ const _encodeUtf8Native = str => utf8TextEncoder.encode(str);
2330
+
2331
+ /**
2332
+ * @param {string} str
2333
+ * @return {Uint8Array}
2334
+ */
2335
+ /* c8 ignore next */
2336
+ const encodeUtf8 = utf8TextEncoder ? _encodeUtf8Native : _encodeUtf8Polyfill;
2337
+
2338
+ /* c8 ignore next */
2339
+ let utf8TextDecoder = typeof TextDecoder === 'undefined' ? null : new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
2340
+
2341
+ /* c8 ignore start */
2342
+ if (utf8TextDecoder && utf8TextDecoder.decode(new Uint8Array()).length === 1) {
2343
+ // Safari doesn't handle BOM correctly.
2344
+ // This fixes a bug in Safari 13.0.5 where it produces a BOM the first time it is called.
2345
+ // utf8TextDecoder.decode(new Uint8Array()).length === 1 on the first call and
2346
+ // utf8TextDecoder.decode(new Uint8Array()).length === 1 on the second call
2347
+ // Another issue is that from then on no BOM chars are recognized anymore
2348
+ /* c8 ignore next */
2349
+ utf8TextDecoder = null;
2350
+ }
2351
+
2352
+ /**
2353
+ * Efficient schema-less binary encoding with support for variable length encoding.
2354
+ *
2355
+ * Use [lib0/encoding] with [lib0/decoding]. Every encoding function has a corresponding decoding function.
2356
+ *
2357
+ * Encodes numbers in little-endian order (least to most significant byte order)
2358
+ * and is compatible with Golang's binary encoding (https://golang.org/pkg/encoding/binary/)
2359
+ * which is also used in Protocol Buffers.
2360
+ *
2361
+ * ```js
2362
+ * // encoding step
2363
+ * const encoder = encoding.createEncoder()
2364
+ * encoding.writeVarUint(encoder, 256)
2365
+ * encoding.writeVarString(encoder, 'Hello world!')
2366
+ * const buf = encoding.toUint8Array(encoder)
2367
+ * ```
2368
+ *
2369
+ * ```js
2370
+ * // decoding step
2371
+ * const decoder = decoding.createDecoder(buf)
2372
+ * decoding.readVarUint(decoder) // => 256
2373
+ * decoding.readVarString(decoder) // => 'Hello world!'
2374
+ * decoding.hasContent(decoder) // => false - all data is read
2375
+ * ```
2376
+ *
2377
+ * @module encoding
2378
+ */
2379
+
2380
+
2381
+ /**
2382
+ * A BinaryEncoder handles the encoding to an Uint8Array.
2383
+ */
2384
+ class Encoder {
2385
+ constructor () {
2386
+ this.cpos = 0;
2387
+ this.cbuf = new Uint8Array(100);
2388
+ /**
2389
+ * @type {Array<Uint8Array>}
2390
+ */
2391
+ this.bufs = [];
2392
+ }
2393
+ }
2394
+
2395
+ /**
2396
+ * The current length of the encoded data.
2397
+ *
2398
+ * @function
2399
+ * @param {Encoder} encoder
2400
+ * @return {number}
2401
+ */
2402
+ const length = encoder => {
2403
+ let len = encoder.cpos;
2404
+ for (let i = 0; i < encoder.bufs.length; i++) {
2405
+ len += encoder.bufs[i].length;
2406
+ }
2407
+ return len
2408
+ };
2409
+
2410
+ /**
2411
+ * Transform to Uint8Array.
2412
+ *
2413
+ * @function
2414
+ * @param {Encoder} encoder
2415
+ * @return {Uint8Array} The created ArrayBuffer.
2416
+ */
2417
+ const toUint8Array = encoder => {
2418
+ const uint8arr = new Uint8Array(length(encoder));
2419
+ let curPos = 0;
2420
+ for (let i = 0; i < encoder.bufs.length; i++) {
2421
+ const d = encoder.bufs[i];
2422
+ uint8arr.set(d, curPos);
2423
+ curPos += d.length;
2424
+ }
2425
+ uint8arr.set(new Uint8Array(encoder.cbuf.buffer, 0, encoder.cpos), curPos);
2426
+ return uint8arr
2427
+ };
2428
+
2429
+ /**
2430
+ * Verify that it is possible to write `len` bytes wtihout checking. If
2431
+ * necessary, a new Buffer with the required length is attached.
2432
+ *
2433
+ * @param {Encoder} encoder
2434
+ * @param {number} len
2435
+ */
2436
+ const verifyLen = (encoder, len) => {
2437
+ const bufferLen = encoder.cbuf.length;
2438
+ if (bufferLen - encoder.cpos < len) {
2439
+ encoder.bufs.push(new Uint8Array(encoder.cbuf.buffer, 0, encoder.cpos));
2440
+ encoder.cbuf = new Uint8Array(max(bufferLen, len) * 2);
2441
+ encoder.cpos = 0;
2442
+ }
2443
+ };
2444
+
2445
+ /**
2446
+ * Write one byte to the encoder.
2447
+ *
2448
+ * @function
2449
+ * @param {Encoder} encoder
2450
+ * @param {number} num The byte that is to be encoded.
2451
+ */
2452
+ const write = (encoder, num) => {
2453
+ const bufferLen = encoder.cbuf.length;
2454
+ if (encoder.cpos === bufferLen) {
2455
+ encoder.bufs.push(encoder.cbuf);
2456
+ encoder.cbuf = new Uint8Array(bufferLen * 2);
2457
+ encoder.cpos = 0;
2458
+ }
2459
+ encoder.cbuf[encoder.cpos++] = num;
2460
+ };
2461
+
2462
+ /**
2463
+ * Write a variable length unsigned integer. Max encodable integer is 2^53.
2464
+ *
2465
+ * @function
2466
+ * @param {Encoder} encoder
2467
+ * @param {number} num The number that is to be encoded.
2468
+ */
2469
+ const writeVarUint = (encoder, num) => {
2470
+ while (num > BITS7) {
2471
+ write(encoder, BIT8 | (BITS7 & num));
2472
+ num = floor(num / 128); // shift >>> 7
2473
+ }
2474
+ write(encoder, BITS7 & num);
2475
+ };
2476
+
2477
+ /**
2478
+ * Write a variable length integer.
2479
+ *
2480
+ * We use the 7th bit instead for signaling that this is a negative number.
2481
+ *
2482
+ * @function
2483
+ * @param {Encoder} encoder
2484
+ * @param {number} num The number that is to be encoded.
2485
+ */
2486
+ const writeVarInt = (encoder, num) => {
2487
+ const isNegative = isNegativeZero(num);
2488
+ if (isNegative) {
2489
+ num = -num;
2490
+ }
2491
+ // |- whether to continue reading |- whether is negative |- number
2492
+ write(encoder, (num > BITS6 ? BIT8 : 0) | (isNegative ? BIT7 : 0) | (BITS6 & num));
2493
+ num = floor(num / 64); // shift >>> 6
2494
+ // We don't need to consider the case of num === 0 so we can use a different
2495
+ // pattern here than above.
2496
+ while (num > 0) {
2497
+ write(encoder, (num > BITS7 ? BIT8 : 0) | (BITS7 & num));
2498
+ num = floor(num / 128); // shift >>> 7
2499
+ }
2500
+ };
2501
+
2502
+ /**
2503
+ * A cache to store strings temporarily
2504
+ */
2505
+ const _strBuffer = new Uint8Array(30000);
2506
+ const _maxStrBSize = _strBuffer.length / 3;
2507
+
2508
+ /**
2509
+ * Write a variable length string.
2510
+ *
2511
+ * @function
2512
+ * @param {Encoder} encoder
2513
+ * @param {String} str The string that is to be encoded.
2514
+ */
2515
+ const _writeVarStringNative = (encoder, str) => {
2516
+ if (str.length < _maxStrBSize) {
2517
+ // We can encode the string into the existing buffer
2518
+ /* c8 ignore next */
2519
+ const written = utf8TextEncoder.encodeInto(str, _strBuffer).written || 0;
2520
+ writeVarUint(encoder, written);
2521
+ for (let i = 0; i < written; i++) {
2522
+ write(encoder, _strBuffer[i]);
2523
+ }
2524
+ } else {
2525
+ writeVarUint8Array(encoder, encodeUtf8(str));
2526
+ }
2527
+ };
2528
+
2529
+ /**
2530
+ * Write a variable length string.
2531
+ *
2532
+ * @function
2533
+ * @param {Encoder} encoder
2534
+ * @param {String} str The string that is to be encoded.
2535
+ */
2536
+ const _writeVarStringPolyfill = (encoder, str) => {
2537
+ const encodedString = unescape(encodeURIComponent(str));
2538
+ const len = encodedString.length;
2539
+ writeVarUint(encoder, len);
2540
+ for (let i = 0; i < len; i++) {
2541
+ write(encoder, /** @type {number} */ (encodedString.codePointAt(i)));
2542
+ }
2543
+ };
2544
+
2545
+ /**
2546
+ * Write a variable length string.
2547
+ *
2548
+ * @function
2549
+ * @param {Encoder} encoder
2550
+ * @param {String} str The string that is to be encoded.
2551
+ */
2552
+ /* c8 ignore next */
2553
+ const writeVarString = (utf8TextEncoder && /** @type {any} */ (utf8TextEncoder).encodeInto) ? _writeVarStringNative : _writeVarStringPolyfill;
2554
+
2555
+ /**
2556
+ * Append fixed-length Uint8Array to the encoder.
2557
+ *
2558
+ * @function
2559
+ * @param {Encoder} encoder
2560
+ * @param {Uint8Array} uint8Array
2561
+ */
2562
+ const writeUint8Array = (encoder, uint8Array) => {
2563
+ const bufferLen = encoder.cbuf.length;
2564
+ const cpos = encoder.cpos;
2565
+ const leftCopyLen = min(bufferLen - cpos, uint8Array.length);
2566
+ const rightCopyLen = uint8Array.length - leftCopyLen;
2567
+ encoder.cbuf.set(uint8Array.subarray(0, leftCopyLen), cpos);
2568
+ encoder.cpos += leftCopyLen;
2569
+ if (rightCopyLen > 0) {
2570
+ // Still something to write, write right half..
2571
+ // Append new buffer
2572
+ encoder.bufs.push(encoder.cbuf);
2573
+ // must have at least size of remaining buffer
2574
+ encoder.cbuf = new Uint8Array(max(bufferLen * 2, rightCopyLen));
2575
+ // copy array
2576
+ encoder.cbuf.set(uint8Array.subarray(leftCopyLen));
2577
+ encoder.cpos = rightCopyLen;
2578
+ }
2579
+ };
2580
+
2581
+ /**
2582
+ * Append an Uint8Array to Encoder.
2583
+ *
2584
+ * @function
2585
+ * @param {Encoder} encoder
2586
+ * @param {Uint8Array} uint8Array
2587
+ */
2588
+ const writeVarUint8Array = (encoder, uint8Array) => {
2589
+ writeVarUint(encoder, uint8Array.byteLength);
2590
+ writeUint8Array(encoder, uint8Array);
2591
+ };
2592
+
2593
+ /**
2594
+ * Create an DataView of the next `len` bytes. Use it to write data after
2595
+ * calling this function.
2596
+ *
2597
+ * ```js
2598
+ * // write float32 using DataView
2599
+ * const dv = writeOnDataView(encoder, 4)
2600
+ * dv.setFloat32(0, 1.1)
2601
+ * // read float32 using DataView
2602
+ * const dv = readFromDataView(encoder, 4)
2603
+ * dv.getFloat32(0) // => 1.100000023841858 (leaving it to the reader to find out why this is the correct result)
2604
+ * ```
2605
+ *
2606
+ * @param {Encoder} encoder
2607
+ * @param {number} len
2608
+ * @return {DataView}
2609
+ */
2610
+ const writeOnDataView = (encoder, len) => {
2611
+ verifyLen(encoder, len);
2612
+ const dview = new DataView(encoder.cbuf.buffer, encoder.cpos, len);
2613
+ encoder.cpos += len;
2614
+ return dview
2615
+ };
2616
+
2617
+ /**
2618
+ * @param {Encoder} encoder
2619
+ * @param {number} num
2620
+ */
2621
+ const writeFloat32 = (encoder, num) => writeOnDataView(encoder, 4).setFloat32(0, num, false);
2622
+
2623
+ /**
2624
+ * @param {Encoder} encoder
2625
+ * @param {number} num
2626
+ */
2627
+ const writeFloat64 = (encoder, num) => writeOnDataView(encoder, 8).setFloat64(0, num, false);
2628
+
2629
+ /**
2630
+ * @param {Encoder} encoder
2631
+ * @param {bigint} num
2632
+ */
2633
+ const writeBigInt64 = (encoder, num) => /** @type {any} */ (writeOnDataView(encoder, 8)).setBigInt64(0, num, false);
2634
+
2635
+ /**
2636
+ * @param {Encoder} encoder
2637
+ * @param {bigint} num
2638
+ */
2639
+ const writeBigUint64 = (encoder, num) => /** @type {any} */ (writeOnDataView(encoder, 8)).setBigUint64(0, num, false);
2640
+
2641
+ const floatTestBed = new DataView(new ArrayBuffer(4));
2642
+ /**
2643
+ * Check if a number can be encoded as a 32 bit float.
2644
+ *
2645
+ * @param {number} num
2646
+ * @return {boolean}
2647
+ */
2648
+ const isFloat32 = num => {
2649
+ floatTestBed.setFloat32(0, num);
2650
+ return floatTestBed.getFloat32(0) === num
2651
+ };
2652
+
2653
+ /**
2654
+ * Encode data with efficient binary format.
2655
+ *
2656
+ * Differences to JSON:
2657
+ * • Transforms data to a binary format (not to a string)
2658
+ * • Encodes undefined, NaN, and ArrayBuffer (these can't be represented in JSON)
2659
+ * • Numbers are efficiently encoded either as a variable length integer, as a
2660
+ * 32 bit float, as a 64 bit float, or as a 64 bit bigint.
2661
+ *
2662
+ * Encoding table:
2663
+ *
2664
+ * | Data Type | Prefix | Encoding Method | Comment |
2665
+ * | ------------------- | -------- | ------------------ | ------- |
2666
+ * | undefined | 127 | | Functions, symbol, and everything that cannot be identified is encoded as undefined |
2667
+ * | null | 126 | | |
2668
+ * | integer | 125 | writeVarInt | Only encodes 32 bit signed integers |
2669
+ * | float32 | 124 | writeFloat32 | |
2670
+ * | float64 | 123 | writeFloat64 | |
2671
+ * | bigint | 122 | writeBigInt64 | |
2672
+ * | boolean (false) | 121 | | True and false are different data types so we save the following byte |
2673
+ * | boolean (true) | 120 | | - 0b01111000 so the last bit determines whether true or false |
2674
+ * | string | 119 | writeVarString | |
2675
+ * | object<string,any> | 118 | custom | Writes {length} then {length} key-value pairs |
2676
+ * | array<any> | 117 | custom | Writes {length} then {length} json values |
2677
+ * | Uint8Array | 116 | writeVarUint8Array | We use Uint8Array for any kind of binary data |
2678
+ *
2679
+ * Reasons for the decreasing prefix:
2680
+ * We need the first bit for extendability (later we may want to encode the
2681
+ * prefix with writeVarUint). The remaining 7 bits are divided as follows:
2682
+ * [0-30] the beginning of the data range is used for custom purposes
2683
+ * (defined by the function that uses this library)
2684
+ * [31-127] the end of the data range is used for data encoding by
2685
+ * lib0/encoding.js
2686
+ *
2687
+ * @param {Encoder} encoder
2688
+ * @param {undefined|null|number|bigint|boolean|string|Object<string,any>|Array<any>|Uint8Array} data
2689
+ */
2690
+ const writeAny = (encoder, data) => {
2691
+ switch (typeof data) {
2692
+ case 'string':
2693
+ // TYPE 119: STRING
2694
+ write(encoder, 119);
2695
+ writeVarString(encoder, data);
2696
+ break
2697
+ case 'number':
2698
+ if (isInteger(data) && abs(data) <= BITS31) {
2699
+ // TYPE 125: INTEGER
2700
+ write(encoder, 125);
2701
+ writeVarInt(encoder, data);
2702
+ } else if (isFloat32(data)) {
2703
+ // TYPE 124: FLOAT32
2704
+ write(encoder, 124);
2705
+ writeFloat32(encoder, data);
2706
+ } else {
2707
+ // TYPE 123: FLOAT64
2708
+ write(encoder, 123);
2709
+ writeFloat64(encoder, data);
2710
+ }
2711
+ break
2712
+ case 'bigint':
2713
+ // TYPE 122: BigInt
2714
+ write(encoder, 122);
2715
+ writeBigInt64(encoder, data);
2716
+ break
2717
+ case 'object':
2718
+ if (data === null) {
2719
+ // TYPE 126: null
2720
+ write(encoder, 126);
2721
+ } else if (isArray(data)) {
2722
+ // TYPE 117: Array
2723
+ write(encoder, 117);
2724
+ writeVarUint(encoder, data.length);
2725
+ for (let i = 0; i < data.length; i++) {
2726
+ writeAny(encoder, data[i]);
2727
+ }
2728
+ } else if (data instanceof Uint8Array) {
2729
+ // TYPE 116: ArrayBuffer
2730
+ write(encoder, 116);
2731
+ writeVarUint8Array(encoder, data);
2732
+ } else {
2733
+ // TYPE 118: Object
2734
+ write(encoder, 118);
2735
+ const keys = Object.keys(data);
2736
+ writeVarUint(encoder, keys.length);
2737
+ for (let i = 0; i < keys.length; i++) {
2738
+ const key = keys[i];
2739
+ writeVarString(encoder, key);
2740
+ writeAny(encoder, data[key]);
2741
+ }
2742
+ }
2743
+ break
2744
+ case 'boolean':
2745
+ // TYPE 120/121: boolean (true/false)
2746
+ write(encoder, data ? 120 : 121);
2747
+ break
2748
+ default:
2749
+ // TYPE 127: undefined
2750
+ write(encoder, 127);
2751
+ }
2752
+ };
2753
+
2754
+ function encodeYMessage(msg) {
2755
+ const encoder = new Encoder();
2756
+ writeVarString(encoder, msg.type);
2757
+ writeVarString(encoder, msg.table);
2758
+ writeVarString(encoder, msg.prop);
2759
+ switch (msg.type) {
2760
+ case 'u-ack':
2761
+ case 'u-reject':
2762
+ writeBigUint64(encoder, BigInt(msg.i));
2763
+ break;
2764
+ default:
2765
+ writeAny(encoder, msg.k);
2766
+ switch (msg.type) {
2767
+ case 'aware':
2768
+ writeVarUint8Array(encoder, msg.u);
2769
+ break;
2770
+ case 'doc-open':
2771
+ writeAny(encoder, msg.serverRev);
2772
+ writeAny(encoder, msg.sv);
2773
+ break;
2774
+ case 'doc-close':
2775
+ break;
2776
+ case 'sv':
2777
+ writeVarUint8Array(encoder, msg.sv);
2778
+ break;
2779
+ case 'u-c':
2780
+ writeVarUint8Array(encoder, msg.u);
2781
+ writeBigUint64(encoder, BigInt(msg.i));
2782
+ break;
2783
+ case 'u-s':
2784
+ writeVarUint8Array(encoder, msg.u);
2785
+ break;
2786
+ }
2787
+ }
2788
+ return toUint8Array(encoder);
2789
+ }
2790
+
2791
+ /**
2792
+ * Error helpers.
2793
+ *
2794
+ * @module error
2795
+ */
2796
+
2797
+ /**
2798
+ * @param {string} s
2799
+ * @return {Error}
2800
+ */
2801
+ /* c8 ignore next */
2802
+ const create = s => new Error(s);
2803
+
2804
+ /**
2805
+ * Efficient schema-less binary decoding with support for variable length encoding.
2806
+ *
2807
+ * Use [lib0/decoding] with [lib0/encoding]. Every encoding function has a corresponding decoding function.
2808
+ *
2809
+ * Encodes numbers in little-endian order (least to most significant byte order)
2810
+ * and is compatible with Golang's binary encoding (https://golang.org/pkg/encoding/binary/)
2811
+ * which is also used in Protocol Buffers.
2812
+ *
2813
+ * ```js
2814
+ * // encoding step
2815
+ * const encoder = encoding.createEncoder()
2816
+ * encoding.writeVarUint(encoder, 256)
2817
+ * encoding.writeVarString(encoder, 'Hello world!')
2818
+ * const buf = encoding.toUint8Array(encoder)
2819
+ * ```
2820
+ *
2821
+ * ```js
2822
+ * // decoding step
2823
+ * const decoder = decoding.createDecoder(buf)
2824
+ * decoding.readVarUint(decoder) // => 256
2825
+ * decoding.readVarString(decoder) // => 'Hello world!'
2826
+ * decoding.hasContent(decoder) // => false - all data is read
2827
+ * ```
2828
+ *
2829
+ * @module decoding
2830
+ */
2831
+
2832
+
2833
+ const errorUnexpectedEndOfArray = create('Unexpected end of array');
2834
+ const errorIntegerOutOfRange = create('Integer out of Range');
2835
+
2836
+ /**
2837
+ * A Decoder handles the decoding of an Uint8Array.
2838
+ */
2839
+ class Decoder {
2840
+ /**
2841
+ * @param {Uint8Array} uint8Array Binary data to decode
2842
+ */
2843
+ constructor (uint8Array) {
2844
+ /**
2845
+ * Decoding target.
2846
+ *
2847
+ * @type {Uint8Array}
2848
+ */
2849
+ this.arr = uint8Array;
2850
+ /**
2851
+ * Current decoding position.
2852
+ *
2853
+ * @type {number}
2854
+ */
2855
+ this.pos = 0;
2856
+ }
2857
+ }
2858
+
2859
+ /**
2860
+ * @function
2861
+ * @param {Decoder} decoder
2862
+ * @return {boolean}
2863
+ */
2864
+ const hasContent = decoder => decoder.pos !== decoder.arr.length;
2865
+
2866
+ /**
2867
+ * Create an Uint8Array view of the next `len` bytes and advance the position by `len`.
2868
+ *
2869
+ * Important: The Uint8Array still points to the underlying ArrayBuffer. Make sure to discard the result as soon as possible to prevent any memory leaks.
2870
+ * Use `buffer.copyUint8Array` to copy the result into a new Uint8Array.
2871
+ *
2872
+ * @function
2873
+ * @param {Decoder} decoder The decoder instance
2874
+ * @param {number} len The length of bytes to read
2875
+ * @return {Uint8Array}
2876
+ */
2877
+ const readUint8Array = (decoder, len) => {
2878
+ const view = new Uint8Array(decoder.arr.buffer, decoder.pos + decoder.arr.byteOffset, len);
2879
+ decoder.pos += len;
2880
+ return view
2881
+ };
2882
+
2883
+ /**
2884
+ * Read variable length Uint8Array.
2885
+ *
2886
+ * Important: The Uint8Array still points to the underlying ArrayBuffer. Make sure to discard the result as soon as possible to prevent any memory leaks.
2887
+ * Use `buffer.copyUint8Array` to copy the result into a new Uint8Array.
2888
+ *
2889
+ * @function
2890
+ * @param {Decoder} decoder
2891
+ * @return {Uint8Array}
2892
+ */
2893
+ const readVarUint8Array = decoder => readUint8Array(decoder, readVarUint(decoder));
2894
+
2895
+ /**
2896
+ * Read one byte as unsigned integer.
2897
+ * @function
2898
+ * @param {Decoder} decoder The decoder instance
2899
+ * @return {number} Unsigned 8-bit integer
2900
+ */
2901
+ const readUint8 = decoder => decoder.arr[decoder.pos++];
2902
+
2903
+ /**
2904
+ * Read unsigned integer (32bit) with variable length.
2905
+ * 1/8th of the storage is used as encoding overhead.
2906
+ * * numbers < 2^7 is stored in one bytlength
2907
+ * * numbers < 2^14 is stored in two bylength
2908
+ *
2909
+ * @function
2910
+ * @param {Decoder} decoder
2911
+ * @return {number} An unsigned integer.length
2912
+ */
2913
+ const readVarUint = decoder => {
2914
+ let num = 0;
2915
+ let mult = 1;
2916
+ const len = decoder.arr.length;
2917
+ while (decoder.pos < len) {
2918
+ const r = decoder.arr[decoder.pos++];
2919
+ // num = num | ((r & binary.BITS7) << len)
2920
+ num = num + (r & BITS7) * mult; // shift $r << (7*#iterations) and add it to num
2921
+ mult *= 128; // next iteration, shift 7 "more" to the left
2922
+ if (r < BIT8) {
2923
+ return num
2924
+ }
2925
+ /* c8 ignore start */
2926
+ if (num > MAX_SAFE_INTEGER) {
2927
+ throw errorIntegerOutOfRange
2928
+ }
2929
+ /* c8 ignore stop */
2930
+ }
2931
+ throw errorUnexpectedEndOfArray
2932
+ };
2933
+
2934
+ /**
2935
+ * Read signed integer (32bit) with variable length.
2936
+ * 1/8th of the storage is used as encoding overhead.
2937
+ * * numbers < 2^7 is stored in one bytlength
2938
+ * * numbers < 2^14 is stored in two bylength
2939
+ * @todo This should probably create the inverse ~num if number is negative - but this would be a breaking change.
2940
+ *
2941
+ * @function
2942
+ * @param {Decoder} decoder
2943
+ * @return {number} An unsigned integer.length
2944
+ */
2945
+ const readVarInt = decoder => {
2946
+ let r = decoder.arr[decoder.pos++];
2947
+ let num = r & BITS6;
2948
+ let mult = 64;
2949
+ const sign = (r & BIT7) > 0 ? -1 : 1;
2950
+ if ((r & BIT8) === 0) {
2951
+ // don't continue reading
2952
+ return sign * num
2953
+ }
2954
+ const len = decoder.arr.length;
2955
+ while (decoder.pos < len) {
2956
+ r = decoder.arr[decoder.pos++];
2957
+ // num = num | ((r & binary.BITS7) << len)
2958
+ num = num + (r & BITS7) * mult;
2959
+ mult *= 128;
2960
+ if (r < BIT8) {
2961
+ return sign * num
2962
+ }
2963
+ /* c8 ignore start */
2964
+ if (num > MAX_SAFE_INTEGER) {
2965
+ throw errorIntegerOutOfRange
2966
+ }
2967
+ /* c8 ignore stop */
2968
+ }
2969
+ throw errorUnexpectedEndOfArray
2970
+ };
2971
+
2972
+ /**
2973
+ * We don't test this function anymore as we use native decoding/encoding by default now.
2974
+ * Better not modify this anymore..
2975
+ *
2976
+ * Transforming utf8 to a string is pretty expensive. The code performs 10x better
2977
+ * when String.fromCodePoint is fed with all characters as arguments.
2978
+ * But most environments have a maximum number of arguments per functions.
2979
+ * For effiency reasons we apply a maximum of 10000 characters at once.
2980
+ *
2981
+ * @function
2982
+ * @param {Decoder} decoder
2983
+ * @return {String} The read String.
2984
+ */
2985
+ /* c8 ignore start */
2986
+ const _readVarStringPolyfill = decoder => {
2987
+ let remainingLen = readVarUint(decoder);
2988
+ if (remainingLen === 0) {
2989
+ return ''
2990
+ } else {
2991
+ let encodedString = String.fromCodePoint(readUint8(decoder)); // remember to decrease remainingLen
2992
+ if (--remainingLen < 100) { // do not create a Uint8Array for small strings
2993
+ while (remainingLen--) {
2994
+ encodedString += String.fromCodePoint(readUint8(decoder));
2995
+ }
2996
+ } else {
2997
+ while (remainingLen > 0) {
2998
+ const nextLen = remainingLen < 10000 ? remainingLen : 10000;
2999
+ // this is dangerous, we create a fresh array view from the existing buffer
3000
+ const bytes = decoder.arr.subarray(decoder.pos, decoder.pos + nextLen);
3001
+ decoder.pos += nextLen;
3002
+ // Starting with ES5.1 we can supply a generic array-like object as arguments
3003
+ encodedString += String.fromCodePoint.apply(null, /** @type {any} */ (bytes));
3004
+ remainingLen -= nextLen;
3005
+ }
3006
+ }
3007
+ return decodeURIComponent(escape(encodedString))
3008
+ }
3009
+ };
3010
+ /* c8 ignore stop */
3011
+
3012
+ /**
3013
+ * @function
3014
+ * @param {Decoder} decoder
3015
+ * @return {String} The read String
3016
+ */
3017
+ const _readVarStringNative = decoder =>
3018
+ /** @type any */ (utf8TextDecoder).decode(readVarUint8Array(decoder));
3019
+
3020
+ /**
3021
+ * Read string of variable length
3022
+ * * varUint is used to store the length of the string
3023
+ *
3024
+ * @function
3025
+ * @param {Decoder} decoder
3026
+ * @return {String} The read String
3027
+ *
3028
+ */
3029
+ /* c8 ignore next */
3030
+ const readVarString = utf8TextDecoder ? _readVarStringNative : _readVarStringPolyfill;
3031
+
3032
+ /**
3033
+ * @param {Decoder} decoder
3034
+ * @param {number} len
3035
+ * @return {DataView}
3036
+ */
3037
+ const readFromDataView = (decoder, len) => {
3038
+ const dv = new DataView(decoder.arr.buffer, decoder.arr.byteOffset + decoder.pos, len);
3039
+ decoder.pos += len;
3040
+ return dv
3041
+ };
3042
+
3043
+ /**
3044
+ * @param {Decoder} decoder
3045
+ */
3046
+ const readFloat32 = decoder => readFromDataView(decoder, 4).getFloat32(0, false);
3047
+
3048
+ /**
3049
+ * @param {Decoder} decoder
3050
+ */
3051
+ const readFloat64 = decoder => readFromDataView(decoder, 8).getFloat64(0, false);
3052
+
3053
+ /**
3054
+ * @param {Decoder} decoder
3055
+ */
3056
+ const readBigInt64 = decoder => /** @type {any} */ (readFromDataView(decoder, 8)).getBigInt64(0, false);
3057
+
3058
+ /**
3059
+ * @param {Decoder} decoder
3060
+ */
3061
+ const readBigUint64 = decoder => /** @type {any} */ (readFromDataView(decoder, 8)).getBigUint64(0, false);
3062
+
3063
+ /**
3064
+ * @type {Array<function(Decoder):any>}
3065
+ */
3066
+ const readAnyLookupTable = [
3067
+ decoder => undefined, // CASE 127: undefined
3068
+ decoder => null, // CASE 126: null
3069
+ readVarInt, // CASE 125: integer
3070
+ readFloat32, // CASE 124: float32
3071
+ readFloat64, // CASE 123: float64
3072
+ readBigInt64, // CASE 122: bigint
3073
+ decoder => false, // CASE 121: boolean (false)
3074
+ decoder => true, // CASE 120: boolean (true)
3075
+ readVarString, // CASE 119: string
3076
+ decoder => { // CASE 118: object<string,any>
3077
+ const len = readVarUint(decoder);
3078
+ /**
3079
+ * @type {Object<string,any>}
3080
+ */
3081
+ const obj = {};
3082
+ for (let i = 0; i < len; i++) {
3083
+ const key = readVarString(decoder);
3084
+ obj[key] = readAny(decoder);
3085
+ }
3086
+ return obj
3087
+ },
3088
+ decoder => { // CASE 117: array<any>
3089
+ const len = readVarUint(decoder);
3090
+ const arr = [];
3091
+ for (let i = 0; i < len; i++) {
3092
+ arr.push(readAny(decoder));
3093
+ }
3094
+ return arr
3095
+ },
3096
+ readVarUint8Array // CASE 116: Uint8Array
3097
+ ];
3098
+
3099
+ /**
3100
+ * @param {Decoder} decoder
3101
+ */
3102
+ const readAny = decoder => readAnyLookupTable[127 - readUint8(decoder)](decoder);
3103
+
3104
+ function decodeYMessage(a) {
3105
+ const decoder = new Decoder(a);
3106
+ const type = readVarString(decoder);
3107
+ const table = readVarString(decoder);
3108
+ const prop = readVarString(decoder);
3109
+ switch (type) {
3110
+ case 'u-ack':
3111
+ case 'u-reject':
3112
+ return {
3113
+ type,
3114
+ table,
3115
+ prop,
3116
+ i: Number(readBigUint64(decoder)),
3117
+ };
3118
+ default: {
3119
+ const k = readAny(decoder);
3120
+ switch (type) {
3121
+ case 'in-sync':
3122
+ return { type, table, prop, k };
3123
+ case 'aware':
3124
+ return {
3125
+ type,
3126
+ table,
3127
+ prop,
3128
+ k,
3129
+ u: readVarUint8Array(decoder),
3130
+ };
3131
+ case 'doc-open':
3132
+ return {
3133
+ type,
3134
+ table,
3135
+ prop,
3136
+ k,
3137
+ serverRev: readAny(decoder),
3138
+ sv: readAny(decoder),
3139
+ };
3140
+ case 'doc-close':
3141
+ return { type, table, prop, k };
3142
+ case 'sv':
3143
+ return {
3144
+ type,
3145
+ table,
3146
+ prop,
3147
+ k,
3148
+ sv: readVarUint8Array(decoder),
3149
+ };
3150
+ case 'u-c':
3151
+ return {
3152
+ type,
3153
+ table,
3154
+ prop,
3155
+ k,
3156
+ u: readVarUint8Array(decoder),
3157
+ i: Number(readBigUint64(decoder)),
3158
+ };
3159
+ case 'u-s':
3160
+ return {
3161
+ type,
3162
+ table,
3163
+ prop,
3164
+ k,
3165
+ u: readVarUint8Array(decoder)
3166
+ };
3167
+ default:
3168
+ throw new TypeError(`Unknown message type: ${type}`);
3169
+ }
3170
+ }
3171
+ }
3172
+ }
3173
+
3174
+ async function asyncIterablePipeline(source, ...stages) {
3175
+ var _a, e_1, _b, _c;
3176
+ // Chain generators by sending outdata from one to another
3177
+ let result = source(); // Start with the source generator
3178
+ for (let i = 0; i < stages.length; i++) {
3179
+ result = stages[i](result); // Pass on the result to next generator
3180
+ }
3181
+ try {
3182
+ // Start running the machine. If the last stage is a sink, it will consume the data and never emit anything
3183
+ // to us here...
3184
+ 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) {
3185
+ _c = result_1_1.value;
3186
+ _d = false;
3187
+ const chunk = _c;
3188
+ }
3189
+ }
3190
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
3191
+ finally {
3192
+ try {
3193
+ if (!_d && !_a && (_b = result_1.return)) await _b.call(result_1);
3194
+ }
3195
+ finally { if (e_1) throw e_1.error; }
3196
+ }
3197
+ }
3198
+
3199
+ function consumeChunkedBinaryStream(source) {
3200
+ return __asyncGenerator(this, arguments, function* consumeChunkedBinaryStream_1() {
3201
+ var _a, e_1, _b, _c;
3202
+ let state = 0;
3203
+ let sizeBuf = new Uint8Array(4);
3204
+ let sizeBufPos = 0;
3205
+ let bufs = [];
3206
+ let len = 0;
3207
+ try {
3208
+ 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) {
3209
+ _c = source_1_1.value;
3210
+ _d = false;
3211
+ const chunk = _c;
3212
+ const dw = new DataView(chunk.buffer, chunk.byteOffset, chunk.byteLength);
3213
+ let pos = 0;
3214
+ while (pos < chunk.byteLength) {
3215
+ switch (state) {
3216
+ case 0:
3217
+ // Beginning of a size header
3218
+ if (pos + 4 > chunk.byteLength) {
3219
+ for (const b of chunk.slice(pos)) {
3220
+ if (sizeBufPos === 4)
3221
+ break;
3222
+ sizeBuf[sizeBufPos++] = b;
3223
+ ++pos;
3224
+ }
3225
+ if (sizeBufPos < 4) {
3226
+ // Need more bytes in order to read length.
3227
+ // Will go out from while loop as well because pos is defenitely = chunk.byteLength here.
3228
+ break;
3229
+ }
3230
+ }
3231
+ else if (sizeBufPos > 0 && sizeBufPos < 4) {
3232
+ for (const b of chunk.slice(pos, pos + 4 - sizeBufPos)) {
3233
+ sizeBuf[sizeBufPos++] = b;
3234
+ ++pos;
3235
+ }
3236
+ }
3237
+ // Intentional fall-through...
3238
+ case 1:
3239
+ len =
3240
+ sizeBufPos === 4
3241
+ ? new DataView(sizeBuf.buffer, 0, 4).getUint32(0, false)
3242
+ : dw.getUint32(pos, false);
3243
+ if (sizeBufPos)
3244
+ sizeBufPos = 0; // in this case pos is already forwarded
3245
+ else
3246
+ pos += 4; // else pos is not yet forwarded - that's why we do it now
3247
+ // Intentional fall-through...
3248
+ case 2:
3249
+ // Eat the chunk
3250
+ if (pos >= chunk.byteLength) {
3251
+ state = 2;
3252
+ break;
3253
+ }
3254
+ if (pos + len > chunk.byteLength) {
3255
+ bufs.push(chunk.slice(pos));
3256
+ len -= (chunk.byteLength - pos);
3257
+ state = 2;
3258
+ pos = chunk.byteLength; // will break while loop.
3259
+ }
3260
+ else {
3261
+ if (bufs.length > 0) {
3262
+ const concats = new Uint8Array(bufs.reduce((p, c) => p + c.byteLength, len));
3263
+ let p = 0;
3264
+ for (const buf of bufs) {
3265
+ concats.set(buf, p);
3266
+ p += buf.byteLength;
3267
+ }
3268
+ concats.set(chunk.slice(pos, pos + len), p);
3269
+ bufs = [];
3270
+ yield yield __await(concats);
3271
+ }
3272
+ else {
3273
+ yield yield __await(chunk.slice(pos, pos + len));
3274
+ }
3275
+ pos += len;
3276
+ state = 0;
3277
+ }
3278
+ break;
3279
+ }
3280
+ }
3281
+ }
3282
+ }
3283
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
3284
+ finally {
3285
+ try {
3286
+ if (!_d && !_a && (_b = source_1.return)) yield __await(_b.call(source_1));
3287
+ }
3288
+ finally { if (e_1) throw e_1.error; }
3289
+ }
3290
+ });
3291
+ }
3292
+
3293
+ function getFetchResponseBodyGenerator(res) {
3294
+ return function () {
3295
+ return __asyncGenerator(this, arguments, function* () {
3296
+ if (!res.body)
3297
+ throw new Error("Response body is not readable");
3298
+ const reader = res.body.getReader();
3299
+ try {
3300
+ while (true) {
3301
+ const { done, value } = yield __await(reader.read());
3302
+ if (done)
3303
+ return yield __await(void 0);
3304
+ yield yield __await(value);
3305
+ }
3306
+ }
3307
+ finally {
3308
+ reader.releaseLock();
3309
+ }
3310
+ });
3311
+ };
3312
+ }
3313
+
2245
3314
  function listSyncifiedChanges(tablesToSyncify, currentUser, schema, alreadySyncedRealms) {
2246
3315
  return __awaiter(this, void 0, void 0, function* () {
2247
3316
  const txid = `upload-${randomString$1(8)}`;
@@ -3308,7 +4377,7 @@ function updateSyncRateLimitDelays(db, res) {
3308
4377
  }
3309
4378
 
3310
4379
  //import {BisonWebStreamReader} from "dreambase-library/dist/typeson-simplified/BisonWebStreamReader";
3311
- function syncWithServer(changes, syncState, baseRevs, db, databaseUrl, schema, clientIdentity, currentUser) {
4380
+ function syncWithServer(changes, y, syncState, baseRevs, db, databaseUrl, schema, clientIdentity, currentUser) {
3312
4381
  return __awaiter(this, void 0, void 0, function* () {
3313
4382
  //
3314
4383
  // Push changes to server using fetch
@@ -3346,6 +4415,7 @@ function syncWithServer(changes, syncState, baseRevs, db, databaseUrl, schema, c
3346
4415
  : undefined,
3347
4416
  baseRevs,
3348
4417
  changes: encodeIdsForServer(db.dx.core.schema, currentUser, changes),
4418
+ y,
3349
4419
  };
3350
4420
  console.debug('Sync request', syncRequest);
3351
4421
  db.syncStateChangedEvent.next({
@@ -3559,6 +4629,339 @@ function applyServerChanges(changes, db) {
3559
4629
  });
3560
4630
  }
3561
4631
 
4632
+ const DEXIE_CLOUD_SYNCER_ID = 'dexie-cloud-syncer';
4633
+
4634
+ function listUpdatesSince(yTable, sinceIncluding) {
4635
+ return yTable
4636
+ .where('i')
4637
+ .between(sinceIncluding, Infinity, true)
4638
+ .toArray();
4639
+ }
4640
+
4641
+ function $Y(db) {
4642
+ const $Y = db.dx._options.Y;
4643
+ if (!$Y)
4644
+ throw new Error('Y library not supplied to Dexie constructor');
4645
+ return $Y;
4646
+ }
4647
+
4648
+ /** Queries the local database for YMessages to send to server.
4649
+ *
4650
+ * There are 2 messages that this function can provide:
4651
+ * YUpdateFromClientRequest ( for local updates )
4652
+ * YStateVector ( for state vector of foreign updates so that server can reduce the number of udpates to send back )
4653
+ *
4654
+ * Notice that we do not do a step 1 sync phase here to get a state vector from the server. Reason we can avoid
4655
+ * the 2-step sync is that we are client-server and not client-client here and we keep track of the client changes
4656
+ * sent to server by letting server acknowledge them. There is always a chance that some client update has already
4657
+ * been sent and that the client failed to receive the ack. However, if this happens it does not matter - the change
4658
+ * would be sent again and Yjs handles duplicate changes anyway. And it's rare so we earn the cost of roundtrips by
4659
+ * avoiding the step1 sync and instead keep track of this in the `unsentFrom` property of the SyncState.
4660
+ *
4661
+ * @param db
4662
+ * @returns
4663
+ */
4664
+ function listYClientMessagesAndStateVector(db) {
4665
+ var _a;
4666
+ return __awaiter(this, void 0, void 0, function* () {
4667
+ const result = [];
4668
+ const lastUpdateIds = {};
4669
+ for (const table of db.tables) {
4670
+ if (table.schema.yProps && ((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[table.name].markedForSync)) {
4671
+ for (const yProp of table.schema.yProps) {
4672
+ const Y = $Y(db); // This is how we retrieve the user-provided Y library
4673
+ const yTable = db.table(yProp.updatesTable); // the updates-table for this combo of table+propName
4674
+ const syncState = (yield yTable.get(DEXIE_CLOUD_SYNCER_ID));
4675
+ // unsentFrom = the `i` value of updates that aren't yet sent to server (or at least not acked by the server yet)
4676
+ const unsentFrom = (syncState === null || syncState === void 0 ? void 0 : syncState.unsentFrom) || 1;
4677
+ // 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
4678
+ const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
4679
+ // Compute the least value of these two (but since receivedUntil is inclusive we need to add +1 to it)
4680
+ const unsyncedFrom = Math.min(unsentFrom, receivedUntil + 1);
4681
+ // Query all these updates for all docs of this table+prop combination
4682
+ const updates = yield listUpdatesSince(yTable, unsyncedFrom);
4683
+ if (updates.length > 0)
4684
+ lastUpdateIds[yTable.name] = updates[updates.length - 1].i;
4685
+ // Now sort them by document and whether they are local or not + ignore local updates already sent:
4686
+ const perDoc = {};
4687
+ for (const update of updates) {
4688
+ // Sort updates into buckets of the doc primary key + the flag (whether it's local or foreign)
4689
+ const isLocal = ((update.f || 0) & 0x01) === 0x01;
4690
+ if (isLocal && update.i < unsentFrom)
4691
+ continue; // This local update has already been sent and acked.
4692
+ const docKey = JSON.stringify(update.k) + '/' + isLocal;
4693
+ let entry = perDoc[docKey];
4694
+ if (!entry) {
4695
+ perDoc[docKey] = entry = {
4696
+ i: update.i,
4697
+ k: update.k,
4698
+ isLocal,
4699
+ u: [],
4700
+ };
4701
+ entry.u.push(update.u);
4702
+ }
4703
+ else {
4704
+ entry.u.push(update.u);
4705
+ entry.i = Math.max(update.i, entry.i);
4706
+ }
4707
+ }
4708
+ // Now, go through all these and:
4709
+ // * For local updates, compute a merged update per document.
4710
+ // * For foreign updates, compute a state vector to pass to server, so that server can
4711
+ // avoid re-sending updates that we already have (they might have been sent of websocket
4712
+ // and when that happens, we do not mark them in any way nor do we update receivedUntil -
4713
+ // we only update receivedUntil after a "full sync" (syncWithServer()))
4714
+ for (const { k, isLocal, u, i } of Object.values(perDoc)) {
4715
+ const mergedUpdate = u.length === 1 ? u[0] : Y.mergeUpdatesV2(u);
4716
+ if (isLocal) {
4717
+ result.push({
4718
+ type: 'u-c',
4719
+ table: table.name,
4720
+ prop: yProp.prop,
4721
+ k,
4722
+ u: mergedUpdate,
4723
+ i,
4724
+ });
4725
+ }
4726
+ else {
4727
+ const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
4728
+ result.push({
4729
+ type: 'sv',
4730
+ table: table.name,
4731
+ prop: yProp.prop,
4732
+ k,
4733
+ sv: stateVector,
4734
+ });
4735
+ }
4736
+ }
4737
+ }
4738
+ }
4739
+ }
4740
+ return {
4741
+ yMessages: result,
4742
+ lastUpdateIds
4743
+ };
4744
+ });
4745
+ }
4746
+
4747
+ function getUpdatesTable(db, table, ydocProp) {
4748
+ var _a, _b, _c;
4749
+ 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;
4750
+ if (!utbl)
4751
+ throw new Error(`No updatesTable found for ${table}.${ydocProp}`);
4752
+ return db.table(utbl);
4753
+ }
4754
+
4755
+ function applyYServerMessages(yMessages, db) {
4756
+ return __awaiter(this, void 0, void 0, function* () {
4757
+ const result = {};
4758
+ for (const m of yMessages) {
4759
+ switch (m.type) {
4760
+ case 'u-s': {
4761
+ const utbl = getUpdatesTable(db, m.table, m.prop);
4762
+ result[utbl.name] = yield utbl.add({
4763
+ k: m.k,
4764
+ u: m.u,
4765
+ });
4766
+ break;
4767
+ }
4768
+ case 'u-ack': {
4769
+ const utbl = getUpdatesTable(db, m.table, m.prop);
4770
+ yield db.transaction('rw', utbl, (tx) => __awaiter(this, void 0, void 0, function* () {
4771
+ let syncer = (yield tx
4772
+ .table(utbl.name)
4773
+ .get(DEXIE_CLOUD_SYNCER_ID));
4774
+ 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) }));
4775
+ }));
4776
+ break;
4777
+ }
4778
+ case 'u-reject': {
4779
+ // Acces control or constraint rejected the update.
4780
+ // We delete it. It's not going to be sent again.
4781
+ // What's missing is a way to notify consumers, such as Tiptap editor, that the update was rejected.
4782
+ // This is only an issue when the document is open. We could find the open document and
4783
+ // in a perfect world, we should send a reverse update to the open document to undo the change.
4784
+ // See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
4785
+ console.debug(`Y update rejected. Deleting it.`);
4786
+ const utbl = getUpdatesTable(db, m.table, m.prop);
4787
+ yield utbl.delete(m.i);
4788
+ break;
4789
+ }
4790
+ case 'in-sync': {
4791
+ const doc = DexieYProvider.getDocCache(db.dx).find(m.table, m.k, m.prop);
4792
+ if (doc && !doc.isSynced) {
4793
+ doc.emit('sync', [true]);
4794
+ }
4795
+ break;
4796
+ }
4797
+ }
4798
+ }
4799
+ return result;
4800
+ });
4801
+ }
4802
+
4803
+ function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db, serverRevision) {
4804
+ var _a, _b;
4805
+ return __awaiter(this, void 0, void 0, function* () {
4806
+ // We want to update unsentFrom for each yTable to the value specified in first argument
4807
+ // because we got those values before we synced with server and here we are back from server
4808
+ // that has successfully received all those messages - no matter if the last update was a client or server update,
4809
+ // we can safely store unsentFrom to a value of the last update + 1 here.
4810
+ // We also want to update receivedUntil for each yTable to the value specified in the second argument,
4811
+ // because that contains the highest resulted id of each update from server after storing it.
4812
+ // We could do these two tasks separately, but that would require two update calls on the same YSyncState, so
4813
+ // to optimize the dexie calls, we merge these two maps into a single one so we can do a single update request
4814
+ // per yTable.
4815
+ const mergedSpec = {};
4816
+ for (const [yTable, lastUpdateId] of Object.entries(lastUpdateIdsBeforeSync)) {
4817
+ (_a = mergedSpec[yTable]) !== null && _a !== void 0 ? _a : (mergedSpec[yTable] = {});
4818
+ mergedSpec[yTable].unsentFrom = lastUpdateId + 1;
4819
+ }
4820
+ for (const [yTable, lastUpdateId] of Object.entries(receivedUntilsAfterSync)) {
4821
+ (_b = mergedSpec[yTable]) !== null && _b !== void 0 ? _b : (mergedSpec[yTable] = {});
4822
+ mergedSpec[yTable].receivedUntil = lastUpdateId;
4823
+ }
4824
+ // Now go through the merged map and update YSyncStates accordingly:
4825
+ for (const [yTable, { unsentFrom, receivedUntil }] of Object.entries(mergedSpec)) {
4826
+ // We're already in a transaction, but for the sake of
4827
+ // code readability and correctness, let's launch an atomic sub transaction:
4828
+ yield db.transaction('rw', yTable, () => __awaiter(this, void 0, void 0, function* () {
4829
+ const state = yield db.table(yTable).get(DEXIE_CLOUD_SYNCER_ID);
4830
+ if (!state) {
4831
+ yield db.table(yTable).add({
4832
+ i: DEXIE_CLOUD_SYNCER_ID,
4833
+ unsentFrom: unsentFrom || 1,
4834
+ receivedUntil: receivedUntil || 0,
4835
+ serverRev: serverRevision,
4836
+ });
4837
+ }
4838
+ else {
4839
+ if (unsentFrom) {
4840
+ state.unsentFrom = Math.max(unsentFrom, state.unsentFrom || 1);
4841
+ }
4842
+ if (receivedUntil) {
4843
+ state.receivedUntil = Math.max(receivedUntil, state.receivedUntil || 0);
4844
+ state.serverRev = serverRevision;
4845
+ }
4846
+ yield db.table(yTable).put(state);
4847
+ }
4848
+ }));
4849
+ }
4850
+ });
4851
+ }
4852
+
4853
+ const BINSTREAM_TYPE_REALMID = 1;
4854
+ const BINSTREAM_TYPE_TABLE_AND_PROP = 2;
4855
+ const BINSTREAM_TYPE_DOCUMENT = 3;
4856
+ function downloadYDocsFromServer(db, databaseUrl, { yDownloadedRealms, realms }) {
4857
+ return __awaiter(this, void 0, void 0, function* () {
4858
+ if (yDownloadedRealms && realms && realms.every(realmId => yDownloadedRealms[realmId] === '*')) {
4859
+ return; // Already done!
4860
+ }
4861
+ console.debug('Downloading Y.Docs from added realms');
4862
+ const user = yield loadAccessToken(db);
4863
+ const headers = {
4864
+ 'Content-Type': 'application/json',
4865
+ Accept: 'application/octet-stream',
4866
+ };
4867
+ if (user) {
4868
+ headers.Authorization = `Bearer ${user.accessToken}`;
4869
+ }
4870
+ const res = yield fetch(`${databaseUrl}/y/download`, {
4871
+ body: TSON.stringify({ downloadedRealms: yDownloadedRealms || {} }),
4872
+ method: 'POST',
4873
+ headers,
4874
+ credentials: 'include',
4875
+ });
4876
+ if (!res.ok) {
4877
+ throw new Error(`Failed to download Yjs documents from server. Status: ${res.status}`);
4878
+ }
4879
+ yield asyncIterablePipeline(getFetchResponseBodyGenerator(res), consumeChunkedBinaryStream, consumeDownloadChunks);
4880
+ function consumeDownloadChunks(chunks) {
4881
+ return __asyncGenerator(this, arguments, function* consumeDownloadChunks_1() {
4882
+ var _a, e_1, _b, _c;
4883
+ let currentRealmId = null;
4884
+ let currentTable = null;
4885
+ let currentProp = null;
4886
+ let docsToInsert = [];
4887
+ function storeCollectedDocs(completedRealm) {
4888
+ return __awaiter(this, void 0, void 0, function* () {
4889
+ const lastDoc = docsToInsert[docsToInsert.length - 1];
4890
+ if (docsToInsert.length > 0) {
4891
+ if (!currentRealmId || !currentTable || !currentProp) {
4892
+ throw new Error(`Protocol error from ${databaseUrl}/y/download`);
4893
+ }
4894
+ const yTable = getUpdatesTable(db, currentTable, currentProp);
4895
+ yield yTable.bulkAdd(docsToInsert);
4896
+ docsToInsert = [];
4897
+ }
4898
+ if (currentRealmId && currentTable && currentProp && (lastDoc || completedRealm)) {
4899
+ yield db.$syncState.update('syncState', completedRealm
4900
+ ? '*'
4901
+ : {
4902
+ [`yDownloadedRealms.${currentRealmId}`]: {
4903
+ tbl: currentTable,
4904
+ prop: currentProp,
4905
+ key: lastDoc.k,
4906
+ },
4907
+ });
4908
+ }
4909
+ });
4910
+ }
4911
+ try {
4912
+ try {
4913
+ 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) {
4914
+ _c = chunks_1_1.value;
4915
+ _d = false;
4916
+ const chunk = _c;
4917
+ const decoder = new Decoder(chunk);
4918
+ while (hasContent(decoder)) {
4919
+ switch (readUint8(decoder)) {
4920
+ case BINSTREAM_TYPE_REALMID:
4921
+ yield __await(storeCollectedDocs(true));
4922
+ currentRealmId = readVarString(decoder);
4923
+ break;
4924
+ case BINSTREAM_TYPE_TABLE_AND_PROP:
4925
+ yield __await(storeCollectedDocs(false)); // still on same realm
4926
+ currentTable = readVarString(decoder);
4927
+ currentProp = readVarString(decoder);
4928
+ break;
4929
+ case BINSTREAM_TYPE_DOCUMENT: {
4930
+ const k = readAny(decoder);
4931
+ const u = readVarUint8Array(decoder);
4932
+ docsToInsert.push({
4933
+ k,
4934
+ u,
4935
+ });
4936
+ break;
4937
+ }
4938
+ }
4939
+ }
4940
+ yield __await(storeCollectedDocs(false)); // Chunk full - migth still be on same realm
4941
+ }
4942
+ }
4943
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
4944
+ finally {
4945
+ try {
4946
+ if (!_d && !_a && (_b = chunks_1.return)) yield __await(_b.call(chunks_1));
4947
+ }
4948
+ finally { if (e_1) throw e_1.error; }
4949
+ }
4950
+ yield __await(storeCollectedDocs(true)); // Everything downloaded - finalize last downloaded realm to "*"
4951
+ }
4952
+ catch (error) {
4953
+ if (!(error instanceof Dexie.DexieError)) {
4954
+ // Network error might have happened.
4955
+ // Store what we've collected so far:
4956
+ yield __await(storeCollectedDocs(false));
4957
+ }
4958
+ throw error;
4959
+ }
4960
+ });
4961
+ }
4962
+ });
4963
+ }
4964
+
3562
4965
  const CURRENT_SYNC_WORKER = 'currentSyncWorker';
3563
4966
  function sync(db, options, schema, syncOptions) {
3564
4967
  return _sync
@@ -3647,10 +5050,11 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
3647
5050
  //
3648
5051
  // List changes to sync
3649
5052
  //
3650
- const [clientChangeSet, syncState, baseRevs] = yield db.transaction('r', db.tables, () => __awaiter(this, void 0, void 0, function* () {
5053
+ const [clientChangeSet, syncState, baseRevs, { yMessages, lastUpdateIds }] = yield db.transaction('r', db.tables, () => __awaiter(this, void 0, void 0, function* () {
3651
5054
  const syncState = yield db.getPersistedSyncState();
3652
5055
  const baseRevs = yield db.$baseRevs.toArray();
3653
5056
  let clientChanges = yield listClientChanges(mutationTables);
5057
+ const yResults = yield listYClientMessagesAndStateVector(db);
3654
5058
  throwIfCancelled(cancelToken);
3655
5059
  if (doSyncify) {
3656
5060
  const alreadySyncedRealms = [
@@ -3660,11 +5064,11 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
3660
5064
  const syncificationInserts = yield listSyncifiedChanges(tablesToSyncify, currentUser, schema, alreadySyncedRealms);
3661
5065
  throwIfCancelled(cancelToken);
3662
5066
  clientChanges = clientChanges.concat(syncificationInserts);
3663
- return [clientChanges, syncState, baseRevs];
5067
+ return [clientChanges, syncState, baseRevs, yResults];
3664
5068
  }
3665
- return [clientChanges, syncState, baseRevs];
5069
+ return [clientChanges, syncState, baseRevs, yResults];
3666
5070
  }));
3667
- const pushSyncIsNeeded = clientChangeSet.some((set) => set.muts.some((mut) => mut.keys.length > 0));
5071
+ const pushSyncIsNeeded = clientChangeSet.some((set) => set.muts.some((mut) => mut.keys.length > 0)) || yMessages.some(m => m.type === 'u-c');
3668
5072
  if (justCheckIfNeeded) {
3669
5073
  console.debug('Sync is needed:', pushSyncIsNeeded);
3670
5074
  return pushSyncIsNeeded;
@@ -3679,12 +5083,12 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
3679
5083
  // Push changes to server
3680
5084
  //
3681
5085
  throwIfCancelled(cancelToken);
3682
- const res = yield syncWithServer(clientChangeSet, syncState, baseRevs, db, databaseUrl, schema, clientIdentity, currentUser);
5086
+ const res = yield syncWithServer(clientChangeSet, yMessages, syncState, baseRevs, db, databaseUrl, schema, clientIdentity, currentUser);
3683
5087
  console.debug('Sync response', res);
3684
5088
  //
3685
5089
  // Apply changes locally and clear old change entries:
3686
5090
  //
3687
- const done = yield db.transaction('rw', db.tables, (tx) => __awaiter(this, void 0, void 0, function* () {
5091
+ const { done, newSyncState } = yield db.transaction('rw', db.tables, (tx) => __awaiter(this, void 0, void 0, function* () {
3688
5092
  // @ts-ignore
3689
5093
  tx.idbtrans.disableChangeTracking = true;
3690
5094
  // @ts-ignore
@@ -3776,17 +5180,35 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
3776
5180
  // apply server changes
3777
5181
  //
3778
5182
  yield applyServerChanges(filteredChanges, db);
5183
+ if (res.yMessages) {
5184
+ //
5185
+ // apply yMessages
5186
+ //
5187
+ const receivedUntils = yield applyYServerMessages(res.yMessages, db);
5188
+ //
5189
+ // update Y SyncStates
5190
+ //
5191
+ yield updateYSyncStates(lastUpdateIds, receivedUntils, db, res.serverRevision);
5192
+ }
3779
5193
  //
3780
- // Update syncState
5194
+ // Update regular syncState
3781
5195
  //
3782
5196
  db.$syncState.put(newSyncState, 'syncState');
3783
- return addedClientChanges.length === 0;
5197
+ return {
5198
+ done: addedClientChanges.length === 0,
5199
+ newSyncState
5200
+ };
3784
5201
  }));
3785
5202
  if (!done) {
3786
5203
  console.debug('MORE SYNC NEEDED. Go for it again!');
3787
5204
  yield checkSyncRateLimitDelay(db);
3788
5205
  return yield _sync(db, options, schema, { isInitialSync, cancelToken });
3789
5206
  }
5207
+ const usingYProps = Object.values(schema).some(tbl => { var _a; return (_a = tbl.yProps) === null || _a === void 0 ? void 0 : _a.length; });
5208
+ const serverSupportsYprops = !!res.yMessages;
5209
+ if (usingYProps && serverSupportsYprops) {
5210
+ yield downloadYDocsFromServer(db, databaseUrl, newSyncState);
5211
+ }
3790
5212
  console.debug('SYNC DONE', { isInitialSync });
3791
5213
  db.syncCompleteEvent.next();
3792
5214
  return false; // Not needed anymore
@@ -3839,6 +5261,18 @@ function deleteObjectsFromRemovedRealms(db, res, prevState) {
3839
5261
  }
3840
5262
  }
3841
5263
  }
5264
+ if (rejectedRealms.size > 0) {
5265
+ // Remove rejected/deleted realms from yDownloadedRealms because of the following use case:
5266
+ // 1. User becomes added to the realm
5267
+ // 2. User syncs and all documents of the realm is downloaded (downloadYDocsFromServer.ts)
5268
+ // 3. User leaves the realm and all docs are deleted locally (built-in-trigger of deleting their rows in this file)
5269
+ // 4. User is yet again added to the realm. At this point, we must make sure the docs are not considered already downloaded.
5270
+ const updateSpec = {};
5271
+ for (const realmId of rejectedRealms) {
5272
+ updateSpec[`yDownloadedRealms.${realmId}`] = undefined; // Setting to undefined will delete the property
5273
+ }
5274
+ yield db.$syncState.update('syncState', updateSpec);
5275
+ }
3842
5276
  });
3843
5277
  }
3844
5278
  function filterServerChangesThroughAddedClientChanges(serverChanges, addedClientChanges) {
@@ -3850,13 +5284,15 @@ function filterServerChangesThroughAddedClientChanges(serverChanges, addedClient
3850
5284
  return toDBOperationSet(changes);
3851
5285
  }
3852
5286
 
5287
+ const LIMIT_NUM_MESSAGES_PER_TIME = 10; // Allow a maximum of 10 messages per...
5288
+ const TIME_WINDOW = 10000; // ...10 seconds.
5289
+ const PAUSE_PERIOD = 1000; // Pause for 1 second if reached
3853
5290
  function MessagesFromServerConsumer(db) {
3854
5291
  const queue = [];
3855
5292
  const readyToServe = new BehaviorSubject(true);
3856
5293
  const event = new BehaviorSubject(null);
3857
5294
  let isWorking = false;
3858
- let loopWarning = 0;
3859
- let loopDetection = [0, 0, 0, 0, 0, 0, 0, 0, 0, Date.now()];
5295
+ let loopDetection = new Array(LIMIT_NUM_MESSAGES_PER_TIME).fill(0);
3860
5296
  event.subscribe(() => __awaiter(this, void 0, void 0, function* () {
3861
5297
  if (isWorking)
3862
5298
  return;
@@ -3870,20 +5306,11 @@ function MessagesFromServerConsumer(db) {
3870
5306
  }
3871
5307
  finally {
3872
5308
  if (loopDetection[loopDetection.length - 1] - loopDetection[0] <
3873
- 10000) {
5309
+ TIME_WINDOW) {
3874
5310
  // Ten loops within 10 seconds. Slow down!
3875
- if (Date.now() - loopWarning < 5000) {
3876
- // Last time we did this, we ended up here too. Wait for a minute.
3877
- console.warn(`Slowing down websocket loop for one minute`);
3878
- loopWarning = Date.now() + 60000;
3879
- yield new Promise((resolve) => setTimeout(resolve, 60000));
3880
- }
3881
- else {
3882
- // This is a one-time event. Just pause 10 seconds.
3883
- console.warn(`Slowing down websocket loop for 10 seconds`);
3884
- loopWarning = Date.now() + 10000;
3885
- yield new Promise((resolve) => setTimeout(resolve, 10000));
3886
- }
5311
+ // This is a one-time event. Just pause 10 seconds.
5312
+ console.warn(`Slowing down websocket loop for ${PAUSE_PERIOD} milliseconds`);
5313
+ yield new Promise((resolve) => setTimeout(resolve, PAUSE_PERIOD));
3887
5314
  }
3888
5315
  isWorking = false;
3889
5316
  readyToServe.next(true);
@@ -4155,6 +5582,7 @@ function DexieCloudDB(dx) {
4155
5582
  };
4156
5583
  Object.assign(db, helperMethods);
4157
5584
  db.messageConsumer = MessagesFromServerConsumer(db);
5585
+ db.messageProducer = new Subject();
4158
5586
  wm$1.set(dx.cloud, db);
4159
5587
  }
4160
5588
  return db;
@@ -4490,24 +5918,6 @@ const safariVersion = isSafari
4490
5918
  const DISABLE_SERVICEWORKER_STRATEGY = (isSafari && safariVersion <= 605) || // Disable for Safari for now.
4491
5919
  isFirefox; // Disable for Firefox for now. Seems to have a bug in reading CryptoKeys from IDB from service workers
4492
5920
 
4493
- /* Helper function to subscribe to database close no matter if it was unexpectedly closed or manually using db.close()
4494
- */
4495
- function dbOnClosed(db, handler) {
4496
- db.on.close.subscribe(handler);
4497
- // @ts-ignore
4498
- const origClose = db._close;
4499
- // @ts-ignore
4500
- db._close = function () {
4501
- origClose.call(this);
4502
- handler();
4503
- };
4504
- return () => {
4505
- db.on.close.unsubscribe(handler);
4506
- // @ts-ignore
4507
- db._close = origClose;
4508
- };
4509
- }
4510
-
4511
5921
  const IS_SERVICE_WORKER = typeof self !== "undefined" && "clients" in self && !self.document;
4512
5922
 
4513
5923
  function throwVersionIncrementNeeded() {
@@ -4973,13 +6383,18 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
4973
6383
  values = values.filter((_, idx) => !failures[idx]);
4974
6384
  }
4975
6385
  const ts = Date.now();
6386
+ // Canonicalize req.criteria.index to null if it's on the primary key.
6387
+ const criteria = 'criteria' in req && req.criteria
6388
+ ? Object.assign(Object.assign({}, req.criteria), { index: req.criteria.index === schema.primaryKey.keyPath // Use null to inform server that criteria is on primary key
6389
+ ? null // This will disable the server from trying to log consistent operations where it shouldnt.
6390
+ : req.criteria.index }) : undefined;
4976
6391
  const mut = req.type === 'delete'
4977
6392
  ? {
4978
6393
  type: 'delete',
4979
6394
  ts,
4980
6395
  opNo,
4981
6396
  keys,
4982
- criteria: req.criteria,
6397
+ criteria,
4983
6398
  txid,
4984
6399
  userId,
4985
6400
  }
@@ -4993,14 +6408,14 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
4993
6408
  userId,
4994
6409
  values,
4995
6410
  }
4996
- : req.criteria && req.changeSpec
6411
+ : criteria && req.changeSpec
4997
6412
  ? {
4998
6413
  // Common changeSpec for all keys
4999
6414
  type: 'modify',
5000
6415
  ts,
5001
6416
  opNo,
5002
6417
  keys,
5003
- criteria: req.criteria,
6418
+ criteria,
5004
6419
  changeSpec: req.changeSpec,
5005
6420
  txid,
5006
6421
  userId,
@@ -5028,7 +6443,7 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
5028
6443
  if ('isAdditionalChunk' in req && req.isAdditionalChunk) {
5029
6444
  mut.isAdditionalChunk = true;
5030
6445
  }
5031
- return keys.length > 0 || ('criteria' in req && req.criteria)
6446
+ return keys.length > 0 || criteria
5032
6447
  ? mutsTable
5033
6448
  .mutate({ type: 'add', trans, values: [mut] }) // Log entry
5034
6449
  .then(() => res) // Return original response
@@ -5042,6 +6457,7 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
5042
6457
 
5043
6458
  function overrideParseStoresSpec(origFunc, dexie) {
5044
6459
  return function (stores, dbSchema) {
6460
+ var _a;
5045
6461
  const storesClone = Object.assign(Object.assign({}, DEXIE_CLOUD_SCHEMA), stores);
5046
6462
  // Merge indexes of DEXIE_CLOUD_SCHEMA with stores
5047
6463
  Object.keys(DEXIE_CLOUD_SCHEMA).forEach((tableName) => {
@@ -5102,6 +6518,14 @@ function overrideParseStoresSpec(origFunc, dexie) {
5102
6518
  }
5103
6519
  });
5104
6520
  const rv = origFunc.call(this, storesClone, dbSchema);
6521
+ for (const [tableName, spec] of Object.entries(dbSchema)) {
6522
+ if ((_a = spec.yProps) === null || _a === void 0 ? void 0 : _a.length) {
6523
+ const cloudTableSchema = cloudSchema[tableName];
6524
+ if (cloudTableSchema) {
6525
+ cloudTableSchema.yProps = spec.yProps.map((yProp) => yProp.prop);
6526
+ }
6527
+ }
6528
+ }
5105
6529
  return rv;
5106
6530
  };
5107
6531
  }
@@ -5187,22 +6611,70 @@ class TokenExpiredError extends Error {
5187
6611
  }
5188
6612
  }
5189
6613
 
6614
+ function createYClientUpdateObservable(db) {
6615
+ const yTableRecords = flatten(db.tables
6616
+ .filter((table) => { var _a; return ((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[table.name].markedForSync) && table.schema.yProps; })
6617
+ .map((table) => table.schema.yProps.map((p) => ({
6618
+ table: table.name,
6619
+ ydocProp: p.prop,
6620
+ updatesTable: p.updatesTable,
6621
+ }))));
6622
+ return merge(...yTableRecords.map(({ table, ydocProp, updatesTable }) => {
6623
+ let currentUnsentFrom = 1;
6624
+ return liveQuery(() => __awaiter(this, void 0, void 0, function* () {
6625
+ const yTbl = db.table(updatesTable);
6626
+ const unsentFrom = yield yTbl
6627
+ .where({ i: DEXIE_CLOUD_SYNCER_ID })
6628
+ .first()
6629
+ .then((syncer) => (syncer === null || syncer === void 0 ? void 0 : syncer.unsentFrom) || 1);
6630
+ currentUnsentFrom = Math.max(currentUnsentFrom, unsentFrom);
6631
+ const addedUpdates = yield listUpdatesSince(yTbl, currentUnsentFrom);
6632
+ // Update currentUnsentFrom to only listen for updates that will be newer than the ones we emitted.
6633
+ currentUnsentFrom = Math.max(currentUnsentFrom, ...addedUpdates.map((update) => update.i + 1));
6634
+ return addedUpdates
6635
+ .filter((update) => update.f && update.f & 1) // Only include local updates
6636
+ .map((update) => {
6637
+ return {
6638
+ type: 'u-c',
6639
+ table,
6640
+ prop: ydocProp,
6641
+ k: update.k,
6642
+ u: update.u,
6643
+ i: update.i,
6644
+ };
6645
+ });
6646
+ }));
6647
+ })).pipe(mergeMap$1((messages) => messages)); // Flattens the array of messages. If messageProducer emits empty array, nothing is emitted but if messageProducer emits array of messages, they are emitted one by one.
6648
+ }
6649
+
6650
+ function getAwarenessLibrary(db) {
6651
+ var _a, _b;
6652
+ if (!((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.awarenessProtocol)) {
6653
+ throw new Dexie.MissingAPIError('awarenessProtocol was not provided to db.cloud.configure(). Please import * as awarenessProtocol from "y-protocols/awareness".');
6654
+ }
6655
+ return (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.awarenessProtocol;
6656
+ }
6657
+ const awarenessWeakMap = new WeakMap();
6658
+ const getDocAwareness = (doc) => awarenessWeakMap.get(doc);
6659
+
5190
6660
  const SERVER_PING_TIMEOUT = 20000;
5191
6661
  const CLIENT_PING_INTERVAL = 30000;
5192
6662
  const FAIL_RETRY_WAIT_TIME = 60000;
5193
6663
  class WSObservable extends Observable$1 {
5194
- constructor(databaseUrl, rev, realmSetHash, clientIdentity, messageProducer, webSocketStatus, token, tokenExpiration) {
5195
- super((subscriber) => new WSConnection(databaseUrl, rev, realmSetHash, clientIdentity, token, tokenExpiration, subscriber, messageProducer, webSocketStatus));
6664
+ constructor(db, rev, realmSetHash, clientIdentity, messageProducer, webSocketStatus, token, tokenExpiration) {
6665
+ super((subscriber) => new WSConnection(db, rev, realmSetHash, clientIdentity, token, tokenExpiration, subscriber, messageProducer, webSocketStatus));
5196
6666
  }
5197
6667
  }
5198
6668
  let counter = 0;
5199
6669
  class WSConnection extends Subscription$1 {
5200
- constructor(databaseUrl, rev, realmSetHash, clientIdentity, token, tokenExpiration, subscriber, messageProducer, webSocketStatus) {
6670
+ constructor(db, rev, realmSetHash, clientIdentity, token, tokenExpiration, subscriber, messageProducer, webSocketStatus) {
5201
6671
  super(() => this.teardown());
5202
6672
  this.id = ++counter;
6673
+ this.subscriptions = new Set();
5203
6674
  this.reconnecting = false;
5204
6675
  console.debug('New WebSocket Connection', this.id, token ? 'authorized' : 'unauthorized');
5205
- this.databaseUrl = databaseUrl;
6676
+ this.db = db;
6677
+ this.databaseUrl = db.cloud.options.databaseUrl;
5206
6678
  this.rev = rev;
5207
6679
  this.realmSetHash = realmSetHash;
5208
6680
  this.clientIdentity = clientIdentity;
@@ -5211,7 +6683,6 @@ class WSConnection extends Subscription$1 {
5211
6683
  this.subscriber = subscriber;
5212
6684
  this.lastUserActivity = new Date();
5213
6685
  this.messageProducer = messageProducer;
5214
- this.messageProducerSubscription = null;
5215
6686
  this.webSocketStatus = webSocketStatus;
5216
6687
  this.connect();
5217
6688
  }
@@ -5232,10 +6703,10 @@ class WSConnection extends Subscription$1 {
5232
6703
  catch (_a) { }
5233
6704
  }
5234
6705
  this.ws = null;
5235
- if (this.messageProducerSubscription) {
5236
- this.messageProducerSubscription.unsubscribe();
5237
- this.messageProducerSubscription = null;
6706
+ for (const sub of this.subscriptions) {
6707
+ sub.unsubscribe();
5238
6708
  }
6709
+ this.subscriptions.clear();
5239
6710
  }
5240
6711
  reconnect() {
5241
6712
  if (this.reconnecting)
@@ -5329,7 +6800,7 @@ class WSConnection extends Subscription$1 {
5329
6800
  // Connect the WebSocket to given url:
5330
6801
  console.debug('dexie-cloud WebSocket create');
5331
6802
  const ws = (this.ws = new WebSocket(`${wsUrl}/changes?${searchParams}`));
5332
- //ws.binaryType = "arraybuffer"; // For future when subscribing to actual changes.
6803
+ ws.binaryType = "arraybuffer";
5333
6804
  ws.onclose = (event) => {
5334
6805
  if (!this.pinger)
5335
6806
  return;
@@ -5342,14 +6813,30 @@ class WSConnection extends Subscription$1 {
5342
6813
  console.debug('dexie-cloud WebSocket onmessage', event.data);
5343
6814
  this.lastServerActivity = new Date();
5344
6815
  try {
5345
- const msg = TSON.parse(event.data);
6816
+ const msg = typeof event.data === 'string'
6817
+ ? TSON.parse(event.data)
6818
+ : decodeYMessage(new Uint8Array(event.data));
5346
6819
  if (msg.type === 'error') {
5347
6820
  throw new Error(`Error message from dexie-cloud: ${msg.error}`);
5348
6821
  }
5349
- if (msg.type === 'rev') {
6822
+ else if (msg.type === 'rev') {
5350
6823
  this.rev = msg.rev; // No meaning but seems reasonable.
5351
6824
  }
5352
- if (msg.type !== 'pong') {
6825
+ else if (msg.type === 'aware') {
6826
+ const docCache = DexieYProvider.getDocCache(this.db.dx);
6827
+ const doc = docCache.find(msg.table, msg.k, msg.prop);
6828
+ if (doc) {
6829
+ const awareness = getDocAwareness(doc);
6830
+ if (awareness) {
6831
+ const awap = getAwarenessLibrary(this.db);
6832
+ awap.applyAwarenessUpdate(awareness, msg.u, 'server');
6833
+ }
6834
+ }
6835
+ }
6836
+ else if (msg.type === 'u-ack' || msg.type === 'u-reject' || msg.type === 'u-s' || msg.type === 'in-sync') {
6837
+ applyYServerMessages([msg], this.db);
6838
+ }
6839
+ else if (msg.type !== 'pong') {
5353
6840
  this.subscriber.next(msg);
5354
6841
  }
5355
6842
  }
@@ -5377,16 +6864,24 @@ class WSConnection extends Subscription$1 {
5377
6864
  }
5378
6865
  };
5379
6866
  });
5380
- this.messageProducerSubscription = this.messageProducer.subscribe((msg) => {
5381
- var _a;
6867
+ this.subscriptions.add(this.messageProducer.subscribe((msg) => {
6868
+ var _a, _b;
5382
6869
  if (!this.closed) {
5383
6870
  if (msg.type === 'ready' &&
5384
6871
  this.webSocketStatus.value !== 'connected') {
5385
6872
  this.webSocketStatus.next('connected');
5386
6873
  }
5387
- (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(TSON.stringify(msg));
6874
+ if (msg.type === 'ready') {
6875
+ (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(TSON.stringify(msg));
6876
+ }
6877
+ else {
6878
+ // If it's not a "ready" message, it's an YMessage.
6879
+ // YMessages can be sent binary encoded.
6880
+ (_b = this.ws) === null || _b === void 0 ? void 0 : _b.send(encodeYMessage(msg));
6881
+ }
5388
6882
  }
5389
- });
6883
+ }));
6884
+ this.subscriptions.add(createYClientUpdateObservable(this.db).subscribe(this.db.messageProducer));
5390
6885
  }
5391
6886
  catch (error) {
5392
6887
  this.pauseUntil = new Date(Date.now() + FAIL_RETRY_WAIT_TIME);
@@ -5428,7 +6923,7 @@ function connectWebSocket(db) {
5428
6923
  if (!((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl)) {
5429
6924
  throw new Error(`No database URL to connect WebSocket to`);
5430
6925
  }
5431
- 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
6926
+ 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
5432
6927
  switchMap(() => db.getPersistedSyncState()), // We need the info on which server revision we are at:
5433
6928
  filter((syncState) => syncState && syncState.serverRevision), // We wont send anything to server before inital sync has taken place
5434
6929
  switchMap((syncState) => __awaiter(this, void 0, void 0, function* () {
@@ -5439,6 +6934,7 @@ function connectWebSocket(db) {
5439
6934
  realmSetHash: yield computeRealmSetHash(syncState)
5440
6935
  });
5441
6936
  })));
6937
+ const messageProducer = merge(readyForChangesMessage, db.messageProducer);
5442
6938
  function createObservable() {
5443
6939
  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.
5444
6940
  take(1), // Don't continue waking up whenever syncState change
@@ -5465,7 +6961,7 @@ function connectWebSocket(db) {
5465
6961
  // If no new entries, server won't bother the client. If new entries, server sends only those
5466
6962
  // and the baseRev of the last from same client-ID.
5467
6963
  if (userLogin) {
5468
- 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);
6964
+ return new WSObservable(db, db.cloud.persistedSyncState.value.serverRevision, realmSetHash, db.cloud.persistedSyncState.value.clientIdentity, messageProducer, db.cloud.webSocketStatus, userLogin.accessToken, userLogin.accessTokenExpiration);
5469
6965
  }
5470
6966
  else {
5471
6967
  return from$1([]);
@@ -6271,6 +7767,128 @@ const getInvitesObservable = associate((db) => {
6271
7767
  })), []);
6272
7768
  });
6273
7769
 
7770
+ function createYHandler(db) {
7771
+ const awap = getAwarenessLibrary(db);
7772
+ return (provider) => {
7773
+ var _a;
7774
+ const doc = provider.doc;
7775
+ const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
7776
+ if (!((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[parentTable].markedForSync)) {
7777
+ return; // The table that holds the doc is not marked for sync - leave it to dexie. No syncing, no awareness.
7778
+ }
7779
+ let awareness = new awap.Awareness(doc);
7780
+ awarenessWeakMap.set(doc, awareness);
7781
+ provider.awareness = awareness;
7782
+ awareness.on('update', ({ added, updated, removed }, origin) => {
7783
+ // Send the update
7784
+ const changedClients = added.concat(updated).concat(removed);
7785
+ if (origin !== 'server') {
7786
+ const update = awap.encodeAwarenessUpdate(awareness, changedClients);
7787
+ db.messageProducer.next({
7788
+ type: 'aware',
7789
+ table: parentTable,
7790
+ prop: parentProp,
7791
+ k: doc.meta.parentId,
7792
+ u: update,
7793
+ });
7794
+ if (provider.destroyed) {
7795
+ // We're called from awareness.on('destroy') that did
7796
+ // removeAwarenessStates.
7797
+ // It's time to also send the doc-close message that dexie-cloud understands
7798
+ // and uses to stop subscribing for updates and awareness updates and brings
7799
+ // down the cached information in memory on the WS connection for this.
7800
+ db.messageProducer.next({
7801
+ type: 'doc-close',
7802
+ table: parentTable,
7803
+ prop: parentProp,
7804
+ k: doc.meta.parentId
7805
+ });
7806
+ }
7807
+ }
7808
+ });
7809
+ awareness.on('destroy', () => {
7810
+ // Signal to server that this provider is destroyed (the update event will be triggered, which
7811
+ // in turn will trigger db.messageProducer that will send the message to the server if WS is connected)
7812
+ awap.removeAwarenessStates(awareness, [doc.clientID], 'provider destroyed');
7813
+ });
7814
+ // Now wait til document is loaded and then open the document on the server
7815
+ provider.on('load', () => __awaiter(this, void 0, void 0, function* () {
7816
+ if (provider.destroyed)
7817
+ return;
7818
+ let connected = false;
7819
+ let currentFlowId = 1;
7820
+ const subscription = db.cloud.webSocketStatus.subscribe((wsStatus) => {
7821
+ if (provider.destroyed)
7822
+ return;
7823
+ // Keep "connected" state in a variable so we can check it after async operations
7824
+ connected = wsStatus === 'connected';
7825
+ // We are or got connected. Open the document on the server.
7826
+ if (wsStatus === "connected") {
7827
+ ++currentFlowId;
7828
+ openDocumentOnServer().catch(error => {
7829
+ console.warn(`Error catched in createYHandler.ts: ${error}`);
7830
+ });
7831
+ }
7832
+ });
7833
+ // Wait until WebSocket is connected
7834
+ provider.addCleanupHandler(subscription);
7835
+ /** Sends an 'doc-open' message to server whenever websocket becomes
7836
+ * connected, or if it is already connected.
7837
+ * The flow is aborted in case websocket is disconnected while querying
7838
+ * information required to compute the state vector. Flow is also
7839
+ * aborted in case document or provider has been destroyed during
7840
+ * the async parts of the task.
7841
+ *
7842
+ * The state vector is only computed from the updates that have occured
7843
+ * after the last full sync - which could very often be zero - in which
7844
+ * case no state vector is sent (then the server already knows us by
7845
+ * revision)
7846
+ *
7847
+ * When server gets the doc-open message, it will authorized us for
7848
+ * whether we are allowed to read / write to this document, and then
7849
+ * keep the cached information in memory on the WS connection for this
7850
+ * particular document, as well as subscribe to updates and awareness updates
7851
+ * from other clients on the document.
7852
+ */
7853
+ function openDocumentOnServer(wsStatus) {
7854
+ return __awaiter(this, void 0, void 0, function* () {
7855
+ const myFlow = currentFlowId; // So we can abort when a new flow is started
7856
+ const yTbl = db.table(updatesTable);
7857
+ const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
7858
+ // After every await, check if we still should be working on this task.
7859
+ if (provider.destroyed || currentFlowId !== myFlow || !connected)
7860
+ return;
7861
+ const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
7862
+ const docOpenMsg = {
7863
+ type: 'doc-open',
7864
+ table: parentTable,
7865
+ prop: parentProp,
7866
+ k: parentId,
7867
+ serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
7868
+ };
7869
+ const serverUpdatesSinceLastSync = yield yTbl
7870
+ .where('i')
7871
+ .between(receivedUntil, Infinity, false)
7872
+ .filter((update) => cmp(update.k, parentId) === 0 && // Only updates for this document
7873
+ ((update.f || 0) & 1) === 0 // Don't include local changes
7874
+ )
7875
+ .toArray();
7876
+ // After every await, check if we still should be working on this task.
7877
+ if (provider.destroyed || currentFlowId !== myFlow || !connected)
7878
+ return;
7879
+ if (serverUpdatesSinceLastSync.length > 0) {
7880
+ const Y = $Y(db); // Get the Yjs library from Dexie constructor options
7881
+ const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
7882
+ const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
7883
+ docOpenMsg.sv = stateVector;
7884
+ }
7885
+ db.messageProducer.next(docOpenMsg);
7886
+ });
7887
+ }
7888
+ }));
7889
+ };
7890
+ }
7891
+
6274
7892
  const DEFAULT_OPTIONS = {
6275
7893
  nameSuffix: true,
6276
7894
  };
@@ -6300,8 +7918,9 @@ function dexieCloud(dexie) {
6300
7918
  if (closed)
6301
7919
  throw new Dexie.DatabaseClosedError();
6302
7920
  }
6303
- dbOnClosed(dexie, () => {
7921
+ dexie.once('close', () => {
6304
7922
  subscriptions.forEach((subscription) => subscription.unsubscribe());
7923
+ subscriptions.splice(0, subscriptions.length);
6305
7924
  closed = true;
6306
7925
  localSyncWorker && localSyncWorker.stop();
6307
7926
  localSyncWorker = null;
@@ -6310,7 +7929,7 @@ function dexieCloud(dexie) {
6310
7929
  const syncComplete = new Subject();
6311
7930
  dexie.cloud = {
6312
7931
  // @ts-ignore
6313
- version: "4.0.7",
7932
+ version: "4.1.0-alpha.2",
6314
7933
  options: Object.assign({}, DEFAULT_OPTIONS),
6315
7934
  schema: null,
6316
7935
  get currentUserId() {
@@ -6456,6 +8075,7 @@ function dexieCloud(dexie) {
6456
8075
  throw new Error(`Internal error`); // options cannot be null if configuredProgramatically is set.
6457
8076
  const newPersistedOptions = Object.assign({}, options);
6458
8077
  delete newPersistedOptions.fetchTokens;
8078
+ delete newPersistedOptions.awarenessProtocol;
6459
8079
  yield db.$syncState.put(newPersistedOptions, 'options');
6460
8080
  }
6461
8081
  if (((_h = db.cloud.options) === null || _h === void 0 ? void 0 : _h.tryUseServiceWorker) &&
@@ -6533,12 +8153,29 @@ function dexieCloud(dexie) {
6533
8153
  currentUserEmitter.pipe(skip(1), take(1)),
6534
8154
  db.cloud.persistedSyncState.pipe(skip(1), take(1)),
6535
8155
  ]));
8156
+ const yHandler = createYHandler(db);
8157
+ db.dx.on('y', yHandler);
8158
+ db.dx.once('close', () => {
8159
+ var _a;
8160
+ (_a = db.dx.on.y) === null || _a === void 0 ? void 0 : _a.unsubscribe(yHandler);
8161
+ });
6536
8162
  }
6537
8163
  // HERE: If requireAuth, do athentication now.
6538
8164
  let changedUser = false;
6539
8165
  const user = yield db.getCurrentUser();
6540
- if ((_c = db.cloud.options) === null || _c === void 0 ? void 0 : _c.requireAuth) {
6541
- if (!user.isLoggedIn) {
8166
+ const requireAuth = (_c = db.cloud.options) === null || _c === void 0 ? void 0 : _c.requireAuth;
8167
+ if (requireAuth) {
8168
+ if (typeof requireAuth === 'object') {
8169
+ // requireAuth contains login hints. Check if we already fulfil it:
8170
+ if (!user.isLoggedIn ||
8171
+ (requireAuth.userId && user.userId !== requireAuth.userId) ||
8172
+ (requireAuth.email && user.email !== requireAuth.email)) {
8173
+ // If not, login the configured user:
8174
+ changedUser = yield login(db, requireAuth);
8175
+ }
8176
+ }
8177
+ else if (!user.isLoggedIn) {
8178
+ // requireAuth is true and user is not logged in
6542
8179
  changedUser = yield login(db);
6543
8180
  }
6544
8181
  }
@@ -6594,7 +8231,7 @@ function dexieCloud(dexie) {
6594
8231
  }
6595
8232
  }
6596
8233
  // @ts-ignore
6597
- dexieCloud.version = "4.0.7";
8234
+ dexieCloud.version = "4.1.0-alpha.2";
6598
8235
  Dexie.Cloud = dexieCloud;
6599
8236
 
6600
8237
  // In case the SW lives for a while, let it reuse already opened connections: