@tanstack/powersync-db-collection 0.1.67 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/PowerSyncTransactor.cjs +31 -18
- package/dist/cjs/PowerSyncTransactor.cjs.map +1 -1
- package/dist/cjs/PowerSyncTransactor.d.cts +4 -4
- package/dist/cjs/definitions.cjs.map +1 -1
- package/dist/cjs/definitions.d.cts +4 -4
- package/dist/cjs/helpers.cjs.map +1 -1
- package/dist/cjs/helpers.d.cts +6 -9
- package/dist/cjs/index.d.cts +1 -0
- package/dist/cjs/powersync.cjs +254 -82
- package/dist/cjs/powersync.cjs.map +1 -1
- package/dist/cjs/serialization.cjs.map +1 -1
- package/dist/cjs/serialization.d.cts +2 -6
- package/dist/esm/PowerSyncTransactor.d.ts +4 -4
- package/dist/esm/PowerSyncTransactor.js +32 -19
- package/dist/esm/PowerSyncTransactor.js.map +1 -1
- package/dist/esm/definitions.d.ts +4 -4
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/helpers.d.ts +6 -9
- package/dist/esm/helpers.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/powersync.js +255 -83
- package/dist/esm/powersync.js.map +1 -1
- package/dist/esm/serialization.d.ts +2 -6
- package/dist/esm/serialization.js.map +1 -1
- package/package.json +7 -6
- package/src/PowerSyncTransactor.ts +46 -25
- package/src/definitions.ts +11 -5
- package/src/helpers.ts +10 -18
- package/src/index.ts +1 -0
- package/src/powersync.ts +316 -101
- package/src/serialization.ts +8 -17
|
@@ -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 SyncAppliedReceipt,\n SyncConfig,\n} from '@tanstack/db'\nimport type {\n AnyTableColumnType,\n ExtractedTable,\n ExtractedTableColumns,\n MapBaseColumnType,\n OptionalExtractedTable,\n} from './helpers'\nimport type {\n BasePowerSyncCollectionConfig,\n ConfigWithArbitraryCollectionTypes,\n ConfigWithSQLiteInputType,\n ConfigWithSQLiteTypes,\n CustomSQLiteSerializer,\n EnhancedPowerSyncCollectionConfig,\n InferPowerSyncOutputType,\n PowerSyncCollectionConfig,\n PowerSyncCollectionUtils,\n} from './definitions'\nimport type { PendingOperation } from './PendingOperationStore'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { LockContext, Table, TriggerDiffRecord } from '@powersync/common'\n\n/**\n * Creates PowerSync collection options for use with a standard Collection.\n *\n * @template TTable - The SQLite-based typing\n * @template TSchema - The validation schema type (optionally supports a custom input type)\n * @param config - Configuration options for the PowerSync collection\n * @returns Collection options with utilities\n */\n\n// Overload 1: No schema is provided\n\n/**\n * Creates a PowerSync collection configuration with basic default validation.\n * Input and Output types are the SQLite column types.\n *\n * @example\n * ```typescript\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * }),\n * })\n *\n * type Document = (typeof APP_SCHEMA)[\"types\"][\"documents\"]\n *\n * const db = new PowerSyncDatabase({\n * database: {\n * dbFilename: \"test.sqlite\",\n * },\n * schema: APP_SCHEMA,\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents\n * })\n * )\n * ```\n */\nexport function powerSyncCollectionOptions<TTable extends Table = Table>(\n config: BasePowerSyncCollectionConfig<TTable, never> & ConfigWithSQLiteTypes,\n): EnhancedPowerSyncCollectionConfig<\n TTable,\n OptionalExtractedTable<TTable>,\n never\n>\n\n// Overload 2: Schema is provided and the TInput matches SQLite types.\n\n/**\n * Creates a PowerSync collection configuration with schema validation.\n *\n * The input types satisfy the SQLite column types.\n *\n * The output types are defined by the provided schema. This schema can enforce additional\n * validation or type transforms.\n * Arbitrary output typed mutations are encoded to SQLite for persistence. We provide a basic standard\n * serialization implementation to serialize column values. Custom or advanced types require providing additional\n * serializer specifications. Partial column overrides can be supplied to `serializer`.\n *\n * @example\n * ```typescript\n * import { z } from \"zod\"\n *\n * // The PowerSync SQLite schema\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * // Dates are stored as ISO date strings in SQLite\n * created_at: column.text\n * }),\n * })\n *\n * // Advanced Zod validations. The output type of this schema\n * // is constrained to the SQLite schema of APP_SCHEMA\n * const schema = z.object({\n * id: z.string(),\n * // Notice that `name` is not nullable (is required) here and it has additional validation\n * name: z.string().min(3, { message: \"Should be at least 3 characters\" }).nullable(),\n * // The input type is still the SQLite string type. While collections will output smart Date instances.\n * created_at: z.string().transform(val => new Date(val))\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents,\n * schema,\n * serializer: {\n * // The default is toISOString, this is just to demonstrate custom overrides\n * created_at: (outputValue) => outputValue.toISOString(),\n * },\n * })\n * )\n * ```\n */\nexport function powerSyncCollectionOptions<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // TInput is the SQLite types. We can use the supplied schema to validate sync input\n OptionalExtractedTable<TTable>,\n AnyTableColumnType<TTable>\n >,\n>(\n config: BasePowerSyncCollectionConfig<TTable, TSchema> &\n ConfigWithSQLiteInputType<TTable, TSchema>,\n): EnhancedPowerSyncCollectionConfig<\n TTable,\n InferPowerSyncOutputType<TTable, TSchema>,\n TSchema\n> & {\n schema: TSchema\n}\n\n// Overload 3: Schema is provided with arbitrary TInput and TOutput\n/**\n * Creates a PowerSync collection configuration with schema validation.\n *\n * The input types are not linked to the internal SQLite table types. This can\n * give greater flexibility, e.g. by accepting rich types as input for `insert` or `update` operations.\n * An additional `deserializationSchema` is required in order to process incoming SQLite updates to the output type.\n *\n * The output types are defined by the provided schema. This schema can enforce additional\n * validation or type transforms.\n * Arbitrary output typed mutations are encoded to SQLite for persistence. We provide a basic standard\n * serialization implementation to serialize column values. Custom or advanced types require providing additional\n * serializer specifications. Partial column overrides can be supplied to `serializer`.\n *\n * @example\n * ```typescript\n * import { z } from \"zod\"\n *\n * // The PowerSync SQLite schema\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * // Booleans are represented as integers in SQLite\n * is_active: column.integer\n * }),\n * })\n *\n * // Advanced Zod validations.\n * // We accept boolean values as input for operations and expose Booleans in query results\n * const schema = z.object({\n * id: z.string(),\n * isActive: z.boolean(), // TInput and TOutput are boolean\n * })\n *\n * // The deserializationSchema converts the SQLite synced INTEGER (0/1) values to booleans.\n * const deserializationSchema = z.object({\n * id: z.string(),\n * isActive: z.number().nullable().transform((val) => val == null ? true : val > 0),\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents,\n * schema,\n * deserializationSchema,\n * })\n * )\n * ```\n */\nexport function powerSyncCollectionOptions<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // The input and output must have the same keys, the value types can be arbitrary\n AnyTableColumnType<TTable>,\n AnyTableColumnType<TTable>\n >,\n>(\n config: BasePowerSyncCollectionConfig<TTable, TSchema> &\n ConfigWithArbitraryCollectionTypes<TTable, TSchema>,\n): EnhancedPowerSyncCollectionConfig<\n TTable,\n InferPowerSyncOutputType<TTable, TSchema>,\n TSchema\n> & {\n utils: PowerSyncCollectionUtils<TTable>\n schema: TSchema\n}\n\n/**\n * Implementation of powerSyncCollectionOptions that handles both schema and non-schema configurations.\n */\n\nexport function powerSyncCollectionOptions<\n TTable extends Table,\n TSchema extends StandardSchemaV1<any> = never,\n>(\n config: PowerSyncCollectionConfig<TTable, TSchema>,\n): ReturnType<typeof createPowerSyncCollectionConfig<TTable, TSchema>> {\n const outputConfig = createPowerSyncCollectionConfig(config)\n return withCollectionConfigFactory(outputConfig, () =>\n createPowerSyncCollectionConfig(config),\n )\n}\n\nfunction createPowerSyncCollectionConfig<\n TTable extends Table,\n TSchema extends StandardSchemaV1<any> = never,\n>(config: PowerSyncCollectionConfig<TTable, TSchema>) {\n const {\n database,\n table,\n schema: inputSchema,\n syncBatchSize = DEFAULT_BATCH_SIZE,\n syncMode = 'eager',\n ...restConfig\n } = config\n\n const deserializationSchema =\n `deserializationSchema` in config ? config.deserializationSchema : null\n const serializer = `serializer` in config ? config.serializer : undefined\n const onDeserializationError =\n `onDeserializationError` in config\n ? config.onDeserializationError\n : undefined\n\n // The SQLite table type\n type TableType = ExtractedTable<TTable>\n\n // The collection output type\n type OutputType = InferPowerSyncOutputType<TTable, TSchema>\n\n const { viewName, trackMetadata: metadataIsTracked } = table\n\n /**\n * Deserializes data from the incoming sync stream\n */\n const deserializeSyncRow = (value: TableType): OutputType => {\n const validationSchema = deserializationSchema || schema\n const validation = validationSchema[`~standard`].validate(value)\n if (`value` in validation) {\n return validation.value\n } else if (`issues` in validation) {\n const issueMessage = `Failed to validate incoming data for ${viewName}. Issues: ${validation.issues.map((issue) => `${issue.path} - ${issue.message}`)}`\n database.logger.error(issueMessage)\n onDeserializationError!(validation)\n throw new Error(issueMessage)\n } else {\n const unknownErrorMessage = `Unknown deserialization error for ${viewName}`\n database.logger.error(unknownErrorMessage)\n onDeserializationError!({ issues: [{ message: unknownErrorMessage }] })\n throw new Error(unknownErrorMessage)\n }\n }\n\n // We can do basic runtime validations for columns if not explicit schema has been provided\n const schema = inputSchema ?? (convertTableToSchema(table) as TSchema)\n /**\n * The onInsert, onUpdate, and onDelete handlers should only return\n * after we have written the changes to TanStack DB.\n * We currently only write to TanStack DB from a diff trigger.\n * We wait for the diff trigger to observe the change,\n * and only then return from the on[X] handlers.\n * This ensures that when the transaction is reported as\n * complete to the caller, the in-memory state is already\n * consistent with the database.\n */\n const pendingOperationStore = PendingOperationStore.GLOBAL\n // Keep the tracked table unique in case of multiple tabs.\n const trackedTableName = `__${viewName}_tracking_${Math.floor(\n Math.random() * 0xffffffff,\n )\n .toString(16)\n .padStart(8, `0`)}`\n\n const transactor = new PowerSyncTransactor({\n database,\n })\n\n /**\n * \"sync\"\n * Notice that this describes the Sync between the local SQLite table\n * and the in-memory tanstack-db collection.\n */\n const sync: SyncConfig<OutputType, string> = {\n sync: (params) => {\n const { begin, write, collection, commit, markReady, markError } = params\n const abortController = new AbortController()\n\n let disposeTracking:\n | ((options?: { context?: LockContext }) => Promise<void>)\n | null = null\n let trackingSetup: Promise<void> | 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 // Cleanup can race trigger creation. Wait until the disposer has been\n // published so an abort cannot strand a freshly-created trigger.\n const setup = trackingSetup\n if (setup) {\n await setup.catch(() => undefined)\n }\n\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 establishTracking(\n options: Parameters<typeof createDiffTrigger>[0],\n appliedReceipts: Array<SyncAppliedReceipt>,\n ): Promise<void> {\n const setup = (async () => {\n const dispose = await createDiffTrigger(options, appliedReceipts)\n disposeTracking = dispose\n })()\n trackingSetup = setup\n\n try {\n await setup\n } finally {\n if (trackingSetup === setup) {\n trackingSetup = null\n }\n }\n }\n\n async function createDiffTrigger(\n options: {\n setupContext?: LockContext\n immediate?: boolean\n when: Record<DiffTriggerOperation, string>\n writeType: (rowId: string) => OperationType\n batchQuery: (\n lockContext: LockContext,\n batchSize: number,\n cursor: number,\n ) => Promise<Array<TableType>>\n },\n appliedReceipts: Array<SyncAppliedReceipt>,\n ) {\n const { setupContext, immediate, when, writeType, batchQuery } = options\n\n return await database.triggers.createDiffTrigger({\n source: viewName,\n destination: trackedTableName,\n setupContext,\n when,\n hooks: {\n beforeCreate: async (context) => {\n let currentBatchCount = syncBatchSize\n let cursor = 0\n while (currentBatchCount == syncBatchSize) {\n begin(immediate ? { immediate: true } : undefined)\n\n const batchItems = await batchQuery(\n context,\n syncBatchSize,\n cursor,\n )\n currentBatchCount = batchItems.length\n cursor += currentBatchCount\n for (const row of batchItems) {\n write({\n type: writeType(row.id),\n value: deserializeSyncRow(row),\n })\n }\n appliedReceipts.push(commit())\n }\n database.logger.info(\n `Sync is ready for ${viewName} into ${trackedTableName}`,\n )\n },\n },\n })\n }\n\n async function flushDiffRecords(): Promise<void> {\n // PowerSync can notify after creating the tracking table but before its\n // create call returns. Preserve that notification until the disposer,\n // which proves the trigger is usable, has been published.\n const setup = trackingSetup\n if (setup) {\n await setup.catch(() => undefined)\n }\n if (!disposeTracking) {\n return\n }\n\n const ignoredReceipts: Array<SyncAppliedReceipt> = []\n await database\n .writeTransaction(async (context) => {\n await flushDiffRecordsWithContext(context, ignoredReceipts)\n })\n .catch((error) => {\n database.logger.error(\n `An error has been detected in the sync handler`,\n error,\n )\n })\n }\n\n // We can use this directly if we want to pair a flush with dispose+recreate diff trigger.\n async function flushDiffRecordsWithContext(\n context: LockContext,\n appliedReceipts: Array<SyncAppliedReceipt>,\n ): Promise<void> {\n // There is nothing to flush if no tracking table is currently active.\n if (!disposeTracking) {\n return\n }\n\n try {\n begin()\n const operations = await context.getAll<TriggerDiffRecord>(\n `SELECT * FROM ${trackedTableName} ORDER BY operation_id ASC`,\n )\n const pendingOperations: Array<PendingOperation> = []\n\n for (const op of operations) {\n const { id, operation, timestamp, value } = op\n const parsedValue = deserializeSyncRow({\n id,\n ...JSON.parse(value),\n })\n const parsedPreviousValue =\n op.operation == DiffTriggerOperation.UPDATE\n ? deserializeSyncRow({\n id,\n ...JSON.parse(op.previous_value),\n })\n : undefined\n write({\n type: mapOperation(operation),\n value: parsedValue,\n previousValue: parsedPreviousValue,\n })\n pendingOperations.push({\n id,\n operation,\n timestamp,\n tableName: viewName,\n })\n }\n\n // clear the current operations\n await context.execute(`DELETE FROM ${trackedTableName}`)\n\n const applied = commit()\n appliedReceipts.push(applied)\n // Mutation persistence is what releases the Collection's FIFO gate.\n // Confirm these local operations after the sync transaction is\n // staged; waiting for its applied receipt would deadlock the user\n // transaction that currently parks it.\n pendingOperationStore.resolvePendingFor(pendingOperations)\n } catch (error) {\n database.logger.error(\n `An error has been detected in the sync handler`,\n error,\n )\n }\n }\n\n // The sync function needs to be synchronous.\n async function start(afterOnChangeRegistered?: () => Promise<void>) {\n database.logger.info(\n `Sync is starting for ${viewName} into ${trackedTableName}`,\n )\n database.onChangeWithCallback(\n {\n onChange: async () => {\n await flushDiffRecords()\n },\n },\n {\n signal: abortController.signal,\n triggerImmediate: false,\n tables: [trackedTableName],\n },\n )\n\n await afterOnChangeRegistered?.()\n\n // If the abort controller was aborted while processing the request above\n if (abortController.signal.aborted) {\n await safelyDisposeTracking()\n } else {\n abortController.signal.addEventListener(\n `abort`,\n async () => {\n await safelyDisposeTracking()\n },\n { once: true },\n )\n }\n }\n\n // Eager mode.\n // Registers a diff trigger for the entire table.\n function runEagerSync() {\n let onUnload: CleanupFn | void | null = null\n\n start(async () => {\n const cleanup = await restConfig.onLoad?.()\n if (abortController.signal.aborted) {\n cleanup?.()\n return\n }\n onUnload = cleanup\n\n const appliedReceipts: Array<SyncAppliedReceipt> = []\n await establishTracking(\n {\n // Initial eager hydration must make the source usable before\n // PowerSync can persist a mutation queued during startup.\n immediate: true,\n when: {\n [DiffTriggerOperation.INSERT]: `TRUE`,\n [DiffTriggerOperation.UPDATE]: `TRUE`,\n [DiffTriggerOperation.DELETE]: `TRUE`,\n },\n writeType: (_rowId: string) => `insert`,\n batchQuery: (\n lockContext: LockContext,\n batchSize: number,\n cursor: number,\n ) =>\n lockContext.getAll<TableType>(\n sanitizeSQL`SELECT * FROM ${viewName} LIMIT ? OFFSET ?`,\n [batchSize, cursor],\n ),\n },\n appliedReceipts,\n )\n await Promise.all(appliedReceipts)\n markReady()\n }).catch((error) => {\n database.logger.error(\n `Could not start syncing process for ${viewName} into ${trackedTableName}`,\n error,\n )\n if (collection.status === `loading`) {\n markError(error)\n }\n })\n\n return () => {\n database.logger.info(\n `Sync has been stopped for ${viewName} into ${trackedTableName}`,\n )\n abortController.abort()\n onUnload?.()\n }\n }\n\n // On-demand mode.\n // Registers a diff trigger for the active WHERE expressions.\n function runOnDemandSync() {\n type DemandRecord = {\n options: LoadSubsetOptions\n active: boolean\n cleanup?: CleanupFn\n }\n type PendingRelease = {\n options: LoadSubsetOptions\n failures: number\n }\n\n const demands = new Map<LoadSubsetOptions, DemandRecord>()\n const releasedSubsets = new WeakSet<LoadSubsetOptions>()\n const pendingReleases: Array<PendingRelease> = []\n let stopped = false\n let trackingRevision = 0\n let reconciledTrackingRevision = 0\n let rebuildPromise: Promise<void> | null = null\n let drainingReleases = false\n let releaseRetryTimer: ReturnType<typeof setTimeout> | undefined\n const startup = start()\n void startup.catch((error) =>\n database.logger.error(\n `Could not start syncing process for ${viewName} into ${trackedTableName}`,\n error,\n ),\n )\n\n const activeWhereExpressions = () =>\n Array.from(demands.values())\n .filter((demand) => demand.active)\n .map((demand) => demand.options.where)\n\n // One reconciliation owns every queued revision so callers cannot\n // settle against a stale trigger configuration.\n const reconcileTracking = async (): Promise<void> => {\n while (!stopped && reconciledTrackingRevision !== trackingRevision) {\n const revision = trackingRevision\n const isCurrent = () => !stopped && trackingRevision === revision\n const appliedReceipts: Array<SyncAppliedReceipt> = []\n\n await database.writeLock(async (ctx) => {\n if (!isCurrent()) return\n await flushDiffRecordsWithContext(ctx, appliedReceipts)\n if (!isCurrent()) return\n await safelyDisposeTracking(ctx)\n if (!isCurrent()) return\n\n const active = activeWhereExpressions()\n if (active.length === 0) return\n const combinedWhere =\n active.length === 1\n ? active[0]\n : or(active[0], active[1], ...active.slice(2))\n const compiledNewData = compileSQLite(\n { where: combinedWhere },\n { jsonColumn: 'NEW.data' },\n )\n const compiledOldData = compileSQLite(\n { where: combinedWhere },\n { jsonColumn: 'OLD.data' },\n )\n const compiledView = compileSQLite({ where: combinedWhere })\n const newDataWhenClause = toInlinedWhereClause(compiledNewData)\n const oldDataWhenClause = toInlinedWhereClause(compiledOldData)\n const viewWhereClause = toInlinedWhereClause(compiledView)\n\n await establishTracking(\n {\n setupContext: ctx,\n when: {\n [DiffTriggerOperation.INSERT]: newDataWhenClause,\n [DiffTriggerOperation.UPDATE]: `(${newDataWhenClause}) OR (${oldDataWhenClause})`,\n [DiffTriggerOperation.DELETE]: oldDataWhenClause,\n },\n writeType: (rowId: string) =>\n collection.has(rowId) ? `update` : `insert`,\n batchQuery: (\n lockContext: LockContext,\n batchSize: number,\n cursor: number,\n ) =>\n lockContext.getAll<TableType>(\n `SELECT * FROM ${viewName} WHERE ${viewWhereClause} LIMIT ? OFFSET ?`,\n [batchSize, cursor],\n ),\n },\n appliedReceipts,\n )\n if (!isCurrent()) await safelyDisposeTracking(ctx)\n })\n await Promise.all(appliedReceipts)\n if (isCurrent()) {\n reconciledTrackingRevision = revision\n }\n }\n }\n\n const rebuildTracking = async (): Promise<void> => {\n // New demand can join after reconciliation exits but before its\n // shared promise clears. Each waiter must check its revision again.\n while (!stopped && reconciledTrackingRevision !== trackingRevision) {\n await (rebuildPromise ??= reconcileTracking()\n .catch((error) => {\n // A rebuild may already have removed every active diff trigger.\n // Do not leave healthy consumers ready against a stale source.\n if (!stopped) markError(error)\n throw error\n })\n .finally(() => {\n rebuildPromise = null\n }))\n }\n }\n\n const loadSubset = async (\n options: LoadSubsetOptions,\n ): Promise<void> => {\n if (stopped) return\n // Never create a trigger that has no observer to drain its diff table.\n await startup\n if (\n // Cleanup can run while startup is pending.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n stopped ||\n releasedSubsets.has(options) ||\n options.signal?.aborted\n ) {\n return\n }\n\n const demand: DemandRecord = { options, active: false }\n demands.set(options, demand)\n try {\n const cleanup = await restConfig.onLoadSubset?.(options)\n if (cleanup) demand.cleanup = cleanup\n } catch (error) {\n demands.delete(options)\n throw error\n }\n\n if (\n // The user hook can reenter cleanup.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n stopped ||\n releasedSubsets.has(options) ||\n options.signal?.aborted ||\n demands.get(options) !== demand\n ) {\n demands.delete(options)\n demand.cleanup?.()\n return\n }\n\n demand.active = true\n trackingRevision++\n await rebuildTracking()\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 cleanupDemand = (demand: DemandRecord): void => {\n demands.delete(demand.options)\n try {\n demand.cleanup?.()\n } catch (error) {\n database.logger.error(\n `Could not clean up subset hook for ${viewName}`,\n error,\n )\n }\n }\n\n const performPhysicalRelease = async (\n options: LoadSubsetOptions,\n ): Promise<void> => {\n const compiledDeparting = compileSQLite({ where: options.where })\n const departingWhereSQL = toInlinedWhereClause(compiledDeparting)\n let rowsToEvict: Array<{ id: string }>\n for (;;) {\n if (stopped) return\n const revision = trackingRevision\n const active = activeWhereExpressions()\n let evictionSQL: string\n if (active.length === 0) {\n evictionSQL = `SELECT id FROM ${viewName} WHERE ${departingWhereSQL}`\n } else {\n const combinedRemaining =\n active.length === 1\n ? active[0]!\n : or(active[0], active[1], ...active.slice(2))\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 rowsToEvict = await database.getAll<{ id: string }>(evictionSQL)\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- cleanup can run during the query\n if (stopped) return\n if (trackingRevision === revision) break\n }\n if (rowsToEvict.length > 0) {\n begin()\n for (const { id } of rowsToEvict) {\n write({ type: `delete`, key: id })\n }\n void commit()\n }\n await rebuildTracking()\n }\n\n function scheduleReleaseDrain(delay = 0): void {\n if (stopped || drainingReleases || releaseRetryTimer) return\n if (delay > 0) {\n releaseRetryTimer = setTimeout(() => {\n releaseRetryTimer = undefined\n void drainReleases()\n }, delay)\n return\n }\n void drainReleases()\n }\n\n async function drainReleases(): Promise<void> {\n if (stopped || drainingReleases) return\n drainingReleases = true\n let retryDelay = 0\n try {\n const attempts = pendingReleases.length\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- each release can reenter cleanup\n for (let index = 0; !stopped && index < attempts; index++) {\n const pending = pendingReleases.shift()!\n try {\n await performPhysicalRelease(pending.options)\n } catch (error) {\n pending.failures++\n pendingReleases.push(pending)\n const delay = Math.min(\n 1000 * 2 ** (pending.failures - 1),\n 30000,\n )\n retryDelay =\n retryDelay === 0 ? delay : Math.min(retryDelay, delay)\n database.logger.error(\n `Could not release subset tracking for ${viewName}; retrying`,\n error,\n )\n }\n }\n } finally {\n drainingReleases = false\n }\n if (pendingReleases.length > 0) scheduleReleaseDrain(retryDelay)\n }\n\n const unloadSubset = (options: LoadSubsetOptions): void => {\n releasedSubsets.add(options)\n const demand = demands.get(options)\n if (!demand) return\n\n const wasActive = demand.active\n if (wasActive) trackingRevision++\n cleanupDemand(demand)\n\n if (wasActive) {\n pendingReleases.push({ options, failures: 0 })\n // New work must not wait for another release's backoff.\n clearTimeout(releaseRetryTimer)\n releaseRetryTimer = undefined\n scheduleReleaseDrain()\n }\n }\n\n markReady()\n\n return {\n cleanup: () => {\n stopped = true\n clearTimeout(releaseRetryTimer)\n releaseRetryTimer = undefined\n database.logger.info(\n `Sync has been stopped for ${viewName} into ${trackedTableName}`,\n )\n abortController.abort()\n for (const demand of demands.values()) {\n cleanupDemand(demand)\n }\n pendingReleases.length = 0\n },\n loadSubset: (options: LoadSubsetOptions) => loadSubset(options),\n unloadSubset,\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, LogLevels, sanitizeSQL } from '@powersync/common'\nimport { or, withCollectionConfigFactory } from '@tanstack/db'\nimport { compileSQLite } from './sqlite-compiler'\nimport { PendingOperationStore } from './PendingOperationStore'\nimport { PowerSyncTransactor } from './PowerSyncTransactor'\nimport { DEFAULT_BATCH_SIZE } from './definitions'\nimport { asPowerSyncRecord, mapOperation } from './helpers'\nimport { convertTableToSchema } from './schema'\nimport { serializeForSQLite } from './serialization'\nimport type {\n CleanupFn,\n LoadSubsetOptions,\n OperationType,\n SyncAppliedReceipt,\n SyncConfig,\n} from '@tanstack/db'\nimport type {\n AnyTableColumnType,\n ExtractedTable,\n OptionalExtractedTable,\n} from './helpers'\nimport type {\n BasePowerSyncCollectionConfig,\n ConfigWithArbitraryCollectionTypes,\n ConfigWithSQLiteInputType,\n ConfigWithSQLiteTypes,\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\nfunction isPromiseLike(value: unknown): value is PromiseLike<unknown> {\n return (\n !!value &&\n (typeof value === `object` || typeof value === `function`) &&\n typeof (value as { then?: unknown }).then === `function`\n )\n}\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.log({ level: LogLevels.error, message: issueMessage })\n onDeserializationError!(validation)\n throw new Error(issueMessage)\n } else {\n const unknownErrorMessage = `Unknown deserialization error for ${viewName}`\n database.logger.log({\n level: LogLevels.error,\n message: unknownErrorMessage,\n })\n onDeserializationError!({ issues: [{ message: unknownErrorMessage }] })\n throw new Error(unknownErrorMessage)\n }\n }\n\n // We can do basic runtime validations for columns if not explicit schema has been provided\n const schema = inputSchema ?? (convertTableToSchema(table) as TSchema)\n /**\n * The onInsert, onUpdate, and onDelete handlers should only return\n * after we have written the changes to TanStack DB.\n * We currently only write to TanStack DB from a diff trigger.\n * We wait for the diff trigger to observe the change,\n * and only then return from the on[X] handlers.\n * This ensures that when the transaction is reported as\n * complete to the caller, the in-memory state is already\n * consistent with the database.\n */\n const pendingOperationStore = PendingOperationStore.GLOBAL\n // Keep the tracked table unique in case of multiple tabs.\n const trackedTableName = `__${viewName}_tracking_${Math.floor(\n Math.random() * 0xffffffff,\n )\n .toString(16)\n .padStart(8, `0`)}`\n\n const transactor = new PowerSyncTransactor({\n database,\n })\n\n /**\n * \"sync\"\n * Notice that this describes the Sync between the local SQLite table\n * and the in-memory tanstack-db collection.\n */\n const sync: SyncConfig<OutputType, string> = {\n sync: (params) => {\n const { begin, write, collection, commit, markReady, markError } = params\n const abortController = new AbortController()\n\n let disposeTracking:\n | ((options?: { context?: LockContext }) => Promise<void>)\n | null = null\n let trackingSetup: Promise<void> | null = null\n let activeTrackingDisposal: Promise<void> | null = null\n let abortTrackingDisposal: Promise<void> | 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 // Cleanup can race trigger creation. Wait until the disposer has been\n // published so an abort cannot strand a freshly-created trigger.\n const setup = trackingSetup\n if (setup) {\n await setup.catch(() => undefined)\n }\n\n const dispose = disposeTracking\n if (!dispose) {\n // Reconciliation may have claimed the disposer after this caller\n // observed setup. Join that invocation instead of settling early.\n await activeTrackingDisposal\n return\n }\n\n disposeTracking = null\n let disposal: Promise<void>\n try {\n disposal = Promise.resolve(dispose(context ? { context } : undefined))\n } catch (error) {\n disposal = Promise.reject(error)\n }\n activeTrackingDisposal = disposal\n try {\n await disposal\n } finally {\n if (activeTrackingDisposal === disposal) {\n activeTrackingDisposal = null\n }\n }\n }\n\n function disposeTrackingAfterAbort(): Promise<void> {\n const disposal = (abortTrackingDisposal ??= safelyDisposeTracking())\n // Abort event dispatch does not observe a listener's returned promise.\n // The eager cleanup callback awaits this same promise below.\n void disposal.catch(() => undefined)\n return disposal\n }\n\n async function establishTracking(\n options: Parameters<typeof createDiffTrigger>[0],\n appliedReceipts: Array<SyncAppliedReceipt>,\n ): Promise<void> {\n const setup = (async () => {\n const dispose = await createDiffTrigger(options, appliedReceipts)\n disposeTracking = dispose\n })()\n trackingSetup = setup\n\n try {\n await setup\n } finally {\n if (trackingSetup === setup) {\n trackingSetup = null\n }\n }\n }\n\n async function createDiffTrigger(\n options: {\n setupContext?: LockContext\n immediate?: boolean\n when: Record<DiffTriggerOperation, string>\n writeType: (rowId: string) => OperationType\n batchQuery: (\n lockContext: LockContext,\n batchSize: number,\n cursor: number,\n ) => Promise<Array<TableType>>\n },\n appliedReceipts: Array<SyncAppliedReceipt>,\n ) {\n const { setupContext, immediate, when, writeType, batchQuery } = options\n\n return await database.triggers.createDiffTrigger({\n source: viewName,\n destination: trackedTableName,\n columns: table.columns.map((column) => column.name),\n setupContext,\n when,\n hooks: {\n beforeCreate: async (context) => {\n let currentBatchCount = syncBatchSize\n let cursor = 0\n while (currentBatchCount == syncBatchSize) {\n begin(immediate ? { immediate: true } : undefined)\n\n const batchItems = await batchQuery(\n context,\n syncBatchSize,\n cursor,\n )\n currentBatchCount = batchItems.length\n cursor += currentBatchCount\n for (const row of batchItems) {\n write({\n type: writeType(row.id),\n value: deserializeSyncRow(row),\n })\n }\n appliedReceipts.push(commit())\n }\n database.logger.log({\n level: LogLevels.info,\n message: `Sync is ready for ${viewName} into ${trackedTableName}`,\n })\n },\n },\n })\n }\n\n async function flushDiffRecords(): Promise<void> {\n // PowerSync can notify after creating the tracking table but before its\n // create call returns. Preserve that notification until the disposer,\n // which proves the trigger is usable, has been published.\n const setup = trackingSetup\n if (setup) {\n await setup.catch(() => undefined)\n }\n if (!disposeTracking) {\n return\n }\n\n const ignoredReceipts: Array<SyncAppliedReceipt> = []\n await database\n .writeTransaction(async (context) => {\n await flushDiffRecordsWithContext(context, ignoredReceipts)\n })\n .catch((error) => {\n database.logger.log({\n level: LogLevels.error,\n message: `An error has been detected in the sync handler`,\n error,\n })\n })\n }\n\n // We can use this directly if we want to pair a flush with dispose+recreate diff trigger.\n async function flushDiffRecordsWithContext(\n context: LockContext,\n appliedReceipts: Array<SyncAppliedReceipt>,\n ): Promise<void> {\n // There is nothing to flush if no tracking table is currently active.\n if (!disposeTracking) {\n return\n }\n\n try {\n begin()\n const operations = await context.getAll<TriggerDiffRecord>(\n `SELECT * FROM ${trackedTableName} ORDER BY operation_id ASC`,\n )\n const pendingOperations: Array<PendingOperation> = []\n\n for (const op of operations) {\n const { id, operation, timestamp, value } = op\n const parsedValue = deserializeSyncRow({\n id,\n ...JSON.parse(value),\n })\n const parsedPreviousValue =\n op.operation == DiffTriggerOperation.UPDATE\n ? deserializeSyncRow({\n id,\n ...JSON.parse(op.previous_value),\n })\n : undefined\n write({\n type: mapOperation(operation),\n value: parsedValue,\n previousValue: parsedPreviousValue,\n })\n pendingOperations.push({\n id,\n operation,\n timestamp,\n tableName: viewName,\n })\n }\n\n // clear the current operations\n await context.execute(`DELETE FROM ${trackedTableName}`)\n\n const applied = commit()\n appliedReceipts.push(applied)\n // Mutation persistence is what releases the Collection's FIFO gate.\n // Confirm these local operations after the sync transaction is\n // staged; waiting for its applied receipt would deadlock the user\n // transaction that currently parks it.\n pendingOperationStore.resolvePendingFor(pendingOperations)\n } catch (error) {\n database.logger.log({\n level: LogLevels.error,\n message: `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.log({\n level: LogLevels.info,\n message: `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 disposeTrackingAfterAbort()\n } else {\n abortController.signal.addEventListener(\n `abort`,\n () => {\n void disposeTrackingAfterAbort()\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 let onUnloadStarted = false\n\n const startup = start(async () => {\n const cleanup = await restConfig.onLoad?.()\n onUnload = cleanup\n if (abortController.signal.aborted) {\n return\n }\n\n const appliedReceipts: Array<SyncAppliedReceipt> = []\n await establishTracking(\n {\n // Initial eager hydration must make the source usable before\n // PowerSync can persist a mutation queued during startup.\n immediate: true,\n when: {\n [DiffTriggerOperation.INSERT]: `TRUE`,\n [DiffTriggerOperation.UPDATE]: `TRUE`,\n [DiffTriggerOperation.DELETE]: `TRUE`,\n },\n writeType: (_rowId: string) => `insert`,\n batchQuery: (\n lockContext: LockContext,\n batchSize: number,\n cursor: number,\n ) =>\n lockContext.getAll<TableType>(\n sanitizeSQL`SELECT * FROM ${viewName} LIMIT ? OFFSET ?`,\n [batchSize, cursor],\n ),\n },\n appliedReceipts,\n )\n await Promise.all(appliedReceipts)\n markReady()\n }).catch((error) => {\n database.logger.log({\n level: LogLevels.error,\n message: `Could not start syncing process for ${viewName} into ${trackedTableName}`,\n error,\n })\n if (collection.status === `loading`) {\n markError(error)\n }\n })\n\n const invokeOnUnload = (): void | Promise<void> => {\n if (onUnloadStarted || !onUnload) return\n onUnloadStarted = true\n const cleanup = onUnload\n onUnload = null\n return cleanup()\n }\n\n return async () => {\n database.logger.log({\n level: LogLevels.info,\n message: `Sync has been stopped for ${viewName} into ${trackedTableName}`,\n })\n abortController.abort()\n const trackingDisposal = disposeTrackingAfterAbort()\n\n let onUnloadFailure: { error: unknown } | undefined\n const settleOnUnload = async () => {\n try {\n await invokeOnUnload()\n } catch (error) {\n onUnloadFailure ??= { error }\n }\n }\n\n // Invoke an already-installed callback in this stack, but also wait\n // for startup to publish a callback acquired concurrently with abort.\n const settlements = await Promise.allSettled([\n startup,\n trackingDisposal,\n settleOnUnload(),\n ])\n const disposalSettlement = settlements[1]\n await settleOnUnload()\n if (disposalSettlement.status === `rejected` && onUnloadFailure) {\n throw new AggregateError(\n [disposalSettlement.reason, onUnloadFailure.error],\n `PowerSync tracking disposal and load-hook cleanup both failed`,\n { cause: disposalSettlement.reason },\n )\n }\n if (disposalSettlement.status === `rejected`) {\n throw disposalSettlement.reason\n }\n if (onUnloadFailure) throw onUnloadFailure.error\n }\n }\n\n // On-demand mode.\n // Registers a diff trigger for the active WHERE expressions.\n function runOnDemandSync() {\n type DemandRecord = {\n options: LoadSubsetOptions\n active: boolean\n cleanup?: CleanupFn\n }\n type PendingRelease = {\n options: LoadSubsetOptions\n failures: number\n }\n type DemandCleanupTask = {\n order: number\n promise: Promise<unknown>\n }\n type DemandCleanupFailure = {\n order: number\n error: unknown\n }\n type DemandCleanupCollector = {\n tasks: Array<DemandCleanupTask>\n ownedTasks: Set<Promise<unknown>>\n failures: Array<DemandCleanupFailure>\n nextOrder: number\n }\n\n const demands = new Map<LoadSubsetOptions, DemandRecord>()\n const releasedSubsets = new WeakSet<LoadSubsetOptions>()\n const pendingReleases: Array<PendingRelease> = []\n const pendingDemandLoads = new Set<Promise<void>>()\n const pendingDemandCleanups = new Set<Promise<unknown>>()\n let stopped = false\n let trackingRevision = 0\n let reconciledTrackingRevision = 0\n let rebuildPromise: Promise<void> | null = null\n let drainingReleases = false\n let releaseRetryTimer: ReturnType<typeof setTimeout> | undefined\n let demandCleanupCollector: DemandCleanupCollector | null = null\n const startup = start()\n void startup.catch((error) =>\n database.logger.log({\n level: LogLevels.error,\n message: `Could not start syncing process for ${viewName} into ${trackedTableName}`,\n error,\n }),\n )\n\n const activeWhereExpressions = () =>\n Array.from(demands.values())\n .filter((demand) => demand.active)\n .map((demand) => demand.options.where)\n\n // One reconciliation owns every queued revision so callers cannot\n // settle against a stale trigger configuration.\n const reconcileTracking = async (): Promise<void> => {\n while (!stopped && reconciledTrackingRevision !== trackingRevision) {\n const revision = trackingRevision\n const isCurrent = () => !stopped && trackingRevision === revision\n const appliedReceipts: Array<SyncAppliedReceipt> = []\n\n await database.writeLock(async (ctx) => {\n if (!isCurrent()) return\n await flushDiffRecordsWithContext(ctx, appliedReceipts)\n if (!isCurrent()) return\n await safelyDisposeTracking(ctx)\n if (!isCurrent()) return\n\n const active = activeWhereExpressions()\n // Tracking was absent during an error. Positive baseline rows\n // alone cannot reveal deletes or predicate exits from that gap.\n const missing =\n collection.status === `error`\n ? new Set(collection.keys())\n : undefined\n if (active.length > 0) {\n const combinedWhere =\n active.length === 1\n ? active[0]\n : or(active[0], active[1], ...active.slice(2))\n const compiledNewData = compileSQLite(\n { where: combinedWhere },\n { jsonColumn: 'NEW.data' },\n )\n const compiledOldData = compileSQLite(\n { where: combinedWhere },\n { jsonColumn: 'OLD.data' },\n )\n const compiledView = compileSQLite({ where: combinedWhere })\n const newDataWhenClause = toInlinedWhereClause(compiledNewData)\n const oldDataWhenClause = toInlinedWhereClause(compiledOldData)\n const viewWhereClause = toInlinedWhereClause(compiledView)\n await establishTracking(\n {\n setupContext: ctx,\n when: {\n [DiffTriggerOperation.INSERT]: newDataWhenClause,\n [DiffTriggerOperation.UPDATE]: `(${newDataWhenClause}) OR (${oldDataWhenClause})`,\n [DiffTriggerOperation.DELETE]: oldDataWhenClause,\n },\n writeType: (rowId: string) =>\n collection.has(rowId) ? `update` : `insert`,\n batchQuery: (\n lockContext: LockContext,\n batchSize: number,\n cursor: number,\n ) =>\n lockContext\n .getAll<TableType>(\n `SELECT * FROM ${viewName} WHERE ${viewWhereClause} LIMIT ? OFFSET ?`,\n [batchSize, cursor],\n )\n .then((rows) => {\n for (const row of rows) missing?.delete(row.id)\n return rows\n }),\n },\n appliedReceipts,\n )\n }\n if (!isCurrent()) await safelyDisposeTracking(ctx)\n else if (missing?.size) {\n begin()\n for (const key of missing) write({ type: `delete`, key })\n appliedReceipts.push(commit())\n }\n })\n await Promise.all(appliedReceipts)\n if (isCurrent()) {\n reconciledTrackingRevision = revision\n // Replacing the trigger alone is not recovery: its baseline\n // writes must also be applied before the source is ready again.\n if (collection.status === `error`) markReady()\n }\n }\n }\n\n const rebuildTracking = async (): Promise<void> => {\n // New demand can join after reconciliation exits but before its\n // shared promise clears. Each waiter must check its revision again.\n while (!stopped && reconciledTrackingRevision !== trackingRevision) {\n await (rebuildPromise ??= reconcileTracking()\n .catch((error) => {\n // A rebuild may already have removed every active diff trigger.\n // Do not leave healthy consumers ready against a stale source.\n if (!stopped) markError(error)\n throw error\n })\n .finally(() => {\n rebuildPromise = null\n }))\n }\n }\n\n const loadSubset = async (\n options: LoadSubsetOptions,\n ): Promise<void> => {\n if (stopped) return\n // Never create a trigger that has no observer to drain its diff table.\n await startup\n if (\n // Cleanup can run while startup is pending.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n stopped ||\n releasedSubsets.has(options) ||\n options.signal?.aborted\n ) {\n return\n }\n\n const demand: DemandRecord = { options, active: false }\n demands.set(options, demand)\n try {\n const cleanup = await restConfig.onLoadSubset?.(options)\n if (cleanup) demand.cleanup = cleanup\n } catch (error) {\n demands.delete(options)\n const collector = demandCleanupCollector\n if (collector) {\n collector.failures.push({\n order: collector.nextOrder++,\n error,\n })\n }\n throw error\n }\n\n if (\n // The user hook can reenter cleanup.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n stopped ||\n releasedSubsets.has(options) ||\n options.signal?.aborted ||\n demands.get(options) !== demand\n ) {\n cleanupDemand(demand)\n return\n }\n\n demand.active = true\n trackingRevision++\n await rebuildTracking()\n }\n\n const trackLoadSubset = (options: LoadSubsetOptions): Promise<void> => {\n const task = loadSubset(options)\n pendingDemandLoads.add(task)\n const finish = () => pendingDemandLoads.delete(task)\n void task.then(finish, finish)\n return task\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 reportDemandCleanupFailure = (error: unknown): void => {\n database.logger.log({\n level: LogLevels.error,\n message: `Could not clean up subset hook for ${viewName}`,\n error,\n })\n }\n\n const cleanupDemand = (demand: DemandRecord): void => {\n demands.delete(demand.options)\n const collector = demandCleanupCollector\n const order = collector ? collector.nextOrder++ : undefined\n try {\n const cleanup = demand.cleanup\n demand.cleanup = undefined\n const result: unknown = cleanup?.()\n if (!isPromiseLike(result)) return\n\n const task = Promise.resolve(result)\n pendingDemandCleanups.add(task)\n const finish = () => pendingDemandCleanups.delete(task)\n void task.then(finish, finish)\n if (collector) {\n collector.tasks.push({ order: order!, promise: task })\n collector.ownedTasks.add(task)\n void task.catch(() => undefined)\n } else {\n void task.catch((error) => {\n if (!demandCleanupCollector?.ownedTasks.has(task)) {\n reportDemandCleanupFailure(error)\n }\n })\n }\n } catch (error) {\n if (collector) {\n collector.failures.push({ order: order!, error })\n } else {\n reportDemandCleanupFailure(error)\n }\n }\n }\n\n const throwDemandCleanupFailures = (\n collector: DemandCleanupCollector,\n primaryFailures: ReadonlyArray<unknown> = [],\n ): void => {\n const errors = [\n ...primaryFailures,\n ...collector.failures\n .slice()\n .sort((left, right) => left.order - right.order)\n .map(({ error }) => error),\n ]\n if (errors.length === 1) throw errors[0]\n if (errors.length > 1) {\n throw new AggregateError(errors, `PowerSync cleanup failed`, {\n cause: errors[0],\n })\n }\n }\n\n const performPhysicalRelease = async (\n options: LoadSubsetOptions,\n ): Promise<void> => {\n const compiledDeparting = compileSQLite({ where: options.where })\n const departingWhereSQL = toInlinedWhereClause(compiledDeparting)\n let rowsToEvict: Array<{ id: string }>\n for (;;) {\n if (stopped) return\n const revision = trackingRevision\n const active = activeWhereExpressions()\n let evictionSQL: string\n if (active.length === 0) {\n evictionSQL = `SELECT id FROM ${viewName} WHERE ${departingWhereSQL}`\n } else {\n const combinedRemaining =\n active.length === 1\n ? active[0]!\n : or(active[0], active[1], ...active.slice(2))\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 rowsToEvict = await database.getAll<{ id: string }>(evictionSQL)\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- cleanup can run during the query\n if (stopped) return\n if (trackingRevision === revision) break\n }\n if (rowsToEvict.length > 0) {\n begin()\n for (const { id } of rowsToEvict) {\n write({ type: `delete`, key: id })\n }\n void commit()\n }\n await rebuildTracking()\n }\n\n function scheduleReleaseDrain(delay = 0): void {\n if (stopped || drainingReleases || releaseRetryTimer) return\n if (delay > 0) {\n releaseRetryTimer = setTimeout(() => {\n releaseRetryTimer = undefined\n void drainReleases()\n }, delay)\n return\n }\n void drainReleases()\n }\n\n async function drainReleases(): Promise<void> {\n if (stopped || drainingReleases) return\n drainingReleases = true\n let retryDelay = 0\n try {\n const attempts = pendingReleases.length\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- each release can reenter cleanup\n for (let index = 0; !stopped && index < attempts; index++) {\n const pending = pendingReleases.shift()!\n try {\n await performPhysicalRelease(pending.options)\n } catch (error) {\n pending.failures++\n pendingReleases.push(pending)\n const delay = Math.min(\n 1000 * 2 ** (pending.failures - 1),\n 30000,\n )\n retryDelay =\n retryDelay === 0 ? delay : Math.min(retryDelay, delay)\n database.logger.log({\n level: LogLevels.error,\n message: `Could not release subset tracking for ${viewName}; retrying`,\n error,\n })\n }\n }\n } finally {\n drainingReleases = false\n }\n if (pendingReleases.length > 0) scheduleReleaseDrain(retryDelay)\n }\n\n const unloadSubset = (options: LoadSubsetOptions): void => {\n releasedSubsets.add(options)\n const demand = demands.get(options)\n if (!demand) return\n\n const wasActive = demand.active\n if (wasActive) trackingRevision++\n cleanupDemand(demand)\n\n if (wasActive) {\n pendingReleases.push({ options, failures: 0 })\n // New work must not wait for another release's backoff.\n clearTimeout(releaseRetryTimer)\n releaseRetryTimer = undefined\n scheduleReleaseDrain()\n }\n }\n\n markReady()\n\n return {\n cleanup: () => {\n stopped = true\n clearTimeout(releaseRetryTimer)\n releaseRetryTimer = undefined\n database.logger.log({\n level: LogLevels.info,\n message: `Sync has been stopped for ${viewName} into ${trackedTableName}`,\n })\n abortController.abort()\n const trackingDisposal = disposeTrackingAfterAbort()\n const pendingLoads = [...pendingDemandLoads]\n const pendingTasks = [...pendingDemandCleanups]\n const collector: DemandCleanupCollector = {\n tasks: pendingTasks.map((promise, order) => ({ order, promise })),\n ownedTasks: new Set(pendingTasks),\n failures: [],\n nextOrder: pendingTasks.length,\n }\n demandCleanupCollector = collector\n for (const demand of demands.values()) {\n cleanupDemand(demand)\n }\n pendingReleases.length = 0\n\n return (async () => {\n const ownershipSettlements = await Promise.allSettled([\n trackingDisposal,\n ...pendingLoads,\n ])\n const disposalSettlement = ownershipSettlements[0]\n\n let settledTasks = 0\n while (settledTasks < collector.tasks.length) {\n const tasks = collector.tasks.slice(settledTasks)\n settledTasks = collector.tasks.length\n const outcomes = await Promise.allSettled(\n tasks.map(({ promise }) => promise),\n )\n for (const [index, outcome] of outcomes.entries()) {\n if (outcome.status === `rejected`) {\n collector.failures.push({\n order: tasks[index]!.order,\n error: outcome.reason,\n })\n }\n }\n }\n throwDemandCleanupFailures(\n collector,\n disposalSettlement.status === `rejected`\n ? [disposalSettlement.reason]\n : [],\n )\n })().finally(() => {\n if (demandCleanupCollector === collector) {\n demandCleanupCollector = null\n }\n })\n },\n loadSubset: trackLoadSubset,\n unloadSubset,\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<TTable>(value, table, serializer),\n }),\n },\n }\n return outputConfig\n}\n"],"names":["withCollectionConfigFactory","DEFAULT_BATCH_SIZE","schema","LogLevels","convertTableToSchema","PendingOperationStore","PowerSyncTransactor","DiffTriggerOperation","mapOperation","sanitizeSQL","or","compileSQLite","asPowerSyncRecord","serializeForSQLite"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serialization.cjs","sources":["../../src/serialization.ts"],"sourcesContent":["import { ColumnType } from '@powersync/common'\nimport type { Table } from '@powersync/common'\nimport type { CustomSQLiteSerializer } from './definitions'\nimport type {\n
|
|
1
|
+
{"version":3,"file":"serialization.cjs","sources":["../../src/serialization.ts"],"sourcesContent":["import { ColumnType } from '@powersync/common'\nimport type { Table } from '@powersync/common'\nimport type { CustomSQLiteSerializer } from './definitions'\nimport type {\n AnyTableColumnType,\n ExtractedTable,\n ExtractedTableColumns,\n} from './helpers'\n\n/**\n * Serializes an object for persistence to a SQLite table, mapping its values to appropriate SQLite types.\n *\n * This function takes an object representing a row, a table schema, and an optional custom serializer map.\n * It returns a new object with values transformed to be compatible with SQLite column types.\n *\n * ## Parameters\n * - `value`: The object to serialize (row data).\n * - `tableSchema`: The schema describing the SQLite table columns and types.\n * - `customSerializer`: An optional map of custom serialization functions for specific keys.\n *\n * ## Behavior\n * - For each key in `value`, finds the corresponding column in `tableSchema`.\n * - If a custom serializer is provided for a key, it is used to transform the value.\n * - Otherwise, values are mapped according to the column type:\n * - `TEXT`: Strings are passed through; Dates are converted to ISO strings; other types are JSON-stringified.\n * - `INTEGER`/`REAL`: Numbers are passed through; booleans are mapped to 1/0; other types are coerced to numbers.\n * - Throws if a column type is unknown or a value cannot be converted.\n *\n * ## Returns\n * - An object with the same keys as `value`, with values transformed for SQLite compatibility.\n *\n * ## Errors\n * - Throws if a key in `value` does not exist in the schema.\n * - Throws if a value cannot be converted to the required SQLite type.\n */\nexport function serializeForSQLite<TTable extends Table>(\n value: AnyTableColumnType<TTable>,\n tableSchema: TTable,\n customSerializer: CustomSQLiteSerializer<\n AnyTableColumnType<TTable>,\n ExtractedTableColumns<TTable>\n > = {},\n): ExtractedTable<TTable> {\n return Object.fromEntries(\n // eslint-disable-next-line no-shadow\n Object.entries(value).map(([key, value]) => {\n // First get the output schema type\n const outputType =\n key == `id`\n ? ColumnType.TEXT\n : tableSchema.columns.find((column) => column.name == key)?.type\n if (!outputType) {\n throw new Error(`Could not find schema for ${key} column.`)\n }\n\n if (value == null) {\n return [key, value]\n }\n\n const customTransform = customSerializer[key]\n if (customTransform) {\n return [key, customTransform(value)]\n }\n\n // Map to the output\n switch (outputType) {\n case ColumnType.TEXT:\n if (typeof value == `string`) {\n return [key, value]\n } else if (value instanceof Date) {\n return [key, value.toISOString()]\n } else {\n return [key, JSON.stringify(value)]\n }\n case ColumnType.INTEGER:\n case ColumnType.REAL:\n if (typeof value == `number`) {\n return [key, value]\n } else if (typeof value == `boolean`) {\n return [key, value ? 1 : 0]\n } else {\n const numberValue = Number(value)\n if (isNaN(numberValue)) {\n throw new Error(\n `Could not convert ${key}=${value} to a number for SQLite`,\n )\n }\n return [key, numberValue]\n }\n }\n }),\n ) as ExtractedTable<TTable>\n}\n"],"names":["value","ColumnType"],"mappings":";;;AAmCO,SAAS,mBACd,OACA,aACA,mBAGI,CAAA,GACoB;AACxB,SAAO,OAAO;AAAA;AAAA,IAEZ,OAAO,QAAQ,KAAK,EAAE,IAAI,CAAC,CAAC,KAAKA,MAAK,MAAM;AAE1C,YAAM,aACJ,OAAO,OACHC,OAAAA,WAAW,OACX,YAAY,QAAQ,KAAK,CAAC,WAAW,OAAO,QAAQ,GAAG,GAAG;AAChE,UAAI,CAAC,YAAY;AACf,cAAM,IAAI,MAAM,6BAA6B,GAAG,UAAU;AAAA,MAC5D;AAEA,UAAID,UAAS,MAAM;AACjB,eAAO,CAAC,KAAKA,MAAK;AAAA,MACpB;AAEA,YAAM,kBAAkB,iBAAiB,GAAG;AAC5C,UAAI,iBAAiB;AACnB,eAAO,CAAC,KAAK,gBAAgBA,MAAK,CAAC;AAAA,MACrC;AAGA,cAAQ,YAAA;AAAA,QACN,KAAKC,OAAAA,WAAW;AACd,cAAI,OAAOD,UAAS,UAAU;AAC5B,mBAAO,CAAC,KAAKA,MAAK;AAAA,UACpB,WAAWA,kBAAiB,MAAM;AAChC,mBAAO,CAAC,KAAKA,OAAM,aAAa;AAAA,UAClC,OAAO;AACL,mBAAO,CAAC,KAAK,KAAK,UAAUA,MAAK,CAAC;AAAA,UACpC;AAAA,QACF,KAAKC,OAAAA,WAAW;AAAA,QAChB,KAAKA,OAAAA,WAAW;AACd,cAAI,OAAOD,UAAS,UAAU;AAC5B,mBAAO,CAAC,KAAKA,MAAK;AAAA,UACpB,WAAW,OAAOA,UAAS,WAAW;AACpC,mBAAO,CAAC,KAAKA,SAAQ,IAAI,CAAC;AAAA,UAC5B,OAAO;AACL,kBAAM,cAAc,OAAOA,MAAK;AAChC,gBAAI,MAAM,WAAW,GAAG;AACtB,oBAAM,IAAI;AAAA,gBACR,qBAAqB,GAAG,IAAIA,MAAK;AAAA,cAAA;AAAA,YAErC;AACA,mBAAO,CAAC,KAAK,WAAW;AAAA,UAC1B;AAAA,MAAA;AAAA,IAEN,CAAC;AAAA,EAAA;AAEL;;"}
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import { Table } from '@powersync/common';
|
|
2
2
|
import { CustomSQLiteSerializer } from './definitions.cjs';
|
|
3
|
-
import { ExtractedTable, ExtractedTableColumns
|
|
3
|
+
import { AnyTableColumnType, ExtractedTable, ExtractedTableColumns } from './helpers.cjs';
|
|
4
4
|
/**
|
|
5
5
|
* Serializes an object for persistence to a SQLite table, mapping its values to appropriate SQLite types.
|
|
6
6
|
*
|
|
7
7
|
* This function takes an object representing a row, a table schema, and an optional custom serializer map.
|
|
8
8
|
* It returns a new object with values transformed to be compatible with SQLite column types.
|
|
9
9
|
*
|
|
10
|
-
* ## Generics
|
|
11
|
-
* - `TOutput`: The shape of the input object, typically matching the row data.
|
|
12
|
-
* - `TTable`: The table schema, which must match the keys of `TOutput`.
|
|
13
|
-
*
|
|
14
10
|
* ## Parameters
|
|
15
11
|
* - `value`: The object to serialize (row data).
|
|
16
12
|
* - `tableSchema`: The schema describing the SQLite table columns and types.
|
|
@@ -31,4 +27,4 @@ import { ExtractedTable, ExtractedTableColumns, MapBaseColumnType } from './help
|
|
|
31
27
|
* - Throws if a key in `value` does not exist in the schema.
|
|
32
28
|
* - Throws if a value cannot be converted to the required SQLite type.
|
|
33
29
|
*/
|
|
34
|
-
export declare function serializeForSQLite<
|
|
30
|
+
export declare function serializeForSQLite<TTable extends Table>(value: AnyTableColumnType<TTable>, tableSchema: TTable, customSerializer?: CustomSQLiteSerializer<AnyTableColumnType<TTable>, ExtractedTableColumns<TTable>>): ExtractedTable<TTable>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { PendingOperationStore, PendingOperation } from './PendingOperationStore.js';
|
|
2
|
-
import {
|
|
2
|
+
import { CommonPowerSyncDatabase, LockContext } from '@powersync/common';
|
|
3
3
|
import { PendingMutation, Transaction } from '@tanstack/db';
|
|
4
4
|
import { PowerSyncCollectionMeta } from './definitions.js';
|
|
5
5
|
export type TransactorOptions = {
|
|
6
|
-
database:
|
|
6
|
+
database: CommonPowerSyncDatabase;
|
|
7
7
|
};
|
|
8
8
|
/**
|
|
9
9
|
* Applies mutations to the PowerSync database. This method is called automatically by the collection's
|
|
@@ -41,7 +41,7 @@ export type TransactorOptions = {
|
|
|
41
41
|
* @returns A promise that resolves when the mutations have been persisted to PowerSync
|
|
42
42
|
*/
|
|
43
43
|
export declare class PowerSyncTransactor {
|
|
44
|
-
database:
|
|
44
|
+
database: CommonPowerSyncDatabase;
|
|
45
45
|
pendingOperationStore: PendingOperationStore;
|
|
46
46
|
constructor(options: TransactorOptions);
|
|
47
47
|
/**
|
|
@@ -57,7 +57,7 @@ export declare class PowerSyncTransactor {
|
|
|
57
57
|
* - Executing the mutation
|
|
58
58
|
* - Returning the last pending diff operation if required
|
|
59
59
|
*/
|
|
60
|
-
protected handleOperationWithCompletion(mutation: PendingMutation<any>, context: LockContext, waitForCompletion: boolean, handler: (tableName: string, mutation: PendingMutation<any>, serializeValue: (value: any) => Record<string, unknown>) => Promise<void>): Promise<PendingOperation | null>;
|
|
60
|
+
protected handleOperationWithCompletion(mutation: PendingMutation<any>, context: LockContext, waitForCompletion: boolean, handler: (tableName: string, mutation: PendingMutation<any>, serializeValue: (value: any) => Record<string, unknown>) => Promise<void | false>): Promise<PendingOperation | null>;
|
|
61
61
|
protected getMutationCollectionMeta(mutation: PendingMutation<any>): PowerSyncCollectionMeta<any>;
|
|
62
62
|
/**
|
|
63
63
|
* Processes collection mutation metadata for persistence to the database.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sanitizeSQL } from "@powersync/common";
|
|
1
|
+
import { sanitizeSQL, LogLevels } from "@powersync/common";
|
|
2
2
|
import { LoadSubsetOperationAbortedError } from "@tanstack/db";
|
|
3
3
|
import DebugModule from "debug";
|
|
4
4
|
import { PendingOperationStore } from "./PendingOperationStore.js";
|
|
@@ -17,20 +17,20 @@ class PowerSyncTransactor {
|
|
|
17
17
|
if (mutations.length == 0) {
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
|
-
const
|
|
21
|
-
(mutation) => mutation.collection.id
|
|
22
|
-
);
|
|
23
|
-
const collectionIds = Array.from(new Set(mutationsCollectionIds));
|
|
20
|
+
const collectionsById = /* @__PURE__ */ new Map();
|
|
24
21
|
const lastCollectionMutationIndexes = /* @__PURE__ */ new Map();
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
collectionId,
|
|
29
|
-
|
|
30
|
-
);
|
|
22
|
+
for (const [index, mutation] of mutations.entries()) {
|
|
23
|
+
const collectionId = mutation.collection.id;
|
|
24
|
+
if (!collectionsById.has(collectionId)) {
|
|
25
|
+
collectionsById.set(collectionId, mutation.collection);
|
|
26
|
+
}
|
|
27
|
+
const changesDatabase = mutation.type != `update` || Object.keys(mutation.changes).some((key) => key != `id`) || typeof mutation.metadata != `undefined` && this.getMutationCollectionMeta(mutation).metadataIsTracked;
|
|
28
|
+
if (changesDatabase) {
|
|
29
|
+
lastCollectionMutationIndexes.set(collectionId, index);
|
|
30
|
+
}
|
|
31
31
|
}
|
|
32
32
|
await Promise.all(
|
|
33
|
-
|
|
33
|
+
Array.from(collectionsById.values()).map(async (collection) => {
|
|
34
34
|
if (collection.isReady()) {
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
@@ -119,7 +119,8 @@ class PowerSyncTransactor {
|
|
|
119
119
|
waitForCompletion,
|
|
120
120
|
// eslint-disable-next-line no-shadow
|
|
121
121
|
async (tableName, mutation2, serializeValue) => {
|
|
122
|
-
const
|
|
122
|
+
const { id: _id, ...changes } = mutation2.changes;
|
|
123
|
+
const values = serializeValue(changes);
|
|
123
124
|
const keys = Object.keys(values).map((key) => sanitizeSQL`${key}`);
|
|
124
125
|
const queryParameters = Object.values(values);
|
|
125
126
|
const metadataValue = this.processMutationMetadata(mutation2);
|
|
@@ -127,14 +128,18 @@ class PowerSyncTransactor {
|
|
|
127
128
|
keys.push(`_metadata`);
|
|
128
129
|
queryParameters.push(metadataValue);
|
|
129
130
|
}
|
|
131
|
+
if (keys.length == 0) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
130
134
|
await context.execute(
|
|
131
135
|
`
|
|
132
136
|
UPDATE ${tableName}
|
|
133
137
|
SET ${keys.map((key) => `${key} = ?`).join(`, `)}
|
|
134
138
|
WHERE id = ?
|
|
135
139
|
`,
|
|
136
|
-
[...queryParameters, asPowerSyncRecord(mutation2.
|
|
140
|
+
[...queryParameters, asPowerSyncRecord(mutation2.original).id]
|
|
137
141
|
);
|
|
142
|
+
return;
|
|
138
143
|
}
|
|
139
144
|
);
|
|
140
145
|
}
|
|
@@ -173,7 +178,14 @@ class PowerSyncTransactor {
|
|
|
173
178
|
*/
|
|
174
179
|
async handleOperationWithCompletion(mutation, context, waitForCompletion, handler) {
|
|
175
180
|
const { tableName, trackedTableName, serializeValue } = this.getMutationCollectionMeta(mutation);
|
|
176
|
-
await handler(
|
|
181
|
+
const executed = await handler(
|
|
182
|
+
sanitizeSQL`${tableName}`,
|
|
183
|
+
mutation,
|
|
184
|
+
serializeValue
|
|
185
|
+
);
|
|
186
|
+
if (executed === false) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
177
189
|
if (!waitForCompletion) {
|
|
178
190
|
return null;
|
|
179
191
|
}
|
|
@@ -202,10 +214,11 @@ class PowerSyncTransactor {
|
|
|
202
214
|
const { metadataIsTracked } = this.getMutationCollectionMeta(mutation);
|
|
203
215
|
if (!metadataIsTracked) {
|
|
204
216
|
if (typeof mutation.metadata != `undefined`) {
|
|
205
|
-
this.database.logger.
|
|
206
|
-
|
|
207
|
-
mutation.metadata
|
|
208
|
-
|
|
217
|
+
this.database.logger.log({
|
|
218
|
+
level: LogLevels.warn,
|
|
219
|
+
message: `Metadata provided for collection ${mutation.collection.id} but the PowerSync table does not track metadata. The PowerSync table should be configured with trackMetadata: true.`,
|
|
220
|
+
error: mutation.metadata
|
|
221
|
+
});
|
|
209
222
|
}
|
|
210
223
|
return null;
|
|
211
224
|
} else if (typeof mutation.metadata == `undefined`) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PowerSyncTransactor.js","sources":["../../src/PowerSyncTransactor.ts"],"sourcesContent":["import { sanitizeSQL } from '@powersync/common'\nimport { LoadSubsetOperationAbortedError } from '@tanstack/db'\nimport DebugModule from 'debug'\nimport { PendingOperationStore } from './PendingOperationStore'\nimport { asPowerSyncRecord, mapOperationToPowerSync } from './helpers'\nimport type { AbstractPowerSyncDatabase, LockContext } from '@powersync/common'\nimport type { PendingMutation, Transaction } from '@tanstack/db'\nimport type { PendingOperation } from './PendingOperationStore'\nimport type {\n EnhancedPowerSyncCollectionConfig,\n PowerSyncCollectionMeta,\n} from './definitions'\n\nconst debug = DebugModule.debug(`ts/db:powersync`)\n\nexport type TransactorOptions = {\n database: AbstractPowerSyncDatabase\n}\n\n/**\n * Applies mutations to the PowerSync database. This method is called automatically by the collection's\n * insert, update, and delete operations. You typically don't need to call this directly unless you\n * have special transaction requirements.\n *\n * @example\n * ```typescript\n * // Create a collection\n * const collection = createCollection(\n * powerSyncCollectionOptions<Document>({\n * database: db,\n * table: APP_SCHEMA.props.documents,\n * })\n * )\n *\n * const addTx = createTransaction({\n * autoCommit: false,\n * mutationFn: async ({ transaction }) => {\n * await new PowerSyncTransactor({ database: db }).applyTransaction(transaction)\n * },\n * })\n *\n * addTx.mutate(() => {\n * for (let i = 0; i < 5; i++) {\n * collection.insert({ id: randomUUID(), name: `tx-${i}` })\n * }\n * })\n *\n * await addTx.commit()\n * await addTx.isPersisted.promise\n * ```\n *\n * @param transaction - The transaction containing mutations to apply\n * @returns A promise that resolves when the mutations have been persisted to PowerSync\n */\nexport class PowerSyncTransactor {\n database: AbstractPowerSyncDatabase\n pendingOperationStore: PendingOperationStore\n\n constructor(options: TransactorOptions) {\n this.database = options.database\n this.pendingOperationStore = PendingOperationStore.GLOBAL\n }\n\n /**\n * Persists a {@link Transaction} to the PowerSync SQLite database.\n */\n async applyTransaction(transaction: Transaction<any>) {\n const { mutations } = transaction\n\n if (mutations.length == 0) {\n return\n }\n /**\n * The transaction might contain operations for different collections.\n * We can do some optimizations for single-collection transactions.\n */\n const mutationsCollectionIds = mutations.map(\n (mutation) => mutation.collection.id,\n )\n const collectionIds = Array.from(new Set(mutationsCollectionIds))\n const lastCollectionMutationIndexes = new Map<string, number>()\n const allCollections = collectionIds\n .map((id) => mutations.find((mutation) => mutation.collection.id == id)!)\n .map((mutation) => mutation.collection)\n for (const collectionId of collectionIds) {\n lastCollectionMutationIndexes.set(\n collectionId,\n mutationsCollectionIds.lastIndexOf(collectionId),\n )\n }\n\n // Check all the observers are ready before taking a lock\n await Promise.all(\n allCollections.map(async (collection) => {\n if (collection.isReady()) {\n return\n }\n // Observe this session without starting new demand from mutationFn.\n // Cleanup and startup failure must settle the wait before taking a lock.\n await new Promise<void>((resolve, reject) => {\n const check = () => {\n if (collection.isReady()) {\n unsubscribe()\n resolve()\n } else if (\n collection.status === `error` ||\n collection.status === `cleaned-up`\n ) {\n unsubscribe()\n reject(\n collection.status === `error`\n ? (collection._lifecycle.getSyncError() ??\n new Error(`Collection failed before readiness`))\n : new LoadSubsetOperationAbortedError(),\n )\n }\n }\n const unsubscribe = collection.on(`status:change`, check)\n check()\n })\n }),\n )\n\n // Persist to PowerSync\n const { whenComplete } = await this.database.writeTransaction(\n async (tx) => {\n const pendingOperations: Array<PendingOperation | null> = []\n\n for (const [index, mutation] of mutations.entries()) {\n /**\n * Each collection processes events independently. We need to make sure the\n * last operation for each collection has been observed.\n */\n const shouldWait =\n index == lastCollectionMutationIndexes.get(mutation.collection.id)\n switch (mutation.type) {\n case `insert`:\n pendingOperations.push(\n await this.handleInsert(mutation, tx, shouldWait),\n )\n break\n case `update`:\n pendingOperations.push(\n await this.handleUpdate(mutation, tx, shouldWait),\n )\n break\n case `delete`:\n pendingOperations.push(\n await this.handleDelete(mutation, tx, shouldWait),\n )\n break\n }\n }\n\n /**\n * Return a promise from the writeTransaction, without awaiting it.\n * This promise will resolve once the entire transaction has been\n * observed via the diff triggers.\n * We return without awaiting in order to free the write lock.\n */\n return {\n whenComplete: Promise.all(\n pendingOperations\n .filter((op) => !!op)\n .map((op) => this.pendingOperationStore.waitFor(op)),\n ),\n }\n },\n )\n\n // Wait for the change to be observed via the diff trigger\n await whenComplete\n }\n\n protected async handleInsert(\n mutation: PendingMutation<any>,\n context: LockContext,\n waitForCompletion: boolean = false,\n ): Promise<PendingOperation | null> {\n debug(`insert`, mutation)\n\n return this.handleOperationWithCompletion(\n mutation,\n context,\n waitForCompletion,\n // eslint-disable-next-line no-shadow\n async (tableName, mutation, serializeValue) => {\n const values = serializeValue(mutation.modified)\n const keys = Object.keys(values).map((key) => sanitizeSQL`${key}`)\n const queryParameters = Object.values(values)\n\n const metadataValue = this.processMutationMetadata(mutation)\n if (metadataValue != null) {\n keys.push(`_metadata`)\n queryParameters.push(metadataValue)\n }\n\n await context.execute(\n `\n INSERT into ${tableName}\n (${keys.join(`, `)})\n VALUES\n (${keys.map((_) => `?`).join(`, `)})\n `,\n queryParameters,\n )\n },\n )\n }\n\n protected async handleUpdate(\n mutation: PendingMutation<any>,\n context: LockContext,\n waitForCompletion: boolean = false,\n ): Promise<PendingOperation | null> {\n debug(`update`, mutation)\n\n return this.handleOperationWithCompletion(\n mutation,\n context,\n waitForCompletion,\n // eslint-disable-next-line no-shadow\n async (tableName, mutation, serializeValue) => {\n const values = serializeValue(mutation.modified)\n const keys = Object.keys(values).map((key) => sanitizeSQL`${key}`)\n const queryParameters = Object.values(values)\n\n const metadataValue = this.processMutationMetadata(mutation)\n if (metadataValue != null) {\n keys.push(`_metadata`)\n queryParameters.push(metadataValue)\n }\n\n await context.execute(\n `\n UPDATE ${tableName}\n SET ${keys.map((key) => `${key} = ?`).join(`, `)}\n WHERE id = ?\n `,\n [...queryParameters, asPowerSyncRecord(mutation.modified).id],\n )\n },\n )\n }\n\n protected async handleDelete(\n mutation: PendingMutation<any>,\n context: LockContext,\n waitForCompletion: boolean = false,\n ): Promise<PendingOperation | null> {\n debug(`update`, mutation)\n\n return this.handleOperationWithCompletion(\n mutation,\n context,\n waitForCompletion,\n // eslint-disable-next-line no-shadow\n async (tableName, mutation) => {\n const metadataValue = this.processMutationMetadata(mutation)\n if (metadataValue != null) {\n /**\n * Delete operations with metadata require a different approach to handle metadata.\n * This will delete the record.\n */\n await context.execute(\n `\n UPDATE ${tableName} SET _deleted = TRUE, _metadata = ? WHERE id = ?\n `,\n [metadataValue, asPowerSyncRecord(mutation.original).id],\n )\n } else {\n await context.execute(\n `\n DELETE FROM ${tableName} WHERE id = ?\n `,\n [asPowerSyncRecord(mutation.original).id],\n )\n }\n },\n )\n }\n\n /**\n * Helper function which wraps a persistence operation by:\n * - Fetching the mutation's collection's SQLite table details\n * - Executing the mutation\n * - Returning the last pending diff operation if required\n */\n protected async handleOperationWithCompletion(\n mutation: PendingMutation<any>,\n context: LockContext,\n waitForCompletion: boolean,\n handler: (\n tableName: string,\n mutation: PendingMutation<any>,\n serializeValue: (value: any) => Record<string, unknown>,\n ) => Promise<void>,\n ): Promise<PendingOperation | null> {\n const { tableName, trackedTableName, serializeValue } =\n this.getMutationCollectionMeta(mutation)\n\n await handler(sanitizeSQL`${tableName}`, mutation, serializeValue)\n\n if (!waitForCompletion) {\n return null\n }\n\n // Need to get the operation in order to wait for it\n const diffOperation = await context.get<{ id: string; timestamp: string }>(\n sanitizeSQL`SELECT id, timestamp FROM ${trackedTableName} ORDER BY operation_id DESC LIMIT 1`,\n )\n return {\n tableName,\n id: diffOperation.id,\n operation: mapOperationToPowerSync(mutation.type),\n timestamp: diffOperation.timestamp,\n }\n }\n\n protected getMutationCollectionMeta(\n mutation: PendingMutation<any>,\n ): PowerSyncCollectionMeta<any> {\n if (\n typeof (mutation.collection.config as any).utils?.getMeta != `function`\n ) {\n throw new Error(`Collection is not a PowerSync collection.`)\n }\n return (\n mutation.collection\n .config as unknown as EnhancedPowerSyncCollectionConfig<any>\n ).utils.getMeta()\n }\n\n /**\n * Processes collection mutation metadata for persistence to the database.\n * We only support storing string metadata.\n * @returns null if no metadata should be stored.\n */\n protected processMutationMetadata(\n mutation: PendingMutation<any>,\n ): string | null {\n const { metadataIsTracked } = this.getMutationCollectionMeta(mutation)\n if (!metadataIsTracked) {\n // If it's not supported, we don't store metadata.\n if (typeof mutation.metadata != `undefined`) {\n // Log a warning if metadata is provided but not tracked.\n this.database.logger.warn(\n `Metadata provided for collection ${mutation.collection.id} but the PowerSync table does not track metadata. The PowerSync table should be configured with trackMetadata: true.`,\n mutation.metadata,\n )\n }\n return null\n } else if (typeof mutation.metadata == `undefined`) {\n return null\n } else if (typeof mutation.metadata == `string`) {\n return mutation.metadata\n } else {\n return JSON.stringify(mutation.metadata)\n }\n }\n}\n"],"names":["mutation"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PowerSyncTransactor.js","sources":["../../src/PowerSyncTransactor.ts"],"sourcesContent":["import { LogLevels, sanitizeSQL } from '@powersync/common'\nimport { LoadSubsetOperationAbortedError } from '@tanstack/db'\nimport DebugModule from 'debug'\nimport { PendingOperationStore } from './PendingOperationStore'\nimport { asPowerSyncRecord, mapOperationToPowerSync } from './helpers'\nimport type { CommonPowerSyncDatabase, LockContext } from '@powersync/common'\nimport type { PendingMutation, Transaction } from '@tanstack/db'\nimport type { PendingOperation } from './PendingOperationStore'\nimport type {\n EnhancedPowerSyncCollectionConfig,\n PowerSyncCollectionMeta,\n} from './definitions'\n\nconst debug = DebugModule.debug(`ts/db:powersync`)\n\nexport type TransactorOptions = {\n database: CommonPowerSyncDatabase\n}\n\n/**\n * Applies mutations to the PowerSync database. This method is called automatically by the collection's\n * insert, update, and delete operations. You typically don't need to call this directly unless you\n * have special transaction requirements.\n *\n * @example\n * ```typescript\n * // Create a collection\n * const collection = createCollection(\n * powerSyncCollectionOptions<Document>({\n * database: db,\n * table: APP_SCHEMA.props.documents,\n * })\n * )\n *\n * const addTx = createTransaction({\n * autoCommit: false,\n * mutationFn: async ({ transaction }) => {\n * await new PowerSyncTransactor({ database: db }).applyTransaction(transaction)\n * },\n * })\n *\n * addTx.mutate(() => {\n * for (let i = 0; i < 5; i++) {\n * collection.insert({ id: randomUUID(), name: `tx-${i}` })\n * }\n * })\n *\n * await addTx.commit()\n * await addTx.isPersisted.promise\n * ```\n *\n * @param transaction - The transaction containing mutations to apply\n * @returns A promise that resolves when the mutations have been persisted to PowerSync\n */\nexport class PowerSyncTransactor {\n database: CommonPowerSyncDatabase\n pendingOperationStore: PendingOperationStore\n\n constructor(options: TransactorOptions) {\n this.database = options.database\n this.pendingOperationStore = PendingOperationStore.GLOBAL\n }\n\n /**\n * Persists a {@link Transaction} to the PowerSync SQLite database.\n */\n async applyTransaction(transaction: Transaction<any>) {\n const { mutations } = transaction\n\n if (mutations.length == 0) {\n return\n }\n /**\n * The transaction might contain operations for different collections.\n * We can do some optimizations for single-collection transactions.\n */\n const collectionsById = new Map<\n string,\n PendingMutation<any>[`collection`]\n >()\n const lastCollectionMutationIndexes = new Map<string, number>()\n for (const [index, mutation] of mutations.entries()) {\n const collectionId = mutation.collection.id\n if (!collectionsById.has(collectionId)) {\n collectionsById.set(collectionId, mutation.collection)\n }\n const changesDatabase =\n mutation.type != `update` ||\n Object.keys(mutation.changes).some((key) => key != `id`) ||\n (typeof mutation.metadata != `undefined` &&\n this.getMutationCollectionMeta(mutation).metadataIsTracked)\n if (changesDatabase) {\n lastCollectionMutationIndexes.set(collectionId, index)\n }\n }\n\n // Check all the observers are ready before taking a lock\n await Promise.all(\n Array.from(collectionsById.values()).map(async (collection) => {\n if (collection.isReady()) {\n return\n }\n // Observe this session without starting new demand from mutationFn.\n // Cleanup and startup failure must settle the wait before taking a lock.\n await new Promise<void>((resolve, reject) => {\n const check = () => {\n if (collection.isReady()) {\n unsubscribe()\n resolve()\n } else if (\n collection.status === `error` ||\n collection.status === `cleaned-up`\n ) {\n unsubscribe()\n reject(\n collection.status === `error`\n ? (collection._lifecycle.getSyncError() ??\n new Error(`Collection failed before readiness`))\n : new LoadSubsetOperationAbortedError(),\n )\n }\n }\n const unsubscribe = collection.on(`status:change`, check)\n check()\n })\n }),\n )\n\n // Persist to PowerSync\n const { whenComplete } = await this.database.writeTransaction(\n async (tx) => {\n const pendingOperations: Array<PendingOperation | null> = []\n\n for (const [index, mutation] of mutations.entries()) {\n /**\n * Each collection processes events independently. We need to make sure the\n * last operation for each collection has been observed.\n */\n const shouldWait =\n index == lastCollectionMutationIndexes.get(mutation.collection.id)\n switch (mutation.type) {\n case `insert`:\n pendingOperations.push(\n await this.handleInsert(mutation, tx, shouldWait),\n )\n break\n case `update`:\n pendingOperations.push(\n await this.handleUpdate(mutation, tx, shouldWait),\n )\n break\n case `delete`:\n pendingOperations.push(\n await this.handleDelete(mutation, tx, shouldWait),\n )\n break\n }\n }\n\n /**\n * Return a promise from the writeTransaction, without awaiting it.\n * This promise will resolve once the entire transaction has been\n * observed via the diff triggers.\n * We return without awaiting in order to free the write lock.\n */\n return {\n whenComplete: Promise.all(\n pendingOperations\n .filter((op) => !!op)\n .map((op) => this.pendingOperationStore.waitFor(op)),\n ),\n }\n },\n )\n\n // Wait for the change to be observed via the diff trigger\n await whenComplete\n }\n\n protected async handleInsert(\n mutation: PendingMutation<any>,\n context: LockContext,\n waitForCompletion: boolean = false,\n ): Promise<PendingOperation | null> {\n debug(`insert`, mutation)\n\n return this.handleOperationWithCompletion(\n mutation,\n context,\n waitForCompletion,\n // eslint-disable-next-line no-shadow\n async (tableName, mutation, serializeValue) => {\n const values = serializeValue(mutation.modified)\n const keys = Object.keys(values).map((key) => sanitizeSQL`${key}`)\n const queryParameters = Object.values(values)\n\n const metadataValue = this.processMutationMetadata(mutation)\n if (metadataValue != null) {\n keys.push(`_metadata`)\n queryParameters.push(metadataValue)\n }\n\n await context.execute(\n `\n INSERT into ${tableName}\n (${keys.join(`, `)})\n VALUES\n (${keys.map((_) => `?`).join(`, `)})\n `,\n queryParameters,\n )\n },\n )\n }\n\n protected async handleUpdate(\n mutation: PendingMutation<any>,\n context: LockContext,\n waitForCompletion: boolean = false,\n ): Promise<PendingOperation | null> {\n debug(`update`, mutation)\n\n return this.handleOperationWithCompletion(\n mutation,\n context,\n waitForCompletion,\n // eslint-disable-next-line no-shadow\n async (tableName, mutation, serializeValue) => {\n const { id: _id, ...changes } = mutation.changes\n const values = serializeValue(changes)\n const keys = Object.keys(values).map((key) => sanitizeSQL`${key}`)\n const queryParameters = Object.values(values)\n\n const metadataValue = this.processMutationMetadata(mutation)\n if (metadataValue != null) {\n keys.push(`_metadata`)\n queryParameters.push(metadataValue)\n }\n\n if (keys.length == 0) {\n return false\n }\n\n await context.execute(\n `\n UPDATE ${tableName}\n SET ${keys.map((key) => `${key} = ?`).join(`, `)}\n WHERE id = ?\n `,\n [...queryParameters, asPowerSyncRecord(mutation.original).id],\n )\n\n return\n },\n )\n }\n\n protected async handleDelete(\n mutation: PendingMutation<any>,\n context: LockContext,\n waitForCompletion: boolean = false,\n ): Promise<PendingOperation | null> {\n debug(`update`, mutation)\n\n return this.handleOperationWithCompletion(\n mutation,\n context,\n waitForCompletion,\n // eslint-disable-next-line no-shadow\n async (tableName, mutation) => {\n const metadataValue = this.processMutationMetadata(mutation)\n if (metadataValue != null) {\n /**\n * Delete operations with metadata require a different approach to handle metadata.\n * This will delete the record.\n */\n await context.execute(\n `\n UPDATE ${tableName} SET _deleted = TRUE, _metadata = ? WHERE id = ?\n `,\n [metadataValue, asPowerSyncRecord(mutation.original).id],\n )\n } else {\n await context.execute(\n `\n DELETE FROM ${tableName} WHERE id = ?\n `,\n [asPowerSyncRecord(mutation.original).id],\n )\n }\n },\n )\n }\n\n /**\n * Helper function which wraps a persistence operation by:\n * - Fetching the mutation's collection's SQLite table details\n * - Executing the mutation\n * - Returning the last pending diff operation if required\n */\n protected async handleOperationWithCompletion(\n mutation: PendingMutation<any>,\n context: LockContext,\n waitForCompletion: boolean,\n handler: (\n tableName: string,\n mutation: PendingMutation<any>,\n serializeValue: (value: any) => Record<string, unknown>,\n ) => Promise<void | false>,\n ): Promise<PendingOperation | null> {\n const { tableName, trackedTableName, serializeValue } =\n this.getMutationCollectionMeta(mutation)\n\n const executed = await handler(\n sanitizeSQL`${tableName}`,\n mutation,\n serializeValue,\n )\n\n if (executed === false) {\n return null\n }\n\n if (!waitForCompletion) {\n return null\n }\n\n // Need to get the operation in order to wait for it\n const diffOperation = await context.get<{ id: string; timestamp: string }>(\n sanitizeSQL`SELECT id, timestamp FROM ${trackedTableName} ORDER BY operation_id DESC LIMIT 1`,\n )\n return {\n tableName,\n id: diffOperation.id,\n operation: mapOperationToPowerSync(mutation.type),\n timestamp: diffOperation.timestamp,\n }\n }\n\n protected getMutationCollectionMeta(\n mutation: PendingMutation<any>,\n ): PowerSyncCollectionMeta<any> {\n if (\n typeof (mutation.collection.config as any).utils?.getMeta != `function`\n ) {\n throw new Error(`Collection is not a PowerSync collection.`)\n }\n return (\n mutation.collection\n .config as unknown as EnhancedPowerSyncCollectionConfig<any>\n ).utils.getMeta()\n }\n\n /**\n * Processes collection mutation metadata for persistence to the database.\n * We only support storing string metadata.\n * @returns null if no metadata should be stored.\n */\n protected processMutationMetadata(\n mutation: PendingMutation<any>,\n ): string | null {\n const { metadataIsTracked } = this.getMutationCollectionMeta(mutation)\n if (!metadataIsTracked) {\n // If it's not supported, we don't store metadata.\n if (typeof mutation.metadata != `undefined`) {\n // Log a warning if metadata is provided but not tracked.\n this.database.logger.log({\n level: LogLevels.warn,\n message: `Metadata provided for collection ${mutation.collection.id} but the PowerSync table does not track metadata. The PowerSync table should be configured with trackMetadata: true.`,\n error: mutation.metadata,\n })\n }\n return null\n } else if (typeof mutation.metadata == `undefined`) {\n return null\n } else if (typeof mutation.metadata == `string`) {\n return mutation.metadata\n } else {\n return JSON.stringify(mutation.metadata)\n }\n }\n}\n"],"names":["mutation"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CommonPowerSyncDatabase, Table } from '@powersync/common';
|
|
2
2
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
3
3
|
import { BaseCollectionConfig, CleanupFn, CollectionConfig, InferSchemaOutput, LoadSubsetOptions } from '@tanstack/db';
|
|
4
4
|
import { AnyTableColumnType, ExtractedTable, OptionalExtractedTable, PowerSyncRecord } from './helpers.js';
|
|
@@ -7,7 +7,7 @@ import { AnyTableColumnType, ExtractedTable, OptionalExtractedTable, PowerSyncRe
|
|
|
7
7
|
* - Standard SQLite types are to be used OR
|
|
8
8
|
* - If the provided schema should be used.
|
|
9
9
|
*/
|
|
10
|
-
export type InferPowerSyncOutputType<TTable extends Table = Table, TSchema extends StandardSchemaV1<PowerSyncRecord> = never> = TSchema extends never ? ExtractedTable<TTable> : InferSchemaOutput<TSchema>;
|
|
10
|
+
export type InferPowerSyncOutputType<TTable extends Table = Table, TSchema extends StandardSchemaV1<PowerSyncRecord> = never> = [TSchema] extends [never] ? ExtractedTable<TTable> : InferSchemaOutput<TSchema>;
|
|
11
11
|
/**
|
|
12
12
|
* A mapping type for custom serialization of object properties to SQLite-compatible values.
|
|
13
13
|
*
|
|
@@ -143,11 +143,11 @@ export type OnDemandSyncHooks = {
|
|
|
143
143
|
*/
|
|
144
144
|
onLoadSubset?: (options: LoadSubsetOptions) => CleanupFn | void | Promise<CleanupFn | void>;
|
|
145
145
|
};
|
|
146
|
-
export type BasePowerSyncCollectionConfig<TTable extends Table = Table, TSchema extends StandardSchemaV1 = never> = Omit<BaseCollectionConfig<
|
|
146
|
+
export type BasePowerSyncCollectionConfig<TTable extends Table = Table, TSchema extends StandardSchemaV1<any> = never> = Omit<BaseCollectionConfig<InferPowerSyncOutputType<TTable, TSchema>, string, TSchema>, `onInsert` | `onUpdate` | `onDelete` | `getKey` | `syncMode`> & {
|
|
147
147
|
/** The PowerSync schema Table definition */
|
|
148
148
|
table: TTable;
|
|
149
149
|
/** The PowerSync database instance */
|
|
150
|
-
database:
|
|
150
|
+
database: CommonPowerSyncDatabase;
|
|
151
151
|
/**
|
|
152
152
|
* The maximum number of documents to read from the SQLite table
|
|
153
153
|
* in a single batch during the initial sync between PowerSync and the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sources":["../../src/definitions.ts"],"sourcesContent":["import type { AbstractPowerSyncDatabase, Table } from '@powersync/common'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type {\n BaseCollectionConfig,\n CleanupFn,\n CollectionConfig,\n InferSchemaOutput,\n LoadSubsetOptions,\n} from '@tanstack/db'\nimport type {\n AnyTableColumnType,\n ExtractedTable,\n OptionalExtractedTable,\n PowerSyncRecord,\n} from './helpers'\n\n/**\n * Small helper which determines the output type if:\n * - Standard SQLite types are to be used OR\n * - If the provided schema should be used.\n */\nexport type InferPowerSyncOutputType<\n TTable extends Table = Table,\n TSchema extends StandardSchemaV1<PowerSyncRecord> = never,\n> = TSchema extends never ? ExtractedTable<TTable> : InferSchemaOutput<TSchema>\n\n/**\n * A mapping type for custom serialization of object properties to SQLite-compatible values.\n *\n * This type allows you to override, for keys in the input object (`TOutput`), a function that transforms\n * the value to the corresponding SQLite type (`TSQLite`). Keys not specified will use the default SQLite serialization.\n *\n * ## Generics\n * - `TOutput`: The input object type, representing the row data to be serialized.\n * - `TSQLite`: The target SQLite-compatible type for each property, typically inferred from the table schema.\n *\n * ## Usage\n * Use this type to define a map of serialization functions for specific keys when you need custom handling\n * (e.g., converting complex objects, formatting dates, or handling enums).\n *\n * Example:\n * ```ts\n * const serializer: CustomSQLiteSerializer<MyRowType, MySQLiteType> = {\n * createdAt: (date) => date.toISOString(),\n * status: (status) => status ? 1 : 0,\n * meta: (meta) => JSON.stringify(meta),\n * };\n * ```\n *\n * ## Behavior\n * - Each key maps to a function that receives the value and returns the SQLite-compatible value.\n * - Used by `serializeForSQLite` to override default serialization for specific columns.\n */\nexport type CustomSQLiteSerializer<\n TOutput extends Record<string, unknown>,\n TSQLite extends Record<string, unknown>,\n> = Partial<{\n [Key in keyof TOutput]: (\n value: TOutput[Key],\n ) => Key extends keyof TSQLite ? TSQLite[Key] : never\n}>\n\nexport type SerializerConfig<\n TOutput extends Record<string, unknown>,\n TSQLite extends Record<string, unknown>,\n> = {\n /**\n * Optional partial serializer object for customizing how individual columns are serialized for SQLite.\n *\n * This should be a partial map of column keys to serialization functions, following the\n * {@link CustomSQLiteSerializer} type. Each function receives the column value and returns a value\n * compatible with SQLite storage.\n *\n * If not provided for a column, the default behavior is used:\n * - `TEXT`: Strings are stored as-is; Dates are converted to ISO strings; other types are JSON-stringified.\n * - `INTEGER`/`REAL`: Numbers are stored as-is; booleans are mapped to 1/0.\n *\n * Use this option to override serialization for specific columns, such as formatting dates, handling enums,\n * or serializing complex objects.\n *\n * Example:\n * ```typescript\n * serializer: {\n * createdAt: (date) => date.getTime(), // Store as timestamp\n * meta: (meta) => JSON.stringify(meta), // Custom object serialization\n * }\n * ```\n */\n serializer?: CustomSQLiteSerializer<TOutput, TSQLite>\n\n /**\n * Application logic should ensure that incoming synced data is always valid.\n * Failing to deserialize and apply incoming changes results in data inconsistency - which is a fatal error.\n * Use this callback to react to deserialization errors.\n */\n onDeserializationError: (error: StandardSchemaV1.FailureResult) => void\n}\n\n/**\n * Config for when TInput and TOutput are both the SQLite types.\n */\nexport type ConfigWithSQLiteTypes = {}\n\n/**\n * Config where TInput is the SQLite types while TOutput can be defined by TSchema.\n * We can use the same schema to validate TInput and incoming SQLite changes.\n */\nexport type ConfigWithSQLiteInputType<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // TInput is the SQLite types.\n OptionalExtractedTable<TTable>,\n AnyTableColumnType<TTable>\n >,\n> = SerializerConfig<\n StandardSchemaV1.InferOutput<TSchema>,\n ExtractedTable<TTable>\n> & {\n schema: TSchema\n}\n\n/**\n * Config where TInput and TOutput have arbitrarily typed values.\n * The keys of the types need to equal the SQLite types.\n * Since TInput is not the SQLite types, we require a schema in order to deserialize incoming SQLite updates. The schema should validate from SQLite to TOutput.\n */\nexport type ConfigWithArbitraryCollectionTypes<\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> = SerializerConfig<\n StandardSchemaV1.InferOutput<TSchema>,\n ExtractedTable<TTable>\n> & {\n schema: TSchema\n /**\n * Schema for deserializing and validating input data from the sync stream.\n *\n * This schema defines how to transform and validate data coming from SQLite types (as stored in the database)\n * into the desired output types (`TOutput`) expected by your application or validation logic.\n *\n * The generic parameters allow for arbitrary input and output types, so you can specify custom conversion rules\n * for each column. This is especially useful when your application expects richer types (e.g., Date, enums, objects)\n * than what SQLite natively supports.\n *\n * Use this to ensure that incoming data from the sync stream is properly converted and validated before use.\n *\n * Example:\n * ```typescript\n * deserializationSchema: z.object({\n * createdAt: z.preprocess((val) => new Date(val as string), z.date()),\n * meta: z.preprocess((val) => JSON.parse(val as string), z.object({ ... })),\n * })\n * ```\n *\n * This enables robust type safety and validation for incoming data, bridging the gap between SQLite storage\n * and your application's expected types.\n */\n deserializationSchema: StandardSchemaV1<\n ExtractedTable<TTable>,\n StandardSchemaV1.InferOutput<TSchema>\n >\n}\n/**\n * Eager sync mode hooks.\n * Called once when the collection sync starts and stops.\n */\nexport type EagerSyncHooks = {\n syncMode?: 'eager'\n /**\n * Called when the collection sync starts.\n * Use this to set up external data sources (e.g. subscribing to a sync stream).\n *\n * @returns A cleanup function that is called when the collection sync is cleaned up.\n */\n onLoad?: () => CleanupFn | void | Promise<CleanupFn | void>\n onLoadSubset?: never\n}\n\n/**\n * On-demand sync mode hooks.\n * Called each time a subset is loaded or unloaded in response to live query changes.\n */\nexport type OnDemandSyncHooks = {\n syncMode: 'on-demand'\n onLoad?: never\n /**\n * Called when a subset of data is requested by a live query.\n * Use this to set up external data sources for the requested subset\n * (e.g. subscribing to a sync stream with parameters derived from the query predicate).\n *\n * @returns A cleanup function that is called when the subset is unloaded.\n */\n\n onLoadSubset?: (\n options: LoadSubsetOptions,\n ) => CleanupFn | void | Promise<CleanupFn | void>\n}\n\nexport type BasePowerSyncCollectionConfig<\n TTable extends Table = Table,\n TSchema extends StandardSchemaV1 = never,\n> = Omit<\n BaseCollectionConfig<ExtractedTable<TTable>, string, TSchema>,\n `onInsert` | `onUpdate` | `onDelete` | `getKey` | `syncMode`\n> & {\n /** The PowerSync schema Table definition */\n table: TTable\n /** The PowerSync database instance */\n database: AbstractPowerSyncDatabase\n /**\n * The maximum number of documents to read from the SQLite table\n * in a single batch during the initial sync between PowerSync and the\n * in-memory TanStack DB collection.\n *\n * @remarks\n * - Defaults to {@link DEFAULT_BATCH_SIZE} if not specified.\n * - Larger values reduce the number of round trips to the storage\n * engine but increase memory usage per batch.\n * - Smaller values may lower memory usage and allow earlier\n * streaming of initial results, at the cost of more query calls.\n */\n syncBatchSize?: number\n} & (EagerSyncHooks | OnDemandSyncHooks)\n\n/**\n * Configuration interface for PowerSync collection options.\n * @template TTable - The PowerSync table schema definition\n * @template TSchema - The validation schema type\n */\n/**\n * Configuration options for creating a PowerSync collection.\n *\n * @example\n * ```typescript\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * }),\n * })\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 type PowerSyncCollectionConfig<\n TTable extends Table = Table,\n TSchema extends StandardSchemaV1<any> = never,\n> = BasePowerSyncCollectionConfig<TTable, TSchema> &\n (\n | ConfigWithSQLiteTypes\n | ConfigWithSQLiteInputType<TTable, TSchema>\n | ConfigWithArbitraryCollectionTypes<TTable, TSchema>\n )\n\n/**\n * Metadata for the PowerSync Collection.\n */\nexport type PowerSyncCollectionMeta<TTable extends Table = Table> = {\n /**\n * The SQLite table representing the collection.\n */\n tableName: string\n /**\n * The internal table used to track diffs for the collection.\n */\n trackedTableName: string\n\n /**\n * Serializes a collection value to the SQLite type\n */\n serializeValue: (value: any) => ExtractedTable<TTable>\n\n /**\n * Whether the PowerSync table tracks metadata.\n */\n metadataIsTracked: boolean\n}\n\n/**\n * A CollectionConfig which includes utilities for PowerSync.\n */\nexport type EnhancedPowerSyncCollectionConfig<\n TTable extends Table,\n OutputType extends Record<string, unknown> = Record<string, unknown>,\n TSchema extends StandardSchemaV1 = never,\n> = CollectionConfig<\n OutputType,\n string,\n TSchema,\n PowerSyncCollectionUtils<TTable>\n> & {\n id?: string\n utils: PowerSyncCollectionUtils<TTable>\n schema?: TSchema\n}\n\n/**\n * Collection-level utilities for PowerSync.\n */\nexport type PowerSyncCollectionUtils<TTable extends Table = Table> = {\n getMeta: () => PowerSyncCollectionMeta<TTable>\n}\n\n/**\n * Default value for {@link PowerSyncCollectionConfig#syncBatchSize}.\n */\nexport const DEFAULT_BATCH_SIZE = 1000\n"],"names":[],"mappings":"AAiUO,MAAM,qBAAqB;"}
|
|
1
|
+
{"version":3,"file":"definitions.js","sources":["../../src/definitions.ts"],"sourcesContent":["import type { CommonPowerSyncDatabase, Table } from '@powersync/common'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type {\n BaseCollectionConfig,\n CleanupFn,\n CollectionConfig,\n InferSchemaOutput,\n LoadSubsetOptions,\n} from '@tanstack/db'\nimport type {\n AnyTableColumnType,\n ExtractedTable,\n OptionalExtractedTable,\n PowerSyncRecord,\n} from './helpers'\n\n/**\n * Small helper which determines the output type if:\n * - Standard SQLite types are to be used OR\n * - If the provided schema should be used.\n */\nexport type InferPowerSyncOutputType<\n TTable extends Table = Table,\n TSchema extends StandardSchemaV1<PowerSyncRecord> = never,\n> = [TSchema] extends [never]\n ? ExtractedTable<TTable>\n : InferSchemaOutput<TSchema>\n\n/**\n * A mapping type for custom serialization of object properties to SQLite-compatible values.\n *\n * This type allows you to override, for keys in the input object (`TOutput`), a function that transforms\n * the value to the corresponding SQLite type (`TSQLite`). Keys not specified will use the default SQLite serialization.\n *\n * ## Generics\n * - `TOutput`: The input object type, representing the row data to be serialized.\n * - `TSQLite`: The target SQLite-compatible type for each property, typically inferred from the table schema.\n *\n * ## Usage\n * Use this type to define a map of serialization functions for specific keys when you need custom handling\n * (e.g., converting complex objects, formatting dates, or handling enums).\n *\n * Example:\n * ```ts\n * const serializer: CustomSQLiteSerializer<MyRowType, MySQLiteType> = {\n * createdAt: (date) => date.toISOString(),\n * status: (status) => status ? 1 : 0,\n * meta: (meta) => JSON.stringify(meta),\n * };\n * ```\n *\n * ## Behavior\n * - Each key maps to a function that receives the value and returns the SQLite-compatible value.\n * - Used by `serializeForSQLite` to override default serialization for specific columns.\n */\nexport type CustomSQLiteSerializer<\n TOutput extends Record<string, unknown>,\n TSQLite extends Record<string, unknown>,\n> = Partial<{\n [Key in keyof TOutput]: (\n value: TOutput[Key],\n ) => Key extends keyof TSQLite ? TSQLite[Key] : never\n}>\n\nexport type SerializerConfig<\n TOutput extends Record<string, unknown>,\n TSQLite extends Record<string, unknown>,\n> = {\n /**\n * Optional partial serializer object for customizing how individual columns are serialized for SQLite.\n *\n * This should be a partial map of column keys to serialization functions, following the\n * {@link CustomSQLiteSerializer} type. Each function receives the column value and returns a value\n * compatible with SQLite storage.\n *\n * If not provided for a column, the default behavior is used:\n * - `TEXT`: Strings are stored as-is; Dates are converted to ISO strings; other types are JSON-stringified.\n * - `INTEGER`/`REAL`: Numbers are stored as-is; booleans are mapped to 1/0.\n *\n * Use this option to override serialization for specific columns, such as formatting dates, handling enums,\n * or serializing complex objects.\n *\n * Example:\n * ```typescript\n * serializer: {\n * createdAt: (date) => date.getTime(), // Store as timestamp\n * meta: (meta) => JSON.stringify(meta), // Custom object serialization\n * }\n * ```\n */\n serializer?: CustomSQLiteSerializer<TOutput, TSQLite>\n\n /**\n * Application logic should ensure that incoming synced data is always valid.\n * Failing to deserialize and apply incoming changes results in data inconsistency - which is a fatal error.\n * Use this callback to react to deserialization errors.\n */\n onDeserializationError: (error: StandardSchemaV1.FailureResult) => void\n}\n\n/**\n * Config for when TInput and TOutput are both the SQLite types.\n */\nexport type ConfigWithSQLiteTypes = {}\n\n/**\n * Config where TInput is the SQLite types while TOutput can be defined by TSchema.\n * We can use the same schema to validate TInput and incoming SQLite changes.\n */\nexport type ConfigWithSQLiteInputType<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // TInput is the SQLite types.\n OptionalExtractedTable<TTable>,\n AnyTableColumnType<TTable>\n >,\n> = SerializerConfig<\n StandardSchemaV1.InferOutput<TSchema>,\n ExtractedTable<TTable>\n> & {\n schema: TSchema\n}\n\n/**\n * Config where TInput and TOutput have arbitrarily typed values.\n * The keys of the types need to equal the SQLite types.\n * Since TInput is not the SQLite types, we require a schema in order to deserialize incoming SQLite updates. The schema should validate from SQLite to TOutput.\n */\nexport type ConfigWithArbitraryCollectionTypes<\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> = SerializerConfig<\n StandardSchemaV1.InferOutput<TSchema>,\n ExtractedTable<TTable>\n> & {\n schema: TSchema\n /**\n * Schema for deserializing and validating input data from the sync stream.\n *\n * This schema defines how to transform and validate data coming from SQLite types (as stored in the database)\n * into the desired output types (`TOutput`) expected by your application or validation logic.\n *\n * The generic parameters allow for arbitrary input and output types, so you can specify custom conversion rules\n * for each column. This is especially useful when your application expects richer types (e.g., Date, enums, objects)\n * than what SQLite natively supports.\n *\n * Use this to ensure that incoming data from the sync stream is properly converted and validated before use.\n *\n * Example:\n * ```typescript\n * deserializationSchema: z.object({\n * createdAt: z.preprocess((val) => new Date(val as string), z.date()),\n * meta: z.preprocess((val) => JSON.parse(val as string), z.object({ ... })),\n * })\n * ```\n *\n * This enables robust type safety and validation for incoming data, bridging the gap between SQLite storage\n * and your application's expected types.\n */\n deserializationSchema: StandardSchemaV1<\n ExtractedTable<TTable>,\n StandardSchemaV1.InferOutput<TSchema>\n >\n}\n/**\n * Eager sync mode hooks.\n * Called once when the collection sync starts and stops.\n */\nexport type EagerSyncHooks = {\n syncMode?: 'eager'\n /**\n * Called when the collection sync starts.\n * Use this to set up external data sources (e.g. subscribing to a sync stream).\n *\n * @returns A cleanup function that is called when the collection sync is cleaned up.\n */\n onLoad?: () => CleanupFn | void | Promise<CleanupFn | void>\n onLoadSubset?: never\n}\n\n/**\n * On-demand sync mode hooks.\n * Called each time a subset is loaded or unloaded in response to live query changes.\n */\nexport type OnDemandSyncHooks = {\n syncMode: 'on-demand'\n onLoad?: never\n /**\n * Called when a subset of data is requested by a live query.\n * Use this to set up external data sources for the requested subset\n * (e.g. subscribing to a sync stream with parameters derived from the query predicate).\n *\n * @returns A cleanup function that is called when the subset is unloaded.\n */\n\n onLoadSubset?: (\n options: LoadSubsetOptions,\n ) => CleanupFn | void | Promise<CleanupFn | void>\n}\n\nexport type BasePowerSyncCollectionConfig<\n TTable extends Table = Table,\n TSchema extends StandardSchemaV1<any> = never,\n> = Omit<\n BaseCollectionConfig<\n InferPowerSyncOutputType<TTable, TSchema>,\n string,\n TSchema\n >,\n `onInsert` | `onUpdate` | `onDelete` | `getKey` | `syncMode`\n> & {\n /** The PowerSync schema Table definition */\n table: TTable\n /** The PowerSync database instance */\n database: CommonPowerSyncDatabase\n /**\n * The maximum number of documents to read from the SQLite table\n * in a single batch during the initial sync between PowerSync and the\n * in-memory TanStack DB collection.\n *\n * @remarks\n * - Defaults to {@link DEFAULT_BATCH_SIZE} if not specified.\n * - Larger values reduce the number of round trips to the storage\n * engine but increase memory usage per batch.\n * - Smaller values may lower memory usage and allow earlier\n * streaming of initial results, at the cost of more query calls.\n */\n syncBatchSize?: number\n} & (EagerSyncHooks | OnDemandSyncHooks)\n\n/**\n * Configuration interface for PowerSync collection options.\n * @template TTable - The PowerSync table schema definition\n * @template TSchema - The validation schema type\n */\n/**\n * Configuration options for creating a PowerSync collection.\n *\n * @example\n * ```typescript\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * }),\n * })\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 type PowerSyncCollectionConfig<\n TTable extends Table = Table,\n TSchema extends StandardSchemaV1<any> = never,\n> = BasePowerSyncCollectionConfig<TTable, TSchema> &\n (\n | ConfigWithSQLiteTypes\n | ConfigWithSQLiteInputType<TTable, TSchema>\n | ConfigWithArbitraryCollectionTypes<TTable, TSchema>\n )\n\n/**\n * Metadata for the PowerSync Collection.\n */\nexport type PowerSyncCollectionMeta<TTable extends Table = Table> = {\n /**\n * The SQLite table representing the collection.\n */\n tableName: string\n /**\n * The internal table used to track diffs for the collection.\n */\n trackedTableName: string\n\n /**\n * Serializes a collection value to the SQLite type\n */\n serializeValue: (value: any) => ExtractedTable<TTable>\n\n /**\n * Whether the PowerSync table tracks metadata.\n */\n metadataIsTracked: boolean\n}\n\n/**\n * A CollectionConfig which includes utilities for PowerSync.\n */\nexport type EnhancedPowerSyncCollectionConfig<\n TTable extends Table,\n OutputType extends Record<string, unknown> = Record<string, unknown>,\n TSchema extends StandardSchemaV1 = never,\n> = CollectionConfig<\n OutputType,\n string,\n TSchema,\n PowerSyncCollectionUtils<TTable>\n> & {\n id?: string\n utils: PowerSyncCollectionUtils<TTable>\n schema?: TSchema\n}\n\n/**\n * Collection-level utilities for PowerSync.\n */\nexport type PowerSyncCollectionUtils<TTable extends Table = Table> = {\n getMeta: () => PowerSyncCollectionMeta<TTable>\n}\n\n/**\n * Default value for {@link PowerSyncCollectionConfig#syncBatchSize}.\n */\nexport const DEFAULT_BATCH_SIZE = 1000\n"],"names":[],"mappings":"AAuUO,MAAM,qBAAqB;"}
|