@tanstack/react-db 0.3.8 → 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.
- package/dist/cjs/useLiveInfiniteQuery.cjs +8 -0
- package/dist/cjs/useLiveInfiniteQuery.cjs.map +1 -1
- package/dist/cjs/useLiveInfiniteQuery.d.cts +26 -10
- package/dist/cjs/useLiveQuery.cjs +108 -13
- package/dist/cjs/useLiveQuery.cjs.map +1 -1
- package/dist/cjs/useLiveQuery.d.cts +36 -1
- package/dist/cjs/useLiveSuspenseQuery.cjs +67 -12
- package/dist/cjs/useLiveSuspenseQuery.cjs.map +1 -1
- package/dist/esm/useLiveInfiniteQuery.d.ts +26 -10
- package/dist/esm/useLiveInfiniteQuery.js +8 -0
- package/dist/esm/useLiveInfiniteQuery.js.map +1 -1
- package/dist/esm/useLiveQuery.d.ts +36 -1
- package/dist/esm/useLiveQuery.js +109 -14
- package/dist/esm/useLiveQuery.js.map +1 -1
- package/dist/esm/useLiveSuspenseQuery.js +68 -13
- package/dist/esm/useLiveSuspenseQuery.js.map +1 -1
- package/package.json +3 -5
- package/src/useLiveInfiniteQuery.ts +41 -14
- package/src/useLiveQuery.ts +225 -13
- package/src/useLiveSuspenseQuery.ts +132 -23
|
@@ -8,6 +8,11 @@ const useLiveQuery = require("./useLiveQuery.cjs");
|
|
|
8
8
|
const DEFAULT_GC_TIME_MS = 1;
|
|
9
9
|
const unpreparedQueryValue = /* @__PURE__ */ Symbol(`unpreparedQueryValue`);
|
|
10
10
|
function useLiveInfiniteQuery(queryFnOrCollection, config, deps) {
|
|
11
|
+
if (`getNextPageParam` in config) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
`getNextPageParam is not supported by useLiveInfiniteQuery. Use an on-demand collection and fulfill meta.loadSubsetOptions in queryFn for server pagination.`
|
|
14
|
+
);
|
|
15
|
+
}
|
|
11
16
|
const pageSize = db.normalizeLiveQueryWindowPageSize(config.pageSize);
|
|
12
17
|
const initialPageParam = config.initialPageParam ?? 0;
|
|
13
18
|
const contextDbClient = DbProvider.useOptionalDbClient();
|
|
@@ -166,6 +171,9 @@ function useLiveInfiniteQuery(queryFnOrCollection, config, deps) {
|
|
|
166
171
|
status: snapshot.status,
|
|
167
172
|
isLoading: snapshot.isLoading,
|
|
168
173
|
isReady: snapshot.isReady,
|
|
174
|
+
persistedStatus: snapshot.persistedStatus,
|
|
175
|
+
isPersistedReady: snapshot.isPersistedReady,
|
|
176
|
+
persistedError: snapshot.persistedError,
|
|
169
177
|
isIdle: snapshot.isIdle,
|
|
170
178
|
isError: snapshot.isError,
|
|
171
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\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 initialPageParam?: number\n /**\n * @deprecated This callback is not used by the current implementation.\n * Pagination is determined internally via a peek-ahead strategy.\n * Provided for API compatibility with TanStack Query conventions.\n */\n getNextPageParam?: (\n lastPage: Array<InferResultType<TContext>[number]>,\n allPages: Array<Array<InferResultType<TContext>[number]>>,\n lastPageParam: number,\n allPageParams: Array<number>,\n ) => number | undefined\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 getNextPageParam\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 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;AA2FO;AAKL;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,6 +1,6 @@
|
|
|
1
1
|
import { LiveQueryKey, useLiveQuery } from './useLiveQuery.cjs';
|
|
2
|
-
import { Collection, Context, DbClient, InferResultType, InitialQueryBuilder, NonSingleResult, QueryBuilder } from '@tanstack/db';
|
|
3
|
-
export type UseLiveInfiniteQueryConfig<
|
|
2
|
+
import { Collection, CollectionStatus, Context, DbClient, InferResultType, InitialQueryBuilder, LiveQueryPersistedStatus, NonSingleResult, QueryBuilder } from '@tanstack/db';
|
|
3
|
+
export type UseLiveInfiniteQueryConfig<_TContext extends Context> = {
|
|
4
4
|
/**
|
|
5
5
|
* Explicit identity for queries that contain opaque functional variants or
|
|
6
6
|
* are hot enough that deriving identity from structured IR is too expensive.
|
|
@@ -10,13 +10,8 @@ export type UseLiveInfiniteQueryConfig<TContext extends Context> = {
|
|
|
10
10
|
/** Override the nearest DbProvider for this query. */
|
|
11
11
|
client?: DbClient;
|
|
12
12
|
pageSize?: number;
|
|
13
|
+
/** First result-page label, not a server cursor or remote offset. */
|
|
13
14
|
initialPageParam?: number;
|
|
14
|
-
/**
|
|
15
|
-
* @deprecated This callback is not used by the current implementation.
|
|
16
|
-
* Pagination is determined internally via a peek-ahead strategy.
|
|
17
|
-
* Provided for API compatibility with TanStack Query conventions.
|
|
18
|
-
*/
|
|
19
|
-
getNextPageParam?: (lastPage: Array<InferResultType<TContext>[number]>, allPages: Array<Array<InferResultType<TContext>[number]>>, lastPageParam: number, allPageParams: Array<number>) => number | undefined;
|
|
20
15
|
};
|
|
21
16
|
export type UseLiveInfiniteQueryReturn<TContext extends Context> = Omit<ReturnType<typeof useLiveQuery<TContext>>, `data`> & {
|
|
22
17
|
data: InferResultType<TContext>;
|
|
@@ -27,6 +22,27 @@ export type UseLiveInfiniteQueryReturn<TContext extends Context> = Omit<ReturnTy
|
|
|
27
22
|
isFetchingNextPage: boolean;
|
|
28
23
|
error: unknown;
|
|
29
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
|
+
};
|
|
30
46
|
/**
|
|
31
47
|
* Create an infinite query using a query function with live updates.
|
|
32
48
|
*
|
|
@@ -34,9 +50,9 @@ export type UseLiveInfiniteQueryReturn<TContext extends Context> = Omit<ReturnTy
|
|
|
34
50
|
* without recreating the live query collection on each page change.
|
|
35
51
|
*
|
|
36
52
|
* @param queryFn - Query function that defines what data to fetch. Must include `.orderBy()` for setWindow to work.
|
|
37
|
-
* @param config - Configuration including pageSize and
|
|
53
|
+
* @param config - Configuration including pageSize and an optional initial page label
|
|
38
54
|
* @param deps - Deprecated array of dependencies that trigger query re-execution when changed
|
|
39
55
|
* @returns Object with pages, data, and pagination controls
|
|
40
56
|
*/
|
|
41
|
-
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>):
|
|
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>;
|
|
42
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
|
-
|
|
42
|
-
`)
|
|
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:
|
|
167
|
+
gcTime: defaultGcTime
|
|
128
168
|
});
|
|
129
169
|
}
|
|
130
170
|
if (typeof value === `object`) {
|
|
131
171
|
return db.createLiveQueryCollection({
|
|
132
172
|
startSync: true,
|
|
133
|
-
gcTime:
|
|
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 (
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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 {};
|