@tanstack/react-db 0.5.12 → 1.0.0-rc.1.1
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.
- package/dist/cjs/live-query-internals.cjs.map +1 -1
- package/dist/cjs/live-query-internals.d.cts +3 -1
- package/dist/cjs/source-id-bindings.cjs +9 -42
- package/dist/cjs/source-id-bindings.cjs.map +1 -1
- package/dist/cjs/source-id-bindings.d.cts +6 -17
- package/dist/cjs/useLiveInfiniteQuery.cjs +93 -173
- package/dist/cjs/useLiveInfiniteQuery.cjs.map +1 -1
- package/dist/cjs/useLiveInfiniteQuery.d.cts +3 -14
- package/dist/cjs/useLiveQuery.cjs +187 -224
- package/dist/cjs/useLiveQuery.cjs.map +1 -1
- package/dist/cjs/useLiveQuery.d.cts +37 -136
- package/dist/cjs/useLiveSuspenseQuery.cjs +71 -64
- package/dist/cjs/useLiveSuspenseQuery.cjs.map +1 -1
- package/dist/cjs/useLiveSuspenseQuery.d.cts +10 -9
- package/dist/cjs/usePacedMutations.cjs.map +1 -1
- package/dist/cjs/usePacedMutations.d.cts +1 -1
- package/dist/esm/live-query-internals.d.ts +3 -1
- package/dist/esm/live-query-internals.js.map +1 -1
- package/dist/esm/source-id-bindings.d.ts +6 -17
- package/dist/esm/source-id-bindings.js +9 -40
- package/dist/esm/source-id-bindings.js.map +1 -1
- package/dist/esm/useLiveInfiniteQuery.d.ts +3 -14
- package/dist/esm/useLiveInfiniteQuery.js +95 -175
- package/dist/esm/useLiveInfiniteQuery.js.map +1 -1
- package/dist/esm/useLiveQuery.d.ts +37 -136
- package/dist/esm/useLiveQuery.js +189 -226
- package/dist/esm/useLiveQuery.js.map +1 -1
- package/dist/esm/useLiveSuspenseQuery.d.ts +10 -9
- package/dist/esm/useLiveSuspenseQuery.js +71 -64
- package/dist/esm/useLiveSuspenseQuery.js.map +1 -1
- package/dist/esm/usePacedMutations.d.ts +1 -1
- package/dist/esm/usePacedMutations.js.map +1 -1
- package/package.json +3 -3
- package/skills/react-db/SKILL.md +24 -15
- package/src/live-query-internals.ts +3 -0
- package/src/source-id-bindings.ts +16 -62
- package/src/useLiveInfiniteQuery.ts +138 -296
- package/src/useLiveQuery.ts +329 -495
- package/src/useLiveSuspenseQuery.ts +122 -132
- package/src/usePacedMutations.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLiveSuspenseQuery.cjs","sources":["../../src/useLiveSuspenseQuery.ts"],"sourcesContent":["'use client'\n\nimport { useRef } from 'react'\nimport { useLiveQueryForSuspense } from './useLiveQuery'\nimport { getLiveQueryResultInfo } from './live-query-internals'\nimport type { UseLiveQueryConfig } from './useLiveQuery'\nimport type {\n Collection,\n Context,\n DbClient,\n GetResult,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n NonSingleResult,\n QueryBuilder,\n SingleResult,\n} from '@tanstack/db'\n\n// React can discard a render that suspends, including its refs. Keep the\n// initial-render failure across retries, scoped by client because streamed\n// preloads of the same collection can fail independently.\nconst initialRenderErrors = new WeakMap<\n Collection<any, any, any>,\n {\n byClient: WeakMap<DbClient, { error: unknown; clientQuery?: object }>\n unscoped?: { error: unknown; clientQuery?: object }\n }\n>()\n\nfunction clearInitialRenderError(\n collection: Collection<any, any, any>,\n client: DbClient | undefined,\n) {\n const entry = initialRenderErrors.get(collection)\n if (!entry) return\n if (client) entry.byClient.delete(client)\n else delete entry.unscoped\n}\n\nfunction rememberInitialRenderError(\n collection: Collection<any, any, any>,\n client: DbClient | undefined,\n error: unknown,\n clientQuery?: object,\n) {\n if (collection.status === `cleaned-up`) return\n let entry = initialRenderErrors.get(collection)\n if (!entry) {\n entry = { byClient: new WeakMap() }\n collection.once(`status:cleaned-up`, () => {\n initialRenderErrors.delete(collection)\n })\n initialRenderErrors.set(collection, entry)\n }\n if (client) entry.byClient.set(client, { error, clientQuery })\n else entry.unscoped = { error }\n}\n\n/**\n * Create a live query with React Suspense support\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 and state - data is guaranteed to be defined\n * @throws Promise when data is loading (caught by Suspense boundary)\n * @throws Error when collection fails (caught by Error boundary)\n * @example\n * // Basic usage with Suspense\n * function TodoList() {\n * const { data } = useLiveSuspenseQuery({\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 * return (\n * <ul>\n * {data.map(todo => <li key={todo.id}>{todo.text}</li>)}\n * </ul>\n * )\n * }\n *\n * function App() {\n * return (\n * <Suspense fallback={<div>Loading...</div>}>\n * <TodoList />\n * </Suspense>\n * )\n * }\n *\n * @example\n * // Single result query\n * const { data } = useLiveSuspenseQuery(\n * (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.id, 1))\n * .findOne()\n * )\n * // data is guaranteed to be the single item (or undefined if not found)\n *\n * @example\n * // Structured captured values are included in derived query identity and trigger re-suspension\n * const { data } = useLiveSuspenseQuery({\n * query: (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => gt(todos.priority, minPriority)),\n * })\n *\n * @example\n * // With Error boundary\n * function App() {\n * return (\n * <ErrorBoundary fallback={<div>Error loading data</div>}>\n * <Suspense fallback={<div>Loading...</div>}>\n * <TodoList />\n * </Suspense>\n * </ErrorBoundary>\n * )\n * }\n *\n * @remarks\n * **Important:** This hook does NOT support disabled queries (returning undefined/null).\n * Following TanStack Query's useSuspenseQuery design, the query callback must always\n * return a valid query, collection, or config object.\n *\n * ❌ **This will cause a type error:**\n * ```ts\n * useLiveSuspenseQuery(\n * (q) => userId ? q.from({ users }) : undefined // ❌ Error!\n * )\n * ```\n *\n * ✅ **Use conditional rendering instead:**\n * ```ts\n * function Profile({ userId }: { userId: string }) {\n * const { data } = useLiveSuspenseQuery({\n * query: (q) => q.from({ users }).where(({ users }) => eq(users.id, userId)),\n * })\n * return <div>{data.name}</div>\n * }\n *\n * // In parent component:\n * {userId ? <Profile userId={userId} /> : <div>No user</div>}\n * ```\n *\n * ✅ **For optional inputs, conditionally render a component with complete query inputs:**\n * ```ts\n * {userId ? <Profile userId={userId} /> : <div>No user</div>}\n * ```\n */\n// Overload 1: Accept query function that always returns QueryBuilder\nexport function useLiveSuspenseQuery<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}\n\n// Overload 2: Accept config object\nexport function useLiveSuspenseQuery<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}\n\n// Overload 3: Accept legacy config object\nexport function useLiveSuspenseQuery<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}\n\n// Overload 4: Accept pre-created live query collection\nexport function useLiveSuspenseQuery<\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}\n\n// Overload 5: Accept pre-created live query collection with singleResult: true\nexport function useLiveSuspenseQuery<\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}\n\n// Implementation - uses useLiveQuery internally and adds Suspense logic\nexport function useLiveSuspenseQuery(\n configOrQueryOrCollection: any,\n deps?: Array<unknown>,\n) {\n const promiseRef = useRef<Promise<void> | null>(null)\n const collectionRef = useRef<Collection<any, any, any> | null>(null)\n const hasBeenReadyRef = useRef(false)\n\n // Use useLiveQuery to handle collection management and reactivity\n const result =\n deps === undefined\n ? useLiveQueryForSuspense(configOrQueryOrCollection, undefined)\n : useLiveQueryForSuspense(configOrQueryOrCollection, deps)\n\n if (!result.isEnabled) {\n // Suspense queries cannot be disabled - this matches TanStack Query's useSuspenseQuery behavior\n throw new Error(\n `useLiveSuspenseQuery does not support disabled queries (callback returned undefined/null). ` +\n `The Suspense pattern requires data to always be defined (T, not T | undefined). ` +\n `Solutions: ` +\n `1) Use conditional rendering - don't render the component until the condition is met. ` +\n `2) Use useLiveQuery instead, which supports disabled queries with the 'isEnabled' flag.`,\n )\n }\n\n const queryInfo = getLiveQueryResultInfo(result)\n\n // Reset promise and ready state when query identity changes\n if (collectionRef.current !== result.collection) {\n promiseRef.current = null\n collectionRef.current = result.collection\n hasBeenReadyRef.current = false\n }\n\n // SUSPENSE LOGIC: Throw promise or error based on collection status\n\n const collectionStatus = result.collection.status\n\n // Track when we reach ready state\n if (result.isReady || queryInfo.observer.isInitialRenderReady()) {\n hasBeenReadyRef.current = true\n promiseRef.current = null\n clearInitialRenderError(result.collection, queryInfo.client)\n }\n\n const observerError = queryInfo.observer.getError()\n // A client request can reject with undefined, which getError() cannot\n // distinguish from no error. The request status preserves that distinction.\n const clientQuery =\n queryInfo.client && queryInfo.queryHash\n ? queryInfo.client._getLiveQuery(queryInfo.queryHash)\n : undefined\n // A configured persisted restore may still satisfy this render after the\n // client query fails. Let the observer classify that failure below.\n if (\n !hasBeenReadyRef.current &&\n (result.persistedStatus === `unavailable` ||\n result.persistedStatus === `error`) &&\n (observerError !== undefined || clientQuery?.status === `error`)\n ) {\n promiseRef.current = null\n throw observerError === undefined ? clientQuery?.error : observerError\n }\n\n const errorEntry = initialRenderErrors.get(result.collection)\n const initialRenderError = queryInfo.client\n ? errorEntry?.byClient.get(queryInfo.client)\n : errorEntry?.unscoped\n // A replacement client query must not inherit the prior request's error.\n if (\n initialRenderError?.clientQuery !== undefined &&\n initialRenderError.clientQuery !== clientQuery\n ) {\n clearInitialRenderError(result.collection, queryInfo.client)\n } else if (initialRenderError && !hasBeenReadyRef.current) {\n promiseRef.current = null\n throw initialRenderError.error\n }\n\n // Only throw errors during initial load (before first ready)\n // After success, errors surface as stale data (matches TanStack Query behavior)\n if (\n collectionStatus === `error` &&\n !hasBeenReadyRef.current &&\n (result.persistedStatus === `unavailable` ||\n result.persistedStatus === `error`)\n ) {\n promiseRef.current = null\n // TODO: Once collections hold a reference to their last error object (#671),\n // we should rethrow that actual error instead of creating a generic message\n throw new Error(`Collection \"${result.collection.id}\" failed to load`)\n }\n\n if (\n !hasBeenReadyRef.current &&\n (result.isLoading ||\n result.isIdle ||\n result.isError ||\n (collectionStatus === `error` &&\n result.persistedStatus !== `unavailable`))\n ) {\n if (queryInfo.client?._isSsrStreamingEnabled() && !queryInfo.queryHash) {\n const reason = queryInfo.identityError\n ? `${queryInfo.identityError.reason} at ${queryInfo.identityError.path}`\n : `the query has no stable identity`\n throw new Error(\n `Cannot stream this live query during SSR because ${reason}. Provide an explicit serializable queryKey.`,\n )\n }\n // Create or reuse promise for current collection\n if (!promiseRef.current) {\n const collection = result.collection\n const client = queryInfo.client\n const queryHash = queryInfo.queryHash\n let active = true\n const stopWatchingCleanup = collection.once(`status:cleaned-up`, () => {\n active = false\n })\n let preload: Promise<void>\n try {\n preload = queryInfo.observer.preloadForInitialRender()\n } catch (error) {\n stopWatchingCleanup()\n throw error\n }\n const preloadClientQuery =\n client && queryHash ? client._getLiveQuery(queryHash) : undefined\n promiseRef.current = preload\n .catch((error: unknown) => {\n const latestClientQuery =\n client && queryHash ? client._getLiveQuery(queryHash) : undefined\n if (active && latestClientQuery === preloadClientQuery)\n rememberInitialRenderError(\n collection,\n client,\n error,\n preloadClientQuery,\n )\n throw error\n })\n .finally(stopWatchingCleanup)\n }\n // React Suspense catches this promise and retries after preload settles.\n throw promiseRef.current\n }\n\n // Return data without status/loading flags (handled by Suspense/ErrorBoundary)\n // If error after success, return last known good state (stale data)\n return {\n state: result.state,\n data: result.data,\n collection: result.collection,\n }\n}\n"],"names":[],"mappings":";;;;;;AAsBA;AAQA;AAIE;AACA;AACA;AAAwC;AAE1C;AAEA;AAME;AACA;AACA;AACE;AACA;AACE;AAAqC;AAEvC;AAAyC;AAE3C;AAA6D;AAE/D;AAoJO;AAIL;AACA;AACA;AAGA;AAKA;AAEE;AAAU;AACR;AAAA;AAQJ;AAGA;AACE;AACA;AACA;AAA0B;AAK5B;AAGA;AACE;AACA;AACA;AAA2D;AAG7D;AAGA;AAMA;AAME;AACA;AAAyD;AAG3D;AACA;AAIA;AAIE;AAA2D;AAE3D;AACA;AAAyB;AAK3B;AAME;AAGA;AAAqE;AAGvE;AAQE;AACE;AAGA;AAAU;AACkD;AAAA;AAI9D;AACE;AACA;AACA;AACA;AACA;AACE;AAAS;AAEX;AACA;AACE;AAA6B;AAE7B;AACA;AAAM;AAER;AAEA;AAEI;AAEA;AACE;AAAA;AACE;AACA;AACA;AACA;AAEJ;AAAM;AAEoB;AAGhC;AAAiB;AAKnB;AAAO;AACS;AACD;AACM;AAEvB;;"}
|
|
1
|
+
{"version":3,"file":"useLiveSuspenseQuery.cjs","sources":["../../src/useLiveSuspenseQuery.ts"],"sourcesContent":["'use client'\n\nimport { useRef } from 'react'\nimport { rejectLiveQueryInput, toBoundaryError } from '@tanstack/db'\nimport { useLiveQueryForSuspense } from './useLiveQuery'\nimport { getLiveQueryResultInfo } from './live-query-internals'\nimport type { UseLiveQueryConfig } from './useLiveQuery'\nimport type {\n Collection,\n Context,\n DbClient,\n GetResult,\n HealthNode,\n InferResultType,\n InitialQueryBuilder,\n NonSingleResult,\n QueryBuilder,\n SingleResult,\n} from '@tanstack/db'\n\n// React can discard a render that suspends, including its refs. Keep the\n// initial-render failure across retries, scoped by client because streamed\n// preloads of the same collection can fail independently.\nconst initialRenderErrors = new WeakMap<\n Collection<any, any, any>,\n {\n byClient: WeakMap<DbClient, { error: unknown; clientQuery?: object }>\n unscoped?: { error: unknown; clientQuery?: object }\n }\n>()\n\nfunction clearInitialRenderError(\n collection: Collection<any, any, any>,\n client: DbClient | undefined,\n) {\n const entry = initialRenderErrors.get(collection)\n if (!entry) return\n if (client) entry.byClient.delete(client)\n else delete entry.unscoped\n}\n\nfunction rememberInitialRenderError(\n collection: Collection<any, any, any>,\n client: DbClient | undefined,\n error: unknown,\n clientQuery?: object,\n) {\n if (collection.status === `cleaned-up`) return\n let entry = initialRenderErrors.get(collection)\n if (!entry) {\n entry = { byClient: new WeakMap() }\n collection.once(`status:cleaned-up`, () => {\n initialRenderErrors.delete(collection)\n })\n initialRenderErrors.set(collection, entry)\n }\n if (client) entry.byClient.set(client, { error, clientQuery })\n else entry.unscoped = { error }\n}\n\n/**\n * Create a live query with React Suspense support\n * @param queryFn - Query function that defines what data to fetch\n * @returns Object with reactive data and state - data is guaranteed to be defined\n * @throws Promise when data is loading (caught by Suspense boundary)\n * @throws Error when the first load fails or the Collection crashes (caught by\n * an Error boundary): the cause itself when it is an Error, otherwise an Error\n * with the problem's message (`health.error.message`) whose `cause` is the\n * value the sync adapter reported\n * @example\n * // Basic usage with Suspense\n * function TodoList() {\n * const { data } = useLiveSuspenseQuery({\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 * return (\n * <ul>\n * {data.map(todo => <li key={todo.id}>{todo.text}</li>)}\n * </ul>\n * )\n * }\n *\n * function App() {\n * return (\n * <Suspense fallback={<div>Loading...</div>}>\n * <TodoList />\n * </Suspense>\n * )\n * }\n *\n * @example\n * // Single result query\n * const { data } = useLiveSuspenseQuery(\n * (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.id, 1))\n * .findOne()\n * )\n * // data is guaranteed to be the single item (or undefined if not found)\n *\n * @example\n * // Structured captured values are included in derived query identity and trigger re-suspension\n * const { data } = useLiveSuspenseQuery({\n * query: (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => gt(todos.priority, minPriority)),\n * })\n *\n * @example\n * // With Error boundary\n * function App() {\n * return (\n * <ErrorBoundary fallback={<div>Error loading data</div>}>\n * <Suspense fallback={<div>Loading...</div>}>\n * <TodoList />\n * </Suspense>\n * </ErrorBoundary>\n * )\n * }\n *\n * @remarks\n * **Important:** This hook does NOT support disabled queries (returning undefined/null).\n * Following TanStack Query's useSuspenseQuery design, the query callback must always\n * return a valid query, collection, or config object.\n *\n * ❌ **This will cause a type error:**\n * ```ts\n * useLiveSuspenseQuery(\n * (q) => userId ? q.from({ users }) : undefined // ❌ Error!\n * )\n * ```\n *\n * ✅ **Use conditional rendering instead:**\n * ```ts\n * function Profile({ userId }: { userId: string }) {\n * const { data } = useLiveSuspenseQuery({\n * query: (q) => q.from({ users }).where(({ users }) => eq(users.id, userId)),\n * })\n * return <div>{data.name}</div>\n * }\n *\n * // In parent component:\n * {userId ? <Profile userId={userId} /> : <div>No user</div>}\n * ```\n *\n * ✅ **For optional inputs, conditionally render a component with complete query inputs:**\n * ```ts\n * {userId ? <Profile userId={userId} /> : <div>No user</div>}\n * ```\n */\n// Overload 1: Accept query function that always returns QueryBuilder\nexport function useLiveSuspenseQuery<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 health: HealthNode | undefined\n}\n\n// Overload 2: Accept config object\nexport function useLiveSuspenseQuery<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 health: HealthNode | undefined\n}\n\n// Overload 4: Accept pre-created live query collection\nexport function useLiveSuspenseQuery<\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 health: HealthNode | undefined\n}\n\n// Overload 5: Accept pre-created live query collection with singleResult: true\nexport function useLiveSuspenseQuery<\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 health: HealthNode | undefined\n}\n\n// Implementation - uses useLiveQuery internally and adds Suspense logic\nexport function useLiveSuspenseQuery(configOrQueryOrCollection: any) {\n const promiseRef = useRef<Promise<void> | null>(null)\n const collectionRef = useRef<Collection<any, any, any> | null>(null)\n const hasBeenReadyRef = useRef(false)\n\n // Use useLiveQuery to handle collection management and reactivity\n const result = useLiveQueryForSuspense(configOrQueryOrCollection)\n\n if (!result.isEnabled) {\n // Suspense queries cannot be disabled - this matches TanStack Query's useSuspenseQuery behavior\n throw new Error(\n `useLiveSuspenseQuery does not support disabled queries (callback returned undefined/null). ` +\n `The Suspense pattern requires data to always be defined (T, not T | undefined). ` +\n `Solutions: ` +\n `1) Use conditional rendering - don't render the component until the condition is met. ` +\n `2) Use useLiveQuery instead, which supports disabled queries with the 'isEnabled' flag.`,\n )\n }\n\n const queryInfo = getLiveQueryResultInfo(result)\n // The render boundary: an error thrown before this render commits releases\n // the sync starts it deferred. A thrown promise suspends; its preload owns\n // those starts.\n try {\n // Reset promise and ready state when query identity changes\n if (collectionRef.current !== result.collection) {\n promiseRef.current = null\n collectionRef.current = result.collection\n hasBeenReadyRef.current = false\n }\n\n // SUSPENSE LOGIC: Throw promise or error based on collection status\n\n const collectionStatus = result.collection.status\n\n // Track when we reach ready state\n if (result.isReady) {\n hasBeenReadyRef.current = true\n promiseRef.current = null\n clearInitialRenderError(result.collection, queryInfo.client)\n }\n\n const observerError = queryInfo.observer.getError()\n // A client request can reject with undefined, which getError() cannot\n // distinguish from no error. The request status preserves that distinction.\n const clientQuery =\n queryInfo.client && queryInfo.queryHash\n ? queryInfo.client._getLiveQuery(queryInfo.queryHash)\n : undefined\n if (\n !hasBeenReadyRef.current &&\n (observerError !== undefined || clientQuery?.status === `error`)\n ) {\n promiseRef.current = null\n throw observerError === undefined ? clientQuery?.error : observerError\n }\n\n if (queryInfo.client?._isSsrStreamingEnabled() && !queryInfo.queryHash) {\n const reason = queryInfo.identityError\n ? `${queryInfo.identityError.reason} at ${queryInfo.identityError.path}`\n : `the query has no stable identity`\n throw new Error(\n `Cannot stream this live query during SSR because ${reason}. Provide an explicit serializable queryKey.`,\n )\n }\n const errorEntry = initialRenderErrors.get(result.collection)\n const initialRenderError = queryInfo.client\n ? errorEntry?.byClient.get(queryInfo.client)\n : errorEntry?.unscoped\n // A replacement client query must not inherit the prior request's error.\n if (\n initialRenderError?.clientQuery !== undefined &&\n initialRenderError.clientQuery !== clientQuery\n ) {\n clearInitialRenderError(result.collection, queryInfo.client)\n } else if (initialRenderError && !hasBeenReadyRef.current) {\n promiseRef.current = null\n throw toBoundaryError(initialRenderError.error)\n }\n\n // A failed first load throws only before the first ready; later sync\n // problems surface in `health` with the data kept (as in TanStack Query).\n // A crash throws at any time: the Collection can no longer serve reads.\n if (\n collectionStatus === `crashed` ||\n (collectionStatus === `error` && !hasBeenReadyRef.current)\n ) {\n promiseRef.current = null\n throw toBoundaryError(result.collection.health.error?.cause)\n }\n\n if (\n !hasBeenReadyRef.current &&\n (result.isLoading ||\n result.isIdle ||\n result.isLoadingError ||\n result.isCrashed)\n ) {\n // Create or reuse promise for current collection\n if (!promiseRef.current) {\n const collection = result.collection\n const client = queryInfo.client\n const queryHash = queryInfo.queryHash\n let active = true\n const stopWatchingCleanup = collection.once(`status:cleaned-up`, () => {\n active = false\n })\n let preload: Promise<void>\n try {\n preload = queryInfo.observer.preload()\n } catch (error) {\n stopWatchingCleanup()\n throw error\n }\n const preloadClientQuery =\n client && queryHash ? client._getLiveQuery(queryHash) : undefined\n promiseRef.current = preload\n .catch((error: unknown) => {\n const latestClientQuery =\n client && queryHash ? client._getLiveQuery(queryHash) : undefined\n if (active && latestClientQuery === preloadClientQuery)\n rememberInitialRenderError(\n collection,\n client,\n error,\n preloadClientQuery,\n )\n throw error\n })\n .finally(stopWatchingCleanup)\n }\n // React Suspense catches this promise and retries after preload settles.\n throw promiseRef.current\n }\n\n // Return data without status/loading flags (handled by Suspense/ErrorBoundary)\n // If error after success, return last known good state (stale data)\n return {\n state: result.state,\n data: result.data,\n collection: result.collection,\n health: result.health,\n }\n } catch (error) {\n if (error instanceof Promise) throw error\n return rejectLiveQueryInput(queryInfo.deferredCollections, error)\n }\n}\n"],"names":[],"mappings":";;;;;;;AAuBA;AAQA;AAIE;AACA;AACA;AAAwC;AAE1C;AAEA;AAME;AACA;AACA;AACE;AACA;AACE;AAAqC;AAEvC;AAAyC;AAE3C;AAA6D;AAE/D;AA+IO;AACL;AACA;AACA;AAGA;AAEA;AAEE;AAAU;AACR;AAAA;AAQJ;AAIA;AAEE;AACE;AACA;AACA;AAA0B;AAK5B;AAGA;AACE;AACA;AACA;AAA2D;AAG7D;AAGA;AAIA;AAIE;AACA;AAAyD;AAG3D;AACE;AAGA;AAAU;AACkD;AAAA;AAG9D;AACA;AAIA;AAIE;AAA2D;AAE3D;AACA;AAA8C;AAMhD;AAIE;AACA;AAA2D;AAG7D;AAQE;AACE;AACA;AACA;AACA;AACA;AACE;AAAS;AAEX;AACA;AACE;AAA6B;AAE7B;AACA;AAAM;AAER;AAEA;AAEI;AAEA;AACE;AAAA;AACE;AACA;AACA;AACA;AAEJ;AAAM;AAEoB;AAGhC;AAAiB;AAKnB;AAAO;AACS;AACD;AACM;AACJ;AAAA;AAGjB;AACA;AAAgE;AAEpE;;"}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { UseLiveQueryConfig } from './useLiveQuery.cjs';
|
|
2
|
-
import { Collection, Context, GetResult, InferResultType, InitialQueryBuilder,
|
|
2
|
+
import { Collection, Context, GetResult, HealthNode, InferResultType, InitialQueryBuilder, NonSingleResult, QueryBuilder, SingleResult } from '@tanstack/db';
|
|
3
3
|
/**
|
|
4
4
|
* Create a live query with React Suspense support
|
|
5
5
|
* @param queryFn - Query function that defines what data to fetch
|
|
6
|
-
* @param deps - Deprecated array of dependencies that trigger query re-execution when changed
|
|
7
6
|
* @returns Object with reactive data and state - data is guaranteed to be defined
|
|
8
7
|
* @throws Promise when data is loading (caught by Suspense boundary)
|
|
9
|
-
* @throws Error when
|
|
8
|
+
* @throws Error when the first load fails or the Collection crashes (caught by
|
|
9
|
+
* an Error boundary): the cause itself when it is an Error, otherwise an Error
|
|
10
|
+
* with the problem's message (`health.error.message`) whose `cause` is the
|
|
11
|
+
* value the sync adapter reported
|
|
10
12
|
* @example
|
|
11
13
|
* // Basic usage with Suspense
|
|
12
14
|
* function TodoList() {
|
|
@@ -90,28 +92,27 @@ import { Collection, Context, GetResult, InferResultType, InitialQueryBuilder, L
|
|
|
90
92
|
* {userId ? <Profile userId={userId} /> : <div>No user</div>}
|
|
91
93
|
* ```
|
|
92
94
|
*/
|
|
93
|
-
export declare function useLiveSuspenseQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext
|
|
95
|
+
export declare function useLiveSuspenseQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>): {
|
|
94
96
|
state: Map<string | number, GetResult<TContext>>;
|
|
95
97
|
data: InferResultType<TContext>;
|
|
96
98
|
collection: Collection<GetResult<TContext>, string | number, {}>;
|
|
99
|
+
health: HealthNode | undefined;
|
|
97
100
|
};
|
|
98
101
|
export declare function useLiveSuspenseQuery<TContext extends Context>(config: UseLiveQueryConfig<TContext>): {
|
|
99
102
|
state: Map<string | number, GetResult<TContext>>;
|
|
100
103
|
data: InferResultType<TContext>;
|
|
101
104
|
collection: Collection<GetResult<TContext>, string | number, {}>;
|
|
102
|
-
|
|
103
|
-
export declare function useLiveSuspenseQuery<TContext extends Context>(config: LiveQueryCollectionConfig<TContext>, deps?: Array<unknown>): {
|
|
104
|
-
state: Map<string | number, GetResult<TContext>>;
|
|
105
|
-
data: InferResultType<TContext>;
|
|
106
|
-
collection: Collection<GetResult<TContext>, string | number, {}>;
|
|
105
|
+
health: HealthNode | undefined;
|
|
107
106
|
};
|
|
108
107
|
export declare function useLiveSuspenseQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult): {
|
|
109
108
|
state: Map<TKey, TResult>;
|
|
110
109
|
data: Array<TResult>;
|
|
111
110
|
collection: Collection<TResult, TKey, TUtils>;
|
|
111
|
+
health: HealthNode | undefined;
|
|
112
112
|
};
|
|
113
113
|
export declare function useLiveSuspenseQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: Collection<TResult, TKey, TUtils> & SingleResult): {
|
|
114
114
|
state: Map<TKey, TResult>;
|
|
115
115
|
data: TResult | undefined;
|
|
116
116
|
collection: Collection<TResult, TKey, TUtils> & SingleResult;
|
|
117
|
+
health: HealthNode | undefined;
|
|
117
118
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePacedMutations.cjs","sources":["../../src/usePacedMutations.ts"],"sourcesContent":["import { useCallback, useInsertionEffect, useMemo, useRef } from 'react'\nimport { createPacedMutations } from '@tanstack/db'\nimport type { PacedMutationsConfig, Transaction } from '@tanstack/db'\n\n/**\n * React hook for managing paced mutations with timing strategies.\n *\n * Provides optimistic mutations with pluggable strategies like debouncing,\n * queuing, or throttling. The optimistic updates are applied immediately via\n * `onMutate`, and the actual persistence is controlled by the strategy.\n * Callbacks follow the latest committed render, so a suspended render cannot\n * replace the callbacks used by active mutations.\n *\n * @param config - Configuration including onMutate, mutationFn and strategy\n * @returns A mutate function that accepts variables and returns Transaction objects\n *\n * @example\n * ```tsx\n * // Debounced auto-save\n * function AutoSaveForm({ formId }: { formId: string }) {\n * const mutate = usePacedMutations<string>({\n * onMutate: (value) => {\n * // Apply optimistic update immediately\n * formCollection.update(formId, draft => {\n * draft.content = value\n * })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.save(transaction.mutations)\n * },\n * strategy: debounceStrategy({ wait: 500 })\n * })\n *\n * const handleChange = async (value: string) => {\n * const tx = mutate(value)\n *\n * // Optional: await persistence or handle errors\n * try {\n * await tx.
|
|
1
|
+
{"version":3,"file":"usePacedMutations.cjs","sources":["../../src/usePacedMutations.ts"],"sourcesContent":["import { useCallback, useInsertionEffect, useMemo, useRef } from 'react'\nimport { createPacedMutations } from '@tanstack/db'\nimport type { PacedMutationsConfig, Transaction } from '@tanstack/db'\n\n/**\n * React hook for managing paced mutations with timing strategies.\n *\n * Provides optimistic mutations with pluggable strategies like debouncing,\n * queuing, or throttling. The optimistic updates are applied immediately via\n * `onMutate`, and the actual persistence is controlled by the strategy.\n * Callbacks follow the latest committed render, so a suspended render cannot\n * replace the callbacks used by active mutations.\n *\n * @param config - Configuration including onMutate, mutationFn and strategy\n * @returns A mutate function that accepts variables and returns Transaction objects\n *\n * @example\n * ```tsx\n * // Debounced auto-save\n * function AutoSaveForm({ formId }: { formId: string }) {\n * const mutate = usePacedMutations<string>({\n * onMutate: (value) => {\n * // Apply optimistic update immediately\n * formCollection.update(formId, draft => {\n * draft.content = value\n * })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.save(transaction.mutations)\n * },\n * strategy: debounceStrategy({ wait: 500 })\n * })\n *\n * const handleChange = async (value: string) => {\n * const tx = mutate(value)\n *\n * // Optional: await persistence or handle errors\n * try {\n * await tx.when('settled')\n * console.log('Saved!')\n * } catch (error) {\n * console.error('Save failed:', error)\n * }\n * }\n *\n * return <textarea onChange={e => handleChange(e.target.value)} />\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Throttled slider updates\n * function VolumeSlider() {\n * const mutate = usePacedMutations<number>({\n * onMutate: (volume) => {\n * settingsCollection.update('volume', draft => {\n * draft.value = volume\n * })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.updateVolume(transaction.mutations)\n * },\n * strategy: throttleStrategy({ wait: 200 })\n * })\n *\n * return <input type=\"range\" onChange={e => mutate(+e.target.value)} />\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Debounce with leading/trailing for color picker (persist first + final only)\n * function ColorPicker() {\n * const mutate = usePacedMutations<string>({\n * onMutate: (color) => {\n * themeCollection.update('primary', draft => {\n * draft.color = color\n * })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.updateTheme(transaction.mutations)\n * },\n * strategy: debounceStrategy({ wait: 0, leading: true, trailing: true })\n * })\n *\n * return (\n * <input\n * type=\"color\"\n * onChange={e => mutate(e.target.value)}\n * />\n * )\n * }\n * ```\n */\nexport function usePacedMutations<\n TVariables = unknown,\n T extends object = Record<string, unknown>,\n>(\n config: PacedMutationsConfig<TVariables, T>,\n): (variables: TVariables) => Transaction<T> {\n // Publish callbacks only for committed renders, before descendant layout\n // effects can mutate. Updating during render leaks suspended callbacks.\n const onMutateRef = useRef(config.onMutate)\n const mutationFnRef = useRef(config.mutationFn)\n\n useInsertionEffect(() => {\n onMutateRef.current = config.onMutate\n mutationFnRef.current = config.mutationFn\n }, [config.onMutate, config.mutationFn])\n\n // Create stable wrappers that always call the latest version\n const stableOnMutate = useCallback<typeof config.onMutate>((variables) => {\n return onMutateRef.current(variables)\n }, [])\n\n const stableMutationFn = useCallback<typeof config.mutationFn>((params) => {\n return mutationFnRef.current(params)\n }, [])\n\n // Create paced mutations instance with proper dependency tracking\n // Serialize strategy for stable comparison since strategy objects are recreated on each render\n const mutate = useMemo(() => {\n return createPacedMutations<TVariables, T>({\n ...config,\n onMutate: stableOnMutate,\n mutationFn: stableMutationFn,\n })\n }, [\n stableOnMutate,\n stableMutationFn,\n config.metadata,\n // Serialize strategy to avoid recreating when object reference changes but values are same\n JSON.stringify({\n type: config.strategy._type,\n options: config.strategy.options,\n }),\n ])\n\n // Return stable mutate callback\n const stableMutate = useCallback(mutate, [mutate])\n\n return stableMutate\n}\n"],"names":["useRef","useInsertionEffect","useCallback","useMemo","createPacedMutations"],"mappings":";;;;AA8FO,SAAS,kBAId,QAC2C;AAG3C,QAAM,cAAcA,MAAAA,OAAO,OAAO,QAAQ;AAC1C,QAAM,gBAAgBA,MAAAA,OAAO,OAAO,UAAU;AAE9CC,QAAAA,mBAAmB,MAAM;AACvB,gBAAY,UAAU,OAAO;AAC7B,kBAAc,UAAU,OAAO;AAAA,EACjC,GAAG,CAAC,OAAO,UAAU,OAAO,UAAU,CAAC;AAGvC,QAAM,iBAAiBC,kBAAoC,CAAC,cAAc;AACxE,WAAO,YAAY,QAAQ,SAAS;AAAA,EACtC,GAAG,CAAA,CAAE;AAEL,QAAM,mBAAmBA,kBAAsC,CAAC,WAAW;AACzE,WAAO,cAAc,QAAQ,MAAM;AAAA,EACrC,GAAG,CAAA,CAAE;AAIL,QAAM,SAASC,MAAAA,QAAQ,MAAM;AAC3B,WAAOC,wBAAoC;AAAA,MACzC,GAAG;AAAA,MACH,UAAU;AAAA,MACV,YAAY;AAAA,IAAA,CACb;AAAA,EACH,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA,OAAO;AAAA;AAAA,IAEP,KAAK,UAAU;AAAA,MACb,MAAM,OAAO,SAAS;AAAA,MACtB,SAAS,OAAO,SAAS;AAAA,IAAA,CAC1B;AAAA,EAAA,CACF;AAGD,QAAM,eAAeF,MAAAA,YAAY,QAAQ,CAAC,MAAM,CAAC;AAEjD,SAAO;AACT;;"}
|
|
@@ -33,7 +33,7 @@ import { PacedMutationsConfig, Transaction } from '@tanstack/db';
|
|
|
33
33
|
*
|
|
34
34
|
* // Optional: await persistence or handle errors
|
|
35
35
|
* try {
|
|
36
|
-
* await tx.
|
|
36
|
+
* await tx.when('settled')
|
|
37
37
|
* console.log('Saved!')
|
|
38
38
|
* } catch (error) {
|
|
39
39
|
* console.error('Save failed:', error)
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { DbClient, LiveQueryObserver, UnhashableQueryIRError } from '@tanstack/db';
|
|
1
|
+
import { DbClient, DeferredLiveQueryCollections, LiveQueryObserver, UnhashableQueryIRError } from '@tanstack/db';
|
|
2
2
|
export type LiveQueryResultInfo = {
|
|
3
3
|
client: DbClient | undefined;
|
|
4
4
|
queryHash: string | undefined;
|
|
5
5
|
identityError: UnhashableQueryIRError | undefined;
|
|
6
6
|
observer: LiveQueryObserver<object, string | number>;
|
|
7
|
+
/** Sync starts this render deferred, released if the render throws. */
|
|
8
|
+
deferredCollections: DeferredLiveQueryCollections;
|
|
7
9
|
};
|
|
8
10
|
export declare function setLiveQueryResultInfo(result: object, info: LiveQueryResultInfo): void;
|
|
9
11
|
export declare function getLiveQueryResultInfo(result: object): LiveQueryResultInfo;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-query-internals.js","sources":["../../src/live-query-internals.ts"],"sourcesContent":["import type {\n DbClient,\n LiveQueryObserver,\n UnhashableQueryIRError,\n} from '@tanstack/db'\n\nconst liveQueryResultInfo = Symbol(`liveQueryResultInfo`)\n\nexport type LiveQueryResultInfo = {\n client: DbClient | undefined\n queryHash: string | undefined\n identityError: UnhashableQueryIRError | undefined\n observer: LiveQueryObserver<object, string | number>\n}\n\ntype ResultWithInfo = {\n [liveQueryResultInfo]?: LiveQueryResultInfo\n}\n\nexport function setLiveQueryResultInfo(\n result: object,\n info: LiveQueryResultInfo,\n): void {\n Object.defineProperty(result, liveQueryResultInfo, {\n configurable: true,\n value: info,\n })\n}\n\nexport function getLiveQueryResultInfo(result: object): LiveQueryResultInfo {\n const info = (result as ResultWithInfo)[liveQueryResultInfo]\n if (!info) {\n throw new Error(`Missing internal live query result information.`)\n }\n return info\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"live-query-internals.js","sources":["../../src/live-query-internals.ts"],"sourcesContent":["import type {\n DbClient,\n DeferredLiveQueryCollections,\n LiveQueryObserver,\n UnhashableQueryIRError,\n} from '@tanstack/db'\n\nconst liveQueryResultInfo = Symbol(`liveQueryResultInfo`)\n\nexport type LiveQueryResultInfo = {\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}\n\ntype ResultWithInfo = {\n [liveQueryResultInfo]?: LiveQueryResultInfo\n}\n\nexport function setLiveQueryResultInfo(\n result: object,\n info: LiveQueryResultInfo,\n): void {\n Object.defineProperty(result, liveQueryResultInfo, {\n configurable: true,\n value: info,\n })\n}\n\nexport function getLiveQueryResultInfo(result: object): LiveQueryResultInfo {\n const info = (result as ResultWithInfo)[liveQueryResultInfo]\n if (!info) {\n throw new Error(`Missing internal live query result information.`)\n }\n return info\n}\n"],"names":[],"mappings":"AAOA,MAAM,6CAA6B,qBAAqB;AAejD,SAAS,uBACd,QACA,MACM;AACN,SAAO,eAAe,QAAQ,qBAAqB;AAAA,IACjD,cAAc;AAAA,IACd,OAAO;AAAA,EAAA,CACR;AACH;AAEO,SAAS,uBAAuB,QAAqC;AAC1E,QAAM,OAAQ,OAA0B,mBAAmB;AAC3D,MAAI,CAAC,MAAM;AACT,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AACA,SAAO;AACT;"}
|
|
@@ -1,20 +1,9 @@
|
|
|
1
1
|
import { DbClient } from '@tanstack/db';
|
|
2
|
-
export { resumeDeferredLiveQueryCollections as resumeSyncStarts } from '@tanstack/db';
|
|
3
|
-
export declare function getSourceObjectToken(source: object): number;
|
|
4
|
-
export declare function getPreparedSources(preparedValue: unknown, client?: DbClient, hookName?: string): Array<{
|
|
5
|
-
id: string;
|
|
6
|
-
}>;
|
|
7
|
-
/** The source object each Collection ID named while one hook is mounted. */
|
|
8
|
-
export type SourceIdBindings = {
|
|
9
|
-
unscoped: Map<string, number>;
|
|
10
|
-
byClient: WeakMap<DbClient, Map<string, number>>;
|
|
11
|
-
};
|
|
12
|
-
export declare function createSourceIdBindings(): SourceIdBindings;
|
|
13
2
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* or the hook could reuse a live query that reads the old source. Claims the
|
|
17
|
-
* prepared value's sources, or calls `release` and throws on a collision so a
|
|
18
|
-
* rejected render keeps no sync-start deferral.
|
|
3
|
+
* Reports a descriptor source that no DbClient bound, naming the provider a
|
|
4
|
+
* React tree needs. Returns `undefined` when a client can bind descriptors.
|
|
19
5
|
*/
|
|
20
|
-
export declare function
|
|
6
|
+
export declare function getUnboundDescriptorReporter(client: DbClient | undefined, hookName: string): ((alias: string) => never) | undefined;
|
|
7
|
+
export declare function getPreparedSources(preparedValue: unknown, client: DbClient | undefined, hookName: string): Array<{
|
|
8
|
+
id: string;
|
|
9
|
+
}>;
|
|
@@ -1,51 +1,20 @@
|
|
|
1
1
|
import { getPreparedLiveQuerySources } from "@tanstack/db";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
sourceObjectTokens.set(source, id);
|
|
10
|
-
}
|
|
11
|
-
return id;
|
|
2
|
+
function getUnboundDescriptorReporter(client, hookName) {
|
|
3
|
+
if (client) return void 0;
|
|
4
|
+
return (alias) => {
|
|
5
|
+
throw new Error(
|
|
6
|
+
`[${hookName}] Collection descriptor "${alias}" requires a DbClient when the query is consumed. Wrap this component in <DbProvider client={client}> or use a concrete Collection.`
|
|
7
|
+
);
|
|
8
|
+
};
|
|
12
9
|
}
|
|
13
10
|
function getPreparedSources(preparedValue, client, hookName) {
|
|
14
11
|
return getPreparedLiveQuerySources(
|
|
15
12
|
preparedValue,
|
|
16
|
-
|
|
17
|
-
throw new Error(
|
|
18
|
-
`[${hookName}] Collection descriptor "${alias}" requires a DbClient when the query is consumed. Wrap this component in <DbProvider client={client}> or use a concrete Collection.`
|
|
19
|
-
);
|
|
20
|
-
} : void 0
|
|
13
|
+
getUnboundDescriptorReporter(client, hookName)
|
|
21
14
|
);
|
|
22
15
|
}
|
|
23
|
-
function createSourceIdBindings() {
|
|
24
|
-
return { unscoped: /* @__PURE__ */ new Map(), byClient: /* @__PURE__ */ new WeakMap() };
|
|
25
|
-
}
|
|
26
|
-
function claimSourceIds(bindings, preparedValue, client, hookName, release) {
|
|
27
|
-
const prior = client ? bindings.byClient.get(client) : bindings.unscoped;
|
|
28
|
-
const seen = /* @__PURE__ */ new Map();
|
|
29
|
-
for (const source of getPreparedSources(preparedValue, client, hookName)) {
|
|
30
|
-
const token = getSourceObjectToken(source);
|
|
31
|
-
const previous = seen.get(source.id) ?? prior?.get(source.id);
|
|
32
|
-
if (previous !== void 0 && previous !== token) {
|
|
33
|
-
release();
|
|
34
|
-
throw new Error(
|
|
35
|
-
`[${hookName}] Source Collection "${source.id}" was replaced by a different Collection with the same ID while this hook is mounted. Unmount the hook and clean up its previous source and client scope before reusing the ID.`
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
seen.set(source.id, token);
|
|
39
|
-
}
|
|
40
|
-
const claimed = prior ?? /* @__PURE__ */ new Map();
|
|
41
|
-
for (const [id, token] of seen) claimed.set(id, token);
|
|
42
|
-
if (client) bindings.byClient.set(client, claimed);
|
|
43
|
-
}
|
|
44
16
|
export {
|
|
45
|
-
claimSourceIds,
|
|
46
|
-
createSourceIdBindings,
|
|
47
17
|
getPreparedSources,
|
|
48
|
-
|
|
49
|
-
resumeDeferredLiveQueryCollections as resumeSyncStarts
|
|
18
|
+
getUnboundDescriptorReporter
|
|
50
19
|
};
|
|
51
20
|
//# sourceMappingURL=source-id-bindings.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source-id-bindings.js","sources":["../../src/source-id-bindings.ts"],"sourcesContent":["import { getPreparedLiveQuerySources } from '@tanstack/db'\nimport type { DbClient } from '@tanstack/db'\n\
|
|
1
|
+
{"version":3,"file":"source-id-bindings.js","sources":["../../src/source-id-bindings.ts"],"sourcesContent":["import { getPreparedLiveQuerySources } from '@tanstack/db'\nimport type { DbClient } from '@tanstack/db'\n\n/**\n * Reports a descriptor source that no DbClient bound, naming the provider a\n * React tree needs. Returns `undefined` when a client can bind descriptors.\n */\nexport function getUnboundDescriptorReporter(\n client: DbClient | undefined,\n hookName: string,\n): ((alias: string) => never) | undefined {\n if (client) return undefined\n return (alias) => {\n throw new Error(\n `[${hookName}] Collection descriptor \"${alias}\" requires a DbClient when the query is consumed. Wrap this component in <DbProvider client={client}> or use a concrete Collection.`,\n )\n }\n}\n\nexport function getPreparedSources(\n preparedValue: unknown,\n client: DbClient | undefined,\n hookName: string,\n): Array<{ id: string }> {\n return getPreparedLiveQuerySources(\n preparedValue,\n getUnboundDescriptorReporter(client, hookName),\n )\n}\n"],"names":[],"mappings":";AAOO,SAAS,6BACd,QACA,UACwC;AACxC,MAAI,OAAQ,QAAO;AACnB,SAAO,CAAC,UAAU;AAChB,UAAM,IAAI;AAAA,MACR,IAAI,QAAQ,4BAA4B,KAAK;AAAA,IAAA;AAAA,EAEjD;AACF;AAEO,SAAS,mBACd,eACA,QACA,UACuB;AACvB,SAAO;AAAA,IACL;AAAA,IACA,6BAA6B,QAAQ,QAAQ;AAAA,EAAA;AAEjD;"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LiveQueryKey, useLiveQuery } from './useLiveQuery.js';
|
|
2
|
-
import { Collection, CollectionStatus, Context, DbClient, InferResultType, InitialQueryBuilder,
|
|
2
|
+
import { Collection, CollectionStatus, Context, DbClient, InferResultType, InitialQueryBuilder, LiveQueryStatusFlags, NonSingleResult, QueryBuilder } from '@tanstack/db';
|
|
3
3
|
export type UseLiveInfiniteQueryConfig<_TContext extends Context> = {
|
|
4
4
|
/**
|
|
5
5
|
* Explicit identity for queries that contain opaque functional variants or
|
|
@@ -20,28 +20,18 @@ export type UseLiveInfiniteQueryReturn<TContext extends Context> = Omit<ReturnTy
|
|
|
20
20
|
fetchNextPage: () => Promise<void>;
|
|
21
21
|
hasNextPage: boolean;
|
|
22
22
|
isFetchingNextPage: boolean;
|
|
23
|
-
error: unknown;
|
|
24
23
|
};
|
|
25
|
-
export type UseLiveInfiniteQueryReturnWithCollection<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>> = {
|
|
24
|
+
export type UseLiveInfiniteQueryReturnWithCollection<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>> = LiveQueryStatusFlags & {
|
|
26
25
|
data: Array<TResult>;
|
|
27
26
|
state: Map<TKey, TResult>;
|
|
28
27
|
collection: Collection<TResult, TKey, TUtils> & NonSingleResult;
|
|
29
28
|
status: CollectionStatus;
|
|
30
|
-
isLoading: boolean;
|
|
31
|
-
isReady: boolean;
|
|
32
|
-
persistedStatus: LiveQueryPersistedStatus;
|
|
33
|
-
isPersistedReady: boolean;
|
|
34
|
-
persistedError: unknown | undefined;
|
|
35
|
-
isIdle: boolean;
|
|
36
|
-
isError: boolean;
|
|
37
|
-
isCleanedUp: boolean;
|
|
38
29
|
isEnabled: true;
|
|
39
30
|
pages: Array<Array<TResult>>;
|
|
40
31
|
pageParams: Array<number>;
|
|
41
32
|
fetchNextPage: () => Promise<void>;
|
|
42
33
|
hasNextPage: boolean;
|
|
43
34
|
isFetchingNextPage: boolean;
|
|
44
|
-
error: unknown;
|
|
45
35
|
};
|
|
46
36
|
/**
|
|
47
37
|
* Create an infinite query using a query function with live updates.
|
|
@@ -51,8 +41,7 @@ export type UseLiveInfiniteQueryReturnWithCollection<TResult extends object, TKe
|
|
|
51
41
|
*
|
|
52
42
|
* @param queryFn - Query function that defines what data to fetch. Must include `.orderBy()` for setWindow to work.
|
|
53
43
|
* @param config - Configuration including pageSize and an optional initial page label
|
|
54
|
-
* @param deps - Deprecated array of dependencies that trigger query re-execution when changed
|
|
55
44
|
* @returns Object with pages, data, and pagination controls
|
|
56
45
|
*/
|
|
57
46
|
export declare function useLiveInfiniteQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult, config: UseLiveInfiniteQueryConfig<any>): UseLiveInfiniteQueryReturnWithCollection<TResult, TKey, TUtils>;
|
|
58
|
-
export declare function useLiveInfiniteQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>, config: UseLiveInfiniteQueryConfig<TContext
|
|
47
|
+
export declare function useLiveInfiniteQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>, config: UseLiveInfiniteQueryConfig<TContext>): UseLiveInfiniteQueryReturn<TContext>;
|