@tanstack/db
Version:
A reactive client store for building super fast apps on sync
294 lines (293 loc) • 12.1 kB
text/typescript
import { Store } from '@tanstack/store';
import { Transaction } from './transactions.cjs';
import { SortedMap } from './SortedMap.cjs';
import { ChangeListener, ChangeMessage, CollectionConfig, Fn, InsertConfig, OperationConfig, Transaction as TransactionType, UtilsRecord } from './types.cjs';
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>>;
}