@tanstack/powersync-db-collection 0.1.69 → 0.2.1
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.
- package/dist/cjs/PowerSyncTransactor.cjs +31 -18
- package/dist/cjs/PowerSyncTransactor.cjs.map +1 -1
- package/dist/cjs/PowerSyncTransactor.d.cts +4 -4
- package/dist/cjs/definitions.cjs.map +1 -1
- package/dist/cjs/definitions.d.cts +4 -4
- package/dist/cjs/helpers.cjs.map +1 -1
- package/dist/cjs/helpers.d.cts +6 -9
- package/dist/cjs/index.d.cts +1 -0
- package/dist/cjs/powersync.cjs +214 -53
- package/dist/cjs/powersync.cjs.map +1 -1
- package/dist/cjs/serialization.cjs.map +1 -1
- package/dist/cjs/serialization.d.cts +2 -6
- package/dist/esm/PowerSyncTransactor.d.ts +4 -4
- package/dist/esm/PowerSyncTransactor.js +32 -19
- package/dist/esm/PowerSyncTransactor.js.map +1 -1
- package/dist/esm/definitions.d.ts +4 -4
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/helpers.d.ts +6 -9
- package/dist/esm/helpers.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/powersync.js +215 -54
- package/dist/esm/powersync.js.map +1 -1
- package/dist/esm/serialization.d.ts +2 -6
- package/dist/esm/serialization.js.map +1 -1
- package/package.json +7 -6
- package/src/PowerSyncTransactor.ts +46 -25
- package/src/definitions.ts +11 -5
- package/src/helpers.ts +10 -18
- package/src/index.ts +1 -0
- package/src/powersync.ts +258 -62
- package/src/serialization.ts +8 -17
package/dist/esm/helpers.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DiffTriggerOperation,
|
|
1
|
+
import { DiffTriggerOperation, ExtractColumnValueType, Table } from '@powersync/common';
|
|
2
2
|
/**
|
|
3
3
|
* All PowerSync table records include a UUID `id` column.
|
|
4
4
|
*/
|
|
@@ -20,9 +20,9 @@ type OptionalIfUndefined<T> = {
|
|
|
20
20
|
/**
|
|
21
21
|
* Provides the base column types for a table. This excludes the `id` column.
|
|
22
22
|
*/
|
|
23
|
-
export type ExtractedTableColumns<TTable extends Table> = {
|
|
24
|
-
[K in keyof
|
|
25
|
-
};
|
|
23
|
+
export type ExtractedTableColumns<TTable extends Table> = TTable extends Table<infer Columns> ? {
|
|
24
|
+
[K in keyof Columns]: ExtractColumnValueType<Columns[K]>;
|
|
25
|
+
} : never;
|
|
26
26
|
/**
|
|
27
27
|
* Utility type that extracts the typed structure of a table based on its column definitions.
|
|
28
28
|
* Maps each column to its corresponding TypeScript type using ExtractColumnValueType.
|
|
@@ -42,7 +42,7 @@ export type ExtractedTable<TTable extends Table> = ExtractedTableColumns<TTable>
|
|
|
42
42
|
id: string;
|
|
43
43
|
};
|
|
44
44
|
export type OptionalExtractedTable<TTable extends Table> = OptionalIfUndefined<{
|
|
45
|
-
[K in keyof TTable
|
|
45
|
+
[K in keyof ExtractedTableColumns<TTable>]: WithUndefinedIfNull<ExtractedTableColumns<TTable>[K]>;
|
|
46
46
|
}> & {
|
|
47
47
|
id: string;
|
|
48
48
|
};
|
|
@@ -51,14 +51,11 @@ export type OptionalExtractedTable<TTable extends Table> = OptionalIfUndefined<{
|
|
|
51
51
|
* requires the keys be equal, but the values can have any value type.
|
|
52
52
|
*/
|
|
53
53
|
export type AnyTableColumnType<TTable extends Table> = {
|
|
54
|
-
[K in keyof TTable
|
|
54
|
+
[K in keyof ExtractedTableColumns<TTable>]: unknown;
|
|
55
55
|
} & {
|
|
56
56
|
id: string;
|
|
57
57
|
};
|
|
58
58
|
export declare function asPowerSyncRecord(record: any): PowerSyncRecord;
|
|
59
|
-
export type MapBaseColumnType<TOutput> = {
|
|
60
|
-
[Key in keyof TOutput]: BaseColumnType<any>;
|
|
61
|
-
};
|
|
62
59
|
/**
|
|
63
60
|
* Maps {@link DiffTriggerOperation} to TanstackDB operations
|
|
64
61
|
*/
|
package/dist/esm/helpers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.js","sources":["../../src/helpers.ts"],"sourcesContent":["import { DiffTriggerOperation } from '@powersync/common'\nimport type {
|
|
1
|
+
{"version":3,"file":"helpers.js","sources":["../../src/helpers.ts"],"sourcesContent":["import { DiffTriggerOperation } from '@powersync/common'\nimport type { ExtractColumnValueType, Table } from '@powersync/common'\n\n/**\n * All PowerSync table records include a UUID `id` column.\n */\nexport type PowerSyncRecord = {\n id: string\n [key: string]: unknown\n}\n\n/**\n * Utility type: If T includes null, also allow undefined (to support optional fields in insert/update operations).\n * PowerSync records are typically typed as `string | null`, where insert\n * and update operations may also allow not specifying a value at all (optional).\n */\ntype WithUndefinedIfNull<T> = null extends T ? T | undefined : T\ntype OptionalIfUndefined<T> = {\n [K in keyof T as undefined extends T[K] ? K : never]?: T[K]\n} & {\n [K in keyof T as undefined extends T[K] ? never : K]: T[K]\n}\n\n/**\n * Provides the base column types for a table. This excludes the `id` column.\n */\nexport type ExtractedTableColumns<TTable extends Table> =\n TTable extends Table<infer Columns>\n ? {\n [K in keyof Columns]: ExtractColumnValueType<Columns[K]>\n }\n : never\n/**\n * Utility type that extracts the typed structure of a table based on its column definitions.\n * Maps each column to its corresponding TypeScript type using ExtractColumnValueType.\n *\n * @template TTable - The PowerSync table definition\n * @example\n * ```typescript\n * const table = new Table({\n * name: column.text,\n * age: column.integer\n * })\n * type TableType = ExtractedTable<typeof table>\n * // Results in: { id: string, name: string | null, age: number | null }\n * ```\n */\nexport type ExtractedTable<TTable extends Table> =\n ExtractedTableColumns<TTable> & {\n id: string\n }\n\nexport type OptionalExtractedTable<TTable extends Table> = OptionalIfUndefined<{\n [K in keyof ExtractedTableColumns<TTable>]: WithUndefinedIfNull<\n ExtractedTableColumns<TTable>[K]\n >\n}> & {\n id: string\n}\n\n/**\n * Maps the schema of TTable to a type which\n * requires the keys be equal, but the values can have any value type.\n */\nexport type AnyTableColumnType<TTable extends Table> = {\n [K in keyof ExtractedTableColumns<TTable>]: unknown\n} & { id: string }\n\nexport function asPowerSyncRecord(record: any): PowerSyncRecord {\n if (typeof record.id !== `string`) {\n throw new Error(`Record must have a string id field`)\n }\n return record as PowerSyncRecord\n}\n\n/**\n * Maps {@link DiffTriggerOperation} to TanstackDB operations\n */\nexport function mapOperation(operation: DiffTriggerOperation) {\n switch (operation) {\n case DiffTriggerOperation.INSERT:\n return `insert`\n case DiffTriggerOperation.UPDATE:\n return `update`\n case DiffTriggerOperation.DELETE:\n return `delete`\n }\n}\n\n/**\n * Maps TanstackDB operations to {@link DiffTriggerOperation}\n */\nexport function mapOperationToPowerSync(operation: string) {\n switch (operation) {\n case `insert`:\n return DiffTriggerOperation.INSERT\n case `update`:\n return DiffTriggerOperation.UPDATE\n case `delete`:\n return DiffTriggerOperation.DELETE\n default:\n throw new Error(`Unknown operation ${operation} received`)\n }\n}\n"],"names":[],"mappings":";AAoEO,SAAS,kBAAkB,QAA8B;AAC9D,MAAI,OAAO,OAAO,OAAO,UAAU;AACjC,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACtD;AACA,SAAO;AACT;AAKO,SAAS,aAAa,WAAiC;AAC5D,UAAQ,WAAA;AAAA,IACN,KAAK,qBAAqB;AACxB,aAAO;AAAA,IACT,KAAK,qBAAqB;AACxB,aAAO;AAAA,IACT,KAAK,qBAAqB;AACxB,aAAO;AAAA,EAAA;AAEb;AAKO,SAAS,wBAAwB,WAAmB;AACzD,UAAQ,WAAA;AAAA,IACN,KAAK;AACH,aAAO,qBAAqB;AAAA,IAC9B,KAAK;AACH,aAAO,qBAAqB;AAAA,IAC9B,KAAK;AACH,aAAO,qBAAqB;AAAA,IAC9B;AACE,YAAM,IAAI,MAAM,qBAAqB,SAAS,WAAW;AAAA,EAAA;AAE/D;"}
|
package/dist/esm/index.d.ts
CHANGED
package/dist/esm/powersync.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DiffTriggerOperation, sanitizeSQL } from "@powersync/common";
|
|
1
|
+
import { LogLevels, DiffTriggerOperation, sanitizeSQL } from "@powersync/common";
|
|
2
2
|
import { withCollectionConfigFactory, or } from "@tanstack/db";
|
|
3
3
|
import { compileSQLite } from "./sqlite-compiler.js";
|
|
4
4
|
import { PendingOperationStore } from "./PendingOperationStore.js";
|
|
@@ -7,6 +7,9 @@ import { DEFAULT_BATCH_SIZE } from "./definitions.js";
|
|
|
7
7
|
import { asPowerSyncRecord, mapOperation } from "./helpers.js";
|
|
8
8
|
import { convertTableToSchema } from "./schema.js";
|
|
9
9
|
import { serializeForSQLite } from "./serialization.js";
|
|
10
|
+
function isPromiseLike(value) {
|
|
11
|
+
return !!value && (typeof value === `object` || typeof value === `function`) && typeof value.then === `function`;
|
|
12
|
+
}
|
|
10
13
|
function powerSyncCollectionOptions(config) {
|
|
11
14
|
const outputConfig = createPowerSyncCollectionConfig(config);
|
|
12
15
|
return withCollectionConfigFactory(
|
|
@@ -34,12 +37,15 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
34
37
|
return validation.value;
|
|
35
38
|
} else if (`issues` in validation) {
|
|
36
39
|
const issueMessage = `Failed to validate incoming data for ${viewName}. Issues: ${validation.issues.map((issue) => `${issue.path} - ${issue.message}`)}`;
|
|
37
|
-
database.logger.error
|
|
40
|
+
database.logger.log({ level: LogLevels.error, message: issueMessage });
|
|
38
41
|
onDeserializationError(validation);
|
|
39
42
|
throw new Error(issueMessage);
|
|
40
43
|
} else {
|
|
41
44
|
const unknownErrorMessage = `Unknown deserialization error for ${viewName}`;
|
|
42
|
-
database.logger.
|
|
45
|
+
database.logger.log({
|
|
46
|
+
level: LogLevels.error,
|
|
47
|
+
message: unknownErrorMessage
|
|
48
|
+
});
|
|
43
49
|
onDeserializationError({ issues: [{ message: unknownErrorMessage }] });
|
|
44
50
|
throw new Error(unknownErrorMessage);
|
|
45
51
|
}
|
|
@@ -58,6 +64,8 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
58
64
|
const abortController = new AbortController();
|
|
59
65
|
let disposeTracking = null;
|
|
60
66
|
let trackingSetup = null;
|
|
67
|
+
let activeTrackingDisposal = null;
|
|
68
|
+
let abortTrackingDisposal = null;
|
|
61
69
|
if (syncMode === `eager`) {
|
|
62
70
|
return runEagerSync();
|
|
63
71
|
} else {
|
|
@@ -70,10 +78,29 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
70
78
|
}
|
|
71
79
|
const dispose = disposeTracking;
|
|
72
80
|
if (!dispose) {
|
|
81
|
+
await activeTrackingDisposal;
|
|
73
82
|
return;
|
|
74
83
|
}
|
|
75
84
|
disposeTracking = null;
|
|
76
|
-
|
|
85
|
+
let disposal;
|
|
86
|
+
try {
|
|
87
|
+
disposal = Promise.resolve(dispose(context ? { context } : void 0));
|
|
88
|
+
} catch (error) {
|
|
89
|
+
disposal = Promise.reject(error);
|
|
90
|
+
}
|
|
91
|
+
activeTrackingDisposal = disposal;
|
|
92
|
+
try {
|
|
93
|
+
await disposal;
|
|
94
|
+
} finally {
|
|
95
|
+
if (activeTrackingDisposal === disposal) {
|
|
96
|
+
activeTrackingDisposal = null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function disposeTrackingAfterAbort() {
|
|
101
|
+
const disposal = abortTrackingDisposal ??= safelyDisposeTracking();
|
|
102
|
+
void disposal.catch(() => void 0);
|
|
103
|
+
return disposal;
|
|
77
104
|
}
|
|
78
105
|
async function establishTracking(options, appliedReceipts) {
|
|
79
106
|
const setup = (async () => {
|
|
@@ -94,6 +121,7 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
94
121
|
return await database.triggers.createDiffTrigger({
|
|
95
122
|
source: viewName,
|
|
96
123
|
destination: trackedTableName,
|
|
124
|
+
columns: table.columns.map((column) => column.name),
|
|
97
125
|
setupContext,
|
|
98
126
|
when,
|
|
99
127
|
hooks: {
|
|
@@ -117,9 +145,10 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
117
145
|
}
|
|
118
146
|
appliedReceipts.push(commit());
|
|
119
147
|
}
|
|
120
|
-
database.logger.
|
|
121
|
-
|
|
122
|
-
|
|
148
|
+
database.logger.log({
|
|
149
|
+
level: LogLevels.info,
|
|
150
|
+
message: `Sync is ready for ${viewName} into ${trackedTableName}`
|
|
151
|
+
});
|
|
123
152
|
}
|
|
124
153
|
}
|
|
125
154
|
});
|
|
@@ -136,10 +165,11 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
136
165
|
await database.writeTransaction(async (context) => {
|
|
137
166
|
await flushDiffRecordsWithContext(context, ignoredReceipts);
|
|
138
167
|
}).catch((error) => {
|
|
139
|
-
database.logger.
|
|
140
|
-
|
|
168
|
+
database.logger.log({
|
|
169
|
+
level: LogLevels.error,
|
|
170
|
+
message: `An error has been detected in the sync handler`,
|
|
141
171
|
error
|
|
142
|
-
);
|
|
172
|
+
});
|
|
143
173
|
});
|
|
144
174
|
}
|
|
145
175
|
async function flushDiffRecordsWithContext(context, appliedReceipts) {
|
|
@@ -179,16 +209,18 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
179
209
|
appliedReceipts.push(applied);
|
|
180
210
|
pendingOperationStore.resolvePendingFor(pendingOperations);
|
|
181
211
|
} catch (error) {
|
|
182
|
-
database.logger.
|
|
183
|
-
|
|
212
|
+
database.logger.log({
|
|
213
|
+
level: LogLevels.error,
|
|
214
|
+
message: `An error has been detected in the sync handler`,
|
|
184
215
|
error
|
|
185
|
-
);
|
|
216
|
+
});
|
|
186
217
|
}
|
|
187
218
|
}
|
|
188
219
|
async function start(afterOnChangeRegistered) {
|
|
189
|
-
database.logger.
|
|
190
|
-
|
|
191
|
-
|
|
220
|
+
database.logger.log({
|
|
221
|
+
level: LogLevels.info,
|
|
222
|
+
message: `Sync is starting for ${viewName} into ${trackedTableName}`
|
|
223
|
+
});
|
|
192
224
|
database.onChangeWithCallback(
|
|
193
225
|
{
|
|
194
226
|
onChange: async () => {
|
|
@@ -203,12 +235,12 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
203
235
|
);
|
|
204
236
|
await afterOnChangeRegistered?.();
|
|
205
237
|
if (abortController.signal.aborted) {
|
|
206
|
-
await
|
|
238
|
+
await disposeTrackingAfterAbort();
|
|
207
239
|
} else {
|
|
208
240
|
abortController.signal.addEventListener(
|
|
209
241
|
`abort`,
|
|
210
|
-
|
|
211
|
-
|
|
242
|
+
() => {
|
|
243
|
+
void disposeTrackingAfterAbort();
|
|
212
244
|
},
|
|
213
245
|
{ once: true }
|
|
214
246
|
);
|
|
@@ -216,13 +248,13 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
216
248
|
}
|
|
217
249
|
function runEagerSync() {
|
|
218
250
|
let onUnload = null;
|
|
219
|
-
|
|
251
|
+
let onUnloadStarted = false;
|
|
252
|
+
const startup = start(async () => {
|
|
220
253
|
const cleanup = await restConfig.onLoad?.();
|
|
254
|
+
onUnload = cleanup;
|
|
221
255
|
if (abortController.signal.aborted) {
|
|
222
|
-
cleanup?.();
|
|
223
256
|
return;
|
|
224
257
|
}
|
|
225
|
-
onUnload = cleanup;
|
|
226
258
|
const appliedReceipts = [];
|
|
227
259
|
await establishTracking(
|
|
228
260
|
{
|
|
@@ -245,38 +277,77 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
245
277
|
await Promise.all(appliedReceipts);
|
|
246
278
|
markReady();
|
|
247
279
|
}).catch((error) => {
|
|
248
|
-
database.logger.
|
|
249
|
-
|
|
280
|
+
database.logger.log({
|
|
281
|
+
level: LogLevels.error,
|
|
282
|
+
message: `Could not start syncing process for ${viewName} into ${trackedTableName}`,
|
|
250
283
|
error
|
|
251
|
-
);
|
|
284
|
+
});
|
|
252
285
|
if (collection.status === `loading`) {
|
|
253
286
|
markError(error);
|
|
254
287
|
}
|
|
255
288
|
});
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
289
|
+
const invokeOnUnload = () => {
|
|
290
|
+
if (onUnloadStarted || !onUnload) return;
|
|
291
|
+
onUnloadStarted = true;
|
|
292
|
+
const cleanup = onUnload;
|
|
293
|
+
onUnload = null;
|
|
294
|
+
return cleanup();
|
|
295
|
+
};
|
|
296
|
+
return async () => {
|
|
297
|
+
database.logger.log({
|
|
298
|
+
level: LogLevels.info,
|
|
299
|
+
message: `Sync has been stopped for ${viewName} into ${trackedTableName}`
|
|
300
|
+
});
|
|
260
301
|
abortController.abort();
|
|
261
|
-
|
|
302
|
+
const trackingDisposal = disposeTrackingAfterAbort();
|
|
303
|
+
let onUnloadFailure;
|
|
304
|
+
const settleOnUnload = async () => {
|
|
305
|
+
try {
|
|
306
|
+
await invokeOnUnload();
|
|
307
|
+
} catch (error) {
|
|
308
|
+
onUnloadFailure ??= { error };
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
const settlements = await Promise.allSettled([
|
|
312
|
+
startup,
|
|
313
|
+
trackingDisposal,
|
|
314
|
+
settleOnUnload()
|
|
315
|
+
]);
|
|
316
|
+
const disposalSettlement = settlements[1];
|
|
317
|
+
await settleOnUnload();
|
|
318
|
+
if (disposalSettlement.status === `rejected` && onUnloadFailure) {
|
|
319
|
+
throw new AggregateError(
|
|
320
|
+
[disposalSettlement.reason, onUnloadFailure.error],
|
|
321
|
+
`PowerSync tracking disposal and load-hook cleanup both failed`,
|
|
322
|
+
{ cause: disposalSettlement.reason }
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
if (disposalSettlement.status === `rejected`) {
|
|
326
|
+
throw disposalSettlement.reason;
|
|
327
|
+
}
|
|
328
|
+
if (onUnloadFailure) throw onUnloadFailure.error;
|
|
262
329
|
};
|
|
263
330
|
}
|
|
264
331
|
function runOnDemandSync() {
|
|
265
332
|
const demands = /* @__PURE__ */ new Map();
|
|
266
333
|
const releasedSubsets = /* @__PURE__ */ new WeakSet();
|
|
267
334
|
const pendingReleases = [];
|
|
335
|
+
const pendingDemandLoads = /* @__PURE__ */ new Set();
|
|
336
|
+
const pendingDemandCleanups = /* @__PURE__ */ new Set();
|
|
268
337
|
let stopped = false;
|
|
269
338
|
let trackingRevision = 0;
|
|
270
339
|
let reconciledTrackingRevision = 0;
|
|
271
340
|
let rebuildPromise = null;
|
|
272
341
|
let drainingReleases = false;
|
|
273
342
|
let releaseRetryTimer;
|
|
343
|
+
let demandCleanupCollector = null;
|
|
274
344
|
const startup = start();
|
|
275
345
|
void startup.catch(
|
|
276
|
-
(error) => database.logger.
|
|
277
|
-
|
|
346
|
+
(error) => database.logger.log({
|
|
347
|
+
level: LogLevels.error,
|
|
348
|
+
message: `Could not start syncing process for ${viewName} into ${trackedTableName}`,
|
|
278
349
|
error
|
|
279
|
-
)
|
|
350
|
+
})
|
|
280
351
|
);
|
|
281
352
|
const activeWhereExpressions = () => Array.from(demands.values()).filter((demand) => demand.active).map((demand) => demand.options.where);
|
|
282
353
|
const reconcileTracking = async () => {
|
|
@@ -367,6 +438,13 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
367
438
|
if (cleanup) demand.cleanup = cleanup;
|
|
368
439
|
} catch (error) {
|
|
369
440
|
demands.delete(options);
|
|
441
|
+
const collector = demandCleanupCollector;
|
|
442
|
+
if (collector) {
|
|
443
|
+
collector.failures.push({
|
|
444
|
+
order: collector.nextOrder++,
|
|
445
|
+
error
|
|
446
|
+
});
|
|
447
|
+
}
|
|
370
448
|
throw error;
|
|
371
449
|
}
|
|
372
450
|
if (
|
|
@@ -374,14 +452,20 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
374
452
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
375
453
|
stopped || releasedSubsets.has(options) || options.signal?.aborted || demands.get(options) !== demand
|
|
376
454
|
) {
|
|
377
|
-
|
|
378
|
-
demand.cleanup?.();
|
|
455
|
+
cleanupDemand(demand);
|
|
379
456
|
return;
|
|
380
457
|
}
|
|
381
458
|
demand.active = true;
|
|
382
459
|
trackingRevision++;
|
|
383
460
|
await rebuildTracking();
|
|
384
461
|
};
|
|
462
|
+
const trackLoadSubset = (options) => {
|
|
463
|
+
const task = loadSubset(options);
|
|
464
|
+
pendingDemandLoads.add(task);
|
|
465
|
+
const finish = () => pendingDemandLoads.delete(task);
|
|
466
|
+
void task.then(finish, finish);
|
|
467
|
+
return task;
|
|
468
|
+
};
|
|
385
469
|
const toInlinedWhereClause = (compiled) => {
|
|
386
470
|
if (!compiled.where) return "TRUE";
|
|
387
471
|
const sqlParts = compiled.where.split("?");
|
|
@@ -390,15 +474,55 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
390
474
|
...compiled.params
|
|
391
475
|
);
|
|
392
476
|
};
|
|
477
|
+
const reportDemandCleanupFailure = (error) => {
|
|
478
|
+
database.logger.log({
|
|
479
|
+
level: LogLevels.error,
|
|
480
|
+
message: `Could not clean up subset hook for ${viewName}`,
|
|
481
|
+
error
|
|
482
|
+
});
|
|
483
|
+
};
|
|
393
484
|
const cleanupDemand = (demand) => {
|
|
394
485
|
demands.delete(demand.options);
|
|
486
|
+
const collector = demandCleanupCollector;
|
|
487
|
+
const order = collector ? collector.nextOrder++ : void 0;
|
|
395
488
|
try {
|
|
396
|
-
demand.cleanup
|
|
489
|
+
const cleanup = demand.cleanup;
|
|
490
|
+
demand.cleanup = void 0;
|
|
491
|
+
const result = cleanup?.();
|
|
492
|
+
if (!isPromiseLike(result)) return;
|
|
493
|
+
const task = Promise.resolve(result);
|
|
494
|
+
pendingDemandCleanups.add(task);
|
|
495
|
+
const finish = () => pendingDemandCleanups.delete(task);
|
|
496
|
+
void task.then(finish, finish);
|
|
497
|
+
if (collector) {
|
|
498
|
+
collector.tasks.push({ order, promise: task });
|
|
499
|
+
collector.ownedTasks.add(task);
|
|
500
|
+
void task.catch(() => void 0);
|
|
501
|
+
} else {
|
|
502
|
+
void task.catch((error) => {
|
|
503
|
+
if (!demandCleanupCollector?.ownedTasks.has(task)) {
|
|
504
|
+
reportDemandCleanupFailure(error);
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
}
|
|
397
508
|
} catch (error) {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
509
|
+
if (collector) {
|
|
510
|
+
collector.failures.push({ order, error });
|
|
511
|
+
} else {
|
|
512
|
+
reportDemandCleanupFailure(error);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
const throwDemandCleanupFailures = (collector, primaryFailures = []) => {
|
|
517
|
+
const errors = [
|
|
518
|
+
...primaryFailures,
|
|
519
|
+
...collector.failures.slice().sort((left, right) => left.order - right.order).map(({ error }) => error)
|
|
520
|
+
];
|
|
521
|
+
if (errors.length === 1) throw errors[0];
|
|
522
|
+
if (errors.length > 1) {
|
|
523
|
+
throw new AggregateError(errors, `PowerSync cleanup failed`, {
|
|
524
|
+
cause: errors[0]
|
|
525
|
+
});
|
|
402
526
|
}
|
|
403
527
|
};
|
|
404
528
|
const performPhysicalRelease = async (options) => {
|
|
@@ -462,10 +586,11 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
462
586
|
3e4
|
|
463
587
|
);
|
|
464
588
|
retryDelay = retryDelay === 0 ? delay : Math.min(retryDelay, delay);
|
|
465
|
-
database.logger.
|
|
466
|
-
|
|
589
|
+
database.logger.log({
|
|
590
|
+
level: LogLevels.error,
|
|
591
|
+
message: `Could not release subset tracking for ${viewName}; retrying`,
|
|
467
592
|
error
|
|
468
|
-
);
|
|
593
|
+
});
|
|
469
594
|
}
|
|
470
595
|
}
|
|
471
596
|
} finally {
|
|
@@ -493,16 +618,58 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
493
618
|
stopped = true;
|
|
494
619
|
clearTimeout(releaseRetryTimer);
|
|
495
620
|
releaseRetryTimer = void 0;
|
|
496
|
-
database.logger.
|
|
497
|
-
|
|
498
|
-
|
|
621
|
+
database.logger.log({
|
|
622
|
+
level: LogLevels.info,
|
|
623
|
+
message: `Sync has been stopped for ${viewName} into ${trackedTableName}`
|
|
624
|
+
});
|
|
499
625
|
abortController.abort();
|
|
626
|
+
const trackingDisposal = disposeTrackingAfterAbort();
|
|
627
|
+
const pendingLoads = [...pendingDemandLoads];
|
|
628
|
+
const pendingTasks = [...pendingDemandCleanups];
|
|
629
|
+
const collector = {
|
|
630
|
+
tasks: pendingTasks.map((promise, order) => ({ order, promise })),
|
|
631
|
+
ownedTasks: new Set(pendingTasks),
|
|
632
|
+
failures: [],
|
|
633
|
+
nextOrder: pendingTasks.length
|
|
634
|
+
};
|
|
635
|
+
demandCleanupCollector = collector;
|
|
500
636
|
for (const demand of demands.values()) {
|
|
501
637
|
cleanupDemand(demand);
|
|
502
638
|
}
|
|
503
639
|
pendingReleases.length = 0;
|
|
640
|
+
return (async () => {
|
|
641
|
+
const ownershipSettlements = await Promise.allSettled([
|
|
642
|
+
trackingDisposal,
|
|
643
|
+
...pendingLoads
|
|
644
|
+
]);
|
|
645
|
+
const disposalSettlement = ownershipSettlements[0];
|
|
646
|
+
let settledTasks = 0;
|
|
647
|
+
while (settledTasks < collector.tasks.length) {
|
|
648
|
+
const tasks = collector.tasks.slice(settledTasks);
|
|
649
|
+
settledTasks = collector.tasks.length;
|
|
650
|
+
const outcomes = await Promise.allSettled(
|
|
651
|
+
tasks.map(({ promise }) => promise)
|
|
652
|
+
);
|
|
653
|
+
for (const [index, outcome] of outcomes.entries()) {
|
|
654
|
+
if (outcome.status === `rejected`) {
|
|
655
|
+
collector.failures.push({
|
|
656
|
+
order: tasks[index].order,
|
|
657
|
+
error: outcome.reason
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
throwDemandCleanupFailures(
|
|
663
|
+
collector,
|
|
664
|
+
disposalSettlement.status === `rejected` ? [disposalSettlement.reason] : []
|
|
665
|
+
);
|
|
666
|
+
})().finally(() => {
|
|
667
|
+
if (demandCleanupCollector === collector) {
|
|
668
|
+
demandCleanupCollector = null;
|
|
669
|
+
}
|
|
670
|
+
});
|
|
504
671
|
},
|
|
505
|
-
loadSubset:
|
|
672
|
+
loadSubset: trackLoadSubset,
|
|
506
673
|
unloadSubset
|
|
507
674
|
};
|
|
508
675
|
}
|
|
@@ -533,13 +700,7 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
533
700
|
tableName: viewName,
|
|
534
701
|
trackedTableName,
|
|
535
702
|
metadataIsTracked,
|
|
536
|
-
serializeValue: (value) => serializeForSQLite(
|
|
537
|
-
value,
|
|
538
|
-
// This is required by the input generic
|
|
539
|
-
table,
|
|
540
|
-
// Coerce serializer to the shape that corresponds to the Table constructed from OutputType
|
|
541
|
-
serializer
|
|
542
|
-
)
|
|
703
|
+
serializeValue: (value) => serializeForSQLite(value, table, serializer)
|
|
543
704
|
})
|
|
544
705
|
}
|
|
545
706
|
};
|