@tanstack/query-db-collection 1.0.11 → 1.0.13

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.
@@ -152,13 +152,15 @@ function queryCollectionOptions(config) {
152
152
  } else {
153
153
  return new Promise((resolve, reject) => {
154
154
  const unsubscribe = observer.subscribe((result) => {
155
- if (result.isSuccess) {
156
- unsubscribe();
157
- resolve();
158
- } else if (result.isError) {
159
- unsubscribe();
160
- reject(result.error);
161
- }
155
+ queueMicrotask(() => {
156
+ if (result.isSuccess) {
157
+ unsubscribe();
158
+ resolve();
159
+ } else if (result.isError) {
160
+ unsubscribe();
161
+ reject(result.error);
162
+ }
163
+ });
162
164
  });
163
165
  });
164
166
  }
@@ -185,13 +187,15 @@ function queryCollectionOptions(config) {
185
187
  );
186
188
  const readyPromise = new Promise((resolve, reject) => {
187
189
  const unsubscribe = localObserver.subscribe((result) => {
188
- if (result.isSuccess) {
189
- unsubscribe();
190
- resolve();
191
- } else if (result.isError) {
192
- unsubscribe();
193
- reject(result.error);
194
- }
190
+ queueMicrotask(() => {
191
+ if (result.isSuccess) {
192
+ unsubscribe();
193
+ resolve();
194
+ } else if (result.isError) {
195
+ unsubscribe();
196
+ reject(result.error);
197
+ }
198
+ });
195
199
  });
196
200
  });
197
201
  if (syncStarted || collection.subscriberCount > 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"query.cjs","sources":["../../src/query.ts"],"sourcesContent":["import { QueryObserver, hashKey } from '@tanstack/query-core'\nimport { deepEquals } from '@tanstack/db'\nimport {\n GetKeyRequiredError,\n QueryClientRequiredError,\n QueryFnRequiredError,\n QueryKeyRequiredError,\n} from './errors'\nimport { createWriteUtils } from './manual-sync'\nimport { serializeLoadSubsetOptions } from './serialization'\nimport type {\n BaseCollectionConfig,\n ChangeMessage,\n CollectionConfig,\n DeleteMutationFnParams,\n InsertMutationFnParams,\n LoadSubsetOptions,\n SyncConfig,\n UpdateMutationFnParams,\n UtilsRecord,\n} from '@tanstack/db'\nimport type {\n FetchStatus,\n QueryClient,\n QueryFunctionContext,\n QueryKey,\n QueryObserverOptions,\n QueryObserverResult,\n} from '@tanstack/query-core'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\n\n// Re-export for external use\nexport type { SyncOperation } from './manual-sync'\n\n// Schema output type inference helper (matches electric.ts pattern)\ntype InferSchemaOutput<T> = T extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<T> extends object\n ? StandardSchemaV1.InferOutput<T>\n : Record<string, unknown>\n : Record<string, unknown>\n\n// Schema input type inference helper (matches electric.ts pattern)\ntype InferSchemaInput<T> = T extends StandardSchemaV1\n ? StandardSchemaV1.InferInput<T> extends object\n ? StandardSchemaV1.InferInput<T>\n : Record<string, unknown>\n : Record<string, unknown>\n\ntype TQueryKeyBuilder<TQueryKey> = (opts: LoadSubsetOptions) => TQueryKey\n\n/**\n * Configuration options for creating a Query Collection\n * @template T - The explicit type of items stored in the collection\n * @template TQueryFn - The queryFn type\n * @template TError - The type of errors that can occur during queries\n * @template TQueryKey - The type of the query key\n * @template TKey - The type of the item keys\n * @template TSchema - The schema type for validation\n */\nexport interface QueryCollectionConfig<\n T extends object = object,\n TQueryFn extends (context: QueryFunctionContext<any>) => Promise<any> = (\n context: QueryFunctionContext<any>,\n ) => Promise<any>,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = never,\n TQueryData = Awaited<ReturnType<TQueryFn>>,\n> extends BaseCollectionConfig<T, TKey, TSchema> {\n /** The query key used by TanStack Query to identify this query */\n queryKey: TQueryKey | TQueryKeyBuilder<TQueryKey>\n /** Function that fetches data from the server. Must return the complete collection state */\n queryFn: TQueryFn extends (\n context: QueryFunctionContext<TQueryKey>,\n ) => Promise<Array<any>>\n ? (context: QueryFunctionContext<TQueryKey>) => Promise<Array<T>>\n : TQueryFn\n /* Function that extracts array items from wrapped API responses (e.g metadata, pagination) */\n select?: (data: TQueryData) => Array<T>\n /** The TanStack Query client instance */\n queryClient: QueryClient\n\n // Query-specific options\n /** Whether the query should automatically run (default: true) */\n enabled?: boolean\n refetchInterval?: QueryObserverOptions<\n Array<T>,\n TError,\n Array<T>,\n Array<T>,\n TQueryKey\n >[`refetchInterval`]\n retry?: QueryObserverOptions<\n Array<T>,\n TError,\n Array<T>,\n Array<T>,\n TQueryKey\n >[`retry`]\n retryDelay?: QueryObserverOptions<\n Array<T>,\n TError,\n Array<T>,\n Array<T>,\n TQueryKey\n >[`retryDelay`]\n staleTime?: QueryObserverOptions<\n Array<T>,\n TError,\n Array<T>,\n Array<T>,\n TQueryKey\n >[`staleTime`]\n\n /**\n * Metadata to pass to the query.\n * Available in queryFn via context.meta\n *\n * @example\n * // Using meta for error context\n * queryFn: async (context) => {\n * try {\n * return await api.getTodos(userId)\n * } catch (error) {\n * // Use meta for better error messages\n * throw new Error(\n * context.meta?.errorMessage || 'Failed to load todos'\n * )\n * }\n * },\n * meta: {\n * errorMessage: `Failed to load todos for user ${userId}`\n * }\n */\n meta?: Record<string, unknown>\n}\n\n/**\n * Type for the refetch utility function\n * Returns the QueryObserverResult from TanStack Query\n */\nexport type RefetchFn = (opts?: {\n throwOnError?: boolean\n}) => Promise<QueryObserverResult<any, any> | void>\n\n/**\n * Utility methods available on Query Collections for direct writes and manual operations.\n * Direct writes bypass the normal query/mutation flow and write directly to the synced data store.\n * @template TItem - The type of items stored in the collection\n * @template TKey - The type of the item keys\n * @template TInsertInput - The type accepted for insert operations\n * @template TError - The type of errors that can occur during queries\n */\nexport interface QueryCollectionUtils<\n TItem extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TInsertInput extends object = TItem,\n TError = unknown,\n> extends UtilsRecord {\n /** Manually trigger a refetch of the query */\n refetch: RefetchFn\n /** Insert one or more items directly into the synced data store without triggering a query refetch or optimistic update */\n writeInsert: (data: TInsertInput | Array<TInsertInput>) => void\n /** Update one or more items directly in the synced data store without triggering a query refetch or optimistic update */\n writeUpdate: (updates: Partial<TItem> | Array<Partial<TItem>>) => void\n /** Delete one or more items directly from the synced data store without triggering a query refetch or optimistic update */\n writeDelete: (keys: TKey | Array<TKey>) => void\n /** Insert or update one or more items directly in the synced data store without triggering a query refetch or optimistic update */\n writeUpsert: (data: Partial<TItem> | Array<Partial<TItem>>) => void\n /** Execute multiple write operations as a single atomic batch to the synced data store */\n writeBatch: (callback: () => void) => void\n\n // Query Observer State (getters)\n /** Get the last error encountered by the query (if any); reset on success */\n lastError: TError | undefined\n /** Check if the collection is in an error state */\n isError: boolean\n /**\n * Get the number of consecutive sync failures.\n * Incremented only when query fails completely (not per retry attempt); reset on success.\n */\n errorCount: number\n /** Check if query is currently fetching (initial or background) */\n isFetching: boolean\n /** Check if query is refetching in background (not initial fetch) */\n isRefetching: boolean\n /** Check if query is loading for the first time (no data yet) */\n isLoading: boolean\n /** Get timestamp of last successful data update (in milliseconds) */\n dataUpdatedAt: number\n /** Get current fetch status */\n fetchStatus: `fetching` | `paused` | `idle`\n\n /**\n * Clear the error state and trigger a refetch of the query\n * @returns Promise that resolves when the refetch completes successfully\n * @throws Error if the refetch fails\n */\n clearError: () => Promise<void>\n}\n\n/**\n * Internal state object for tracking query observer and errors\n */\ninterface QueryCollectionState {\n lastError: any\n errorCount: number\n lastErrorUpdatedAt: number\n observers: Map<\n string,\n QueryObserver<Array<any>, any, Array<any>, Array<any>, any>\n >\n}\n\n/**\n * Implementation class for QueryCollectionUtils with explicit dependency injection\n * for better testability and architectural clarity\n */\nclass QueryCollectionUtilsImpl {\n private state: QueryCollectionState\n private refetchFn: RefetchFn\n\n // Write methods\n public refetch: RefetchFn\n public writeInsert: any\n public writeUpdate: any\n public writeDelete: any\n public writeUpsert: any\n public writeBatch: any\n\n constructor(\n state: QueryCollectionState,\n refetch: RefetchFn,\n writeUtils: ReturnType<typeof createWriteUtils>,\n ) {\n this.state = state\n this.refetchFn = refetch\n\n // Initialize methods to use passed dependencies\n this.refetch = refetch\n this.writeInsert = writeUtils.writeInsert\n this.writeUpdate = writeUtils.writeUpdate\n this.writeDelete = writeUtils.writeDelete\n this.writeUpsert = writeUtils.writeUpsert\n this.writeBatch = writeUtils.writeBatch\n }\n\n public async clearError() {\n this.state.lastError = undefined\n this.state.errorCount = 0\n this.state.lastErrorUpdatedAt = 0\n await this.refetchFn({ throwOnError: true })\n }\n\n // Getters for error state\n public get lastError() {\n return this.state.lastError\n }\n\n public get isError() {\n return !!this.state.lastError\n }\n\n public get errorCount() {\n return this.state.errorCount\n }\n\n // Getters for QueryObserver state\n public get isFetching() {\n // check if any observer is fetching\n return Array.from(this.state.observers.values()).some(\n (observer) => observer.getCurrentResult().isFetching,\n )\n }\n\n public get isRefetching() {\n // check if any observer is refetching\n return Array.from(this.state.observers.values()).some(\n (observer) => observer.getCurrentResult().isRefetching,\n )\n }\n\n public get isLoading() {\n // check if any observer is loading\n return Array.from(this.state.observers.values()).some(\n (observer) => observer.getCurrentResult().isLoading,\n )\n }\n\n public get dataUpdatedAt() {\n // compute the max dataUpdatedAt of all observers\n return Math.max(\n 0,\n ...Array.from(this.state.observers.values()).map(\n (observer) => observer.getCurrentResult().dataUpdatedAt,\n ),\n )\n }\n\n public get fetchStatus(): Array<FetchStatus> {\n return Array.from(this.state.observers.values()).map(\n (observer) => observer.getCurrentResult().fetchStatus,\n )\n }\n}\n\n/**\n * Creates query collection options for use with a standard Collection.\n * This integrates TanStack Query with TanStack DB for automatic synchronization.\n *\n * Supports automatic type inference following the priority order:\n * 1. Schema inference (highest priority)\n * 2. QueryFn return type inference (second priority)\n *\n * @template T - Type of the schema if a schema is provided otherwise it is the type of the values returned by the queryFn\n * @template TError - The type of errors that can occur during queries\n * @template TQueryKey - The type of the query key\n * @template TKey - The type of the item keys\n * @param config - Configuration options for the Query collection\n * @returns Collection options with utilities for direct writes and manual operations\n *\n * @example\n * // Type inferred from queryFn return type (NEW!)\n * const todosCollection = createCollection(\n * queryCollectionOptions({\n * queryKey: ['todos'],\n * queryFn: async () => {\n * const response = await fetch('/api/todos')\n * return response.json() as Todo[] // Type automatically inferred!\n * },\n * queryClient,\n * getKey: (item) => item.id, // item is typed as Todo\n * })\n * )\n *\n * @example\n * // Explicit type\n * const todosCollection = createCollection<Todo>(\n * queryCollectionOptions({\n * queryKey: ['todos'],\n * queryFn: async () => fetch('/api/todos').then(r => r.json()),\n * queryClient,\n * getKey: (item) => item.id,\n * })\n * )\n *\n * @example\n * // Schema inference\n * const todosCollection = createCollection(\n * queryCollectionOptions({\n * queryKey: ['todos'],\n * queryFn: async () => fetch('/api/todos').then(r => r.json()),\n * queryClient,\n * schema: todoSchema, // Type inferred from schema\n * getKey: (item) => item.id,\n * })\n * )\n *\n * @example\n * // With persistence handlers\n * const todosCollection = createCollection(\n * queryCollectionOptions({\n * queryKey: ['todos'],\n * queryFn: fetchTodos,\n * queryClient,\n * getKey: (item) => item.id,\n * onInsert: async ({ transaction }) => {\n * await api.createTodos(transaction.mutations.map(m => m.modified))\n * },\n * onUpdate: async ({ transaction }) => {\n * await api.updateTodos(transaction.mutations)\n * },\n * onDelete: async ({ transaction }) => {\n * await api.deleteTodos(transaction.mutations.map(m => m.key))\n * }\n * })\n * )\n *\n * @example\n * // The select option extracts the items array from a response with metadata\n * const todosCollection = createCollection(\n * queryCollectionOptions({\n * queryKey: ['todos'],\n * queryFn: async () => fetch('/api/todos').then(r => r.json()),\n * select: (data) => data.items, // Extract the array of items\n * queryClient,\n * schema: todoSchema,\n * getKey: (item) => item.id,\n * })\n * )\n */\n// Overload for when schema is provided and select present\nexport function queryCollectionOptions<\n T extends StandardSchemaV1,\n TQueryFn extends (context: QueryFunctionContext<any>) => Promise<any>,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n TQueryData = Awaited<ReturnType<TQueryFn>>,\n>(\n config: QueryCollectionConfig<\n InferSchemaOutput<T>,\n TQueryFn,\n TError,\n TQueryKey,\n TKey,\n T\n > & {\n schema: T\n select: (data: TQueryData) => Array<InferSchemaInput<T>>\n },\n): CollectionConfig<\n InferSchemaOutput<T>,\n TKey,\n T,\n QueryCollectionUtils<InferSchemaOutput<T>, TKey, InferSchemaInput<T>, TError>\n> & {\n schema: T\n utils: QueryCollectionUtils<\n InferSchemaOutput<T>,\n TKey,\n InferSchemaInput<T>,\n TError\n >\n}\n\n// Overload for when no schema is provided and select present\nexport function queryCollectionOptions<\n T extends object,\n TQueryFn extends (context: QueryFunctionContext<any>) => Promise<any> = (\n context: QueryFunctionContext<any>,\n ) => Promise<any>,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n TQueryData = Awaited<ReturnType<TQueryFn>>,\n>(\n config: QueryCollectionConfig<\n T,\n TQueryFn,\n TError,\n TQueryKey,\n TKey,\n never,\n TQueryData\n > & {\n schema?: never // prohibit schema\n select: (data: TQueryData) => Array<T>\n },\n): CollectionConfig<\n T,\n TKey,\n never,\n QueryCollectionUtils<T, TKey, T, TError>\n> & {\n schema?: never // no schema in the result\n utils: QueryCollectionUtils<T, TKey, T, TError>\n}\n\n// Overload for when schema is provided\nexport function queryCollectionOptions<\n T extends StandardSchemaV1,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n>(\n config: QueryCollectionConfig<\n InferSchemaOutput<T>,\n (\n context: QueryFunctionContext<any>,\n ) => Promise<Array<InferSchemaOutput<T>>>,\n TError,\n TQueryKey,\n TKey,\n T\n > & {\n schema: T\n },\n): CollectionConfig<\n InferSchemaOutput<T>,\n TKey,\n T,\n QueryCollectionUtils<InferSchemaOutput<T>, TKey, InferSchemaInput<T>, TError>\n> & {\n schema: T\n utils: QueryCollectionUtils<\n InferSchemaOutput<T>,\n TKey,\n InferSchemaInput<T>,\n TError\n >\n}\n\n// Overload for when no schema is provided\nexport function queryCollectionOptions<\n T extends object,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n>(\n config: QueryCollectionConfig<\n T,\n (context: QueryFunctionContext<any>) => Promise<Array<T>>,\n TError,\n TQueryKey,\n TKey\n > & {\n schema?: never // prohibit schema\n },\n): CollectionConfig<\n T,\n TKey,\n never,\n QueryCollectionUtils<T, TKey, T, TError>\n> & {\n schema?: never // no schema in the result\n utils: QueryCollectionUtils<T, TKey, T, TError>\n}\n\nexport function queryCollectionOptions(\n config: QueryCollectionConfig<Record<string, unknown>>,\n): CollectionConfig<\n Record<string, unknown>,\n string | number,\n never,\n QueryCollectionUtils\n> & {\n utils: QueryCollectionUtils\n} {\n const {\n queryKey,\n queryFn,\n select,\n queryClient,\n enabled,\n refetchInterval,\n retry,\n retryDelay,\n staleTime,\n getKey,\n onInsert,\n onUpdate,\n onDelete,\n meta,\n ...baseCollectionConfig\n } = config\n\n // Default to eager sync mode if not provided\n const syncMode = baseCollectionConfig.syncMode ?? `eager`\n\n // Validate required parameters\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!queryKey) {\n throw new QueryKeyRequiredError()\n }\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!queryFn) {\n throw new QueryFnRequiredError()\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!queryClient) {\n throw new QueryClientRequiredError()\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!getKey) {\n throw new GetKeyRequiredError()\n }\n\n /** State object to hold error tracking and observer reference */\n const state: QueryCollectionState = {\n lastError: undefined as any,\n errorCount: 0,\n lastErrorUpdatedAt: 0,\n observers: new Map<\n string,\n QueryObserver<Array<any>, any, Array<any>, Array<any>, any>\n >(),\n }\n\n // hashedQueryKey → queryKey\n const hashToQueryKey = new Map<string, QueryKey>()\n\n // queryKey → Set<RowKey>\n const queryToRows = new Map<string, Set<string | number>>()\n\n // RowKey → Set<queryKey>\n const rowToQueries = new Map<string | number, Set<string>>()\n\n // queryKey → QueryObserver's unsubscribe function\n const unsubscribes = new Map<string, () => void>()\n\n // queryKey → reference count (how many loadSubset calls are active)\n // Reference counting for QueryObserver lifecycle management\n // =========================================================\n // Tracks how many live query subscriptions are using each QueryObserver.\n // Multiple live queries with identical predicates share the same QueryObserver for efficiency.\n //\n // Lifecycle:\n // - Increment: when createQueryFromOpts creates or reuses an observer\n // - Decrement: when subscription.unsubscribe() passes predicates to collection._sync.unloadSubset()\n // - Reset: when cleanupQuery() is triggered by TanStack Query's cache GC\n //\n // When refcount reaches 0, unloadSubset():\n // 1. Computes the same queryKey from the predicates\n // 2. Uses existing machinery (queryToRows map) to find rows that query loaded\n // 3. Decrements refcount and GCs rows where count reaches 0\n const queryRefCounts = new Map<string, number>()\n\n // Helper function to add a row to the internal state\n const addRow = (rowKey: string | number, hashedQueryKey: string) => {\n const rowToQueriesSet = rowToQueries.get(rowKey) || new Set()\n rowToQueriesSet.add(hashedQueryKey)\n rowToQueries.set(rowKey, rowToQueriesSet)\n\n const queryToRowsSet = queryToRows.get(hashedQueryKey) || new Set()\n queryToRowsSet.add(rowKey)\n queryToRows.set(hashedQueryKey, queryToRowsSet)\n }\n\n // Helper function to remove a row from the internal state\n const removeRow = (rowKey: string | number, hashedQuerKey: string) => {\n const rowToQueriesSet = rowToQueries.get(rowKey) || new Set()\n rowToQueriesSet.delete(hashedQuerKey)\n rowToQueries.set(rowKey, rowToQueriesSet)\n\n const queryToRowsSet = queryToRows.get(hashedQuerKey) || new Set()\n queryToRowsSet.delete(rowKey)\n queryToRows.set(hashedQuerKey, queryToRowsSet)\n\n return rowToQueriesSet.size === 0\n }\n\n const internalSync: SyncConfig<any>[`sync`] = (params) => {\n const { begin, write, commit, markReady, collection } = params\n\n // Track whether sync has been started\n let syncStarted = false\n\n /**\n * Generate a consistent query key from LoadSubsetOptions.\n * CRITICAL: Must use identical logic in both createQueryFromOpts and unloadSubset\n * so that refcount increment/decrement operations target the same hashedQueryKey.\n * Inconsistent keys would cause refcount leaks and prevent proper cleanup.\n */\n const generateQueryKeyFromOptions = (opts: LoadSubsetOptions): QueryKey => {\n if (typeof queryKey === `function`) {\n // Function-based queryKey: use it to build the key from opts\n return queryKey(opts)\n } else if (syncMode === `on-demand`) {\n // Static queryKey in on-demand mode: automatically append serialized predicates\n // to create separate cache entries for different predicate combinations\n const serialized = serializeLoadSubsetOptions(opts)\n return serialized !== undefined ? [...queryKey, serialized] : queryKey\n } else {\n // Static queryKey in eager mode: use as-is\n return queryKey\n }\n }\n\n const createQueryFromOpts = (\n opts: LoadSubsetOptions = {},\n queryFunction: typeof queryFn = queryFn,\n ): true | Promise<void> => {\n // Generate key using common function\n const key = generateQueryKeyFromOptions(opts)\n const hashedQueryKey = hashKey(key)\n const extendedMeta = { ...meta, loadSubsetOptions: opts }\n\n if (state.observers.has(hashedQueryKey)) {\n // We already have a query for this queryKey\n // Increment reference count since another consumer is using this observer\n queryRefCounts.set(\n hashedQueryKey,\n (queryRefCounts.get(hashedQueryKey) || 0) + 1,\n )\n\n // Get the current result and return based on its state\n const observer = state.observers.get(hashedQueryKey)!\n const currentResult = observer.getCurrentResult()\n\n if (currentResult.isSuccess) {\n // Data is already available, return true synchronously\n return true\n } else if (currentResult.isError) {\n // Error already occurred, reject immediately\n return Promise.reject(currentResult.error)\n } else {\n // Query is still loading, wait for the first result\n return new Promise<void>((resolve, reject) => {\n const unsubscribe = observer.subscribe((result) => {\n if (result.isSuccess) {\n unsubscribe()\n resolve()\n } else if (result.isError) {\n unsubscribe()\n reject(result.error)\n }\n })\n })\n }\n }\n\n const observerOptions: QueryObserverOptions<\n Array<any>,\n any,\n Array<any>,\n Array<any>,\n any\n > = {\n queryKey: key,\n queryFn: queryFunction,\n meta: extendedMeta,\n structuralSharing: true,\n notifyOnChangeProps: `all`,\n\n // Only include options that are explicitly defined to allow QueryClient defaultOptions to be used\n ...(enabled !== undefined && { enabled }),\n ...(refetchInterval !== undefined && { refetchInterval }),\n ...(retry !== undefined && { retry }),\n ...(retryDelay !== undefined && { retryDelay }),\n ...(staleTime !== undefined && { staleTime }),\n }\n\n const localObserver = new QueryObserver<\n Array<any>,\n any,\n Array<any>,\n Array<any>,\n any\n >(queryClient, observerOptions)\n\n hashToQueryKey.set(hashedQueryKey, key)\n state.observers.set(hashedQueryKey, localObserver)\n\n // Increment reference count for this query\n queryRefCounts.set(\n hashedQueryKey,\n (queryRefCounts.get(hashedQueryKey) || 0) + 1,\n )\n\n // Create a promise that resolves when the query result is first available\n const readyPromise = new Promise<void>((resolve, reject) => {\n const unsubscribe = localObserver.subscribe((result) => {\n if (result.isSuccess) {\n unsubscribe()\n resolve()\n } else if (result.isError) {\n unsubscribe()\n reject(result.error)\n }\n })\n })\n\n // If sync has started or there are subscribers to the collection, subscribe to the query straight away\n // This creates the main subscription that handles data updates\n if (syncStarted || collection.subscriberCount > 0) {\n subscribeToQuery(localObserver, hashedQueryKey)\n }\n\n return readyPromise\n }\n\n type UpdateHandler = Parameters<QueryObserver[`subscribe`]>[0]\n\n const makeQueryResultHandler = (queryKey: QueryKey) => {\n const hashedQueryKey = hashKey(queryKey)\n const handleQueryResult: UpdateHandler = (result) => {\n if (result.isSuccess) {\n // Clear error state\n state.lastError = undefined\n state.errorCount = 0\n\n const rawData = result.data\n const newItemsArray = select ? select(rawData) : rawData\n\n if (\n !Array.isArray(newItemsArray) ||\n newItemsArray.some((item) => typeof item !== `object`)\n ) {\n const errorMessage = select\n ? `@tanstack/query-db-collection: select() must return an array of objects. Got: ${typeof newItemsArray} for queryKey ${JSON.stringify(queryKey)}`\n : `@tanstack/query-db-collection: queryFn must return an array of objects. Got: ${typeof newItemsArray} for queryKey ${JSON.stringify(queryKey)}`\n\n console.error(errorMessage)\n return\n }\n\n const currentSyncedItems: Map<string | number, any> = new Map(\n collection._state.syncedData.entries(),\n )\n const newItemsMap = new Map<string | number, any>()\n newItemsArray.forEach((item) => {\n const key = getKey(item)\n newItemsMap.set(key, item)\n })\n\n begin()\n\n currentSyncedItems.forEach((oldItem, key) => {\n const newItem = newItemsMap.get(key)\n if (!newItem) {\n const needToRemove = removeRow(key, hashedQueryKey) // returns true if the row is no longer referenced by any queries\n if (needToRemove) {\n write({ type: `delete`, value: oldItem })\n }\n } else if (!deepEquals(oldItem, newItem)) {\n // Only update if there are actual differences in the properties\n write({ type: `update`, value: newItem })\n }\n })\n\n newItemsMap.forEach((newItem, key) => {\n addRow(key, hashedQueryKey)\n if (!currentSyncedItems.has(key)) {\n write({ type: `insert`, value: newItem })\n }\n })\n\n commit()\n\n // Mark collection as ready after first successful query result\n markReady()\n } else if (result.isError) {\n if (result.errorUpdatedAt !== state.lastErrorUpdatedAt) {\n state.lastError = result.error\n state.errorCount++\n state.lastErrorUpdatedAt = result.errorUpdatedAt\n }\n\n console.error(\n `[QueryCollection] Error observing query ${String(queryKey)}:`,\n result.error,\n )\n\n // Mark collection as ready even on error to avoid blocking apps\n markReady()\n }\n }\n return handleQueryResult\n }\n\n const isSubscribed = (hashedQueryKey: string) => {\n return unsubscribes.has(hashedQueryKey)\n }\n\n const subscribeToQuery = (\n observer: QueryObserver<Array<any>, any, Array<any>, Array<any>, any>,\n hashedQueryKey: string,\n ) => {\n if (!isSubscribed(hashedQueryKey)) {\n const cachedQueryKey = hashToQueryKey.get(hashedQueryKey)!\n const handleQueryResult = makeQueryResultHandler(cachedQueryKey)\n const unsubscribeFn = observer.subscribe(handleQueryResult)\n unsubscribes.set(hashedQueryKey, unsubscribeFn)\n\n // Process the current result immediately if available\n // This ensures data is synced when resubscribing to a query with cached data\n const currentResult = observer.getCurrentResult()\n if (currentResult.isSuccess || currentResult.isError) {\n handleQueryResult(currentResult)\n }\n }\n }\n\n const subscribeToQueries = () => {\n state.observers.forEach(subscribeToQuery)\n }\n\n const unsubscribeFromQueries = () => {\n unsubscribes.forEach((unsubscribeFn) => {\n unsubscribeFn()\n })\n unsubscribes.clear()\n }\n\n // Mark that sync has started\n syncStarted = true\n\n // Set up event listener for subscriber changes\n const unsubscribeFromCollectionEvents = collection.on(\n `subscribers:change`,\n ({ subscriberCount }) => {\n if (subscriberCount > 0) {\n subscribeToQueries()\n } else if (subscriberCount === 0) {\n unsubscribeFromQueries()\n }\n },\n )\n\n // If syncMode is eager, create the initial query without any predicates\n if (syncMode === `eager`) {\n // Catch any errors to prevent unhandled rejections\n const initialResult = createQueryFromOpts({})\n if (initialResult instanceof Promise) {\n initialResult.catch(() => {\n // Errors are already handled by the query result handler\n })\n }\n } else {\n // In on-demand mode, mark ready immediately since there's no initial query\n markReady()\n }\n\n // Always subscribe when sync starts (this could be from preload(), startSync config, or first subscriber)\n // We'll dynamically unsubscribe/resubscribe based on subscriber count to maintain staleTime behavior\n subscribeToQueries()\n\n // Ensure we process any existing query data (QueryObserver doesn't invoke its callback automatically with initial state)\n state.observers.forEach((observer, hashedQueryKey) => {\n const cachedQueryKey = hashToQueryKey.get(hashedQueryKey)!\n const handleQueryResult = makeQueryResultHandler(cachedQueryKey)\n handleQueryResult(observer.getCurrentResult())\n })\n\n /**\n * Perform row-level cleanup and remove all tracking for a query.\n * Callers are responsible for ensuring the query is safe to cleanup.\n */\n const cleanupQueryInternal = (hashedQueryKey: string) => {\n unsubscribes.get(hashedQueryKey)?.()\n unsubscribes.delete(hashedQueryKey)\n\n const rowKeys = queryToRows.get(hashedQueryKey) ?? new Set()\n const rowsToDelete: Array<any> = []\n\n rowKeys.forEach((rowKey) => {\n const queries = rowToQueries.get(rowKey)\n\n if (!queries) {\n return\n }\n\n queries.delete(hashedQueryKey)\n\n if (queries.size === 0) {\n rowToQueries.delete(rowKey)\n\n if (collection.has(rowKey)) {\n rowsToDelete.push(collection.get(rowKey))\n }\n }\n })\n\n if (rowsToDelete.length > 0) {\n begin()\n rowsToDelete.forEach((row) => {\n write({ type: `delete`, value: row })\n })\n commit()\n }\n\n state.observers.delete(hashedQueryKey)\n queryToRows.delete(hashedQueryKey)\n hashToQueryKey.delete(hashedQueryKey)\n queryRefCounts.delete(hashedQueryKey)\n }\n\n /**\n * Attempt to cleanup a query when it appears unused.\n * Respects refcounts and invalidateQueries cycles via hasListeners().\n */\n const cleanupQueryIfIdle = (hashedQueryKey: string) => {\n const refcount = queryRefCounts.get(hashedQueryKey) || 0\n const observer = state.observers.get(hashedQueryKey)\n\n if (refcount <= 0) {\n // Drop our subscription so hasListeners reflects only active consumers\n unsubscribes.get(hashedQueryKey)?.()\n unsubscribes.delete(hashedQueryKey)\n }\n\n const hasListeners = observer?.hasListeners() ?? false\n\n if (hasListeners) {\n // During invalidateQueries, TanStack Query keeps internal listeners alive.\n // Leave refcount at 0 but keep observer so it can resubscribe.\n queryRefCounts.set(hashedQueryKey, 0)\n return\n }\n\n // No listeners means the query is truly idle.\n // Even if refcount > 0, we treat hasListeners as authoritative to prevent leaks.\n // This can happen if subscriptions are GC'd without calling unloadSubset.\n if (refcount > 0) {\n console.warn(\n `[cleanupQueryIfIdle] Invariant violation: refcount=${refcount} but no listeners. Cleaning up to prevent leak.`,\n { hashedQueryKey },\n )\n }\n\n cleanupQueryInternal(hashedQueryKey)\n }\n\n /**\n * Force cleanup used by explicit collection cleanup.\n * Ignores refcounts/hasListeners and removes everything.\n */\n const forceCleanupQuery = (hashedQueryKey: string) => {\n cleanupQueryInternal(hashedQueryKey)\n }\n\n // Subscribe to the query client's cache to handle queries that are GCed by tanstack query\n const unsubscribeQueryCache = queryClient\n .getQueryCache()\n .subscribe((event) => {\n const hashedKey = event.query.queryHash\n if (event.type === `removed`) {\n // Only cleanup if this is OUR query (we track it)\n if (hashToQueryKey.has(hashedKey)) {\n // TanStack Query GC'd this query after gcTime expired.\n // Use the guarded cleanup path to avoid deleting rows for active queries.\n cleanupQueryIfIdle(hashedKey)\n }\n }\n })\n\n const cleanup = async () => {\n unsubscribeFromCollectionEvents()\n unsubscribeFromQueries()\n\n const allQueryKeys = [...hashToQueryKey.values()]\n const allHashedKeys = [...state.observers.keys()]\n\n // Force cleanup all queries (explicit cleanup path)\n // This ignores hasListeners and always cleans up\n for (const hashedKey of allHashedKeys) {\n forceCleanupQuery(hashedKey)\n }\n\n // Unsubscribe from cache events (cleanup already happened above)\n unsubscribeQueryCache()\n\n // Remove queries from TanStack Query cache\n await Promise.all(\n allQueryKeys.map(async (qKey) => {\n await queryClient.cancelQueries({ queryKey: qKey, exact: true })\n queryClient.removeQueries({ queryKey: qKey, exact: true })\n }),\n )\n }\n\n /**\n * Unload a query subset - the subscription-based cleanup path (on-demand mode).\n *\n * Called when a live query subscription unsubscribes (via collection._sync.unloadSubset()).\n *\n * Flow:\n * 1. Receives the same predicates that were passed to loadSubset\n * 2. Computes the queryKey using generateQueryKeyFromOptions (same logic as loadSubset)\n * 3. Decrements refcount\n * 4. If refcount reaches 0:\n * - Checks hasListeners() to detect invalidateQueries cycles\n * - If hasListeners is true: resets refcount (TanStack Query keeping observer alive)\n * - If hasListeners is false: calls forceCleanupQuery() to perform row-level GC\n *\n * The hasListeners() check prevents premature cleanup during invalidateQueries:\n * - invalidateQueries causes temporary unsubscribe/resubscribe\n * - During unsubscribe, our refcount drops to 0\n * - But observer.hasListeners() is still true (TanStack Query's internal listeners)\n * - We skip cleanup and reset refcount, allowing resubscribe to succeed\n *\n * We don't cancel in-flight requests. Unsubscribing from the observer is sufficient\n * to prevent late-arriving data from being processed. The request completes and is cached\n * by TanStack Query, allowing quick remounts to restore data without refetching.\n */\n const unloadSubset = (options: LoadSubsetOptions) => {\n // 1. Same predicates → 2. Same queryKey\n const key = generateQueryKeyFromOptions(options)\n const hashedQueryKey = hashKey(key)\n\n // 3. Decrement refcount\n const currentCount = queryRefCounts.get(hashedQueryKey) || 0\n const newCount = currentCount - 1\n\n // Update refcount\n if (newCount <= 0) {\n queryRefCounts.set(hashedQueryKey, 0)\n cleanupQueryIfIdle(hashedQueryKey)\n } else {\n // Still have other references, just decrement\n queryRefCounts.set(hashedQueryKey, newCount)\n }\n }\n\n // Create deduplicated loadSubset wrapper for non-eager modes\n // This prevents redundant snapshot requests when multiple concurrent\n // live queries request overlapping or subset predicates\n const loadSubsetDedupe =\n syncMode === `eager` ? undefined : createQueryFromOpts\n\n return {\n loadSubset: loadSubsetDedupe,\n unloadSubset: syncMode === `eager` ? undefined : unloadSubset,\n cleanup,\n }\n }\n\n /**\n * Refetch the query data\n *\n * Uses queryObserver.refetch() because:\n * - Bypasses `enabled: false` to support manual/imperative refetch patterns (e.g., button-triggered fetch)\n * - Ensures clearError() works even when enabled: false\n * - Always refetches THIS specific collection (exact targeting via observer)\n * - Respects retry, retryDelay, and other observer options\n *\n * This matches TanStack Query's hook behavior where refetch() bypasses enabled: false.\n * See: https://tanstack.com/query/latest/docs/framework/react/guides/disabling-queries\n *\n * Used by both:\n * - utils.refetch() - for explicit user-triggered refetches\n * - Internal handlers (onInsert/onUpdate/onDelete) - after mutations to get fresh data\n *\n * @returns Promise that resolves when the refetch is complete, with QueryObserverResult\n */\n const refetch: RefetchFn = async (opts) => {\n const allQueryKeys = [...hashToQueryKey.values()]\n const refetchPromises = allQueryKeys.map((qKey) => {\n const queryObserver = state.observers.get(hashKey(qKey))!\n return queryObserver.refetch({\n throwOnError: opts?.throwOnError,\n })\n })\n\n await Promise.all(refetchPromises)\n }\n\n /**\n * Updates the query cache with new items, handling both direct arrays\n * and wrapped response formats (when `select` is used).\n */\n const updateCacheData = (items: Array<any>): void => {\n // Get the base query key (handle both static and function-based keys)\n const key =\n typeof queryKey === `function`\n ? queryKey({})\n : (queryKey as unknown as QueryKey)\n\n if (select) {\n // When `select` is used, the cache contains a wrapped response (e.g., { data: [...], meta: {...} })\n // We need to update the cache while preserving the wrapper structure\n queryClient.setQueryData(key, (oldData: any) => {\n if (!oldData || typeof oldData !== `object`) {\n // No existing cache or not an object - don't corrupt the cache\n return oldData\n }\n\n if (Array.isArray(oldData)) {\n // Cache is already a raw array (shouldn't happen with select, but handle it)\n return items\n }\n\n // Use the select function to identify which property contains the items array.\n // This is more robust than guessing based on property order.\n const selectedArray = select(oldData)\n\n if (Array.isArray(selectedArray)) {\n // Find the property that matches the selected array by reference equality\n for (const propKey of Object.keys(oldData)) {\n if (oldData[propKey] === selectedArray) {\n // Found the exact property - create a shallow copy with updated items\n return { ...oldData, [propKey]: items }\n }\n }\n }\n\n // Fallback: check common property names used for data arrays\n if (Array.isArray(oldData.data)) {\n return { ...oldData, data: items }\n }\n if (Array.isArray(oldData.items)) {\n return { ...oldData, items: items }\n }\n if (Array.isArray(oldData.results)) {\n return { ...oldData, results: items }\n }\n\n // Last resort: find first array property\n for (const propKey of Object.keys(oldData)) {\n if (Array.isArray(oldData[propKey])) {\n return { ...oldData, [propKey]: items }\n }\n }\n\n // Couldn't safely identify the array property - don't corrupt the cache\n // Return oldData unchanged to avoid breaking select\n return oldData\n })\n } else {\n // No select - cache contains raw array, just set it directly\n queryClient.setQueryData(key, items)\n }\n }\n\n // Create write context for manual write operations\n let writeContext: {\n collection: any\n queryClient: QueryClient\n queryKey: Array<unknown>\n getKey: (item: any) => string | number\n begin: () => void\n write: (message: Omit<ChangeMessage<any>, `key`>) => void\n commit: () => void\n updateCacheData?: (items: Array<any>) => void\n } | null = null\n\n // Enhanced internalSync that captures write functions for manual use\n const enhancedInternalSync: SyncConfig<any>[`sync`] = (params) => {\n const { begin, write, commit, collection } = params\n\n // Get the base query key for the context (handle both static and function-based keys)\n const contextQueryKey =\n typeof queryKey === `function`\n ? (queryKey({}) as unknown as Array<unknown>)\n : (queryKey as unknown as Array<unknown>)\n\n // Store references for manual write operations\n writeContext = {\n collection,\n queryClient,\n queryKey: contextQueryKey,\n getKey: getKey as (item: any) => string | number,\n begin,\n write,\n commit,\n updateCacheData,\n }\n\n // Call the original internalSync logic\n return internalSync(params)\n }\n\n // Create write utils using the manual-sync module\n const writeUtils = createWriteUtils<any, string | number, any>(\n () => writeContext,\n )\n\n // Create wrapper handlers for direct persistence operations that handle refetching\n const wrappedOnInsert = onInsert\n ? async (params: InsertMutationFnParams<any>) => {\n const handlerResult = (await onInsert(params)) ?? {}\n const shouldRefetch =\n (handlerResult as { refetch?: boolean }).refetch !== false\n\n if (shouldRefetch) {\n await refetch()\n }\n\n return handlerResult\n }\n : undefined\n\n const wrappedOnUpdate = onUpdate\n ? async (params: UpdateMutationFnParams<any>) => {\n const handlerResult = (await onUpdate(params)) ?? {}\n const shouldRefetch =\n (handlerResult as { refetch?: boolean }).refetch !== false\n\n if (shouldRefetch) {\n await refetch()\n }\n\n return handlerResult\n }\n : undefined\n\n const wrappedOnDelete = onDelete\n ? async (params: DeleteMutationFnParams<any>) => {\n const handlerResult = (await onDelete(params)) ?? {}\n const shouldRefetch =\n (handlerResult as { refetch?: boolean }).refetch !== false\n\n if (shouldRefetch) {\n await refetch()\n }\n\n return handlerResult\n }\n : undefined\n\n // Create utils instance with state and dependencies passed explicitly\n const utils: any = new QueryCollectionUtilsImpl(state, refetch, writeUtils)\n\n return {\n ...baseCollectionConfig,\n getKey,\n syncMode,\n sync: { sync: enhancedInternalSync },\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils,\n }\n}\n"],"names":["QueryKeyRequiredError","QueryFnRequiredError","QueryClientRequiredError","GetKeyRequiredError","serializeLoadSubsetOptions","hashKey","QueryObserver","queryKey","deepEquals","createWriteUtils"],"mappings":"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"}
1
+ {"version":3,"file":"query.cjs","sources":["../../src/query.ts"],"sourcesContent":["import { QueryObserver, hashKey } from '@tanstack/query-core'\nimport { deepEquals } from '@tanstack/db'\nimport {\n GetKeyRequiredError,\n QueryClientRequiredError,\n QueryFnRequiredError,\n QueryKeyRequiredError,\n} from './errors'\nimport { createWriteUtils } from './manual-sync'\nimport { serializeLoadSubsetOptions } from './serialization'\nimport type {\n BaseCollectionConfig,\n ChangeMessage,\n CollectionConfig,\n DeleteMutationFnParams,\n InsertMutationFnParams,\n LoadSubsetOptions,\n SyncConfig,\n UpdateMutationFnParams,\n UtilsRecord,\n} from '@tanstack/db'\nimport type {\n FetchStatus,\n QueryClient,\n QueryFunctionContext,\n QueryKey,\n QueryObserverOptions,\n QueryObserverResult,\n} from '@tanstack/query-core'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\n\n// Re-export for external use\nexport type { SyncOperation } from './manual-sync'\n\n// Schema output type inference helper (matches electric.ts pattern)\ntype InferSchemaOutput<T> = T extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<T> extends object\n ? StandardSchemaV1.InferOutput<T>\n : Record<string, unknown>\n : Record<string, unknown>\n\n// Schema input type inference helper (matches electric.ts pattern)\ntype InferSchemaInput<T> = T extends StandardSchemaV1\n ? StandardSchemaV1.InferInput<T> extends object\n ? StandardSchemaV1.InferInput<T>\n : Record<string, unknown>\n : Record<string, unknown>\n\ntype TQueryKeyBuilder<TQueryKey> = (opts: LoadSubsetOptions) => TQueryKey\n\n/**\n * Configuration options for creating a Query Collection\n * @template T - The explicit type of items stored in the collection\n * @template TQueryFn - The queryFn type\n * @template TError - The type of errors that can occur during queries\n * @template TQueryKey - The type of the query key\n * @template TKey - The type of the item keys\n * @template TSchema - The schema type for validation\n */\nexport interface QueryCollectionConfig<\n T extends object = object,\n TQueryFn extends (context: QueryFunctionContext<any>) => Promise<any> = (\n context: QueryFunctionContext<any>,\n ) => Promise<any>,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = never,\n TQueryData = Awaited<ReturnType<TQueryFn>>,\n> extends BaseCollectionConfig<T, TKey, TSchema> {\n /** The query key used by TanStack Query to identify this query */\n queryKey: TQueryKey | TQueryKeyBuilder<TQueryKey>\n /** Function that fetches data from the server. Must return the complete collection state */\n queryFn: TQueryFn extends (\n context: QueryFunctionContext<TQueryKey>,\n ) => Promise<Array<any>>\n ? (context: QueryFunctionContext<TQueryKey>) => Promise<Array<T>>\n : TQueryFn\n /* Function that extracts array items from wrapped API responses (e.g metadata, pagination) */\n select?: (data: TQueryData) => Array<T>\n /** The TanStack Query client instance */\n queryClient: QueryClient\n\n // Query-specific options\n /** Whether the query should automatically run (default: true) */\n enabled?: boolean\n refetchInterval?: QueryObserverOptions<\n Array<T>,\n TError,\n Array<T>,\n Array<T>,\n TQueryKey\n >[`refetchInterval`]\n retry?: QueryObserverOptions<\n Array<T>,\n TError,\n Array<T>,\n Array<T>,\n TQueryKey\n >[`retry`]\n retryDelay?: QueryObserverOptions<\n Array<T>,\n TError,\n Array<T>,\n Array<T>,\n TQueryKey\n >[`retryDelay`]\n staleTime?: QueryObserverOptions<\n Array<T>,\n TError,\n Array<T>,\n Array<T>,\n TQueryKey\n >[`staleTime`]\n\n /**\n * Metadata to pass to the query.\n * Available in queryFn via context.meta\n *\n * @example\n * // Using meta for error context\n * queryFn: async (context) => {\n * try {\n * return await api.getTodos(userId)\n * } catch (error) {\n * // Use meta for better error messages\n * throw new Error(\n * context.meta?.errorMessage || 'Failed to load todos'\n * )\n * }\n * },\n * meta: {\n * errorMessage: `Failed to load todos for user ${userId}`\n * }\n */\n meta?: Record<string, unknown>\n}\n\n/**\n * Type for the refetch utility function\n * Returns the QueryObserverResult from TanStack Query\n */\nexport type RefetchFn = (opts?: {\n throwOnError?: boolean\n}) => Promise<QueryObserverResult<any, any> | void>\n\n/**\n * Utility methods available on Query Collections for direct writes and manual operations.\n * Direct writes bypass the normal query/mutation flow and write directly to the synced data store.\n * @template TItem - The type of items stored in the collection\n * @template TKey - The type of the item keys\n * @template TInsertInput - The type accepted for insert operations\n * @template TError - The type of errors that can occur during queries\n */\nexport interface QueryCollectionUtils<\n TItem extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TInsertInput extends object = TItem,\n TError = unknown,\n> extends UtilsRecord {\n /** Manually trigger a refetch of the query */\n refetch: RefetchFn\n /** Insert one or more items directly into the synced data store without triggering a query refetch or optimistic update */\n writeInsert: (data: TInsertInput | Array<TInsertInput>) => void\n /** Update one or more items directly in the synced data store without triggering a query refetch or optimistic update */\n writeUpdate: (updates: Partial<TItem> | Array<Partial<TItem>>) => void\n /** Delete one or more items directly from the synced data store without triggering a query refetch or optimistic update */\n writeDelete: (keys: TKey | Array<TKey>) => void\n /** Insert or update one or more items directly in the synced data store without triggering a query refetch or optimistic update */\n writeUpsert: (data: Partial<TItem> | Array<Partial<TItem>>) => void\n /** Execute multiple write operations as a single atomic batch to the synced data store */\n writeBatch: (callback: () => void) => void\n\n // Query Observer State (getters)\n /** Get the last error encountered by the query (if any); reset on success */\n lastError: TError | undefined\n /** Check if the collection is in an error state */\n isError: boolean\n /**\n * Get the number of consecutive sync failures.\n * Incremented only when query fails completely (not per retry attempt); reset on success.\n */\n errorCount: number\n /** Check if query is currently fetching (initial or background) */\n isFetching: boolean\n /** Check if query is refetching in background (not initial fetch) */\n isRefetching: boolean\n /** Check if query is loading for the first time (no data yet) */\n isLoading: boolean\n /** Get timestamp of last successful data update (in milliseconds) */\n dataUpdatedAt: number\n /** Get current fetch status */\n fetchStatus: `fetching` | `paused` | `idle`\n\n /**\n * Clear the error state and trigger a refetch of the query\n * @returns Promise that resolves when the refetch completes successfully\n * @throws Error if the refetch fails\n */\n clearError: () => Promise<void>\n}\n\n/**\n * Internal state object for tracking query observer and errors\n */\ninterface QueryCollectionState {\n lastError: any\n errorCount: number\n lastErrorUpdatedAt: number\n observers: Map<\n string,\n QueryObserver<Array<any>, any, Array<any>, Array<any>, any>\n >\n}\n\n/**\n * Implementation class for QueryCollectionUtils with explicit dependency injection\n * for better testability and architectural clarity\n */\nclass QueryCollectionUtilsImpl {\n private state: QueryCollectionState\n private refetchFn: RefetchFn\n\n // Write methods\n public refetch: RefetchFn\n public writeInsert: any\n public writeUpdate: any\n public writeDelete: any\n public writeUpsert: any\n public writeBatch: any\n\n constructor(\n state: QueryCollectionState,\n refetch: RefetchFn,\n writeUtils: ReturnType<typeof createWriteUtils>,\n ) {\n this.state = state\n this.refetchFn = refetch\n\n // Initialize methods to use passed dependencies\n this.refetch = refetch\n this.writeInsert = writeUtils.writeInsert\n this.writeUpdate = writeUtils.writeUpdate\n this.writeDelete = writeUtils.writeDelete\n this.writeUpsert = writeUtils.writeUpsert\n this.writeBatch = writeUtils.writeBatch\n }\n\n public async clearError() {\n this.state.lastError = undefined\n this.state.errorCount = 0\n this.state.lastErrorUpdatedAt = 0\n await this.refetchFn({ throwOnError: true })\n }\n\n // Getters for error state\n public get lastError() {\n return this.state.lastError\n }\n\n public get isError() {\n return !!this.state.lastError\n }\n\n public get errorCount() {\n return this.state.errorCount\n }\n\n // Getters for QueryObserver state\n public get isFetching() {\n // check if any observer is fetching\n return Array.from(this.state.observers.values()).some(\n (observer) => observer.getCurrentResult().isFetching,\n )\n }\n\n public get isRefetching() {\n // check if any observer is refetching\n return Array.from(this.state.observers.values()).some(\n (observer) => observer.getCurrentResult().isRefetching,\n )\n }\n\n public get isLoading() {\n // check if any observer is loading\n return Array.from(this.state.observers.values()).some(\n (observer) => observer.getCurrentResult().isLoading,\n )\n }\n\n public get dataUpdatedAt() {\n // compute the max dataUpdatedAt of all observers\n return Math.max(\n 0,\n ...Array.from(this.state.observers.values()).map(\n (observer) => observer.getCurrentResult().dataUpdatedAt,\n ),\n )\n }\n\n public get fetchStatus(): Array<FetchStatus> {\n return Array.from(this.state.observers.values()).map(\n (observer) => observer.getCurrentResult().fetchStatus,\n )\n }\n}\n\n/**\n * Creates query collection options for use with a standard Collection.\n * This integrates TanStack Query with TanStack DB for automatic synchronization.\n *\n * Supports automatic type inference following the priority order:\n * 1. Schema inference (highest priority)\n * 2. QueryFn return type inference (second priority)\n *\n * @template T - Type of the schema if a schema is provided otherwise it is the type of the values returned by the queryFn\n * @template TError - The type of errors that can occur during queries\n * @template TQueryKey - The type of the query key\n * @template TKey - The type of the item keys\n * @param config - Configuration options for the Query collection\n * @returns Collection options with utilities for direct writes and manual operations\n *\n * @example\n * // Type inferred from queryFn return type (NEW!)\n * const todosCollection = createCollection(\n * queryCollectionOptions({\n * queryKey: ['todos'],\n * queryFn: async () => {\n * const response = await fetch('/api/todos')\n * return response.json() as Todo[] // Type automatically inferred!\n * },\n * queryClient,\n * getKey: (item) => item.id, // item is typed as Todo\n * })\n * )\n *\n * @example\n * // Explicit type\n * const todosCollection = createCollection<Todo>(\n * queryCollectionOptions({\n * queryKey: ['todos'],\n * queryFn: async () => fetch('/api/todos').then(r => r.json()),\n * queryClient,\n * getKey: (item) => item.id,\n * })\n * )\n *\n * @example\n * // Schema inference\n * const todosCollection = createCollection(\n * queryCollectionOptions({\n * queryKey: ['todos'],\n * queryFn: async () => fetch('/api/todos').then(r => r.json()),\n * queryClient,\n * schema: todoSchema, // Type inferred from schema\n * getKey: (item) => item.id,\n * })\n * )\n *\n * @example\n * // With persistence handlers\n * const todosCollection = createCollection(\n * queryCollectionOptions({\n * queryKey: ['todos'],\n * queryFn: fetchTodos,\n * queryClient,\n * getKey: (item) => item.id,\n * onInsert: async ({ transaction }) => {\n * await api.createTodos(transaction.mutations.map(m => m.modified))\n * },\n * onUpdate: async ({ transaction }) => {\n * await api.updateTodos(transaction.mutations)\n * },\n * onDelete: async ({ transaction }) => {\n * await api.deleteTodos(transaction.mutations.map(m => m.key))\n * }\n * })\n * )\n *\n * @example\n * // The select option extracts the items array from a response with metadata\n * const todosCollection = createCollection(\n * queryCollectionOptions({\n * queryKey: ['todos'],\n * queryFn: async () => fetch('/api/todos').then(r => r.json()),\n * select: (data) => data.items, // Extract the array of items\n * queryClient,\n * schema: todoSchema,\n * getKey: (item) => item.id,\n * })\n * )\n */\n// Overload for when schema is provided and select present\nexport function queryCollectionOptions<\n T extends StandardSchemaV1,\n TQueryFn extends (context: QueryFunctionContext<any>) => Promise<any>,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n TQueryData = Awaited<ReturnType<TQueryFn>>,\n>(\n config: QueryCollectionConfig<\n InferSchemaOutput<T>,\n TQueryFn,\n TError,\n TQueryKey,\n TKey,\n T\n > & {\n schema: T\n select: (data: TQueryData) => Array<InferSchemaInput<T>>\n },\n): CollectionConfig<\n InferSchemaOutput<T>,\n TKey,\n T,\n QueryCollectionUtils<InferSchemaOutput<T>, TKey, InferSchemaInput<T>, TError>\n> & {\n schema: T\n utils: QueryCollectionUtils<\n InferSchemaOutput<T>,\n TKey,\n InferSchemaInput<T>,\n TError\n >\n}\n\n// Overload for when no schema is provided and select present\nexport function queryCollectionOptions<\n T extends object,\n TQueryFn extends (context: QueryFunctionContext<any>) => Promise<any> = (\n context: QueryFunctionContext<any>,\n ) => Promise<any>,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n TQueryData = Awaited<ReturnType<TQueryFn>>,\n>(\n config: QueryCollectionConfig<\n T,\n TQueryFn,\n TError,\n TQueryKey,\n TKey,\n never,\n TQueryData\n > & {\n schema?: never // prohibit schema\n select: (data: TQueryData) => Array<T>\n },\n): CollectionConfig<\n T,\n TKey,\n never,\n QueryCollectionUtils<T, TKey, T, TError>\n> & {\n schema?: never // no schema in the result\n utils: QueryCollectionUtils<T, TKey, T, TError>\n}\n\n// Overload for when schema is provided\nexport function queryCollectionOptions<\n T extends StandardSchemaV1,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n>(\n config: QueryCollectionConfig<\n InferSchemaOutput<T>,\n (\n context: QueryFunctionContext<any>,\n ) => Promise<Array<InferSchemaOutput<T>>>,\n TError,\n TQueryKey,\n TKey,\n T\n > & {\n schema: T\n },\n): CollectionConfig<\n InferSchemaOutput<T>,\n TKey,\n T,\n QueryCollectionUtils<InferSchemaOutput<T>, TKey, InferSchemaInput<T>, TError>\n> & {\n schema: T\n utils: QueryCollectionUtils<\n InferSchemaOutput<T>,\n TKey,\n InferSchemaInput<T>,\n TError\n >\n}\n\n// Overload for when no schema is provided\nexport function queryCollectionOptions<\n T extends object,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n>(\n config: QueryCollectionConfig<\n T,\n (context: QueryFunctionContext<any>) => Promise<Array<T>>,\n TError,\n TQueryKey,\n TKey\n > & {\n schema?: never // prohibit schema\n },\n): CollectionConfig<\n T,\n TKey,\n never,\n QueryCollectionUtils<T, TKey, T, TError>\n> & {\n schema?: never // no schema in the result\n utils: QueryCollectionUtils<T, TKey, T, TError>\n}\n\nexport function queryCollectionOptions(\n config: QueryCollectionConfig<Record<string, unknown>>,\n): CollectionConfig<\n Record<string, unknown>,\n string | number,\n never,\n QueryCollectionUtils\n> & {\n utils: QueryCollectionUtils\n} {\n const {\n queryKey,\n queryFn,\n select,\n queryClient,\n enabled,\n refetchInterval,\n retry,\n retryDelay,\n staleTime,\n getKey,\n onInsert,\n onUpdate,\n onDelete,\n meta,\n ...baseCollectionConfig\n } = config\n\n // Default to eager sync mode if not provided\n const syncMode = baseCollectionConfig.syncMode ?? `eager`\n\n // Validate required parameters\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!queryKey) {\n throw new QueryKeyRequiredError()\n }\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!queryFn) {\n throw new QueryFnRequiredError()\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!queryClient) {\n throw new QueryClientRequiredError()\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!getKey) {\n throw new GetKeyRequiredError()\n }\n\n /** State object to hold error tracking and observer reference */\n const state: QueryCollectionState = {\n lastError: undefined as any,\n errorCount: 0,\n lastErrorUpdatedAt: 0,\n observers: new Map<\n string,\n QueryObserver<Array<any>, any, Array<any>, Array<any>, any>\n >(),\n }\n\n // hashedQueryKey → queryKey\n const hashToQueryKey = new Map<string, QueryKey>()\n\n // queryKey → Set<RowKey>\n const queryToRows = new Map<string, Set<string | number>>()\n\n // RowKey → Set<queryKey>\n const rowToQueries = new Map<string | number, Set<string>>()\n\n // queryKey → QueryObserver's unsubscribe function\n const unsubscribes = new Map<string, () => void>()\n\n // queryKey → reference count (how many loadSubset calls are active)\n // Reference counting for QueryObserver lifecycle management\n // =========================================================\n // Tracks how many live query subscriptions are using each QueryObserver.\n // Multiple live queries with identical predicates share the same QueryObserver for efficiency.\n //\n // Lifecycle:\n // - Increment: when createQueryFromOpts creates or reuses an observer\n // - Decrement: when subscription.unsubscribe() passes predicates to collection._sync.unloadSubset()\n // - Reset: when cleanupQuery() is triggered by TanStack Query's cache GC\n //\n // When refcount reaches 0, unloadSubset():\n // 1. Computes the same queryKey from the predicates\n // 2. Uses existing machinery (queryToRows map) to find rows that query loaded\n // 3. Decrements refcount and GCs rows where count reaches 0\n const queryRefCounts = new Map<string, number>()\n\n // Helper function to add a row to the internal state\n const addRow = (rowKey: string | number, hashedQueryKey: string) => {\n const rowToQueriesSet = rowToQueries.get(rowKey) || new Set()\n rowToQueriesSet.add(hashedQueryKey)\n rowToQueries.set(rowKey, rowToQueriesSet)\n\n const queryToRowsSet = queryToRows.get(hashedQueryKey) || new Set()\n queryToRowsSet.add(rowKey)\n queryToRows.set(hashedQueryKey, queryToRowsSet)\n }\n\n // Helper function to remove a row from the internal state\n const removeRow = (rowKey: string | number, hashedQuerKey: string) => {\n const rowToQueriesSet = rowToQueries.get(rowKey) || new Set()\n rowToQueriesSet.delete(hashedQuerKey)\n rowToQueries.set(rowKey, rowToQueriesSet)\n\n const queryToRowsSet = queryToRows.get(hashedQuerKey) || new Set()\n queryToRowsSet.delete(rowKey)\n queryToRows.set(hashedQuerKey, queryToRowsSet)\n\n return rowToQueriesSet.size === 0\n }\n\n const internalSync: SyncConfig<any>[`sync`] = (params) => {\n const { begin, write, commit, markReady, collection } = params\n\n // Track whether sync has been started\n let syncStarted = false\n\n /**\n * Generate a consistent query key from LoadSubsetOptions.\n * CRITICAL: Must use identical logic in both createQueryFromOpts and unloadSubset\n * so that refcount increment/decrement operations target the same hashedQueryKey.\n * Inconsistent keys would cause refcount leaks and prevent proper cleanup.\n */\n const generateQueryKeyFromOptions = (opts: LoadSubsetOptions): QueryKey => {\n if (typeof queryKey === `function`) {\n // Function-based queryKey: use it to build the key from opts\n return queryKey(opts)\n } else if (syncMode === `on-demand`) {\n // Static queryKey in on-demand mode: automatically append serialized predicates\n // to create separate cache entries for different predicate combinations\n const serialized = serializeLoadSubsetOptions(opts)\n return serialized !== undefined ? [...queryKey, serialized] : queryKey\n } else {\n // Static queryKey in eager mode: use as-is\n return queryKey\n }\n }\n\n const createQueryFromOpts = (\n opts: LoadSubsetOptions = {},\n queryFunction: typeof queryFn = queryFn,\n ): true | Promise<void> => {\n // Generate key using common function\n const key = generateQueryKeyFromOptions(opts)\n const hashedQueryKey = hashKey(key)\n const extendedMeta = { ...meta, loadSubsetOptions: opts }\n\n if (state.observers.has(hashedQueryKey)) {\n // We already have a query for this queryKey\n // Increment reference count since another consumer is using this observer\n queryRefCounts.set(\n hashedQueryKey,\n (queryRefCounts.get(hashedQueryKey) || 0) + 1,\n )\n\n // Get the current result and return based on its state\n const observer = state.observers.get(hashedQueryKey)!\n const currentResult = observer.getCurrentResult()\n\n if (currentResult.isSuccess) {\n // Data is already available, return true synchronously\n return true\n } else if (currentResult.isError) {\n // Error already occurred, reject immediately\n return Promise.reject(currentResult.error)\n } else {\n // Query is still loading, wait for the first result\n return new Promise<void>((resolve, reject) => {\n const unsubscribe = observer.subscribe((result) => {\n // Use a microtask in case `subscribe` is called synchronously, before `unsubscribe` is initialized\n queueMicrotask(() => {\n if (result.isSuccess) {\n unsubscribe()\n resolve()\n } else if (result.isError) {\n unsubscribe()\n reject(result.error)\n }\n })\n })\n })\n }\n }\n\n const observerOptions: QueryObserverOptions<\n Array<any>,\n any,\n Array<any>,\n Array<any>,\n any\n > = {\n queryKey: key,\n queryFn: queryFunction,\n meta: extendedMeta,\n structuralSharing: true,\n notifyOnChangeProps: `all`,\n\n // Only include options that are explicitly defined to allow QueryClient defaultOptions to be used\n ...(enabled !== undefined && { enabled }),\n ...(refetchInterval !== undefined && { refetchInterval }),\n ...(retry !== undefined && { retry }),\n ...(retryDelay !== undefined && { retryDelay }),\n ...(staleTime !== undefined && { staleTime }),\n }\n\n const localObserver = new QueryObserver<\n Array<any>,\n any,\n Array<any>,\n Array<any>,\n any\n >(queryClient, observerOptions)\n\n hashToQueryKey.set(hashedQueryKey, key)\n state.observers.set(hashedQueryKey, localObserver)\n\n // Increment reference count for this query\n queryRefCounts.set(\n hashedQueryKey,\n (queryRefCounts.get(hashedQueryKey) || 0) + 1,\n )\n\n // Create a promise that resolves when the query result is first available\n const readyPromise = new Promise<void>((resolve, reject) => {\n const unsubscribe = localObserver.subscribe((result) => {\n // Use a microtask in case `subscribe` is called synchronously, before `unsubscribe` is initialized\n queueMicrotask(() => {\n if (result.isSuccess) {\n unsubscribe()\n resolve()\n } else if (result.isError) {\n unsubscribe()\n reject(result.error)\n }\n })\n })\n })\n\n // If sync has started or there are subscribers to the collection, subscribe to the query straight away\n // This creates the main subscription that handles data updates\n if (syncStarted || collection.subscriberCount > 0) {\n subscribeToQuery(localObserver, hashedQueryKey)\n }\n\n return readyPromise\n }\n\n type UpdateHandler = Parameters<QueryObserver[`subscribe`]>[0]\n\n const makeQueryResultHandler = (queryKey: QueryKey) => {\n const hashedQueryKey = hashKey(queryKey)\n const handleQueryResult: UpdateHandler = (result) => {\n if (result.isSuccess) {\n // Clear error state\n state.lastError = undefined\n state.errorCount = 0\n\n const rawData = result.data\n const newItemsArray = select ? select(rawData) : rawData\n\n if (\n !Array.isArray(newItemsArray) ||\n newItemsArray.some((item) => typeof item !== `object`)\n ) {\n const errorMessage = select\n ? `@tanstack/query-db-collection: select() must return an array of objects. Got: ${typeof newItemsArray} for queryKey ${JSON.stringify(queryKey)}`\n : `@tanstack/query-db-collection: queryFn must return an array of objects. Got: ${typeof newItemsArray} for queryKey ${JSON.stringify(queryKey)}`\n\n console.error(errorMessage)\n return\n }\n\n const currentSyncedItems: Map<string | number, any> = new Map(\n collection._state.syncedData.entries(),\n )\n const newItemsMap = new Map<string | number, any>()\n newItemsArray.forEach((item) => {\n const key = getKey(item)\n newItemsMap.set(key, item)\n })\n\n begin()\n\n currentSyncedItems.forEach((oldItem, key) => {\n const newItem = newItemsMap.get(key)\n if (!newItem) {\n const needToRemove = removeRow(key, hashedQueryKey) // returns true if the row is no longer referenced by any queries\n if (needToRemove) {\n write({ type: `delete`, value: oldItem })\n }\n } else if (!deepEquals(oldItem, newItem)) {\n // Only update if there are actual differences in the properties\n write({ type: `update`, value: newItem })\n }\n })\n\n newItemsMap.forEach((newItem, key) => {\n addRow(key, hashedQueryKey)\n if (!currentSyncedItems.has(key)) {\n write({ type: `insert`, value: newItem })\n }\n })\n\n commit()\n\n // Mark collection as ready after first successful query result\n markReady()\n } else if (result.isError) {\n if (result.errorUpdatedAt !== state.lastErrorUpdatedAt) {\n state.lastError = result.error\n state.errorCount++\n state.lastErrorUpdatedAt = result.errorUpdatedAt\n }\n\n console.error(\n `[QueryCollection] Error observing query ${String(queryKey)}:`,\n result.error,\n )\n\n // Mark collection as ready even on error to avoid blocking apps\n markReady()\n }\n }\n return handleQueryResult\n }\n\n const isSubscribed = (hashedQueryKey: string) => {\n return unsubscribes.has(hashedQueryKey)\n }\n\n const subscribeToQuery = (\n observer: QueryObserver<Array<any>, any, Array<any>, Array<any>, any>,\n hashedQueryKey: string,\n ) => {\n if (!isSubscribed(hashedQueryKey)) {\n const cachedQueryKey = hashToQueryKey.get(hashedQueryKey)!\n const handleQueryResult = makeQueryResultHandler(cachedQueryKey)\n const unsubscribeFn = observer.subscribe(handleQueryResult)\n unsubscribes.set(hashedQueryKey, unsubscribeFn)\n\n // Process the current result immediately if available\n // This ensures data is synced when resubscribing to a query with cached data\n const currentResult = observer.getCurrentResult()\n if (currentResult.isSuccess || currentResult.isError) {\n handleQueryResult(currentResult)\n }\n }\n }\n\n const subscribeToQueries = () => {\n state.observers.forEach(subscribeToQuery)\n }\n\n const unsubscribeFromQueries = () => {\n unsubscribes.forEach((unsubscribeFn) => {\n unsubscribeFn()\n })\n unsubscribes.clear()\n }\n\n // Mark that sync has started\n syncStarted = true\n\n // Set up event listener for subscriber changes\n const unsubscribeFromCollectionEvents = collection.on(\n `subscribers:change`,\n ({ subscriberCount }) => {\n if (subscriberCount > 0) {\n subscribeToQueries()\n } else if (subscriberCount === 0) {\n unsubscribeFromQueries()\n }\n },\n )\n\n // If syncMode is eager, create the initial query without any predicates\n if (syncMode === `eager`) {\n // Catch any errors to prevent unhandled rejections\n const initialResult = createQueryFromOpts({})\n if (initialResult instanceof Promise) {\n initialResult.catch(() => {\n // Errors are already handled by the query result handler\n })\n }\n } else {\n // In on-demand mode, mark ready immediately since there's no initial query\n markReady()\n }\n\n // Always subscribe when sync starts (this could be from preload(), startSync config, or first subscriber)\n // We'll dynamically unsubscribe/resubscribe based on subscriber count to maintain staleTime behavior\n subscribeToQueries()\n\n // Ensure we process any existing query data (QueryObserver doesn't invoke its callback automatically with initial state)\n state.observers.forEach((observer, hashedQueryKey) => {\n const cachedQueryKey = hashToQueryKey.get(hashedQueryKey)!\n const handleQueryResult = makeQueryResultHandler(cachedQueryKey)\n handleQueryResult(observer.getCurrentResult())\n })\n\n /**\n * Perform row-level cleanup and remove all tracking for a query.\n * Callers are responsible for ensuring the query is safe to cleanup.\n */\n const cleanupQueryInternal = (hashedQueryKey: string) => {\n unsubscribes.get(hashedQueryKey)?.()\n unsubscribes.delete(hashedQueryKey)\n\n const rowKeys = queryToRows.get(hashedQueryKey) ?? new Set()\n const rowsToDelete: Array<any> = []\n\n rowKeys.forEach((rowKey) => {\n const queries = rowToQueries.get(rowKey)\n\n if (!queries) {\n return\n }\n\n queries.delete(hashedQueryKey)\n\n if (queries.size === 0) {\n rowToQueries.delete(rowKey)\n\n if (collection.has(rowKey)) {\n rowsToDelete.push(collection.get(rowKey))\n }\n }\n })\n\n if (rowsToDelete.length > 0) {\n begin()\n rowsToDelete.forEach((row) => {\n write({ type: `delete`, value: row })\n })\n commit()\n }\n\n state.observers.delete(hashedQueryKey)\n queryToRows.delete(hashedQueryKey)\n hashToQueryKey.delete(hashedQueryKey)\n queryRefCounts.delete(hashedQueryKey)\n }\n\n /**\n * Attempt to cleanup a query when it appears unused.\n * Respects refcounts and invalidateQueries cycles via hasListeners().\n */\n const cleanupQueryIfIdle = (hashedQueryKey: string) => {\n const refcount = queryRefCounts.get(hashedQueryKey) || 0\n const observer = state.observers.get(hashedQueryKey)\n\n if (refcount <= 0) {\n // Drop our subscription so hasListeners reflects only active consumers\n unsubscribes.get(hashedQueryKey)?.()\n unsubscribes.delete(hashedQueryKey)\n }\n\n const hasListeners = observer?.hasListeners() ?? false\n\n if (hasListeners) {\n // During invalidateQueries, TanStack Query keeps internal listeners alive.\n // Leave refcount at 0 but keep observer so it can resubscribe.\n queryRefCounts.set(hashedQueryKey, 0)\n return\n }\n\n // No listeners means the query is truly idle.\n // Even if refcount > 0, we treat hasListeners as authoritative to prevent leaks.\n // This can happen if subscriptions are GC'd without calling unloadSubset.\n if (refcount > 0) {\n console.warn(\n `[cleanupQueryIfIdle] Invariant violation: refcount=${refcount} but no listeners. Cleaning up to prevent leak.`,\n { hashedQueryKey },\n )\n }\n\n cleanupQueryInternal(hashedQueryKey)\n }\n\n /**\n * Force cleanup used by explicit collection cleanup.\n * Ignores refcounts/hasListeners and removes everything.\n */\n const forceCleanupQuery = (hashedQueryKey: string) => {\n cleanupQueryInternal(hashedQueryKey)\n }\n\n // Subscribe to the query client's cache to handle queries that are GCed by tanstack query\n const unsubscribeQueryCache = queryClient\n .getQueryCache()\n .subscribe((event) => {\n const hashedKey = event.query.queryHash\n if (event.type === `removed`) {\n // Only cleanup if this is OUR query (we track it)\n if (hashToQueryKey.has(hashedKey)) {\n // TanStack Query GC'd this query after gcTime expired.\n // Use the guarded cleanup path to avoid deleting rows for active queries.\n cleanupQueryIfIdle(hashedKey)\n }\n }\n })\n\n const cleanup = async () => {\n unsubscribeFromCollectionEvents()\n unsubscribeFromQueries()\n\n const allQueryKeys = [...hashToQueryKey.values()]\n const allHashedKeys = [...state.observers.keys()]\n\n // Force cleanup all queries (explicit cleanup path)\n // This ignores hasListeners and always cleans up\n for (const hashedKey of allHashedKeys) {\n forceCleanupQuery(hashedKey)\n }\n\n // Unsubscribe from cache events (cleanup already happened above)\n unsubscribeQueryCache()\n\n // Remove queries from TanStack Query cache\n await Promise.all(\n allQueryKeys.map(async (qKey) => {\n await queryClient.cancelQueries({ queryKey: qKey, exact: true })\n queryClient.removeQueries({ queryKey: qKey, exact: true })\n }),\n )\n }\n\n /**\n * Unload a query subset - the subscription-based cleanup path (on-demand mode).\n *\n * Called when a live query subscription unsubscribes (via collection._sync.unloadSubset()).\n *\n * Flow:\n * 1. Receives the same predicates that were passed to loadSubset\n * 2. Computes the queryKey using generateQueryKeyFromOptions (same logic as loadSubset)\n * 3. Decrements refcount\n * 4. If refcount reaches 0:\n * - Checks hasListeners() to detect invalidateQueries cycles\n * - If hasListeners is true: resets refcount (TanStack Query keeping observer alive)\n * - If hasListeners is false: calls forceCleanupQuery() to perform row-level GC\n *\n * The hasListeners() check prevents premature cleanup during invalidateQueries:\n * - invalidateQueries causes temporary unsubscribe/resubscribe\n * - During unsubscribe, our refcount drops to 0\n * - But observer.hasListeners() is still true (TanStack Query's internal listeners)\n * - We skip cleanup and reset refcount, allowing resubscribe to succeed\n *\n * We don't cancel in-flight requests. Unsubscribing from the observer is sufficient\n * to prevent late-arriving data from being processed. The request completes and is cached\n * by TanStack Query, allowing quick remounts to restore data without refetching.\n */\n const unloadSubset = (options: LoadSubsetOptions) => {\n // 1. Same predicates → 2. Same queryKey\n const key = generateQueryKeyFromOptions(options)\n const hashedQueryKey = hashKey(key)\n\n // 3. Decrement refcount\n const currentCount = queryRefCounts.get(hashedQueryKey) || 0\n const newCount = currentCount - 1\n\n // Update refcount\n if (newCount <= 0) {\n queryRefCounts.set(hashedQueryKey, 0)\n cleanupQueryIfIdle(hashedQueryKey)\n } else {\n // Still have other references, just decrement\n queryRefCounts.set(hashedQueryKey, newCount)\n }\n }\n\n // Create deduplicated loadSubset wrapper for non-eager modes\n // This prevents redundant snapshot requests when multiple concurrent\n // live queries request overlapping or subset predicates\n const loadSubsetDedupe =\n syncMode === `eager` ? undefined : createQueryFromOpts\n\n return {\n loadSubset: loadSubsetDedupe,\n unloadSubset: syncMode === `eager` ? undefined : unloadSubset,\n cleanup,\n }\n }\n\n /**\n * Refetch the query data\n *\n * Uses queryObserver.refetch() because:\n * - Bypasses `enabled: false` to support manual/imperative refetch patterns (e.g., button-triggered fetch)\n * - Ensures clearError() works even when enabled: false\n * - Always refetches THIS specific collection (exact targeting via observer)\n * - Respects retry, retryDelay, and other observer options\n *\n * This matches TanStack Query's hook behavior where refetch() bypasses enabled: false.\n * See: https://tanstack.com/query/latest/docs/framework/react/guides/disabling-queries\n *\n * Used by both:\n * - utils.refetch() - for explicit user-triggered refetches\n * - Internal handlers (onInsert/onUpdate/onDelete) - after mutations to get fresh data\n *\n * @returns Promise that resolves when the refetch is complete, with QueryObserverResult\n */\n const refetch: RefetchFn = async (opts) => {\n const allQueryKeys = [...hashToQueryKey.values()]\n const refetchPromises = allQueryKeys.map((qKey) => {\n const queryObserver = state.observers.get(hashKey(qKey))!\n return queryObserver.refetch({\n throwOnError: opts?.throwOnError,\n })\n })\n\n await Promise.all(refetchPromises)\n }\n\n /**\n * Updates the query cache with new items, handling both direct arrays\n * and wrapped response formats (when `select` is used).\n */\n const updateCacheData = (items: Array<any>): void => {\n // Get the base query key (handle both static and function-based keys)\n const key =\n typeof queryKey === `function`\n ? queryKey({})\n : (queryKey as unknown as QueryKey)\n\n if (select) {\n // When `select` is used, the cache contains a wrapped response (e.g., { data: [...], meta: {...} })\n // We need to update the cache while preserving the wrapper structure\n queryClient.setQueryData(key, (oldData: any) => {\n if (!oldData || typeof oldData !== `object`) {\n // No existing cache or not an object - don't corrupt the cache\n return oldData\n }\n\n if (Array.isArray(oldData)) {\n // Cache is already a raw array (shouldn't happen with select, but handle it)\n return items\n }\n\n // Use the select function to identify which property contains the items array.\n // This is more robust than guessing based on property order.\n const selectedArray = select(oldData)\n\n if (Array.isArray(selectedArray)) {\n // Find the property that matches the selected array by reference equality\n for (const propKey of Object.keys(oldData)) {\n if (oldData[propKey] === selectedArray) {\n // Found the exact property - create a shallow copy with updated items\n return { ...oldData, [propKey]: items }\n }\n }\n }\n\n // Fallback: check common property names used for data arrays\n if (Array.isArray(oldData.data)) {\n return { ...oldData, data: items }\n }\n if (Array.isArray(oldData.items)) {\n return { ...oldData, items: items }\n }\n if (Array.isArray(oldData.results)) {\n return { ...oldData, results: items }\n }\n\n // Last resort: find first array property\n for (const propKey of Object.keys(oldData)) {\n if (Array.isArray(oldData[propKey])) {\n return { ...oldData, [propKey]: items }\n }\n }\n\n // Couldn't safely identify the array property - don't corrupt the cache\n // Return oldData unchanged to avoid breaking select\n return oldData\n })\n } else {\n // No select - cache contains raw array, just set it directly\n queryClient.setQueryData(key, items)\n }\n }\n\n // Create write context for manual write operations\n let writeContext: {\n collection: any\n queryClient: QueryClient\n queryKey: Array<unknown>\n getKey: (item: any) => string | number\n begin: () => void\n write: (message: Omit<ChangeMessage<any>, `key`>) => void\n commit: () => void\n updateCacheData?: (items: Array<any>) => void\n } | null = null\n\n // Enhanced internalSync that captures write functions for manual use\n const enhancedInternalSync: SyncConfig<any>[`sync`] = (params) => {\n const { begin, write, commit, collection } = params\n\n // Get the base query key for the context (handle both static and function-based keys)\n const contextQueryKey =\n typeof queryKey === `function`\n ? (queryKey({}) as unknown as Array<unknown>)\n : (queryKey as unknown as Array<unknown>)\n\n // Store references for manual write operations\n writeContext = {\n collection,\n queryClient,\n queryKey: contextQueryKey,\n getKey: getKey as (item: any) => string | number,\n begin,\n write,\n commit,\n updateCacheData,\n }\n\n // Call the original internalSync logic\n return internalSync(params)\n }\n\n // Create write utils using the manual-sync module\n const writeUtils = createWriteUtils<any, string | number, any>(\n () => writeContext,\n )\n\n // Create wrapper handlers for direct persistence operations that handle refetching\n const wrappedOnInsert = onInsert\n ? async (params: InsertMutationFnParams<any>) => {\n const handlerResult = (await onInsert(params)) ?? {}\n const shouldRefetch =\n (handlerResult as { refetch?: boolean }).refetch !== false\n\n if (shouldRefetch) {\n await refetch()\n }\n\n return handlerResult\n }\n : undefined\n\n const wrappedOnUpdate = onUpdate\n ? async (params: UpdateMutationFnParams<any>) => {\n const handlerResult = (await onUpdate(params)) ?? {}\n const shouldRefetch =\n (handlerResult as { refetch?: boolean }).refetch !== false\n\n if (shouldRefetch) {\n await refetch()\n }\n\n return handlerResult\n }\n : undefined\n\n const wrappedOnDelete = onDelete\n ? async (params: DeleteMutationFnParams<any>) => {\n const handlerResult = (await onDelete(params)) ?? {}\n const shouldRefetch =\n (handlerResult as { refetch?: boolean }).refetch !== false\n\n if (shouldRefetch) {\n await refetch()\n }\n\n return handlerResult\n }\n : undefined\n\n // Create utils instance with state and dependencies passed explicitly\n const utils: any = new QueryCollectionUtilsImpl(state, refetch, writeUtils)\n\n return {\n ...baseCollectionConfig,\n getKey,\n syncMode,\n sync: { sync: enhancedInternalSync },\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils,\n }\n}\n"],"names":["QueryKeyRequiredError","QueryFnRequiredError","QueryClientRequiredError","GetKeyRequiredError","serializeLoadSubsetOptions","hashKey","QueryObserver","queryKey","deepEquals","createWriteUtils"],"mappings":"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"}
package/dist/esm/query.js CHANGED
@@ -150,13 +150,15 @@ function queryCollectionOptions(config) {
150
150
  } else {
151
151
  return new Promise((resolve, reject) => {
152
152
  const unsubscribe = observer.subscribe((result) => {
153
- if (result.isSuccess) {
154
- unsubscribe();
155
- resolve();
156
- } else if (result.isError) {
157
- unsubscribe();
158
- reject(result.error);
159
- }
153
+ queueMicrotask(() => {
154
+ if (result.isSuccess) {
155
+ unsubscribe();
156
+ resolve();
157
+ } else if (result.isError) {
158
+ unsubscribe();
159
+ reject(result.error);
160
+ }
161
+ });
160
162
  });
161
163
  });
162
164
  }
@@ -183,13 +185,15 @@ function queryCollectionOptions(config) {
183
185
  );
184
186
  const readyPromise = new Promise((resolve, reject) => {
185
187
  const unsubscribe = localObserver.subscribe((result) => {
186
- if (result.isSuccess) {
187
- unsubscribe();
188
- resolve();
189
- } else if (result.isError) {
190
- unsubscribe();
191
- reject(result.error);
192
- }
188
+ queueMicrotask(() => {
189
+ if (result.isSuccess) {
190
+ unsubscribe();
191
+ resolve();
192
+ } else if (result.isError) {
193
+ unsubscribe();
194
+ reject(result.error);
195
+ }
196
+ });
193
197
  });
194
198
  });
195
199
  if (syncStarted || collection.subscriberCount > 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"query.js","sources":["../../src/query.ts"],"sourcesContent":["import { QueryObserver, hashKey } from '@tanstack/query-core'\nimport { deepEquals } from '@tanstack/db'\nimport {\n GetKeyRequiredError,\n QueryClientRequiredError,\n QueryFnRequiredError,\n QueryKeyRequiredError,\n} from './errors'\nimport { createWriteUtils } from './manual-sync'\nimport { serializeLoadSubsetOptions } from './serialization'\nimport type {\n BaseCollectionConfig,\n ChangeMessage,\n CollectionConfig,\n DeleteMutationFnParams,\n InsertMutationFnParams,\n LoadSubsetOptions,\n SyncConfig,\n UpdateMutationFnParams,\n UtilsRecord,\n} from '@tanstack/db'\nimport type {\n FetchStatus,\n QueryClient,\n QueryFunctionContext,\n QueryKey,\n QueryObserverOptions,\n QueryObserverResult,\n} from '@tanstack/query-core'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\n\n// Re-export for external use\nexport type { SyncOperation } from './manual-sync'\n\n// Schema output type inference helper (matches electric.ts pattern)\ntype InferSchemaOutput<T> = T extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<T> extends object\n ? StandardSchemaV1.InferOutput<T>\n : Record<string, unknown>\n : Record<string, unknown>\n\n// Schema input type inference helper (matches electric.ts pattern)\ntype InferSchemaInput<T> = T extends StandardSchemaV1\n ? StandardSchemaV1.InferInput<T> extends object\n ? StandardSchemaV1.InferInput<T>\n : Record<string, unknown>\n : Record<string, unknown>\n\ntype TQueryKeyBuilder<TQueryKey> = (opts: LoadSubsetOptions) => TQueryKey\n\n/**\n * Configuration options for creating a Query Collection\n * @template T - The explicit type of items stored in the collection\n * @template TQueryFn - The queryFn type\n * @template TError - The type of errors that can occur during queries\n * @template TQueryKey - The type of the query key\n * @template TKey - The type of the item keys\n * @template TSchema - The schema type for validation\n */\nexport interface QueryCollectionConfig<\n T extends object = object,\n TQueryFn extends (context: QueryFunctionContext<any>) => Promise<any> = (\n context: QueryFunctionContext<any>,\n ) => Promise<any>,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = never,\n TQueryData = Awaited<ReturnType<TQueryFn>>,\n> extends BaseCollectionConfig<T, TKey, TSchema> {\n /** The query key used by TanStack Query to identify this query */\n queryKey: TQueryKey | TQueryKeyBuilder<TQueryKey>\n /** Function that fetches data from the server. Must return the complete collection state */\n queryFn: TQueryFn extends (\n context: QueryFunctionContext<TQueryKey>,\n ) => Promise<Array<any>>\n ? (context: QueryFunctionContext<TQueryKey>) => Promise<Array<T>>\n : TQueryFn\n /* Function that extracts array items from wrapped API responses (e.g metadata, pagination) */\n select?: (data: TQueryData) => Array<T>\n /** The TanStack Query client instance */\n queryClient: QueryClient\n\n // Query-specific options\n /** Whether the query should automatically run (default: true) */\n enabled?: boolean\n refetchInterval?: QueryObserverOptions<\n Array<T>,\n TError,\n Array<T>,\n Array<T>,\n TQueryKey\n >[`refetchInterval`]\n retry?: QueryObserverOptions<\n Array<T>,\n TError,\n Array<T>,\n Array<T>,\n TQueryKey\n >[`retry`]\n retryDelay?: QueryObserverOptions<\n Array<T>,\n TError,\n Array<T>,\n Array<T>,\n TQueryKey\n >[`retryDelay`]\n staleTime?: QueryObserverOptions<\n Array<T>,\n TError,\n Array<T>,\n Array<T>,\n TQueryKey\n >[`staleTime`]\n\n /**\n * Metadata to pass to the query.\n * Available in queryFn via context.meta\n *\n * @example\n * // Using meta for error context\n * queryFn: async (context) => {\n * try {\n * return await api.getTodos(userId)\n * } catch (error) {\n * // Use meta for better error messages\n * throw new Error(\n * context.meta?.errorMessage || 'Failed to load todos'\n * )\n * }\n * },\n * meta: {\n * errorMessage: `Failed to load todos for user ${userId}`\n * }\n */\n meta?: Record<string, unknown>\n}\n\n/**\n * Type for the refetch utility function\n * Returns the QueryObserverResult from TanStack Query\n */\nexport type RefetchFn = (opts?: {\n throwOnError?: boolean\n}) => Promise<QueryObserverResult<any, any> | void>\n\n/**\n * Utility methods available on Query Collections for direct writes and manual operations.\n * Direct writes bypass the normal query/mutation flow and write directly to the synced data store.\n * @template TItem - The type of items stored in the collection\n * @template TKey - The type of the item keys\n * @template TInsertInput - The type accepted for insert operations\n * @template TError - The type of errors that can occur during queries\n */\nexport interface QueryCollectionUtils<\n TItem extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TInsertInput extends object = TItem,\n TError = unknown,\n> extends UtilsRecord {\n /** Manually trigger a refetch of the query */\n refetch: RefetchFn\n /** Insert one or more items directly into the synced data store without triggering a query refetch or optimistic update */\n writeInsert: (data: TInsertInput | Array<TInsertInput>) => void\n /** Update one or more items directly in the synced data store without triggering a query refetch or optimistic update */\n writeUpdate: (updates: Partial<TItem> | Array<Partial<TItem>>) => void\n /** Delete one or more items directly from the synced data store without triggering a query refetch or optimistic update */\n writeDelete: (keys: TKey | Array<TKey>) => void\n /** Insert or update one or more items directly in the synced data store without triggering a query refetch or optimistic update */\n writeUpsert: (data: Partial<TItem> | Array<Partial<TItem>>) => void\n /** Execute multiple write operations as a single atomic batch to the synced data store */\n writeBatch: (callback: () => void) => void\n\n // Query Observer State (getters)\n /** Get the last error encountered by the query (if any); reset on success */\n lastError: TError | undefined\n /** Check if the collection is in an error state */\n isError: boolean\n /**\n * Get the number of consecutive sync failures.\n * Incremented only when query fails completely (not per retry attempt); reset on success.\n */\n errorCount: number\n /** Check if query is currently fetching (initial or background) */\n isFetching: boolean\n /** Check if query is refetching in background (not initial fetch) */\n isRefetching: boolean\n /** Check if query is loading for the first time (no data yet) */\n isLoading: boolean\n /** Get timestamp of last successful data update (in milliseconds) */\n dataUpdatedAt: number\n /** Get current fetch status */\n fetchStatus: `fetching` | `paused` | `idle`\n\n /**\n * Clear the error state and trigger a refetch of the query\n * @returns Promise that resolves when the refetch completes successfully\n * @throws Error if the refetch fails\n */\n clearError: () => Promise<void>\n}\n\n/**\n * Internal state object for tracking query observer and errors\n */\ninterface QueryCollectionState {\n lastError: any\n errorCount: number\n lastErrorUpdatedAt: number\n observers: Map<\n string,\n QueryObserver<Array<any>, any, Array<any>, Array<any>, any>\n >\n}\n\n/**\n * Implementation class for QueryCollectionUtils with explicit dependency injection\n * for better testability and architectural clarity\n */\nclass QueryCollectionUtilsImpl {\n private state: QueryCollectionState\n private refetchFn: RefetchFn\n\n // Write methods\n public refetch: RefetchFn\n public writeInsert: any\n public writeUpdate: any\n public writeDelete: any\n public writeUpsert: any\n public writeBatch: any\n\n constructor(\n state: QueryCollectionState,\n refetch: RefetchFn,\n writeUtils: ReturnType<typeof createWriteUtils>,\n ) {\n this.state = state\n this.refetchFn = refetch\n\n // Initialize methods to use passed dependencies\n this.refetch = refetch\n this.writeInsert = writeUtils.writeInsert\n this.writeUpdate = writeUtils.writeUpdate\n this.writeDelete = writeUtils.writeDelete\n this.writeUpsert = writeUtils.writeUpsert\n this.writeBatch = writeUtils.writeBatch\n }\n\n public async clearError() {\n this.state.lastError = undefined\n this.state.errorCount = 0\n this.state.lastErrorUpdatedAt = 0\n await this.refetchFn({ throwOnError: true })\n }\n\n // Getters for error state\n public get lastError() {\n return this.state.lastError\n }\n\n public get isError() {\n return !!this.state.lastError\n }\n\n public get errorCount() {\n return this.state.errorCount\n }\n\n // Getters for QueryObserver state\n public get isFetching() {\n // check if any observer is fetching\n return Array.from(this.state.observers.values()).some(\n (observer) => observer.getCurrentResult().isFetching,\n )\n }\n\n public get isRefetching() {\n // check if any observer is refetching\n return Array.from(this.state.observers.values()).some(\n (observer) => observer.getCurrentResult().isRefetching,\n )\n }\n\n public get isLoading() {\n // check if any observer is loading\n return Array.from(this.state.observers.values()).some(\n (observer) => observer.getCurrentResult().isLoading,\n )\n }\n\n public get dataUpdatedAt() {\n // compute the max dataUpdatedAt of all observers\n return Math.max(\n 0,\n ...Array.from(this.state.observers.values()).map(\n (observer) => observer.getCurrentResult().dataUpdatedAt,\n ),\n )\n }\n\n public get fetchStatus(): Array<FetchStatus> {\n return Array.from(this.state.observers.values()).map(\n (observer) => observer.getCurrentResult().fetchStatus,\n )\n }\n}\n\n/**\n * Creates query collection options for use with a standard Collection.\n * This integrates TanStack Query with TanStack DB for automatic synchronization.\n *\n * Supports automatic type inference following the priority order:\n * 1. Schema inference (highest priority)\n * 2. QueryFn return type inference (second priority)\n *\n * @template T - Type of the schema if a schema is provided otherwise it is the type of the values returned by the queryFn\n * @template TError - The type of errors that can occur during queries\n * @template TQueryKey - The type of the query key\n * @template TKey - The type of the item keys\n * @param config - Configuration options for the Query collection\n * @returns Collection options with utilities for direct writes and manual operations\n *\n * @example\n * // Type inferred from queryFn return type (NEW!)\n * const todosCollection = createCollection(\n * queryCollectionOptions({\n * queryKey: ['todos'],\n * queryFn: async () => {\n * const response = await fetch('/api/todos')\n * return response.json() as Todo[] // Type automatically inferred!\n * },\n * queryClient,\n * getKey: (item) => item.id, // item is typed as Todo\n * })\n * )\n *\n * @example\n * // Explicit type\n * const todosCollection = createCollection<Todo>(\n * queryCollectionOptions({\n * queryKey: ['todos'],\n * queryFn: async () => fetch('/api/todos').then(r => r.json()),\n * queryClient,\n * getKey: (item) => item.id,\n * })\n * )\n *\n * @example\n * // Schema inference\n * const todosCollection = createCollection(\n * queryCollectionOptions({\n * queryKey: ['todos'],\n * queryFn: async () => fetch('/api/todos').then(r => r.json()),\n * queryClient,\n * schema: todoSchema, // Type inferred from schema\n * getKey: (item) => item.id,\n * })\n * )\n *\n * @example\n * // With persistence handlers\n * const todosCollection = createCollection(\n * queryCollectionOptions({\n * queryKey: ['todos'],\n * queryFn: fetchTodos,\n * queryClient,\n * getKey: (item) => item.id,\n * onInsert: async ({ transaction }) => {\n * await api.createTodos(transaction.mutations.map(m => m.modified))\n * },\n * onUpdate: async ({ transaction }) => {\n * await api.updateTodos(transaction.mutations)\n * },\n * onDelete: async ({ transaction }) => {\n * await api.deleteTodos(transaction.mutations.map(m => m.key))\n * }\n * })\n * )\n *\n * @example\n * // The select option extracts the items array from a response with metadata\n * const todosCollection = createCollection(\n * queryCollectionOptions({\n * queryKey: ['todos'],\n * queryFn: async () => fetch('/api/todos').then(r => r.json()),\n * select: (data) => data.items, // Extract the array of items\n * queryClient,\n * schema: todoSchema,\n * getKey: (item) => item.id,\n * })\n * )\n */\n// Overload for when schema is provided and select present\nexport function queryCollectionOptions<\n T extends StandardSchemaV1,\n TQueryFn extends (context: QueryFunctionContext<any>) => Promise<any>,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n TQueryData = Awaited<ReturnType<TQueryFn>>,\n>(\n config: QueryCollectionConfig<\n InferSchemaOutput<T>,\n TQueryFn,\n TError,\n TQueryKey,\n TKey,\n T\n > & {\n schema: T\n select: (data: TQueryData) => Array<InferSchemaInput<T>>\n },\n): CollectionConfig<\n InferSchemaOutput<T>,\n TKey,\n T,\n QueryCollectionUtils<InferSchemaOutput<T>, TKey, InferSchemaInput<T>, TError>\n> & {\n schema: T\n utils: QueryCollectionUtils<\n InferSchemaOutput<T>,\n TKey,\n InferSchemaInput<T>,\n TError\n >\n}\n\n// Overload for when no schema is provided and select present\nexport function queryCollectionOptions<\n T extends object,\n TQueryFn extends (context: QueryFunctionContext<any>) => Promise<any> = (\n context: QueryFunctionContext<any>,\n ) => Promise<any>,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n TQueryData = Awaited<ReturnType<TQueryFn>>,\n>(\n config: QueryCollectionConfig<\n T,\n TQueryFn,\n TError,\n TQueryKey,\n TKey,\n never,\n TQueryData\n > & {\n schema?: never // prohibit schema\n select: (data: TQueryData) => Array<T>\n },\n): CollectionConfig<\n T,\n TKey,\n never,\n QueryCollectionUtils<T, TKey, T, TError>\n> & {\n schema?: never // no schema in the result\n utils: QueryCollectionUtils<T, TKey, T, TError>\n}\n\n// Overload for when schema is provided\nexport function queryCollectionOptions<\n T extends StandardSchemaV1,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n>(\n config: QueryCollectionConfig<\n InferSchemaOutput<T>,\n (\n context: QueryFunctionContext<any>,\n ) => Promise<Array<InferSchemaOutput<T>>>,\n TError,\n TQueryKey,\n TKey,\n T\n > & {\n schema: T\n },\n): CollectionConfig<\n InferSchemaOutput<T>,\n TKey,\n T,\n QueryCollectionUtils<InferSchemaOutput<T>, TKey, InferSchemaInput<T>, TError>\n> & {\n schema: T\n utils: QueryCollectionUtils<\n InferSchemaOutput<T>,\n TKey,\n InferSchemaInput<T>,\n TError\n >\n}\n\n// Overload for when no schema is provided\nexport function queryCollectionOptions<\n T extends object,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n>(\n config: QueryCollectionConfig<\n T,\n (context: QueryFunctionContext<any>) => Promise<Array<T>>,\n TError,\n TQueryKey,\n TKey\n > & {\n schema?: never // prohibit schema\n },\n): CollectionConfig<\n T,\n TKey,\n never,\n QueryCollectionUtils<T, TKey, T, TError>\n> & {\n schema?: never // no schema in the result\n utils: QueryCollectionUtils<T, TKey, T, TError>\n}\n\nexport function queryCollectionOptions(\n config: QueryCollectionConfig<Record<string, unknown>>,\n): CollectionConfig<\n Record<string, unknown>,\n string | number,\n never,\n QueryCollectionUtils\n> & {\n utils: QueryCollectionUtils\n} {\n const {\n queryKey,\n queryFn,\n select,\n queryClient,\n enabled,\n refetchInterval,\n retry,\n retryDelay,\n staleTime,\n getKey,\n onInsert,\n onUpdate,\n onDelete,\n meta,\n ...baseCollectionConfig\n } = config\n\n // Default to eager sync mode if not provided\n const syncMode = baseCollectionConfig.syncMode ?? `eager`\n\n // Validate required parameters\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!queryKey) {\n throw new QueryKeyRequiredError()\n }\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!queryFn) {\n throw new QueryFnRequiredError()\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!queryClient) {\n throw new QueryClientRequiredError()\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!getKey) {\n throw new GetKeyRequiredError()\n }\n\n /** State object to hold error tracking and observer reference */\n const state: QueryCollectionState = {\n lastError: undefined as any,\n errorCount: 0,\n lastErrorUpdatedAt: 0,\n observers: new Map<\n string,\n QueryObserver<Array<any>, any, Array<any>, Array<any>, any>\n >(),\n }\n\n // hashedQueryKey → queryKey\n const hashToQueryKey = new Map<string, QueryKey>()\n\n // queryKey → Set<RowKey>\n const queryToRows = new Map<string, Set<string | number>>()\n\n // RowKey → Set<queryKey>\n const rowToQueries = new Map<string | number, Set<string>>()\n\n // queryKey → QueryObserver's unsubscribe function\n const unsubscribes = new Map<string, () => void>()\n\n // queryKey → reference count (how many loadSubset calls are active)\n // Reference counting for QueryObserver lifecycle management\n // =========================================================\n // Tracks how many live query subscriptions are using each QueryObserver.\n // Multiple live queries with identical predicates share the same QueryObserver for efficiency.\n //\n // Lifecycle:\n // - Increment: when createQueryFromOpts creates or reuses an observer\n // - Decrement: when subscription.unsubscribe() passes predicates to collection._sync.unloadSubset()\n // - Reset: when cleanupQuery() is triggered by TanStack Query's cache GC\n //\n // When refcount reaches 0, unloadSubset():\n // 1. Computes the same queryKey from the predicates\n // 2. Uses existing machinery (queryToRows map) to find rows that query loaded\n // 3. Decrements refcount and GCs rows where count reaches 0\n const queryRefCounts = new Map<string, number>()\n\n // Helper function to add a row to the internal state\n const addRow = (rowKey: string | number, hashedQueryKey: string) => {\n const rowToQueriesSet = rowToQueries.get(rowKey) || new Set()\n rowToQueriesSet.add(hashedQueryKey)\n rowToQueries.set(rowKey, rowToQueriesSet)\n\n const queryToRowsSet = queryToRows.get(hashedQueryKey) || new Set()\n queryToRowsSet.add(rowKey)\n queryToRows.set(hashedQueryKey, queryToRowsSet)\n }\n\n // Helper function to remove a row from the internal state\n const removeRow = (rowKey: string | number, hashedQuerKey: string) => {\n const rowToQueriesSet = rowToQueries.get(rowKey) || new Set()\n rowToQueriesSet.delete(hashedQuerKey)\n rowToQueries.set(rowKey, rowToQueriesSet)\n\n const queryToRowsSet = queryToRows.get(hashedQuerKey) || new Set()\n queryToRowsSet.delete(rowKey)\n queryToRows.set(hashedQuerKey, queryToRowsSet)\n\n return rowToQueriesSet.size === 0\n }\n\n const internalSync: SyncConfig<any>[`sync`] = (params) => {\n const { begin, write, commit, markReady, collection } = params\n\n // Track whether sync has been started\n let syncStarted = false\n\n /**\n * Generate a consistent query key from LoadSubsetOptions.\n * CRITICAL: Must use identical logic in both createQueryFromOpts and unloadSubset\n * so that refcount increment/decrement operations target the same hashedQueryKey.\n * Inconsistent keys would cause refcount leaks and prevent proper cleanup.\n */\n const generateQueryKeyFromOptions = (opts: LoadSubsetOptions): QueryKey => {\n if (typeof queryKey === `function`) {\n // Function-based queryKey: use it to build the key from opts\n return queryKey(opts)\n } else if (syncMode === `on-demand`) {\n // Static queryKey in on-demand mode: automatically append serialized predicates\n // to create separate cache entries for different predicate combinations\n const serialized = serializeLoadSubsetOptions(opts)\n return serialized !== undefined ? [...queryKey, serialized] : queryKey\n } else {\n // Static queryKey in eager mode: use as-is\n return queryKey\n }\n }\n\n const createQueryFromOpts = (\n opts: LoadSubsetOptions = {},\n queryFunction: typeof queryFn = queryFn,\n ): true | Promise<void> => {\n // Generate key using common function\n const key = generateQueryKeyFromOptions(opts)\n const hashedQueryKey = hashKey(key)\n const extendedMeta = { ...meta, loadSubsetOptions: opts }\n\n if (state.observers.has(hashedQueryKey)) {\n // We already have a query for this queryKey\n // Increment reference count since another consumer is using this observer\n queryRefCounts.set(\n hashedQueryKey,\n (queryRefCounts.get(hashedQueryKey) || 0) + 1,\n )\n\n // Get the current result and return based on its state\n const observer = state.observers.get(hashedQueryKey)!\n const currentResult = observer.getCurrentResult()\n\n if (currentResult.isSuccess) {\n // Data is already available, return true synchronously\n return true\n } else if (currentResult.isError) {\n // Error already occurred, reject immediately\n return Promise.reject(currentResult.error)\n } else {\n // Query is still loading, wait for the first result\n return new Promise<void>((resolve, reject) => {\n const unsubscribe = observer.subscribe((result) => {\n if (result.isSuccess) {\n unsubscribe()\n resolve()\n } else if (result.isError) {\n unsubscribe()\n reject(result.error)\n }\n })\n })\n }\n }\n\n const observerOptions: QueryObserverOptions<\n Array<any>,\n any,\n Array<any>,\n Array<any>,\n any\n > = {\n queryKey: key,\n queryFn: queryFunction,\n meta: extendedMeta,\n structuralSharing: true,\n notifyOnChangeProps: `all`,\n\n // Only include options that are explicitly defined to allow QueryClient defaultOptions to be used\n ...(enabled !== undefined && { enabled }),\n ...(refetchInterval !== undefined && { refetchInterval }),\n ...(retry !== undefined && { retry }),\n ...(retryDelay !== undefined && { retryDelay }),\n ...(staleTime !== undefined && { staleTime }),\n }\n\n const localObserver = new QueryObserver<\n Array<any>,\n any,\n Array<any>,\n Array<any>,\n any\n >(queryClient, observerOptions)\n\n hashToQueryKey.set(hashedQueryKey, key)\n state.observers.set(hashedQueryKey, localObserver)\n\n // Increment reference count for this query\n queryRefCounts.set(\n hashedQueryKey,\n (queryRefCounts.get(hashedQueryKey) || 0) + 1,\n )\n\n // Create a promise that resolves when the query result is first available\n const readyPromise = new Promise<void>((resolve, reject) => {\n const unsubscribe = localObserver.subscribe((result) => {\n if (result.isSuccess) {\n unsubscribe()\n resolve()\n } else if (result.isError) {\n unsubscribe()\n reject(result.error)\n }\n })\n })\n\n // If sync has started or there are subscribers to the collection, subscribe to the query straight away\n // This creates the main subscription that handles data updates\n if (syncStarted || collection.subscriberCount > 0) {\n subscribeToQuery(localObserver, hashedQueryKey)\n }\n\n return readyPromise\n }\n\n type UpdateHandler = Parameters<QueryObserver[`subscribe`]>[0]\n\n const makeQueryResultHandler = (queryKey: QueryKey) => {\n const hashedQueryKey = hashKey(queryKey)\n const handleQueryResult: UpdateHandler = (result) => {\n if (result.isSuccess) {\n // Clear error state\n state.lastError = undefined\n state.errorCount = 0\n\n const rawData = result.data\n const newItemsArray = select ? select(rawData) : rawData\n\n if (\n !Array.isArray(newItemsArray) ||\n newItemsArray.some((item) => typeof item !== `object`)\n ) {\n const errorMessage = select\n ? `@tanstack/query-db-collection: select() must return an array of objects. Got: ${typeof newItemsArray} for queryKey ${JSON.stringify(queryKey)}`\n : `@tanstack/query-db-collection: queryFn must return an array of objects. Got: ${typeof newItemsArray} for queryKey ${JSON.stringify(queryKey)}`\n\n console.error(errorMessage)\n return\n }\n\n const currentSyncedItems: Map<string | number, any> = new Map(\n collection._state.syncedData.entries(),\n )\n const newItemsMap = new Map<string | number, any>()\n newItemsArray.forEach((item) => {\n const key = getKey(item)\n newItemsMap.set(key, item)\n })\n\n begin()\n\n currentSyncedItems.forEach((oldItem, key) => {\n const newItem = newItemsMap.get(key)\n if (!newItem) {\n const needToRemove = removeRow(key, hashedQueryKey) // returns true if the row is no longer referenced by any queries\n if (needToRemove) {\n write({ type: `delete`, value: oldItem })\n }\n } else if (!deepEquals(oldItem, newItem)) {\n // Only update if there are actual differences in the properties\n write({ type: `update`, value: newItem })\n }\n })\n\n newItemsMap.forEach((newItem, key) => {\n addRow(key, hashedQueryKey)\n if (!currentSyncedItems.has(key)) {\n write({ type: `insert`, value: newItem })\n }\n })\n\n commit()\n\n // Mark collection as ready after first successful query result\n markReady()\n } else if (result.isError) {\n if (result.errorUpdatedAt !== state.lastErrorUpdatedAt) {\n state.lastError = result.error\n state.errorCount++\n state.lastErrorUpdatedAt = result.errorUpdatedAt\n }\n\n console.error(\n `[QueryCollection] Error observing query ${String(queryKey)}:`,\n result.error,\n )\n\n // Mark collection as ready even on error to avoid blocking apps\n markReady()\n }\n }\n return handleQueryResult\n }\n\n const isSubscribed = (hashedQueryKey: string) => {\n return unsubscribes.has(hashedQueryKey)\n }\n\n const subscribeToQuery = (\n observer: QueryObserver<Array<any>, any, Array<any>, Array<any>, any>,\n hashedQueryKey: string,\n ) => {\n if (!isSubscribed(hashedQueryKey)) {\n const cachedQueryKey = hashToQueryKey.get(hashedQueryKey)!\n const handleQueryResult = makeQueryResultHandler(cachedQueryKey)\n const unsubscribeFn = observer.subscribe(handleQueryResult)\n unsubscribes.set(hashedQueryKey, unsubscribeFn)\n\n // Process the current result immediately if available\n // This ensures data is synced when resubscribing to a query with cached data\n const currentResult = observer.getCurrentResult()\n if (currentResult.isSuccess || currentResult.isError) {\n handleQueryResult(currentResult)\n }\n }\n }\n\n const subscribeToQueries = () => {\n state.observers.forEach(subscribeToQuery)\n }\n\n const unsubscribeFromQueries = () => {\n unsubscribes.forEach((unsubscribeFn) => {\n unsubscribeFn()\n })\n unsubscribes.clear()\n }\n\n // Mark that sync has started\n syncStarted = true\n\n // Set up event listener for subscriber changes\n const unsubscribeFromCollectionEvents = collection.on(\n `subscribers:change`,\n ({ subscriberCount }) => {\n if (subscriberCount > 0) {\n subscribeToQueries()\n } else if (subscriberCount === 0) {\n unsubscribeFromQueries()\n }\n },\n )\n\n // If syncMode is eager, create the initial query without any predicates\n if (syncMode === `eager`) {\n // Catch any errors to prevent unhandled rejections\n const initialResult = createQueryFromOpts({})\n if (initialResult instanceof Promise) {\n initialResult.catch(() => {\n // Errors are already handled by the query result handler\n })\n }\n } else {\n // In on-demand mode, mark ready immediately since there's no initial query\n markReady()\n }\n\n // Always subscribe when sync starts (this could be from preload(), startSync config, or first subscriber)\n // We'll dynamically unsubscribe/resubscribe based on subscriber count to maintain staleTime behavior\n subscribeToQueries()\n\n // Ensure we process any existing query data (QueryObserver doesn't invoke its callback automatically with initial state)\n state.observers.forEach((observer, hashedQueryKey) => {\n const cachedQueryKey = hashToQueryKey.get(hashedQueryKey)!\n const handleQueryResult = makeQueryResultHandler(cachedQueryKey)\n handleQueryResult(observer.getCurrentResult())\n })\n\n /**\n * Perform row-level cleanup and remove all tracking for a query.\n * Callers are responsible for ensuring the query is safe to cleanup.\n */\n const cleanupQueryInternal = (hashedQueryKey: string) => {\n unsubscribes.get(hashedQueryKey)?.()\n unsubscribes.delete(hashedQueryKey)\n\n const rowKeys = queryToRows.get(hashedQueryKey) ?? new Set()\n const rowsToDelete: Array<any> = []\n\n rowKeys.forEach((rowKey) => {\n const queries = rowToQueries.get(rowKey)\n\n if (!queries) {\n return\n }\n\n queries.delete(hashedQueryKey)\n\n if (queries.size === 0) {\n rowToQueries.delete(rowKey)\n\n if (collection.has(rowKey)) {\n rowsToDelete.push(collection.get(rowKey))\n }\n }\n })\n\n if (rowsToDelete.length > 0) {\n begin()\n rowsToDelete.forEach((row) => {\n write({ type: `delete`, value: row })\n })\n commit()\n }\n\n state.observers.delete(hashedQueryKey)\n queryToRows.delete(hashedQueryKey)\n hashToQueryKey.delete(hashedQueryKey)\n queryRefCounts.delete(hashedQueryKey)\n }\n\n /**\n * Attempt to cleanup a query when it appears unused.\n * Respects refcounts and invalidateQueries cycles via hasListeners().\n */\n const cleanupQueryIfIdle = (hashedQueryKey: string) => {\n const refcount = queryRefCounts.get(hashedQueryKey) || 0\n const observer = state.observers.get(hashedQueryKey)\n\n if (refcount <= 0) {\n // Drop our subscription so hasListeners reflects only active consumers\n unsubscribes.get(hashedQueryKey)?.()\n unsubscribes.delete(hashedQueryKey)\n }\n\n const hasListeners = observer?.hasListeners() ?? false\n\n if (hasListeners) {\n // During invalidateQueries, TanStack Query keeps internal listeners alive.\n // Leave refcount at 0 but keep observer so it can resubscribe.\n queryRefCounts.set(hashedQueryKey, 0)\n return\n }\n\n // No listeners means the query is truly idle.\n // Even if refcount > 0, we treat hasListeners as authoritative to prevent leaks.\n // This can happen if subscriptions are GC'd without calling unloadSubset.\n if (refcount > 0) {\n console.warn(\n `[cleanupQueryIfIdle] Invariant violation: refcount=${refcount} but no listeners. Cleaning up to prevent leak.`,\n { hashedQueryKey },\n )\n }\n\n cleanupQueryInternal(hashedQueryKey)\n }\n\n /**\n * Force cleanup used by explicit collection cleanup.\n * Ignores refcounts/hasListeners and removes everything.\n */\n const forceCleanupQuery = (hashedQueryKey: string) => {\n cleanupQueryInternal(hashedQueryKey)\n }\n\n // Subscribe to the query client's cache to handle queries that are GCed by tanstack query\n const unsubscribeQueryCache = queryClient\n .getQueryCache()\n .subscribe((event) => {\n const hashedKey = event.query.queryHash\n if (event.type === `removed`) {\n // Only cleanup if this is OUR query (we track it)\n if (hashToQueryKey.has(hashedKey)) {\n // TanStack Query GC'd this query after gcTime expired.\n // Use the guarded cleanup path to avoid deleting rows for active queries.\n cleanupQueryIfIdle(hashedKey)\n }\n }\n })\n\n const cleanup = async () => {\n unsubscribeFromCollectionEvents()\n unsubscribeFromQueries()\n\n const allQueryKeys = [...hashToQueryKey.values()]\n const allHashedKeys = [...state.observers.keys()]\n\n // Force cleanup all queries (explicit cleanup path)\n // This ignores hasListeners and always cleans up\n for (const hashedKey of allHashedKeys) {\n forceCleanupQuery(hashedKey)\n }\n\n // Unsubscribe from cache events (cleanup already happened above)\n unsubscribeQueryCache()\n\n // Remove queries from TanStack Query cache\n await Promise.all(\n allQueryKeys.map(async (qKey) => {\n await queryClient.cancelQueries({ queryKey: qKey, exact: true })\n queryClient.removeQueries({ queryKey: qKey, exact: true })\n }),\n )\n }\n\n /**\n * Unload a query subset - the subscription-based cleanup path (on-demand mode).\n *\n * Called when a live query subscription unsubscribes (via collection._sync.unloadSubset()).\n *\n * Flow:\n * 1. Receives the same predicates that were passed to loadSubset\n * 2. Computes the queryKey using generateQueryKeyFromOptions (same logic as loadSubset)\n * 3. Decrements refcount\n * 4. If refcount reaches 0:\n * - Checks hasListeners() to detect invalidateQueries cycles\n * - If hasListeners is true: resets refcount (TanStack Query keeping observer alive)\n * - If hasListeners is false: calls forceCleanupQuery() to perform row-level GC\n *\n * The hasListeners() check prevents premature cleanup during invalidateQueries:\n * - invalidateQueries causes temporary unsubscribe/resubscribe\n * - During unsubscribe, our refcount drops to 0\n * - But observer.hasListeners() is still true (TanStack Query's internal listeners)\n * - We skip cleanup and reset refcount, allowing resubscribe to succeed\n *\n * We don't cancel in-flight requests. Unsubscribing from the observer is sufficient\n * to prevent late-arriving data from being processed. The request completes and is cached\n * by TanStack Query, allowing quick remounts to restore data without refetching.\n */\n const unloadSubset = (options: LoadSubsetOptions) => {\n // 1. Same predicates → 2. Same queryKey\n const key = generateQueryKeyFromOptions(options)\n const hashedQueryKey = hashKey(key)\n\n // 3. Decrement refcount\n const currentCount = queryRefCounts.get(hashedQueryKey) || 0\n const newCount = currentCount - 1\n\n // Update refcount\n if (newCount <= 0) {\n queryRefCounts.set(hashedQueryKey, 0)\n cleanupQueryIfIdle(hashedQueryKey)\n } else {\n // Still have other references, just decrement\n queryRefCounts.set(hashedQueryKey, newCount)\n }\n }\n\n // Create deduplicated loadSubset wrapper for non-eager modes\n // This prevents redundant snapshot requests when multiple concurrent\n // live queries request overlapping or subset predicates\n const loadSubsetDedupe =\n syncMode === `eager` ? undefined : createQueryFromOpts\n\n return {\n loadSubset: loadSubsetDedupe,\n unloadSubset: syncMode === `eager` ? undefined : unloadSubset,\n cleanup,\n }\n }\n\n /**\n * Refetch the query data\n *\n * Uses queryObserver.refetch() because:\n * - Bypasses `enabled: false` to support manual/imperative refetch patterns (e.g., button-triggered fetch)\n * - Ensures clearError() works even when enabled: false\n * - Always refetches THIS specific collection (exact targeting via observer)\n * - Respects retry, retryDelay, and other observer options\n *\n * This matches TanStack Query's hook behavior where refetch() bypasses enabled: false.\n * See: https://tanstack.com/query/latest/docs/framework/react/guides/disabling-queries\n *\n * Used by both:\n * - utils.refetch() - for explicit user-triggered refetches\n * - Internal handlers (onInsert/onUpdate/onDelete) - after mutations to get fresh data\n *\n * @returns Promise that resolves when the refetch is complete, with QueryObserverResult\n */\n const refetch: RefetchFn = async (opts) => {\n const allQueryKeys = [...hashToQueryKey.values()]\n const refetchPromises = allQueryKeys.map((qKey) => {\n const queryObserver = state.observers.get(hashKey(qKey))!\n return queryObserver.refetch({\n throwOnError: opts?.throwOnError,\n })\n })\n\n await Promise.all(refetchPromises)\n }\n\n /**\n * Updates the query cache with new items, handling both direct arrays\n * and wrapped response formats (when `select` is used).\n */\n const updateCacheData = (items: Array<any>): void => {\n // Get the base query key (handle both static and function-based keys)\n const key =\n typeof queryKey === `function`\n ? queryKey({})\n : (queryKey as unknown as QueryKey)\n\n if (select) {\n // When `select` is used, the cache contains a wrapped response (e.g., { data: [...], meta: {...} })\n // We need to update the cache while preserving the wrapper structure\n queryClient.setQueryData(key, (oldData: any) => {\n if (!oldData || typeof oldData !== `object`) {\n // No existing cache or not an object - don't corrupt the cache\n return oldData\n }\n\n if (Array.isArray(oldData)) {\n // Cache is already a raw array (shouldn't happen with select, but handle it)\n return items\n }\n\n // Use the select function to identify which property contains the items array.\n // This is more robust than guessing based on property order.\n const selectedArray = select(oldData)\n\n if (Array.isArray(selectedArray)) {\n // Find the property that matches the selected array by reference equality\n for (const propKey of Object.keys(oldData)) {\n if (oldData[propKey] === selectedArray) {\n // Found the exact property - create a shallow copy with updated items\n return { ...oldData, [propKey]: items }\n }\n }\n }\n\n // Fallback: check common property names used for data arrays\n if (Array.isArray(oldData.data)) {\n return { ...oldData, data: items }\n }\n if (Array.isArray(oldData.items)) {\n return { ...oldData, items: items }\n }\n if (Array.isArray(oldData.results)) {\n return { ...oldData, results: items }\n }\n\n // Last resort: find first array property\n for (const propKey of Object.keys(oldData)) {\n if (Array.isArray(oldData[propKey])) {\n return { ...oldData, [propKey]: items }\n }\n }\n\n // Couldn't safely identify the array property - don't corrupt the cache\n // Return oldData unchanged to avoid breaking select\n return oldData\n })\n } else {\n // No select - cache contains raw array, just set it directly\n queryClient.setQueryData(key, items)\n }\n }\n\n // Create write context for manual write operations\n let writeContext: {\n collection: any\n queryClient: QueryClient\n queryKey: Array<unknown>\n getKey: (item: any) => string | number\n begin: () => void\n write: (message: Omit<ChangeMessage<any>, `key`>) => void\n commit: () => void\n updateCacheData?: (items: Array<any>) => void\n } | null = null\n\n // Enhanced internalSync that captures write functions for manual use\n const enhancedInternalSync: SyncConfig<any>[`sync`] = (params) => {\n const { begin, write, commit, collection } = params\n\n // Get the base query key for the context (handle both static and function-based keys)\n const contextQueryKey =\n typeof queryKey === `function`\n ? (queryKey({}) as unknown as Array<unknown>)\n : (queryKey as unknown as Array<unknown>)\n\n // Store references for manual write operations\n writeContext = {\n collection,\n queryClient,\n queryKey: contextQueryKey,\n getKey: getKey as (item: any) => string | number,\n begin,\n write,\n commit,\n updateCacheData,\n }\n\n // Call the original internalSync logic\n return internalSync(params)\n }\n\n // Create write utils using the manual-sync module\n const writeUtils = createWriteUtils<any, string | number, any>(\n () => writeContext,\n )\n\n // Create wrapper handlers for direct persistence operations that handle refetching\n const wrappedOnInsert = onInsert\n ? async (params: InsertMutationFnParams<any>) => {\n const handlerResult = (await onInsert(params)) ?? {}\n const shouldRefetch =\n (handlerResult as { refetch?: boolean }).refetch !== false\n\n if (shouldRefetch) {\n await refetch()\n }\n\n return handlerResult\n }\n : undefined\n\n const wrappedOnUpdate = onUpdate\n ? async (params: UpdateMutationFnParams<any>) => {\n const handlerResult = (await onUpdate(params)) ?? {}\n const shouldRefetch =\n (handlerResult as { refetch?: boolean }).refetch !== false\n\n if (shouldRefetch) {\n await refetch()\n }\n\n return handlerResult\n }\n : undefined\n\n const wrappedOnDelete = onDelete\n ? async (params: DeleteMutationFnParams<any>) => {\n const handlerResult = (await onDelete(params)) ?? {}\n const shouldRefetch =\n (handlerResult as { refetch?: boolean }).refetch !== false\n\n if (shouldRefetch) {\n await refetch()\n }\n\n return handlerResult\n }\n : undefined\n\n // Create utils instance with state and dependencies passed explicitly\n const utils: any = new QueryCollectionUtilsImpl(state, refetch, writeUtils)\n\n return {\n ...baseCollectionConfig,\n getKey,\n syncMode,\n sync: { sync: enhancedInternalSync },\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils,\n }\n}\n"],"names":["queryKey"],"mappings":"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"}
1
+ {"version":3,"file":"query.js","sources":["../../src/query.ts"],"sourcesContent":["import { QueryObserver, hashKey } from '@tanstack/query-core'\nimport { deepEquals } from '@tanstack/db'\nimport {\n GetKeyRequiredError,\n QueryClientRequiredError,\n QueryFnRequiredError,\n QueryKeyRequiredError,\n} from './errors'\nimport { createWriteUtils } from './manual-sync'\nimport { serializeLoadSubsetOptions } from './serialization'\nimport type {\n BaseCollectionConfig,\n ChangeMessage,\n CollectionConfig,\n DeleteMutationFnParams,\n InsertMutationFnParams,\n LoadSubsetOptions,\n SyncConfig,\n UpdateMutationFnParams,\n UtilsRecord,\n} from '@tanstack/db'\nimport type {\n FetchStatus,\n QueryClient,\n QueryFunctionContext,\n QueryKey,\n QueryObserverOptions,\n QueryObserverResult,\n} from '@tanstack/query-core'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\n\n// Re-export for external use\nexport type { SyncOperation } from './manual-sync'\n\n// Schema output type inference helper (matches electric.ts pattern)\ntype InferSchemaOutput<T> = T extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<T> extends object\n ? StandardSchemaV1.InferOutput<T>\n : Record<string, unknown>\n : Record<string, unknown>\n\n// Schema input type inference helper (matches electric.ts pattern)\ntype InferSchemaInput<T> = T extends StandardSchemaV1\n ? StandardSchemaV1.InferInput<T> extends object\n ? StandardSchemaV1.InferInput<T>\n : Record<string, unknown>\n : Record<string, unknown>\n\ntype TQueryKeyBuilder<TQueryKey> = (opts: LoadSubsetOptions) => TQueryKey\n\n/**\n * Configuration options for creating a Query Collection\n * @template T - The explicit type of items stored in the collection\n * @template TQueryFn - The queryFn type\n * @template TError - The type of errors that can occur during queries\n * @template TQueryKey - The type of the query key\n * @template TKey - The type of the item keys\n * @template TSchema - The schema type for validation\n */\nexport interface QueryCollectionConfig<\n T extends object = object,\n TQueryFn extends (context: QueryFunctionContext<any>) => Promise<any> = (\n context: QueryFunctionContext<any>,\n ) => Promise<any>,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = never,\n TQueryData = Awaited<ReturnType<TQueryFn>>,\n> extends BaseCollectionConfig<T, TKey, TSchema> {\n /** The query key used by TanStack Query to identify this query */\n queryKey: TQueryKey | TQueryKeyBuilder<TQueryKey>\n /** Function that fetches data from the server. Must return the complete collection state */\n queryFn: TQueryFn extends (\n context: QueryFunctionContext<TQueryKey>,\n ) => Promise<Array<any>>\n ? (context: QueryFunctionContext<TQueryKey>) => Promise<Array<T>>\n : TQueryFn\n /* Function that extracts array items from wrapped API responses (e.g metadata, pagination) */\n select?: (data: TQueryData) => Array<T>\n /** The TanStack Query client instance */\n queryClient: QueryClient\n\n // Query-specific options\n /** Whether the query should automatically run (default: true) */\n enabled?: boolean\n refetchInterval?: QueryObserverOptions<\n Array<T>,\n TError,\n Array<T>,\n Array<T>,\n TQueryKey\n >[`refetchInterval`]\n retry?: QueryObserverOptions<\n Array<T>,\n TError,\n Array<T>,\n Array<T>,\n TQueryKey\n >[`retry`]\n retryDelay?: QueryObserverOptions<\n Array<T>,\n TError,\n Array<T>,\n Array<T>,\n TQueryKey\n >[`retryDelay`]\n staleTime?: QueryObserverOptions<\n Array<T>,\n TError,\n Array<T>,\n Array<T>,\n TQueryKey\n >[`staleTime`]\n\n /**\n * Metadata to pass to the query.\n * Available in queryFn via context.meta\n *\n * @example\n * // Using meta for error context\n * queryFn: async (context) => {\n * try {\n * return await api.getTodos(userId)\n * } catch (error) {\n * // Use meta for better error messages\n * throw new Error(\n * context.meta?.errorMessage || 'Failed to load todos'\n * )\n * }\n * },\n * meta: {\n * errorMessage: `Failed to load todos for user ${userId}`\n * }\n */\n meta?: Record<string, unknown>\n}\n\n/**\n * Type for the refetch utility function\n * Returns the QueryObserverResult from TanStack Query\n */\nexport type RefetchFn = (opts?: {\n throwOnError?: boolean\n}) => Promise<QueryObserverResult<any, any> | void>\n\n/**\n * Utility methods available on Query Collections for direct writes and manual operations.\n * Direct writes bypass the normal query/mutation flow and write directly to the synced data store.\n * @template TItem - The type of items stored in the collection\n * @template TKey - The type of the item keys\n * @template TInsertInput - The type accepted for insert operations\n * @template TError - The type of errors that can occur during queries\n */\nexport interface QueryCollectionUtils<\n TItem extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TInsertInput extends object = TItem,\n TError = unknown,\n> extends UtilsRecord {\n /** Manually trigger a refetch of the query */\n refetch: RefetchFn\n /** Insert one or more items directly into the synced data store without triggering a query refetch or optimistic update */\n writeInsert: (data: TInsertInput | Array<TInsertInput>) => void\n /** Update one or more items directly in the synced data store without triggering a query refetch or optimistic update */\n writeUpdate: (updates: Partial<TItem> | Array<Partial<TItem>>) => void\n /** Delete one or more items directly from the synced data store without triggering a query refetch or optimistic update */\n writeDelete: (keys: TKey | Array<TKey>) => void\n /** Insert or update one or more items directly in the synced data store without triggering a query refetch or optimistic update */\n writeUpsert: (data: Partial<TItem> | Array<Partial<TItem>>) => void\n /** Execute multiple write operations as a single atomic batch to the synced data store */\n writeBatch: (callback: () => void) => void\n\n // Query Observer State (getters)\n /** Get the last error encountered by the query (if any); reset on success */\n lastError: TError | undefined\n /** Check if the collection is in an error state */\n isError: boolean\n /**\n * Get the number of consecutive sync failures.\n * Incremented only when query fails completely (not per retry attempt); reset on success.\n */\n errorCount: number\n /** Check if query is currently fetching (initial or background) */\n isFetching: boolean\n /** Check if query is refetching in background (not initial fetch) */\n isRefetching: boolean\n /** Check if query is loading for the first time (no data yet) */\n isLoading: boolean\n /** Get timestamp of last successful data update (in milliseconds) */\n dataUpdatedAt: number\n /** Get current fetch status */\n fetchStatus: `fetching` | `paused` | `idle`\n\n /**\n * Clear the error state and trigger a refetch of the query\n * @returns Promise that resolves when the refetch completes successfully\n * @throws Error if the refetch fails\n */\n clearError: () => Promise<void>\n}\n\n/**\n * Internal state object for tracking query observer and errors\n */\ninterface QueryCollectionState {\n lastError: any\n errorCount: number\n lastErrorUpdatedAt: number\n observers: Map<\n string,\n QueryObserver<Array<any>, any, Array<any>, Array<any>, any>\n >\n}\n\n/**\n * Implementation class for QueryCollectionUtils with explicit dependency injection\n * for better testability and architectural clarity\n */\nclass QueryCollectionUtilsImpl {\n private state: QueryCollectionState\n private refetchFn: RefetchFn\n\n // Write methods\n public refetch: RefetchFn\n public writeInsert: any\n public writeUpdate: any\n public writeDelete: any\n public writeUpsert: any\n public writeBatch: any\n\n constructor(\n state: QueryCollectionState,\n refetch: RefetchFn,\n writeUtils: ReturnType<typeof createWriteUtils>,\n ) {\n this.state = state\n this.refetchFn = refetch\n\n // Initialize methods to use passed dependencies\n this.refetch = refetch\n this.writeInsert = writeUtils.writeInsert\n this.writeUpdate = writeUtils.writeUpdate\n this.writeDelete = writeUtils.writeDelete\n this.writeUpsert = writeUtils.writeUpsert\n this.writeBatch = writeUtils.writeBatch\n }\n\n public async clearError() {\n this.state.lastError = undefined\n this.state.errorCount = 0\n this.state.lastErrorUpdatedAt = 0\n await this.refetchFn({ throwOnError: true })\n }\n\n // Getters for error state\n public get lastError() {\n return this.state.lastError\n }\n\n public get isError() {\n return !!this.state.lastError\n }\n\n public get errorCount() {\n return this.state.errorCount\n }\n\n // Getters for QueryObserver state\n public get isFetching() {\n // check if any observer is fetching\n return Array.from(this.state.observers.values()).some(\n (observer) => observer.getCurrentResult().isFetching,\n )\n }\n\n public get isRefetching() {\n // check if any observer is refetching\n return Array.from(this.state.observers.values()).some(\n (observer) => observer.getCurrentResult().isRefetching,\n )\n }\n\n public get isLoading() {\n // check if any observer is loading\n return Array.from(this.state.observers.values()).some(\n (observer) => observer.getCurrentResult().isLoading,\n )\n }\n\n public get dataUpdatedAt() {\n // compute the max dataUpdatedAt of all observers\n return Math.max(\n 0,\n ...Array.from(this.state.observers.values()).map(\n (observer) => observer.getCurrentResult().dataUpdatedAt,\n ),\n )\n }\n\n public get fetchStatus(): Array<FetchStatus> {\n return Array.from(this.state.observers.values()).map(\n (observer) => observer.getCurrentResult().fetchStatus,\n )\n }\n}\n\n/**\n * Creates query collection options for use with a standard Collection.\n * This integrates TanStack Query with TanStack DB for automatic synchronization.\n *\n * Supports automatic type inference following the priority order:\n * 1. Schema inference (highest priority)\n * 2. QueryFn return type inference (second priority)\n *\n * @template T - Type of the schema if a schema is provided otherwise it is the type of the values returned by the queryFn\n * @template TError - The type of errors that can occur during queries\n * @template TQueryKey - The type of the query key\n * @template TKey - The type of the item keys\n * @param config - Configuration options for the Query collection\n * @returns Collection options with utilities for direct writes and manual operations\n *\n * @example\n * // Type inferred from queryFn return type (NEW!)\n * const todosCollection = createCollection(\n * queryCollectionOptions({\n * queryKey: ['todos'],\n * queryFn: async () => {\n * const response = await fetch('/api/todos')\n * return response.json() as Todo[] // Type automatically inferred!\n * },\n * queryClient,\n * getKey: (item) => item.id, // item is typed as Todo\n * })\n * )\n *\n * @example\n * // Explicit type\n * const todosCollection = createCollection<Todo>(\n * queryCollectionOptions({\n * queryKey: ['todos'],\n * queryFn: async () => fetch('/api/todos').then(r => r.json()),\n * queryClient,\n * getKey: (item) => item.id,\n * })\n * )\n *\n * @example\n * // Schema inference\n * const todosCollection = createCollection(\n * queryCollectionOptions({\n * queryKey: ['todos'],\n * queryFn: async () => fetch('/api/todos').then(r => r.json()),\n * queryClient,\n * schema: todoSchema, // Type inferred from schema\n * getKey: (item) => item.id,\n * })\n * )\n *\n * @example\n * // With persistence handlers\n * const todosCollection = createCollection(\n * queryCollectionOptions({\n * queryKey: ['todos'],\n * queryFn: fetchTodos,\n * queryClient,\n * getKey: (item) => item.id,\n * onInsert: async ({ transaction }) => {\n * await api.createTodos(transaction.mutations.map(m => m.modified))\n * },\n * onUpdate: async ({ transaction }) => {\n * await api.updateTodos(transaction.mutations)\n * },\n * onDelete: async ({ transaction }) => {\n * await api.deleteTodos(transaction.mutations.map(m => m.key))\n * }\n * })\n * )\n *\n * @example\n * // The select option extracts the items array from a response with metadata\n * const todosCollection = createCollection(\n * queryCollectionOptions({\n * queryKey: ['todos'],\n * queryFn: async () => fetch('/api/todos').then(r => r.json()),\n * select: (data) => data.items, // Extract the array of items\n * queryClient,\n * schema: todoSchema,\n * getKey: (item) => item.id,\n * })\n * )\n */\n// Overload for when schema is provided and select present\nexport function queryCollectionOptions<\n T extends StandardSchemaV1,\n TQueryFn extends (context: QueryFunctionContext<any>) => Promise<any>,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n TQueryData = Awaited<ReturnType<TQueryFn>>,\n>(\n config: QueryCollectionConfig<\n InferSchemaOutput<T>,\n TQueryFn,\n TError,\n TQueryKey,\n TKey,\n T\n > & {\n schema: T\n select: (data: TQueryData) => Array<InferSchemaInput<T>>\n },\n): CollectionConfig<\n InferSchemaOutput<T>,\n TKey,\n T,\n QueryCollectionUtils<InferSchemaOutput<T>, TKey, InferSchemaInput<T>, TError>\n> & {\n schema: T\n utils: QueryCollectionUtils<\n InferSchemaOutput<T>,\n TKey,\n InferSchemaInput<T>,\n TError\n >\n}\n\n// Overload for when no schema is provided and select present\nexport function queryCollectionOptions<\n T extends object,\n TQueryFn extends (context: QueryFunctionContext<any>) => Promise<any> = (\n context: QueryFunctionContext<any>,\n ) => Promise<any>,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n TQueryData = Awaited<ReturnType<TQueryFn>>,\n>(\n config: QueryCollectionConfig<\n T,\n TQueryFn,\n TError,\n TQueryKey,\n TKey,\n never,\n TQueryData\n > & {\n schema?: never // prohibit schema\n select: (data: TQueryData) => Array<T>\n },\n): CollectionConfig<\n T,\n TKey,\n never,\n QueryCollectionUtils<T, TKey, T, TError>\n> & {\n schema?: never // no schema in the result\n utils: QueryCollectionUtils<T, TKey, T, TError>\n}\n\n// Overload for when schema is provided\nexport function queryCollectionOptions<\n T extends StandardSchemaV1,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n>(\n config: QueryCollectionConfig<\n InferSchemaOutput<T>,\n (\n context: QueryFunctionContext<any>,\n ) => Promise<Array<InferSchemaOutput<T>>>,\n TError,\n TQueryKey,\n TKey,\n T\n > & {\n schema: T\n },\n): CollectionConfig<\n InferSchemaOutput<T>,\n TKey,\n T,\n QueryCollectionUtils<InferSchemaOutput<T>, TKey, InferSchemaInput<T>, TError>\n> & {\n schema: T\n utils: QueryCollectionUtils<\n InferSchemaOutput<T>,\n TKey,\n InferSchemaInput<T>,\n TError\n >\n}\n\n// Overload for when no schema is provided\nexport function queryCollectionOptions<\n T extends object,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n>(\n config: QueryCollectionConfig<\n T,\n (context: QueryFunctionContext<any>) => Promise<Array<T>>,\n TError,\n TQueryKey,\n TKey\n > & {\n schema?: never // prohibit schema\n },\n): CollectionConfig<\n T,\n TKey,\n never,\n QueryCollectionUtils<T, TKey, T, TError>\n> & {\n schema?: never // no schema in the result\n utils: QueryCollectionUtils<T, TKey, T, TError>\n}\n\nexport function queryCollectionOptions(\n config: QueryCollectionConfig<Record<string, unknown>>,\n): CollectionConfig<\n Record<string, unknown>,\n string | number,\n never,\n QueryCollectionUtils\n> & {\n utils: QueryCollectionUtils\n} {\n const {\n queryKey,\n queryFn,\n select,\n queryClient,\n enabled,\n refetchInterval,\n retry,\n retryDelay,\n staleTime,\n getKey,\n onInsert,\n onUpdate,\n onDelete,\n meta,\n ...baseCollectionConfig\n } = config\n\n // Default to eager sync mode if not provided\n const syncMode = baseCollectionConfig.syncMode ?? `eager`\n\n // Validate required parameters\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!queryKey) {\n throw new QueryKeyRequiredError()\n }\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!queryFn) {\n throw new QueryFnRequiredError()\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!queryClient) {\n throw new QueryClientRequiredError()\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!getKey) {\n throw new GetKeyRequiredError()\n }\n\n /** State object to hold error tracking and observer reference */\n const state: QueryCollectionState = {\n lastError: undefined as any,\n errorCount: 0,\n lastErrorUpdatedAt: 0,\n observers: new Map<\n string,\n QueryObserver<Array<any>, any, Array<any>, Array<any>, any>\n >(),\n }\n\n // hashedQueryKey → queryKey\n const hashToQueryKey = new Map<string, QueryKey>()\n\n // queryKey → Set<RowKey>\n const queryToRows = new Map<string, Set<string | number>>()\n\n // RowKey → Set<queryKey>\n const rowToQueries = new Map<string | number, Set<string>>()\n\n // queryKey → QueryObserver's unsubscribe function\n const unsubscribes = new Map<string, () => void>()\n\n // queryKey → reference count (how many loadSubset calls are active)\n // Reference counting for QueryObserver lifecycle management\n // =========================================================\n // Tracks how many live query subscriptions are using each QueryObserver.\n // Multiple live queries with identical predicates share the same QueryObserver for efficiency.\n //\n // Lifecycle:\n // - Increment: when createQueryFromOpts creates or reuses an observer\n // - Decrement: when subscription.unsubscribe() passes predicates to collection._sync.unloadSubset()\n // - Reset: when cleanupQuery() is triggered by TanStack Query's cache GC\n //\n // When refcount reaches 0, unloadSubset():\n // 1. Computes the same queryKey from the predicates\n // 2. Uses existing machinery (queryToRows map) to find rows that query loaded\n // 3. Decrements refcount and GCs rows where count reaches 0\n const queryRefCounts = new Map<string, number>()\n\n // Helper function to add a row to the internal state\n const addRow = (rowKey: string | number, hashedQueryKey: string) => {\n const rowToQueriesSet = rowToQueries.get(rowKey) || new Set()\n rowToQueriesSet.add(hashedQueryKey)\n rowToQueries.set(rowKey, rowToQueriesSet)\n\n const queryToRowsSet = queryToRows.get(hashedQueryKey) || new Set()\n queryToRowsSet.add(rowKey)\n queryToRows.set(hashedQueryKey, queryToRowsSet)\n }\n\n // Helper function to remove a row from the internal state\n const removeRow = (rowKey: string | number, hashedQuerKey: string) => {\n const rowToQueriesSet = rowToQueries.get(rowKey) || new Set()\n rowToQueriesSet.delete(hashedQuerKey)\n rowToQueries.set(rowKey, rowToQueriesSet)\n\n const queryToRowsSet = queryToRows.get(hashedQuerKey) || new Set()\n queryToRowsSet.delete(rowKey)\n queryToRows.set(hashedQuerKey, queryToRowsSet)\n\n return rowToQueriesSet.size === 0\n }\n\n const internalSync: SyncConfig<any>[`sync`] = (params) => {\n const { begin, write, commit, markReady, collection } = params\n\n // Track whether sync has been started\n let syncStarted = false\n\n /**\n * Generate a consistent query key from LoadSubsetOptions.\n * CRITICAL: Must use identical logic in both createQueryFromOpts and unloadSubset\n * so that refcount increment/decrement operations target the same hashedQueryKey.\n * Inconsistent keys would cause refcount leaks and prevent proper cleanup.\n */\n const generateQueryKeyFromOptions = (opts: LoadSubsetOptions): QueryKey => {\n if (typeof queryKey === `function`) {\n // Function-based queryKey: use it to build the key from opts\n return queryKey(opts)\n } else if (syncMode === `on-demand`) {\n // Static queryKey in on-demand mode: automatically append serialized predicates\n // to create separate cache entries for different predicate combinations\n const serialized = serializeLoadSubsetOptions(opts)\n return serialized !== undefined ? [...queryKey, serialized] : queryKey\n } else {\n // Static queryKey in eager mode: use as-is\n return queryKey\n }\n }\n\n const createQueryFromOpts = (\n opts: LoadSubsetOptions = {},\n queryFunction: typeof queryFn = queryFn,\n ): true | Promise<void> => {\n // Generate key using common function\n const key = generateQueryKeyFromOptions(opts)\n const hashedQueryKey = hashKey(key)\n const extendedMeta = { ...meta, loadSubsetOptions: opts }\n\n if (state.observers.has(hashedQueryKey)) {\n // We already have a query for this queryKey\n // Increment reference count since another consumer is using this observer\n queryRefCounts.set(\n hashedQueryKey,\n (queryRefCounts.get(hashedQueryKey) || 0) + 1,\n )\n\n // Get the current result and return based on its state\n const observer = state.observers.get(hashedQueryKey)!\n const currentResult = observer.getCurrentResult()\n\n if (currentResult.isSuccess) {\n // Data is already available, return true synchronously\n return true\n } else if (currentResult.isError) {\n // Error already occurred, reject immediately\n return Promise.reject(currentResult.error)\n } else {\n // Query is still loading, wait for the first result\n return new Promise<void>((resolve, reject) => {\n const unsubscribe = observer.subscribe((result) => {\n // Use a microtask in case `subscribe` is called synchronously, before `unsubscribe` is initialized\n queueMicrotask(() => {\n if (result.isSuccess) {\n unsubscribe()\n resolve()\n } else if (result.isError) {\n unsubscribe()\n reject(result.error)\n }\n })\n })\n })\n }\n }\n\n const observerOptions: QueryObserverOptions<\n Array<any>,\n any,\n Array<any>,\n Array<any>,\n any\n > = {\n queryKey: key,\n queryFn: queryFunction,\n meta: extendedMeta,\n structuralSharing: true,\n notifyOnChangeProps: `all`,\n\n // Only include options that are explicitly defined to allow QueryClient defaultOptions to be used\n ...(enabled !== undefined && { enabled }),\n ...(refetchInterval !== undefined && { refetchInterval }),\n ...(retry !== undefined && { retry }),\n ...(retryDelay !== undefined && { retryDelay }),\n ...(staleTime !== undefined && { staleTime }),\n }\n\n const localObserver = new QueryObserver<\n Array<any>,\n any,\n Array<any>,\n Array<any>,\n any\n >(queryClient, observerOptions)\n\n hashToQueryKey.set(hashedQueryKey, key)\n state.observers.set(hashedQueryKey, localObserver)\n\n // Increment reference count for this query\n queryRefCounts.set(\n hashedQueryKey,\n (queryRefCounts.get(hashedQueryKey) || 0) + 1,\n )\n\n // Create a promise that resolves when the query result is first available\n const readyPromise = new Promise<void>((resolve, reject) => {\n const unsubscribe = localObserver.subscribe((result) => {\n // Use a microtask in case `subscribe` is called synchronously, before `unsubscribe` is initialized\n queueMicrotask(() => {\n if (result.isSuccess) {\n unsubscribe()\n resolve()\n } else if (result.isError) {\n unsubscribe()\n reject(result.error)\n }\n })\n })\n })\n\n // If sync has started or there are subscribers to the collection, subscribe to the query straight away\n // This creates the main subscription that handles data updates\n if (syncStarted || collection.subscriberCount > 0) {\n subscribeToQuery(localObserver, hashedQueryKey)\n }\n\n return readyPromise\n }\n\n type UpdateHandler = Parameters<QueryObserver[`subscribe`]>[0]\n\n const makeQueryResultHandler = (queryKey: QueryKey) => {\n const hashedQueryKey = hashKey(queryKey)\n const handleQueryResult: UpdateHandler = (result) => {\n if (result.isSuccess) {\n // Clear error state\n state.lastError = undefined\n state.errorCount = 0\n\n const rawData = result.data\n const newItemsArray = select ? select(rawData) : rawData\n\n if (\n !Array.isArray(newItemsArray) ||\n newItemsArray.some((item) => typeof item !== `object`)\n ) {\n const errorMessage = select\n ? `@tanstack/query-db-collection: select() must return an array of objects. Got: ${typeof newItemsArray} for queryKey ${JSON.stringify(queryKey)}`\n : `@tanstack/query-db-collection: queryFn must return an array of objects. Got: ${typeof newItemsArray} for queryKey ${JSON.stringify(queryKey)}`\n\n console.error(errorMessage)\n return\n }\n\n const currentSyncedItems: Map<string | number, any> = new Map(\n collection._state.syncedData.entries(),\n )\n const newItemsMap = new Map<string | number, any>()\n newItemsArray.forEach((item) => {\n const key = getKey(item)\n newItemsMap.set(key, item)\n })\n\n begin()\n\n currentSyncedItems.forEach((oldItem, key) => {\n const newItem = newItemsMap.get(key)\n if (!newItem) {\n const needToRemove = removeRow(key, hashedQueryKey) // returns true if the row is no longer referenced by any queries\n if (needToRemove) {\n write({ type: `delete`, value: oldItem })\n }\n } else if (!deepEquals(oldItem, newItem)) {\n // Only update if there are actual differences in the properties\n write({ type: `update`, value: newItem })\n }\n })\n\n newItemsMap.forEach((newItem, key) => {\n addRow(key, hashedQueryKey)\n if (!currentSyncedItems.has(key)) {\n write({ type: `insert`, value: newItem })\n }\n })\n\n commit()\n\n // Mark collection as ready after first successful query result\n markReady()\n } else if (result.isError) {\n if (result.errorUpdatedAt !== state.lastErrorUpdatedAt) {\n state.lastError = result.error\n state.errorCount++\n state.lastErrorUpdatedAt = result.errorUpdatedAt\n }\n\n console.error(\n `[QueryCollection] Error observing query ${String(queryKey)}:`,\n result.error,\n )\n\n // Mark collection as ready even on error to avoid blocking apps\n markReady()\n }\n }\n return handleQueryResult\n }\n\n const isSubscribed = (hashedQueryKey: string) => {\n return unsubscribes.has(hashedQueryKey)\n }\n\n const subscribeToQuery = (\n observer: QueryObserver<Array<any>, any, Array<any>, Array<any>, any>,\n hashedQueryKey: string,\n ) => {\n if (!isSubscribed(hashedQueryKey)) {\n const cachedQueryKey = hashToQueryKey.get(hashedQueryKey)!\n const handleQueryResult = makeQueryResultHandler(cachedQueryKey)\n const unsubscribeFn = observer.subscribe(handleQueryResult)\n unsubscribes.set(hashedQueryKey, unsubscribeFn)\n\n // Process the current result immediately if available\n // This ensures data is synced when resubscribing to a query with cached data\n const currentResult = observer.getCurrentResult()\n if (currentResult.isSuccess || currentResult.isError) {\n handleQueryResult(currentResult)\n }\n }\n }\n\n const subscribeToQueries = () => {\n state.observers.forEach(subscribeToQuery)\n }\n\n const unsubscribeFromQueries = () => {\n unsubscribes.forEach((unsubscribeFn) => {\n unsubscribeFn()\n })\n unsubscribes.clear()\n }\n\n // Mark that sync has started\n syncStarted = true\n\n // Set up event listener for subscriber changes\n const unsubscribeFromCollectionEvents = collection.on(\n `subscribers:change`,\n ({ subscriberCount }) => {\n if (subscriberCount > 0) {\n subscribeToQueries()\n } else if (subscriberCount === 0) {\n unsubscribeFromQueries()\n }\n },\n )\n\n // If syncMode is eager, create the initial query without any predicates\n if (syncMode === `eager`) {\n // Catch any errors to prevent unhandled rejections\n const initialResult = createQueryFromOpts({})\n if (initialResult instanceof Promise) {\n initialResult.catch(() => {\n // Errors are already handled by the query result handler\n })\n }\n } else {\n // In on-demand mode, mark ready immediately since there's no initial query\n markReady()\n }\n\n // Always subscribe when sync starts (this could be from preload(), startSync config, or first subscriber)\n // We'll dynamically unsubscribe/resubscribe based on subscriber count to maintain staleTime behavior\n subscribeToQueries()\n\n // Ensure we process any existing query data (QueryObserver doesn't invoke its callback automatically with initial state)\n state.observers.forEach((observer, hashedQueryKey) => {\n const cachedQueryKey = hashToQueryKey.get(hashedQueryKey)!\n const handleQueryResult = makeQueryResultHandler(cachedQueryKey)\n handleQueryResult(observer.getCurrentResult())\n })\n\n /**\n * Perform row-level cleanup and remove all tracking for a query.\n * Callers are responsible for ensuring the query is safe to cleanup.\n */\n const cleanupQueryInternal = (hashedQueryKey: string) => {\n unsubscribes.get(hashedQueryKey)?.()\n unsubscribes.delete(hashedQueryKey)\n\n const rowKeys = queryToRows.get(hashedQueryKey) ?? new Set()\n const rowsToDelete: Array<any> = []\n\n rowKeys.forEach((rowKey) => {\n const queries = rowToQueries.get(rowKey)\n\n if (!queries) {\n return\n }\n\n queries.delete(hashedQueryKey)\n\n if (queries.size === 0) {\n rowToQueries.delete(rowKey)\n\n if (collection.has(rowKey)) {\n rowsToDelete.push(collection.get(rowKey))\n }\n }\n })\n\n if (rowsToDelete.length > 0) {\n begin()\n rowsToDelete.forEach((row) => {\n write({ type: `delete`, value: row })\n })\n commit()\n }\n\n state.observers.delete(hashedQueryKey)\n queryToRows.delete(hashedQueryKey)\n hashToQueryKey.delete(hashedQueryKey)\n queryRefCounts.delete(hashedQueryKey)\n }\n\n /**\n * Attempt to cleanup a query when it appears unused.\n * Respects refcounts and invalidateQueries cycles via hasListeners().\n */\n const cleanupQueryIfIdle = (hashedQueryKey: string) => {\n const refcount = queryRefCounts.get(hashedQueryKey) || 0\n const observer = state.observers.get(hashedQueryKey)\n\n if (refcount <= 0) {\n // Drop our subscription so hasListeners reflects only active consumers\n unsubscribes.get(hashedQueryKey)?.()\n unsubscribes.delete(hashedQueryKey)\n }\n\n const hasListeners = observer?.hasListeners() ?? false\n\n if (hasListeners) {\n // During invalidateQueries, TanStack Query keeps internal listeners alive.\n // Leave refcount at 0 but keep observer so it can resubscribe.\n queryRefCounts.set(hashedQueryKey, 0)\n return\n }\n\n // No listeners means the query is truly idle.\n // Even if refcount > 0, we treat hasListeners as authoritative to prevent leaks.\n // This can happen if subscriptions are GC'd without calling unloadSubset.\n if (refcount > 0) {\n console.warn(\n `[cleanupQueryIfIdle] Invariant violation: refcount=${refcount} but no listeners. Cleaning up to prevent leak.`,\n { hashedQueryKey },\n )\n }\n\n cleanupQueryInternal(hashedQueryKey)\n }\n\n /**\n * Force cleanup used by explicit collection cleanup.\n * Ignores refcounts/hasListeners and removes everything.\n */\n const forceCleanupQuery = (hashedQueryKey: string) => {\n cleanupQueryInternal(hashedQueryKey)\n }\n\n // Subscribe to the query client's cache to handle queries that are GCed by tanstack query\n const unsubscribeQueryCache = queryClient\n .getQueryCache()\n .subscribe((event) => {\n const hashedKey = event.query.queryHash\n if (event.type === `removed`) {\n // Only cleanup if this is OUR query (we track it)\n if (hashToQueryKey.has(hashedKey)) {\n // TanStack Query GC'd this query after gcTime expired.\n // Use the guarded cleanup path to avoid deleting rows for active queries.\n cleanupQueryIfIdle(hashedKey)\n }\n }\n })\n\n const cleanup = async () => {\n unsubscribeFromCollectionEvents()\n unsubscribeFromQueries()\n\n const allQueryKeys = [...hashToQueryKey.values()]\n const allHashedKeys = [...state.observers.keys()]\n\n // Force cleanup all queries (explicit cleanup path)\n // This ignores hasListeners and always cleans up\n for (const hashedKey of allHashedKeys) {\n forceCleanupQuery(hashedKey)\n }\n\n // Unsubscribe from cache events (cleanup already happened above)\n unsubscribeQueryCache()\n\n // Remove queries from TanStack Query cache\n await Promise.all(\n allQueryKeys.map(async (qKey) => {\n await queryClient.cancelQueries({ queryKey: qKey, exact: true })\n queryClient.removeQueries({ queryKey: qKey, exact: true })\n }),\n )\n }\n\n /**\n * Unload a query subset - the subscription-based cleanup path (on-demand mode).\n *\n * Called when a live query subscription unsubscribes (via collection._sync.unloadSubset()).\n *\n * Flow:\n * 1. Receives the same predicates that were passed to loadSubset\n * 2. Computes the queryKey using generateQueryKeyFromOptions (same logic as loadSubset)\n * 3. Decrements refcount\n * 4. If refcount reaches 0:\n * - Checks hasListeners() to detect invalidateQueries cycles\n * - If hasListeners is true: resets refcount (TanStack Query keeping observer alive)\n * - If hasListeners is false: calls forceCleanupQuery() to perform row-level GC\n *\n * The hasListeners() check prevents premature cleanup during invalidateQueries:\n * - invalidateQueries causes temporary unsubscribe/resubscribe\n * - During unsubscribe, our refcount drops to 0\n * - But observer.hasListeners() is still true (TanStack Query's internal listeners)\n * - We skip cleanup and reset refcount, allowing resubscribe to succeed\n *\n * We don't cancel in-flight requests. Unsubscribing from the observer is sufficient\n * to prevent late-arriving data from being processed. The request completes and is cached\n * by TanStack Query, allowing quick remounts to restore data without refetching.\n */\n const unloadSubset = (options: LoadSubsetOptions) => {\n // 1. Same predicates → 2. Same queryKey\n const key = generateQueryKeyFromOptions(options)\n const hashedQueryKey = hashKey(key)\n\n // 3. Decrement refcount\n const currentCount = queryRefCounts.get(hashedQueryKey) || 0\n const newCount = currentCount - 1\n\n // Update refcount\n if (newCount <= 0) {\n queryRefCounts.set(hashedQueryKey, 0)\n cleanupQueryIfIdle(hashedQueryKey)\n } else {\n // Still have other references, just decrement\n queryRefCounts.set(hashedQueryKey, newCount)\n }\n }\n\n // Create deduplicated loadSubset wrapper for non-eager modes\n // This prevents redundant snapshot requests when multiple concurrent\n // live queries request overlapping or subset predicates\n const loadSubsetDedupe =\n syncMode === `eager` ? undefined : createQueryFromOpts\n\n return {\n loadSubset: loadSubsetDedupe,\n unloadSubset: syncMode === `eager` ? undefined : unloadSubset,\n cleanup,\n }\n }\n\n /**\n * Refetch the query data\n *\n * Uses queryObserver.refetch() because:\n * - Bypasses `enabled: false` to support manual/imperative refetch patterns (e.g., button-triggered fetch)\n * - Ensures clearError() works even when enabled: false\n * - Always refetches THIS specific collection (exact targeting via observer)\n * - Respects retry, retryDelay, and other observer options\n *\n * This matches TanStack Query's hook behavior where refetch() bypasses enabled: false.\n * See: https://tanstack.com/query/latest/docs/framework/react/guides/disabling-queries\n *\n * Used by both:\n * - utils.refetch() - for explicit user-triggered refetches\n * - Internal handlers (onInsert/onUpdate/onDelete) - after mutations to get fresh data\n *\n * @returns Promise that resolves when the refetch is complete, with QueryObserverResult\n */\n const refetch: RefetchFn = async (opts) => {\n const allQueryKeys = [...hashToQueryKey.values()]\n const refetchPromises = allQueryKeys.map((qKey) => {\n const queryObserver = state.observers.get(hashKey(qKey))!\n return queryObserver.refetch({\n throwOnError: opts?.throwOnError,\n })\n })\n\n await Promise.all(refetchPromises)\n }\n\n /**\n * Updates the query cache with new items, handling both direct arrays\n * and wrapped response formats (when `select` is used).\n */\n const updateCacheData = (items: Array<any>): void => {\n // Get the base query key (handle both static and function-based keys)\n const key =\n typeof queryKey === `function`\n ? queryKey({})\n : (queryKey as unknown as QueryKey)\n\n if (select) {\n // When `select` is used, the cache contains a wrapped response (e.g., { data: [...], meta: {...} })\n // We need to update the cache while preserving the wrapper structure\n queryClient.setQueryData(key, (oldData: any) => {\n if (!oldData || typeof oldData !== `object`) {\n // No existing cache or not an object - don't corrupt the cache\n return oldData\n }\n\n if (Array.isArray(oldData)) {\n // Cache is already a raw array (shouldn't happen with select, but handle it)\n return items\n }\n\n // Use the select function to identify which property contains the items array.\n // This is more robust than guessing based on property order.\n const selectedArray = select(oldData)\n\n if (Array.isArray(selectedArray)) {\n // Find the property that matches the selected array by reference equality\n for (const propKey of Object.keys(oldData)) {\n if (oldData[propKey] === selectedArray) {\n // Found the exact property - create a shallow copy with updated items\n return { ...oldData, [propKey]: items }\n }\n }\n }\n\n // Fallback: check common property names used for data arrays\n if (Array.isArray(oldData.data)) {\n return { ...oldData, data: items }\n }\n if (Array.isArray(oldData.items)) {\n return { ...oldData, items: items }\n }\n if (Array.isArray(oldData.results)) {\n return { ...oldData, results: items }\n }\n\n // Last resort: find first array property\n for (const propKey of Object.keys(oldData)) {\n if (Array.isArray(oldData[propKey])) {\n return { ...oldData, [propKey]: items }\n }\n }\n\n // Couldn't safely identify the array property - don't corrupt the cache\n // Return oldData unchanged to avoid breaking select\n return oldData\n })\n } else {\n // No select - cache contains raw array, just set it directly\n queryClient.setQueryData(key, items)\n }\n }\n\n // Create write context for manual write operations\n let writeContext: {\n collection: any\n queryClient: QueryClient\n queryKey: Array<unknown>\n getKey: (item: any) => string | number\n begin: () => void\n write: (message: Omit<ChangeMessage<any>, `key`>) => void\n commit: () => void\n updateCacheData?: (items: Array<any>) => void\n } | null = null\n\n // Enhanced internalSync that captures write functions for manual use\n const enhancedInternalSync: SyncConfig<any>[`sync`] = (params) => {\n const { begin, write, commit, collection } = params\n\n // Get the base query key for the context (handle both static and function-based keys)\n const contextQueryKey =\n typeof queryKey === `function`\n ? (queryKey({}) as unknown as Array<unknown>)\n : (queryKey as unknown as Array<unknown>)\n\n // Store references for manual write operations\n writeContext = {\n collection,\n queryClient,\n queryKey: contextQueryKey,\n getKey: getKey as (item: any) => string | number,\n begin,\n write,\n commit,\n updateCacheData,\n }\n\n // Call the original internalSync logic\n return internalSync(params)\n }\n\n // Create write utils using the manual-sync module\n const writeUtils = createWriteUtils<any, string | number, any>(\n () => writeContext,\n )\n\n // Create wrapper handlers for direct persistence operations that handle refetching\n const wrappedOnInsert = onInsert\n ? async (params: InsertMutationFnParams<any>) => {\n const handlerResult = (await onInsert(params)) ?? {}\n const shouldRefetch =\n (handlerResult as { refetch?: boolean }).refetch !== false\n\n if (shouldRefetch) {\n await refetch()\n }\n\n return handlerResult\n }\n : undefined\n\n const wrappedOnUpdate = onUpdate\n ? async (params: UpdateMutationFnParams<any>) => {\n const handlerResult = (await onUpdate(params)) ?? {}\n const shouldRefetch =\n (handlerResult as { refetch?: boolean }).refetch !== false\n\n if (shouldRefetch) {\n await refetch()\n }\n\n return handlerResult\n }\n : undefined\n\n const wrappedOnDelete = onDelete\n ? async (params: DeleteMutationFnParams<any>) => {\n const handlerResult = (await onDelete(params)) ?? {}\n const shouldRefetch =\n (handlerResult as { refetch?: boolean }).refetch !== false\n\n if (shouldRefetch) {\n await refetch()\n }\n\n return handlerResult\n }\n : undefined\n\n // Create utils instance with state and dependencies passed explicitly\n const utils: any = new QueryCollectionUtilsImpl(state, refetch, writeUtils)\n\n return {\n ...baseCollectionConfig,\n getKey,\n syncMode,\n sync: { sync: enhancedInternalSync },\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils,\n }\n}\n"],"names":["queryKey"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/query-db-collection",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "TanStack Query collection for TanStack DB",
5
5
  "author": "Kyle Mathews",
6
6
  "license": "MIT",
@@ -39,15 +39,15 @@
39
39
  "src"
40
40
  ],
41
41
  "dependencies": {
42
- "@standard-schema/spec": "^1.0.0",
43
- "@tanstack/db": "0.5.15"
42
+ "@standard-schema/spec": "^1.1.0",
43
+ "@tanstack/db": "0.5.17"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@tanstack/query-core": "^5.0.0",
47
47
  "typescript": ">=4.7"
48
48
  },
49
49
  "devDependencies": {
50
- "@tanstack/query-core": "^5.90.12",
50
+ "@tanstack/query-core": "^5.90.16",
51
51
  "@vitest/coverage-istanbul": "^3.2.4"
52
52
  },
53
53
  "scripts": {
package/src/query.ts CHANGED
@@ -692,13 +692,16 @@ export function queryCollectionOptions(
692
692
  // Query is still loading, wait for the first result
693
693
  return new Promise<void>((resolve, reject) => {
694
694
  const unsubscribe = observer.subscribe((result) => {
695
- if (result.isSuccess) {
696
- unsubscribe()
697
- resolve()
698
- } else if (result.isError) {
699
- unsubscribe()
700
- reject(result.error)
701
- }
695
+ // Use a microtask in case `subscribe` is called synchronously, before `unsubscribe` is initialized
696
+ queueMicrotask(() => {
697
+ if (result.isSuccess) {
698
+ unsubscribe()
699
+ resolve()
700
+ } else if (result.isError) {
701
+ unsubscribe()
702
+ reject(result.error)
703
+ }
704
+ })
702
705
  })
703
706
  })
704
707
  }
@@ -745,13 +748,16 @@ export function queryCollectionOptions(
745
748
  // Create a promise that resolves when the query result is first available
746
749
  const readyPromise = new Promise<void>((resolve, reject) => {
747
750
  const unsubscribe = localObserver.subscribe((result) => {
748
- if (result.isSuccess) {
749
- unsubscribe()
750
- resolve()
751
- } else if (result.isError) {
752
- unsubscribe()
753
- reject(result.error)
754
- }
751
+ // Use a microtask in case `subscribe` is called synchronously, before `unsubscribe` is initialized
752
+ queueMicrotask(() => {
753
+ if (result.isSuccess) {
754
+ unsubscribe()
755
+ resolve()
756
+ } else if (result.isError) {
757
+ unsubscribe()
758
+ reject(result.error)
759
+ }
760
+ })
755
761
  })
756
762
  })
757
763