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