@tanstack/react-db 0.4.1 → 0.5.0

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.
@@ -171,6 +171,9 @@ function useLiveInfiniteQuery(queryFnOrCollection, config, deps) {
171
171
  status: snapshot.status,
172
172
  isLoading: snapshot.isLoading,
173
173
  isReady: snapshot.isReady,
174
+ persistedStatus: snapshot.persistedStatus,
175
+ isPersistedReady: snapshot.isPersistedReady,
176
+ persistedError: snapshot.persistedError,
174
177
  isIdle: snapshot.isIdle,
175
178
  isError: snapshot.isError,
176
179
  isCleanedUp: snapshot.isCleanedUp,
@@ -1 +1 @@
1
- {"version":3,"file":"useLiveInfiniteQuery.cjs","sources":["../../src/useLiveInfiniteQuery.ts"],"sourcesContent":["'use client'\n\nimport { useCallback, useRef, useSyncExternalStore } from 'react'\nimport {\n assertLiveQueryWindowManyResult,\n compareLiveQueryWindowDependencies,\n createLiveQueryCollection,\n createLiveQueryWindowController,\n fetchNextLiveQueryWindowPage,\n getLiveQueryWindowCollectionWarning,\n getLiveQueryWindowInputKind,\n normalizeLiveQueryWindowPageSize,\n resolveLiveQueryWindowInput,\n shouldPreserveLiveQueryWindowPageCount,\n} from '@tanstack/db'\nimport { useOptionalDbClient } from './DbProvider'\nimport {\n prepareDerivedQuery,\n prepareQueryValue,\n warnDeprecatedDepsArray,\n warnUnhashableDerivedIdentity,\n} from './useLiveQuery'\nimport type {\n DerivedIdentityProfiler,\n LiveQueryKey,\n useLiveQuery,\n} from './useLiveQuery'\nimport type {\n Collection,\n CollectionImpl as CollectionImplType,\n Context,\n DbClient,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryWindowController,\n NonSingleResult,\n QueryBuilder,\n} from '@tanstack/db'\n\n// Live queries created here are cleaned up immediately (0 disables GC).\nconst DEFAULT_GC_TIME_MS = 1\nconst unpreparedQueryValue = Symbol(`unpreparedQueryValue`)\n\n// Keep the generic parameter for existing typed config wrappers.\nexport type UseLiveInfiniteQueryConfig<_TContext extends Context> = {\n /**\n * Explicit identity for queries that contain opaque functional variants or\n * are hot enough that deriving identity from structured IR is too expensive.\n * Structured queries should omit this so DB can derive identity directly.\n */\n queryKey?: LiveQueryKey\n /** Override the nearest DbProvider for this query. */\n client?: DbClient\n pageSize?: number\n /** First result-page label, not a server cursor or remote offset. */\n initialPageParam?: number\n}\n\nexport type UseLiveInfiniteQueryReturn<TContext extends Context> = Omit<\n ReturnType<typeof useLiveQuery<TContext>>,\n `data`\n> & {\n data: InferResultType<TContext>\n pages: Array<Array<InferResultType<TContext>[number]>>\n pageParams: Array<number>\n fetchNextPage: () => Promise<void>\n hasNextPage: boolean\n isFetchingNextPage: boolean\n error: unknown\n}\n\ntype EnabledLiveQueryReturn<TContext extends Context> = ReturnType<\n typeof useLiveQuery<TContext>\n>\n\ntype InfiniteQueryRenderState = {\n inputKind: `collection` | `query`\n inputCollection: Collection<any, any, any> | null\n inputQuery: unknown\n client: DbClient | undefined\n identityMode: `collection` | `queryKey` | `legacyDeps` | `derived`\n dependencies: Array<unknown> | null\n pageSize: number\n initialPageParam: number\n collection: Collection<any, any, any>\n controller: LiveQueryWindowController<any, any>\n warning: string | null\n warned: boolean\n deferredCollections: Set<\n CollectionImplType<any, string | number, any, any, any>\n >\n}\n\n/**\n * Create an infinite query using a query function with live updates.\n *\n * Uses `utils.setWindow()` to dynamically adjust the limit/offset window\n * without recreating the live query collection on each page change.\n *\n * @param queryFn - Query function that defines what data to fetch. Must include `.orderBy()` for setWindow to work.\n * @param config - Configuration including pageSize and an optional initial page label\n * @param deps - Deprecated array of dependencies that trigger query re-execution when changed\n * @returns Object with pages, data, and pagination controls\n */\n\n// Overload for pre-created collection (non-single result)\nexport function useLiveInfiniteQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult,\n config: UseLiveInfiniteQueryConfig<any>,\n): UseLiveInfiniteQueryReturn<any>\n\n// Overload for query function\nexport function useLiveInfiniteQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n config: UseLiveInfiniteQueryConfig<TContext>,\n deps?: Array<unknown>,\n): UseLiveInfiniteQueryReturn<TContext>\n\n// Implementation\nexport function useLiveInfiniteQuery<TContext extends Context>(\n queryFnOrCollection: any,\n config: UseLiveInfiniteQueryConfig<TContext>,\n deps?: Array<unknown>,\n): UseLiveInfiniteQueryReturn<TContext> {\n if (`getNextPageParam` in config) {\n throw new Error(\n `getNextPageParam is not supported by useLiveInfiniteQuery. Use an on-demand collection and fulfill meta.loadSubsetOptions in queryFn for server pagination.`,\n )\n }\n const pageSize = normalizeLiveQueryWindowPageSize(config.pageSize)\n const initialPageParam = config.initialPageParam ?? 0\n const contextDbClient = useOptionalDbClient()\n const dbClient = config.client ?? contextDbClient\n\n const inputIsCollection =\n getLiveQueryWindowInputKind(queryFnOrCollection) === `collection`\n\n const committedRef = useRef<InfiniteQueryRenderState | null>(null)\n const committed = committedRef.current\n const inputKind = inputIsCollection ? `collection` : `query`\n const derivedIdentityProfilerRef = useRef<DerivedIdentityProfiler>({\n renderCount: 0,\n totalMs: 0,\n maxMs: 0,\n warned: false,\n })\n const legacyUnhashableIdentityRef = useRef<Array<unknown>>([\n `legacy-unhashable`,\n ])\n const deferredCollections = new Set<\n CollectionImplType<any, string | number, any, any, any>\n >()\n\n let preparedQueryValue: unknown | typeof unpreparedQueryValue =\n unpreparedQueryValue\n let identityDeps: ReadonlyArray<unknown> = []\n let identityMode: InfiniteQueryRenderState[`identityMode`] = `collection`\n\n if (!inputIsCollection) {\n if (config.queryKey !== undefined) {\n identityMode = `queryKey`\n identityDeps = config.queryKey\n } else if (deps !== undefined) {\n identityMode = `legacyDeps`\n identityDeps = deps\n warnDeprecatedDepsArray(`useLiveInfiniteQuery`)\n } else if (\n committed?.identityMode === `derived` &&\n committed.inputQuery === queryFnOrCollection &&\n committed.client === dbClient\n ) {\n identityMode = `derived`\n identityDeps = committed.dependencies ?? []\n } else {\n identityMode = `derived`\n const preparation = prepareDerivedQuery(\n queryFnOrCollection,\n dbClient,\n derivedIdentityProfilerRef.current,\n deferredCollections,\n )\n preparedQueryValue = preparation.value\n if (preparation.status === `hashable`) {\n identityDeps = preparation.identityDeps\n } else {\n warnUnhashableDerivedIdentity(preparation.error)\n identityDeps = legacyUnhashableIdentityRef.current\n }\n }\n }\n\n const usesLegacyDeps =\n !inputIsCollection && config.queryKey === undefined && deps !== undefined\n const dependencyComparison = compareLiveQueryWindowDependencies(\n committed?.dependencies,\n identityDeps,\n )\n const sameClient = committed?.client === dbClient\n const dependenciesChanged =\n !inputIsCollection &&\n (!sameClient ||\n (usesLegacyDeps\n ? dependencyComparison.changed\n : !dependencyComparison.structurallyEqual))\n const dependenciesStructurallyEqual =\n usesLegacyDeps && sameClient && dependencyComparison.structurallyEqual\n const needsNewCollection =\n committed === null ||\n committed.inputKind !== inputKind ||\n (inputIsCollection && committed.inputCollection !== queryFnOrCollection) ||\n dependenciesChanged\n const pageShapeChanged =\n committed === null ||\n committed.pageSize !== pageSize ||\n committed.initialPageParam !== initialPageParam\n const needsNewController =\n committed === null || needsNewCollection || pageShapeChanged\n\n let renderState = committed\n if (needsNewController) {\n let collection = committed?.collection\n let warning: string | null = null\n\n if (needsNewCollection) {\n let inputValue = queryFnOrCollection\n if (!inputIsCollection) {\n if (preparedQueryValue === unpreparedQueryValue) {\n preparedQueryValue = prepareQueryValue(\n queryFnOrCollection,\n dbClient,\n deferredCollections,\n )\n }\n inputValue = () => preparedQueryValue\n }\n const input = resolveLiveQueryWindowInput<TContext>(inputValue)\n if (input.kind === `collection`) {\n collection = input.collection\n } else {\n // Wrap the query with the first page's peek-ahead window; the controller\n // grows the limit from here via setWindow.\n collection = createLiveQueryCollection({\n query: input.query.limit(pageSize + 1).offset(0),\n // Construction happens during render. Synchronization starts only when\n // useSyncExternalStore commits the controller subscription.\n startSync: false,\n gcTime: DEFAULT_GC_TIME_MS,\n })\n }\n }\n\n if (!collection) {\n throw new Error(`useLiveInfiniteQuery: Failed to create a collection.`)\n }\n\n if (inputIsCollection) {\n warning =\n getLiveQueryWindowCollectionWarning(collection, pageSize + 1) ?? null\n } else {\n assertLiveQueryWindowManyResult(collection)\n }\n\n const canPreservePageCount = shouldPreserveLiveQueryWindowPageCount({\n hasPreviousController: committed !== null,\n previousInputKind: committed?.inputKind,\n inputKind,\n sameCollection:\n inputIsCollection && committed?.inputCollection === collection,\n dependenciesChanged,\n dependenciesStructurallyEqual,\n pageShapeChanged,\n })\n const previousPageCount = committed\n ? Math.max(1, committed.controller.getSnapshot().pages.length)\n : 1\n const initialPageCount = canPreservePageCount ? previousPageCount : 1\n renderState = {\n inputKind,\n inputCollection: inputIsCollection ? collection : null,\n inputQuery: inputIsCollection ? null : queryFnOrCollection,\n client: dbClient,\n identityMode,\n dependencies: inputIsCollection ? null : [...identityDeps],\n pageSize,\n initialPageParam,\n collection,\n controller: createLiveQueryWindowController(collection, {\n pageSize,\n initialPageParam,\n initialPageCount,\n }),\n warning,\n warned: false,\n deferredCollections,\n }\n }\n const currentRenderState = renderState!\n const controller = currentRenderState.controller\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n const unsubscribe = controller.subscribe(onStoreChange)\n committedRef.current = currentRenderState\n if (currentRenderState.warning && !currentRenderState.warned) {\n currentRenderState.warned = true\n console.warn(currentRenderState.warning)\n }\n for (const collection of currentRenderState.deferredCollections) {\n collection._resumeSyncStart()\n }\n currentRenderState.deferredCollections.clear()\n return unsubscribe\n },\n [controller, currentRenderState],\n )\n const getSnapshot = useCallback(() => controller.getSnapshot(), [controller])\n const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n\n const fetchNextPage = useCallback(\n () => fetchNextLiveQueryWindowPage(controller),\n [controller],\n )\n\n return {\n data: snapshot.data as InferResultType<TContext>,\n state: snapshot.state as EnabledLiveQueryReturn<TContext>[`state`],\n status: snapshot.status as EnabledLiveQueryReturn<TContext>[`status`],\n isLoading: snapshot.isLoading,\n isReady: snapshot.isReady,\n isIdle: snapshot.isIdle,\n isError: snapshot.isError,\n isCleanedUp: snapshot.isCleanedUp,\n collection:\n snapshot.collection as EnabledLiveQueryReturn<TContext>[`collection`],\n isEnabled: snapshot.isEnabled,\n pages: snapshot.pages as Array<Array<InferResultType<TContext>[number]>>,\n pageParams: snapshot.pageParams as Array<number>,\n fetchNextPage,\n hasNextPage: snapshot.hasNextPage,\n isFetchingNextPage: snapshot.isFetchingNextPage,\n error: snapshot.error,\n }\n}\n"],"names":["warnDeprecatedDepsArray","prepareDerivedQuery","warnUnhashableDerivedIdentity","compareLiveQueryWindowDependencies","prepareQueryValue","assertLiveQueryWindowManyResult","useCallback"],"mappings":";;;;;;;AAwCA;AACA;AAkFO;AAKL;AACE;AAAU;AACR;AAAA;AAGJ;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AAAmE;AACpD;AACJ;AACF;AACC;AAEV;AAA2D;AACzD;AAEF;AAIA;AAEA;AACA;AAEA;AACE;AACE;AACA;AAAsB;AAEtB;AACA;AACAA;AAA8C;AAM9C;AACA;AAAyC;AAEzC;AACA;AAAoBC;AAClB;AACA;AAC2B;AAC3B;AAEF;AACA;AACE;AAA2B;AAE3BC;AACA;AAA2C;AAC7C;AACF;AAGF;AAEA;AAA6BC;AAChB;AACX;AAEF;AACA;AAMA;AAEA;AAKA;AAIA;AAGA;AACA;AACE;AACA;AAEA;AACE;AACA;AACE;AACE;AAAqBC;AACnB;AACA;AACA;AAAA;AAGJ;AAAmB;AAErB;AACA;AACE;AAAmB;AAInB;AAAuC;AACU;AAAA;AAAA;AAGpC;AACH;AACT;AACH;AAGF;AACE;AAAsE;AAGxE;AACE;AACmE;AAEnEC;AAA0C;AAG5C;AAAoE;AAC7B;AACP;AAC9B;AAEsD;AACtD;AACA;AACA;AAEF;AAGA;AACA;AAAc;AACZ;AACkD;AACX;AAC/B;AACR;AACyD;AACzD;AACA;AACA;AACwD;AACtD;AACA;AACA;AACD;AACD;AACQ;AACR;AAAA;AAGJ;AACA;AAEA;AAAkBC;AAEd;AACA;AACA;AACE;AACA;AAAuC;AAEzC;AACE;AAAW;AAEb;AACA;AAAO;AACT;AAC+B;AAEjC;AACA;AAEA;AAAsBA;AACyB;AAClC;AAGb;AAAO;AACU;AACC;AACC;AACG;AACF;AACD;AACC;AACI;AAEX;AACS;AACJ;AACK;AACrB;AACsB;AACO;AACb;AAEpB;;"}
1
+ {"version":3,"file":"useLiveInfiniteQuery.cjs","sources":["../../src/useLiveInfiniteQuery.ts"],"sourcesContent":["'use client'\n\nimport { useCallback, useRef, useSyncExternalStore } from 'react'\nimport {\n assertLiveQueryWindowManyResult,\n compareLiveQueryWindowDependencies,\n createLiveQueryCollection,\n createLiveQueryWindowController,\n fetchNextLiveQueryWindowPage,\n getLiveQueryWindowCollectionWarning,\n getLiveQueryWindowInputKind,\n normalizeLiveQueryWindowPageSize,\n resolveLiveQueryWindowInput,\n shouldPreserveLiveQueryWindowPageCount,\n} from '@tanstack/db'\nimport { useOptionalDbClient } from './DbProvider'\nimport {\n prepareDerivedQuery,\n prepareQueryValue,\n warnDeprecatedDepsArray,\n warnUnhashableDerivedIdentity,\n} from './useLiveQuery'\nimport type {\n DerivedIdentityProfiler,\n LiveQueryKey,\n useLiveQuery,\n} from './useLiveQuery'\nimport type {\n Collection,\n CollectionImpl as CollectionImplType,\n CollectionStatus,\n Context,\n DbClient,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryPersistedStatus,\n LiveQueryWindowController,\n NonSingleResult,\n QueryBuilder,\n} from '@tanstack/db'\n\n// Live queries created here are cleaned up immediately (0 disables GC).\nconst DEFAULT_GC_TIME_MS = 1\nconst unpreparedQueryValue = Symbol(`unpreparedQueryValue`)\n\n// Keep the generic parameter for existing typed config wrappers.\nexport type UseLiveInfiniteQueryConfig<_TContext extends Context> = {\n /**\n * Explicit identity for queries that contain opaque functional variants or\n * are hot enough that deriving identity from structured IR is too expensive.\n * Structured queries should omit this so DB can derive identity directly.\n */\n queryKey?: LiveQueryKey\n /** Override the nearest DbProvider for this query. */\n client?: DbClient\n pageSize?: number\n /** First result-page label, not a server cursor or remote offset. */\n initialPageParam?: number\n}\n\nexport type UseLiveInfiniteQueryReturn<TContext extends Context> = Omit<\n ReturnType<typeof useLiveQuery<TContext>>,\n `data`\n> & {\n data: InferResultType<TContext>\n pages: Array<Array<InferResultType<TContext>[number]>>\n pageParams: Array<number>\n fetchNextPage: () => Promise<void>\n hasNextPage: boolean\n isFetchingNextPage: boolean\n error: unknown\n}\n\nexport type UseLiveInfiniteQueryReturnWithCollection<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n> = {\n data: Array<TResult>\n state: Map<TKey, TResult>\n collection: Collection<TResult, TKey, TUtils> & NonSingleResult\n status: CollectionStatus\n isLoading: boolean\n isReady: boolean\n persistedStatus: LiveQueryPersistedStatus\n isPersistedReady: boolean\n persistedError: unknown | undefined\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: true\n pages: Array<Array<TResult>>\n pageParams: Array<number>\n fetchNextPage: () => Promise<void>\n hasNextPage: boolean\n isFetchingNextPage: boolean\n error: unknown\n}\n\ntype EnabledLiveQueryReturn<TContext extends Context> = ReturnType<\n typeof useLiveQuery<TContext>\n>\n\ntype InfiniteQueryRenderState = {\n inputKind: `collection` | `query`\n inputCollection: Collection<any, any, any> | null\n inputQuery: unknown\n client: DbClient | undefined\n identityMode: `collection` | `queryKey` | `legacyDeps` | `derived`\n dependencies: Array<unknown> | null\n pageSize: number\n initialPageParam: number\n collection: Collection<any, any, any>\n controller: LiveQueryWindowController<any, any>\n warning: string | null\n warned: boolean\n deferredCollections: Set<\n CollectionImplType<any, string | number, any, any, any>\n >\n}\n\n/**\n * Create an infinite query using a query function with live updates.\n *\n * Uses `utils.setWindow()` to dynamically adjust the limit/offset window\n * without recreating the live query collection on each page change.\n *\n * @param queryFn - Query function that defines what data to fetch. Must include `.orderBy()` for setWindow to work.\n * @param config - Configuration including pageSize and an optional initial page label\n * @param deps - Deprecated array of dependencies that trigger query re-execution when changed\n * @returns Object with pages, data, and pagination controls\n */\n\n// Overload for pre-created collection (non-single result)\nexport function useLiveInfiniteQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult,\n config: UseLiveInfiniteQueryConfig<any>,\n): UseLiveInfiniteQueryReturnWithCollection<TResult, TKey, TUtils>\n\n// Overload for query function\nexport function useLiveInfiniteQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n config: UseLiveInfiniteQueryConfig<TContext>,\n deps?: Array<unknown>,\n): UseLiveInfiniteQueryReturn<TContext>\n\n// Implementation\nexport function useLiveInfiniteQuery<TContext extends Context>(\n queryFnOrCollection: any,\n config: UseLiveInfiniteQueryConfig<TContext>,\n deps?: Array<unknown>,\n): UseLiveInfiniteQueryReturn<TContext> {\n if (`getNextPageParam` in config) {\n throw new Error(\n `getNextPageParam is not supported by useLiveInfiniteQuery. Use an on-demand collection and fulfill meta.loadSubsetOptions in queryFn for server pagination.`,\n )\n }\n const pageSize = normalizeLiveQueryWindowPageSize(config.pageSize)\n const initialPageParam = config.initialPageParam ?? 0\n const contextDbClient = useOptionalDbClient()\n const dbClient = config.client ?? contextDbClient\n\n const inputIsCollection =\n getLiveQueryWindowInputKind(queryFnOrCollection) === `collection`\n\n const committedRef = useRef<InfiniteQueryRenderState | null>(null)\n const committed = committedRef.current\n const inputKind = inputIsCollection ? `collection` : `query`\n const derivedIdentityProfilerRef = useRef<DerivedIdentityProfiler>({\n renderCount: 0,\n totalMs: 0,\n maxMs: 0,\n warned: false,\n })\n const legacyUnhashableIdentityRef = useRef<Array<unknown>>([\n `legacy-unhashable`,\n ])\n const deferredCollections = new Set<\n CollectionImplType<any, string | number, any, any, any>\n >()\n\n let preparedQueryValue: unknown | typeof unpreparedQueryValue =\n unpreparedQueryValue\n let identityDeps: ReadonlyArray<unknown> = []\n let identityMode: InfiniteQueryRenderState[`identityMode`] = `collection`\n\n if (!inputIsCollection) {\n if (config.queryKey !== undefined) {\n identityMode = `queryKey`\n identityDeps = config.queryKey\n } else if (deps !== undefined) {\n identityMode = `legacyDeps`\n identityDeps = deps\n warnDeprecatedDepsArray(`useLiveInfiniteQuery`)\n } else if (\n committed?.identityMode === `derived` &&\n committed.inputQuery === queryFnOrCollection &&\n committed.client === dbClient\n ) {\n identityMode = `derived`\n identityDeps = committed.dependencies ?? []\n } else {\n identityMode = `derived`\n const preparation = prepareDerivedQuery(\n queryFnOrCollection,\n dbClient,\n derivedIdentityProfilerRef.current,\n deferredCollections,\n )\n preparedQueryValue = preparation.value\n if (preparation.status === `hashable`) {\n identityDeps = preparation.identityDeps\n } else {\n warnUnhashableDerivedIdentity(preparation.error)\n identityDeps = legacyUnhashableIdentityRef.current\n }\n }\n }\n\n const usesLegacyDeps =\n !inputIsCollection && config.queryKey === undefined && deps !== undefined\n const dependencyComparison = compareLiveQueryWindowDependencies(\n committed?.dependencies,\n identityDeps,\n )\n const sameClient = committed?.client === dbClient\n const dependenciesChanged =\n !inputIsCollection &&\n (!sameClient ||\n (usesLegacyDeps\n ? dependencyComparison.changed\n : !dependencyComparison.structurallyEqual))\n const dependenciesStructurallyEqual =\n usesLegacyDeps && sameClient && dependencyComparison.structurallyEqual\n const needsNewCollection =\n committed === null ||\n committed.inputKind !== inputKind ||\n (inputIsCollection && committed.inputCollection !== queryFnOrCollection) ||\n dependenciesChanged\n const pageShapeChanged =\n committed === null ||\n committed.pageSize !== pageSize ||\n committed.initialPageParam !== initialPageParam\n const needsNewController =\n committed === null || needsNewCollection || pageShapeChanged\n\n let renderState = committed\n if (needsNewController) {\n let collection = committed?.collection\n let warning: string | null = null\n\n if (needsNewCollection) {\n let inputValue = queryFnOrCollection\n if (!inputIsCollection) {\n if (preparedQueryValue === unpreparedQueryValue) {\n preparedQueryValue = prepareQueryValue(\n queryFnOrCollection,\n dbClient,\n deferredCollections,\n )\n }\n inputValue = () => preparedQueryValue\n }\n const input = resolveLiveQueryWindowInput<TContext>(inputValue)\n if (input.kind === `collection`) {\n collection = input.collection\n } else {\n // Wrap the query with the first page's peek-ahead window; the controller\n // grows the limit from here via setWindow.\n collection = createLiveQueryCollection({\n query: input.query.limit(pageSize + 1).offset(0),\n // Construction happens during render. Synchronization starts only when\n // useSyncExternalStore commits the controller subscription.\n startSync: false,\n gcTime: DEFAULT_GC_TIME_MS,\n })\n }\n }\n\n if (!collection) {\n throw new Error(`useLiveInfiniteQuery: Failed to create a collection.`)\n }\n\n if (inputIsCollection) {\n warning =\n getLiveQueryWindowCollectionWarning(collection, pageSize + 1) ?? null\n } else {\n assertLiveQueryWindowManyResult(collection)\n }\n\n const canPreservePageCount = shouldPreserveLiveQueryWindowPageCount({\n hasPreviousController: committed !== null,\n previousInputKind: committed?.inputKind,\n inputKind,\n sameCollection:\n inputIsCollection && committed?.inputCollection === collection,\n dependenciesChanged,\n dependenciesStructurallyEqual,\n pageShapeChanged,\n })\n const previousPageCount = committed\n ? Math.max(1, committed.controller.getSnapshot().pages.length)\n : 1\n const initialPageCount = canPreservePageCount ? previousPageCount : 1\n renderState = {\n inputKind,\n inputCollection: inputIsCollection ? collection : null,\n inputQuery: inputIsCollection ? null : queryFnOrCollection,\n client: dbClient,\n identityMode,\n dependencies: inputIsCollection ? null : [...identityDeps],\n pageSize,\n initialPageParam,\n collection,\n controller: createLiveQueryWindowController(collection, {\n pageSize,\n initialPageParam,\n initialPageCount,\n }),\n warning,\n warned: false,\n deferredCollections,\n }\n }\n const currentRenderState = renderState!\n const controller = currentRenderState.controller\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n const unsubscribe = controller.subscribe(onStoreChange)\n committedRef.current = currentRenderState\n if (currentRenderState.warning && !currentRenderState.warned) {\n currentRenderState.warned = true\n console.warn(currentRenderState.warning)\n }\n for (const collection of currentRenderState.deferredCollections) {\n collection._resumeSyncStart()\n }\n currentRenderState.deferredCollections.clear()\n return unsubscribe\n },\n [controller, currentRenderState],\n )\n const getSnapshot = useCallback(() => controller.getSnapshot(), [controller])\n const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n\n const fetchNextPage = useCallback(\n () => fetchNextLiveQueryWindowPage(controller),\n [controller],\n )\n\n return {\n data: snapshot.data as InferResultType<TContext>,\n state: snapshot.state as EnabledLiveQueryReturn<TContext>[`state`],\n status: snapshot.status as EnabledLiveQueryReturn<TContext>[`status`],\n isLoading: snapshot.isLoading,\n isReady: snapshot.isReady,\n persistedStatus: snapshot.persistedStatus,\n isPersistedReady: snapshot.isPersistedReady,\n persistedError: snapshot.persistedError,\n isIdle: snapshot.isIdle,\n isError: snapshot.isError,\n isCleanedUp: snapshot.isCleanedUp,\n collection:\n snapshot.collection as EnabledLiveQueryReturn<TContext>[`collection`],\n isEnabled: snapshot.isEnabled,\n pages: snapshot.pages as Array<Array<InferResultType<TContext>[number]>>,\n pageParams: snapshot.pageParams as Array<number>,\n fetchNextPage,\n hasNextPage: snapshot.hasNextPage,\n isFetchingNextPage: snapshot.isFetchingNextPage,\n error: snapshot.error,\n }\n}\n"],"names":["warnDeprecatedDepsArray","prepareDerivedQuery","warnUnhashableDerivedIdentity","compareLiveQueryWindowDependencies","prepareQueryValue","assertLiveQueryWindowManyResult","useCallback"],"mappings":";;;;;;;AA0CA;AACA;AA4GO;AAKL;AACE;AAAU;AACR;AAAA;AAGJ;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AAAmE;AACpD;AACJ;AACF;AACC;AAEV;AAA2D;AACzD;AAEF;AAIA;AAEA;AACA;AAEA;AACE;AACE;AACA;AAAsB;AAEtB;AACA;AACAA;AAA8C;AAM9C;AACA;AAAyC;AAEzC;AACA;AAAoBC;AAClB;AACA;AAC2B;AAC3B;AAEF;AACA;AACE;AAA2B;AAE3BC;AACA;AAA2C;AAC7C;AACF;AAGF;AAEA;AAA6BC;AAChB;AACX;AAEF;AACA;AAMA;AAEA;AAKA;AAIA;AAGA;AACA;AACE;AACA;AAEA;AACE;AACA;AACE;AACE;AAAqBC;AACnB;AACA;AACA;AAAA;AAGJ;AAAmB;AAErB;AACA;AACE;AAAmB;AAInB;AAAuC;AACU;AAAA;AAAA;AAGpC;AACH;AACT;AACH;AAGF;AACE;AAAsE;AAGxE;AACE;AACmE;AAEnEC;AAA0C;AAG5C;AAAoE;AAC7B;AACP;AAC9B;AAEsD;AACtD;AACA;AACA;AAEF;AAGA;AACA;AAAc;AACZ;AACkD;AACX;AAC/B;AACR;AACyD;AACzD;AACA;AACA;AACwD;AACtD;AACA;AACA;AACD;AACD;AACQ;AACR;AAAA;AAGJ;AACA;AAEA;AAAkBC;AAEd;AACA;AACA;AACE;AACA;AAAuC;AAEzC;AACE;AAAW;AAEb;AACA;AAAO;AACT;AAC+B;AAEjC;AACA;AAEA;AAAsBA;AACyB;AAClC;AAGb;AAAO;AACU;AACC;AACC;AACG;AACF;AACQ;AACC;AACF;AACR;AACC;AACI;AAEX;AACS;AACJ;AACK;AACrB;AACsB;AACO;AACb;AAEpB;;"}
@@ -1,5 +1,5 @@
1
1
  import { LiveQueryKey, useLiveQuery } from './useLiveQuery.cjs';
2
- import { Collection, Context, DbClient, InferResultType, InitialQueryBuilder, NonSingleResult, QueryBuilder } from '@tanstack/db';
2
+ import { Collection, CollectionStatus, Context, DbClient, InferResultType, InitialQueryBuilder, LiveQueryPersistedStatus, NonSingleResult, QueryBuilder } from '@tanstack/db';
3
3
  export type UseLiveInfiniteQueryConfig<_TContext extends Context> = {
4
4
  /**
5
5
  * Explicit identity for queries that contain opaque functional variants or
@@ -22,6 +22,27 @@ export type UseLiveInfiniteQueryReturn<TContext extends Context> = Omit<ReturnTy
22
22
  isFetchingNextPage: boolean;
23
23
  error: unknown;
24
24
  };
25
+ export type UseLiveInfiniteQueryReturnWithCollection<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>> = {
26
+ data: Array<TResult>;
27
+ state: Map<TKey, TResult>;
28
+ collection: Collection<TResult, TKey, TUtils> & NonSingleResult;
29
+ status: CollectionStatus;
30
+ isLoading: boolean;
31
+ isReady: boolean;
32
+ persistedStatus: LiveQueryPersistedStatus;
33
+ isPersistedReady: boolean;
34
+ persistedError: unknown | undefined;
35
+ isIdle: boolean;
36
+ isError: boolean;
37
+ isCleanedUp: boolean;
38
+ isEnabled: true;
39
+ pages: Array<Array<TResult>>;
40
+ pageParams: Array<number>;
41
+ fetchNextPage: () => Promise<void>;
42
+ hasNextPage: boolean;
43
+ isFetchingNextPage: boolean;
44
+ error: unknown;
45
+ };
25
46
  /**
26
47
  * Create an infinite query using a query function with live updates.
27
48
  *
@@ -33,5 +54,5 @@ export type UseLiveInfiniteQueryReturn<TContext extends Context> = Omit<ReturnTy
33
54
  * @param deps - Deprecated array of dependencies that trigger query re-execution when changed
34
55
  * @returns Object with pages, data, and pagination controls
35
56
  */
36
- export declare function useLiveInfiniteQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult, config: UseLiveInfiniteQueryConfig<any>): UseLiveInfiniteQueryReturn<any>;
57
+ export declare function useLiveInfiniteQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult, config: UseLiveInfiniteQueryConfig<any>): UseLiveInfiniteQueryReturnWithCollection<TResult, TKey, TUtils>;
37
58
  export declare function useLiveInfiniteQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>, config: UseLiveInfiniteQueryConfig<TContext>, deps?: Array<unknown>): UseLiveInfiniteQueryReturn<TContext>;
@@ -6,6 +6,7 @@ const db = require("@tanstack/db");
6
6
  const DbProvider = require("./DbProvider.cjs");
7
7
  const liveQueryInternals = require("./live-query-internals.cjs");
8
8
  const DEFAULT_GC_TIME_MS = 1;
9
+ const DEFAULT_SUSPENSE_GC_TIME_MS = 5e3;
9
10
  const DERIVED_IDENTITY_SINGLE_RENDER_WARN_MS = 16;
10
11
  const DERIVED_IDENTITY_RENDER_COUNT_WARN_THRESHOLD = 10;
11
12
  const DERIVED_IDENTITY_TOTAL_WARN_MS = 50;
@@ -13,6 +14,41 @@ const warnedDepsCallsites = /* @__PURE__ */ new Set();
13
14
  const warnedDerivedIdentityCallsites = /* @__PURE__ */ new Set();
14
15
  const warnedUnhashableIdentityCallsites = /* @__PURE__ */ new Set();
15
16
  const unpreparedQueryValue = /* @__PURE__ */ Symbol(`unpreparedQueryValue`);
17
+ const unscopedSuspenseCollections = /* @__PURE__ */ new Map();
18
+ const suspenseCollectionsByClient = /* @__PURE__ */ new WeakMap();
19
+ const suspenseSourceIds = /* @__PURE__ */ new WeakMap();
20
+ let nextSuspenseSourceId = 0;
21
+ function getSuspenseSourceId(source) {
22
+ let id = suspenseSourceIds.get(source);
23
+ if (id === void 0) {
24
+ id = ++nextSuspenseSourceId;
25
+ suspenseSourceIds.set(source, id);
26
+ }
27
+ return id;
28
+ }
29
+ function getUnscopedSuspenseKey(preparedValue, queryHash) {
30
+ const query = preparedValue instanceof db.BaseQueryBuilder ? preparedValue : preparedValue && typeof preparedValue === `object` && `query` in preparedValue && preparedValue.query instanceof db.BaseQueryBuilder ? preparedValue.query : void 0;
31
+ if (!query) return queryHash;
32
+ const sourceIds = db.IR.collectCollectionSources(query._getQuery()).map(
33
+ ({ collection }) => getSuspenseSourceId(collection)
34
+ );
35
+ return `${sourceIds.join(`,`)}:${queryHash}`;
36
+ }
37
+ function getSuspenseCollections(client) {
38
+ if (!client) return unscopedSuspenseCollections;
39
+ let collections = suspenseCollectionsByClient.get(client);
40
+ if (!collections) {
41
+ collections = /* @__PURE__ */ new Map();
42
+ suspenseCollectionsByClient.set(client, collections);
43
+ }
44
+ return collections;
45
+ }
46
+ function releaseSuspenseCollection(collections, queryHash, collection) {
47
+ const entry = collections.get(queryHash);
48
+ if (entry?.collection !== collection) return;
49
+ collections.delete(queryHash);
50
+ entry.removeStatusListeners();
51
+ }
16
52
  function warnDeprecatedDepsArray(hookName = `useLiveQuery`) {
17
53
  if (!shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_DEPRECATION_WARNINGS`)) {
18
54
  return;
@@ -38,8 +74,12 @@ function getCurrentTime() {
38
74
  }
39
75
  function getWarningCallsite(stackIndex) {
40
76
  const stack = new Error().stack ?? `unknown`;
41
- return stack.split(`
42
- `)[stackIndex]?.trim() ?? stack;
77
+ const lines = stack.split(`
78
+ `);
79
+ const userFrame = lines.slice(1).find(
80
+ (line) => !line.includes(`useLiveQuery.ts`) && !line.includes(`useLiveSuspenseQuery.ts`) && !line.includes(`useLiveInfiniteQuery.ts`)
81
+ );
82
+ return userFrame?.trim() ?? lines[stackIndex]?.trim() ?? stack;
43
83
  }
44
84
  function warnDerivedIdentityHotPath(profiler, durationMs) {
45
85
  if (profiler.warned || !shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)) {
@@ -112,7 +152,7 @@ function warnUnhashableDerivedIdentity(error) {
112
152
  `[useLiveQuery] This query cannot derive a stable identity because ${error.reason} at ${error.path}. It will keep the legacy mount-stable behavior for now. Add queryKey: [...] to make captured values reactive. Unhashable queries without queryKey will throw in 1.0.`
113
153
  );
114
154
  }
115
- function createCollectionFromPreparedQuery(value) {
155
+ function createCollectionFromPreparedQuery(value, defaultGcTime = DEFAULT_GC_TIME_MS) {
116
156
  if (value === void 0 || value === null) {
117
157
  return null;
118
158
  }
@@ -124,13 +164,13 @@ function createCollectionFromPreparedQuery(value) {
124
164
  return db.createLiveQueryCollection({
125
165
  query: value,
126
166
  startSync: true,
127
- gcTime: DEFAULT_GC_TIME_MS
167
+ gcTime: defaultGcTime
128
168
  });
129
169
  }
130
170
  if (typeof value === `object`) {
131
171
  return db.createLiveQueryCollection({
132
172
  startSync: true,
133
- gcTime: DEFAULT_GC_TIME_MS,
173
+ gcTime: defaultGcTime,
134
174
  ...value
135
175
  });
136
176
  }
@@ -139,6 +179,12 @@ function createCollectionFromPreparedQuery(value) {
139
179
  );
140
180
  }
141
181
  function useLiveQuery(configOrQueryOrCollection, deps) {
182
+ return useLiveQueryImpl(configOrQueryOrCollection, deps);
183
+ }
184
+ function useLiveQueryForSuspense(configOrQueryOrCollection, deps) {
185
+ return useLiveQueryImpl(configOrQueryOrCollection, deps, true);
186
+ }
187
+ function useLiveQueryImpl(configOrQueryOrCollection, deps, forSuspense = false) {
142
188
  const contextDbClient = DbProvider.useOptionalDbClient();
143
189
  const inputIsCollection = db.isCollection(configOrQueryOrCollection);
144
190
  const dbClient = inputIsCollection ? contextDbClient : getExplicitDbClient(configOrQueryOrCollection) ?? contextDbClient;
@@ -165,6 +211,7 @@ function useLiveQuery(configOrQueryOrCollection, deps) {
165
211
  null
166
212
  );
167
213
  const queryHashRef = react.useRef(void 0);
214
+ const suspenseKeyRef = react.useRef(void 0);
168
215
  const identityErrorRef = react.useRef(void 0);
169
216
  const queryKey = !inputIsCollection ? getExplicitQueryKey(configOrQueryOrCollection) : void 0;
170
217
  let preparedQueryValue = unpreparedQueryValue;
@@ -228,6 +275,18 @@ function useLiveQuery(configOrQueryOrCollection, deps) {
228
275
  if (deps !== void 0) {
229
276
  warnDeprecatedDepsArray();
230
277
  }
278
+ const canReuseSuspenseKey = forSuspense && !inputIsCollection && queryHash !== void 0 && !dbClient && collectionRef.current !== null && clientRef.current === dbClient && queryHashRef.current === queryHash && suspenseKeyRef.current !== void 0;
279
+ if (forSuspense && !inputIsCollection && queryHash && !dbClient && !canReuseSuspenseKey && preparedQueryValue === unpreparedQueryValue) {
280
+ preparedQueryValue = prepareQueryValue(
281
+ configOrQueryOrCollection,
282
+ dbClient,
283
+ deferredCollectionsRef.current
284
+ );
285
+ }
286
+ const suspenseKey = queryHash && !dbClient ? canReuseSuspenseKey ? suspenseKeyRef.current : getUnscopedSuspenseKey(preparedQueryValue, queryHash) : queryHash;
287
+ const suspenseCollections = forSuspense && !inputIsCollection && suspenseKey ? getSuspenseCollections(dbClient) : void 0;
288
+ const suspenseEntry = suspenseKey ? suspenseCollections?.get(suspenseKey) : void 0;
289
+ const suspenseCollection = suspenseEntry?.collection;
231
290
  const identityChanged = depsRef.current === null || (deps !== void 0 ? depsRef.current.length !== identityDeps.length || depsRef.current.some((dep, index) => dep !== identityDeps[index]) : !db.deepEquals(depsRef.current, identityDeps));
232
291
  const needsNewCollection = !collectionRef.current || inputIsCollection && configRef.current !== configOrQueryOrCollection || !inputIsCollection && (clientRef.current !== dbClient || identityChanged);
233
292
  const resumeDeferredCollections = () => {
@@ -253,21 +312,56 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
253
312
  collectionRef.current = configOrQueryOrCollection;
254
313
  configRef.current = configOrQueryOrCollection;
255
314
  } else {
256
- if (preparedQueryValue === unpreparedQueryValue) {
257
- preparedQueryValue = prepareQueryValue(
258
- configOrQueryOrCollection,
259
- dbClient,
260
- deferredCollectionsRef.current
315
+ if (suspenseCollection) {
316
+ collectionRef.current = suspenseCollection;
317
+ } else {
318
+ if (preparedQueryValue === unpreparedQueryValue) {
319
+ preparedQueryValue = prepareQueryValue(
320
+ configOrQueryOrCollection,
321
+ dbClient,
322
+ deferredCollectionsRef.current
323
+ );
324
+ }
325
+ collectionRef.current = createCollectionFromPreparedQuery(
326
+ preparedQueryValue,
327
+ forSuspense ? DEFAULT_SUSPENSE_GC_TIME_MS : DEFAULT_GC_TIME_MS
261
328
  );
329
+ if (suspenseCollections && suspenseKey && collectionRef.current) {
330
+ const collection = collectionRef.current;
331
+ const removeCleanupListener = collection.on(
332
+ `status:cleaned-up`,
333
+ () => releaseSuspenseCollection(
334
+ suspenseCollections,
335
+ suspenseKey,
336
+ collection
337
+ )
338
+ );
339
+ const removeErrorListener = collection.on(`status:error`, () => {
340
+ setTimeout(
341
+ () => releaseSuspenseCollection(
342
+ suspenseCollections,
343
+ suspenseKey,
344
+ collection
345
+ ),
346
+ 0
347
+ );
348
+ });
349
+ const entry = {
350
+ collection,
351
+ removeStatusListeners: () => {
352
+ removeCleanupListener();
353
+ removeErrorListener();
354
+ }
355
+ };
356
+ suspenseCollections.set(suspenseKey, entry);
357
+ }
262
358
  }
263
- collectionRef.current = createCollectionFromPreparedQuery(
264
- preparedQueryValue
265
- );
266
359
  configRef.current = configOrQueryOrCollection;
267
360
  depsRef.current = [...identityDeps];
268
361
  }
269
362
  clientRef.current = dbClient;
270
363
  queryHashRef.current = queryHash;
364
+ suspenseKeyRef.current = suspenseKey;
271
365
  identityErrorRef.current = identityError;
272
366
  }
273
367
  if (needsNewCollection) {
@@ -303,6 +397,7 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
303
397
  exports.prepareDerivedQuery = prepareDerivedQuery;
304
398
  exports.prepareQueryValue = prepareQueryValue;
305
399
  exports.useLiveQuery = useLiveQuery;
400
+ exports.useLiveQueryForSuspense = useLiveQueryForSuspense;
306
401
  exports.warnDeprecatedDepsArray = warnDeprecatedDepsArray;
307
402
  exports.warnUnhashableDerivedIdentity = warnUnhashableDerivedIdentity;
308
403
  //# sourceMappingURL=useLiveQuery.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"useLiveQuery.cjs","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["'use client'\n\nimport { useRef, useSyncExternalStore } from 'react'\nimport {\n BaseQueryBuilder,\n UnhashableQueryIRError,\n createLiveQueryCollection,\n createLiveQueryObserver,\n deepEquals,\n getPreparedLiveQueryIdentity,\n getStableValueHash,\n isCollection,\n prepareLiveQueryValue,\n} from '@tanstack/db'\nimport { useOptionalDbClient } from './DbProvider'\nimport { setLiveQueryResultInfo } from './live-query-internals'\nimport type {\n Collection,\n CollectionImpl,\n CollectionStatus,\n Context,\n DbClient,\n GetResult,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n LiveQueryObserver,\n NonSingleResult,\n QueryBuilder,\n SingleResult,\n} from '@tanstack/db'\n\nconst DEFAULT_GC_TIME_MS = 1 // Live queries created by useLiveQuery are cleaned up immediately (0 disables GC)\nconst DERIVED_IDENTITY_SINGLE_RENDER_WARN_MS = 16\nconst DERIVED_IDENTITY_RENDER_COUNT_WARN_THRESHOLD = 10\nconst DERIVED_IDENTITY_TOTAL_WARN_MS = 50\nconst warnedDepsCallsites = new Set<string>()\nconst warnedDerivedIdentityCallsites = new Set<string>()\nconst warnedUnhashableIdentityCallsites = new Set<string>()\nconst unpreparedQueryValue = Symbol(`unpreparedQueryValue`)\n\nexport type DerivedIdentityProfiler = {\n renderCount: number\n totalMs: number\n maxMs: number\n warned: boolean\n}\n\nexport type UseLiveQueryStatus = CollectionStatus | `disabled`\nexport type LiveQueryKey = ReadonlyArray<unknown>\ntype UseLiveQueryConfigOptions<TContext extends Context> = Omit<\n LiveQueryCollectionConfig<TContext>,\n `query`\n> & {\n /**\n * Explicit identity for queries that contain opaque functional variants or\n * are hot enough that deriving identity from structured IR is too expensive.\n * Structured queries should omit this so DB can derive identity directly.\n */\n queryKey?: LiveQueryKey\n /** Override the nearest DbProvider for this query. */\n client?: DbClient\n}\n\ntype ConfiguredQueryBuilder<TContext extends Context> = Extract<\n LiveQueryCollectionConfig<TContext>[`query`],\n QueryBuilder<TContext>\n>\n\nexport type UseLiveQueryConfig<TContext extends Context> =\n UseLiveQueryConfigOptions<TContext> &\n Pick<LiveQueryCollectionConfig<TContext>, `query`>\n\nexport type ConditionalUseLiveQueryConfig<TContext extends Context> =\n UseLiveQueryConfigOptions<TContext> & {\n query:\n | ConfiguredQueryBuilder<TContext>\n | ((\n q: InitialQueryBuilder,\n ) => ConfiguredQueryBuilder<TContext> | undefined | null)\n }\n\nexport function warnDeprecatedDepsArray(\n hookName: `useLiveQuery` | `useLiveInfiniteQuery` = `useLiveQuery`,\n): void {\n if (!shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_DEPRECATION_WARNINGS`)) {\n return\n }\n\n const callsite = getWarningCallsite(4)\n if (warnedDepsCallsites.has(callsite)) {\n return\n }\n warnedDepsCallsites.add(callsite)\n const replacement =\n hookName === `useLiveQuery`\n ? `useLiveQuery({ query })`\n : `useLiveInfiniteQuery(query, { queryKey })`\n console.warn(\n `[${hookName}] The dependency-array form is deprecated and will be removed in 1.0. Use ${replacement} instead. Provide queryKey only for functional/opaque queries or to avoid deriving identity from structured query IR on render.`,\n )\n}\n\nfunction shouldWarnInDevelopment(disableEnvVar: string): boolean {\n if (typeof process === `undefined`) {\n return false\n }\n\n return (\n process.env.NODE_ENV !== `production` && process.env[disableEnvVar] !== `1`\n )\n}\n\nfunction getCurrentTime(): number {\n return typeof performance !== `undefined` &&\n typeof performance.now === `function`\n ? performance.now()\n : Date.now()\n}\n\nfunction getWarningCallsite(stackIndex: number): string {\n const stack = new Error().stack ?? `unknown`\n return stack.split(`\\n`)[stackIndex]?.trim() ?? stack\n}\n\nfunction warnDerivedIdentityHotPath(\n profiler: DerivedIdentityProfiler,\n durationMs: number,\n): void {\n if (\n profiler.warned ||\n !shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)\n ) {\n return\n }\n\n const isSlowSingleRender =\n durationMs >= DERIVED_IDENTITY_SINGLE_RENDER_WARN_MS\n const isHotRenderPath =\n profiler.renderCount >= DERIVED_IDENTITY_RENDER_COUNT_WARN_THRESHOLD &&\n profiler.totalMs >= DERIVED_IDENTITY_TOTAL_WARN_MS\n\n if (!isSlowSingleRender && !isHotRenderPath) {\n return\n }\n\n const callsite = getWarningCallsite(5)\n if (warnedDerivedIdentityCallsites.has(callsite)) {\n profiler.warned = true\n return\n }\n\n warnedDerivedIdentityCallsites.add(callsite)\n profiler.warned = true\n\n const reason = isSlowSingleRender\n ? `one render took ${durationMs.toFixed(1)}ms`\n : `${profiler.renderCount} renders took ${profiler.totalMs.toFixed(1)}ms`\n\n console.warn(\n `[useLiveQuery] Deriving live query identity from structured query IR is running on a hot render path (${reason}, max ${profiler.maxMs.toFixed(1)}ms). ` +\n `Provide an explicit queryKey to skip rebuilding and hashing the IR on every render: useLiveQuery({ queryKey: [...], query }).`,\n )\n}\n\nfunction getExplicitQueryKey(value: unknown): LiveQueryKey | undefined {\n return value &&\n typeof value === `object` &&\n Array.isArray((value as { queryKey?: unknown }).queryKey)\n ? (value as { queryKey: LiveQueryKey }).queryKey\n : undefined\n}\n\nfunction getExplicitDbClient(value: unknown): DbClient | undefined {\n return value &&\n typeof value === `object` &&\n `client` in value &&\n (value as { client?: unknown }).client !== undefined\n ? (value as { client: DbClient }).client\n : undefined\n}\n\nexport function prepareQueryValue(\n value: unknown,\n dbClient: DbClient | undefined,\n deferredCollections: Set<CollectionImpl<any, string | number, any, any, any>>,\n): unknown {\n return prepareLiveQueryValue(value, dbClient, deferredCollections)\n}\n\ntype DerivedQueryPreparation =\n | {\n status: `hashable`\n value: unknown\n identityDeps: Array<unknown>\n }\n | {\n status: `unhashable`\n value: unknown\n error: UnhashableQueryIRError\n }\n\nexport function prepareDerivedQuery(\n value: unknown,\n dbClient: DbClient | undefined,\n profiler: DerivedIdentityProfiler,\n deferredCollections: Set<CollectionImpl<any, string | number, any, any, any>>,\n): DerivedQueryPreparation {\n const shouldProfile = shouldWarnInDevelopment(\n `TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`,\n )\n const start = shouldProfile ? getCurrentTime() : 0\n const preparedValue = prepareQueryValue(value, dbClient, deferredCollections)\n\n try {\n const identity = getPreparedLiveQueryIdentity(preparedValue)\n return {\n status: `hashable`,\n value: preparedValue,\n identityDeps: [`derived`, identity],\n }\n } catch (error) {\n if (error instanceof UnhashableQueryIRError) {\n return { status: `unhashable`, value: preparedValue, error }\n }\n\n throw error\n } finally {\n if (shouldProfile) {\n const durationMs = getCurrentTime() - start\n profiler.renderCount += 1\n profiler.totalMs += durationMs\n profiler.maxMs = Math.max(profiler.maxMs, durationMs)\n warnDerivedIdentityHotPath(profiler, durationMs)\n }\n }\n}\n\nexport function warnUnhashableDerivedIdentity(\n error: UnhashableQueryIRError,\n): void {\n if (!shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)) {\n return\n }\n\n const callsite = getWarningCallsite(4)\n if (warnedUnhashableIdentityCallsites.has(callsite)) {\n return\n }\n warnedUnhashableIdentityCallsites.add(callsite)\n\n console.warn(\n `[useLiveQuery] This query cannot derive a stable identity because ${error.reason} at ${error.path}. ` +\n `It will keep the legacy mount-stable behavior for now. Add queryKey: [...] to make captured values reactive. ` +\n `Unhashable queries without queryKey will throw in 1.0.`,\n )\n}\n\nfunction createCollectionFromPreparedQuery(value: unknown) {\n if (value === undefined || value === null) {\n return null\n }\n\n if (isCollection(value)) {\n value.startSyncImmediate()\n return value\n }\n\n if (value instanceof BaseQueryBuilder) {\n return createLiveQueryCollection({\n query: value,\n startSync: true,\n gcTime: DEFAULT_GC_TIME_MS,\n })\n }\n\n if (typeof value === `object`) {\n return createLiveQueryCollection({\n startSync: true,\n gcTime: DEFAULT_GC_TIME_MS,\n ...(value as LiveQueryCollectionConfig<any>),\n })\n }\n\n throw new Error(\n `useLiveQuery callback must return a QueryBuilder, LiveQueryCollectionConfig, Collection, undefined, or null. Got: ${typeof value}`,\n )\n}\n\n/**\n * Create a live query using a query function.\n * @param queryFn - Query function that defines what data to fetch\n * @param deps - Deprecated array of dependencies that trigger query re-execution when changed\n * @returns Object with reactive data, state, and status information\n * @example\n * // Prefer config object syntax\n * const { data, isLoading } = useLiveQuery({\n * query: (q) =>\n * q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.completed, false))\n * .select(({ todos }) => ({ id: todos.id, text: todos.text }))\n * })\n *\n * @example\n * // Single result query\n * const { data } = useLiveQuery({\n * query: (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.id, 1))\n * .findOne()\n * })\n *\n * @example\n * // Structured captured values are included in derived query identity\n * const { data, state } = useLiveQuery({\n * query: (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => gt(todos.priority, minPriority)),\n * })\n *\n * @example\n * // Return undefined or null to disable a query\n * const { data, isEnabled } = useLiveQuery({\n * query: (q) => {\n * if (!userId) return undefined\n * return q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.userId, userId))\n * },\n * })\n *\n * @example\n * // Join pattern\n * const { data } = useLiveQuery({\n * query: (q) =>\n * q.from({ issues: issueCollection })\n * .join({ persons: personCollection }, ({ issues, persons }) =>\n * eq(issues.userId, persons.id)\n * )\n * .select(({ issues, persons }) => ({\n * id: issues.id,\n * title: issues.title,\n * userName: persons.name\n * }))\n * })\n *\n * @example\n * // Handle loading and error states\n * const { data, isLoading, isError, status } = useLiveQuery({\n * query: (q) => q.from({ todos: todoCollection })\n * })\n *\n * if (isLoading) return <div>Loading...</div>\n * if (isError) return <div>Error: {status}</div>\n *\n * return (\n * <ul>\n * {data.map(todo => <li key={todo.id}>{todo.text}</li>)}\n * </ul>\n * )\n */\n// Overload 1: Accept query function that always returns QueryBuilder\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n deps?: Array<unknown>,\n): {\n state: Map<string | number, GetResult<TContext>>\n data: InferResultType<TContext>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus // Can't be disabled if always returns QueryBuilder\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: true // Always true if always returns QueryBuilder\n}\n\n// Overload 2: Accept query function that can return undefined/null\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext> | undefined | null,\n deps?: Array<unknown>,\n): {\n state: Map<string | number, GetResult<TContext>> | undefined\n data: InferResultType<TContext> | undefined\n collection: Collection<GetResult<TContext>, string | number, {}> | undefined\n status: UseLiveQueryStatus\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: boolean\n}\n\n// Overload 3: Accept query function that can return LiveQueryCollectionConfig\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => LiveQueryCollectionConfig<TContext> | undefined | null,\n deps?: Array<unknown>,\n): {\n state: Map<string | number, GetResult<TContext>> | undefined\n data: InferResultType<TContext> | undefined\n collection: Collection<GetResult<TContext>, string | number, {}> | undefined\n status: UseLiveQueryStatus\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: boolean\n}\n\n// Overload 4: Accept query function that can return Collection\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => Collection<TResult, TKey, TUtils> | undefined | null,\n deps?: Array<unknown>,\n): {\n state: Map<TKey, TResult> | undefined\n data: Array<TResult> | undefined\n collection: Collection<TResult, TKey, TUtils> | undefined\n status: UseLiveQueryStatus\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: boolean\n}\n\n// Overload 5: Accept query function that can return all types\nexport function useLiveQuery<\n TContext extends Context,\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n queryFn: (\n q: InitialQueryBuilder,\n ) =>\n | QueryBuilder<TContext>\n | LiveQueryCollectionConfig<TContext>\n | Collection<TResult, TKey, TUtils>\n | undefined\n | null,\n deps?: Array<unknown>,\n): {\n state:\n | Map<string | number, GetResult<TContext>>\n | Map<TKey, TResult>\n | undefined\n data: InferResultType<TContext> | Array<TResult> | undefined\n collection:\n | Collection<GetResult<TContext>, string | number, {}>\n | Collection<TResult, TKey, TUtils>\n | undefined\n status: UseLiveQueryStatus\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: boolean\n}\n\n/**\n * Create a live query using configuration object\n * @param config - Configuration object with query and options\n * @param deps - Deprecated array of dependencies that trigger query re-execution when changed\n * @returns Object with reactive data, state, and status information\n * @example\n * // Basic config object usage\n * const { data, status } = useLiveQuery({\n * query: (q) => q.from({ todos: todosCollection }),\n * gcTime: 60000\n * })\n *\n * @example\n * // With query builder and options\n * const queryBuilder = new Query()\n * .from({ persons: collection })\n * .where(({ persons }) => gt(persons.age, 30))\n * .select(({ persons }) => ({ id: persons.id, name: persons.name }))\n *\n * const { data, isReady } = useLiveQuery({\n * query: queryBuilder,\n * })\n *\n * @example\n * // Handle all states uniformly\n * const { data, isLoading, isReady, isError } = useLiveQuery({\n * query: (q) => q.from({ items: itemCollection })\n * })\n *\n * if (isLoading) return <div>Loading...</div>\n * if (isError) return <div>Something went wrong</div>\n * if (!isReady) return <div>Preparing...</div>\n *\n * return <div>{data.length} items loaded</div>\n */\n// Overload 6: Accept config object\nexport function useLiveQuery<TContext extends Context>(\n config: UseLiveQueryConfig<TContext>,\n): {\n state: Map<string | number, GetResult<TContext>>\n data: InferResultType<TContext>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus // Can't be disabled when query always returns a builder\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: true // Always true when query always returns a builder\n}\n\n// Overload 7: Accept config object with a query that can return undefined/null\nexport function useLiveQuery<TContext extends Context>(\n config: ConditionalUseLiveQueryConfig<TContext>,\n): {\n state: Map<string | number, GetResult<TContext>> | undefined\n data: InferResultType<TContext> | undefined\n collection: Collection<GetResult<TContext>, string | number, {}> | undefined\n status: UseLiveQueryStatus\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: boolean\n}\n\n// Overload 8: Accept config object with legacy deps\nexport function useLiveQuery<TContext extends Context>(\n config: LiveQueryCollectionConfig<TContext>,\n deps?: Array<unknown>,\n): {\n state: Map<string | number, GetResult<TContext>>\n data: InferResultType<TContext>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus // Can't be disabled when query always returns a builder\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: true // Always true when query always returns a builder\n}\n\n// Overload 9: Accept config object with legacy deps and a query that can return undefined/null\nexport function useLiveQuery<TContext extends Context>(\n config: ConditionalUseLiveQueryConfig<TContext>,\n deps: Array<unknown>,\n): {\n state: Map<string | number, GetResult<TContext>> | undefined\n data: InferResultType<TContext> | undefined\n collection: Collection<GetResult<TContext>, string | number, {}> | undefined\n status: UseLiveQueryStatus\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: boolean\n}\n\n/**\n * Subscribe to an existing live query collection\n * @param liveQueryCollection - Pre-created live query collection to subscribe to\n * @returns Object with reactive data, state, and status information\n * @example\n * // Using pre-created live query collection\n * const myLiveQuery = createLiveQueryCollection((q) =>\n * q.from({ todos: todosCollection }).where(({ todos }) => eq(todos.active, true))\n * )\n * const { data, collection } = useLiveQuery(myLiveQuery)\n *\n * @example\n * // Access collection methods directly\n * const { data, collection, isReady } = useLiveQuery(existingCollection)\n *\n * // Use collection for mutations\n * const handleToggle = (id) => {\n * collection.update(id, draft => { draft.completed = !draft.completed })\n * }\n *\n * @example\n * // Handle states consistently\n * const { data, isLoading, isError } = useLiveQuery(sharedCollection)\n *\n * if (isLoading) return <div>Loading...</div>\n * if (isError) return <div>Error loading data</div>\n *\n * return <div>{data.map(item => <Item key={item.id} {...item} />)}</div>\n */\n// Overload 10: Accept pre-created live query collection\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult,\n): {\n state: Map<TKey, TResult>\n data: Array<TResult>\n collection: Collection<TResult, TKey, TUtils>\n status: CollectionStatus // Can't be disabled for pre-created live query collections\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: true // Always true for pre-created live query collections\n}\n\n// Overload 10: Accept pre-created live query collection with singleResult: true\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & SingleResult,\n): {\n state: Map<TKey, TResult>\n data: TResult | undefined\n collection: Collection<TResult, TKey, TUtils> & SingleResult\n status: CollectionStatus // Can't be disabled for pre-created live query collections\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: true // Always true for pre-created live query collections\n}\n\n// Implementation - use function overloads to infer the actual collection type\nexport function useLiveQuery(\n configOrQueryOrCollection: any,\n deps?: Array<unknown>,\n) {\n const contextDbClient = useOptionalDbClient()\n // Check if it's already a collection\n const inputIsCollection = isCollection(configOrQueryOrCollection)\n const dbClient = inputIsCollection\n ? contextDbClient\n : (getExplicitDbClient(configOrQueryOrCollection) ?? contextDbClient)\n const resolvedDeps = deps ?? []\n\n // Use refs to cache collection and track dependencies\n const collectionRef = useRef<Collection<object, string | number, {}> | null>(\n null,\n )\n const depsRef = useRef<Array<unknown> | null>(null)\n const configRef = useRef<unknown>(null)\n const clientRef = useRef(dbClient)\n const legacyUnhashableIdentityRef = useRef<Array<unknown>>([\n `legacy-unhashable`,\n ])\n\n const derivedIdentityProfilerRef = useRef<DerivedIdentityProfiler>({\n renderCount: 0,\n totalMs: 0,\n maxMs: 0,\n warned: false,\n })\n const deferredCollectionsRef = useRef(\n new Set<CollectionImpl<any, string | number, any, any, any>>(),\n )\n const observerRef = useRef<LiveQueryObserver<object, string | number> | null>(\n null,\n )\n const queryHashRef = useRef<string | undefined>(undefined)\n const identityErrorRef = useRef<UnhashableQueryIRError | undefined>(undefined)\n\n const queryKey = !inputIsCollection\n ? getExplicitQueryKey(configOrQueryOrCollection)\n : undefined\n let preparedQueryValue: unknown | typeof unpreparedQueryValue =\n unpreparedQueryValue\n let identityDeps: ReadonlyArray<unknown>\n let streamIdentity: unknown = undefined\n let identityError: UnhashableQueryIRError | undefined\n\n if (queryKey) {\n identityDeps = queryKey\n streamIdentity = [`queryKey`, queryKey]\n } else if (deps !== undefined) {\n identityDeps = resolvedDeps\n try {\n preparedQueryValue = prepareQueryValue(\n configOrQueryOrCollection,\n dbClient,\n deferredCollectionsRef.current,\n )\n streamIdentity = [\n `deps`,\n resolvedDeps,\n getPreparedLiveQueryIdentity(preparedQueryValue),\n ]\n } catch (error) {\n if (!(error instanceof UnhashableQueryIRError)) throw error\n warnUnhashableDerivedIdentity(error)\n identityError = error\n }\n } else if (inputIsCollection) {\n identityDeps = []\n streamIdentity = [`collection`, configOrQueryOrCollection.id]\n } else {\n const preparation = prepareDerivedQuery(\n configOrQueryOrCollection,\n dbClient,\n derivedIdentityProfilerRef.current,\n deferredCollectionsRef.current,\n )\n preparedQueryValue = preparation.value\n if (preparation.status === `hashable`) {\n identityDeps = preparation.identityDeps\n streamIdentity = preparation.identityDeps\n } else {\n warnUnhashableDerivedIdentity(preparation.error)\n identityDeps = legacyUnhashableIdentityRef.current\n identityError = preparation.error\n }\n }\n\n let queryHash: string | undefined\n if (streamIdentity !== undefined) {\n try {\n queryHash = getStableValueHash(streamIdentity, `queryKey`)\n } catch (error) {\n if (error instanceof UnhashableQueryIRError) {\n if (queryKey !== undefined) throw error\n identityError = error\n } else {\n throw error\n }\n }\n }\n\n if (deps !== undefined) {\n warnDeprecatedDepsArray()\n }\n\n const identityChanged =\n depsRef.current === null ||\n (deps !== undefined\n ? depsRef.current.length !== identityDeps.length ||\n depsRef.current.some((dep, index) => dep !== identityDeps[index])\n : !deepEquals(depsRef.current, identityDeps))\n\n // Check if we need to create/recreate the collection\n const needsNewCollection =\n !collectionRef.current ||\n (inputIsCollection && configRef.current !== configOrQueryOrCollection) ||\n (!inputIsCollection && (clientRef.current !== dbClient || identityChanged))\n\n const resumeDeferredCollections = () => {\n for (const collection of deferredCollectionsRef.current) {\n collection._resumeSyncStart()\n }\n deferredCollectionsRef.current.clear()\n }\n\n if (needsNewCollection) {\n if (inputIsCollection) {\n // Warn when passing a collection directly with on-demand sync mode\n // In on-demand mode, data is only loaded when queries with predicates request it\n // Passing the collection directly doesn't provide any predicates, so no data loads\n const syncMode = (\n configOrQueryOrCollection as { config?: { syncMode?: string } }\n ).config?.syncMode\n if (\n syncMode === `on-demand` &&\n shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)\n ) {\n console.warn(\n `[useLiveQuery] Warning: Passing a collection with syncMode \"on-demand\" directly to useLiveQuery ` +\n `will not load any data. In on-demand mode, data is only loaded when queries with predicates request it.\\n\\n` +\n `Instead, use a query builder function:\\n` +\n ` const { data } = useLiveQuery({ query: (q) => q.from({ c: myCollection }).select(({ c }) => c) })\\n\\n` +\n `Or switch to syncMode \"eager\" if you want all data to sync automatically.`,\n )\n }\n // It's already a collection, ensure sync is started for React hooks\n configOrQueryOrCollection.startSyncImmediate()\n collectionRef.current = configOrQueryOrCollection\n configRef.current = configOrQueryOrCollection\n } else {\n if (preparedQueryValue === unpreparedQueryValue) {\n preparedQueryValue = prepareQueryValue(\n configOrQueryOrCollection,\n dbClient,\n deferredCollectionsRef.current,\n )\n }\n collectionRef.current = createCollectionFromPreparedQuery(\n preparedQueryValue,\n ) as Collection<object, string | number, {}>\n configRef.current = configOrQueryOrCollection\n depsRef.current = [...identityDeps]\n }\n clientRef.current = dbClient\n queryHashRef.current = queryHash\n identityErrorRef.current = identityError\n }\n\n // Recreate the observer when the underlying collection changes. The observer\n // is not disposed explicitly here or on unmount: `useSyncExternalStore`\n // unsubscribes it when the subscribe changes or the component unmounts, which\n // detaches the collection subscription; the observer is then GC'd. (An unmount\n // effect that disposed it would misfire under StrictMode/offscreen effect\n // replay, leaving a disposed observer in the ref.)\n if (needsNewCollection) {\n // Defer the initial notify: useSyncExternalStore must not be notified\n // synchronously during subscribe.\n // Wholesale mode: React re-reads getSnapshot() on notify, keeps the\n // hook's pre-observer loading policy, and — because wholesale delivers\n // nothing synchronously during subscribe — never notifies\n // useSyncExternalStore inside its own subscribe call.\n observerRef.current = createLiveQueryObserver(collectionRef.current, {\n mode: `wholesale`,\n client: dbClient,\n queryHash: queryHashRef.current,\n onPreload: resumeDeferredCollections,\n })\n }\n const observer = observerRef.current!\n\n // Stable subscribe bound to the current observer; the observer owns the\n // subscription, ready-race, and disposal.\n const subscribeRef = useRef<\n ((onStoreChange: () => void) => () => void) | null\n >(null)\n if (!subscribeRef.current || needsNewCollection) {\n subscribeRef.current = (onStoreChange: () => void) => {\n const unsubscribe = observer.subscribe(() => onStoreChange())\n resumeDeferredCollections()\n return unsubscribe\n }\n }\n\n const returned = useSyncExternalStore(\n subscribeRef.current,\n () => observer.getSnapshot(),\n () => observer.getServerSnapshot(),\n )\n setLiveQueryResultInfo(returned, {\n client: dbClient,\n queryHash: queryHashRef.current,\n identityError: identityErrorRef.current,\n observer,\n })\n return returned as any\n}\n"],"names":["useRef","useSyncExternalStore","setLiveQueryResultInfo"],"mappings":";;;;;;;AAgCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA2CO;AAGL;AACE;AAAA;AAGF;AACA;AACE;AAAA;AAEF;AACA;AAIA;AAAQ;AAC8F;AAExG;AAEA;AACE;AACE;AAAO;AAGT;AAGF;AAEA;AACE;AAIF;AAEA;AACE;AACA;AAAmB;AACrB;AAEA;AAIE;AAIE;AAAA;AAGF;AAEA;AAIA;AACE;AAAA;AAGF;AACA;AACE;AACA;AAAA;AAGF;AACA;AAEA;AAIA;AAAQ;AAC2I;AAGrJ;AAEA;AACE;AAKF;AAEA;AACE;AAMF;AAEO;AAKL;AACF;AAcO;AAML;AAAsB;AACpB;AAEF;AACA;AAEA;AACE;AACA;AAAO;AACG;AACD;AAC2B;AAAA;AAGpC;AACE;AAAqD;AAGvD;AAAM;AAEN;AACE;AACA;AACA;AACA;AACA;AAA+C;AACjD;AAEJ;AAEO;AAGL;AACE;AAAA;AAGF;AACA;AACE;AAAA;AAEF;AAEA;AAAQ;AAC4F;AAItG;AAEA;AACE;AACE;AAAO;AAGT;AACE;AACA;AAAO;AAGT;AACE;AAAiC;AACxB;AACI;AACH;AACT;AAGH;AACE;AAAiC;AACpB;AACH;AACJ;AACL;AAGH;AAAU;AACyH;AAErI;AAmWO;AAIL;AAEA;AACA;AAGA;AAGA;AAAsBA;AACpB;AAEF;AACA;AACA;AACA;AAA2D;AACzD;AAGF;AAAmE;AACpD;AACJ;AACF;AACC;AAEV;AAA+BA;AACzB;AAEN;AAAoBA;AAClB;AAEF;AACA;AAEA;AAGA;AAEA;AACA;AACA;AAEA;AACE;AACA;AAAsC;AAEtC;AACA;AACE;AAAqB;AACnB;AACA;AACuB;AAEzB;AAAiB;AACf;AACA;AAC+C;AAAA;AAGjD;AACA;AACA;AAAgB;AAClB;AAEA;AACA;AAA4D;AAE5D;AAAoB;AAClB;AACA;AAC2B;AACJ;AAEzB;AACA;AACE;AACA;AAA6B;AAE7B;AACA;AACA;AAA4B;AAC9B;AAGF;AACA;AACE;AACE;AAAyD;AAEzD;AACE;AACA;AAAgB;AAEhB;AAAM;AACR;AACF;AAGF;AACE;AAAA;AAGF;AAQA;AAKA;AACE;AACE;AAAW;AAEb;AAA+B;AAGjC;AACE;AAIE;AAGA;AAIE;AAAQ;;AACN;AAAA;;AAAA;AAAA;AAAA;AAQJ;AACA;AACA;AAAoB;AAEpB;AACE;AAAqB;AACnB;AACA;AACuB;AAAA;AAG3B;AAAwB;AACtB;AAEF;AACA;AAAkC;AAEpC;AACA;AACA;AAA2B;AAS7B;AAOE;AAAqE;AAC7D;AACE;AACgB;AACb;AACZ;AAEH;AAIA;AAGA;AACE;AACE;AACA;AACA;AAAO;AACT;AAGF;AAAiBC;AACF;AACE;AACA;AAEjBC;AAAiC;AACvB;AACgB;AACQ;AAChC;AAEF;AACF;;;;;;"}
1
+ {"version":3,"file":"useLiveQuery.cjs","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["'use client'\n\nimport { useRef, useSyncExternalStore } from 'react'\nimport {\n BaseQueryBuilder,\n IR,\n UnhashableQueryIRError,\n createLiveQueryCollection,\n createLiveQueryObserver,\n deepEquals,\n getPreparedLiveQueryIdentity,\n getStableValueHash,\n isCollection,\n prepareLiveQueryValue,\n} from '@tanstack/db'\nimport { useOptionalDbClient } from './DbProvider'\nimport { setLiveQueryResultInfo } from './live-query-internals'\nimport type {\n Collection,\n CollectionImpl,\n CollectionStatus,\n Context,\n DbClient,\n GetResult,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n LiveQueryObserver,\n LiveQueryPersistedStatus,\n NonSingleResult,\n QueryBuilder,\n SingleResult,\n} from '@tanstack/db'\n\nconst DEFAULT_GC_TIME_MS = 1 // Live queries created by useLiveQuery are cleaned up immediately (0 disables GC)\n// Suspense renders can be abandoned before React subscribes. Keep their\n// collection briefly so a nearby retry can commit without starting over.\nconst DEFAULT_SUSPENSE_GC_TIME_MS = 5000\nconst DERIVED_IDENTITY_SINGLE_RENDER_WARN_MS = 16\nconst DERIVED_IDENTITY_RENDER_COUNT_WARN_THRESHOLD = 10\nconst DERIVED_IDENTITY_TOTAL_WARN_MS = 50\nconst warnedDepsCallsites = new Set<string>()\nconst warnedDerivedIdentityCallsites = new Set<string>()\nconst warnedUnhashableIdentityCallsites = new Set<string>()\nconst unpreparedQueryValue = Symbol(`unpreparedQueryValue`)\n\ntype SuspenseCollection = Collection<object, string | number, {}>\ntype SuspenseCollectionEntry = {\n collection: SuspenseCollection\n removeStatusListeners: () => void\n}\n\nconst unscopedSuspenseCollections = new Map<string, SuspenseCollectionEntry>()\nconst suspenseCollectionsByClient = new WeakMap<\n DbClient,\n Map<string, SuspenseCollectionEntry>\n>()\nconst suspenseSourceIds = new WeakMap<object, number>()\nlet nextSuspenseSourceId = 0\n\nfunction getSuspenseSourceId(source: object): number {\n let id = suspenseSourceIds.get(source)\n if (id === undefined) {\n id = ++nextSuspenseSourceId\n suspenseSourceIds.set(source, id)\n }\n return id\n}\n\nfunction getUnscopedSuspenseKey(\n preparedValue: unknown,\n queryHash: string,\n): string {\n const query =\n preparedValue instanceof BaseQueryBuilder\n ? preparedValue\n : preparedValue &&\n typeof preparedValue === `object` &&\n `query` in preparedValue &&\n preparedValue.query instanceof BaseQueryBuilder\n ? preparedValue.query\n : undefined\n if (!query) return queryHash\n const sourceIds = IR.collectCollectionSources(query._getQuery()).map(\n ({ collection }) => getSuspenseSourceId(collection),\n )\n return `${sourceIds.join(`,`)}:${queryHash}`\n}\n\nfunction getSuspenseCollections(\n client: DbClient | undefined,\n): Map<string, SuspenseCollectionEntry> {\n if (!client) return unscopedSuspenseCollections\n let collections = suspenseCollectionsByClient.get(client)\n if (!collections) {\n collections = new Map()\n suspenseCollectionsByClient.set(client, collections)\n }\n return collections\n}\n\nfunction releaseSuspenseCollection(\n collections: Map<string, SuspenseCollectionEntry>,\n queryHash: string,\n collection: SuspenseCollection,\n): void {\n const entry = collections.get(queryHash)\n if (entry?.collection !== collection) return\n collections.delete(queryHash)\n entry.removeStatusListeners()\n}\n\nexport type DerivedIdentityProfiler = {\n renderCount: number\n totalMs: number\n maxMs: number\n warned: boolean\n}\n\nexport type UseLiveQueryStatus = CollectionStatus | `disabled`\nexport type LiveQueryKey = ReadonlyArray<unknown>\ntype UseLiveQueryConfigOptions<TContext extends Context> = Omit<\n LiveQueryCollectionConfig<TContext>,\n `query`\n> & {\n /**\n * Explicit identity for queries that contain opaque functional variants or\n * are hot enough that deriving identity from structured IR is too expensive.\n * Structured queries should omit this so DB can derive identity directly.\n */\n queryKey?: LiveQueryKey\n /** Override the nearest DbProvider for this query. */\n client?: DbClient\n}\n\ntype ConfiguredQueryBuilder<TContext extends Context> = Extract<\n LiveQueryCollectionConfig<TContext>[`query`],\n QueryBuilder<TContext>\n>\n\nexport type UseLiveQueryConfig<TContext extends Context> =\n UseLiveQueryConfigOptions<TContext> &\n Pick<LiveQueryCollectionConfig<TContext>, `query`>\n\nexport type ConditionalUseLiveQueryConfig<TContext extends Context> =\n UseLiveQueryConfigOptions<TContext> & {\n query:\n | ConfiguredQueryBuilder<TContext>\n | ((\n q: InitialQueryBuilder,\n ) => ConfiguredQueryBuilder<TContext> | undefined | null)\n }\n\nexport function warnDeprecatedDepsArray(\n hookName: `useLiveQuery` | `useLiveInfiniteQuery` = `useLiveQuery`,\n): void {\n if (!shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_DEPRECATION_WARNINGS`)) {\n return\n }\n\n const callsite = getWarningCallsite(4)\n if (warnedDepsCallsites.has(callsite)) {\n return\n }\n warnedDepsCallsites.add(callsite)\n const replacement =\n hookName === `useLiveQuery`\n ? `useLiveQuery({ query })`\n : `useLiveInfiniteQuery(query, { queryKey })`\n console.warn(\n `[${hookName}] The dependency-array form is deprecated and will be removed in 1.0. Use ${replacement} instead. Provide queryKey only for functional/opaque queries or to avoid deriving identity from structured query IR on render.`,\n )\n}\n\nfunction shouldWarnInDevelopment(disableEnvVar: string): boolean {\n if (typeof process === `undefined`) {\n return false\n }\n\n return (\n process.env.NODE_ENV !== `production` && process.env[disableEnvVar] !== `1`\n )\n}\n\nfunction getCurrentTime(): number {\n return typeof performance !== `undefined` &&\n typeof performance.now === `function`\n ? performance.now()\n : Date.now()\n}\n\nfunction getWarningCallsite(stackIndex: number): string {\n const stack = new Error().stack ?? `unknown`\n const lines = stack.split(`\\n`)\n const userFrame = lines\n .slice(1)\n .find(\n (line) =>\n !line.includes(`useLiveQuery.ts`) &&\n !line.includes(`useLiveSuspenseQuery.ts`) &&\n !line.includes(`useLiveInfiniteQuery.ts`),\n )\n return userFrame?.trim() ?? lines[stackIndex]?.trim() ?? stack\n}\n\nfunction warnDerivedIdentityHotPath(\n profiler: DerivedIdentityProfiler,\n durationMs: number,\n): void {\n if (\n profiler.warned ||\n !shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)\n ) {\n return\n }\n\n const isSlowSingleRender =\n durationMs >= DERIVED_IDENTITY_SINGLE_RENDER_WARN_MS\n const isHotRenderPath =\n profiler.renderCount >= DERIVED_IDENTITY_RENDER_COUNT_WARN_THRESHOLD &&\n profiler.totalMs >= DERIVED_IDENTITY_TOTAL_WARN_MS\n\n if (!isSlowSingleRender && !isHotRenderPath) {\n return\n }\n\n const callsite = getWarningCallsite(5)\n if (warnedDerivedIdentityCallsites.has(callsite)) {\n profiler.warned = true\n return\n }\n\n warnedDerivedIdentityCallsites.add(callsite)\n profiler.warned = true\n\n const reason = isSlowSingleRender\n ? `one render took ${durationMs.toFixed(1)}ms`\n : `${profiler.renderCount} renders took ${profiler.totalMs.toFixed(1)}ms`\n\n console.warn(\n `[useLiveQuery] Deriving live query identity from structured query IR is running on a hot render path (${reason}, max ${profiler.maxMs.toFixed(1)}ms). ` +\n `Provide an explicit queryKey to skip rebuilding and hashing the IR on every render: useLiveQuery({ queryKey: [...], query }).`,\n )\n}\n\nfunction getExplicitQueryKey(value: unknown): LiveQueryKey | undefined {\n return value &&\n typeof value === `object` &&\n Array.isArray((value as { queryKey?: unknown }).queryKey)\n ? (value as { queryKey: LiveQueryKey }).queryKey\n : undefined\n}\n\nfunction getExplicitDbClient(value: unknown): DbClient | undefined {\n return value &&\n typeof value === `object` &&\n `client` in value &&\n (value as { client?: unknown }).client !== undefined\n ? (value as { client: DbClient }).client\n : undefined\n}\n\nexport function prepareQueryValue(\n value: unknown,\n dbClient: DbClient | undefined,\n deferredCollections: Set<CollectionImpl<any, string | number, any, any, any>>,\n): unknown {\n return prepareLiveQueryValue(value, dbClient, deferredCollections)\n}\n\ntype DerivedQueryPreparation =\n | {\n status: `hashable`\n value: unknown\n identityDeps: Array<unknown>\n }\n | {\n status: `unhashable`\n value: unknown\n error: UnhashableQueryIRError\n }\n\nexport function prepareDerivedQuery(\n value: unknown,\n dbClient: DbClient | undefined,\n profiler: DerivedIdentityProfiler,\n deferredCollections: Set<CollectionImpl<any, string | number, any, any, any>>,\n): DerivedQueryPreparation {\n const shouldProfile = shouldWarnInDevelopment(\n `TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`,\n )\n const start = shouldProfile ? getCurrentTime() : 0\n const preparedValue = prepareQueryValue(value, dbClient, deferredCollections)\n\n try {\n const identity = getPreparedLiveQueryIdentity(preparedValue)\n return {\n status: `hashable`,\n value: preparedValue,\n identityDeps: [`derived`, identity],\n }\n } catch (error) {\n if (error instanceof UnhashableQueryIRError) {\n return { status: `unhashable`, value: preparedValue, error }\n }\n\n throw error\n } finally {\n if (shouldProfile) {\n const durationMs = getCurrentTime() - start\n profiler.renderCount += 1\n profiler.totalMs += durationMs\n profiler.maxMs = Math.max(profiler.maxMs, durationMs)\n warnDerivedIdentityHotPath(profiler, durationMs)\n }\n }\n}\n\nexport function warnUnhashableDerivedIdentity(\n error: UnhashableQueryIRError,\n): void {\n if (!shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)) {\n return\n }\n\n const callsite = getWarningCallsite(4)\n if (warnedUnhashableIdentityCallsites.has(callsite)) {\n return\n }\n warnedUnhashableIdentityCallsites.add(callsite)\n\n console.warn(\n `[useLiveQuery] This query cannot derive a stable identity because ${error.reason} at ${error.path}. ` +\n `It will keep the legacy mount-stable behavior for now. Add queryKey: [...] to make captured values reactive. ` +\n `Unhashable queries without queryKey will throw in 1.0.`,\n )\n}\n\nfunction createCollectionFromPreparedQuery(\n value: unknown,\n defaultGcTime = DEFAULT_GC_TIME_MS,\n) {\n if (value === undefined || value === null) {\n return null\n }\n\n if (isCollection(value)) {\n value.startSyncImmediate()\n return value\n }\n\n if (value instanceof BaseQueryBuilder) {\n return createLiveQueryCollection({\n query: value,\n startSync: true,\n gcTime: defaultGcTime,\n })\n }\n\n if (typeof value === `object`) {\n return createLiveQueryCollection({\n startSync: true,\n gcTime: defaultGcTime,\n ...(value as LiveQueryCollectionConfig<any>),\n })\n }\n\n throw new Error(\n `useLiveQuery callback must return a QueryBuilder, LiveQueryCollectionConfig, Collection, undefined, or null. Got: ${typeof value}`,\n )\n}\n\n/**\n * Create a live query using a query function.\n * @param queryFn - Query function that defines what data to fetch\n * @param deps - Deprecated array of dependencies that trigger query re-execution when changed\n * @returns Object with reactive data, state, and status information\n * @example\n * // Prefer config object syntax\n * const { data, isLoading } = useLiveQuery({\n * query: (q) =>\n * q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.completed, false))\n * .select(({ todos }) => ({ id: todos.id, text: todos.text }))\n * })\n *\n * @example\n * // Single result query\n * const { data } = useLiveQuery({\n * query: (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.id, 1))\n * .findOne()\n * })\n *\n * @example\n * // Structured captured values are included in derived query identity\n * const { data, state } = useLiveQuery({\n * query: (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => gt(todos.priority, minPriority)),\n * })\n *\n * @example\n * // Return undefined or null to disable a query\n * const { data, isEnabled } = useLiveQuery({\n * query: (q) => {\n * if (!userId) return undefined\n * return q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.userId, userId))\n * },\n * })\n *\n * @example\n * // Join pattern\n * const { data } = useLiveQuery({\n * query: (q) =>\n * q.from({ issues: issueCollection })\n * .join({ persons: personCollection }, ({ issues, persons }) =>\n * eq(issues.userId, persons.id)\n * )\n * .select(({ issues, persons }) => ({\n * id: issues.id,\n * title: issues.title,\n * userName: persons.name\n * }))\n * })\n *\n * @example\n * // Handle loading and error states\n * const { data, isLoading, isError, status } = useLiveQuery({\n * query: (q) => q.from({ todos: todoCollection })\n * })\n *\n * if (isLoading) return <div>Loading...</div>\n * if (isError) return <div>Error: {status}</div>\n *\n * return (\n * <ul>\n * {data.map(todo => <li key={todo.id}>{todo.text}</li>)}\n * </ul>\n * )\n */\n// Overload 1: Accept query function that always returns QueryBuilder\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n deps?: Array<unknown>,\n): {\n state: Map<string | number, GetResult<TContext>>\n data: InferResultType<TContext>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus // Can't be disabled if always returns QueryBuilder\n isLoading: boolean\n isReady: boolean\n persistedStatus: LiveQueryPersistedStatus\n isPersistedReady: boolean\n persistedError: unknown | undefined\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: true // Always true if always returns QueryBuilder\n}\n\n// Overload 2: Accept query function that can return undefined/null\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext> | undefined | null,\n deps?: Array<unknown>,\n): {\n state: Map<string | number, GetResult<TContext>> | undefined\n data: InferResultType<TContext> | undefined\n collection: Collection<GetResult<TContext>, string | number, {}> | undefined\n status: UseLiveQueryStatus\n isLoading: boolean\n isReady: boolean\n persistedStatus: LiveQueryPersistedStatus\n isPersistedReady: boolean\n persistedError: unknown | undefined\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: boolean\n}\n\n// Overload 3: Accept query function that can return LiveQueryCollectionConfig\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => LiveQueryCollectionConfig<TContext> | undefined | null,\n deps?: Array<unknown>,\n): {\n state: Map<string | number, GetResult<TContext>> | undefined\n data: InferResultType<TContext> | undefined\n collection: Collection<GetResult<TContext>, string | number, {}> | undefined\n status: UseLiveQueryStatus\n isLoading: boolean\n isReady: boolean\n persistedStatus: LiveQueryPersistedStatus\n isPersistedReady: boolean\n persistedError: unknown | undefined\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: boolean\n}\n\n// Overload 4: Accept query function that can return Collection\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => Collection<TResult, TKey, TUtils> | undefined | null,\n deps?: Array<unknown>,\n): {\n state: Map<TKey, TResult> | undefined\n data: Array<TResult> | undefined\n collection: Collection<TResult, TKey, TUtils> | undefined\n status: UseLiveQueryStatus\n isLoading: boolean\n isReady: boolean\n persistedStatus: LiveQueryPersistedStatus\n isPersistedReady: boolean\n persistedError: unknown | undefined\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: boolean\n}\n\n// Overload 5: Accept query function that can return all types\nexport function useLiveQuery<\n TContext extends Context,\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n queryFn: (\n q: InitialQueryBuilder,\n ) =>\n | QueryBuilder<TContext>\n | LiveQueryCollectionConfig<TContext>\n | Collection<TResult, TKey, TUtils>\n | undefined\n | null,\n deps?: Array<unknown>,\n): {\n state:\n | Map<string | number, GetResult<TContext>>\n | Map<TKey, TResult>\n | undefined\n data: InferResultType<TContext> | Array<TResult> | undefined\n collection:\n | Collection<GetResult<TContext>, string | number, {}>\n | Collection<TResult, TKey, TUtils>\n | undefined\n status: UseLiveQueryStatus\n isLoading: boolean\n isReady: boolean\n persistedStatus: LiveQueryPersistedStatus\n isPersistedReady: boolean\n persistedError: unknown | undefined\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: boolean\n}\n\n/**\n * Create a live query using configuration object\n * @param config - Configuration object with query and options\n * @param deps - Deprecated array of dependencies that trigger query re-execution when changed\n * @returns Object with reactive data, state, and status information\n * @example\n * // Basic config object usage\n * const { data, status } = useLiveQuery({\n * query: (q) => q.from({ todos: todosCollection }),\n * gcTime: 60000\n * })\n *\n * @example\n * // With query builder and options\n * const queryBuilder = new Query()\n * .from({ persons: collection })\n * .where(({ persons }) => gt(persons.age, 30))\n * .select(({ persons }) => ({ id: persons.id, name: persons.name }))\n *\n * const { data, isReady } = useLiveQuery({\n * query: queryBuilder,\n * })\n *\n * @example\n * // Handle all states uniformly\n * const { data, isLoading, isReady, isError } = useLiveQuery({\n * query: (q) => q.from({ items: itemCollection })\n * })\n *\n * if (isLoading) return <div>Loading...</div>\n * if (isError) return <div>Something went wrong</div>\n * if (!isReady) return <div>Preparing...</div>\n *\n * return <div>{data.length} items loaded</div>\n */\n// Overload 6: Accept config object\nexport function useLiveQuery<TContext extends Context>(\n config: UseLiveQueryConfig<TContext>,\n): {\n state: Map<string | number, GetResult<TContext>>\n data: InferResultType<TContext>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus // Can't be disabled when query always returns a builder\n isLoading: boolean\n isReady: boolean\n persistedStatus: LiveQueryPersistedStatus\n isPersistedReady: boolean\n persistedError: unknown | undefined\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: true // Always true when query always returns a builder\n}\n\n// Overload 7: Accept config object with a query that can return undefined/null\nexport function useLiveQuery<TContext extends Context>(\n config: ConditionalUseLiveQueryConfig<TContext>,\n): {\n state: Map<string | number, GetResult<TContext>> | undefined\n data: InferResultType<TContext> | undefined\n collection: Collection<GetResult<TContext>, string | number, {}> | undefined\n status: UseLiveQueryStatus\n isLoading: boolean\n isReady: boolean\n persistedStatus: LiveQueryPersistedStatus\n isPersistedReady: boolean\n persistedError: unknown | undefined\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: boolean\n}\n\n// Overload 8: Accept config object with legacy deps\nexport function useLiveQuery<TContext extends Context>(\n config: LiveQueryCollectionConfig<TContext>,\n deps?: Array<unknown>,\n): {\n state: Map<string | number, GetResult<TContext>>\n data: InferResultType<TContext>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus // Can't be disabled when query always returns a builder\n isLoading: boolean\n isReady: boolean\n persistedStatus: LiveQueryPersistedStatus\n isPersistedReady: boolean\n persistedError: unknown | undefined\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: true // Always true when query always returns a builder\n}\n\n// Overload 9: Accept config object with legacy deps and a query that can return undefined/null\nexport function useLiveQuery<TContext extends Context>(\n config: ConditionalUseLiveQueryConfig<TContext>,\n deps: Array<unknown>,\n): {\n state: Map<string | number, GetResult<TContext>> | undefined\n data: InferResultType<TContext> | undefined\n collection: Collection<GetResult<TContext>, string | number, {}> | undefined\n status: UseLiveQueryStatus\n isLoading: boolean\n isReady: boolean\n persistedStatus: LiveQueryPersistedStatus\n isPersistedReady: boolean\n persistedError: unknown | undefined\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: boolean\n}\n\n/**\n * Subscribe to an existing live query collection\n * @param liveQueryCollection - Pre-created live query collection to subscribe to\n * @returns Object with reactive data, state, and status information\n * @example\n * // Using pre-created live query collection\n * const myLiveQuery = createLiveQueryCollection((q) =>\n * q.from({ todos: todosCollection }).where(({ todos }) => eq(todos.active, true))\n * )\n * const { data, collection } = useLiveQuery(myLiveQuery)\n *\n * @example\n * // Access collection methods directly\n * const { data, collection, isReady } = useLiveQuery(existingCollection)\n *\n * // Use collection for mutations\n * const handleToggle = (id) => {\n * collection.update(id, draft => { draft.completed = !draft.completed })\n * }\n *\n * @example\n * // Handle states consistently\n * const { data, isLoading, isError } = useLiveQuery(sharedCollection)\n *\n * if (isLoading) return <div>Loading...</div>\n * if (isError) return <div>Error loading data</div>\n *\n * return <div>{data.map(item => <Item key={item.id} {...item} />)}</div>\n */\n// Overload 10: Accept pre-created live query collection\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult,\n): {\n state: Map<TKey, TResult>\n data: Array<TResult>\n collection: Collection<TResult, TKey, TUtils>\n status: CollectionStatus // Can't be disabled for pre-created live query collections\n isLoading: boolean\n isReady: boolean\n persistedStatus: LiveQueryPersistedStatus\n isPersistedReady: boolean\n persistedError: unknown | undefined\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: true // Always true for pre-created live query collections\n}\n\n// Overload 10: Accept pre-created live query collection with singleResult: true\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & SingleResult,\n): {\n state: Map<TKey, TResult>\n data: TResult | undefined\n collection: Collection<TResult, TKey, TUtils> & SingleResult\n status: CollectionStatus // Can't be disabled for pre-created live query collections\n isLoading: boolean\n isReady: boolean\n persistedStatus: LiveQueryPersistedStatus\n isPersistedReady: boolean\n persistedError: unknown | undefined\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: true // Always true for pre-created live query collections\n}\n\n// Implementation - use function overloads to infer the actual collection type\nexport function useLiveQuery(\n configOrQueryOrCollection: any,\n deps?: Array<unknown>,\n) {\n return useLiveQueryImpl(configOrQueryOrCollection, deps)\n}\n\n/** @internal Shared implementation for the Suspense wrapper. */\nexport function useLiveQueryForSuspense(\n configOrQueryOrCollection: any,\n deps: Array<unknown> | undefined,\n) {\n return useLiveQueryImpl(configOrQueryOrCollection, deps, true)\n}\n\nfunction useLiveQueryImpl(\n configOrQueryOrCollection: any,\n deps: Array<unknown> | undefined,\n forSuspense = false,\n) {\n const contextDbClient = useOptionalDbClient()\n // Check if it's already a collection\n const inputIsCollection = isCollection(configOrQueryOrCollection)\n const dbClient = inputIsCollection\n ? contextDbClient\n : (getExplicitDbClient(configOrQueryOrCollection) ?? contextDbClient)\n const resolvedDeps = deps ?? []\n\n // Use refs to cache collection and track dependencies\n const collectionRef = useRef<Collection<object, string | number, {}> | null>(\n null,\n )\n const depsRef = useRef<Array<unknown> | null>(null)\n const configRef = useRef<unknown>(null)\n const clientRef = useRef(dbClient)\n const legacyUnhashableIdentityRef = useRef<Array<unknown>>([\n `legacy-unhashable`,\n ])\n\n const derivedIdentityProfilerRef = useRef<DerivedIdentityProfiler>({\n renderCount: 0,\n totalMs: 0,\n maxMs: 0,\n warned: false,\n })\n const deferredCollectionsRef = useRef(\n new Set<CollectionImpl<any, string | number, any, any, any>>(),\n )\n const observerRef = useRef<LiveQueryObserver<object, string | number> | null>(\n null,\n )\n const queryHashRef = useRef<string | undefined>(undefined)\n const suspenseKeyRef = useRef<string | undefined>(undefined)\n const identityErrorRef = useRef<UnhashableQueryIRError | undefined>(undefined)\n\n const queryKey = !inputIsCollection\n ? getExplicitQueryKey(configOrQueryOrCollection)\n : undefined\n let preparedQueryValue: unknown | typeof unpreparedQueryValue =\n unpreparedQueryValue\n let identityDeps: ReadonlyArray<unknown>\n let streamIdentity: unknown = undefined\n let identityError: UnhashableQueryIRError | undefined\n\n if (queryKey) {\n identityDeps = queryKey\n streamIdentity = [`queryKey`, queryKey]\n } else if (deps !== undefined) {\n identityDeps = resolvedDeps\n try {\n preparedQueryValue = prepareQueryValue(\n configOrQueryOrCollection,\n dbClient,\n deferredCollectionsRef.current,\n )\n streamIdentity = [\n `deps`,\n resolvedDeps,\n getPreparedLiveQueryIdentity(preparedQueryValue),\n ]\n } catch (error) {\n if (!(error instanceof UnhashableQueryIRError)) throw error\n warnUnhashableDerivedIdentity(error)\n identityError = error\n }\n } else if (inputIsCollection) {\n identityDeps = []\n streamIdentity = [`collection`, configOrQueryOrCollection.id]\n } else {\n const preparation = prepareDerivedQuery(\n configOrQueryOrCollection,\n dbClient,\n derivedIdentityProfilerRef.current,\n deferredCollectionsRef.current,\n )\n preparedQueryValue = preparation.value\n if (preparation.status === `hashable`) {\n identityDeps = preparation.identityDeps\n streamIdentity = preparation.identityDeps\n } else {\n warnUnhashableDerivedIdentity(preparation.error)\n identityDeps = legacyUnhashableIdentityRef.current\n identityError = preparation.error\n }\n }\n\n let queryHash: string | undefined\n if (streamIdentity !== undefined) {\n try {\n queryHash = getStableValueHash(streamIdentity, `queryKey`)\n } catch (error) {\n if (error instanceof UnhashableQueryIRError) {\n if (queryKey !== undefined) throw error\n identityError = error\n } else {\n throw error\n }\n }\n }\n\n if (deps !== undefined) {\n warnDeprecatedDepsArray()\n }\n\n const canReuseSuspenseKey =\n forSuspense &&\n !inputIsCollection &&\n queryHash !== undefined &&\n !dbClient &&\n collectionRef.current !== null &&\n clientRef.current === dbClient &&\n queryHashRef.current === queryHash &&\n suspenseKeyRef.current !== undefined\n\n if (\n forSuspense &&\n !inputIsCollection &&\n queryHash &&\n !dbClient &&\n !canReuseSuspenseKey &&\n preparedQueryValue === unpreparedQueryValue\n ) {\n preparedQueryValue = prepareQueryValue(\n configOrQueryOrCollection,\n dbClient,\n deferredCollectionsRef.current,\n )\n }\n\n const suspenseKey =\n queryHash && !dbClient\n ? canReuseSuspenseKey\n ? suspenseKeyRef.current\n : getUnscopedSuspenseKey(preparedQueryValue, queryHash)\n : queryHash\n\n const suspenseCollections =\n forSuspense && !inputIsCollection && suspenseKey\n ? getSuspenseCollections(dbClient)\n : undefined\n const suspenseEntry = suspenseKey\n ? suspenseCollections?.get(suspenseKey)\n : undefined\n const suspenseCollection = suspenseEntry?.collection\n\n const identityChanged =\n depsRef.current === null ||\n (deps !== undefined\n ? depsRef.current.length !== identityDeps.length ||\n depsRef.current.some((dep, index) => dep !== identityDeps[index])\n : !deepEquals(depsRef.current, identityDeps))\n\n // Check if we need to create/recreate the collection\n const needsNewCollection =\n !collectionRef.current ||\n (inputIsCollection && configRef.current !== configOrQueryOrCollection) ||\n (!inputIsCollection && (clientRef.current !== dbClient || identityChanged))\n\n const resumeDeferredCollections = () => {\n for (const collection of deferredCollectionsRef.current) {\n collection._resumeSyncStart()\n }\n deferredCollectionsRef.current.clear()\n }\n\n if (needsNewCollection) {\n if (inputIsCollection) {\n // Warn when passing a collection directly with on-demand sync mode\n // In on-demand mode, data is only loaded when queries with predicates request it\n // Passing the collection directly doesn't provide any predicates, so no data loads\n const syncMode = (\n configOrQueryOrCollection as { config?: { syncMode?: string } }\n ).config?.syncMode\n if (\n syncMode === `on-demand` &&\n shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)\n ) {\n console.warn(\n `[useLiveQuery] Warning: Passing a collection with syncMode \"on-demand\" directly to useLiveQuery ` +\n `will not load any data. In on-demand mode, data is only loaded when queries with predicates request it.\\n\\n` +\n `Instead, use a query builder function:\\n` +\n ` const { data } = useLiveQuery({ query: (q) => q.from({ c: myCollection }).select(({ c }) => c) })\\n\\n` +\n `Or switch to syncMode \"eager\" if you want all data to sync automatically.`,\n )\n }\n // It's already a collection, ensure sync is started for React hooks\n configOrQueryOrCollection.startSyncImmediate()\n collectionRef.current = configOrQueryOrCollection\n configRef.current = configOrQueryOrCollection\n } else {\n if (suspenseCollection) {\n collectionRef.current = suspenseCollection\n } else {\n if (preparedQueryValue === unpreparedQueryValue) {\n preparedQueryValue = prepareQueryValue(\n configOrQueryOrCollection,\n dbClient,\n deferredCollectionsRef.current,\n )\n }\n collectionRef.current = createCollectionFromPreparedQuery(\n preparedQueryValue,\n forSuspense ? DEFAULT_SUSPENSE_GC_TIME_MS : DEFAULT_GC_TIME_MS,\n ) as SuspenseCollection | null\n if (suspenseCollections && suspenseKey && collectionRef.current) {\n const collection = collectionRef.current\n const removeCleanupListener = collection.on(`status:cleaned-up`, () =>\n releaseSuspenseCollection(\n suspenseCollections,\n suspenseKey,\n collection,\n ),\n )\n const removeErrorListener = collection.on(`status:error`, () => {\n // Keep the failed collection through React's immediate retry so\n // the hook can throw its actual load error to an ErrorBoundary.\n // Retire it afterward so a later boundary reset starts fresh.\n setTimeout(\n () =>\n releaseSuspenseCollection(\n suspenseCollections,\n suspenseKey,\n collection,\n ),\n 0,\n )\n })\n const entry: SuspenseCollectionEntry = {\n collection,\n removeStatusListeners: () => {\n removeCleanupListener()\n removeErrorListener()\n },\n }\n suspenseCollections.set(suspenseKey, entry)\n }\n }\n configRef.current = configOrQueryOrCollection\n depsRef.current = [...identityDeps]\n }\n clientRef.current = dbClient\n queryHashRef.current = queryHash\n suspenseKeyRef.current = suspenseKey\n identityErrorRef.current = identityError\n }\n\n // Recreate the observer when the underlying collection changes. The observer\n // is not disposed explicitly here or on unmount: `useSyncExternalStore`\n // unsubscribes it when the subscribe changes or the component unmounts, which\n // detaches the collection subscription; the observer is then GC'd. (An unmount\n // effect that disposed it would misfire under StrictMode/offscreen effect\n // replay, leaving a disposed observer in the ref.)\n if (needsNewCollection) {\n // Defer the initial notify: useSyncExternalStore must not be notified\n // synchronously during subscribe.\n // Wholesale mode: React re-reads getSnapshot() on notify, keeps the\n // hook's pre-observer loading policy, and — because wholesale delivers\n // nothing synchronously during subscribe — never notifies\n // useSyncExternalStore inside its own subscribe call.\n observerRef.current = createLiveQueryObserver(collectionRef.current, {\n mode: `wholesale`,\n client: dbClient,\n queryHash: queryHashRef.current,\n onPreload: resumeDeferredCollections,\n })\n }\n const observer = observerRef.current!\n\n // Stable subscribe bound to the current observer; the observer owns the\n // subscription, ready-race, and disposal.\n const subscribeRef = useRef<\n ((onStoreChange: () => void) => () => void) | null\n >(null)\n if (!subscribeRef.current || needsNewCollection) {\n subscribeRef.current = (onStoreChange: () => void) => {\n const unsubscribe = observer.subscribe(() => onStoreChange())\n resumeDeferredCollections()\n return unsubscribe\n }\n }\n\n const returned = useSyncExternalStore(\n subscribeRef.current,\n () => observer.getSnapshot(),\n () => observer.getServerSnapshot(),\n )\n setLiveQueryResultInfo(returned, {\n client: dbClient,\n queryHash: queryHashRef.current,\n identityError: identityErrorRef.current,\n observer,\n })\n return returned as any\n}\n"],"names":["useRef","useSyncExternalStore","setLiveQueryResultInfo"],"mappings":";;;;;;;AAkCA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AAIA;AACA;AAEA;AACE;AACA;AACE;AACA;AAAgC;AAElC;AACF;AAEA;AAIE;AASA;AACA;AAAiE;AACb;AAEpD;AACF;AAEA;AAGE;AACA;AACA;AACE;AACA;AAAmD;AAErD;AACF;AAEA;AAKE;AACA;AACA;AACA;AACF;AA2CO;AAGL;AACE;AAAA;AAGF;AACA;AACE;AAAA;AAEF;AACA;AAIA;AAAQ;AAC8F;AAExG;AAEA;AACE;AACE;AAAO;AAGT;AAGF;AAEA;AACE;AAIF;AAEA;AACE;AACA;AAA0B;AAC1B;AAEG;AAI2C;AAE9C;AACF;AAEA;AAIE;AAIE;AAAA;AAGF;AAEA;AAIA;AACE;AAAA;AAGF;AACA;AACE;AACA;AAAA;AAGF;AACA;AAEA;AAIA;AAAQ;AAC2I;AAGrJ;AAEA;AACE;AAKF;AAEA;AACE;AAMF;AAEO;AAKL;AACF;AAcO;AAML;AAAsB;AACpB;AAEF;AACA;AAEA;AACE;AACA;AAAO;AACG;AACD;AAC2B;AAAA;AAGpC;AACE;AAAqD;AAGvD;AAAM;AAEN;AACE;AACA;AACA;AACA;AACA;AAA+C;AACjD;AAEJ;AAEO;AAGL;AACE;AAAA;AAGF;AACA;AACE;AAAA;AAEF;AAEA;AAAQ;AAC4F;AAItG;AAEA;AAIE;AACE;AAAO;AAGT;AACE;AACA;AAAO;AAGT;AACE;AAAiC;AACxB;AACI;AACH;AACT;AAGH;AACE;AAAiC;AACpB;AACH;AACJ;AACL;AAGH;AAAU;AACyH;AAErI;AAoYO;AAIL;AACF;AAGO;AAIL;AACF;AAEA;AAKE;AAEA;AACA;AAGA;AAGA;AAAsBA;AACpB;AAEF;AACA;AACA;AACA;AAA2D;AACzD;AAGF;AAAmE;AACpD;AACJ;AACF;AACC;AAEV;AAA+BA;AACzB;AAEN;AAAoBA;AAClB;AAEF;AACA;AACA;AAEA;AAGA;AAEA;AACA;AACA;AAEA;AACE;AACA;AAAsC;AAEtC;AACA;AACE;AAAqB;AACnB;AACA;AACuB;AAEzB;AAAiB;AACf;AACA;AAC+C;AAAA;AAGjD;AACA;AACA;AAAgB;AAClB;AAEA;AACA;AAA4D;AAE5D;AAAoB;AAClB;AACA;AAC2B;AACJ;AAEzB;AACA;AACE;AACA;AAA6B;AAE7B;AACA;AACA;AAA4B;AAC9B;AAGF;AACA;AACE;AACE;AAAyD;AAEzD;AACE;AACA;AAAgB;AAEhB;AAAM;AACR;AACF;AAGF;AACE;AAAA;AAGF;AAUA;AAQE;AAAqB;AACnB;AACA;AACuB;AAAA;AAI3B;AAOA;AAIA;AAGA;AAEA;AAQA;AAKA;AACE;AACE;AAAW;AAEb;AAA+B;AAGjC;AACE;AAIE;AAGA;AAIE;AAAQ;;AACN;AAAA;;AAAA;AAAA;AAAA;AAQJ;AACA;AACA;AAAoB;AAEpB;AACE;AAAwB;AAExB;AACE;AAAqB;AACnB;AACA;AACuB;AAAA;AAG3B;AAAwB;AACtB;AAC4C;AAE9C;AACE;AACA;AAAyC;AAAG;AAC1C;AACE;AACA;AACA;AAAA;AAGJ;AAIE;AAAA;AAEI;AACE;AACA;AACA;AAAA;AAEJ;AAAA;AAGJ;AAAuC;AACrC;AAEE;AACA;AAAA;AACF;AAEF;AAA0C;AAC5C;AAEF;AACA;AAAkC;AAEpC;AACA;AACA;AACA;AAA2B;AAS7B;AAOE;AAAqE;AAC7D;AACE;AACgB;AACb;AACZ;AAEH;AAIA;AAGA;AACE;AACE;AACA;AACA;AAAO;AACT;AAGF;AAAiBC;AACF;AACE;AACA;AAEjBC;AAAiC;AACvB;AACgB;AACQ;AAChC;AAEF;AACF;;;;;;;"}
@@ -1,4 +1,4 @@
1
- import { UnhashableQueryIRError, Collection, CollectionImpl, CollectionStatus, Context, DbClient, GetResult, InferResultType, InitialQueryBuilder, LiveQueryCollectionConfig, NonSingleResult, QueryBuilder, SingleResult } from '@tanstack/db';
1
+ import { UnhashableQueryIRError, Collection, CollectionImpl, CollectionStatus, Context, DbClient, GetResult, InferResultType, InitialQueryBuilder, LiveQueryCollectionConfig, LiveQueryPersistedStatus, NonSingleResult, QueryBuilder, SingleResult } from '@tanstack/db';
2
2
  export type DerivedIdentityProfiler = {
3
3
  renderCount: number;
4
4
  totalMs: number;
@@ -111,6 +111,9 @@ export declare function useLiveQuery<TContext extends Context>(queryFn: (q: Init
111
111
  status: CollectionStatus;
112
112
  isLoading: boolean;
113
113
  isReady: boolean;
114
+ persistedStatus: LiveQueryPersistedStatus;
115
+ isPersistedReady: boolean;
116
+ persistedError: unknown | undefined;
114
117
  isIdle: boolean;
115
118
  isError: boolean;
116
119
  isCleanedUp: boolean;
@@ -123,6 +126,9 @@ export declare function useLiveQuery<TContext extends Context>(queryFn: (q: Init
123
126
  status: UseLiveQueryStatus;
124
127
  isLoading: boolean;
125
128
  isReady: boolean;
129
+ persistedStatus: LiveQueryPersistedStatus;
130
+ isPersistedReady: boolean;
131
+ persistedError: unknown | undefined;
126
132
  isIdle: boolean;
127
133
  isError: boolean;
128
134
  isCleanedUp: boolean;
@@ -135,6 +141,9 @@ export declare function useLiveQuery<TContext extends Context>(queryFn: (q: Init
135
141
  status: UseLiveQueryStatus;
136
142
  isLoading: boolean;
137
143
  isReady: boolean;
144
+ persistedStatus: LiveQueryPersistedStatus;
145
+ isPersistedReady: boolean;
146
+ persistedError: unknown | undefined;
138
147
  isIdle: boolean;
139
148
  isError: boolean;
140
149
  isCleanedUp: boolean;
@@ -147,6 +156,9 @@ export declare function useLiveQuery<TResult extends object, TKey extends string
147
156
  status: UseLiveQueryStatus;
148
157
  isLoading: boolean;
149
158
  isReady: boolean;
159
+ persistedStatus: LiveQueryPersistedStatus;
160
+ isPersistedReady: boolean;
161
+ persistedError: unknown | undefined;
150
162
  isIdle: boolean;
151
163
  isError: boolean;
152
164
  isCleanedUp: boolean;
@@ -159,6 +171,9 @@ export declare function useLiveQuery<TContext extends Context, TResult extends o
159
171
  status: UseLiveQueryStatus;
160
172
  isLoading: boolean;
161
173
  isReady: boolean;
174
+ persistedStatus: LiveQueryPersistedStatus;
175
+ isPersistedReady: boolean;
176
+ persistedError: unknown | undefined;
162
177
  isIdle: boolean;
163
178
  isError: boolean;
164
179
  isCleanedUp: boolean;
@@ -206,6 +221,9 @@ export declare function useLiveQuery<TContext extends Context>(config: UseLiveQu
206
221
  status: CollectionStatus;
207
222
  isLoading: boolean;
208
223
  isReady: boolean;
224
+ persistedStatus: LiveQueryPersistedStatus;
225
+ isPersistedReady: boolean;
226
+ persistedError: unknown | undefined;
209
227
  isIdle: boolean;
210
228
  isError: boolean;
211
229
  isCleanedUp: boolean;
@@ -218,6 +236,9 @@ export declare function useLiveQuery<TContext extends Context>(config: Condition
218
236
  status: UseLiveQueryStatus;
219
237
  isLoading: boolean;
220
238
  isReady: boolean;
239
+ persistedStatus: LiveQueryPersistedStatus;
240
+ isPersistedReady: boolean;
241
+ persistedError: unknown | undefined;
221
242
  isIdle: boolean;
222
243
  isError: boolean;
223
244
  isCleanedUp: boolean;
@@ -230,6 +251,9 @@ export declare function useLiveQuery<TContext extends Context>(config: LiveQuery
230
251
  status: CollectionStatus;
231
252
  isLoading: boolean;
232
253
  isReady: boolean;
254
+ persistedStatus: LiveQueryPersistedStatus;
255
+ isPersistedReady: boolean;
256
+ persistedError: unknown | undefined;
233
257
  isIdle: boolean;
234
258
  isError: boolean;
235
259
  isCleanedUp: boolean;
@@ -242,6 +266,9 @@ export declare function useLiveQuery<TContext extends Context>(config: Condition
242
266
  status: UseLiveQueryStatus;
243
267
  isLoading: boolean;
244
268
  isReady: boolean;
269
+ persistedStatus: LiveQueryPersistedStatus;
270
+ isPersistedReady: boolean;
271
+ persistedError: unknown | undefined;
245
272
  isIdle: boolean;
246
273
  isError: boolean;
247
274
  isCleanedUp: boolean;
@@ -283,6 +310,9 @@ export declare function useLiveQuery<TResult extends object, TKey extends string
283
310
  status: CollectionStatus;
284
311
  isLoading: boolean;
285
312
  isReady: boolean;
313
+ persistedStatus: LiveQueryPersistedStatus;
314
+ isPersistedReady: boolean;
315
+ persistedError: unknown | undefined;
286
316
  isIdle: boolean;
287
317
  isError: boolean;
288
318
  isCleanedUp: boolean;
@@ -295,9 +325,14 @@ export declare function useLiveQuery<TResult extends object, TKey extends string
295
325
  status: CollectionStatus;
296
326
  isLoading: boolean;
297
327
  isReady: boolean;
328
+ persistedStatus: LiveQueryPersistedStatus;
329
+ isPersistedReady: boolean;
330
+ persistedError: unknown | undefined;
298
331
  isIdle: boolean;
299
332
  isError: boolean;
300
333
  isCleanedUp: boolean;
301
334
  isEnabled: true;
302
335
  };
336
+ /** @internal Shared implementation for the Suspense wrapper. */
337
+ export declare function useLiveQueryForSuspense(configOrQueryOrCollection: any, deps: Array<unknown> | undefined): any;
303
338
  export {};