@powersync/common 1.53.1 → 1.54.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 (217) hide show
  1. package/dist/bundle.cjs +560 -353
  2. package/dist/bundle.cjs.map +1 -1
  3. package/dist/bundle.mjs +560 -353
  4. package/dist/bundle.mjs.map +1 -1
  5. package/dist/bundle.node.cjs +560 -353
  6. package/dist/bundle.node.cjs.map +1 -1
  7. package/dist/bundle.node.mjs +560 -353
  8. package/dist/bundle.node.mjs.map +1 -1
  9. package/dist/index.d.cts +733 -198
  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 +61 -20
  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 -58
  26. package/lib/client/AbstractPowerSyncDatabase.js +59 -48
  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 +15 -1
  66. package/lib/client/sync/stream/AbstractRemote.js.map +1 -1
  67. package/lib/client/sync/stream/AbstractStreamingSyncImplementation.d.ts +61 -14
  68. package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +60 -47
  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 +13 -7
  148. package/lib/utils/async.js +38 -24
  149. package/lib/utils/async.js.map +1 -1
  150. package/lib/utils/mutex.d.ts +8 -0
  151. package/lib/utils/mutex.js +3 -0
  152. package/lib/utils/mutex.js.map +1 -1
  153. package/lib/utils/parseQuery.d.ts +6 -0
  154. package/lib/utils/parseQuery.js +3 -0
  155. package/lib/utils/parseQuery.js.map +1 -1
  156. package/lib/utils/stream_transform.d.ts +3 -1
  157. package/lib/utils/stream_transform.js.map +1 -1
  158. package/package.json +3 -2
  159. package/src/attachments/AttachmentContext.ts +7 -6
  160. package/src/attachments/AttachmentErrorHandler.ts +6 -6
  161. package/src/attachments/AttachmentQueue.ts +71 -23
  162. package/src/attachments/LocalStorageAdapter.ts +14 -8
  163. package/src/attachments/README.md +2 -0
  164. package/src/attachments/RemoteStorageAdapter.ts +4 -4
  165. package/src/attachments/Schema.ts +12 -4
  166. package/src/attachments/WatchedAttachmentItem.ts +3 -1
  167. package/src/client/AbstractPowerSyncDatabase.ts +117 -62
  168. package/src/client/AbstractPowerSyncOpenFactory.ts +6 -0
  169. package/src/client/ConnectionManager.ts +4 -1
  170. package/src/client/Query.ts +9 -0
  171. package/src/client/SQLOpenFactory.ts +12 -0
  172. package/src/client/compilableQueryWatch.ts +6 -0
  173. package/src/client/connection/PowerSyncBackendConnector.ts +3 -0
  174. package/src/client/connection/PowerSyncCredentials.ts +3 -0
  175. package/src/client/constants.ts +3 -0
  176. package/src/client/runOnSchemaChange.ts +3 -0
  177. package/src/client/sync/bucket/BucketStorageAdapter.ts +12 -0
  178. package/src/client/sync/bucket/CrudBatch.ts +2 -0
  179. package/src/client/sync/bucket/CrudEntry.ts +9 -0
  180. package/src/client/sync/bucket/CrudTransaction.ts +3 -0
  181. package/src/client/sync/bucket/SqliteBucketStorage.ts +3 -0
  182. package/src/client/sync/stream/AbstractRemote.ts +30 -1
  183. package/src/client/sync/stream/AbstractStreamingSyncImplementation.ts +86 -59
  184. package/src/client/sync/stream/JsonValue.ts +3 -0
  185. package/src/client/sync/stream/WebsocketClientTransport.ts +3 -1
  186. package/src/client/sync/sync-streams.ts +22 -9
  187. package/src/client/triggers/TriggerManager.ts +19 -18
  188. package/src/client/triggers/TriggerManagerImpl.ts +5 -5
  189. package/src/client/triggers/sanitizeSQL.ts +5 -0
  190. package/src/client/watched/GetAllQuery.ts +5 -1
  191. package/src/client/watched/WatchedQuery.ts +24 -2
  192. package/src/client/watched/processors/AbstractQueryProcessor.ts +6 -6
  193. package/src/client/watched/processors/DifferentialQueryProcessor.ts +28 -5
  194. package/src/client/watched/processors/OnChangeQueryProcessor.ts +9 -3
  195. package/src/client/watched/processors/comparators.ts +8 -0
  196. package/src/db/ConnectionClosedError.ts +2 -0
  197. package/src/db/DBAdapter.ts +58 -6
  198. package/src/db/crud/SyncProgress.ts +6 -1
  199. package/src/db/crud/SyncStatus.ts +40 -21
  200. package/src/db/crud/UploadQueueStatus.ts +3 -0
  201. package/src/db/schema/Column.ts +28 -3
  202. package/src/db/schema/Index.ts +9 -0
  203. package/src/db/schema/IndexedColumn.ts +9 -0
  204. package/src/db/schema/RawTable.ts +7 -1
  205. package/src/db/schema/Schema.ts +8 -3
  206. package/src/db/schema/Table.ts +30 -5
  207. package/src/db/schema/TableV2.ts +2 -0
  208. package/src/index.ts +1 -1
  209. package/src/types/types.ts +6 -0
  210. package/src/utils/AbortOperation.ts +2 -0
  211. package/src/utils/BaseObserver.ts +12 -0
  212. package/src/utils/ControlledExecutor.ts +6 -0
  213. package/src/utils/Logger.ts +9 -0
  214. package/src/utils/async.ts +51 -24
  215. package/src/utils/mutex.ts +12 -0
  216. package/src/utils/parseQuery.ts +6 -0
  217. package/src/utils/stream_transform.ts +3 -1
package/dist/bundle.cjs CHANGED
@@ -1,6 +1,9 @@
1
1
  'use strict';
2
2
 
3
- // https://www.sqlite.org/lang_expr.html#castexpr
3
+ /**
4
+ * @see https://www.sqlite.org/lang_expr.html#castexpr
5
+ * @public
6
+ */
4
7
  exports.ColumnType = void 0;
5
8
  (function (ColumnType) {
6
9
  ColumnType["TEXT"] = "TEXT";
@@ -16,14 +19,24 @@ const integer = {
16
19
  const real = {
17
20
  type: exports.ColumnType.REAL
18
21
  };
19
- // powersync-sqlite-core limits the number of column per table to 1999, due to internal SQLite limits.
20
- // In earlier versions this was limited to 63.
22
+ /**
23
+ * powersync-sqlite-core limits the number of column per table to 1999, due to internal SQLite limits.
24
+ * In earlier versions this was limited to 63.
25
+ *
26
+ * @internal
27
+ */
21
28
  const MAX_AMOUNT_OF_COLUMNS = 1999;
29
+ /**
30
+ * @public
31
+ */
22
32
  const column = {
23
33
  text,
24
34
  integer,
25
35
  real
26
36
  };
37
+ /**
38
+ * @public
39
+ */
27
40
  class Column {
28
41
  options;
29
42
  constructor(options) {
@@ -43,9 +56,15 @@ class Column {
43
56
  }
44
57
  }
45
58
 
59
+ /**
60
+ * @internal
61
+ */
46
62
  const DEFAULT_INDEX_COLUMN_OPTIONS = {
47
63
  ascending: true
48
64
  };
65
+ /**
66
+ * @public
67
+ */
49
68
  class IndexedColumn {
50
69
  options;
51
70
  static createAscending(column) {
@@ -72,9 +91,15 @@ class IndexedColumn {
72
91
  }
73
92
  }
74
93
 
94
+ /**
95
+ * @internal
96
+ */
75
97
  const DEFAULT_INDEX_OPTIONS = {
76
98
  columns: []
77
99
  };
100
+ /**
101
+ * @public
102
+ */
78
103
  class Index {
79
104
  options;
80
105
  static createAscending(options, columnNames) {
@@ -116,6 +141,9 @@ function encodeTableOptions(options) {
116
141
  };
117
142
  }
118
143
 
144
+ /**
145
+ * @internal
146
+ */
119
147
  const DEFAULT_TABLE_OPTIONS = {
120
148
  indexes: [],
121
149
  insertOnly: false,
@@ -124,7 +152,13 @@ const DEFAULT_TABLE_OPTIONS = {
124
152
  trackMetadata: false,
125
153
  ignoreEmptyUpdates: false
126
154
  };
155
+ /**
156
+ * @internal
157
+ */
127
158
  const InvalidSQLCharacters = /["'%,.#\s[\]]/;
159
+ /**
160
+ * @public
161
+ */
128
162
  class Table {
129
163
  options;
130
164
  _mappedColumns;
@@ -315,6 +349,11 @@ class Table {
315
349
  }
316
350
  }
317
351
 
352
+ /**
353
+ * The default name of the local table storing attachment data.
354
+ *
355
+ * @alpha
356
+ */
318
357
  const ATTACHMENT_TABLE = 'attachments';
319
358
  /**
320
359
  * Maps a database row to an AttachmentRecord.
@@ -322,7 +361,7 @@ const ATTACHMENT_TABLE = 'attachments';
322
361
  * @param row - The database row object
323
362
  * @returns The corresponding AttachmentRecord
324
363
  *
325
- * @experimental
364
+ * @alpha
326
365
  */
327
366
  function attachmentFromSql(row) {
328
367
  return {
@@ -340,7 +379,7 @@ function attachmentFromSql(row) {
340
379
  /**
341
380
  * AttachmentState represents the current synchronization state of an attachment.
342
381
  *
343
- * @experimental
382
+ * @alpha
344
383
  */
345
384
  exports.AttachmentState = void 0;
346
385
  (function (AttachmentState) {
@@ -353,7 +392,7 @@ exports.AttachmentState = void 0;
353
392
  /**
354
393
  * AttachmentTable defines the schema for the attachment queue table.
355
394
  *
356
- * @internal
395
+ * @alpha
357
396
  */
358
397
  class AttachmentTable extends Table {
359
398
  constructor(options) {
@@ -381,7 +420,8 @@ class AttachmentTable extends Table {
381
420
  * Provides methods to query, insert, update, and delete attachment records with
382
421
  * proper transaction management through PowerSync.
383
422
  *
384
- * @internal
423
+ * @experimental
424
+ * @alpha
385
425
  */
386
426
  class AttachmentContext {
387
427
  /** PowerSync database instance for executing queries */
@@ -603,6 +643,9 @@ class AttachmentContext {
603
643
  }
604
644
  }
605
645
 
646
+ /**
647
+ * @public
648
+ */
606
649
  exports.WatchedQueryListenerEvent = void 0;
607
650
  (function (WatchedQueryListenerEvent) {
608
651
  WatchedQueryListenerEvent["ON_DATA"] = "onData";
@@ -611,176 +654,18 @@ exports.WatchedQueryListenerEvent = void 0;
611
654
  WatchedQueryListenerEvent["SETTINGS_WILL_UPDATE"] = "settingsWillUpdate";
612
655
  WatchedQueryListenerEvent["CLOSED"] = "closed";
613
656
  })(exports.WatchedQueryListenerEvent || (exports.WatchedQueryListenerEvent = {}));
657
+ /**
658
+ * @internal
659
+ */
614
660
  const DEFAULT_WATCH_THROTTLE_MS = 30;
661
+ /**
662
+ * @internal
663
+ */
615
664
  const DEFAULT_WATCH_QUERY_OPTIONS = {
616
665
  throttleMs: DEFAULT_WATCH_THROTTLE_MS,
617
666
  reportFetching: true
618
667
  };
619
668
 
620
- /**
621
- * Orchestrates attachment synchronization between local and remote storage.
622
- * Handles uploads, downloads, deletions, and state transitions.
623
- *
624
- * @internal
625
- */
626
- class SyncingService {
627
- attachmentService;
628
- localStorage;
629
- remoteStorage;
630
- logger;
631
- errorHandler;
632
- constructor(attachmentService, localStorage, remoteStorage, logger, errorHandler) {
633
- this.attachmentService = attachmentService;
634
- this.localStorage = localStorage;
635
- this.remoteStorage = remoteStorage;
636
- this.logger = logger;
637
- this.errorHandler = errorHandler;
638
- }
639
- /**
640
- * Processes attachments based on their state (upload, download, or delete).
641
- * All updates are saved in a single batch after processing.
642
- *
643
- * @param attachments - Array of attachment records to process
644
- * @param context - Attachment context for database operations
645
- * @returns Promise that resolves when all attachments have been processed and saved
646
- */
647
- async processAttachments(attachments, context) {
648
- const updatedAttachments = [];
649
- for (const attachment of attachments) {
650
- switch (attachment.state) {
651
- case exports.AttachmentState.QUEUED_UPLOAD:
652
- const uploaded = await this.uploadAttachment(attachment);
653
- updatedAttachments.push(uploaded);
654
- break;
655
- case exports.AttachmentState.QUEUED_DOWNLOAD:
656
- const downloaded = await this.downloadAttachment(attachment);
657
- updatedAttachments.push(downloaded);
658
- break;
659
- case exports.AttachmentState.QUEUED_DELETE:
660
- const deleted = await this.deleteAttachment(attachment, context);
661
- updatedAttachments.push(deleted);
662
- break;
663
- }
664
- }
665
- await context.saveAttachments(updatedAttachments);
666
- }
667
- /**
668
- * Uploads an attachment from local storage to remote storage.
669
- * On success, marks as SYNCED. On failure, defers to error handler or archives.
670
- *
671
- * @param attachment - The attachment record to upload
672
- * @returns Updated attachment record with new state
673
- * @throws Error if the attachment has no localUri
674
- */
675
- async uploadAttachment(attachment) {
676
- this.logger.info(`Uploading attachment ${attachment.filename}`);
677
- try {
678
- if (attachment.localUri == null) {
679
- throw new Error(`No localUri for attachment ${attachment.id}`);
680
- }
681
- const fileBlob = await this.localStorage.readFile(attachment.localUri);
682
- await this.remoteStorage.uploadFile(fileBlob, attachment);
683
- return {
684
- ...attachment,
685
- state: exports.AttachmentState.SYNCED,
686
- hasSynced: true
687
- };
688
- }
689
- catch (error) {
690
- const shouldRetry = (await this.errorHandler?.onUploadError(attachment, error)) ?? true;
691
- if (!shouldRetry) {
692
- return {
693
- ...attachment,
694
- state: exports.AttachmentState.ARCHIVED
695
- };
696
- }
697
- return attachment;
698
- }
699
- }
700
- /**
701
- * Downloads an attachment from remote storage to local storage.
702
- * Retrieves the file, converts to base64, and saves locally.
703
- * On success, marks as SYNCED. On failure, defers to error handler or archives.
704
- *
705
- * @param attachment - The attachment record to download
706
- * @returns Updated attachment record with local URI and new state
707
- */
708
- async downloadAttachment(attachment) {
709
- this.logger.info(`Downloading attachment ${attachment.filename}`);
710
- try {
711
- const fileData = await this.remoteStorage.downloadFile(attachment);
712
- const localUri = this.localStorage.getLocalUri(attachment.filename);
713
- await this.localStorage.saveFile(localUri, fileData);
714
- return {
715
- ...attachment,
716
- state: exports.AttachmentState.SYNCED,
717
- localUri: localUri,
718
- hasSynced: true
719
- };
720
- }
721
- catch (error) {
722
- const shouldRetry = (await this.errorHandler?.onDownloadError(attachment, error)) ?? true;
723
- if (!shouldRetry) {
724
- return {
725
- ...attachment,
726
- state: exports.AttachmentState.ARCHIVED
727
- };
728
- }
729
- return attachment;
730
- }
731
- }
732
- /**
733
- * Deletes an attachment from both remote and local storage.
734
- * Removes the remote file, local file (if exists), and the attachment record.
735
- * On failure, defers to error handler or archives.
736
- *
737
- * @param attachment - The attachment record to delete
738
- * @param context - Attachment context for database operations
739
- * @returns Updated attachment record
740
- */
741
- async deleteAttachment(attachment, context) {
742
- try {
743
- await this.remoteStorage.deleteFile(attachment);
744
- if (attachment.localUri) {
745
- await this.localStorage.deleteFile(attachment.localUri);
746
- }
747
- await context.deleteAttachment(attachment.id);
748
- return {
749
- ...attachment,
750
- state: exports.AttachmentState.ARCHIVED
751
- };
752
- }
753
- catch (error) {
754
- const shouldRetry = (await this.errorHandler?.onDeleteError(attachment, error)) ?? true;
755
- if (!shouldRetry) {
756
- return {
757
- ...attachment,
758
- state: exports.AttachmentState.ARCHIVED
759
- };
760
- }
761
- return attachment;
762
- }
763
- }
764
- /**
765
- * Performs cleanup of archived attachments by removing their local files and records.
766
- * Errors during local file deletion are logged but do not prevent record deletion.
767
- */
768
- async deleteArchivedAttachments(context) {
769
- return await context.deleteArchivedAttachments(async (archivedAttachments) => {
770
- for (const attachment of archivedAttachments) {
771
- if (attachment.localUri) {
772
- try {
773
- await this.localStorage.deleteFile(attachment.localUri);
774
- }
775
- catch (error) {
776
- this.logger.error('Error deleting local file for archived attachment', error);
777
- }
778
- }
779
- }
780
- });
781
- }
782
- }
783
-
784
669
  /**
785
670
  * A simple fixed-capacity queue implementation.
786
671
  *
@@ -966,6 +851,9 @@ class Mutex {
966
851
  }
967
852
  }
968
853
  }
854
+ /**
855
+ * @internal
856
+ */
969
857
  function timeoutSignal(timeout) {
970
858
  if (timeout == null)
971
859
  return;
@@ -994,36 +882,200 @@ class AttachmentService {
994
882
  this.context = new AttachmentContext(db, tableName, logger, archivedCacheLimit);
995
883
  }
996
884
  /**
997
- * Creates a differential watch query for active attachments requiring synchronization.
998
- * @returns Watch query that emits changes for queued uploads, downloads, and deletes
885
+ * Creates a differential watch query for active attachments requiring synchronization.
886
+ * @returns Watch query that emits changes for queued uploads, downloads, and deletes
887
+ */
888
+ watchActiveAttachments({ throttleMs } = {}) {
889
+ this.logger.info('Watching active attachments...');
890
+ const watch = this.db
891
+ .query({
892
+ sql: /* sql */ `
893
+ SELECT
894
+ *
895
+ FROM
896
+ ${this.tableName}
897
+ WHERE
898
+ state = ?
899
+ OR state = ?
900
+ OR state = ?
901
+ ORDER BY
902
+ timestamp ASC
903
+ `,
904
+ parameters: [exports.AttachmentState.QUEUED_UPLOAD, exports.AttachmentState.QUEUED_DOWNLOAD, exports.AttachmentState.QUEUED_DELETE]
905
+ })
906
+ .differentialWatch({ throttleMs });
907
+ return watch;
908
+ }
909
+ /**
910
+ * Executes a callback with exclusive access to the attachment context.
911
+ */
912
+ async withContext(callback) {
913
+ return this.mutex.runExclusive(async () => {
914
+ return callback(this.context);
915
+ });
916
+ }
917
+ }
918
+
919
+ /**
920
+ * Orchestrates attachment synchronization between local and remote storage.
921
+ * Handles uploads, downloads, deletions, and state transitions.
922
+ *
923
+ * @internal
924
+ */
925
+ class SyncingService {
926
+ attachmentService;
927
+ localStorage;
928
+ remoteStorage;
929
+ logger;
930
+ errorHandler;
931
+ constructor(attachmentService, localStorage, remoteStorage, logger, errorHandler) {
932
+ this.attachmentService = attachmentService;
933
+ this.localStorage = localStorage;
934
+ this.remoteStorage = remoteStorage;
935
+ this.logger = logger;
936
+ this.errorHandler = errorHandler;
937
+ }
938
+ /**
939
+ * Processes attachments based on their state (upload, download, or delete).
940
+ * All updates are saved in a single batch after processing.
941
+ *
942
+ * @param attachments - Array of attachment records to process
943
+ * @param context - Attachment context for database operations
944
+ * @returns Promise that resolves when all attachments have been processed and saved
945
+ */
946
+ async processAttachments(attachments, context) {
947
+ const updatedAttachments = [];
948
+ for (const attachment of attachments) {
949
+ switch (attachment.state) {
950
+ case exports.AttachmentState.QUEUED_UPLOAD:
951
+ const uploaded = await this.uploadAttachment(attachment);
952
+ updatedAttachments.push(uploaded);
953
+ break;
954
+ case exports.AttachmentState.QUEUED_DOWNLOAD:
955
+ const downloaded = await this.downloadAttachment(attachment);
956
+ updatedAttachments.push(downloaded);
957
+ break;
958
+ case exports.AttachmentState.QUEUED_DELETE:
959
+ const deleted = await this.deleteAttachment(attachment, context);
960
+ updatedAttachments.push(deleted);
961
+ break;
962
+ }
963
+ }
964
+ await context.saveAttachments(updatedAttachments);
965
+ }
966
+ /**
967
+ * Uploads an attachment from local storage to remote storage.
968
+ * On success, marks as SYNCED. On failure, defers to error handler or archives.
969
+ *
970
+ * @param attachment - The attachment record to upload
971
+ * @returns Updated attachment record with new state
972
+ * @throws Error if the attachment has no localUri
973
+ */
974
+ async uploadAttachment(attachment) {
975
+ this.logger.info(`Uploading attachment ${attachment.filename}`);
976
+ try {
977
+ if (attachment.localUri == null) {
978
+ throw new Error(`No localUri for attachment ${attachment.id}`);
979
+ }
980
+ const fileBlob = await this.localStorage.readFile(attachment.localUri);
981
+ await this.remoteStorage.uploadFile(fileBlob, attachment);
982
+ return {
983
+ ...attachment,
984
+ state: exports.AttachmentState.SYNCED,
985
+ hasSynced: true
986
+ };
987
+ }
988
+ catch (error) {
989
+ const shouldRetry = (await this.errorHandler?.onUploadError(attachment, error)) ?? true;
990
+ if (!shouldRetry) {
991
+ return {
992
+ ...attachment,
993
+ state: exports.AttachmentState.ARCHIVED
994
+ };
995
+ }
996
+ return attachment;
997
+ }
998
+ }
999
+ /**
1000
+ * Downloads an attachment from remote storage to local storage.
1001
+ * Retrieves the file, converts to base64, and saves locally.
1002
+ * On success, marks as SYNCED. On failure, defers to error handler or archives.
1003
+ *
1004
+ * @param attachment - The attachment record to download
1005
+ * @returns Updated attachment record with local URI and new state
1006
+ */
1007
+ async downloadAttachment(attachment) {
1008
+ this.logger.info(`Downloading attachment ${attachment.filename}`);
1009
+ try {
1010
+ const fileData = await this.remoteStorage.downloadFile(attachment);
1011
+ const localUri = this.localStorage.getLocalUri(attachment.filename);
1012
+ await this.localStorage.saveFile(localUri, fileData);
1013
+ return {
1014
+ ...attachment,
1015
+ state: exports.AttachmentState.SYNCED,
1016
+ localUri: localUri,
1017
+ hasSynced: true
1018
+ };
1019
+ }
1020
+ catch (error) {
1021
+ const shouldRetry = (await this.errorHandler?.onDownloadError(attachment, error)) ?? true;
1022
+ if (!shouldRetry) {
1023
+ return {
1024
+ ...attachment,
1025
+ state: exports.AttachmentState.ARCHIVED
1026
+ };
1027
+ }
1028
+ return attachment;
1029
+ }
1030
+ }
1031
+ /**
1032
+ * Deletes an attachment from both remote and local storage.
1033
+ * Removes the remote file, local file (if exists), and the attachment record.
1034
+ * On failure, defers to error handler or archives.
1035
+ *
1036
+ * @param attachment - The attachment record to delete
1037
+ * @param context - Attachment context for database operations
1038
+ * @returns Updated attachment record
999
1039
  */
1000
- watchActiveAttachments({ throttleMs } = {}) {
1001
- this.logger.info('Watching active attachments...');
1002
- const watch = this.db
1003
- .query({
1004
- sql: /* sql */ `
1005
- SELECT
1006
- *
1007
- FROM
1008
- ${this.tableName}
1009
- WHERE
1010
- state = ?
1011
- OR state = ?
1012
- OR state = ?
1013
- ORDER BY
1014
- timestamp ASC
1015
- `,
1016
- parameters: [exports.AttachmentState.QUEUED_UPLOAD, exports.AttachmentState.QUEUED_DOWNLOAD, exports.AttachmentState.QUEUED_DELETE]
1017
- })
1018
- .differentialWatch({ throttleMs });
1019
- return watch;
1040
+ async deleteAttachment(attachment, context) {
1041
+ try {
1042
+ await this.remoteStorage.deleteFile(attachment);
1043
+ if (attachment.localUri) {
1044
+ await this.localStorage.deleteFile(attachment.localUri);
1045
+ }
1046
+ await context.deleteAttachment(attachment.id);
1047
+ return {
1048
+ ...attachment,
1049
+ state: exports.AttachmentState.ARCHIVED
1050
+ };
1051
+ }
1052
+ catch (error) {
1053
+ const shouldRetry = (await this.errorHandler?.onDeleteError(attachment, error)) ?? true;
1054
+ if (!shouldRetry) {
1055
+ return {
1056
+ ...attachment,
1057
+ state: exports.AttachmentState.ARCHIVED
1058
+ };
1059
+ }
1060
+ return attachment;
1061
+ }
1020
1062
  }
1021
1063
  /**
1022
- * Executes a callback with exclusive access to the attachment context.
1064
+ * Performs cleanup of archived attachments by removing their local files and records.
1065
+ * Errors during local file deletion are logged but do not prevent record deletion.
1023
1066
  */
1024
- async withContext(callback) {
1025
- return this.mutex.runExclusive(async () => {
1026
- return callback(this.context);
1067
+ async deleteArchivedAttachments(context) {
1068
+ return await context.deleteArchivedAttachments(async (archivedAttachments) => {
1069
+ for (const attachment of archivedAttachments) {
1070
+ if (attachment.localUri) {
1071
+ try {
1072
+ await this.localStorage.deleteFile(attachment.localUri);
1073
+ }
1074
+ catch (error) {
1075
+ this.logger.error('Error deleting local file for archived attachment', error);
1076
+ }
1077
+ }
1078
+ }
1027
1079
  });
1028
1080
  }
1029
1081
  }
@@ -1084,16 +1136,6 @@ class AttachmentQueue {
1084
1136
  * Creates a new AttachmentQueue instance.
1085
1137
  *
1086
1138
  * @param options - Configuration options
1087
- * @param options.db - PowerSync database instance
1088
- * @param options.remoteStorage - Remote storage adapter for upload/download operations
1089
- * @param options.localStorage - Local storage adapter for file persistence
1090
- * @param options.watchAttachments - Callback for monitoring attachment changes in your data model
1091
- * @param options.tableName - Name of the table to store attachment records. Default: 'ps_attachment_queue'
1092
- * @param options.logger - Logger instance. Defaults to db.logger
1093
- * @param options.syncIntervalMs - Periodic polling interval in milliseconds for retrying failed uploads/downloads. Default: 30000
1094
- * @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
1095
- * @param options.downloadAttachments - Whether to automatically download remote attachments. Default: true
1096
- * @param options.archivedCacheLimit - Maximum archived attachments before cleanup. Default: 100
1097
1139
  */
1098
1140
  constructor({ db, localStorage, remoteStorage, watchAttachments, logger, tableName = ATTACHMENT_TABLE, syncIntervalMs = 30 * 1000, syncThrottleDuration = DEFAULT_WATCH_THROTTLE_MS, downloadAttachments = true, archivedCacheLimit = 100, errorHandler }) {
1099
1141
  this.db = db;
@@ -1182,6 +1224,7 @@ class AttachmentQueue {
1182
1224
  state: exports.AttachmentState.QUEUED_DOWNLOAD,
1183
1225
  hasSynced: false,
1184
1226
  metaData: watchedAttachment.metaData,
1227
+ mediaType: watchedAttachment.mediaType,
1185
1228
  timestamp: new Date().getTime()
1186
1229
  });
1187
1230
  continue;
@@ -1269,17 +1312,24 @@ class AttachmentQueue {
1269
1312
  this.statusListenerDispose = undefined;
1270
1313
  }
1271
1314
  }
1315
+ /**
1316
+ * Provides an {@link AttachmentContext} to a callback.
1317
+ *
1318
+ * The callback runs while the attachment queue mutex is held. Do not call
1319
+ * other {@link AttachmentQueue} methods from within the callback, as they may
1320
+ * attempt to acquire the same mutex and block indefinitely.
1321
+ */
1322
+ withAttachmentContext(callback) {
1323
+ /**
1324
+ * AttachmentService is internal and private in this class.
1325
+ * We only need to expose its locking and context functionality for extending classes.
1326
+ */
1327
+ return this.attachmentService.withContext(callback);
1328
+ }
1272
1329
  /**
1273
1330
  * Saves a file to local storage and queues it for upload to remote storage.
1274
1331
  *
1275
1332
  * @param options - File save options
1276
- * @param options.data - The file data as ArrayBuffer, Blob, or base64 string
1277
- * @param options.fileExtension - File extension (e.g., 'jpg', 'pdf')
1278
- * @param options.mediaType - MIME type of the file (e.g., 'image/jpeg')
1279
- * @param options.metaData - Optional metadata to associate with the attachment
1280
- * @param options.id - Optional custom ID. If not provided, a UUID will be generated
1281
- * @param options.updateHook - Optional callback to execute additional database operations
1282
- * within the same transaction as the attachment creation
1283
1333
  * @returns Promise resolving to the created attachment record
1284
1334
  */
1285
1335
  async saveFile({ data, fileExtension, mediaType, metaData, id, updateHook }) {
@@ -1392,6 +1442,9 @@ class AttachmentQueue {
1392
1442
  }
1393
1443
  }
1394
1444
 
1445
+ /**
1446
+ * @alpha
1447
+ */
1395
1448
  exports.EncodingType = void 0;
1396
1449
  (function (EncodingType) {
1397
1450
  EncodingType["UTF8"] = "utf8";
@@ -1855,7 +1908,9 @@ var Logger = /*@__PURE__*/getDefaultExportFromCjs(loggerExports);
1855
1908
  * different SQLite DB implementations.
1856
1909
  */
1857
1910
  /**
1858
- * Implements {@link DBGetUtils} on a {@link SqlRunner}.
1911
+ * Implements {@link DBGetUtils} on a {@link SqlExecutor}.
1912
+ *
1913
+ * @internal
1859
1914
  */
1860
1915
  function DBGetUtilsDefaultMixin(Base) {
1861
1916
  return class extends Base {
@@ -1899,6 +1954,8 @@ function DBGetUtilsDefaultMixin(Base) {
1899
1954
  }
1900
1955
  /**
1901
1956
  * Update table operation numbers from SQLite
1957
+ *
1958
+ * @public
1902
1959
  */
1903
1960
  exports.RowUpdateType = void 0;
1904
1961
  (function (RowUpdateType) {
@@ -1907,8 +1964,10 @@ exports.RowUpdateType = void 0;
1907
1964
  RowUpdateType[RowUpdateType["SQLITE_UPDATE"] = 23] = "SQLITE_UPDATE";
1908
1965
  })(exports.RowUpdateType || (exports.RowUpdateType = {}));
1909
1966
  /**
1910
- * A mixin to implement {@link DBAdapter} by delegating to {@link ConnectionPool.readLock} and
1911
- * {@link ConnectionPool.writeLock}.
1967
+ * A mixin to implement {@link DBAdapter} by delegating to {@link ConnectionPool#readLock} and
1968
+ * {@link ConnectionPool#writeLock}.
1969
+ *
1970
+ * @internal
1912
1971
  */
1913
1972
  function DBAdapterDefaultMixin(Base) {
1914
1973
  return class extends Base {
@@ -1996,9 +2055,15 @@ class TransactionImplementation extends DBGetUtilsDefaultMixin(BaseTransaction)
1996
2055
  }
1997
2056
  }
1998
2057
  }
2058
+ /**
2059
+ * @internal
2060
+ */
1999
2061
  function isBatchedUpdateNotification(update) {
2000
2062
  return 'tables' in update;
2001
2063
  }
2064
+ /**
2065
+ * @internal
2066
+ */
2002
2067
  function extractTableUpdates(update) {
2003
2068
  return isBatchedUpdateNotification(update) ? update.tables : [update.table];
2004
2069
  }
@@ -2026,6 +2091,8 @@ const FULL_SYNC_PRIORITY = 2147483647;
2026
2091
  *
2027
2092
  * Also note that data is downloaded in bulk, which means that individual counters are unlikely
2028
2093
  * to be updated one-by-one.
2094
+ *
2095
+ * @public
2029
2096
  */
2030
2097
  class SyncProgress {
2031
2098
  internal;
@@ -2064,6 +2131,9 @@ class SyncProgress {
2064
2131
  }
2065
2132
  }
2066
2133
 
2134
+ /**
2135
+ * @public
2136
+ */
2067
2137
  class SyncStatus {
2068
2138
  options;
2069
2139
  constructor(options) {
@@ -2074,6 +2144,8 @@ class SyncStatus {
2074
2144
  * implementation).
2075
2145
  *
2076
2146
  * This information is only available after a connection has been requested.
2147
+ *
2148
+ * @deprecated This always returns the Rust client (the only option).
2077
2149
  */
2078
2150
  get clientImplementation() {
2079
2151
  return this.options.clientImplementation;
@@ -2081,7 +2153,7 @@ class SyncStatus {
2081
2153
  /**
2082
2154
  * Indicates if the client is currently connected to the PowerSync service.
2083
2155
  *
2084
- * @returns {boolean} True if connected, false otherwise. Defaults to false if not specified.
2156
+ * @returns True if connected, false otherwise. Defaults to false if not specified.
2085
2157
  */
2086
2158
  get connected() {
2087
2159
  return this.options.connected ?? false;
@@ -2089,7 +2161,7 @@ class SyncStatus {
2089
2161
  /**
2090
2162
  * Indicates if the client is in the process of establishing a connection to the PowerSync service.
2091
2163
  *
2092
- * @returns {boolean} True if connecting, false otherwise. Defaults to false if not specified.
2164
+ * @returns True if connecting, false otherwise. Defaults to false if not specified.
2093
2165
  */
2094
2166
  get connecting() {
2095
2167
  return this.options.connecting ?? false;
@@ -2098,7 +2170,7 @@ class SyncStatus {
2098
2170
  * Time that a last sync has fully completed, if any.
2099
2171
  * This timestamp is reset to null after a restart of the PowerSync service.
2100
2172
  *
2101
- * @returns {Date | undefined} The timestamp of the last successful sync, or undefined if no sync has completed.
2173
+ * @returns The timestamp of the last successful sync, or undefined if no sync has completed.
2102
2174
  */
2103
2175
  get lastSyncedAt() {
2104
2176
  return this.options.lastSyncedAt;
@@ -2106,7 +2178,7 @@ class SyncStatus {
2106
2178
  /**
2107
2179
  * Indicates whether there has been at least one full sync completed since initialization.
2108
2180
  *
2109
- * @returns {boolean | undefined} True if at least one sync has completed, false if no sync has completed,
2181
+ * @returns True if at least one sync has completed, false if no sync has completed,
2110
2182
  * or undefined when the state is still being loaded from the database.
2111
2183
  */
2112
2184
  get hasSynced() {
@@ -2115,10 +2187,10 @@ class SyncStatus {
2115
2187
  /**
2116
2188
  * Provides the current data flow status regarding uploads and downloads.
2117
2189
  *
2118
- * @returns {SyncDataFlowStatus} An object containing:
2190
+ * @returns An object containing:
2119
2191
  * - downloading: True if actively downloading changes (only when connected is also true)
2120
2192
  * - uploading: True if actively uploading changes
2121
- * Defaults to {downloading: false, uploading: false} if not specified.
2193
+ * Defaults to `{downloading: false, uploading: false}` if not specified.
2122
2194
  */
2123
2195
  get dataFlowStatus() {
2124
2196
  return (this.options.dataFlow ?? {
@@ -2143,7 +2215,7 @@ class SyncStatus {
2143
2215
  return this.options.dataFlow?.internalStreamSubscriptions?.map((core) => new SyncStreamStatusView(this, core));
2144
2216
  }
2145
2217
  /**
2146
- * If the `stream` appears in {@link syncStreams}, returns the current status for that stream.
2218
+ * If the `stream` appears in {@link SyncStatus.syncStreams}, returns the current status for that stream.
2147
2219
  */
2148
2220
  forStream(stream) {
2149
2221
  const asJson = JSON.stringify(stream.parameters);
@@ -2153,7 +2225,7 @@ class SyncStatus {
2153
2225
  /**
2154
2226
  * Provides sync status information for all bucket priorities, sorted by priority (highest first).
2155
2227
  *
2156
- * @returns {SyncPriorityStatus[]} An array of status entries for different sync priority levels,
2228
+ * @returns An array of status entries for different sync priority levels,
2157
2229
  * sorted with highest priorities (lower numbers) first.
2158
2230
  */
2159
2231
  get priorityStatusEntries() {
@@ -2188,8 +2260,8 @@ class SyncStatus {
2188
2260
  * For example, if PowerSync just finished synchronizing buckets in priority level 3, calling this method
2189
2261
  * with a priority of 1 may return information for priority level 3.
2190
2262
  *
2191
- * @param {number} priority The bucket priority for which the status should be reported
2192
- * @returns {SyncPriorityStatus} Status information for the requested priority level or the next higher level with available status
2263
+ * @param priority - The bucket priority for which the status should be reported
2264
+ * @returns Status information for the requested priority level or the next higher level with available status
2193
2265
  */
2194
2266
  statusForPriority(priority) {
2195
2267
  // priorityStatusEntries are sorted by ascending priorities (so higher numbers to lower numbers).
@@ -2210,8 +2282,8 @@ class SyncStatus {
2210
2282
  * Compares this SyncStatus instance with another to determine if they are equal.
2211
2283
  * Equality is determined by comparing the serialized JSON representation of both instances.
2212
2284
  *
2213
- * @param {SyncStatus} status The SyncStatus instance to compare against
2214
- * @returns {boolean} True if the instances are considered equal, false otherwise
2285
+ * @param status - The SyncStatus instance to compare against
2286
+ * @returns True if the instances are considered equal, false otherwise
2215
2287
  */
2216
2288
  isEqual(status) {
2217
2289
  /**
@@ -2234,7 +2306,7 @@ class SyncStatus {
2234
2306
  * Creates a human-readable string representation of the current sync status.
2235
2307
  * Includes information about connection state, sync completion, and data flow.
2236
2308
  *
2237
- * @returns {string} A string representation of the sync status
2309
+ * @returns A string representation of the sync status
2238
2310
  */
2239
2311
  getMessage() {
2240
2312
  const dataFlow = this.dataFlowStatus;
@@ -2243,7 +2315,7 @@ class SyncStatus {
2243
2315
  /**
2244
2316
  * Serializes the SyncStatus instance to a plain object.
2245
2317
  *
2246
- * @returns {SyncStatusOptions} A plain object representation of the sync status
2318
+ * @returns A plain object representation of the sync status
2247
2319
  */
2248
2320
  toJSON() {
2249
2321
  return {
@@ -2309,6 +2381,9 @@ class SyncStreamStatusView {
2309
2381
  }
2310
2382
  }
2311
2383
 
2384
+ /**
2385
+ * @public
2386
+ */
2312
2387
  class UploadQueueStats {
2313
2388
  count;
2314
2389
  size;
@@ -2334,6 +2409,9 @@ class UploadQueueStats {
2334
2409
  }
2335
2410
  }
2336
2411
 
2412
+ /**
2413
+ * @internal
2414
+ */
2337
2415
  class BaseObserver {
2338
2416
  listeners = new Set();
2339
2417
  constructor() { }
@@ -2361,6 +2439,9 @@ class BaseObserver {
2361
2439
  }
2362
2440
  }
2363
2441
 
2442
+ /**
2443
+ * @internal
2444
+ */
2364
2445
  class ControlledExecutor {
2365
2446
  task;
2366
2447
  /**
@@ -2430,30 +2511,44 @@ function throttleTrailing(func, wait) {
2430
2511
  }
2431
2512
  };
2432
2513
  }
2433
- /**
2434
- * Throttle a function to be called at most once every "wait" milliseconds,
2435
- * on the leading and trailing edge.
2436
- *
2437
- * Roughly equivalent to lodash/throttle with {leading: true, trailing: true}
2438
- */
2439
- function throttleLeadingTrailing(func, wait) {
2440
- let timeoutId = null;
2441
- let lastCallTime = 0;
2442
- const invokeFunction = () => {
2443
- func();
2444
- lastCallTime = Date.now();
2445
- timeoutId = null;
2446
- };
2447
- return function () {
2448
- const now = Date.now();
2449
- const timeToWait = wait - (now - lastCallTime);
2450
- if (timeToWait <= 0) {
2451
- // Leading edge: Call the function immediately if enough time has passed
2452
- invokeFunction();
2453
- }
2454
- else if (!timeoutId) {
2455
- // Set a timeout for the trailing edge if not already set
2456
- timeoutId = setTimeout(invokeFunction, timeToWait);
2514
+ function asyncNotifier() {
2515
+ let waitingConsumer = null;
2516
+ let hasPendingNotification = false;
2517
+ return {
2518
+ notify() {
2519
+ if (waitingConsumer != null) {
2520
+ waitingConsumer();
2521
+ waitingConsumer = null;
2522
+ }
2523
+ else {
2524
+ hasPendingNotification = true;
2525
+ }
2526
+ },
2527
+ waitForNotification(signal) {
2528
+ return new Promise((resolve) => {
2529
+ if (waitingConsumer != null) {
2530
+ throw new Error('Illegal call to waitForNotification, already has a waiter.');
2531
+ }
2532
+ if (signal.aborted) {
2533
+ resolve();
2534
+ }
2535
+ else if (hasPendingNotification) {
2536
+ resolve();
2537
+ hasPendingNotification = false;
2538
+ }
2539
+ else {
2540
+ function complete() {
2541
+ signal.removeEventListener('abort', onAbort);
2542
+ resolve();
2543
+ }
2544
+ function onAbort() {
2545
+ waitingConsumer = null;
2546
+ resolve();
2547
+ }
2548
+ waitingConsumer = complete;
2549
+ signal.addEventListener('abort', onAbort);
2550
+ }
2551
+ });
2457
2552
  }
2458
2553
  };
2459
2554
  }
@@ -2614,7 +2709,7 @@ class ConnectionManager extends BaseObserver {
2614
2709
  /**
2615
2710
  * Close the sync connection.
2616
2711
  *
2617
- * Use {@link connect} to connect again.
2712
+ * Use {@link ConnectionManager.connect} to connect again.
2618
2713
  */
2619
2714
  async disconnect() {
2620
2715
  // This will help abort pending connects
@@ -2754,6 +2849,8 @@ const _finalizer = 'FinalizationRegistry' in globalThis
2754
2849
  /**
2755
2850
  * An efficient comparator for {@link WatchedQuery} created with {@link Query#watch}. This has the ability to determine if a query
2756
2851
  * result has changes without necessarily processing all items in the result.
2852
+ *
2853
+ * @public
2757
2854
  */
2758
2855
  class ArrayComparator {
2759
2856
  options;
@@ -2781,6 +2878,8 @@ class ArrayComparator {
2781
2878
  }
2782
2879
  /**
2783
2880
  * Watched query comparator that always reports changed result sets.
2881
+ *
2882
+ * @public
2784
2883
  */
2785
2884
  const FalsyComparator = {
2786
2885
  checkEquality: () => false // Default comparator that always returns false
@@ -2988,6 +3087,8 @@ class AbstractQueryProcessor extends MetaBaseObserver {
2988
3087
  /**
2989
3088
  * An empty differential result set.
2990
3089
  * This is used as the initial state for differential incrementally watched queries.
3090
+ *
3091
+ * @internal
2991
3092
  */
2992
3093
  const EMPTY_DIFFERENTIAL = {
2993
3094
  added: [],
@@ -3000,6 +3101,8 @@ const EMPTY_DIFFERENTIAL = {
3000
3101
  * Default implementation of the {@link DifferentialWatchedQueryComparator} for watched queries.
3001
3102
  * It keys items by their `id` property if available, alternatively it uses JSON stringification
3002
3103
  * of the entire item for the key and comparison.
3104
+ *
3105
+ * @internal
3003
3106
  */
3004
3107
  const DEFAULT_ROW_COMPARATOR = {
3005
3108
  keyBy: (item) => {
@@ -3280,6 +3383,8 @@ class CustomQuery {
3280
3383
 
3281
3384
  /**
3282
3385
  * Tests if the input is a {@link SQLOpenOptions}
3386
+ *
3387
+ * @internal
3283
3388
  */
3284
3389
  const isSQLOpenOptions = (test) => {
3285
3390
  // typeof null is `object`, but you cannot use the `in` operator on `null.
@@ -3287,17 +3392,24 @@ const isSQLOpenOptions = (test) => {
3287
3392
  };
3288
3393
  /**
3289
3394
  * Tests if input is a {@link SQLOpenFactory}
3395
+ *
3396
+ * @internal
3290
3397
  */
3291
3398
  const isSQLOpenFactory = (test) => {
3292
3399
  return typeof test?.openDB == 'function';
3293
3400
  };
3294
3401
  /**
3295
3402
  * Tests if input is a {@link DBAdapter}
3403
+ *
3404
+ * @internal
3296
3405
  */
3297
3406
  const isDBAdapter = (test) => {
3298
3407
  return typeof test?.writeTransaction == 'function';
3299
3408
  };
3300
3409
 
3410
+ /**
3411
+ * @internal
3412
+ */
3301
3413
  exports.PSInternalTable = void 0;
3302
3414
  (function (PSInternalTable) {
3303
3415
  PSInternalTable["DATA"] = "ps_data";
@@ -3306,6 +3418,9 @@ exports.PSInternalTable = void 0;
3306
3418
  PSInternalTable["OPLOG"] = "ps_oplog";
3307
3419
  PSInternalTable["UNTYPED"] = "ps_untyped";
3308
3420
  })(exports.PSInternalTable || (exports.PSInternalTable = {}));
3421
+ /**
3422
+ * @internal
3423
+ */
3309
3424
  exports.PowerSyncControlCommand = void 0;
3310
3425
  (function (PowerSyncControlCommand) {
3311
3426
  PowerSyncControlCommand["PROCESS_TEXT_LINE"] = "line_text";
@@ -3323,6 +3438,8 @@ exports.PowerSyncControlCommand = void 0;
3323
3438
 
3324
3439
  /**
3325
3440
  * A batch of client-side changes.
3441
+ *
3442
+ * @public
3326
3443
  */
3327
3444
  class CrudBatch {
3328
3445
  crud;
@@ -3349,6 +3466,8 @@ class CrudBatch {
3349
3466
 
3350
3467
  /**
3351
3468
  * Type of local change.
3469
+ *
3470
+ * @public
3352
3471
  */
3353
3472
  exports.UpdateType = void 0;
3354
3473
  (function (UpdateType) {
@@ -3361,6 +3480,8 @@ exports.UpdateType = void 0;
3361
3480
  })(exports.UpdateType || (exports.UpdateType = {}));
3362
3481
  /**
3363
3482
  * A single client-side change.
3483
+ *
3484
+ * @public
3364
3485
  */
3365
3486
  class CrudEntry {
3366
3487
  /**
@@ -3457,6 +3578,9 @@ class CrudEntry {
3457
3578
  }
3458
3579
  }
3459
3580
 
3581
+ /**
3582
+ * @public
3583
+ */
3460
3584
  class CrudTransaction extends CrudBatch {
3461
3585
  crud;
3462
3586
  complete;
@@ -3485,6 +3609,8 @@ class CrudTransaction extends CrudBatch {
3485
3609
  * Calls to Abortcontroller.abort(reason: any) will result in the
3486
3610
  * `reason` being thrown. This is not necessarily an error,
3487
3611
  * but extends error for better logging purposes.
3612
+ *
3613
+ * @internal
3488
3614
  */
3489
3615
  class AbortOperation extends Error {
3490
3616
  reason;
@@ -10655,7 +10781,7 @@ function requireDist () {
10655
10781
 
10656
10782
  var distExports = requireDist();
10657
10783
 
10658
- var version = "1.53.1";
10784
+ var version = "1.54.0";
10659
10785
  var PACKAGE = {
10660
10786
  version: version};
10661
10787
 
@@ -10785,7 +10911,8 @@ class WebsocketClientTransport {
10785
10911
  removeListeners();
10786
10912
  resolve(new WebsocketDuplexConnectionExports.WebsocketDuplexConnection(websocket, new distExports.Deserializer(), multiplexerDemultiplexerFactory));
10787
10913
  };
10788
- const errorListener = (ev) => {
10914
+ const errorListener = (event) => {
10915
+ const ev = event;
10789
10916
  removeListeners();
10790
10917
  // We add a default error in that case.
10791
10918
  if (ev.error != null) {
@@ -11028,7 +11155,13 @@ const SOCKET_TIMEOUT_MS = 30_000;
11028
11155
  // If there is a backlog of messages (for example on slow connections), keepalive messages could be delayed
11029
11156
  // significantly. Therefore this is longer than the socket timeout.
11030
11157
  const KEEP_ALIVE_LIFETIME_MS = 90_000;
11158
+ /**
11159
+ * @internal
11160
+ */
11031
11161
  const DEFAULT_REMOTE_LOGGER = Logger.get('PowerSyncRemote');
11162
+ /**
11163
+ * @public
11164
+ */
11032
11165
  exports.FetchStrategy = void 0;
11033
11166
  (function (FetchStrategy) {
11034
11167
  /**
@@ -11047,12 +11180,17 @@ exports.FetchStrategy = void 0;
11047
11180
  * The class wrapper is used to distinguish the fetchImplementation
11048
11181
  * option in [AbstractRemoteOptions] from the general fetch method
11049
11182
  * which is typeof "function"
11183
+ *
11184
+ * @internal
11050
11185
  */
11051
11186
  class FetchImplementationProvider {
11052
11187
  getFetch() {
11053
11188
  throw new Error('Unspecified fetch implementation');
11054
11189
  }
11055
11190
  }
11191
+ /**
11192
+ * @internal
11193
+ */
11056
11194
  const DEFAULT_REMOTE_OPTIONS = {
11057
11195
  socketUrlTransformer: (url) => url.replace(/^https?:\/\//, function (match) {
11058
11196
  return match === 'https://' ? 'wss://' : 'ws://';
@@ -11060,6 +11198,9 @@ const DEFAULT_REMOTE_OPTIONS = {
11060
11198
  fetchImplementation: new FetchImplementationProvider(),
11061
11199
  fetchOptions: {}
11062
11200
  };
11201
+ /**
11202
+ * @internal
11203
+ */
11063
11204
  class AbstractRemote {
11064
11205
  connector;
11065
11206
  logger;
@@ -11469,7 +11610,7 @@ class AbstractRemote {
11469
11610
  * Posts a `/sync/stream` request.
11470
11611
  *
11471
11612
  * Depending on the `Content-Type` of the response, this returns strings for sync lines or encoded BSON documents as
11472
- * {@link Uint8Array}s.
11613
+ * `Uint8Array`s.
11473
11614
  */
11474
11615
  async fetchStream(options) {
11475
11616
  const { isBson, stream } = await this.fetchStreamRaw(options);
@@ -11511,16 +11652,26 @@ function isInterruptingInstruction(instruction) {
11511
11652
  return 'EstablishSyncStream' in instruction || 'CloseSyncStream' in instruction;
11512
11653
  }
11513
11654
 
11655
+ /**
11656
+ * @internal
11657
+ */
11514
11658
  exports.LockType = void 0;
11515
11659
  (function (LockType) {
11516
11660
  LockType["CRUD"] = "crud";
11517
11661
  LockType["SYNC"] = "sync";
11518
11662
  })(exports.LockType || (exports.LockType = {}));
11663
+ /**
11664
+ * @public
11665
+ */
11519
11666
  exports.SyncStreamConnectionMethod = void 0;
11520
11667
  (function (SyncStreamConnectionMethod) {
11521
11668
  SyncStreamConnectionMethod["HTTP"] = "http";
11522
11669
  SyncStreamConnectionMethod["WEB_SOCKET"] = "web-socket";
11523
11670
  })(exports.SyncStreamConnectionMethod || (exports.SyncStreamConnectionMethod = {}));
11671
+ /**
11672
+ * @deprecated Deprecated since {@link SyncClientImplementation.RUST} is the only option.
11673
+ * @public
11674
+ */
11524
11675
  exports.SyncClientImplementation = void 0;
11525
11676
  (function (SyncClientImplementation) {
11526
11677
  /**
@@ -11532,8 +11683,8 @@ exports.SyncClientImplementation = void 0;
11532
11683
  * ## Compatibility warning
11533
11684
  *
11534
11685
  * The Rust sync client stores sync data in a format that is slightly different than the one used
11535
- * by the old JavaScript client. When adopting the {@link RUST} client on existing databases, the PowerSync SDK will
11536
- * migrate the format automatically.
11686
+ * by the old JavaScript client. When adopting the {@link SyncClientImplementation.RUST} client on existing databases,
11687
+ * the PowerSync SDK will migrate the format automatically.
11537
11688
  *
11538
11689
  * SDK versions supporting both the JavaScript and the Rust client support both formats with the JavaScript client
11539
11690
  * implementaiton. However, downgrading to an SDK version that only supports the JavaScript client would not be
@@ -11543,14 +11694,29 @@ exports.SyncClientImplementation = void 0;
11543
11694
  })(exports.SyncClientImplementation || (exports.SyncClientImplementation = {}));
11544
11695
  /**
11545
11696
  * The default {@link SyncClientImplementation} to use, {@link SyncClientImplementation.RUST}.
11697
+ *
11698
+ * @deprecated Deprecated since {@link SyncClientImplementation.RUST} is the only option.
11699
+ * @public
11546
11700
  */
11547
11701
  const DEFAULT_SYNC_CLIENT_IMPLEMENTATION = exports.SyncClientImplementation.RUST;
11702
+ /**
11703
+ * @internal
11704
+ */
11548
11705
  const DEFAULT_CRUD_UPLOAD_THROTTLE_MS = 1000;
11706
+ /**
11707
+ * @internal
11708
+ */
11549
11709
  const DEFAULT_RETRY_DELAY_MS = 5000;
11710
+ /**
11711
+ * @internal
11712
+ */
11550
11713
  const DEFAULT_STREAMING_SYNC_OPTIONS = {
11551
11714
  retryDelayMs: DEFAULT_RETRY_DELAY_MS,
11552
11715
  crudUploadThrottleMs: DEFAULT_CRUD_UPLOAD_THROTTLE_MS
11553
11716
  };
11717
+ /**
11718
+ * @internal
11719
+ */
11554
11720
  const DEFAULT_STREAM_CONNECTION_OPTIONS = {
11555
11721
  appMetadata: {},
11556
11722
  connectionMethod: exports.SyncStreamConnectionMethod.WEB_SOCKET,
@@ -11560,22 +11726,21 @@ const DEFAULT_STREAM_CONNECTION_OPTIONS = {
11560
11726
  serializedSchema: undefined,
11561
11727
  includeDefaultStreams: true
11562
11728
  };
11729
+ /**
11730
+ * @internal
11731
+ */
11563
11732
  class AbstractStreamingSyncImplementation extends BaseObserver {
11564
11733
  options;
11565
11734
  abortController;
11566
- // In rare cases, mostly for tests, uploads can be triggered without being properly connected.
11567
- // This allows ensuring that all upload processes can be aborted.
11568
- uploadAbortController;
11569
11735
  crudUpdateListener;
11570
11736
  streamingSyncPromise;
11571
11737
  logger;
11572
11738
  activeStreams;
11573
11739
  connectionMayHaveChanged = false;
11574
- isUploadingCrud = false;
11740
+ crudUploadNotifier = asyncNotifier();
11575
11741
  notifyCompletedUploads;
11576
11742
  handleActiveStreamsChange;
11577
11743
  syncStatus;
11578
- triggerCrudUpload;
11579
11744
  constructor(options) {
11580
11745
  super();
11581
11746
  this.options = options;
@@ -11591,16 +11756,9 @@ class AbstractStreamingSyncImplementation extends BaseObserver {
11591
11756
  }
11592
11757
  });
11593
11758
  this.abortController = null;
11594
- this.triggerCrudUpload = throttleLeadingTrailing(() => {
11595
- if (!this.syncStatus.connected || this.isUploadingCrud) {
11596
- return;
11597
- }
11598
- this.isUploadingCrud = true;
11599
- this._uploadAllCrud().finally(() => {
11600
- this.notifyCompletedUploads?.();
11601
- this.isUploadingCrud = false;
11602
- });
11603
- }, this.options.crudUploadThrottleMs);
11759
+ }
11760
+ triggerCrudUpload() {
11761
+ this.crudUploadNotifier.notify();
11604
11762
  }
11605
11763
  async waitForReady() { }
11606
11764
  waitForStatus(status) {
@@ -11648,7 +11806,6 @@ class AbstractStreamingSyncImplementation extends BaseObserver {
11648
11806
  super.dispose();
11649
11807
  this.crudUpdateListener?.();
11650
11808
  this.crudUpdateListener = undefined;
11651
- this.uploadAbortController?.abort();
11652
11809
  }
11653
11810
  async getWriteCheckpoint() {
11654
11811
  const clientId = await this.options.adapter.getClientId();
@@ -11658,7 +11815,17 @@ class AbstractStreamingSyncImplementation extends BaseObserver {
11658
11815
  this.logger.debug(`Created write checkpoint: ${checkpoint}`);
11659
11816
  return checkpoint;
11660
11817
  }
11661
- async _uploadAllCrud() {
11818
+ async crudUploadLoop(signal) {
11819
+ while (!signal.aborted) {
11820
+ await Promise.all([
11821
+ // Start the initial CRUD upload on connect. Then, keep polling until we're done.
11822
+ this._uploadAllCrud(signal),
11823
+ this.delayRetry(signal, this.options.crudUploadThrottleMs)
11824
+ ]);
11825
+ await this.crudUploadNotifier.waitForNotification(signal);
11826
+ }
11827
+ }
11828
+ async _uploadAllCrud(signal) {
11662
11829
  return this.obtainLock({
11663
11830
  type: exports.LockType.CRUD,
11664
11831
  callback: async () => {
@@ -11666,12 +11833,7 @@ class AbstractStreamingSyncImplementation extends BaseObserver {
11666
11833
  * Keep track of the first item in the CRUD queue for the last `uploadCrud` iteration.
11667
11834
  */
11668
11835
  let checkedCrudItem;
11669
- const controller = new AbortController();
11670
- this.uploadAbortController = controller;
11671
- this.abortController?.signal.addEventListener('abort', () => {
11672
- controller.abort();
11673
- }, { once: true });
11674
- while (!controller.signal.aborted) {
11836
+ while (!signal.aborted) {
11675
11837
  try {
11676
11838
  /**
11677
11839
  * This is the first item in the FIFO CRUD queue.
@@ -11701,7 +11863,10 @@ The next upload iteration will be delayed.`);
11701
11863
  else {
11702
11864
  // Uploading is completed
11703
11865
  const neededUpdate = await this.options.adapter.updateLocalTarget(() => this.getWriteCheckpoint());
11704
- if (neededUpdate == false && checkedCrudItem != null) {
11866
+ if (neededUpdate) {
11867
+ this.notifyCompletedUploads?.();
11868
+ }
11869
+ else if (checkedCrudItem != null) {
11705
11870
  // Only log this if there was something to upload
11706
11871
  this.logger.debug('Upload complete, no write checkpoint needed.');
11707
11872
  }
@@ -11716,7 +11881,7 @@ The next upload iteration will be delayed.`);
11716
11881
  uploadError: ex
11717
11882
  }
11718
11883
  });
11719
- await this.delayRetry(controller.signal);
11884
+ await this.delayRetry(signal);
11720
11885
  if (!this.isConnected) {
11721
11886
  // Exit the upload loop if the sync stream is no longer connected
11722
11887
  break;
@@ -11731,7 +11896,6 @@ The next upload iteration will be delayed.`);
11731
11896
  });
11732
11897
  }
11733
11898
  }
11734
- this.uploadAbortController = undefined;
11735
11899
  }
11736
11900
  });
11737
11901
  }
@@ -11741,7 +11905,10 @@ The next upload iteration will be delayed.`);
11741
11905
  }
11742
11906
  const controller = new AbortController();
11743
11907
  this.abortController = controller;
11744
- this.streamingSyncPromise = this.streamingSync(this.abortController.signal, options);
11908
+ this.streamingSyncPromise = Promise.all([
11909
+ this.crudUploadLoop(controller.signal).catch((ex) => this.logger.error('Error in crud upload loop', ex)),
11910
+ this.streamingSync(controller.signal, options)
11911
+ ]);
11745
11912
  // Return a promise that resolves when the connection status is updated to indicate that we're connected.
11746
11913
  return new Promise((resolve) => {
11747
11914
  const disposer = this.registerListener({
@@ -11779,14 +11946,7 @@ The next upload iteration will be delayed.`);
11779
11946
  this.abortController = null;
11780
11947
  this.updateSyncStatus({ connected: false, connecting: false });
11781
11948
  }
11782
- /**
11783
- * @deprecated use [connect instead]
11784
- */
11785
11949
  async streamingSync(signal, options) {
11786
- if (!signal) {
11787
- this.abortController = new AbortController();
11788
- signal = this.abortController.signal;
11789
- }
11790
11950
  /**
11791
11951
  * Listen for CRUD updates and trigger upstream uploads
11792
11952
  */
@@ -11892,7 +12052,7 @@ The next upload iteration will be delayed.`);
11892
12052
  this.handleActiveStreamsChange?.();
11893
12053
  }
11894
12054
  /**
11895
- * Older versions of the JS SDK used to encode subkeys as JSON in {@link OplogEntry.toJSON}.
12055
+ * Older versions of the JS SDK used to encode subkeys as JSON in `OplogEntry.toJSON`.
11896
12056
  * Because subkeys are always strings, this leads to quotes being added around them in `ps_oplog`.
11897
12057
  * While this is not a problem as long as it's done consistently, it causes issues when a database
11898
12058
  * created by the JS SDK is used with other SDKs, or (more likely) when the new Rust sync client
@@ -11902,7 +12062,7 @@ The next upload iteration will be delayed.`);
11902
12062
  * migration is only triggered when necessary (for now). The function returns whether the new format
11903
12063
  * should be used, so that the JS SDK is able to write to updated databases.
11904
12064
  *
11905
- * @param requireFixedKeyFormat Whether we require the new format or also support the old one.
12065
+ * @param requireFixedKeyFormat - Whether we require the new format or also support the old one.
11906
12066
  * The Rust client requires the new subkey format.
11907
12067
  * @returns Whether the database is now using the new, fixed subkey format.
11908
12068
  */
@@ -12160,14 +12320,13 @@ The next upload iteration will be delayed.`);
12160
12320
  // trigger this for all updates
12161
12321
  this.iterateListeners((cb) => cb.statusUpdated?.(options));
12162
12322
  }
12163
- async delayRetry(signal) {
12323
+ async delayRetry(signal, delay = this.options.retryDelayMs) {
12164
12324
  return new Promise((resolve) => {
12165
12325
  if (signal?.aborted) {
12166
12326
  // If the signal is already aborted, resolve immediately
12167
12327
  resolve();
12168
12328
  return;
12169
12329
  }
12170
- const { retryDelayMs } = this.options;
12171
12330
  let timeoutId;
12172
12331
  const endDelay = () => {
12173
12332
  resolve();
@@ -12178,7 +12337,7 @@ The next upload iteration will be delayed.`);
12178
12337
  signal?.removeEventListener('abort', endDelay);
12179
12338
  };
12180
12339
  signal?.addEventListener('abort', endDelay, { once: true });
12181
- timeoutId = setTimeout(endDelay, retryDelayMs);
12340
+ timeoutId = setTimeout(endDelay, delay);
12182
12341
  });
12183
12342
  }
12184
12343
  updateSubscriptions(subscriptions) {
@@ -12210,7 +12369,8 @@ const MEMORY_TRIGGER_CLAIM_MANAGER = {
12210
12369
 
12211
12370
  /**
12212
12371
  * SQLite operations to track changes for with {@link TriggerManager}
12213
- * @experimental
12372
+ *
12373
+ * @experimental @alpha
12214
12374
  */
12215
12375
  exports.DiffTriggerOperation = void 0;
12216
12376
  (function (DiffTriggerOperation) {
@@ -12272,8 +12432,8 @@ class TriggerManagerImpl {
12272
12432
  get db() {
12273
12433
  return this.options.db;
12274
12434
  }
12275
- async getUUID() {
12276
- const { id: uuid } = await this.db.get(/* sql */ `
12435
+ async getUUID(ctx) {
12436
+ const { id: uuid } = await (ctx ?? this.db).get(/* sql */ `
12277
12437
  SELECT
12278
12438
  uuid () as id
12279
12439
  `);
@@ -12386,7 +12546,7 @@ class TriggerManagerImpl {
12386
12546
  const replicatedColumns = columns ?? sourceDefinition.columns.map((col) => col.name);
12387
12547
  const internalSource = sourceDefinition.internalName;
12388
12548
  const triggerIds = [];
12389
- const id = await this.getUUID();
12549
+ const id = await this.getUUID(setupContext);
12390
12550
  const releaseStorageClaim = useStorage ? await this.options.claimManager.obtainClaim(id) : null;
12391
12551
  /**
12392
12552
  * We default to replicating all columns if no columns array is provided.
@@ -12626,18 +12786,29 @@ const POWERSYNC_TABLE_MATCH = /(^ps_data__|^ps_data_local__)/;
12626
12786
  const DEFAULT_DISCONNECT_CLEAR_OPTIONS = {
12627
12787
  clearLocal: true
12628
12788
  };
12789
+ /**
12790
+ * @internal
12791
+ */
12629
12792
  const DEFAULT_POWERSYNC_CLOSE_OPTIONS = {
12630
12793
  disconnect: true
12631
12794
  };
12795
+ /**
12796
+ * @internal
12797
+ */
12632
12798
  const DEFAULT_POWERSYNC_DB_OPTIONS = {
12633
12799
  retryDelayMs: 5000,
12634
12800
  crudUploadThrottleMs: DEFAULT_CRUD_UPLOAD_THROTTLE_MS
12635
12801
  };
12802
+ /**
12803
+ * @internal
12804
+ */
12636
12805
  const DEFAULT_CRUD_BATCH_LIMIT = 100;
12637
12806
  /**
12638
12807
  * Requesting nested or recursive locks can block the application in some circumstances.
12639
12808
  * This default lock timeout will act as a failsafe to throw an error if a lock cannot
12640
12809
  * be obtained.
12810
+ *
12811
+ * @internal
12641
12812
  */
12642
12813
  const DEFAULT_LOCK_TIMEOUT_MS = 120_000; // 2 mins
12643
12814
  /**
@@ -12647,6 +12818,9 @@ const DEFAULT_LOCK_TIMEOUT_MS = 120_000; // 2 mins
12647
12818
  const isPowerSyncDatabaseOptionsWithSettings = (test) => {
12648
12819
  return typeof test == 'object' && isSQLOpenOptions(test.database);
12649
12820
  };
12821
+ /**
12822
+ * @public
12823
+ */
12650
12824
  class AbstractPowerSyncDatabase extends BaseObserver {
12651
12825
  options;
12652
12826
  /**
@@ -12804,7 +12978,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
12804
12978
  /**
12805
12979
  * Wait for the first sync operation to complete.
12806
12980
  *
12807
- * @param request Either an abort signal (after which the promise will complete regardless of
12981
+ * @param request - Either an abort signal (after which the promise will complete regardless of
12808
12982
  * whether a full sync was completed) or an object providing an abort signal and a priority target.
12809
12983
  * When a priority target is set, the promise may complete when all buckets with the given (or higher)
12810
12984
  * priorities have been synchronized. This can be earlier than a complete sync.
@@ -12959,7 +13133,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
12959
13133
  /**
12960
13134
  * Close the sync connection.
12961
13135
  *
12962
- * Use {@link connect} to connect again.
13136
+ * Use {@link AbstractPowerSyncDatabase.connect} to connect again.
12963
13137
  */
12964
13138
  async disconnect() {
12965
13139
  return this.connectionManager.disconnect();
@@ -12986,8 +13160,8 @@ class AbstractPowerSyncDatabase extends BaseObserver {
12986
13160
  /**
12987
13161
  * Create a sync stream to query its status or to subscribe to it.
12988
13162
  *
12989
- * @param name The name of the stream to subscribe to.
12990
- * @param params Optional parameters for the stream subscription.
13163
+ * @param name - The name of the stream to subscribe to.
13164
+ * @param params - Optional parameters for the stream subscription.
12991
13165
  * @returns A {@link SyncStream} instance that can be subscribed to.
12992
13166
  * @experimental Sync streams are currently in alpha.
12993
13167
  */
@@ -13045,14 +13219,14 @@ class AbstractPowerSyncDatabase extends BaseObserver {
13045
13219
  * Once the data have been successfully uploaded, call {@link CrudBatch.complete} before
13046
13220
  * requesting the next batch.
13047
13221
  *
13048
- * Use {@link limit} to specify the maximum number of updates to return in a single
13222
+ * Use the `limit` parameter to specify the maximum number of updates to return in a single
13049
13223
  * batch.
13050
13224
  *
13051
13225
  * This method does include transaction ids in the result, but does not group
13052
13226
  * data by transaction. One batch may contain data from multiple transactions,
13053
13227
  * and a single transaction may be split over multiple batches.
13054
13228
  *
13055
- * @param limit Maximum number of CRUD entries to include in the batch
13229
+ * @param limit - Maximum number of CRUD entries to include in the batch
13056
13230
  * @returns A batch of CRUD operations to upload, or null if there are none
13057
13231
  */
13058
13232
  async getCrudBatch(limit = DEFAULT_CRUD_BATCH_LIMIT) {
@@ -13079,7 +13253,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
13079
13253
  * Once the data have been successfully uploaded, call {@link CrudTransaction.complete} before
13080
13254
  * requesting the next transaction.
13081
13255
  *
13082
- * Unlike {@link getCrudBatch}, this only returns data from a single transaction at a time.
13256
+ * Unlike {@link AbstractPowerSyncDatabase.getCrudBatch}, this only returns data from a single transaction at a time.
13083
13257
  * All data for the transaction is loaded into memory.
13084
13258
  *
13085
13259
  * @returns A transaction of CRUD operations to upload, or null if there are none
@@ -13094,7 +13268,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
13094
13268
  * This is typically used from the {@link PowerSyncBackendConnector.uploadData} callback. Each entry emitted by the
13095
13269
  * returned iterator is a full transaction containing all local writes made while that transaction was active.
13096
13270
  *
13097
- * Unlike {@link getNextCrudTransaction}, which always returns the oldest transaction that hasn't been
13271
+ * Unlike {@link AbstractPowerSyncDatabase.getNextCrudTransaction}, which always returns the oldest transaction that hasn't been
13098
13272
  * {@link CrudTransaction.complete}d yet, this iterator can be used to receive multiple transactions. Calling
13099
13273
  * {@link CrudTransaction.complete} will mark that and all prior transactions emitted by the iterator as completed.
13100
13274
  *
@@ -13188,8 +13362,8 @@ SELECT * FROM crud_entries;
13188
13362
  * the returned result's `rowsAffected` may be `0` for successful `UPDATE` and `DELETE` statements.
13189
13363
  * Use a `RETURNING` clause and inspect `result.rows` when you need to confirm which rows changed.
13190
13364
  *
13191
- * @param sql The SQL query to execute
13192
- * @param parameters Optional array of parameters to bind to the query
13365
+ * @param sql - The SQL query to execute
13366
+ * @param parameters - Optional array of parameters to bind to the query
13193
13367
  * @returns The query result as an object with structured key-value pairs
13194
13368
  */
13195
13369
  async execute(sql, parameters) {
@@ -13199,8 +13373,8 @@ SELECT * FROM crud_entries;
13199
13373
  * Execute a SQL write (INSERT/UPDATE/DELETE) query directly on the database without any PowerSync processing.
13200
13374
  * This bypasses certain PowerSync abstractions and is useful for accessing the raw database results.
13201
13375
  *
13202
- * @param sql The SQL query to execute
13203
- * @param parameters Optional array of parameters to bind to the query
13376
+ * @param sql - The SQL query to execute
13377
+ * @param parameters - Optional array of parameters to bind to the query
13204
13378
  * @returns The raw query result from the underlying database as a nested array of raw values, where each row is
13205
13379
  * represented as an array of column values without field names.
13206
13380
  */
@@ -13213,8 +13387,8 @@ SELECT * FROM crud_entries;
13213
13387
  * and optionally return results.
13214
13388
  * This is faster than executing separately with each parameter set.
13215
13389
  *
13216
- * @param sql The SQL query to execute
13217
- * @param parameters Optional 2D array of parameter sets, where each inner array is a set of parameters for one execution
13390
+ * @param sql - The SQL query to execute
13391
+ * @param parameters - Optional 2D array of parameter sets, where each inner array is a set of parameters for one execution
13218
13392
  * @returns The query result
13219
13393
  */
13220
13394
  async executeBatch(sql, parameters) {
@@ -13224,8 +13398,8 @@ SELECT * FROM crud_entries;
13224
13398
  /**
13225
13399
  * Execute a read-only query and return results.
13226
13400
  *
13227
- * @param sql The SQL query to execute
13228
- * @param parameters Optional array of parameters to bind to the query
13401
+ * @param sql - The SQL query to execute
13402
+ * @param parameters - Optional array of parameters to bind to the query
13229
13403
  * @returns An array of results
13230
13404
  */
13231
13405
  async getAll(sql, parameters) {
@@ -13235,8 +13409,8 @@ SELECT * FROM crud_entries;
13235
13409
  /**
13236
13410
  * Execute a read-only query and return the first result, or null if the ResultSet is empty.
13237
13411
  *
13238
- * @param sql The SQL query to execute
13239
- * @param parameters Optional array of parameters to bind to the query
13412
+ * @param sql - The SQL query to execute
13413
+ * @param parameters - Optional array of parameters to bind to the query
13240
13414
  * @returns The first result if found, or null if no results are returned
13241
13415
  */
13242
13416
  async getOptional(sql, parameters) {
@@ -13246,8 +13420,8 @@ SELECT * FROM crud_entries;
13246
13420
  /**
13247
13421
  * Execute a read-only query and return the first result, error if the ResultSet is empty.
13248
13422
  *
13249
- * @param sql The SQL query to execute
13250
- * @param parameters Optional array of parameters to bind to the query
13423
+ * @param sql - The SQL query to execute
13424
+ * @param parameters - Optional array of parameters to bind to the query
13251
13425
  * @returns The first result matching the query
13252
13426
  * @throws Error if no rows are returned
13253
13427
  */
@@ -13257,7 +13431,7 @@ SELECT * FROM crud_entries;
13257
13431
  }
13258
13432
  /**
13259
13433
  * Takes a read lock, without starting a transaction.
13260
- * In most cases, {@link readTransaction} should be used instead.
13434
+ * In most cases, {@link AbstractPowerSyncDatabase.readTransaction} should be used instead.
13261
13435
  */
13262
13436
  async readLock(callback) {
13263
13437
  await this.waitForReady();
@@ -13265,7 +13439,7 @@ SELECT * FROM crud_entries;
13265
13439
  }
13266
13440
  /**
13267
13441
  * Takes a global lock, without starting a transaction.
13268
- * In most cases, {@link writeTransaction} should be used instead.
13442
+ * In most cases, {@link AbstractPowerSyncDatabase.writeTransaction} should be used instead.
13269
13443
  */
13270
13444
  async writeLock(callback) {
13271
13445
  await this.waitForReady();
@@ -13276,8 +13450,8 @@ SELECT * FROM crud_entries;
13276
13450
  * Read transactions can run concurrently to a write transaction.
13277
13451
  * Changes from any write transaction are not visible to read transactions started before it.
13278
13452
  *
13279
- * @param callback Function to execute within the transaction
13280
- * @param lockTimeout Time in milliseconds to wait for a lock before throwing an error
13453
+ * @param callback - Function to execute within the transaction
13454
+ * @param lockTimeout - Time in milliseconds to wait for a lock before throwing an error
13281
13455
  * @returns The result of the callback
13282
13456
  * @throws Error if the lock cannot be obtained within the timeout period
13283
13457
  */
@@ -13294,8 +13468,8 @@ SELECT * FROM crud_entries;
13294
13468
  * This takes a global lock - only one write transaction can execute against the database at a time.
13295
13469
  * Statements within the transaction must be done on the provided {@link Transaction} interface.
13296
13470
  *
13297
- * @param callback Function to execute within the transaction
13298
- * @param lockTimeout Time in milliseconds to wait for a lock before throwing an error
13471
+ * @param callback - Function to execute within the transaction
13472
+ * @param lockTimeout - Time in milliseconds to wait for a lock before throwing an error
13299
13473
  * @returns The result of the callback
13300
13474
  * @throws Error if the lock cannot be obtained within the timeout period
13301
13475
  */
@@ -13372,15 +13546,15 @@ SELECT * FROM crud_entries;
13372
13546
  }
13373
13547
  /**
13374
13548
  * Execute a read query every time the source tables are modified.
13375
- * Use {@link SQLWatchOptions.throttleMs} to specify the minimum interval between queries.
13549
+ * Use {@link SQLOnChangeOptions.throttleMs} to specify the minimum interval between queries.
13376
13550
  * Source tables are automatically detected using `EXPLAIN QUERY PLAN`.
13377
13551
  *
13378
13552
  * Note that the `onChange` callback member of the handler is required.
13379
13553
  *
13380
- * @param sql The SQL query to execute
13381
- * @param parameters Optional array of parameters to bind to the query
13382
- * @param handler Callbacks for handling results and errors
13383
- * @param options Options for configuring watch behavior
13554
+ * @param sql - The SQL query to execute
13555
+ * @param parameters - Optional array of parameters to bind to the query
13556
+ * @param handler - Callbacks for handling results and errors
13557
+ * @param options - Options for configuring watch behavior
13384
13558
  */
13385
13559
  watchWithCallback(sql, parameters, handler, options) {
13386
13560
  const { onResult, onError = (e) => this.logger.error(e) } = handler ?? {};
@@ -13393,7 +13567,7 @@ SELECT * FROM crud_entries;
13393
13567
  const watchedQuery = new OnChangeQueryProcessor({
13394
13568
  db: this,
13395
13569
  comparator,
13396
- placeholderData: null,
13570
+ placeholderData: null, // FIXME
13397
13571
  watchOptions: {
13398
13572
  query: {
13399
13573
  compile: () => ({
@@ -13426,12 +13600,12 @@ SELECT * FROM crud_entries;
13426
13600
  }
13427
13601
  /**
13428
13602
  * Execute a read query every time the source tables are modified.
13429
- * Use {@link SQLWatchOptions.throttleMs} to specify the minimum interval between queries.
13603
+ * Use {@link SQLOnChangeOptions.throttleMs} to specify the minimum interval between queries.
13430
13604
  * Source tables are automatically detected using `EXPLAIN QUERY PLAN`.
13431
13605
  *
13432
- * @param sql The SQL query to execute
13433
- * @param parameters Optional array of parameters to bind to the query
13434
- * @param options Options for configuring watch behavior
13606
+ * @param sql - The SQL query to execute
13607
+ * @param parameters - Optional array of parameters to bind to the query
13608
+ * @param options - Options for configuring watch behavior
13435
13609
  * @returns An AsyncIterable that yields QueryResults whenever the data changes
13436
13610
  */
13437
13611
  watchWithAsyncGenerator(sql, parameters, options) {
@@ -13455,9 +13629,9 @@ SELECT * FROM crud_entries;
13455
13629
  * If tables are specified in the options, those are used directly.
13456
13630
  * Otherwise, analyzes the query using EXPLAIN to determine which tables are accessed.
13457
13631
  *
13458
- * @param sql The SQL query to analyze
13459
- * @param parameters Optional parameters for the SQL query
13460
- * @param options Optional watch options that may contain explicit table list
13632
+ * @param sql - The SQL query to analyze
13633
+ * @param parameters - Optional parameters for the SQL query
13634
+ * @param options - Optional watch options that may contain explicit table list
13461
13635
  * @returns Array of table names that the query depends on
13462
13636
  */
13463
13637
  async resolveTables(sql, parameters, options) {
@@ -13486,13 +13660,13 @@ SELECT * FROM crud_entries;
13486
13660
  /**
13487
13661
  * Invoke the provided callback on any changes to any of the specified tables.
13488
13662
  *
13489
- * This is preferred over {@link watchWithCallback} when multiple queries need to be performed
13663
+ * This is preferred over {@link AbstractPowerSyncDatabase.watchWithCallback} when multiple queries need to be performed
13490
13664
  * together when data is changed.
13491
13665
  *
13492
13666
  * Note that the `onChange` callback member of the handler is required.
13493
13667
  *
13494
- * @param handler Callbacks for handling change events and errors
13495
- * @param options Options for configuring watch behavior
13668
+ * @param handler - Callbacks for handling change events and errors
13669
+ * @param options - Options for configuring watch behavior
13496
13670
  * @returns A dispose function to stop watching for changes
13497
13671
  */
13498
13672
  onChangeWithCallback(handler, options) {
@@ -13535,12 +13709,12 @@ SELECT * FROM crud_entries;
13535
13709
  /**
13536
13710
  * Create a Stream of changes to any of the specified tables.
13537
13711
  *
13538
- * This is preferred over {@link watchWithAsyncGenerator} when multiple queries need to be performed
13539
- * together when data is changed.
13712
+ * This is preferred over {@link AbstractPowerSyncDatabase.watchWithAsyncGenerator} when multiple queries need to be
13713
+ * performed together when data is changed.
13540
13714
  *
13541
13715
  * Note: do not declare this as `async *onChange` as it will not work in React Native.
13542
13716
  *
13543
- * @param options Options for configuring watch behavior
13717
+ * @param options - Options for configuring watch behavior
13544
13718
  * @returns An AsyncIterable that yields change events whenever the specified tables change
13545
13719
  */
13546
13720
  onChangeWithAsyncGenerator(options) {
@@ -13578,15 +13752,15 @@ SELECT * FROM crud_entries;
13578
13752
  changedTables.add(table);
13579
13753
  }
13580
13754
  }
13581
- /**
13582
- * @ignore
13583
- */
13584
13755
  async executeReadOnly(sql, params) {
13585
13756
  await this.waitForReady();
13586
13757
  return this.database.readLock((tx) => tx.execute(sql, params));
13587
13758
  }
13588
13759
  }
13589
13760
 
13761
+ /**
13762
+ * @internal
13763
+ */
13590
13764
  class AbstractPowerSyncDatabaseOpenFactory {
13591
13765
  options;
13592
13766
  constructor(options) {
@@ -13611,6 +13785,9 @@ class AbstractPowerSyncDatabaseOpenFactory {
13611
13785
  }
13612
13786
  }
13613
13787
 
13788
+ /**
13789
+ * @internal
13790
+ */
13614
13791
  function runOnSchemaChange(callback, db, options) {
13615
13792
  const triggerWatchedQuery = () => {
13616
13793
  const abortController = new AbortController();
@@ -13635,6 +13812,9 @@ function runOnSchemaChange(callback, db, options) {
13635
13812
  triggerWatchedQuery();
13636
13813
  }
13637
13814
 
13815
+ /**
13816
+ * @public
13817
+ */
13638
13818
  function compilableQueryWatch(db, query, handler, options) {
13639
13819
  const { onResult, onError = (e) => { } } = handler ?? {};
13640
13820
  if (!onResult) {
@@ -13672,8 +13852,14 @@ function compilableQueryWatch(db, query, handler, options) {
13672
13852
  runOnSchemaChange(watchQuery, db, options);
13673
13853
  }
13674
13854
 
13855
+ /**
13856
+ * @internal
13857
+ */
13675
13858
  const MAX_OP_ID = '9223372036854775807';
13676
13859
 
13860
+ /**
13861
+ * @internal
13862
+ */
13677
13863
  class SqliteBucketStorage extends BaseObserver {
13678
13864
  db;
13679
13865
  logger;
@@ -13834,6 +14020,8 @@ class SqliteBucketStorage extends BaseObserver {
13834
14020
  * Thrown when an underlying database connection is closed.
13835
14021
  * This is particularly relevant when worker connections are marked as closed while
13836
14022
  * operations are still in progress.
14023
+ *
14024
+ * @internal
13837
14025
  */
13838
14026
  class ConnectionClosedError extends Error {
13839
14027
  static NAME = 'ConnectionClosedError';
@@ -13853,6 +14041,8 @@ class ConnectionClosedError extends Error {
13853
14041
 
13854
14042
  /**
13855
14043
  * A schema is a collection of tables. It is used to define the structure of a database.
14044
+ *
14045
+ * @public
13856
14046
  */
13857
14047
  class Schema {
13858
14048
  /*
@@ -13889,7 +14079,7 @@ class Schema {
13889
14079
  * Since raw tables are not backed by JSON, running complex queries on them may be more efficient. Further, they allow
13890
14080
  * using client-side table and column constraints.
13891
14081
  *
13892
- * @param tables An object of (table name, raw table definition) entries.
14082
+ * @param tables - An object of (table name, raw table definition) entries.
13893
14083
  */
13894
14084
  withRawTables(tables) {
13895
14085
  for (const [name, rawTableDefinition] of Object.entries(tables)) {
@@ -13935,6 +14125,8 @@ class Schema {
13935
14125
  Generate a new table from the columns and indexes
13936
14126
  @deprecated You should use {@link Table} instead as it now allows TableV2 syntax.
13937
14127
  This will be removed in the next major release.
14128
+
14129
+ @public
13938
14130
  */
13939
14131
  class TableV2 extends Table {
13940
14132
  }
@@ -13945,6 +14137,8 @@ function sanitizeString(input) {
13945
14137
  /**
13946
14138
  * Helper function for sanitizing UUID input strings.
13947
14139
  * Typically used with {@link sanitizeSQL}.
14140
+ *
14141
+ * @alpha
13948
14142
  */
13949
14143
  function sanitizeUUID(uuid) {
13950
14144
  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;
@@ -13981,6 +14175,8 @@ function sanitizeUUID(uuid) {
13981
14175
  * // Incorrect:
13982
14176
  * sanitizeSQL`New.id = '${myID}'` // Produces double quotes: New.id = ''O''Reilly''
13983
14177
  * ```
14178
+ *
14179
+ * @alpha
13984
14180
  */
13985
14181
  function sanitizeSQL(strings, ...values) {
13986
14182
  let result = '';
@@ -14010,6 +14206,8 @@ function sanitizeSQL(strings, ...values) {
14010
14206
 
14011
14207
  /**
14012
14208
  * Performs a {@link AbstractPowerSyncDatabase.getAll} operation for a watched query.
14209
+ *
14210
+ * @public
14013
14211
  */
14014
14212
  class GetAllQuery {
14015
14213
  options;
@@ -14034,6 +14232,9 @@ class GetAllQuery {
14034
14232
  }
14035
14233
 
14036
14234
  const TypedLogger = Logger;
14235
+ /**
14236
+ * @public
14237
+ */
14037
14238
  const LogLevel = {
14038
14239
  TRACE: TypedLogger.TRACE,
14039
14240
  DEBUG: TypedLogger.DEBUG,
@@ -14050,6 +14251,7 @@ const LogLevel = {
14050
14251
  * across all loggers created with `createLogger`. Adjusting settings on this
14051
14252
  * base logger affects all loggers derived from it unless explicitly overridden.
14052
14253
  *
14254
+ * @public
14053
14255
  */
14054
14256
  function createBaseLogger() {
14055
14257
  return Logger;
@@ -14060,6 +14262,8 @@ function createBaseLogger() {
14060
14262
  * Named loggers allow specific modules or areas of your application to have
14061
14263
  * their own logging levels and behaviors. These loggers inherit configuration
14062
14264
  * from the base logger by default but can override settings independently.
14265
+ *
14266
+ * @public
14063
14267
  */
14064
14268
  function createLogger(name, options = {}) {
14065
14269
  const logger = Logger.get(name);
@@ -14069,6 +14273,9 @@ function createLogger(name, options = {}) {
14069
14273
  return logger;
14070
14274
  }
14071
14275
 
14276
+ /**
14277
+ * @internal
14278
+ */
14072
14279
  const parseQuery = (query, parameters) => {
14073
14280
  let sqlStatement;
14074
14281
  if (typeof query == 'string') {