UNPKG

@tanstack/svelte-db

Version:

Svelte integration for @tanstack/db

211 lines (210 loc) 8.14 kB
import type { Collection, CollectionStatus, Context, GetResult, InferResultType, InitialQueryBuilder, LiveQueryCollectionConfig, NonSingleResult, QueryBuilder, SingleResult } from '@tanstack/db'; /** * Return type for useLiveQuery hook * @property state - Reactive Map of query results (key → item) * @property data - Reactive array of query results in order, or single item when using findOne() * @property collection - The underlying query collection instance * @property status - Current query status * @property isLoading - True while initial query data is loading * @property isReady - True when query has received first data and is ready * @property isIdle - True when query hasn't started yet * @property isError - True when query encountered an error * @property isCleanedUp - True when query has been cleaned up */ export interface UseLiveQueryReturn<T extends object, TData = Array<T>> { state: Map<string | number, T>; data: TData; collection: Collection<T, string | number, {}>; status: CollectionStatus; isLoading: boolean; isReady: boolean; isIdle: boolean; isError: boolean; isCleanedUp: boolean; } export interface UseLiveQueryReturnWithCollection<T extends object, TKey extends string | number, TUtils extends Record<string, any>, TData = Array<T>> { state: Map<TKey, T>; data: TData; collection: Collection<T, TKey, TUtils>; status: CollectionStatus; isLoading: boolean; isReady: boolean; isIdle: boolean; isError: boolean; isCleanedUp: boolean; } type MaybeGetter<T> = T | (() => T); /** * Create a live query using a query function * @param queryFn - Query function that defines what data to fetch * @param deps - Array of reactive dependencies that trigger query re-execution when changed * @returns Reactive object with query data, state, and status information * * @remarks * **IMPORTANT - Destructuring in Svelte 5:** * Direct destructuring breaks reactivity. To destructure, wrap with `$derived`: * * ❌ **Incorrect** - Loses reactivity: * ```ts * const { data, isLoading } = useLiveQuery(...) * ``` * * ✅ **Correct** - Maintains reactivity: * ```ts * // Option 1: Use dot notation (recommended) * const query = useLiveQuery(...) * // Access: query.data, query.isLoading * * // Option 2: Wrap with $derived for destructuring * const query = useLiveQuery(...) * const { data, isLoading } = $derived(query) * ``` * * This is a fundamental Svelte 5 limitation, not a library bug. See: * https://github.com/sveltejs/svelte/issues/11002 * * @example * // Basic query with object syntax (recommended pattern) * const todosQuery = useLiveQuery((q) => * q.from({ todos: todosCollection }) * .where(({ todos }) => eq(todos.completed, false)) * .select(({ todos }) => ({ id: todos.id, text: todos.text })) * ) * // Access via: todosQuery.data, todosQuery.isLoading, etc. * * @example * // With reactive dependencies * let minPriority = $state(5) * const todosQuery = useLiveQuery( * (q) => q.from({ todos: todosCollection }) * .where(({ todos }) => gt(todos.priority, minPriority)), * [() => minPriority] // Re-run when minPriority changes * ) * * @example * // Destructuring with $derived (if needed) * const query = useLiveQuery((q) => * q.from({ todos: todosCollection }) * ) * const { data, isLoading, isError } = $derived(query) * // Now data, isLoading, and isError maintain reactivity * * @example * // Join pattern * const issuesQuery = useLiveQuery((q) => * q.from({ issues: issueCollection }) * .join({ persons: personCollection }, ({ issues, persons }) => * eq(issues.userId, persons.id) * ) * .select(({ issues, persons }) => ({ * id: issues.id, * title: issues.title, * userName: persons.name * })) * ) * * @example * // Handle loading and error states in template * const todosQuery = useLiveQuery((q) => * q.from({ todos: todoCollection }) * ) * * // In template: * // {#if todosQuery.isLoading} * // <div>Loading...</div> * // {:else if todosQuery.isError} * // <div>Error: {todosQuery.status}</div> * // {:else} * // <ul> * // {#each todosQuery.data as todo (todo.id)} * // <li>{todo.text}</li> * // {/each} * // </ul> * // {/if} */ export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>, deps?: Array<() => unknown>): UseLiveQueryReturn<GetResult<TContext>, InferResultType<TContext>>; export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext> | undefined | null, deps?: Array<() => unknown>): UseLiveQueryReturn<GetResult<TContext>, InferResultType<TContext> | undefined>; /** * Create a live query using configuration object * @param config - Configuration object with query and options * @param deps - Array of reactive dependencies that trigger query re-execution when changed * @returns Reactive object with query data, state, and status information * @example * // Basic config object usage * const todosQuery = useLiveQuery({ * query: (q) => q.from({ todos: todosCollection }), * gcTime: 60000 * }) * * @example * // With reactive dependencies * let filter = $state('active') * const todosQuery = useLiveQuery({ * query: (q) => q.from({ todos: todosCollection }) * .where(({ todos }) => eq(todos.status, filter)) * }, [() => filter]) * * @example * // Handle all states uniformly * const itemsQuery = useLiveQuery({ * query: (q) => q.from({ items: itemCollection }) * }) * * // In template: * // {#if itemsQuery.isLoading} * // <div>Loading...</div> * // {:else if itemsQuery.isError} * // <div>Something went wrong</div> * // {:else if !itemsQuery.isReady} * // <div>Preparing...</div> * // {:else} * // <div>{itemsQuery.data.length} items loaded</div> * // {/if} */ export declare function useLiveQuery<TContext extends Context>(config: LiveQueryCollectionConfig<TContext>, deps?: Array<() => unknown>): UseLiveQueryReturn<GetResult<TContext>, InferResultType<TContext>>; /** * Subscribe to an existing query collection (can be reactive) * @param liveQueryCollection - Pre-created query collection to subscribe to (can be a getter) * @returns Reactive object with query data, state, and status information * @example * // Using pre-created query collection * const myLiveQuery = createLiveQueryCollection((q) => * q.from({ todos: todosCollection }).where(({ todos }) => eq(todos.active, true)) * ) * const queryResult = useLiveQuery(myLiveQuery) * * @example * // Reactive query collection reference * let selectedQuery = $state(todosQuery) * const queryResult = useLiveQuery(() => selectedQuery) * * // Switch queries reactively * selectedQuery = archiveQuery * * @example * // Access query collection methods directly * const queryResult = useLiveQuery(existingQuery) * * // Use underlying collection for mutations * const handleToggle = (id) => { * queryResult.collection.update(id, draft => { draft.completed = !draft.completed }) * } * * @example * // Handle states consistently * const queryResult = useLiveQuery(sharedQuery) * * // In template: * // {#if queryResult.isLoading} * // <div>Loading...</div> * // {:else if queryResult.isError} * // <div>Error loading data</div> * // {:else} * // {#each queryResult.data as item (item.id)} * // <Item {...item} /> * // {/each} * // {/if} */ export declare function useLiveQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: MaybeGetter<Collection<TResult, TKey, TUtils> & NonSingleResult>): UseLiveQueryReturnWithCollection<TResult, TKey, TUtils, Array<TResult>>; export declare function useLiveQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: MaybeGetter<Collection<TResult, TKey, TUtils> & SingleResult>): UseLiveQueryReturnWithCollection<TResult, TKey, TUtils, TResult | undefined>; export {};