@powersync/common 1.53.2 → 1.55.0

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 (221) hide show
  1. package/dist/bundle.cjs +922 -772
  2. package/dist/bundle.cjs.map +1 -1
  3. package/dist/bundle.mjs +922 -772
  4. package/dist/bundle.mjs.map +1 -1
  5. package/dist/bundle.node.cjs +923 -619
  6. package/dist/bundle.node.cjs.map +1 -1
  7. package/dist/bundle.node.mjs +923 -619
  8. package/dist/bundle.node.mjs.map +1 -1
  9. package/dist/index.d.cts +749 -205
  10. package/lib/attachments/AttachmentContext.d.ts +7 -6
  11. package/lib/attachments/AttachmentContext.js +2 -1
  12. package/lib/attachments/AttachmentContext.js.map +1 -1
  13. package/lib/attachments/AttachmentErrorHandler.d.ts +6 -6
  14. package/lib/attachments/AttachmentQueue.d.ts +82 -33
  15. package/lib/attachments/AttachmentQueue.js +16 -18
  16. package/lib/attachments/AttachmentQueue.js.map +1 -1
  17. package/lib/attachments/LocalStorageAdapter.d.ts +14 -8
  18. package/lib/attachments/LocalStorageAdapter.js +3 -0
  19. package/lib/attachments/LocalStorageAdapter.js.map +1 -1
  20. package/lib/attachments/RemoteStorageAdapter.d.ts +4 -4
  21. package/lib/attachments/Schema.d.ts +12 -4
  22. package/lib/attachments/Schema.js +8 -3
  23. package/lib/attachments/Schema.js.map +1 -1
  24. package/lib/attachments/WatchedAttachmentItem.d.ts +3 -1
  25. package/lib/client/AbstractPowerSyncDatabase.d.ts +110 -60
  26. package/lib/client/AbstractPowerSyncDatabase.js +77 -74
  27. package/lib/client/AbstractPowerSyncDatabase.js.map +1 -1
  28. package/lib/client/AbstractPowerSyncOpenFactory.d.ts +6 -0
  29. package/lib/client/AbstractPowerSyncOpenFactory.js +3 -0
  30. package/lib/client/AbstractPowerSyncOpenFactory.js.map +1 -1
  31. package/lib/client/ConnectionManager.d.ts +4 -1
  32. package/lib/client/ConnectionManager.js +1 -1
  33. package/lib/client/ConnectionManager.js.map +1 -1
  34. package/lib/client/Query.d.ts +9 -0
  35. package/lib/client/SQLOpenFactory.d.ts +12 -0
  36. package/lib/client/SQLOpenFactory.js +6 -0
  37. package/lib/client/SQLOpenFactory.js.map +1 -1
  38. package/lib/client/compilableQueryWatch.d.ts +6 -0
  39. package/lib/client/compilableQueryWatch.js +3 -0
  40. package/lib/client/compilableQueryWatch.js.map +1 -1
  41. package/lib/client/connection/PowerSyncBackendConnector.d.ts +3 -0
  42. package/lib/client/connection/PowerSyncCredentials.d.ts +3 -0
  43. package/lib/client/constants.d.ts +3 -0
  44. package/lib/client/constants.js +3 -0
  45. package/lib/client/constants.js.map +1 -1
  46. package/lib/client/runOnSchemaChange.d.ts +3 -0
  47. package/lib/client/runOnSchemaChange.js +3 -0
  48. package/lib/client/runOnSchemaChange.js.map +1 -1
  49. package/lib/client/sync/bucket/BucketStorageAdapter.d.ts +12 -0
  50. package/lib/client/sync/bucket/BucketStorageAdapter.js +6 -0
  51. package/lib/client/sync/bucket/BucketStorageAdapter.js.map +1 -1
  52. package/lib/client/sync/bucket/CrudBatch.d.ts +2 -0
  53. package/lib/client/sync/bucket/CrudBatch.js +2 -0
  54. package/lib/client/sync/bucket/CrudBatch.js.map +1 -1
  55. package/lib/client/sync/bucket/CrudEntry.d.ts +9 -0
  56. package/lib/client/sync/bucket/CrudEntry.js +4 -0
  57. package/lib/client/sync/bucket/CrudEntry.js.map +1 -1
  58. package/lib/client/sync/bucket/CrudTransaction.d.ts +3 -0
  59. package/lib/client/sync/bucket/CrudTransaction.js +3 -0
  60. package/lib/client/sync/bucket/CrudTransaction.js.map +1 -1
  61. package/lib/client/sync/bucket/SqliteBucketStorage.d.ts +3 -0
  62. package/lib/client/sync/bucket/SqliteBucketStorage.js +3 -0
  63. package/lib/client/sync/bucket/SqliteBucketStorage.js.map +1 -1
  64. package/lib/client/sync/stream/AbstractRemote.d.ts +30 -1
  65. package/lib/client/sync/stream/AbstractRemote.js +59 -27
  66. package/lib/client/sync/stream/AbstractRemote.js.map +1 -1
  67. package/lib/client/sync/stream/AbstractStreamingSyncImplementation.d.ts +55 -5
  68. package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +32 -4
  69. package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js.map +1 -1
  70. package/lib/client/sync/stream/JsonValue.d.ts +3 -0
  71. package/lib/client/sync/stream/WebsocketClientTransport.js +2 -1
  72. package/lib/client/sync/stream/WebsocketClientTransport.js.map +1 -1
  73. package/lib/client/sync/sync-streams.d.ts +22 -7
  74. package/lib/client/triggers/TriggerManager.d.ts +19 -18
  75. package/lib/client/triggers/TriggerManager.js +2 -1
  76. package/lib/client/triggers/TriggerManager.js.map +1 -1
  77. package/lib/client/triggers/TriggerManagerImpl.d.ts +1 -1
  78. package/lib/client/triggers/TriggerManagerImpl.js +3 -3
  79. package/lib/client/triggers/TriggerManagerImpl.js.map +1 -1
  80. package/lib/client/triggers/sanitizeSQL.d.ts +4 -0
  81. package/lib/client/triggers/sanitizeSQL.js +4 -0
  82. package/lib/client/triggers/sanitizeSQL.js.map +1 -1
  83. package/lib/client/watched/GetAllQuery.d.ts +4 -0
  84. package/lib/client/watched/GetAllQuery.js +2 -0
  85. package/lib/client/watched/GetAllQuery.js.map +1 -1
  86. package/lib/client/watched/WatchedQuery.d.ts +24 -2
  87. package/lib/client/watched/WatchedQuery.js +9 -0
  88. package/lib/client/watched/WatchedQuery.js.map +1 -1
  89. package/lib/client/watched/processors/AbstractQueryProcessor.d.ts +1 -1
  90. package/lib/client/watched/processors/AbstractQueryProcessor.js.map +1 -1
  91. package/lib/client/watched/processors/DifferentialQueryProcessor.d.ts +20 -0
  92. package/lib/client/watched/processors/DifferentialQueryProcessor.js +4 -0
  93. package/lib/client/watched/processors/DifferentialQueryProcessor.js.map +1 -1
  94. package/lib/client/watched/processors/OnChangeQueryProcessor.d.ts +4 -0
  95. package/lib/client/watched/processors/OnChangeQueryProcessor.js.map +1 -1
  96. package/lib/client/watched/processors/comparators.d.ts +8 -0
  97. package/lib/client/watched/processors/comparators.js +4 -0
  98. package/lib/client/watched/processors/comparators.js.map +1 -1
  99. package/lib/db/ConnectionClosedError.d.ts +2 -0
  100. package/lib/db/ConnectionClosedError.js +2 -0
  101. package/lib/db/ConnectionClosedError.js.map +1 -1
  102. package/lib/db/DBAdapter.d.ts +56 -6
  103. package/lib/db/DBAdapter.js +15 -3
  104. package/lib/db/DBAdapter.js.map +1 -1
  105. package/lib/db/crud/SyncProgress.d.ts +6 -1
  106. package/lib/db/crud/SyncProgress.js +2 -0
  107. package/lib/db/crud/SyncProgress.js.map +1 -1
  108. package/lib/db/crud/SyncStatus.d.ts +36 -38
  109. package/lib/db/crud/SyncStatus.js +19 -14
  110. package/lib/db/crud/SyncStatus.js.map +1 -1
  111. package/lib/db/crud/UploadQueueStatus.d.ts +3 -0
  112. package/lib/db/crud/UploadQueueStatus.js +3 -0
  113. package/lib/db/crud/UploadQueueStatus.js.map +1 -1
  114. package/lib/db/schema/Column.d.ts +28 -0
  115. package/lib/db/schema/Column.js +16 -3
  116. package/lib/db/schema/Column.js.map +1 -1
  117. package/lib/db/schema/Index.d.ts +9 -0
  118. package/lib/db/schema/Index.js +6 -0
  119. package/lib/db/schema/Index.js.map +1 -1
  120. package/lib/db/schema/IndexedColumn.d.ts +9 -0
  121. package/lib/db/schema/IndexedColumn.js +6 -0
  122. package/lib/db/schema/IndexedColumn.js.map +1 -1
  123. package/lib/db/schema/RawTable.d.ts +7 -1
  124. package/lib/db/schema/Schema.d.ts +6 -1
  125. package/lib/db/schema/Schema.js +3 -1
  126. package/lib/db/schema/Schema.js.map +1 -1
  127. package/lib/db/schema/Table.d.ts +27 -3
  128. package/lib/db/schema/Table.js +9 -0
  129. package/lib/db/schema/Table.js.map +1 -1
  130. package/lib/db/schema/TableV2.d.ts +2 -0
  131. package/lib/db/schema/TableV2.js +2 -0
  132. package/lib/db/schema/TableV2.js.map +1 -1
  133. package/lib/index.d.ts +1 -1
  134. package/lib/types/types.d.ts +6 -0
  135. package/lib/utils/AbortOperation.d.ts +2 -0
  136. package/lib/utils/AbortOperation.js +2 -0
  137. package/lib/utils/AbortOperation.js.map +1 -1
  138. package/lib/utils/BaseObserver.d.ts +12 -0
  139. package/lib/utils/BaseObserver.js +3 -0
  140. package/lib/utils/BaseObserver.js.map +1 -1
  141. package/lib/utils/ControlledExecutor.d.ts +6 -0
  142. package/lib/utils/ControlledExecutor.js +3 -0
  143. package/lib/utils/ControlledExecutor.js.map +1 -1
  144. package/lib/utils/Logger.d.ts +9 -0
  145. package/lib/utils/Logger.js +6 -0
  146. package/lib/utils/Logger.js.map +1 -1
  147. package/lib/utils/async.d.ts +26 -0
  148. package/lib/utils/async.js +114 -27
  149. package/lib/utils/async.js.map +1 -1
  150. package/lib/utils/compatibility.d.ts +8 -0
  151. package/lib/utils/compatibility.js +9 -0
  152. package/lib/utils/compatibility.js.map +1 -0
  153. package/lib/utils/mutex.d.ts +8 -0
  154. package/lib/utils/mutex.js +3 -0
  155. package/lib/utils/mutex.js.map +1 -1
  156. package/lib/utils/parseQuery.d.ts +6 -0
  157. package/lib/utils/parseQuery.js +3 -0
  158. package/lib/utils/parseQuery.js.map +1 -1
  159. package/lib/utils/stream_transform.d.ts +3 -1
  160. package/lib/utils/stream_transform.js.map +1 -1
  161. package/package.json +3 -3
  162. package/src/attachments/AttachmentContext.ts +7 -6
  163. package/src/attachments/AttachmentErrorHandler.ts +6 -6
  164. package/src/attachments/AttachmentQueue.ts +93 -35
  165. package/src/attachments/LocalStorageAdapter.ts +14 -8
  166. package/src/attachments/README.md +2 -0
  167. package/src/attachments/RemoteStorageAdapter.ts +4 -4
  168. package/src/attachments/Schema.ts +12 -4
  169. package/src/attachments/WatchedAttachmentItem.ts +3 -1
  170. package/src/client/AbstractPowerSyncDatabase.ts +135 -91
  171. package/src/client/AbstractPowerSyncOpenFactory.ts +6 -0
  172. package/src/client/ConnectionManager.ts +4 -1
  173. package/src/client/Query.ts +9 -0
  174. package/src/client/SQLOpenFactory.ts +12 -0
  175. package/src/client/compilableQueryWatch.ts +6 -0
  176. package/src/client/connection/PowerSyncBackendConnector.ts +3 -0
  177. package/src/client/connection/PowerSyncCredentials.ts +3 -0
  178. package/src/client/constants.ts +3 -0
  179. package/src/client/runOnSchemaChange.ts +3 -0
  180. package/src/client/sync/bucket/BucketStorageAdapter.ts +12 -0
  181. package/src/client/sync/bucket/CrudBatch.ts +2 -0
  182. package/src/client/sync/bucket/CrudEntry.ts +9 -0
  183. package/src/client/sync/bucket/CrudTransaction.ts +3 -0
  184. package/src/client/sync/bucket/SqliteBucketStorage.ts +3 -0
  185. package/src/client/sync/stream/AbstractRemote.ts +76 -34
  186. package/src/client/sync/stream/AbstractStreamingSyncImplementation.ts +55 -5
  187. package/src/client/sync/stream/JsonValue.ts +3 -0
  188. package/src/client/sync/stream/WebsocketClientTransport.ts +3 -1
  189. package/src/client/sync/sync-streams.ts +22 -9
  190. package/src/client/triggers/TriggerManager.ts +19 -18
  191. package/src/client/triggers/TriggerManagerImpl.ts +5 -5
  192. package/src/client/triggers/sanitizeSQL.ts +5 -0
  193. package/src/client/watched/GetAllQuery.ts +5 -1
  194. package/src/client/watched/WatchedQuery.ts +24 -2
  195. package/src/client/watched/processors/AbstractQueryProcessor.ts +6 -6
  196. package/src/client/watched/processors/DifferentialQueryProcessor.ts +28 -5
  197. package/src/client/watched/processors/OnChangeQueryProcessor.ts +9 -3
  198. package/src/client/watched/processors/comparators.ts +8 -0
  199. package/src/db/ConnectionClosedError.ts +2 -0
  200. package/src/db/DBAdapter.ts +58 -6
  201. package/src/db/crud/SyncProgress.ts +6 -1
  202. package/src/db/crud/SyncStatus.ts +40 -21
  203. package/src/db/crud/UploadQueueStatus.ts +3 -0
  204. package/src/db/schema/Column.ts +28 -3
  205. package/src/db/schema/Index.ts +9 -0
  206. package/src/db/schema/IndexedColumn.ts +9 -0
  207. package/src/db/schema/RawTable.ts +7 -1
  208. package/src/db/schema/Schema.ts +8 -3
  209. package/src/db/schema/Table.ts +30 -5
  210. package/src/db/schema/TableV2.ts +2 -0
  211. package/src/index.ts +1 -1
  212. package/src/types/types.ts +6 -0
  213. package/src/utils/AbortOperation.ts +2 -0
  214. package/src/utils/BaseObserver.ts +12 -0
  215. package/src/utils/ControlledExecutor.ts +6 -0
  216. package/src/utils/Logger.ts +9 -0
  217. package/src/utils/async.ts +136 -28
  218. package/src/utils/compatibility.ts +9 -0
  219. package/src/utils/mutex.ts +12 -0
  220. package/src/utils/parseQuery.ts +6 -0
  221. package/src/utils/stream_transform.ts +3 -1
@@ -1,7 +1,9 @@
1
- import { EventIterator } from 'event-iterator';
2
1
  import { Buffer } from 'node:buffer';
3
2
 
4
- // https://www.sqlite.org/lang_expr.html#castexpr
3
+ /**
4
+ * @see https://www.sqlite.org/lang_expr.html#castexpr
5
+ * @public
6
+ */
5
7
  var ColumnType;
6
8
  (function (ColumnType) {
7
9
  ColumnType["TEXT"] = "TEXT";
@@ -17,14 +19,24 @@ const integer = {
17
19
  const real = {
18
20
  type: ColumnType.REAL
19
21
  };
20
- // powersync-sqlite-core limits the number of column per table to 1999, due to internal SQLite limits.
21
- // In earlier versions this was limited to 63.
22
+ /**
23
+ * powersync-sqlite-core limits the number of column per table to 1999, due to internal SQLite limits.
24
+ * In earlier versions this was limited to 63.
25
+ *
26
+ * @internal
27
+ */
22
28
  const MAX_AMOUNT_OF_COLUMNS = 1999;
29
+ /**
30
+ * @public
31
+ */
23
32
  const column = {
24
33
  text,
25
34
  integer,
26
35
  real
27
36
  };
37
+ /**
38
+ * @public
39
+ */
28
40
  class Column {
29
41
  options;
30
42
  constructor(options) {
@@ -44,9 +56,15 @@ class Column {
44
56
  }
45
57
  }
46
58
 
59
+ /**
60
+ * @internal
61
+ */
47
62
  const DEFAULT_INDEX_COLUMN_OPTIONS = {
48
63
  ascending: true
49
64
  };
65
+ /**
66
+ * @public
67
+ */
50
68
  class IndexedColumn {
51
69
  options;
52
70
  static createAscending(column) {
@@ -73,9 +91,15 @@ class IndexedColumn {
73
91
  }
74
92
  }
75
93
 
94
+ /**
95
+ * @internal
96
+ */
76
97
  const DEFAULT_INDEX_OPTIONS = {
77
98
  columns: []
78
99
  };
100
+ /**
101
+ * @public
102
+ */
79
103
  class Index {
80
104
  options;
81
105
  static createAscending(options, columnNames) {
@@ -117,6 +141,9 @@ function encodeTableOptions(options) {
117
141
  };
118
142
  }
119
143
 
144
+ /**
145
+ * @internal
146
+ */
120
147
  const DEFAULT_TABLE_OPTIONS = {
121
148
  indexes: [],
122
149
  insertOnly: false,
@@ -125,7 +152,13 @@ const DEFAULT_TABLE_OPTIONS = {
125
152
  trackMetadata: false,
126
153
  ignoreEmptyUpdates: false
127
154
  };
155
+ /**
156
+ * @internal
157
+ */
128
158
  const InvalidSQLCharacters = /["'%,.#\s[\]]/;
159
+ /**
160
+ * @public
161
+ */
129
162
  class Table {
130
163
  options;
131
164
  _mappedColumns;
@@ -316,6 +349,11 @@ class Table {
316
349
  }
317
350
  }
318
351
 
352
+ /**
353
+ * The default name of the local table storing attachment data.
354
+ *
355
+ * @alpha
356
+ */
319
357
  const ATTACHMENT_TABLE = 'attachments';
320
358
  /**
321
359
  * Maps a database row to an AttachmentRecord.
@@ -323,7 +361,7 @@ const ATTACHMENT_TABLE = 'attachments';
323
361
  * @param row - The database row object
324
362
  * @returns The corresponding AttachmentRecord
325
363
  *
326
- * @experimental
364
+ * @alpha
327
365
  */
328
366
  function attachmentFromSql(row) {
329
367
  return {
@@ -341,7 +379,7 @@ function attachmentFromSql(row) {
341
379
  /**
342
380
  * AttachmentState represents the current synchronization state of an attachment.
343
381
  *
344
- * @experimental
382
+ * @alpha
345
383
  */
346
384
  var AttachmentState;
347
385
  (function (AttachmentState) {
@@ -354,7 +392,7 @@ var AttachmentState;
354
392
  /**
355
393
  * AttachmentTable defines the schema for the attachment queue table.
356
394
  *
357
- * @internal
395
+ * @alpha
358
396
  */
359
397
  class AttachmentTable extends Table {
360
398
  constructor(options) {
@@ -382,7 +420,8 @@ class AttachmentTable extends Table {
382
420
  * Provides methods to query, insert, update, and delete attachment records with
383
421
  * proper transaction management through PowerSync.
384
422
  *
385
- * @internal
423
+ * @experimental
424
+ * @alpha
386
425
  */
387
426
  class AttachmentContext {
388
427
  /** PowerSync database instance for executing queries */
@@ -604,6 +643,9 @@ class AttachmentContext {
604
643
  }
605
644
  }
606
645
 
646
+ /**
647
+ * @public
648
+ */
607
649
  var WatchedQueryListenerEvent;
608
650
  (function (WatchedQueryListenerEvent) {
609
651
  WatchedQueryListenerEvent["ON_DATA"] = "onData";
@@ -612,176 +654,18 @@ var WatchedQueryListenerEvent;
612
654
  WatchedQueryListenerEvent["SETTINGS_WILL_UPDATE"] = "settingsWillUpdate";
613
655
  WatchedQueryListenerEvent["CLOSED"] = "closed";
614
656
  })(WatchedQueryListenerEvent || (WatchedQueryListenerEvent = {}));
657
+ /**
658
+ * @internal
659
+ */
615
660
  const DEFAULT_WATCH_THROTTLE_MS = 30;
661
+ /**
662
+ * @internal
663
+ */
616
664
  const DEFAULT_WATCH_QUERY_OPTIONS = {
617
665
  throttleMs: DEFAULT_WATCH_THROTTLE_MS,
618
666
  reportFetching: true
619
667
  };
620
668
 
621
- /**
622
- * Orchestrates attachment synchronization between local and remote storage.
623
- * Handles uploads, downloads, deletions, and state transitions.
624
- *
625
- * @internal
626
- */
627
- class SyncingService {
628
- attachmentService;
629
- localStorage;
630
- remoteStorage;
631
- logger;
632
- errorHandler;
633
- constructor(attachmentService, localStorage, remoteStorage, logger, errorHandler) {
634
- this.attachmentService = attachmentService;
635
- this.localStorage = localStorage;
636
- this.remoteStorage = remoteStorage;
637
- this.logger = logger;
638
- this.errorHandler = errorHandler;
639
- }
640
- /**
641
- * Processes attachments based on their state (upload, download, or delete).
642
- * All updates are saved in a single batch after processing.
643
- *
644
- * @param attachments - Array of attachment records to process
645
- * @param context - Attachment context for database operations
646
- * @returns Promise that resolves when all attachments have been processed and saved
647
- */
648
- async processAttachments(attachments, context) {
649
- const updatedAttachments = [];
650
- for (const attachment of attachments) {
651
- switch (attachment.state) {
652
- case AttachmentState.QUEUED_UPLOAD:
653
- const uploaded = await this.uploadAttachment(attachment);
654
- updatedAttachments.push(uploaded);
655
- break;
656
- case AttachmentState.QUEUED_DOWNLOAD:
657
- const downloaded = await this.downloadAttachment(attachment);
658
- updatedAttachments.push(downloaded);
659
- break;
660
- case AttachmentState.QUEUED_DELETE:
661
- const deleted = await this.deleteAttachment(attachment, context);
662
- updatedAttachments.push(deleted);
663
- break;
664
- }
665
- }
666
- await context.saveAttachments(updatedAttachments);
667
- }
668
- /**
669
- * Uploads an attachment from local storage to remote storage.
670
- * On success, marks as SYNCED. On failure, defers to error handler or archives.
671
- *
672
- * @param attachment - The attachment record to upload
673
- * @returns Updated attachment record with new state
674
- * @throws Error if the attachment has no localUri
675
- */
676
- async uploadAttachment(attachment) {
677
- this.logger.info(`Uploading attachment ${attachment.filename}`);
678
- try {
679
- if (attachment.localUri == null) {
680
- throw new Error(`No localUri for attachment ${attachment.id}`);
681
- }
682
- const fileBlob = await this.localStorage.readFile(attachment.localUri);
683
- await this.remoteStorage.uploadFile(fileBlob, attachment);
684
- return {
685
- ...attachment,
686
- state: AttachmentState.SYNCED,
687
- hasSynced: true
688
- };
689
- }
690
- catch (error) {
691
- const shouldRetry = (await this.errorHandler?.onUploadError(attachment, error)) ?? true;
692
- if (!shouldRetry) {
693
- return {
694
- ...attachment,
695
- state: AttachmentState.ARCHIVED
696
- };
697
- }
698
- return attachment;
699
- }
700
- }
701
- /**
702
- * Downloads an attachment from remote storage to local storage.
703
- * Retrieves the file, converts to base64, and saves locally.
704
- * On success, marks as SYNCED. On failure, defers to error handler or archives.
705
- *
706
- * @param attachment - The attachment record to download
707
- * @returns Updated attachment record with local URI and new state
708
- */
709
- async downloadAttachment(attachment) {
710
- this.logger.info(`Downloading attachment ${attachment.filename}`);
711
- try {
712
- const fileData = await this.remoteStorage.downloadFile(attachment);
713
- const localUri = this.localStorage.getLocalUri(attachment.filename);
714
- await this.localStorage.saveFile(localUri, fileData);
715
- return {
716
- ...attachment,
717
- state: AttachmentState.SYNCED,
718
- localUri: localUri,
719
- hasSynced: true
720
- };
721
- }
722
- catch (error) {
723
- const shouldRetry = (await this.errorHandler?.onDownloadError(attachment, error)) ?? true;
724
- if (!shouldRetry) {
725
- return {
726
- ...attachment,
727
- state: AttachmentState.ARCHIVED
728
- };
729
- }
730
- return attachment;
731
- }
732
- }
733
- /**
734
- * Deletes an attachment from both remote and local storage.
735
- * Removes the remote file, local file (if exists), and the attachment record.
736
- * On failure, defers to error handler or archives.
737
- *
738
- * @param attachment - The attachment record to delete
739
- * @param context - Attachment context for database operations
740
- * @returns Updated attachment record
741
- */
742
- async deleteAttachment(attachment, context) {
743
- try {
744
- await this.remoteStorage.deleteFile(attachment);
745
- if (attachment.localUri) {
746
- await this.localStorage.deleteFile(attachment.localUri);
747
- }
748
- await context.deleteAttachment(attachment.id);
749
- return {
750
- ...attachment,
751
- state: AttachmentState.ARCHIVED
752
- };
753
- }
754
- catch (error) {
755
- const shouldRetry = (await this.errorHandler?.onDeleteError(attachment, error)) ?? true;
756
- if (!shouldRetry) {
757
- return {
758
- ...attachment,
759
- state: AttachmentState.ARCHIVED
760
- };
761
- }
762
- return attachment;
763
- }
764
- }
765
- /**
766
- * Performs cleanup of archived attachments by removing their local files and records.
767
- * Errors during local file deletion are logged but do not prevent record deletion.
768
- */
769
- async deleteArchivedAttachments(context) {
770
- return await context.deleteArchivedAttachments(async (archivedAttachments) => {
771
- for (const attachment of archivedAttachments) {
772
- if (attachment.localUri) {
773
- try {
774
- await this.localStorage.deleteFile(attachment.localUri);
775
- }
776
- catch (error) {
777
- this.logger.error('Error deleting local file for archived attachment', error);
778
- }
779
- }
780
- }
781
- });
782
- }
783
- }
784
-
785
669
  /**
786
670
  * A simple fixed-capacity queue implementation.
787
671
  *
@@ -967,6 +851,9 @@ class Mutex {
967
851
  }
968
852
  }
969
853
  }
854
+ /**
855
+ * @internal
856
+ */
970
857
  function timeoutSignal(timeout) {
971
858
  if (timeout == null)
972
859
  return;
@@ -1029,6 +916,170 @@ class AttachmentService {
1029
916
  }
1030
917
  }
1031
918
 
919
+ /**
920
+ * Orchestrates attachment synchronization between local and remote storage.
921
+ * Handles uploads, downloads, deletions, and state transitions.
922
+ *
923
+ * @internal
924
+ */
925
+ class SyncingService {
926
+ attachmentService;
927
+ localStorage;
928
+ remoteStorage;
929
+ logger;
930
+ errorHandler;
931
+ constructor(attachmentService, localStorage, remoteStorage, logger, errorHandler) {
932
+ this.attachmentService = attachmentService;
933
+ this.localStorage = localStorage;
934
+ this.remoteStorage = remoteStorage;
935
+ this.logger = logger;
936
+ this.errorHandler = errorHandler;
937
+ }
938
+ /**
939
+ * Processes attachments based on their state (upload, download, or delete).
940
+ * All updates are saved in a single batch after processing.
941
+ *
942
+ * @param attachments - Array of attachment records to process
943
+ * @param context - Attachment context for database operations
944
+ * @returns Promise that resolves when all attachments have been processed and saved
945
+ */
946
+ async processAttachments(attachments, context) {
947
+ const updatedAttachments = [];
948
+ for (const attachment of attachments) {
949
+ switch (attachment.state) {
950
+ case AttachmentState.QUEUED_UPLOAD:
951
+ const uploaded = await this.uploadAttachment(attachment);
952
+ updatedAttachments.push(uploaded);
953
+ break;
954
+ case AttachmentState.QUEUED_DOWNLOAD:
955
+ const downloaded = await this.downloadAttachment(attachment);
956
+ updatedAttachments.push(downloaded);
957
+ break;
958
+ case AttachmentState.QUEUED_DELETE:
959
+ const deleted = await this.deleteAttachment(attachment, context);
960
+ updatedAttachments.push(deleted);
961
+ break;
962
+ }
963
+ }
964
+ await context.saveAttachments(updatedAttachments);
965
+ }
966
+ /**
967
+ * Uploads an attachment from local storage to remote storage.
968
+ * On success, marks as SYNCED. On failure, defers to error handler or archives.
969
+ *
970
+ * @param attachment - The attachment record to upload
971
+ * @returns Updated attachment record with new state
972
+ * @throws Error if the attachment has no localUri
973
+ */
974
+ async uploadAttachment(attachment) {
975
+ this.logger.info(`Uploading attachment ${attachment.filename}`);
976
+ try {
977
+ if (attachment.localUri == null) {
978
+ throw new Error(`No localUri for attachment ${attachment.id}`);
979
+ }
980
+ const fileBlob = await this.localStorage.readFile(attachment.localUri);
981
+ await this.remoteStorage.uploadFile(fileBlob, attachment);
982
+ return {
983
+ ...attachment,
984
+ state: AttachmentState.SYNCED,
985
+ hasSynced: true
986
+ };
987
+ }
988
+ catch (error) {
989
+ const shouldRetry = (await this.errorHandler?.onUploadError(attachment, error)) ?? true;
990
+ if (!shouldRetry) {
991
+ return {
992
+ ...attachment,
993
+ state: AttachmentState.ARCHIVED
994
+ };
995
+ }
996
+ return attachment;
997
+ }
998
+ }
999
+ /**
1000
+ * Downloads an attachment from remote storage to local storage.
1001
+ * Retrieves the file, converts to base64, and saves locally.
1002
+ * On success, marks as SYNCED. On failure, defers to error handler or archives.
1003
+ *
1004
+ * @param attachment - The attachment record to download
1005
+ * @returns Updated attachment record with local URI and new state
1006
+ */
1007
+ async downloadAttachment(attachment) {
1008
+ this.logger.info(`Downloading attachment ${attachment.filename}`);
1009
+ try {
1010
+ const fileData = await this.remoteStorage.downloadFile(attachment);
1011
+ const localUri = this.localStorage.getLocalUri(attachment.filename);
1012
+ await this.localStorage.saveFile(localUri, fileData);
1013
+ return {
1014
+ ...attachment,
1015
+ state: AttachmentState.SYNCED,
1016
+ localUri: localUri,
1017
+ hasSynced: true
1018
+ };
1019
+ }
1020
+ catch (error) {
1021
+ const shouldRetry = (await this.errorHandler?.onDownloadError(attachment, error)) ?? true;
1022
+ if (!shouldRetry) {
1023
+ return {
1024
+ ...attachment,
1025
+ state: AttachmentState.ARCHIVED
1026
+ };
1027
+ }
1028
+ return attachment;
1029
+ }
1030
+ }
1031
+ /**
1032
+ * Deletes an attachment from both remote and local storage.
1033
+ * Removes the remote file, local file (if exists), and the attachment record.
1034
+ * On failure, defers to error handler or archives.
1035
+ *
1036
+ * @param attachment - The attachment record to delete
1037
+ * @param context - Attachment context for database operations
1038
+ * @returns Updated attachment record
1039
+ */
1040
+ async deleteAttachment(attachment, context) {
1041
+ try {
1042
+ await this.remoteStorage.deleteFile(attachment);
1043
+ if (attachment.localUri) {
1044
+ await this.localStorage.deleteFile(attachment.localUri);
1045
+ }
1046
+ await context.deleteAttachment(attachment.id);
1047
+ return {
1048
+ ...attachment,
1049
+ state: AttachmentState.ARCHIVED
1050
+ };
1051
+ }
1052
+ catch (error) {
1053
+ const shouldRetry = (await this.errorHandler?.onDeleteError(attachment, error)) ?? true;
1054
+ if (!shouldRetry) {
1055
+ return {
1056
+ ...attachment,
1057
+ state: AttachmentState.ARCHIVED
1058
+ };
1059
+ }
1060
+ return attachment;
1061
+ }
1062
+ }
1063
+ /**
1064
+ * Performs cleanup of archived attachments by removing their local files and records.
1065
+ * Errors during local file deletion are logged but do not prevent record deletion.
1066
+ */
1067
+ async deleteArchivedAttachments(context) {
1068
+ return await context.deleteArchivedAttachments(async (archivedAttachments) => {
1069
+ for (const attachment of archivedAttachments) {
1070
+ if (attachment.localUri) {
1071
+ try {
1072
+ await this.localStorage.deleteFile(attachment.localUri);
1073
+ }
1074
+ catch (error) {
1075
+ this.logger.error('Error deleting local file for archived attachment', error);
1076
+ }
1077
+ }
1078
+ }
1079
+ });
1080
+ }
1081
+ }
1082
+
1032
1083
  /**
1033
1084
  * AttachmentQueue manages the lifecycle and synchronization of attachments
1034
1085
  * between local and remote storage.
@@ -1085,16 +1136,6 @@ class AttachmentQueue {
1085
1136
  * Creates a new AttachmentQueue instance.
1086
1137
  *
1087
1138
  * @param options - Configuration options
1088
- * @param options.db - PowerSync database instance
1089
- * @param options.remoteStorage - Remote storage adapter for upload/download operations
1090
- * @param options.localStorage - Local storage adapter for file persistence
1091
- * @param options.watchAttachments - Callback for monitoring attachment changes in your data model
1092
- * @param options.tableName - Name of the table to store attachment records. Default: 'ps_attachment_queue'
1093
- * @param options.logger - Logger instance. Defaults to db.logger
1094
- * @param options.syncIntervalMs - Periodic polling interval in milliseconds for retrying failed uploads/downloads. Default: 30000
1095
- * @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
1096
- * @param options.downloadAttachments - Whether to automatically download remote attachments. Default: true
1097
- * @param options.archivedCacheLimit - Maximum archived attachments before cleanup. Default: 100
1098
1139
  */
1099
1140
  constructor({ db, localStorage, remoteStorage, watchAttachments, logger, tableName = ATTACHMENT_TABLE, syncIntervalMs = 30 * 1000, syncThrottleDuration = DEFAULT_WATCH_THROTTLE_MS, downloadAttachments = true, archivedCacheLimit = 100, errorHandler }) {
1100
1141
  this.db = db;
@@ -1183,6 +1224,7 @@ class AttachmentQueue {
1183
1224
  state: AttachmentState.QUEUED_DOWNLOAD,
1184
1225
  hasSynced: false,
1185
1226
  metaData: watchedAttachment.metaData,
1227
+ mediaType: watchedAttachment.mediaType,
1186
1228
  timestamp: new Date().getTime()
1187
1229
  });
1188
1230
  continue;
@@ -1270,17 +1312,24 @@ class AttachmentQueue {
1270
1312
  this.statusListenerDispose = undefined;
1271
1313
  }
1272
1314
  }
1315
+ /**
1316
+ * Provides an {@link AttachmentContext} to a callback.
1317
+ *
1318
+ * The callback runs while the attachment queue mutex is held. Do not call
1319
+ * other {@link AttachmentQueue} methods from within the callback, as they may
1320
+ * attempt to acquire the same mutex and block indefinitely.
1321
+ */
1322
+ withAttachmentContext(callback) {
1323
+ /**
1324
+ * AttachmentService is internal and private in this class.
1325
+ * We only need to expose its locking and context functionality for extending classes.
1326
+ */
1327
+ return this.attachmentService.withContext(callback);
1328
+ }
1273
1329
  /**
1274
1330
  * Saves a file to local storage and queues it for upload to remote storage.
1275
1331
  *
1276
1332
  * @param options - File save options
1277
- * @param options.data - The file data as ArrayBuffer, Blob, or base64 string
1278
- * @param options.fileExtension - File extension (e.g., 'jpg', 'pdf')
1279
- * @param options.mediaType - MIME type of the file (e.g., 'image/jpeg')
1280
- * @param options.metaData - Optional metadata to associate with the attachment
1281
- * @param options.id - Optional custom ID. If not provided, a UUID will be generated
1282
- * @param options.updateHook - Optional callback to execute additional database operations
1283
- * within the same transaction as the attachment creation
1284
1333
  * @returns Promise resolving to the created attachment record
1285
1334
  */
1286
1335
  async saveFile({ data, fileExtension, mediaType, metaData, id, updateHook }) {
@@ -1393,6 +1442,9 @@ class AttachmentQueue {
1393
1442
  }
1394
1443
  }
1395
1444
 
1445
+ /**
1446
+ * @alpha
1447
+ */
1396
1448
  var EncodingType;
1397
1449
  (function (EncodingType) {
1398
1450
  EncodingType["UTF8"] = "utf8";
@@ -1701,7 +1753,9 @@ var Logger = /*@__PURE__*/getDefaultExportFromCjs(loggerExports);
1701
1753
  * different SQLite DB implementations.
1702
1754
  */
1703
1755
  /**
1704
- * Implements {@link DBGetUtils} on a {@link SqlRunner}.
1756
+ * Implements {@link DBGetUtils} on a {@link SqlExecutor}.
1757
+ *
1758
+ * @internal
1705
1759
  */
1706
1760
  function DBGetUtilsDefaultMixin(Base) {
1707
1761
  return class extends Base {
@@ -1745,6 +1799,8 @@ function DBGetUtilsDefaultMixin(Base) {
1745
1799
  }
1746
1800
  /**
1747
1801
  * Update table operation numbers from SQLite
1802
+ *
1803
+ * @public
1748
1804
  */
1749
1805
  var RowUpdateType;
1750
1806
  (function (RowUpdateType) {
@@ -1753,8 +1809,10 @@ var RowUpdateType;
1753
1809
  RowUpdateType[RowUpdateType["SQLITE_UPDATE"] = 23] = "SQLITE_UPDATE";
1754
1810
  })(RowUpdateType || (RowUpdateType = {}));
1755
1811
  /**
1756
- * A mixin to implement {@link DBAdapter} by delegating to {@link ConnectionPool.readLock} and
1757
- * {@link ConnectionPool.writeLock}.
1812
+ * A mixin to implement {@link DBAdapter} by delegating to {@link ConnectionPool#readLock} and
1813
+ * {@link ConnectionPool#writeLock}.
1814
+ *
1815
+ * @internal
1758
1816
  */
1759
1817
  function DBAdapterDefaultMixin(Base) {
1760
1818
  return class extends Base {
@@ -1842,9 +1900,15 @@ class TransactionImplementation extends DBGetUtilsDefaultMixin(BaseTransaction)
1842
1900
  }
1843
1901
  }
1844
1902
  }
1903
+ /**
1904
+ * @internal
1905
+ */
1845
1906
  function isBatchedUpdateNotification(update) {
1846
1907
  return 'tables' in update;
1847
1908
  }
1909
+ /**
1910
+ * @internal
1911
+ */
1848
1912
  function extractTableUpdates(update) {
1849
1913
  return isBatchedUpdateNotification(update) ? update.tables : [update.table];
1850
1914
  }
@@ -1872,6 +1936,8 @@ const FULL_SYNC_PRIORITY = 2147483647;
1872
1936
  *
1873
1937
  * Also note that data is downloaded in bulk, which means that individual counters are unlikely
1874
1938
  * to be updated one-by-one.
1939
+ *
1940
+ * @public
1875
1941
  */
1876
1942
  class SyncProgress {
1877
1943
  internal;
@@ -1910,6 +1976,9 @@ class SyncProgress {
1910
1976
  }
1911
1977
  }
1912
1978
 
1979
+ /**
1980
+ * @public
1981
+ */
1913
1982
  class SyncStatus {
1914
1983
  options;
1915
1984
  constructor(options) {
@@ -1920,6 +1989,8 @@ class SyncStatus {
1920
1989
  * implementation).
1921
1990
  *
1922
1991
  * This information is only available after a connection has been requested.
1992
+ *
1993
+ * @deprecated This always returns the Rust client (the only option).
1923
1994
  */
1924
1995
  get clientImplementation() {
1925
1996
  return this.options.clientImplementation;
@@ -1927,7 +1998,7 @@ class SyncStatus {
1927
1998
  /**
1928
1999
  * Indicates if the client is currently connected to the PowerSync service.
1929
2000
  *
1930
- * @returns {boolean} True if connected, false otherwise. Defaults to false if not specified.
2001
+ * @returns True if connected, false otherwise. Defaults to false if not specified.
1931
2002
  */
1932
2003
  get connected() {
1933
2004
  return this.options.connected ?? false;
@@ -1935,7 +2006,7 @@ class SyncStatus {
1935
2006
  /**
1936
2007
  * Indicates if the client is in the process of establishing a connection to the PowerSync service.
1937
2008
  *
1938
- * @returns {boolean} True if connecting, false otherwise. Defaults to false if not specified.
2009
+ * @returns True if connecting, false otherwise. Defaults to false if not specified.
1939
2010
  */
1940
2011
  get connecting() {
1941
2012
  return this.options.connecting ?? false;
@@ -1944,7 +2015,7 @@ class SyncStatus {
1944
2015
  * Time that a last sync has fully completed, if any.
1945
2016
  * This timestamp is reset to null after a restart of the PowerSync service.
1946
2017
  *
1947
- * @returns {Date | undefined} The timestamp of the last successful sync, or undefined if no sync has completed.
2018
+ * @returns The timestamp of the last successful sync, or undefined if no sync has completed.
1948
2019
  */
1949
2020
  get lastSyncedAt() {
1950
2021
  return this.options.lastSyncedAt;
@@ -1952,7 +2023,7 @@ class SyncStatus {
1952
2023
  /**
1953
2024
  * Indicates whether there has been at least one full sync completed since initialization.
1954
2025
  *
1955
- * @returns {boolean | undefined} True if at least one sync has completed, false if no sync has completed,
2026
+ * @returns True if at least one sync has completed, false if no sync has completed,
1956
2027
  * or undefined when the state is still being loaded from the database.
1957
2028
  */
1958
2029
  get hasSynced() {
@@ -1961,10 +2032,10 @@ class SyncStatus {
1961
2032
  /**
1962
2033
  * Provides the current data flow status regarding uploads and downloads.
1963
2034
  *
1964
- * @returns {SyncDataFlowStatus} An object containing:
2035
+ * @returns An object containing:
1965
2036
  * - downloading: True if actively downloading changes (only when connected is also true)
1966
2037
  * - uploading: True if actively uploading changes
1967
- * Defaults to {downloading: false, uploading: false} if not specified.
2038
+ * Defaults to `{downloading: false, uploading: false}` if not specified.
1968
2039
  */
1969
2040
  get dataFlowStatus() {
1970
2041
  return (this.options.dataFlow ?? {
@@ -1989,7 +2060,7 @@ class SyncStatus {
1989
2060
  return this.options.dataFlow?.internalStreamSubscriptions?.map((core) => new SyncStreamStatusView(this, core));
1990
2061
  }
1991
2062
  /**
1992
- * If the `stream` appears in {@link syncStreams}, returns the current status for that stream.
2063
+ * If the `stream` appears in {@link SyncStatus.syncStreams}, returns the current status for that stream.
1993
2064
  */
1994
2065
  forStream(stream) {
1995
2066
  const asJson = JSON.stringify(stream.parameters);
@@ -1999,7 +2070,7 @@ class SyncStatus {
1999
2070
  /**
2000
2071
  * Provides sync status information for all bucket priorities, sorted by priority (highest first).
2001
2072
  *
2002
- * @returns {SyncPriorityStatus[]} An array of status entries for different sync priority levels,
2073
+ * @returns An array of status entries for different sync priority levels,
2003
2074
  * sorted with highest priorities (lower numbers) first.
2004
2075
  */
2005
2076
  get priorityStatusEntries() {
@@ -2034,8 +2105,8 @@ class SyncStatus {
2034
2105
  * For example, if PowerSync just finished synchronizing buckets in priority level 3, calling this method
2035
2106
  * with a priority of 1 may return information for priority level 3.
2036
2107
  *
2037
- * @param {number} priority The bucket priority for which the status should be reported
2038
- * @returns {SyncPriorityStatus} Status information for the requested priority level or the next higher level with available status
2108
+ * @param priority - The bucket priority for which the status should be reported
2109
+ * @returns Status information for the requested priority level or the next higher level with available status
2039
2110
  */
2040
2111
  statusForPriority(priority) {
2041
2112
  // priorityStatusEntries are sorted by ascending priorities (so higher numbers to lower numbers).
@@ -2056,8 +2127,8 @@ class SyncStatus {
2056
2127
  * Compares this SyncStatus instance with another to determine if they are equal.
2057
2128
  * Equality is determined by comparing the serialized JSON representation of both instances.
2058
2129
  *
2059
- * @param {SyncStatus} status The SyncStatus instance to compare against
2060
- * @returns {boolean} True if the instances are considered equal, false otherwise
2130
+ * @param status - The SyncStatus instance to compare against
2131
+ * @returns True if the instances are considered equal, false otherwise
2061
2132
  */
2062
2133
  isEqual(status) {
2063
2134
  /**
@@ -2080,7 +2151,7 @@ class SyncStatus {
2080
2151
  * Creates a human-readable string representation of the current sync status.
2081
2152
  * Includes information about connection state, sync completion, and data flow.
2082
2153
  *
2083
- * @returns {string} A string representation of the sync status
2154
+ * @returns A string representation of the sync status
2084
2155
  */
2085
2156
  getMessage() {
2086
2157
  const dataFlow = this.dataFlowStatus;
@@ -2089,7 +2160,7 @@ class SyncStatus {
2089
2160
  /**
2090
2161
  * Serializes the SyncStatus instance to a plain object.
2091
2162
  *
2092
- * @returns {SyncStatusOptions} A plain object representation of the sync status
2163
+ * @returns A plain object representation of the sync status
2093
2164
  */
2094
2165
  toJSON() {
2095
2166
  return {
@@ -2155,6 +2226,9 @@ class SyncStreamStatusView {
2155
2226
  }
2156
2227
  }
2157
2228
 
2229
+ /**
2230
+ * @public
2231
+ */
2158
2232
  class UploadQueueStats {
2159
2233
  count;
2160
2234
  size;
@@ -2180,6 +2254,9 @@ class UploadQueueStats {
2180
2254
  }
2181
2255
  }
2182
2256
 
2257
+ /**
2258
+ * @internal
2259
+ */
2183
2260
  class BaseObserver {
2184
2261
  listeners = new Set();
2185
2262
  constructor() { }
@@ -2207,6 +2284,9 @@ class BaseObserver {
2207
2284
  }
2208
2285
  }
2209
2286
 
2287
+ /**
2288
+ * @internal
2289
+ */
2210
2290
  class ControlledExecutor {
2211
2291
  task;
2212
2292
  /**
@@ -2258,6 +2338,210 @@ class ControlledExecutor {
2258
2338
  }
2259
2339
  }
2260
2340
 
2341
+ /**
2342
+ * Some JavaScript engines, in particular older versions of React Native, don't support Symbol.asyncIterator.
2343
+ *
2344
+ * 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).
2345
+ * This definition is compatible with that polyfill, so transpiled apps can use async iterables created by the PowerSync
2346
+ * SDK.
2347
+ */
2348
+ const symbolAsyncIterator = Symbol.asyncIterator ?? Symbol.for('Symbol.asyncIterator');
2349
+
2350
+ const doneResult = { done: true, value: undefined };
2351
+ function valueResult(value) {
2352
+ return { done: false, value };
2353
+ }
2354
+ /**
2355
+ * Expands a source async iterator by allowing to inject events asynchronously.
2356
+ *
2357
+ * The resulting iterator will emit all events from its source. Additionally though, events can be injected. These
2358
+ * events are dropped once the main iterator completes, but are otherwise forwarded.
2359
+ *
2360
+ * The iterator completes when its source completes, and it supports backpressure by only calling `next()` on the source
2361
+ * in response to a `next()` call from downstream if no pending injected events can be dispatched.
2362
+ */
2363
+ function injectable(source) {
2364
+ let sourceIsDone = false;
2365
+ let waiter = undefined; // An active, waiting next() call.
2366
+ // A pending upstream event that couldn't be dispatched because inject() has been called before it was resolved.
2367
+ let pendingSourceEvent = null;
2368
+ let sourceFetchInFlight = false;
2369
+ let pendingInjectedEvents = [];
2370
+ const consumeWaiter = () => {
2371
+ const pending = waiter;
2372
+ waiter = undefined;
2373
+ return pending;
2374
+ };
2375
+ const fetchFromSource = () => {
2376
+ const resolveWaiter = (propagate) => {
2377
+ sourceFetchInFlight = false;
2378
+ const active = consumeWaiter();
2379
+ if (active) {
2380
+ propagate(active);
2381
+ }
2382
+ else {
2383
+ pendingSourceEvent = propagate;
2384
+ }
2385
+ };
2386
+ sourceFetchInFlight = true;
2387
+ const nextFromSource = source.next();
2388
+ nextFromSource.then((value) => {
2389
+ sourceIsDone = value.done == true;
2390
+ resolveWaiter((w) => w.resolve(value));
2391
+ }, (error) => {
2392
+ resolveWaiter((w) => w.reject(error));
2393
+ });
2394
+ };
2395
+ return {
2396
+ next: () => {
2397
+ return new Promise((resolve, reject) => {
2398
+ // First priority: Dispatch ready upstream events.
2399
+ if (sourceIsDone) {
2400
+ return resolve(doneResult);
2401
+ }
2402
+ if (pendingSourceEvent) {
2403
+ pendingSourceEvent({ resolve, reject });
2404
+ pendingSourceEvent = null;
2405
+ return;
2406
+ }
2407
+ // Second priority: Dispatch injected events
2408
+ if (pendingInjectedEvents.length) {
2409
+ return resolve(valueResult(pendingInjectedEvents.shift()));
2410
+ }
2411
+ // Nothing pending? Fetch from source
2412
+ waiter = { resolve, reject };
2413
+ if (!sourceFetchInFlight) {
2414
+ fetchFromSource();
2415
+ }
2416
+ });
2417
+ },
2418
+ inject: (event) => {
2419
+ const pending = consumeWaiter();
2420
+ if (pending != null) {
2421
+ pending.resolve(valueResult(event));
2422
+ }
2423
+ else {
2424
+ pendingInjectedEvents.push(event);
2425
+ }
2426
+ }
2427
+ };
2428
+ }
2429
+ /**
2430
+ * Splits a byte stream at line endings, emitting each line as a string.
2431
+ */
2432
+ function extractJsonLines(source, decoder) {
2433
+ let buffer = '';
2434
+ const pendingLines = [];
2435
+ let isFinalEvent = false;
2436
+ return {
2437
+ next: async () => {
2438
+ while (true) {
2439
+ if (isFinalEvent) {
2440
+ return doneResult;
2441
+ }
2442
+ {
2443
+ const first = pendingLines.shift();
2444
+ if (first) {
2445
+ return { done: false, value: first };
2446
+ }
2447
+ }
2448
+ const { done, value } = await source.next();
2449
+ if (done) {
2450
+ const remaining = buffer.trim();
2451
+ if (remaining.length != 0) {
2452
+ isFinalEvent = true;
2453
+ return { done: false, value: remaining };
2454
+ }
2455
+ return doneResult;
2456
+ }
2457
+ const data = decoder.decode(value, { stream: true });
2458
+ buffer += data;
2459
+ const lines = buffer.split('\n');
2460
+ for (let i = 0; i < lines.length - 1; i++) {
2461
+ const l = lines[i].trim();
2462
+ if (l.length > 0) {
2463
+ pendingLines.push(l);
2464
+ }
2465
+ }
2466
+ buffer = lines[lines.length - 1];
2467
+ }
2468
+ }
2469
+ };
2470
+ }
2471
+ /**
2472
+ * Splits a concatenated stream of BSON objects by emitting individual objects.
2473
+ */
2474
+ function extractBsonObjects(source) {
2475
+ // Fully read but not emitted yet.
2476
+ const completedObjects = [];
2477
+ // Whether source has returned { done: true }. We do the same once completed objects have been emitted.
2478
+ let isDone = false;
2479
+ const lengthBuffer = new DataView(new ArrayBuffer(4));
2480
+ let objectBody = null;
2481
+ // If we're parsing the length field, a number between 1 and 4 (inclusive) describing remaining bytes in the header.
2482
+ // If we're consuming a document, the bytes remaining.
2483
+ let remainingLength = 4;
2484
+ return {
2485
+ async next() {
2486
+ while (true) {
2487
+ // Before fetching new data from upstream, return completed objects.
2488
+ if (completedObjects.length) {
2489
+ return valueResult(completedObjects.shift());
2490
+ }
2491
+ if (isDone) {
2492
+ return doneResult;
2493
+ }
2494
+ const upstreamEvent = await source.next();
2495
+ if (upstreamEvent.done) {
2496
+ isDone = true;
2497
+ if (objectBody || remainingLength != 4) {
2498
+ throw new Error('illegal end of stream in BSON object');
2499
+ }
2500
+ return doneResult;
2501
+ }
2502
+ const chunk = upstreamEvent.value;
2503
+ for (let i = 0; i < chunk.length;) {
2504
+ const availableInData = chunk.length - i;
2505
+ if (objectBody) {
2506
+ // We're in the middle of reading a BSON document.
2507
+ const bytesToRead = Math.min(availableInData, remainingLength);
2508
+ const copySource = new Uint8Array(chunk.buffer, chunk.byteOffset + i, bytesToRead);
2509
+ objectBody.set(copySource, objectBody.length - remainingLength);
2510
+ i += bytesToRead;
2511
+ remainingLength -= bytesToRead;
2512
+ if (remainingLength == 0) {
2513
+ completedObjects.push(objectBody);
2514
+ // Prepare to read another document, starting with its length
2515
+ objectBody = null;
2516
+ remainingLength = 4;
2517
+ }
2518
+ }
2519
+ else {
2520
+ // Copy up to 4 bytes into lengthBuffer, depending on how many we still need.
2521
+ const bytesToRead = Math.min(availableInData, remainingLength);
2522
+ for (let j = 0; j < bytesToRead; j++) {
2523
+ lengthBuffer.setUint8(4 - remainingLength + j, chunk[i + j]);
2524
+ }
2525
+ i += bytesToRead;
2526
+ remainingLength -= bytesToRead;
2527
+ if (remainingLength == 0) {
2528
+ // Transition from reading length header to reading document. Subtracting 4 because the length of the
2529
+ // header is included in length.
2530
+ const length = lengthBuffer.getInt32(0, true /* little endian */);
2531
+ remainingLength = length - 4;
2532
+ if (remainingLength < 1) {
2533
+ throw new Error(`invalid length for bson: ${length}`);
2534
+ }
2535
+ objectBody = new Uint8Array(length);
2536
+ new DataView(objectBody.buffer).setInt32(0, length, true);
2537
+ }
2538
+ }
2539
+ }
2540
+ }
2541
+ }
2542
+ };
2543
+ }
2544
+
2261
2545
  /**
2262
2546
  * Throttle a function to be called at most once every "wait" milliseconds,
2263
2547
  * on the trailing edge.
@@ -2277,45 +2561,128 @@ function throttleTrailing(func, wait) {
2277
2561
  };
2278
2562
  }
2279
2563
  function asyncNotifier() {
2280
- let waitingConsumer = null;
2281
- let hasPendingNotification = false;
2564
+ const queue = new EventQueue();
2282
2565
  return {
2283
2566
  notify() {
2284
- if (waitingConsumer != null) {
2285
- waitingConsumer();
2286
- waitingConsumer = null;
2287
- }
2567
+ if (queue.countOutstandingEvents > 0) ;
2288
2568
  else {
2289
- hasPendingNotification = true;
2569
+ queue.notify();
2290
2570
  }
2291
2571
  },
2292
2572
  waitForNotification(signal) {
2293
- return new Promise((resolve) => {
2294
- if (waitingConsumer != null) {
2295
- throw new Error('Illegal call to waitForNotification, already has a waiter.');
2296
- }
2297
- if (signal.aborted) {
2298
- resolve();
2573
+ return queue.waitForEvent(signal);
2574
+ }
2575
+ };
2576
+ }
2577
+ class EventQueue {
2578
+ options;
2579
+ waitingConsumer;
2580
+ outstandingEvents;
2581
+ constructor(options = {}) {
2582
+ this.options = options;
2583
+ this.outstandingEvents = [];
2584
+ }
2585
+ /**
2586
+ * The amount of buffered events not yet dispatched to listeners.
2587
+ */
2588
+ get countOutstandingEvents() {
2589
+ return this.outstandingEvents.length;
2590
+ }
2591
+ notifyInner(dispatch) {
2592
+ const existing = this.waitingConsumer;
2593
+ this.waitingConsumer = undefined;
2594
+ const dispatchAndNotifyListeners = (waiter) => {
2595
+ dispatch(waiter);
2596
+ this.options.eventDelivered?.();
2597
+ };
2598
+ if (existing) {
2599
+ dispatchAndNotifyListeners(existing);
2600
+ }
2601
+ else {
2602
+ this.outstandingEvents.push(dispatchAndNotifyListeners);
2603
+ }
2604
+ }
2605
+ notify(value) {
2606
+ this.notifyInner((l) => l.resolve(value));
2607
+ }
2608
+ notifyError(error) {
2609
+ this.notifyInner((l) => l.reject(error));
2610
+ }
2611
+ waitForEvent(signal) {
2612
+ return new Promise((resolve, reject) => {
2613
+ if (this.waitingConsumer != null) {
2614
+ throw new Error('Illegal call to waitForEvent, already has a waiter.');
2615
+ }
2616
+ const complete = () => {
2617
+ signal?.removeEventListener('abort', onAbort);
2618
+ };
2619
+ const onAbort = () => {
2620
+ complete();
2621
+ this.waitingConsumer = undefined;
2622
+ resolve(undefined);
2623
+ };
2624
+ const waiter = {
2625
+ resolve: (value) => {
2626
+ complete();
2627
+ resolve(value);
2628
+ },
2629
+ reject: (error) => {
2630
+ complete();
2631
+ reject(error);
2299
2632
  }
2300
- else if (hasPendingNotification) {
2301
- resolve();
2302
- hasPendingNotification = false;
2633
+ };
2634
+ if (signal.aborted) {
2635
+ resolve(undefined);
2636
+ }
2637
+ else if (this.countOutstandingEvents > 0) {
2638
+ const [event] = this.outstandingEvents.splice(0, 1);
2639
+ event(waiter);
2640
+ }
2641
+ else {
2642
+ this.waitingConsumer = waiter;
2643
+ signal.addEventListener('abort', onAbort);
2644
+ }
2645
+ });
2646
+ }
2647
+ /**
2648
+ * Creates an async iterable backed by event queues.
2649
+ *
2650
+ * @param run A function invoked for every new listener. It receives a queue backing the async iterator.
2651
+ * @param abort An additional abort signal. The `run` callback will also be aborted when `AsyncIterator.return` is
2652
+ * called.
2653
+ * @returns An object conforming to the async iterable protocol.
2654
+ */
2655
+ static queueBasedAsyncIterable(run, abort) {
2656
+ return {
2657
+ [symbolAsyncIterator]: () => {
2658
+ const queue = new EventQueue();
2659
+ const controller = new AbortController();
2660
+ function dispose() {
2661
+ controller.abort();
2662
+ abort?.removeEventListener('abort', dispose);
2303
2663
  }
2304
- else {
2305
- function complete() {
2306
- signal.removeEventListener('abort', onAbort);
2307
- resolve();
2664
+ if (abort) {
2665
+ if (abort.aborted) {
2666
+ controller.abort();
2308
2667
  }
2309
- function onAbort() {
2310
- waitingConsumer = null;
2311
- resolve();
2668
+ else {
2669
+ abort.addEventListener('abort', dispose);
2312
2670
  }
2313
- waitingConsumer = complete;
2314
- signal.addEventListener('abort', onAbort);
2315
2671
  }
2316
- });
2317
- }
2318
- };
2672
+ run(queue, controller.signal);
2673
+ return {
2674
+ async next() {
2675
+ const event = await queue.waitForEvent(controller.signal);
2676
+ return event == null ? doneResult : valueResult(event);
2677
+ },
2678
+ async return() {
2679
+ dispose();
2680
+ return doneResult;
2681
+ }
2682
+ };
2683
+ }
2684
+ };
2685
+ }
2319
2686
  }
2320
2687
 
2321
2688
  /**
@@ -2474,7 +2841,7 @@ class ConnectionManager extends BaseObserver {
2474
2841
  /**
2475
2842
  * Close the sync connection.
2476
2843
  *
2477
- * Use {@link connect} to connect again.
2844
+ * Use {@link ConnectionManager.connect} to connect again.
2478
2845
  */
2479
2846
  async disconnect() {
2480
2847
  // This will help abort pending connects
@@ -2614,6 +2981,8 @@ const _finalizer = 'FinalizationRegistry' in globalThis
2614
2981
  /**
2615
2982
  * An efficient comparator for {@link WatchedQuery} created with {@link Query#watch}. This has the ability to determine if a query
2616
2983
  * result has changes without necessarily processing all items in the result.
2984
+ *
2985
+ * @public
2617
2986
  */
2618
2987
  class ArrayComparator {
2619
2988
  options;
@@ -2641,6 +3010,8 @@ class ArrayComparator {
2641
3010
  }
2642
3011
  /**
2643
3012
  * Watched query comparator that always reports changed result sets.
3013
+ *
3014
+ * @public
2644
3015
  */
2645
3016
  const FalsyComparator = {
2646
3017
  checkEquality: () => false // Default comparator that always returns false
@@ -2848,6 +3219,8 @@ class AbstractQueryProcessor extends MetaBaseObserver {
2848
3219
  /**
2849
3220
  * An empty differential result set.
2850
3221
  * This is used as the initial state for differential incrementally watched queries.
3222
+ *
3223
+ * @internal
2851
3224
  */
2852
3225
  const EMPTY_DIFFERENTIAL = {
2853
3226
  added: [],
@@ -2860,6 +3233,8 @@ const EMPTY_DIFFERENTIAL = {
2860
3233
  * Default implementation of the {@link DifferentialWatchedQueryComparator} for watched queries.
2861
3234
  * It keys items by their `id` property if available, alternatively it uses JSON stringification
2862
3235
  * of the entire item for the key and comparison.
3236
+ *
3237
+ * @internal
2863
3238
  */
2864
3239
  const DEFAULT_ROW_COMPARATOR = {
2865
3240
  keyBy: (item) => {
@@ -3140,6 +3515,8 @@ class CustomQuery {
3140
3515
 
3141
3516
  /**
3142
3517
  * Tests if the input is a {@link SQLOpenOptions}
3518
+ *
3519
+ * @internal
3143
3520
  */
3144
3521
  const isSQLOpenOptions = (test) => {
3145
3522
  // typeof null is `object`, but you cannot use the `in` operator on `null.
@@ -3147,17 +3524,24 @@ const isSQLOpenOptions = (test) => {
3147
3524
  };
3148
3525
  /**
3149
3526
  * Tests if input is a {@link SQLOpenFactory}
3527
+ *
3528
+ * @internal
3150
3529
  */
3151
3530
  const isSQLOpenFactory = (test) => {
3152
3531
  return typeof test?.openDB == 'function';
3153
3532
  };
3154
3533
  /**
3155
3534
  * Tests if input is a {@link DBAdapter}
3535
+ *
3536
+ * @internal
3156
3537
  */
3157
3538
  const isDBAdapter = (test) => {
3158
3539
  return typeof test?.writeTransaction == 'function';
3159
3540
  };
3160
3541
 
3542
+ /**
3543
+ * @internal
3544
+ */
3161
3545
  var PSInternalTable;
3162
3546
  (function (PSInternalTable) {
3163
3547
  PSInternalTable["DATA"] = "ps_data";
@@ -3166,6 +3550,9 @@ var PSInternalTable;
3166
3550
  PSInternalTable["OPLOG"] = "ps_oplog";
3167
3551
  PSInternalTable["UNTYPED"] = "ps_untyped";
3168
3552
  })(PSInternalTable || (PSInternalTable = {}));
3553
+ /**
3554
+ * @internal
3555
+ */
3169
3556
  var PowerSyncControlCommand;
3170
3557
  (function (PowerSyncControlCommand) {
3171
3558
  PowerSyncControlCommand["PROCESS_TEXT_LINE"] = "line_text";
@@ -3183,6 +3570,8 @@ var PowerSyncControlCommand;
3183
3570
 
3184
3571
  /**
3185
3572
  * A batch of client-side changes.
3573
+ *
3574
+ * @public
3186
3575
  */
3187
3576
  class CrudBatch {
3188
3577
  crud;
@@ -3209,6 +3598,8 @@ class CrudBatch {
3209
3598
 
3210
3599
  /**
3211
3600
  * Type of local change.
3601
+ *
3602
+ * @public
3212
3603
  */
3213
3604
  var UpdateType;
3214
3605
  (function (UpdateType) {
@@ -3221,6 +3612,8 @@ var UpdateType;
3221
3612
  })(UpdateType || (UpdateType = {}));
3222
3613
  /**
3223
3614
  * A single client-side change.
3615
+ *
3616
+ * @public
3224
3617
  */
3225
3618
  class CrudEntry {
3226
3619
  /**
@@ -3317,6 +3710,9 @@ class CrudEntry {
3317
3710
  }
3318
3711
  }
3319
3712
 
3713
+ /**
3714
+ * @public
3715
+ */
3320
3716
  class CrudTransaction extends CrudBatch {
3321
3717
  crud;
3322
3718
  complete;
@@ -3345,6 +3741,8 @@ class CrudTransaction extends CrudBatch {
3345
3741
  * Calls to Abortcontroller.abort(reason: any) will result in the
3346
3742
  * `reason` being thrown. This is not necessarily an error,
3347
3743
  * but extends error for better logging purposes.
3744
+ *
3745
+ * @internal
3348
3746
  */
3349
3747
  class AbortOperation extends Error {
3350
3748
  reason;
@@ -8144,7 +8542,7 @@ function requireDist () {
8144
8542
 
8145
8543
  var distExports = requireDist();
8146
8544
 
8147
- var version = "1.53.2";
8545
+ var version = "1.55.0";
8148
8546
  var PACKAGE = {
8149
8547
  version: version};
8150
8548
 
@@ -8220,289 +8618,95 @@ function requireWebsocketDuplexConnection () {
8220
8618
  get: function () {
8221
8619
  return this.done ? 0 : 1;
8222
8620
  },
8223
- enumerable: false,
8224
- configurable: true
8225
- });
8226
- WebsocketDuplexConnection.prototype.close = function (error) {
8227
- if (this.done) {
8228
- _super.prototype.close.call(this, error);
8229
- return;
8230
- }
8231
- this.websocket.removeEventListener("close", this.handleClosed);
8232
- this.websocket.removeEventListener("error", this.handleError);
8233
- this.websocket.removeEventListener("message", this.handleMessage);
8234
- this.websocket.close();
8235
- delete this.websocket;
8236
- _super.prototype.close.call(this, error);
8237
- };
8238
- WebsocketDuplexConnection.prototype.send = function (frame) {
8239
- if (this.done) {
8240
- return;
8241
- }
8242
- var buffer = (0, rsocket_core_1.serializeFrame)(frame);
8243
- this.websocket.send(buffer);
8244
- };
8245
- return WebsocketDuplexConnection;
8246
- }(rsocket_core_1.Deferred));
8247
- WebsocketDuplexConnection.WebsocketDuplexConnection = WebsocketDuplexConnection$1;
8248
-
8249
- return WebsocketDuplexConnection;
8250
- }
8251
-
8252
- var WebsocketDuplexConnectionExports = requireWebsocketDuplexConnection();
8253
-
8254
- /**
8255
- * Adapted from rsocket-websocket-client
8256
- * https://github.com/rsocket/rsocket-js/blob/e224cf379e747c4f1ddc4f2fa111854626cc8575/packages/rsocket-websocket-client/src/WebsocketClientTransport.ts#L17
8257
- * This adds additional error handling for React Native iOS.
8258
- * This particularly adds a close listener to handle cases where the WebSocket
8259
- * connection closes immediately after opening without emitting an error.
8260
- */
8261
- class WebsocketClientTransport {
8262
- url;
8263
- factory;
8264
- constructor(options) {
8265
- this.url = options.url;
8266
- this.factory = options.wsCreator ?? ((url) => new WebSocket(url));
8267
- }
8268
- connect(multiplexerDemultiplexerFactory) {
8269
- return new Promise((resolve, reject) => {
8270
- const websocket = this.factory(this.url);
8271
- websocket.binaryType = 'arraybuffer';
8272
- let removeListeners;
8273
- const openListener = () => {
8274
- removeListeners();
8275
- resolve(new WebsocketDuplexConnectionExports.WebsocketDuplexConnection(websocket, new distExports.Deserializer(), multiplexerDemultiplexerFactory));
8276
- };
8277
- const errorListener = (ev) => {
8278
- removeListeners();
8279
- // We add a default error in that case.
8280
- if (ev.error != null) {
8281
- // undici typically provides an error object
8282
- reject(ev.error);
8283
- }
8284
- else if (ev.message != null) {
8285
- // React Native typically does not provide an error object, but does provide a message
8286
- reject(new Error(`Failed to create websocket connection: ${ev.message}`));
8287
- }
8288
- else {
8289
- // Browsers often provide no details at all
8290
- reject(new Error(`Failed to create websocket connection to ${this.url}`));
8291
- }
8292
- };
8293
- /**
8294
- * In some cases, such as React Native iOS, the WebSocket connection may close immediately after opening
8295
- * without and error. In such cases, we need to handle the close event to reject the promise.
8296
- */
8297
- const closeListener = () => {
8298
- removeListeners();
8299
- reject(new Error('WebSocket connection closed while opening'));
8300
- };
8301
- removeListeners = () => {
8302
- websocket.removeEventListener('open', openListener);
8303
- websocket.removeEventListener('error', errorListener);
8304
- websocket.removeEventListener('close', closeListener);
8305
- };
8306
- websocket.addEventListener('open', openListener);
8307
- websocket.addEventListener('error', errorListener);
8308
- websocket.addEventListener('close', closeListener);
8309
- });
8310
- }
8311
- }
8312
-
8313
- const doneResult = { done: true, value: undefined };
8314
- function valueResult(value) {
8315
- return { done: false, value };
8316
- }
8317
- /**
8318
- * Expands a source async iterator by allowing to inject events asynchronously.
8319
- *
8320
- * The resulting iterator will emit all events from its source. Additionally though, events can be injected. These
8321
- * events are dropped once the main iterator completes, but are otherwise forwarded.
8322
- *
8323
- * The iterator completes when its source completes, and it supports backpressure by only calling `next()` on the source
8324
- * in response to a `next()` call from downstream if no pending injected events can be dispatched.
8325
- */
8326
- function injectable(source) {
8327
- let sourceIsDone = false;
8328
- let waiter = undefined; // An active, waiting next() call.
8329
- // A pending upstream event that couldn't be dispatched because inject() has been called before it was resolved.
8330
- let pendingSourceEvent = null;
8331
- let sourceFetchInFlight = false;
8332
- let pendingInjectedEvents = [];
8333
- const consumeWaiter = () => {
8334
- const pending = waiter;
8335
- waiter = undefined;
8336
- return pending;
8337
- };
8338
- const fetchFromSource = () => {
8339
- const resolveWaiter = (propagate) => {
8340
- sourceFetchInFlight = false;
8341
- const active = consumeWaiter();
8342
- if (active) {
8343
- propagate(active);
8344
- }
8345
- else {
8346
- pendingSourceEvent = propagate;
8347
- }
8348
- };
8349
- sourceFetchInFlight = true;
8350
- const nextFromSource = source.next();
8351
- nextFromSource.then((value) => {
8352
- sourceIsDone = value.done == true;
8353
- resolveWaiter((w) => w.resolve(value));
8354
- }, (error) => {
8355
- resolveWaiter((w) => w.reject(error));
8356
- });
8357
- };
8358
- return {
8359
- next: () => {
8360
- return new Promise((resolve, reject) => {
8361
- // First priority: Dispatch ready upstream events.
8362
- if (sourceIsDone) {
8363
- return resolve(doneResult);
8364
- }
8365
- if (pendingSourceEvent) {
8366
- pendingSourceEvent({ resolve, reject });
8367
- pendingSourceEvent = null;
8368
- return;
8369
- }
8370
- // Second priority: Dispatch injected events
8371
- if (pendingInjectedEvents.length) {
8372
- return resolve(valueResult(pendingInjectedEvents.shift()));
8373
- }
8374
- // Nothing pending? Fetch from source
8375
- waiter = { resolve, reject };
8376
- if (!sourceFetchInFlight) {
8377
- fetchFromSource();
8378
- }
8379
- });
8380
- },
8381
- inject: (event) => {
8382
- const pending = consumeWaiter();
8383
- if (pending != null) {
8384
- pending.resolve(valueResult(event));
8385
- }
8386
- else {
8387
- pendingInjectedEvents.push(event);
8388
- }
8389
- }
8390
- };
8391
- }
8392
- /**
8393
- * Splits a byte stream at line endings, emitting each line as a string.
8394
- */
8395
- function extractJsonLines(source, decoder) {
8396
- let buffer = '';
8397
- const pendingLines = [];
8398
- let isFinalEvent = false;
8399
- return {
8400
- next: async () => {
8401
- while (true) {
8402
- if (isFinalEvent) {
8403
- return doneResult;
8404
- }
8405
- {
8406
- const first = pendingLines.shift();
8407
- if (first) {
8408
- return { done: false, value: first };
8409
- }
8410
- }
8411
- const { done, value } = await source.next();
8412
- if (done) {
8413
- const remaining = buffer.trim();
8414
- if (remaining.length != 0) {
8415
- isFinalEvent = true;
8416
- return { done: false, value: remaining };
8417
- }
8418
- return doneResult;
8419
- }
8420
- const data = decoder.decode(value, { stream: true });
8421
- buffer += data;
8422
- const lines = buffer.split('\n');
8423
- for (let i = 0; i < lines.length - 1; i++) {
8424
- const l = lines[i].trim();
8425
- if (l.length > 0) {
8426
- pendingLines.push(l);
8427
- }
8428
- }
8429
- buffer = lines[lines.length - 1];
8430
- }
8431
- }
8432
- };
8621
+ enumerable: false,
8622
+ configurable: true
8623
+ });
8624
+ WebsocketDuplexConnection.prototype.close = function (error) {
8625
+ if (this.done) {
8626
+ _super.prototype.close.call(this, error);
8627
+ return;
8628
+ }
8629
+ this.websocket.removeEventListener("close", this.handleClosed);
8630
+ this.websocket.removeEventListener("error", this.handleError);
8631
+ this.websocket.removeEventListener("message", this.handleMessage);
8632
+ this.websocket.close();
8633
+ delete this.websocket;
8634
+ _super.prototype.close.call(this, error);
8635
+ };
8636
+ WebsocketDuplexConnection.prototype.send = function (frame) {
8637
+ if (this.done) {
8638
+ return;
8639
+ }
8640
+ var buffer = (0, rsocket_core_1.serializeFrame)(frame);
8641
+ this.websocket.send(buffer);
8642
+ };
8643
+ return WebsocketDuplexConnection;
8644
+ }(rsocket_core_1.Deferred));
8645
+ WebsocketDuplexConnection.WebsocketDuplexConnection = WebsocketDuplexConnection$1;
8646
+
8647
+ return WebsocketDuplexConnection;
8433
8648
  }
8649
+
8650
+ var WebsocketDuplexConnectionExports = requireWebsocketDuplexConnection();
8651
+
8434
8652
  /**
8435
- * Splits a concatenated stream of BSON objects by emitting individual objects.
8653
+ * Adapted from rsocket-websocket-client
8654
+ * https://github.com/rsocket/rsocket-js/blob/e224cf379e747c4f1ddc4f2fa111854626cc8575/packages/rsocket-websocket-client/src/WebsocketClientTransport.ts#L17
8655
+ * This adds additional error handling for React Native iOS.
8656
+ * This particularly adds a close listener to handle cases where the WebSocket
8657
+ * connection closes immediately after opening without emitting an error.
8436
8658
  */
8437
- function extractBsonObjects(source) {
8438
- // Fully read but not emitted yet.
8439
- const completedObjects = [];
8440
- // Whether source has returned { done: true }. We do the same once completed objects have been emitted.
8441
- let isDone = false;
8442
- const lengthBuffer = new DataView(new ArrayBuffer(4));
8443
- let objectBody = null;
8444
- // If we're parsing the length field, a number between 1 and 4 (inclusive) describing remaining bytes in the header.
8445
- // If we're consuming a document, the bytes remaining.
8446
- let remainingLength = 4;
8447
- return {
8448
- async next() {
8449
- while (true) {
8450
- // Before fetching new data from upstream, return completed objects.
8451
- if (completedObjects.length) {
8452
- return valueResult(completedObjects.shift());
8453
- }
8454
- if (isDone) {
8455
- return doneResult;
8659
+ class WebsocketClientTransport {
8660
+ url;
8661
+ factory;
8662
+ constructor(options) {
8663
+ this.url = options.url;
8664
+ this.factory = options.wsCreator ?? ((url) => new WebSocket(url));
8665
+ }
8666
+ connect(multiplexerDemultiplexerFactory) {
8667
+ return new Promise((resolve, reject) => {
8668
+ const websocket = this.factory(this.url);
8669
+ websocket.binaryType = 'arraybuffer';
8670
+ let removeListeners;
8671
+ const openListener = () => {
8672
+ removeListeners();
8673
+ resolve(new WebsocketDuplexConnectionExports.WebsocketDuplexConnection(websocket, new distExports.Deserializer(), multiplexerDemultiplexerFactory));
8674
+ };
8675
+ const errorListener = (event) => {
8676
+ const ev = event;
8677
+ removeListeners();
8678
+ // We add a default error in that case.
8679
+ if (ev.error != null) {
8680
+ // undici typically provides an error object
8681
+ reject(ev.error);
8456
8682
  }
8457
- const upstreamEvent = await source.next();
8458
- if (upstreamEvent.done) {
8459
- isDone = true;
8460
- if (objectBody || remainingLength != 4) {
8461
- throw new Error('illegal end of stream in BSON object');
8462
- }
8463
- return doneResult;
8683
+ else if (ev.message != null) {
8684
+ // React Native typically does not provide an error object, but does provide a message
8685
+ reject(new Error(`Failed to create websocket connection: ${ev.message}`));
8464
8686
  }
8465
- const chunk = upstreamEvent.value;
8466
- for (let i = 0; i < chunk.length;) {
8467
- const availableInData = chunk.length - i;
8468
- if (objectBody) {
8469
- // We're in the middle of reading a BSON document.
8470
- const bytesToRead = Math.min(availableInData, remainingLength);
8471
- const copySource = new Uint8Array(chunk.buffer, chunk.byteOffset + i, bytesToRead);
8472
- objectBody.set(copySource, objectBody.length - remainingLength);
8473
- i += bytesToRead;
8474
- remainingLength -= bytesToRead;
8475
- if (remainingLength == 0) {
8476
- completedObjects.push(objectBody);
8477
- // Prepare to read another document, starting with its length
8478
- objectBody = null;
8479
- remainingLength = 4;
8480
- }
8481
- }
8482
- else {
8483
- // Copy up to 4 bytes into lengthBuffer, depending on how many we still need.
8484
- const bytesToRead = Math.min(availableInData, remainingLength);
8485
- for (let j = 0; j < bytesToRead; j++) {
8486
- lengthBuffer.setUint8(4 - remainingLength + j, chunk[i + j]);
8487
- }
8488
- i += bytesToRead;
8489
- remainingLength -= bytesToRead;
8490
- if (remainingLength == 0) {
8491
- // Transition from reading length header to reading document. Subtracting 4 because the length of the
8492
- // header is included in length.
8493
- const length = lengthBuffer.getInt32(0, true /* little endian */);
8494
- remainingLength = length - 4;
8495
- if (remainingLength < 1) {
8496
- throw new Error(`invalid length for bson: ${length}`);
8497
- }
8498
- objectBody = new Uint8Array(length);
8499
- new DataView(objectBody.buffer).setInt32(0, length, true);
8500
- }
8501
- }
8687
+ else {
8688
+ // Browsers often provide no details at all
8689
+ reject(new Error(`Failed to create websocket connection to ${this.url}`));
8502
8690
  }
8503
- }
8504
- }
8505
- };
8691
+ };
8692
+ /**
8693
+ * In some cases, such as React Native iOS, the WebSocket connection may close immediately after opening
8694
+ * without and error. In such cases, we need to handle the close event to reject the promise.
8695
+ */
8696
+ const closeListener = () => {
8697
+ removeListeners();
8698
+ reject(new Error('WebSocket connection closed while opening'));
8699
+ };
8700
+ removeListeners = () => {
8701
+ websocket.removeEventListener('open', openListener);
8702
+ websocket.removeEventListener('error', errorListener);
8703
+ websocket.removeEventListener('close', closeListener);
8704
+ };
8705
+ websocket.addEventListener('open', openListener);
8706
+ websocket.addEventListener('error', errorListener);
8707
+ websocket.addEventListener('close', closeListener);
8708
+ });
8709
+ }
8506
8710
  }
8507
8711
 
8508
8712
  const POWERSYNC_TRAILING_SLASH_MATCH = /\/+$/;
@@ -8517,7 +8721,13 @@ const SOCKET_TIMEOUT_MS = 30_000;
8517
8721
  // If there is a backlog of messages (for example on slow connections), keepalive messages could be delayed
8518
8722
  // significantly. Therefore this is longer than the socket timeout.
8519
8723
  const KEEP_ALIVE_LIFETIME_MS = 90_000;
8724
+ /**
8725
+ * @internal
8726
+ */
8520
8727
  const DEFAULT_REMOTE_LOGGER = Logger.get('PowerSyncRemote');
8728
+ /**
8729
+ * @public
8730
+ */
8521
8731
  var FetchStrategy;
8522
8732
  (function (FetchStrategy) {
8523
8733
  /**
@@ -8536,12 +8746,17 @@ var FetchStrategy;
8536
8746
  * The class wrapper is used to distinguish the fetchImplementation
8537
8747
  * option in [AbstractRemoteOptions] from the general fetch method
8538
8748
  * which is typeof "function"
8749
+ *
8750
+ * @internal
8539
8751
  */
8540
8752
  class FetchImplementationProvider {
8541
8753
  getFetch() {
8542
8754
  throw new Error('Unspecified fetch implementation');
8543
8755
  }
8544
8756
  }
8757
+ /**
8758
+ * @internal
8759
+ */
8545
8760
  const DEFAULT_REMOTE_OPTIONS = {
8546
8761
  socketUrlTransformer: (url) => url.replace(/^https?:\/\//, function (match) {
8547
8762
  return match === 'https://' ? 'wss://' : 'ws://';
@@ -8549,6 +8764,9 @@ const DEFAULT_REMOTE_OPTIONS = {
8549
8764
  fetchImplementation: new FetchImplementationProvider(),
8550
8765
  fetchOptions: {}
8551
8766
  };
8767
+ /**
8768
+ * @internal
8769
+ */
8552
8770
  class AbstractRemote {
8553
8771
  connector;
8554
8772
  logger;
@@ -8709,8 +8927,19 @@ class AbstractRemote {
8709
8927
  let pendingSocket = null;
8710
8928
  let keepAliveTimeout;
8711
8929
  let rsocket = null;
8712
- let queue = null;
8930
+ let paused = false;
8931
+ const queue = new EventQueue({
8932
+ eventDelivered: () => {
8933
+ if (queue.countOutstandingEvents <= SYNC_QUEUE_REQUEST_LOW_WATER) {
8934
+ paused = false;
8935
+ requestMore();
8936
+ }
8937
+ }
8938
+ });
8713
8939
  let didClose = false;
8940
+ let connectionEstablished = false;
8941
+ let pendingEventsCount = syncQueueRequestSize;
8942
+ let res = null;
8714
8943
  const abortRequest = () => {
8715
8944
  if (didClose) {
8716
8945
  return;
@@ -8723,10 +8952,23 @@ class AbstractRemote {
8723
8952
  if (rsocket) {
8724
8953
  rsocket.close();
8725
8954
  }
8726
- if (queue) {
8727
- queue.stop();
8728
- }
8955
+ // Send a bogus event to the queue to ensure a pending listener gets woken up. We check for didClose and would
8956
+ // return a doneEvent.
8957
+ queue.notify(null);
8729
8958
  };
8959
+ function push(event) {
8960
+ queue.notify(event);
8961
+ if (queue.countOutstandingEvents >= SYNC_QUEUE_REQUEST_HIGH_WATER) {
8962
+ paused = true;
8963
+ }
8964
+ }
8965
+ function requestMore() {
8966
+ const delta = syncQueueRequestSize - pendingEventsCount;
8967
+ if (!paused && delta > 0) {
8968
+ res?.request(delta);
8969
+ pendingEventsCount = syncQueueRequestSize;
8970
+ }
8971
+ }
8730
8972
  // Handle upstream abort
8731
8973
  if (options.abortSignal.aborted) {
8732
8974
  throw new AbortOperation('Connection request aborted');
@@ -8781,25 +9023,19 @@ class AbstractRemote {
8781
9023
  // Helps to prevent double close scenarios
8782
9024
  rsocket.onClose(() => (rsocket = null));
8783
9025
  return await new Promise((resolve, reject) => {
8784
- let connectionEstablished = false;
8785
- let pendingEventsCount = syncQueueRequestSize;
8786
- let paused = false;
8787
- let res = null;
8788
- function requestMore() {
8789
- const delta = syncQueueRequestSize - pendingEventsCount;
8790
- if (!paused && delta > 0) {
8791
- res?.request(delta);
8792
- pendingEventsCount = syncQueueRequestSize;
9026
+ const queueAsIterator = {
9027
+ next: async () => {
9028
+ if (didClose)
9029
+ return doneResult;
9030
+ const notification = await queue.waitForEvent(options.abortSignal);
9031
+ if (didClose) {
9032
+ return doneResult;
9033
+ }
9034
+ else {
9035
+ return valueResult(notification);
9036
+ }
8793
9037
  }
8794
- }
8795
- const events = new EventIterator((q) => {
8796
- queue = q;
8797
- q.on('highWater', () => (paused = true));
8798
- q.on('lowWater', () => {
8799
- paused = false;
8800
- requestMore();
8801
- });
8802
- }, { highWaterMark: SYNC_QUEUE_REQUEST_HIGH_WATER, lowWaterMark: SYNC_QUEUE_REQUEST_LOW_WATER })[Symbol.asyncIterator]();
9038
+ };
8803
9039
  res = rsocket.requestStream({
8804
9040
  data: toBuffer(options.data),
8805
9041
  metadata: toBuffer({
@@ -8835,11 +9071,11 @@ class AbstractRemote {
8835
9071
  // The connection is active
8836
9072
  if (!connectionEstablished) {
8837
9073
  connectionEstablished = true;
8838
- resolve(events);
9074
+ resolve(queueAsIterator);
8839
9075
  }
8840
9076
  const { data } = payload;
8841
9077
  if (data) {
8842
- queue.push(data);
9078
+ push(data);
8843
9079
  }
8844
9080
  // Less events are now pending
8845
9081
  pendingEventsCount--;
@@ -8958,7 +9194,7 @@ class AbstractRemote {
8958
9194
  * Posts a `/sync/stream` request.
8959
9195
  *
8960
9196
  * Depending on the `Content-Type` of the response, this returns strings for sync lines or encoded BSON documents as
8961
- * {@link Uint8Array}s.
9197
+ * `Uint8Array`s.
8962
9198
  */
8963
9199
  async fetchStream(options) {
8964
9200
  const { isBson, stream } = await this.fetchStreamRaw(options);
@@ -9000,16 +9236,26 @@ function isInterruptingInstruction(instruction) {
9000
9236
  return 'EstablishSyncStream' in instruction || 'CloseSyncStream' in instruction;
9001
9237
  }
9002
9238
 
9239
+ /**
9240
+ * @internal
9241
+ */
9003
9242
  var LockType;
9004
9243
  (function (LockType) {
9005
9244
  LockType["CRUD"] = "crud";
9006
9245
  LockType["SYNC"] = "sync";
9007
9246
  })(LockType || (LockType = {}));
9247
+ /**
9248
+ * @public
9249
+ */
9008
9250
  var SyncStreamConnectionMethod;
9009
9251
  (function (SyncStreamConnectionMethod) {
9010
9252
  SyncStreamConnectionMethod["HTTP"] = "http";
9011
9253
  SyncStreamConnectionMethod["WEB_SOCKET"] = "web-socket";
9012
9254
  })(SyncStreamConnectionMethod || (SyncStreamConnectionMethod = {}));
9255
+ /**
9256
+ * @deprecated Deprecated since {@link SyncClientImplementation.RUST} is the only option.
9257
+ * @public
9258
+ */
9013
9259
  var SyncClientImplementation;
9014
9260
  (function (SyncClientImplementation) {
9015
9261
  /**
@@ -9021,8 +9267,8 @@ var SyncClientImplementation;
9021
9267
  * ## Compatibility warning
9022
9268
  *
9023
9269
  * The Rust sync client stores sync data in a format that is slightly different than the one used
9024
- * by the old JavaScript client. When adopting the {@link RUST} client on existing databases, the PowerSync SDK will
9025
- * migrate the format automatically.
9270
+ * by the old JavaScript client. When adopting the {@link SyncClientImplementation.RUST} client on existing databases,
9271
+ * the PowerSync SDK will migrate the format automatically.
9026
9272
  *
9027
9273
  * SDK versions supporting both the JavaScript and the Rust client support both formats with the JavaScript client
9028
9274
  * implementaiton. However, downgrading to an SDK version that only supports the JavaScript client would not be
@@ -9032,14 +9278,29 @@ var SyncClientImplementation;
9032
9278
  })(SyncClientImplementation || (SyncClientImplementation = {}));
9033
9279
  /**
9034
9280
  * The default {@link SyncClientImplementation} to use, {@link SyncClientImplementation.RUST}.
9281
+ *
9282
+ * @deprecated Deprecated since {@link SyncClientImplementation.RUST} is the only option.
9283
+ * @public
9035
9284
  */
9036
9285
  const DEFAULT_SYNC_CLIENT_IMPLEMENTATION = SyncClientImplementation.RUST;
9286
+ /**
9287
+ * @internal
9288
+ */
9037
9289
  const DEFAULT_CRUD_UPLOAD_THROTTLE_MS = 1000;
9290
+ /**
9291
+ * @internal
9292
+ */
9038
9293
  const DEFAULT_RETRY_DELAY_MS = 5000;
9294
+ /**
9295
+ * @internal
9296
+ */
9039
9297
  const DEFAULT_STREAMING_SYNC_OPTIONS = {
9040
9298
  retryDelayMs: DEFAULT_RETRY_DELAY_MS,
9041
9299
  crudUploadThrottleMs: DEFAULT_CRUD_UPLOAD_THROTTLE_MS
9042
9300
  };
9301
+ /**
9302
+ * @internal
9303
+ */
9043
9304
  const DEFAULT_STREAM_CONNECTION_OPTIONS = {
9044
9305
  appMetadata: {},
9045
9306
  connectionMethod: SyncStreamConnectionMethod.WEB_SOCKET,
@@ -9049,6 +9310,9 @@ const DEFAULT_STREAM_CONNECTION_OPTIONS = {
9049
9310
  serializedSchema: undefined,
9050
9311
  includeDefaultStreams: true
9051
9312
  };
9313
+ /**
9314
+ * @internal
9315
+ */
9052
9316
  class AbstractStreamingSyncImplementation extends BaseObserver {
9053
9317
  options;
9054
9318
  abortController;
@@ -9372,7 +9636,7 @@ The next upload iteration will be delayed.`);
9372
9636
  this.handleActiveStreamsChange?.();
9373
9637
  }
9374
9638
  /**
9375
- * Older versions of the JS SDK used to encode subkeys as JSON in {@link OplogEntry.toJSON}.
9639
+ * Older versions of the JS SDK used to encode subkeys as JSON in `OplogEntry.toJSON`.
9376
9640
  * Because subkeys are always strings, this leads to quotes being added around them in `ps_oplog`.
9377
9641
  * While this is not a problem as long as it's done consistently, it causes issues when a database
9378
9642
  * created by the JS SDK is used with other SDKs, or (more likely) when the new Rust sync client
@@ -9382,7 +9646,7 @@ The next upload iteration will be delayed.`);
9382
9646
  * migration is only triggered when necessary (for now). The function returns whether the new format
9383
9647
  * should be used, so that the JS SDK is able to write to updated databases.
9384
9648
  *
9385
- * @param requireFixedKeyFormat Whether we require the new format or also support the old one.
9649
+ * @param requireFixedKeyFormat - Whether we require the new format or also support the old one.
9386
9650
  * The Rust client requires the new subkey format.
9387
9651
  * @returns Whether the database is now using the new, fixed subkey format.
9388
9652
  */
@@ -9689,7 +9953,8 @@ const MEMORY_TRIGGER_CLAIM_MANAGER = {
9689
9953
 
9690
9954
  /**
9691
9955
  * SQLite operations to track changes for with {@link TriggerManager}
9692
- * @experimental
9956
+ *
9957
+ * @experimental @alpha
9693
9958
  */
9694
9959
  var DiffTriggerOperation;
9695
9960
  (function (DiffTriggerOperation) {
@@ -9751,8 +10016,8 @@ class TriggerManagerImpl {
9751
10016
  get db() {
9752
10017
  return this.options.db;
9753
10018
  }
9754
- async getUUID() {
9755
- const { id: uuid } = await this.db.get(/* sql */ `
10019
+ async getUUID(ctx) {
10020
+ const { id: uuid } = await (ctx ?? this.db).get(/* sql */ `
9756
10021
  SELECT
9757
10022
  uuid () as id
9758
10023
  `);
@@ -9865,7 +10130,7 @@ class TriggerManagerImpl {
9865
10130
  const replicatedColumns = columns ?? sourceDefinition.columns.map((col) => col.name);
9866
10131
  const internalSource = sourceDefinition.internalName;
9867
10132
  const triggerIds = [];
9868
- const id = await this.getUUID();
10133
+ const id = await this.getUUID(setupContext);
9869
10134
  const releaseStorageClaim = useStorage ? await this.options.claimManager.obtainClaim(id) : null;
9870
10135
  /**
9871
10136
  * We default to replicating all columns if no columns array is provided.
@@ -10105,18 +10370,29 @@ const POWERSYNC_TABLE_MATCH = /(^ps_data__|^ps_data_local__)/;
10105
10370
  const DEFAULT_DISCONNECT_CLEAR_OPTIONS = {
10106
10371
  clearLocal: true
10107
10372
  };
10373
+ /**
10374
+ * @internal
10375
+ */
10108
10376
  const DEFAULT_POWERSYNC_CLOSE_OPTIONS = {
10109
10377
  disconnect: true
10110
10378
  };
10379
+ /**
10380
+ * @internal
10381
+ */
10111
10382
  const DEFAULT_POWERSYNC_DB_OPTIONS = {
10112
10383
  retryDelayMs: 5000,
10113
10384
  crudUploadThrottleMs: DEFAULT_CRUD_UPLOAD_THROTTLE_MS
10114
10385
  };
10386
+ /**
10387
+ * @internal
10388
+ */
10115
10389
  const DEFAULT_CRUD_BATCH_LIMIT = 100;
10116
10390
  /**
10117
10391
  * Requesting nested or recursive locks can block the application in some circumstances.
10118
10392
  * This default lock timeout will act as a failsafe to throw an error if a lock cannot
10119
10393
  * be obtained.
10394
+ *
10395
+ * @internal
10120
10396
  */
10121
10397
  const DEFAULT_LOCK_TIMEOUT_MS = 120_000; // 2 mins
10122
10398
  /**
@@ -10126,6 +10402,9 @@ const DEFAULT_LOCK_TIMEOUT_MS = 120_000; // 2 mins
10126
10402
  const isPowerSyncDatabaseOptionsWithSettings = (test) => {
10127
10403
  return typeof test == 'object' && isSQLOpenOptions(test.database);
10128
10404
  };
10405
+ /**
10406
+ * @public
10407
+ */
10129
10408
  class AbstractPowerSyncDatabase extends BaseObserver {
10130
10409
  options;
10131
10410
  /**
@@ -10283,7 +10562,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
10283
10562
  /**
10284
10563
  * Wait for the first sync operation to complete.
10285
10564
  *
10286
- * @param request Either an abort signal (after which the promise will complete regardless of
10565
+ * @param request - Either an abort signal (after which the promise will complete regardless of
10287
10566
  * whether a full sync was completed) or an object providing an abort signal and a priority target.
10288
10567
  * When a priority target is set, the promise may complete when all buckets with the given (or higher)
10289
10568
  * priorities have been synchronized. This can be earlier than a complete sync.
@@ -10438,7 +10717,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
10438
10717
  /**
10439
10718
  * Close the sync connection.
10440
10719
  *
10441
- * Use {@link connect} to connect again.
10720
+ * Use {@link AbstractPowerSyncDatabase.connect} to connect again.
10442
10721
  */
10443
10722
  async disconnect() {
10444
10723
  return this.connectionManager.disconnect();
@@ -10465,8 +10744,8 @@ class AbstractPowerSyncDatabase extends BaseObserver {
10465
10744
  /**
10466
10745
  * Create a sync stream to query its status or to subscribe to it.
10467
10746
  *
10468
- * @param name The name of the stream to subscribe to.
10469
- * @param params Optional parameters for the stream subscription.
10747
+ * @param name - The name of the stream to subscribe to.
10748
+ * @param params - Optional parameters for the stream subscription.
10470
10749
  * @returns A {@link SyncStream} instance that can be subscribed to.
10471
10750
  * @experimental Sync streams are currently in alpha.
10472
10751
  */
@@ -10524,14 +10803,14 @@ class AbstractPowerSyncDatabase extends BaseObserver {
10524
10803
  * Once the data have been successfully uploaded, call {@link CrudBatch.complete} before
10525
10804
  * requesting the next batch.
10526
10805
  *
10527
- * Use {@link limit} to specify the maximum number of updates to return in a single
10806
+ * Use the `limit` parameter to specify the maximum number of updates to return in a single
10528
10807
  * batch.
10529
10808
  *
10530
10809
  * This method does include transaction ids in the result, but does not group
10531
10810
  * data by transaction. One batch may contain data from multiple transactions,
10532
10811
  * and a single transaction may be split over multiple batches.
10533
10812
  *
10534
- * @param limit Maximum number of CRUD entries to include in the batch
10813
+ * @param limit - Maximum number of CRUD entries to include in the batch
10535
10814
  * @returns A batch of CRUD operations to upload, or null if there are none
10536
10815
  */
10537
10816
  async getCrudBatch(limit = DEFAULT_CRUD_BATCH_LIMIT) {
@@ -10558,13 +10837,13 @@ class AbstractPowerSyncDatabase extends BaseObserver {
10558
10837
  * Once the data have been successfully uploaded, call {@link CrudTransaction.complete} before
10559
10838
  * requesting the next transaction.
10560
10839
  *
10561
- * Unlike {@link getCrudBatch}, this only returns data from a single transaction at a time.
10840
+ * Unlike {@link AbstractPowerSyncDatabase.getCrudBatch}, this only returns data from a single transaction at a time.
10562
10841
  * All data for the transaction is loaded into memory.
10563
10842
  *
10564
10843
  * @returns A transaction of CRUD operations to upload, or null if there are none
10565
10844
  */
10566
10845
  async getNextCrudTransaction() {
10567
- const iterator = this.getCrudTransactions()[Symbol.asyncIterator]();
10846
+ const iterator = this.getCrudTransactions()[symbolAsyncIterator]();
10568
10847
  return (await iterator.next()).value;
10569
10848
  }
10570
10849
  /**
@@ -10573,7 +10852,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
10573
10852
  * This is typically used from the {@link PowerSyncBackendConnector.uploadData} callback. Each entry emitted by the
10574
10853
  * returned iterator is a full transaction containing all local writes made while that transaction was active.
10575
10854
  *
10576
- * Unlike {@link getNextCrudTransaction}, which always returns the oldest transaction that hasn't been
10855
+ * Unlike {@link AbstractPowerSyncDatabase.getNextCrudTransaction}, which always returns the oldest transaction that hasn't been
10577
10856
  * {@link CrudTransaction.complete}d yet, this iterator can be used to receive multiple transactions. Calling
10578
10857
  * {@link CrudTransaction.complete} will mark that and all prior transactions emitted by the iterator as completed.
10579
10858
  *
@@ -10600,7 +10879,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
10600
10879
  */
10601
10880
  getCrudTransactions() {
10602
10881
  return {
10603
- [Symbol.asyncIterator]: () => {
10882
+ [symbolAsyncIterator]: () => {
10604
10883
  let lastCrudItemId = -1;
10605
10884
  const sql = `
10606
10885
  WITH RECURSIVE crud_entries AS (
@@ -10667,8 +10946,8 @@ SELECT * FROM crud_entries;
10667
10946
  * the returned result's `rowsAffected` may be `0` for successful `UPDATE` and `DELETE` statements.
10668
10947
  * Use a `RETURNING` clause and inspect `result.rows` when you need to confirm which rows changed.
10669
10948
  *
10670
- * @param sql The SQL query to execute
10671
- * @param parameters Optional array of parameters to bind to the query
10949
+ * @param sql - The SQL query to execute
10950
+ * @param parameters - Optional array of parameters to bind to the query
10672
10951
  * @returns The query result as an object with structured key-value pairs
10673
10952
  */
10674
10953
  async execute(sql, parameters) {
@@ -10678,8 +10957,8 @@ SELECT * FROM crud_entries;
10678
10957
  * Execute a SQL write (INSERT/UPDATE/DELETE) query directly on the database without any PowerSync processing.
10679
10958
  * This bypasses certain PowerSync abstractions and is useful for accessing the raw database results.
10680
10959
  *
10681
- * @param sql The SQL query to execute
10682
- * @param parameters Optional array of parameters to bind to the query
10960
+ * @param sql - The SQL query to execute
10961
+ * @param parameters - Optional array of parameters to bind to the query
10683
10962
  * @returns The raw query result from the underlying database as a nested array of raw values, where each row is
10684
10963
  * represented as an array of column values without field names.
10685
10964
  */
@@ -10692,8 +10971,8 @@ SELECT * FROM crud_entries;
10692
10971
  * and optionally return results.
10693
10972
  * This is faster than executing separately with each parameter set.
10694
10973
  *
10695
- * @param sql The SQL query to execute
10696
- * @param parameters Optional 2D array of parameter sets, where each inner array is a set of parameters for one execution
10974
+ * @param sql - The SQL query to execute
10975
+ * @param parameters - Optional 2D array of parameter sets, where each inner array is a set of parameters for one execution
10697
10976
  * @returns The query result
10698
10977
  */
10699
10978
  async executeBatch(sql, parameters) {
@@ -10703,8 +10982,8 @@ SELECT * FROM crud_entries;
10703
10982
  /**
10704
10983
  * Execute a read-only query and return results.
10705
10984
  *
10706
- * @param sql The SQL query to execute
10707
- * @param parameters Optional array of parameters to bind to the query
10985
+ * @param sql - The SQL query to execute
10986
+ * @param parameters - Optional array of parameters to bind to the query
10708
10987
  * @returns An array of results
10709
10988
  */
10710
10989
  async getAll(sql, parameters) {
@@ -10714,8 +10993,8 @@ SELECT * FROM crud_entries;
10714
10993
  /**
10715
10994
  * Execute a read-only query and return the first result, or null if the ResultSet is empty.
10716
10995
  *
10717
- * @param sql The SQL query to execute
10718
- * @param parameters Optional array of parameters to bind to the query
10996
+ * @param sql - The SQL query to execute
10997
+ * @param parameters - Optional array of parameters to bind to the query
10719
10998
  * @returns The first result if found, or null if no results are returned
10720
10999
  */
10721
11000
  async getOptional(sql, parameters) {
@@ -10725,8 +11004,8 @@ SELECT * FROM crud_entries;
10725
11004
  /**
10726
11005
  * Execute a read-only query and return the first result, error if the ResultSet is empty.
10727
11006
  *
10728
- * @param sql The SQL query to execute
10729
- * @param parameters Optional array of parameters to bind to the query
11007
+ * @param sql - The SQL query to execute
11008
+ * @param parameters - Optional array of parameters to bind to the query
10730
11009
  * @returns The first result matching the query
10731
11010
  * @throws Error if no rows are returned
10732
11011
  */
@@ -10736,7 +11015,7 @@ SELECT * FROM crud_entries;
10736
11015
  }
10737
11016
  /**
10738
11017
  * Takes a read lock, without starting a transaction.
10739
- * In most cases, {@link readTransaction} should be used instead.
11018
+ * In most cases, {@link AbstractPowerSyncDatabase.readTransaction} should be used instead.
10740
11019
  */
10741
11020
  async readLock(callback) {
10742
11021
  await this.waitForReady();
@@ -10744,7 +11023,7 @@ SELECT * FROM crud_entries;
10744
11023
  }
10745
11024
  /**
10746
11025
  * Takes a global lock, without starting a transaction.
10747
- * In most cases, {@link writeTransaction} should be used instead.
11026
+ * In most cases, {@link AbstractPowerSyncDatabase.writeTransaction} should be used instead.
10748
11027
  */
10749
11028
  async writeLock(callback) {
10750
11029
  await this.waitForReady();
@@ -10755,8 +11034,8 @@ SELECT * FROM crud_entries;
10755
11034
  * Read transactions can run concurrently to a write transaction.
10756
11035
  * Changes from any write transaction are not visible to read transactions started before it.
10757
11036
  *
10758
- * @param callback Function to execute within the transaction
10759
- * @param lockTimeout Time in milliseconds to wait for a lock before throwing an error
11037
+ * @param callback - Function to execute within the transaction
11038
+ * @param lockTimeout - Time in milliseconds to wait for a lock before throwing an error
10760
11039
  * @returns The result of the callback
10761
11040
  * @throws Error if the lock cannot be obtained within the timeout period
10762
11041
  */
@@ -10773,8 +11052,8 @@ SELECT * FROM crud_entries;
10773
11052
  * This takes a global lock - only one write transaction can execute against the database at a time.
10774
11053
  * Statements within the transaction must be done on the provided {@link Transaction} interface.
10775
11054
  *
10776
- * @param callback Function to execute within the transaction
10777
- * @param lockTimeout Time in milliseconds to wait for a lock before throwing an error
11055
+ * @param callback - Function to execute within the transaction
11056
+ * @param lockTimeout - Time in milliseconds to wait for a lock before throwing an error
10778
11057
  * @returns The result of the callback
10779
11058
  * @throws Error if the lock cannot be obtained within the timeout period
10780
11059
  */
@@ -10851,15 +11130,15 @@ SELECT * FROM crud_entries;
10851
11130
  }
10852
11131
  /**
10853
11132
  * Execute a read query every time the source tables are modified.
10854
- * Use {@link SQLWatchOptions.throttleMs} to specify the minimum interval between queries.
11133
+ * Use {@link SQLOnChangeOptions.throttleMs} to specify the minimum interval between queries.
10855
11134
  * Source tables are automatically detected using `EXPLAIN QUERY PLAN`.
10856
11135
  *
10857
11136
  * Note that the `onChange` callback member of the handler is required.
10858
11137
  *
10859
- * @param sql The SQL query to execute
10860
- * @param parameters Optional array of parameters to bind to the query
10861
- * @param handler Callbacks for handling results and errors
10862
- * @param options Options for configuring watch behavior
11138
+ * @param sql - The SQL query to execute
11139
+ * @param parameters - Optional array of parameters to bind to the query
11140
+ * @param handler - Callbacks for handling results and errors
11141
+ * @param options - Options for configuring watch behavior
10863
11142
  */
10864
11143
  watchWithCallback(sql, parameters, handler, options) {
10865
11144
  const { onResult, onError = (e) => this.logger.error(e) } = handler ?? {};
@@ -10872,7 +11151,7 @@ SELECT * FROM crud_entries;
10872
11151
  const watchedQuery = new OnChangeQueryProcessor({
10873
11152
  db: this,
10874
11153
  comparator,
10875
- placeholderData: null,
11154
+ placeholderData: null, // FIXME
10876
11155
  watchOptions: {
10877
11156
  query: {
10878
11157
  compile: () => ({
@@ -10905,38 +11184,35 @@ SELECT * FROM crud_entries;
10905
11184
  }
10906
11185
  /**
10907
11186
  * Execute a read query every time the source tables are modified.
10908
- * Use {@link SQLWatchOptions.throttleMs} to specify the minimum interval between queries.
11187
+ * Use {@link SQLOnChangeOptions.throttleMs} to specify the minimum interval between queries.
10909
11188
  * Source tables are automatically detected using `EXPLAIN QUERY PLAN`.
10910
11189
  *
10911
- * @param sql The SQL query to execute
10912
- * @param parameters Optional array of parameters to bind to the query
10913
- * @param options Options for configuring watch behavior
11190
+ * @param sql - The SQL query to execute
11191
+ * @param parameters - Optional array of parameters to bind to the query
11192
+ * @param options - Options for configuring watch behavior
10914
11193
  * @returns An AsyncIterable that yields QueryResults whenever the data changes
10915
11194
  */
10916
11195
  watchWithAsyncGenerator(sql, parameters, options) {
10917
- return new EventIterator((eventOptions) => {
11196
+ return EventQueue.queueBasedAsyncIterable((queue, abort) => {
10918
11197
  const handler = {
10919
11198
  onResult: (result) => {
10920
- eventOptions.push(result);
11199
+ queue.notify(result);
10921
11200
  },
10922
11201
  onError: (error) => {
10923
- eventOptions.fail(error);
11202
+ queue.notifyError(error);
10924
11203
  }
10925
11204
  };
10926
- this.watchWithCallback(sql, parameters, handler, options);
10927
- options?.signal?.addEventListener('abort', () => {
10928
- eventOptions.stop();
10929
- });
10930
- });
11205
+ this.watchWithCallback(sql, parameters, handler, { ...options, signal: abort });
11206
+ }, options?.signal);
10931
11207
  }
10932
11208
  /**
10933
11209
  * Resolves the list of tables that are used in a SQL query.
10934
11210
  * If tables are specified in the options, those are used directly.
10935
11211
  * Otherwise, analyzes the query using EXPLAIN to determine which tables are accessed.
10936
11212
  *
10937
- * @param sql The SQL query to analyze
10938
- * @param parameters Optional parameters for the SQL query
10939
- * @param options Optional watch options that may contain explicit table list
11213
+ * @param sql - The SQL query to analyze
11214
+ * @param parameters - Optional parameters for the SQL query
11215
+ * @param options - Optional watch options that may contain explicit table list
10940
11216
  * @returns Array of table names that the query depends on
10941
11217
  */
10942
11218
  async resolveTables(sql, parameters, options) {
@@ -10965,13 +11241,13 @@ SELECT * FROM crud_entries;
10965
11241
  /**
10966
11242
  * Invoke the provided callback on any changes to any of the specified tables.
10967
11243
  *
10968
- * This is preferred over {@link watchWithCallback} when multiple queries need to be performed
11244
+ * This is preferred over {@link AbstractPowerSyncDatabase.watchWithCallback} when multiple queries need to be performed
10969
11245
  * together when data is changed.
10970
11246
  *
10971
11247
  * Note that the `onChange` callback member of the handler is required.
10972
11248
  *
10973
- * @param handler Callbacks for handling change events and errors
10974
- * @param options Options for configuring watch behavior
11249
+ * @param handler - Callbacks for handling change events and errors
11250
+ * @param options - Options for configuring watch behavior
10975
11251
  * @returns A dispose function to stop watching for changes
10976
11252
  */
10977
11253
  onChangeWithCallback(handler, options) {
@@ -11014,31 +11290,26 @@ SELECT * FROM crud_entries;
11014
11290
  /**
11015
11291
  * Create a Stream of changes to any of the specified tables.
11016
11292
  *
11017
- * This is preferred over {@link watchWithAsyncGenerator} when multiple queries need to be performed
11018
- * together when data is changed.
11019
- *
11020
- * Note: do not declare this as `async *onChange` as it will not work in React Native.
11293
+ * This is preferred over {@link AbstractPowerSyncDatabase.watchWithAsyncGenerator} when multiple queries need to be
11294
+ * performed together when data is changed.
11021
11295
  *
11022
- * @param options Options for configuring watch behavior
11296
+ * @param options - Options for configuring watch behavior
11023
11297
  * @returns An AsyncIterable that yields change events whenever the specified tables change
11024
11298
  */
11299
+ // Note: do not declare this as `async *onChange` as it will not work in React Native.
11025
11300
  onChangeWithAsyncGenerator(options) {
11026
- const resolvedOptions = options ?? {};
11027
- return new EventIterator((eventOptions) => {
11028
- const dispose = this.onChangeWithCallback({
11301
+ return EventQueue.queueBasedAsyncIterable((queue, abort) => {
11302
+ this.onChangeWithCallback({
11029
11303
  onChange: (event) => {
11030
- eventOptions.push(event);
11304
+ queue.notify(event);
11031
11305
  },
11032
11306
  onError: (error) => {
11033
- eventOptions.fail(error);
11307
+ queue.notifyError(error);
11034
11308
  }
11035
- }, options);
11036
- resolvedOptions.signal?.addEventListener('abort', () => {
11037
- eventOptions.stop();
11038
- // Maybe fail?
11039
- });
11040
- return () => dispose();
11041
- });
11309
+ }, { ...options, signal: abort });
11310
+ // Note: We don't have to track the dispose function returned by onChangeWithCallback, it cleans up
11311
+ // after the abort signal completes.
11312
+ }, options?.signal);
11042
11313
  }
11043
11314
  handleTableChanges(changedTables, watchedTables, onDetectedChanges) {
11044
11315
  if (changedTables.size > 0) {
@@ -11057,15 +11328,15 @@ SELECT * FROM crud_entries;
11057
11328
  changedTables.add(table);
11058
11329
  }
11059
11330
  }
11060
- /**
11061
- * @ignore
11062
- */
11063
11331
  async executeReadOnly(sql, params) {
11064
11332
  await this.waitForReady();
11065
11333
  return this.database.readLock((tx) => tx.execute(sql, params));
11066
11334
  }
11067
11335
  }
11068
11336
 
11337
+ /**
11338
+ * @internal
11339
+ */
11069
11340
  class AbstractPowerSyncDatabaseOpenFactory {
11070
11341
  options;
11071
11342
  constructor(options) {
@@ -11090,6 +11361,9 @@ class AbstractPowerSyncDatabaseOpenFactory {
11090
11361
  }
11091
11362
  }
11092
11363
 
11364
+ /**
11365
+ * @internal
11366
+ */
11093
11367
  function runOnSchemaChange(callback, db, options) {
11094
11368
  const triggerWatchedQuery = () => {
11095
11369
  const abortController = new AbortController();
@@ -11114,6 +11388,9 @@ function runOnSchemaChange(callback, db, options) {
11114
11388
  triggerWatchedQuery();
11115
11389
  }
11116
11390
 
11391
+ /**
11392
+ * @public
11393
+ */
11117
11394
  function compilableQueryWatch(db, query, handler, options) {
11118
11395
  const { onResult, onError = (e) => { } } = handler ?? {};
11119
11396
  if (!onResult) {
@@ -11151,8 +11428,14 @@ function compilableQueryWatch(db, query, handler, options) {
11151
11428
  runOnSchemaChange(watchQuery, db, options);
11152
11429
  }
11153
11430
 
11431
+ /**
11432
+ * @internal
11433
+ */
11154
11434
  const MAX_OP_ID = '9223372036854775807';
11155
11435
 
11436
+ /**
11437
+ * @internal
11438
+ */
11156
11439
  class SqliteBucketStorage extends BaseObserver {
11157
11440
  db;
11158
11441
  logger;
@@ -11313,6 +11596,8 @@ class SqliteBucketStorage extends BaseObserver {
11313
11596
  * Thrown when an underlying database connection is closed.
11314
11597
  * This is particularly relevant when worker connections are marked as closed while
11315
11598
  * operations are still in progress.
11599
+ *
11600
+ * @internal
11316
11601
  */
11317
11602
  class ConnectionClosedError extends Error {
11318
11603
  static NAME = 'ConnectionClosedError';
@@ -11332,6 +11617,8 @@ class ConnectionClosedError extends Error {
11332
11617
 
11333
11618
  /**
11334
11619
  * A schema is a collection of tables. It is used to define the structure of a database.
11620
+ *
11621
+ * @public
11335
11622
  */
11336
11623
  class Schema {
11337
11624
  /*
@@ -11368,7 +11655,7 @@ class Schema {
11368
11655
  * Since raw tables are not backed by JSON, running complex queries on them may be more efficient. Further, they allow
11369
11656
  * using client-side table and column constraints.
11370
11657
  *
11371
- * @param tables An object of (table name, raw table definition) entries.
11658
+ * @param tables - An object of (table name, raw table definition) entries.
11372
11659
  */
11373
11660
  withRawTables(tables) {
11374
11661
  for (const [name, rawTableDefinition] of Object.entries(tables)) {
@@ -11414,6 +11701,8 @@ class Schema {
11414
11701
  Generate a new table from the columns and indexes
11415
11702
  @deprecated You should use {@link Table} instead as it now allows TableV2 syntax.
11416
11703
  This will be removed in the next major release.
11704
+
11705
+ @public
11417
11706
  */
11418
11707
  class TableV2 extends Table {
11419
11708
  }
@@ -11424,6 +11713,8 @@ function sanitizeString(input) {
11424
11713
  /**
11425
11714
  * Helper function for sanitizing UUID input strings.
11426
11715
  * Typically used with {@link sanitizeSQL}.
11716
+ *
11717
+ * @alpha
11427
11718
  */
11428
11719
  function sanitizeUUID(uuid) {
11429
11720
  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;
@@ -11460,6 +11751,8 @@ function sanitizeUUID(uuid) {
11460
11751
  * // Incorrect:
11461
11752
  * sanitizeSQL`New.id = '${myID}'` // Produces double quotes: New.id = ''O''Reilly''
11462
11753
  * ```
11754
+ *
11755
+ * @alpha
11463
11756
  */
11464
11757
  function sanitizeSQL(strings, ...values) {
11465
11758
  let result = '';
@@ -11489,6 +11782,8 @@ function sanitizeSQL(strings, ...values) {
11489
11782
 
11490
11783
  /**
11491
11784
  * Performs a {@link AbstractPowerSyncDatabase.getAll} operation for a watched query.
11785
+ *
11786
+ * @public
11492
11787
  */
11493
11788
  class GetAllQuery {
11494
11789
  options;
@@ -11513,6 +11808,9 @@ class GetAllQuery {
11513
11808
  }
11514
11809
 
11515
11810
  const TypedLogger = Logger;
11811
+ /**
11812
+ * @public
11813
+ */
11516
11814
  const LogLevel = {
11517
11815
  TRACE: TypedLogger.TRACE,
11518
11816
  DEBUG: TypedLogger.DEBUG,
@@ -11529,6 +11827,7 @@ const LogLevel = {
11529
11827
  * across all loggers created with `createLogger`. Adjusting settings on this
11530
11828
  * base logger affects all loggers derived from it unless explicitly overridden.
11531
11829
  *
11830
+ * @public
11532
11831
  */
11533
11832
  function createBaseLogger() {
11534
11833
  return Logger;
@@ -11539,6 +11838,8 @@ function createBaseLogger() {
11539
11838
  * Named loggers allow specific modules or areas of your application to have
11540
11839
  * their own logging levels and behaviors. These loggers inherit configuration
11541
11840
  * from the base logger by default but can override settings independently.
11841
+ *
11842
+ * @public
11542
11843
  */
11543
11844
  function createLogger(name, options = {}) {
11544
11845
  const logger = Logger.get(name);
@@ -11548,6 +11849,9 @@ function createLogger(name, options = {}) {
11548
11849
  return logger;
11549
11850
  }
11550
11851
 
11852
+ /**
11853
+ * @internal
11854
+ */
11551
11855
  const parseQuery = (query, parameters) => {
11552
11856
  let sqlStatement;
11553
11857
  if (typeof query == 'string') {