{"version":3,"file":"useLiveQuery.cjs","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["'use client'\n\nimport { useRef, useSyncExternalStore } from 'react'\nimport {\n  BaseQueryBuilder,\n  UnhashableQueryIRError,\n  createLiveQueryCollection,\n  createLiveQueryObserver,\n  deepEquals,\n  getPreparedLiveQueryIdentity,\n  getStableValueHash,\n  isCollection,\n  prepareLiveQueryValue,\n} from '@tanstack/db'\nimport { useOptionalDbClient } from './DbProvider'\nimport { setLiveQueryResultInfo } from './live-query-internals'\nimport type {\n  Collection,\n  CollectionImpl,\n  CollectionStatus,\n  Context,\n  DbClient,\n  GetResult,\n  InferResultType,\n  InitialQueryBuilder,\n  LiveQueryCollectionConfig,\n  LiveQueryObserver,\n  NonSingleResult,\n  QueryBuilder,\n  SingleResult,\n} from '@tanstack/db'\n\nconst DEFAULT_GC_TIME_MS = 1 // Live queries created by useLiveQuery are cleaned up immediately (0 disables GC)\nconst DERIVED_IDENTITY_SINGLE_RENDER_WARN_MS = 16\nconst DERIVED_IDENTITY_RENDER_COUNT_WARN_THRESHOLD = 10\nconst DERIVED_IDENTITY_TOTAL_WARN_MS = 50\nconst warnedDepsCallsites = new Set<string>()\nconst warnedDerivedIdentityCallsites = new Set<string>()\nconst warnedUnhashableIdentityCallsites = new Set<string>()\nconst unpreparedQueryValue = Symbol(`unpreparedQueryValue`)\n\nexport type DerivedIdentityProfiler = {\n  renderCount: number\n  totalMs: number\n  maxMs: number\n  warned: boolean\n}\n\nexport type UseLiveQueryStatus = CollectionStatus | `disabled`\nexport type LiveQueryKey = ReadonlyArray<unknown>\nexport type UseLiveQueryConfig<TContext extends Context> =\n  LiveQueryCollectionConfig<TContext> & {\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\nexport function warnDeprecatedDepsArray(\n  hookName: `useLiveQuery` | `useLiveInfiniteQuery` = `useLiveQuery`,\n): void {\n  if (!shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_DEPRECATION_WARNINGS`)) {\n    return\n  }\n\n  const callsite = getWarningCallsite(4)\n  if (warnedDepsCallsites.has(callsite)) {\n    return\n  }\n  warnedDepsCallsites.add(callsite)\n  const replacement =\n    hookName === `useLiveQuery`\n      ? `useLiveQuery({ query })`\n      : `useLiveInfiniteQuery(query, { queryKey })`\n  console.warn(\n    `[${hookName}] The dependency-array form is deprecated and will be removed in 1.0. Use ${replacement} instead. Provide queryKey only for functional/opaque queries or to avoid deriving identity from structured query IR on render.`,\n  )\n}\n\nfunction shouldWarnInDevelopment(disableEnvVar: string): boolean {\n  if (typeof process === `undefined`) {\n    return false\n  }\n\n  return (\n    process.env.NODE_ENV !== `production` && process.env[disableEnvVar] !== `1`\n  )\n}\n\nfunction getCurrentTime(): number {\n  return typeof performance !== `undefined` &&\n    typeof performance.now === `function`\n    ? performance.now()\n    : Date.now()\n}\n\nfunction getWarningCallsite(stackIndex: number): string {\n  const stack = new Error().stack ?? `unknown`\n  return stack.split(`\\n`)[stackIndex]?.trim() ?? stack\n}\n\nfunction warnDerivedIdentityHotPath(\n  profiler: DerivedIdentityProfiler,\n  durationMs: number,\n): void {\n  if (\n    profiler.warned ||\n    !shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)\n  ) {\n    return\n  }\n\n  const isSlowSingleRender =\n    durationMs >= DERIVED_IDENTITY_SINGLE_RENDER_WARN_MS\n  const isHotRenderPath =\n    profiler.renderCount >= DERIVED_IDENTITY_RENDER_COUNT_WARN_THRESHOLD &&\n    profiler.totalMs >= DERIVED_IDENTITY_TOTAL_WARN_MS\n\n  if (!isSlowSingleRender && !isHotRenderPath) {\n    return\n  }\n\n  const callsite = getWarningCallsite(5)\n  if (warnedDerivedIdentityCallsites.has(callsite)) {\n    profiler.warned = true\n    return\n  }\n\n  warnedDerivedIdentityCallsites.add(callsite)\n  profiler.warned = true\n\n  const reason = isSlowSingleRender\n    ? `one render took ${durationMs.toFixed(1)}ms`\n    : `${profiler.renderCount} renders took ${profiler.totalMs.toFixed(1)}ms`\n\n  console.warn(\n    `[useLiveQuery] Deriving live query identity from structured query IR is running on a hot render path (${reason}, max ${profiler.maxMs.toFixed(1)}ms). ` +\n      `Provide an explicit queryKey to skip rebuilding and hashing the IR on every render: useLiveQuery({ queryKey: [...], query }).`,\n  )\n}\n\nfunction getExplicitQueryKey(value: unknown): LiveQueryKey | undefined {\n  return value &&\n    typeof value === `object` &&\n    Array.isArray((value as { queryKey?: unknown }).queryKey)\n    ? (value as { queryKey: LiveQueryKey }).queryKey\n    : undefined\n}\n\nfunction getExplicitDbClient(value: unknown): DbClient | undefined {\n  return value &&\n    typeof value === `object` &&\n    `client` in value &&\n    (value as { client?: unknown }).client !== undefined\n    ? (value as { client: DbClient }).client\n    : undefined\n}\n\nexport function prepareQueryValue(\n  value: unknown,\n  dbClient: DbClient | undefined,\n  deferredCollections: Set<CollectionImpl<any, string | number, any, any, any>>,\n): unknown {\n  return prepareLiveQueryValue(value, dbClient, deferredCollections)\n}\n\ntype DerivedQueryPreparation =\n  | {\n      status: `hashable`\n      value: unknown\n      identityDeps: Array<unknown>\n    }\n  | {\n      status: `unhashable`\n      value: unknown\n      error: UnhashableQueryIRError\n    }\n\nexport function prepareDerivedQuery(\n  value: unknown,\n  dbClient: DbClient | undefined,\n  profiler: DerivedIdentityProfiler,\n  deferredCollections: Set<CollectionImpl<any, string | number, any, any, any>>,\n): DerivedQueryPreparation {\n  const shouldProfile = shouldWarnInDevelopment(\n    `TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`,\n  )\n  const start = shouldProfile ? getCurrentTime() : 0\n  const preparedValue = prepareQueryValue(value, dbClient, deferredCollections)\n\n  try {\n    const identity = getPreparedLiveQueryIdentity(preparedValue)\n    return {\n      status: `hashable`,\n      value: preparedValue,\n      identityDeps: [`derived`, identity],\n    }\n  } catch (error) {\n    if (error instanceof UnhashableQueryIRError) {\n      return { status: `unhashable`, value: preparedValue, error }\n    }\n\n    throw error\n  } finally {\n    if (shouldProfile) {\n      const durationMs = getCurrentTime() - start\n      profiler.renderCount += 1\n      profiler.totalMs += durationMs\n      profiler.maxMs = Math.max(profiler.maxMs, durationMs)\n      warnDerivedIdentityHotPath(profiler, durationMs)\n    }\n  }\n}\n\nexport function warnUnhashableDerivedIdentity(\n  error: UnhashableQueryIRError,\n): void {\n  if (!shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)) {\n    return\n  }\n\n  const callsite = getWarningCallsite(4)\n  if (warnedUnhashableIdentityCallsites.has(callsite)) {\n    return\n  }\n  warnedUnhashableIdentityCallsites.add(callsite)\n\n  console.warn(\n    `[useLiveQuery] This query cannot derive a stable identity because ${error.reason} at ${error.path}. ` +\n      `It will keep the legacy mount-stable behavior for now. Add queryKey: [...] to make captured values reactive. ` +\n      `Unhashable queries without queryKey will throw in 1.0.`,\n  )\n}\n\nfunction createCollectionFromPreparedQuery(value: unknown) {\n  if (value === undefined || value === null) {\n    return null\n  }\n\n  if (isCollection(value)) {\n    value.startSyncImmediate()\n    return value\n  }\n\n  if (value instanceof BaseQueryBuilder) {\n    return createLiveQueryCollection({\n      query: value,\n      startSync: true,\n      gcTime: DEFAULT_GC_TIME_MS,\n    })\n  }\n\n  if (typeof value === `object`) {\n    return createLiveQueryCollection({\n      startSync: true,\n      gcTime: DEFAULT_GC_TIME_MS,\n      ...(value as LiveQueryCollectionConfig<any>),\n    })\n  }\n\n  throw new Error(\n    `useLiveQuery callback must return a QueryBuilder, LiveQueryCollectionConfig, Collection, undefined, or null. Got: ${typeof value}`,\n  )\n}\n\n/**\n * Create a live query using a query function.\n * @param queryFn - Query function that defines what data to fetch\n * @param deps - Deprecated array of dependencies that trigger query re-execution when changed\n * @returns Object with reactive data, state, and status information\n * @example\n * // Prefer config object syntax\n * const { data, isLoading } = useLiveQuery({\n *   query: (q) =>\n *     q.from({ todos: todosCollection })\n *      .where(({ todos }) => eq(todos.completed, false))\n *      .select(({ todos }) => ({ id: todos.id, text: todos.text }))\n * })\n *\n *  @example\n * // Single result query\n * const { data } = useLiveQuery({\n *   query: (q) => q.from({ todos: todosCollection })\n *          .where(({ todos }) => eq(todos.id, 1))\n *          .findOne()\n * })\n *\n * @example\n * // Structured captured values are included in derived query identity\n * const { data, state } = useLiveQuery({\n *   query: (q) => q.from({ todos: todosCollection })\n *          .where(({ todos }) => gt(todos.priority, minPriority)),\n * })\n *\n * @example\n * // Join pattern\n * const { data } = useLiveQuery({\n *   query: (q) =>\n *     q.from({ issues: issueCollection })\n *      .join({ persons: personCollection }, ({ issues, persons }) =>\n *        eq(issues.userId, persons.id)\n *      )\n *      .select(({ issues, persons }) => ({\n *        id: issues.id,\n *        title: issues.title,\n *        userName: persons.name\n *      }))\n * })\n *\n * @example\n * // Handle loading and error states\n * const { data, isLoading, isError, status } = useLiveQuery({\n *   query: (q) => q.from({ todos: todoCollection })\n * })\n *\n * if (isLoading) return <div>Loading...</div>\n * if (isError) return <div>Error: {status}</div>\n *\n * return (\n *   <ul>\n *     {data.map(todo => <li key={todo.id}>{todo.text}</li>)}\n *   </ul>\n * )\n */\n// Overload 1: Accept query function that always returns QueryBuilder\nexport function useLiveQuery<TContext extends Context>(\n  queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n  deps?: Array<unknown>,\n): {\n  state: Map<string | number, GetResult<TContext>>\n  data: InferResultType<TContext>\n  collection: Collection<GetResult<TContext>, string | number, {}>\n  status: CollectionStatus // Can't be disabled if always returns QueryBuilder\n  isLoading: boolean\n  isReady: boolean\n  isIdle: boolean\n  isError: boolean\n  isCleanedUp: boolean\n  isEnabled: true // Always true if always returns QueryBuilder\n}\n\n// Overload 2: Accept query function that can return undefined/null\nexport function useLiveQuery<TContext extends Context>(\n  queryFn: (\n    q: InitialQueryBuilder,\n  ) => QueryBuilder<TContext> | undefined | null,\n  deps?: Array<unknown>,\n): {\n  state: Map<string | number, GetResult<TContext>> | undefined\n  data: InferResultType<TContext> | undefined\n  collection: Collection<GetResult<TContext>, string | number, {}> | undefined\n  status: UseLiveQueryStatus\n  isLoading: boolean\n  isReady: boolean\n  isIdle: boolean\n  isError: boolean\n  isCleanedUp: boolean\n  isEnabled: boolean\n}\n\n// Overload 3: Accept query function that can return LiveQueryCollectionConfig\nexport function useLiveQuery<TContext extends Context>(\n  queryFn: (\n    q: InitialQueryBuilder,\n  ) => LiveQueryCollectionConfig<TContext> | undefined | null,\n  deps?: Array<unknown>,\n): {\n  state: Map<string | number, GetResult<TContext>> | undefined\n  data: InferResultType<TContext> | undefined\n  collection: Collection<GetResult<TContext>, string | number, {}> | undefined\n  status: UseLiveQueryStatus\n  isLoading: boolean\n  isReady: boolean\n  isIdle: boolean\n  isError: boolean\n  isCleanedUp: boolean\n  isEnabled: boolean\n}\n\n// Overload 4: Accept query function that can return Collection\nexport function useLiveQuery<\n  TResult extends object,\n  TKey extends string | number,\n  TUtils extends Record<string, any>,\n>(\n  queryFn: (\n    q: InitialQueryBuilder,\n  ) => Collection<TResult, TKey, TUtils> | undefined | null,\n  deps?: Array<unknown>,\n): {\n  state: Map<TKey, TResult> | undefined\n  data: Array<TResult> | undefined\n  collection: Collection<TResult, TKey, TUtils> | undefined\n  status: UseLiveQueryStatus\n  isLoading: boolean\n  isReady: boolean\n  isIdle: boolean\n  isError: boolean\n  isCleanedUp: boolean\n  isEnabled: boolean\n}\n\n// Overload 5: Accept query function that can return all types\nexport function useLiveQuery<\n  TContext extends Context,\n  TResult extends object,\n  TKey extends string | number,\n  TUtils extends Record<string, any>,\n>(\n  queryFn: (\n    q: InitialQueryBuilder,\n  ) =>\n    | QueryBuilder<TContext>\n    | LiveQueryCollectionConfig<TContext>\n    | Collection<TResult, TKey, TUtils>\n    | undefined\n    | null,\n  deps?: Array<unknown>,\n): {\n  state:\n    | Map<string | number, GetResult<TContext>>\n    | Map<TKey, TResult>\n    | undefined\n  data: InferResultType<TContext> | Array<TResult> | undefined\n  collection:\n    | Collection<GetResult<TContext>, string | number, {}>\n    | Collection<TResult, TKey, TUtils>\n    | undefined\n  status: UseLiveQueryStatus\n  isLoading: boolean\n  isReady: boolean\n  isIdle: boolean\n  isError: boolean\n  isCleanedUp: boolean\n  isEnabled: boolean\n}\n\n/**\n * Create a live query using configuration object\n * @param config - Configuration object with query and options\n * @param deps - Deprecated array of dependencies that trigger query re-execution when changed\n * @returns Object with reactive data, state, and status information\n * @example\n * // Basic config object usage\n * const { data, status } = useLiveQuery({\n *   query: (q) => q.from({ todos: todosCollection }),\n *   gcTime: 60000\n * })\n *\n * @example\n * // With query builder and options\n * const queryBuilder = new Query()\n *   .from({ persons: collection })\n *   .where(({ persons }) => gt(persons.age, 30))\n *   .select(({ persons }) => ({ id: persons.id, name: persons.name }))\n *\n * const { data, isReady } = useLiveQuery({\n *   query: queryBuilder,\n * })\n *\n * @example\n * // Handle all states uniformly\n * const { data, isLoading, isReady, isError } = useLiveQuery({\n *   query: (q) => q.from({ items: itemCollection })\n * })\n *\n * if (isLoading) return <div>Loading...</div>\n * if (isError) return <div>Something went wrong</div>\n * if (!isReady) return <div>Preparing...</div>\n *\n * return <div>{data.length} items loaded</div>\n */\n// Overload 6: Accept config object\nexport function useLiveQuery<TContext extends Context>(\n  config: UseLiveQueryConfig<TContext>,\n): {\n  state: Map<string | number, GetResult<TContext>>\n  data: InferResultType<TContext>\n  collection: Collection<GetResult<TContext>, string | number, {}>\n  status: CollectionStatus // Can't be disabled for config objects\n  isLoading: boolean\n  isReady: boolean\n  isIdle: boolean\n  isError: boolean\n  isCleanedUp: boolean\n  isEnabled: true // Always true for config objects\n}\n\n// Overload 7: Accept config object with legacy deps\nexport function useLiveQuery<TContext extends Context>(\n  config: LiveQueryCollectionConfig<TContext>,\n  deps?: Array<unknown>,\n): {\n  state: Map<string | number, GetResult<TContext>>\n  data: InferResultType<TContext>\n  collection: Collection<GetResult<TContext>, string | number, {}>\n  status: CollectionStatus // Can't be disabled for config objects\n  isLoading: boolean\n  isReady: boolean\n  isIdle: boolean\n  isError: boolean\n  isCleanedUp: boolean\n  isEnabled: true // Always true for config objects\n}\n\n/**\n * Subscribe to an existing live query collection\n * @param liveQueryCollection - Pre-created live query collection to subscribe to\n * @returns Object with reactive data, state, and status information\n * @example\n * // Using pre-created live query collection\n * const myLiveQuery = createLiveQueryCollection((q) =>\n *   q.from({ todos: todosCollection }).where(({ todos }) => eq(todos.active, true))\n * )\n * const { data, collection } = useLiveQuery(myLiveQuery)\n *\n * @example\n * // Access collection methods directly\n * const { data, collection, isReady } = useLiveQuery(existingCollection)\n *\n * // Use collection for mutations\n * const handleToggle = (id) => {\n *   collection.update(id, draft => { draft.completed = !draft.completed })\n * }\n *\n * @example\n * // Handle states consistently\n * const { data, isLoading, isError } = useLiveQuery(sharedCollection)\n *\n * if (isLoading) return <div>Loading...</div>\n * if (isError) return <div>Error loading data</div>\n *\n * return <div>{data.map(item => <Item key={item.id} {...item} />)}</div>\n */\n// Overload 8: Accept pre-created live query collection\nexport function useLiveQuery<\n  TResult extends object,\n  TKey extends string | number,\n  TUtils extends Record<string, any>,\n>(\n  liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult,\n): {\n  state: Map<TKey, TResult>\n  data: Array<TResult>\n  collection: Collection<TResult, TKey, TUtils>\n  status: CollectionStatus // Can't be disabled for pre-created live query collections\n  isLoading: boolean\n  isReady: boolean\n  isIdle: boolean\n  isError: boolean\n  isCleanedUp: boolean\n  isEnabled: true // Always true for pre-created live query collections\n}\n\n// Overload 9: Accept pre-created live query collection with singleResult: true\nexport function useLiveQuery<\n  TResult extends object,\n  TKey extends string | number,\n  TUtils extends Record<string, any>,\n>(\n  liveQueryCollection: Collection<TResult, TKey, TUtils> & SingleResult,\n): {\n  state: Map<TKey, TResult>\n  data: TResult | undefined\n  collection: Collection<TResult, TKey, TUtils> & SingleResult\n  status: CollectionStatus // Can't be disabled for pre-created live query collections\n  isLoading: boolean\n  isReady: boolean\n  isIdle: boolean\n  isError: boolean\n  isCleanedUp: boolean\n  isEnabled: true // Always true for pre-created live query collections\n}\n\n// Implementation - use function overloads to infer the actual collection type\nexport function useLiveQuery(\n  configOrQueryOrCollection: any,\n  deps?: Array<unknown>,\n) {\n  const contextDbClient = useOptionalDbClient()\n  // Check if it's already a collection\n  const inputIsCollection = isCollection(configOrQueryOrCollection)\n  const dbClient = inputIsCollection\n    ? contextDbClient\n    : (getExplicitDbClient(configOrQueryOrCollection) ?? contextDbClient)\n  const resolvedDeps = deps ?? []\n\n  // Use refs to cache collection and track dependencies\n  const collectionRef = useRef<Collection<object, string | number, {}> | null>(\n    null,\n  )\n  const depsRef = useRef<Array<unknown> | null>(null)\n  const configRef = useRef<unknown>(null)\n  const clientRef = useRef(dbClient)\n  const legacyUnhashableIdentityRef = useRef<Array<unknown>>([\n    `legacy-unhashable`,\n  ])\n\n  const derivedIdentityProfilerRef = useRef<DerivedIdentityProfiler>({\n    renderCount: 0,\n    totalMs: 0,\n    maxMs: 0,\n    warned: false,\n  })\n  const deferredCollectionsRef = useRef(\n    new Set<CollectionImpl<any, string | number, any, any, any>>(),\n  )\n  const observerRef = useRef<LiveQueryObserver<object, string | number> | null>(\n    null,\n  )\n  const queryHashRef = useRef<string | undefined>(undefined)\n  const identityErrorRef = useRef<UnhashableQueryIRError | undefined>(undefined)\n\n  const queryKey = !inputIsCollection\n    ? getExplicitQueryKey(configOrQueryOrCollection)\n    : undefined\n  let preparedQueryValue: unknown | typeof unpreparedQueryValue =\n    unpreparedQueryValue\n  let identityDeps: ReadonlyArray<unknown>\n  let streamIdentity: unknown = undefined\n  let identityError: UnhashableQueryIRError | undefined\n\n  if (queryKey) {\n    identityDeps = queryKey\n    streamIdentity = [`queryKey`, queryKey]\n  } else if (deps !== undefined) {\n    identityDeps = resolvedDeps\n    try {\n      preparedQueryValue = prepareQueryValue(\n        configOrQueryOrCollection,\n        dbClient,\n        deferredCollectionsRef.current,\n      )\n      streamIdentity = [\n        `deps`,\n        resolvedDeps,\n        getPreparedLiveQueryIdentity(preparedQueryValue),\n      ]\n    } catch (error) {\n      if (!(error instanceof UnhashableQueryIRError)) throw error\n      warnUnhashableDerivedIdentity(error)\n      identityError = error\n    }\n  } else if (inputIsCollection) {\n    identityDeps = []\n    streamIdentity = [`collection`, configOrQueryOrCollection.id]\n  } else {\n    const preparation = prepareDerivedQuery(\n      configOrQueryOrCollection,\n      dbClient,\n      derivedIdentityProfilerRef.current,\n      deferredCollectionsRef.current,\n    )\n    preparedQueryValue = preparation.value\n    if (preparation.status === `hashable`) {\n      identityDeps = preparation.identityDeps\n      streamIdentity = preparation.identityDeps\n    } else {\n      warnUnhashableDerivedIdentity(preparation.error)\n      identityDeps = legacyUnhashableIdentityRef.current\n      identityError = preparation.error\n    }\n  }\n\n  let queryHash: string | undefined\n  if (streamIdentity !== undefined) {\n    try {\n      queryHash = getStableValueHash(streamIdentity, `queryKey`)\n    } catch (error) {\n      if (error instanceof UnhashableQueryIRError) {\n        if (queryKey !== undefined) throw error\n        identityError = error\n      } else {\n        throw error\n      }\n    }\n  }\n\n  if (deps !== undefined) {\n    warnDeprecatedDepsArray()\n  }\n\n  const identityChanged =\n    depsRef.current === null ||\n    (deps !== undefined\n      ? depsRef.current.length !== identityDeps.length ||\n        depsRef.current.some((dep, index) => dep !== identityDeps[index])\n      : !deepEquals(depsRef.current, identityDeps))\n\n  // Check if we need to create/recreate the collection\n  const needsNewCollection =\n    !collectionRef.current ||\n    (inputIsCollection && configRef.current !== configOrQueryOrCollection) ||\n    (!inputIsCollection && (clientRef.current !== dbClient || identityChanged))\n\n  const resumeDeferredCollections = () => {\n    for (const collection of deferredCollectionsRef.current) {\n      collection._resumeSyncStart()\n    }\n    deferredCollectionsRef.current.clear()\n  }\n\n  if (needsNewCollection) {\n    if (inputIsCollection) {\n      // Warn when passing a collection directly with on-demand sync mode\n      // In on-demand mode, data is only loaded when queries with predicates request it\n      // Passing the collection directly doesn't provide any predicates, so no data loads\n      const syncMode = (\n        configOrQueryOrCollection as { config?: { syncMode?: string } }\n      ).config?.syncMode\n      if (\n        syncMode === `on-demand` &&\n        shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)\n      ) {\n        console.warn(\n          `[useLiveQuery] Warning: Passing a collection with syncMode \"on-demand\" directly to useLiveQuery ` +\n            `will not load any data. In on-demand mode, data is only loaded when queries with predicates request it.\\n\\n` +\n            `Instead, use a query builder function:\\n` +\n            `  const { data } = useLiveQuery({ query: (q) => q.from({ c: myCollection }).select(({ c }) => c) })\\n\\n` +\n            `Or switch to syncMode \"eager\" if you want all data to sync automatically.`,\n        )\n      }\n      // It's already a collection, ensure sync is started for React hooks\n      configOrQueryOrCollection.startSyncImmediate()\n      collectionRef.current = configOrQueryOrCollection\n      configRef.current = configOrQueryOrCollection\n    } else {\n      if (preparedQueryValue === unpreparedQueryValue) {\n        preparedQueryValue = prepareQueryValue(\n          configOrQueryOrCollection,\n          dbClient,\n          deferredCollectionsRef.current,\n        )\n      }\n      collectionRef.current = createCollectionFromPreparedQuery(\n        preparedQueryValue,\n      ) as Collection<object, string | number, {}>\n      configRef.current = configOrQueryOrCollection\n      depsRef.current = [...identityDeps]\n    }\n    clientRef.current = dbClient\n    queryHashRef.current = queryHash\n    identityErrorRef.current = identityError\n  }\n\n  // Recreate the observer when the underlying collection changes. The observer\n  // is not disposed explicitly here or on unmount: `useSyncExternalStore`\n  // unsubscribes it when the subscribe changes or the component unmounts, which\n  // detaches the collection subscription; the observer is then GC'd. (An unmount\n  // effect that disposed it would misfire under StrictMode/offscreen effect\n  // replay, leaving a disposed observer in the ref.)\n  if (needsNewCollection) {\n    // Defer the initial notify: useSyncExternalStore must not be notified\n    // synchronously during subscribe.\n    // Wholesale mode: React re-reads getSnapshot() on notify, keeps the\n    // hook's pre-observer loading policy, and — because wholesale delivers\n    // nothing synchronously during subscribe — never notifies\n    // useSyncExternalStore inside its own subscribe call.\n    observerRef.current = createLiveQueryObserver(collectionRef.current, {\n      mode: `wholesale`,\n      client: dbClient,\n      queryHash: queryHashRef.current,\n      onPreload: resumeDeferredCollections,\n    })\n  }\n  const observer = observerRef.current!\n\n  // Stable subscribe bound to the current observer; the observer owns the\n  // subscription, ready-race, and disposal.\n  const subscribeRef = useRef<\n    ((onStoreChange: () => void) => () => void) | null\n  >(null)\n  if (!subscribeRef.current || needsNewCollection) {\n    subscribeRef.current = (onStoreChange: () => void) => {\n      const unsubscribe = observer.subscribe(() => onStoreChange())\n      resumeDeferredCollections()\n      return unsubscribe\n    }\n  }\n\n  const returned = useSyncExternalStore(\n    subscribeRef.current,\n    () => observer.getSnapshot(),\n    () => observer.getServerSnapshot(),\n  )\n  setLiveQueryResultInfo(returned, {\n    client: dbClient,\n    queryHash: queryHashRef.current,\n    identityError: identityErrorRef.current,\n    observer,\n  })\n  return returned as any\n}\n"],"names":["useRef","useSyncExternalStore","setLiveQueryResultInfo"],"mappings":";;;;;;;AAgCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAuBO;AAGL;AACE;AAAA;AAGF;AACA;AACE;AAAA;AAEF;AACA;AAIA;AAAQ;AAC8F;AAExG;AAEA;AACE;AACE;AAAO;AAGT;AAGF;AAEA;AACE;AAIF;AAEA;AACE;AACA;AAAmB;AACrB;AAEA;AAIE;AAIE;AAAA;AAGF;AAEA;AAIA;AACE;AAAA;AAGF;AACA;AACE;AACA;AAAA;AAGF;AACA;AAEA;AAIA;AAAQ;AAC2I;AAGrJ;AAEA;AACE;AAKF;AAEA;AACE;AAMF;AAEO;AAKL;AACF;AAcO;AAML;AAAsB;AACpB;AAEF;AACA;AAEA;AACE;AACA;AAAO;AACG;AACD;AAC2B;AAAA;AAGpC;AACE;AAAqD;AAGvD;AAAM;AAEN;AACE;AACA;AACA;AACA;AACA;AAA+C;AACjD;AAEJ;AAEO;AAGL;AACE;AAAA;AAGF;AACA;AACE;AAAA;AAEF;AAEA;AAAQ;AAC4F;AAItG;AAEA;AACE;AACE;AAAO;AAGT;AACE;AACA;AAAO;AAGT;AACE;AAAiC;AACxB;AACI;AACH;AACT;AAGH;AACE;AAAiC;AACpB;AACH;AACJ;AACL;AAGH;AAAU;AACyH;AAErI;AAwTO;AAIL;AAEA;AACA;AAGA;AAGA;AAAsBA;AACpB;AAEF;AACA;AACA;AACA;AAA2D;AACzD;AAGF;AAAmE;AACpD;AACJ;AACF;AACC;AAEV;AAA+BA;AACzB;AAEN;AAAoBA;AAClB;AAEF;AACA;AAEA;AAGA;AAEA;AACA;AACA;AAEA;AACE;AACA;AAAsC;AAEtC;AACA;AACE;AAAqB;AACnB;AACA;AACuB;AAEzB;AAAiB;AACf;AACA;AAC+C;AAAA;AAGjD;AACA;AACA;AAAgB;AAClB;AAEA;AACA;AAA4D;AAE5D;AAAoB;AAClB;AACA;AAC2B;AACJ;AAEzB;AACA;AACE;AACA;AAA6B;AAE7B;AACA;AACA;AAA4B;AAC9B;AAGF;AACA;AACE;AACE;AAAyD;AAEzD;AACE;AACA;AAAgB;AAEhB;AAAM;AACR;AACF;AAGF;AACE;AAAA;AAGF;AAQA;AAKA;AACE;AACE;AAAW;AAEb;AAA+B;AAGjC;AACE;AAIE;AAGA;AAIE;AAAQ;;AACN;AAAA;;AAAA;AAAA;AAAA;AAQJ;AACA;AACA;AAAoB;AAEpB;AACE;AAAqB;AACnB;AACA;AACuB;AAAA;AAG3B;AAAwB;AACtB;AAEF;AACA;AAAkC;AAEpC;AACA;AACA;AAA2B;AAS7B;AAOE;AAAqE;AAC7D;AACE;AACgB;AACb;AACZ;AAEH;AAIA;AAGA;AACE;AACE;AACA;AACA;AAAO;AACT;AAGF;AAAiBC;AACF;AACE;AACA;AAEjBC;AAAiC;AACvB;AACgB;AACQ;AAChC;AAEF;AACF;;;;;;"}