@tanstack/react-db 0.5.4 → 0.5.5
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.
|
@@ -167,7 +167,9 @@ function useLiveInfiniteQuery(queryFnOrCollection, config, deps) {
|
|
|
167
167
|
);
|
|
168
168
|
return {
|
|
169
169
|
data: snapshot.data,
|
|
170
|
-
state
|
|
170
|
+
get state() {
|
|
171
|
+
return snapshot.state;
|
|
172
|
+
},
|
|
171
173
|
status: snapshot.status,
|
|
172
174
|
isLoading: snapshot.isLoading,
|
|
173
175
|
isReady: snapshot.isReady,
|
|
@@ -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 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
|
+
{"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 get state() {\n return snapshot.state as EnabledLiveQueryReturn<TContext>[`state`]\n },\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;AAEb;AAAgB;AAClB;AACiB;AACG;AACF;AACQ;AACC;AACF;AACR;AACC;AACI;AAEX;AACS;AACJ;AACK;AACrB;AACsB;AACO;AACb;AAEpB;;"}
|
|
@@ -165,7 +165,9 @@ function useLiveInfiniteQuery(queryFnOrCollection, config, deps) {
|
|
|
165
165
|
);
|
|
166
166
|
return {
|
|
167
167
|
data: snapshot.data,
|
|
168
|
-
state
|
|
168
|
+
get state() {
|
|
169
|
+
return snapshot.state;
|
|
170
|
+
},
|
|
169
171
|
status: snapshot.status,
|
|
170
172
|
isLoading: snapshot.isLoading,
|
|
171
173
|
isReady: snapshot.isReady,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLiveInfiniteQuery.js","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":[],"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;AACA;AAA8C;AAM9C;AACA;AAAyC;AAEzC;AACA;AAAoB;AAClB;AACA;AAC2B;AAC3B;AAEF;AACA;AACE;AAA2B;AAE3B;AACA;AAA2C;AAC7C;AACF;AAGF;AAEA;AAA6B;AAChB;AACX;AAEF;AACA;AAMA;AAEA;AAKA;AAIA;AAGA;AACA;AACE;AACA;AAEA;AACE;AACA;AACE;AACE;AAAqB;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;AAEnE;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;AAAkB;AAEd;AACA;AACA;AACE;AACA;AAAuC;AAEzC;AACE;AAAW;AAEb;AACA;AAAO;AACT;AAC+B;AAEjC;AACA;AAEA;AAAsB;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
|
+
{"version":3,"file":"useLiveInfiniteQuery.js","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 get state() {\n return snapshot.state as EnabledLiveQueryReturn<TContext>[`state`]\n },\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":[],"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;AACA;AAA8C;AAM9C;AACA;AAAyC;AAEzC;AACA;AAAoB;AAClB;AACA;AAC2B;AAC3B;AAEF;AACA;AACE;AAA2B;AAE3B;AACA;AAA2C;AAC7C;AACF;AAGF;AAEA;AAA6B;AAChB;AACX;AAEF;AACA;AAMA;AAEA;AAKA;AAIA;AAGA;AACA;AACE;AACA;AAEA;AACE;AACA;AACE;AACE;AAAqB;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;AAEnE;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;AAAkB;AAEd;AACA;AACA;AACE;AACA;AAAuC;AAEzC;AACE;AAAW;AAEb;AACA;AAAO;AACT;AAC+B;AAEjC;AACA;AAEA;AAAsB;AACyB;AAClC;AAGb;AAAO;AACU;AAEb;AAAgB;AAClB;AACiB;AACG;AACF;AACQ;AACC;AACF;AACR;AACC;AACI;AAEX;AACS;AACJ;AACK;AACrB;AACsB;AACO;AACb;AAEpB;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/react-db",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "React integration for @tanstack/db",
|
|
5
5
|
"author": "Kyle Mathews",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"skills"
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@tanstack/db": "0.12.
|
|
43
|
+
"@tanstack/db": "0.12.1"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"react": ">=18.0.0"
|
|
@@ -355,7 +355,9 @@ export function useLiveInfiniteQuery<TContext extends Context>(
|
|
|
355
355
|
|
|
356
356
|
return {
|
|
357
357
|
data: snapshot.data as InferResultType<TContext>,
|
|
358
|
-
|
|
358
|
+
get state() {
|
|
359
|
+
return snapshot.state as EnabledLiveQueryReturn<TContext>[`state`]
|
|
360
|
+
},
|
|
359
361
|
status: snapshot.status as EnabledLiveQueryReturn<TContext>[`status`],
|
|
360
362
|
isLoading: snapshot.isLoading,
|
|
361
363
|
isReady: snapshot.isReady,
|