UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

294 lines (293 loc) 12.1 kB
import { Store } from '@tanstack/store'; import { Transaction } from './transactions.js'; import { SortedMap } from './SortedMap.js'; import { ChangeListener, ChangeMessage, CollectionConfig, Fn, InsertConfig, OperationConfig, Transaction as TransactionType, UtilsRecord } from './types.js'; export declare const collectionsStore: Map<string, CollectionImpl<any, any>>; /** * Enhanced Collection interface that includes both data type T and utilities TUtils * @template T - The type of items in the collection * @template TUtils - The utilities record type */ export interface Collection<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord = {}> extends CollectionImpl<T, TKey> { readonly utils: TUtils; } /** * Creates a new Collection instance with the given configuration * * @template T - The type of items in the collection * @template TKey - The type of the key for the collection * @template TUtils - The utilities record type * @param options - Collection options with optional utilities * @returns A new Collection with utilities exposed both at top level and under .utils */ export declare function createCollection<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord = {}>(options: CollectionConfig<T, TKey> & { utils?: TUtils; }): Collection<T, TKey, TUtils>; /** * Preloads a collection with the given configuration * Returns a promise that resolves once the sync tool has done its first commit (initial sync is finished) * If the collection has already loaded, it resolves immediately * * This function is useful in route loaders or similar pre-rendering scenarios where you want * to ensure data is available before a route transition completes. It uses the same shared collection * instance that will be used by useCollection, ensuring data consistency. * * @example * ```typescript * // In a route loader * async function loader({ params }) { * await preloadCollection({ * id: `users-${params.userId}`, * sync: { ... }, * }); * * return null; * } * ``` * * @template T - The type of items in the collection * @param config - Configuration for the collection, including id and sync * @returns Promise that resolves when the initial sync is finished */ export declare function preloadCollection<T extends object = Record<string, unknown>, TKey extends string | number = string | number>(config: CollectionConfig<T, TKey>): Promise<CollectionImpl<T, TKey>>; /** * Custom error class for schema validation errors */ export declare class SchemaValidationError extends Error { type: `insert` | `update`; issues: ReadonlyArray<{ message: string; path?: ReadonlyArray<string | number | symbol>; }>; constructor(type: `insert` | `update`, issues: ReadonlyArray<{ message: string; path?: ReadonlyArray<string | number | symbol>; }>, message?: string); } export declare class CollectionImpl<T extends object = Record<string, unknown>, TKey extends string | number = string | number> { transactions: SortedMap<string, Transaction<any>>; syncedData: Map<TKey, T>; syncedMetadata: Map<TKey, unknown>; derivedUpserts: Map<TKey, T>; derivedDeletes: Set<TKey>; private _size; private changeListeners; private changeKeyListeners; utils: Record<string, Fn>; private pendingSyncedTransactions; private syncedKeys; config: CollectionConfig<T, TKey>; private hasReceivedFirstCommit; private onFirstCommitCallbacks; /** * Register a callback to be executed on the next commit * Useful for preloading collections * @param callback Function to call after the next commit */ onFirstCommit(callback: () => void): void; id: string; /** * Creates a new Collection instance * * @param config - Configuration object for the collection * @throws Error if sync config is missing */ constructor(config: CollectionConfig<T, TKey>); /** * Recompute optimistic state from active transactions */ private recomputeOptimisticState; /** * Calculate the current size based on synced data and optimistic changes */ private calculateSize; /** * Collect events for optimistic changes */ private collectOptimisticChanges; /** * Get the previous value for a key given previous optimistic state */ private getPreviousValue; /** * Emit multiple events at once to all listeners */ private emitEvents; /** * Get the current value for a key (virtual derived state) */ get(key: TKey): T | undefined; /** * Check if a key exists in the collection (virtual derived state) */ has(key: TKey): boolean; /** * Get the current size of the collection (cached) */ get size(): number; /** * Get all keys (virtual derived state) */ keys(): IterableIterator<TKey>; /** * Get all values (virtual derived state) */ values(): IterableIterator<T>; /** * Get all entries (virtual derived state) */ entries(): IterableIterator<[TKey, T]>; /** * Attempts to commit pending synced transactions if there are no active transactions * This method processes operations from pending transactions and applies them to the synced data */ commitPendingTransactions: () => void; private ensureStandardSchema; getKeyFromItem(item: T): TKey; generateGlobalKey(key: any, item: any): string; private validateData; /** * Inserts one or more items into the collection * @param items - Single item or array of items to insert * @param config - Optional configuration including metadata and custom keys * @returns A TransactionType object representing the insert operation(s) * @throws {SchemaValidationError} If the data fails schema validation * @example * // Insert a single item * insert({ text: "Buy groceries", completed: false }) * * // Insert multiple items * insert([ * { text: "Buy groceries", completed: false }, * { text: "Walk dog", completed: false } * ]) * * // Insert with custom key * insert({ text: "Buy groceries" }, { key: "grocery-task" }) */ insert: (data: T | Array<T>, config?: InsertConfig) => Transaction<Record<string, unknown>>; /** * Updates one or more items in the collection using a callback function * @param items - Single item/key or array of items/keys to update * @param configOrCallback - Either update configuration or update callback * @param maybeCallback - Update callback if config was provided * @returns A Transaction object representing the update operation(s) * @throws {SchemaValidationError} If the updated data fails schema validation * @example * // Update a single item * update(todo, (draft) => { draft.completed = true }) * * // Update multiple items * update([todo1, todo2], (drafts) => { * drafts.forEach(draft => { draft.completed = true }) * }) * * // Update with metadata * update(todo, { metadata: { reason: "user update" } }, (draft) => { draft.text = "Updated text" }) */ /** * Updates one or more items in the collection using a callback function * @param ids - Single ID or array of IDs to update * @param configOrCallback - Either update configuration or update callback * @param maybeCallback - Update callback if config was provided * @returns A Transaction object representing the update operation(s) * @throws {SchemaValidationError} If the updated data fails schema validation * @example * // Update a single item * update("todo-1", (draft) => { draft.completed = true }) * * // Update multiple items * update(["todo-1", "todo-2"], (drafts) => { * drafts.forEach(draft => { draft.completed = true }) * }) * * // Update with metadata * update("todo-1", { metadata: { reason: "user update" } }, (draft) => { draft.text = "Updated text" }) */ update<TItem extends object = T>(key: Array<TKey | unknown>, callback: (drafts: Array<TItem>) => void): TransactionType; update<TItem extends object = T>(keys: Array<TKey | unknown>, config: OperationConfig, callback: (drafts: Array<TItem>) => void): TransactionType; update<TItem extends object = T>(id: TKey | unknown, callback: (draft: TItem) => void): TransactionType; update<TItem extends object = T>(id: TKey | unknown, config: OperationConfig, callback: (draft: TItem) => void): TransactionType; /** * Deletes one or more items from the collection * @param ids - Single ID or array of IDs to delete * @param config - Optional configuration including metadata * @returns A TransactionType object representing the delete operation(s) * @example * // Delete a single item * delete("todo-1") * * // Delete multiple items * delete(["todo-1", "todo-2"]) * * // Delete with metadata * delete("todo-1", { metadata: { reason: "completed" } }) */ delete: (keys: Array<TKey> | TKey, config?: OperationConfig) => TransactionType<any>; /** * Gets the current state of the collection as a Map * * @returns A Map containing all items in the collection, with keys as identifiers */ get state(): Map<TKey, T>; /** * Gets the current state of the collection as a Map, but only resolves when data is available * Waits for the first sync commit to complete before resolving * * @returns Promise that resolves to a Map containing all items in the collection */ stateWhenReady(): Promise<Map<TKey, T>>; /** * Gets the current state of the collection as an Array * * @returns An Array containing all items in the collection */ get toArray(): T[]; /** * Gets the current state of the collection as an Array, but only resolves when data is available * Waits for the first sync commit to complete before resolving * * @returns Promise that resolves to an Array containing all items in the collection */ toArrayWhenReady(): Promise<Array<T>>; /** * Returns the current state of the collection as an array of changes * @returns An array of changes */ currentStateAsChanges(): Array<ChangeMessage<T>>; /** * Subscribe to changes in the collection * @param callback - A function that will be called with the changes in the collection * @returns A function that can be called to unsubscribe from the changes */ subscribeChanges(callback: (changes: Array<ChangeMessage<T>>) => void, { includeInitialState }?: { includeInitialState?: boolean; }): () => void; /** * Subscribe to changes for a specific key */ subscribeChangesKey(key: TKey, listener: ChangeListener<T, TKey>, { includeInitialState }?: { includeInitialState?: boolean; }): () => void; /** * Trigger a recomputation when transactions change * This method should be called by the Transaction class when state changes */ onTransactionStateChange(): void; private _storeMap; /** * Returns a Tanstack Store Map that is updated when the collection changes * This is a temporary solution to enable the existing framework hooks to work * with the new internals of Collection until they are rewritten. * TODO: Remove this once the framework hooks are rewritten. */ asStoreMap(): Store<Map<TKey, T>>; private _storeArray; /** * Returns a Tanstack Store Array that is updated when the collection changes * This is a temporary solution to enable the existing framework hooks to work * with the new internals of Collection until they are rewritten. * TODO: Remove this once the framework hooks are rewritten. */ asStoreArray(): Store<Array<T>>; }