{"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:\n      snapshot.isEnabled as EnabledLiveQueryReturn<TContext>[`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;AAEA;AACK;AACK;AACrB;AACsB;AACO;AACb;AAEpB;;"}