{"version":3,"file":"useLiveQuery.cjs","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["'use client'\n\nimport { useEffect, useLayoutEffect, useRef, useSyncExternalStore } from 'react'\nimport {\n  UnhashableQueryIRError,\n  createLiveQueryObserver,\n  createLiveQuerySourceIdClaims,\n  deepEquals,\n  getLiveQueryKey,\n  getSourceObjectToken,\n  getStableValueHash,\n  identifyLiveQuery,\n  isCollection,\n  prepareLiveQueryValue,\n  rejectLiveQueryInput,\n  releaseDeferredOnReclaim,\n  resolveLiveQueryValue,\n  resumeDeferredLiveQueryCollections,\n} from '@tanstack/db'\nimport { useOptionalDbClient } from './DbProvider'\nimport { shouldWarnInDevelopment } from './development'\nimport {\n  getPreparedSources,\n  getUnboundDescriptorReporter,\n} from './source-id-bindings'\nimport type {\n  Collection,\n  CollectionImpl,\n  CollectionStatus,\n  Context,\n  DbClient,\n  DeferredLiveQueryCollections,\n  GetResult,\n  InferResultType,\n  InitialQueryBuilder,\n  LiveQueryCollectionConfig,\n  LiveQueryIdentityScope,\n  LiveQueryKey,\n  LiveQueryObserver,\n  LiveQuerySourceIdClaims,\n  LiveQueryStatusFlags,\n  NonSingleResult,\n  PooledLiveQueries,\n  QueryBuilder,\n  SingleResult,\n} from '@tanstack/db'\n\n// Use the client layout commit to schedule window moves. Server rendering has\n// no commit; React18 diagnoses a layout effect there even when it is empty.\nconst useWindowCommitEffect =\n  typeof window === `undefined` ? useEffect : useLayoutEffect\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 warnedDerivedIdentityCallsites = new Set<string>()\nconst unpreparedQueryValue = Symbol(`unpreparedQueryValue`)\n\ntype SuspenseCollection = Collection<object, string | number, {}>\ntype SuspenseCollectionEntry = {\n  collection: SuspenseCollection\n  removeStatusListeners: Array<() => void>\n}\n\nconst unscopedSuspenseScope = {}\nconst suspenseCollectionsByClient = new WeakMap<\n  object,\n  Map<string, SuspenseCollectionEntry>\n>()\nfunction getSourceQualifiedSuspenseKey(\n  preparedValue: unknown,\n  queryHash: string,\n  client: DbClient | undefined,\n): string {\n  const sourceIds = getPreparedSources(\n    preparedValue,\n    client,\n    `useLiveSuspenseQuery`,\n  ).map(getSourceObjectToken)\n  const {\n    query: _query,\n    pool: _pool,\n    ...options\n  } = (preparedValue as { query?: unknown } | null | undefined)?.query\n    ? (preparedValue as Record<string, unknown>)\n    : {}\n  // Local output options qualify sharing; query identity still binds mounted\n  // options. Object/function declarations compare by exact runtime reference.\n  options.autoIndex ??= `off`\n  for (const key in options) {\n    const value = options[key]\n    if (value === undefined) delete options[key]\n    else if (\n      value &&\n      (typeof value === `object` || typeof value === `function`)\n    )\n      options[key] = getSourceObjectToken(value)\n  }\n  return getStableValueHash([sourceIds, options, queryHash])\n}\n\nfunction getSuspenseCollections(\n  client: DbClient | undefined,\n): Map<string, SuspenseCollectionEntry> {\n  const scope = client ?? unscopedSuspenseScope\n  let collections = suspenseCollectionsByClient.get(scope)\n  if (!collections) {\n    collections = new Map()\n    suspenseCollectionsByClient.set(scope, collections)\n  }\n  return collections\n}\n\nexport type DerivedIdentityProfiler = {\n  renderCount: number\n  totalMs: number\n  maxMs: number\n  warned: boolean\n}\n\n/** Identity timing and warning state have no work to do in production. */\nexport function createDerivedIdentityProfiler():\n  DerivedIdentityProfiler | undefined {\n  try {\n    if (process.env.NODE_ENV !== `production`) {\n      return { renderCount: 0, totalMs: 0, maxMs: 0, warned: false }\n    }\n  } catch {\n    // Unbundled browsers may omit process; the warning policy handles that.\n  }\n  return undefined\n}\n\nexport type UseLiveQueryStatus = CollectionStatus | `disabled`\nexport type { LiveQueryKey }\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   * Pass `pooledLiveQueries` to share one partition among equality-filtered\n   * queries of one shape. Suspense and DbClient queries never pool.\n   */\n  pool?: PooledLiveQueries\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\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    !(\n      shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`) &&\n      process.env.NODE_ENV !== `production`\n    )\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\nexport { prepareLiveQueryValue as prepareQueryValue }\n\ntype DerivedQueryPreparation = {\n  value: unknown\n  identityDeps: Array<unknown>\n}\n\n/** How a hook checks a prepared query whose identity it compares. */\nexport function getIdentityScope(\n  claims: LiveQuerySourceIdClaims,\n  dbClient: DbClient | undefined,\n  hookName: string,\n): LiveQueryIdentityScope {\n  return {\n    claims,\n    client: dbClient,\n    bindingName: hookName,\n    onUnboundDescriptor: getUnboundDescriptorReporter(dbClient, hookName),\n  }\n}\n\n/**\n * Prepare a query without `queryKey`, derive its identity, and claim its\n * source Collection IDs. A query whose IR has no stable identity throws\n * `UnhashableQueryIRError`. The hook's render boundary releases the sync\n * starts a rejected render deferred.\n */\nexport function prepareDerivedQuery(\n  value: unknown,\n  profiler: DerivedIdentityProfiler | undefined,\n  scope: LiveQueryIdentityScope,\n  deferredCollections: Set<CollectionImpl<any, string | number, any, any, any>>,\n): DerivedQueryPreparation {\n  const shouldProfile =\n    profiler !== undefined &&\n    /* @__PURE__ */ shouldWarnInDevelopment(\n      `TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`,\n    )\n  // Keep literal build guards at both timing sites so production bundlers can\n  // erase the timer and warning machinery, as well as the profiler initializer.\n  const start =\n    shouldProfile && process.env.NODE_ENV !== `production`\n      ? getCurrentTime()\n      : 0\n  const preparedValue = prepareLiveQueryValue(\n    value,\n    scope.client,\n    deferredCollections,\n  )\n\n  try {\n    return {\n      value: preparedValue,\n      identityDeps: [`derived`, identifyLiveQuery(preparedValue, scope)],\n    }\n  } finally {\n    if (shouldProfile && process.env.NODE_ENV !== `production`) {\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\n/**\n * Create a live query using a query function.\n * @param queryFn - Query function that defines what data to fetch\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, isLoadingError, status } = useLiveQuery({\n *   query: (q) => q.from({ todos: todoCollection })\n * })\n *\n * if (isLoading) return <div>Loading...</div>\n * if (isLoadingError) 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): {\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  isEnabled: true // Always true if always returns QueryBuilder\n} & LiveQueryStatusFlags\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): {\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  isEnabled: boolean\n} & LiveQueryStatusFlags\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): {\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  isEnabled: boolean\n} & LiveQueryStatusFlags\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): {\n  state: Map<TKey, TResult> | undefined\n  data: Array<TResult> | undefined\n  collection: Collection<TResult, TKey, TUtils> | undefined\n  status: UseLiveQueryStatus\n  isEnabled: boolean\n} & LiveQueryStatusFlags\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): {\n  state:\n    Map<string | number, GetResult<TContext>> | Map<TKey, TResult> | 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  isEnabled: boolean\n} & LiveQueryStatusFlags\n\n/**\n * Create a live query using configuration object\n * @param config - Configuration object with query and options\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, isLoadingError } = useLiveQuery({\n *   query: (q) => q.from({ items: itemCollection })\n * })\n *\n * if (isLoading) return <div>Loading...</div>\n * if (isLoadingError) 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  isEnabled: true // Always true when query always returns a builder\n} & LiveQueryStatusFlags\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  isEnabled: boolean\n} & LiveQueryStatusFlags\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, isLoadingError } = useLiveQuery(sharedCollection)\n *\n * if (isLoading) return <div>Loading...</div>\n * if (isLoadingError) 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  isEnabled: true // Always true for pre-created live query collections\n} & LiveQueryStatusFlags\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  isEnabled: true // Always true for pre-created live query collections\n} & LiveQueryStatusFlags\n\n// Implementation - use function overloads to infer the actual collection type\nexport function useLiveQuery(configOrQueryOrCollection: any) {\n  return useLiveQueryImpl(configOrQueryOrCollection) as any\n}\n\n/** @internal Shared implementation for the Suspense wrapper. */\nexport function useLiveQueryForSuspense(configOrQueryOrCollection: unknown) {\n  return useLiveQueryImpl(configOrQueryOrCollection, true)\n}\n\n// What one hook instance keeps across renders. Fields produced by resolution\n// are installed with their first value, rather than initialized and overwritten\n// in the same render.\ntype HookInstance = {\n  collection: Collection<object, string | number, {}> | null\n  identityDeps: Array<unknown> | null\n  deferredCollections: Set<CollectionImpl<any, string | number, any, any, any>>\n  sourceIds: LiveQuerySourceIdClaims\n  config?: unknown\n  client?: DbClient\n  derivedIdentityProfiler?: DerivedIdentityProfiler\n  cancelReclaimRelease?: () => void\n  observer?: LiveQueryObserver<object, string | number>\n  queryHash?: string\n  identityError?: UnhashableQueryIRError\n  subscribe?: (onStoreChange: () => void) => () => void\n}\n\ntype LiveQueryResult = ReturnType<\n  LiveQueryObserver<object, string | number>[`getSnapshot`]\n>\n\nfunction useLiveQueryImpl(configOrQueryOrCollection: unknown): LiveQueryResult\nfunction useLiveQueryImpl(\n  configOrQueryOrCollection: unknown,\n  forSuspense: true,\n): {\n  result: LiveQueryResult\n  client: DbClient | undefined\n  queryHash: string | undefined\n  identityError: UnhashableQueryIRError | undefined\n  observer: LiveQueryObserver<object, string | number>\n  /** Sync starts this render deferred, released if the render throws. */\n  deferredCollections: DeferredLiveQueryCollections\n}\nfunction useLiveQueryImpl(configOrQueryOrCollection: any, forSuspense = false) {\n  const contextDbClient = useOptionalDbClient()\n  // Check if it's already a collection\n  const inputIsCollection = isCollection(configOrQueryOrCollection)\n  const dbClient: DbClient | undefined =\n    (inputIsCollection || typeof configOrQueryOrCollection !== `object`\n      ? undefined\n      : configOrQueryOrCollection?.client) ?? contextDbClient\n\n  const instanceRef = useRef<HookInstance | null>(null)\n  const instance: HookInstance = (instanceRef.current ??= {\n    collection: null,\n    identityDeps: null,\n    derivedIdentityProfiler: createDerivedIdentityProfiler(),\n    deferredCollections: new Set(),\n    sourceIds: createLiveQuerySourceIdClaims(),\n  })\n  // A commit releases the starts itself, so it drops the reclaim listener.\n  const resumeDeferredCollections = () => {\n    instance.cancelReclaimRelease?.()\n    instance.cancelReclaimRelease = undefined\n    resumeDeferredLiveQueryCollections(instance.deferredCollections)\n  }\n\n  // The render boundary: a render that throws anywhere after preparation\n  // never commits, so it releases the sync starts it deferred here.\n  try {\n    const queryKey = !inputIsCollection\n      ? getLiveQueryKey(configOrQueryOrCollection)\n      : undefined\n    let preparedQueryValue: unknown | typeof unpreparedQueryValue =\n      unpreparedQueryValue\n    let identityDeps: ReadonlyArray<unknown>\n    let streamIdentity: ReadonlyArray<unknown>\n    let identityError: UnhashableQueryIRError | undefined\n\n    if (queryKey) {\n      identityDeps = queryKey\n      streamIdentity = [`queryKey`, queryKey]\n    } else if (inputIsCollection) {\n      identityDeps = []\n      streamIdentity = [`collection`, configOrQueryOrCollection.id]\n    } else {\n      const preparation = prepareDerivedQuery(\n        configOrQueryOrCollection,\n        instance.derivedIdentityProfiler,\n        getIdentityScope(instance.sourceIds, dbClient, `useLiveQuery`),\n        instance.deferredCollections,\n      )\n      preparedQueryValue = preparation.value\n      identityDeps = preparation.identityDeps\n      streamIdentity = preparation.identityDeps\n    }\n\n    let queryHash: string | undefined\n    try {\n      queryHash = getStableValueHash(streamIdentity, `queryKey`)\n    } catch (error) {\n      if (!(error instanceof UnhashableQueryIRError) || queryKey !== undefined)\n        throw error\n      identityError = error\n    }\n\n    // Check if we need to create/recreate the collection\n    let needsNewCollection =\n      !instance.collection ||\n      (inputIsCollection\n        ? instance.config !== configOrQueryOrCollection\n        : instance.client !== dbClient ||\n          !deepEquals(instance.identityDeps, identityDeps))\n\n    if (\n      needsNewCollection &&\n      !inputIsCollection &&\n      preparedQueryValue === unpreparedQueryValue\n    ) {\n      preparedQueryValue = prepareLiveQueryValue(\n        configOrQueryOrCollection,\n        dbClient,\n        instance.deferredCollections,\n      )\n    }\n\n    let commitWindow: (() => void) | undefined\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        if (\n          /* @__PURE__ */ shouldWarnInDevelopment(\n            `TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`,\n          ) &&\n          process.env.NODE_ENV !== `production` &&\n          (configOrQueryOrCollection as { config?: { syncMode?: string } })\n            .config?.syncMode === `on-demand`\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        instance.collection = configOrQueryOrCollection\n      } else {\n        // Only a new binding consults the shared cache. Mounted bindings keep\n        // their Collection and its first options until query identity changes.\n        // The creation path above has already prepared keyed inputs too.\n        const suspenseKey =\n          forSuspense && queryHash\n            ? getSourceQualifiedSuspenseKey(\n                preparedQueryValue,\n                queryHash,\n                dbClient,\n              )\n            : undefined\n        const suspenseCollections = suspenseKey\n          ? getSuspenseCollections(dbClient)\n          : undefined\n        const suspenseCollection = suspenseCollections?.get(\n          suspenseKey!,\n        )?.collection\n        if (suspenseCollection) {\n          instance.collection = suspenseCollection\n        } else {\n          // A keyed query claims nothing, so it reports an unbound descriptor\n          // here; the other forms reported it when they claimed.\n          if (!dbClient && queryKey !== undefined) {\n            getPreparedSources(preparedQueryValue, dbClient, `useLiveQuery`)\n          }\n          instance.collection = resolveLiveQueryValue(preparedQueryValue, {\n            gcTime: forSuspense\n              ? DEFAULT_SUSPENSE_GC_TIME_MS\n              : DEFAULT_GC_TIME_MS,\n            // Hydration and Suspense key the live-query Collection by identity,\n            // so they never pool, even when the config passes a pool.\n            pool: forSuspense || dbClient ? false : undefined,\n            previousCollection:\n              forSuspense || dbClient ? undefined : instance.collection,\n            windowOwner: instance,\n            onWindowChange: (move) => {\n              commitWindow = move\n            },\n            // Without a DbClient, Suspense starts it through a preload below.\n            startSync: !forSuspense || dbClient !== undefined,\n          }) as SuspenseCollection | null\n          // A render that never commits releases its deferred sync starts\n          // when garbage collection reclaims the live query it built. A\n          // Collection the callback returned is not this render's to reclaim.\n          if (instance.collection && !isCollection(preparedQueryValue)) {\n            instance.cancelReclaimRelease?.()\n            instance.cancelReclaimRelease = releaseDeferredOnReclaim(\n              instance.collection,\n              instance.deferredCollections,\n            )\n          }\n          if (suspenseCollections && instance.collection) {\n            const collection = instance.collection\n            const releaseCollection = () => {\n              const entry = suspenseCollections.get(suspenseKey!)\n              if (entry?.collection !== collection) return\n              suspenseCollections.delete(suspenseKey!)\n              entry.removeStatusListeners.forEach((remove) => remove())\n            }\n            // Keep a failed or crashed collection through React's immediate\n            // retry so the hook can throw its actual error to an ErrorBoundary.\n            // Retire it afterward so a later boundary reset starts fresh.\n            const retireAfterRetry = () => setTimeout(releaseCollection, 0)\n            suspenseCollections.set(suspenseKey!, {\n              collection,\n              removeStatusListeners: [\n                collection.on(`status:cleaned-up`, releaseCollection),\n                collection.on(`status:error`, retireAfterRetry),\n                collection.on(`status:crashed`, retireAfterRetry),\n              ],\n            })\n          }\n        }\n        // An abandoned render must leave invalidation pending. Its retry or\n        // urgent replacement still prepares and commits the requested move.\n        if (!commitWindow) instance.identityDeps = [...identityDeps]\n      }\n      instance.config = configOrQueryOrCollection\n      instance.client = dbClient\n      instance.queryHash = queryHash\n      instance.identityError = identityError\n    }\n\n    if (commitWindow) needsNewCollection = false\n    useWindowCommitEffect(() => {\n      if (commitWindow) {\n        instance.identityDeps = [...identityDeps]\n        commitWindow()\n      }\n    })\n\n    if (needsNewCollection) {\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      // 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      const observer = (instance.observer = createLiveQueryObserver(\n        instance.collection,\n        {\n          mode: `wholesale`,\n          client: dbClient,\n          queryHash: instance.queryHash,\n          onPreload: resumeDeferredCollections,\n          suspendUntilReady: forSuspense,\n        },\n      ))\n      // The subscribe function stays bound to the observer that created it.\n      instance.subscribe = (onStoreChange: () => void) => {\n        const unsubscribe = observer.subscribe(onStoreChange)\n        resumeDeferredCollections()\n        return unsubscribe\n      }\n    }\n    const observer = instance.observer!\n    // A Suspense render asks for its data. Preloading starts the sync run, so a\n    // source that already holds the rows makes it ready before the snapshot is\n    // read instead of suspending once. useLiveSuspenseQuery surfaces failures.\n    // A DbClient defers source starts past render and owns its streamed query.\n    if (\n      forSuspense &&\n      !dbClient &&\n      (instance.collection?.status === `idle` ||\n        instance.collection?.status === `cleaned-up`)\n    ) {\n      observer.preload().catch(() => {})\n    }\n\n    const returned = useSyncExternalStore(\n      instance.subscribe!,\n      () => observer.getSnapshot(),\n      () => observer.getServerSnapshot(forSuspense),\n    )\n    // Pass render-local metadata directly to the Suspense wrapper. The public\n    // hook returns the observer's stable snapshot without a wrapper allocation.\n    if (forSuspense) {\n      return {\n        result: returned,\n        client: dbClient,\n        queryHash: instance.queryHash,\n        identityError: instance.identityError,\n        observer,\n        deferredCollections: instance.deferredCollections,\n      }\n    }\n    return returned\n  } catch (error) {\n    return rejectLiveQueryInput(instance.deferredCollections, error)\n  }\n}\n"],"names":["getPreparedSources","shouldWarnInDevelopment","prepareLiveQueryValue","resumeDeferredLiveQueryCollections","releaseDeferredOnReclaim","createLiveQueryObserver","useSyncExternalStore"],"mappings":";;;;;;;;AAiDA;AAGA;AAGA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AAIA;AAKE;AAAkBA;AAChB;AACA;AACA;AAEF;AAAM;AACG;AACD;AACH;AAML;AACA;AACE;AACA;AAA2C;AAKzC;AAAyC;AAE7C;AACF;AAEA;AAGE;AACA;AACA;AACE;AACA;AAAkD;AAEpD;AACF;AAUO;AAEL;AACE;AACE;AAAuD;AACzD;AACM;AAGR;AACF;AAyCA;AACE;AAIF;AAEA;AACE;AACA;AAA0B;AAC1B;AAEG;AAI2C;AAE9C;AACF;AAEA;AAIE;AAOE;AAAA;AAGF;AAEA;AAIA;AACE;AAAA;AAGF;AACA;AACE;AACA;AAAA;AAGF;AACA;AAEA;AAIA;AAAQ;AAC2I;AAGrJ;AAUO;AAKL;AAAO;AACL;AACQ;AACK;AACuD;AAExE;AAQO;AAML;AAEkBC;AACd;AAIJ;AAIA;AAAsBC;AACpB;AACM;AACN;AAGF;AACE;AAAO;AACE;AAC0D;AAAA;AAGnE;AACE;AACA;AACA;AACA;AACA;AAA+C;AACjD;AAEJ;AA2QO;AACL;AACF;AAGO;AACL;AACF;AAqCA;AACE;AAEA;AACA;AAKA;AACA;AAAwD;AAC1C;AACE;AACW;AACA;AACd;AAGb;AACE;AACA;AACAC;AAA+D;AAKjE;AACE;AAGA;AAEA;AACA;AACA;AAEA;AACE;AACA;AAAsC;AAEtC;AACA;AAA4D;AAE5D;AAAoB;AAClB;AACS;AACoD;AACpD;AAEX;AACA;AACA;AAA6B;AAG/B;AACA;AACE;AAAyD;AAEzD;AACE;AACF;AAAgB;AAIlB;AAOA;AAKE;AAAqBD;AACnB;AACA;AACS;AAAA;AAIb;AACA;AACE;AAIE;AACkBD;AACd;AAMF;AAAQ;;AACN;AAAA;;AAAA;AAAA;AAAA;AAQJ;AACA;AAAsB;AAKtB;AAEM;AACE;AACA;AACA;AAGR;AAGA;AAAgD;AAC9C;AAEF;AACE;AAAsB;AAItB;AACED;AAA+D;AAEjE;AAAgE;AAG1D;AAAA;AAAA;AAGoC;AAES;AACpC;AAEX;AAAe;AACjB;AAAA;AAEwC;AAK1C;AACE;AACA;AAAgCI;AACrB;AACA;AAAA;AAGb;AACE;AACA;AACE;AACA;AACA;AACA;AAAwD;AAK1D;AACA;AAAsC;AACpC;AACuB;AAC+B;AACN;AACE;AAAA;AAEnD;AACH;AAIF;AAA2D;AAE7D;AACA;AACA;AACA;AAAyB;AAG3B;AACA;AACE;AACE;AACA;AAAA;AACF;AAGF;AAaE;AAAsCC;AAC3B;AACT;AACQ;AACE;AACY;AACT;AACQ;AAAA;AAIvB;AACE;AACA;AACA;AAAO;AACT;AAEF;AAKA;AAME;AAA+B;AAAE;AAGnC;AAAiBC;AACN;AACM;AAC6B;AAI9C;AACE;AAAO;AACG;AACA;AACY;AACI;AACxB;AAC8B;AAAA;AAGlC;AAAO;AAEP;AAA+D;AAEnE;;;;;;;;;;"}