@tanstack/powersync-db-collection 0.1.14 → 0.1.16
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/PendingOperationStore.cjs.map +1 -1
- package/dist/cjs/PowerSyncTransactor.cjs.map +1 -1
- package/dist/cjs/definitions.cjs.map +1 -1
- package/dist/cjs/helpers.cjs.map +1 -1
- package/dist/cjs/powersync.cjs.map +1 -1
- package/dist/cjs/schema.cjs.map +1 -1
- package/dist/cjs/serialization.cjs.map +1 -1
- package/dist/esm/PendingOperationStore.js.map +1 -1
- package/dist/esm/PowerSyncTransactor.js.map +1 -1
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/helpers.js.map +1 -1
- package/dist/esm/powersync.js.map +1 -1
- package/dist/esm/schema.js.map +1 -1
- package/dist/esm/serialization.js.map +1 -1
- package/package.json +39 -39
- package/src/PendingOperationStore.ts +3 -3
- package/src/PowerSyncTransactor.ts +28 -28
- package/src/definitions.ts +5 -5
- package/src/helpers.ts +2 -2
- package/src/index.ts +3 -3
- package/src/powersync.ts +28 -28
- package/src/schema.ts +7 -7
- package/src/serialization.ts +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PendingOperationStore.cjs","sources":["../../src/PendingOperationStore.ts"],"sourcesContent":["import pDefer from
|
|
1
|
+
{"version":3,"file":"PendingOperationStore.cjs","sources":["../../src/PendingOperationStore.ts"],"sourcesContent":["import pDefer from 'p-defer'\nimport type { DiffTriggerOperation } from '@powersync/common'\nimport type { DeferredPromise } from 'p-defer'\n\nexport type PendingOperation = {\n tableName: string\n operation: DiffTriggerOperation\n id: string\n timestamp: string\n}\n\n/**\n * Optimistic mutations have their optimistic state discarded once transactions have\n * been applied.\n * We need to ensure that an applied transaction has been observed by the sync diff trigger\n * before resolving the transaction application call.\n * This store allows registering a wait for a pending operation to have been observed.\n */\nexport class PendingOperationStore {\n private pendingOperations = new Map<PendingOperation, DeferredPromise<void>>()\n\n /**\n * Globally accessible PendingOperationStore\n */\n static GLOBAL = new PendingOperationStore()\n\n /**\n * @returns A promise which will resolve once the specified operation has been seen.\n */\n waitFor(operation: PendingOperation): Promise<void> {\n const managedPromise = pDefer<void>()\n this.pendingOperations.set(operation, managedPromise)\n return managedPromise.promise\n }\n\n /**\n * Marks a set of operations as seen. This will resolve any pending promises.\n */\n resolvePendingFor(operations: Array<PendingOperation>) {\n for (const operation of operations) {\n for (const [pendingOp, deferred] of this.pendingOperations.entries()) {\n if (\n pendingOp.tableName == operation.tableName &&\n pendingOp.operation == operation.operation &&\n pendingOp.id == operation.id &&\n pendingOp.timestamp == operation.timestamp\n ) {\n deferred.resolve()\n this.pendingOperations.delete(pendingOp)\n }\n }\n }\n }\n}\n"],"names":[],"mappings":";;;AAkBO,MAAM,yBAAN,MAAM,uBAAsB;AAAA,EAA5B,cAAA;AACL,SAAQ,wCAAwB,IAAA;AAAA,EAA6C;AAAA;AAAA;AAAA;AAAA,EAU7E,QAAQ,WAA4C;AAClD,UAAM,iBAAiB,OAAA;AACvB,SAAK,kBAAkB,IAAI,WAAW,cAAc;AACpD,WAAO,eAAe;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,YAAqC;AACrD,eAAW,aAAa,YAAY;AAClC,iBAAW,CAAC,WAAW,QAAQ,KAAK,KAAK,kBAAkB,WAAW;AACpE,YACE,UAAU,aAAa,UAAU,aACjC,UAAU,aAAa,UAAU,aACjC,UAAU,MAAM,UAAU,MAC1B,UAAU,aAAa,UAAU,WACjC;AACA,mBAAS,QAAA;AACT,eAAK,kBAAkB,OAAO,SAAS;AAAA,QACzC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AA7BE,uBAAO,SAAS,IAAI,uBAAA;AANf,IAAM,wBAAN;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PowerSyncTransactor.cjs","sources":["../../src/PowerSyncTransactor.ts"],"sourcesContent":["import { sanitizeSQL } from \"@powersync/common\"\nimport DebugModule from \"debug\"\nimport { asPowerSyncRecord, mapOperationToPowerSync } from \"./helpers\"\nimport { PendingOperationStore } from \"./PendingOperationStore\"\nimport type { AbstractPowerSyncDatabase, LockContext } from \"@powersync/common\"\nimport type { PendingMutation, Transaction } from \"@tanstack/db\"\nimport type { EnhancedPowerSyncCollectionConfig } from \"./definitions\"\nimport type { PendingOperation } from \"./PendingOperationStore\"\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 await new Promise<void>((resolve) => collection.onFirstReady(resolve))\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 async (tableName, mutation, serializeValue) => {\n const values = serializeValue(mutation.modified)\n const keys = Object.keys(values).map((key) => sanitizeSQL`${key}`)\n\n await context.execute(\n `\n INSERT into ${tableName} \n (${keys.join(`, `)}) \n VALUES \n (${keys.map((_) => `?`).join(`, `)})\n `,\n Object.values(values)\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 async (tableName, mutation, serializeValue) => {\n const values = serializeValue(mutation.modified)\n const keys = Object.keys(values).map((key) => sanitizeSQL`${key}`)\n\n await context.execute(\n `\n UPDATE ${tableName} \n SET ${keys.map((key) => `${key} = ?`).join(`, `)}\n WHERE id = ?\n `,\n [...Object.values(values), 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 async (tableName, mutation) => {\n await context.execute(\n `\n DELETE FROM ${tableName} WHERE id = ?\n `,\n [asPowerSyncRecord(mutation.original).id]\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 if (\n typeof (mutation.collection.config as any).utils?.getMeta != `function`\n ) {\n throw new Error(`Could not get tableName from mutation's collection config.\n The provided mutation might not have originated from PowerSync.`)\n }\n\n const { tableName, trackedTableName, serializeValue } = (\n mutation.collection\n .config as unknown as EnhancedPowerSyncCollectionConfig<any>\n ).utils.getMeta()\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 timestamp 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"],"names":["PendingOperationStore","mutation","sanitizeSQL","asPowerSyncRecord","mapOperationToPowerSync"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PowerSyncTransactor.cjs","sources":["../../src/PowerSyncTransactor.ts"],"sourcesContent":["import { sanitizeSQL } from '@powersync/common'\nimport DebugModule from 'debug'\nimport { asPowerSyncRecord, mapOperationToPowerSync } from './helpers'\nimport { PendingOperationStore } from './PendingOperationStore'\nimport type { AbstractPowerSyncDatabase, LockContext } from '@powersync/common'\nimport type { PendingMutation, Transaction } from '@tanstack/db'\nimport type { EnhancedPowerSyncCollectionConfig } from './definitions'\nimport type { PendingOperation } from './PendingOperationStore'\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 await new Promise<void>((resolve) => collection.onFirstReady(resolve))\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 async (tableName, mutation, serializeValue) => {\n const values = serializeValue(mutation.modified)\n const keys = Object.keys(values).map((key) => sanitizeSQL`${key}`)\n\n await context.execute(\n `\n INSERT into ${tableName} \n (${keys.join(`, `)}) \n VALUES \n (${keys.map((_) => `?`).join(`, `)})\n `,\n Object.values(values),\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 async (tableName, mutation, serializeValue) => {\n const values = serializeValue(mutation.modified)\n const keys = Object.keys(values).map((key) => sanitizeSQL`${key}`)\n\n await context.execute(\n `\n UPDATE ${tableName} \n SET ${keys.map((key) => `${key} = ?`).join(`, `)}\n WHERE id = ?\n `,\n [...Object.values(values), 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 async (tableName, mutation) => {\n await context.execute(\n `\n DELETE FROM ${tableName} WHERE id = ?\n `,\n [asPowerSyncRecord(mutation.original).id],\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 if (\n typeof (mutation.collection.config as any).utils?.getMeta != `function`\n ) {\n throw new Error(`Could not get tableName from mutation's collection config.\n The provided mutation might not have originated from PowerSync.`)\n }\n\n const { tableName, trackedTableName, serializeValue } = (\n mutation.collection\n .config as unknown as EnhancedPowerSyncCollectionConfig<any>\n ).utils.getMeta()\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 timestamp 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"],"names":["PendingOperationStore","mutation","sanitizeSQL","asPowerSyncRecord","mapOperationToPowerSync"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.cjs","sources":["../../src/definitions.ts"],"sourcesContent":["import type { AbstractPowerSyncDatabase, Table } from
|
|
1
|
+
{"version":3,"file":"definitions.cjs","sources":["../../src/definitions.ts"],"sourcesContent":["import type { AbstractPowerSyncDatabase, Table } from '@powersync/common'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type {\n BaseCollectionConfig,\n CollectionConfig,\n InferSchemaOutput,\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}\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`\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}\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/**\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<OutputType, string, TSchema> & {\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":";;AAiRO,MAAM,qBAAqB;;"}
|
package/dist/cjs/helpers.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.cjs","sources":["../../src/helpers.ts"],"sourcesContent":["import { DiffTriggerOperation } from
|
|
1
|
+
{"version":3,"file":"helpers.cjs","sources":["../../src/helpers.ts"],"sourcesContent":["import { DiffTriggerOperation } from '@powersync/common'\nimport type {\n BaseColumnType,\n ExtractColumnValueType,\n Table,\n} from '@powersync/common'\n\n/**\n * All PowerSync table records include a UUID `id` column.\n */\nexport type PowerSyncRecord = {\n id: string\n [key: string]: unknown\n}\n\n/**\n * Utility type: If T includes null, also allow undefined (to support optional fields in insert/update operations).\n * PowerSync records are typically typed as `string | null`, where insert\n * and update operations may also allow not specifying a value at all (optional).\n */\ntype WithUndefinedIfNull<T> = null extends T ? T | undefined : T\ntype OptionalIfUndefined<T> = {\n [K in keyof T as undefined extends T[K] ? K : never]?: T[K]\n} & {\n [K in keyof T as undefined extends T[K] ? never : K]: T[K]\n}\n\n/**\n * Provides the base column types for a table. This excludes the `id` column.\n */\nexport type ExtractedTableColumns<TTable extends Table> = {\n [K in keyof TTable[`columnMap`]]: ExtractColumnValueType<\n TTable[`columnMap`][K]\n >\n}\n/**\n * Utility type that extracts the typed structure of a table based on its column definitions.\n * Maps each column to its corresponding TypeScript type using ExtractColumnValueType.\n *\n * @template TTable - The PowerSync table definition\n * @example\n * ```typescript\n * const table = new Table({\n * name: column.text,\n * age: column.integer\n * })\n * type TableType = ExtractedTable<typeof table>\n * // Results in: { id: string, name: string | null, age: number | null }\n * ```\n */\nexport type ExtractedTable<TTable extends Table> =\n ExtractedTableColumns<TTable> & {\n id: string\n }\n\nexport type OptionalExtractedTable<TTable extends Table> = OptionalIfUndefined<{\n [K in keyof TTable[`columnMap`]]: WithUndefinedIfNull<\n ExtractColumnValueType<TTable[`columnMap`][K]>\n >\n}> & {\n id: string\n}\n\n/**\n * Maps the schema of TTable to a type which\n * requires the keys be equal, but the values can have any value type.\n */\nexport type AnyTableColumnType<TTable extends Table> = {\n [K in keyof TTable[`columnMap`]]: any\n} & { id: string }\n\nexport function asPowerSyncRecord(record: any): PowerSyncRecord {\n if (typeof record.id !== `string`) {\n throw new Error(`Record must have a string id field`)\n }\n return record as PowerSyncRecord\n}\n\n// Helper type to ensure the keys of TOutput match the Table columns\nexport type MapBaseColumnType<TOutput> = {\n [Key in keyof TOutput]: BaseColumnType<any>\n}\n\n/**\n * Maps {@link DiffTriggerOperation} to TanstackDB operations\n */\nexport function mapOperation(operation: DiffTriggerOperation) {\n switch (operation) {\n case DiffTriggerOperation.INSERT:\n return `insert`\n case DiffTriggerOperation.UPDATE:\n return `update`\n case DiffTriggerOperation.DELETE:\n return `delete`\n }\n}\n\n/**\n * Maps TanstackDB operations to {@link DiffTriggerOperation}\n */\nexport function mapOperationToPowerSync(operation: string) {\n switch (operation) {\n case `insert`:\n return DiffTriggerOperation.INSERT\n case `update`:\n return DiffTriggerOperation.UPDATE\n case `delete`:\n return DiffTriggerOperation.DELETE\n default:\n throw new Error(`Unknown operation ${operation} received`)\n }\n}\n"],"names":["DiffTriggerOperation"],"mappings":";;;AAuEO,SAAS,kBAAkB,QAA8B;AAC9D,MAAI,OAAO,OAAO,OAAO,UAAU;AACjC,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACtD;AACA,SAAO;AACT;AAUO,SAAS,aAAa,WAAiC;AAC5D,UAAQ,WAAA;AAAA,IACN,KAAKA,OAAAA,qBAAqB;AACxB,aAAO;AAAA,IACT,KAAKA,OAAAA,qBAAqB;AACxB,aAAO;AAAA,IACT,KAAKA,OAAAA,qBAAqB;AACxB,aAAO;AAAA,EAAA;AAEb;AAKO,SAAS,wBAAwB,WAAmB;AACzD,UAAQ,WAAA;AAAA,IACN,KAAK;AACH,aAAOA,OAAAA,qBAAqB;AAAA,IAC9B,KAAK;AACH,aAAOA,OAAAA,qBAAqB;AAAA,IAC9B,KAAK;AACH,aAAOA,OAAAA,qBAAqB;AAAA,IAC9B;AACE,YAAM,IAAI,MAAM,qBAAqB,SAAS,WAAW;AAAA,EAAA;AAE/D;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"powersync.cjs","sources":["../../src/powersync.ts"],"sourcesContent":["import { DiffTriggerOperation, sanitizeSQL } from \"@powersync/common\"\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 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 { SyncConfig } from \"@tanstack/db\"\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\"\nimport type { 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>(config: PowerSyncCollectionConfig<TTable, TSchema>) {\n const {\n database,\n table,\n schema: inputSchema,\n syncBatchSize = DEFAULT_BATCH_SIZE,\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 } = 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, commit, markReady } = params\n const abortController = new AbortController()\n\n // The sync function needs to be synchronous\n async function start() {\n database.logger.info(\n `Sync is starting for ${viewName} into ${trackedTableName}`\n )\n database.onChangeWithCallback(\n {\n onChange: async () => {\n await database\n .writeTransaction(async (context) => {\n begin()\n const operations = await context.getAll<TriggerDiffRecord>(\n `SELECT * FROM ${trackedTableName} ORDER BY timestamp ASC`\n )\n const pendingOperations: Array<PendingOperation> = []\n\n for (const op of operations) {\n const { id, operation, timestamp, value } = op\n const parsedValue = deserializeSyncRow({\n id,\n ...JSON.parse(value),\n })\n const parsedPreviousValue =\n op.operation == DiffTriggerOperation.UPDATE\n ? deserializeSyncRow({\n id,\n ...JSON.parse(op.previous_value),\n })\n : undefined\n write({\n type: mapOperation(operation),\n value: parsedValue,\n previousValue: parsedPreviousValue,\n })\n pendingOperations.push({\n id,\n operation,\n timestamp,\n tableName: viewName,\n })\n }\n\n // clear the current operations\n await context.execute(`DELETE FROM ${trackedTableName}`)\n\n commit()\n pendingOperationStore.resolvePendingFor(pendingOperations)\n })\n .catch((error) => {\n database.logger.error(\n `An error has been detected in the sync handler`,\n error\n )\n })\n },\n },\n {\n signal: abortController.signal,\n triggerImmediate: false,\n tables: [trackedTableName],\n }\n )\n\n const disposeTracking = await database.triggers.createDiffTrigger({\n source: viewName,\n destination: trackedTableName,\n when: {\n [DiffTriggerOperation.INSERT]: `TRUE`,\n [DiffTriggerOperation.UPDATE]: `TRUE`,\n [DiffTriggerOperation.DELETE]: `TRUE`,\n },\n hooks: {\n beforeCreate: async (context) => {\n let currentBatchCount = syncBatchSize\n let cursor = 0\n while (currentBatchCount == syncBatchSize) {\n begin()\n const batchItems = await context.getAll<TableType>(\n sanitizeSQL`SELECT * FROM ${viewName} LIMIT ? OFFSET ?`,\n [syncBatchSize, cursor]\n )\n currentBatchCount = batchItems.length\n cursor += currentBatchCount\n for (const row of batchItems) {\n write({\n type: `insert`,\n value: deserializeSyncRow(row),\n })\n }\n commit()\n }\n markReady()\n database.logger.info(\n `Sync is ready for ${viewName} into ${trackedTableName}`\n )\n },\n },\n })\n\n // If the abort controller was aborted while processing the request above\n if (abortController.signal.aborted) {\n await disposeTracking()\n } else {\n abortController.signal.addEventListener(\n `abort`,\n () => {\n disposeTracking()\n },\n { once: true }\n )\n }\n }\n\n start().catch((error) =>\n database.logger.error(\n `Could not start syncing process for ${viewName} into ${trackedTableName}`,\n error\n )\n )\n\n return () => {\n database.logger.info(\n `Sync has been stopped for ${viewName} into ${trackedTableName}`\n )\n abortController.abort()\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 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 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":["DEFAULT_BATCH_SIZE","schema","convertTableToSchema","PendingOperationStore","PowerSyncTransactor","DiffTriggerOperation","mapOperation","sanitizeSQL","asPowerSyncRecord","serializeForSQLite"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"powersync.cjs","sources":["../../src/powersync.ts"],"sourcesContent":["import { DiffTriggerOperation, sanitizeSQL } from '@powersync/common'\nimport { 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 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 { SyncConfig } from '@tanstack/db'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { 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>(config: PowerSyncCollectionConfig<TTable, TSchema>) {\n const {\n database,\n table,\n schema: inputSchema,\n syncBatchSize = DEFAULT_BATCH_SIZE,\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 } = 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, commit, markReady } = params\n const abortController = new AbortController()\n\n // The sync function needs to be synchronous\n async function start() {\n database.logger.info(\n `Sync is starting for ${viewName} into ${trackedTableName}`,\n )\n database.onChangeWithCallback(\n {\n onChange: async () => {\n await database\n .writeTransaction(async (context) => {\n begin()\n const operations = await context.getAll<TriggerDiffRecord>(\n `SELECT * FROM ${trackedTableName} ORDER BY timestamp ASC`,\n )\n const pendingOperations: Array<PendingOperation> = []\n\n for (const op of operations) {\n const { id, operation, timestamp, value } = op\n const parsedValue = deserializeSyncRow({\n id,\n ...JSON.parse(value),\n })\n const parsedPreviousValue =\n op.operation == DiffTriggerOperation.UPDATE\n ? deserializeSyncRow({\n id,\n ...JSON.parse(op.previous_value),\n })\n : undefined\n write({\n type: mapOperation(operation),\n value: parsedValue,\n previousValue: parsedPreviousValue,\n })\n pendingOperations.push({\n id,\n operation,\n timestamp,\n tableName: viewName,\n })\n }\n\n // clear the current operations\n await context.execute(`DELETE FROM ${trackedTableName}`)\n\n commit()\n pendingOperationStore.resolvePendingFor(pendingOperations)\n })\n .catch((error) => {\n database.logger.error(\n `An error has been detected in the sync handler`,\n error,\n )\n })\n },\n },\n {\n signal: abortController.signal,\n triggerImmediate: false,\n tables: [trackedTableName],\n },\n )\n\n const disposeTracking = await database.triggers.createDiffTrigger({\n source: viewName,\n destination: trackedTableName,\n when: {\n [DiffTriggerOperation.INSERT]: `TRUE`,\n [DiffTriggerOperation.UPDATE]: `TRUE`,\n [DiffTriggerOperation.DELETE]: `TRUE`,\n },\n hooks: {\n beforeCreate: async (context) => {\n let currentBatchCount = syncBatchSize\n let cursor = 0\n while (currentBatchCount == syncBatchSize) {\n begin()\n const batchItems = await context.getAll<TableType>(\n sanitizeSQL`SELECT * FROM ${viewName} LIMIT ? OFFSET ?`,\n [syncBatchSize, cursor],\n )\n currentBatchCount = batchItems.length\n cursor += currentBatchCount\n for (const row of batchItems) {\n write({\n type: `insert`,\n value: deserializeSyncRow(row),\n })\n }\n commit()\n }\n markReady()\n database.logger.info(\n `Sync is ready for ${viewName} into ${trackedTableName}`,\n )\n },\n },\n })\n\n // If the abort controller was aborted while processing the request above\n if (abortController.signal.aborted) {\n await disposeTracking()\n } else {\n abortController.signal.addEventListener(\n `abort`,\n () => {\n disposeTracking()\n },\n { once: true },\n )\n }\n }\n\n start().catch((error) =>\n database.logger.error(\n `Could not start syncing process for ${viewName} into ${trackedTableName}`,\n error,\n ),\n )\n\n return () => {\n database.logger.info(\n `Sync has been stopped for ${viewName} into ${trackedTableName}`,\n )\n abortController.abort()\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 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 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":["DEFAULT_BATCH_SIZE","schema","convertTableToSchema","PendingOperationStore","PowerSyncTransactor","DiffTriggerOperation","mapOperation","sanitizeSQL","asPowerSyncRecord","serializeForSQLite"],"mappings":"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"}
|
package/dist/cjs/schema.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.cjs","sources":["../../src/schema.ts"],"sourcesContent":["import { ColumnType } from
|
|
1
|
+
{"version":3,"file":"schema.cjs","sources":["../../src/schema.ts"],"sourcesContent":["import { ColumnType } from '@powersync/common'\nimport type { Table } from '@powersync/common'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { ExtractedTable } from './helpers'\n\n/**\n * Converts a PowerSync Table instance to a StandardSchemaV1 schema.\n * Creates a schema that validates the structure and types of table records\n * according to the PowerSync table definition.\n *\n * @template TTable - The PowerSync schema-typed Table definition\n * @param table - The PowerSync Table instance to convert\n * @returns A StandardSchemaV1-compatible schema with proper type validation\n *\n * @example\n * ```typescript\n * const usersTable = new Table({\n * name: column.text,\n * age: column.integer\n * })\n * ```\n */\nexport function convertTableToSchema<TTable extends Table>(\n table: TTable,\n): StandardSchemaV1<ExtractedTable<TTable>> {\n type TExtracted = ExtractedTable<TTable>\n // Create validate function that checks types according to column definitions\n const validate = (\n value: unknown,\n ):\n | StandardSchemaV1.SuccessResult<TExtracted>\n | StandardSchemaV1.FailureResult => {\n if (typeof value != `object` || value == null) {\n return {\n issues: [\n {\n message: `Value must be an object`,\n },\n ],\n }\n }\n\n const issues: Array<StandardSchemaV1.Issue> = []\n\n // Check id field\n if (!(`id` in value) || typeof (value as any).id != `string`) {\n issues.push({\n message: `id field must be a string`,\n path: [`id`],\n })\n }\n\n // Check each column\n for (const column of table.columns) {\n const val = (value as TExtracted)[column.name as keyof TExtracted]\n\n if (val == null) {\n continue\n }\n\n switch (column.type) {\n case ColumnType.TEXT:\n if (typeof val != `string`) {\n issues.push({\n message: `${column.name} must be a string or null`,\n path: [column.name],\n })\n }\n break\n case ColumnType.INTEGER:\n case ColumnType.REAL:\n if (typeof val != `number`) {\n issues.push({\n message: `${column.name} must be a number or null`,\n path: [column.name],\n })\n }\n break\n }\n }\n\n if (issues.length > 0) {\n return { issues }\n }\n\n return { value: { ...value } as TExtracted }\n }\n\n return {\n '~standard': {\n version: 1,\n vendor: `powersync`,\n validate,\n types: {\n input: {} as TExtracted,\n output: {} as TExtracted,\n },\n },\n }\n}\n"],"names":["ColumnType"],"mappings":";;;AAsBO,SAAS,qBACd,OAC0C;AAG1C,QAAM,WAAW,CACf,UAGoC;AACpC,QAAI,OAAO,SAAS,YAAY,SAAS,MAAM;AAC7C,aAAO;AAAA,QACL,QAAQ;AAAA,UACN;AAAA,YACE,SAAS;AAAA,UAAA;AAAA,QACX;AAAA,MACF;AAAA,IAEJ;AAEA,UAAM,SAAwC,CAAA;AAG9C,QAAI,EAAE,QAAQ,UAAU,OAAQ,MAAc,MAAM,UAAU;AAC5D,aAAO,KAAK;AAAA,QACV,SAAS;AAAA,QACT,MAAM,CAAC,IAAI;AAAA,MAAA,CACZ;AAAA,IACH;AAGA,eAAW,UAAU,MAAM,SAAS;AAClC,YAAM,MAAO,MAAqB,OAAO,IAAwB;AAEjE,UAAI,OAAO,MAAM;AACf;AAAA,MACF;AAEA,cAAQ,OAAO,MAAA;AAAA,QACb,KAAKA,OAAAA,WAAW;AACd,cAAI,OAAO,OAAO,UAAU;AAC1B,mBAAO,KAAK;AAAA,cACV,SAAS,GAAG,OAAO,IAAI;AAAA,cACvB,MAAM,CAAC,OAAO,IAAI;AAAA,YAAA,CACnB;AAAA,UACH;AACA;AAAA,QACF,KAAKA,OAAAA,WAAW;AAAA,QAChB,KAAKA,OAAAA,WAAW;AACd,cAAI,OAAO,OAAO,UAAU;AAC1B,mBAAO,KAAK;AAAA,cACV,SAAS,GAAG,OAAO,IAAI;AAAA,cACvB,MAAM,CAAC,OAAO,IAAI;AAAA,YAAA,CACnB;AAAA,UACH;AACA;AAAA,MAAA;AAAA,IAEN;AAEA,QAAI,OAAO,SAAS,GAAG;AACrB,aAAO,EAAE,OAAA;AAAA,IACX;AAEA,WAAO,EAAE,OAAO,EAAE,GAAG,QAAM;AAAA,EAC7B;AAEA,SAAO;AAAA,IACL,aAAa;AAAA,MACX,SAAS;AAAA,MACT,QAAQ;AAAA,MACR;AAAA,MACA,OAAO;AAAA,QACL,OAAO,CAAA;AAAA,QACP,QAAQ,CAAA;AAAA,MAAC;AAAA,IACX;AAAA,EACF;AAEJ;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serialization.cjs","sources":["../../src/serialization.ts"],"sourcesContent":["import { ColumnType } from
|
|
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 ExtractedTable,\n ExtractedTableColumns,\n MapBaseColumnType,\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 * ## Generics\n * - `TOutput`: The shape of the input object, typically matching the row data.\n * - `TTable`: The table schema, which must match the keys of `TOutput`.\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<\n TOutput extends Record<string, unknown>,\n // The keys should match\n TTable extends Table<MapBaseColumnType<TOutput>> = Table<\n MapBaseColumnType<TOutput>\n >,\n>(\n value: TOutput,\n tableSchema: TTable,\n customSerializer: Partial<\n CustomSQLiteSerializer<TOutput, ExtractedTableColumns<TTable>>\n > = {},\n): ExtractedTable<TTable> {\n return Object.fromEntries(\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 as TOutput[string])]\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 )\n}\n"],"names":["value","ColumnType"],"mappings":";;;AAuCO,SAAS,mBAOd,OACA,aACA,mBAEI,CAAA,GACoB;AACxB,SAAO,OAAO;AAAA,IACZ,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,MAAwB,CAAC;AAAA,MACxD;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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PendingOperationStore.js","sources":["../../src/PendingOperationStore.ts"],"sourcesContent":["import pDefer from
|
|
1
|
+
{"version":3,"file":"PendingOperationStore.js","sources":["../../src/PendingOperationStore.ts"],"sourcesContent":["import pDefer from 'p-defer'\nimport type { DiffTriggerOperation } from '@powersync/common'\nimport type { DeferredPromise } from 'p-defer'\n\nexport type PendingOperation = {\n tableName: string\n operation: DiffTriggerOperation\n id: string\n timestamp: string\n}\n\n/**\n * Optimistic mutations have their optimistic state discarded once transactions have\n * been applied.\n * We need to ensure that an applied transaction has been observed by the sync diff trigger\n * before resolving the transaction application call.\n * This store allows registering a wait for a pending operation to have been observed.\n */\nexport class PendingOperationStore {\n private pendingOperations = new Map<PendingOperation, DeferredPromise<void>>()\n\n /**\n * Globally accessible PendingOperationStore\n */\n static GLOBAL = new PendingOperationStore()\n\n /**\n * @returns A promise which will resolve once the specified operation has been seen.\n */\n waitFor(operation: PendingOperation): Promise<void> {\n const managedPromise = pDefer<void>()\n this.pendingOperations.set(operation, managedPromise)\n return managedPromise.promise\n }\n\n /**\n * Marks a set of operations as seen. This will resolve any pending promises.\n */\n resolvePendingFor(operations: Array<PendingOperation>) {\n for (const operation of operations) {\n for (const [pendingOp, deferred] of this.pendingOperations.entries()) {\n if (\n pendingOp.tableName == operation.tableName &&\n pendingOp.operation == operation.operation &&\n pendingOp.id == operation.id &&\n pendingOp.timestamp == operation.timestamp\n ) {\n deferred.resolve()\n this.pendingOperations.delete(pendingOp)\n }\n }\n }\n }\n}\n"],"names":[],"mappings":";AAkBO,MAAM,yBAAN,MAAM,uBAAsB;AAAA,EAA5B,cAAA;AACL,SAAQ,wCAAwB,IAAA;AAAA,EAA6C;AAAA;AAAA;AAAA;AAAA,EAU7E,QAAQ,WAA4C;AAClD,UAAM,iBAAiB,OAAA;AACvB,SAAK,kBAAkB,IAAI,WAAW,cAAc;AACpD,WAAO,eAAe;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,YAAqC;AACrD,eAAW,aAAa,YAAY;AAClC,iBAAW,CAAC,WAAW,QAAQ,KAAK,KAAK,kBAAkB,WAAW;AACpE,YACE,UAAU,aAAa,UAAU,aACjC,UAAU,aAAa,UAAU,aACjC,UAAU,MAAM,UAAU,MAC1B,UAAU,aAAa,UAAU,WACjC;AACA,mBAAS,QAAA;AACT,eAAK,kBAAkB,OAAO,SAAS;AAAA,QACzC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AA7BE,uBAAO,SAAS,IAAI,uBAAA;AANf,IAAM,wBAAN;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PowerSyncTransactor.js","sources":["../../src/PowerSyncTransactor.ts"],"sourcesContent":["import { sanitizeSQL } from \"@powersync/common\"\nimport DebugModule from \"debug\"\nimport { asPowerSyncRecord, mapOperationToPowerSync } from \"./helpers\"\nimport { PendingOperationStore } from \"./PendingOperationStore\"\nimport type { AbstractPowerSyncDatabase, LockContext } from \"@powersync/common\"\nimport type { PendingMutation, Transaction } from \"@tanstack/db\"\nimport type { EnhancedPowerSyncCollectionConfig } from \"./definitions\"\nimport type { PendingOperation } from \"./PendingOperationStore\"\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 await new Promise<void>((resolve) => collection.onFirstReady(resolve))\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 async (tableName, mutation, serializeValue) => {\n const values = serializeValue(mutation.modified)\n const keys = Object.keys(values).map((key) => sanitizeSQL`${key}`)\n\n await context.execute(\n `\n INSERT into ${tableName} \n (${keys.join(`, `)}) \n VALUES \n (${keys.map((_) => `?`).join(`, `)})\n `,\n Object.values(values)\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 async (tableName, mutation, serializeValue) => {\n const values = serializeValue(mutation.modified)\n const keys = Object.keys(values).map((key) => sanitizeSQL`${key}`)\n\n await context.execute(\n `\n UPDATE ${tableName} \n SET ${keys.map((key) => `${key} = ?`).join(`, `)}\n WHERE id = ?\n `,\n [...Object.values(values), 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 async (tableName, mutation) => {\n await context.execute(\n `\n DELETE FROM ${tableName} WHERE id = ?\n `,\n [asPowerSyncRecord(mutation.original).id]\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 if (\n typeof (mutation.collection.config as any).utils?.getMeta != `function`\n ) {\n throw new Error(`Could not get tableName from mutation's collection config.\n The provided mutation might not have originated from PowerSync.`)\n }\n\n const { tableName, trackedTableName, serializeValue } = (\n mutation.collection\n .config as unknown as EnhancedPowerSyncCollectionConfig<any>\n ).utils.getMeta()\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 timestamp 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"],"names":["mutation"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PowerSyncTransactor.js","sources":["../../src/PowerSyncTransactor.ts"],"sourcesContent":["import { sanitizeSQL } from '@powersync/common'\nimport DebugModule from 'debug'\nimport { asPowerSyncRecord, mapOperationToPowerSync } from './helpers'\nimport { PendingOperationStore } from './PendingOperationStore'\nimport type { AbstractPowerSyncDatabase, LockContext } from '@powersync/common'\nimport type { PendingMutation, Transaction } from '@tanstack/db'\nimport type { EnhancedPowerSyncCollectionConfig } from './definitions'\nimport type { PendingOperation } from './PendingOperationStore'\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 await new Promise<void>((resolve) => collection.onFirstReady(resolve))\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 async (tableName, mutation, serializeValue) => {\n const values = serializeValue(mutation.modified)\n const keys = Object.keys(values).map((key) => sanitizeSQL`${key}`)\n\n await context.execute(\n `\n INSERT into ${tableName} \n (${keys.join(`, `)}) \n VALUES \n (${keys.map((_) => `?`).join(`, `)})\n `,\n Object.values(values),\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 async (tableName, mutation, serializeValue) => {\n const values = serializeValue(mutation.modified)\n const keys = Object.keys(values).map((key) => sanitizeSQL`${key}`)\n\n await context.execute(\n `\n UPDATE ${tableName} \n SET ${keys.map((key) => `${key} = ?`).join(`, `)}\n WHERE id = ?\n `,\n [...Object.values(values), 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 async (tableName, mutation) => {\n await context.execute(\n `\n DELETE FROM ${tableName} WHERE id = ?\n `,\n [asPowerSyncRecord(mutation.original).id],\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 if (\n typeof (mutation.collection.config as any).utils?.getMeta != `function`\n ) {\n throw new Error(`Could not get tableName from mutation's collection config.\n The provided mutation might not have originated from PowerSync.`)\n }\n\n const { tableName, trackedTableName, serializeValue } = (\n mutation.collection\n .config as unknown as EnhancedPowerSyncCollectionConfig<any>\n ).utils.getMeta()\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 timestamp 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"],"names":["mutation"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sources":["../../src/definitions.ts"],"sourcesContent":["import type { AbstractPowerSyncDatabase, Table } from
|
|
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 CollectionConfig,\n InferSchemaOutput,\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}\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`\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}\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/**\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<OutputType, string, TSchema> & {\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":"AAiRO,MAAM,qBAAqB;"}
|
package/dist/esm/helpers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.js","sources":["../../src/helpers.ts"],"sourcesContent":["import { DiffTriggerOperation } from
|
|
1
|
+
{"version":3,"file":"helpers.js","sources":["../../src/helpers.ts"],"sourcesContent":["import { DiffTriggerOperation } from '@powersync/common'\nimport type {\n BaseColumnType,\n ExtractColumnValueType,\n Table,\n} from '@powersync/common'\n\n/**\n * All PowerSync table records include a UUID `id` column.\n */\nexport type PowerSyncRecord = {\n id: string\n [key: string]: unknown\n}\n\n/**\n * Utility type: If T includes null, also allow undefined (to support optional fields in insert/update operations).\n * PowerSync records are typically typed as `string | null`, where insert\n * and update operations may also allow not specifying a value at all (optional).\n */\ntype WithUndefinedIfNull<T> = null extends T ? T | undefined : T\ntype OptionalIfUndefined<T> = {\n [K in keyof T as undefined extends T[K] ? K : never]?: T[K]\n} & {\n [K in keyof T as undefined extends T[K] ? never : K]: T[K]\n}\n\n/**\n * Provides the base column types for a table. This excludes the `id` column.\n */\nexport type ExtractedTableColumns<TTable extends Table> = {\n [K in keyof TTable[`columnMap`]]: ExtractColumnValueType<\n TTable[`columnMap`][K]\n >\n}\n/**\n * Utility type that extracts the typed structure of a table based on its column definitions.\n * Maps each column to its corresponding TypeScript type using ExtractColumnValueType.\n *\n * @template TTable - The PowerSync table definition\n * @example\n * ```typescript\n * const table = new Table({\n * name: column.text,\n * age: column.integer\n * })\n * type TableType = ExtractedTable<typeof table>\n * // Results in: { id: string, name: string | null, age: number | null }\n * ```\n */\nexport type ExtractedTable<TTable extends Table> =\n ExtractedTableColumns<TTable> & {\n id: string\n }\n\nexport type OptionalExtractedTable<TTable extends Table> = OptionalIfUndefined<{\n [K in keyof TTable[`columnMap`]]: WithUndefinedIfNull<\n ExtractColumnValueType<TTable[`columnMap`][K]>\n >\n}> & {\n id: string\n}\n\n/**\n * Maps the schema of TTable to a type which\n * requires the keys be equal, but the values can have any value type.\n */\nexport type AnyTableColumnType<TTable extends Table> = {\n [K in keyof TTable[`columnMap`]]: any\n} & { id: string }\n\nexport function asPowerSyncRecord(record: any): PowerSyncRecord {\n if (typeof record.id !== `string`) {\n throw new Error(`Record must have a string id field`)\n }\n return record as PowerSyncRecord\n}\n\n// Helper type to ensure the keys of TOutput match the Table columns\nexport type MapBaseColumnType<TOutput> = {\n [Key in keyof TOutput]: BaseColumnType<any>\n}\n\n/**\n * Maps {@link DiffTriggerOperation} to TanstackDB operations\n */\nexport function mapOperation(operation: DiffTriggerOperation) {\n switch (operation) {\n case DiffTriggerOperation.INSERT:\n return `insert`\n case DiffTriggerOperation.UPDATE:\n return `update`\n case DiffTriggerOperation.DELETE:\n return `delete`\n }\n}\n\n/**\n * Maps TanstackDB operations to {@link DiffTriggerOperation}\n */\nexport function mapOperationToPowerSync(operation: string) {\n switch (operation) {\n case `insert`:\n return DiffTriggerOperation.INSERT\n case `update`:\n return DiffTriggerOperation.UPDATE\n case `delete`:\n return DiffTriggerOperation.DELETE\n default:\n throw new Error(`Unknown operation ${operation} received`)\n }\n}\n"],"names":[],"mappings":";AAuEO,SAAS,kBAAkB,QAA8B;AAC9D,MAAI,OAAO,OAAO,OAAO,UAAU;AACjC,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACtD;AACA,SAAO;AACT;AAUO,SAAS,aAAa,WAAiC;AAC5D,UAAQ,WAAA;AAAA,IACN,KAAK,qBAAqB;AACxB,aAAO;AAAA,IACT,KAAK,qBAAqB;AACxB,aAAO;AAAA,IACT,KAAK,qBAAqB;AACxB,aAAO;AAAA,EAAA;AAEb;AAKO,SAAS,wBAAwB,WAAmB;AACzD,UAAQ,WAAA;AAAA,IACN,KAAK;AACH,aAAO,qBAAqB;AAAA,IAC9B,KAAK;AACH,aAAO,qBAAqB;AAAA,IAC9B,KAAK;AACH,aAAO,qBAAqB;AAAA,IAC9B;AACE,YAAM,IAAI,MAAM,qBAAqB,SAAS,WAAW;AAAA,EAAA;AAE/D;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"powersync.js","sources":["../../src/powersync.ts"],"sourcesContent":["import { DiffTriggerOperation, sanitizeSQL } from \"@powersync/common\"\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 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 { SyncConfig } from \"@tanstack/db\"\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\"\nimport type { 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>(config: PowerSyncCollectionConfig<TTable, TSchema>) {\n const {\n database,\n table,\n schema: inputSchema,\n syncBatchSize = DEFAULT_BATCH_SIZE,\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 } = 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, commit, markReady } = params\n const abortController = new AbortController()\n\n // The sync function needs to be synchronous\n async function start() {\n database.logger.info(\n `Sync is starting for ${viewName} into ${trackedTableName}`\n )\n database.onChangeWithCallback(\n {\n onChange: async () => {\n await database\n .writeTransaction(async (context) => {\n begin()\n const operations = await context.getAll<TriggerDiffRecord>(\n `SELECT * FROM ${trackedTableName} ORDER BY timestamp ASC`\n )\n const pendingOperations: Array<PendingOperation> = []\n\n for (const op of operations) {\n const { id, operation, timestamp, value } = op\n const parsedValue = deserializeSyncRow({\n id,\n ...JSON.parse(value),\n })\n const parsedPreviousValue =\n op.operation == DiffTriggerOperation.UPDATE\n ? deserializeSyncRow({\n id,\n ...JSON.parse(op.previous_value),\n })\n : undefined\n write({\n type: mapOperation(operation),\n value: parsedValue,\n previousValue: parsedPreviousValue,\n })\n pendingOperations.push({\n id,\n operation,\n timestamp,\n tableName: viewName,\n })\n }\n\n // clear the current operations\n await context.execute(`DELETE FROM ${trackedTableName}`)\n\n commit()\n pendingOperationStore.resolvePendingFor(pendingOperations)\n })\n .catch((error) => {\n database.logger.error(\n `An error has been detected in the sync handler`,\n error\n )\n })\n },\n },\n {\n signal: abortController.signal,\n triggerImmediate: false,\n tables: [trackedTableName],\n }\n )\n\n const disposeTracking = await database.triggers.createDiffTrigger({\n source: viewName,\n destination: trackedTableName,\n when: {\n [DiffTriggerOperation.INSERT]: `TRUE`,\n [DiffTriggerOperation.UPDATE]: `TRUE`,\n [DiffTriggerOperation.DELETE]: `TRUE`,\n },\n hooks: {\n beforeCreate: async (context) => {\n let currentBatchCount = syncBatchSize\n let cursor = 0\n while (currentBatchCount == syncBatchSize) {\n begin()\n const batchItems = await context.getAll<TableType>(\n sanitizeSQL`SELECT * FROM ${viewName} LIMIT ? OFFSET ?`,\n [syncBatchSize, cursor]\n )\n currentBatchCount = batchItems.length\n cursor += currentBatchCount\n for (const row of batchItems) {\n write({\n type: `insert`,\n value: deserializeSyncRow(row),\n })\n }\n commit()\n }\n markReady()\n database.logger.info(\n `Sync is ready for ${viewName} into ${trackedTableName}`\n )\n },\n },\n })\n\n // If the abort controller was aborted while processing the request above\n if (abortController.signal.aborted) {\n await disposeTracking()\n } else {\n abortController.signal.addEventListener(\n `abort`,\n () => {\n disposeTracking()\n },\n { once: true }\n )\n }\n }\n\n start().catch((error) =>\n database.logger.error(\n `Could not start syncing process for ${viewName} into ${trackedTableName}`,\n error\n )\n )\n\n return () => {\n database.logger.info(\n `Sync has been stopped for ${viewName} into ${trackedTableName}`\n )\n abortController.abort()\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 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 serializeValue: (value) =>\n serializeForSQLite(\n value,\n // This is required by the input generic\n table as Table<\n MapBaseColumnType<InferPowerSyncOutputType<TTable, TSchema>>\n >,\n // Coerce serializer to the shape that corresponds to the Table constructed from OutputType\n serializer as CustomSQLiteSerializer<\n OutputType,\n ExtractedTableColumns<Table<MapBaseColumnType<OutputType>>>\n >\n ),\n }),\n },\n }\n return outputConfig\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"powersync.js","sources":["../../src/powersync.ts"],"sourcesContent":["import { DiffTriggerOperation, sanitizeSQL } from '@powersync/common'\nimport { 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 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 { SyncConfig } from '@tanstack/db'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { 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>(config: PowerSyncCollectionConfig<TTable, TSchema>) {\n const {\n database,\n table,\n schema: inputSchema,\n syncBatchSize = DEFAULT_BATCH_SIZE,\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 } = 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, commit, markReady } = params\n const abortController = new AbortController()\n\n // The sync function needs to be synchronous\n async function start() {\n database.logger.info(\n `Sync is starting for ${viewName} into ${trackedTableName}`,\n )\n database.onChangeWithCallback(\n {\n onChange: async () => {\n await database\n .writeTransaction(async (context) => {\n begin()\n const operations = await context.getAll<TriggerDiffRecord>(\n `SELECT * FROM ${trackedTableName} ORDER BY timestamp ASC`,\n )\n const pendingOperations: Array<PendingOperation> = []\n\n for (const op of operations) {\n const { id, operation, timestamp, value } = op\n const parsedValue = deserializeSyncRow({\n id,\n ...JSON.parse(value),\n })\n const parsedPreviousValue =\n op.operation == DiffTriggerOperation.UPDATE\n ? deserializeSyncRow({\n id,\n ...JSON.parse(op.previous_value),\n })\n : undefined\n write({\n type: mapOperation(operation),\n value: parsedValue,\n previousValue: parsedPreviousValue,\n })\n pendingOperations.push({\n id,\n operation,\n timestamp,\n tableName: viewName,\n })\n }\n\n // clear the current operations\n await context.execute(`DELETE FROM ${trackedTableName}`)\n\n commit()\n pendingOperationStore.resolvePendingFor(pendingOperations)\n })\n .catch((error) => {\n database.logger.error(\n `An error has been detected in the sync handler`,\n error,\n )\n })\n },\n },\n {\n signal: abortController.signal,\n triggerImmediate: false,\n tables: [trackedTableName],\n },\n )\n\n const disposeTracking = await database.triggers.createDiffTrigger({\n source: viewName,\n destination: trackedTableName,\n when: {\n [DiffTriggerOperation.INSERT]: `TRUE`,\n [DiffTriggerOperation.UPDATE]: `TRUE`,\n [DiffTriggerOperation.DELETE]: `TRUE`,\n },\n hooks: {\n beforeCreate: async (context) => {\n let currentBatchCount = syncBatchSize\n let cursor = 0\n while (currentBatchCount == syncBatchSize) {\n begin()\n const batchItems = await context.getAll<TableType>(\n sanitizeSQL`SELECT * FROM ${viewName} LIMIT ? OFFSET ?`,\n [syncBatchSize, cursor],\n )\n currentBatchCount = batchItems.length\n cursor += currentBatchCount\n for (const row of batchItems) {\n write({\n type: `insert`,\n value: deserializeSyncRow(row),\n })\n }\n commit()\n }\n markReady()\n database.logger.info(\n `Sync is ready for ${viewName} into ${trackedTableName}`,\n )\n },\n },\n })\n\n // If the abort controller was aborted while processing the request above\n if (abortController.signal.aborted) {\n await disposeTracking()\n } else {\n abortController.signal.addEventListener(\n `abort`,\n () => {\n disposeTracking()\n },\n { once: true },\n )\n }\n }\n\n start().catch((error) =>\n database.logger.error(\n `Could not start syncing process for ${viewName} into ${trackedTableName}`,\n error,\n ),\n )\n\n return () => {\n database.logger.info(\n `Sync has been stopped for ${viewName} into ${trackedTableName}`,\n )\n abortController.abort()\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 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 serializeValue: (value) =>\n serializeForSQLite(\n value,\n // This is required by the input generic\n table as Table<\n MapBaseColumnType<InferPowerSyncOutputType<TTable, TSchema>>\n >,\n // Coerce serializer to the shape that corresponds to the Table constructed from OutputType\n serializer as CustomSQLiteSerializer<\n OutputType,\n ExtractedTableColumns<Table<MapBaseColumnType<OutputType>>>\n >,\n ),\n }),\n },\n }\n return outputConfig\n}\n"],"names":[],"mappings":"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"}
|
package/dist/esm/schema.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sources":["../../src/schema.ts"],"sourcesContent":["import { ColumnType } from
|
|
1
|
+
{"version":3,"file":"schema.js","sources":["../../src/schema.ts"],"sourcesContent":["import { ColumnType } from '@powersync/common'\nimport type { Table } from '@powersync/common'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { ExtractedTable } from './helpers'\n\n/**\n * Converts a PowerSync Table instance to a StandardSchemaV1 schema.\n * Creates a schema that validates the structure and types of table records\n * according to the PowerSync table definition.\n *\n * @template TTable - The PowerSync schema-typed Table definition\n * @param table - The PowerSync Table instance to convert\n * @returns A StandardSchemaV1-compatible schema with proper type validation\n *\n * @example\n * ```typescript\n * const usersTable = new Table({\n * name: column.text,\n * age: column.integer\n * })\n * ```\n */\nexport function convertTableToSchema<TTable extends Table>(\n table: TTable,\n): StandardSchemaV1<ExtractedTable<TTable>> {\n type TExtracted = ExtractedTable<TTable>\n // Create validate function that checks types according to column definitions\n const validate = (\n value: unknown,\n ):\n | StandardSchemaV1.SuccessResult<TExtracted>\n | StandardSchemaV1.FailureResult => {\n if (typeof value != `object` || value == null) {\n return {\n issues: [\n {\n message: `Value must be an object`,\n },\n ],\n }\n }\n\n const issues: Array<StandardSchemaV1.Issue> = []\n\n // Check id field\n if (!(`id` in value) || typeof (value as any).id != `string`) {\n issues.push({\n message: `id field must be a string`,\n path: [`id`],\n })\n }\n\n // Check each column\n for (const column of table.columns) {\n const val = (value as TExtracted)[column.name as keyof TExtracted]\n\n if (val == null) {\n continue\n }\n\n switch (column.type) {\n case ColumnType.TEXT:\n if (typeof val != `string`) {\n issues.push({\n message: `${column.name} must be a string or null`,\n path: [column.name],\n })\n }\n break\n case ColumnType.INTEGER:\n case ColumnType.REAL:\n if (typeof val != `number`) {\n issues.push({\n message: `${column.name} must be a number or null`,\n path: [column.name],\n })\n }\n break\n }\n }\n\n if (issues.length > 0) {\n return { issues }\n }\n\n return { value: { ...value } as TExtracted }\n }\n\n return {\n '~standard': {\n version: 1,\n vendor: `powersync`,\n validate,\n types: {\n input: {} as TExtracted,\n output: {} as TExtracted,\n },\n },\n }\n}\n"],"names":[],"mappings":";AAsBO,SAAS,qBACd,OAC0C;AAG1C,QAAM,WAAW,CACf,UAGoC;AACpC,QAAI,OAAO,SAAS,YAAY,SAAS,MAAM;AAC7C,aAAO;AAAA,QACL,QAAQ;AAAA,UACN;AAAA,YACE,SAAS;AAAA,UAAA;AAAA,QACX;AAAA,MACF;AAAA,IAEJ;AAEA,UAAM,SAAwC,CAAA;AAG9C,QAAI,EAAE,QAAQ,UAAU,OAAQ,MAAc,MAAM,UAAU;AAC5D,aAAO,KAAK;AAAA,QACV,SAAS;AAAA,QACT,MAAM,CAAC,IAAI;AAAA,MAAA,CACZ;AAAA,IACH;AAGA,eAAW,UAAU,MAAM,SAAS;AAClC,YAAM,MAAO,MAAqB,OAAO,IAAwB;AAEjE,UAAI,OAAO,MAAM;AACf;AAAA,MACF;AAEA,cAAQ,OAAO,MAAA;AAAA,QACb,KAAK,WAAW;AACd,cAAI,OAAO,OAAO,UAAU;AAC1B,mBAAO,KAAK;AAAA,cACV,SAAS,GAAG,OAAO,IAAI;AAAA,cACvB,MAAM,CAAC,OAAO,IAAI;AAAA,YAAA,CACnB;AAAA,UACH;AACA;AAAA,QACF,KAAK,WAAW;AAAA,QAChB,KAAK,WAAW;AACd,cAAI,OAAO,OAAO,UAAU;AAC1B,mBAAO,KAAK;AAAA,cACV,SAAS,GAAG,OAAO,IAAI;AAAA,cACvB,MAAM,CAAC,OAAO,IAAI;AAAA,YAAA,CACnB;AAAA,UACH;AACA;AAAA,MAAA;AAAA,IAEN;AAEA,QAAI,OAAO,SAAS,GAAG;AACrB,aAAO,EAAE,OAAA;AAAA,IACX;AAEA,WAAO,EAAE,OAAO,EAAE,GAAG,QAAM;AAAA,EAC7B;AAEA,SAAO;AAAA,IACL,aAAa;AAAA,MACX,SAAS;AAAA,MACT,QAAQ;AAAA,MACR;AAAA,MACA,OAAO;AAAA,QACL,OAAO,CAAA;AAAA,QACP,QAAQ,CAAA;AAAA,MAAC;AAAA,IACX;AAAA,EACF;AAEJ;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serialization.js","sources":["../../src/serialization.ts"],"sourcesContent":["import { ColumnType } from
|
|
1
|
+
{"version":3,"file":"serialization.js","sources":["../../src/serialization.ts"],"sourcesContent":["import { ColumnType } from '@powersync/common'\nimport type { Table } from '@powersync/common'\nimport type { CustomSQLiteSerializer } from './definitions'\nimport type {\n ExtractedTable,\n ExtractedTableColumns,\n MapBaseColumnType,\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 * ## Generics\n * - `TOutput`: The shape of the input object, typically matching the row data.\n * - `TTable`: The table schema, which must match the keys of `TOutput`.\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<\n TOutput extends Record<string, unknown>,\n // The keys should match\n TTable extends Table<MapBaseColumnType<TOutput>> = Table<\n MapBaseColumnType<TOutput>\n >,\n>(\n value: TOutput,\n tableSchema: TTable,\n customSerializer: Partial<\n CustomSQLiteSerializer<TOutput, ExtractedTableColumns<TTable>>\n > = {},\n): ExtractedTable<TTable> {\n return Object.fromEntries(\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 as TOutput[string])]\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 )\n}\n"],"names":["value"],"mappings":";AAuCO,SAAS,mBAOd,OACA,aACA,mBAEI,CAAA,GACoB;AACxB,SAAO,OAAO;AAAA,IACZ,OAAO,QAAQ,KAAK,EAAE,IAAI,CAAC,CAAC,KAAKA,MAAK,MAAM;AAE1C,YAAM,aACJ,OAAO,OACH,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,UAAIA,UAAS,MAAM;AACjB,eAAO,CAAC,KAAKA,MAAK;AAAA,MACpB;AAEA,YAAM,kBAAkB,iBAAiB,GAAG;AAC5C,UAAI,iBAAiB;AACnB,eAAO,CAAC,KAAK,gBAAgBA,MAAwB,CAAC;AAAA,MACxD;AAGA,cAAQ,YAAA;AAAA,QACN,KAAK,WAAW;AACd,cAAI,OAAOA,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,KAAK,WAAW;AAAA,QAChB,KAAK,WAAW;AACd,cAAI,OAAOA,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;"}
|
package/package.json
CHANGED
|
@@ -1,47 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/powersync-db-collection",
|
|
3
|
+
"version": "0.1.16",
|
|
3
4
|
"description": "PowerSync collection for TanStack DB",
|
|
4
|
-
"version": "0.1.14",
|
|
5
|
-
"dependencies": {
|
|
6
|
-
"@standard-schema/spec": "^1.0.0",
|
|
7
|
-
"@tanstack/store": "^0.8.0",
|
|
8
|
-
"debug": "^4.4.3",
|
|
9
|
-
"p-defer": "^4.0.1",
|
|
10
|
-
"@tanstack/db": "0.5.10"
|
|
11
|
-
},
|
|
12
|
-
"peerDependencies": {
|
|
13
|
-
"@powersync/common": "^1.41.0"
|
|
14
|
-
},
|
|
15
|
-
"devDependencies": {
|
|
16
|
-
"@powersync/common": "^1.43.1",
|
|
17
|
-
"@powersync/node": "^0.14.3",
|
|
18
|
-
"@types/debug": "^4.1.12",
|
|
19
|
-
"@vitest/coverage-istanbul": "^3.2.4"
|
|
20
|
-
},
|
|
21
|
-
"exports": {
|
|
22
|
-
".": {
|
|
23
|
-
"import": {
|
|
24
|
-
"types": "./dist/esm/index.d.ts",
|
|
25
|
-
"default": "./dist/esm/index.js"
|
|
26
|
-
},
|
|
27
|
-
"require": {
|
|
28
|
-
"types": "./dist/cjs/index.d.cts",
|
|
29
|
-
"default": "./dist/cjs/index.cjs"
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
"./package.json": "./package.json"
|
|
33
|
-
},
|
|
34
|
-
"files": [
|
|
35
|
-
"dist",
|
|
36
|
-
"src"
|
|
37
|
-
],
|
|
38
|
-
"main": "dist/cjs/index.cjs",
|
|
39
|
-
"module": "dist/esm/index.js",
|
|
40
5
|
"author": "POWERSYNC",
|
|
41
6
|
"license": "MIT",
|
|
42
7
|
"repository": {
|
|
43
8
|
"type": "git",
|
|
44
|
-
"url": "https://github.com/TanStack/db.git",
|
|
9
|
+
"url": "git+https://github.com/TanStack/db.git",
|
|
45
10
|
"directory": "packages/powersync-db-collection"
|
|
46
11
|
},
|
|
47
12
|
"homepage": "https://tanstack.com/db",
|
|
@@ -58,13 +23,48 @@
|
|
|
58
23
|
"optimistic",
|
|
59
24
|
"typescript"
|
|
60
25
|
],
|
|
61
|
-
"sideEffects": false,
|
|
62
26
|
"type": "module",
|
|
27
|
+
"main": "dist/cjs/index.cjs",
|
|
28
|
+
"module": "dist/esm/index.js",
|
|
63
29
|
"types": "dist/esm/index.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"import": {
|
|
33
|
+
"types": "./dist/esm/index.d.ts",
|
|
34
|
+
"default": "./dist/esm/index.js"
|
|
35
|
+
},
|
|
36
|
+
"require": {
|
|
37
|
+
"types": "./dist/cjs/index.d.cts",
|
|
38
|
+
"default": "./dist/cjs/index.cjs"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"./package.json": "./package.json"
|
|
42
|
+
},
|
|
43
|
+
"sideEffects": false,
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"src"
|
|
47
|
+
],
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@standard-schema/spec": "^1.0.0",
|
|
50
|
+
"@tanstack/store": "^0.8.0",
|
|
51
|
+
"debug": "^4.4.3",
|
|
52
|
+
"p-defer": "^4.0.1",
|
|
53
|
+
"@tanstack/db": "0.5.12"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"@powersync/common": "^1.41.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@powersync/common": "^1.43.1",
|
|
60
|
+
"@powersync/node": "^0.14.3",
|
|
61
|
+
"@types/debug": "^4.1.12",
|
|
62
|
+
"@vitest/coverage-istanbul": "^3.2.4"
|
|
63
|
+
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"build": "vite build",
|
|
66
66
|
"dev": "vite build --watch",
|
|
67
67
|
"lint": "eslint . --fix",
|
|
68
|
-
"test": "
|
|
68
|
+
"test": "vitest --run"
|
|
69
69
|
}
|
|
70
70
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import pDefer from
|
|
2
|
-
import type { DiffTriggerOperation } from
|
|
3
|
-
import type { DeferredPromise } from
|
|
1
|
+
import pDefer from 'p-defer'
|
|
2
|
+
import type { DiffTriggerOperation } from '@powersync/common'
|
|
3
|
+
import type { DeferredPromise } from 'p-defer'
|
|
4
4
|
|
|
5
5
|
export type PendingOperation = {
|
|
6
6
|
tableName: string
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { sanitizeSQL } from
|
|
2
|
-
import DebugModule from
|
|
3
|
-
import { asPowerSyncRecord, mapOperationToPowerSync } from
|
|
4
|
-
import { PendingOperationStore } from
|
|
5
|
-
import type { AbstractPowerSyncDatabase, LockContext } from
|
|
6
|
-
import type { PendingMutation, Transaction } from
|
|
7
|
-
import type { EnhancedPowerSyncCollectionConfig } from
|
|
8
|
-
import type { PendingOperation } from
|
|
1
|
+
import { sanitizeSQL } from '@powersync/common'
|
|
2
|
+
import DebugModule from 'debug'
|
|
3
|
+
import { asPowerSyncRecord, mapOperationToPowerSync } from './helpers'
|
|
4
|
+
import { PendingOperationStore } from './PendingOperationStore'
|
|
5
|
+
import type { AbstractPowerSyncDatabase, LockContext } from '@powersync/common'
|
|
6
|
+
import type { PendingMutation, Transaction } from '@tanstack/db'
|
|
7
|
+
import type { EnhancedPowerSyncCollectionConfig } from './definitions'
|
|
8
|
+
import type { PendingOperation } from './PendingOperationStore'
|
|
9
9
|
|
|
10
10
|
const debug = DebugModule.debug(`ts/db:powersync`)
|
|
11
11
|
|
|
@@ -71,7 +71,7 @@ export class PowerSyncTransactor {
|
|
|
71
71
|
* We can do some optimizations for single-collection transactions.
|
|
72
72
|
*/
|
|
73
73
|
const mutationsCollectionIds = mutations.map(
|
|
74
|
-
(mutation) => mutation.collection.id
|
|
74
|
+
(mutation) => mutation.collection.id,
|
|
75
75
|
)
|
|
76
76
|
const collectionIds = Array.from(new Set(mutationsCollectionIds))
|
|
77
77
|
const lastCollectionMutationIndexes = new Map<string, number>()
|
|
@@ -81,7 +81,7 @@ export class PowerSyncTransactor {
|
|
|
81
81
|
for (const collectionId of collectionIds) {
|
|
82
82
|
lastCollectionMutationIndexes.set(
|
|
83
83
|
collectionId,
|
|
84
|
-
mutationsCollectionIds.lastIndexOf(collectionId)
|
|
84
|
+
mutationsCollectionIds.lastIndexOf(collectionId),
|
|
85
85
|
)
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -92,7 +92,7 @@ export class PowerSyncTransactor {
|
|
|
92
92
|
return
|
|
93
93
|
}
|
|
94
94
|
await new Promise<void>((resolve) => collection.onFirstReady(resolve))
|
|
95
|
-
})
|
|
95
|
+
}),
|
|
96
96
|
)
|
|
97
97
|
|
|
98
98
|
// Persist to PowerSync
|
|
@@ -110,17 +110,17 @@ export class PowerSyncTransactor {
|
|
|
110
110
|
switch (mutation.type) {
|
|
111
111
|
case `insert`:
|
|
112
112
|
pendingOperations.push(
|
|
113
|
-
await this.handleInsert(mutation, tx, shouldWait)
|
|
113
|
+
await this.handleInsert(mutation, tx, shouldWait),
|
|
114
114
|
)
|
|
115
115
|
break
|
|
116
116
|
case `update`:
|
|
117
117
|
pendingOperations.push(
|
|
118
|
-
await this.handleUpdate(mutation, tx, shouldWait)
|
|
118
|
+
await this.handleUpdate(mutation, tx, shouldWait),
|
|
119
119
|
)
|
|
120
120
|
break
|
|
121
121
|
case `delete`:
|
|
122
122
|
pendingOperations.push(
|
|
123
|
-
await this.handleDelete(mutation, tx, shouldWait)
|
|
123
|
+
await this.handleDelete(mutation, tx, shouldWait),
|
|
124
124
|
)
|
|
125
125
|
break
|
|
126
126
|
}
|
|
@@ -136,10 +136,10 @@ export class PowerSyncTransactor {
|
|
|
136
136
|
whenComplete: Promise.all(
|
|
137
137
|
pendingOperations
|
|
138
138
|
.filter((op) => !!op)
|
|
139
|
-
.map((op) => this.pendingOperationStore.waitFor(op))
|
|
139
|
+
.map((op) => this.pendingOperationStore.waitFor(op)),
|
|
140
140
|
),
|
|
141
141
|
}
|
|
142
|
-
}
|
|
142
|
+
},
|
|
143
143
|
)
|
|
144
144
|
|
|
145
145
|
// Wait for the change to be observed via the diff trigger
|
|
@@ -149,7 +149,7 @@ export class PowerSyncTransactor {
|
|
|
149
149
|
protected async handleInsert(
|
|
150
150
|
mutation: PendingMutation<any>,
|
|
151
151
|
context: LockContext,
|
|
152
|
-
waitForCompletion: boolean = false
|
|
152
|
+
waitForCompletion: boolean = false,
|
|
153
153
|
): Promise<PendingOperation | null> {
|
|
154
154
|
debug(`insert`, mutation)
|
|
155
155
|
|
|
@@ -168,16 +168,16 @@ export class PowerSyncTransactor {
|
|
|
168
168
|
VALUES
|
|
169
169
|
(${keys.map((_) => `?`).join(`, `)})
|
|
170
170
|
`,
|
|
171
|
-
Object.values(values)
|
|
171
|
+
Object.values(values),
|
|
172
172
|
)
|
|
173
|
-
}
|
|
173
|
+
},
|
|
174
174
|
)
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
protected async handleUpdate(
|
|
178
178
|
mutation: PendingMutation<any>,
|
|
179
179
|
context: LockContext,
|
|
180
|
-
waitForCompletion: boolean = false
|
|
180
|
+
waitForCompletion: boolean = false,
|
|
181
181
|
): Promise<PendingOperation | null> {
|
|
182
182
|
debug(`update`, mutation)
|
|
183
183
|
|
|
@@ -195,16 +195,16 @@ export class PowerSyncTransactor {
|
|
|
195
195
|
SET ${keys.map((key) => `${key} = ?`).join(`, `)}
|
|
196
196
|
WHERE id = ?
|
|
197
197
|
`,
|
|
198
|
-
[...Object.values(values), asPowerSyncRecord(mutation.modified).id]
|
|
198
|
+
[...Object.values(values), asPowerSyncRecord(mutation.modified).id],
|
|
199
199
|
)
|
|
200
|
-
}
|
|
200
|
+
},
|
|
201
201
|
)
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
protected async handleDelete(
|
|
205
205
|
mutation: PendingMutation<any>,
|
|
206
206
|
context: LockContext,
|
|
207
|
-
waitForCompletion: boolean = false
|
|
207
|
+
waitForCompletion: boolean = false,
|
|
208
208
|
): Promise<PendingOperation | null> {
|
|
209
209
|
debug(`update`, mutation)
|
|
210
210
|
|
|
@@ -217,9 +217,9 @@ export class PowerSyncTransactor {
|
|
|
217
217
|
`
|
|
218
218
|
DELETE FROM ${tableName} WHERE id = ?
|
|
219
219
|
`,
|
|
220
|
-
[asPowerSyncRecord(mutation.original).id]
|
|
220
|
+
[asPowerSyncRecord(mutation.original).id],
|
|
221
221
|
)
|
|
222
|
-
}
|
|
222
|
+
},
|
|
223
223
|
)
|
|
224
224
|
}
|
|
225
225
|
|
|
@@ -236,8 +236,8 @@ export class PowerSyncTransactor {
|
|
|
236
236
|
handler: (
|
|
237
237
|
tableName: string,
|
|
238
238
|
mutation: PendingMutation<any>,
|
|
239
|
-
serializeValue: (value: any) => Record<string, unknown
|
|
240
|
-
) => Promise<void
|
|
239
|
+
serializeValue: (value: any) => Record<string, unknown>,
|
|
240
|
+
) => Promise<void>,
|
|
241
241
|
): Promise<PendingOperation | null> {
|
|
242
242
|
if (
|
|
243
243
|
typeof (mutation.collection.config as any).utils?.getMeta != `function`
|
|
@@ -259,7 +259,7 @@ export class PowerSyncTransactor {
|
|
|
259
259
|
|
|
260
260
|
// Need to get the operation in order to wait for it
|
|
261
261
|
const diffOperation = await context.get<{ id: string; timestamp: string }>(
|
|
262
|
-
sanitizeSQL`SELECT id, timestamp FROM ${trackedTableName} ORDER BY timestamp DESC LIMIT 1
|
|
262
|
+
sanitizeSQL`SELECT id, timestamp FROM ${trackedTableName} ORDER BY timestamp DESC LIMIT 1`,
|
|
263
263
|
)
|
|
264
264
|
return {
|
|
265
265
|
tableName,
|
package/src/definitions.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import type { AbstractPowerSyncDatabase, Table } from
|
|
2
|
-
import type { StandardSchemaV1 } from
|
|
1
|
+
import type { AbstractPowerSyncDatabase, Table } from '@powersync/common'
|
|
2
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
|
3
3
|
import type {
|
|
4
4
|
BaseCollectionConfig,
|
|
5
5
|
CollectionConfig,
|
|
6
6
|
InferSchemaOutput,
|
|
7
|
-
} from
|
|
7
|
+
} from '@tanstack/db'
|
|
8
8
|
import type {
|
|
9
9
|
AnyTableColumnType,
|
|
10
10
|
ExtractedTable,
|
|
11
11
|
OptionalExtractedTable,
|
|
12
12
|
PowerSyncRecord,
|
|
13
|
-
} from
|
|
13
|
+
} from './helpers'
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Small helper which determines the output type if:
|
|
@@ -54,7 +54,7 @@ export type CustomSQLiteSerializer<
|
|
|
54
54
|
TSQLite extends Record<string, unknown>,
|
|
55
55
|
> = Partial<{
|
|
56
56
|
[Key in keyof TOutput]: (
|
|
57
|
-
value: TOutput[Key]
|
|
57
|
+
value: TOutput[Key],
|
|
58
58
|
) => Key extends keyof TSQLite ? TSQLite[Key] : never
|
|
59
59
|
}>
|
|
60
60
|
|
package/src/helpers.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { DiffTriggerOperation } from
|
|
1
|
+
import { DiffTriggerOperation } from '@powersync/common'
|
|
2
2
|
import type {
|
|
3
3
|
BaseColumnType,
|
|
4
4
|
ExtractColumnValueType,
|
|
5
5
|
Table,
|
|
6
|
-
} from
|
|
6
|
+
} from '@powersync/common'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* All PowerSync table records include a UUID `id` column.
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
1
|
+
export * from './definitions'
|
|
2
|
+
export * from './powersync'
|
|
3
|
+
export * from './PowerSyncTransactor'
|
package/src/powersync.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { DiffTriggerOperation, sanitizeSQL } from
|
|
2
|
-
import { PendingOperationStore } from
|
|
3
|
-
import { PowerSyncTransactor } from
|
|
4
|
-
import { DEFAULT_BATCH_SIZE } from
|
|
5
|
-
import { asPowerSyncRecord, mapOperation } from
|
|
6
|
-
import { convertTableToSchema } from
|
|
7
|
-
import { serializeForSQLite } from
|
|
1
|
+
import { DiffTriggerOperation, sanitizeSQL } from '@powersync/common'
|
|
2
|
+
import { PendingOperationStore } from './PendingOperationStore'
|
|
3
|
+
import { PowerSyncTransactor } from './PowerSyncTransactor'
|
|
4
|
+
import { DEFAULT_BATCH_SIZE } from './definitions'
|
|
5
|
+
import { asPowerSyncRecord, mapOperation } from './helpers'
|
|
6
|
+
import { convertTableToSchema } from './schema'
|
|
7
|
+
import { serializeForSQLite } from './serialization'
|
|
8
8
|
import type {
|
|
9
9
|
AnyTableColumnType,
|
|
10
10
|
ExtractedTable,
|
|
11
11
|
ExtractedTableColumns,
|
|
12
12
|
MapBaseColumnType,
|
|
13
13
|
OptionalExtractedTable,
|
|
14
|
-
} from
|
|
14
|
+
} from './helpers'
|
|
15
15
|
import type {
|
|
16
16
|
BasePowerSyncCollectionConfig,
|
|
17
17
|
ConfigWithArbitraryCollectionTypes,
|
|
@@ -22,11 +22,11 @@ import type {
|
|
|
22
22
|
InferPowerSyncOutputType,
|
|
23
23
|
PowerSyncCollectionConfig,
|
|
24
24
|
PowerSyncCollectionUtils,
|
|
25
|
-
} from
|
|
26
|
-
import type { PendingOperation } from
|
|
27
|
-
import type { SyncConfig } from
|
|
28
|
-
import type { StandardSchemaV1 } from
|
|
29
|
-
import type { Table, TriggerDiffRecord } from
|
|
25
|
+
} from './definitions'
|
|
26
|
+
import type { PendingOperation } from './PendingOperationStore'
|
|
27
|
+
import type { SyncConfig } from '@tanstack/db'
|
|
28
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
|
29
|
+
import type { Table, TriggerDiffRecord } from '@powersync/common'
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Creates PowerSync collection options for use with a standard Collection.
|
|
@@ -69,7 +69,7 @@ import type { Table, TriggerDiffRecord } from "@powersync/common"
|
|
|
69
69
|
* ```
|
|
70
70
|
*/
|
|
71
71
|
export function powerSyncCollectionOptions<TTable extends Table = Table>(
|
|
72
|
-
config: BasePowerSyncCollectionConfig<TTable, never> & ConfigWithSQLiteTypes
|
|
72
|
+
config: BasePowerSyncCollectionConfig<TTable, never> & ConfigWithSQLiteTypes,
|
|
73
73
|
): EnhancedPowerSyncCollectionConfig<
|
|
74
74
|
TTable,
|
|
75
75
|
OptionalExtractedTable<TTable>,
|
|
@@ -134,7 +134,7 @@ export function powerSyncCollectionOptions<
|
|
|
134
134
|
>,
|
|
135
135
|
>(
|
|
136
136
|
config: BasePowerSyncCollectionConfig<TTable, TSchema> &
|
|
137
|
-
ConfigWithSQLiteInputType<TTable, TSchema
|
|
137
|
+
ConfigWithSQLiteInputType<TTable, TSchema>,
|
|
138
138
|
): EnhancedPowerSyncCollectionConfig<
|
|
139
139
|
TTable,
|
|
140
140
|
InferPowerSyncOutputType<TTable, TSchema>,
|
|
@@ -202,7 +202,7 @@ export function powerSyncCollectionOptions<
|
|
|
202
202
|
>,
|
|
203
203
|
>(
|
|
204
204
|
config: BasePowerSyncCollectionConfig<TTable, TSchema> &
|
|
205
|
-
ConfigWithArbitraryCollectionTypes<TTable, TSchema
|
|
205
|
+
ConfigWithArbitraryCollectionTypes<TTable, TSchema>,
|
|
206
206
|
): EnhancedPowerSyncCollectionConfig<
|
|
207
207
|
TTable,
|
|
208
208
|
InferPowerSyncOutputType<TTable, TSchema>,
|
|
@@ -280,7 +280,7 @@ export function powerSyncCollectionOptions<
|
|
|
280
280
|
const pendingOperationStore = PendingOperationStore.GLOBAL
|
|
281
281
|
// Keep the tracked table unique in case of multiple tabs.
|
|
282
282
|
const trackedTableName = `__${viewName}_tracking_${Math.floor(
|
|
283
|
-
Math.random() * 0xffffffff
|
|
283
|
+
Math.random() * 0xffffffff,
|
|
284
284
|
)
|
|
285
285
|
.toString(16)
|
|
286
286
|
.padStart(8, `0`)}`
|
|
@@ -302,7 +302,7 @@ export function powerSyncCollectionOptions<
|
|
|
302
302
|
// The sync function needs to be synchronous
|
|
303
303
|
async function start() {
|
|
304
304
|
database.logger.info(
|
|
305
|
-
`Sync is starting for ${viewName} into ${trackedTableName}
|
|
305
|
+
`Sync is starting for ${viewName} into ${trackedTableName}`,
|
|
306
306
|
)
|
|
307
307
|
database.onChangeWithCallback(
|
|
308
308
|
{
|
|
@@ -311,7 +311,7 @@ export function powerSyncCollectionOptions<
|
|
|
311
311
|
.writeTransaction(async (context) => {
|
|
312
312
|
begin()
|
|
313
313
|
const operations = await context.getAll<TriggerDiffRecord>(
|
|
314
|
-
`SELECT * FROM ${trackedTableName} ORDER BY timestamp ASC
|
|
314
|
+
`SELECT * FROM ${trackedTableName} ORDER BY timestamp ASC`,
|
|
315
315
|
)
|
|
316
316
|
const pendingOperations: Array<PendingOperation> = []
|
|
317
317
|
|
|
@@ -350,7 +350,7 @@ export function powerSyncCollectionOptions<
|
|
|
350
350
|
.catch((error) => {
|
|
351
351
|
database.logger.error(
|
|
352
352
|
`An error has been detected in the sync handler`,
|
|
353
|
-
error
|
|
353
|
+
error,
|
|
354
354
|
)
|
|
355
355
|
})
|
|
356
356
|
},
|
|
@@ -359,7 +359,7 @@ export function powerSyncCollectionOptions<
|
|
|
359
359
|
signal: abortController.signal,
|
|
360
360
|
triggerImmediate: false,
|
|
361
361
|
tables: [trackedTableName],
|
|
362
|
-
}
|
|
362
|
+
},
|
|
363
363
|
)
|
|
364
364
|
|
|
365
365
|
const disposeTracking = await database.triggers.createDiffTrigger({
|
|
@@ -378,7 +378,7 @@ export function powerSyncCollectionOptions<
|
|
|
378
378
|
begin()
|
|
379
379
|
const batchItems = await context.getAll<TableType>(
|
|
380
380
|
sanitizeSQL`SELECT * FROM ${viewName} LIMIT ? OFFSET ?`,
|
|
381
|
-
[syncBatchSize, cursor]
|
|
381
|
+
[syncBatchSize, cursor],
|
|
382
382
|
)
|
|
383
383
|
currentBatchCount = batchItems.length
|
|
384
384
|
cursor += currentBatchCount
|
|
@@ -392,7 +392,7 @@ export function powerSyncCollectionOptions<
|
|
|
392
392
|
}
|
|
393
393
|
markReady()
|
|
394
394
|
database.logger.info(
|
|
395
|
-
`Sync is ready for ${viewName} into ${trackedTableName}
|
|
395
|
+
`Sync is ready for ${viewName} into ${trackedTableName}`,
|
|
396
396
|
)
|
|
397
397
|
},
|
|
398
398
|
},
|
|
@@ -407,7 +407,7 @@ export function powerSyncCollectionOptions<
|
|
|
407
407
|
() => {
|
|
408
408
|
disposeTracking()
|
|
409
409
|
},
|
|
410
|
-
{ once: true }
|
|
410
|
+
{ once: true },
|
|
411
411
|
)
|
|
412
412
|
}
|
|
413
413
|
}
|
|
@@ -415,13 +415,13 @@ export function powerSyncCollectionOptions<
|
|
|
415
415
|
start().catch((error) =>
|
|
416
416
|
database.logger.error(
|
|
417
417
|
`Could not start syncing process for ${viewName} into ${trackedTableName}`,
|
|
418
|
-
error
|
|
419
|
-
)
|
|
418
|
+
error,
|
|
419
|
+
),
|
|
420
420
|
)
|
|
421
421
|
|
|
422
422
|
return () => {
|
|
423
423
|
database.logger.info(
|
|
424
|
-
`Sync has been stopped for ${viewName} into ${trackedTableName}
|
|
424
|
+
`Sync has been stopped for ${viewName} into ${trackedTableName}`,
|
|
425
425
|
)
|
|
426
426
|
abortController.abort()
|
|
427
427
|
}
|
|
@@ -470,7 +470,7 @@ export function powerSyncCollectionOptions<
|
|
|
470
470
|
serializer as CustomSQLiteSerializer<
|
|
471
471
|
OutputType,
|
|
472
472
|
ExtractedTableColumns<Table<MapBaseColumnType<OutputType>>>
|
|
473
|
-
|
|
473
|
+
>,
|
|
474
474
|
),
|
|
475
475
|
}),
|
|
476
476
|
},
|
package/src/schema.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ColumnType } from
|
|
2
|
-
import type { Table } from
|
|
3
|
-
import type { StandardSchemaV1 } from
|
|
4
|
-
import type { ExtractedTable } from
|
|
1
|
+
import { ColumnType } from '@powersync/common'
|
|
2
|
+
import type { Table } from '@powersync/common'
|
|
3
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
|
4
|
+
import type { ExtractedTable } from './helpers'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Converts a PowerSync Table instance to a StandardSchemaV1 schema.
|
|
@@ -21,12 +21,12 @@ import type { ExtractedTable } from "./helpers"
|
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
23
23
|
export function convertTableToSchema<TTable extends Table>(
|
|
24
|
-
table: TTable
|
|
24
|
+
table: TTable,
|
|
25
25
|
): StandardSchemaV1<ExtractedTable<TTable>> {
|
|
26
26
|
type TExtracted = ExtractedTable<TTable>
|
|
27
27
|
// Create validate function that checks types according to column definitions
|
|
28
28
|
const validate = (
|
|
29
|
-
value: unknown
|
|
29
|
+
value: unknown,
|
|
30
30
|
):
|
|
31
31
|
| StandardSchemaV1.SuccessResult<TExtracted>
|
|
32
32
|
| StandardSchemaV1.FailureResult => {
|
|
@@ -87,7 +87,7 @@ export function convertTableToSchema<TTable extends Table>(
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
return {
|
|
90
|
-
|
|
90
|
+
'~standard': {
|
|
91
91
|
version: 1,
|
|
92
92
|
vendor: `powersync`,
|
|
93
93
|
validate,
|
package/src/serialization.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { ColumnType } from
|
|
2
|
-
import type { Table } from
|
|
3
|
-
import type { CustomSQLiteSerializer } from
|
|
1
|
+
import { ColumnType } from '@powersync/common'
|
|
2
|
+
import type { Table } from '@powersync/common'
|
|
3
|
+
import type { CustomSQLiteSerializer } from './definitions'
|
|
4
4
|
import type {
|
|
5
5
|
ExtractedTable,
|
|
6
6
|
ExtractedTableColumns,
|
|
7
7
|
MapBaseColumnType,
|
|
8
|
-
} from
|
|
8
|
+
} from './helpers'
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Serializes an object for persistence to a SQLite table, mapping its values to appropriate SQLite types.
|
|
@@ -48,7 +48,7 @@ export function serializeForSQLite<
|
|
|
48
48
|
tableSchema: TTable,
|
|
49
49
|
customSerializer: Partial<
|
|
50
50
|
CustomSQLiteSerializer<TOutput, ExtractedTableColumns<TTable>>
|
|
51
|
-
> = {}
|
|
51
|
+
> = {},
|
|
52
52
|
): ExtractedTable<TTable> {
|
|
53
53
|
return Object.fromEntries(
|
|
54
54
|
Object.entries(value).map(([key, value]) => {
|
|
@@ -90,12 +90,12 @@ export function serializeForSQLite<
|
|
|
90
90
|
const numberValue = Number(value)
|
|
91
91
|
if (isNaN(numberValue)) {
|
|
92
92
|
throw new Error(
|
|
93
|
-
`Could not convert ${key}=${value} to a number for SQLite
|
|
93
|
+
`Could not convert ${key}=${value} to a number for SQLite`,
|
|
94
94
|
)
|
|
95
95
|
}
|
|
96
96
|
return [key, numberValue]
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
-
})
|
|
99
|
+
}),
|
|
100
100
|
)
|
|
101
101
|
}
|