@tanstack/powersync-db-collection 0.1.59 → 0.1.60
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 +24 -3
- package/dist/cjs/powersync.cjs.map +1 -1
- package/dist/esm/powersync.js +24 -3
- package/dist/esm/powersync.js.map +1 -1
- package/package.json +2 -2
- package/src/powersync.ts +25 -3
package/dist/cjs/powersync.cjs
CHANGED
|
@@ -221,7 +221,10 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
221
221
|
};
|
|
222
222
|
}
|
|
223
223
|
function runOnDemandSync() {
|
|
224
|
-
|
|
224
|
+
const unloadSubsetCallbacks = /* @__PURE__ */ new Map();
|
|
225
|
+
const releasedSubsets = /* @__PURE__ */ new WeakSet();
|
|
226
|
+
let stopped = false;
|
|
227
|
+
const hasStopped = () => stopped;
|
|
225
228
|
start().catch(
|
|
226
229
|
(error) => database.logger.error(
|
|
227
230
|
`Could not start syncing process for ${viewName} into ${trackedTableName}`,
|
|
@@ -230,9 +233,21 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
230
233
|
);
|
|
231
234
|
const activeWhereExpressions = [];
|
|
232
235
|
const loadSubset = async (options) => {
|
|
236
|
+
if (hasStopped()) return;
|
|
233
237
|
if (options) {
|
|
234
238
|
activeWhereExpressions.push(options.where);
|
|
235
|
-
|
|
239
|
+
const cleanup = await restConfig.onLoadSubset?.(options);
|
|
240
|
+
if (hasStopped()) {
|
|
241
|
+
cleanup?.();
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (cleanup) {
|
|
245
|
+
if (releasedSubsets.has(options) || options.signal?.aborted) {
|
|
246
|
+
cleanup();
|
|
247
|
+
} else {
|
|
248
|
+
unloadSubsetCallbacks.set(options, cleanup);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
236
251
|
}
|
|
237
252
|
if (activeWhereExpressions.length === 0) {
|
|
238
253
|
await database.writeLock(async (ctx) => {
|
|
@@ -287,7 +302,9 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
287
302
|
);
|
|
288
303
|
};
|
|
289
304
|
const unloadSubset = async (options) => {
|
|
290
|
-
|
|
305
|
+
releasedSubsets.add(options);
|
|
306
|
+
unloadSubsetCallbacks.get(options)?.();
|
|
307
|
+
unloadSubsetCallbacks.delete(options);
|
|
291
308
|
const idx = activeWhereExpressions.indexOf(options.where);
|
|
292
309
|
if (idx !== -1) {
|
|
293
310
|
activeWhereExpressions.splice(idx, 1);
|
|
@@ -322,10 +339,14 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
322
339
|
markReady();
|
|
323
340
|
return {
|
|
324
341
|
cleanup: () => {
|
|
342
|
+
stopped = true;
|
|
325
343
|
database.logger.info(
|
|
326
344
|
`Sync has been stopped for ${viewName} into ${trackedTableName}`
|
|
327
345
|
);
|
|
328
346
|
abortController.abort();
|
|
347
|
+
for (const cleanup of unloadSubsetCallbacks.values()) cleanup();
|
|
348
|
+
unloadSubsetCallbacks.clear();
|
|
349
|
+
activeWhereExpressions.length = 0;
|
|
329
350
|
},
|
|
330
351
|
loadSubset: (options) => loadSubset(options),
|
|
331
352
|
unloadSubset: (options) => unloadSubset(options)
|
|
@@ -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 } = 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 )\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 let onUnloadSubset: CleanupFn | void | null = null\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 (options) {\n activeWhereExpressions.push(options.where)\n onUnloadSubset = await restConfig.onLoadSubset?.(options)\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 onUnloadSubset?.()\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 database.logger.info(\n `Sync has been stopped for ${viewName} into ${trackedTableName}`,\n )\n abortController.abort()\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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"}
|
|
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 } = 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 )\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":"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"}
|
package/dist/esm/powersync.js
CHANGED
|
@@ -219,7 +219,10 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
219
219
|
};
|
|
220
220
|
}
|
|
221
221
|
function runOnDemandSync() {
|
|
222
|
-
|
|
222
|
+
const unloadSubsetCallbacks = /* @__PURE__ */ new Map();
|
|
223
|
+
const releasedSubsets = /* @__PURE__ */ new WeakSet();
|
|
224
|
+
let stopped = false;
|
|
225
|
+
const hasStopped = () => stopped;
|
|
223
226
|
start().catch(
|
|
224
227
|
(error) => database.logger.error(
|
|
225
228
|
`Could not start syncing process for ${viewName} into ${trackedTableName}`,
|
|
@@ -228,9 +231,21 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
228
231
|
);
|
|
229
232
|
const activeWhereExpressions = [];
|
|
230
233
|
const loadSubset = async (options) => {
|
|
234
|
+
if (hasStopped()) return;
|
|
231
235
|
if (options) {
|
|
232
236
|
activeWhereExpressions.push(options.where);
|
|
233
|
-
|
|
237
|
+
const cleanup = await restConfig.onLoadSubset?.(options);
|
|
238
|
+
if (hasStopped()) {
|
|
239
|
+
cleanup?.();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (cleanup) {
|
|
243
|
+
if (releasedSubsets.has(options) || options.signal?.aborted) {
|
|
244
|
+
cleanup();
|
|
245
|
+
} else {
|
|
246
|
+
unloadSubsetCallbacks.set(options, cleanup);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
234
249
|
}
|
|
235
250
|
if (activeWhereExpressions.length === 0) {
|
|
236
251
|
await database.writeLock(async (ctx) => {
|
|
@@ -285,7 +300,9 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
285
300
|
);
|
|
286
301
|
};
|
|
287
302
|
const unloadSubset = async (options) => {
|
|
288
|
-
|
|
303
|
+
releasedSubsets.add(options);
|
|
304
|
+
unloadSubsetCallbacks.get(options)?.();
|
|
305
|
+
unloadSubsetCallbacks.delete(options);
|
|
289
306
|
const idx = activeWhereExpressions.indexOf(options.where);
|
|
290
307
|
if (idx !== -1) {
|
|
291
308
|
activeWhereExpressions.splice(idx, 1);
|
|
@@ -320,10 +337,14 @@ function createPowerSyncCollectionConfig(config) {
|
|
|
320
337
|
markReady();
|
|
321
338
|
return {
|
|
322
339
|
cleanup: () => {
|
|
340
|
+
stopped = true;
|
|
323
341
|
database.logger.info(
|
|
324
342
|
`Sync has been stopped for ${viewName} into ${trackedTableName}`
|
|
325
343
|
);
|
|
326
344
|
abortController.abort();
|
|
345
|
+
for (const cleanup of unloadSubsetCallbacks.values()) cleanup();
|
|
346
|
+
unloadSubsetCallbacks.clear();
|
|
347
|
+
activeWhereExpressions.length = 0;
|
|
327
348
|
},
|
|
328
349
|
loadSubset: (options) => loadSubset(options),
|
|
329
350
|
unloadSubset: (options) => unloadSubset(options)
|
|
@@ -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 } = 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 )\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 let onUnloadSubset: CleanupFn | void | null = null\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 (options) {\n activeWhereExpressions.push(options.where)\n onUnloadSubset = await restConfig.onLoadSubset?.(options)\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 onUnloadSubset?.()\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 database.logger.info(\n `Sync has been stopped for ${viewName} into ${trackedTableName}`,\n )\n abortController.abort()\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 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 } = 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 )\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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"}
|
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.60",
|
|
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.1"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"@powersync/common": "^1.41.0"
|
package/src/powersync.ts
CHANGED
|
@@ -541,7 +541,10 @@ function createPowerSyncCollectionConfig<
|
|
|
541
541
|
// On-demand mode.
|
|
542
542
|
// Registers a diff trigger for the active WHERE expressions.
|
|
543
543
|
function runOnDemandSync() {
|
|
544
|
-
|
|
544
|
+
const unloadSubsetCallbacks = new Map<LoadSubsetOptions, CleanupFn>()
|
|
545
|
+
const releasedSubsets = new WeakSet<LoadSubsetOptions>()
|
|
546
|
+
let stopped = false
|
|
547
|
+
const hasStopped = () => stopped
|
|
545
548
|
|
|
546
549
|
start().catch((error) =>
|
|
547
550
|
database.logger.error(
|
|
@@ -557,9 +560,22 @@ function createPowerSyncCollectionConfig<
|
|
|
557
560
|
const loadSubset = async (
|
|
558
561
|
options?: LoadSubsetOptions,
|
|
559
562
|
): Promise<void> => {
|
|
563
|
+
if (hasStopped()) return
|
|
564
|
+
|
|
560
565
|
if (options) {
|
|
561
566
|
activeWhereExpressions.push(options.where)
|
|
562
|
-
|
|
567
|
+
const cleanup = await restConfig.onLoadSubset?.(options)
|
|
568
|
+
if (hasStopped()) {
|
|
569
|
+
cleanup?.()
|
|
570
|
+
return
|
|
571
|
+
}
|
|
572
|
+
if (cleanup) {
|
|
573
|
+
if (releasedSubsets.has(options) || options.signal?.aborted) {
|
|
574
|
+
cleanup()
|
|
575
|
+
} else {
|
|
576
|
+
unloadSubsetCallbacks.set(options, cleanup)
|
|
577
|
+
}
|
|
578
|
+
}
|
|
563
579
|
}
|
|
564
580
|
|
|
565
581
|
// No predicates remain, so stop tracking entirely. Both calls are no-ops
|
|
@@ -639,7 +655,9 @@ function createPowerSyncCollectionConfig<
|
|
|
639
655
|
}
|
|
640
656
|
|
|
641
657
|
const unloadSubset = async (options: LoadSubsetOptions) => {
|
|
642
|
-
|
|
658
|
+
releasedSubsets.add(options)
|
|
659
|
+
unloadSubsetCallbacks.get(options)?.()
|
|
660
|
+
unloadSubsetCallbacks.delete(options)
|
|
643
661
|
|
|
644
662
|
const idx = activeWhereExpressions.indexOf(options.where)
|
|
645
663
|
if (idx !== -1) {
|
|
@@ -688,10 +706,14 @@ function createPowerSyncCollectionConfig<
|
|
|
688
706
|
|
|
689
707
|
return {
|
|
690
708
|
cleanup: () => {
|
|
709
|
+
stopped = true
|
|
691
710
|
database.logger.info(
|
|
692
711
|
`Sync has been stopped for ${viewName} into ${trackedTableName}`,
|
|
693
712
|
)
|
|
694
713
|
abortController.abort()
|
|
714
|
+
for (const cleanup of unloadSubsetCallbacks.values()) cleanup()
|
|
715
|
+
unloadSubsetCallbacks.clear()
|
|
716
|
+
activeWhereExpressions.length = 0
|
|
695
717
|
},
|
|
696
718
|
loadSubset: (options: LoadSubsetOptions) => loadSubset(options),
|
|
697
719
|
unloadSubset: (options: LoadSubsetOptions) => unloadSubset(options),
|