UNPKG

@tanstack/db-collections

Version:

A collection for (aspirationally) every way of loading your data

232 lines (231 loc) 10.1 kB
import { CollectionConfig, DeleteMutationFnParams, InsertMutationFnParams, SyncConfig, UpdateMutationFnParams, UtilsRecord } from '@tanstack/db'; import { StandardSchemaV1 } from '@standard-schema/spec'; import { GetExtensions, Row, ShapeStreamOptions } from '@electric-sql/client'; /** * Type representing a transaction ID in Electric SQL */ export type Txid = number; type InferSchemaOutput<T> = T extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<T> extends Row<unknown> ? StandardSchemaV1.InferOutput<T> : Record<string, unknown> : Record<string, unknown>; type ResolveType<TExplicit extends Row<unknown> = Row<unknown>, TSchema extends StandardSchemaV1 = never, TFallback extends object = Record<string, unknown>> = unknown extends GetExtensions<TExplicit> ? [TSchema] extends [never] ? TFallback : InferSchemaOutput<TSchema> : TExplicit; /** * Configuration interface for Electric collection options * @template TExplicit - The explicit type of items in the collection (highest priority) * @template TSchema - The schema type for validation and type inference (second priority) * @template TFallback - The fallback type if no explicit or schema type is provided * * @remarks * Type resolution follows a priority order: * 1. If you provide an explicit type via generic parameter, it will be used * 2. If no explicit type is provided but a schema is, the schema's output type will be inferred * 3. If neither explicit type nor schema is provided, the fallback type will be used * * You should provide EITHER an explicit type OR a schema, but not both, as they would conflict. */ export interface ElectricCollectionConfig<TExplicit extends Row<unknown> = Row<unknown>, TSchema extends StandardSchemaV1 = never, TFallback extends Row<unknown> = Row<unknown>> { /** * Configuration options for the ElectricSQL ShapeStream */ shapeOptions: ShapeStreamOptions<GetExtensions<ResolveType<TExplicit, TSchema, TFallback>>>; /** * All standard Collection configuration properties */ id?: string; schema?: TSchema; getKey: CollectionConfig<ResolveType<TExplicit, TSchema, TFallback>>[`getKey`]; sync?: CollectionConfig<ResolveType<TExplicit, TSchema, TFallback>>[`sync`]; /** * Optional asynchronous handler function called before an insert operation * Must return an object containing a txid number or array of txids * @param params Object containing transaction and collection information * @returns Promise resolving to an object with txid or txids * @example * // Basic Electric insert handler - MUST return { txid: number } * onInsert: async ({ transaction }) => { * const newItem = transaction.mutations[0].modified * const result = await api.todos.create({ * data: newItem * }) * return { txid: result.txid } // Required for Electric sync matching * } * * @example * // Insert handler with multiple items - return array of txids * onInsert: async ({ transaction }) => { * const items = transaction.mutations.map(m => m.modified) * const results = await Promise.all( * items.map(item => api.todos.create({ data: item })) * ) * return { txid: results.map(r => r.txid) } // Array of txids * } * * @example * // Insert handler with error handling * onInsert: async ({ transaction }) => { * try { * const newItem = transaction.mutations[0].modified * const result = await api.createTodo(newItem) * return { txid: result.txid } * } catch (error) { * console.error('Insert failed:', error) * throw error // This will cause the transaction to fail * } * } * * @example * // Insert handler with batch operation - single txid * onInsert: async ({ transaction }) => { * const items = transaction.mutations.map(m => m.modified) * const result = await api.todos.createMany({ * data: items * }) * return { txid: result.txid } // Single txid for batch operation * } */ onInsert?: (params: InsertMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>) => Promise<{ txid: Txid | Array<Txid>; }>; /** * Optional asynchronous handler function called before an update operation * Must return an object containing a txid number or array of txids * @param params Object containing transaction and collection information * @returns Promise resolving to an object with txid or txids * @example * // Basic Electric update handler - MUST return { txid: number } * onUpdate: async ({ transaction }) => { * const { original, changes } = transaction.mutations[0] * const result = await api.todos.update({ * where: { id: original.id }, * data: changes // Only the changed fields * }) * return { txid: result.txid } // Required for Electric sync matching * } * * @example * // Update handler with multiple items - return array of txids * onUpdate: async ({ transaction }) => { * const updates = await Promise.all( * transaction.mutations.map(m => * api.todos.update({ * where: { id: m.original.id }, * data: m.changes * }) * ) * ) * return { txid: updates.map(u => u.txid) } // Array of txids * } * * @example * // Update handler with optimistic rollback * onUpdate: async ({ transaction }) => { * const mutation = transaction.mutations[0] * try { * const result = await api.updateTodo(mutation.original.id, mutation.changes) * return { txid: result.txid } * } catch (error) { * // Transaction will automatically rollback optimistic changes * console.error('Update failed, rolling back:', error) * throw error * } * } */ onUpdate?: (params: UpdateMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>) => Promise<{ txid: Txid | Array<Txid>; }>; /** * Optional asynchronous handler function called before a delete operation * Must return an object containing a txid number or array of txids * @param params Object containing transaction and collection information * @returns Promise resolving to an object with txid or txids * @example * // Basic Electric delete handler - MUST return { txid: number } * onDelete: async ({ transaction }) => { * const mutation = transaction.mutations[0] * const result = await api.todos.delete({ * id: mutation.original.id * }) * return { txid: result.txid } // Required for Electric sync matching * } * * @example * // Delete handler with multiple items - return array of txids * onDelete: async ({ transaction }) => { * const deletes = await Promise.all( * transaction.mutations.map(m => * api.todos.delete({ * where: { id: m.key } * }) * ) * ) * return { txid: deletes.map(d => d.txid) } // Array of txids * } * * @example * // Delete handler with batch operation - single txid * onDelete: async ({ transaction }) => { * const idsToDelete = transaction.mutations.map(m => m.original.id) * const result = await api.todos.deleteMany({ * ids: idsToDelete * }) * return { txid: result.txid } // Single txid for batch operation * } * * @example * // Delete handler with optimistic rollback * onDelete: async ({ transaction }) => { * const mutation = transaction.mutations[0] * try { * const result = await api.deleteTodo(mutation.original.id) * return { txid: result.txid } * } catch (error) { * // Transaction will automatically rollback optimistic changes * console.error('Delete failed, rolling back:', error) * throw error * } * } * */ onDelete?: (params: DeleteMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>) => Promise<{ txid: Txid | Array<Txid>; }>; } /** * Type for the awaitTxId utility function */ export type AwaitTxIdFn = (txId: Txid, timeout?: number) => Promise<boolean>; /** * Electric collection utilities type */ export interface ElectricCollectionUtils extends UtilsRecord { awaitTxId: AwaitTxIdFn; } /** * Creates Electric collection options for use with a standard Collection * * @template TExplicit - The explicit type of items in the collection (highest priority) * @template TSchema - The schema type for validation and type inference (second priority) * @template TFallback - The fallback type if no explicit or schema type is provided * @param config - Configuration options for the Electric collection * @returns Collection options with utilities */ export declare function electricCollectionOptions<TExplicit extends Row<unknown> = Row<unknown>, TSchema extends StandardSchemaV1 = never, TFallback extends Row<unknown> = Row<unknown>>(config: ElectricCollectionConfig<TExplicit, TSchema, TFallback>): { sync: SyncConfig<ResolveType<TExplicit, TSchema, TFallback>, string | number>; onInsert: ((params: InsertMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>) => Promise<{ txid: Txid | Array<Txid>; }>) | undefined; onUpdate: ((params: UpdateMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>) => Promise<{ txid: Txid | Array<Txid>; }>) | undefined; onDelete: ((params: DeleteMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>) => Promise<{ txid: Txid | Array<Txid>; }>) | undefined; utils: { awaitTxId: AwaitTxIdFn; }; /** * All standard Collection configuration properties */ id?: string; schema?: TSchema | undefined; getKey: (item: ResolveType<TExplicit, TSchema, TFallback>) => string | number; }; export {};