import { BaseCollectionConfig, CollectionConfig, LoadSubsetOptions, UtilsRecord } from '@tanstack/db'; import { QueryClient, QueryFunctionContext, QueryKey, QueryObserverOptions, QueryObserverResult } from '@tanstack/query-core'; import { StandardSchemaV1 } from '@standard-schema/spec'; export type { SyncOperation } from './manual-sync.js'; type InferSchemaOutput = T extends StandardSchemaV1 ? StandardSchemaV1.InferOutput extends object ? StandardSchemaV1.InferOutput : Record : Record; type InferSchemaInput = T extends StandardSchemaV1 ? StandardSchemaV1.InferInput extends object ? StandardSchemaV1.InferInput : Record : Record; type TQueryKeyBuilder = (opts: LoadSubsetOptions) => TQueryKey; /** * Configuration options for creating a Query Collection * @template T - The explicit type of items stored in the collection * @template TQueryFn - The queryFn type * @template TError - The type of errors that can occur during queries * @template TQueryKey - The type of the query key * @template TKey - The type of the item keys * @template TSchema - The schema type for validation */ export interface QueryCollectionConfig) => any = (context: QueryFunctionContext) => any, TError = unknown, TQueryKey extends QueryKey = QueryKey, TKey extends string | number = string | number, TSchema extends StandardSchemaV1 = never, TQueryData = Awaited>> extends BaseCollectionConfig { /** The query key used by TanStack Query to identify this query */ queryKey: TQueryKey | TQueryKeyBuilder; /** Function that fetches data from the server. Must return the complete collection state */ queryFn: TQueryFn extends (context: QueryFunctionContext) => Promise> | Array ? (context: QueryFunctionContext) => Promise> | Array : TQueryFn; select?: (data: TQueryData) => Array; /** The TanStack Query client instance */ queryClient: QueryClient; /** Whether the query should automatically run (default: true) */ enabled?: QueryObserverOptions, TQueryData, TQueryKey>[`enabled`]; refetchInterval?: QueryObserverOptions, TQueryData, TQueryKey>[`refetchInterval`]; retry?: QueryObserverOptions, TQueryData, TQueryKey>[`retry`]; retryDelay?: QueryObserverOptions, TQueryData, TQueryKey>[`retryDelay`]; staleTime?: QueryObserverOptions, TQueryData, TQueryKey>[`staleTime`]; persistedGcTime?: number; /** * Metadata to pass to the query. * Available in queryFn via context.meta * * @example * // Using meta for error context * queryFn: async (context) => { * try { * return await api.getTodos(userId) * } catch (error) { * // Use meta for better error messages * throw new Error( * context.meta?.errorMessage || 'Failed to load todos' * ) * } * }, * meta: { * errorMessage: `Failed to load todos for user ${userId}` * } */ meta?: Record; } /** * Type for the refetch utility function * Returns the QueryObserverResult from TanStack Query */ export type RefetchFn = (opts?: { throwOnError?: boolean; }) => Promise | void>>; /** * Utility methods available on Query Collections for direct writes and manual operations. * Direct writes bypass the normal query/mutation flow and write directly to the synced data store. * @template TItem - The type of items stored in the collection * @template TKey - The type of the item keys * @template TInsertInput - The type accepted for insert operations * @template TError - The type of errors that can occur during queries */ export interface QueryCollectionUtils, TKey extends string | number = string | number, TInsertInput extends object = TItem, TError = unknown> extends UtilsRecord { /** Manually trigger a refetch of the query */ refetch: RefetchFn; /** Insert one or more items directly into the synced data store without triggering a query refetch or optimistic update */ writeInsert: (data: TInsertInput | Array) => void; /** Update one or more items directly in the synced data store without triggering a query refetch or optimistic update */ writeUpdate: (updates: Partial | Array>) => void; /** Delete one or more items directly from the synced data store without triggering a query refetch or optimistic update */ writeDelete: (keys: TKey | Array) => void; /** Insert or update one or more items directly in the synced data store without triggering a query refetch or optimistic update */ writeUpsert: (data: Partial | Array>) => void; /** Execute multiple write operations as a single atomic batch to the synced data store */ writeBatch: (callback: () => void) => void; /** Get the last error encountered by the query (if any); reset on success */ lastError: TError | undefined; /** Check if the collection is in an error state */ isError: boolean; /** * Get the number of consecutive sync failures. * Incremented only when query fails completely (not per retry attempt); reset on success. */ errorCount: number; /** Check if query is currently fetching (initial or background) */ isFetching: boolean; /** Check if query is refetching in background (not initial fetch) */ isRefetching: boolean; /** Check if query is loading for the first time (no data yet) */ isLoading: boolean; /** Get timestamp of last successful data update (in milliseconds) */ dataUpdatedAt: number; /** Get current fetch status */ fetchStatus: `fetching` | `paused` | `idle`; /** * Clear the error state and trigger a refetch of the query * @returns Promise that resolves when the refetch completes successfully * @throws Error if the refetch fails */ clearError: () => Promise; } /** * Creates query collection options for use with a standard Collection. * This integrates TanStack Query with TanStack DB for automatic synchronization. * * Supports automatic type inference following the priority order: * 1. Schema inference (highest priority) * 2. QueryFn return type inference (second priority) * * @template T - Type of the schema if a schema is provided otherwise it is the type of the values returned by the queryFn * @template TError - The type of errors that can occur during queries * @template TQueryKey - The type of the query key * @template TKey - The type of the item keys * @param config - Configuration options for the Query collection * @returns Collection options with utilities for direct writes and manual operations * * @example * // Type inferred from queryFn return type (NEW!) * const todosCollection = createCollection( * queryCollectionOptions({ * queryKey: ['todos'], * queryFn: async () => { * const response = await fetch('/api/todos') * return response.json() as Todo[] // Type automatically inferred! * }, * queryClient, * getKey: (item) => item.id, // item is typed as Todo * }) * ) * * @example * // Explicit type * const todosCollection = createCollection( * queryCollectionOptions({ * queryKey: ['todos'], * queryFn: async () => fetch('/api/todos').then(r => r.json()), * queryClient, * getKey: (item) => item.id, * }) * ) * * @example * // Schema inference * const todosCollection = createCollection( * queryCollectionOptions({ * queryKey: ['todos'], * queryFn: async () => fetch('/api/todos').then(r => r.json()), * queryClient, * schema: todoSchema, // Type inferred from schema * getKey: (item) => item.id, * }) * ) * * @example * // With persistence handlers * const todosCollection = createCollection( * queryCollectionOptions({ * queryKey: ['todos'], * queryFn: fetchTodos, * queryClient, * getKey: (item) => item.id, * onInsert: async ({ transaction }) => { * await api.createTodos(transaction.mutations.map(m => m.modified)) * }, * onUpdate: async ({ transaction }) => { * await api.updateTodos(transaction.mutations) * }, * onDelete: async ({ transaction }) => { * await api.deleteTodos(transaction.mutations.map(m => m.key)) * } * }) * ) * * @example * // The select option extracts the items array from a response with metadata * const todosCollection = createCollection( * queryCollectionOptions({ * queryKey: ['todos'], * queryFn: async () => fetch('/api/todos').then(r => r.json()), * select: (data) => data.items, // Extract the array of items * queryClient, * schema: todoSchema, * getKey: (item) => item.id, * }) * ) */ export declare function queryCollectionOptions) => any, TError = unknown, TQueryKey extends QueryKey = QueryKey, TKey extends string | number = string | number, TQueryData = Awaited>>(config: QueryCollectionConfig, TQueryFn, TError, TQueryKey, TKey, T> & { schema: T; select: (data: TQueryData) => Array>; }): CollectionConfig, TKey, T, QueryCollectionUtils, TKey, InferSchemaInput, TError>> & { schema: T; utils: QueryCollectionUtils, TKey, InferSchemaInput, TError>; }; export declare function queryCollectionOptions) => any = (context: QueryFunctionContext) => any, TError = unknown, TQueryKey extends QueryKey = QueryKey, TKey extends string | number = string | number, TQueryData = Awaited>>(config: QueryCollectionConfig & { schema?: never; select: (data: TQueryData) => Array; }): CollectionConfig> & { schema?: never; utils: QueryCollectionUtils; }; export declare function queryCollectionOptions(config: QueryCollectionConfig, (context: QueryFunctionContext) => Array> | Promise>>, TError, TQueryKey, TKey, T> & { schema: T; }): CollectionConfig, TKey, T, QueryCollectionUtils, TKey, InferSchemaInput, TError>> & { schema: T; utils: QueryCollectionUtils, TKey, InferSchemaInput, TError>; }; export declare function queryCollectionOptions(config: QueryCollectionConfig) => Array | Promise>, TError, TQueryKey, TKey> & { schema?: never; }): CollectionConfig> & { schema?: never; utils: QueryCollectionUtils; };