@powersync/web 1.38.2 → 1.38.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/worker/SharedSyncImplementation.umd.js +921 -771
  2. package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
  3. package/dist/worker/WASQLiteDB.umd.js +924 -774
  4. package/dist/worker/WASQLiteDB.umd.js.map +1 -1
  5. package/lib/package.json +2 -2
  6. package/lib/src/attachments/IndexDBFileSystemAdapter.js +1 -0
  7. package/lib/src/attachments/IndexDBFileSystemAdapter.js.map +1 -0
  8. package/lib/src/db/NavigatorTriggerClaimManager.js +1 -0
  9. package/lib/src/db/NavigatorTriggerClaimManager.js.map +1 -0
  10. package/lib/src/db/PowerSyncDatabase.js +1 -0
  11. package/lib/src/db/PowerSyncDatabase.js.map +1 -0
  12. package/lib/src/db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.js +1 -0
  13. package/lib/src/db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.js.map +1 -0
  14. package/lib/src/db/adapters/AsyncWebAdapter.js +1 -0
  15. package/lib/src/db/adapters/AsyncWebAdapter.js.map +1 -0
  16. package/lib/src/db/adapters/SSRDBAdapter.js +1 -0
  17. package/lib/src/db/adapters/SSRDBAdapter.js.map +1 -0
  18. package/lib/src/db/adapters/WebDBAdapter.js +1 -0
  19. package/lib/src/db/adapters/WebDBAdapter.js.map +1 -0
  20. package/lib/src/db/adapters/wa-sqlite/ConcurrentConnection.js +1 -0
  21. package/lib/src/db/adapters/wa-sqlite/ConcurrentConnection.js.map +1 -0
  22. package/lib/src/db/adapters/wa-sqlite/DatabaseClient.js +1 -0
  23. package/lib/src/db/adapters/wa-sqlite/DatabaseClient.js.map +1 -0
  24. package/lib/src/db/adapters/wa-sqlite/DatabaseServer.js +1 -0
  25. package/lib/src/db/adapters/wa-sqlite/DatabaseServer.js.map +1 -0
  26. package/lib/src/db/adapters/wa-sqlite/RawSqliteConnection.js +1 -0
  27. package/lib/src/db/adapters/wa-sqlite/RawSqliteConnection.js.map +1 -0
  28. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +1 -0
  29. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js.map +1 -0
  30. package/lib/src/db/adapters/wa-sqlite/WASQLitePowerSyncDatabaseOpenFactory.js +1 -0
  31. package/lib/src/db/adapters/wa-sqlite/WASQLitePowerSyncDatabaseOpenFactory.js.map +1 -0
  32. package/lib/src/db/adapters/wa-sqlite/vfs.js +1 -0
  33. package/lib/src/db/adapters/wa-sqlite/vfs.js.map +1 -0
  34. package/lib/src/db/adapters/web-sql-flags.js +1 -0
  35. package/lib/src/db/adapters/web-sql-flags.js.map +1 -0
  36. package/lib/src/db/sync/SSRWebStreamingSyncImplementation.js +1 -0
  37. package/lib/src/db/sync/SSRWebStreamingSyncImplementation.js.map +1 -0
  38. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +1 -0
  39. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js.map +1 -0
  40. package/lib/src/db/sync/WebRemote.js +1 -0
  41. package/lib/src/db/sync/WebRemote.js.map +1 -0
  42. package/lib/src/db/sync/WebStreamingSyncImplementation.js +1 -0
  43. package/lib/src/db/sync/WebStreamingSyncImplementation.js.map +1 -0
  44. package/lib/src/db/sync/userAgent.js +1 -0
  45. package/lib/src/db/sync/userAgent.js.map +1 -0
  46. package/lib/src/index.js +1 -0
  47. package/lib/src/index.js.map +1 -0
  48. package/lib/src/shared/navigator.js +1 -0
  49. package/lib/src/shared/navigator.js.map +1 -0
  50. package/lib/src/shared/tab_close_signal.js +1 -0
  51. package/lib/src/shared/tab_close_signal.js.map +1 -0
  52. package/lib/src/worker/db/MultiDatabaseServer.js +1 -0
  53. package/lib/src/worker/db/MultiDatabaseServer.js.map +1 -0
  54. package/lib/src/worker/db/WASQLiteDB.worker.js +1 -0
  55. package/lib/src/worker/db/WASQLiteDB.worker.js.map +1 -0
  56. package/lib/src/worker/db/open-worker-database.js +1 -0
  57. package/lib/src/worker/db/open-worker-database.js.map +1 -0
  58. package/lib/src/worker/sync/AbstractSharedSyncClientProvider.js +1 -0
  59. package/lib/src/worker/sync/AbstractSharedSyncClientProvider.js.map +1 -0
  60. package/lib/src/worker/sync/BroadcastLogger.js +1 -0
  61. package/lib/src/worker/sync/BroadcastLogger.js.map +1 -0
  62. package/lib/src/worker/sync/SharedSyncImplementation.js +1 -0
  63. package/lib/src/worker/sync/SharedSyncImplementation.js.map +1 -0
  64. package/lib/src/worker/sync/SharedSyncImplementation.worker.js +1 -0
  65. package/lib/src/worker/sync/SharedSyncImplementation.worker.js.map +1 -0
  66. package/lib/src/worker/sync/WorkerClient.js +1 -0
  67. package/lib/src/worker/sync/WorkerClient.js.map +1 -0
  68. package/lib/tsconfig.tsbuildinfo +1 -1
  69. package/package.json +3 -3
@@ -1733,7 +1733,10 @@ __webpack_require__.r(__webpack_exports__);
1733
1733
  /* harmony export */ sanitizeUUID: () => (/* binding */ sanitizeUUID),
1734
1734
  /* harmony export */ timeoutSignal: () => (/* binding */ timeoutSignal)
1735
1735
  /* harmony export */ });
1736
- // https://www.sqlite.org/lang_expr.html#castexpr
1736
+ /**
1737
+ * @see https://www.sqlite.org/lang_expr.html#castexpr
1738
+ * @public
1739
+ */
1737
1740
  var ColumnType;
1738
1741
  (function (ColumnType) {
1739
1742
  ColumnType["TEXT"] = "TEXT";
@@ -1749,14 +1752,24 @@ const integer = {
1749
1752
  const real = {
1750
1753
  type: ColumnType.REAL
1751
1754
  };
1752
- // powersync-sqlite-core limits the number of column per table to 1999, due to internal SQLite limits.
1753
- // In earlier versions this was limited to 63.
1755
+ /**
1756
+ * powersync-sqlite-core limits the number of column per table to 1999, due to internal SQLite limits.
1757
+ * In earlier versions this was limited to 63.
1758
+ *
1759
+ * @internal
1760
+ */
1754
1761
  const MAX_AMOUNT_OF_COLUMNS = 1999;
1762
+ /**
1763
+ * @public
1764
+ */
1755
1765
  const column = {
1756
1766
  text,
1757
1767
  integer,
1758
1768
  real
1759
1769
  };
1770
+ /**
1771
+ * @public
1772
+ */
1760
1773
  class Column {
1761
1774
  options;
1762
1775
  constructor(options) {
@@ -1776,9 +1789,15 @@ class Column {
1776
1789
  }
1777
1790
  }
1778
1791
 
1792
+ /**
1793
+ * @internal
1794
+ */
1779
1795
  const DEFAULT_INDEX_COLUMN_OPTIONS = {
1780
1796
  ascending: true
1781
1797
  };
1798
+ /**
1799
+ * @public
1800
+ */
1782
1801
  class IndexedColumn {
1783
1802
  options;
1784
1803
  static createAscending(column) {
@@ -1805,9 +1824,15 @@ class IndexedColumn {
1805
1824
  }
1806
1825
  }
1807
1826
 
1827
+ /**
1828
+ * @internal
1829
+ */
1808
1830
  const DEFAULT_INDEX_OPTIONS = {
1809
1831
  columns: []
1810
1832
  };
1833
+ /**
1834
+ * @public
1835
+ */
1811
1836
  class Index {
1812
1837
  options;
1813
1838
  static createAscending(options, columnNames) {
@@ -1849,6 +1874,9 @@ function encodeTableOptions(options) {
1849
1874
  };
1850
1875
  }
1851
1876
 
1877
+ /**
1878
+ * @internal
1879
+ */
1852
1880
  const DEFAULT_TABLE_OPTIONS = {
1853
1881
  indexes: [],
1854
1882
  insertOnly: false,
@@ -1857,7 +1885,13 @@ const DEFAULT_TABLE_OPTIONS = {
1857
1885
  trackMetadata: false,
1858
1886
  ignoreEmptyUpdates: false
1859
1887
  };
1888
+ /**
1889
+ * @internal
1890
+ */
1860
1891
  const InvalidSQLCharacters = /["'%,.#\s[\]]/;
1892
+ /**
1893
+ * @public
1894
+ */
1861
1895
  class Table {
1862
1896
  options;
1863
1897
  _mappedColumns;
@@ -2048,6 +2082,11 @@ class Table {
2048
2082
  }
2049
2083
  }
2050
2084
 
2085
+ /**
2086
+ * The default name of the local table storing attachment data.
2087
+ *
2088
+ * @alpha
2089
+ */
2051
2090
  const ATTACHMENT_TABLE = 'attachments';
2052
2091
  /**
2053
2092
  * Maps a database row to an AttachmentRecord.
@@ -2055,7 +2094,7 @@ const ATTACHMENT_TABLE = 'attachments';
2055
2094
  * @param row - The database row object
2056
2095
  * @returns The corresponding AttachmentRecord
2057
2096
  *
2058
- * @experimental
2097
+ * @alpha
2059
2098
  */
2060
2099
  function attachmentFromSql(row) {
2061
2100
  return {
@@ -2073,7 +2112,7 @@ function attachmentFromSql(row) {
2073
2112
  /**
2074
2113
  * AttachmentState represents the current synchronization state of an attachment.
2075
2114
  *
2076
- * @experimental
2115
+ * @alpha
2077
2116
  */
2078
2117
  var AttachmentState;
2079
2118
  (function (AttachmentState) {
@@ -2086,7 +2125,7 @@ var AttachmentState;
2086
2125
  /**
2087
2126
  * AttachmentTable defines the schema for the attachment queue table.
2088
2127
  *
2089
- * @internal
2128
+ * @alpha
2090
2129
  */
2091
2130
  class AttachmentTable extends Table {
2092
2131
  constructor(options) {
@@ -2114,7 +2153,8 @@ class AttachmentTable extends Table {
2114
2153
  * Provides methods to query, insert, update, and delete attachment records with
2115
2154
  * proper transaction management through PowerSync.
2116
2155
  *
2117
- * @internal
2156
+ * @experimental
2157
+ * @alpha
2118
2158
  */
2119
2159
  class AttachmentContext {
2120
2160
  /** PowerSync database instance for executing queries */
@@ -2336,6 +2376,9 @@ class AttachmentContext {
2336
2376
  }
2337
2377
  }
2338
2378
 
2379
+ /**
2380
+ * @public
2381
+ */
2339
2382
  var WatchedQueryListenerEvent;
2340
2383
  (function (WatchedQueryListenerEvent) {
2341
2384
  WatchedQueryListenerEvent["ON_DATA"] = "onData";
@@ -2344,176 +2387,18 @@ var WatchedQueryListenerEvent;
2344
2387
  WatchedQueryListenerEvent["SETTINGS_WILL_UPDATE"] = "settingsWillUpdate";
2345
2388
  WatchedQueryListenerEvent["CLOSED"] = "closed";
2346
2389
  })(WatchedQueryListenerEvent || (WatchedQueryListenerEvent = {}));
2390
+ /**
2391
+ * @internal
2392
+ */
2347
2393
  const DEFAULT_WATCH_THROTTLE_MS = 30;
2394
+ /**
2395
+ * @internal
2396
+ */
2348
2397
  const DEFAULT_WATCH_QUERY_OPTIONS = {
2349
2398
  throttleMs: DEFAULT_WATCH_THROTTLE_MS,
2350
2399
  reportFetching: true
2351
2400
  };
2352
2401
 
2353
- /**
2354
- * Orchestrates attachment synchronization between local and remote storage.
2355
- * Handles uploads, downloads, deletions, and state transitions.
2356
- *
2357
- * @internal
2358
- */
2359
- class SyncingService {
2360
- attachmentService;
2361
- localStorage;
2362
- remoteStorage;
2363
- logger;
2364
- errorHandler;
2365
- constructor(attachmentService, localStorage, remoteStorage, logger, errorHandler) {
2366
- this.attachmentService = attachmentService;
2367
- this.localStorage = localStorage;
2368
- this.remoteStorage = remoteStorage;
2369
- this.logger = logger;
2370
- this.errorHandler = errorHandler;
2371
- }
2372
- /**
2373
- * Processes attachments based on their state (upload, download, or delete).
2374
- * All updates are saved in a single batch after processing.
2375
- *
2376
- * @param attachments - Array of attachment records to process
2377
- * @param context - Attachment context for database operations
2378
- * @returns Promise that resolves when all attachments have been processed and saved
2379
- */
2380
- async processAttachments(attachments, context) {
2381
- const updatedAttachments = [];
2382
- for (const attachment of attachments) {
2383
- switch (attachment.state) {
2384
- case AttachmentState.QUEUED_UPLOAD:
2385
- const uploaded = await this.uploadAttachment(attachment);
2386
- updatedAttachments.push(uploaded);
2387
- break;
2388
- case AttachmentState.QUEUED_DOWNLOAD:
2389
- const downloaded = await this.downloadAttachment(attachment);
2390
- updatedAttachments.push(downloaded);
2391
- break;
2392
- case AttachmentState.QUEUED_DELETE:
2393
- const deleted = await this.deleteAttachment(attachment, context);
2394
- updatedAttachments.push(deleted);
2395
- break;
2396
- }
2397
- }
2398
- await context.saveAttachments(updatedAttachments);
2399
- }
2400
- /**
2401
- * Uploads an attachment from local storage to remote storage.
2402
- * On success, marks as SYNCED. On failure, defers to error handler or archives.
2403
- *
2404
- * @param attachment - The attachment record to upload
2405
- * @returns Updated attachment record with new state
2406
- * @throws Error if the attachment has no localUri
2407
- */
2408
- async uploadAttachment(attachment) {
2409
- this.logger.info(`Uploading attachment ${attachment.filename}`);
2410
- try {
2411
- if (attachment.localUri == null) {
2412
- throw new Error(`No localUri for attachment ${attachment.id}`);
2413
- }
2414
- const fileBlob = await this.localStorage.readFile(attachment.localUri);
2415
- await this.remoteStorage.uploadFile(fileBlob, attachment);
2416
- return {
2417
- ...attachment,
2418
- state: AttachmentState.SYNCED,
2419
- hasSynced: true
2420
- };
2421
- }
2422
- catch (error) {
2423
- const shouldRetry = (await this.errorHandler?.onUploadError(attachment, error)) ?? true;
2424
- if (!shouldRetry) {
2425
- return {
2426
- ...attachment,
2427
- state: AttachmentState.ARCHIVED
2428
- };
2429
- }
2430
- return attachment;
2431
- }
2432
- }
2433
- /**
2434
- * Downloads an attachment from remote storage to local storage.
2435
- * Retrieves the file, converts to base64, and saves locally.
2436
- * On success, marks as SYNCED. On failure, defers to error handler or archives.
2437
- *
2438
- * @param attachment - The attachment record to download
2439
- * @returns Updated attachment record with local URI and new state
2440
- */
2441
- async downloadAttachment(attachment) {
2442
- this.logger.info(`Downloading attachment ${attachment.filename}`);
2443
- try {
2444
- const fileData = await this.remoteStorage.downloadFile(attachment);
2445
- const localUri = this.localStorage.getLocalUri(attachment.filename);
2446
- await this.localStorage.saveFile(localUri, fileData);
2447
- return {
2448
- ...attachment,
2449
- state: AttachmentState.SYNCED,
2450
- localUri: localUri,
2451
- hasSynced: true
2452
- };
2453
- }
2454
- catch (error) {
2455
- const shouldRetry = (await this.errorHandler?.onDownloadError(attachment, error)) ?? true;
2456
- if (!shouldRetry) {
2457
- return {
2458
- ...attachment,
2459
- state: AttachmentState.ARCHIVED
2460
- };
2461
- }
2462
- return attachment;
2463
- }
2464
- }
2465
- /**
2466
- * Deletes an attachment from both remote and local storage.
2467
- * Removes the remote file, local file (if exists), and the attachment record.
2468
- * On failure, defers to error handler or archives.
2469
- *
2470
- * @param attachment - The attachment record to delete
2471
- * @param context - Attachment context for database operations
2472
- * @returns Updated attachment record
2473
- */
2474
- async deleteAttachment(attachment, context) {
2475
- try {
2476
- await this.remoteStorage.deleteFile(attachment);
2477
- if (attachment.localUri) {
2478
- await this.localStorage.deleteFile(attachment.localUri);
2479
- }
2480
- await context.deleteAttachment(attachment.id);
2481
- return {
2482
- ...attachment,
2483
- state: AttachmentState.ARCHIVED
2484
- };
2485
- }
2486
- catch (error) {
2487
- const shouldRetry = (await this.errorHandler?.onDeleteError(attachment, error)) ?? true;
2488
- if (!shouldRetry) {
2489
- return {
2490
- ...attachment,
2491
- state: AttachmentState.ARCHIVED
2492
- };
2493
- }
2494
- return attachment;
2495
- }
2496
- }
2497
- /**
2498
- * Performs cleanup of archived attachments by removing their local files and records.
2499
- * Errors during local file deletion are logged but do not prevent record deletion.
2500
- */
2501
- async deleteArchivedAttachments(context) {
2502
- return await context.deleteArchivedAttachments(async (archivedAttachments) => {
2503
- for (const attachment of archivedAttachments) {
2504
- if (attachment.localUri) {
2505
- try {
2506
- await this.localStorage.deleteFile(attachment.localUri);
2507
- }
2508
- catch (error) {
2509
- this.logger.error('Error deleting local file for archived attachment', error);
2510
- }
2511
- }
2512
- }
2513
- });
2514
- }
2515
- }
2516
-
2517
2402
  /**
2518
2403
  * A simple fixed-capacity queue implementation.
2519
2404
  *
@@ -2699,6 +2584,9 @@ class Mutex {
2699
2584
  }
2700
2585
  }
2701
2586
  }
2587
+ /**
2588
+ * @internal
2589
+ */
2702
2590
  function timeoutSignal(timeout) {
2703
2591
  if (timeout == null)
2704
2592
  return;
@@ -2761,6 +2649,170 @@ class AttachmentService {
2761
2649
  }
2762
2650
  }
2763
2651
 
2652
+ /**
2653
+ * Orchestrates attachment synchronization between local and remote storage.
2654
+ * Handles uploads, downloads, deletions, and state transitions.
2655
+ *
2656
+ * @internal
2657
+ */
2658
+ class SyncingService {
2659
+ attachmentService;
2660
+ localStorage;
2661
+ remoteStorage;
2662
+ logger;
2663
+ errorHandler;
2664
+ constructor(attachmentService, localStorage, remoteStorage, logger, errorHandler) {
2665
+ this.attachmentService = attachmentService;
2666
+ this.localStorage = localStorage;
2667
+ this.remoteStorage = remoteStorage;
2668
+ this.logger = logger;
2669
+ this.errorHandler = errorHandler;
2670
+ }
2671
+ /**
2672
+ * Processes attachments based on their state (upload, download, or delete).
2673
+ * All updates are saved in a single batch after processing.
2674
+ *
2675
+ * @param attachments - Array of attachment records to process
2676
+ * @param context - Attachment context for database operations
2677
+ * @returns Promise that resolves when all attachments have been processed and saved
2678
+ */
2679
+ async processAttachments(attachments, context) {
2680
+ const updatedAttachments = [];
2681
+ for (const attachment of attachments) {
2682
+ switch (attachment.state) {
2683
+ case AttachmentState.QUEUED_UPLOAD:
2684
+ const uploaded = await this.uploadAttachment(attachment);
2685
+ updatedAttachments.push(uploaded);
2686
+ break;
2687
+ case AttachmentState.QUEUED_DOWNLOAD:
2688
+ const downloaded = await this.downloadAttachment(attachment);
2689
+ updatedAttachments.push(downloaded);
2690
+ break;
2691
+ case AttachmentState.QUEUED_DELETE:
2692
+ const deleted = await this.deleteAttachment(attachment, context);
2693
+ updatedAttachments.push(deleted);
2694
+ break;
2695
+ }
2696
+ }
2697
+ await context.saveAttachments(updatedAttachments);
2698
+ }
2699
+ /**
2700
+ * Uploads an attachment from local storage to remote storage.
2701
+ * On success, marks as SYNCED. On failure, defers to error handler or archives.
2702
+ *
2703
+ * @param attachment - The attachment record to upload
2704
+ * @returns Updated attachment record with new state
2705
+ * @throws Error if the attachment has no localUri
2706
+ */
2707
+ async uploadAttachment(attachment) {
2708
+ this.logger.info(`Uploading attachment ${attachment.filename}`);
2709
+ try {
2710
+ if (attachment.localUri == null) {
2711
+ throw new Error(`No localUri for attachment ${attachment.id}`);
2712
+ }
2713
+ const fileBlob = await this.localStorage.readFile(attachment.localUri);
2714
+ await this.remoteStorage.uploadFile(fileBlob, attachment);
2715
+ return {
2716
+ ...attachment,
2717
+ state: AttachmentState.SYNCED,
2718
+ hasSynced: true
2719
+ };
2720
+ }
2721
+ catch (error) {
2722
+ const shouldRetry = (await this.errorHandler?.onUploadError(attachment, error)) ?? true;
2723
+ if (!shouldRetry) {
2724
+ return {
2725
+ ...attachment,
2726
+ state: AttachmentState.ARCHIVED
2727
+ };
2728
+ }
2729
+ return attachment;
2730
+ }
2731
+ }
2732
+ /**
2733
+ * Downloads an attachment from remote storage to local storage.
2734
+ * Retrieves the file, converts to base64, and saves locally.
2735
+ * On success, marks as SYNCED. On failure, defers to error handler or archives.
2736
+ *
2737
+ * @param attachment - The attachment record to download
2738
+ * @returns Updated attachment record with local URI and new state
2739
+ */
2740
+ async downloadAttachment(attachment) {
2741
+ this.logger.info(`Downloading attachment ${attachment.filename}`);
2742
+ try {
2743
+ const fileData = await this.remoteStorage.downloadFile(attachment);
2744
+ const localUri = this.localStorage.getLocalUri(attachment.filename);
2745
+ await this.localStorage.saveFile(localUri, fileData);
2746
+ return {
2747
+ ...attachment,
2748
+ state: AttachmentState.SYNCED,
2749
+ localUri: localUri,
2750
+ hasSynced: true
2751
+ };
2752
+ }
2753
+ catch (error) {
2754
+ const shouldRetry = (await this.errorHandler?.onDownloadError(attachment, error)) ?? true;
2755
+ if (!shouldRetry) {
2756
+ return {
2757
+ ...attachment,
2758
+ state: AttachmentState.ARCHIVED
2759
+ };
2760
+ }
2761
+ return attachment;
2762
+ }
2763
+ }
2764
+ /**
2765
+ * Deletes an attachment from both remote and local storage.
2766
+ * Removes the remote file, local file (if exists), and the attachment record.
2767
+ * On failure, defers to error handler or archives.
2768
+ *
2769
+ * @param attachment - The attachment record to delete
2770
+ * @param context - Attachment context for database operations
2771
+ * @returns Updated attachment record
2772
+ */
2773
+ async deleteAttachment(attachment, context) {
2774
+ try {
2775
+ await this.remoteStorage.deleteFile(attachment);
2776
+ if (attachment.localUri) {
2777
+ await this.localStorage.deleteFile(attachment.localUri);
2778
+ }
2779
+ await context.deleteAttachment(attachment.id);
2780
+ return {
2781
+ ...attachment,
2782
+ state: AttachmentState.ARCHIVED
2783
+ };
2784
+ }
2785
+ catch (error) {
2786
+ const shouldRetry = (await this.errorHandler?.onDeleteError(attachment, error)) ?? true;
2787
+ if (!shouldRetry) {
2788
+ return {
2789
+ ...attachment,
2790
+ state: AttachmentState.ARCHIVED
2791
+ };
2792
+ }
2793
+ return attachment;
2794
+ }
2795
+ }
2796
+ /**
2797
+ * Performs cleanup of archived attachments by removing their local files and records.
2798
+ * Errors during local file deletion are logged but do not prevent record deletion.
2799
+ */
2800
+ async deleteArchivedAttachments(context) {
2801
+ return await context.deleteArchivedAttachments(async (archivedAttachments) => {
2802
+ for (const attachment of archivedAttachments) {
2803
+ if (attachment.localUri) {
2804
+ try {
2805
+ await this.localStorage.deleteFile(attachment.localUri);
2806
+ }
2807
+ catch (error) {
2808
+ this.logger.error('Error deleting local file for archived attachment', error);
2809
+ }
2810
+ }
2811
+ }
2812
+ });
2813
+ }
2814
+ }
2815
+
2764
2816
  /**
2765
2817
  * AttachmentQueue manages the lifecycle and synchronization of attachments
2766
2818
  * between local and remote storage.
@@ -2817,16 +2869,6 @@ class AttachmentQueue {
2817
2869
  * Creates a new AttachmentQueue instance.
2818
2870
  *
2819
2871
  * @param options - Configuration options
2820
- * @param options.db - PowerSync database instance
2821
- * @param options.remoteStorage - Remote storage adapter for upload/download operations
2822
- * @param options.localStorage - Local storage adapter for file persistence
2823
- * @param options.watchAttachments - Callback for monitoring attachment changes in your data model
2824
- * @param options.tableName - Name of the table to store attachment records. Default: 'ps_attachment_queue'
2825
- * @param options.logger - Logger instance. Defaults to db.logger
2826
- * @param options.syncIntervalMs - Periodic polling interval in milliseconds for retrying failed uploads/downloads. Default: 30000
2827
- * @param options.syncThrottleDuration - Throttle duration in milliseconds for the reactive watch query that detects attachment changes. Prevents rapid-fire syncs during bulk changes. Default: 30
2828
- * @param options.downloadAttachments - Whether to automatically download remote attachments. Default: true
2829
- * @param options.archivedCacheLimit - Maximum archived attachments before cleanup. Default: 100
2830
2872
  */
2831
2873
  constructor({ db, localStorage, remoteStorage, watchAttachments, logger, tableName = ATTACHMENT_TABLE, syncIntervalMs = 30 * 1000, syncThrottleDuration = DEFAULT_WATCH_THROTTLE_MS, downloadAttachments = true, archivedCacheLimit = 100, errorHandler }) {
2832
2874
  this.db = db;
@@ -2915,6 +2957,7 @@ class AttachmentQueue {
2915
2957
  state: AttachmentState.QUEUED_DOWNLOAD,
2916
2958
  hasSynced: false,
2917
2959
  metaData: watchedAttachment.metaData,
2960
+ mediaType: watchedAttachment.mediaType,
2918
2961
  timestamp: new Date().getTime()
2919
2962
  });
2920
2963
  continue;
@@ -3002,17 +3045,24 @@ class AttachmentQueue {
3002
3045
  this.statusListenerDispose = undefined;
3003
3046
  }
3004
3047
  }
3048
+ /**
3049
+ * Provides an {@link AttachmentContext} to a callback.
3050
+ *
3051
+ * The callback runs while the attachment queue mutex is held. Do not call
3052
+ * other {@link AttachmentQueue} methods from within the callback, as they may
3053
+ * attempt to acquire the same mutex and block indefinitely.
3054
+ */
3055
+ withAttachmentContext(callback) {
3056
+ /**
3057
+ * AttachmentService is internal and private in this class.
3058
+ * We only need to expose its locking and context functionality for extending classes.
3059
+ */
3060
+ return this.attachmentService.withContext(callback);
3061
+ }
3005
3062
  /**
3006
3063
  * Saves a file to local storage and queues it for upload to remote storage.
3007
3064
  *
3008
3065
  * @param options - File save options
3009
- * @param options.data - The file data as ArrayBuffer, Blob, or base64 string
3010
- * @param options.fileExtension - File extension (e.g., 'jpg', 'pdf')
3011
- * @param options.mediaType - MIME type of the file (e.g., 'image/jpeg')
3012
- * @param options.metaData - Optional metadata to associate with the attachment
3013
- * @param options.id - Optional custom ID. If not provided, a UUID will be generated
3014
- * @param options.updateHook - Optional callback to execute additional database operations
3015
- * within the same transaction as the attachment creation
3016
3066
  * @returns Promise resolving to the created attachment record
3017
3067
  */
3018
3068
  async saveFile({ data, fileExtension, mediaType, metaData, id, updateHook }) {
@@ -3125,171 +3175,19 @@ class AttachmentQueue {
3125
3175
  }
3126
3176
  }
3127
3177
 
3178
+ /**
3179
+ * @alpha
3180
+ */
3128
3181
  var EncodingType;
3129
3182
  (function (EncodingType) {
3130
3183
  EncodingType["UTF8"] = "utf8";
3131
3184
  EncodingType["Base64"] = "base64";
3132
3185
  })(EncodingType || (EncodingType = {}));
3133
3186
 
3134
- const symbolAsyncIterator = Symbol.asyncIterator ?? Symbol.for('Symbol.asyncIterator');
3135
-
3136
3187
  function getDefaultExportFromCjs (x) {
3137
3188
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
3138
3189
  }
3139
3190
 
3140
- var dom = {};
3141
-
3142
- var eventIterator = {};
3143
-
3144
- var hasRequiredEventIterator;
3145
-
3146
- function requireEventIterator () {
3147
- if (hasRequiredEventIterator) return eventIterator;
3148
- hasRequiredEventIterator = 1;
3149
- Object.defineProperty(eventIterator, "__esModule", { value: true });
3150
- class EventQueue {
3151
- constructor() {
3152
- this.pullQueue = [];
3153
- this.pushQueue = [];
3154
- this.eventHandlers = {};
3155
- this.isPaused = false;
3156
- this.isStopped = false;
3157
- }
3158
- push(value) {
3159
- if (this.isStopped)
3160
- return;
3161
- const resolution = { value, done: false };
3162
- if (this.pullQueue.length) {
3163
- const placeholder = this.pullQueue.shift();
3164
- if (placeholder)
3165
- placeholder.resolve(resolution);
3166
- }
3167
- else {
3168
- this.pushQueue.push(Promise.resolve(resolution));
3169
- if (this.highWaterMark !== undefined &&
3170
- this.pushQueue.length >= this.highWaterMark &&
3171
- !this.isPaused) {
3172
- this.isPaused = true;
3173
- if (this.eventHandlers.highWater) {
3174
- this.eventHandlers.highWater();
3175
- }
3176
- else if (console) {
3177
- console.warn(`EventIterator queue reached ${this.pushQueue.length} items`);
3178
- }
3179
- }
3180
- }
3181
- }
3182
- stop() {
3183
- if (this.isStopped)
3184
- return;
3185
- this.isStopped = true;
3186
- this.remove();
3187
- for (const placeholder of this.pullQueue) {
3188
- placeholder.resolve({ value: undefined, done: true });
3189
- }
3190
- this.pullQueue.length = 0;
3191
- }
3192
- fail(error) {
3193
- if (this.isStopped)
3194
- return;
3195
- this.isStopped = true;
3196
- this.remove();
3197
- if (this.pullQueue.length) {
3198
- for (const placeholder of this.pullQueue) {
3199
- placeholder.reject(error);
3200
- }
3201
- this.pullQueue.length = 0;
3202
- }
3203
- else {
3204
- const rejection = Promise.reject(error);
3205
- /* Attach error handler to avoid leaking an unhandled promise rejection. */
3206
- rejection.catch(() => { });
3207
- this.pushQueue.push(rejection);
3208
- }
3209
- }
3210
- remove() {
3211
- Promise.resolve().then(() => {
3212
- if (this.removeCallback)
3213
- this.removeCallback();
3214
- });
3215
- }
3216
- [symbolAsyncIterator]() {
3217
- return {
3218
- next: (value) => {
3219
- const result = this.pushQueue.shift();
3220
- if (result) {
3221
- if (this.lowWaterMark !== undefined &&
3222
- this.pushQueue.length <= this.lowWaterMark &&
3223
- this.isPaused) {
3224
- this.isPaused = false;
3225
- if (this.eventHandlers.lowWater) {
3226
- this.eventHandlers.lowWater();
3227
- }
3228
- }
3229
- return result;
3230
- }
3231
- else if (this.isStopped) {
3232
- return Promise.resolve({ value: undefined, done: true });
3233
- }
3234
- else {
3235
- return new Promise((resolve, reject) => {
3236
- this.pullQueue.push({ resolve, reject });
3237
- });
3238
- }
3239
- },
3240
- return: () => {
3241
- this.isStopped = true;
3242
- this.pushQueue.length = 0;
3243
- this.remove();
3244
- return Promise.resolve({ value: undefined, done: true });
3245
- },
3246
- };
3247
- }
3248
- }
3249
- class EventIterator {
3250
- constructor(listen, { highWaterMark = 100, lowWaterMark = 1 } = {}) {
3251
- const queue = new EventQueue();
3252
- queue.highWaterMark = highWaterMark;
3253
- queue.lowWaterMark = lowWaterMark;
3254
- queue.removeCallback =
3255
- listen({
3256
- push: value => queue.push(value),
3257
- stop: () => queue.stop(),
3258
- fail: error => queue.fail(error),
3259
- on: (event, fn) => {
3260
- queue.eventHandlers[event] = fn;
3261
- },
3262
- }) || (() => { });
3263
- this[symbolAsyncIterator] = () => queue[symbolAsyncIterator]();
3264
- Object.freeze(this);
3265
- }
3266
- }
3267
- eventIterator.EventIterator = EventIterator;
3268
- eventIterator.default = EventIterator;
3269
- return eventIterator;
3270
- }
3271
-
3272
- var hasRequiredDom;
3273
-
3274
- function requireDom () {
3275
- if (hasRequiredDom) return dom;
3276
- hasRequiredDom = 1;
3277
- Object.defineProperty(dom, "__esModule", { value: true });
3278
- const event_iterator_1 = requireEventIterator();
3279
- dom.EventIterator = event_iterator_1.EventIterator;
3280
- function subscribe(event, options, evOptions) {
3281
- return new event_iterator_1.EventIterator(({ push }) => {
3282
- this.addEventListener(event, push, options);
3283
- return () => this.removeEventListener(event, push, options);
3284
- }, evOptions);
3285
- }
3286
- dom.subscribe = subscribe;
3287
- dom.default = event_iterator_1.EventIterator;
3288
- return dom;
3289
- }
3290
-
3291
- var domExports = requireDom();
3292
-
3293
3191
  var logger$1 = {exports: {}};
3294
3192
 
3295
3193
  /*!
@@ -3588,7 +3486,9 @@ var Logger = /*@__PURE__*/getDefaultExportFromCjs(loggerExports);
3588
3486
  * different SQLite DB implementations.
3589
3487
  */
3590
3488
  /**
3591
- * Implements {@link DBGetUtils} on a {@link SqlRunner}.
3489
+ * Implements {@link DBGetUtils} on a {@link SqlExecutor}.
3490
+ *
3491
+ * @internal
3592
3492
  */
3593
3493
  function DBGetUtilsDefaultMixin(Base) {
3594
3494
  return class extends Base {
@@ -3632,6 +3532,8 @@ function DBGetUtilsDefaultMixin(Base) {
3632
3532
  }
3633
3533
  /**
3634
3534
  * Update table operation numbers from SQLite
3535
+ *
3536
+ * @public
3635
3537
  */
3636
3538
  var RowUpdateType;
3637
3539
  (function (RowUpdateType) {
@@ -3640,8 +3542,10 @@ var RowUpdateType;
3640
3542
  RowUpdateType[RowUpdateType["SQLITE_UPDATE"] = 23] = "SQLITE_UPDATE";
3641
3543
  })(RowUpdateType || (RowUpdateType = {}));
3642
3544
  /**
3643
- * A mixin to implement {@link DBAdapter} by delegating to {@link ConnectionPool.readLock} and
3644
- * {@link ConnectionPool.writeLock}.
3545
+ * A mixin to implement {@link DBAdapter} by delegating to {@link ConnectionPool#readLock} and
3546
+ * {@link ConnectionPool#writeLock}.
3547
+ *
3548
+ * @internal
3645
3549
  */
3646
3550
  function DBAdapterDefaultMixin(Base) {
3647
3551
  return class extends Base {
@@ -3729,9 +3633,15 @@ class TransactionImplementation extends DBGetUtilsDefaultMixin(BaseTransaction)
3729
3633
  }
3730
3634
  }
3731
3635
  }
3636
+ /**
3637
+ * @internal
3638
+ */
3732
3639
  function isBatchedUpdateNotification(update) {
3733
3640
  return 'tables' in update;
3734
3641
  }
3642
+ /**
3643
+ * @internal
3644
+ */
3735
3645
  function extractTableUpdates(update) {
3736
3646
  return isBatchedUpdateNotification(update) ? update.tables : [update.table];
3737
3647
  }
@@ -3759,6 +3669,8 @@ const FULL_SYNC_PRIORITY = 2147483647;
3759
3669
  *
3760
3670
  * Also note that data is downloaded in bulk, which means that individual counters are unlikely
3761
3671
  * to be updated one-by-one.
3672
+ *
3673
+ * @public
3762
3674
  */
3763
3675
  class SyncProgress {
3764
3676
  internal;
@@ -3797,6 +3709,9 @@ class SyncProgress {
3797
3709
  }
3798
3710
  }
3799
3711
 
3712
+ /**
3713
+ * @public
3714
+ */
3800
3715
  class SyncStatus {
3801
3716
  options;
3802
3717
  constructor(options) {
@@ -3807,6 +3722,8 @@ class SyncStatus {
3807
3722
  * implementation).
3808
3723
  *
3809
3724
  * This information is only available after a connection has been requested.
3725
+ *
3726
+ * @deprecated This always returns the Rust client (the only option).
3810
3727
  */
3811
3728
  get clientImplementation() {
3812
3729
  return this.options.clientImplementation;
@@ -3814,7 +3731,7 @@ class SyncStatus {
3814
3731
  /**
3815
3732
  * Indicates if the client is currently connected to the PowerSync service.
3816
3733
  *
3817
- * @returns {boolean} True if connected, false otherwise. Defaults to false if not specified.
3734
+ * @returns True if connected, false otherwise. Defaults to false if not specified.
3818
3735
  */
3819
3736
  get connected() {
3820
3737
  return this.options.connected ?? false;
@@ -3822,7 +3739,7 @@ class SyncStatus {
3822
3739
  /**
3823
3740
  * Indicates if the client is in the process of establishing a connection to the PowerSync service.
3824
3741
  *
3825
- * @returns {boolean} True if connecting, false otherwise. Defaults to false if not specified.
3742
+ * @returns True if connecting, false otherwise. Defaults to false if not specified.
3826
3743
  */
3827
3744
  get connecting() {
3828
3745
  return this.options.connecting ?? false;
@@ -3831,7 +3748,7 @@ class SyncStatus {
3831
3748
  * Time that a last sync has fully completed, if any.
3832
3749
  * This timestamp is reset to null after a restart of the PowerSync service.
3833
3750
  *
3834
- * @returns {Date | undefined} The timestamp of the last successful sync, or undefined if no sync has completed.
3751
+ * @returns The timestamp of the last successful sync, or undefined if no sync has completed.
3835
3752
  */
3836
3753
  get lastSyncedAt() {
3837
3754
  return this.options.lastSyncedAt;
@@ -3839,7 +3756,7 @@ class SyncStatus {
3839
3756
  /**
3840
3757
  * Indicates whether there has been at least one full sync completed since initialization.
3841
3758
  *
3842
- * @returns {boolean | undefined} True if at least one sync has completed, false if no sync has completed,
3759
+ * @returns True if at least one sync has completed, false if no sync has completed,
3843
3760
  * or undefined when the state is still being loaded from the database.
3844
3761
  */
3845
3762
  get hasSynced() {
@@ -3848,10 +3765,10 @@ class SyncStatus {
3848
3765
  /**
3849
3766
  * Provides the current data flow status regarding uploads and downloads.
3850
3767
  *
3851
- * @returns {SyncDataFlowStatus} An object containing:
3768
+ * @returns An object containing:
3852
3769
  * - downloading: True if actively downloading changes (only when connected is also true)
3853
3770
  * - uploading: True if actively uploading changes
3854
- * Defaults to {downloading: false, uploading: false} if not specified.
3771
+ * Defaults to `{downloading: false, uploading: false}` if not specified.
3855
3772
  */
3856
3773
  get dataFlowStatus() {
3857
3774
  return (this.options.dataFlow ?? {
@@ -3876,7 +3793,7 @@ class SyncStatus {
3876
3793
  return this.options.dataFlow?.internalStreamSubscriptions?.map((core) => new SyncStreamStatusView(this, core));
3877
3794
  }
3878
3795
  /**
3879
- * If the `stream` appears in {@link syncStreams}, returns the current status for that stream.
3796
+ * If the `stream` appears in {@link SyncStatus.syncStreams}, returns the current status for that stream.
3880
3797
  */
3881
3798
  forStream(stream) {
3882
3799
  const asJson = JSON.stringify(stream.parameters);
@@ -3886,7 +3803,7 @@ class SyncStatus {
3886
3803
  /**
3887
3804
  * Provides sync status information for all bucket priorities, sorted by priority (highest first).
3888
3805
  *
3889
- * @returns {SyncPriorityStatus[]} An array of status entries for different sync priority levels,
3806
+ * @returns An array of status entries for different sync priority levels,
3890
3807
  * sorted with highest priorities (lower numbers) first.
3891
3808
  */
3892
3809
  get priorityStatusEntries() {
@@ -3921,8 +3838,8 @@ class SyncStatus {
3921
3838
  * For example, if PowerSync just finished synchronizing buckets in priority level 3, calling this method
3922
3839
  * with a priority of 1 may return information for priority level 3.
3923
3840
  *
3924
- * @param {number} priority The bucket priority for which the status should be reported
3925
- * @returns {SyncPriorityStatus} Status information for the requested priority level or the next higher level with available status
3841
+ * @param priority - The bucket priority for which the status should be reported
3842
+ * @returns Status information for the requested priority level or the next higher level with available status
3926
3843
  */
3927
3844
  statusForPriority(priority) {
3928
3845
  // priorityStatusEntries are sorted by ascending priorities (so higher numbers to lower numbers).
@@ -3943,8 +3860,8 @@ class SyncStatus {
3943
3860
  * Compares this SyncStatus instance with another to determine if they are equal.
3944
3861
  * Equality is determined by comparing the serialized JSON representation of both instances.
3945
3862
  *
3946
- * @param {SyncStatus} status The SyncStatus instance to compare against
3947
- * @returns {boolean} True if the instances are considered equal, false otherwise
3863
+ * @param status - The SyncStatus instance to compare against
3864
+ * @returns True if the instances are considered equal, false otherwise
3948
3865
  */
3949
3866
  isEqual(status) {
3950
3867
  /**
@@ -3967,7 +3884,7 @@ class SyncStatus {
3967
3884
  * Creates a human-readable string representation of the current sync status.
3968
3885
  * Includes information about connection state, sync completion, and data flow.
3969
3886
  *
3970
- * @returns {string} A string representation of the sync status
3887
+ * @returns A string representation of the sync status
3971
3888
  */
3972
3889
  getMessage() {
3973
3890
  const dataFlow = this.dataFlowStatus;
@@ -3976,7 +3893,7 @@ class SyncStatus {
3976
3893
  /**
3977
3894
  * Serializes the SyncStatus instance to a plain object.
3978
3895
  *
3979
- * @returns {SyncStatusOptions} A plain object representation of the sync status
3896
+ * @returns A plain object representation of the sync status
3980
3897
  */
3981
3898
  toJSON() {
3982
3899
  return {
@@ -4042,6 +3959,9 @@ class SyncStreamStatusView {
4042
3959
  }
4043
3960
  }
4044
3961
 
3962
+ /**
3963
+ * @public
3964
+ */
4045
3965
  class UploadQueueStats {
4046
3966
  count;
4047
3967
  size;
@@ -4067,6 +3987,9 @@ class UploadQueueStats {
4067
3987
  }
4068
3988
  }
4069
3989
 
3990
+ /**
3991
+ * @internal
3992
+ */
4070
3993
  class BaseObserver {
4071
3994
  listeners = new Set();
4072
3995
  constructor() { }
@@ -4094,6 +4017,9 @@ class BaseObserver {
4094
4017
  }
4095
4018
  }
4096
4019
 
4020
+ /**
4021
+ * @internal
4022
+ */
4097
4023
  class ControlledExecutor {
4098
4024
  task;
4099
4025
  /**
@@ -4145,6 +4071,210 @@ class ControlledExecutor {
4145
4071
  }
4146
4072
  }
4147
4073
 
4074
+ /**
4075
+ * Some JavaScript engines, in particular older versions of React Native, don't support Symbol.asyncIterator.
4076
+ *
4077
+ * For those, users relying on async generators typically lower them with a transpiler and [this polyfill](https://github.com/Azure/azure-sdk-for-js/blob/%40azure/core-asynciterator-polyfill_1.0.2/sdk/core/core-asynciterator-polyfill/src/index.ts#L4-L6).
4078
+ * This definition is compatible with that polyfill, so transpiled apps can use async iterables created by the PowerSync
4079
+ * SDK.
4080
+ */
4081
+ const symbolAsyncIterator = Symbol.asyncIterator ?? Symbol.for('Symbol.asyncIterator');
4082
+
4083
+ const doneResult = { done: true, value: undefined };
4084
+ function valueResult(value) {
4085
+ return { done: false, value };
4086
+ }
4087
+ /**
4088
+ * Expands a source async iterator by allowing to inject events asynchronously.
4089
+ *
4090
+ * The resulting iterator will emit all events from its source. Additionally though, events can be injected. These
4091
+ * events are dropped once the main iterator completes, but are otherwise forwarded.
4092
+ *
4093
+ * The iterator completes when its source completes, and it supports backpressure by only calling `next()` on the source
4094
+ * in response to a `next()` call from downstream if no pending injected events can be dispatched.
4095
+ */
4096
+ function injectable(source) {
4097
+ let sourceIsDone = false;
4098
+ let waiter = undefined; // An active, waiting next() call.
4099
+ // A pending upstream event that couldn't be dispatched because inject() has been called before it was resolved.
4100
+ let pendingSourceEvent = null;
4101
+ let sourceFetchInFlight = false;
4102
+ let pendingInjectedEvents = [];
4103
+ const consumeWaiter = () => {
4104
+ const pending = waiter;
4105
+ waiter = undefined;
4106
+ return pending;
4107
+ };
4108
+ const fetchFromSource = () => {
4109
+ const resolveWaiter = (propagate) => {
4110
+ sourceFetchInFlight = false;
4111
+ const active = consumeWaiter();
4112
+ if (active) {
4113
+ propagate(active);
4114
+ }
4115
+ else {
4116
+ pendingSourceEvent = propagate;
4117
+ }
4118
+ };
4119
+ sourceFetchInFlight = true;
4120
+ const nextFromSource = source.next();
4121
+ nextFromSource.then((value) => {
4122
+ sourceIsDone = value.done == true;
4123
+ resolveWaiter((w) => w.resolve(value));
4124
+ }, (error) => {
4125
+ resolveWaiter((w) => w.reject(error));
4126
+ });
4127
+ };
4128
+ return {
4129
+ next: () => {
4130
+ return new Promise((resolve, reject) => {
4131
+ // First priority: Dispatch ready upstream events.
4132
+ if (sourceIsDone) {
4133
+ return resolve(doneResult);
4134
+ }
4135
+ if (pendingSourceEvent) {
4136
+ pendingSourceEvent({ resolve, reject });
4137
+ pendingSourceEvent = null;
4138
+ return;
4139
+ }
4140
+ // Second priority: Dispatch injected events
4141
+ if (pendingInjectedEvents.length) {
4142
+ return resolve(valueResult(pendingInjectedEvents.shift()));
4143
+ }
4144
+ // Nothing pending? Fetch from source
4145
+ waiter = { resolve, reject };
4146
+ if (!sourceFetchInFlight) {
4147
+ fetchFromSource();
4148
+ }
4149
+ });
4150
+ },
4151
+ inject: (event) => {
4152
+ const pending = consumeWaiter();
4153
+ if (pending != null) {
4154
+ pending.resolve(valueResult(event));
4155
+ }
4156
+ else {
4157
+ pendingInjectedEvents.push(event);
4158
+ }
4159
+ }
4160
+ };
4161
+ }
4162
+ /**
4163
+ * Splits a byte stream at line endings, emitting each line as a string.
4164
+ */
4165
+ function extractJsonLines(source, decoder) {
4166
+ let buffer = '';
4167
+ const pendingLines = [];
4168
+ let isFinalEvent = false;
4169
+ return {
4170
+ next: async () => {
4171
+ while (true) {
4172
+ if (isFinalEvent) {
4173
+ return doneResult;
4174
+ }
4175
+ {
4176
+ const first = pendingLines.shift();
4177
+ if (first) {
4178
+ return { done: false, value: first };
4179
+ }
4180
+ }
4181
+ const { done, value } = await source.next();
4182
+ if (done) {
4183
+ const remaining = buffer.trim();
4184
+ if (remaining.length != 0) {
4185
+ isFinalEvent = true;
4186
+ return { done: false, value: remaining };
4187
+ }
4188
+ return doneResult;
4189
+ }
4190
+ const data = decoder.decode(value, { stream: true });
4191
+ buffer += data;
4192
+ const lines = buffer.split('\n');
4193
+ for (let i = 0; i < lines.length - 1; i++) {
4194
+ const l = lines[i].trim();
4195
+ if (l.length > 0) {
4196
+ pendingLines.push(l);
4197
+ }
4198
+ }
4199
+ buffer = lines[lines.length - 1];
4200
+ }
4201
+ }
4202
+ };
4203
+ }
4204
+ /**
4205
+ * Splits a concatenated stream of BSON objects by emitting individual objects.
4206
+ */
4207
+ function extractBsonObjects(source) {
4208
+ // Fully read but not emitted yet.
4209
+ const completedObjects = [];
4210
+ // Whether source has returned { done: true }. We do the same once completed objects have been emitted.
4211
+ let isDone = false;
4212
+ const lengthBuffer = new DataView(new ArrayBuffer(4));
4213
+ let objectBody = null;
4214
+ // If we're parsing the length field, a number between 1 and 4 (inclusive) describing remaining bytes in the header.
4215
+ // If we're consuming a document, the bytes remaining.
4216
+ let remainingLength = 4;
4217
+ return {
4218
+ async next() {
4219
+ while (true) {
4220
+ // Before fetching new data from upstream, return completed objects.
4221
+ if (completedObjects.length) {
4222
+ return valueResult(completedObjects.shift());
4223
+ }
4224
+ if (isDone) {
4225
+ return doneResult;
4226
+ }
4227
+ const upstreamEvent = await source.next();
4228
+ if (upstreamEvent.done) {
4229
+ isDone = true;
4230
+ if (objectBody || remainingLength != 4) {
4231
+ throw new Error('illegal end of stream in BSON object');
4232
+ }
4233
+ return doneResult;
4234
+ }
4235
+ const chunk = upstreamEvent.value;
4236
+ for (let i = 0; i < chunk.length;) {
4237
+ const availableInData = chunk.length - i;
4238
+ if (objectBody) {
4239
+ // We're in the middle of reading a BSON document.
4240
+ const bytesToRead = Math.min(availableInData, remainingLength);
4241
+ const copySource = new Uint8Array(chunk.buffer, chunk.byteOffset + i, bytesToRead);
4242
+ objectBody.set(copySource, objectBody.length - remainingLength);
4243
+ i += bytesToRead;
4244
+ remainingLength -= bytesToRead;
4245
+ if (remainingLength == 0) {
4246
+ completedObjects.push(objectBody);
4247
+ // Prepare to read another document, starting with its length
4248
+ objectBody = null;
4249
+ remainingLength = 4;
4250
+ }
4251
+ }
4252
+ else {
4253
+ // Copy up to 4 bytes into lengthBuffer, depending on how many we still need.
4254
+ const bytesToRead = Math.min(availableInData, remainingLength);
4255
+ for (let j = 0; j < bytesToRead; j++) {
4256
+ lengthBuffer.setUint8(4 - remainingLength + j, chunk[i + j]);
4257
+ }
4258
+ i += bytesToRead;
4259
+ remainingLength -= bytesToRead;
4260
+ if (remainingLength == 0) {
4261
+ // Transition from reading length header to reading document. Subtracting 4 because the length of the
4262
+ // header is included in length.
4263
+ const length = lengthBuffer.getInt32(0, true /* little endian */);
4264
+ remainingLength = length - 4;
4265
+ if (remainingLength < 1) {
4266
+ throw new Error(`invalid length for bson: ${length}`);
4267
+ }
4268
+ objectBody = new Uint8Array(length);
4269
+ new DataView(objectBody.buffer).setInt32(0, length, true);
4270
+ }
4271
+ }
4272
+ }
4273
+ }
4274
+ }
4275
+ };
4276
+ }
4277
+
4148
4278
  /**
4149
4279
  * Throttle a function to be called at most once every "wait" milliseconds,
4150
4280
  * on the trailing edge.
@@ -4164,45 +4294,128 @@ function throttleTrailing(func, wait) {
4164
4294
  };
4165
4295
  }
4166
4296
  function asyncNotifier() {
4167
- let waitingConsumer = null;
4168
- let hasPendingNotification = false;
4297
+ const queue = new EventQueue();
4169
4298
  return {
4170
4299
  notify() {
4171
- if (waitingConsumer != null) {
4172
- waitingConsumer();
4173
- waitingConsumer = null;
4174
- }
4300
+ if (queue.countOutstandingEvents > 0) ;
4175
4301
  else {
4176
- hasPendingNotification = true;
4302
+ queue.notify();
4177
4303
  }
4178
4304
  },
4179
4305
  waitForNotification(signal) {
4180
- return new Promise((resolve) => {
4181
- if (waitingConsumer != null) {
4182
- throw new Error('Illegal call to waitForNotification, already has a waiter.');
4183
- }
4184
- if (signal.aborted) {
4185
- resolve();
4306
+ return queue.waitForEvent(signal);
4307
+ }
4308
+ };
4309
+ }
4310
+ class EventQueue {
4311
+ options;
4312
+ waitingConsumer;
4313
+ outstandingEvents;
4314
+ constructor(options = {}) {
4315
+ this.options = options;
4316
+ this.outstandingEvents = [];
4317
+ }
4318
+ /**
4319
+ * The amount of buffered events not yet dispatched to listeners.
4320
+ */
4321
+ get countOutstandingEvents() {
4322
+ return this.outstandingEvents.length;
4323
+ }
4324
+ notifyInner(dispatch) {
4325
+ const existing = this.waitingConsumer;
4326
+ this.waitingConsumer = undefined;
4327
+ const dispatchAndNotifyListeners = (waiter) => {
4328
+ dispatch(waiter);
4329
+ this.options.eventDelivered?.();
4330
+ };
4331
+ if (existing) {
4332
+ dispatchAndNotifyListeners(existing);
4333
+ }
4334
+ else {
4335
+ this.outstandingEvents.push(dispatchAndNotifyListeners);
4336
+ }
4337
+ }
4338
+ notify(value) {
4339
+ this.notifyInner((l) => l.resolve(value));
4340
+ }
4341
+ notifyError(error) {
4342
+ this.notifyInner((l) => l.reject(error));
4343
+ }
4344
+ waitForEvent(signal) {
4345
+ return new Promise((resolve, reject) => {
4346
+ if (this.waitingConsumer != null) {
4347
+ throw new Error('Illegal call to waitForEvent, already has a waiter.');
4348
+ }
4349
+ const complete = () => {
4350
+ signal?.removeEventListener('abort', onAbort);
4351
+ };
4352
+ const onAbort = () => {
4353
+ complete();
4354
+ this.waitingConsumer = undefined;
4355
+ resolve(undefined);
4356
+ };
4357
+ const waiter = {
4358
+ resolve: (value) => {
4359
+ complete();
4360
+ resolve(value);
4361
+ },
4362
+ reject: (error) => {
4363
+ complete();
4364
+ reject(error);
4186
4365
  }
4187
- else if (hasPendingNotification) {
4188
- resolve();
4189
- hasPendingNotification = false;
4366
+ };
4367
+ if (signal.aborted) {
4368
+ resolve(undefined);
4369
+ }
4370
+ else if (this.countOutstandingEvents > 0) {
4371
+ const [event] = this.outstandingEvents.splice(0, 1);
4372
+ event(waiter);
4373
+ }
4374
+ else {
4375
+ this.waitingConsumer = waiter;
4376
+ signal.addEventListener('abort', onAbort);
4377
+ }
4378
+ });
4379
+ }
4380
+ /**
4381
+ * Creates an async iterable backed by event queues.
4382
+ *
4383
+ * @param run A function invoked for every new listener. It receives a queue backing the async iterator.
4384
+ * @param abort An additional abort signal. The `run` callback will also be aborted when `AsyncIterator.return` is
4385
+ * called.
4386
+ * @returns An object conforming to the async iterable protocol.
4387
+ */
4388
+ static queueBasedAsyncIterable(run, abort) {
4389
+ return {
4390
+ [symbolAsyncIterator]: () => {
4391
+ const queue = new EventQueue();
4392
+ const controller = new AbortController();
4393
+ function dispose() {
4394
+ controller.abort();
4395
+ abort?.removeEventListener('abort', dispose);
4190
4396
  }
4191
- else {
4192
- function complete() {
4193
- signal.removeEventListener('abort', onAbort);
4194
- resolve();
4397
+ if (abort) {
4398
+ if (abort.aborted) {
4399
+ controller.abort();
4195
4400
  }
4196
- function onAbort() {
4197
- waitingConsumer = null;
4198
- resolve();
4401
+ else {
4402
+ abort.addEventListener('abort', dispose);
4199
4403
  }
4200
- waitingConsumer = complete;
4201
- signal.addEventListener('abort', onAbort);
4202
4404
  }
4203
- });
4204
- }
4205
- };
4405
+ run(queue, controller.signal);
4406
+ return {
4407
+ async next() {
4408
+ const event = await queue.waitForEvent(controller.signal);
4409
+ return event == null ? doneResult : valueResult(event);
4410
+ },
4411
+ async return() {
4412
+ dispose();
4413
+ return doneResult;
4414
+ }
4415
+ };
4416
+ }
4417
+ };
4418
+ }
4206
4419
  }
4207
4420
 
4208
4421
  /**
@@ -4361,7 +4574,7 @@ class ConnectionManager extends BaseObserver {
4361
4574
  /**
4362
4575
  * Close the sync connection.
4363
4576
  *
4364
- * Use {@link connect} to connect again.
4577
+ * Use {@link ConnectionManager.connect} to connect again.
4365
4578
  */
4366
4579
  async disconnect() {
4367
4580
  // This will help abort pending connects
@@ -4501,6 +4714,8 @@ const _finalizer = 'FinalizationRegistry' in globalThis
4501
4714
  /**
4502
4715
  * An efficient comparator for {@link WatchedQuery} created with {@link Query#watch}. This has the ability to determine if a query
4503
4716
  * result has changes without necessarily processing all items in the result.
4717
+ *
4718
+ * @public
4504
4719
  */
4505
4720
  class ArrayComparator {
4506
4721
  options;
@@ -4528,6 +4743,8 @@ class ArrayComparator {
4528
4743
  }
4529
4744
  /**
4530
4745
  * Watched query comparator that always reports changed result sets.
4746
+ *
4747
+ * @public
4531
4748
  */
4532
4749
  const FalsyComparator = {
4533
4750
  checkEquality: () => false // Default comparator that always returns false
@@ -4735,6 +4952,8 @@ class AbstractQueryProcessor extends MetaBaseObserver {
4735
4952
  /**
4736
4953
  * An empty differential result set.
4737
4954
  * This is used as the initial state for differential incrementally watched queries.
4955
+ *
4956
+ * @internal
4738
4957
  */
4739
4958
  const EMPTY_DIFFERENTIAL = {
4740
4959
  added: [],
@@ -4747,6 +4966,8 @@ const EMPTY_DIFFERENTIAL = {
4747
4966
  * Default implementation of the {@link DifferentialWatchedQueryComparator} for watched queries.
4748
4967
  * It keys items by their `id` property if available, alternatively it uses JSON stringification
4749
4968
  * of the entire item for the key and comparison.
4969
+ *
4970
+ * @internal
4750
4971
  */
4751
4972
  const DEFAULT_ROW_COMPARATOR = {
4752
4973
  keyBy: (item) => {
@@ -5027,6 +5248,8 @@ class CustomQuery {
5027
5248
 
5028
5249
  /**
5029
5250
  * Tests if the input is a {@link SQLOpenOptions}
5251
+ *
5252
+ * @internal
5030
5253
  */
5031
5254
  const isSQLOpenOptions = (test) => {
5032
5255
  // typeof null is `object`, but you cannot use the `in` operator on `null.
@@ -5034,17 +5257,24 @@ const isSQLOpenOptions = (test) => {
5034
5257
  };
5035
5258
  /**
5036
5259
  * Tests if input is a {@link SQLOpenFactory}
5260
+ *
5261
+ * @internal
5037
5262
  */
5038
5263
  const isSQLOpenFactory = (test) => {
5039
5264
  return typeof test?.openDB == 'function';
5040
5265
  };
5041
5266
  /**
5042
5267
  * Tests if input is a {@link DBAdapter}
5268
+ *
5269
+ * @internal
5043
5270
  */
5044
5271
  const isDBAdapter = (test) => {
5045
5272
  return typeof test?.writeTransaction == 'function';
5046
5273
  };
5047
5274
 
5275
+ /**
5276
+ * @internal
5277
+ */
5048
5278
  var PSInternalTable;
5049
5279
  (function (PSInternalTable) {
5050
5280
  PSInternalTable["DATA"] = "ps_data";
@@ -5053,6 +5283,9 @@ var PSInternalTable;
5053
5283
  PSInternalTable["OPLOG"] = "ps_oplog";
5054
5284
  PSInternalTable["UNTYPED"] = "ps_untyped";
5055
5285
  })(PSInternalTable || (PSInternalTable = {}));
5286
+ /**
5287
+ * @internal
5288
+ */
5056
5289
  var PowerSyncControlCommand;
5057
5290
  (function (PowerSyncControlCommand) {
5058
5291
  PowerSyncControlCommand["PROCESS_TEXT_LINE"] = "line_text";
@@ -5070,6 +5303,8 @@ var PowerSyncControlCommand;
5070
5303
 
5071
5304
  /**
5072
5305
  * A batch of client-side changes.
5306
+ *
5307
+ * @public
5073
5308
  */
5074
5309
  class CrudBatch {
5075
5310
  crud;
@@ -5096,6 +5331,8 @@ class CrudBatch {
5096
5331
 
5097
5332
  /**
5098
5333
  * Type of local change.
5334
+ *
5335
+ * @public
5099
5336
  */
5100
5337
  var UpdateType;
5101
5338
  (function (UpdateType) {
@@ -5108,6 +5345,8 @@ var UpdateType;
5108
5345
  })(UpdateType || (UpdateType = {}));
5109
5346
  /**
5110
5347
  * A single client-side change.
5348
+ *
5349
+ * @public
5111
5350
  */
5112
5351
  class CrudEntry {
5113
5352
  /**
@@ -5204,6 +5443,9 @@ class CrudEntry {
5204
5443
  }
5205
5444
  }
5206
5445
 
5446
+ /**
5447
+ * @public
5448
+ */
5207
5449
  class CrudTransaction extends CrudBatch {
5208
5450
  crud;
5209
5451
  complete;
@@ -5232,6 +5474,8 @@ class CrudTransaction extends CrudBatch {
5232
5474
  * Calls to Abortcontroller.abort(reason: any) will result in the
5233
5475
  * `reason` being thrown. This is not necessarily an error,
5234
5476
  * but extends error for better logging purposes.
5477
+ *
5478
+ * @internal
5235
5479
  */
5236
5480
  class AbortOperation extends Error {
5237
5481
  reason;
@@ -12402,7 +12646,7 @@ function requireDist () {
12402
12646
 
12403
12647
  var distExports = requireDist();
12404
12648
 
12405
- var version = "1.53.2";
12649
+ var version = "1.55.0";
12406
12650
  var PACKAGE = {
12407
12651
  version: version};
12408
12652
 
@@ -12478,289 +12722,95 @@ function requireWebsocketDuplexConnection () {
12478
12722
  get: function () {
12479
12723
  return this.done ? 0 : 1;
12480
12724
  },
12481
- enumerable: false,
12482
- configurable: true
12483
- });
12484
- WebsocketDuplexConnection.prototype.close = function (error) {
12485
- if (this.done) {
12486
- _super.prototype.close.call(this, error);
12487
- return;
12488
- }
12489
- this.websocket.removeEventListener("close", this.handleClosed);
12490
- this.websocket.removeEventListener("error", this.handleError);
12491
- this.websocket.removeEventListener("message", this.handleMessage);
12492
- this.websocket.close();
12493
- delete this.websocket;
12494
- _super.prototype.close.call(this, error);
12495
- };
12496
- WebsocketDuplexConnection.prototype.send = function (frame) {
12497
- if (this.done) {
12498
- return;
12499
- }
12500
- var buffer = (0, rsocket_core_1.serializeFrame)(frame);
12501
- this.websocket.send(buffer);
12502
- };
12503
- return WebsocketDuplexConnection;
12504
- }(rsocket_core_1.Deferred));
12505
- WebsocketDuplexConnection.WebsocketDuplexConnection = WebsocketDuplexConnection$1;
12506
-
12507
- return WebsocketDuplexConnection;
12508
- }
12509
-
12510
- var WebsocketDuplexConnectionExports = requireWebsocketDuplexConnection();
12511
-
12512
- /**
12513
- * Adapted from rsocket-websocket-client
12514
- * https://github.com/rsocket/rsocket-js/blob/e224cf379e747c4f1ddc4f2fa111854626cc8575/packages/rsocket-websocket-client/src/WebsocketClientTransport.ts#L17
12515
- * This adds additional error handling for React Native iOS.
12516
- * This particularly adds a close listener to handle cases where the WebSocket
12517
- * connection closes immediately after opening without emitting an error.
12518
- */
12519
- class WebsocketClientTransport {
12520
- url;
12521
- factory;
12522
- constructor(options) {
12523
- this.url = options.url;
12524
- this.factory = options.wsCreator ?? ((url) => new WebSocket(url));
12525
- }
12526
- connect(multiplexerDemultiplexerFactory) {
12527
- return new Promise((resolve, reject) => {
12528
- const websocket = this.factory(this.url);
12529
- websocket.binaryType = 'arraybuffer';
12530
- let removeListeners;
12531
- const openListener = () => {
12532
- removeListeners();
12533
- resolve(new WebsocketDuplexConnectionExports.WebsocketDuplexConnection(websocket, new distExports.Deserializer(), multiplexerDemultiplexerFactory));
12534
- };
12535
- const errorListener = (ev) => {
12536
- removeListeners();
12537
- // We add a default error in that case.
12538
- if (ev.error != null) {
12539
- // undici typically provides an error object
12540
- reject(ev.error);
12541
- }
12542
- else if (ev.message != null) {
12543
- // React Native typically does not provide an error object, but does provide a message
12544
- reject(new Error(`Failed to create websocket connection: ${ev.message}`));
12545
- }
12546
- else {
12547
- // Browsers often provide no details at all
12548
- reject(new Error(`Failed to create websocket connection to ${this.url}`));
12549
- }
12550
- };
12551
- /**
12552
- * In some cases, such as React Native iOS, the WebSocket connection may close immediately after opening
12553
- * without and error. In such cases, we need to handle the close event to reject the promise.
12554
- */
12555
- const closeListener = () => {
12556
- removeListeners();
12557
- reject(new Error('WebSocket connection closed while opening'));
12558
- };
12559
- removeListeners = () => {
12560
- websocket.removeEventListener('open', openListener);
12561
- websocket.removeEventListener('error', errorListener);
12562
- websocket.removeEventListener('close', closeListener);
12563
- };
12564
- websocket.addEventListener('open', openListener);
12565
- websocket.addEventListener('error', errorListener);
12566
- websocket.addEventListener('close', closeListener);
12567
- });
12568
- }
12569
- }
12570
-
12571
- const doneResult = { done: true, value: undefined };
12572
- function valueResult(value) {
12573
- return { done: false, value };
12574
- }
12575
- /**
12576
- * Expands a source async iterator by allowing to inject events asynchronously.
12577
- *
12578
- * The resulting iterator will emit all events from its source. Additionally though, events can be injected. These
12579
- * events are dropped once the main iterator completes, but are otherwise forwarded.
12580
- *
12581
- * The iterator completes when its source completes, and it supports backpressure by only calling `next()` on the source
12582
- * in response to a `next()` call from downstream if no pending injected events can be dispatched.
12583
- */
12584
- function injectable(source) {
12585
- let sourceIsDone = false;
12586
- let waiter = undefined; // An active, waiting next() call.
12587
- // A pending upstream event that couldn't be dispatched because inject() has been called before it was resolved.
12588
- let pendingSourceEvent = null;
12589
- let sourceFetchInFlight = false;
12590
- let pendingInjectedEvents = [];
12591
- const consumeWaiter = () => {
12592
- const pending = waiter;
12593
- waiter = undefined;
12594
- return pending;
12595
- };
12596
- const fetchFromSource = () => {
12597
- const resolveWaiter = (propagate) => {
12598
- sourceFetchInFlight = false;
12599
- const active = consumeWaiter();
12600
- if (active) {
12601
- propagate(active);
12602
- }
12603
- else {
12604
- pendingSourceEvent = propagate;
12605
- }
12606
- };
12607
- sourceFetchInFlight = true;
12608
- const nextFromSource = source.next();
12609
- nextFromSource.then((value) => {
12610
- sourceIsDone = value.done == true;
12611
- resolveWaiter((w) => w.resolve(value));
12612
- }, (error) => {
12613
- resolveWaiter((w) => w.reject(error));
12614
- });
12615
- };
12616
- return {
12617
- next: () => {
12618
- return new Promise((resolve, reject) => {
12619
- // First priority: Dispatch ready upstream events.
12620
- if (sourceIsDone) {
12621
- return resolve(doneResult);
12622
- }
12623
- if (pendingSourceEvent) {
12624
- pendingSourceEvent({ resolve, reject });
12625
- pendingSourceEvent = null;
12626
- return;
12627
- }
12628
- // Second priority: Dispatch injected events
12629
- if (pendingInjectedEvents.length) {
12630
- return resolve(valueResult(pendingInjectedEvents.shift()));
12631
- }
12632
- // Nothing pending? Fetch from source
12633
- waiter = { resolve, reject };
12634
- if (!sourceFetchInFlight) {
12635
- fetchFromSource();
12636
- }
12637
- });
12638
- },
12639
- inject: (event) => {
12640
- const pending = consumeWaiter();
12641
- if (pending != null) {
12642
- pending.resolve(valueResult(event));
12643
- }
12644
- else {
12645
- pendingInjectedEvents.push(event);
12646
- }
12647
- }
12648
- };
12649
- }
12650
- /**
12651
- * Splits a byte stream at line endings, emitting each line as a string.
12652
- */
12653
- function extractJsonLines(source, decoder) {
12654
- let buffer = '';
12655
- const pendingLines = [];
12656
- let isFinalEvent = false;
12657
- return {
12658
- next: async () => {
12659
- while (true) {
12660
- if (isFinalEvent) {
12661
- return doneResult;
12662
- }
12663
- {
12664
- const first = pendingLines.shift();
12665
- if (first) {
12666
- return { done: false, value: first };
12667
- }
12668
- }
12669
- const { done, value } = await source.next();
12670
- if (done) {
12671
- const remaining = buffer.trim();
12672
- if (remaining.length != 0) {
12673
- isFinalEvent = true;
12674
- return { done: false, value: remaining };
12675
- }
12676
- return doneResult;
12677
- }
12678
- const data = decoder.decode(value, { stream: true });
12679
- buffer += data;
12680
- const lines = buffer.split('\n');
12681
- for (let i = 0; i < lines.length - 1; i++) {
12682
- const l = lines[i].trim();
12683
- if (l.length > 0) {
12684
- pendingLines.push(l);
12685
- }
12686
- }
12687
- buffer = lines[lines.length - 1];
12688
- }
12689
- }
12690
- };
12725
+ enumerable: false,
12726
+ configurable: true
12727
+ });
12728
+ WebsocketDuplexConnection.prototype.close = function (error) {
12729
+ if (this.done) {
12730
+ _super.prototype.close.call(this, error);
12731
+ return;
12732
+ }
12733
+ this.websocket.removeEventListener("close", this.handleClosed);
12734
+ this.websocket.removeEventListener("error", this.handleError);
12735
+ this.websocket.removeEventListener("message", this.handleMessage);
12736
+ this.websocket.close();
12737
+ delete this.websocket;
12738
+ _super.prototype.close.call(this, error);
12739
+ };
12740
+ WebsocketDuplexConnection.prototype.send = function (frame) {
12741
+ if (this.done) {
12742
+ return;
12743
+ }
12744
+ var buffer = (0, rsocket_core_1.serializeFrame)(frame);
12745
+ this.websocket.send(buffer);
12746
+ };
12747
+ return WebsocketDuplexConnection;
12748
+ }(rsocket_core_1.Deferred));
12749
+ WebsocketDuplexConnection.WebsocketDuplexConnection = WebsocketDuplexConnection$1;
12750
+
12751
+ return WebsocketDuplexConnection;
12691
12752
  }
12753
+
12754
+ var WebsocketDuplexConnectionExports = requireWebsocketDuplexConnection();
12755
+
12692
12756
  /**
12693
- * Splits a concatenated stream of BSON objects by emitting individual objects.
12757
+ * Adapted from rsocket-websocket-client
12758
+ * https://github.com/rsocket/rsocket-js/blob/e224cf379e747c4f1ddc4f2fa111854626cc8575/packages/rsocket-websocket-client/src/WebsocketClientTransport.ts#L17
12759
+ * This adds additional error handling for React Native iOS.
12760
+ * This particularly adds a close listener to handle cases where the WebSocket
12761
+ * connection closes immediately after opening without emitting an error.
12694
12762
  */
12695
- function extractBsonObjects(source) {
12696
- // Fully read but not emitted yet.
12697
- const completedObjects = [];
12698
- // Whether source has returned { done: true }. We do the same once completed objects have been emitted.
12699
- let isDone = false;
12700
- const lengthBuffer = new DataView(new ArrayBuffer(4));
12701
- let objectBody = null;
12702
- // If we're parsing the length field, a number between 1 and 4 (inclusive) describing remaining bytes in the header.
12703
- // If we're consuming a document, the bytes remaining.
12704
- let remainingLength = 4;
12705
- return {
12706
- async next() {
12707
- while (true) {
12708
- // Before fetching new data from upstream, return completed objects.
12709
- if (completedObjects.length) {
12710
- return valueResult(completedObjects.shift());
12711
- }
12712
- if (isDone) {
12713
- return doneResult;
12763
+ class WebsocketClientTransport {
12764
+ url;
12765
+ factory;
12766
+ constructor(options) {
12767
+ this.url = options.url;
12768
+ this.factory = options.wsCreator ?? ((url) => new WebSocket(url));
12769
+ }
12770
+ connect(multiplexerDemultiplexerFactory) {
12771
+ return new Promise((resolve, reject) => {
12772
+ const websocket = this.factory(this.url);
12773
+ websocket.binaryType = 'arraybuffer';
12774
+ let removeListeners;
12775
+ const openListener = () => {
12776
+ removeListeners();
12777
+ resolve(new WebsocketDuplexConnectionExports.WebsocketDuplexConnection(websocket, new distExports.Deserializer(), multiplexerDemultiplexerFactory));
12778
+ };
12779
+ const errorListener = (event) => {
12780
+ const ev = event;
12781
+ removeListeners();
12782
+ // We add a default error in that case.
12783
+ if (ev.error != null) {
12784
+ // undici typically provides an error object
12785
+ reject(ev.error);
12714
12786
  }
12715
- const upstreamEvent = await source.next();
12716
- if (upstreamEvent.done) {
12717
- isDone = true;
12718
- if (objectBody || remainingLength != 4) {
12719
- throw new Error('illegal end of stream in BSON object');
12720
- }
12721
- return doneResult;
12787
+ else if (ev.message != null) {
12788
+ // React Native typically does not provide an error object, but does provide a message
12789
+ reject(new Error(`Failed to create websocket connection: ${ev.message}`));
12722
12790
  }
12723
- const chunk = upstreamEvent.value;
12724
- for (let i = 0; i < chunk.length;) {
12725
- const availableInData = chunk.length - i;
12726
- if (objectBody) {
12727
- // We're in the middle of reading a BSON document.
12728
- const bytesToRead = Math.min(availableInData, remainingLength);
12729
- const copySource = new Uint8Array(chunk.buffer, chunk.byteOffset + i, bytesToRead);
12730
- objectBody.set(copySource, objectBody.length - remainingLength);
12731
- i += bytesToRead;
12732
- remainingLength -= bytesToRead;
12733
- if (remainingLength == 0) {
12734
- completedObjects.push(objectBody);
12735
- // Prepare to read another document, starting with its length
12736
- objectBody = null;
12737
- remainingLength = 4;
12738
- }
12739
- }
12740
- else {
12741
- // Copy up to 4 bytes into lengthBuffer, depending on how many we still need.
12742
- const bytesToRead = Math.min(availableInData, remainingLength);
12743
- for (let j = 0; j < bytesToRead; j++) {
12744
- lengthBuffer.setUint8(4 - remainingLength + j, chunk[i + j]);
12745
- }
12746
- i += bytesToRead;
12747
- remainingLength -= bytesToRead;
12748
- if (remainingLength == 0) {
12749
- // Transition from reading length header to reading document. Subtracting 4 because the length of the
12750
- // header is included in length.
12751
- const length = lengthBuffer.getInt32(0, true /* little endian */);
12752
- remainingLength = length - 4;
12753
- if (remainingLength < 1) {
12754
- throw new Error(`invalid length for bson: ${length}`);
12755
- }
12756
- objectBody = new Uint8Array(length);
12757
- new DataView(objectBody.buffer).setInt32(0, length, true);
12758
- }
12759
- }
12791
+ else {
12792
+ // Browsers often provide no details at all
12793
+ reject(new Error(`Failed to create websocket connection to ${this.url}`));
12760
12794
  }
12761
- }
12762
- }
12763
- };
12795
+ };
12796
+ /**
12797
+ * In some cases, such as React Native iOS, the WebSocket connection may close immediately after opening
12798
+ * without and error. In such cases, we need to handle the close event to reject the promise.
12799
+ */
12800
+ const closeListener = () => {
12801
+ removeListeners();
12802
+ reject(new Error('WebSocket connection closed while opening'));
12803
+ };
12804
+ removeListeners = () => {
12805
+ websocket.removeEventListener('open', openListener);
12806
+ websocket.removeEventListener('error', errorListener);
12807
+ websocket.removeEventListener('close', closeListener);
12808
+ };
12809
+ websocket.addEventListener('open', openListener);
12810
+ websocket.addEventListener('error', errorListener);
12811
+ websocket.addEventListener('close', closeListener);
12812
+ });
12813
+ }
12764
12814
  }
12765
12815
 
12766
12816
  const POWERSYNC_TRAILING_SLASH_MATCH = /\/+$/;
@@ -12775,7 +12825,13 @@ const SOCKET_TIMEOUT_MS = 30_000;
12775
12825
  // If there is a backlog of messages (for example on slow connections), keepalive messages could be delayed
12776
12826
  // significantly. Therefore this is longer than the socket timeout.
12777
12827
  const KEEP_ALIVE_LIFETIME_MS = 90_000;
12828
+ /**
12829
+ * @internal
12830
+ */
12778
12831
  const DEFAULT_REMOTE_LOGGER = Logger.get('PowerSyncRemote');
12832
+ /**
12833
+ * @public
12834
+ */
12779
12835
  var FetchStrategy;
12780
12836
  (function (FetchStrategy) {
12781
12837
  /**
@@ -12794,12 +12850,17 @@ var FetchStrategy;
12794
12850
  * The class wrapper is used to distinguish the fetchImplementation
12795
12851
  * option in [AbstractRemoteOptions] from the general fetch method
12796
12852
  * which is typeof "function"
12853
+ *
12854
+ * @internal
12797
12855
  */
12798
12856
  class FetchImplementationProvider {
12799
12857
  getFetch() {
12800
12858
  throw new Error('Unspecified fetch implementation');
12801
12859
  }
12802
12860
  }
12861
+ /**
12862
+ * @internal
12863
+ */
12803
12864
  const DEFAULT_REMOTE_OPTIONS = {
12804
12865
  socketUrlTransformer: (url) => url.replace(/^https?:\/\//, function (match) {
12805
12866
  return match === 'https://' ? 'wss://' : 'ws://';
@@ -12807,6 +12868,9 @@ const DEFAULT_REMOTE_OPTIONS = {
12807
12868
  fetchImplementation: new FetchImplementationProvider(),
12808
12869
  fetchOptions: {}
12809
12870
  };
12871
+ /**
12872
+ * @internal
12873
+ */
12810
12874
  class AbstractRemote {
12811
12875
  connector;
12812
12876
  logger;
@@ -12967,8 +13031,19 @@ class AbstractRemote {
12967
13031
  let pendingSocket = null;
12968
13032
  let keepAliveTimeout;
12969
13033
  let rsocket = null;
12970
- let queue = null;
13034
+ let paused = false;
13035
+ const queue = new EventQueue({
13036
+ eventDelivered: () => {
13037
+ if (queue.countOutstandingEvents <= SYNC_QUEUE_REQUEST_LOW_WATER) {
13038
+ paused = false;
13039
+ requestMore();
13040
+ }
13041
+ }
13042
+ });
12971
13043
  let didClose = false;
13044
+ let connectionEstablished = false;
13045
+ let pendingEventsCount = syncQueueRequestSize;
13046
+ let res = null;
12972
13047
  const abortRequest = () => {
12973
13048
  if (didClose) {
12974
13049
  return;
@@ -12981,10 +13056,23 @@ class AbstractRemote {
12981
13056
  if (rsocket) {
12982
13057
  rsocket.close();
12983
13058
  }
12984
- if (queue) {
12985
- queue.stop();
12986
- }
13059
+ // Send a bogus event to the queue to ensure a pending listener gets woken up. We check for didClose and would
13060
+ // return a doneEvent.
13061
+ queue.notify(null);
12987
13062
  };
13063
+ function push(event) {
13064
+ queue.notify(event);
13065
+ if (queue.countOutstandingEvents >= SYNC_QUEUE_REQUEST_HIGH_WATER) {
13066
+ paused = true;
13067
+ }
13068
+ }
13069
+ function requestMore() {
13070
+ const delta = syncQueueRequestSize - pendingEventsCount;
13071
+ if (!paused && delta > 0) {
13072
+ res?.request(delta);
13073
+ pendingEventsCount = syncQueueRequestSize;
13074
+ }
13075
+ }
12988
13076
  // Handle upstream abort
12989
13077
  if (options.abortSignal.aborted) {
12990
13078
  throw new AbortOperation('Connection request aborted');
@@ -13039,25 +13127,19 @@ class AbstractRemote {
13039
13127
  // Helps to prevent double close scenarios
13040
13128
  rsocket.onClose(() => (rsocket = null));
13041
13129
  return await new Promise((resolve, reject) => {
13042
- let connectionEstablished = false;
13043
- let pendingEventsCount = syncQueueRequestSize;
13044
- let paused = false;
13045
- let res = null;
13046
- function requestMore() {
13047
- const delta = syncQueueRequestSize - pendingEventsCount;
13048
- if (!paused && delta > 0) {
13049
- res?.request(delta);
13050
- pendingEventsCount = syncQueueRequestSize;
13130
+ const queueAsIterator = {
13131
+ next: async () => {
13132
+ if (didClose)
13133
+ return doneResult;
13134
+ const notification = await queue.waitForEvent(options.abortSignal);
13135
+ if (didClose) {
13136
+ return doneResult;
13137
+ }
13138
+ else {
13139
+ return valueResult(notification);
13140
+ }
13051
13141
  }
13052
- }
13053
- const events = new domExports.EventIterator((q) => {
13054
- queue = q;
13055
- q.on('highWater', () => (paused = true));
13056
- q.on('lowWater', () => {
13057
- paused = false;
13058
- requestMore();
13059
- });
13060
- }, { highWaterMark: SYNC_QUEUE_REQUEST_HIGH_WATER, lowWaterMark: SYNC_QUEUE_REQUEST_LOW_WATER })[symbolAsyncIterator]();
13142
+ };
13061
13143
  res = rsocket.requestStream({
13062
13144
  data: toBuffer(options.data),
13063
13145
  metadata: toBuffer({
@@ -13093,11 +13175,11 @@ class AbstractRemote {
13093
13175
  // The connection is active
13094
13176
  if (!connectionEstablished) {
13095
13177
  connectionEstablished = true;
13096
- resolve(events);
13178
+ resolve(queueAsIterator);
13097
13179
  }
13098
13180
  const { data } = payload;
13099
13181
  if (data) {
13100
- queue.push(data);
13182
+ push(data);
13101
13183
  }
13102
13184
  // Less events are now pending
13103
13185
  pendingEventsCount--;
@@ -13216,7 +13298,7 @@ class AbstractRemote {
13216
13298
  * Posts a `/sync/stream` request.
13217
13299
  *
13218
13300
  * Depending on the `Content-Type` of the response, this returns strings for sync lines or encoded BSON documents as
13219
- * {@link Uint8Array}s.
13301
+ * `Uint8Array`s.
13220
13302
  */
13221
13303
  async fetchStream(options) {
13222
13304
  const { isBson, stream } = await this.fetchStreamRaw(options);
@@ -13258,16 +13340,26 @@ function isInterruptingInstruction(instruction) {
13258
13340
  return 'EstablishSyncStream' in instruction || 'CloseSyncStream' in instruction;
13259
13341
  }
13260
13342
 
13343
+ /**
13344
+ * @internal
13345
+ */
13261
13346
  var LockType;
13262
13347
  (function (LockType) {
13263
13348
  LockType["CRUD"] = "crud";
13264
13349
  LockType["SYNC"] = "sync";
13265
13350
  })(LockType || (LockType = {}));
13351
+ /**
13352
+ * @public
13353
+ */
13266
13354
  var SyncStreamConnectionMethod;
13267
13355
  (function (SyncStreamConnectionMethod) {
13268
13356
  SyncStreamConnectionMethod["HTTP"] = "http";
13269
13357
  SyncStreamConnectionMethod["WEB_SOCKET"] = "web-socket";
13270
13358
  })(SyncStreamConnectionMethod || (SyncStreamConnectionMethod = {}));
13359
+ /**
13360
+ * @deprecated Deprecated since {@link SyncClientImplementation.RUST} is the only option.
13361
+ * @public
13362
+ */
13271
13363
  var SyncClientImplementation;
13272
13364
  (function (SyncClientImplementation) {
13273
13365
  /**
@@ -13279,8 +13371,8 @@ var SyncClientImplementation;
13279
13371
  * ## Compatibility warning
13280
13372
  *
13281
13373
  * The Rust sync client stores sync data in a format that is slightly different than the one used
13282
- * by the old JavaScript client. When adopting the {@link RUST} client on existing databases, the PowerSync SDK will
13283
- * migrate the format automatically.
13374
+ * by the old JavaScript client. When adopting the {@link SyncClientImplementation.RUST} client on existing databases,
13375
+ * the PowerSync SDK will migrate the format automatically.
13284
13376
  *
13285
13377
  * SDK versions supporting both the JavaScript and the Rust client support both formats with the JavaScript client
13286
13378
  * implementaiton. However, downgrading to an SDK version that only supports the JavaScript client would not be
@@ -13290,14 +13382,29 @@ var SyncClientImplementation;
13290
13382
  })(SyncClientImplementation || (SyncClientImplementation = {}));
13291
13383
  /**
13292
13384
  * The default {@link SyncClientImplementation} to use, {@link SyncClientImplementation.RUST}.
13385
+ *
13386
+ * @deprecated Deprecated since {@link SyncClientImplementation.RUST} is the only option.
13387
+ * @public
13293
13388
  */
13294
13389
  const DEFAULT_SYNC_CLIENT_IMPLEMENTATION = SyncClientImplementation.RUST;
13390
+ /**
13391
+ * @internal
13392
+ */
13295
13393
  const DEFAULT_CRUD_UPLOAD_THROTTLE_MS = 1000;
13394
+ /**
13395
+ * @internal
13396
+ */
13296
13397
  const DEFAULT_RETRY_DELAY_MS = 5000;
13398
+ /**
13399
+ * @internal
13400
+ */
13297
13401
  const DEFAULT_STREAMING_SYNC_OPTIONS = {
13298
13402
  retryDelayMs: DEFAULT_RETRY_DELAY_MS,
13299
13403
  crudUploadThrottleMs: DEFAULT_CRUD_UPLOAD_THROTTLE_MS
13300
13404
  };
13405
+ /**
13406
+ * @internal
13407
+ */
13301
13408
  const DEFAULT_STREAM_CONNECTION_OPTIONS = {
13302
13409
  appMetadata: {},
13303
13410
  connectionMethod: SyncStreamConnectionMethod.WEB_SOCKET,
@@ -13307,6 +13414,9 @@ const DEFAULT_STREAM_CONNECTION_OPTIONS = {
13307
13414
  serializedSchema: undefined,
13308
13415
  includeDefaultStreams: true
13309
13416
  };
13417
+ /**
13418
+ * @internal
13419
+ */
13310
13420
  class AbstractStreamingSyncImplementation extends BaseObserver {
13311
13421
  options;
13312
13422
  abortController;
@@ -13630,7 +13740,7 @@ The next upload iteration will be delayed.`);
13630
13740
  this.handleActiveStreamsChange?.();
13631
13741
  }
13632
13742
  /**
13633
- * Older versions of the JS SDK used to encode subkeys as JSON in {@link OplogEntry.toJSON}.
13743
+ * Older versions of the JS SDK used to encode subkeys as JSON in `OplogEntry.toJSON`.
13634
13744
  * Because subkeys are always strings, this leads to quotes being added around them in `ps_oplog`.
13635
13745
  * While this is not a problem as long as it's done consistently, it causes issues when a database
13636
13746
  * created by the JS SDK is used with other SDKs, or (more likely) when the new Rust sync client
@@ -13640,7 +13750,7 @@ The next upload iteration will be delayed.`);
13640
13750
  * migration is only triggered when necessary (for now). The function returns whether the new format
13641
13751
  * should be used, so that the JS SDK is able to write to updated databases.
13642
13752
  *
13643
- * @param requireFixedKeyFormat Whether we require the new format or also support the old one.
13753
+ * @param requireFixedKeyFormat - Whether we require the new format or also support the old one.
13644
13754
  * The Rust client requires the new subkey format.
13645
13755
  * @returns Whether the database is now using the new, fixed subkey format.
13646
13756
  */
@@ -13947,7 +14057,8 @@ const MEMORY_TRIGGER_CLAIM_MANAGER = {
13947
14057
 
13948
14058
  /**
13949
14059
  * SQLite operations to track changes for with {@link TriggerManager}
13950
- * @experimental
14060
+ *
14061
+ * @experimental @alpha
13951
14062
  */
13952
14063
  var DiffTriggerOperation;
13953
14064
  (function (DiffTriggerOperation) {
@@ -14009,8 +14120,8 @@ class TriggerManagerImpl {
14009
14120
  get db() {
14010
14121
  return this.options.db;
14011
14122
  }
14012
- async getUUID() {
14013
- const { id: uuid } = await this.db.get(/* sql */ `
14123
+ async getUUID(ctx) {
14124
+ const { id: uuid } = await (ctx ?? this.db).get(/* sql */ `
14014
14125
  SELECT
14015
14126
  uuid () as id
14016
14127
  `);
@@ -14123,7 +14234,7 @@ class TriggerManagerImpl {
14123
14234
  const replicatedColumns = columns ?? sourceDefinition.columns.map((col) => col.name);
14124
14235
  const internalSource = sourceDefinition.internalName;
14125
14236
  const triggerIds = [];
14126
- const id = await this.getUUID();
14237
+ const id = await this.getUUID(setupContext);
14127
14238
  const releaseStorageClaim = useStorage ? await this.options.claimManager.obtainClaim(id) : null;
14128
14239
  /**
14129
14240
  * We default to replicating all columns if no columns array is provided.
@@ -14363,18 +14474,29 @@ const POWERSYNC_TABLE_MATCH = /(^ps_data__|^ps_data_local__)/;
14363
14474
  const DEFAULT_DISCONNECT_CLEAR_OPTIONS = {
14364
14475
  clearLocal: true
14365
14476
  };
14477
+ /**
14478
+ * @internal
14479
+ */
14366
14480
  const DEFAULT_POWERSYNC_CLOSE_OPTIONS = {
14367
14481
  disconnect: true
14368
14482
  };
14483
+ /**
14484
+ * @internal
14485
+ */
14369
14486
  const DEFAULT_POWERSYNC_DB_OPTIONS = {
14370
14487
  retryDelayMs: 5000,
14371
14488
  crudUploadThrottleMs: DEFAULT_CRUD_UPLOAD_THROTTLE_MS
14372
14489
  };
14490
+ /**
14491
+ * @internal
14492
+ */
14373
14493
  const DEFAULT_CRUD_BATCH_LIMIT = 100;
14374
14494
  /**
14375
14495
  * Requesting nested or recursive locks can block the application in some circumstances.
14376
14496
  * This default lock timeout will act as a failsafe to throw an error if a lock cannot
14377
14497
  * be obtained.
14498
+ *
14499
+ * @internal
14378
14500
  */
14379
14501
  const DEFAULT_LOCK_TIMEOUT_MS = 120_000; // 2 mins
14380
14502
  /**
@@ -14384,6 +14506,9 @@ const DEFAULT_LOCK_TIMEOUT_MS = 120_000; // 2 mins
14384
14506
  const isPowerSyncDatabaseOptionsWithSettings = (test) => {
14385
14507
  return typeof test == 'object' && isSQLOpenOptions(test.database);
14386
14508
  };
14509
+ /**
14510
+ * @public
14511
+ */
14387
14512
  class AbstractPowerSyncDatabase extends BaseObserver {
14388
14513
  options;
14389
14514
  /**
@@ -14541,7 +14666,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
14541
14666
  /**
14542
14667
  * Wait for the first sync operation to complete.
14543
14668
  *
14544
- * @param request Either an abort signal (after which the promise will complete regardless of
14669
+ * @param request - Either an abort signal (after which the promise will complete regardless of
14545
14670
  * whether a full sync was completed) or an object providing an abort signal and a priority target.
14546
14671
  * When a priority target is set, the promise may complete when all buckets with the given (or higher)
14547
14672
  * priorities have been synchronized. This can be earlier than a complete sync.
@@ -14696,7 +14821,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
14696
14821
  /**
14697
14822
  * Close the sync connection.
14698
14823
  *
14699
- * Use {@link connect} to connect again.
14824
+ * Use {@link AbstractPowerSyncDatabase.connect} to connect again.
14700
14825
  */
14701
14826
  async disconnect() {
14702
14827
  return this.connectionManager.disconnect();
@@ -14723,8 +14848,8 @@ class AbstractPowerSyncDatabase extends BaseObserver {
14723
14848
  /**
14724
14849
  * Create a sync stream to query its status or to subscribe to it.
14725
14850
  *
14726
- * @param name The name of the stream to subscribe to.
14727
- * @param params Optional parameters for the stream subscription.
14851
+ * @param name - The name of the stream to subscribe to.
14852
+ * @param params - Optional parameters for the stream subscription.
14728
14853
  * @returns A {@link SyncStream} instance that can be subscribed to.
14729
14854
  * @experimental Sync streams are currently in alpha.
14730
14855
  */
@@ -14782,14 +14907,14 @@ class AbstractPowerSyncDatabase extends BaseObserver {
14782
14907
  * Once the data have been successfully uploaded, call {@link CrudBatch.complete} before
14783
14908
  * requesting the next batch.
14784
14909
  *
14785
- * Use {@link limit} to specify the maximum number of updates to return in a single
14910
+ * Use the `limit` parameter to specify the maximum number of updates to return in a single
14786
14911
  * batch.
14787
14912
  *
14788
14913
  * This method does include transaction ids in the result, but does not group
14789
14914
  * data by transaction. One batch may contain data from multiple transactions,
14790
14915
  * and a single transaction may be split over multiple batches.
14791
14916
  *
14792
- * @param limit Maximum number of CRUD entries to include in the batch
14917
+ * @param limit - Maximum number of CRUD entries to include in the batch
14793
14918
  * @returns A batch of CRUD operations to upload, or null if there are none
14794
14919
  */
14795
14920
  async getCrudBatch(limit = DEFAULT_CRUD_BATCH_LIMIT) {
@@ -14816,7 +14941,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
14816
14941
  * Once the data have been successfully uploaded, call {@link CrudTransaction.complete} before
14817
14942
  * requesting the next transaction.
14818
14943
  *
14819
- * Unlike {@link getCrudBatch}, this only returns data from a single transaction at a time.
14944
+ * Unlike {@link AbstractPowerSyncDatabase.getCrudBatch}, this only returns data from a single transaction at a time.
14820
14945
  * All data for the transaction is loaded into memory.
14821
14946
  *
14822
14947
  * @returns A transaction of CRUD operations to upload, or null if there are none
@@ -14831,7 +14956,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
14831
14956
  * This is typically used from the {@link PowerSyncBackendConnector.uploadData} callback. Each entry emitted by the
14832
14957
  * returned iterator is a full transaction containing all local writes made while that transaction was active.
14833
14958
  *
14834
- * Unlike {@link getNextCrudTransaction}, which always returns the oldest transaction that hasn't been
14959
+ * Unlike {@link AbstractPowerSyncDatabase.getNextCrudTransaction}, which always returns the oldest transaction that hasn't been
14835
14960
  * {@link CrudTransaction.complete}d yet, this iterator can be used to receive multiple transactions. Calling
14836
14961
  * {@link CrudTransaction.complete} will mark that and all prior transactions emitted by the iterator as completed.
14837
14962
  *
@@ -14925,8 +15050,8 @@ SELECT * FROM crud_entries;
14925
15050
  * the returned result's `rowsAffected` may be `0` for successful `UPDATE` and `DELETE` statements.
14926
15051
  * Use a `RETURNING` clause and inspect `result.rows` when you need to confirm which rows changed.
14927
15052
  *
14928
- * @param sql The SQL query to execute
14929
- * @param parameters Optional array of parameters to bind to the query
15053
+ * @param sql - The SQL query to execute
15054
+ * @param parameters - Optional array of parameters to bind to the query
14930
15055
  * @returns The query result as an object with structured key-value pairs
14931
15056
  */
14932
15057
  async execute(sql, parameters) {
@@ -14936,8 +15061,8 @@ SELECT * FROM crud_entries;
14936
15061
  * Execute a SQL write (INSERT/UPDATE/DELETE) query directly on the database without any PowerSync processing.
14937
15062
  * This bypasses certain PowerSync abstractions and is useful for accessing the raw database results.
14938
15063
  *
14939
- * @param sql The SQL query to execute
14940
- * @param parameters Optional array of parameters to bind to the query
15064
+ * @param sql - The SQL query to execute
15065
+ * @param parameters - Optional array of parameters to bind to the query
14941
15066
  * @returns The raw query result from the underlying database as a nested array of raw values, where each row is
14942
15067
  * represented as an array of column values without field names.
14943
15068
  */
@@ -14950,8 +15075,8 @@ SELECT * FROM crud_entries;
14950
15075
  * and optionally return results.
14951
15076
  * This is faster than executing separately with each parameter set.
14952
15077
  *
14953
- * @param sql The SQL query to execute
14954
- * @param parameters Optional 2D array of parameter sets, where each inner array is a set of parameters for one execution
15078
+ * @param sql - The SQL query to execute
15079
+ * @param parameters - Optional 2D array of parameter sets, where each inner array is a set of parameters for one execution
14955
15080
  * @returns The query result
14956
15081
  */
14957
15082
  async executeBatch(sql, parameters) {
@@ -14961,8 +15086,8 @@ SELECT * FROM crud_entries;
14961
15086
  /**
14962
15087
  * Execute a read-only query and return results.
14963
15088
  *
14964
- * @param sql The SQL query to execute
14965
- * @param parameters Optional array of parameters to bind to the query
15089
+ * @param sql - The SQL query to execute
15090
+ * @param parameters - Optional array of parameters to bind to the query
14966
15091
  * @returns An array of results
14967
15092
  */
14968
15093
  async getAll(sql, parameters) {
@@ -14972,8 +15097,8 @@ SELECT * FROM crud_entries;
14972
15097
  /**
14973
15098
  * Execute a read-only query and return the first result, or null if the ResultSet is empty.
14974
15099
  *
14975
- * @param sql The SQL query to execute
14976
- * @param parameters Optional array of parameters to bind to the query
15100
+ * @param sql - The SQL query to execute
15101
+ * @param parameters - Optional array of parameters to bind to the query
14977
15102
  * @returns The first result if found, or null if no results are returned
14978
15103
  */
14979
15104
  async getOptional(sql, parameters) {
@@ -14983,8 +15108,8 @@ SELECT * FROM crud_entries;
14983
15108
  /**
14984
15109
  * Execute a read-only query and return the first result, error if the ResultSet is empty.
14985
15110
  *
14986
- * @param sql The SQL query to execute
14987
- * @param parameters Optional array of parameters to bind to the query
15111
+ * @param sql - The SQL query to execute
15112
+ * @param parameters - Optional array of parameters to bind to the query
14988
15113
  * @returns The first result matching the query
14989
15114
  * @throws Error if no rows are returned
14990
15115
  */
@@ -14994,7 +15119,7 @@ SELECT * FROM crud_entries;
14994
15119
  }
14995
15120
  /**
14996
15121
  * Takes a read lock, without starting a transaction.
14997
- * In most cases, {@link readTransaction} should be used instead.
15122
+ * In most cases, {@link AbstractPowerSyncDatabase.readTransaction} should be used instead.
14998
15123
  */
14999
15124
  async readLock(callback) {
15000
15125
  await this.waitForReady();
@@ -15002,7 +15127,7 @@ SELECT * FROM crud_entries;
15002
15127
  }
15003
15128
  /**
15004
15129
  * Takes a global lock, without starting a transaction.
15005
- * In most cases, {@link writeTransaction} should be used instead.
15130
+ * In most cases, {@link AbstractPowerSyncDatabase.writeTransaction} should be used instead.
15006
15131
  */
15007
15132
  async writeLock(callback) {
15008
15133
  await this.waitForReady();
@@ -15013,8 +15138,8 @@ SELECT * FROM crud_entries;
15013
15138
  * Read transactions can run concurrently to a write transaction.
15014
15139
  * Changes from any write transaction are not visible to read transactions started before it.
15015
15140
  *
15016
- * @param callback Function to execute within the transaction
15017
- * @param lockTimeout Time in milliseconds to wait for a lock before throwing an error
15141
+ * @param callback - Function to execute within the transaction
15142
+ * @param lockTimeout - Time in milliseconds to wait for a lock before throwing an error
15018
15143
  * @returns The result of the callback
15019
15144
  * @throws Error if the lock cannot be obtained within the timeout period
15020
15145
  */
@@ -15031,8 +15156,8 @@ SELECT * FROM crud_entries;
15031
15156
  * This takes a global lock - only one write transaction can execute against the database at a time.
15032
15157
  * Statements within the transaction must be done on the provided {@link Transaction} interface.
15033
15158
  *
15034
- * @param callback Function to execute within the transaction
15035
- * @param lockTimeout Time in milliseconds to wait for a lock before throwing an error
15159
+ * @param callback - Function to execute within the transaction
15160
+ * @param lockTimeout - Time in milliseconds to wait for a lock before throwing an error
15036
15161
  * @returns The result of the callback
15037
15162
  * @throws Error if the lock cannot be obtained within the timeout period
15038
15163
  */
@@ -15109,15 +15234,15 @@ SELECT * FROM crud_entries;
15109
15234
  }
15110
15235
  /**
15111
15236
  * Execute a read query every time the source tables are modified.
15112
- * Use {@link SQLWatchOptions.throttleMs} to specify the minimum interval between queries.
15237
+ * Use {@link SQLOnChangeOptions.throttleMs} to specify the minimum interval between queries.
15113
15238
  * Source tables are automatically detected using `EXPLAIN QUERY PLAN`.
15114
15239
  *
15115
15240
  * Note that the `onChange` callback member of the handler is required.
15116
15241
  *
15117
- * @param sql The SQL query to execute
15118
- * @param parameters Optional array of parameters to bind to the query
15119
- * @param handler Callbacks for handling results and errors
15120
- * @param options Options for configuring watch behavior
15242
+ * @param sql - The SQL query to execute
15243
+ * @param parameters - Optional array of parameters to bind to the query
15244
+ * @param handler - Callbacks for handling results and errors
15245
+ * @param options - Options for configuring watch behavior
15121
15246
  */
15122
15247
  watchWithCallback(sql, parameters, handler, options) {
15123
15248
  const { onResult, onError = (e) => this.logger.error(e) } = handler ?? {};
@@ -15130,7 +15255,7 @@ SELECT * FROM crud_entries;
15130
15255
  const watchedQuery = new OnChangeQueryProcessor({
15131
15256
  db: this,
15132
15257
  comparator,
15133
- placeholderData: null,
15258
+ placeholderData: null, // FIXME
15134
15259
  watchOptions: {
15135
15260
  query: {
15136
15261
  compile: () => ({
@@ -15163,38 +15288,35 @@ SELECT * FROM crud_entries;
15163
15288
  }
15164
15289
  /**
15165
15290
  * Execute a read query every time the source tables are modified.
15166
- * Use {@link SQLWatchOptions.throttleMs} to specify the minimum interval between queries.
15291
+ * Use {@link SQLOnChangeOptions.throttleMs} to specify the minimum interval between queries.
15167
15292
  * Source tables are automatically detected using `EXPLAIN QUERY PLAN`.
15168
15293
  *
15169
- * @param sql The SQL query to execute
15170
- * @param parameters Optional array of parameters to bind to the query
15171
- * @param options Options for configuring watch behavior
15294
+ * @param sql - The SQL query to execute
15295
+ * @param parameters - Optional array of parameters to bind to the query
15296
+ * @param options - Options for configuring watch behavior
15172
15297
  * @returns An AsyncIterable that yields QueryResults whenever the data changes
15173
15298
  */
15174
15299
  watchWithAsyncGenerator(sql, parameters, options) {
15175
- return new domExports.EventIterator((eventOptions) => {
15300
+ return EventQueue.queueBasedAsyncIterable((queue, abort) => {
15176
15301
  const handler = {
15177
15302
  onResult: (result) => {
15178
- eventOptions.push(result);
15303
+ queue.notify(result);
15179
15304
  },
15180
15305
  onError: (error) => {
15181
- eventOptions.fail(error);
15306
+ queue.notifyError(error);
15182
15307
  }
15183
15308
  };
15184
- this.watchWithCallback(sql, parameters, handler, options);
15185
- options?.signal?.addEventListener('abort', () => {
15186
- eventOptions.stop();
15187
- });
15188
- });
15309
+ this.watchWithCallback(sql, parameters, handler, { ...options, signal: abort });
15310
+ }, options?.signal);
15189
15311
  }
15190
15312
  /**
15191
15313
  * Resolves the list of tables that are used in a SQL query.
15192
15314
  * If tables are specified in the options, those are used directly.
15193
15315
  * Otherwise, analyzes the query using EXPLAIN to determine which tables are accessed.
15194
15316
  *
15195
- * @param sql The SQL query to analyze
15196
- * @param parameters Optional parameters for the SQL query
15197
- * @param options Optional watch options that may contain explicit table list
15317
+ * @param sql - The SQL query to analyze
15318
+ * @param parameters - Optional parameters for the SQL query
15319
+ * @param options - Optional watch options that may contain explicit table list
15198
15320
  * @returns Array of table names that the query depends on
15199
15321
  */
15200
15322
  async resolveTables(sql, parameters, options) {
@@ -15223,13 +15345,13 @@ SELECT * FROM crud_entries;
15223
15345
  /**
15224
15346
  * Invoke the provided callback on any changes to any of the specified tables.
15225
15347
  *
15226
- * This is preferred over {@link watchWithCallback} when multiple queries need to be performed
15348
+ * This is preferred over {@link AbstractPowerSyncDatabase.watchWithCallback} when multiple queries need to be performed
15227
15349
  * together when data is changed.
15228
15350
  *
15229
15351
  * Note that the `onChange` callback member of the handler is required.
15230
15352
  *
15231
- * @param handler Callbacks for handling change events and errors
15232
- * @param options Options for configuring watch behavior
15353
+ * @param handler - Callbacks for handling change events and errors
15354
+ * @param options - Options for configuring watch behavior
15233
15355
  * @returns A dispose function to stop watching for changes
15234
15356
  */
15235
15357
  onChangeWithCallback(handler, options) {
@@ -15272,31 +15394,26 @@ SELECT * FROM crud_entries;
15272
15394
  /**
15273
15395
  * Create a Stream of changes to any of the specified tables.
15274
15396
  *
15275
- * This is preferred over {@link watchWithAsyncGenerator} when multiple queries need to be performed
15276
- * together when data is changed.
15277
- *
15278
- * Note: do not declare this as `async *onChange` as it will not work in React Native.
15397
+ * This is preferred over {@link AbstractPowerSyncDatabase.watchWithAsyncGenerator} when multiple queries need to be
15398
+ * performed together when data is changed.
15279
15399
  *
15280
- * @param options Options for configuring watch behavior
15400
+ * @param options - Options for configuring watch behavior
15281
15401
  * @returns An AsyncIterable that yields change events whenever the specified tables change
15282
15402
  */
15403
+ // Note: do not declare this as `async *onChange` as it will not work in React Native.
15283
15404
  onChangeWithAsyncGenerator(options) {
15284
- const resolvedOptions = options ?? {};
15285
- return new domExports.EventIterator((eventOptions) => {
15286
- const dispose = this.onChangeWithCallback({
15405
+ return EventQueue.queueBasedAsyncIterable((queue, abort) => {
15406
+ this.onChangeWithCallback({
15287
15407
  onChange: (event) => {
15288
- eventOptions.push(event);
15408
+ queue.notify(event);
15289
15409
  },
15290
15410
  onError: (error) => {
15291
- eventOptions.fail(error);
15411
+ queue.notifyError(error);
15292
15412
  }
15293
- }, options);
15294
- resolvedOptions.signal?.addEventListener('abort', () => {
15295
- eventOptions.stop();
15296
- // Maybe fail?
15297
- });
15298
- return () => dispose();
15299
- });
15413
+ }, { ...options, signal: abort });
15414
+ // Note: We don't have to track the dispose function returned by onChangeWithCallback, it cleans up
15415
+ // after the abort signal completes.
15416
+ }, options?.signal);
15300
15417
  }
15301
15418
  handleTableChanges(changedTables, watchedTables, onDetectedChanges) {
15302
15419
  if (changedTables.size > 0) {
@@ -15315,15 +15432,15 @@ SELECT * FROM crud_entries;
15315
15432
  changedTables.add(table);
15316
15433
  }
15317
15434
  }
15318
- /**
15319
- * @ignore
15320
- */
15321
15435
  async executeReadOnly(sql, params) {
15322
15436
  await this.waitForReady();
15323
15437
  return this.database.readLock((tx) => tx.execute(sql, params));
15324
15438
  }
15325
15439
  }
15326
15440
 
15441
+ /**
15442
+ * @internal
15443
+ */
15327
15444
  class AbstractPowerSyncDatabaseOpenFactory {
15328
15445
  options;
15329
15446
  constructor(options) {
@@ -15348,6 +15465,9 @@ class AbstractPowerSyncDatabaseOpenFactory {
15348
15465
  }
15349
15466
  }
15350
15467
 
15468
+ /**
15469
+ * @internal
15470
+ */
15351
15471
  function runOnSchemaChange(callback, db, options) {
15352
15472
  const triggerWatchedQuery = () => {
15353
15473
  const abortController = new AbortController();
@@ -15372,6 +15492,9 @@ function runOnSchemaChange(callback, db, options) {
15372
15492
  triggerWatchedQuery();
15373
15493
  }
15374
15494
 
15495
+ /**
15496
+ * @public
15497
+ */
15375
15498
  function compilableQueryWatch(db, query, handler, options) {
15376
15499
  const { onResult, onError = (e) => { } } = handler ?? {};
15377
15500
  if (!onResult) {
@@ -15409,8 +15532,14 @@ function compilableQueryWatch(db, query, handler, options) {
15409
15532
  runOnSchemaChange(watchQuery, db, options);
15410
15533
  }
15411
15534
 
15535
+ /**
15536
+ * @internal
15537
+ */
15412
15538
  const MAX_OP_ID = '9223372036854775807';
15413
15539
 
15540
+ /**
15541
+ * @internal
15542
+ */
15414
15543
  class SqliteBucketStorage extends BaseObserver {
15415
15544
  db;
15416
15545
  logger;
@@ -15571,6 +15700,8 @@ class SqliteBucketStorage extends BaseObserver {
15571
15700
  * Thrown when an underlying database connection is closed.
15572
15701
  * This is particularly relevant when worker connections are marked as closed while
15573
15702
  * operations are still in progress.
15703
+ *
15704
+ * @internal
15574
15705
  */
15575
15706
  class ConnectionClosedError extends Error {
15576
15707
  static NAME = 'ConnectionClosedError';
@@ -15590,6 +15721,8 @@ class ConnectionClosedError extends Error {
15590
15721
 
15591
15722
  /**
15592
15723
  * A schema is a collection of tables. It is used to define the structure of a database.
15724
+ *
15725
+ * @public
15593
15726
  */
15594
15727
  class Schema {
15595
15728
  /*
@@ -15626,7 +15759,7 @@ class Schema {
15626
15759
  * Since raw tables are not backed by JSON, running complex queries on them may be more efficient. Further, they allow
15627
15760
  * using client-side table and column constraints.
15628
15761
  *
15629
- * @param tables An object of (table name, raw table definition) entries.
15762
+ * @param tables - An object of (table name, raw table definition) entries.
15630
15763
  */
15631
15764
  withRawTables(tables) {
15632
15765
  for (const [name, rawTableDefinition] of Object.entries(tables)) {
@@ -15672,6 +15805,8 @@ class Schema {
15672
15805
  Generate a new table from the columns and indexes
15673
15806
  @deprecated You should use {@link Table} instead as it now allows TableV2 syntax.
15674
15807
  This will be removed in the next major release.
15808
+
15809
+ @public
15675
15810
  */
15676
15811
  class TableV2 extends Table {
15677
15812
  }
@@ -15682,6 +15817,8 @@ function sanitizeString(input) {
15682
15817
  /**
15683
15818
  * Helper function for sanitizing UUID input strings.
15684
15819
  * Typically used with {@link sanitizeSQL}.
15820
+ *
15821
+ * @alpha
15685
15822
  */
15686
15823
  function sanitizeUUID(uuid) {
15687
15824
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
@@ -15718,6 +15855,8 @@ function sanitizeUUID(uuid) {
15718
15855
  * // Incorrect:
15719
15856
  * sanitizeSQL`New.id = '${myID}'` // Produces double quotes: New.id = ''O''Reilly''
15720
15857
  * ```
15858
+ *
15859
+ * @alpha
15721
15860
  */
15722
15861
  function sanitizeSQL(strings, ...values) {
15723
15862
  let result = '';
@@ -15747,6 +15886,8 @@ function sanitizeSQL(strings, ...values) {
15747
15886
 
15748
15887
  /**
15749
15888
  * Performs a {@link AbstractPowerSyncDatabase.getAll} operation for a watched query.
15889
+ *
15890
+ * @public
15750
15891
  */
15751
15892
  class GetAllQuery {
15752
15893
  options;
@@ -15771,6 +15912,9 @@ class GetAllQuery {
15771
15912
  }
15772
15913
 
15773
15914
  const TypedLogger = Logger;
15915
+ /**
15916
+ * @public
15917
+ */
15774
15918
  const LogLevel = {
15775
15919
  TRACE: TypedLogger.TRACE,
15776
15920
  DEBUG: TypedLogger.DEBUG,
@@ -15787,6 +15931,7 @@ const LogLevel = {
15787
15931
  * across all loggers created with `createLogger`. Adjusting settings on this
15788
15932
  * base logger affects all loggers derived from it unless explicitly overridden.
15789
15933
  *
15934
+ * @public
15790
15935
  */
15791
15936
  function createBaseLogger() {
15792
15937
  return Logger;
@@ -15797,6 +15942,8 @@ function createBaseLogger() {
15797
15942
  * Named loggers allow specific modules or areas of your application to have
15798
15943
  * their own logging levels and behaviors. These loggers inherit configuration
15799
15944
  * from the base logger by default but can override settings independently.
15945
+ *
15946
+ * @public
15800
15947
  */
15801
15948
  function createLogger(name, options = {}) {
15802
15949
  const logger = Logger.get(name);
@@ -15806,6 +15953,9 @@ function createLogger(name, options = {}) {
15806
15953
  return logger;
15807
15954
  }
15808
15955
 
15956
+ /**
15957
+ * @internal
15958
+ */
15809
15959
  const parseQuery = (query, parameters) => {
15810
15960
  let sqlStatement;
15811
15961
  if (typeof query == 'string') {