@powersync/common 1.57.1 → 2.0.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 (219) hide show
  1. package/lib/attachments/AttachmentContext.d.ts +5 -5
  2. package/lib/attachments/AttachmentContext.js +6 -2
  3. package/lib/attachments/AttachmentContext.js.map +1 -1
  4. package/lib/attachments/AttachmentQueue.d.ts +6 -6
  5. package/lib/attachments/AttachmentQueue.js +9 -8
  6. package/lib/attachments/AttachmentQueue.js.map +1 -1
  7. package/lib/attachments/AttachmentService.d.ts +3 -3
  8. package/lib/attachments/AttachmentService.js +4 -3
  9. package/lib/attachments/AttachmentService.js.map +1 -1
  10. package/lib/attachments/Schema.d.ts +2 -2
  11. package/lib/attachments/SyncingService.d.ts +2 -2
  12. package/lib/attachments/SyncingService.js +21 -6
  13. package/lib/attachments/SyncingService.js.map +1 -1
  14. package/lib/client/{AbstractPowerSyncDatabase.d.ts → CommonPowerSyncDatabase.d.ts} +56 -232
  15. package/lib/client/CommonPowerSyncDatabase.js +2 -0
  16. package/lib/client/CommonPowerSyncDatabase.js.map +1 -0
  17. package/lib/client/SQLOpenFactory.d.ts +35 -13
  18. package/lib/client/SQLOpenFactory.js +1 -25
  19. package/lib/client/SQLOpenFactory.js.map +1 -1
  20. package/lib/client/compilableQueryWatch.d.ts +2 -2
  21. package/lib/client/compilableQueryWatch.js +1 -2
  22. package/lib/client/compilableQueryWatch.js.map +1 -1
  23. package/lib/client/connection/PowerSyncBackendConnector.d.ts +3 -3
  24. package/lib/client/runOnSchemaChange.d.ts +2 -2
  25. package/lib/client/runOnSchemaChange.js.map +1 -1
  26. package/lib/client/sync/bucket/CrudEntry.d.ts +3 -34
  27. package/lib/client/sync/bucket/CrudEntry.js +0 -99
  28. package/lib/client/sync/bucket/CrudEntry.js.map +1 -1
  29. package/lib/client/sync/options.d.ts +65 -0
  30. package/lib/client/sync/options.js +25 -0
  31. package/lib/client/sync/options.js.map +1 -0
  32. package/lib/client/sync/sync-streams.d.ts +1 -1
  33. package/lib/client/triggers/TriggerManager.d.ts +9 -38
  34. package/lib/client/watched/GetAllQuery.d.ts +3 -3
  35. package/lib/client/watched/GetAllQuery.js +1 -1
  36. package/lib/client/watched/GetAllQuery.js.map +1 -1
  37. package/lib/client/watched/WatchedQuery.d.ts +2 -10
  38. package/lib/client/watched/WatchedQuery.js +0 -11
  39. package/lib/client/watched/WatchedQuery.js.map +1 -1
  40. package/lib/client/watched/processors/DifferentialQueryProcessor.d.ts +0 -50
  41. package/lib/client/watched/processors/DifferentialQueryProcessor.js +1 -176
  42. package/lib/client/watched/processors/DifferentialQueryProcessor.js.map +1 -1
  43. package/lib/client/watched/processors/OnChangeQueryProcessor.d.ts +0 -22
  44. package/lib/client/watched/processors/OnChangeQueryProcessor.js +1 -82
  45. package/lib/client/watched/processors/OnChangeQueryProcessor.js.map +1 -1
  46. package/lib/db/DBAdapter.d.ts +72 -154
  47. package/lib/db/DBAdapter.js +70 -108
  48. package/lib/db/DBAdapter.js.map +1 -1
  49. package/lib/db/QueryResult.d.ts +104 -0
  50. package/lib/db/QueryResult.js +96 -0
  51. package/lib/db/QueryResult.js.map +1 -0
  52. package/lib/db/crud/SyncProgress.d.ts +1 -13
  53. package/lib/db/crud/SyncProgress.js +1 -62
  54. package/lib/db/crud/SyncProgress.js.map +1 -1
  55. package/lib/db/crud/SyncStatus.d.ts +32 -66
  56. package/lib/db/crud/SyncStatus.js +1 -253
  57. package/lib/db/crud/SyncStatus.js.map +1 -1
  58. package/lib/db/schema/Column.d.ts +0 -7
  59. package/lib/db/schema/Column.js +0 -7
  60. package/lib/db/schema/Column.js.map +1 -1
  61. package/lib/db/schema/Index.d.ts +2 -6
  62. package/lib/db/schema/Index.js +1 -4
  63. package/lib/db/schema/Index.js.map +1 -1
  64. package/lib/db/schema/IndexedColumn.d.ts +2 -6
  65. package/lib/db/schema/IndexedColumn.js +1 -4
  66. package/lib/db/schema/IndexedColumn.js.map +1 -1
  67. package/lib/db/schema/Schema.d.ts +4 -28
  68. package/lib/db/schema/Schema.js.map +1 -1
  69. package/lib/db/schema/Table.d.ts +50 -96
  70. package/lib/db/schema/Table.js +83 -91
  71. package/lib/db/schema/Table.js.map +1 -1
  72. package/lib/index.d.ts +5 -21
  73. package/lib/index.js +5 -22
  74. package/lib/index.js.map +1 -1
  75. package/lib/utils/BaseObserver.d.ts +1 -1
  76. package/lib/utils/BaseObserver.js +1 -1
  77. package/lib/utils/Logger.d.ts +52 -23
  78. package/lib/utils/Logger.js +35 -33
  79. package/lib/utils/Logger.js.map +1 -1
  80. package/lib/utils/MetaBaseObserver.d.ts +11 -11
  81. package/lib/utils/MetaBaseObserver.js +1 -50
  82. package/lib/utils/MetaBaseObserver.js.map +1 -1
  83. package/lib/utils/mutex.d.ts +3 -53
  84. package/lib/utils/mutex.js +1 -156
  85. package/lib/utils/mutex.js.map +1 -1
  86. package/package.json +8 -45
  87. package/src/attachments/AttachmentContext.ts +11 -10
  88. package/src/attachments/AttachmentQueue.ts +15 -14
  89. package/src/attachments/AttachmentService.ts +7 -6
  90. package/src/attachments/Schema.ts +2 -2
  91. package/src/attachments/SyncingService.ts +23 -9
  92. package/src/client/CommonPowerSyncDatabase.ts +540 -0
  93. package/src/client/SQLOpenFactory.ts +38 -22
  94. package/src/client/compilableQueryWatch.ts +3 -4
  95. package/src/client/connection/PowerSyncBackendConnector.ts +3 -3
  96. package/src/client/runOnSchemaChange.ts +2 -2
  97. package/src/client/sync/bucket/CrudEntry.ts +4 -104
  98. package/src/client/sync/options.ts +77 -0
  99. package/src/client/sync/sync-streams.ts +1 -1
  100. package/src/client/triggers/TriggerManager.ts +10 -41
  101. package/src/client/watched/GetAllQuery.ts +3 -3
  102. package/src/client/watched/WatchedQuery.ts +2 -15
  103. package/src/client/watched/processors/DifferentialQueryProcessor.ts +0 -223
  104. package/src/client/watched/processors/OnChangeQueryProcessor.ts +0 -111
  105. package/src/db/DBAdapter.ts +141 -226
  106. package/src/db/QueryResult.ts +195 -0
  107. package/src/db/crud/SyncProgress.ts +2 -42
  108. package/src/db/crud/SyncStatus.ts +45 -220
  109. package/src/db/schema/Column.ts +0 -8
  110. package/src/db/schema/Index.ts +3 -6
  111. package/src/db/schema/IndexedColumn.ts +3 -6
  112. package/src/db/schema/Schema.ts +4 -4
  113. package/src/db/schema/Table.ts +111 -186
  114. package/src/index.ts +5 -21
  115. package/src/utils/BaseObserver.ts +1 -1
  116. package/src/utils/Logger.ts +80 -34
  117. package/src/utils/MetaBaseObserver.ts +14 -60
  118. package/src/utils/mutex.ts +4 -201
  119. package/dist/bundle.cjs +0 -14390
  120. package/dist/bundle.cjs.map +0 -1
  121. package/dist/bundle.mjs +0 -14309
  122. package/dist/bundle.mjs.map +0 -1
  123. package/dist/bundle.node.cjs +0 -12021
  124. package/dist/bundle.node.cjs.map +0 -1
  125. package/dist/bundle.node.mjs +0 -11940
  126. package/dist/bundle.node.mjs.map +0 -1
  127. package/dist/index.d.cts +0 -4651
  128. package/legacy/sync_protocol.d.ts +0 -103
  129. package/lib/client/AbstractPowerSyncDatabase.js +0 -990
  130. package/lib/client/AbstractPowerSyncDatabase.js.map +0 -1
  131. package/lib/client/AbstractPowerSyncOpenFactory.d.ts +0 -28
  132. package/lib/client/AbstractPowerSyncOpenFactory.js +0 -28
  133. package/lib/client/AbstractPowerSyncOpenFactory.js.map +0 -1
  134. package/lib/client/ConnectionManager.d.ts +0 -115
  135. package/lib/client/ConnectionManager.js +0 -294
  136. package/lib/client/ConnectionManager.js.map +0 -1
  137. package/lib/client/CustomQuery.d.ts +0 -22
  138. package/lib/client/CustomQuery.js +0 -43
  139. package/lib/client/CustomQuery.js.map +0 -1
  140. package/lib/client/constants.d.ts +0 -4
  141. package/lib/client/constants.js +0 -5
  142. package/lib/client/constants.js.map +0 -1
  143. package/lib/client/sync/bucket/BucketStorageAdapter.d.ts +0 -56
  144. package/lib/client/sync/bucket/BucketStorageAdapter.js +0 -29
  145. package/lib/client/sync/bucket/BucketStorageAdapter.js.map +0 -1
  146. package/lib/client/sync/bucket/SqliteBucketStorage.d.ts +0 -37
  147. package/lib/client/sync/bucket/SqliteBucketStorage.js +0 -165
  148. package/lib/client/sync/bucket/SqliteBucketStorage.js.map +0 -1
  149. package/lib/client/sync/stream/AbstractRemote.d.ts +0 -180
  150. package/lib/client/sync/stream/AbstractRemote.js +0 -505
  151. package/lib/client/sync/stream/AbstractRemote.js.map +0 -1
  152. package/lib/client/sync/stream/AbstractStreamingSyncImplementation.d.ts +0 -265
  153. package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +0 -705
  154. package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js.map +0 -1
  155. package/lib/client/sync/stream/WebsocketClientTransport.d.ts +0 -15
  156. package/lib/client/sync/stream/WebsocketClientTransport.js +0 -62
  157. package/lib/client/sync/stream/WebsocketClientTransport.js.map +0 -1
  158. package/lib/client/sync/stream/core-instruction.d.ts +0 -77
  159. package/lib/client/sync/stream/core-instruction.js +0 -30
  160. package/lib/client/sync/stream/core-instruction.js.map +0 -1
  161. package/lib/client/triggers/MemoryTriggerClaimManager.d.ts +0 -6
  162. package/lib/client/triggers/MemoryTriggerClaimManager.js +0 -21
  163. package/lib/client/triggers/MemoryTriggerClaimManager.js.map +0 -1
  164. package/lib/client/triggers/TriggerManagerImpl.d.ts +0 -39
  165. package/lib/client/triggers/TriggerManagerImpl.js +0 -410
  166. package/lib/client/triggers/TriggerManagerImpl.js.map +0 -1
  167. package/lib/client/watched/processors/AbstractQueryProcessor.d.ts +0 -68
  168. package/lib/client/watched/processors/AbstractQueryProcessor.js +0 -151
  169. package/lib/client/watched/processors/AbstractQueryProcessor.js.map +0 -1
  170. package/lib/db/ConnectionClosedError.d.ts +0 -12
  171. package/lib/db/ConnectionClosedError.js +0 -23
  172. package/lib/db/ConnectionClosedError.js.map +0 -1
  173. package/lib/db/schema/TableV2.d.ts +0 -11
  174. package/lib/db/schema/TableV2.js +0 -11
  175. package/lib/db/schema/TableV2.js.map +0 -1
  176. package/lib/utils/AbortOperation.d.ts +0 -11
  177. package/lib/utils/AbortOperation.js +0 -21
  178. package/lib/utils/AbortOperation.js.map +0 -1
  179. package/lib/utils/ControlledExecutor.d.ts +0 -31
  180. package/lib/utils/ControlledExecutor.js +0 -54
  181. package/lib/utils/ControlledExecutor.js.map +0 -1
  182. package/lib/utils/async.d.ts +0 -46
  183. package/lib/utils/async.js +0 -147
  184. package/lib/utils/async.js.map +0 -1
  185. package/lib/utils/compatibility.d.ts +0 -8
  186. package/lib/utils/compatibility.js +0 -9
  187. package/lib/utils/compatibility.js.map +0 -1
  188. package/lib/utils/parseQuery.d.ts +0 -12
  189. package/lib/utils/parseQuery.js +0 -20
  190. package/lib/utils/parseQuery.js.map +0 -1
  191. package/lib/utils/queue.d.ts +0 -16
  192. package/lib/utils/queue.js +0 -42
  193. package/lib/utils/queue.js.map +0 -1
  194. package/lib/utils/stream_transform.d.ts +0 -41
  195. package/lib/utils/stream_transform.js +0 -211
  196. package/lib/utils/stream_transform.js.map +0 -1
  197. package/src/client/AbstractPowerSyncDatabase.ts +0 -1408
  198. package/src/client/AbstractPowerSyncOpenFactory.ts +0 -45
  199. package/src/client/ConnectionManager.ts +0 -405
  200. package/src/client/CustomQuery.ts +0 -56
  201. package/src/client/constants.ts +0 -4
  202. package/src/client/sync/bucket/BucketStorageAdapter.ts +0 -65
  203. package/src/client/sync/bucket/SqliteBucketStorage.ts +0 -202
  204. package/src/client/sync/stream/AbstractRemote.ts +0 -644
  205. package/src/client/sync/stream/AbstractStreamingSyncImplementation.ts +0 -970
  206. package/src/client/sync/stream/WebsocketClientTransport.ts +0 -82
  207. package/src/client/sync/stream/core-instruction.ts +0 -109
  208. package/src/client/triggers/MemoryTriggerClaimManager.ts +0 -25
  209. package/src/client/triggers/TriggerManagerImpl.ts +0 -501
  210. package/src/client/watched/processors/AbstractQueryProcessor.ts +0 -226
  211. package/src/db/ConnectionClosedError.ts +0 -25
  212. package/src/db/schema/TableV2.ts +0 -11
  213. package/src/utils/AbortOperation.ts +0 -19
  214. package/src/utils/ControlledExecutor.ts +0 -78
  215. package/src/utils/async.ts +0 -185
  216. package/src/utils/compatibility.ts +0 -9
  217. package/src/utils/parseQuery.ts +0 -31
  218. package/src/utils/queue.ts +0 -48
  219. package/src/utils/stream_transform.ts +0 -260
@@ -1,501 +0,0 @@
1
- import { LockContext } from '../../db/DBAdapter.js';
2
- import { Schema } from '../../db/schema/Schema.js';
3
- import type { AbstractPowerSyncDatabase } from '../AbstractPowerSyncDatabase.js';
4
- import { DEFAULT_WATCH_THROTTLE_MS } from '../watched/WatchedQuery.js';
5
- import {
6
- CreateDiffTriggerOptions,
7
- DiffTriggerOperation,
8
- TrackDiffOptions,
9
- TriggerManager,
10
- TriggerManagerConfig,
11
- TriggerRemoveCallback,
12
- TriggerRemoveCallbackOptions,
13
- WithDiffOptions
14
- } from './TriggerManager.js';
15
-
16
- export type TriggerManagerImplOptions = TriggerManagerConfig & {
17
- db: AbstractPowerSyncDatabase;
18
- schema: Schema;
19
- };
20
-
21
- export type TriggerManagerImplConfiguration = {
22
- useStorageByDefault: boolean;
23
- };
24
-
25
- export const DEFAULT_TRIGGER_MANAGER_CONFIGURATION: TriggerManagerImplConfiguration = {
26
- useStorageByDefault: false
27
- };
28
-
29
- /**
30
- * A record of persisted table/trigger information.
31
- * This is used for fail-safe cleanup.
32
- */
33
- type TrackedTableRecord = {
34
- /**
35
- * The id of the trigger. This is used in the SQLite trigger name
36
- */
37
- id: string;
38
- /**
39
- * The destination table name for the trigger
40
- */
41
- table: string;
42
- /**
43
- * Array of actual trigger names found for this table/id combo
44
- */
45
- triggerNames: string[];
46
- };
47
-
48
- const TRIGGER_CLEANUP_INTERVAL_MS = 120_000; // 2 minutes
49
-
50
- /**
51
- * @internal
52
- * @experimental
53
- */
54
- export class TriggerManagerImpl implements TriggerManager {
55
- protected schema: Schema;
56
-
57
- protected defaultConfig: TriggerManagerImplConfiguration;
58
- protected cleanupTimeout: ReturnType<typeof setTimeout> | null;
59
- protected isDisposed: boolean;
60
-
61
- constructor(protected options: TriggerManagerImplOptions) {
62
- this.schema = options.schema;
63
- options.db.registerListener({
64
- schemaChanged: (schema) => {
65
- this.schema = schema;
66
- }
67
- });
68
- this.isDisposed = false;
69
-
70
- /**
71
- * Configure a cleanup to run on an interval.
72
- * The interval is configured using setTimeout to take the async
73
- * execution time of the callback into account.
74
- */
75
- this.defaultConfig = DEFAULT_TRIGGER_MANAGER_CONFIGURATION;
76
- const cleanupCallback = async () => {
77
- this.cleanupTimeout = null;
78
- if (this.isDisposed) {
79
- return;
80
- }
81
- try {
82
- await this.cleanupResources();
83
- } catch (ex) {
84
- this.db.logger.error(`Caught error while attempting to cleanup triggers`, ex);
85
- } finally {
86
- // if not closed, set another timeout
87
- if (this.isDisposed) {
88
- return;
89
- }
90
- this.cleanupTimeout = setTimeout(cleanupCallback, TRIGGER_CLEANUP_INTERVAL_MS);
91
- }
92
- };
93
- this.cleanupTimeout = setTimeout(cleanupCallback, TRIGGER_CLEANUP_INTERVAL_MS);
94
- }
95
-
96
- protected get db() {
97
- return this.options.db;
98
- }
99
-
100
- protected async getUUID(ctx?: LockContext) {
101
- const { id: uuid } = await (ctx ?? this.db).get<{ id: string }>(/* sql */ `
102
- SELECT
103
- uuid () as id
104
- `);
105
-
106
- // Replace dashes with underscores for SQLite table/trigger name compatibility
107
- return uuid.replace(/-/g, '_');
108
- }
109
-
110
- protected async removeTriggers(tx: LockContext, triggerIds: string[]) {
111
- for (const triggerId of triggerIds) {
112
- await tx.execute(/* sql */ `DROP TRIGGER IF EXISTS ${triggerId}; `);
113
- }
114
- }
115
-
116
- dispose() {
117
- this.isDisposed = true;
118
- if (this.cleanupTimeout) {
119
- clearTimeout(this.cleanupTimeout);
120
- }
121
- }
122
-
123
- /**
124
- * Updates default config settings for platform specific use-cases.
125
- */
126
- updateDefaults(config: TriggerManagerImplConfiguration) {
127
- this.defaultConfig = {
128
- ...this.defaultConfig,
129
- ...config
130
- };
131
- }
132
-
133
- protected generateTriggerName(operation: DiffTriggerOperation, destinationTable: string, triggerId: string) {
134
- return `__ps_temp_trigger_${operation.toLowerCase()}__${destinationTable}__${triggerId}`;
135
- }
136
-
137
- /**
138
- * Cleanup any SQLite triggers or tables that are no longer in use.
139
- */
140
- async cleanupResources() {
141
- // we use the database here since cleanupResources is called during the PowerSyncDatabase initialization
142
- await this.db.database.writeLock(async (ctx) => {
143
- /**
144
- * Note: We only cleanup persisted triggers. These are tracked in the sqlite_master table.
145
- * temporary triggers will not be affected by this.
146
- * Query all triggers that match our naming pattern
147
- */
148
- const triggers = await ctx.getAll<{ name: string }>(/* sql */ `
149
- SELECT
150
- name
151
- FROM
152
- sqlite_master
153
- WHERE
154
- type = 'trigger'
155
- AND name LIKE '__ps_temp_trigger_%'
156
- `);
157
-
158
- /** Use regex to extract table names and IDs from trigger names
159
- * Trigger naming convention: __ps_temp_trigger_<operation>__<destination_table>__<id>
160
- */
161
- const triggerPattern = /^__ps_temp_trigger_(?:insert|update|delete)__(.+)__([a-f0-9_]{36})$/i;
162
- const trackedItems = new Map<string, TrackedTableRecord>();
163
-
164
- for (const trigger of triggers) {
165
- const match = trigger.name.match(triggerPattern);
166
- if (match) {
167
- const [, table, id] = match;
168
- // Collect all trigger names for each id combo
169
- const existing = trackedItems.get(id);
170
- if (existing) {
171
- existing.triggerNames.push(trigger.name);
172
- } else {
173
- trackedItems.set(id, { table, id, triggerNames: [trigger.name] });
174
- }
175
- }
176
- }
177
-
178
- for (const trackedItem of trackedItems.values()) {
179
- // check if there is anything holding on to this item
180
- const hasClaim = await this.options.claimManager.checkClaim(trackedItem.id);
181
- if (hasClaim) {
182
- // This does not require cleanup
183
- continue;
184
- }
185
-
186
- this.db.logger.debug(`Clearing resources for trigger ${trackedItem.id} with table ${trackedItem.table}`);
187
-
188
- // We need to delete the triggers and table
189
- for (const triggerName of trackedItem.triggerNames) {
190
- await ctx.execute(`DROP TRIGGER IF EXISTS ${triggerName}`);
191
- }
192
- await ctx.execute(`DROP TABLE IF EXISTS ${trackedItem.table}`);
193
- }
194
- });
195
- }
196
-
197
- async createDiffTrigger(options: CreateDiffTriggerOptions) {
198
- await this.db.waitForReady();
199
- const {
200
- source,
201
- destination,
202
- columns,
203
- when,
204
- hooks,
205
- setupContext,
206
- // Fall back to the provided default if not given on this level
207
- useStorage = this.defaultConfig.useStorageByDefault
208
- } = options;
209
- const operations = Object.keys(when) as DiffTriggerOperation[];
210
- if (operations.length == 0) {
211
- throw new Error('At least one WHEN operation must be specified for the trigger.');
212
- }
213
-
214
- /**
215
- * The clause to use when executing
216
- * CREATE ${tableTriggerTypeClause} TABLE
217
- * OR
218
- * CREATE ${tableTriggerTypeClause} TRIGGER
219
- */
220
- const tableTriggerTypeClause = !useStorage ? 'TEMP' : '';
221
-
222
- const whenClauses = Object.fromEntries(
223
- Object.entries(when).map(([operation, filter]) => [operation, `WHEN ${filter}`])
224
- );
225
-
226
- /**
227
- * Allow specifying the View name as the source.
228
- * We can lookup the internal table name from the schema.
229
- */
230
- const sourceDefinition = this.schema.tables.find((table) => table.viewName == source);
231
- if (!sourceDefinition) {
232
- throw new Error(`Source table or view "${source}" not found in the schema.`);
233
- }
234
-
235
- const replicatedColumns = columns ?? sourceDefinition.columns.map((col) => col.name);
236
-
237
- const internalSource = sourceDefinition.internalName;
238
- const triggerIds: string[] = [];
239
-
240
- const id = await this.getUUID(setupContext);
241
-
242
- const releaseStorageClaim = useStorage ? await this.options.claimManager.obtainClaim(id) : null;
243
-
244
- /**
245
- * We default to replicating all columns if no columns array is provided.
246
- */
247
- const jsonFragment = (source: 'NEW' | 'OLD' = 'NEW') => {
248
- if (columns == null) {
249
- // Track all columns
250
- return `${source}.data`;
251
- } else if (columns.length == 0) {
252
- // Don't track any columns except for the id
253
- return `'{}'`;
254
- } else {
255
- // Filter the data by the replicated columns
256
- return `json_object(${replicatedColumns.map((col) => `'${col}', json_extract(${source}.data, '$.${col}')`).join(', ')})`;
257
- }
258
- };
259
-
260
- const disposeWarningListener = this.db.registerListener({
261
- schemaChanged: () => {
262
- this.db.logger.warn(
263
- `The PowerSync schema has changed while previously configured triggers are still operational. This might cause unexpected results.`
264
- );
265
- }
266
- });
267
-
268
- /**
269
- * Declare the cleanup function early since if any of the init steps fail,
270
- * we need to ensure we can cleanup the created resources.
271
- * We unfortunately cannot rely on transaction rollback.
272
- */
273
- const cleanup = async (options?: TriggerRemoveCallbackOptions) => {
274
- const { context } = options ?? {};
275
- disposeWarningListener();
276
- const doCleanup = async (tx: LockContext) => {
277
- await this.removeTriggers(tx, triggerIds);
278
- await tx.execute(`DROP TABLE IF EXISTS ${destination};`);
279
- await releaseStorageClaim?.();
280
- };
281
- if (context) {
282
- await doCleanup(context);
283
- } else {
284
- await this.db.writeLock(doCleanup);
285
- }
286
- };
287
-
288
- const setup = async (tx: LockContext) => {
289
- // Allow user code to execute in this lock context before the trigger is created.
290
- await hooks?.beforeCreate?.(tx);
291
- await tx.execute(/* sql */ `
292
- CREATE ${tableTriggerTypeClause} TABLE ${destination} (
293
- operation_id INTEGER PRIMARY KEY AUTOINCREMENT,
294
- id TEXT,
295
- operation TEXT,
296
- timestamp TEXT,
297
- value TEXT,
298
- previous_value TEXT
299
- )
300
- `);
301
-
302
- if (operations.includes(DiffTriggerOperation.INSERT)) {
303
- const insertTriggerId = this.generateTriggerName(DiffTriggerOperation.INSERT, destination, id);
304
- triggerIds.push(insertTriggerId);
305
-
306
- await tx.execute(/* sql */ `
307
- CREATE ${tableTriggerTypeClause} TRIGGER ${insertTriggerId} AFTER INSERT ON ${internalSource} ${whenClauses[
308
- DiffTriggerOperation.INSERT
309
- ]} BEGIN
310
- INSERT INTO
311
- ${destination} (id, operation, timestamp, value)
312
- VALUES
313
- (
314
- NEW.id,
315
- 'INSERT',
316
- strftime ('%Y-%m-%dT%H:%M:%fZ', 'now'),
317
- ${jsonFragment('NEW')}
318
- );
319
-
320
- END
321
- `);
322
- }
323
-
324
- if (operations.includes(DiffTriggerOperation.UPDATE)) {
325
- const updateTriggerId = this.generateTriggerName(DiffTriggerOperation.UPDATE, destination, id);
326
- triggerIds.push(updateTriggerId);
327
-
328
- await tx.execute(/* sql */ `
329
- CREATE ${tableTriggerTypeClause} TRIGGER ${updateTriggerId} AFTER
330
- UPDATE ON ${internalSource} ${whenClauses[DiffTriggerOperation.UPDATE]} BEGIN
331
- INSERT INTO
332
- ${destination} (id, operation, timestamp, value, previous_value)
333
- VALUES
334
- (
335
- NEW.id,
336
- 'UPDATE',
337
- strftime ('%Y-%m-%dT%H:%M:%fZ', 'now'),
338
- ${jsonFragment('NEW')},
339
- ${jsonFragment('OLD')}
340
- );
341
-
342
- END;
343
- `);
344
- }
345
-
346
- if (operations.includes(DiffTriggerOperation.DELETE)) {
347
- const deleteTriggerId = this.generateTriggerName(DiffTriggerOperation.DELETE, destination, id);
348
- triggerIds.push(deleteTriggerId);
349
-
350
- // Create delete trigger for basic JSON
351
- await tx.execute(/* sql */ `
352
- CREATE ${tableTriggerTypeClause} TRIGGER ${deleteTriggerId} AFTER DELETE ON ${internalSource} ${whenClauses[
353
- DiffTriggerOperation.DELETE
354
- ]} BEGIN
355
- INSERT INTO
356
- ${destination} (id, operation, timestamp, value)
357
- VALUES
358
- (
359
- OLD.id,
360
- 'DELETE',
361
- strftime ('%Y-%m-%dT%H:%M:%fZ', 'now'),
362
- ${jsonFragment('OLD')}
363
- );
364
-
365
- END;
366
- `);
367
- }
368
- };
369
-
370
- try {
371
- if (setupContext) {
372
- await setup(setupContext);
373
- } else {
374
- await this.db.writeLock(setup);
375
- }
376
- return cleanup;
377
- } catch (error) {
378
- try {
379
- await cleanup(setupContext ? { context: setupContext } : undefined);
380
- } catch (cleanupError) {
381
- throw new AggregateError([error, cleanupError], 'Error during operation and cleanup');
382
- }
383
- throw error;
384
- }
385
- }
386
-
387
- async trackTableDiff(options: TrackDiffOptions): Promise<TriggerRemoveCallback> {
388
- const { source, when, columns, hooks, throttleMs = DEFAULT_WATCH_THROTTLE_MS } = options;
389
-
390
- await this.db.waitForReady();
391
-
392
- /**
393
- * Allow specifying the View name as the source.
394
- * We can lookup the internal table name from the schema.
395
- */
396
- const sourceDefinition = this.schema.tables.find((table) => table.viewName == source);
397
- if (!sourceDefinition) {
398
- throw new Error(`Source table or view "${source}" not found in the schema.`);
399
- }
400
-
401
- // The columns to present in the onChange context methods.
402
- // If no array is provided, we use all columns from the source table.
403
- const contextColumns = columns ?? sourceDefinition.columns.map((col) => col.name);
404
-
405
- const id = await this.getUUID();
406
- const destination = `__ps_temp_track_${source}_${id}`;
407
-
408
- // register an onChange before the trigger is created
409
- const abortController = new AbortController();
410
- const abortOnChange = () => abortController.abort();
411
- this.db.onChange(
412
- {
413
- // Note that the onChange events here have their execution scheduled.
414
- // Callbacks are throttled and are sequential.
415
- onChange: async () => {
416
- if (abortController.signal.aborted) return;
417
-
418
- // Run the handler in a write lock to keep the state of the
419
- // destination table consistent.
420
- await this.db.writeTransaction(async (tx) => {
421
- const callbackResult = await options.onChange({
422
- execute: (query, params) => tx.execute(query, params),
423
- executeBatch: (query, params) => tx.executeBatch(query, params),
424
- executeRaw: (query, params) => tx.executeRaw(query, params),
425
- get: (query, params) => tx.get(query, params),
426
- getAll: (query, params) => tx.getAll(query, params),
427
- getOptional: (query, params) => tx.getOptional(query, params),
428
- destinationTable: destination,
429
- withDiff: async <T>(query: string, params: any, options?: WithDiffOptions) => {
430
- // Wrap the query to expose the destination table
431
- const operationIdSelect = options?.castOperationIdAsText
432
- ? 'id, operation, CAST(operation_id AS TEXT) as operation_id, timestamp, value, previous_value'
433
- : '*';
434
- const wrappedQuery = /* sql */ `
435
- WITH
436
- DIFF AS (
437
- SELECT
438
- ${operationIdSelect}
439
- FROM
440
- ${destination}
441
- ORDER BY
442
- operation_id ASC
443
- ) ${query}
444
- `;
445
- return tx.getAll<T>(wrappedQuery, params);
446
- },
447
- withExtractedDiff: async <T>(query: string, params: any) => {
448
- // Wrap the query to expose the destination table
449
- const wrappedQuery = /* sql */ `
450
- WITH
451
- DIFF AS (
452
- SELECT
453
- id,
454
- ${contextColumns.length > 0
455
- ? `${contextColumns.map((col) => `json_extract(value, '$.${col}') as ${col}`).join(', ')},`
456
- : ''} operation_id as __operation_id,
457
- operation as __operation,
458
- timestamp as __timestamp,
459
- previous_value as __previous_value
460
- FROM
461
- ${destination}
462
- ORDER BY
463
- __operation_id ASC
464
- ) ${query}
465
- `;
466
- return tx.getAll<T>(wrappedQuery, params);
467
- }
468
- });
469
-
470
- // Clear the destination table after processing
471
- await tx.execute(/* sql */ `DELETE FROM ${destination};`);
472
- return callbackResult;
473
- });
474
- }
475
- },
476
- { tables: [destination], signal: abortController.signal, throttleMs }
477
- );
478
-
479
- try {
480
- const removeTrigger = await this.createDiffTrigger({
481
- source,
482
- destination,
483
- columns: contextColumns,
484
- when,
485
- hooks
486
- });
487
-
488
- return async () => {
489
- abortOnChange();
490
- await removeTrigger();
491
- };
492
- } catch (error) {
493
- try {
494
- abortOnChange();
495
- } catch (cleanupError) {
496
- throw new AggregateError([error, cleanupError], 'Error during operation and cleanup');
497
- }
498
- throw error;
499
- }
500
- }
501
- }