@tanstack/powersync-db-collection 0.1.62 → 0.1.64
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/powersync.cjs +50 -37
- package/dist/cjs/powersync.cjs.map +1 -1
- package/dist/esm/powersync.js +50 -37
- package/dist/esm/powersync.js.map +1 -1
- package/package.json +2 -2
- package/src/powersync.ts +83 -56
package/dist/cjs/powersync.cjs
CHANGED
|
@@ -72,8 +72,8 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
72
72
|
disposeTracking = null;
|
|
73
73
|
await dispose(context ? { context } : void 0);
|
|
74
74
|
}
|
|
75
|
-
async function createDiffTrigger(options) {
|
|
76
|
-
const { setupContext, when, writeType, batchQuery
|
|
75
|
+
async function createDiffTrigger(options, appliedReceipts) {
|
|
76
|
+
const { setupContext, immediate, when, writeType, batchQuery } = options;
|
|
77
77
|
return await database.triggers.createDiffTrigger({
|
|
78
78
|
source: viewName,
|
|
79
79
|
destination: trackedTableName,
|
|
@@ -84,7 +84,7 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
84
84
|
let currentBatchCount = syncBatchSize;
|
|
85
85
|
let cursor = 0;
|
|
86
86
|
while (currentBatchCount == syncBatchSize) {
|
|
87
|
-
begin();
|
|
87
|
+
begin(immediate ? { immediate: true } : void 0);
|
|
88
88
|
const batchItems = await batchQuery(
|
|
89
89
|
context,
|
|
90
90
|
syncBatchSize,
|
|
@@ -98,9 +98,8 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
98
98
|
value: deserializeSyncRow(row)
|
|
99
99
|
});
|
|
100
100
|
}
|
|
101
|
-
commit();
|
|
101
|
+
appliedReceipts.push(commit());
|
|
102
102
|
}
|
|
103
|
-
onReady();
|
|
104
103
|
database.logger.info(
|
|
105
104
|
`Sync is ready for ${viewName} into ${trackedTableName}`
|
|
106
105
|
);
|
|
@@ -109,8 +108,9 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
109
108
|
});
|
|
110
109
|
}
|
|
111
110
|
async function flushDiffRecords() {
|
|
111
|
+
const ignoredReceipts = [];
|
|
112
112
|
await database.writeTransaction(async (context) => {
|
|
113
|
-
await flushDiffRecordsWithContext(context);
|
|
113
|
+
await flushDiffRecordsWithContext(context, ignoredReceipts);
|
|
114
114
|
}).catch((error) => {
|
|
115
115
|
database.logger.error(
|
|
116
116
|
`An error has been detected in the sync handler`,
|
|
@@ -118,7 +118,7 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
118
118
|
);
|
|
119
119
|
});
|
|
120
120
|
}
|
|
121
|
-
async function flushDiffRecordsWithContext(context) {
|
|
121
|
+
async function flushDiffRecordsWithContext(context, appliedReceipts) {
|
|
122
122
|
if (!disposeTracking) {
|
|
123
123
|
return;
|
|
124
124
|
}
|
|
@@ -151,7 +151,8 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
151
151
|
});
|
|
152
152
|
}
|
|
153
153
|
await context.execute(`DELETE FROM ${trackedTableName}`);
|
|
154
|
-
commit();
|
|
154
|
+
const applied = commit();
|
|
155
|
+
appliedReceipts.push(applied);
|
|
155
156
|
pendingOperationStore.resolvePendingFor(pendingOperations);
|
|
156
157
|
} catch (error) {
|
|
157
158
|
database.logger.error(
|
|
@@ -193,19 +194,27 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
193
194
|
let onUnload = null;
|
|
194
195
|
start(async () => {
|
|
195
196
|
onUnload = await restConfig.onLoad?.();
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
197
|
+
const appliedReceipts = [];
|
|
198
|
+
disposeTracking = await createDiffTrigger(
|
|
199
|
+
{
|
|
200
|
+
// Initial eager hydration must make the source usable before
|
|
201
|
+
// PowerSync can persist a mutation queued during startup.
|
|
202
|
+
immediate: true,
|
|
203
|
+
when: {
|
|
204
|
+
[common.DiffTriggerOperation.INSERT]: `TRUE`,
|
|
205
|
+
[common.DiffTriggerOperation.UPDATE]: `TRUE`,
|
|
206
|
+
[common.DiffTriggerOperation.DELETE]: `TRUE`
|
|
207
|
+
},
|
|
208
|
+
writeType: (_rowId) => `insert`,
|
|
209
|
+
batchQuery: (lockContext, batchSize, cursor) => lockContext.getAll(
|
|
210
|
+
common.sanitizeSQL`SELECT * FROM ${viewName} LIMIT ? OFFSET ?`,
|
|
211
|
+
[batchSize, cursor]
|
|
212
|
+
)
|
|
201
213
|
},
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
),
|
|
207
|
-
onReady: () => markReady()
|
|
208
|
-
});
|
|
214
|
+
appliedReceipts
|
|
215
|
+
);
|
|
216
|
+
await Promise.all(appliedReceipts);
|
|
217
|
+
markReady();
|
|
209
218
|
}).catch((error) => {
|
|
210
219
|
database.logger.error(
|
|
211
220
|
`Could not start syncing process for ${viewName} into ${trackedTableName}`,
|
|
@@ -237,6 +246,7 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
237
246
|
const activeWhereExpressions = [];
|
|
238
247
|
const loadSubset = async (options) => {
|
|
239
248
|
if (hasStopped()) return;
|
|
249
|
+
const appliedReceipts = [];
|
|
240
250
|
if (options) {
|
|
241
251
|
activeWhereExpressions.push(options.where);
|
|
242
252
|
const cleanup = await restConfig.onLoadSubset?.(options);
|
|
@@ -254,9 +264,10 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
254
264
|
}
|
|
255
265
|
if (activeWhereExpressions.length === 0) {
|
|
256
266
|
await database.writeLock(async (ctx) => {
|
|
257
|
-
await flushDiffRecordsWithContext(ctx);
|
|
267
|
+
await flushDiffRecordsWithContext(ctx, appliedReceipts);
|
|
258
268
|
await safelyDisposeTracking(ctx);
|
|
259
269
|
});
|
|
270
|
+
await Promise.all(appliedReceipts);
|
|
260
271
|
return;
|
|
261
272
|
}
|
|
262
273
|
const combinedWhere = activeWhereExpressions.length === 1 ? activeWhereExpressions[0] : db.or(
|
|
@@ -277,24 +288,26 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
277
288
|
const oldDataWhenClause = toInlinedWhereClause(compiledOldData);
|
|
278
289
|
const viewWhereClause = toInlinedWhereClause(compiledView);
|
|
279
290
|
await database.writeLock(async (ctx) => {
|
|
280
|
-
await flushDiffRecordsWithContext(ctx);
|
|
291
|
+
await flushDiffRecordsWithContext(ctx, appliedReceipts);
|
|
281
292
|
await safelyDisposeTracking(ctx);
|
|
282
|
-
disposeTracking = await createDiffTrigger(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
293
|
+
disposeTracking = await createDiffTrigger(
|
|
294
|
+
{
|
|
295
|
+
setupContext: ctx,
|
|
296
|
+
when: {
|
|
297
|
+
[common.DiffTriggerOperation.INSERT]: newDataWhenClause,
|
|
298
|
+
[common.DiffTriggerOperation.UPDATE]: `(${newDataWhenClause}) OR (${oldDataWhenClause})`,
|
|
299
|
+
[common.DiffTriggerOperation.DELETE]: oldDataWhenClause
|
|
300
|
+
},
|
|
301
|
+
writeType: (rowId) => collection.has(rowId) ? `update` : `insert`,
|
|
302
|
+
batchQuery: (lockContext, batchSize, cursor) => lockContext.getAll(
|
|
303
|
+
`SELECT * FROM ${viewName} WHERE ${viewWhereClause} LIMIT ? OFFSET ?`,
|
|
304
|
+
[batchSize, cursor]
|
|
305
|
+
)
|
|
288
306
|
},
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
`SELECT * FROM ${viewName} WHERE ${viewWhereClause} LIMIT ? OFFSET ?`,
|
|
292
|
-
[batchSize, cursor]
|
|
293
|
-
),
|
|
294
|
-
onReady: () => {
|
|
295
|
-
}
|
|
296
|
-
});
|
|
307
|
+
appliedReceipts
|
|
308
|
+
);
|
|
297
309
|
});
|
|
310
|
+
await Promise.all(appliedReceipts);
|
|
298
311
|
};
|
|
299
312
|
const toInlinedWhereClause = (compiled) => {
|
|
300
313
|
if (!compiled.where) return "TRUE";
|
|
@@ -335,7 +348,7 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
335
348
|
for (const { id } of rowsToEvict) {
|
|
336
349
|
write({ type: `delete`, key: id });
|
|
337
350
|
}
|
|
338
|
-
commit();
|
|
351
|
+
void commit();
|
|
339
352
|
}
|
|
340
353
|
await loadSubset();
|
|
341
354
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"powersync.cjs","sources":["../../src/powersync.ts"],"sourcesContent":["import { DiffTriggerOperation, sanitizeSQL } from '@powersync/common'\nimport { or, withCollectionConfigFactory } from '@tanstack/db'\nimport { compileSQLite } from './sqlite-compiler'\nimport { PendingOperationStore } from './PendingOperationStore'\nimport { PowerSyncTransactor } from './PowerSyncTransactor'\nimport { DEFAULT_BATCH_SIZE } from './definitions'\nimport { asPowerSyncRecord, mapOperation } from './helpers'\nimport { convertTableToSchema } from './schema'\nimport { serializeForSQLite } from './serialization'\nimport type {\n CleanupFn,\n LoadSubsetOptions,\n OperationType,\n SyncConfig,\n} from '@tanstack/db'\nimport type {\n AnyTableColumnType,\n ExtractedTable,\n ExtractedTableColumns,\n MapBaseColumnType,\n OptionalExtractedTable,\n} from './helpers'\nimport type {\n BasePowerSyncCollectionConfig,\n ConfigWithArbitraryCollectionTypes,\n ConfigWithSQLiteInputType,\n ConfigWithSQLiteTypes,\n CustomSQLiteSerializer,\n EnhancedPowerSyncCollectionConfig,\n InferPowerSyncOutputType,\n PowerSyncCollectionConfig,\n PowerSyncCollectionUtils,\n} from './definitions'\nimport type { PendingOperation } from './PendingOperationStore'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { LockContext, Table, TriggerDiffRecord } from '@powersync/common'\n\n/**\n * Creates PowerSync collection options for use with a standard Collection.\n *\n * @template TTable - The SQLite-based typing\n * @template TSchema - The validation schema type (optionally supports a custom input type)\n * @param config - Configuration options for the PowerSync collection\n * @returns Collection options with utilities\n */\n\n// Overload 1: No schema is provided\n\n/**\n * Creates a PowerSync collection configuration with basic default validation.\n * Input and Output types are the SQLite column types.\n *\n * @example\n * ```typescript\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * }),\n * })\n *\n * type Document = (typeof APP_SCHEMA)[\"types\"][\"documents\"]\n *\n * const db = new PowerSyncDatabase({\n * database: {\n * dbFilename: \"test.sqlite\",\n * },\n * schema: APP_SCHEMA,\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents\n * })\n * )\n * ```\n */\nexport function powerSyncCollectionOptions<TTable extends Table = Table>(\n config: BasePowerSyncCollectionConfig<TTable, never> & ConfigWithSQLiteTypes,\n): EnhancedPowerSyncCollectionConfig<\n TTable,\n OptionalExtractedTable<TTable>,\n never\n>\n\n// Overload 2: Schema is provided and the TInput matches SQLite types.\n\n/**\n * Creates a PowerSync collection configuration with schema validation.\n *\n * The input types satisfy the SQLite column types.\n *\n * The output types are defined by the provided schema. This schema can enforce additional\n * validation or type transforms.\n * Arbitrary output typed mutations are encoded to SQLite for persistence. We provide a basic standard\n * serialization implementation to serialize column values. Custom or advanced types require providing additional\n * serializer specifications. Partial column overrides can be supplied to `serializer`.\n *\n * @example\n * ```typescript\n * import { z } from \"zod\"\n *\n * // The PowerSync SQLite schema\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * // Dates are stored as ISO date strings in SQLite\n * created_at: column.text\n * }),\n * })\n *\n * // Advanced Zod validations. The output type of this schema\n * // is constrained to the SQLite schema of APP_SCHEMA\n * const schema = z.object({\n * id: z.string(),\n * // Notice that `name` is not nullable (is required) here and it has additional validation\n * name: z.string().min(3, { message: \"Should be at least 3 characters\" }).nullable(),\n * // The input type is still the SQLite string type. While collections will output smart Date instances.\n * created_at: z.string().transform(val => new Date(val))\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents,\n * schema,\n * serializer: {\n * // The default is toISOString, this is just to demonstrate custom overrides\n * created_at: (outputValue) => outputValue.toISOString(),\n * },\n * })\n * )\n * ```\n */\nexport function powerSyncCollectionOptions<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // TInput is the SQLite types. We can use the supplied schema to validate sync input\n OptionalExtractedTable<TTable>,\n AnyTableColumnType<TTable>\n >,\n>(\n config: BasePowerSyncCollectionConfig<TTable, TSchema> &\n ConfigWithSQLiteInputType<TTable, TSchema>,\n): EnhancedPowerSyncCollectionConfig<\n TTable,\n InferPowerSyncOutputType<TTable, TSchema>,\n TSchema\n> & {\n schema: TSchema\n}\n\n// Overload 3: Schema is provided with arbitrary TInput and TOutput\n/**\n * Creates a PowerSync collection configuration with schema validation.\n *\n * The input types are not linked to the internal SQLite table types. This can\n * give greater flexibility, e.g. by accepting rich types as input for `insert` or `update` operations.\n * An additional `deserializationSchema` is required in order to process incoming SQLite updates to the output type.\n *\n * The output types are defined by the provided schema. This schema can enforce additional\n * validation or type transforms.\n * Arbitrary output typed mutations are encoded to SQLite for persistence. We provide a basic standard\n * serialization implementation to serialize column values. Custom or advanced types require providing additional\n * serializer specifications. Partial column overrides can be supplied to `serializer`.\n *\n * @example\n * ```typescript\n * import { z } from \"zod\"\n *\n * // The PowerSync SQLite schema\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * // Booleans are represented as integers in SQLite\n * is_active: column.integer\n * }),\n * })\n *\n * // Advanced Zod validations.\n * // We accept boolean values as input for operations and expose Booleans in query results\n * const schema = z.object({\n * id: z.string(),\n * isActive: z.boolean(), // TInput and TOutput are boolean\n * })\n *\n * // The deserializationSchema converts the SQLite synced INTEGER (0/1) values to booleans.\n * const deserializationSchema = z.object({\n * id: z.string(),\n * isActive: z.number().nullable().transform((val) => val == null ? true : val > 0),\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents,\n * schema,\n * deserializationSchema,\n * })\n * )\n * ```\n */\nexport function powerSyncCollectionOptions<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // The input and output must have the same keys, the value types can be arbitrary\n AnyTableColumnType<TTable>,\n AnyTableColumnType<TTable>\n >,\n>(\n config: BasePowerSyncCollectionConfig<TTable, TSchema> &\n ConfigWithArbitraryCollectionTypes<TTable, TSchema>,\n): EnhancedPowerSyncCollectionConfig<\n TTable,\n InferPowerSyncOutputType<TTable, TSchema>,\n TSchema\n> & {\n utils: PowerSyncCollectionUtils<TTable>\n schema: TSchema\n}\n\n/**\n * Implementation of powerSyncCollectionOptions that handles both schema and non-schema configurations.\n */\n\nexport function powerSyncCollectionOptions<\n TTable extends Table,\n TSchema extends StandardSchemaV1<any> = never,\n>(\n config: PowerSyncCollectionConfig<TTable, TSchema>,\n): ReturnType<typeof createPowerSyncCollectionConfig<TTable, TSchema>> {\n const outputConfig = createPowerSyncCollectionConfig(config)\n return withCollectionConfigFactory(outputConfig, () =>\n createPowerSyncCollectionConfig(config),\n )\n}\n\nfunction createPowerSyncCollectionConfig<\n TTable extends Table,\n TSchema extends StandardSchemaV1<any> = never,\n>(config: PowerSyncCollectionConfig<TTable, TSchema>) {\n const {\n database,\n table,\n schema: inputSchema,\n syncBatchSize = DEFAULT_BATCH_SIZE,\n syncMode = 'eager',\n ...restConfig\n } = config\n\n const deserializationSchema =\n `deserializationSchema` in config ? config.deserializationSchema : null\n const serializer = `serializer` in config ? config.serializer : undefined\n const onDeserializationError =\n `onDeserializationError` in config\n ? config.onDeserializationError\n : undefined\n\n // The SQLite table type\n type TableType = ExtractedTable<TTable>\n\n // The collection output type\n type OutputType = InferPowerSyncOutputType<TTable, TSchema>\n\n const { viewName, trackMetadata: metadataIsTracked } = table\n\n /**\n * Deserializes data from the incoming sync stream\n */\n const deserializeSyncRow = (value: TableType): OutputType => {\n const validationSchema = deserializationSchema || schema\n const validation = validationSchema[`~standard`].validate(value)\n if (`value` in validation) {\n return validation.value\n } else if (`issues` in validation) {\n const issueMessage = `Failed to validate incoming data for ${viewName}. Issues: ${validation.issues.map((issue) => `${issue.path} - ${issue.message}`)}`\n database.logger.error(issueMessage)\n onDeserializationError!(validation)\n throw new Error(issueMessage)\n } else {\n const unknownErrorMessage = `Unknown deserialization error for ${viewName}`\n database.logger.error(unknownErrorMessage)\n onDeserializationError!({ issues: [{ message: unknownErrorMessage }] })\n throw new Error(unknownErrorMessage)\n }\n }\n\n // We can do basic runtime validations for columns if not explicit schema has been provided\n const schema = inputSchema ?? (convertTableToSchema(table) as TSchema)\n /**\n * The onInsert, onUpdate, and onDelete handlers should only return\n * after we have written the changes to TanStack DB.\n * We currently only write to TanStack DB from a diff trigger.\n * We wait for the diff trigger to observe the change,\n * and only then return from the on[X] handlers.\n * This ensures that when the transaction is reported as\n * complete to the caller, the in-memory state is already\n * consistent with the database.\n */\n const pendingOperationStore = PendingOperationStore.GLOBAL\n // Keep the tracked table unique in case of multiple tabs.\n const trackedTableName = `__${viewName}_tracking_${Math.floor(\n Math.random() * 0xffffffff,\n )\n .toString(16)\n .padStart(8, `0`)}`\n\n const transactor = new PowerSyncTransactor({\n database,\n })\n\n /**\n * \"sync\"\n * Notice that this describes the Sync between the local SQLite table\n * and the in-memory tanstack-db collection.\n */\n const sync: SyncConfig<OutputType, string> = {\n sync: (params) => {\n const { begin, write, collection, commit, markReady, markError } = params\n const abortController = new AbortController()\n\n let disposeTracking:\n | ((options?: { context?: LockContext }) => Promise<void>)\n | null = null\n\n if (syncMode === `eager`) {\n return runEagerSync()\n } else {\n return runOnDemandSync()\n }\n\n /**\n * Disposes the current diff trigger, if one is active, and clears the\n * tracking state.\n */\n async function safelyDisposeTracking(\n context?: LockContext,\n ): Promise<void> {\n const dispose = disposeTracking\n if (!dispose) {\n return\n }\n\n disposeTracking = null\n await dispose(context ? { context } : undefined)\n }\n\n async function createDiffTrigger(options: {\n setupContext?: LockContext\n when: Record<DiffTriggerOperation, string>\n writeType: (rowId: string) => OperationType\n batchQuery: (\n lockContext: LockContext,\n batchSize: number,\n cursor: number,\n ) => Promise<Array<TableType>>\n onReady: () => void\n }) {\n const { setupContext, when, writeType, batchQuery, onReady } = options\n\n return await database.triggers.createDiffTrigger({\n source: viewName,\n destination: trackedTableName,\n setupContext,\n when,\n hooks: {\n beforeCreate: async (context) => {\n let currentBatchCount = syncBatchSize\n let cursor = 0\n while (currentBatchCount == syncBatchSize) {\n begin()\n\n const batchItems = await batchQuery(\n context,\n syncBatchSize,\n cursor,\n )\n currentBatchCount = batchItems.length\n cursor += currentBatchCount\n for (const row of batchItems) {\n write({\n type: writeType(row.id),\n value: deserializeSyncRow(row),\n })\n }\n commit()\n }\n onReady()\n database.logger.info(\n `Sync is ready for ${viewName} into ${trackedTableName}`,\n )\n },\n },\n })\n }\n\n async function flushDiffRecords(): Promise<void> {\n await database\n .writeTransaction(async (context) => {\n await flushDiffRecordsWithContext(context)\n })\n .catch((error) => {\n database.logger.error(\n `An error has been detected in the sync handler`,\n error,\n )\n })\n }\n\n // We can use this directly if we want to pair a flush with dispose+recreate diff trigger.\n async function flushDiffRecordsWithContext(\n context: LockContext,\n ): Promise<void> {\n // There is nothing to flush if no tracking table is currently active.\n if (!disposeTracking) {\n return\n }\n\n try {\n begin()\n const operations = await context.getAll<TriggerDiffRecord>(\n `SELECT * FROM ${trackedTableName} ORDER BY operation_id ASC`,\n )\n const pendingOperations: Array<PendingOperation> = []\n\n for (const op of operations) {\n const { id, operation, timestamp, value } = op\n const parsedValue = deserializeSyncRow({\n id,\n ...JSON.parse(value),\n })\n const parsedPreviousValue =\n op.operation == DiffTriggerOperation.UPDATE\n ? deserializeSyncRow({\n id,\n ...JSON.parse(op.previous_value),\n })\n : undefined\n write({\n type: mapOperation(operation),\n value: parsedValue,\n previousValue: parsedPreviousValue,\n })\n pendingOperations.push({\n id,\n operation,\n timestamp,\n tableName: viewName,\n })\n }\n\n // clear the current operations\n await context.execute(`DELETE FROM ${trackedTableName}`)\n\n commit()\n pendingOperationStore.resolvePendingFor(pendingOperations)\n } catch (error) {\n database.logger.error(\n `An error has been detected in the sync handler`,\n error,\n )\n }\n }\n\n // The sync function needs to be synchronous.\n async function start(afterOnChangeRegistered?: () => Promise<void>) {\n database.logger.info(\n `Sync is starting for ${viewName} into ${trackedTableName}`,\n )\n database.onChangeWithCallback(\n {\n onChange: async () => {\n await flushDiffRecords()\n },\n },\n {\n signal: abortController.signal,\n triggerImmediate: false,\n tables: [trackedTableName],\n },\n )\n\n await afterOnChangeRegistered?.()\n\n // If the abort controller was aborted while processing the request above\n if (abortController.signal.aborted) {\n await safelyDisposeTracking()\n } else {\n abortController.signal.addEventListener(\n `abort`,\n async () => {\n await safelyDisposeTracking()\n },\n { once: true },\n )\n }\n }\n\n // Eager mode.\n // Registers a diff trigger for the entire table.\n function runEagerSync() {\n let onUnload: CleanupFn | void | null = null\n\n start(async () => {\n onUnload = await restConfig.onLoad?.()\n\n disposeTracking = await createDiffTrigger({\n when: {\n [DiffTriggerOperation.INSERT]: `TRUE`,\n [DiffTriggerOperation.UPDATE]: `TRUE`,\n [DiffTriggerOperation.DELETE]: `TRUE`,\n },\n writeType: (_rowId: string) => `insert`,\n batchQuery: (\n lockContext: LockContext,\n batchSize: number,\n cursor: number,\n ) =>\n lockContext.getAll<TableType>(\n sanitizeSQL`SELECT * FROM ${viewName} LIMIT ? OFFSET ?`,\n [batchSize, cursor],\n ),\n onReady: () => markReady(),\n })\n }).catch((error) => {\n database.logger.error(\n `Could not start syncing process for ${viewName} into ${trackedTableName}`,\n error,\n )\n if (collection.status === `loading`) {\n markError(error)\n }\n })\n\n return () => {\n database.logger.info(\n `Sync has been stopped for ${viewName} into ${trackedTableName}`,\n )\n abortController.abort()\n onUnload?.()\n }\n }\n\n // On-demand mode.\n // Registers a diff trigger for the active WHERE expressions.\n function runOnDemandSync() {\n const unloadSubsetCallbacks = new Map<LoadSubsetOptions, CleanupFn>()\n const releasedSubsets = new WeakSet<LoadSubsetOptions>()\n let stopped = false\n const hasStopped = () => stopped\n\n start().catch((error) =>\n database.logger.error(\n `Could not start syncing process for ${viewName} into ${trackedTableName}`,\n error,\n ),\n )\n\n // Tracks all active WHERE expressions for on-demand sync filtering.\n // Each loadSubset call pushes its predicate; unloadSubset removes it.\n const activeWhereExpressions: Array<LoadSubsetOptions['where']> = []\n\n const loadSubset = async (\n options?: LoadSubsetOptions,\n ): Promise<void> => {\n if (hasStopped()) return\n\n if (options) {\n activeWhereExpressions.push(options.where)\n const cleanup = await restConfig.onLoadSubset?.(options)\n if (hasStopped()) {\n cleanup?.()\n return\n }\n if (cleanup) {\n if (releasedSubsets.has(options) || options.signal?.aborted) {\n cleanup()\n } else {\n unloadSubsetCallbacks.set(options, cleanup)\n }\n }\n }\n\n // No predicates remain, so stop tracking entirely. Both calls are no-ops\n // when no tracking table is currently active.\n if (activeWhereExpressions.length === 0) {\n await database.writeLock(async (ctx) => {\n await flushDiffRecordsWithContext(ctx)\n await safelyDisposeTracking(ctx)\n })\n return\n }\n\n const combinedWhere =\n activeWhereExpressions.length === 1\n ? activeWhereExpressions[0]\n : or(\n activeWhereExpressions[0],\n activeWhereExpressions[1],\n ...activeWhereExpressions.slice(2),\n )\n\n const compiledNewData = compileSQLite(\n { where: combinedWhere },\n { jsonColumn: 'NEW.data' },\n )\n\n const compiledOldData = compileSQLite(\n { where: combinedWhere },\n { jsonColumn: 'OLD.data' },\n )\n\n const compiledView = compileSQLite({ where: combinedWhere })\n\n const newDataWhenClause = toInlinedWhereClause(compiledNewData)\n const oldDataWhenClause = toInlinedWhereClause(compiledOldData)\n const viewWhereClause = toInlinedWhereClause(compiledView)\n\n await database.writeLock(async (ctx) => {\n // Replace any active tracking with one covering the new set of\n // predicates.\n await flushDiffRecordsWithContext(ctx)\n await safelyDisposeTracking(ctx)\n\n disposeTracking = await createDiffTrigger({\n setupContext: ctx,\n when: {\n [DiffTriggerOperation.INSERT]: newDataWhenClause,\n [DiffTriggerOperation.UPDATE]: `(${newDataWhenClause}) OR (${oldDataWhenClause})`,\n [DiffTriggerOperation.DELETE]: oldDataWhenClause,\n },\n writeType: (rowId: string) =>\n collection.has(rowId) ? `update` : `insert`,\n batchQuery: (\n lockContext: LockContext,\n batchSize: number,\n cursor: number,\n ) =>\n lockContext.getAll<TableType>(\n `SELECT * FROM ${viewName} WHERE ${viewWhereClause} LIMIT ? OFFSET ?`,\n [batchSize, cursor],\n ),\n onReady: () => {},\n })\n })\n }\n\n const toInlinedWhereClause = (compiled: {\n where?: string\n params: Array<unknown>\n }): string => {\n if (!compiled.where) return 'TRUE'\n const sqlParts = compiled.where.split('?')\n return sanitizeSQL(\n sqlParts as unknown as TemplateStringsArray,\n ...compiled.params,\n )\n }\n\n const unloadSubset = async (options: LoadSubsetOptions) => {\n releasedSubsets.add(options)\n unloadSubsetCallbacks.get(options)?.()\n unloadSubsetCallbacks.delete(options)\n\n const idx = activeWhereExpressions.indexOf(options.where)\n if (idx !== -1) {\n activeWhereExpressions.splice(idx, 1)\n }\n\n // Evict rows that were exclusively loaded by the departing predicate.\n // These are rows matching the departing WHERE that are no longer covered\n // by any remaining active predicate.\n const compiledDeparting = compileSQLite({ where: options.where })\n const departingWhereSQL = toInlinedWhereClause(compiledDeparting)\n\n let evictionSQL: string\n if (activeWhereExpressions.length === 0) {\n evictionSQL = `SELECT id FROM ${viewName} WHERE ${departingWhereSQL}`\n } else {\n const combinedRemaining =\n activeWhereExpressions.length === 1\n ? activeWhereExpressions[0]!\n : or(\n activeWhereExpressions[0],\n activeWhereExpressions[1],\n ...activeWhereExpressions.slice(2),\n )\n const compiledRemaining = compileSQLite({\n where: combinedRemaining,\n })\n const remainingWhereSQL = toInlinedWhereClause(compiledRemaining)\n evictionSQL = `SELECT id FROM ${viewName} WHERE (${departingWhereSQL}) AND NOT (${remainingWhereSQL})`\n }\n\n const rowsToEvict = await database.getAll<{ id: string }>(evictionSQL)\n if (rowsToEvict.length > 0) {\n begin()\n for (const { id } of rowsToEvict) {\n write({ type: `delete`, key: id })\n }\n commit()\n }\n\n // Recreate the diff trigger for the remaining active WHERE expressions.\n await loadSubset()\n }\n\n markReady()\n\n return {\n cleanup: () => {\n stopped = true\n database.logger.info(\n `Sync has been stopped for ${viewName} into ${trackedTableName}`,\n )\n abortController.abort()\n for (const cleanup of unloadSubsetCallbacks.values()) cleanup()\n unloadSubsetCallbacks.clear()\n activeWhereExpressions.length = 0\n },\n loadSubset: (options: LoadSubsetOptions) => loadSubset(options),\n unloadSubset: (options: LoadSubsetOptions) => unloadSubset(options),\n }\n }\n },\n // Expose the getSyncMetadata function\n getSyncMetadata: undefined,\n }\n\n const getKey = (record: OutputType) => asPowerSyncRecord(record).id\n\n const outputConfig: EnhancedPowerSyncCollectionConfig<\n TTable,\n OutputType,\n TSchema\n > = {\n ...restConfig,\n schema,\n getKey,\n // Syncing should start immediately since we need to monitor the changes for mutations\n startSync: true,\n syncMode,\n sync,\n onInsert: async (params) => {\n // The transaction here should only ever contain a single insert mutation\n return await transactor.applyTransaction(params.transaction)\n },\n onUpdate: async (params) => {\n // The transaction here should only ever contain a single update mutation\n return await transactor.applyTransaction(params.transaction)\n },\n onDelete: async (params) => {\n // The transaction here should only ever contain a single delete mutation\n return await transactor.applyTransaction(params.transaction)\n },\n utils: {\n getMeta: () => ({\n tableName: viewName,\n trackedTableName,\n metadataIsTracked,\n serializeValue: (value) =>\n serializeForSQLite(\n value,\n // This is required by the input generic\n table as Table<\n MapBaseColumnType<InferPowerSyncOutputType<TTable, TSchema>>\n >,\n // Coerce serializer to the shape that corresponds to the Table constructed from OutputType\n serializer as CustomSQLiteSerializer<\n OutputType,\n ExtractedTableColumns<Table<MapBaseColumnType<OutputType>>>\n >,\n ),\n }),\n },\n }\n return outputConfig\n}\n"],"names":["withCollectionConfigFactory","DEFAULT_BATCH_SIZE","schema","convertTableToSchema","PendingOperationStore","PowerSyncTransactor","DiffTriggerOperation","mapOperation","sanitizeSQL","or","compileSQLite","asPowerSyncRecord","serializeForSQLite"],"mappings":";;;;;;;;;;;AAiOO,SAAS,2BAId,QACqE;AACrE,QAAM,eAAe,gCAAgC,MAAM;AAC3D,SAAOA,GAAAA;AAAAA,IAA4B;AAAA,IAAc,MAC/C,gCAAgC,MAAM;AAAA,EAAA;AAE1C;AAEA,SAAS,gCAGP,QAAoD;AACpD,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR,gBAAgBC,YAAAA;AAAAA,IAChB,WAAW;AAAA,IACX,GAAG;AAAA,EAAA,IACD;AAEJ,QAAM,wBACJ,2BAA2B,SAAS,OAAO,wBAAwB;AACrE,QAAM,aAAa,gBAAgB,SAAS,OAAO,aAAa;AAChE,QAAM,yBACJ,4BAA4B,SACxB,OAAO,yBACP;AAQN,QAAM,EAAE,UAAU,eAAe,kBAAA,IAAsB;AAKvD,QAAM,qBAAqB,CAAC,UAAiC;AAC3D,UAAM,mBAAmB,yBAAyBC;AAClD,UAAM,aAAa,iBAAiB,WAAW,EAAE,SAAS,KAAK;AAC/D,QAAI,WAAW,YAAY;AACzB,aAAO,WAAW;AAAA,IACpB,WAAW,YAAY,YAAY;AACjC,YAAM,eAAe,wCAAwC,QAAQ,aAAa,WAAW,OAAO,IAAI,CAAC,UAAU,GAAG,MAAM,IAAI,MAAM,MAAM,OAAO,EAAE,CAAC;AACtJ,eAAS,OAAO,MAAM,YAAY;AAClC,6BAAwB,UAAU;AAClC,YAAM,IAAI,MAAM,YAAY;AAAA,IAC9B,OAAO;AACL,YAAM,sBAAsB,qCAAqC,QAAQ;AACzE,eAAS,OAAO,MAAM,mBAAmB;AACzC,6BAAwB,EAAE,QAAQ,CAAC,EAAE,SAAS,oBAAA,CAAqB,GAAG;AACtE,YAAM,IAAI,MAAM,mBAAmB;AAAA,IACrC;AAAA,EACF;AAGA,QAAMA,WAAS,eAAgBC,OAAAA,qBAAqB,KAAK;AAWzD,QAAM,wBAAwBC,sBAAAA,sBAAsB;AAEpD,QAAM,mBAAmB,KAAK,QAAQ,aAAa,KAAK;AAAA,IACtD,KAAK,WAAW;AAAA,EAAA,EAEf,SAAS,EAAE,EACX,SAAS,GAAG,GAAG,CAAC;AAEnB,QAAM,aAAa,IAAIC,wCAAoB;AAAA,IACzC;AAAA,EAAA,CACD;AAOD,QAAM,OAAuC;AAAA,IAC3C,MAAM,CAAC,WAAW;AAChB,YAAM,EAAE,OAAO,OAAO,YAAY,QAAQ,WAAW,cAAc;AACnE,YAAM,kBAAkB,IAAI,gBAAA;AAE5B,UAAI,kBAEO;AAEX,UAAI,aAAa,SAAS;AACxB,eAAO,aAAA;AAAA,MACT,OAAO;AACL,eAAO,gBAAA;AAAA,MACT;AAMA,qBAAe,sBACb,SACe;AACf,cAAM,UAAU;AAChB,YAAI,CAAC,SAAS;AACZ;AAAA,QACF;AAEA,0BAAkB;AAClB,cAAM,QAAQ,UAAU,EAAE,QAAA,IAAY,MAAS;AAAA,MACjD;AAEA,qBAAe,kBAAkB,SAU9B;AACD,cAAM,EAAE,cAAc,MAAM,WAAW,YAAY,YAAY;AAE/D,eAAO,MAAM,SAAS,SAAS,kBAAkB;AAAA,UAC/C,QAAQ;AAAA,UACR,aAAa;AAAA,UACb;AAAA,UACA;AAAA,UACA,OAAO;AAAA,YACL,cAAc,OAAO,YAAY;AAC/B,kBAAI,oBAAoB;AACxB,kBAAI,SAAS;AACb,qBAAO,qBAAqB,eAAe;AACzC,sBAAA;AAEA,sBAAM,aAAa,MAAM;AAAA,kBACvB;AAAA,kBACA;AAAA,kBACA;AAAA,gBAAA;AAEF,oCAAoB,WAAW;AAC/B,0BAAU;AACV,2BAAW,OAAO,YAAY;AAC5B,wBAAM;AAAA,oBACJ,MAAM,UAAU,IAAI,EAAE;AAAA,oBACtB,OAAO,mBAAmB,GAAG;AAAA,kBAAA,CAC9B;AAAA,gBACH;AACA,uBAAA;AAAA,cACF;AACA,sBAAA;AACA,uBAAS,OAAO;AAAA,gBACd,qBAAqB,QAAQ,SAAS,gBAAgB;AAAA,cAAA;AAAA,YAE1D;AAAA,UAAA;AAAA,QACF,CACD;AAAA,MACH;AAEA,qBAAe,mBAAkC;AAC/C,cAAM,SACH,iBAAiB,OAAO,YAAY;AACnC,gBAAM,4BAA4B,OAAO;AAAA,QAC3C,CAAC,EACA,MAAM,CAAC,UAAU;AAChB,mBAAS,OAAO;AAAA,YACd;AAAA,YACA;AAAA,UAAA;AAAA,QAEJ,CAAC;AAAA,MACL;AAGA,qBAAe,4BACb,SACe;AAEf,YAAI,CAAC,iBAAiB;AACpB;AAAA,QACF;AAEA,YAAI;AACF,gBAAA;AACA,gBAAM,aAAa,MAAM,QAAQ;AAAA,YAC/B,iBAAiB,gBAAgB;AAAA,UAAA;AAEnC,gBAAM,oBAA6C,CAAA;AAEnD,qBAAW,MAAM,YAAY;AAC3B,kBAAM,EAAE,IAAI,WAAW,WAAW,UAAU;AAC5C,kBAAM,cAAc,mBAAmB;AAAA,cACrC;AAAA,cACA,GAAG,KAAK,MAAM,KAAK;AAAA,YAAA,CACpB;AACD,kBAAM,sBACJ,GAAG,aAAaC,OAAAA,qBAAqB,SACjC,mBAAmB;AAAA,cACjB;AAAA,cACA,GAAG,KAAK,MAAM,GAAG,cAAc;AAAA,YAAA,CAChC,IACD;AACN,kBAAM;AAAA,cACJ,MAAMC,QAAAA,aAAa,SAAS;AAAA,cAC5B,OAAO;AAAA,cACP,eAAe;AAAA,YAAA,CAChB;AACD,8BAAkB,KAAK;AAAA,cACrB;AAAA,cACA;AAAA,cACA;AAAA,cACA,WAAW;AAAA,YAAA,CACZ;AAAA,UACH;AAGA,gBAAM,QAAQ,QAAQ,eAAe,gBAAgB,EAAE;AAEvD,iBAAA;AACA,gCAAsB,kBAAkB,iBAAiB;AAAA,QAC3D,SAAS,OAAO;AACd,mBAAS,OAAO;AAAA,YACd;AAAA,YACA;AAAA,UAAA;AAAA,QAEJ;AAAA,MACF;AAGA,qBAAe,MAAM,yBAA+C;AAClE,iBAAS,OAAO;AAAA,UACd,wBAAwB,QAAQ,SAAS,gBAAgB;AAAA,QAAA;AAE3D,iBAAS;AAAA,UACP;AAAA,YACE,UAAU,YAAY;AACpB,oBAAM,iBAAA;AAAA,YACR;AAAA,UAAA;AAAA,UAEF;AAAA,YACE,QAAQ,gBAAgB;AAAA,YACxB,kBAAkB;AAAA,YAClB,QAAQ,CAAC,gBAAgB;AAAA,UAAA;AAAA,QAC3B;AAGF,cAAM,0BAAA;AAGN,YAAI,gBAAgB,OAAO,SAAS;AAClC,gBAAM,sBAAA;AAAA,QACR,OAAO;AACL,0BAAgB,OAAO;AAAA,YACrB;AAAA,YACA,YAAY;AACV,oBAAM,sBAAA;AAAA,YACR;AAAA,YACA,EAAE,MAAM,KAAA;AAAA,UAAK;AAAA,QAEjB;AAAA,MACF;AAIA,eAAS,eAAe;AACtB,YAAI,WAAoC;AAExC,cAAM,YAAY;AAChB,qBAAW,MAAM,WAAW,SAAA;AAE5B,4BAAkB,MAAM,kBAAkB;AAAA,YACxC,MAAM;AAAA,cACJ,CAACD,OAAAA,qBAAqB,MAAM,GAAG;AAAA,cAC/B,CAACA,OAAAA,qBAAqB,MAAM,GAAG;AAAA,cAC/B,CAACA,OAAAA,qBAAqB,MAAM,GAAG;AAAA,YAAA;AAAA,YAEjC,WAAW,CAAC,WAAmB;AAAA,YAC/B,YAAY,CACV,aACA,WACA,WAEA,YAAY;AAAA,cACVE,mCAA4B,QAAQ;AAAA,cACpC,CAAC,WAAW,MAAM;AAAA,YAAA;AAAA,YAEtB,SAAS,MAAM,UAAA;AAAA,UAAU,CAC1B;AAAA,QACH,CAAC,EAAE,MAAM,CAAC,UAAU;AAClB,mBAAS,OAAO;AAAA,YACd,uCAAuC,QAAQ,SAAS,gBAAgB;AAAA,YACxE;AAAA,UAAA;AAEF,cAAI,WAAW,WAAW,WAAW;AACnC,sBAAU,KAAK;AAAA,UACjB;AAAA,QACF,CAAC;AAED,eAAO,MAAM;AACX,mBAAS,OAAO;AAAA,YACd,6BAA6B,QAAQ,SAAS,gBAAgB;AAAA,UAAA;AAEhE,0BAAgB,MAAA;AAChB,qBAAA;AAAA,QACF;AAAA,MACF;AAIA,eAAS,kBAAkB;AACzB,cAAM,4CAA4B,IAAA;AAClC,cAAM,sCAAsB,QAAA;AAC5B,YAAI,UAAU;AACd,cAAM,aAAa,MAAM;AAEzB,cAAA,EAAQ;AAAA,UAAM,CAAC,UACb,SAAS,OAAO;AAAA,YACd,uCAAuC,QAAQ,SAAS,gBAAgB;AAAA,YACxE;AAAA,UAAA;AAAA,QACF;AAKF,cAAM,yBAA4D,CAAA;AAElE,cAAM,aAAa,OACjB,YACkB;AAClB,cAAI,aAAc;AAElB,cAAI,SAAS;AACX,mCAAuB,KAAK,QAAQ,KAAK;AACzC,kBAAM,UAAU,MAAM,WAAW,eAAe,OAAO;AACvD,gBAAI,cAAc;AAChB,wBAAA;AACA;AAAA,YACF;AACA,gBAAI,SAAS;AACX,kBAAI,gBAAgB,IAAI,OAAO,KAAK,QAAQ,QAAQ,SAAS;AAC3D,wBAAA;AAAA,cACF,OAAO;AACL,sCAAsB,IAAI,SAAS,OAAO;AAAA,cAC5C;AAAA,YACF;AAAA,UACF;AAIA,cAAI,uBAAuB,WAAW,GAAG;AACvC,kBAAM,SAAS,UAAU,OAAO,QAAQ;AACtC,oBAAM,4BAA4B,GAAG;AACrC,oBAAM,sBAAsB,GAAG;AAAA,YACjC,CAAC;AACD;AAAA,UACF;AAEA,gBAAM,gBACJ,uBAAuB,WAAW,IAC9B,uBAAuB,CAAC,IACxBC,GAAAA;AAAAA,YACE,uBAAuB,CAAC;AAAA,YACxB,uBAAuB,CAAC;AAAA,YACxB,GAAG,uBAAuB,MAAM,CAAC;AAAA,UAAA;AAGzC,gBAAM,kBAAkBC,eAAAA;AAAAA,YACtB,EAAE,OAAO,cAAA;AAAA,YACT,EAAE,YAAY,WAAA;AAAA,UAAW;AAG3B,gBAAM,kBAAkBA,eAAAA;AAAAA,YACtB,EAAE,OAAO,cAAA;AAAA,YACT,EAAE,YAAY,WAAA;AAAA,UAAW;AAG3B,gBAAM,eAAeA,eAAAA,cAAc,EAAE,OAAO,eAAe;AAE3D,gBAAM,oBAAoB,qBAAqB,eAAe;AAC9D,gBAAM,oBAAoB,qBAAqB,eAAe;AAC9D,gBAAM,kBAAkB,qBAAqB,YAAY;AAEzD,gBAAM,SAAS,UAAU,OAAO,QAAQ;AAGtC,kBAAM,4BAA4B,GAAG;AACrC,kBAAM,sBAAsB,GAAG;AAE/B,8BAAkB,MAAM,kBAAkB;AAAA,cACxC,cAAc;AAAA,cACd,MAAM;AAAA,gBACJ,CAACJ,OAAAA,qBAAqB,MAAM,GAAG;AAAA,gBAC/B,CAACA,OAAAA,qBAAqB,MAAM,GAAG,IAAI,iBAAiB,SAAS,iBAAiB;AAAA,gBAC9E,CAACA,OAAAA,qBAAqB,MAAM,GAAG;AAAA,cAAA;AAAA,cAEjC,WAAW,CAAC,UACV,WAAW,IAAI,KAAK,IAAI,WAAW;AAAA,cACrC,YAAY,CACV,aACA,WACA,WAEA,YAAY;AAAA,gBACV,iBAAiB,QAAQ,UAAU,eAAe;AAAA,gBAClD,CAAC,WAAW,MAAM;AAAA,cAAA;AAAA,cAEtB,SAAS,MAAM;AAAA,cAAC;AAAA,YAAA,CACjB;AAAA,UACH,CAAC;AAAA,QACH;AAEA,cAAM,uBAAuB,CAAC,aAGhB;AACZ,cAAI,CAAC,SAAS,MAAO,QAAO;AAC5B,gBAAM,WAAW,SAAS,MAAM,MAAM,GAAG;AACzC,iBAAOE,OAAAA;AAAAA,YACL;AAAA,YACA,GAAG,SAAS;AAAA,UAAA;AAAA,QAEhB;AAEA,cAAM,eAAe,OAAO,YAA+B;AACzD,0BAAgB,IAAI,OAAO;AAC3B,gCAAsB,IAAI,OAAO,IAAA;AACjC,gCAAsB,OAAO,OAAO;AAEpC,gBAAM,MAAM,uBAAuB,QAAQ,QAAQ,KAAK;AACxD,cAAI,QAAQ,IAAI;AACd,mCAAuB,OAAO,KAAK,CAAC;AAAA,UACtC;AAKA,gBAAM,oBAAoBE,eAAAA,cAAc,EAAE,OAAO,QAAQ,OAAO;AAChE,gBAAM,oBAAoB,qBAAqB,iBAAiB;AAEhE,cAAI;AACJ,cAAI,uBAAuB,WAAW,GAAG;AACvC,0BAAc,kBAAkB,QAAQ,UAAU,iBAAiB;AAAA,UACrE,OAAO;AACL,kBAAM,oBACJ,uBAAuB,WAAW,IAC9B,uBAAuB,CAAC,IACxBD,GAAAA;AAAAA,cACE,uBAAuB,CAAC;AAAA,cACxB,uBAAuB,CAAC;AAAA,cACxB,GAAG,uBAAuB,MAAM,CAAC;AAAA,YAAA;AAEzC,kBAAM,oBAAoBC,eAAAA,cAAc;AAAA,cACtC,OAAO;AAAA,YAAA,CACR;AACD,kBAAM,oBAAoB,qBAAqB,iBAAiB;AAChE,0BAAc,kBAAkB,QAAQ,WAAW,iBAAiB,cAAc,iBAAiB;AAAA,UACrG;AAEA,gBAAM,cAAc,MAAM,SAAS,OAAuB,WAAW;AACrE,cAAI,YAAY,SAAS,GAAG;AAC1B,kBAAA;AACA,uBAAW,EAAE,GAAA,KAAQ,aAAa;AAChC,oBAAM,EAAE,MAAM,UAAU,KAAK,IAAI;AAAA,YACnC;AACA,mBAAA;AAAA,UACF;AAGA,gBAAM,WAAA;AAAA,QACR;AAEA,kBAAA;AAEA,eAAO;AAAA,UACL,SAAS,MAAM;AACb,sBAAU;AACV,qBAAS,OAAO;AAAA,cACd,6BAA6B,QAAQ,SAAS,gBAAgB;AAAA,YAAA;AAEhE,4BAAgB,MAAA;AAChB,uBAAW,WAAW,sBAAsB,OAAA,EAAU,SAAA;AACtD,kCAAsB,MAAA;AACtB,mCAAuB,SAAS;AAAA,UAClC;AAAA,UACA,YAAY,CAAC,YAA+B,WAAW,OAAO;AAAA,UAC9D,cAAc,CAAC,YAA+B,aAAa,OAAO;AAAA,QAAA;AAAA,MAEtE;AAAA,IACF;AAAA;AAAA,IAEA,iBAAiB;AAAA,EAAA;AAGnB,QAAM,SAAS,CAAC,WAAuBC,QAAAA,kBAAkB,MAAM,EAAE;AAEjE,QAAM,eAIF;AAAA,IACF,GAAG;AAAA,IAAA,QACHT;AAAAA,IACA;AAAA;AAAA,IAEA,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA,UAAU,OAAO,WAAW;AAE1B,aAAO,MAAM,WAAW,iBAAiB,OAAO,WAAW;AAAA,IAC7D;AAAA,IACA,UAAU,OAAO,WAAW;AAE1B,aAAO,MAAM,WAAW,iBAAiB,OAAO,WAAW;AAAA,IAC7D;AAAA,IACA,UAAU,OAAO,WAAW;AAE1B,aAAO,MAAM,WAAW,iBAAiB,OAAO,WAAW;AAAA,IAC7D;AAAA,IACA,OAAO;AAAA,MACL,SAAS,OAAO;AAAA,QACd,WAAW;AAAA,QACX;AAAA,QACA;AAAA,QACA,gBAAgB,CAAC,UACfU,cAAAA;AAAAA,UACE;AAAA;AAAA,UAEA;AAAA;AAAA,UAIA;AAAA,QAAA;AAAA,MAIF;AAAA,IACJ;AAAA,EACF;AAEF,SAAO;AACT;;"}
|
|
1
|
+
{"version":3,"file":"powersync.cjs","sources":["../../src/powersync.ts"],"sourcesContent":["import { DiffTriggerOperation, sanitizeSQL } from '@powersync/common'\nimport { or, withCollectionConfigFactory } from '@tanstack/db'\nimport { compileSQLite } from './sqlite-compiler'\nimport { PendingOperationStore } from './PendingOperationStore'\nimport { PowerSyncTransactor } from './PowerSyncTransactor'\nimport { DEFAULT_BATCH_SIZE } from './definitions'\nimport { asPowerSyncRecord, mapOperation } from './helpers'\nimport { convertTableToSchema } from './schema'\nimport { serializeForSQLite } from './serialization'\nimport type {\n CleanupFn,\n LoadSubsetOptions,\n OperationType,\n SyncAppliedReceipt,\n SyncConfig,\n} from '@tanstack/db'\nimport type {\n AnyTableColumnType,\n ExtractedTable,\n ExtractedTableColumns,\n MapBaseColumnType,\n OptionalExtractedTable,\n} from './helpers'\nimport type {\n BasePowerSyncCollectionConfig,\n ConfigWithArbitraryCollectionTypes,\n ConfigWithSQLiteInputType,\n ConfigWithSQLiteTypes,\n CustomSQLiteSerializer,\n EnhancedPowerSyncCollectionConfig,\n InferPowerSyncOutputType,\n PowerSyncCollectionConfig,\n PowerSyncCollectionUtils,\n} from './definitions'\nimport type { PendingOperation } from './PendingOperationStore'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { LockContext, Table, TriggerDiffRecord } from '@powersync/common'\n\n/**\n * Creates PowerSync collection options for use with a standard Collection.\n *\n * @template TTable - The SQLite-based typing\n * @template TSchema - The validation schema type (optionally supports a custom input type)\n * @param config - Configuration options for the PowerSync collection\n * @returns Collection options with utilities\n */\n\n// Overload 1: No schema is provided\n\n/**\n * Creates a PowerSync collection configuration with basic default validation.\n * Input and Output types are the SQLite column types.\n *\n * @example\n * ```typescript\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * }),\n * })\n *\n * type Document = (typeof APP_SCHEMA)[\"types\"][\"documents\"]\n *\n * const db = new PowerSyncDatabase({\n * database: {\n * dbFilename: \"test.sqlite\",\n * },\n * schema: APP_SCHEMA,\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents\n * })\n * )\n * ```\n */\nexport function powerSyncCollectionOptions<TTable extends Table = Table>(\n config: BasePowerSyncCollectionConfig<TTable, never> & ConfigWithSQLiteTypes,\n): EnhancedPowerSyncCollectionConfig<\n TTable,\n OptionalExtractedTable<TTable>,\n never\n>\n\n// Overload 2: Schema is provided and the TInput matches SQLite types.\n\n/**\n * Creates a PowerSync collection configuration with schema validation.\n *\n * The input types satisfy the SQLite column types.\n *\n * The output types are defined by the provided schema. This schema can enforce additional\n * validation or type transforms.\n * Arbitrary output typed mutations are encoded to SQLite for persistence. We provide a basic standard\n * serialization implementation to serialize column values. Custom or advanced types require providing additional\n * serializer specifications. Partial column overrides can be supplied to `serializer`.\n *\n * @example\n * ```typescript\n * import { z } from \"zod\"\n *\n * // The PowerSync SQLite schema\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * // Dates are stored as ISO date strings in SQLite\n * created_at: column.text\n * }),\n * })\n *\n * // Advanced Zod validations. The output type of this schema\n * // is constrained to the SQLite schema of APP_SCHEMA\n * const schema = z.object({\n * id: z.string(),\n * // Notice that `name` is not nullable (is required) here and it has additional validation\n * name: z.string().min(3, { message: \"Should be at least 3 characters\" }).nullable(),\n * // The input type is still the SQLite string type. While collections will output smart Date instances.\n * created_at: z.string().transform(val => new Date(val))\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents,\n * schema,\n * serializer: {\n * // The default is toISOString, this is just to demonstrate custom overrides\n * created_at: (outputValue) => outputValue.toISOString(),\n * },\n * })\n * )\n * ```\n */\nexport function powerSyncCollectionOptions<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // TInput is the SQLite types. We can use the supplied schema to validate sync input\n OptionalExtractedTable<TTable>,\n AnyTableColumnType<TTable>\n >,\n>(\n config: BasePowerSyncCollectionConfig<TTable, TSchema> &\n ConfigWithSQLiteInputType<TTable, TSchema>,\n): EnhancedPowerSyncCollectionConfig<\n TTable,\n InferPowerSyncOutputType<TTable, TSchema>,\n TSchema\n> & {\n schema: TSchema\n}\n\n// Overload 3: Schema is provided with arbitrary TInput and TOutput\n/**\n * Creates a PowerSync collection configuration with schema validation.\n *\n * The input types are not linked to the internal SQLite table types. This can\n * give greater flexibility, e.g. by accepting rich types as input for `insert` or `update` operations.\n * An additional `deserializationSchema` is required in order to process incoming SQLite updates to the output type.\n *\n * The output types are defined by the provided schema. This schema can enforce additional\n * validation or type transforms.\n * Arbitrary output typed mutations are encoded to SQLite for persistence. We provide a basic standard\n * serialization implementation to serialize column values. Custom or advanced types require providing additional\n * serializer specifications. Partial column overrides can be supplied to `serializer`.\n *\n * @example\n * ```typescript\n * import { z } from \"zod\"\n *\n * // The PowerSync SQLite schema\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * // Booleans are represented as integers in SQLite\n * is_active: column.integer\n * }),\n * })\n *\n * // Advanced Zod validations.\n * // We accept boolean values as input for operations and expose Booleans in query results\n * const schema = z.object({\n * id: z.string(),\n * isActive: z.boolean(), // TInput and TOutput are boolean\n * })\n *\n * // The deserializationSchema converts the SQLite synced INTEGER (0/1) values to booleans.\n * const deserializationSchema = z.object({\n * id: z.string(),\n * isActive: z.number().nullable().transform((val) => val == null ? true : val > 0),\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents,\n * schema,\n * deserializationSchema,\n * })\n * )\n * ```\n */\nexport function powerSyncCollectionOptions<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // The input and output must have the same keys, the value types can be arbitrary\n AnyTableColumnType<TTable>,\n AnyTableColumnType<TTable>\n >,\n>(\n config: BasePowerSyncCollectionConfig<TTable, TSchema> &\n ConfigWithArbitraryCollectionTypes<TTable, TSchema>,\n): EnhancedPowerSyncCollectionConfig<\n TTable,\n InferPowerSyncOutputType<TTable, TSchema>,\n TSchema\n> & {\n utils: PowerSyncCollectionUtils<TTable>\n schema: TSchema\n}\n\n/**\n * Implementation of powerSyncCollectionOptions that handles both schema and non-schema configurations.\n */\n\nexport function powerSyncCollectionOptions<\n TTable extends Table,\n TSchema extends StandardSchemaV1<any> = never,\n>(\n config: PowerSyncCollectionConfig<TTable, TSchema>,\n): ReturnType<typeof createPowerSyncCollectionConfig<TTable, TSchema>> {\n const outputConfig = createPowerSyncCollectionConfig(config)\n return withCollectionConfigFactory(outputConfig, () =>\n createPowerSyncCollectionConfig(config),\n )\n}\n\nfunction createPowerSyncCollectionConfig<\n TTable extends Table,\n TSchema extends StandardSchemaV1<any> = never,\n>(config: PowerSyncCollectionConfig<TTable, TSchema>) {\n const {\n database,\n table,\n schema: inputSchema,\n syncBatchSize = DEFAULT_BATCH_SIZE,\n syncMode = 'eager',\n ...restConfig\n } = config\n\n const deserializationSchema =\n `deserializationSchema` in config ? config.deserializationSchema : null\n const serializer = `serializer` in config ? config.serializer : undefined\n const onDeserializationError =\n `onDeserializationError` in config\n ? config.onDeserializationError\n : undefined\n\n // The SQLite table type\n type TableType = ExtractedTable<TTable>\n\n // The collection output type\n type OutputType = InferPowerSyncOutputType<TTable, TSchema>\n\n const { viewName, trackMetadata: metadataIsTracked } = table\n\n /**\n * Deserializes data from the incoming sync stream\n */\n const deserializeSyncRow = (value: TableType): OutputType => {\n const validationSchema = deserializationSchema || schema\n const validation = validationSchema[`~standard`].validate(value)\n if (`value` in validation) {\n return validation.value\n } else if (`issues` in validation) {\n const issueMessage = `Failed to validate incoming data for ${viewName}. Issues: ${validation.issues.map((issue) => `${issue.path} - ${issue.message}`)}`\n database.logger.error(issueMessage)\n onDeserializationError!(validation)\n throw new Error(issueMessage)\n } else {\n const unknownErrorMessage = `Unknown deserialization error for ${viewName}`\n database.logger.error(unknownErrorMessage)\n onDeserializationError!({ issues: [{ message: unknownErrorMessage }] })\n throw new Error(unknownErrorMessage)\n }\n }\n\n // We can do basic runtime validations for columns if not explicit schema has been provided\n const schema = inputSchema ?? (convertTableToSchema(table) as TSchema)\n /**\n * The onInsert, onUpdate, and onDelete handlers should only return\n * after we have written the changes to TanStack DB.\n * We currently only write to TanStack DB from a diff trigger.\n * We wait for the diff trigger to observe the change,\n * and only then return from the on[X] handlers.\n * This ensures that when the transaction is reported as\n * complete to the caller, the in-memory state is already\n * consistent with the database.\n */\n const pendingOperationStore = PendingOperationStore.GLOBAL\n // Keep the tracked table unique in case of multiple tabs.\n const trackedTableName = `__${viewName}_tracking_${Math.floor(\n Math.random() * 0xffffffff,\n )\n .toString(16)\n .padStart(8, `0`)}`\n\n const transactor = new PowerSyncTransactor({\n database,\n })\n\n /**\n * \"sync\"\n * Notice that this describes the Sync between the local SQLite table\n * and the in-memory tanstack-db collection.\n */\n const sync: SyncConfig<OutputType, string> = {\n sync: (params) => {\n const { begin, write, collection, commit, markReady, markError } = params\n const abortController = new AbortController()\n\n let disposeTracking:\n | ((options?: { context?: LockContext }) => Promise<void>)\n | null = null\n\n if (syncMode === `eager`) {\n return runEagerSync()\n } else {\n return runOnDemandSync()\n }\n\n /**\n * Disposes the current diff trigger, if one is active, and clears the\n * tracking state.\n */\n async function safelyDisposeTracking(\n context?: LockContext,\n ): Promise<void> {\n const dispose = disposeTracking\n if (!dispose) {\n return\n }\n\n disposeTracking = null\n await dispose(context ? { context } : undefined)\n }\n\n async function createDiffTrigger(\n options: {\n setupContext?: LockContext\n immediate?: boolean\n when: Record<DiffTriggerOperation, string>\n writeType: (rowId: string) => OperationType\n batchQuery: (\n lockContext: LockContext,\n batchSize: number,\n cursor: number,\n ) => Promise<Array<TableType>>\n },\n appliedReceipts: Array<SyncAppliedReceipt>,\n ) {\n const { setupContext, immediate, when, writeType, batchQuery } = options\n\n return await database.triggers.createDiffTrigger({\n source: viewName,\n destination: trackedTableName,\n setupContext,\n when,\n hooks: {\n beforeCreate: async (context) => {\n let currentBatchCount = syncBatchSize\n let cursor = 0\n while (currentBatchCount == syncBatchSize) {\n begin(immediate ? { immediate: true } : undefined)\n\n const batchItems = await batchQuery(\n context,\n syncBatchSize,\n cursor,\n )\n currentBatchCount = batchItems.length\n cursor += currentBatchCount\n for (const row of batchItems) {\n write({\n type: writeType(row.id),\n value: deserializeSyncRow(row),\n })\n }\n appliedReceipts.push(commit())\n }\n database.logger.info(\n `Sync is ready for ${viewName} into ${trackedTableName}`,\n )\n },\n },\n })\n }\n\n async function flushDiffRecords(): Promise<void> {\n const ignoredReceipts: Array<SyncAppliedReceipt> = []\n await database\n .writeTransaction(async (context) => {\n await flushDiffRecordsWithContext(context, ignoredReceipts)\n })\n .catch((error) => {\n database.logger.error(\n `An error has been detected in the sync handler`,\n error,\n )\n })\n }\n\n // We can use this directly if we want to pair a flush with dispose+recreate diff trigger.\n async function flushDiffRecordsWithContext(\n context: LockContext,\n appliedReceipts: Array<SyncAppliedReceipt>,\n ): Promise<void> {\n // There is nothing to flush if no tracking table is currently active.\n if (!disposeTracking) {\n return\n }\n\n try {\n begin()\n const operations = await context.getAll<TriggerDiffRecord>(\n `SELECT * FROM ${trackedTableName} ORDER BY operation_id ASC`,\n )\n const pendingOperations: Array<PendingOperation> = []\n\n for (const op of operations) {\n const { id, operation, timestamp, value } = op\n const parsedValue = deserializeSyncRow({\n id,\n ...JSON.parse(value),\n })\n const parsedPreviousValue =\n op.operation == DiffTriggerOperation.UPDATE\n ? deserializeSyncRow({\n id,\n ...JSON.parse(op.previous_value),\n })\n : undefined\n write({\n type: mapOperation(operation),\n value: parsedValue,\n previousValue: parsedPreviousValue,\n })\n pendingOperations.push({\n id,\n operation,\n timestamp,\n tableName: viewName,\n })\n }\n\n // clear the current operations\n await context.execute(`DELETE FROM ${trackedTableName}`)\n\n const applied = commit()\n appliedReceipts.push(applied)\n // Mutation persistence is what releases the Collection's FIFO gate.\n // Confirm these local operations after the sync transaction is\n // staged; waiting for its applied receipt would deadlock the user\n // transaction that currently parks it.\n pendingOperationStore.resolvePendingFor(pendingOperations)\n } catch (error) {\n database.logger.error(\n `An error has been detected in the sync handler`,\n error,\n )\n }\n }\n\n // The sync function needs to be synchronous.\n async function start(afterOnChangeRegistered?: () => Promise<void>) {\n database.logger.info(\n `Sync is starting for ${viewName} into ${trackedTableName}`,\n )\n database.onChangeWithCallback(\n {\n onChange: async () => {\n await flushDiffRecords()\n },\n },\n {\n signal: abortController.signal,\n triggerImmediate: false,\n tables: [trackedTableName],\n },\n )\n\n await afterOnChangeRegistered?.()\n\n // If the abort controller was aborted while processing the request above\n if (abortController.signal.aborted) {\n await safelyDisposeTracking()\n } else {\n abortController.signal.addEventListener(\n `abort`,\n async () => {\n await safelyDisposeTracking()\n },\n { once: true },\n )\n }\n }\n\n // Eager mode.\n // Registers a diff trigger for the entire table.\n function runEagerSync() {\n let onUnload: CleanupFn | void | null = null\n\n start(async () => {\n onUnload = await restConfig.onLoad?.()\n\n const appliedReceipts: Array<SyncAppliedReceipt> = []\n disposeTracking = await createDiffTrigger(\n {\n // Initial eager hydration must make the source usable before\n // PowerSync can persist a mutation queued during startup.\n immediate: true,\n when: {\n [DiffTriggerOperation.INSERT]: `TRUE`,\n [DiffTriggerOperation.UPDATE]: `TRUE`,\n [DiffTriggerOperation.DELETE]: `TRUE`,\n },\n writeType: (_rowId: string) => `insert`,\n batchQuery: (\n lockContext: LockContext,\n batchSize: number,\n cursor: number,\n ) =>\n lockContext.getAll<TableType>(\n sanitizeSQL`SELECT * FROM ${viewName} LIMIT ? OFFSET ?`,\n [batchSize, cursor],\n ),\n },\n appliedReceipts,\n )\n await Promise.all(appliedReceipts)\n markReady()\n }).catch((error) => {\n database.logger.error(\n `Could not start syncing process for ${viewName} into ${trackedTableName}`,\n error,\n )\n if (collection.status === `loading`) {\n markError(error)\n }\n })\n\n return () => {\n database.logger.info(\n `Sync has been stopped for ${viewName} into ${trackedTableName}`,\n )\n abortController.abort()\n onUnload?.()\n }\n }\n\n // On-demand mode.\n // Registers a diff trigger for the active WHERE expressions.\n function runOnDemandSync() {\n const unloadSubsetCallbacks = new Map<LoadSubsetOptions, CleanupFn>()\n const releasedSubsets = new WeakSet<LoadSubsetOptions>()\n let stopped = false\n const hasStopped = () => stopped\n\n start().catch((error) =>\n database.logger.error(\n `Could not start syncing process for ${viewName} into ${trackedTableName}`,\n error,\n ),\n )\n\n // Tracks all active WHERE expressions for on-demand sync filtering.\n // Each loadSubset call pushes its predicate; unloadSubset removes it.\n const activeWhereExpressions: Array<LoadSubsetOptions['where']> = []\n\n const loadSubset = async (\n options?: LoadSubsetOptions,\n ): Promise<void> => {\n if (hasStopped()) return\n const appliedReceipts: Array<SyncAppliedReceipt> = []\n\n if (options) {\n activeWhereExpressions.push(options.where)\n const cleanup = await restConfig.onLoadSubset?.(options)\n if (hasStopped()) {\n cleanup?.()\n return\n }\n if (cleanup) {\n if (releasedSubsets.has(options) || options.signal?.aborted) {\n cleanup()\n } else {\n unloadSubsetCallbacks.set(options, cleanup)\n }\n }\n }\n\n // No predicates remain, so stop tracking entirely. Both calls are no-ops\n // when no tracking table is currently active.\n if (activeWhereExpressions.length === 0) {\n await database.writeLock(async (ctx) => {\n await flushDiffRecordsWithContext(ctx, appliedReceipts)\n await safelyDisposeTracking(ctx)\n })\n await Promise.all(appliedReceipts)\n return\n }\n\n const combinedWhere =\n activeWhereExpressions.length === 1\n ? activeWhereExpressions[0]\n : or(\n activeWhereExpressions[0],\n activeWhereExpressions[1],\n ...activeWhereExpressions.slice(2),\n )\n\n const compiledNewData = compileSQLite(\n { where: combinedWhere },\n { jsonColumn: 'NEW.data' },\n )\n\n const compiledOldData = compileSQLite(\n { where: combinedWhere },\n { jsonColumn: 'OLD.data' },\n )\n\n const compiledView = compileSQLite({ where: combinedWhere })\n\n const newDataWhenClause = toInlinedWhereClause(compiledNewData)\n const oldDataWhenClause = toInlinedWhereClause(compiledOldData)\n const viewWhereClause = toInlinedWhereClause(compiledView)\n\n await database.writeLock(async (ctx) => {\n // Replace any active tracking with one covering the new set of\n // predicates.\n await flushDiffRecordsWithContext(ctx, appliedReceipts)\n await safelyDisposeTracking(ctx)\n\n disposeTracking = await createDiffTrigger(\n {\n setupContext: ctx,\n when: {\n [DiffTriggerOperation.INSERT]: newDataWhenClause,\n [DiffTriggerOperation.UPDATE]: `(${newDataWhenClause}) OR (${oldDataWhenClause})`,\n [DiffTriggerOperation.DELETE]: oldDataWhenClause,\n },\n writeType: (rowId: string) =>\n collection.has(rowId) ? `update` : `insert`,\n batchQuery: (\n lockContext: LockContext,\n batchSize: number,\n cursor: number,\n ) =>\n lockContext.getAll<TableType>(\n `SELECT * FROM ${viewName} WHERE ${viewWhereClause} LIMIT ? OFFSET ?`,\n [batchSize, cursor],\n ),\n },\n appliedReceipts,\n )\n })\n await Promise.all(appliedReceipts)\n }\n\n const toInlinedWhereClause = (compiled: {\n where?: string\n params: Array<unknown>\n }): string => {\n if (!compiled.where) return 'TRUE'\n const sqlParts = compiled.where.split('?')\n return sanitizeSQL(\n sqlParts as unknown as TemplateStringsArray,\n ...compiled.params,\n )\n }\n\n const unloadSubset = async (options: LoadSubsetOptions) => {\n releasedSubsets.add(options)\n unloadSubsetCallbacks.get(options)?.()\n unloadSubsetCallbacks.delete(options)\n\n const idx = activeWhereExpressions.indexOf(options.where)\n if (idx !== -1) {\n activeWhereExpressions.splice(idx, 1)\n }\n\n // Evict rows that were exclusively loaded by the departing predicate.\n // These are rows matching the departing WHERE that are no longer covered\n // by any remaining active predicate.\n const compiledDeparting = compileSQLite({ where: options.where })\n const departingWhereSQL = toInlinedWhereClause(compiledDeparting)\n\n let evictionSQL: string\n if (activeWhereExpressions.length === 0) {\n evictionSQL = `SELECT id FROM ${viewName} WHERE ${departingWhereSQL}`\n } else {\n const combinedRemaining =\n activeWhereExpressions.length === 1\n ? activeWhereExpressions[0]!\n : or(\n activeWhereExpressions[0],\n activeWhereExpressions[1],\n ...activeWhereExpressions.slice(2),\n )\n const compiledRemaining = compileSQLite({\n where: combinedRemaining,\n })\n const remainingWhereSQL = toInlinedWhereClause(compiledRemaining)\n evictionSQL = `SELECT id FROM ${viewName} WHERE (${departingWhereSQL}) AND NOT (${remainingWhereSQL})`\n }\n\n const rowsToEvict = await database.getAll<{ id: string }>(evictionSQL)\n if (rowsToEvict.length > 0) {\n begin()\n for (const { id } of rowsToEvict) {\n write({ type: `delete`, key: id })\n }\n // Eviction does not establish new subset coverage. Keep trigger\n // replacement in the same unload turn even when this delete waits\n // behind a persisting mutation; the later load tracks its own\n // establishing receipts.\n void commit()\n }\n\n // Recreate the diff trigger for the remaining active WHERE expressions.\n await loadSubset()\n }\n\n markReady()\n\n return {\n cleanup: () => {\n stopped = true\n database.logger.info(\n `Sync has been stopped for ${viewName} into ${trackedTableName}`,\n )\n abortController.abort()\n for (const cleanup of unloadSubsetCallbacks.values()) cleanup()\n unloadSubsetCallbacks.clear()\n activeWhereExpressions.length = 0\n },\n loadSubset: (options: LoadSubsetOptions) => loadSubset(options),\n unloadSubset: (options: LoadSubsetOptions) => unloadSubset(options),\n }\n }\n },\n // Expose the getSyncMetadata function\n getSyncMetadata: undefined,\n }\n\n const getKey = (record: OutputType) => asPowerSyncRecord(record).id\n\n const outputConfig: EnhancedPowerSyncCollectionConfig<\n TTable,\n OutputType,\n TSchema\n > = {\n ...restConfig,\n schema,\n getKey,\n // Syncing should start immediately since we need to monitor the changes for mutations\n startSync: true,\n syncMode,\n sync,\n onInsert: async (params) => {\n // The transaction here should only ever contain a single insert mutation\n return await transactor.applyTransaction(params.transaction)\n },\n onUpdate: async (params) => {\n // The transaction here should only ever contain a single update mutation\n return await transactor.applyTransaction(params.transaction)\n },\n onDelete: async (params) => {\n // The transaction here should only ever contain a single delete mutation\n return await transactor.applyTransaction(params.transaction)\n },\n utils: {\n getMeta: () => ({\n tableName: viewName,\n trackedTableName,\n metadataIsTracked,\n serializeValue: (value) =>\n serializeForSQLite(\n value,\n // This is required by the input generic\n table as Table<\n MapBaseColumnType<InferPowerSyncOutputType<TTable, TSchema>>\n >,\n // Coerce serializer to the shape that corresponds to the Table constructed from OutputType\n serializer as CustomSQLiteSerializer<\n OutputType,\n ExtractedTableColumns<Table<MapBaseColumnType<OutputType>>>\n >,\n ),\n }),\n },\n }\n return outputConfig\n}\n"],"names":["withCollectionConfigFactory","DEFAULT_BATCH_SIZE","schema","convertTableToSchema","PendingOperationStore","PowerSyncTransactor","DiffTriggerOperation","mapOperation","sanitizeSQL","or","compileSQLite","asPowerSyncRecord","serializeForSQLite"],"mappings":"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"}
|
package/dist/esm/powersync.js
CHANGED
|
@@ -70,8 +70,8 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
70
70
|
disposeTracking = null;
|
|
71
71
|
await dispose(context ? { context } : void 0);
|
|
72
72
|
}
|
|
73
|
-
async function createDiffTrigger(options) {
|
|
74
|
-
const { setupContext, when, writeType, batchQuery
|
|
73
|
+
async function createDiffTrigger(options, appliedReceipts) {
|
|
74
|
+
const { setupContext, immediate, when, writeType, batchQuery } = options;
|
|
75
75
|
return await database.triggers.createDiffTrigger({
|
|
76
76
|
source: viewName,
|
|
77
77
|
destination: trackedTableName,
|
|
@@ -82,7 +82,7 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
82
82
|
let currentBatchCount = syncBatchSize;
|
|
83
83
|
let cursor = 0;
|
|
84
84
|
while (currentBatchCount == syncBatchSize) {
|
|
85
|
-
begin();
|
|
85
|
+
begin(immediate ? { immediate: true } : void 0);
|
|
86
86
|
const batchItems = await batchQuery(
|
|
87
87
|
context,
|
|
88
88
|
syncBatchSize,
|
|
@@ -96,9 +96,8 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
96
96
|
value: deserializeSyncRow(row)
|
|
97
97
|
});
|
|
98
98
|
}
|
|
99
|
-
commit();
|
|
99
|
+
appliedReceipts.push(commit());
|
|
100
100
|
}
|
|
101
|
-
onReady();
|
|
102
101
|
database.logger.info(
|
|
103
102
|
`Sync is ready for ${viewName} into ${trackedTableName}`
|
|
104
103
|
);
|
|
@@ -107,8 +106,9 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
107
106
|
});
|
|
108
107
|
}
|
|
109
108
|
async function flushDiffRecords() {
|
|
109
|
+
const ignoredReceipts = [];
|
|
110
110
|
await database.writeTransaction(async (context) => {
|
|
111
|
-
await flushDiffRecordsWithContext(context);
|
|
111
|
+
await flushDiffRecordsWithContext(context, ignoredReceipts);
|
|
112
112
|
}).catch((error) => {
|
|
113
113
|
database.logger.error(
|
|
114
114
|
`An error has been detected in the sync handler`,
|
|
@@ -116,7 +116,7 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
116
116
|
);
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
|
-
async function flushDiffRecordsWithContext(context) {
|
|
119
|
+
async function flushDiffRecordsWithContext(context, appliedReceipts) {
|
|
120
120
|
if (!disposeTracking) {
|
|
121
121
|
return;
|
|
122
122
|
}
|
|
@@ -149,7 +149,8 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
149
149
|
});
|
|
150
150
|
}
|
|
151
151
|
await context.execute(`DELETE FROM ${trackedTableName}`);
|
|
152
|
-
commit();
|
|
152
|
+
const applied = commit();
|
|
153
|
+
appliedReceipts.push(applied);
|
|
153
154
|
pendingOperationStore.resolvePendingFor(pendingOperations);
|
|
154
155
|
} catch (error) {
|
|
155
156
|
database.logger.error(
|
|
@@ -191,19 +192,27 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
191
192
|
let onUnload = null;
|
|
192
193
|
start(async () => {
|
|
193
194
|
onUnload = await restConfig.onLoad?.();
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
195
|
+
const appliedReceipts = [];
|
|
196
|
+
disposeTracking = await createDiffTrigger(
|
|
197
|
+
{
|
|
198
|
+
// Initial eager hydration must make the source usable before
|
|
199
|
+
// PowerSync can persist a mutation queued during startup.
|
|
200
|
+
immediate: true,
|
|
201
|
+
when: {
|
|
202
|
+
[DiffTriggerOperation.INSERT]: `TRUE`,
|
|
203
|
+
[DiffTriggerOperation.UPDATE]: `TRUE`,
|
|
204
|
+
[DiffTriggerOperation.DELETE]: `TRUE`
|
|
205
|
+
},
|
|
206
|
+
writeType: (_rowId) => `insert`,
|
|
207
|
+
batchQuery: (lockContext, batchSize, cursor) => lockContext.getAll(
|
|
208
|
+
sanitizeSQL`SELECT * FROM ${viewName} LIMIT ? OFFSET ?`,
|
|
209
|
+
[batchSize, cursor]
|
|
210
|
+
)
|
|
199
211
|
},
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
),
|
|
205
|
-
onReady: () => markReady()
|
|
206
|
-
});
|
|
212
|
+
appliedReceipts
|
|
213
|
+
);
|
|
214
|
+
await Promise.all(appliedReceipts);
|
|
215
|
+
markReady();
|
|
207
216
|
}).catch((error) => {
|
|
208
217
|
database.logger.error(
|
|
209
218
|
`Could not start syncing process for ${viewName} into ${trackedTableName}`,
|
|
@@ -235,6 +244,7 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
235
244
|
const activeWhereExpressions = [];
|
|
236
245
|
const loadSubset = async (options) => {
|
|
237
246
|
if (hasStopped()) return;
|
|
247
|
+
const appliedReceipts = [];
|
|
238
248
|
if (options) {
|
|
239
249
|
activeWhereExpressions.push(options.where);
|
|
240
250
|
const cleanup = await restConfig.onLoadSubset?.(options);
|
|
@@ -252,9 +262,10 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
252
262
|
}
|
|
253
263
|
if (activeWhereExpressions.length === 0) {
|
|
254
264
|
await database.writeLock(async (ctx) => {
|
|
255
|
-
await flushDiffRecordsWithContext(ctx);
|
|
265
|
+
await flushDiffRecordsWithContext(ctx, appliedReceipts);
|
|
256
266
|
await safelyDisposeTracking(ctx);
|
|
257
267
|
});
|
|
268
|
+
await Promise.all(appliedReceipts);
|
|
258
269
|
return;
|
|
259
270
|
}
|
|
260
271
|
const combinedWhere = activeWhereExpressions.length === 1 ? activeWhereExpressions[0] : or(
|
|
@@ -275,24 +286,26 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
275
286
|
const oldDataWhenClause = toInlinedWhereClause(compiledOldData);
|
|
276
287
|
const viewWhereClause = toInlinedWhereClause(compiledView);
|
|
277
288
|
await database.writeLock(async (ctx) => {
|
|
278
|
-
await flushDiffRecordsWithContext(ctx);
|
|
289
|
+
await flushDiffRecordsWithContext(ctx, appliedReceipts);
|
|
279
290
|
await safelyDisposeTracking(ctx);
|
|
280
|
-
disposeTracking = await createDiffTrigger(
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
291
|
+
disposeTracking = await createDiffTrigger(
|
|
292
|
+
{
|
|
293
|
+
setupContext: ctx,
|
|
294
|
+
when: {
|
|
295
|
+
[DiffTriggerOperation.INSERT]: newDataWhenClause,
|
|
296
|
+
[DiffTriggerOperation.UPDATE]: `(${newDataWhenClause}) OR (${oldDataWhenClause})`,
|
|
297
|
+
[DiffTriggerOperation.DELETE]: oldDataWhenClause
|
|
298
|
+
},
|
|
299
|
+
writeType: (rowId) => collection.has(rowId) ? `update` : `insert`,
|
|
300
|
+
batchQuery: (lockContext, batchSize, cursor) => lockContext.getAll(
|
|
301
|
+
`SELECT * FROM ${viewName} WHERE ${viewWhereClause} LIMIT ? OFFSET ?`,
|
|
302
|
+
[batchSize, cursor]
|
|
303
|
+
)
|
|
286
304
|
},
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
`SELECT * FROM ${viewName} WHERE ${viewWhereClause} LIMIT ? OFFSET ?`,
|
|
290
|
-
[batchSize, cursor]
|
|
291
|
-
),
|
|
292
|
-
onReady: () => {
|
|
293
|
-
}
|
|
294
|
-
});
|
|
305
|
+
appliedReceipts
|
|
306
|
+
);
|
|
295
307
|
});
|
|
308
|
+
await Promise.all(appliedReceipts);
|
|
296
309
|
};
|
|
297
310
|
const toInlinedWhereClause = (compiled) => {
|
|
298
311
|
if (!compiled.where) return "TRUE";
|
|
@@ -333,7 +346,7 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
333
346
|
for (const { id } of rowsToEvict) {
|
|
334
347
|
write({ type: `delete`, key: id });
|
|
335
348
|
}
|
|
336
|
-
commit();
|
|
349
|
+
void commit();
|
|
337
350
|
}
|
|
338
351
|
await loadSubset();
|
|
339
352
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"powersync.js","sources":["../../src/powersync.ts"],"sourcesContent":["import { DiffTriggerOperation, sanitizeSQL } from '@powersync/common'\nimport { or, withCollectionConfigFactory } from '@tanstack/db'\nimport { compileSQLite } from './sqlite-compiler'\nimport { PendingOperationStore } from './PendingOperationStore'\nimport { PowerSyncTransactor } from './PowerSyncTransactor'\nimport { DEFAULT_BATCH_SIZE } from './definitions'\nimport { asPowerSyncRecord, mapOperation } from './helpers'\nimport { convertTableToSchema } from './schema'\nimport { serializeForSQLite } from './serialization'\nimport type {\n CleanupFn,\n LoadSubsetOptions,\n OperationType,\n SyncConfig,\n} from '@tanstack/db'\nimport type {\n AnyTableColumnType,\n ExtractedTable,\n ExtractedTableColumns,\n MapBaseColumnType,\n OptionalExtractedTable,\n} from './helpers'\nimport type {\n BasePowerSyncCollectionConfig,\n ConfigWithArbitraryCollectionTypes,\n ConfigWithSQLiteInputType,\n ConfigWithSQLiteTypes,\n CustomSQLiteSerializer,\n EnhancedPowerSyncCollectionConfig,\n InferPowerSyncOutputType,\n PowerSyncCollectionConfig,\n PowerSyncCollectionUtils,\n} from './definitions'\nimport type { PendingOperation } from './PendingOperationStore'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { LockContext, Table, TriggerDiffRecord } from '@powersync/common'\n\n/**\n * Creates PowerSync collection options for use with a standard Collection.\n *\n * @template TTable - The SQLite-based typing\n * @template TSchema - The validation schema type (optionally supports a custom input type)\n * @param config - Configuration options for the PowerSync collection\n * @returns Collection options with utilities\n */\n\n// Overload 1: No schema is provided\n\n/**\n * Creates a PowerSync collection configuration with basic default validation.\n * Input and Output types are the SQLite column types.\n *\n * @example\n * ```typescript\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * }),\n * })\n *\n * type Document = (typeof APP_SCHEMA)[\"types\"][\"documents\"]\n *\n * const db = new PowerSyncDatabase({\n * database: {\n * dbFilename: \"test.sqlite\",\n * },\n * schema: APP_SCHEMA,\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents\n * })\n * )\n * ```\n */\nexport function powerSyncCollectionOptions<TTable extends Table = Table>(\n config: BasePowerSyncCollectionConfig<TTable, never> & ConfigWithSQLiteTypes,\n): EnhancedPowerSyncCollectionConfig<\n TTable,\n OptionalExtractedTable<TTable>,\n never\n>\n\n// Overload 2: Schema is provided and the TInput matches SQLite types.\n\n/**\n * Creates a PowerSync collection configuration with schema validation.\n *\n * The input types satisfy the SQLite column types.\n *\n * The output types are defined by the provided schema. This schema can enforce additional\n * validation or type transforms.\n * Arbitrary output typed mutations are encoded to SQLite for persistence. We provide a basic standard\n * serialization implementation to serialize column values. Custom or advanced types require providing additional\n * serializer specifications. Partial column overrides can be supplied to `serializer`.\n *\n * @example\n * ```typescript\n * import { z } from \"zod\"\n *\n * // The PowerSync SQLite schema\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * // Dates are stored as ISO date strings in SQLite\n * created_at: column.text\n * }),\n * })\n *\n * // Advanced Zod validations. The output type of this schema\n * // is constrained to the SQLite schema of APP_SCHEMA\n * const schema = z.object({\n * id: z.string(),\n * // Notice that `name` is not nullable (is required) here and it has additional validation\n * name: z.string().min(3, { message: \"Should be at least 3 characters\" }).nullable(),\n * // The input type is still the SQLite string type. While collections will output smart Date instances.\n * created_at: z.string().transform(val => new Date(val))\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents,\n * schema,\n * serializer: {\n * // The default is toISOString, this is just to demonstrate custom overrides\n * created_at: (outputValue) => outputValue.toISOString(),\n * },\n * })\n * )\n * ```\n */\nexport function powerSyncCollectionOptions<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // TInput is the SQLite types. We can use the supplied schema to validate sync input\n OptionalExtractedTable<TTable>,\n AnyTableColumnType<TTable>\n >,\n>(\n config: BasePowerSyncCollectionConfig<TTable, TSchema> &\n ConfigWithSQLiteInputType<TTable, TSchema>,\n): EnhancedPowerSyncCollectionConfig<\n TTable,\n InferPowerSyncOutputType<TTable, TSchema>,\n TSchema\n> & {\n schema: TSchema\n}\n\n// Overload 3: Schema is provided with arbitrary TInput and TOutput\n/**\n * Creates a PowerSync collection configuration with schema validation.\n *\n * The input types are not linked to the internal SQLite table types. This can\n * give greater flexibility, e.g. by accepting rich types as input for `insert` or `update` operations.\n * An additional `deserializationSchema` is required in order to process incoming SQLite updates to the output type.\n *\n * The output types are defined by the provided schema. This schema can enforce additional\n * validation or type transforms.\n * Arbitrary output typed mutations are encoded to SQLite for persistence. We provide a basic standard\n * serialization implementation to serialize column values. Custom or advanced types require providing additional\n * serializer specifications. Partial column overrides can be supplied to `serializer`.\n *\n * @example\n * ```typescript\n * import { z } from \"zod\"\n *\n * // The PowerSync SQLite schema\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * // Booleans are represented as integers in SQLite\n * is_active: column.integer\n * }),\n * })\n *\n * // Advanced Zod validations.\n * // We accept boolean values as input for operations and expose Booleans in query results\n * const schema = z.object({\n * id: z.string(),\n * isActive: z.boolean(), // TInput and TOutput are boolean\n * })\n *\n * // The deserializationSchema converts the SQLite synced INTEGER (0/1) values to booleans.\n * const deserializationSchema = z.object({\n * id: z.string(),\n * isActive: z.number().nullable().transform((val) => val == null ? true : val > 0),\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents,\n * schema,\n * deserializationSchema,\n * })\n * )\n * ```\n */\nexport function powerSyncCollectionOptions<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // The input and output must have the same keys, the value types can be arbitrary\n AnyTableColumnType<TTable>,\n AnyTableColumnType<TTable>\n >,\n>(\n config: BasePowerSyncCollectionConfig<TTable, TSchema> &\n ConfigWithArbitraryCollectionTypes<TTable, TSchema>,\n): EnhancedPowerSyncCollectionConfig<\n TTable,\n InferPowerSyncOutputType<TTable, TSchema>,\n TSchema\n> & {\n utils: PowerSyncCollectionUtils<TTable>\n schema: TSchema\n}\n\n/**\n * Implementation of powerSyncCollectionOptions that handles both schema and non-schema configurations.\n */\n\nexport function powerSyncCollectionOptions<\n TTable extends Table,\n TSchema extends StandardSchemaV1<any> = never,\n>(\n config: PowerSyncCollectionConfig<TTable, TSchema>,\n): ReturnType<typeof createPowerSyncCollectionConfig<TTable, TSchema>> {\n const outputConfig = createPowerSyncCollectionConfig(config)\n return withCollectionConfigFactory(outputConfig, () =>\n createPowerSyncCollectionConfig(config),\n )\n}\n\nfunction createPowerSyncCollectionConfig<\n TTable extends Table,\n TSchema extends StandardSchemaV1<any> = never,\n>(config: PowerSyncCollectionConfig<TTable, TSchema>) {\n const {\n database,\n table,\n schema: inputSchema,\n syncBatchSize = DEFAULT_BATCH_SIZE,\n syncMode = 'eager',\n ...restConfig\n } = config\n\n const deserializationSchema =\n `deserializationSchema` in config ? config.deserializationSchema : null\n const serializer = `serializer` in config ? config.serializer : undefined\n const onDeserializationError =\n `onDeserializationError` in config\n ? config.onDeserializationError\n : undefined\n\n // The SQLite table type\n type TableType = ExtractedTable<TTable>\n\n // The collection output type\n type OutputType = InferPowerSyncOutputType<TTable, TSchema>\n\n const { viewName, trackMetadata: metadataIsTracked } = table\n\n /**\n * Deserializes data from the incoming sync stream\n */\n const deserializeSyncRow = (value: TableType): OutputType => {\n const validationSchema = deserializationSchema || schema\n const validation = validationSchema[`~standard`].validate(value)\n if (`value` in validation) {\n return validation.value\n } else if (`issues` in validation) {\n const issueMessage = `Failed to validate incoming data for ${viewName}. Issues: ${validation.issues.map((issue) => `${issue.path} - ${issue.message}`)}`\n database.logger.error(issueMessage)\n onDeserializationError!(validation)\n throw new Error(issueMessage)\n } else {\n const unknownErrorMessage = `Unknown deserialization error for ${viewName}`\n database.logger.error(unknownErrorMessage)\n onDeserializationError!({ issues: [{ message: unknownErrorMessage }] })\n throw new Error(unknownErrorMessage)\n }\n }\n\n // We can do basic runtime validations for columns if not explicit schema has been provided\n const schema = inputSchema ?? (convertTableToSchema(table) as TSchema)\n /**\n * The onInsert, onUpdate, and onDelete handlers should only return\n * after we have written the changes to TanStack DB.\n * We currently only write to TanStack DB from a diff trigger.\n * We wait for the diff trigger to observe the change,\n * and only then return from the on[X] handlers.\n * This ensures that when the transaction is reported as\n * complete to the caller, the in-memory state is already\n * consistent with the database.\n */\n const pendingOperationStore = PendingOperationStore.GLOBAL\n // Keep the tracked table unique in case of multiple tabs.\n const trackedTableName = `__${viewName}_tracking_${Math.floor(\n Math.random() * 0xffffffff,\n )\n .toString(16)\n .padStart(8, `0`)}`\n\n const transactor = new PowerSyncTransactor({\n database,\n })\n\n /**\n * \"sync\"\n * Notice that this describes the Sync between the local SQLite table\n * and the in-memory tanstack-db collection.\n */\n const sync: SyncConfig<OutputType, string> = {\n sync: (params) => {\n const { begin, write, collection, commit, markReady, markError } = params\n const abortController = new AbortController()\n\n let disposeTracking:\n | ((options?: { context?: LockContext }) => Promise<void>)\n | null = null\n\n if (syncMode === `eager`) {\n return runEagerSync()\n } else {\n return runOnDemandSync()\n }\n\n /**\n * Disposes the current diff trigger, if one is active, and clears the\n * tracking state.\n */\n async function safelyDisposeTracking(\n context?: LockContext,\n ): Promise<void> {\n const dispose = disposeTracking\n if (!dispose) {\n return\n }\n\n disposeTracking = null\n await dispose(context ? { context } : undefined)\n }\n\n async function createDiffTrigger(options: {\n setupContext?: LockContext\n when: Record<DiffTriggerOperation, string>\n writeType: (rowId: string) => OperationType\n batchQuery: (\n lockContext: LockContext,\n batchSize: number,\n cursor: number,\n ) => Promise<Array<TableType>>\n onReady: () => void\n }) {\n const { setupContext, when, writeType, batchQuery, onReady } = options\n\n return await database.triggers.createDiffTrigger({\n source: viewName,\n destination: trackedTableName,\n setupContext,\n when,\n hooks: {\n beforeCreate: async (context) => {\n let currentBatchCount = syncBatchSize\n let cursor = 0\n while (currentBatchCount == syncBatchSize) {\n begin()\n\n const batchItems = await batchQuery(\n context,\n syncBatchSize,\n cursor,\n )\n currentBatchCount = batchItems.length\n cursor += currentBatchCount\n for (const row of batchItems) {\n write({\n type: writeType(row.id),\n value: deserializeSyncRow(row),\n })\n }\n commit()\n }\n onReady()\n database.logger.info(\n `Sync is ready for ${viewName} into ${trackedTableName}`,\n )\n },\n },\n })\n }\n\n async function flushDiffRecords(): Promise<void> {\n await database\n .writeTransaction(async (context) => {\n await flushDiffRecordsWithContext(context)\n })\n .catch((error) => {\n database.logger.error(\n `An error has been detected in the sync handler`,\n error,\n )\n })\n }\n\n // We can use this directly if we want to pair a flush with dispose+recreate diff trigger.\n async function flushDiffRecordsWithContext(\n context: LockContext,\n ): Promise<void> {\n // There is nothing to flush if no tracking table is currently active.\n if (!disposeTracking) {\n return\n }\n\n try {\n begin()\n const operations = await context.getAll<TriggerDiffRecord>(\n `SELECT * FROM ${trackedTableName} ORDER BY operation_id ASC`,\n )\n const pendingOperations: Array<PendingOperation> = []\n\n for (const op of operations) {\n const { id, operation, timestamp, value } = op\n const parsedValue = deserializeSyncRow({\n id,\n ...JSON.parse(value),\n })\n const parsedPreviousValue =\n op.operation == DiffTriggerOperation.UPDATE\n ? deserializeSyncRow({\n id,\n ...JSON.parse(op.previous_value),\n })\n : undefined\n write({\n type: mapOperation(operation),\n value: parsedValue,\n previousValue: parsedPreviousValue,\n })\n pendingOperations.push({\n id,\n operation,\n timestamp,\n tableName: viewName,\n })\n }\n\n // clear the current operations\n await context.execute(`DELETE FROM ${trackedTableName}`)\n\n commit()\n pendingOperationStore.resolvePendingFor(pendingOperations)\n } catch (error) {\n database.logger.error(\n `An error has been detected in the sync handler`,\n error,\n )\n }\n }\n\n // The sync function needs to be synchronous.\n async function start(afterOnChangeRegistered?: () => Promise<void>) {\n database.logger.info(\n `Sync is starting for ${viewName} into ${trackedTableName}`,\n )\n database.onChangeWithCallback(\n {\n onChange: async () => {\n await flushDiffRecords()\n },\n },\n {\n signal: abortController.signal,\n triggerImmediate: false,\n tables: [trackedTableName],\n },\n )\n\n await afterOnChangeRegistered?.()\n\n // If the abort controller was aborted while processing the request above\n if (abortController.signal.aborted) {\n await safelyDisposeTracking()\n } else {\n abortController.signal.addEventListener(\n `abort`,\n async () => {\n await safelyDisposeTracking()\n },\n { once: true },\n )\n }\n }\n\n // Eager mode.\n // Registers a diff trigger for the entire table.\n function runEagerSync() {\n let onUnload: CleanupFn | void | null = null\n\n start(async () => {\n onUnload = await restConfig.onLoad?.()\n\n disposeTracking = await createDiffTrigger({\n when: {\n [DiffTriggerOperation.INSERT]: `TRUE`,\n [DiffTriggerOperation.UPDATE]: `TRUE`,\n [DiffTriggerOperation.DELETE]: `TRUE`,\n },\n writeType: (_rowId: string) => `insert`,\n batchQuery: (\n lockContext: LockContext,\n batchSize: number,\n cursor: number,\n ) =>\n lockContext.getAll<TableType>(\n sanitizeSQL`SELECT * FROM ${viewName} LIMIT ? OFFSET ?`,\n [batchSize, cursor],\n ),\n onReady: () => markReady(),\n })\n }).catch((error) => {\n database.logger.error(\n `Could not start syncing process for ${viewName} into ${trackedTableName}`,\n error,\n )\n if (collection.status === `loading`) {\n markError(error)\n }\n })\n\n return () => {\n database.logger.info(\n `Sync has been stopped for ${viewName} into ${trackedTableName}`,\n )\n abortController.abort()\n onUnload?.()\n }\n }\n\n // On-demand mode.\n // Registers a diff trigger for the active WHERE expressions.\n function runOnDemandSync() {\n const unloadSubsetCallbacks = new Map<LoadSubsetOptions, CleanupFn>()\n const releasedSubsets = new WeakSet<LoadSubsetOptions>()\n let stopped = false\n const hasStopped = () => stopped\n\n start().catch((error) =>\n database.logger.error(\n `Could not start syncing process for ${viewName} into ${trackedTableName}`,\n error,\n ),\n )\n\n // Tracks all active WHERE expressions for on-demand sync filtering.\n // Each loadSubset call pushes its predicate; unloadSubset removes it.\n const activeWhereExpressions: Array<LoadSubsetOptions['where']> = []\n\n const loadSubset = async (\n options?: LoadSubsetOptions,\n ): Promise<void> => {\n if (hasStopped()) return\n\n if (options) {\n activeWhereExpressions.push(options.where)\n const cleanup = await restConfig.onLoadSubset?.(options)\n if (hasStopped()) {\n cleanup?.()\n return\n }\n if (cleanup) {\n if (releasedSubsets.has(options) || options.signal?.aborted) {\n cleanup()\n } else {\n unloadSubsetCallbacks.set(options, cleanup)\n }\n }\n }\n\n // No predicates remain, so stop tracking entirely. Both calls are no-ops\n // when no tracking table is currently active.\n if (activeWhereExpressions.length === 0) {\n await database.writeLock(async (ctx) => {\n await flushDiffRecordsWithContext(ctx)\n await safelyDisposeTracking(ctx)\n })\n return\n }\n\n const combinedWhere =\n activeWhereExpressions.length === 1\n ? activeWhereExpressions[0]\n : or(\n activeWhereExpressions[0],\n activeWhereExpressions[1],\n ...activeWhereExpressions.slice(2),\n )\n\n const compiledNewData = compileSQLite(\n { where: combinedWhere },\n { jsonColumn: 'NEW.data' },\n )\n\n const compiledOldData = compileSQLite(\n { where: combinedWhere },\n { jsonColumn: 'OLD.data' },\n )\n\n const compiledView = compileSQLite({ where: combinedWhere })\n\n const newDataWhenClause = toInlinedWhereClause(compiledNewData)\n const oldDataWhenClause = toInlinedWhereClause(compiledOldData)\n const viewWhereClause = toInlinedWhereClause(compiledView)\n\n await database.writeLock(async (ctx) => {\n // Replace any active tracking with one covering the new set of\n // predicates.\n await flushDiffRecordsWithContext(ctx)\n await safelyDisposeTracking(ctx)\n\n disposeTracking = await createDiffTrigger({\n setupContext: ctx,\n when: {\n [DiffTriggerOperation.INSERT]: newDataWhenClause,\n [DiffTriggerOperation.UPDATE]: `(${newDataWhenClause}) OR (${oldDataWhenClause})`,\n [DiffTriggerOperation.DELETE]: oldDataWhenClause,\n },\n writeType: (rowId: string) =>\n collection.has(rowId) ? `update` : `insert`,\n batchQuery: (\n lockContext: LockContext,\n batchSize: number,\n cursor: number,\n ) =>\n lockContext.getAll<TableType>(\n `SELECT * FROM ${viewName} WHERE ${viewWhereClause} LIMIT ? OFFSET ?`,\n [batchSize, cursor],\n ),\n onReady: () => {},\n })\n })\n }\n\n const toInlinedWhereClause = (compiled: {\n where?: string\n params: Array<unknown>\n }): string => {\n if (!compiled.where) return 'TRUE'\n const sqlParts = compiled.where.split('?')\n return sanitizeSQL(\n sqlParts as unknown as TemplateStringsArray,\n ...compiled.params,\n )\n }\n\n const unloadSubset = async (options: LoadSubsetOptions) => {\n releasedSubsets.add(options)\n unloadSubsetCallbacks.get(options)?.()\n unloadSubsetCallbacks.delete(options)\n\n const idx = activeWhereExpressions.indexOf(options.where)\n if (idx !== -1) {\n activeWhereExpressions.splice(idx, 1)\n }\n\n // Evict rows that were exclusively loaded by the departing predicate.\n // These are rows matching the departing WHERE that are no longer covered\n // by any remaining active predicate.\n const compiledDeparting = compileSQLite({ where: options.where })\n const departingWhereSQL = toInlinedWhereClause(compiledDeparting)\n\n let evictionSQL: string\n if (activeWhereExpressions.length === 0) {\n evictionSQL = `SELECT id FROM ${viewName} WHERE ${departingWhereSQL}`\n } else {\n const combinedRemaining =\n activeWhereExpressions.length === 1\n ? activeWhereExpressions[0]!\n : or(\n activeWhereExpressions[0],\n activeWhereExpressions[1],\n ...activeWhereExpressions.slice(2),\n )\n const compiledRemaining = compileSQLite({\n where: combinedRemaining,\n })\n const remainingWhereSQL = toInlinedWhereClause(compiledRemaining)\n evictionSQL = `SELECT id FROM ${viewName} WHERE (${departingWhereSQL}) AND NOT (${remainingWhereSQL})`\n }\n\n const rowsToEvict = await database.getAll<{ id: string }>(evictionSQL)\n if (rowsToEvict.length > 0) {\n begin()\n for (const { id } of rowsToEvict) {\n write({ type: `delete`, key: id })\n }\n commit()\n }\n\n // Recreate the diff trigger for the remaining active WHERE expressions.\n await loadSubset()\n }\n\n markReady()\n\n return {\n cleanup: () => {\n stopped = true\n database.logger.info(\n `Sync has been stopped for ${viewName} into ${trackedTableName}`,\n )\n abortController.abort()\n for (const cleanup of unloadSubsetCallbacks.values()) cleanup()\n unloadSubsetCallbacks.clear()\n activeWhereExpressions.length = 0\n },\n loadSubset: (options: LoadSubsetOptions) => loadSubset(options),\n unloadSubset: (options: LoadSubsetOptions) => unloadSubset(options),\n }\n }\n },\n // Expose the getSyncMetadata function\n getSyncMetadata: undefined,\n }\n\n const getKey = (record: OutputType) => asPowerSyncRecord(record).id\n\n const outputConfig: EnhancedPowerSyncCollectionConfig<\n TTable,\n OutputType,\n TSchema\n > = {\n ...restConfig,\n schema,\n getKey,\n // Syncing should start immediately since we need to monitor the changes for mutations\n startSync: true,\n syncMode,\n sync,\n onInsert: async (params) => {\n // The transaction here should only ever contain a single insert mutation\n return await transactor.applyTransaction(params.transaction)\n },\n onUpdate: async (params) => {\n // The transaction here should only ever contain a single update mutation\n return await transactor.applyTransaction(params.transaction)\n },\n onDelete: async (params) => {\n // The transaction here should only ever contain a single delete mutation\n return await transactor.applyTransaction(params.transaction)\n },\n utils: {\n getMeta: () => ({\n tableName: viewName,\n trackedTableName,\n metadataIsTracked,\n serializeValue: (value) =>\n serializeForSQLite(\n value,\n // This is required by the input generic\n table as Table<\n MapBaseColumnType<InferPowerSyncOutputType<TTable, TSchema>>\n >,\n // Coerce serializer to the shape that corresponds to the Table constructed from OutputType\n serializer as CustomSQLiteSerializer<\n OutputType,\n ExtractedTableColumns<Table<MapBaseColumnType<OutputType>>>\n >,\n ),\n }),\n },\n }\n return outputConfig\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"powersync.js","sources":["../../src/powersync.ts"],"sourcesContent":["import { DiffTriggerOperation, sanitizeSQL } from '@powersync/common'\nimport { or, withCollectionConfigFactory } from '@tanstack/db'\nimport { compileSQLite } from './sqlite-compiler'\nimport { PendingOperationStore } from './PendingOperationStore'\nimport { PowerSyncTransactor } from './PowerSyncTransactor'\nimport { DEFAULT_BATCH_SIZE } from './definitions'\nimport { asPowerSyncRecord, mapOperation } from './helpers'\nimport { convertTableToSchema } from './schema'\nimport { serializeForSQLite } from './serialization'\nimport type {\n CleanupFn,\n LoadSubsetOptions,\n OperationType,\n SyncAppliedReceipt,\n SyncConfig,\n} from '@tanstack/db'\nimport type {\n AnyTableColumnType,\n ExtractedTable,\n ExtractedTableColumns,\n MapBaseColumnType,\n OptionalExtractedTable,\n} from './helpers'\nimport type {\n BasePowerSyncCollectionConfig,\n ConfigWithArbitraryCollectionTypes,\n ConfigWithSQLiteInputType,\n ConfigWithSQLiteTypes,\n CustomSQLiteSerializer,\n EnhancedPowerSyncCollectionConfig,\n InferPowerSyncOutputType,\n PowerSyncCollectionConfig,\n PowerSyncCollectionUtils,\n} from './definitions'\nimport type { PendingOperation } from './PendingOperationStore'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { LockContext, Table, TriggerDiffRecord } from '@powersync/common'\n\n/**\n * Creates PowerSync collection options for use with a standard Collection.\n *\n * @template TTable - The SQLite-based typing\n * @template TSchema - The validation schema type (optionally supports a custom input type)\n * @param config - Configuration options for the PowerSync collection\n * @returns Collection options with utilities\n */\n\n// Overload 1: No schema is provided\n\n/**\n * Creates a PowerSync collection configuration with basic default validation.\n * Input and Output types are the SQLite column types.\n *\n * @example\n * ```typescript\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * }),\n * })\n *\n * type Document = (typeof APP_SCHEMA)[\"types\"][\"documents\"]\n *\n * const db = new PowerSyncDatabase({\n * database: {\n * dbFilename: \"test.sqlite\",\n * },\n * schema: APP_SCHEMA,\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents\n * })\n * )\n * ```\n */\nexport function powerSyncCollectionOptions<TTable extends Table = Table>(\n config: BasePowerSyncCollectionConfig<TTable, never> & ConfigWithSQLiteTypes,\n): EnhancedPowerSyncCollectionConfig<\n TTable,\n OptionalExtractedTable<TTable>,\n never\n>\n\n// Overload 2: Schema is provided and the TInput matches SQLite types.\n\n/**\n * Creates a PowerSync collection configuration with schema validation.\n *\n * The input types satisfy the SQLite column types.\n *\n * The output types are defined by the provided schema. This schema can enforce additional\n * validation or type transforms.\n * Arbitrary output typed mutations are encoded to SQLite for persistence. We provide a basic standard\n * serialization implementation to serialize column values. Custom or advanced types require providing additional\n * serializer specifications. Partial column overrides can be supplied to `serializer`.\n *\n * @example\n * ```typescript\n * import { z } from \"zod\"\n *\n * // The PowerSync SQLite schema\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * // Dates are stored as ISO date strings in SQLite\n * created_at: column.text\n * }),\n * })\n *\n * // Advanced Zod validations. The output type of this schema\n * // is constrained to the SQLite schema of APP_SCHEMA\n * const schema = z.object({\n * id: z.string(),\n * // Notice that `name` is not nullable (is required) here and it has additional validation\n * name: z.string().min(3, { message: \"Should be at least 3 characters\" }).nullable(),\n * // The input type is still the SQLite string type. While collections will output smart Date instances.\n * created_at: z.string().transform(val => new Date(val))\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents,\n * schema,\n * serializer: {\n * // The default is toISOString, this is just to demonstrate custom overrides\n * created_at: (outputValue) => outputValue.toISOString(),\n * },\n * })\n * )\n * ```\n */\nexport function powerSyncCollectionOptions<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // TInput is the SQLite types. We can use the supplied schema to validate sync input\n OptionalExtractedTable<TTable>,\n AnyTableColumnType<TTable>\n >,\n>(\n config: BasePowerSyncCollectionConfig<TTable, TSchema> &\n ConfigWithSQLiteInputType<TTable, TSchema>,\n): EnhancedPowerSyncCollectionConfig<\n TTable,\n InferPowerSyncOutputType<TTable, TSchema>,\n TSchema\n> & {\n schema: TSchema\n}\n\n// Overload 3: Schema is provided with arbitrary TInput and TOutput\n/**\n * Creates a PowerSync collection configuration with schema validation.\n *\n * The input types are not linked to the internal SQLite table types. This can\n * give greater flexibility, e.g. by accepting rich types as input for `insert` or `update` operations.\n * An additional `deserializationSchema` is required in order to process incoming SQLite updates to the output type.\n *\n * The output types are defined by the provided schema. This schema can enforce additional\n * validation or type transforms.\n * Arbitrary output typed mutations are encoded to SQLite for persistence. We provide a basic standard\n * serialization implementation to serialize column values. Custom or advanced types require providing additional\n * serializer specifications. Partial column overrides can be supplied to `serializer`.\n *\n * @example\n * ```typescript\n * import { z } from \"zod\"\n *\n * // The PowerSync SQLite schema\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * // Booleans are represented as integers in SQLite\n * is_active: column.integer\n * }),\n * })\n *\n * // Advanced Zod validations.\n * // We accept boolean values as input for operations and expose Booleans in query results\n * const schema = z.object({\n * id: z.string(),\n * isActive: z.boolean(), // TInput and TOutput are boolean\n * })\n *\n * // The deserializationSchema converts the SQLite synced INTEGER (0/1) values to booleans.\n * const deserializationSchema = z.object({\n * id: z.string(),\n * isActive: z.number().nullable().transform((val) => val == null ? true : val > 0),\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents,\n * schema,\n * deserializationSchema,\n * })\n * )\n * ```\n */\nexport function powerSyncCollectionOptions<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // The input and output must have the same keys, the value types can be arbitrary\n AnyTableColumnType<TTable>,\n AnyTableColumnType<TTable>\n >,\n>(\n config: BasePowerSyncCollectionConfig<TTable, TSchema> &\n ConfigWithArbitraryCollectionTypes<TTable, TSchema>,\n): EnhancedPowerSyncCollectionConfig<\n TTable,\n InferPowerSyncOutputType<TTable, TSchema>,\n TSchema\n> & {\n utils: PowerSyncCollectionUtils<TTable>\n schema: TSchema\n}\n\n/**\n * Implementation of powerSyncCollectionOptions that handles both schema and non-schema configurations.\n */\n\nexport function powerSyncCollectionOptions<\n TTable extends Table,\n TSchema extends StandardSchemaV1<any> = never,\n>(\n config: PowerSyncCollectionConfig<TTable, TSchema>,\n): ReturnType<typeof createPowerSyncCollectionConfig<TTable, TSchema>> {\n const outputConfig = createPowerSyncCollectionConfig(config)\n return withCollectionConfigFactory(outputConfig, () =>\n createPowerSyncCollectionConfig(config),\n )\n}\n\nfunction createPowerSyncCollectionConfig<\n TTable extends Table,\n TSchema extends StandardSchemaV1<any> = never,\n>(config: PowerSyncCollectionConfig<TTable, TSchema>) {\n const {\n database,\n table,\n schema: inputSchema,\n syncBatchSize = DEFAULT_BATCH_SIZE,\n syncMode = 'eager',\n ...restConfig\n } = config\n\n const deserializationSchema =\n `deserializationSchema` in config ? config.deserializationSchema : null\n const serializer = `serializer` in config ? config.serializer : undefined\n const onDeserializationError =\n `onDeserializationError` in config\n ? config.onDeserializationError\n : undefined\n\n // The SQLite table type\n type TableType = ExtractedTable<TTable>\n\n // The collection output type\n type OutputType = InferPowerSyncOutputType<TTable, TSchema>\n\n const { viewName, trackMetadata: metadataIsTracked } = table\n\n /**\n * Deserializes data from the incoming sync stream\n */\n const deserializeSyncRow = (value: TableType): OutputType => {\n const validationSchema = deserializationSchema || schema\n const validation = validationSchema[`~standard`].validate(value)\n if (`value` in validation) {\n return validation.value\n } else if (`issues` in validation) {\n const issueMessage = `Failed to validate incoming data for ${viewName}. Issues: ${validation.issues.map((issue) => `${issue.path} - ${issue.message}`)}`\n database.logger.error(issueMessage)\n onDeserializationError!(validation)\n throw new Error(issueMessage)\n } else {\n const unknownErrorMessage = `Unknown deserialization error for ${viewName}`\n database.logger.error(unknownErrorMessage)\n onDeserializationError!({ issues: [{ message: unknownErrorMessage }] })\n throw new Error(unknownErrorMessage)\n }\n }\n\n // We can do basic runtime validations for columns if not explicit schema has been provided\n const schema = inputSchema ?? (convertTableToSchema(table) as TSchema)\n /**\n * The onInsert, onUpdate, and onDelete handlers should only return\n * after we have written the changes to TanStack DB.\n * We currently only write to TanStack DB from a diff trigger.\n * We wait for the diff trigger to observe the change,\n * and only then return from the on[X] handlers.\n * This ensures that when the transaction is reported as\n * complete to the caller, the in-memory state is already\n * consistent with the database.\n */\n const pendingOperationStore = PendingOperationStore.GLOBAL\n // Keep the tracked table unique in case of multiple tabs.\n const trackedTableName = `__${viewName}_tracking_${Math.floor(\n Math.random() * 0xffffffff,\n )\n .toString(16)\n .padStart(8, `0`)}`\n\n const transactor = new PowerSyncTransactor({\n database,\n })\n\n /**\n * \"sync\"\n * Notice that this describes the Sync between the local SQLite table\n * and the in-memory tanstack-db collection.\n */\n const sync: SyncConfig<OutputType, string> = {\n sync: (params) => {\n const { begin, write, collection, commit, markReady, markError } = params\n const abortController = new AbortController()\n\n let disposeTracking:\n | ((options?: { context?: LockContext }) => Promise<void>)\n | null = null\n\n if (syncMode === `eager`) {\n return runEagerSync()\n } else {\n return runOnDemandSync()\n }\n\n /**\n * Disposes the current diff trigger, if one is active, and clears the\n * tracking state.\n */\n async function safelyDisposeTracking(\n context?: LockContext,\n ): Promise<void> {\n const dispose = disposeTracking\n if (!dispose) {\n return\n }\n\n disposeTracking = null\n await dispose(context ? { context } : undefined)\n }\n\n async function createDiffTrigger(\n options: {\n setupContext?: LockContext\n immediate?: boolean\n when: Record<DiffTriggerOperation, string>\n writeType: (rowId: string) => OperationType\n batchQuery: (\n lockContext: LockContext,\n batchSize: number,\n cursor: number,\n ) => Promise<Array<TableType>>\n },\n appliedReceipts: Array<SyncAppliedReceipt>,\n ) {\n const { setupContext, immediate, when, writeType, batchQuery } = options\n\n return await database.triggers.createDiffTrigger({\n source: viewName,\n destination: trackedTableName,\n setupContext,\n when,\n hooks: {\n beforeCreate: async (context) => {\n let currentBatchCount = syncBatchSize\n let cursor = 0\n while (currentBatchCount == syncBatchSize) {\n begin(immediate ? { immediate: true } : undefined)\n\n const batchItems = await batchQuery(\n context,\n syncBatchSize,\n cursor,\n )\n currentBatchCount = batchItems.length\n cursor += currentBatchCount\n for (const row of batchItems) {\n write({\n type: writeType(row.id),\n value: deserializeSyncRow(row),\n })\n }\n appliedReceipts.push(commit())\n }\n database.logger.info(\n `Sync is ready for ${viewName} into ${trackedTableName}`,\n )\n },\n },\n })\n }\n\n async function flushDiffRecords(): Promise<void> {\n const ignoredReceipts: Array<SyncAppliedReceipt> = []\n await database\n .writeTransaction(async (context) => {\n await flushDiffRecordsWithContext(context, ignoredReceipts)\n })\n .catch((error) => {\n database.logger.error(\n `An error has been detected in the sync handler`,\n error,\n )\n })\n }\n\n // We can use this directly if we want to pair a flush with dispose+recreate diff trigger.\n async function flushDiffRecordsWithContext(\n context: LockContext,\n appliedReceipts: Array<SyncAppliedReceipt>,\n ): Promise<void> {\n // There is nothing to flush if no tracking table is currently active.\n if (!disposeTracking) {\n return\n }\n\n try {\n begin()\n const operations = await context.getAll<TriggerDiffRecord>(\n `SELECT * FROM ${trackedTableName} ORDER BY operation_id ASC`,\n )\n const pendingOperations: Array<PendingOperation> = []\n\n for (const op of operations) {\n const { id, operation, timestamp, value } = op\n const parsedValue = deserializeSyncRow({\n id,\n ...JSON.parse(value),\n })\n const parsedPreviousValue =\n op.operation == DiffTriggerOperation.UPDATE\n ? deserializeSyncRow({\n id,\n ...JSON.parse(op.previous_value),\n })\n : undefined\n write({\n type: mapOperation(operation),\n value: parsedValue,\n previousValue: parsedPreviousValue,\n })\n pendingOperations.push({\n id,\n operation,\n timestamp,\n tableName: viewName,\n })\n }\n\n // clear the current operations\n await context.execute(`DELETE FROM ${trackedTableName}`)\n\n const applied = commit()\n appliedReceipts.push(applied)\n // Mutation persistence is what releases the Collection's FIFO gate.\n // Confirm these local operations after the sync transaction is\n // staged; waiting for its applied receipt would deadlock the user\n // transaction that currently parks it.\n pendingOperationStore.resolvePendingFor(pendingOperations)\n } catch (error) {\n database.logger.error(\n `An error has been detected in the sync handler`,\n error,\n )\n }\n }\n\n // The sync function needs to be synchronous.\n async function start(afterOnChangeRegistered?: () => Promise<void>) {\n database.logger.info(\n `Sync is starting for ${viewName} into ${trackedTableName}`,\n )\n database.onChangeWithCallback(\n {\n onChange: async () => {\n await flushDiffRecords()\n },\n },\n {\n signal: abortController.signal,\n triggerImmediate: false,\n tables: [trackedTableName],\n },\n )\n\n await afterOnChangeRegistered?.()\n\n // If the abort controller was aborted while processing the request above\n if (abortController.signal.aborted) {\n await safelyDisposeTracking()\n } else {\n abortController.signal.addEventListener(\n `abort`,\n async () => {\n await safelyDisposeTracking()\n },\n { once: true },\n )\n }\n }\n\n // Eager mode.\n // Registers a diff trigger for the entire table.\n function runEagerSync() {\n let onUnload: CleanupFn | void | null = null\n\n start(async () => {\n onUnload = await restConfig.onLoad?.()\n\n const appliedReceipts: Array<SyncAppliedReceipt> = []\n disposeTracking = await createDiffTrigger(\n {\n // Initial eager hydration must make the source usable before\n // PowerSync can persist a mutation queued during startup.\n immediate: true,\n when: {\n [DiffTriggerOperation.INSERT]: `TRUE`,\n [DiffTriggerOperation.UPDATE]: `TRUE`,\n [DiffTriggerOperation.DELETE]: `TRUE`,\n },\n writeType: (_rowId: string) => `insert`,\n batchQuery: (\n lockContext: LockContext,\n batchSize: number,\n cursor: number,\n ) =>\n lockContext.getAll<TableType>(\n sanitizeSQL`SELECT * FROM ${viewName} LIMIT ? OFFSET ?`,\n [batchSize, cursor],\n ),\n },\n appliedReceipts,\n )\n await Promise.all(appliedReceipts)\n markReady()\n }).catch((error) => {\n database.logger.error(\n `Could not start syncing process for ${viewName} into ${trackedTableName}`,\n error,\n )\n if (collection.status === `loading`) {\n markError(error)\n }\n })\n\n return () => {\n database.logger.info(\n `Sync has been stopped for ${viewName} into ${trackedTableName}`,\n )\n abortController.abort()\n onUnload?.()\n }\n }\n\n // On-demand mode.\n // Registers a diff trigger for the active WHERE expressions.\n function runOnDemandSync() {\n const unloadSubsetCallbacks = new Map<LoadSubsetOptions, CleanupFn>()\n const releasedSubsets = new WeakSet<LoadSubsetOptions>()\n let stopped = false\n const hasStopped = () => stopped\n\n start().catch((error) =>\n database.logger.error(\n `Could not start syncing process for ${viewName} into ${trackedTableName}`,\n error,\n ),\n )\n\n // Tracks all active WHERE expressions for on-demand sync filtering.\n // Each loadSubset call pushes its predicate; unloadSubset removes it.\n const activeWhereExpressions: Array<LoadSubsetOptions['where']> = []\n\n const loadSubset = async (\n options?: LoadSubsetOptions,\n ): Promise<void> => {\n if (hasStopped()) return\n const appliedReceipts: Array<SyncAppliedReceipt> = []\n\n if (options) {\n activeWhereExpressions.push(options.where)\n const cleanup = await restConfig.onLoadSubset?.(options)\n if (hasStopped()) {\n cleanup?.()\n return\n }\n if (cleanup) {\n if (releasedSubsets.has(options) || options.signal?.aborted) {\n cleanup()\n } else {\n unloadSubsetCallbacks.set(options, cleanup)\n }\n }\n }\n\n // No predicates remain, so stop tracking entirely. Both calls are no-ops\n // when no tracking table is currently active.\n if (activeWhereExpressions.length === 0) {\n await database.writeLock(async (ctx) => {\n await flushDiffRecordsWithContext(ctx, appliedReceipts)\n await safelyDisposeTracking(ctx)\n })\n await Promise.all(appliedReceipts)\n return\n }\n\n const combinedWhere =\n activeWhereExpressions.length === 1\n ? activeWhereExpressions[0]\n : or(\n activeWhereExpressions[0],\n activeWhereExpressions[1],\n ...activeWhereExpressions.slice(2),\n )\n\n const compiledNewData = compileSQLite(\n { where: combinedWhere },\n { jsonColumn: 'NEW.data' },\n )\n\n const compiledOldData = compileSQLite(\n { where: combinedWhere },\n { jsonColumn: 'OLD.data' },\n )\n\n const compiledView = compileSQLite({ where: combinedWhere })\n\n const newDataWhenClause = toInlinedWhereClause(compiledNewData)\n const oldDataWhenClause = toInlinedWhereClause(compiledOldData)\n const viewWhereClause = toInlinedWhereClause(compiledView)\n\n await database.writeLock(async (ctx) => {\n // Replace any active tracking with one covering the new set of\n // predicates.\n await flushDiffRecordsWithContext(ctx, appliedReceipts)\n await safelyDisposeTracking(ctx)\n\n disposeTracking = await createDiffTrigger(\n {\n setupContext: ctx,\n when: {\n [DiffTriggerOperation.INSERT]: newDataWhenClause,\n [DiffTriggerOperation.UPDATE]: `(${newDataWhenClause}) OR (${oldDataWhenClause})`,\n [DiffTriggerOperation.DELETE]: oldDataWhenClause,\n },\n writeType: (rowId: string) =>\n collection.has(rowId) ? `update` : `insert`,\n batchQuery: (\n lockContext: LockContext,\n batchSize: number,\n cursor: number,\n ) =>\n lockContext.getAll<TableType>(\n `SELECT * FROM ${viewName} WHERE ${viewWhereClause} LIMIT ? OFFSET ?`,\n [batchSize, cursor],\n ),\n },\n appliedReceipts,\n )\n })\n await Promise.all(appliedReceipts)\n }\n\n const toInlinedWhereClause = (compiled: {\n where?: string\n params: Array<unknown>\n }): string => {\n if (!compiled.where) return 'TRUE'\n const sqlParts = compiled.where.split('?')\n return sanitizeSQL(\n sqlParts as unknown as TemplateStringsArray,\n ...compiled.params,\n )\n }\n\n const unloadSubset = async (options: LoadSubsetOptions) => {\n releasedSubsets.add(options)\n unloadSubsetCallbacks.get(options)?.()\n unloadSubsetCallbacks.delete(options)\n\n const idx = activeWhereExpressions.indexOf(options.where)\n if (idx !== -1) {\n activeWhereExpressions.splice(idx, 1)\n }\n\n // Evict rows that were exclusively loaded by the departing predicate.\n // These are rows matching the departing WHERE that are no longer covered\n // by any remaining active predicate.\n const compiledDeparting = compileSQLite({ where: options.where })\n const departingWhereSQL = toInlinedWhereClause(compiledDeparting)\n\n let evictionSQL: string\n if (activeWhereExpressions.length === 0) {\n evictionSQL = `SELECT id FROM ${viewName} WHERE ${departingWhereSQL}`\n } else {\n const combinedRemaining =\n activeWhereExpressions.length === 1\n ? activeWhereExpressions[0]!\n : or(\n activeWhereExpressions[0],\n activeWhereExpressions[1],\n ...activeWhereExpressions.slice(2),\n )\n const compiledRemaining = compileSQLite({\n where: combinedRemaining,\n })\n const remainingWhereSQL = toInlinedWhereClause(compiledRemaining)\n evictionSQL = `SELECT id FROM ${viewName} WHERE (${departingWhereSQL}) AND NOT (${remainingWhereSQL})`\n }\n\n const rowsToEvict = await database.getAll<{ id: string }>(evictionSQL)\n if (rowsToEvict.length > 0) {\n begin()\n for (const { id } of rowsToEvict) {\n write({ type: `delete`, key: id })\n }\n // Eviction does not establish new subset coverage. Keep trigger\n // replacement in the same unload turn even when this delete waits\n // behind a persisting mutation; the later load tracks its own\n // establishing receipts.\n void commit()\n }\n\n // Recreate the diff trigger for the remaining active WHERE expressions.\n await loadSubset()\n }\n\n markReady()\n\n return {\n cleanup: () => {\n stopped = true\n database.logger.info(\n `Sync has been stopped for ${viewName} into ${trackedTableName}`,\n )\n abortController.abort()\n for (const cleanup of unloadSubsetCallbacks.values()) cleanup()\n unloadSubsetCallbacks.clear()\n activeWhereExpressions.length = 0\n },\n loadSubset: (options: LoadSubsetOptions) => loadSubset(options),\n unloadSubset: (options: LoadSubsetOptions) => unloadSubset(options),\n }\n }\n },\n // Expose the getSyncMetadata function\n getSyncMetadata: undefined,\n }\n\n const getKey = (record: OutputType) => asPowerSyncRecord(record).id\n\n const outputConfig: EnhancedPowerSyncCollectionConfig<\n TTable,\n OutputType,\n TSchema\n > = {\n ...restConfig,\n schema,\n getKey,\n // Syncing should start immediately since we need to monitor the changes for mutations\n startSync: true,\n syncMode,\n sync,\n onInsert: async (params) => {\n // The transaction here should only ever contain a single insert mutation\n return await transactor.applyTransaction(params.transaction)\n },\n onUpdate: async (params) => {\n // The transaction here should only ever contain a single update mutation\n return await transactor.applyTransaction(params.transaction)\n },\n onDelete: async (params) => {\n // The transaction here should only ever contain a single delete mutation\n return await transactor.applyTransaction(params.transaction)\n },\n utils: {\n getMeta: () => ({\n tableName: viewName,\n trackedTableName,\n metadataIsTracked,\n serializeValue: (value) =>\n serializeForSQLite(\n value,\n // This is required by the input generic\n table as Table<\n MapBaseColumnType<InferPowerSyncOutputType<TTable, TSchema>>\n >,\n // Coerce serializer to the shape that corresponds to the Table constructed from OutputType\n serializer as CustomSQLiteSerializer<\n OutputType,\n ExtractedTableColumns<Table<MapBaseColumnType<OutputType>>>\n >,\n ),\n }),\n },\n }\n return outputConfig\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/powersync-db-collection",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.64",
|
|
4
4
|
"description": "PowerSync collection for TanStack DB",
|
|
5
5
|
"author": "POWERSYNC",
|
|
6
6
|
"license": "MIT",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@tanstack/store": "^0.9.2",
|
|
51
51
|
"debug": "^4.4.3",
|
|
52
52
|
"p-defer": "^4.0.1",
|
|
53
|
-
"@tanstack/db": "0.8.
|
|
53
|
+
"@tanstack/db": "0.8.5"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"@powersync/common": "^1.41.0"
|
package/src/powersync.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
CleanupFn,
|
|
12
12
|
LoadSubsetOptions,
|
|
13
13
|
OperationType,
|
|
14
|
+
SyncAppliedReceipt,
|
|
14
15
|
SyncConfig,
|
|
15
16
|
} from '@tanstack/db'
|
|
16
17
|
import type {
|
|
@@ -345,18 +346,21 @@ function createPowerSyncCollectionConfig<
|
|
|
345
346
|
await dispose(context ? { context } : undefined)
|
|
346
347
|
}
|
|
347
348
|
|
|
348
|
-
async function createDiffTrigger(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
349
|
+
async function createDiffTrigger(
|
|
350
|
+
options: {
|
|
351
|
+
setupContext?: LockContext
|
|
352
|
+
immediate?: boolean
|
|
353
|
+
when: Record<DiffTriggerOperation, string>
|
|
354
|
+
writeType: (rowId: string) => OperationType
|
|
355
|
+
batchQuery: (
|
|
356
|
+
lockContext: LockContext,
|
|
357
|
+
batchSize: number,
|
|
358
|
+
cursor: number,
|
|
359
|
+
) => Promise<Array<TableType>>
|
|
360
|
+
},
|
|
361
|
+
appliedReceipts: Array<SyncAppliedReceipt>,
|
|
362
|
+
) {
|
|
363
|
+
const { setupContext, immediate, when, writeType, batchQuery } = options
|
|
360
364
|
|
|
361
365
|
return await database.triggers.createDiffTrigger({
|
|
362
366
|
source: viewName,
|
|
@@ -368,7 +372,7 @@ function createPowerSyncCollectionConfig<
|
|
|
368
372
|
let currentBatchCount = syncBatchSize
|
|
369
373
|
let cursor = 0
|
|
370
374
|
while (currentBatchCount == syncBatchSize) {
|
|
371
|
-
begin()
|
|
375
|
+
begin(immediate ? { immediate: true } : undefined)
|
|
372
376
|
|
|
373
377
|
const batchItems = await batchQuery(
|
|
374
378
|
context,
|
|
@@ -383,9 +387,8 @@ function createPowerSyncCollectionConfig<
|
|
|
383
387
|
value: deserializeSyncRow(row),
|
|
384
388
|
})
|
|
385
389
|
}
|
|
386
|
-
commit()
|
|
390
|
+
appliedReceipts.push(commit())
|
|
387
391
|
}
|
|
388
|
-
onReady()
|
|
389
392
|
database.logger.info(
|
|
390
393
|
`Sync is ready for ${viewName} into ${trackedTableName}`,
|
|
391
394
|
)
|
|
@@ -395,9 +398,10 @@ function createPowerSyncCollectionConfig<
|
|
|
395
398
|
}
|
|
396
399
|
|
|
397
400
|
async function flushDiffRecords(): Promise<void> {
|
|
401
|
+
const ignoredReceipts: Array<SyncAppliedReceipt> = []
|
|
398
402
|
await database
|
|
399
403
|
.writeTransaction(async (context) => {
|
|
400
|
-
await flushDiffRecordsWithContext(context)
|
|
404
|
+
await flushDiffRecordsWithContext(context, ignoredReceipts)
|
|
401
405
|
})
|
|
402
406
|
.catch((error) => {
|
|
403
407
|
database.logger.error(
|
|
@@ -410,6 +414,7 @@ function createPowerSyncCollectionConfig<
|
|
|
410
414
|
// We can use this directly if we want to pair a flush with dispose+recreate diff trigger.
|
|
411
415
|
async function flushDiffRecordsWithContext(
|
|
412
416
|
context: LockContext,
|
|
417
|
+
appliedReceipts: Array<SyncAppliedReceipt>,
|
|
413
418
|
): Promise<void> {
|
|
414
419
|
// There is nothing to flush if no tracking table is currently active.
|
|
415
420
|
if (!disposeTracking) {
|
|
@@ -452,7 +457,12 @@ function createPowerSyncCollectionConfig<
|
|
|
452
457
|
// clear the current operations
|
|
453
458
|
await context.execute(`DELETE FROM ${trackedTableName}`)
|
|
454
459
|
|
|
455
|
-
commit()
|
|
460
|
+
const applied = commit()
|
|
461
|
+
appliedReceipts.push(applied)
|
|
462
|
+
// Mutation persistence is what releases the Collection's FIFO gate.
|
|
463
|
+
// Confirm these local operations after the sync transaction is
|
|
464
|
+
// staged; waiting for its applied receipt would deadlock the user
|
|
465
|
+
// transaction that currently parks it.
|
|
456
466
|
pendingOperationStore.resolvePendingFor(pendingOperations)
|
|
457
467
|
} catch (error) {
|
|
458
468
|
database.logger.error(
|
|
@@ -504,24 +514,32 @@ function createPowerSyncCollectionConfig<
|
|
|
504
514
|
start(async () => {
|
|
505
515
|
onUnload = await restConfig.onLoad?.()
|
|
506
516
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
517
|
+
const appliedReceipts: Array<SyncAppliedReceipt> = []
|
|
518
|
+
disposeTracking = await createDiffTrigger(
|
|
519
|
+
{
|
|
520
|
+
// Initial eager hydration must make the source usable before
|
|
521
|
+
// PowerSync can persist a mutation queued during startup.
|
|
522
|
+
immediate: true,
|
|
523
|
+
when: {
|
|
524
|
+
[DiffTriggerOperation.INSERT]: `TRUE`,
|
|
525
|
+
[DiffTriggerOperation.UPDATE]: `TRUE`,
|
|
526
|
+
[DiffTriggerOperation.DELETE]: `TRUE`,
|
|
527
|
+
},
|
|
528
|
+
writeType: (_rowId: string) => `insert`,
|
|
529
|
+
batchQuery: (
|
|
530
|
+
lockContext: LockContext,
|
|
531
|
+
batchSize: number,
|
|
532
|
+
cursor: number,
|
|
533
|
+
) =>
|
|
534
|
+
lockContext.getAll<TableType>(
|
|
535
|
+
sanitizeSQL`SELECT * FROM ${viewName} LIMIT ? OFFSET ?`,
|
|
536
|
+
[batchSize, cursor],
|
|
537
|
+
),
|
|
512
538
|
},
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
cursor: number,
|
|
518
|
-
) =>
|
|
519
|
-
lockContext.getAll<TableType>(
|
|
520
|
-
sanitizeSQL`SELECT * FROM ${viewName} LIMIT ? OFFSET ?`,
|
|
521
|
-
[batchSize, cursor],
|
|
522
|
-
),
|
|
523
|
-
onReady: () => markReady(),
|
|
524
|
-
})
|
|
539
|
+
appliedReceipts,
|
|
540
|
+
)
|
|
541
|
+
await Promise.all(appliedReceipts)
|
|
542
|
+
markReady()
|
|
525
543
|
}).catch((error) => {
|
|
526
544
|
database.logger.error(
|
|
527
545
|
`Could not start syncing process for ${viewName} into ${trackedTableName}`,
|
|
@@ -564,6 +582,7 @@ function createPowerSyncCollectionConfig<
|
|
|
564
582
|
options?: LoadSubsetOptions,
|
|
565
583
|
): Promise<void> => {
|
|
566
584
|
if (hasStopped()) return
|
|
585
|
+
const appliedReceipts: Array<SyncAppliedReceipt> = []
|
|
567
586
|
|
|
568
587
|
if (options) {
|
|
569
588
|
activeWhereExpressions.push(options.where)
|
|
@@ -585,9 +604,10 @@ function createPowerSyncCollectionConfig<
|
|
|
585
604
|
// when no tracking table is currently active.
|
|
586
605
|
if (activeWhereExpressions.length === 0) {
|
|
587
606
|
await database.writeLock(async (ctx) => {
|
|
588
|
-
await flushDiffRecordsWithContext(ctx)
|
|
607
|
+
await flushDiffRecordsWithContext(ctx, appliedReceipts)
|
|
589
608
|
await safelyDisposeTracking(ctx)
|
|
590
609
|
})
|
|
610
|
+
await Promise.all(appliedReceipts)
|
|
591
611
|
return
|
|
592
612
|
}
|
|
593
613
|
|
|
@@ -619,30 +639,33 @@ function createPowerSyncCollectionConfig<
|
|
|
619
639
|
await database.writeLock(async (ctx) => {
|
|
620
640
|
// Replace any active tracking with one covering the new set of
|
|
621
641
|
// predicates.
|
|
622
|
-
await flushDiffRecordsWithContext(ctx)
|
|
642
|
+
await flushDiffRecordsWithContext(ctx, appliedReceipts)
|
|
623
643
|
await safelyDisposeTracking(ctx)
|
|
624
644
|
|
|
625
|
-
disposeTracking = await createDiffTrigger(
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
645
|
+
disposeTracking = await createDiffTrigger(
|
|
646
|
+
{
|
|
647
|
+
setupContext: ctx,
|
|
648
|
+
when: {
|
|
649
|
+
[DiffTriggerOperation.INSERT]: newDataWhenClause,
|
|
650
|
+
[DiffTriggerOperation.UPDATE]: `(${newDataWhenClause}) OR (${oldDataWhenClause})`,
|
|
651
|
+
[DiffTriggerOperation.DELETE]: oldDataWhenClause,
|
|
652
|
+
},
|
|
653
|
+
writeType: (rowId: string) =>
|
|
654
|
+
collection.has(rowId) ? `update` : `insert`,
|
|
655
|
+
batchQuery: (
|
|
656
|
+
lockContext: LockContext,
|
|
657
|
+
batchSize: number,
|
|
658
|
+
cursor: number,
|
|
659
|
+
) =>
|
|
660
|
+
lockContext.getAll<TableType>(
|
|
661
|
+
`SELECT * FROM ${viewName} WHERE ${viewWhereClause} LIMIT ? OFFSET ?`,
|
|
662
|
+
[batchSize, cursor],
|
|
663
|
+
),
|
|
631
664
|
},
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
batchQuery: (
|
|
635
|
-
lockContext: LockContext,
|
|
636
|
-
batchSize: number,
|
|
637
|
-
cursor: number,
|
|
638
|
-
) =>
|
|
639
|
-
lockContext.getAll<TableType>(
|
|
640
|
-
`SELECT * FROM ${viewName} WHERE ${viewWhereClause} LIMIT ? OFFSET ?`,
|
|
641
|
-
[batchSize, cursor],
|
|
642
|
-
),
|
|
643
|
-
onReady: () => {},
|
|
644
|
-
})
|
|
665
|
+
appliedReceipts,
|
|
666
|
+
)
|
|
645
667
|
})
|
|
668
|
+
await Promise.all(appliedReceipts)
|
|
646
669
|
}
|
|
647
670
|
|
|
648
671
|
const toInlinedWhereClause = (compiled: {
|
|
@@ -698,7 +721,11 @@ function createPowerSyncCollectionConfig<
|
|
|
698
721
|
for (const { id } of rowsToEvict) {
|
|
699
722
|
write({ type: `delete`, key: id })
|
|
700
723
|
}
|
|
701
|
-
|
|
724
|
+
// Eviction does not establish new subset coverage. Keep trigger
|
|
725
|
+
// replacement in the same unload turn even when this delete waits
|
|
726
|
+
// behind a persisting mutation; the later load tracks its own
|
|
727
|
+
// establishing receipts.
|
|
728
|
+
void commit()
|
|
702
729
|
}
|
|
703
730
|
|
|
704
731
|
// Recreate the diff trigger for the remaining active WHERE expressions.
|