@tanstack/query-db-collection 0.2.16 → 0.2.18
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.
- package/dist/cjs/query.cjs.map +1 -1
- package/dist/cjs/query.d.cts +28 -170
- package/dist/esm/query.d.ts +28 -170
- package/dist/esm/query.js.map +1 -1
- package/package.json +2 -2
- package/src/query.ts +98 -240
package/dist/cjs/query.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.cjs","sources":["../../src/query.ts"],"sourcesContent":["import { QueryObserver } from \"@tanstack/query-core\"\nimport {\n GetKeyRequiredError,\n QueryClientRequiredError,\n QueryFnRequiredError,\n QueryKeyRequiredError,\n} from \"./errors\"\nimport { createWriteUtils } from \"./manual-sync\"\nimport type {\n QueryClient,\n QueryFunctionContext,\n QueryKey,\n QueryObserverOptions,\n} from \"@tanstack/query-core\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n DeleteMutationFn,\n DeleteMutationFnParams,\n InsertMutationFn,\n InsertMutationFnParams,\n SyncConfig,\n UpdateMutationFn,\n UpdateMutationFnParams,\n UtilsRecord,\n} from \"@tanstack/db\"\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// QueryFn return type inference helper\ntype InferQueryFnOutput<TQueryFn> = TQueryFn extends (\n context: QueryFunctionContext<any>\n) => Promise<Array<infer TItem>>\n ? TItem extends object\n ? TItem\n : Record<string, unknown>\n : Record<string, unknown>\n\n// Type resolution system with priority order (matches electric.ts pattern)\ntype ResolveType<\n TExplicit extends object | unknown = unknown,\n TSchema extends StandardSchemaV1 = never,\n TQueryFn = unknown,\n> = unknown extends TExplicit\n ? [TSchema] extends [never]\n ? InferQueryFnOutput<TQueryFn>\n : InferSchemaOutput<TSchema>\n : TExplicit\n\n/**\n * Configuration options for creating a Query Collection\n * @template TExplicit - The explicit type of items stored in the collection (highest priority)\n * @template TSchema - The schema type for validation and type inference (second priority)\n * @template TQueryFn - The queryFn type for inferring return type (third priority)\n * @template TError - The type of errors that can occur during queries\n * @template TQueryKey - The type of the query key\n */\nexport interface QueryCollectionConfig<\n TExplicit extends object = object,\n TSchema extends StandardSchemaV1 = never,\n TQueryFn extends (\n context: QueryFunctionContext<any>\n ) => Promise<Array<any>> = (\n context: QueryFunctionContext<any>\n ) => Promise<Array<any>>,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n> {\n /** The query key used by TanStack Query to identify this query */\n queryKey: 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 ? TQueryFn\n : (\n context: QueryFunctionContext<TQueryKey>\n ) => Promise<Array<ResolveType<TExplicit, TSchema, TQueryFn>>>\n\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<ResolveType<TExplicit, TSchema, TQueryFn>>,\n TError,\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n TQueryKey\n >[`refetchInterval`]\n retry?: QueryObserverOptions<\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n TError,\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n TQueryKey\n >[`retry`]\n retryDelay?: QueryObserverOptions<\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n TError,\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n TQueryKey\n >[`retryDelay`]\n staleTime?: QueryObserverOptions<\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n TError,\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n TQueryKey\n >[`staleTime`]\n\n // Standard Collection configuration properties\n /** Unique identifier for the collection */\n id?: string\n /** Function to extract the unique key from an item */\n getKey: CollectionConfig<ResolveType<TExplicit, TSchema, TQueryFn>>[`getKey`]\n /** Schema for validating items */\n schema?: TSchema\n sync?: CollectionConfig<ResolveType<TExplicit, TSchema, TQueryFn>>[`sync`]\n startSync?: CollectionConfig<\n ResolveType<TExplicit, TSchema, TQueryFn>\n >[`startSync`]\n\n // Direct persistence handlers\n /**\n * Optional asynchronous handler function called before an insert operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to void or { refetch?: boolean } to control refetching\n * @example\n * // Basic query collection insert handler\n * onInsert: async ({ transaction }) => {\n * const newItem = transaction.mutations[0].modified\n * await api.createTodo(newItem)\n * // Automatically refetches query after insert\n * }\n *\n * @example\n * // Insert handler with refetch control\n * onInsert: async ({ transaction }) => {\n * const newItem = transaction.mutations[0].modified\n * await api.createTodo(newItem)\n * return { refetch: false } // Skip automatic refetch\n * }\n *\n * @example\n * // Insert handler with multiple items\n * onInsert: async ({ transaction }) => {\n * const items = transaction.mutations.map(m => m.modified)\n * await api.createTodos(items)\n * // Will refetch query to get updated data\n * }\n *\n * @example\n * // Insert handler with error handling\n * onInsert: async ({ transaction }) => {\n * try {\n * const newItem = transaction.mutations[0].modified\n * await api.createTodo(newItem)\n * } catch (error) {\n * console.error('Insert failed:', error)\n * throw error // Transaction will rollback optimistic changes\n * }\n * }\n */\n onInsert?: InsertMutationFn<ResolveType<TExplicit, TSchema, TQueryFn>>\n\n /**\n * Optional asynchronous handler function called before an update operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to void or { refetch?: boolean } to control refetching\n * @example\n * // Basic query collection update handler\n * onUpdate: async ({ transaction }) => {\n * const mutation = transaction.mutations[0]\n * await api.updateTodo(mutation.original.id, mutation.changes)\n * // Automatically refetches query after update\n * }\n *\n * @example\n * // Update handler with multiple items\n * onUpdate: async ({ transaction }) => {\n * const updates = transaction.mutations.map(m => ({\n * id: m.key,\n * changes: m.changes\n * }))\n * await api.updateTodos(updates)\n * // Will refetch query to get updated data\n * }\n *\n * @example\n * // Update handler with manual refetch\n * onUpdate: async ({ transaction, collection }) => {\n * const mutation = transaction.mutations[0]\n * await api.updateTodo(mutation.original.id, mutation.changes)\n *\n * // Manually trigger refetch\n * await collection.utils.refetch()\n *\n * return { refetch: false } // Skip automatic refetch\n * }\n *\n * @example\n * // Update handler with related collection refetch\n * onUpdate: async ({ transaction, collection }) => {\n * const mutation = transaction.mutations[0]\n * await api.updateTodo(mutation.original.id, mutation.changes)\n *\n * // Refetch related collections when this item changes\n * await Promise.all([\n * collection.utils.refetch(), // Refetch this collection\n * usersCollection.utils.refetch(), // Refetch users\n * tagsCollection.utils.refetch() // Refetch tags\n * ])\n *\n * return { refetch: false } // Skip automatic refetch since we handled it manually\n * }\n */\n onUpdate?: UpdateMutationFn<ResolveType<TExplicit, TSchema, TQueryFn>>\n\n /**\n * Optional asynchronous handler function called before a delete operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to void or { refetch?: boolean } to control refetching\n * @example\n * // Basic query collection delete handler\n * onDelete: async ({ transaction }) => {\n * const mutation = transaction.mutations[0]\n * await api.deleteTodo(mutation.original.id)\n * // Automatically refetches query after delete\n * }\n *\n * @example\n * // Delete handler with refetch control\n * onDelete: async ({ transaction }) => {\n * const mutation = transaction.mutations[0]\n * await api.deleteTodo(mutation.original.id)\n * return { refetch: false } // Skip automatic refetch\n * }\n *\n * @example\n * // Delete handler with multiple items\n * onDelete: async ({ transaction }) => {\n * const keysToDelete = transaction.mutations.map(m => m.key)\n * await api.deleteTodos(keysToDelete)\n * // Will refetch query to get updated data\n * }\n *\n * @example\n * // Delete handler with related collection refetch\n * onDelete: async ({ transaction, collection }) => {\n * const mutation = transaction.mutations[0]\n * await api.deleteTodo(mutation.original.id)\n *\n * // Refetch related collections when this item is deleted\n * await Promise.all([\n * collection.utils.refetch(), // Refetch this collection\n * usersCollection.utils.refetch(), // Refetch users\n * projectsCollection.utils.refetch() // Refetch projects\n * ])\n *\n * return { refetch: false } // Skip automatic refetch since we handled it manually\n * }\n */\n onDelete?: DeleteMutationFn<ResolveType<TExplicit, TSchema, TQueryFn>>\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 */\nexport type RefetchFn = (opts?: { throwOnError?: boolean }) => Promise<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 /** 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 /**\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 * 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. Explicit type (highest priority)\n * 2. Schema inference (second priority)\n * 3. QueryFn return type inference (third priority)\n * 4. Fallback to Record<string, unknown>\n *\n * @template TExplicit - The explicit type of items in the collection (highest priority)\n * @template TSchema - The schema type for validation and type inference (second priority)\n * @template TQueryFn - The queryFn type for inferring return type (third priority)\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 TInsertInput - The type accepted for insert operations\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 (highest priority)\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 (second priority)\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 */\nexport function queryCollectionOptions<\n TExplicit extends object = object,\n TSchema extends StandardSchemaV1 = never,\n TQueryFn extends (\n context: QueryFunctionContext<any>\n ) => Promise<Array<any>> = (\n context: QueryFunctionContext<any>\n ) => Promise<Array<any>>,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n TInsertInput extends object = ResolveType<TExplicit, TSchema, TQueryFn>,\n>(\n config: QueryCollectionConfig<TExplicit, TSchema, TQueryFn, TError, TQueryKey>\n): CollectionConfig<ResolveType<TExplicit, TSchema, TQueryFn>> & {\n utils: QueryCollectionUtils<\n ResolveType<TExplicit, TSchema, TQueryFn>,\n TKey,\n TInsertInput,\n TError\n >\n} {\n type TItem = ResolveType<TExplicit, TSchema, TQueryFn>\n\n const {\n queryKey,\n queryFn,\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 // 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 if (!getKey) {\n throw new GetKeyRequiredError()\n }\n\n /** The last error encountered by the query */\n let lastError: TError | undefined\n /** The number of consecutive sync failures */\n let errorCount = 0\n /** The timestamp for when the query most recently returned the status as \"error\" */\n let lastErrorUpdatedAt = 0\n\n const internalSync: SyncConfig<TItem>[`sync`] = (params) => {\n const { begin, write, commit, markReady, collection } = params\n\n const observerOptions: QueryObserverOptions<\n Array<TItem>,\n TError,\n Array<TItem>,\n Array<TItem>,\n TQueryKey\n > = {\n queryKey: queryKey,\n queryFn: queryFn,\n meta: meta,\n enabled: enabled,\n refetchInterval: refetchInterval,\n retry: retry,\n retryDelay: retryDelay,\n staleTime: staleTime,\n structuralSharing: true,\n notifyOnChangeProps: `all`,\n }\n\n const localObserver = new QueryObserver<\n Array<TItem>,\n TError,\n Array<TItem>,\n Array<TItem>,\n TQueryKey\n >(queryClient, observerOptions)\n\n type UpdateHandler = Parameters<typeof localObserver.subscribe>[0]\n const handleUpdate: UpdateHandler = (result) => {\n if (result.isSuccess) {\n // Clear error state\n lastError = undefined\n errorCount = 0\n\n const newItemsArray = result.data\n\n if (\n !Array.isArray(newItemsArray) ||\n newItemsArray.some((item) => typeof item !== `object`)\n ) {\n console.error(\n `[QueryCollection] queryFn did not return an array of objects. Skipping update.`,\n newItemsArray\n )\n return\n }\n\n const currentSyncedItems = new Map(collection.syncedData)\n const newItemsMap = new Map<string | number, TItem>()\n newItemsArray.forEach((item) => {\n const key = getKey(item)\n newItemsMap.set(key, item)\n })\n\n begin()\n\n // Helper function for shallow equality check of objects\n const shallowEqual = (\n obj1: Record<string, any>,\n obj2: Record<string, any>\n ): boolean => {\n // Get all keys from both objects\n const keys1 = Object.keys(obj1)\n const keys2 = Object.keys(obj2)\n\n // If number of keys is different, objects are not equal\n if (keys1.length !== keys2.length) return false\n\n // Check if all keys in obj1 have the same values in obj2\n return keys1.every((key) => {\n // Skip comparing functions and complex objects deeply\n if (typeof obj1[key] === `function`) return true\n return obj1[key] === obj2[key]\n })\n }\n\n currentSyncedItems.forEach((oldItem, key) => {\n const newItem = newItemsMap.get(key)\n if (!newItem) {\n write({ type: `delete`, value: oldItem })\n } else if (\n !shallowEqual(\n oldItem as Record<string, any>,\n newItem as Record<string, any>\n )\n ) {\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 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 !== lastErrorUpdatedAt) {\n lastError = result.error\n errorCount++\n 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\n const actualUnsubscribeFn = localObserver.subscribe(handleUpdate)\n\n // Ensure we process any existing query data (QueryObserver doesn't invoke its callback automatically with initial\n // state)\n handleUpdate(localObserver.getCurrentResult())\n\n return async () => {\n actualUnsubscribeFn()\n await queryClient.cancelQueries({ queryKey })\n queryClient.removeQueries({ queryKey })\n }\n }\n\n /**\n * Refetch the query data\n * @returns Promise that resolves when the refetch is complete\n */\n const refetch: RefetchFn = (opts) => {\n return queryClient.refetchQueries(\n {\n queryKey: queryKey,\n },\n {\n throwOnError: opts?.throwOnError,\n }\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: TItem) => TKey\n begin: () => void\n write: (message: Omit<ChangeMessage<TItem>, `key`>) => void\n commit: () => void\n } | null = null\n\n // Enhanced internalSync that captures write functions for manual use\n const enhancedInternalSync: SyncConfig<TItem>[`sync`] = (params) => {\n const { begin, write, commit, collection } = params\n\n // Store references for manual write operations\n writeContext = {\n collection,\n queryClient,\n queryKey: queryKey as unknown as Array<unknown>,\n getKey: getKey as (item: TItem) => TKey,\n begin,\n write,\n commit,\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<TItem, TKey, TInsertInput>(\n () => writeContext\n )\n\n // Create wrapper handlers for direct persistence operations that handle refetching\n const wrappedOnInsert = onInsert\n ? async (params: InsertMutationFnParams<TItem>) => {\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<TItem>) => {\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<TItem>) => {\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 return {\n ...baseCollectionConfig,\n getKey,\n sync: { sync: enhancedInternalSync },\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils: {\n refetch,\n ...writeUtils,\n lastError: () => lastError,\n isError: () => !!lastError,\n errorCount: () => errorCount,\n clearError: () => {\n lastError = undefined\n errorCount = 0\n lastErrorUpdatedAt = 0\n return refetch({ throwOnError: true })\n },\n },\n }\n}\n"],"names":["QueryKeyRequiredError","QueryFnRequiredError","QueryClientRequiredError","GetKeyRequiredError","QueryObserver","createWriteUtils"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"query.cjs","sources":["../../src/query.ts"],"sourcesContent":["import { QueryObserver } from \"@tanstack/query-core\"\nimport {\n GetKeyRequiredError,\n QueryClientRequiredError,\n QueryFnRequiredError,\n QueryKeyRequiredError,\n} from \"./errors\"\nimport { createWriteUtils } from \"./manual-sync\"\nimport type {\n QueryClient,\n QueryFunctionContext,\n QueryKey,\n QueryObserverOptions,\n} from \"@tanstack/query-core\"\nimport type {\n BaseCollectionConfig,\n ChangeMessage,\n CollectionConfig,\n DeleteMutationFnParams,\n InsertMutationFnParams,\n SyncConfig,\n UpdateMutationFnParams,\n UtilsRecord,\n} from \"@tanstack/db\"\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\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 (\n context: QueryFunctionContext<any>\n ) => Promise<Array<any>> = (\n context: QueryFunctionContext<any>\n ) => Promise<Array<any>>,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = never,\n> extends BaseCollectionConfig<T, TKey, TSchema> {\n /** The query key used by TanStack Query to identify this query */\n queryKey: 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 ? TQueryFn\n : (context: QueryFunctionContext<TQueryKey>) => Promise<Array<T>>\n\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 */\nexport type RefetchFn = (opts?: { throwOnError?: boolean }) => Promise<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 /** 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 /**\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 * 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\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<InferSchemaOutput<T>, TKey, T> & {\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<T, TKey> & {\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 utils: QueryCollectionUtils\n} {\n const {\n queryKey,\n queryFn,\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 // 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 if (!getKey) {\n throw new GetKeyRequiredError()\n }\n\n /** The last error encountered by the query */\n let lastError: any\n /** The number of consecutive sync failures */\n let errorCount = 0\n /** The timestamp for when the query most recently returned the status as \"error\" */\n let lastErrorUpdatedAt = 0\n\n const internalSync: SyncConfig<any>[`sync`] = (params) => {\n const { begin, write, commit, markReady, collection } = params\n\n const observerOptions: QueryObserverOptions<\n Array<any>,\n any,\n Array<any>,\n Array<any>,\n any\n > = {\n queryKey: queryKey,\n queryFn: queryFn,\n meta: meta,\n enabled: enabled,\n refetchInterval: refetchInterval,\n retry: retry,\n retryDelay: retryDelay,\n staleTime: staleTime,\n structuralSharing: true,\n notifyOnChangeProps: `all`,\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 type UpdateHandler = Parameters<typeof localObserver.subscribe>[0]\n const handleUpdate: UpdateHandler = (result) => {\n if (result.isSuccess) {\n // Clear error state\n lastError = undefined\n errorCount = 0\n\n const newItemsArray = result.data\n\n if (\n !Array.isArray(newItemsArray) ||\n newItemsArray.some((item) => typeof item !== `object`)\n ) {\n console.error(\n `[QueryCollection] queryFn did not return an array of objects. Skipping update.`,\n newItemsArray\n )\n return\n }\n\n const currentSyncedItems = new Map(collection.syncedData)\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 // Helper function for shallow equality check of objects\n const shallowEqual = (\n obj1: Record<string, any>,\n obj2: Record<string, any>\n ): boolean => {\n // Get all keys from both objects\n const keys1 = Object.keys(obj1)\n const keys2 = Object.keys(obj2)\n\n // If number of keys is different, objects are not equal\n if (keys1.length !== keys2.length) return false\n\n // Check if all keys in obj1 have the same values in obj2\n return keys1.every((key) => {\n // Skip comparing functions and complex objects deeply\n if (typeof obj1[key] === `function`) return true\n return obj1[key] === obj2[key]\n })\n }\n\n currentSyncedItems.forEach((oldItem, key) => {\n const newItem = newItemsMap.get(key)\n if (!newItem) {\n write({ type: `delete`, value: oldItem })\n } else if (\n !shallowEqual(\n oldItem as Record<string, any>,\n newItem as Record<string, any>\n )\n ) {\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 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 !== lastErrorUpdatedAt) {\n lastError = result.error\n errorCount++\n 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\n const actualUnsubscribeFn = localObserver.subscribe(handleUpdate)\n\n // Ensure we process any existing query data (QueryObserver doesn't invoke its callback automatically with initial\n // state)\n handleUpdate(localObserver.getCurrentResult())\n\n return async () => {\n actualUnsubscribeFn()\n await queryClient.cancelQueries({ queryKey })\n queryClient.removeQueries({ queryKey })\n }\n }\n\n /**\n * Refetch the query data\n * @returns Promise that resolves when the refetch is complete\n */\n const refetch: RefetchFn = (opts) => {\n return queryClient.refetchQueries(\n {\n queryKey: queryKey,\n },\n {\n throwOnError: opts?.throwOnError,\n }\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 } | 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 // Store references for manual write operations\n writeContext = {\n collection,\n queryClient,\n queryKey: queryKey as unknown as Array<unknown>,\n getKey: getKey as (item: any) => string | number,\n begin,\n write,\n commit,\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 return {\n ...baseCollectionConfig,\n getKey,\n sync: { sync: enhancedInternalSync },\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils: {\n refetch,\n ...writeUtils,\n lastError: () => lastError,\n isError: () => !!lastError,\n errorCount: () => errorCount,\n clearError: () => {\n lastError = undefined\n errorCount = 0\n lastErrorUpdatedAt = 0\n return refetch({ throwOnError: true })\n },\n },\n }\n}\n"],"names":["QueryKeyRequiredError","QueryFnRequiredError","QueryClientRequiredError","GetKeyRequiredError","QueryObserver","createWriteUtils"],"mappings":"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"}
|
package/dist/cjs/query.d.cts
CHANGED
|
@@ -1,177 +1,31 @@
|
|
|
1
1
|
import { QueryClient, QueryFunctionContext, QueryKey, QueryObserverOptions } from '@tanstack/query-core';
|
|
2
|
-
import {
|
|
2
|
+
import { BaseCollectionConfig, CollectionConfig, UtilsRecord } from '@tanstack/db';
|
|
3
3
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
4
4
|
export type { SyncOperation } from './manual-sync.cjs';
|
|
5
5
|
type InferSchemaOutput<T> = T extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<T> extends object ? StandardSchemaV1.InferOutput<T> : Record<string, unknown> : Record<string, unknown>;
|
|
6
|
-
type
|
|
7
|
-
type ResolveType<TExplicit extends object | unknown = unknown, TSchema extends StandardSchemaV1 = never, TQueryFn = unknown> = unknown extends TExplicit ? [TSchema] extends [never] ? InferQueryFnOutput<TQueryFn> : InferSchemaOutput<TSchema> : TExplicit;
|
|
6
|
+
type InferSchemaInput<T> = T extends StandardSchemaV1 ? StandardSchemaV1.InferInput<T> extends object ? StandardSchemaV1.InferInput<T> : Record<string, unknown> : Record<string, unknown>;
|
|
8
7
|
/**
|
|
9
8
|
* Configuration options for creating a Query Collection
|
|
10
|
-
* @template
|
|
11
|
-
* @template
|
|
12
|
-
* @template TQueryFn - The queryFn type for inferring return type (third priority)
|
|
9
|
+
* @template T - The explicit type of items stored in the collection
|
|
10
|
+
* @template TQueryFn - The queryFn type
|
|
13
11
|
* @template TError - The type of errors that can occur during queries
|
|
14
12
|
* @template TQueryKey - The type of the query key
|
|
13
|
+
* @template TKey - The type of the item keys
|
|
14
|
+
* @template TSchema - The schema type for validation
|
|
15
15
|
*/
|
|
16
|
-
export interface QueryCollectionConfig<
|
|
16
|
+
export interface QueryCollectionConfig<T extends object = object, TQueryFn extends (context: QueryFunctionContext<any>) => Promise<Array<any>> = (context: QueryFunctionContext<any>) => Promise<Array<any>>, TError = unknown, TQueryKey extends QueryKey = QueryKey, TKey extends string | number = string | number, TSchema extends StandardSchemaV1 = never> extends BaseCollectionConfig<T, TKey, TSchema> {
|
|
17
17
|
/** The query key used by TanStack Query to identify this query */
|
|
18
18
|
queryKey: TQueryKey;
|
|
19
19
|
/** Function that fetches data from the server. Must return the complete collection state */
|
|
20
|
-
queryFn: TQueryFn extends (context: QueryFunctionContext<TQueryKey>) => Promise<Array<any>> ? TQueryFn : (context: QueryFunctionContext<TQueryKey>) => Promise<Array<
|
|
20
|
+
queryFn: TQueryFn extends (context: QueryFunctionContext<TQueryKey>) => Promise<Array<any>> ? TQueryFn : (context: QueryFunctionContext<TQueryKey>) => Promise<Array<T>>;
|
|
21
21
|
/** The TanStack Query client instance */
|
|
22
22
|
queryClient: QueryClient;
|
|
23
23
|
/** Whether the query should automatically run (default: true) */
|
|
24
24
|
enabled?: boolean;
|
|
25
|
-
refetchInterval?: QueryObserverOptions<Array<
|
|
26
|
-
retry?: QueryObserverOptions<Array<
|
|
27
|
-
retryDelay?: QueryObserverOptions<Array<
|
|
28
|
-
staleTime?: QueryObserverOptions<Array<
|
|
29
|
-
/** Unique identifier for the collection */
|
|
30
|
-
id?: string;
|
|
31
|
-
/** Function to extract the unique key from an item */
|
|
32
|
-
getKey: CollectionConfig<ResolveType<TExplicit, TSchema, TQueryFn>>[`getKey`];
|
|
33
|
-
/** Schema for validating items */
|
|
34
|
-
schema?: TSchema;
|
|
35
|
-
sync?: CollectionConfig<ResolveType<TExplicit, TSchema, TQueryFn>>[`sync`];
|
|
36
|
-
startSync?: CollectionConfig<ResolveType<TExplicit, TSchema, TQueryFn>>[`startSync`];
|
|
37
|
-
/**
|
|
38
|
-
* Optional asynchronous handler function called before an insert operation
|
|
39
|
-
* @param params Object containing transaction and collection information
|
|
40
|
-
* @returns Promise resolving to void or { refetch?: boolean } to control refetching
|
|
41
|
-
* @example
|
|
42
|
-
* // Basic query collection insert handler
|
|
43
|
-
* onInsert: async ({ transaction }) => {
|
|
44
|
-
* const newItem = transaction.mutations[0].modified
|
|
45
|
-
* await api.createTodo(newItem)
|
|
46
|
-
* // Automatically refetches query after insert
|
|
47
|
-
* }
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* // Insert handler with refetch control
|
|
51
|
-
* onInsert: async ({ transaction }) => {
|
|
52
|
-
* const newItem = transaction.mutations[0].modified
|
|
53
|
-
* await api.createTodo(newItem)
|
|
54
|
-
* return { refetch: false } // Skip automatic refetch
|
|
55
|
-
* }
|
|
56
|
-
*
|
|
57
|
-
* @example
|
|
58
|
-
* // Insert handler with multiple items
|
|
59
|
-
* onInsert: async ({ transaction }) => {
|
|
60
|
-
* const items = transaction.mutations.map(m => m.modified)
|
|
61
|
-
* await api.createTodos(items)
|
|
62
|
-
* // Will refetch query to get updated data
|
|
63
|
-
* }
|
|
64
|
-
*
|
|
65
|
-
* @example
|
|
66
|
-
* // Insert handler with error handling
|
|
67
|
-
* onInsert: async ({ transaction }) => {
|
|
68
|
-
* try {
|
|
69
|
-
* const newItem = transaction.mutations[0].modified
|
|
70
|
-
* await api.createTodo(newItem)
|
|
71
|
-
* } catch (error) {
|
|
72
|
-
* console.error('Insert failed:', error)
|
|
73
|
-
* throw error // Transaction will rollback optimistic changes
|
|
74
|
-
* }
|
|
75
|
-
* }
|
|
76
|
-
*/
|
|
77
|
-
onInsert?: InsertMutationFn<ResolveType<TExplicit, TSchema, TQueryFn>>;
|
|
78
|
-
/**
|
|
79
|
-
* Optional asynchronous handler function called before an update operation
|
|
80
|
-
* @param params Object containing transaction and collection information
|
|
81
|
-
* @returns Promise resolving to void or { refetch?: boolean } to control refetching
|
|
82
|
-
* @example
|
|
83
|
-
* // Basic query collection update handler
|
|
84
|
-
* onUpdate: async ({ transaction }) => {
|
|
85
|
-
* const mutation = transaction.mutations[0]
|
|
86
|
-
* await api.updateTodo(mutation.original.id, mutation.changes)
|
|
87
|
-
* // Automatically refetches query after update
|
|
88
|
-
* }
|
|
89
|
-
*
|
|
90
|
-
* @example
|
|
91
|
-
* // Update handler with multiple items
|
|
92
|
-
* onUpdate: async ({ transaction }) => {
|
|
93
|
-
* const updates = transaction.mutations.map(m => ({
|
|
94
|
-
* id: m.key,
|
|
95
|
-
* changes: m.changes
|
|
96
|
-
* }))
|
|
97
|
-
* await api.updateTodos(updates)
|
|
98
|
-
* // Will refetch query to get updated data
|
|
99
|
-
* }
|
|
100
|
-
*
|
|
101
|
-
* @example
|
|
102
|
-
* // Update handler with manual refetch
|
|
103
|
-
* onUpdate: async ({ transaction, collection }) => {
|
|
104
|
-
* const mutation = transaction.mutations[0]
|
|
105
|
-
* await api.updateTodo(mutation.original.id, mutation.changes)
|
|
106
|
-
*
|
|
107
|
-
* // Manually trigger refetch
|
|
108
|
-
* await collection.utils.refetch()
|
|
109
|
-
*
|
|
110
|
-
* return { refetch: false } // Skip automatic refetch
|
|
111
|
-
* }
|
|
112
|
-
*
|
|
113
|
-
* @example
|
|
114
|
-
* // Update handler with related collection refetch
|
|
115
|
-
* onUpdate: async ({ transaction, collection }) => {
|
|
116
|
-
* const mutation = transaction.mutations[0]
|
|
117
|
-
* await api.updateTodo(mutation.original.id, mutation.changes)
|
|
118
|
-
*
|
|
119
|
-
* // Refetch related collections when this item changes
|
|
120
|
-
* await Promise.all([
|
|
121
|
-
* collection.utils.refetch(), // Refetch this collection
|
|
122
|
-
* usersCollection.utils.refetch(), // Refetch users
|
|
123
|
-
* tagsCollection.utils.refetch() // Refetch tags
|
|
124
|
-
* ])
|
|
125
|
-
*
|
|
126
|
-
* return { refetch: false } // Skip automatic refetch since we handled it manually
|
|
127
|
-
* }
|
|
128
|
-
*/
|
|
129
|
-
onUpdate?: UpdateMutationFn<ResolveType<TExplicit, TSchema, TQueryFn>>;
|
|
130
|
-
/**
|
|
131
|
-
* Optional asynchronous handler function called before a delete operation
|
|
132
|
-
* @param params Object containing transaction and collection information
|
|
133
|
-
* @returns Promise resolving to void or { refetch?: boolean } to control refetching
|
|
134
|
-
* @example
|
|
135
|
-
* // Basic query collection delete handler
|
|
136
|
-
* onDelete: async ({ transaction }) => {
|
|
137
|
-
* const mutation = transaction.mutations[0]
|
|
138
|
-
* await api.deleteTodo(mutation.original.id)
|
|
139
|
-
* // Automatically refetches query after delete
|
|
140
|
-
* }
|
|
141
|
-
*
|
|
142
|
-
* @example
|
|
143
|
-
* // Delete handler with refetch control
|
|
144
|
-
* onDelete: async ({ transaction }) => {
|
|
145
|
-
* const mutation = transaction.mutations[0]
|
|
146
|
-
* await api.deleteTodo(mutation.original.id)
|
|
147
|
-
* return { refetch: false } // Skip automatic refetch
|
|
148
|
-
* }
|
|
149
|
-
*
|
|
150
|
-
* @example
|
|
151
|
-
* // Delete handler with multiple items
|
|
152
|
-
* onDelete: async ({ transaction }) => {
|
|
153
|
-
* const keysToDelete = transaction.mutations.map(m => m.key)
|
|
154
|
-
* await api.deleteTodos(keysToDelete)
|
|
155
|
-
* // Will refetch query to get updated data
|
|
156
|
-
* }
|
|
157
|
-
*
|
|
158
|
-
* @example
|
|
159
|
-
* // Delete handler with related collection refetch
|
|
160
|
-
* onDelete: async ({ transaction, collection }) => {
|
|
161
|
-
* const mutation = transaction.mutations[0]
|
|
162
|
-
* await api.deleteTodo(mutation.original.id)
|
|
163
|
-
*
|
|
164
|
-
* // Refetch related collections when this item is deleted
|
|
165
|
-
* await Promise.all([
|
|
166
|
-
* collection.utils.refetch(), // Refetch this collection
|
|
167
|
-
* usersCollection.utils.refetch(), // Refetch users
|
|
168
|
-
* projectsCollection.utils.refetch() // Refetch projects
|
|
169
|
-
* ])
|
|
170
|
-
*
|
|
171
|
-
* return { refetch: false } // Skip automatic refetch since we handled it manually
|
|
172
|
-
* }
|
|
173
|
-
*/
|
|
174
|
-
onDelete?: DeleteMutationFn<ResolveType<TExplicit, TSchema, TQueryFn>>;
|
|
25
|
+
refetchInterval?: QueryObserverOptions<Array<T>, TError, Array<T>, Array<T>, TQueryKey>[`refetchInterval`];
|
|
26
|
+
retry?: QueryObserverOptions<Array<T>, TError, Array<T>, Array<T>, TQueryKey>[`retry`];
|
|
27
|
+
retryDelay?: QueryObserverOptions<Array<T>, TError, Array<T>, Array<T>, TQueryKey>[`retryDelay`];
|
|
28
|
+
staleTime?: QueryObserverOptions<Array<T>, TError, Array<T>, Array<T>, TQueryKey>[`staleTime`];
|
|
175
29
|
/**
|
|
176
30
|
* Metadata to pass to the query.
|
|
177
31
|
* Available in queryFn via context.meta
|
|
@@ -242,18 +96,13 @@ export interface QueryCollectionUtils<TItem extends object = Record<string, unkn
|
|
|
242
96
|
* This integrates TanStack Query with TanStack DB for automatic synchronization.
|
|
243
97
|
*
|
|
244
98
|
* Supports automatic type inference following the priority order:
|
|
245
|
-
* 1.
|
|
246
|
-
* 2.
|
|
247
|
-
* 3. QueryFn return type inference (third priority)
|
|
248
|
-
* 4. Fallback to Record<string, unknown>
|
|
99
|
+
* 1. Schema inference (highest priority)
|
|
100
|
+
* 2. QueryFn return type inference (second priority)
|
|
249
101
|
*
|
|
250
|
-
* @template
|
|
251
|
-
* @template TSchema - The schema type for validation and type inference (second priority)
|
|
252
|
-
* @template TQueryFn - The queryFn type for inferring return type (third priority)
|
|
102
|
+
* @template T - Type of the schema if a schema is provided otherwise it is the type of the values returned by the queryFn
|
|
253
103
|
* @template TError - The type of errors that can occur during queries
|
|
254
104
|
* @template TQueryKey - The type of the query key
|
|
255
105
|
* @template TKey - The type of the item keys
|
|
256
|
-
* @template TInsertInput - The type accepted for insert operations
|
|
257
106
|
* @param config - Configuration options for the Query collection
|
|
258
107
|
* @returns Collection options with utilities for direct writes and manual operations
|
|
259
108
|
*
|
|
@@ -272,7 +121,7 @@ export interface QueryCollectionUtils<TItem extends object = Record<string, unkn
|
|
|
272
121
|
* )
|
|
273
122
|
*
|
|
274
123
|
* @example
|
|
275
|
-
* // Explicit type
|
|
124
|
+
* // Explicit type
|
|
276
125
|
* const todosCollection = createCollection<Todo>(
|
|
277
126
|
* queryCollectionOptions({
|
|
278
127
|
* queryKey: ['todos'],
|
|
@@ -283,7 +132,7 @@ export interface QueryCollectionUtils<TItem extends object = Record<string, unkn
|
|
|
283
132
|
* )
|
|
284
133
|
*
|
|
285
134
|
* @example
|
|
286
|
-
* // Schema inference
|
|
135
|
+
* // Schema inference
|
|
287
136
|
* const todosCollection = createCollection(
|
|
288
137
|
* queryCollectionOptions({
|
|
289
138
|
* queryKey: ['todos'],
|
|
@@ -314,6 +163,15 @@ export interface QueryCollectionUtils<TItem extends object = Record<string, unkn
|
|
|
314
163
|
* })
|
|
315
164
|
* )
|
|
316
165
|
*/
|
|
317
|
-
export declare function queryCollectionOptions<
|
|
318
|
-
|
|
166
|
+
export declare function queryCollectionOptions<T extends StandardSchemaV1, TError = unknown, TQueryKey extends QueryKey = QueryKey, TKey extends string | number = string | number>(config: QueryCollectionConfig<InferSchemaOutput<T>, (context: QueryFunctionContext<any>) => Promise<Array<InferSchemaOutput<T>>>, TError, TQueryKey, TKey, T> & {
|
|
167
|
+
schema: T;
|
|
168
|
+
}): CollectionConfig<InferSchemaOutput<T>, TKey, T> & {
|
|
169
|
+
schema: T;
|
|
170
|
+
utils: QueryCollectionUtils<InferSchemaOutput<T>, TKey, InferSchemaInput<T>, TError>;
|
|
171
|
+
};
|
|
172
|
+
export declare function queryCollectionOptions<T extends object, TError = unknown, TQueryKey extends QueryKey = QueryKey, TKey extends string | number = string | number>(config: QueryCollectionConfig<T, (context: QueryFunctionContext<any>) => Promise<Array<T>>, TError, TQueryKey, TKey> & {
|
|
173
|
+
schema?: never;
|
|
174
|
+
}): CollectionConfig<T, TKey> & {
|
|
175
|
+
schema?: never;
|
|
176
|
+
utils: QueryCollectionUtils<T, TKey, T, TError>;
|
|
319
177
|
};
|
package/dist/esm/query.d.ts
CHANGED
|
@@ -1,177 +1,31 @@
|
|
|
1
1
|
import { QueryClient, QueryFunctionContext, QueryKey, QueryObserverOptions } from '@tanstack/query-core';
|
|
2
|
-
import {
|
|
2
|
+
import { BaseCollectionConfig, CollectionConfig, UtilsRecord } from '@tanstack/db';
|
|
3
3
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
4
4
|
export type { SyncOperation } from './manual-sync.js';
|
|
5
5
|
type InferSchemaOutput<T> = T extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<T> extends object ? StandardSchemaV1.InferOutput<T> : Record<string, unknown> : Record<string, unknown>;
|
|
6
|
-
type
|
|
7
|
-
type ResolveType<TExplicit extends object | unknown = unknown, TSchema extends StandardSchemaV1 = never, TQueryFn = unknown> = unknown extends TExplicit ? [TSchema] extends [never] ? InferQueryFnOutput<TQueryFn> : InferSchemaOutput<TSchema> : TExplicit;
|
|
6
|
+
type InferSchemaInput<T> = T extends StandardSchemaV1 ? StandardSchemaV1.InferInput<T> extends object ? StandardSchemaV1.InferInput<T> : Record<string, unknown> : Record<string, unknown>;
|
|
8
7
|
/**
|
|
9
8
|
* Configuration options for creating a Query Collection
|
|
10
|
-
* @template
|
|
11
|
-
* @template
|
|
12
|
-
* @template TQueryFn - The queryFn type for inferring return type (third priority)
|
|
9
|
+
* @template T - The explicit type of items stored in the collection
|
|
10
|
+
* @template TQueryFn - The queryFn type
|
|
13
11
|
* @template TError - The type of errors that can occur during queries
|
|
14
12
|
* @template TQueryKey - The type of the query key
|
|
13
|
+
* @template TKey - The type of the item keys
|
|
14
|
+
* @template TSchema - The schema type for validation
|
|
15
15
|
*/
|
|
16
|
-
export interface QueryCollectionConfig<
|
|
16
|
+
export interface QueryCollectionConfig<T extends object = object, TQueryFn extends (context: QueryFunctionContext<any>) => Promise<Array<any>> = (context: QueryFunctionContext<any>) => Promise<Array<any>>, TError = unknown, TQueryKey extends QueryKey = QueryKey, TKey extends string | number = string | number, TSchema extends StandardSchemaV1 = never> extends BaseCollectionConfig<T, TKey, TSchema> {
|
|
17
17
|
/** The query key used by TanStack Query to identify this query */
|
|
18
18
|
queryKey: TQueryKey;
|
|
19
19
|
/** Function that fetches data from the server. Must return the complete collection state */
|
|
20
|
-
queryFn: TQueryFn extends (context: QueryFunctionContext<TQueryKey>) => Promise<Array<any>> ? TQueryFn : (context: QueryFunctionContext<TQueryKey>) => Promise<Array<
|
|
20
|
+
queryFn: TQueryFn extends (context: QueryFunctionContext<TQueryKey>) => Promise<Array<any>> ? TQueryFn : (context: QueryFunctionContext<TQueryKey>) => Promise<Array<T>>;
|
|
21
21
|
/** The TanStack Query client instance */
|
|
22
22
|
queryClient: QueryClient;
|
|
23
23
|
/** Whether the query should automatically run (default: true) */
|
|
24
24
|
enabled?: boolean;
|
|
25
|
-
refetchInterval?: QueryObserverOptions<Array<
|
|
26
|
-
retry?: QueryObserverOptions<Array<
|
|
27
|
-
retryDelay?: QueryObserverOptions<Array<
|
|
28
|
-
staleTime?: QueryObserverOptions<Array<
|
|
29
|
-
/** Unique identifier for the collection */
|
|
30
|
-
id?: string;
|
|
31
|
-
/** Function to extract the unique key from an item */
|
|
32
|
-
getKey: CollectionConfig<ResolveType<TExplicit, TSchema, TQueryFn>>[`getKey`];
|
|
33
|
-
/** Schema for validating items */
|
|
34
|
-
schema?: TSchema;
|
|
35
|
-
sync?: CollectionConfig<ResolveType<TExplicit, TSchema, TQueryFn>>[`sync`];
|
|
36
|
-
startSync?: CollectionConfig<ResolveType<TExplicit, TSchema, TQueryFn>>[`startSync`];
|
|
37
|
-
/**
|
|
38
|
-
* Optional asynchronous handler function called before an insert operation
|
|
39
|
-
* @param params Object containing transaction and collection information
|
|
40
|
-
* @returns Promise resolving to void or { refetch?: boolean } to control refetching
|
|
41
|
-
* @example
|
|
42
|
-
* // Basic query collection insert handler
|
|
43
|
-
* onInsert: async ({ transaction }) => {
|
|
44
|
-
* const newItem = transaction.mutations[0].modified
|
|
45
|
-
* await api.createTodo(newItem)
|
|
46
|
-
* // Automatically refetches query after insert
|
|
47
|
-
* }
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* // Insert handler with refetch control
|
|
51
|
-
* onInsert: async ({ transaction }) => {
|
|
52
|
-
* const newItem = transaction.mutations[0].modified
|
|
53
|
-
* await api.createTodo(newItem)
|
|
54
|
-
* return { refetch: false } // Skip automatic refetch
|
|
55
|
-
* }
|
|
56
|
-
*
|
|
57
|
-
* @example
|
|
58
|
-
* // Insert handler with multiple items
|
|
59
|
-
* onInsert: async ({ transaction }) => {
|
|
60
|
-
* const items = transaction.mutations.map(m => m.modified)
|
|
61
|
-
* await api.createTodos(items)
|
|
62
|
-
* // Will refetch query to get updated data
|
|
63
|
-
* }
|
|
64
|
-
*
|
|
65
|
-
* @example
|
|
66
|
-
* // Insert handler with error handling
|
|
67
|
-
* onInsert: async ({ transaction }) => {
|
|
68
|
-
* try {
|
|
69
|
-
* const newItem = transaction.mutations[0].modified
|
|
70
|
-
* await api.createTodo(newItem)
|
|
71
|
-
* } catch (error) {
|
|
72
|
-
* console.error('Insert failed:', error)
|
|
73
|
-
* throw error // Transaction will rollback optimistic changes
|
|
74
|
-
* }
|
|
75
|
-
* }
|
|
76
|
-
*/
|
|
77
|
-
onInsert?: InsertMutationFn<ResolveType<TExplicit, TSchema, TQueryFn>>;
|
|
78
|
-
/**
|
|
79
|
-
* Optional asynchronous handler function called before an update operation
|
|
80
|
-
* @param params Object containing transaction and collection information
|
|
81
|
-
* @returns Promise resolving to void or { refetch?: boolean } to control refetching
|
|
82
|
-
* @example
|
|
83
|
-
* // Basic query collection update handler
|
|
84
|
-
* onUpdate: async ({ transaction }) => {
|
|
85
|
-
* const mutation = transaction.mutations[0]
|
|
86
|
-
* await api.updateTodo(mutation.original.id, mutation.changes)
|
|
87
|
-
* // Automatically refetches query after update
|
|
88
|
-
* }
|
|
89
|
-
*
|
|
90
|
-
* @example
|
|
91
|
-
* // Update handler with multiple items
|
|
92
|
-
* onUpdate: async ({ transaction }) => {
|
|
93
|
-
* const updates = transaction.mutations.map(m => ({
|
|
94
|
-
* id: m.key,
|
|
95
|
-
* changes: m.changes
|
|
96
|
-
* }))
|
|
97
|
-
* await api.updateTodos(updates)
|
|
98
|
-
* // Will refetch query to get updated data
|
|
99
|
-
* }
|
|
100
|
-
*
|
|
101
|
-
* @example
|
|
102
|
-
* // Update handler with manual refetch
|
|
103
|
-
* onUpdate: async ({ transaction, collection }) => {
|
|
104
|
-
* const mutation = transaction.mutations[0]
|
|
105
|
-
* await api.updateTodo(mutation.original.id, mutation.changes)
|
|
106
|
-
*
|
|
107
|
-
* // Manually trigger refetch
|
|
108
|
-
* await collection.utils.refetch()
|
|
109
|
-
*
|
|
110
|
-
* return { refetch: false } // Skip automatic refetch
|
|
111
|
-
* }
|
|
112
|
-
*
|
|
113
|
-
* @example
|
|
114
|
-
* // Update handler with related collection refetch
|
|
115
|
-
* onUpdate: async ({ transaction, collection }) => {
|
|
116
|
-
* const mutation = transaction.mutations[0]
|
|
117
|
-
* await api.updateTodo(mutation.original.id, mutation.changes)
|
|
118
|
-
*
|
|
119
|
-
* // Refetch related collections when this item changes
|
|
120
|
-
* await Promise.all([
|
|
121
|
-
* collection.utils.refetch(), // Refetch this collection
|
|
122
|
-
* usersCollection.utils.refetch(), // Refetch users
|
|
123
|
-
* tagsCollection.utils.refetch() // Refetch tags
|
|
124
|
-
* ])
|
|
125
|
-
*
|
|
126
|
-
* return { refetch: false } // Skip automatic refetch since we handled it manually
|
|
127
|
-
* }
|
|
128
|
-
*/
|
|
129
|
-
onUpdate?: UpdateMutationFn<ResolveType<TExplicit, TSchema, TQueryFn>>;
|
|
130
|
-
/**
|
|
131
|
-
* Optional asynchronous handler function called before a delete operation
|
|
132
|
-
* @param params Object containing transaction and collection information
|
|
133
|
-
* @returns Promise resolving to void or { refetch?: boolean } to control refetching
|
|
134
|
-
* @example
|
|
135
|
-
* // Basic query collection delete handler
|
|
136
|
-
* onDelete: async ({ transaction }) => {
|
|
137
|
-
* const mutation = transaction.mutations[0]
|
|
138
|
-
* await api.deleteTodo(mutation.original.id)
|
|
139
|
-
* // Automatically refetches query after delete
|
|
140
|
-
* }
|
|
141
|
-
*
|
|
142
|
-
* @example
|
|
143
|
-
* // Delete handler with refetch control
|
|
144
|
-
* onDelete: async ({ transaction }) => {
|
|
145
|
-
* const mutation = transaction.mutations[0]
|
|
146
|
-
* await api.deleteTodo(mutation.original.id)
|
|
147
|
-
* return { refetch: false } // Skip automatic refetch
|
|
148
|
-
* }
|
|
149
|
-
*
|
|
150
|
-
* @example
|
|
151
|
-
* // Delete handler with multiple items
|
|
152
|
-
* onDelete: async ({ transaction }) => {
|
|
153
|
-
* const keysToDelete = transaction.mutations.map(m => m.key)
|
|
154
|
-
* await api.deleteTodos(keysToDelete)
|
|
155
|
-
* // Will refetch query to get updated data
|
|
156
|
-
* }
|
|
157
|
-
*
|
|
158
|
-
* @example
|
|
159
|
-
* // Delete handler with related collection refetch
|
|
160
|
-
* onDelete: async ({ transaction, collection }) => {
|
|
161
|
-
* const mutation = transaction.mutations[0]
|
|
162
|
-
* await api.deleteTodo(mutation.original.id)
|
|
163
|
-
*
|
|
164
|
-
* // Refetch related collections when this item is deleted
|
|
165
|
-
* await Promise.all([
|
|
166
|
-
* collection.utils.refetch(), // Refetch this collection
|
|
167
|
-
* usersCollection.utils.refetch(), // Refetch users
|
|
168
|
-
* projectsCollection.utils.refetch() // Refetch projects
|
|
169
|
-
* ])
|
|
170
|
-
*
|
|
171
|
-
* return { refetch: false } // Skip automatic refetch since we handled it manually
|
|
172
|
-
* }
|
|
173
|
-
*/
|
|
174
|
-
onDelete?: DeleteMutationFn<ResolveType<TExplicit, TSchema, TQueryFn>>;
|
|
25
|
+
refetchInterval?: QueryObserverOptions<Array<T>, TError, Array<T>, Array<T>, TQueryKey>[`refetchInterval`];
|
|
26
|
+
retry?: QueryObserverOptions<Array<T>, TError, Array<T>, Array<T>, TQueryKey>[`retry`];
|
|
27
|
+
retryDelay?: QueryObserverOptions<Array<T>, TError, Array<T>, Array<T>, TQueryKey>[`retryDelay`];
|
|
28
|
+
staleTime?: QueryObserverOptions<Array<T>, TError, Array<T>, Array<T>, TQueryKey>[`staleTime`];
|
|
175
29
|
/**
|
|
176
30
|
* Metadata to pass to the query.
|
|
177
31
|
* Available in queryFn via context.meta
|
|
@@ -242,18 +96,13 @@ export interface QueryCollectionUtils<TItem extends object = Record<string, unkn
|
|
|
242
96
|
* This integrates TanStack Query with TanStack DB for automatic synchronization.
|
|
243
97
|
*
|
|
244
98
|
* Supports automatic type inference following the priority order:
|
|
245
|
-
* 1.
|
|
246
|
-
* 2.
|
|
247
|
-
* 3. QueryFn return type inference (third priority)
|
|
248
|
-
* 4. Fallback to Record<string, unknown>
|
|
99
|
+
* 1. Schema inference (highest priority)
|
|
100
|
+
* 2. QueryFn return type inference (second priority)
|
|
249
101
|
*
|
|
250
|
-
* @template
|
|
251
|
-
* @template TSchema - The schema type for validation and type inference (second priority)
|
|
252
|
-
* @template TQueryFn - The queryFn type for inferring return type (third priority)
|
|
102
|
+
* @template T - Type of the schema if a schema is provided otherwise it is the type of the values returned by the queryFn
|
|
253
103
|
* @template TError - The type of errors that can occur during queries
|
|
254
104
|
* @template TQueryKey - The type of the query key
|
|
255
105
|
* @template TKey - The type of the item keys
|
|
256
|
-
* @template TInsertInput - The type accepted for insert operations
|
|
257
106
|
* @param config - Configuration options for the Query collection
|
|
258
107
|
* @returns Collection options with utilities for direct writes and manual operations
|
|
259
108
|
*
|
|
@@ -272,7 +121,7 @@ export interface QueryCollectionUtils<TItem extends object = Record<string, unkn
|
|
|
272
121
|
* )
|
|
273
122
|
*
|
|
274
123
|
* @example
|
|
275
|
-
* // Explicit type
|
|
124
|
+
* // Explicit type
|
|
276
125
|
* const todosCollection = createCollection<Todo>(
|
|
277
126
|
* queryCollectionOptions({
|
|
278
127
|
* queryKey: ['todos'],
|
|
@@ -283,7 +132,7 @@ export interface QueryCollectionUtils<TItem extends object = Record<string, unkn
|
|
|
283
132
|
* )
|
|
284
133
|
*
|
|
285
134
|
* @example
|
|
286
|
-
* // Schema inference
|
|
135
|
+
* // Schema inference
|
|
287
136
|
* const todosCollection = createCollection(
|
|
288
137
|
* queryCollectionOptions({
|
|
289
138
|
* queryKey: ['todos'],
|
|
@@ -314,6 +163,15 @@ export interface QueryCollectionUtils<TItem extends object = Record<string, unkn
|
|
|
314
163
|
* })
|
|
315
164
|
* )
|
|
316
165
|
*/
|
|
317
|
-
export declare function queryCollectionOptions<
|
|
318
|
-
|
|
166
|
+
export declare function queryCollectionOptions<T extends StandardSchemaV1, TError = unknown, TQueryKey extends QueryKey = QueryKey, TKey extends string | number = string | number>(config: QueryCollectionConfig<InferSchemaOutput<T>, (context: QueryFunctionContext<any>) => Promise<Array<InferSchemaOutput<T>>>, TError, TQueryKey, TKey, T> & {
|
|
167
|
+
schema: T;
|
|
168
|
+
}): CollectionConfig<InferSchemaOutput<T>, TKey, T> & {
|
|
169
|
+
schema: T;
|
|
170
|
+
utils: QueryCollectionUtils<InferSchemaOutput<T>, TKey, InferSchemaInput<T>, TError>;
|
|
171
|
+
};
|
|
172
|
+
export declare function queryCollectionOptions<T extends object, TError = unknown, TQueryKey extends QueryKey = QueryKey, TKey extends string | number = string | number>(config: QueryCollectionConfig<T, (context: QueryFunctionContext<any>) => Promise<Array<T>>, TError, TQueryKey, TKey> & {
|
|
173
|
+
schema?: never;
|
|
174
|
+
}): CollectionConfig<T, TKey> & {
|
|
175
|
+
schema?: never;
|
|
176
|
+
utils: QueryCollectionUtils<T, TKey, T, TError>;
|
|
319
177
|
};
|
package/dist/esm/query.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.js","sources":["../../src/query.ts"],"sourcesContent":["import { QueryObserver } from \"@tanstack/query-core\"\nimport {\n GetKeyRequiredError,\n QueryClientRequiredError,\n QueryFnRequiredError,\n QueryKeyRequiredError,\n} from \"./errors\"\nimport { createWriteUtils } from \"./manual-sync\"\nimport type {\n QueryClient,\n QueryFunctionContext,\n QueryKey,\n QueryObserverOptions,\n} from \"@tanstack/query-core\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n DeleteMutationFn,\n DeleteMutationFnParams,\n InsertMutationFn,\n InsertMutationFnParams,\n SyncConfig,\n UpdateMutationFn,\n UpdateMutationFnParams,\n UtilsRecord,\n} from \"@tanstack/db\"\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// QueryFn return type inference helper\ntype InferQueryFnOutput<TQueryFn> = TQueryFn extends (\n context: QueryFunctionContext<any>\n) => Promise<Array<infer TItem>>\n ? TItem extends object\n ? TItem\n : Record<string, unknown>\n : Record<string, unknown>\n\n// Type resolution system with priority order (matches electric.ts pattern)\ntype ResolveType<\n TExplicit extends object | unknown = unknown,\n TSchema extends StandardSchemaV1 = never,\n TQueryFn = unknown,\n> = unknown extends TExplicit\n ? [TSchema] extends [never]\n ? InferQueryFnOutput<TQueryFn>\n : InferSchemaOutput<TSchema>\n : TExplicit\n\n/**\n * Configuration options for creating a Query Collection\n * @template TExplicit - The explicit type of items stored in the collection (highest priority)\n * @template TSchema - The schema type for validation and type inference (second priority)\n * @template TQueryFn - The queryFn type for inferring return type (third priority)\n * @template TError - The type of errors that can occur during queries\n * @template TQueryKey - The type of the query key\n */\nexport interface QueryCollectionConfig<\n TExplicit extends object = object,\n TSchema extends StandardSchemaV1 = never,\n TQueryFn extends (\n context: QueryFunctionContext<any>\n ) => Promise<Array<any>> = (\n context: QueryFunctionContext<any>\n ) => Promise<Array<any>>,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n> {\n /** The query key used by TanStack Query to identify this query */\n queryKey: 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 ? TQueryFn\n : (\n context: QueryFunctionContext<TQueryKey>\n ) => Promise<Array<ResolveType<TExplicit, TSchema, TQueryFn>>>\n\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<ResolveType<TExplicit, TSchema, TQueryFn>>,\n TError,\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n TQueryKey\n >[`refetchInterval`]\n retry?: QueryObserverOptions<\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n TError,\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n TQueryKey\n >[`retry`]\n retryDelay?: QueryObserverOptions<\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n TError,\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n TQueryKey\n >[`retryDelay`]\n staleTime?: QueryObserverOptions<\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n TError,\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n Array<ResolveType<TExplicit, TSchema, TQueryFn>>,\n TQueryKey\n >[`staleTime`]\n\n // Standard Collection configuration properties\n /** Unique identifier for the collection */\n id?: string\n /** Function to extract the unique key from an item */\n getKey: CollectionConfig<ResolveType<TExplicit, TSchema, TQueryFn>>[`getKey`]\n /** Schema for validating items */\n schema?: TSchema\n sync?: CollectionConfig<ResolveType<TExplicit, TSchema, TQueryFn>>[`sync`]\n startSync?: CollectionConfig<\n ResolveType<TExplicit, TSchema, TQueryFn>\n >[`startSync`]\n\n // Direct persistence handlers\n /**\n * Optional asynchronous handler function called before an insert operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to void or { refetch?: boolean } to control refetching\n * @example\n * // Basic query collection insert handler\n * onInsert: async ({ transaction }) => {\n * const newItem = transaction.mutations[0].modified\n * await api.createTodo(newItem)\n * // Automatically refetches query after insert\n * }\n *\n * @example\n * // Insert handler with refetch control\n * onInsert: async ({ transaction }) => {\n * const newItem = transaction.mutations[0].modified\n * await api.createTodo(newItem)\n * return { refetch: false } // Skip automatic refetch\n * }\n *\n * @example\n * // Insert handler with multiple items\n * onInsert: async ({ transaction }) => {\n * const items = transaction.mutations.map(m => m.modified)\n * await api.createTodos(items)\n * // Will refetch query to get updated data\n * }\n *\n * @example\n * // Insert handler with error handling\n * onInsert: async ({ transaction }) => {\n * try {\n * const newItem = transaction.mutations[0].modified\n * await api.createTodo(newItem)\n * } catch (error) {\n * console.error('Insert failed:', error)\n * throw error // Transaction will rollback optimistic changes\n * }\n * }\n */\n onInsert?: InsertMutationFn<ResolveType<TExplicit, TSchema, TQueryFn>>\n\n /**\n * Optional asynchronous handler function called before an update operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to void or { refetch?: boolean } to control refetching\n * @example\n * // Basic query collection update handler\n * onUpdate: async ({ transaction }) => {\n * const mutation = transaction.mutations[0]\n * await api.updateTodo(mutation.original.id, mutation.changes)\n * // Automatically refetches query after update\n * }\n *\n * @example\n * // Update handler with multiple items\n * onUpdate: async ({ transaction }) => {\n * const updates = transaction.mutations.map(m => ({\n * id: m.key,\n * changes: m.changes\n * }))\n * await api.updateTodos(updates)\n * // Will refetch query to get updated data\n * }\n *\n * @example\n * // Update handler with manual refetch\n * onUpdate: async ({ transaction, collection }) => {\n * const mutation = transaction.mutations[0]\n * await api.updateTodo(mutation.original.id, mutation.changes)\n *\n * // Manually trigger refetch\n * await collection.utils.refetch()\n *\n * return { refetch: false } // Skip automatic refetch\n * }\n *\n * @example\n * // Update handler with related collection refetch\n * onUpdate: async ({ transaction, collection }) => {\n * const mutation = transaction.mutations[0]\n * await api.updateTodo(mutation.original.id, mutation.changes)\n *\n * // Refetch related collections when this item changes\n * await Promise.all([\n * collection.utils.refetch(), // Refetch this collection\n * usersCollection.utils.refetch(), // Refetch users\n * tagsCollection.utils.refetch() // Refetch tags\n * ])\n *\n * return { refetch: false } // Skip automatic refetch since we handled it manually\n * }\n */\n onUpdate?: UpdateMutationFn<ResolveType<TExplicit, TSchema, TQueryFn>>\n\n /**\n * Optional asynchronous handler function called before a delete operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to void or { refetch?: boolean } to control refetching\n * @example\n * // Basic query collection delete handler\n * onDelete: async ({ transaction }) => {\n * const mutation = transaction.mutations[0]\n * await api.deleteTodo(mutation.original.id)\n * // Automatically refetches query after delete\n * }\n *\n * @example\n * // Delete handler with refetch control\n * onDelete: async ({ transaction }) => {\n * const mutation = transaction.mutations[0]\n * await api.deleteTodo(mutation.original.id)\n * return { refetch: false } // Skip automatic refetch\n * }\n *\n * @example\n * // Delete handler with multiple items\n * onDelete: async ({ transaction }) => {\n * const keysToDelete = transaction.mutations.map(m => m.key)\n * await api.deleteTodos(keysToDelete)\n * // Will refetch query to get updated data\n * }\n *\n * @example\n * // Delete handler with related collection refetch\n * onDelete: async ({ transaction, collection }) => {\n * const mutation = transaction.mutations[0]\n * await api.deleteTodo(mutation.original.id)\n *\n * // Refetch related collections when this item is deleted\n * await Promise.all([\n * collection.utils.refetch(), // Refetch this collection\n * usersCollection.utils.refetch(), // Refetch users\n * projectsCollection.utils.refetch() // Refetch projects\n * ])\n *\n * return { refetch: false } // Skip automatic refetch since we handled it manually\n * }\n */\n onDelete?: DeleteMutationFn<ResolveType<TExplicit, TSchema, TQueryFn>>\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 */\nexport type RefetchFn = (opts?: { throwOnError?: boolean }) => Promise<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 /** 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 /**\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 * 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. Explicit type (highest priority)\n * 2. Schema inference (second priority)\n * 3. QueryFn return type inference (third priority)\n * 4. Fallback to Record<string, unknown>\n *\n * @template TExplicit - The explicit type of items in the collection (highest priority)\n * @template TSchema - The schema type for validation and type inference (second priority)\n * @template TQueryFn - The queryFn type for inferring return type (third priority)\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 TInsertInput - The type accepted for insert operations\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 (highest priority)\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 (second priority)\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 */\nexport function queryCollectionOptions<\n TExplicit extends object = object,\n TSchema extends StandardSchemaV1 = never,\n TQueryFn extends (\n context: QueryFunctionContext<any>\n ) => Promise<Array<any>> = (\n context: QueryFunctionContext<any>\n ) => Promise<Array<any>>,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n TInsertInput extends object = ResolveType<TExplicit, TSchema, TQueryFn>,\n>(\n config: QueryCollectionConfig<TExplicit, TSchema, TQueryFn, TError, TQueryKey>\n): CollectionConfig<ResolveType<TExplicit, TSchema, TQueryFn>> & {\n utils: QueryCollectionUtils<\n ResolveType<TExplicit, TSchema, TQueryFn>,\n TKey,\n TInsertInput,\n TError\n >\n} {\n type TItem = ResolveType<TExplicit, TSchema, TQueryFn>\n\n const {\n queryKey,\n queryFn,\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 // 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 if (!getKey) {\n throw new GetKeyRequiredError()\n }\n\n /** The last error encountered by the query */\n let lastError: TError | undefined\n /** The number of consecutive sync failures */\n let errorCount = 0\n /** The timestamp for when the query most recently returned the status as \"error\" */\n let lastErrorUpdatedAt = 0\n\n const internalSync: SyncConfig<TItem>[`sync`] = (params) => {\n const { begin, write, commit, markReady, collection } = params\n\n const observerOptions: QueryObserverOptions<\n Array<TItem>,\n TError,\n Array<TItem>,\n Array<TItem>,\n TQueryKey\n > = {\n queryKey: queryKey,\n queryFn: queryFn,\n meta: meta,\n enabled: enabled,\n refetchInterval: refetchInterval,\n retry: retry,\n retryDelay: retryDelay,\n staleTime: staleTime,\n structuralSharing: true,\n notifyOnChangeProps: `all`,\n }\n\n const localObserver = new QueryObserver<\n Array<TItem>,\n TError,\n Array<TItem>,\n Array<TItem>,\n TQueryKey\n >(queryClient, observerOptions)\n\n type UpdateHandler = Parameters<typeof localObserver.subscribe>[0]\n const handleUpdate: UpdateHandler = (result) => {\n if (result.isSuccess) {\n // Clear error state\n lastError = undefined\n errorCount = 0\n\n const newItemsArray = result.data\n\n if (\n !Array.isArray(newItemsArray) ||\n newItemsArray.some((item) => typeof item !== `object`)\n ) {\n console.error(\n `[QueryCollection] queryFn did not return an array of objects. Skipping update.`,\n newItemsArray\n )\n return\n }\n\n const currentSyncedItems = new Map(collection.syncedData)\n const newItemsMap = new Map<string | number, TItem>()\n newItemsArray.forEach((item) => {\n const key = getKey(item)\n newItemsMap.set(key, item)\n })\n\n begin()\n\n // Helper function for shallow equality check of objects\n const shallowEqual = (\n obj1: Record<string, any>,\n obj2: Record<string, any>\n ): boolean => {\n // Get all keys from both objects\n const keys1 = Object.keys(obj1)\n const keys2 = Object.keys(obj2)\n\n // If number of keys is different, objects are not equal\n if (keys1.length !== keys2.length) return false\n\n // Check if all keys in obj1 have the same values in obj2\n return keys1.every((key) => {\n // Skip comparing functions and complex objects deeply\n if (typeof obj1[key] === `function`) return true\n return obj1[key] === obj2[key]\n })\n }\n\n currentSyncedItems.forEach((oldItem, key) => {\n const newItem = newItemsMap.get(key)\n if (!newItem) {\n write({ type: `delete`, value: oldItem })\n } else if (\n !shallowEqual(\n oldItem as Record<string, any>,\n newItem as Record<string, any>\n )\n ) {\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 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 !== lastErrorUpdatedAt) {\n lastError = result.error\n errorCount++\n 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\n const actualUnsubscribeFn = localObserver.subscribe(handleUpdate)\n\n // Ensure we process any existing query data (QueryObserver doesn't invoke its callback automatically with initial\n // state)\n handleUpdate(localObserver.getCurrentResult())\n\n return async () => {\n actualUnsubscribeFn()\n await queryClient.cancelQueries({ queryKey })\n queryClient.removeQueries({ queryKey })\n }\n }\n\n /**\n * Refetch the query data\n * @returns Promise that resolves when the refetch is complete\n */\n const refetch: RefetchFn = (opts) => {\n return queryClient.refetchQueries(\n {\n queryKey: queryKey,\n },\n {\n throwOnError: opts?.throwOnError,\n }\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: TItem) => TKey\n begin: () => void\n write: (message: Omit<ChangeMessage<TItem>, `key`>) => void\n commit: () => void\n } | null = null\n\n // Enhanced internalSync that captures write functions for manual use\n const enhancedInternalSync: SyncConfig<TItem>[`sync`] = (params) => {\n const { begin, write, commit, collection } = params\n\n // Store references for manual write operations\n writeContext = {\n collection,\n queryClient,\n queryKey: queryKey as unknown as Array<unknown>,\n getKey: getKey as (item: TItem) => TKey,\n begin,\n write,\n commit,\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<TItem, TKey, TInsertInput>(\n () => writeContext\n )\n\n // Create wrapper handlers for direct persistence operations that handle refetching\n const wrappedOnInsert = onInsert\n ? async (params: InsertMutationFnParams<TItem>) => {\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<TItem>) => {\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<TItem>) => {\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 return {\n ...baseCollectionConfig,\n getKey,\n sync: { sync: enhancedInternalSync },\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils: {\n refetch,\n ...writeUtils,\n lastError: () => lastError,\n isError: () => !!lastError,\n errorCount: () => errorCount,\n clearError: () => {\n lastError = undefined\n errorCount = 0\n lastErrorUpdatedAt = 0\n return refetch({ throwOnError: true })\n },\n },\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"query.js","sources":["../../src/query.ts"],"sourcesContent":["import { QueryObserver } from \"@tanstack/query-core\"\nimport {\n GetKeyRequiredError,\n QueryClientRequiredError,\n QueryFnRequiredError,\n QueryKeyRequiredError,\n} from \"./errors\"\nimport { createWriteUtils } from \"./manual-sync\"\nimport type {\n QueryClient,\n QueryFunctionContext,\n QueryKey,\n QueryObserverOptions,\n} from \"@tanstack/query-core\"\nimport type {\n BaseCollectionConfig,\n ChangeMessage,\n CollectionConfig,\n DeleteMutationFnParams,\n InsertMutationFnParams,\n SyncConfig,\n UpdateMutationFnParams,\n UtilsRecord,\n} from \"@tanstack/db\"\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\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 (\n context: QueryFunctionContext<any>\n ) => Promise<Array<any>> = (\n context: QueryFunctionContext<any>\n ) => Promise<Array<any>>,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = never,\n> extends BaseCollectionConfig<T, TKey, TSchema> {\n /** The query key used by TanStack Query to identify this query */\n queryKey: 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 ? TQueryFn\n : (context: QueryFunctionContext<TQueryKey>) => Promise<Array<T>>\n\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 */\nexport type RefetchFn = (opts?: { throwOnError?: boolean }) => Promise<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 /** 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 /**\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 * 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\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<InferSchemaOutput<T>, TKey, T> & {\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<T, TKey> & {\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 utils: QueryCollectionUtils\n} {\n const {\n queryKey,\n queryFn,\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 // 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 if (!getKey) {\n throw new GetKeyRequiredError()\n }\n\n /** The last error encountered by the query */\n let lastError: any\n /** The number of consecutive sync failures */\n let errorCount = 0\n /** The timestamp for when the query most recently returned the status as \"error\" */\n let lastErrorUpdatedAt = 0\n\n const internalSync: SyncConfig<any>[`sync`] = (params) => {\n const { begin, write, commit, markReady, collection } = params\n\n const observerOptions: QueryObserverOptions<\n Array<any>,\n any,\n Array<any>,\n Array<any>,\n any\n > = {\n queryKey: queryKey,\n queryFn: queryFn,\n meta: meta,\n enabled: enabled,\n refetchInterval: refetchInterval,\n retry: retry,\n retryDelay: retryDelay,\n staleTime: staleTime,\n structuralSharing: true,\n notifyOnChangeProps: `all`,\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 type UpdateHandler = Parameters<typeof localObserver.subscribe>[0]\n const handleUpdate: UpdateHandler = (result) => {\n if (result.isSuccess) {\n // Clear error state\n lastError = undefined\n errorCount = 0\n\n const newItemsArray = result.data\n\n if (\n !Array.isArray(newItemsArray) ||\n newItemsArray.some((item) => typeof item !== `object`)\n ) {\n console.error(\n `[QueryCollection] queryFn did not return an array of objects. Skipping update.`,\n newItemsArray\n )\n return\n }\n\n const currentSyncedItems = new Map(collection.syncedData)\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 // Helper function for shallow equality check of objects\n const shallowEqual = (\n obj1: Record<string, any>,\n obj2: Record<string, any>\n ): boolean => {\n // Get all keys from both objects\n const keys1 = Object.keys(obj1)\n const keys2 = Object.keys(obj2)\n\n // If number of keys is different, objects are not equal\n if (keys1.length !== keys2.length) return false\n\n // Check if all keys in obj1 have the same values in obj2\n return keys1.every((key) => {\n // Skip comparing functions and complex objects deeply\n if (typeof obj1[key] === `function`) return true\n return obj1[key] === obj2[key]\n })\n }\n\n currentSyncedItems.forEach((oldItem, key) => {\n const newItem = newItemsMap.get(key)\n if (!newItem) {\n write({ type: `delete`, value: oldItem })\n } else if (\n !shallowEqual(\n oldItem as Record<string, any>,\n newItem as Record<string, any>\n )\n ) {\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 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 !== lastErrorUpdatedAt) {\n lastError = result.error\n errorCount++\n 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\n const actualUnsubscribeFn = localObserver.subscribe(handleUpdate)\n\n // Ensure we process any existing query data (QueryObserver doesn't invoke its callback automatically with initial\n // state)\n handleUpdate(localObserver.getCurrentResult())\n\n return async () => {\n actualUnsubscribeFn()\n await queryClient.cancelQueries({ queryKey })\n queryClient.removeQueries({ queryKey })\n }\n }\n\n /**\n * Refetch the query data\n * @returns Promise that resolves when the refetch is complete\n */\n const refetch: RefetchFn = (opts) => {\n return queryClient.refetchQueries(\n {\n queryKey: queryKey,\n },\n {\n throwOnError: opts?.throwOnError,\n }\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 } | 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 // Store references for manual write operations\n writeContext = {\n collection,\n queryClient,\n queryKey: queryKey as unknown as Array<unknown>,\n getKey: getKey as (item: any) => string | number,\n begin,\n write,\n commit,\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 return {\n ...baseCollectionConfig,\n getKey,\n sync: { sync: enhancedInternalSync },\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils: {\n refetch,\n ...writeUtils,\n lastError: () => lastError,\n isError: () => !!lastError,\n errorCount: () => errorCount,\n clearError: () => {\n lastError = undefined\n errorCount = 0\n lastErrorUpdatedAt = 0\n return refetch({ throwOnError: true })\n },\n },\n }\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/query-db-collection",
|
|
3
3
|
"description": "TanStack Query collection for TanStack DB",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.18",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@standard-schema/spec": "^1.0.0",
|
|
7
|
-
"@tanstack/db": "0.
|
|
7
|
+
"@tanstack/db": "0.3.0"
|
|
8
8
|
},
|
|
9
9
|
"devDependencies": {
|
|
10
10
|
"@tanstack/query-core": "^5.87.4",
|
package/src/query.ts
CHANGED
|
@@ -13,14 +13,12 @@ import type {
|
|
|
13
13
|
QueryObserverOptions,
|
|
14
14
|
} from "@tanstack/query-core"
|
|
15
15
|
import type {
|
|
16
|
+
BaseCollectionConfig,
|
|
16
17
|
ChangeMessage,
|
|
17
18
|
CollectionConfig,
|
|
18
|
-
DeleteMutationFn,
|
|
19
19
|
DeleteMutationFnParams,
|
|
20
|
-
InsertMutationFn,
|
|
21
20
|
InsertMutationFnParams,
|
|
22
21
|
SyncConfig,
|
|
23
|
-
UpdateMutationFn,
|
|
24
22
|
UpdateMutationFnParams,
|
|
25
23
|
UtilsRecord,
|
|
26
24
|
} from "@tanstack/db"
|
|
@@ -36,37 +34,24 @@ type InferSchemaOutput<T> = T extends StandardSchemaV1
|
|
|
36
34
|
: Record<string, unknown>
|
|
37
35
|
: Record<string, unknown>
|
|
38
36
|
|
|
39
|
-
//
|
|
40
|
-
type
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
? TItem extends object
|
|
44
|
-
? TItem
|
|
37
|
+
// Schema input type inference helper (matches electric.ts pattern)
|
|
38
|
+
type InferSchemaInput<T> = T extends StandardSchemaV1
|
|
39
|
+
? StandardSchemaV1.InferInput<T> extends object
|
|
40
|
+
? StandardSchemaV1.InferInput<T>
|
|
45
41
|
: Record<string, unknown>
|
|
46
42
|
: Record<string, unknown>
|
|
47
43
|
|
|
48
|
-
// Type resolution system with priority order (matches electric.ts pattern)
|
|
49
|
-
type ResolveType<
|
|
50
|
-
TExplicit extends object | unknown = unknown,
|
|
51
|
-
TSchema extends StandardSchemaV1 = never,
|
|
52
|
-
TQueryFn = unknown,
|
|
53
|
-
> = unknown extends TExplicit
|
|
54
|
-
? [TSchema] extends [never]
|
|
55
|
-
? InferQueryFnOutput<TQueryFn>
|
|
56
|
-
: InferSchemaOutput<TSchema>
|
|
57
|
-
: TExplicit
|
|
58
|
-
|
|
59
44
|
/**
|
|
60
45
|
* Configuration options for creating a Query Collection
|
|
61
|
-
* @template
|
|
62
|
-
* @template
|
|
63
|
-
* @template TQueryFn - The queryFn type for inferring return type (third priority)
|
|
46
|
+
* @template T - The explicit type of items stored in the collection
|
|
47
|
+
* @template TQueryFn - The queryFn type
|
|
64
48
|
* @template TError - The type of errors that can occur during queries
|
|
65
49
|
* @template TQueryKey - The type of the query key
|
|
50
|
+
* @template TKey - The type of the item keys
|
|
51
|
+
* @template TSchema - The schema type for validation
|
|
66
52
|
*/
|
|
67
53
|
export interface QueryCollectionConfig<
|
|
68
|
-
|
|
69
|
-
TSchema extends StandardSchemaV1 = never,
|
|
54
|
+
T extends object = object,
|
|
70
55
|
TQueryFn extends (
|
|
71
56
|
context: QueryFunctionContext<any>
|
|
72
57
|
) => Promise<Array<any>> = (
|
|
@@ -74,7 +59,9 @@ export interface QueryCollectionConfig<
|
|
|
74
59
|
) => Promise<Array<any>>,
|
|
75
60
|
TError = unknown,
|
|
76
61
|
TQueryKey extends QueryKey = QueryKey,
|
|
77
|
-
|
|
62
|
+
TKey extends string | number = string | number,
|
|
63
|
+
TSchema extends StandardSchemaV1 = never,
|
|
64
|
+
> extends BaseCollectionConfig<T, TKey, TSchema> {
|
|
78
65
|
/** The query key used by TanStack Query to identify this query */
|
|
79
66
|
queryKey: TQueryKey
|
|
80
67
|
/** Function that fetches data from the server. Must return the complete collection state */
|
|
@@ -82,9 +69,7 @@ export interface QueryCollectionConfig<
|
|
|
82
69
|
context: QueryFunctionContext<TQueryKey>
|
|
83
70
|
) => Promise<Array<any>>
|
|
84
71
|
? TQueryFn
|
|
85
|
-
: (
|
|
86
|
-
context: QueryFunctionContext<TQueryKey>
|
|
87
|
-
) => Promise<Array<ResolveType<TExplicit, TSchema, TQueryFn>>>
|
|
72
|
+
: (context: QueryFunctionContext<TQueryKey>) => Promise<Array<T>>
|
|
88
73
|
|
|
89
74
|
/** The TanStack Query client instance */
|
|
90
75
|
queryClient: QueryClient
|
|
@@ -93,188 +78,34 @@ export interface QueryCollectionConfig<
|
|
|
93
78
|
/** Whether the query should automatically run (default: true) */
|
|
94
79
|
enabled?: boolean
|
|
95
80
|
refetchInterval?: QueryObserverOptions<
|
|
96
|
-
Array<
|
|
81
|
+
Array<T>,
|
|
97
82
|
TError,
|
|
98
|
-
Array<
|
|
99
|
-
Array<
|
|
83
|
+
Array<T>,
|
|
84
|
+
Array<T>,
|
|
100
85
|
TQueryKey
|
|
101
86
|
>[`refetchInterval`]
|
|
102
87
|
retry?: QueryObserverOptions<
|
|
103
|
-
Array<
|
|
88
|
+
Array<T>,
|
|
104
89
|
TError,
|
|
105
|
-
Array<
|
|
106
|
-
Array<
|
|
90
|
+
Array<T>,
|
|
91
|
+
Array<T>,
|
|
107
92
|
TQueryKey
|
|
108
93
|
>[`retry`]
|
|
109
94
|
retryDelay?: QueryObserverOptions<
|
|
110
|
-
Array<
|
|
95
|
+
Array<T>,
|
|
111
96
|
TError,
|
|
112
|
-
Array<
|
|
113
|
-
Array<
|
|
97
|
+
Array<T>,
|
|
98
|
+
Array<T>,
|
|
114
99
|
TQueryKey
|
|
115
100
|
>[`retryDelay`]
|
|
116
101
|
staleTime?: QueryObserverOptions<
|
|
117
|
-
Array<
|
|
102
|
+
Array<T>,
|
|
118
103
|
TError,
|
|
119
|
-
Array<
|
|
120
|
-
Array<
|
|
104
|
+
Array<T>,
|
|
105
|
+
Array<T>,
|
|
121
106
|
TQueryKey
|
|
122
107
|
>[`staleTime`]
|
|
123
108
|
|
|
124
|
-
// Standard Collection configuration properties
|
|
125
|
-
/** Unique identifier for the collection */
|
|
126
|
-
id?: string
|
|
127
|
-
/** Function to extract the unique key from an item */
|
|
128
|
-
getKey: CollectionConfig<ResolveType<TExplicit, TSchema, TQueryFn>>[`getKey`]
|
|
129
|
-
/** Schema for validating items */
|
|
130
|
-
schema?: TSchema
|
|
131
|
-
sync?: CollectionConfig<ResolveType<TExplicit, TSchema, TQueryFn>>[`sync`]
|
|
132
|
-
startSync?: CollectionConfig<
|
|
133
|
-
ResolveType<TExplicit, TSchema, TQueryFn>
|
|
134
|
-
>[`startSync`]
|
|
135
|
-
|
|
136
|
-
// Direct persistence handlers
|
|
137
|
-
/**
|
|
138
|
-
* Optional asynchronous handler function called before an insert operation
|
|
139
|
-
* @param params Object containing transaction and collection information
|
|
140
|
-
* @returns Promise resolving to void or { refetch?: boolean } to control refetching
|
|
141
|
-
* @example
|
|
142
|
-
* // Basic query collection insert handler
|
|
143
|
-
* onInsert: async ({ transaction }) => {
|
|
144
|
-
* const newItem = transaction.mutations[0].modified
|
|
145
|
-
* await api.createTodo(newItem)
|
|
146
|
-
* // Automatically refetches query after insert
|
|
147
|
-
* }
|
|
148
|
-
*
|
|
149
|
-
* @example
|
|
150
|
-
* // Insert handler with refetch control
|
|
151
|
-
* onInsert: async ({ transaction }) => {
|
|
152
|
-
* const newItem = transaction.mutations[0].modified
|
|
153
|
-
* await api.createTodo(newItem)
|
|
154
|
-
* return { refetch: false } // Skip automatic refetch
|
|
155
|
-
* }
|
|
156
|
-
*
|
|
157
|
-
* @example
|
|
158
|
-
* // Insert handler with multiple items
|
|
159
|
-
* onInsert: async ({ transaction }) => {
|
|
160
|
-
* const items = transaction.mutations.map(m => m.modified)
|
|
161
|
-
* await api.createTodos(items)
|
|
162
|
-
* // Will refetch query to get updated data
|
|
163
|
-
* }
|
|
164
|
-
*
|
|
165
|
-
* @example
|
|
166
|
-
* // Insert handler with error handling
|
|
167
|
-
* onInsert: async ({ transaction }) => {
|
|
168
|
-
* try {
|
|
169
|
-
* const newItem = transaction.mutations[0].modified
|
|
170
|
-
* await api.createTodo(newItem)
|
|
171
|
-
* } catch (error) {
|
|
172
|
-
* console.error('Insert failed:', error)
|
|
173
|
-
* throw error // Transaction will rollback optimistic changes
|
|
174
|
-
* }
|
|
175
|
-
* }
|
|
176
|
-
*/
|
|
177
|
-
onInsert?: InsertMutationFn<ResolveType<TExplicit, TSchema, TQueryFn>>
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Optional asynchronous handler function called before an update operation
|
|
181
|
-
* @param params Object containing transaction and collection information
|
|
182
|
-
* @returns Promise resolving to void or { refetch?: boolean } to control refetching
|
|
183
|
-
* @example
|
|
184
|
-
* // Basic query collection update handler
|
|
185
|
-
* onUpdate: async ({ transaction }) => {
|
|
186
|
-
* const mutation = transaction.mutations[0]
|
|
187
|
-
* await api.updateTodo(mutation.original.id, mutation.changes)
|
|
188
|
-
* // Automatically refetches query after update
|
|
189
|
-
* }
|
|
190
|
-
*
|
|
191
|
-
* @example
|
|
192
|
-
* // Update handler with multiple items
|
|
193
|
-
* onUpdate: async ({ transaction }) => {
|
|
194
|
-
* const updates = transaction.mutations.map(m => ({
|
|
195
|
-
* id: m.key,
|
|
196
|
-
* changes: m.changes
|
|
197
|
-
* }))
|
|
198
|
-
* await api.updateTodos(updates)
|
|
199
|
-
* // Will refetch query to get updated data
|
|
200
|
-
* }
|
|
201
|
-
*
|
|
202
|
-
* @example
|
|
203
|
-
* // Update handler with manual refetch
|
|
204
|
-
* onUpdate: async ({ transaction, collection }) => {
|
|
205
|
-
* const mutation = transaction.mutations[0]
|
|
206
|
-
* await api.updateTodo(mutation.original.id, mutation.changes)
|
|
207
|
-
*
|
|
208
|
-
* // Manually trigger refetch
|
|
209
|
-
* await collection.utils.refetch()
|
|
210
|
-
*
|
|
211
|
-
* return { refetch: false } // Skip automatic refetch
|
|
212
|
-
* }
|
|
213
|
-
*
|
|
214
|
-
* @example
|
|
215
|
-
* // Update handler with related collection refetch
|
|
216
|
-
* onUpdate: async ({ transaction, collection }) => {
|
|
217
|
-
* const mutation = transaction.mutations[0]
|
|
218
|
-
* await api.updateTodo(mutation.original.id, mutation.changes)
|
|
219
|
-
*
|
|
220
|
-
* // Refetch related collections when this item changes
|
|
221
|
-
* await Promise.all([
|
|
222
|
-
* collection.utils.refetch(), // Refetch this collection
|
|
223
|
-
* usersCollection.utils.refetch(), // Refetch users
|
|
224
|
-
* tagsCollection.utils.refetch() // Refetch tags
|
|
225
|
-
* ])
|
|
226
|
-
*
|
|
227
|
-
* return { refetch: false } // Skip automatic refetch since we handled it manually
|
|
228
|
-
* }
|
|
229
|
-
*/
|
|
230
|
-
onUpdate?: UpdateMutationFn<ResolveType<TExplicit, TSchema, TQueryFn>>
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Optional asynchronous handler function called before a delete operation
|
|
234
|
-
* @param params Object containing transaction and collection information
|
|
235
|
-
* @returns Promise resolving to void or { refetch?: boolean } to control refetching
|
|
236
|
-
* @example
|
|
237
|
-
* // Basic query collection delete handler
|
|
238
|
-
* onDelete: async ({ transaction }) => {
|
|
239
|
-
* const mutation = transaction.mutations[0]
|
|
240
|
-
* await api.deleteTodo(mutation.original.id)
|
|
241
|
-
* // Automatically refetches query after delete
|
|
242
|
-
* }
|
|
243
|
-
*
|
|
244
|
-
* @example
|
|
245
|
-
* // Delete handler with refetch control
|
|
246
|
-
* onDelete: async ({ transaction }) => {
|
|
247
|
-
* const mutation = transaction.mutations[0]
|
|
248
|
-
* await api.deleteTodo(mutation.original.id)
|
|
249
|
-
* return { refetch: false } // Skip automatic refetch
|
|
250
|
-
* }
|
|
251
|
-
*
|
|
252
|
-
* @example
|
|
253
|
-
* // Delete handler with multiple items
|
|
254
|
-
* onDelete: async ({ transaction }) => {
|
|
255
|
-
* const keysToDelete = transaction.mutations.map(m => m.key)
|
|
256
|
-
* await api.deleteTodos(keysToDelete)
|
|
257
|
-
* // Will refetch query to get updated data
|
|
258
|
-
* }
|
|
259
|
-
*
|
|
260
|
-
* @example
|
|
261
|
-
* // Delete handler with related collection refetch
|
|
262
|
-
* onDelete: async ({ transaction, collection }) => {
|
|
263
|
-
* const mutation = transaction.mutations[0]
|
|
264
|
-
* await api.deleteTodo(mutation.original.id)
|
|
265
|
-
*
|
|
266
|
-
* // Refetch related collections when this item is deleted
|
|
267
|
-
* await Promise.all([
|
|
268
|
-
* collection.utils.refetch(), // Refetch this collection
|
|
269
|
-
* usersCollection.utils.refetch(), // Refetch users
|
|
270
|
-
* projectsCollection.utils.refetch() // Refetch projects
|
|
271
|
-
* ])
|
|
272
|
-
*
|
|
273
|
-
* return { refetch: false } // Skip automatic refetch since we handled it manually
|
|
274
|
-
* }
|
|
275
|
-
*/
|
|
276
|
-
onDelete?: DeleteMutationFn<ResolveType<TExplicit, TSchema, TQueryFn>>
|
|
277
|
-
|
|
278
109
|
/**
|
|
279
110
|
* Metadata to pass to the query.
|
|
280
111
|
* Available in queryFn via context.meta
|
|
@@ -351,18 +182,13 @@ export interface QueryCollectionUtils<
|
|
|
351
182
|
* This integrates TanStack Query with TanStack DB for automatic synchronization.
|
|
352
183
|
*
|
|
353
184
|
* Supports automatic type inference following the priority order:
|
|
354
|
-
* 1.
|
|
355
|
-
* 2.
|
|
356
|
-
* 3. QueryFn return type inference (third priority)
|
|
357
|
-
* 4. Fallback to Record<string, unknown>
|
|
185
|
+
* 1. Schema inference (highest priority)
|
|
186
|
+
* 2. QueryFn return type inference (second priority)
|
|
358
187
|
*
|
|
359
|
-
* @template
|
|
360
|
-
* @template TSchema - The schema type for validation and type inference (second priority)
|
|
361
|
-
* @template TQueryFn - The queryFn type for inferring return type (third priority)
|
|
188
|
+
* @template T - Type of the schema if a schema is provided otherwise it is the type of the values returned by the queryFn
|
|
362
189
|
* @template TError - The type of errors that can occur during queries
|
|
363
190
|
* @template TQueryKey - The type of the query key
|
|
364
191
|
* @template TKey - The type of the item keys
|
|
365
|
-
* @template TInsertInput - The type accepted for insert operations
|
|
366
192
|
* @param config - Configuration options for the Query collection
|
|
367
193
|
* @returns Collection options with utilities for direct writes and manual operations
|
|
368
194
|
*
|
|
@@ -381,7 +207,7 @@ export interface QueryCollectionUtils<
|
|
|
381
207
|
* )
|
|
382
208
|
*
|
|
383
209
|
* @example
|
|
384
|
-
* // Explicit type
|
|
210
|
+
* // Explicit type
|
|
385
211
|
* const todosCollection = createCollection<Todo>(
|
|
386
212
|
* queryCollectionOptions({
|
|
387
213
|
* queryKey: ['todos'],
|
|
@@ -392,7 +218,7 @@ export interface QueryCollectionUtils<
|
|
|
392
218
|
* )
|
|
393
219
|
*
|
|
394
220
|
* @example
|
|
395
|
-
* // Schema inference
|
|
221
|
+
* // Schema inference
|
|
396
222
|
* const todosCollection = createCollection(
|
|
397
223
|
* queryCollectionOptions({
|
|
398
224
|
* queryKey: ['todos'],
|
|
@@ -423,30 +249,62 @@ export interface QueryCollectionUtils<
|
|
|
423
249
|
* })
|
|
424
250
|
* )
|
|
425
251
|
*/
|
|
252
|
+
|
|
253
|
+
// Overload for when schema is provided
|
|
426
254
|
export function queryCollectionOptions<
|
|
427
|
-
|
|
428
|
-
TSchema extends StandardSchemaV1 = never,
|
|
429
|
-
TQueryFn extends (
|
|
430
|
-
context: QueryFunctionContext<any>
|
|
431
|
-
) => Promise<Array<any>> = (
|
|
432
|
-
context: QueryFunctionContext<any>
|
|
433
|
-
) => Promise<Array<any>>,
|
|
255
|
+
T extends StandardSchemaV1,
|
|
434
256
|
TError = unknown,
|
|
435
257
|
TQueryKey extends QueryKey = QueryKey,
|
|
436
258
|
TKey extends string | number = string | number,
|
|
437
|
-
TInsertInput extends object = ResolveType<TExplicit, TSchema, TQueryFn>,
|
|
438
259
|
>(
|
|
439
|
-
config: QueryCollectionConfig<
|
|
440
|
-
|
|
260
|
+
config: QueryCollectionConfig<
|
|
261
|
+
InferSchemaOutput<T>,
|
|
262
|
+
(
|
|
263
|
+
context: QueryFunctionContext<any>
|
|
264
|
+
) => Promise<Array<InferSchemaOutput<T>>>,
|
|
265
|
+
TError,
|
|
266
|
+
TQueryKey,
|
|
267
|
+
TKey,
|
|
268
|
+
T
|
|
269
|
+
> & {
|
|
270
|
+
schema: T
|
|
271
|
+
}
|
|
272
|
+
): CollectionConfig<InferSchemaOutput<T>, TKey, T> & {
|
|
273
|
+
schema: T
|
|
441
274
|
utils: QueryCollectionUtils<
|
|
442
|
-
|
|
275
|
+
InferSchemaOutput<T>,
|
|
443
276
|
TKey,
|
|
444
|
-
|
|
277
|
+
InferSchemaInput<T>,
|
|
445
278
|
TError
|
|
446
279
|
>
|
|
447
|
-
}
|
|
448
|
-
type TItem = ResolveType<TExplicit, TSchema, TQueryFn>
|
|
280
|
+
}
|
|
449
281
|
|
|
282
|
+
// Overload for when no schema is provided
|
|
283
|
+
export function queryCollectionOptions<
|
|
284
|
+
T extends object,
|
|
285
|
+
TError = unknown,
|
|
286
|
+
TQueryKey extends QueryKey = QueryKey,
|
|
287
|
+
TKey extends string | number = string | number,
|
|
288
|
+
>(
|
|
289
|
+
config: QueryCollectionConfig<
|
|
290
|
+
T,
|
|
291
|
+
(context: QueryFunctionContext<any>) => Promise<Array<T>>,
|
|
292
|
+
TError,
|
|
293
|
+
TQueryKey,
|
|
294
|
+
TKey
|
|
295
|
+
> & {
|
|
296
|
+
schema?: never // prohibit schema
|
|
297
|
+
}
|
|
298
|
+
): CollectionConfig<T, TKey> & {
|
|
299
|
+
schema?: never // no schema in the result
|
|
300
|
+
utils: QueryCollectionUtils<T, TKey, T, TError>
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function queryCollectionOptions(
|
|
304
|
+
config: QueryCollectionConfig<Record<string, unknown>>
|
|
305
|
+
): CollectionConfig & {
|
|
306
|
+
utils: QueryCollectionUtils
|
|
307
|
+
} {
|
|
450
308
|
const {
|
|
451
309
|
queryKey,
|
|
452
310
|
queryFn,
|
|
@@ -485,21 +343,21 @@ export function queryCollectionOptions<
|
|
|
485
343
|
}
|
|
486
344
|
|
|
487
345
|
/** The last error encountered by the query */
|
|
488
|
-
let lastError:
|
|
346
|
+
let lastError: any
|
|
489
347
|
/** The number of consecutive sync failures */
|
|
490
348
|
let errorCount = 0
|
|
491
349
|
/** The timestamp for when the query most recently returned the status as "error" */
|
|
492
350
|
let lastErrorUpdatedAt = 0
|
|
493
351
|
|
|
494
|
-
const internalSync: SyncConfig<
|
|
352
|
+
const internalSync: SyncConfig<any>[`sync`] = (params) => {
|
|
495
353
|
const { begin, write, commit, markReady, collection } = params
|
|
496
354
|
|
|
497
355
|
const observerOptions: QueryObserverOptions<
|
|
498
|
-
Array<
|
|
499
|
-
|
|
500
|
-
Array<
|
|
501
|
-
Array<
|
|
502
|
-
|
|
356
|
+
Array<any>,
|
|
357
|
+
any,
|
|
358
|
+
Array<any>,
|
|
359
|
+
Array<any>,
|
|
360
|
+
any
|
|
503
361
|
> = {
|
|
504
362
|
queryKey: queryKey,
|
|
505
363
|
queryFn: queryFn,
|
|
@@ -514,11 +372,11 @@ export function queryCollectionOptions<
|
|
|
514
372
|
}
|
|
515
373
|
|
|
516
374
|
const localObserver = new QueryObserver<
|
|
517
|
-
Array<
|
|
518
|
-
|
|
519
|
-
Array<
|
|
520
|
-
Array<
|
|
521
|
-
|
|
375
|
+
Array<any>,
|
|
376
|
+
any,
|
|
377
|
+
Array<any>,
|
|
378
|
+
Array<any>,
|
|
379
|
+
any
|
|
522
380
|
>(queryClient, observerOptions)
|
|
523
381
|
|
|
524
382
|
type UpdateHandler = Parameters<typeof localObserver.subscribe>[0]
|
|
@@ -542,7 +400,7 @@ export function queryCollectionOptions<
|
|
|
542
400
|
}
|
|
543
401
|
|
|
544
402
|
const currentSyncedItems = new Map(collection.syncedData)
|
|
545
|
-
const newItemsMap = new Map<string | number,
|
|
403
|
+
const newItemsMap = new Map<string | number, any>()
|
|
546
404
|
newItemsArray.forEach((item) => {
|
|
547
405
|
const key = getKey(item)
|
|
548
406
|
newItemsMap.set(key, item)
|
|
@@ -645,14 +503,14 @@ export function queryCollectionOptions<
|
|
|
645
503
|
collection: any
|
|
646
504
|
queryClient: QueryClient
|
|
647
505
|
queryKey: Array<unknown>
|
|
648
|
-
getKey: (item:
|
|
506
|
+
getKey: (item: any) => string | number
|
|
649
507
|
begin: () => void
|
|
650
|
-
write: (message: Omit<ChangeMessage<
|
|
508
|
+
write: (message: Omit<ChangeMessage<any>, `key`>) => void
|
|
651
509
|
commit: () => void
|
|
652
510
|
} | null = null
|
|
653
511
|
|
|
654
512
|
// Enhanced internalSync that captures write functions for manual use
|
|
655
|
-
const enhancedInternalSync: SyncConfig<
|
|
513
|
+
const enhancedInternalSync: SyncConfig<any>[`sync`] = (params) => {
|
|
656
514
|
const { begin, write, commit, collection } = params
|
|
657
515
|
|
|
658
516
|
// Store references for manual write operations
|
|
@@ -660,7 +518,7 @@ export function queryCollectionOptions<
|
|
|
660
518
|
collection,
|
|
661
519
|
queryClient,
|
|
662
520
|
queryKey: queryKey as unknown as Array<unknown>,
|
|
663
|
-
getKey: getKey as (item:
|
|
521
|
+
getKey: getKey as (item: any) => string | number,
|
|
664
522
|
begin,
|
|
665
523
|
write,
|
|
666
524
|
commit,
|
|
@@ -671,13 +529,13 @@ export function queryCollectionOptions<
|
|
|
671
529
|
}
|
|
672
530
|
|
|
673
531
|
// Create write utils using the manual-sync module
|
|
674
|
-
const writeUtils = createWriteUtils<
|
|
532
|
+
const writeUtils = createWriteUtils<any, string | number, any>(
|
|
675
533
|
() => writeContext
|
|
676
534
|
)
|
|
677
535
|
|
|
678
536
|
// Create wrapper handlers for direct persistence operations that handle refetching
|
|
679
537
|
const wrappedOnInsert = onInsert
|
|
680
|
-
? async (params: InsertMutationFnParams<
|
|
538
|
+
? async (params: InsertMutationFnParams<any>) => {
|
|
681
539
|
const handlerResult = (await onInsert(params)) ?? {}
|
|
682
540
|
const shouldRefetch =
|
|
683
541
|
(handlerResult as { refetch?: boolean }).refetch !== false
|
|
@@ -691,7 +549,7 @@ export function queryCollectionOptions<
|
|
|
691
549
|
: undefined
|
|
692
550
|
|
|
693
551
|
const wrappedOnUpdate = onUpdate
|
|
694
|
-
? async (params: UpdateMutationFnParams<
|
|
552
|
+
? async (params: UpdateMutationFnParams<any>) => {
|
|
695
553
|
const handlerResult = (await onUpdate(params)) ?? {}
|
|
696
554
|
const shouldRefetch =
|
|
697
555
|
(handlerResult as { refetch?: boolean }).refetch !== false
|
|
@@ -705,7 +563,7 @@ export function queryCollectionOptions<
|
|
|
705
563
|
: undefined
|
|
706
564
|
|
|
707
565
|
const wrappedOnDelete = onDelete
|
|
708
|
-
? async (params: DeleteMutationFnParams<
|
|
566
|
+
? async (params: DeleteMutationFnParams<any>) => {
|
|
709
567
|
const handlerResult = (await onDelete(params)) ?? {}
|
|
710
568
|
const shouldRefetch =
|
|
711
569
|
(handlerResult as { refetch?: boolean }).refetch !== false
|