{"version":3,"file":"useLiveInfiniteQuery.cjs","sources":["../../src/useLiveInfiniteQuery.ts"],"sourcesContent":["'use client'\n\nimport { useCallback, useRef, useSyncExternalStore } from 'react'\nimport {\n  bindLiveQueryWindow,\n  createLiveQuerySourceIdClaims,\n  fetchNextLiveQueryWindowPage,\n  getLiveQueryKey,\n  isCollection,\n  readLiveQueryWindowPageShape,\n  rejectLiveQueryInput,\n  releaseDeferredOnReclaim,\n  resumeDeferredLiveQueryCollections,\n} from '@tanstack/db'\nimport { useOptionalDbClient } from './DbProvider'\nimport {\n  getIdentityScope,\n  prepareDerivedQuery,\n  prepareQueryValue,\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  LiveQuerySourceIdClaims,\n  LiveQueryStatusFlags,\n  LiveQueryWindowBinding,\n  NonSingleResult,\n  QueryBuilder,\n} from '@tanstack/db'\n\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}\n\nexport type UseLiveInfiniteQueryReturnWithCollection<\n  TResult extends object,\n  TKey extends string | number,\n  TUtils extends Record<string, any>,\n> = LiveQueryStatusFlags & {\n  data: Array<TResult>\n  state: Map<TKey, TResult>\n  collection: Collection<TResult, TKey, TUtils> & NonSingleResult\n  status: CollectionStatus\n  isEnabled: true\n  pages: Array<Array<TResult>>\n  pageParams: Array<number>\n  fetchNextPage: () => Promise<void>\n  hasNextPage: boolean\n  isFetchingNextPage: boolean\n}\n\ntype EnabledLiveQueryReturn<TContext extends Context> = ReturnType<\n  typeof useLiveQuery<TContext>\n>\n\n/** One render's window binding and what its commit still owes. */\ntype InfiniteQueryRender = {\n  binding: LiveQueryWindowBinding\n  /** The query callback whose identity this render derived, if any. */\n  derivedQuery: unknown\n  deferredCollections: Set<\n    CollectionImplType<any, string | number, any, any, any>\n  >\n  /** Drops the reclaim release when this render commits. */\n  cancelReclaimRelease: (() => void) | undefined\n  warned: boolean\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 * @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): UseLiveInfiniteQueryReturn<TContext>\n\n// Implementation\nexport function useLiveInfiniteQuery<TContext extends Context>(\n  queryFnOrCollection: any,\n  config: UseLiveInfiniteQueryConfig<TContext>,\n): UseLiveInfiniteQueryReturn<TContext> {\n  const pageShape = readLiveQueryWindowPageShape(config)\n  const contextDbClient = useOptionalDbClient()\n  const dbClient = config.client ?? contextDbClient\n  const inputIsCollection = isCollection(queryFnOrCollection)\n\n  const committedRef = useRef<InfiniteQueryRender | null>(null)\n  const committed = committedRef.current\n  // Like useLiveQuery's instance memo, keep the latest render whose binding\n  // built a window collection. A second render before commit, such as a\n  // StrictMode double render, reuses that collection instead of starting\n  // another. Each render still builds its own controller and page count from\n  // the committed binding, which an uncommitted render never changes.\n  const renderedRef = useRef<InfiniteQueryRender | null>(null)\n  const sourceIdsRef = useRef<LiveQuerySourceIdClaims | null>(null)\n  const derivedIdentityProfilerRef = useRef<DerivedIdentityProfiler>({\n    renderCount: 0,\n    totalMs: 0,\n    maxMs: 0,\n    warned: false,\n  })\n  const deferredCollections = new Set<\n    CollectionImplType<any, string | number, any, any, any>\n  >()\n  const identityScope = () =>\n    getIdentityScope(\n      (sourceIdsRef.current ??= createLiveQuerySourceIdClaims()),\n      dbClient,\n      `useLiveInfiniteQuery`,\n    )\n\n  const queryKey = getLiveQueryKey(config)\n  let preparedQueryValue: unknown | typeof unpreparedQueryValue =\n    unpreparedQueryValue\n  let dependencies: ReadonlyArray<unknown> | undefined\n  let derivedQuery: unknown = undefined\n\n  const rendered = renderedRef.current\n  let binding: LiveQueryWindowBinding\n  // The render boundary: a render that throws anywhere after preparation,\n  // such as an unhashable, single-result, or uncompilable query, never\n  // commits, so it releases the sync starts it deferred here.\n  try {\n    if (!inputIsCollection) {\n      if (queryKey !== undefined) {\n        dependencies = queryKey\n      } else if (\n        committed !== null &&\n        committed.derivedQuery === queryFnOrCollection &&\n        committed.binding.scope === dbClient\n      ) {\n        // The same callback in the same client scope keeps its identity.\n        derivedQuery = queryFnOrCollection\n        dependencies = committed.binding.dependencies\n      } else {\n        const preparation = prepareDerivedQuery(\n          queryFnOrCollection,\n          derivedIdentityProfilerRef.current,\n          identityScope(),\n          deferredCollections,\n        )\n        preparedQueryValue = preparation.value\n        derivedQuery = queryFnOrCollection\n        dependencies = preparation.identityDeps\n      }\n    }\n\n    // The binding calls this only when it compiles a new window collection.\n    const compileQuery = () => {\n      if (preparedQueryValue === unpreparedQueryValue) {\n        preparedQueryValue = prepareQueryValue(\n          queryFnOrCollection,\n          dbClient,\n          deferredCollections,\n        )\n      }\n      return preparedQueryValue\n    }\n\n    binding = bindLiveQueryWindow(\n      committed?.binding ?? null,\n      {\n        ...pageShape,\n        input:\n          typeof queryFnOrCollection === `function`\n            ? compileQuery\n            : queryFnOrCollection,\n        dependencies,\n        scope: dbClient,\n        client: dbClient,\n        queryKey,\n        recompile: `identity`,\n      },\n      rendered?.binding,\n    )\n  } catch (error) {\n    rejectLiveQueryInput(deferredCollections, error)\n  }\n\n  let currentRenderState = committed\n  if (binding !== committed?.binding) {\n    const newCollection = binding.collection !== committed?.binding.collection\n    if (newCollection && binding.collection === rendered?.binding.collection) {\n      // Sources the earlier render deferred still resume at this commit.\n      for (const deferred of rendered.deferredCollections) {\n        deferredCollections.add(deferred)\n      }\n    }\n    const reusesRendered =\n      newCollection && binding.collection === rendered?.binding.collection\n    currentRenderState = {\n      binding,\n      derivedQuery,\n      deferredCollections,\n      // A render that never commits releases its deferred sync starts when\n      // garbage collection reclaims the window collection it built. A reused\n      // collection keeps the release its first render registered.\n      cancelReclaimRelease: reusesRendered\n        ? rendered.cancelReclaimRelease\n        : newCollection && binding.ownsCollection\n          ? releaseDeferredOnReclaim(binding.collection, deferredCollections)\n          : undefined,\n      warned: false,\n    }\n    // Keep a created collection for the next render only once it validated\n    // and started, so a retry after a startup error builds a new one.\n    if (binding.supplied) renderedRef.current = null\n    else if (newCollection && binding.ownsCollection) {\n      renderedRef.current = currentRenderState\n    }\n  }\n  const renderState = currentRenderState!\n  const controller = renderState.binding.controller\n\n  const subscribe = useCallback(\n    (onStoreChange: () => void) => {\n      const unsubscribe = controller.subscribe(onStoreChange)\n      committedRef.current = renderState\n      if (renderState.binding.warning && !renderState.warned) {\n        renderState.warned = true\n        console.warn(renderState.binding.warning)\n      }\n      // A commit releases the starts itself, so it drops the reclaim release.\n      renderState.cancelReclaimRelease?.()\n      resumeDeferredLiveQueryCollections(renderState.deferredCollections)\n      return unsubscribe\n    },\n    [controller, renderState],\n  )\n  const getSnapshot = useCallback(() => controller.getSnapshot(), [controller])\n  const snapshot = useSyncExternalStore(subscribe, getSnapshot, () =>\n    controller.getServerSnapshot(),\n  )\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    isIdle: snapshot.isIdle,\n    isLoadingError: snapshot.isLoadingError,\n    isCrashed: snapshot.isCrashed,\n    isCleanedUp: snapshot.isCleanedUp,\n    isSyncError: snapshot.isSyncError,\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    health: snapshot.health,\n  }\n}\n"],"names":["getIdentityScope","prepareDerivedQuery","prepareQueryValue","bindLiveQueryWindow","rejectLiveQueryInput","useCallback","resumeDeferredLiveQueryCollections","useSyncExternalStore"],"mappings":";;;;;;;AAwCA;AA2FO;AAIL;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AAAmE;AACpD;AACJ;AACF;AACC;AAEV;AAGA;AACEA;AAC4B;AAC1B;AACA;AAGJ;AACA;AAEA;AACA;AAEA;AACA;AAIA;AACE;AACE;AACE;AAAe;AAOf;AACA;AAAiC;AAEjC;AAAoBC;AAClB;AAC2B;AAC3B;AACA;AAEF;AACA;AACA;AAA2B;AAC7B;AAIF;AACE;AACE;AAAqBC;AACnB;AACA;AACA;AAAA;AAGJ;AAAO;AAGT;AAAUC;AACc;AACtB;AACK;AAIG;AACN;AACO;AACC;AACR;AACW;AAAA;AAEH;AAAA;AAGZC;AAA+C;AAGjD;AACA;AACE;AACA;AAEE;AACE;AAAgC;AAClC;AAEF;AAEA;AAAqB;AACnB;AACA;AACA;AAAA;AAAA;AAAA;AAQM;AACE;AAIV;AAA4C;AAE1C;AAAsB;AACxB;AAEF;AACA;AAEA;AAAkBC;AAEd;AACA;AACA;AACE;AACA;AAAwC;AAG1C;AACAC;AACA;AAAO;AACT;AACwB;AAE1B;AACA;AAAiBC;AAAqB;AAAW;AACpC;AAGb;AAAsBF;AACyB;AAClC;AAGb;AAAO;AACU;AAEb;AAAgB;AAClB;AACiB;AACG;AACF;AACD;AACQ;AACL;AACE;AACA;AAEX;AACS;AACJ;AACK;AACrB;AACsB;AACO;AACZ;AAErB;;"}