@tanstack/powersync-db-collection 0.1.67 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/PowerSyncTransactor.cjs +31 -18
- package/dist/cjs/PowerSyncTransactor.cjs.map +1 -1
- package/dist/cjs/PowerSyncTransactor.d.cts +4 -4
- package/dist/cjs/definitions.cjs.map +1 -1
- package/dist/cjs/definitions.d.cts +4 -4
- package/dist/cjs/helpers.cjs.map +1 -1
- package/dist/cjs/helpers.d.cts +6 -9
- package/dist/cjs/index.d.cts +1 -0
- package/dist/cjs/powersync.cjs +254 -82
- package/dist/cjs/powersync.cjs.map +1 -1
- package/dist/cjs/serialization.cjs.map +1 -1
- package/dist/cjs/serialization.d.cts +2 -6
- package/dist/esm/PowerSyncTransactor.d.ts +4 -4
- package/dist/esm/PowerSyncTransactor.js +32 -19
- package/dist/esm/PowerSyncTransactor.js.map +1 -1
- package/dist/esm/definitions.d.ts +4 -4
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/helpers.d.ts +6 -9
- package/dist/esm/helpers.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/powersync.js +255 -83
- package/dist/esm/powersync.js.map +1 -1
- package/dist/esm/serialization.d.ts +2 -6
- package/dist/esm/serialization.js.map +1 -1
- package/package.json +7 -6
- package/src/PowerSyncTransactor.ts +46 -25
- package/src/definitions.ts +11 -5
- package/src/helpers.ts +10 -18
- package/src/index.ts +1 -0
- package/src/powersync.ts +316 -101
- package/src/serialization.ts +8 -17
|
@@ -19,20 +19,20 @@ class PowerSyncTransactor {
|
|
|
19
19
|
if (mutations.length == 0) {
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
|
-
const
|
|
23
|
-
(mutation) => mutation.collection.id
|
|
24
|
-
);
|
|
25
|
-
const collectionIds = Array.from(new Set(mutationsCollectionIds));
|
|
22
|
+
const collectionsById = /* @__PURE__ */ new Map();
|
|
26
23
|
const lastCollectionMutationIndexes = /* @__PURE__ */ new Map();
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
collectionId,
|
|
31
|
-
|
|
32
|
-
);
|
|
24
|
+
for (const [index, mutation] of mutations.entries()) {
|
|
25
|
+
const collectionId = mutation.collection.id;
|
|
26
|
+
if (!collectionsById.has(collectionId)) {
|
|
27
|
+
collectionsById.set(collectionId, mutation.collection);
|
|
28
|
+
}
|
|
29
|
+
const changesDatabase = mutation.type != `update` || Object.keys(mutation.changes).some((key) => key != `id`) || typeof mutation.metadata != `undefined` && this.getMutationCollectionMeta(mutation).metadataIsTracked;
|
|
30
|
+
if (changesDatabase) {
|
|
31
|
+
lastCollectionMutationIndexes.set(collectionId, index);
|
|
32
|
+
}
|
|
33
33
|
}
|
|
34
34
|
await Promise.all(
|
|
35
|
-
|
|
35
|
+
Array.from(collectionsById.values()).map(async (collection) => {
|
|
36
36
|
if (collection.isReady()) {
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
@@ -121,7 +121,8 @@ class PowerSyncTransactor {
|
|
|
121
121
|
waitForCompletion,
|
|
122
122
|
// eslint-disable-next-line no-shadow
|
|
123
123
|
async (tableName, mutation2, serializeValue) => {
|
|
124
|
-
const
|
|
124
|
+
const { id: _id, ...changes } = mutation2.changes;
|
|
125
|
+
const values = serializeValue(changes);
|
|
125
126
|
const keys = Object.keys(values).map((key) => common.sanitizeSQL`${key}`);
|
|
126
127
|
const queryParameters = Object.values(values);
|
|
127
128
|
const metadataValue = this.processMutationMetadata(mutation2);
|
|
@@ -129,14 +130,18 @@ class PowerSyncTransactor {
|
|
|
129
130
|
keys.push(`_metadata`);
|
|
130
131
|
queryParameters.push(metadataValue);
|
|
131
132
|
}
|
|
133
|
+
if (keys.length == 0) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
132
136
|
await context.execute(
|
|
133
137
|
`
|
|
134
138
|
UPDATE ${tableName}
|
|
135
139
|
SET ${keys.map((key) => `${key} = ?`).join(`, `)}
|
|
136
140
|
WHERE id = ?
|
|
137
141
|
`,
|
|
138
|
-
[...queryParameters, helpers.asPowerSyncRecord(mutation2.
|
|
142
|
+
[...queryParameters, helpers.asPowerSyncRecord(mutation2.original).id]
|
|
139
143
|
);
|
|
144
|
+
return;
|
|
140
145
|
}
|
|
141
146
|
);
|
|
142
147
|
}
|
|
@@ -175,7 +180,14 @@ class PowerSyncTransactor {
|
|
|
175
180
|
*/
|
|
176
181
|
async handleOperationWithCompletion(mutation, context, waitForCompletion, handler) {
|
|
177
182
|
const { tableName, trackedTableName, serializeValue } = this.getMutationCollectionMeta(mutation);
|
|
178
|
-
await handler(
|
|
183
|
+
const executed = await handler(
|
|
184
|
+
common.sanitizeSQL`${tableName}`,
|
|
185
|
+
mutation,
|
|
186
|
+
serializeValue
|
|
187
|
+
);
|
|
188
|
+
if (executed === false) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
179
191
|
if (!waitForCompletion) {
|
|
180
192
|
return null;
|
|
181
193
|
}
|
|
@@ -204,10 +216,11 @@ class PowerSyncTransactor {
|
|
|
204
216
|
const { metadataIsTracked } = this.getMutationCollectionMeta(mutation);
|
|
205
217
|
if (!metadataIsTracked) {
|
|
206
218
|
if (typeof mutation.metadata != `undefined`) {
|
|
207
|
-
this.database.logger.
|
|
208
|
-
|
|
209
|
-
mutation.metadata
|
|
210
|
-
|
|
219
|
+
this.database.logger.log({
|
|
220
|
+
level: common.LogLevels.warn,
|
|
221
|
+
message: `Metadata provided for collection ${mutation.collection.id} but the PowerSync table does not track metadata. The PowerSync table should be configured with trackMetadata: true.`,
|
|
222
|
+
error: mutation.metadata
|
|
223
|
+
});
|
|
211
224
|
}
|
|
212
225
|
return null;
|
|
213
226
|
} else if (typeof mutation.metadata == `undefined`) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PowerSyncTransactor.cjs","sources":["../../src/PowerSyncTransactor.ts"],"sourcesContent":["import { sanitizeSQL } from '@powersync/common'\nimport { LoadSubsetOperationAbortedError } from '@tanstack/db'\nimport DebugModule from 'debug'\nimport { PendingOperationStore } from './PendingOperationStore'\nimport { asPowerSyncRecord, mapOperationToPowerSync } from './helpers'\nimport type { AbstractPowerSyncDatabase, LockContext } from '@powersync/common'\nimport type { PendingMutation, Transaction } from '@tanstack/db'\nimport type { PendingOperation } from './PendingOperationStore'\nimport type {\n EnhancedPowerSyncCollectionConfig,\n PowerSyncCollectionMeta,\n} from './definitions'\n\nconst debug = DebugModule.debug(`ts/db:powersync`)\n\nexport type TransactorOptions = {\n database: AbstractPowerSyncDatabase\n}\n\n/**\n * Applies mutations to the PowerSync database. This method is called automatically by the collection's\n * insert, update, and delete operations. You typically don't need to call this directly unless you\n * have special transaction requirements.\n *\n * @example\n * ```typescript\n * // Create a collection\n * const collection = createCollection(\n * powerSyncCollectionOptions<Document>({\n * database: db,\n * table: APP_SCHEMA.props.documents,\n * })\n * )\n *\n * const addTx = createTransaction({\n * autoCommit: false,\n * mutationFn: async ({ transaction }) => {\n * await new PowerSyncTransactor({ database: db }).applyTransaction(transaction)\n * },\n * })\n *\n * addTx.mutate(() => {\n * for (let i = 0; i < 5; i++) {\n * collection.insert({ id: randomUUID(), name: `tx-${i}` })\n * }\n * })\n *\n * await addTx.commit()\n * await addTx.isPersisted.promise\n * ```\n *\n * @param transaction - The transaction containing mutations to apply\n * @returns A promise that resolves when the mutations have been persisted to PowerSync\n */\nexport class PowerSyncTransactor {\n database: AbstractPowerSyncDatabase\n pendingOperationStore: PendingOperationStore\n\n constructor(options: TransactorOptions) {\n this.database = options.database\n this.pendingOperationStore = PendingOperationStore.GLOBAL\n }\n\n /**\n * Persists a {@link Transaction} to the PowerSync SQLite database.\n */\n async applyTransaction(transaction: Transaction<any>) {\n const { mutations } = transaction\n\n if (mutations.length == 0) {\n return\n }\n /**\n * The transaction might contain operations for different collections.\n * We can do some optimizations for single-collection transactions.\n */\n const mutationsCollectionIds = mutations.map(\n (mutation) => mutation.collection.id,\n )\n const collectionIds = Array.from(new Set(mutationsCollectionIds))\n const lastCollectionMutationIndexes = new Map<string, number>()\n const allCollections = collectionIds\n .map((id) => mutations.find((mutation) => mutation.collection.id == id)!)\n .map((mutation) => mutation.collection)\n for (const collectionId of collectionIds) {\n lastCollectionMutationIndexes.set(\n collectionId,\n mutationsCollectionIds.lastIndexOf(collectionId),\n )\n }\n\n // Check all the observers are ready before taking a lock\n await Promise.all(\n allCollections.map(async (collection) => {\n if (collection.isReady()) {\n return\n }\n // Observe this session without starting new demand from mutationFn.\n // Cleanup and startup failure must settle the wait before taking a lock.\n await new Promise<void>((resolve, reject) => {\n const check = () => {\n if (collection.isReady()) {\n unsubscribe()\n resolve()\n } else if (\n collection.status === `error` ||\n collection.status === `cleaned-up`\n ) {\n unsubscribe()\n reject(\n collection.status === `error`\n ? (collection._lifecycle.getSyncError() ??\n new Error(`Collection failed before readiness`))\n : new LoadSubsetOperationAbortedError(),\n )\n }\n }\n const unsubscribe = collection.on(`status:change`, check)\n check()\n })\n }),\n )\n\n // Persist to PowerSync\n const { whenComplete } = await this.database.writeTransaction(\n async (tx) => {\n const pendingOperations: Array<PendingOperation | null> = []\n\n for (const [index, mutation] of mutations.entries()) {\n /**\n * Each collection processes events independently. We need to make sure the\n * last operation for each collection has been observed.\n */\n const shouldWait =\n index == lastCollectionMutationIndexes.get(mutation.collection.id)\n switch (mutation.type) {\n case `insert`:\n pendingOperations.push(\n await this.handleInsert(mutation, tx, shouldWait),\n )\n break\n case `update`:\n pendingOperations.push(\n await this.handleUpdate(mutation, tx, shouldWait),\n )\n break\n case `delete`:\n pendingOperations.push(\n await this.handleDelete(mutation, tx, shouldWait),\n )\n break\n }\n }\n\n /**\n * Return a promise from the writeTransaction, without awaiting it.\n * This promise will resolve once the entire transaction has been\n * observed via the diff triggers.\n * We return without awaiting in order to free the write lock.\n */\n return {\n whenComplete: Promise.all(\n pendingOperations\n .filter((op) => !!op)\n .map((op) => this.pendingOperationStore.waitFor(op)),\n ),\n }\n },\n )\n\n // Wait for the change to be observed via the diff trigger\n await whenComplete\n }\n\n protected async handleInsert(\n mutation: PendingMutation<any>,\n context: LockContext,\n waitForCompletion: boolean = false,\n ): Promise<PendingOperation | null> {\n debug(`insert`, mutation)\n\n return this.handleOperationWithCompletion(\n mutation,\n context,\n waitForCompletion,\n // eslint-disable-next-line no-shadow\n async (tableName, mutation, serializeValue) => {\n const values = serializeValue(mutation.modified)\n const keys = Object.keys(values).map((key) => sanitizeSQL`${key}`)\n const queryParameters = Object.values(values)\n\n const metadataValue = this.processMutationMetadata(mutation)\n if (metadataValue != null) {\n keys.push(`_metadata`)\n queryParameters.push(metadataValue)\n }\n\n await context.execute(\n `\n INSERT into ${tableName}\n (${keys.join(`, `)})\n VALUES\n (${keys.map((_) => `?`).join(`, `)})\n `,\n queryParameters,\n )\n },\n )\n }\n\n protected async handleUpdate(\n mutation: PendingMutation<any>,\n context: LockContext,\n waitForCompletion: boolean = false,\n ): Promise<PendingOperation | null> {\n debug(`update`, mutation)\n\n return this.handleOperationWithCompletion(\n mutation,\n context,\n waitForCompletion,\n // eslint-disable-next-line no-shadow\n async (tableName, mutation, serializeValue) => {\n const values = serializeValue(mutation.modified)\n const keys = Object.keys(values).map((key) => sanitizeSQL`${key}`)\n const queryParameters = Object.values(values)\n\n const metadataValue = this.processMutationMetadata(mutation)\n if (metadataValue != null) {\n keys.push(`_metadata`)\n queryParameters.push(metadataValue)\n }\n\n await context.execute(\n `\n UPDATE ${tableName}\n SET ${keys.map((key) => `${key} = ?`).join(`, `)}\n WHERE id = ?\n `,\n [...queryParameters, asPowerSyncRecord(mutation.modified).id],\n )\n },\n )\n }\n\n protected async handleDelete(\n mutation: PendingMutation<any>,\n context: LockContext,\n waitForCompletion: boolean = false,\n ): Promise<PendingOperation | null> {\n debug(`update`, mutation)\n\n return this.handleOperationWithCompletion(\n mutation,\n context,\n waitForCompletion,\n // eslint-disable-next-line no-shadow\n async (tableName, mutation) => {\n const metadataValue = this.processMutationMetadata(mutation)\n if (metadataValue != null) {\n /**\n * Delete operations with metadata require a different approach to handle metadata.\n * This will delete the record.\n */\n await context.execute(\n `\n UPDATE ${tableName} SET _deleted = TRUE, _metadata = ? WHERE id = ?\n `,\n [metadataValue, asPowerSyncRecord(mutation.original).id],\n )\n } else {\n await context.execute(\n `\n DELETE FROM ${tableName} WHERE id = ?\n `,\n [asPowerSyncRecord(mutation.original).id],\n )\n }\n },\n )\n }\n\n /**\n * Helper function which wraps a persistence operation by:\n * - Fetching the mutation's collection's SQLite table details\n * - Executing the mutation\n * - Returning the last pending diff operation if required\n */\n protected async handleOperationWithCompletion(\n mutation: PendingMutation<any>,\n context: LockContext,\n waitForCompletion: boolean,\n handler: (\n tableName: string,\n mutation: PendingMutation<any>,\n serializeValue: (value: any) => Record<string, unknown>,\n ) => Promise<void>,\n ): Promise<PendingOperation | null> {\n const { tableName, trackedTableName, serializeValue } =\n this.getMutationCollectionMeta(mutation)\n\n await handler(sanitizeSQL`${tableName}`, mutation, serializeValue)\n\n if (!waitForCompletion) {\n return null\n }\n\n // Need to get the operation in order to wait for it\n const diffOperation = await context.get<{ id: string; timestamp: string }>(\n sanitizeSQL`SELECT id, timestamp FROM ${trackedTableName} ORDER BY operation_id DESC LIMIT 1`,\n )\n return {\n tableName,\n id: diffOperation.id,\n operation: mapOperationToPowerSync(mutation.type),\n timestamp: diffOperation.timestamp,\n }\n }\n\n protected getMutationCollectionMeta(\n mutation: PendingMutation<any>,\n ): PowerSyncCollectionMeta<any> {\n if (\n typeof (mutation.collection.config as any).utils?.getMeta != `function`\n ) {\n throw new Error(`Collection is not a PowerSync collection.`)\n }\n return (\n mutation.collection\n .config as unknown as EnhancedPowerSyncCollectionConfig<any>\n ).utils.getMeta()\n }\n\n /**\n * Processes collection mutation metadata for persistence to the database.\n * We only support storing string metadata.\n * @returns null if no metadata should be stored.\n */\n protected processMutationMetadata(\n mutation: PendingMutation<any>,\n ): string | null {\n const { metadataIsTracked } = this.getMutationCollectionMeta(mutation)\n if (!metadataIsTracked) {\n // If it's not supported, we don't store metadata.\n if (typeof mutation.metadata != `undefined`) {\n // Log a warning if metadata is provided but not tracked.\n this.database.logger.warn(\n `Metadata provided for collection ${mutation.collection.id} but the PowerSync table does not track metadata. The PowerSync table should be configured with trackMetadata: true.`,\n mutation.metadata,\n )\n }\n return null\n } else if (typeof mutation.metadata == `undefined`) {\n return null\n } else if (typeof mutation.metadata == `string`) {\n return mutation.metadata\n } else {\n return JSON.stringify(mutation.metadata)\n }\n }\n}\n"],"names":["PendingOperationStore","LoadSubsetOperationAbortedError","mutation","sanitizeSQL","asPowerSyncRecord","mapOperationToPowerSync"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PowerSyncTransactor.cjs","sources":["../../src/PowerSyncTransactor.ts"],"sourcesContent":["import { LogLevels, sanitizeSQL } from '@powersync/common'\nimport { LoadSubsetOperationAbortedError } from '@tanstack/db'\nimport DebugModule from 'debug'\nimport { PendingOperationStore } from './PendingOperationStore'\nimport { asPowerSyncRecord, mapOperationToPowerSync } from './helpers'\nimport type { CommonPowerSyncDatabase, LockContext } from '@powersync/common'\nimport type { PendingMutation, Transaction } from '@tanstack/db'\nimport type { PendingOperation } from './PendingOperationStore'\nimport type {\n EnhancedPowerSyncCollectionConfig,\n PowerSyncCollectionMeta,\n} from './definitions'\n\nconst debug = DebugModule.debug(`ts/db:powersync`)\n\nexport type TransactorOptions = {\n database: CommonPowerSyncDatabase\n}\n\n/**\n * Applies mutations to the PowerSync database. This method is called automatically by the collection's\n * insert, update, and delete operations. You typically don't need to call this directly unless you\n * have special transaction requirements.\n *\n * @example\n * ```typescript\n * // Create a collection\n * const collection = createCollection(\n * powerSyncCollectionOptions<Document>({\n * database: db,\n * table: APP_SCHEMA.props.documents,\n * })\n * )\n *\n * const addTx = createTransaction({\n * autoCommit: false,\n * mutationFn: async ({ transaction }) => {\n * await new PowerSyncTransactor({ database: db }).applyTransaction(transaction)\n * },\n * })\n *\n * addTx.mutate(() => {\n * for (let i = 0; i < 5; i++) {\n * collection.insert({ id: randomUUID(), name: `tx-${i}` })\n * }\n * })\n *\n * await addTx.commit()\n * await addTx.isPersisted.promise\n * ```\n *\n * @param transaction - The transaction containing mutations to apply\n * @returns A promise that resolves when the mutations have been persisted to PowerSync\n */\nexport class PowerSyncTransactor {\n database: CommonPowerSyncDatabase\n pendingOperationStore: PendingOperationStore\n\n constructor(options: TransactorOptions) {\n this.database = options.database\n this.pendingOperationStore = PendingOperationStore.GLOBAL\n }\n\n /**\n * Persists a {@link Transaction} to the PowerSync SQLite database.\n */\n async applyTransaction(transaction: Transaction<any>) {\n const { mutations } = transaction\n\n if (mutations.length == 0) {\n return\n }\n /**\n * The transaction might contain operations for different collections.\n * We can do some optimizations for single-collection transactions.\n */\n const collectionsById = new Map<\n string,\n PendingMutation<any>[`collection`]\n >()\n const lastCollectionMutationIndexes = new Map<string, number>()\n for (const [index, mutation] of mutations.entries()) {\n const collectionId = mutation.collection.id\n if (!collectionsById.has(collectionId)) {\n collectionsById.set(collectionId, mutation.collection)\n }\n const changesDatabase =\n mutation.type != `update` ||\n Object.keys(mutation.changes).some((key) => key != `id`) ||\n (typeof mutation.metadata != `undefined` &&\n this.getMutationCollectionMeta(mutation).metadataIsTracked)\n if (changesDatabase) {\n lastCollectionMutationIndexes.set(collectionId, index)\n }\n }\n\n // Check all the observers are ready before taking a lock\n await Promise.all(\n Array.from(collectionsById.values()).map(async (collection) => {\n if (collection.isReady()) {\n return\n }\n // Observe this session without starting new demand from mutationFn.\n // Cleanup and startup failure must settle the wait before taking a lock.\n await new Promise<void>((resolve, reject) => {\n const check = () => {\n if (collection.isReady()) {\n unsubscribe()\n resolve()\n } else if (\n collection.status === `error` ||\n collection.status === `cleaned-up`\n ) {\n unsubscribe()\n reject(\n collection.status === `error`\n ? (collection._lifecycle.getSyncError() ??\n new Error(`Collection failed before readiness`))\n : new LoadSubsetOperationAbortedError(),\n )\n }\n }\n const unsubscribe = collection.on(`status:change`, check)\n check()\n })\n }),\n )\n\n // Persist to PowerSync\n const { whenComplete } = await this.database.writeTransaction(\n async (tx) => {\n const pendingOperations: Array<PendingOperation | null> = []\n\n for (const [index, mutation] of mutations.entries()) {\n /**\n * Each collection processes events independently. We need to make sure the\n * last operation for each collection has been observed.\n */\n const shouldWait =\n index == lastCollectionMutationIndexes.get(mutation.collection.id)\n switch (mutation.type) {\n case `insert`:\n pendingOperations.push(\n await this.handleInsert(mutation, tx, shouldWait),\n )\n break\n case `update`:\n pendingOperations.push(\n await this.handleUpdate(mutation, tx, shouldWait),\n )\n break\n case `delete`:\n pendingOperations.push(\n await this.handleDelete(mutation, tx, shouldWait),\n )\n break\n }\n }\n\n /**\n * Return a promise from the writeTransaction, without awaiting it.\n * This promise will resolve once the entire transaction has been\n * observed via the diff triggers.\n * We return without awaiting in order to free the write lock.\n */\n return {\n whenComplete: Promise.all(\n pendingOperations\n .filter((op) => !!op)\n .map((op) => this.pendingOperationStore.waitFor(op)),\n ),\n }\n },\n )\n\n // Wait for the change to be observed via the diff trigger\n await whenComplete\n }\n\n protected async handleInsert(\n mutation: PendingMutation<any>,\n context: LockContext,\n waitForCompletion: boolean = false,\n ): Promise<PendingOperation | null> {\n debug(`insert`, mutation)\n\n return this.handleOperationWithCompletion(\n mutation,\n context,\n waitForCompletion,\n // eslint-disable-next-line no-shadow\n async (tableName, mutation, serializeValue) => {\n const values = serializeValue(mutation.modified)\n const keys = Object.keys(values).map((key) => sanitizeSQL`${key}`)\n const queryParameters = Object.values(values)\n\n const metadataValue = this.processMutationMetadata(mutation)\n if (metadataValue != null) {\n keys.push(`_metadata`)\n queryParameters.push(metadataValue)\n }\n\n await context.execute(\n `\n INSERT into ${tableName}\n (${keys.join(`, `)})\n VALUES\n (${keys.map((_) => `?`).join(`, `)})\n `,\n queryParameters,\n )\n },\n )\n }\n\n protected async handleUpdate(\n mutation: PendingMutation<any>,\n context: LockContext,\n waitForCompletion: boolean = false,\n ): Promise<PendingOperation | null> {\n debug(`update`, mutation)\n\n return this.handleOperationWithCompletion(\n mutation,\n context,\n waitForCompletion,\n // eslint-disable-next-line no-shadow\n async (tableName, mutation, serializeValue) => {\n const { id: _id, ...changes } = mutation.changes\n const values = serializeValue(changes)\n const keys = Object.keys(values).map((key) => sanitizeSQL`${key}`)\n const queryParameters = Object.values(values)\n\n const metadataValue = this.processMutationMetadata(mutation)\n if (metadataValue != null) {\n keys.push(`_metadata`)\n queryParameters.push(metadataValue)\n }\n\n if (keys.length == 0) {\n return false\n }\n\n await context.execute(\n `\n UPDATE ${tableName}\n SET ${keys.map((key) => `${key} = ?`).join(`, `)}\n WHERE id = ?\n `,\n [...queryParameters, asPowerSyncRecord(mutation.original).id],\n )\n\n return\n },\n )\n }\n\n protected async handleDelete(\n mutation: PendingMutation<any>,\n context: LockContext,\n waitForCompletion: boolean = false,\n ): Promise<PendingOperation | null> {\n debug(`update`, mutation)\n\n return this.handleOperationWithCompletion(\n mutation,\n context,\n waitForCompletion,\n // eslint-disable-next-line no-shadow\n async (tableName, mutation) => {\n const metadataValue = this.processMutationMetadata(mutation)\n if (metadataValue != null) {\n /**\n * Delete operations with metadata require a different approach to handle metadata.\n * This will delete the record.\n */\n await context.execute(\n `\n UPDATE ${tableName} SET _deleted = TRUE, _metadata = ? WHERE id = ?\n `,\n [metadataValue, asPowerSyncRecord(mutation.original).id],\n )\n } else {\n await context.execute(\n `\n DELETE FROM ${tableName} WHERE id = ?\n `,\n [asPowerSyncRecord(mutation.original).id],\n )\n }\n },\n )\n }\n\n /**\n * Helper function which wraps a persistence operation by:\n * - Fetching the mutation's collection's SQLite table details\n * - Executing the mutation\n * - Returning the last pending diff operation if required\n */\n protected async handleOperationWithCompletion(\n mutation: PendingMutation<any>,\n context: LockContext,\n waitForCompletion: boolean,\n handler: (\n tableName: string,\n mutation: PendingMutation<any>,\n serializeValue: (value: any) => Record<string, unknown>,\n ) => Promise<void | false>,\n ): Promise<PendingOperation | null> {\n const { tableName, trackedTableName, serializeValue } =\n this.getMutationCollectionMeta(mutation)\n\n const executed = await handler(\n sanitizeSQL`${tableName}`,\n mutation,\n serializeValue,\n )\n\n if (executed === false) {\n return null\n }\n\n if (!waitForCompletion) {\n return null\n }\n\n // Need to get the operation in order to wait for it\n const diffOperation = await context.get<{ id: string; timestamp: string }>(\n sanitizeSQL`SELECT id, timestamp FROM ${trackedTableName} ORDER BY operation_id DESC LIMIT 1`,\n )\n return {\n tableName,\n id: diffOperation.id,\n operation: mapOperationToPowerSync(mutation.type),\n timestamp: diffOperation.timestamp,\n }\n }\n\n protected getMutationCollectionMeta(\n mutation: PendingMutation<any>,\n ): PowerSyncCollectionMeta<any> {\n if (\n typeof (mutation.collection.config as any).utils?.getMeta != `function`\n ) {\n throw new Error(`Collection is not a PowerSync collection.`)\n }\n return (\n mutation.collection\n .config as unknown as EnhancedPowerSyncCollectionConfig<any>\n ).utils.getMeta()\n }\n\n /**\n * Processes collection mutation metadata for persistence to the database.\n * We only support storing string metadata.\n * @returns null if no metadata should be stored.\n */\n protected processMutationMetadata(\n mutation: PendingMutation<any>,\n ): string | null {\n const { metadataIsTracked } = this.getMutationCollectionMeta(mutation)\n if (!metadataIsTracked) {\n // If it's not supported, we don't store metadata.\n if (typeof mutation.metadata != `undefined`) {\n // Log a warning if metadata is provided but not tracked.\n this.database.logger.log({\n level: LogLevels.warn,\n message: `Metadata provided for collection ${mutation.collection.id} but the PowerSync table does not track metadata. The PowerSync table should be configured with trackMetadata: true.`,\n error: mutation.metadata,\n })\n }\n return null\n } else if (typeof mutation.metadata == `undefined`) {\n return null\n } else if (typeof mutation.metadata == `string`) {\n return mutation.metadata\n } else {\n return JSON.stringify(mutation.metadata)\n }\n }\n}\n"],"names":["PendingOperationStore","LoadSubsetOperationAbortedError","mutation","sanitizeSQL","asPowerSyncRecord","mapOperationToPowerSync","LogLevels"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { PendingOperationStore, PendingOperation } from './PendingOperationStore.cjs';
|
|
2
|
-
import {
|
|
2
|
+
import { CommonPowerSyncDatabase, LockContext } from '@powersync/common';
|
|
3
3
|
import { PendingMutation, Transaction } from '@tanstack/db';
|
|
4
4
|
import { PowerSyncCollectionMeta } from './definitions.cjs';
|
|
5
5
|
export type TransactorOptions = {
|
|
6
|
-
database:
|
|
6
|
+
database: CommonPowerSyncDatabase;
|
|
7
7
|
};
|
|
8
8
|
/**
|
|
9
9
|
* Applies mutations to the PowerSync database. This method is called automatically by the collection's
|
|
@@ -41,7 +41,7 @@ export type TransactorOptions = {
|
|
|
41
41
|
* @returns A promise that resolves when the mutations have been persisted to PowerSync
|
|
42
42
|
*/
|
|
43
43
|
export declare class PowerSyncTransactor {
|
|
44
|
-
database:
|
|
44
|
+
database: CommonPowerSyncDatabase;
|
|
45
45
|
pendingOperationStore: PendingOperationStore;
|
|
46
46
|
constructor(options: TransactorOptions);
|
|
47
47
|
/**
|
|
@@ -57,7 +57,7 @@ export declare class PowerSyncTransactor {
|
|
|
57
57
|
* - Executing the mutation
|
|
58
58
|
* - Returning the last pending diff operation if required
|
|
59
59
|
*/
|
|
60
|
-
protected handleOperationWithCompletion(mutation: PendingMutation<any>, context: LockContext, waitForCompletion: boolean, handler: (tableName: string, mutation: PendingMutation<any>, serializeValue: (value: any) => Record<string, unknown>) => Promise<void>): Promise<PendingOperation | null>;
|
|
60
|
+
protected handleOperationWithCompletion(mutation: PendingMutation<any>, context: LockContext, waitForCompletion: boolean, handler: (tableName: string, mutation: PendingMutation<any>, serializeValue: (value: any) => Record<string, unknown>) => Promise<void | false>): Promise<PendingOperation | null>;
|
|
61
61
|
protected getMutationCollectionMeta(mutation: PendingMutation<any>): PowerSyncCollectionMeta<any>;
|
|
62
62
|
/**
|
|
63
63
|
* Processes collection mutation metadata for persistence to the database.
|
|
@@ -1 +1 @@
|
|
|
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 CleanupFn,\n CollectionConfig,\n InferSchemaOutput,\n LoadSubsetOptions,\n} from '@tanstack/db'\nimport type {\n AnyTableColumnType,\n ExtractedTable,\n OptionalExtractedTable,\n PowerSyncRecord,\n} from './helpers'\n\n/**\n * Small helper which determines the output type if:\n * - Standard SQLite types are to be used OR\n * - If the provided schema should be used.\n */\nexport type InferPowerSyncOutputType<\n TTable extends Table = Table,\n TSchema extends StandardSchemaV1<PowerSyncRecord> = never,\n> = TSchema extends never ? ExtractedTable<TTable> : InferSchemaOutput<TSchema>\n\n/**\n * A mapping type for custom serialization of object properties to SQLite-compatible values.\n *\n * This type allows you to override, for keys in the input object (`TOutput`), a function that transforms\n * the value to the corresponding SQLite type (`TSQLite`). Keys not specified will use the default SQLite serialization.\n *\n * ## Generics\n * - `TOutput`: The input object type, representing the row data to be serialized.\n * - `TSQLite`: The target SQLite-compatible type for each property, typically inferred from the table schema.\n *\n * ## Usage\n * Use this type to define a map of serialization functions for specific keys when you need custom handling\n * (e.g., converting complex objects, formatting dates, or handling enums).\n *\n * Example:\n * ```ts\n * const serializer: CustomSQLiteSerializer<MyRowType, MySQLiteType> = {\n * createdAt: (date) => date.toISOString(),\n * status: (status) => status ? 1 : 0,\n * meta: (meta) => JSON.stringify(meta),\n * };\n * ```\n *\n * ## Behavior\n * - Each key maps to a function that receives the value and returns the SQLite-compatible value.\n * - Used by `serializeForSQLite` to override default serialization for specific columns.\n */\nexport type CustomSQLiteSerializer<\n TOutput extends Record<string, unknown>,\n TSQLite extends Record<string, unknown>,\n> = Partial<{\n [Key in keyof TOutput]: (\n value: TOutput[Key],\n ) => Key extends keyof TSQLite ? TSQLite[Key] : never\n}>\n\nexport type SerializerConfig<\n TOutput extends Record<string, unknown>,\n TSQLite extends Record<string, unknown>,\n> = {\n /**\n * Optional partial serializer object for customizing how individual columns are serialized for SQLite.\n *\n * This should be a partial map of column keys to serialization functions, following the\n * {@link CustomSQLiteSerializer} type. Each function receives the column value and returns a value\n * compatible with SQLite storage.\n *\n * If not provided for a column, the default behavior is used:\n * - `TEXT`: Strings are stored as-is; Dates are converted to ISO strings; other types are JSON-stringified.\n * - `INTEGER`/`REAL`: Numbers are stored as-is; booleans are mapped to 1/0.\n *\n * Use this option to override serialization for specific columns, such as formatting dates, handling enums,\n * or serializing complex objects.\n *\n * Example:\n * ```typescript\n * serializer: {\n * createdAt: (date) => date.getTime(), // Store as timestamp\n * meta: (meta) => JSON.stringify(meta), // Custom object serialization\n * }\n * ```\n */\n serializer?: CustomSQLiteSerializer<TOutput, TSQLite>\n\n /**\n * Application logic should ensure that incoming synced data is always valid.\n * Failing to deserialize and apply incoming changes results in data inconsistency - which is a fatal error.\n * Use this callback to react to deserialization errors.\n */\n onDeserializationError: (error: StandardSchemaV1.FailureResult) => void\n}\n\n/**\n * Config for when TInput and TOutput are both the SQLite types.\n */\nexport type ConfigWithSQLiteTypes = {}\n\n/**\n * Config where TInput is the SQLite types while TOutput can be defined by TSchema.\n * We can use the same schema to validate TInput and incoming SQLite changes.\n */\nexport type ConfigWithSQLiteInputType<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // TInput is the SQLite types.\n OptionalExtractedTable<TTable>,\n AnyTableColumnType<TTable>\n >,\n> = SerializerConfig<\n StandardSchemaV1.InferOutput<TSchema>,\n ExtractedTable<TTable>\n> & {\n schema: TSchema\n}\n\n/**\n * Config where TInput and TOutput have arbitrarily typed values.\n * The keys of the types need to equal the SQLite types.\n * Since TInput is not the SQLite types, we require a schema in order to deserialize incoming SQLite updates. The schema should validate from SQLite to TOutput.\n */\nexport type ConfigWithArbitraryCollectionTypes<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // The input and output must have the same keys, the value types can be arbitrary\n AnyTableColumnType<TTable>,\n AnyTableColumnType<TTable>\n >,\n> = SerializerConfig<\n StandardSchemaV1.InferOutput<TSchema>,\n ExtractedTable<TTable>\n> & {\n schema: TSchema\n /**\n * Schema for deserializing and validating input data from the sync stream.\n *\n * This schema defines how to transform and validate data coming from SQLite types (as stored in the database)\n * into the desired output types (`TOutput`) expected by your application or validation logic.\n *\n * The generic parameters allow for arbitrary input and output types, so you can specify custom conversion rules\n * for each column. This is especially useful when your application expects richer types (e.g., Date, enums, objects)\n * than what SQLite natively supports.\n *\n * Use this to ensure that incoming data from the sync stream is properly converted and validated before use.\n *\n * Example:\n * ```typescript\n * deserializationSchema: z.object({\n * createdAt: z.preprocess((val) => new Date(val as string), z.date()),\n * meta: z.preprocess((val) => JSON.parse(val as string), z.object({ ... })),\n * })\n * ```\n *\n * This enables robust type safety and validation for incoming data, bridging the gap between SQLite storage\n * and your application's expected types.\n */\n deserializationSchema: StandardSchemaV1<\n ExtractedTable<TTable>,\n StandardSchemaV1.InferOutput<TSchema>\n >\n}\n/**\n * Eager sync mode hooks.\n * Called once when the collection sync starts and stops.\n */\nexport type EagerSyncHooks = {\n syncMode?: 'eager'\n /**\n * Called when the collection sync starts.\n * Use this to set up external data sources (e.g. subscribing to a sync stream).\n *\n * @returns A cleanup function that is called when the collection sync is cleaned up.\n */\n onLoad?: () => CleanupFn | void | Promise<CleanupFn | void>\n onLoadSubset?: never\n}\n\n/**\n * On-demand sync mode hooks.\n * Called each time a subset is loaded or unloaded in response to live query changes.\n */\nexport type OnDemandSyncHooks = {\n syncMode: 'on-demand'\n onLoad?: never\n /**\n * Called when a subset of data is requested by a live query.\n * Use this to set up external data sources for the requested subset\n * (e.g. subscribing to a sync stream with parameters derived from the query predicate).\n *\n * @returns A cleanup function that is called when the subset is unloaded.\n */\n\n onLoadSubset?: (\n options: LoadSubsetOptions,\n ) => CleanupFn | void | Promise<CleanupFn | void>\n}\n\nexport type BasePowerSyncCollectionConfig<\n TTable extends Table = Table,\n TSchema extends StandardSchemaV1 = never,\n> = Omit<\n BaseCollectionConfig<ExtractedTable<TTable>, string, TSchema>,\n `onInsert` | `onUpdate` | `onDelete` | `getKey` | `syncMode`\n> & {\n /** The PowerSync schema Table definition */\n table: TTable\n /** The PowerSync database instance */\n database: AbstractPowerSyncDatabase\n /**\n * The maximum number of documents to read from the SQLite table\n * in a single batch during the initial sync between PowerSync and the\n * in-memory TanStack DB collection.\n *\n * @remarks\n * - Defaults to {@link DEFAULT_BATCH_SIZE} if not specified.\n * - Larger values reduce the number of round trips to the storage\n * engine but increase memory usage per batch.\n * - Smaller values may lower memory usage and allow earlier\n * streaming of initial results, at the cost of more query calls.\n */\n syncBatchSize?: number\n} & (EagerSyncHooks | OnDemandSyncHooks)\n\n/**\n * Configuration interface for PowerSync collection options.\n * @template TTable - The PowerSync table schema definition\n * @template TSchema - The validation schema type\n */\n/**\n * Configuration options for creating a PowerSync collection.\n *\n * @example\n * ```typescript\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * }),\n * })\n *\n * const db = new PowerSyncDatabase({\n * database: {\n * dbFilename: \"test.sqlite\",\n * },\n * schema: APP_SCHEMA,\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents\n * })\n * )\n * ```\n */\nexport type PowerSyncCollectionConfig<\n TTable extends Table = Table,\n TSchema extends StandardSchemaV1<any> = never,\n> = BasePowerSyncCollectionConfig<TTable, TSchema> &\n (\n | ConfigWithSQLiteTypes\n | ConfigWithSQLiteInputType<TTable, TSchema>\n | ConfigWithArbitraryCollectionTypes<TTable, TSchema>\n )\n\n/**\n * Metadata for the PowerSync Collection.\n */\nexport type PowerSyncCollectionMeta<TTable extends Table = Table> = {\n /**\n * The SQLite table representing the collection.\n */\n tableName: string\n /**\n * The internal table used to track diffs for the collection.\n */\n trackedTableName: string\n\n /**\n * Serializes a collection value to the SQLite type\n */\n serializeValue: (value: any) => ExtractedTable<TTable>\n\n /**\n * Whether the PowerSync table tracks metadata.\n */\n metadataIsTracked: boolean\n}\n\n/**\n * A CollectionConfig which includes utilities for PowerSync.\n */\nexport type EnhancedPowerSyncCollectionConfig<\n TTable extends Table,\n OutputType extends Record<string, unknown> = Record<string, unknown>,\n TSchema extends StandardSchemaV1 = never,\n> = CollectionConfig<\n OutputType,\n string,\n TSchema,\n PowerSyncCollectionUtils<TTable>\n> & {\n id?: string\n utils: PowerSyncCollectionUtils<TTable>\n schema?: TSchema\n}\n\n/**\n * Collection-level utilities for PowerSync.\n */\nexport type PowerSyncCollectionUtils<TTable extends Table = Table> = {\n getMeta: () => PowerSyncCollectionMeta<TTable>\n}\n\n/**\n * Default value for {@link PowerSyncCollectionConfig#syncBatchSize}.\n */\nexport const DEFAULT_BATCH_SIZE = 1000\n"],"names":[],"mappings":";;AAiUO,MAAM,qBAAqB;;"}
|
|
1
|
+
{"version":3,"file":"definitions.cjs","sources":["../../src/definitions.ts"],"sourcesContent":["import type { CommonPowerSyncDatabase, Table } from '@powersync/common'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type {\n BaseCollectionConfig,\n CleanupFn,\n CollectionConfig,\n InferSchemaOutput,\n LoadSubsetOptions,\n} from '@tanstack/db'\nimport type {\n AnyTableColumnType,\n ExtractedTable,\n OptionalExtractedTable,\n PowerSyncRecord,\n} from './helpers'\n\n/**\n * Small helper which determines the output type if:\n * - Standard SQLite types are to be used OR\n * - If the provided schema should be used.\n */\nexport type InferPowerSyncOutputType<\n TTable extends Table = Table,\n TSchema extends StandardSchemaV1<PowerSyncRecord> = never,\n> = [TSchema] extends [never]\n ? ExtractedTable<TTable>\n : InferSchemaOutput<TSchema>\n\n/**\n * A mapping type for custom serialization of object properties to SQLite-compatible values.\n *\n * This type allows you to override, for keys in the input object (`TOutput`), a function that transforms\n * the value to the corresponding SQLite type (`TSQLite`). Keys not specified will use the default SQLite serialization.\n *\n * ## Generics\n * - `TOutput`: The input object type, representing the row data to be serialized.\n * - `TSQLite`: The target SQLite-compatible type for each property, typically inferred from the table schema.\n *\n * ## Usage\n * Use this type to define a map of serialization functions for specific keys when you need custom handling\n * (e.g., converting complex objects, formatting dates, or handling enums).\n *\n * Example:\n * ```ts\n * const serializer: CustomSQLiteSerializer<MyRowType, MySQLiteType> = {\n * createdAt: (date) => date.toISOString(),\n * status: (status) => status ? 1 : 0,\n * meta: (meta) => JSON.stringify(meta),\n * };\n * ```\n *\n * ## Behavior\n * - Each key maps to a function that receives the value and returns the SQLite-compatible value.\n * - Used by `serializeForSQLite` to override default serialization for specific columns.\n */\nexport type CustomSQLiteSerializer<\n TOutput extends Record<string, unknown>,\n TSQLite extends Record<string, unknown>,\n> = Partial<{\n [Key in keyof TOutput]: (\n value: TOutput[Key],\n ) => Key extends keyof TSQLite ? TSQLite[Key] : never\n}>\n\nexport type SerializerConfig<\n TOutput extends Record<string, unknown>,\n TSQLite extends Record<string, unknown>,\n> = {\n /**\n * Optional partial serializer object for customizing how individual columns are serialized for SQLite.\n *\n * This should be a partial map of column keys to serialization functions, following the\n * {@link CustomSQLiteSerializer} type. Each function receives the column value and returns a value\n * compatible with SQLite storage.\n *\n * If not provided for a column, the default behavior is used:\n * - `TEXT`: Strings are stored as-is; Dates are converted to ISO strings; other types are JSON-stringified.\n * - `INTEGER`/`REAL`: Numbers are stored as-is; booleans are mapped to 1/0.\n *\n * Use this option to override serialization for specific columns, such as formatting dates, handling enums,\n * or serializing complex objects.\n *\n * Example:\n * ```typescript\n * serializer: {\n * createdAt: (date) => date.getTime(), // Store as timestamp\n * meta: (meta) => JSON.stringify(meta), // Custom object serialization\n * }\n * ```\n */\n serializer?: CustomSQLiteSerializer<TOutput, TSQLite>\n\n /**\n * Application logic should ensure that incoming synced data is always valid.\n * Failing to deserialize and apply incoming changes results in data inconsistency - which is a fatal error.\n * Use this callback to react to deserialization errors.\n */\n onDeserializationError: (error: StandardSchemaV1.FailureResult) => void\n}\n\n/**\n * Config for when TInput and TOutput are both the SQLite types.\n */\nexport type ConfigWithSQLiteTypes = {}\n\n/**\n * Config where TInput is the SQLite types while TOutput can be defined by TSchema.\n * We can use the same schema to validate TInput and incoming SQLite changes.\n */\nexport type ConfigWithSQLiteInputType<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // TInput is the SQLite types.\n OptionalExtractedTable<TTable>,\n AnyTableColumnType<TTable>\n >,\n> = SerializerConfig<\n StandardSchemaV1.InferOutput<TSchema>,\n ExtractedTable<TTable>\n> & {\n schema: TSchema\n}\n\n/**\n * Config where TInput and TOutput have arbitrarily typed values.\n * The keys of the types need to equal the SQLite types.\n * Since TInput is not the SQLite types, we require a schema in order to deserialize incoming SQLite updates. The schema should validate from SQLite to TOutput.\n */\nexport type ConfigWithArbitraryCollectionTypes<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // The input and output must have the same keys, the value types can be arbitrary\n AnyTableColumnType<TTable>,\n AnyTableColumnType<TTable>\n >,\n> = SerializerConfig<\n StandardSchemaV1.InferOutput<TSchema>,\n ExtractedTable<TTable>\n> & {\n schema: TSchema\n /**\n * Schema for deserializing and validating input data from the sync stream.\n *\n * This schema defines how to transform and validate data coming from SQLite types (as stored in the database)\n * into the desired output types (`TOutput`) expected by your application or validation logic.\n *\n * The generic parameters allow for arbitrary input and output types, so you can specify custom conversion rules\n * for each column. This is especially useful when your application expects richer types (e.g., Date, enums, objects)\n * than what SQLite natively supports.\n *\n * Use this to ensure that incoming data from the sync stream is properly converted and validated before use.\n *\n * Example:\n * ```typescript\n * deserializationSchema: z.object({\n * createdAt: z.preprocess((val) => new Date(val as string), z.date()),\n * meta: z.preprocess((val) => JSON.parse(val as string), z.object({ ... })),\n * })\n * ```\n *\n * This enables robust type safety and validation for incoming data, bridging the gap between SQLite storage\n * and your application's expected types.\n */\n deserializationSchema: StandardSchemaV1<\n ExtractedTable<TTable>,\n StandardSchemaV1.InferOutput<TSchema>\n >\n}\n/**\n * Eager sync mode hooks.\n * Called once when the collection sync starts and stops.\n */\nexport type EagerSyncHooks = {\n syncMode?: 'eager'\n /**\n * Called when the collection sync starts.\n * Use this to set up external data sources (e.g. subscribing to a sync stream).\n *\n * @returns A cleanup function that is called when the collection sync is cleaned up.\n */\n onLoad?: () => CleanupFn | void | Promise<CleanupFn | void>\n onLoadSubset?: never\n}\n\n/**\n * On-demand sync mode hooks.\n * Called each time a subset is loaded or unloaded in response to live query changes.\n */\nexport type OnDemandSyncHooks = {\n syncMode: 'on-demand'\n onLoad?: never\n /**\n * Called when a subset of data is requested by a live query.\n * Use this to set up external data sources for the requested subset\n * (e.g. subscribing to a sync stream with parameters derived from the query predicate).\n *\n * @returns A cleanup function that is called when the subset is unloaded.\n */\n\n onLoadSubset?: (\n options: LoadSubsetOptions,\n ) => CleanupFn | void | Promise<CleanupFn | void>\n}\n\nexport type BasePowerSyncCollectionConfig<\n TTable extends Table = Table,\n TSchema extends StandardSchemaV1<any> = never,\n> = Omit<\n BaseCollectionConfig<\n InferPowerSyncOutputType<TTable, TSchema>,\n string,\n TSchema\n >,\n `onInsert` | `onUpdate` | `onDelete` | `getKey` | `syncMode`\n> & {\n /** The PowerSync schema Table definition */\n table: TTable\n /** The PowerSync database instance */\n database: CommonPowerSyncDatabase\n /**\n * The maximum number of documents to read from the SQLite table\n * in a single batch during the initial sync between PowerSync and the\n * in-memory TanStack DB collection.\n *\n * @remarks\n * - Defaults to {@link DEFAULT_BATCH_SIZE} if not specified.\n * - Larger values reduce the number of round trips to the storage\n * engine but increase memory usage per batch.\n * - Smaller values may lower memory usage and allow earlier\n * streaming of initial results, at the cost of more query calls.\n */\n syncBatchSize?: number\n} & (EagerSyncHooks | OnDemandSyncHooks)\n\n/**\n * Configuration interface for PowerSync collection options.\n * @template TTable - The PowerSync table schema definition\n * @template TSchema - The validation schema type\n */\n/**\n * Configuration options for creating a PowerSync collection.\n *\n * @example\n * ```typescript\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * }),\n * })\n *\n * const db = new PowerSyncDatabase({\n * database: {\n * dbFilename: \"test.sqlite\",\n * },\n * schema: APP_SCHEMA,\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents\n * })\n * )\n * ```\n */\nexport type PowerSyncCollectionConfig<\n TTable extends Table = Table,\n TSchema extends StandardSchemaV1<any> = never,\n> = BasePowerSyncCollectionConfig<TTable, TSchema> &\n (\n | ConfigWithSQLiteTypes\n | ConfigWithSQLiteInputType<TTable, TSchema>\n | ConfigWithArbitraryCollectionTypes<TTable, TSchema>\n )\n\n/**\n * Metadata for the PowerSync Collection.\n */\nexport type PowerSyncCollectionMeta<TTable extends Table = Table> = {\n /**\n * The SQLite table representing the collection.\n */\n tableName: string\n /**\n * The internal table used to track diffs for the collection.\n */\n trackedTableName: string\n\n /**\n * Serializes a collection value to the SQLite type\n */\n serializeValue: (value: any) => ExtractedTable<TTable>\n\n /**\n * Whether the PowerSync table tracks metadata.\n */\n metadataIsTracked: boolean\n}\n\n/**\n * A CollectionConfig which includes utilities for PowerSync.\n */\nexport type EnhancedPowerSyncCollectionConfig<\n TTable extends Table,\n OutputType extends Record<string, unknown> = Record<string, unknown>,\n TSchema extends StandardSchemaV1 = never,\n> = CollectionConfig<\n OutputType,\n string,\n TSchema,\n PowerSyncCollectionUtils<TTable>\n> & {\n id?: string\n utils: PowerSyncCollectionUtils<TTable>\n schema?: TSchema\n}\n\n/**\n * Collection-level utilities for PowerSync.\n */\nexport type PowerSyncCollectionUtils<TTable extends Table = Table> = {\n getMeta: () => PowerSyncCollectionMeta<TTable>\n}\n\n/**\n * Default value for {@link PowerSyncCollectionConfig#syncBatchSize}.\n */\nexport const DEFAULT_BATCH_SIZE = 1000\n"],"names":[],"mappings":";;AAuUO,MAAM,qBAAqB;;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CommonPowerSyncDatabase, Table } from '@powersync/common';
|
|
2
2
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
3
3
|
import { BaseCollectionConfig, CleanupFn, CollectionConfig, InferSchemaOutput, LoadSubsetOptions } from '@tanstack/db';
|
|
4
4
|
import { AnyTableColumnType, ExtractedTable, OptionalExtractedTable, PowerSyncRecord } from './helpers.cjs';
|
|
@@ -7,7 +7,7 @@ import { AnyTableColumnType, ExtractedTable, OptionalExtractedTable, PowerSyncRe
|
|
|
7
7
|
* - Standard SQLite types are to be used OR
|
|
8
8
|
* - If the provided schema should be used.
|
|
9
9
|
*/
|
|
10
|
-
export type InferPowerSyncOutputType<TTable extends Table = Table, TSchema extends StandardSchemaV1<PowerSyncRecord> = never> = TSchema extends never ? ExtractedTable<TTable> : InferSchemaOutput<TSchema>;
|
|
10
|
+
export type InferPowerSyncOutputType<TTable extends Table = Table, TSchema extends StandardSchemaV1<PowerSyncRecord> = never> = [TSchema] extends [never] ? ExtractedTable<TTable> : InferSchemaOutput<TSchema>;
|
|
11
11
|
/**
|
|
12
12
|
* A mapping type for custom serialization of object properties to SQLite-compatible values.
|
|
13
13
|
*
|
|
@@ -143,11 +143,11 @@ export type OnDemandSyncHooks = {
|
|
|
143
143
|
*/
|
|
144
144
|
onLoadSubset?: (options: LoadSubsetOptions) => CleanupFn | void | Promise<CleanupFn | void>;
|
|
145
145
|
};
|
|
146
|
-
export type BasePowerSyncCollectionConfig<TTable extends Table = Table, TSchema extends StandardSchemaV1 = never> = Omit<BaseCollectionConfig<
|
|
146
|
+
export type BasePowerSyncCollectionConfig<TTable extends Table = Table, TSchema extends StandardSchemaV1<any> = never> = Omit<BaseCollectionConfig<InferPowerSyncOutputType<TTable, TSchema>, string, TSchema>, `onInsert` | `onUpdate` | `onDelete` | `getKey` | `syncMode`> & {
|
|
147
147
|
/** The PowerSync schema Table definition */
|
|
148
148
|
table: TTable;
|
|
149
149
|
/** The PowerSync database instance */
|
|
150
|
-
database:
|
|
150
|
+
database: CommonPowerSyncDatabase;
|
|
151
151
|
/**
|
|
152
152
|
* The maximum number of documents to read from the SQLite table
|
|
153
153
|
* in a single batch during the initial sync between PowerSync and the
|
package/dist/cjs/helpers.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.cjs","sources":["../../src/helpers.ts"],"sourcesContent":["import { DiffTriggerOperation } from '@powersync/common'\nimport type {
|
|
1
|
+
{"version":3,"file":"helpers.cjs","sources":["../../src/helpers.ts"],"sourcesContent":["import { DiffTriggerOperation } from '@powersync/common'\nimport type { ExtractColumnValueType, Table } from '@powersync/common'\n\n/**\n * All PowerSync table records include a UUID `id` column.\n */\nexport type PowerSyncRecord = {\n id: string\n [key: string]: unknown\n}\n\n/**\n * Utility type: If T includes null, also allow undefined (to support optional fields in insert/update operations).\n * PowerSync records are typically typed as `string | null`, where insert\n * and update operations may also allow not specifying a value at all (optional).\n */\ntype WithUndefinedIfNull<T> = null extends T ? T | undefined : T\ntype OptionalIfUndefined<T> = {\n [K in keyof T as undefined extends T[K] ? K : never]?: T[K]\n} & {\n [K in keyof T as undefined extends T[K] ? never : K]: T[K]\n}\n\n/**\n * Provides the base column types for a table. This excludes the `id` column.\n */\nexport type ExtractedTableColumns<TTable extends Table> =\n TTable extends Table<infer Columns>\n ? {\n [K in keyof Columns]: ExtractColumnValueType<Columns[K]>\n }\n : never\n/**\n * Utility type that extracts the typed structure of a table based on its column definitions.\n * Maps each column to its corresponding TypeScript type using ExtractColumnValueType.\n *\n * @template TTable - The PowerSync table definition\n * @example\n * ```typescript\n * const table = new Table({\n * name: column.text,\n * age: column.integer\n * })\n * type TableType = ExtractedTable<typeof table>\n * // Results in: { id: string, name: string | null, age: number | null }\n * ```\n */\nexport type ExtractedTable<TTable extends Table> =\n ExtractedTableColumns<TTable> & {\n id: string\n }\n\nexport type OptionalExtractedTable<TTable extends Table> = OptionalIfUndefined<{\n [K in keyof ExtractedTableColumns<TTable>]: WithUndefinedIfNull<\n ExtractedTableColumns<TTable>[K]\n >\n}> & {\n id: string\n}\n\n/**\n * Maps the schema of TTable to a type which\n * requires the keys be equal, but the values can have any value type.\n */\nexport type AnyTableColumnType<TTable extends Table> = {\n [K in keyof ExtractedTableColumns<TTable>]: unknown\n} & { id: string }\n\nexport function asPowerSyncRecord(record: any): PowerSyncRecord {\n if (typeof record.id !== `string`) {\n throw new Error(`Record must have a string id field`)\n }\n return record as PowerSyncRecord\n}\n\n/**\n * Maps {@link DiffTriggerOperation} to TanstackDB operations\n */\nexport function mapOperation(operation: DiffTriggerOperation) {\n switch (operation) {\n case DiffTriggerOperation.INSERT:\n return `insert`\n case DiffTriggerOperation.UPDATE:\n return `update`\n case DiffTriggerOperation.DELETE:\n return `delete`\n }\n}\n\n/**\n * Maps TanstackDB operations to {@link DiffTriggerOperation}\n */\nexport function mapOperationToPowerSync(operation: string) {\n switch (operation) {\n case `insert`:\n return DiffTriggerOperation.INSERT\n case `update`:\n return DiffTriggerOperation.UPDATE\n case `delete`:\n return DiffTriggerOperation.DELETE\n default:\n throw new Error(`Unknown operation ${operation} received`)\n }\n}\n"],"names":["DiffTriggerOperation"],"mappings":";;;AAoEO,SAAS,kBAAkB,QAA8B;AAC9D,MAAI,OAAO,OAAO,OAAO,UAAU;AACjC,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACtD;AACA,SAAO;AACT;AAKO,SAAS,aAAa,WAAiC;AAC5D,UAAQ,WAAA;AAAA,IACN,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;;;;"}
|
package/dist/cjs/helpers.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DiffTriggerOperation,
|
|
1
|
+
import { DiffTriggerOperation, ExtractColumnValueType, Table } from '@powersync/common';
|
|
2
2
|
/**
|
|
3
3
|
* All PowerSync table records include a UUID `id` column.
|
|
4
4
|
*/
|
|
@@ -20,9 +20,9 @@ type OptionalIfUndefined<T> = {
|
|
|
20
20
|
/**
|
|
21
21
|
* Provides the base column types for a table. This excludes the `id` column.
|
|
22
22
|
*/
|
|
23
|
-
export type ExtractedTableColumns<TTable extends Table> = {
|
|
24
|
-
[K in keyof
|
|
25
|
-
};
|
|
23
|
+
export type ExtractedTableColumns<TTable extends Table> = TTable extends Table<infer Columns> ? {
|
|
24
|
+
[K in keyof Columns]: ExtractColumnValueType<Columns[K]>;
|
|
25
|
+
} : never;
|
|
26
26
|
/**
|
|
27
27
|
* Utility type that extracts the typed structure of a table based on its column definitions.
|
|
28
28
|
* Maps each column to its corresponding TypeScript type using ExtractColumnValueType.
|
|
@@ -42,7 +42,7 @@ export type ExtractedTable<TTable extends Table> = ExtractedTableColumns<TTable>
|
|
|
42
42
|
id: string;
|
|
43
43
|
};
|
|
44
44
|
export type OptionalExtractedTable<TTable extends Table> = OptionalIfUndefined<{
|
|
45
|
-
[K in keyof TTable
|
|
45
|
+
[K in keyof ExtractedTableColumns<TTable>]: WithUndefinedIfNull<ExtractedTableColumns<TTable>[K]>;
|
|
46
46
|
}> & {
|
|
47
47
|
id: string;
|
|
48
48
|
};
|
|
@@ -51,14 +51,11 @@ export type OptionalExtractedTable<TTable extends Table> = OptionalIfUndefined<{
|
|
|
51
51
|
* requires the keys be equal, but the values can have any value type.
|
|
52
52
|
*/
|
|
53
53
|
export type AnyTableColumnType<TTable extends Table> = {
|
|
54
|
-
[K in keyof TTable
|
|
54
|
+
[K in keyof ExtractedTableColumns<TTable>]: unknown;
|
|
55
55
|
} & {
|
|
56
56
|
id: string;
|
|
57
57
|
};
|
|
58
58
|
export declare function asPowerSyncRecord(record: any): PowerSyncRecord;
|
|
59
|
-
export type MapBaseColumnType<TOutput> = {
|
|
60
|
-
[Key in keyof TOutput]: BaseColumnType<any>;
|
|
61
|
-
};
|
|
62
59
|
/**
|
|
63
60
|
* Maps {@link DiffTriggerOperation} to TanstackDB operations
|
|
64
61
|
*/
|
package/dist/cjs/index.d.cts
CHANGED