@tanstack/react-db 0.0.17 → 0.0.19

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.
@@ -1 +1 @@
1
- {"version":3,"file":"useLiveQuery.cjs","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["import { useRef, useSyncExternalStore } from \"react\"\nimport { createLiveQueryCollection } from \"@tanstack/db\"\nimport type {\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n QueryBuilder,\n} from \"@tanstack/db\"\n\n// Overload 1: Accept just the query function\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: Array<GetResult<TContext>>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n}\n\n// Overload 2: Accept config object\nexport function useLiveQuery<TContext extends Context>(\n config: LiveQueryCollectionConfig<TContext>,\n deps?: Array<unknown>\n): {\n state: Map<string | number, GetResult<TContext>>\n data: Array<GetResult<TContext>>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n}\n\n// Overload 3: 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>\n): {\n state: Map<TKey, TResult>\n data: Array<TResult>\n collection: Collection<TResult, TKey, TUtils>\n status: CollectionStatus\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\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 // Check if it's already a collection by checking for specific collection methods\n const isCollection =\n configOrQueryOrCollection &&\n typeof configOrQueryOrCollection === `object` &&\n typeof configOrQueryOrCollection.subscribeChanges === `function` &&\n typeof configOrQueryOrCollection.startSyncImmediate === `function` &&\n typeof configOrQueryOrCollection.id === `string`\n\n // Use refs to cache collection and track dependencies\n const collectionRef = useRef<any>(null)\n const depsRef = useRef<Array<unknown> | null>(null)\n const configRef = useRef<any>(null)\n\n // Check if we need to create/recreate the collection\n const needsNewCollection =\n !collectionRef.current ||\n (isCollection && configRef.current !== configOrQueryOrCollection) ||\n (!isCollection &&\n (depsRef.current === null ||\n depsRef.current.length !== deps.length ||\n depsRef.current.some((dep, i) => dep !== deps[i])))\n\n if (needsNewCollection) {\n if (isCollection) {\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 // Original logic for creating collections\n // Ensure we always start sync for React hooks\n if (typeof configOrQueryOrCollection === `function`) {\n collectionRef.current = createLiveQueryCollection({\n query: configOrQueryOrCollection,\n startSync: true,\n gcTime: 0, // Live queries created by useLiveQuery are cleaned up immediately\n })\n } else {\n collectionRef.current = createLiveQueryCollection({\n startSync: true,\n gcTime: 0, // Live queries created by useLiveQuery are cleaned up immediately\n ...configOrQueryOrCollection,\n })\n }\n depsRef.current = [...deps]\n }\n }\n\n // Use refs to track version and memoized snapshot\n const versionRef = useRef(0)\n const snapshotRef = useRef<{\n state: Map<any, any>\n data: Array<any>\n collection: Collection<any, any, any>\n _version: number\n } | null>(null)\n\n // Reset refs when collection changes\n if (needsNewCollection) {\n versionRef.current = 0\n snapshotRef.current = null\n }\n\n // Create stable subscribe function using ref\n const subscribeRef = useRef<\n ((onStoreChange: () => void) => () => void) | null\n >(null)\n if (!subscribeRef.current || needsNewCollection) {\n subscribeRef.current = (onStoreChange: () => void) => {\n const unsubscribe = collectionRef.current!.subscribeChanges(() => {\n versionRef.current += 1\n onStoreChange()\n })\n return () => {\n unsubscribe()\n }\n }\n }\n\n // Create stable getSnapshot function using ref\n const getSnapshotRef = useRef<\n | (() => {\n state: Map<any, any>\n data: Array<any>\n collection: Collection<any, any, any>\n })\n | null\n >(null)\n if (!getSnapshotRef.current || needsNewCollection) {\n getSnapshotRef.current = () => {\n const currentVersion = versionRef.current\n const currentCollection = collectionRef.current!\n\n // If we don't have a snapshot or the version changed, create a new one\n if (\n !snapshotRef.current ||\n snapshotRef.current._version !== currentVersion\n ) {\n snapshotRef.current = {\n get state() {\n return new Map(currentCollection.entries())\n },\n get data() {\n return Array.from(currentCollection.values())\n },\n collection: currentCollection,\n _version: currentVersion,\n }\n }\n\n return snapshotRef.current\n }\n }\n\n // Use useSyncExternalStore to subscribe to collection changes\n const snapshot = useSyncExternalStore(\n subscribeRef.current,\n getSnapshotRef.current\n )\n\n return {\n state: snapshot.state,\n data: snapshot.data,\n collection: snapshot.collection,\n status: snapshot.collection.status,\n isLoading:\n snapshot.collection.status === `loading` ||\n snapshot.collection.status === `initialCommit`,\n isReady: snapshot.collection.status === `ready`,\n isIdle: snapshot.collection.status === `idle`,\n isError: snapshot.collection.status === `error`,\n isCleanedUp: snapshot.collection.status === `cleaned-up`,\n }\n}\n"],"names":["useRef","createLiveQueryCollection","useSyncExternalStore"],"mappings":";;;;AAgEO,SAAS,aACd,2BACA,OAAuB,IACvB;AAEA,QAAM,eACJ,6BACA,OAAO,8BAA8B,YACrC,OAAO,0BAA0B,qBAAqB,cACtD,OAAO,0BAA0B,uBAAuB,cACxD,OAAO,0BAA0B,OAAO;AAGpC,QAAA,gBAAgBA,aAAY,IAAI;AAChC,QAAA,UAAUA,aAA8B,IAAI;AAC5C,QAAA,YAAYA,aAAY,IAAI;AAG5B,QAAA,qBACJ,CAAC,cAAc,WACd,gBAAgB,UAAU,YAAY,6BACtC,CAAC,iBACC,QAAQ,YAAY,QACnB,QAAQ,QAAQ,WAAW,KAAK,UAChC,QAAQ,QAAQ,KAAK,CAAC,KAAK,MAAM,QAAQ,KAAK,CAAC,CAAC;AAEtD,MAAI,oBAAoB;AACtB,QAAI,cAAc;AAEhB,gCAA0B,mBAAmB;AAC7C,oBAAc,UAAU;AACxB,gBAAU,UAAU;AAAA,IAAA,OACf;AAGD,UAAA,OAAO,8BAA8B,YAAY;AACnD,sBAAc,UAAUC,6BAA0B;AAAA,UAChD,OAAO;AAAA,UACP,WAAW;AAAA,UACX,QAAQ;AAAA;AAAA,QAAA,CACT;AAAA,MAAA,OACI;AACL,sBAAc,UAAUA,6BAA0B;AAAA,UAChD,WAAW;AAAA,UACX,QAAQ;AAAA;AAAA,UACR,GAAG;AAAA,QAAA,CACJ;AAAA,MAAA;AAEK,cAAA,UAAU,CAAC,GAAG,IAAI;AAAA,IAAA;AAAA,EAC5B;AAII,QAAA,aAAaD,aAAO,CAAC;AACrB,QAAA,cAAcA,aAKV,IAAI;AAGd,MAAI,oBAAoB;AACtB,eAAW,UAAU;AACrB,gBAAY,UAAU;AAAA,EAAA;AAIlB,QAAA,eAAeA,aAEnB,IAAI;AACF,MAAA,CAAC,aAAa,WAAW,oBAAoB;AAClC,iBAAA,UAAU,CAAC,kBAA8B;AACpD,YAAM,cAAc,cAAc,QAAS,iBAAiB,MAAM;AAChE,mBAAW,WAAW;AACR,sBAAA;AAAA,MAAA,CACf;AACD,aAAO,MAAM;AACC,oBAAA;AAAA,MACd;AAAA,IACF;AAAA,EAAA;AAII,QAAA,iBAAiBA,aAOrB,IAAI;AACF,MAAA,CAAC,eAAe,WAAW,oBAAoB;AACjD,mBAAe,UAAU,MAAM;AAC7B,YAAM,iBAAiB,WAAW;AAClC,YAAM,oBAAoB,cAAc;AAGxC,UACE,CAAC,YAAY,WACb,YAAY,QAAQ,aAAa,gBACjC;AACA,oBAAY,UAAU;AAAA,UACpB,IAAI,QAAQ;AACV,mBAAO,IAAI,IAAI,kBAAkB,SAAS;AAAA,UAC5C;AAAA,UACA,IAAI,OAAO;AACT,mBAAO,MAAM,KAAK,kBAAkB,OAAA,CAAQ;AAAA,UAC9C;AAAA,UACA,YAAY;AAAA,UACZ,UAAU;AAAA,QACZ;AAAA,MAAA;AAGF,aAAO,YAAY;AAAA,IACrB;AAAA,EAAA;AAIF,QAAM,WAAWE,MAAA;AAAA,IACf,aAAa;AAAA,IACb,eAAe;AAAA,EACjB;AAEO,SAAA;AAAA,IACL,OAAO,SAAS;AAAA,IAChB,MAAM,SAAS;AAAA,IACf,YAAY,SAAS;AAAA,IACrB,QAAQ,SAAS,WAAW;AAAA,IAC5B,WACE,SAAS,WAAW,WAAW,aAC/B,SAAS,WAAW,WAAW;AAAA,IACjC,SAAS,SAAS,WAAW,WAAW;AAAA,IACxC,QAAQ,SAAS,WAAW,WAAW;AAAA,IACvC,SAAS,SAAS,WAAW,WAAW;AAAA,IACxC,aAAa,SAAS,WAAW,WAAW;AAAA,EAC9C;AACF;;"}
1
+ {"version":3,"file":"useLiveQuery.cjs","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["import { useRef, useSyncExternalStore } from \"react\"\nimport { createLiveQueryCollection } from \"@tanstack/db\"\nimport type {\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n QueryBuilder,\n} from \"@tanstack/db\"\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 - Array of dependencies that trigger query re-execution when changed\n * @returns Object with reactive data, state, and status information\n * @example\n * // Basic query with object syntax\n * const { data, isLoading } = useLiveQuery((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 * // With dependencies that trigger re-execution\n * const { data, state } = useLiveQuery(\n * (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => gt(todos.priority, minPriority)),\n * [minPriority] // Re-run when minPriority changes\n * )\n *\n * @example\n * // Join pattern\n * const { data } = useLiveQuery((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((q) =>\n * 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 just the query function\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: Array<GetResult<TContext>>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n}\n\n/**\n * Create a live query using configuration object\n * @param config - Configuration object with query and options\n * @param deps - 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({ query: queryBuilder })\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 2: Accept config object\nexport function useLiveQuery<TContext extends Context>(\n config: LiveQueryCollectionConfig<TContext>,\n deps?: Array<unknown>\n): {\n state: Map<string | number, GetResult<TContext>>\n data: Array<GetResult<TContext>>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\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 3: 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>\n): {\n state: Map<TKey, TResult>\n data: Array<TResult>\n collection: Collection<TResult, TKey, TUtils>\n status: CollectionStatus\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\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 // Check if it's already a collection by checking for specific collection methods\n const isCollection =\n configOrQueryOrCollection &&\n typeof configOrQueryOrCollection === `object` &&\n typeof configOrQueryOrCollection.subscribeChanges === `function` &&\n typeof configOrQueryOrCollection.startSyncImmediate === `function` &&\n typeof configOrQueryOrCollection.id === `string`\n\n // Use refs to cache collection and track dependencies\n const collectionRef = useRef<any>(null)\n const depsRef = useRef<Array<unknown> | null>(null)\n const configRef = useRef<any>(null)\n\n // Check if we need to create/recreate the collection\n const needsNewCollection =\n !collectionRef.current ||\n (isCollection && configRef.current !== configOrQueryOrCollection) ||\n (!isCollection &&\n (depsRef.current === null ||\n depsRef.current.length !== deps.length ||\n depsRef.current.some((dep, i) => dep !== deps[i])))\n\n if (needsNewCollection) {\n if (isCollection) {\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 // Original logic for creating collections\n // Ensure we always start sync for React hooks\n if (typeof configOrQueryOrCollection === `function`) {\n collectionRef.current = createLiveQueryCollection({\n query: configOrQueryOrCollection,\n startSync: true,\n gcTime: 0, // Live queries created by useLiveQuery are cleaned up immediately\n })\n } else {\n collectionRef.current = createLiveQueryCollection({\n startSync: true,\n gcTime: 0, // Live queries created by useLiveQuery are cleaned up immediately\n ...configOrQueryOrCollection,\n })\n }\n depsRef.current = [...deps]\n }\n }\n\n // Use refs to track version and memoized snapshot\n const versionRef = useRef(0)\n const snapshotRef = useRef<{\n state: Map<any, any>\n data: Array<any>\n collection: Collection<any, any, any>\n _version: number\n } | null>(null)\n\n // Reset refs when collection changes\n if (needsNewCollection) {\n versionRef.current = 0\n snapshotRef.current = null\n }\n\n // Create stable subscribe function using ref\n const subscribeRef = useRef<\n ((onStoreChange: () => void) => () => void) | null\n >(null)\n if (!subscribeRef.current || needsNewCollection) {\n subscribeRef.current = (onStoreChange: () => void) => {\n const unsubscribe = collectionRef.current!.subscribeChanges(() => {\n versionRef.current += 1\n onStoreChange()\n })\n return () => {\n unsubscribe()\n }\n }\n }\n\n // Create stable getSnapshot function using ref\n const getSnapshotRef = useRef<\n | (() => {\n state: Map<any, any>\n data: Array<any>\n collection: Collection<any, any, any>\n })\n | null\n >(null)\n if (!getSnapshotRef.current || needsNewCollection) {\n getSnapshotRef.current = () => {\n const currentVersion = versionRef.current\n const currentCollection = collectionRef.current!\n\n // If we don't have a snapshot or the version changed, create a new one\n if (\n !snapshotRef.current ||\n snapshotRef.current._version !== currentVersion\n ) {\n snapshotRef.current = {\n get state() {\n return new Map(currentCollection.entries())\n },\n get data() {\n return Array.from(currentCollection.values())\n },\n collection: currentCollection,\n _version: currentVersion,\n }\n }\n\n return snapshotRef.current\n }\n }\n\n // Use useSyncExternalStore to subscribe to collection changes\n const snapshot = useSyncExternalStore(\n subscribeRef.current,\n getSnapshotRef.current\n )\n\n return {\n state: snapshot.state,\n data: snapshot.data,\n collection: snapshot.collection,\n status: snapshot.collection.status,\n isLoading:\n snapshot.collection.status === `loading` ||\n snapshot.collection.status === `initialCommit`,\n isReady: snapshot.collection.status === `ready`,\n isIdle: snapshot.collection.status === `idle`,\n isError: snapshot.collection.status === `error`,\n isCleanedUp: snapshot.collection.status === `cleaned-up`,\n }\n}\n"],"names":["useRef","createLiveQueryCollection","useSyncExternalStore"],"mappings":";;;;AAgLO,SAAS,aACd,2BACA,OAAuB,IACvB;AAEA,QAAM,eACJ,6BACA,OAAO,8BAA8B,YACrC,OAAO,0BAA0B,qBAAqB,cACtD,OAAO,0BAA0B,uBAAuB,cACxD,OAAO,0BAA0B,OAAO;AAG1C,QAAM,gBAAgBA,MAAAA,OAAY,IAAI;AACtC,QAAM,UAAUA,MAAAA,OAA8B,IAAI;AAClD,QAAM,YAAYA,MAAAA,OAAY,IAAI;AAGlC,QAAM,qBACJ,CAAC,cAAc,WACd,gBAAgB,UAAU,YAAY,6BACtC,CAAC,iBACC,QAAQ,YAAY,QACnB,QAAQ,QAAQ,WAAW,KAAK,UAChC,QAAQ,QAAQ,KAAK,CAAC,KAAK,MAAM,QAAQ,KAAK,CAAC,CAAC;AAEtD,MAAI,oBAAoB;AACtB,QAAI,cAAc;AAEhB,gCAA0B,mBAAA;AAC1B,oBAAc,UAAU;AACxB,gBAAU,UAAU;AAAA,IACtB,OAAO;AAGL,UAAI,OAAO,8BAA8B,YAAY;AACnD,sBAAc,UAAUC,6BAA0B;AAAA,UAChD,OAAO;AAAA,UACP,WAAW;AAAA,UACX,QAAQ;AAAA;AAAA,QAAA,CACT;AAAA,MACH,OAAO;AACL,sBAAc,UAAUA,6BAA0B;AAAA,UAChD,WAAW;AAAA,UACX,QAAQ;AAAA;AAAA,UACR,GAAG;AAAA,QAAA,CACJ;AAAA,MACH;AACA,cAAQ,UAAU,CAAC,GAAG,IAAI;AAAA,IAC5B;AAAA,EACF;AAGA,QAAM,aAAaD,MAAAA,OAAO,CAAC;AAC3B,QAAM,cAAcA,MAAAA,OAKV,IAAI;AAGd,MAAI,oBAAoB;AACtB,eAAW,UAAU;AACrB,gBAAY,UAAU;AAAA,EACxB;AAGA,QAAM,eAAeA,MAAAA,OAEnB,IAAI;AACN,MAAI,CAAC,aAAa,WAAW,oBAAoB;AAC/C,iBAAa,UAAU,CAAC,kBAA8B;AACpD,YAAM,cAAc,cAAc,QAAS,iBAAiB,MAAM;AAChE,mBAAW,WAAW;AACtB,sBAAA;AAAA,MACF,CAAC;AACD,aAAO,MAAM;AACX,oBAAA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,iBAAiBA,MAAAA,OAOrB,IAAI;AACN,MAAI,CAAC,eAAe,WAAW,oBAAoB;AACjD,mBAAe,UAAU,MAAM;AAC7B,YAAM,iBAAiB,WAAW;AAClC,YAAM,oBAAoB,cAAc;AAGxC,UACE,CAAC,YAAY,WACb,YAAY,QAAQ,aAAa,gBACjC;AACA,oBAAY,UAAU;AAAA,UACpB,IAAI,QAAQ;AACV,mBAAO,IAAI,IAAI,kBAAkB,SAAS;AAAA,UAC5C;AAAA,UACA,IAAI,OAAO;AACT,mBAAO,MAAM,KAAK,kBAAkB,OAAA,CAAQ;AAAA,UAC9C;AAAA,UACA,YAAY;AAAA,UACZ,UAAU;AAAA,QAAA;AAAA,MAEd;AAEA,aAAO,YAAY;AAAA,IACrB;AAAA,EACF;AAGA,QAAM,WAAWE,MAAAA;AAAAA,IACf,aAAa;AAAA,IACb,eAAe;AAAA,EAAA;AAGjB,SAAO;AAAA,IACL,OAAO,SAAS;AAAA,IAChB,MAAM,SAAS;AAAA,IACf,YAAY,SAAS;AAAA,IACrB,QAAQ,SAAS,WAAW;AAAA,IAC5B,WACE,SAAS,WAAW,WAAW,aAC/B,SAAS,WAAW,WAAW;AAAA,IACjC,SAAS,SAAS,WAAW,WAAW;AAAA,IACxC,QAAQ,SAAS,WAAW,WAAW;AAAA,IACvC,SAAS,SAAS,WAAW,WAAW;AAAA,IACxC,aAAa,SAAS,WAAW,WAAW;AAAA,EAAA;AAEhD;;"}
@@ -1,4 +1,54 @@
1
1
  import { Collection, CollectionStatus, Context, GetResult, InitialQueryBuilder, LiveQueryCollectionConfig, QueryBuilder } from '@tanstack/db';
2
+ /**
3
+ * Create a live query using a query function
4
+ * @param queryFn - Query function that defines what data to fetch
5
+ * @param deps - Array of dependencies that trigger query re-execution when changed
6
+ * @returns Object with reactive data, state, and status information
7
+ * @example
8
+ * // Basic query with object syntax
9
+ * const { data, isLoading } = useLiveQuery((q) =>
10
+ * q.from({ todos: todosCollection })
11
+ * .where(({ todos }) => eq(todos.completed, false))
12
+ * .select(({ todos }) => ({ id: todos.id, text: todos.text }))
13
+ * )
14
+ *
15
+ * @example
16
+ * // With dependencies that trigger re-execution
17
+ * const { data, state } = useLiveQuery(
18
+ * (q) => q.from({ todos: todosCollection })
19
+ * .where(({ todos }) => gt(todos.priority, minPriority)),
20
+ * [minPriority] // Re-run when minPriority changes
21
+ * )
22
+ *
23
+ * @example
24
+ * // Join pattern
25
+ * const { data } = useLiveQuery((q) =>
26
+ * q.from({ issues: issueCollection })
27
+ * .join({ persons: personCollection }, ({ issues, persons }) =>
28
+ * eq(issues.userId, persons.id)
29
+ * )
30
+ * .select(({ issues, persons }) => ({
31
+ * id: issues.id,
32
+ * title: issues.title,
33
+ * userName: persons.name
34
+ * }))
35
+ * )
36
+ *
37
+ * @example
38
+ * // Handle loading and error states
39
+ * const { data, isLoading, isError, status } = useLiveQuery((q) =>
40
+ * q.from({ todos: todoCollection })
41
+ * )
42
+ *
43
+ * if (isLoading) return <div>Loading...</div>
44
+ * if (isError) return <div>Error: {status}</div>
45
+ *
46
+ * return (
47
+ * <ul>
48
+ * {data.map(todo => <li key={todo.id}>{todo.text}</li>)}
49
+ * </ul>
50
+ * )
51
+ */
2
52
  export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>, deps?: Array<unknown>): {
3
53
  state: Map<string | number, GetResult<TContext>>;
4
54
  data: Array<GetResult<TContext>>;
@@ -10,6 +60,39 @@ export declare function useLiveQuery<TContext extends Context>(queryFn: (q: Init
10
60
  isError: boolean;
11
61
  isCleanedUp: boolean;
12
62
  };
63
+ /**
64
+ * Create a live query using configuration object
65
+ * @param config - Configuration object with query and options
66
+ * @param deps - Array of dependencies that trigger query re-execution when changed
67
+ * @returns Object with reactive data, state, and status information
68
+ * @example
69
+ * // Basic config object usage
70
+ * const { data, status } = useLiveQuery({
71
+ * query: (q) => q.from({ todos: todosCollection }),
72
+ * gcTime: 60000
73
+ * })
74
+ *
75
+ * @example
76
+ * // With query builder and options
77
+ * const queryBuilder = new Query()
78
+ * .from({ persons: collection })
79
+ * .where(({ persons }) => gt(persons.age, 30))
80
+ * .select(({ persons }) => ({ id: persons.id, name: persons.name }))
81
+ *
82
+ * const { data, isReady } = useLiveQuery({ query: queryBuilder })
83
+ *
84
+ * @example
85
+ * // Handle all states uniformly
86
+ * const { data, isLoading, isReady, isError } = useLiveQuery({
87
+ * query: (q) => q.from({ items: itemCollection })
88
+ * })
89
+ *
90
+ * if (isLoading) return <div>Loading...</div>
91
+ * if (isError) return <div>Something went wrong</div>
92
+ * if (!isReady) return <div>Preparing...</div>
93
+ *
94
+ * return <div>{data.length} items loaded</div>
95
+ */
13
96
  export declare function useLiveQuery<TContext extends Context>(config: LiveQueryCollectionConfig<TContext>, deps?: Array<unknown>): {
14
97
  state: Map<string | number, GetResult<TContext>>;
15
98
  data: Array<GetResult<TContext>>;
@@ -21,6 +104,35 @@ export declare function useLiveQuery<TContext extends Context>(config: LiveQuery
21
104
  isError: boolean;
22
105
  isCleanedUp: boolean;
23
106
  };
107
+ /**
108
+ * Subscribe to an existing live query collection
109
+ * @param liveQueryCollection - Pre-created live query collection to subscribe to
110
+ * @returns Object with reactive data, state, and status information
111
+ * @example
112
+ * // Using pre-created live query collection
113
+ * const myLiveQuery = createLiveQueryCollection((q) =>
114
+ * q.from({ todos: todosCollection }).where(({ todos }) => eq(todos.active, true))
115
+ * )
116
+ * const { data, collection } = useLiveQuery(myLiveQuery)
117
+ *
118
+ * @example
119
+ * // Access collection methods directly
120
+ * const { data, collection, isReady } = useLiveQuery(existingCollection)
121
+ *
122
+ * // Use collection for mutations
123
+ * const handleToggle = (id) => {
124
+ * collection.update(id, draft => { draft.completed = !draft.completed })
125
+ * }
126
+ *
127
+ * @example
128
+ * // Handle states consistently
129
+ * const { data, isLoading, isError } = useLiveQuery(sharedCollection)
130
+ *
131
+ * if (isLoading) return <div>Loading...</div>
132
+ * if (isError) return <div>Error loading data</div>
133
+ *
134
+ * return <div>{data.map(item => <Item key={item.id} {...item} />)}</div>
135
+ */
24
136
  export declare function useLiveQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: Collection<TResult, TKey, TUtils>): {
25
137
  state: Map<TKey, TResult>;
26
138
  data: Array<TResult>;
@@ -1,4 +1,54 @@
1
1
  import { Collection, CollectionStatus, Context, GetResult, InitialQueryBuilder, LiveQueryCollectionConfig, QueryBuilder } from '@tanstack/db';
2
+ /**
3
+ * Create a live query using a query function
4
+ * @param queryFn - Query function that defines what data to fetch
5
+ * @param deps - Array of dependencies that trigger query re-execution when changed
6
+ * @returns Object with reactive data, state, and status information
7
+ * @example
8
+ * // Basic query with object syntax
9
+ * const { data, isLoading } = useLiveQuery((q) =>
10
+ * q.from({ todos: todosCollection })
11
+ * .where(({ todos }) => eq(todos.completed, false))
12
+ * .select(({ todos }) => ({ id: todos.id, text: todos.text }))
13
+ * )
14
+ *
15
+ * @example
16
+ * // With dependencies that trigger re-execution
17
+ * const { data, state } = useLiveQuery(
18
+ * (q) => q.from({ todos: todosCollection })
19
+ * .where(({ todos }) => gt(todos.priority, minPriority)),
20
+ * [minPriority] // Re-run when minPriority changes
21
+ * )
22
+ *
23
+ * @example
24
+ * // Join pattern
25
+ * const { data } = useLiveQuery((q) =>
26
+ * q.from({ issues: issueCollection })
27
+ * .join({ persons: personCollection }, ({ issues, persons }) =>
28
+ * eq(issues.userId, persons.id)
29
+ * )
30
+ * .select(({ issues, persons }) => ({
31
+ * id: issues.id,
32
+ * title: issues.title,
33
+ * userName: persons.name
34
+ * }))
35
+ * )
36
+ *
37
+ * @example
38
+ * // Handle loading and error states
39
+ * const { data, isLoading, isError, status } = useLiveQuery((q) =>
40
+ * q.from({ todos: todoCollection })
41
+ * )
42
+ *
43
+ * if (isLoading) return <div>Loading...</div>
44
+ * if (isError) return <div>Error: {status}</div>
45
+ *
46
+ * return (
47
+ * <ul>
48
+ * {data.map(todo => <li key={todo.id}>{todo.text}</li>)}
49
+ * </ul>
50
+ * )
51
+ */
2
52
  export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>, deps?: Array<unknown>): {
3
53
  state: Map<string | number, GetResult<TContext>>;
4
54
  data: Array<GetResult<TContext>>;
@@ -10,6 +60,39 @@ export declare function useLiveQuery<TContext extends Context>(queryFn: (q: Init
10
60
  isError: boolean;
11
61
  isCleanedUp: boolean;
12
62
  };
63
+ /**
64
+ * Create a live query using configuration object
65
+ * @param config - Configuration object with query and options
66
+ * @param deps - Array of dependencies that trigger query re-execution when changed
67
+ * @returns Object with reactive data, state, and status information
68
+ * @example
69
+ * // Basic config object usage
70
+ * const { data, status } = useLiveQuery({
71
+ * query: (q) => q.from({ todos: todosCollection }),
72
+ * gcTime: 60000
73
+ * })
74
+ *
75
+ * @example
76
+ * // With query builder and options
77
+ * const queryBuilder = new Query()
78
+ * .from({ persons: collection })
79
+ * .where(({ persons }) => gt(persons.age, 30))
80
+ * .select(({ persons }) => ({ id: persons.id, name: persons.name }))
81
+ *
82
+ * const { data, isReady } = useLiveQuery({ query: queryBuilder })
83
+ *
84
+ * @example
85
+ * // Handle all states uniformly
86
+ * const { data, isLoading, isReady, isError } = useLiveQuery({
87
+ * query: (q) => q.from({ items: itemCollection })
88
+ * })
89
+ *
90
+ * if (isLoading) return <div>Loading...</div>
91
+ * if (isError) return <div>Something went wrong</div>
92
+ * if (!isReady) return <div>Preparing...</div>
93
+ *
94
+ * return <div>{data.length} items loaded</div>
95
+ */
13
96
  export declare function useLiveQuery<TContext extends Context>(config: LiveQueryCollectionConfig<TContext>, deps?: Array<unknown>): {
14
97
  state: Map<string | number, GetResult<TContext>>;
15
98
  data: Array<GetResult<TContext>>;
@@ -21,6 +104,35 @@ export declare function useLiveQuery<TContext extends Context>(config: LiveQuery
21
104
  isError: boolean;
22
105
  isCleanedUp: boolean;
23
106
  };
107
+ /**
108
+ * Subscribe to an existing live query collection
109
+ * @param liveQueryCollection - Pre-created live query collection to subscribe to
110
+ * @returns Object with reactive data, state, and status information
111
+ * @example
112
+ * // Using pre-created live query collection
113
+ * const myLiveQuery = createLiveQueryCollection((q) =>
114
+ * q.from({ todos: todosCollection }).where(({ todos }) => eq(todos.active, true))
115
+ * )
116
+ * const { data, collection } = useLiveQuery(myLiveQuery)
117
+ *
118
+ * @example
119
+ * // Access collection methods directly
120
+ * const { data, collection, isReady } = useLiveQuery(existingCollection)
121
+ *
122
+ * // Use collection for mutations
123
+ * const handleToggle = (id) => {
124
+ * collection.update(id, draft => { draft.completed = !draft.completed })
125
+ * }
126
+ *
127
+ * @example
128
+ * // Handle states consistently
129
+ * const { data, isLoading, isError } = useLiveQuery(sharedCollection)
130
+ *
131
+ * if (isLoading) return <div>Loading...</div>
132
+ * if (isError) return <div>Error loading data</div>
133
+ *
134
+ * return <div>{data.map(item => <Item key={item.id} {...item} />)}</div>
135
+ */
24
136
  export declare function useLiveQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: Collection<TResult, TKey, TUtils>): {
25
137
  state: Map<TKey, TResult>;
26
138
  data: Array<TResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"useLiveQuery.js","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["import { useRef, useSyncExternalStore } from \"react\"\nimport { createLiveQueryCollection } from \"@tanstack/db\"\nimport type {\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n QueryBuilder,\n} from \"@tanstack/db\"\n\n// Overload 1: Accept just the query function\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: Array<GetResult<TContext>>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n}\n\n// Overload 2: Accept config object\nexport function useLiveQuery<TContext extends Context>(\n config: LiveQueryCollectionConfig<TContext>,\n deps?: Array<unknown>\n): {\n state: Map<string | number, GetResult<TContext>>\n data: Array<GetResult<TContext>>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n}\n\n// Overload 3: 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>\n): {\n state: Map<TKey, TResult>\n data: Array<TResult>\n collection: Collection<TResult, TKey, TUtils>\n status: CollectionStatus\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\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 // Check if it's already a collection by checking for specific collection methods\n const isCollection =\n configOrQueryOrCollection &&\n typeof configOrQueryOrCollection === `object` &&\n typeof configOrQueryOrCollection.subscribeChanges === `function` &&\n typeof configOrQueryOrCollection.startSyncImmediate === `function` &&\n typeof configOrQueryOrCollection.id === `string`\n\n // Use refs to cache collection and track dependencies\n const collectionRef = useRef<any>(null)\n const depsRef = useRef<Array<unknown> | null>(null)\n const configRef = useRef<any>(null)\n\n // Check if we need to create/recreate the collection\n const needsNewCollection =\n !collectionRef.current ||\n (isCollection && configRef.current !== configOrQueryOrCollection) ||\n (!isCollection &&\n (depsRef.current === null ||\n depsRef.current.length !== deps.length ||\n depsRef.current.some((dep, i) => dep !== deps[i])))\n\n if (needsNewCollection) {\n if (isCollection) {\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 // Original logic for creating collections\n // Ensure we always start sync for React hooks\n if (typeof configOrQueryOrCollection === `function`) {\n collectionRef.current = createLiveQueryCollection({\n query: configOrQueryOrCollection,\n startSync: true,\n gcTime: 0, // Live queries created by useLiveQuery are cleaned up immediately\n })\n } else {\n collectionRef.current = createLiveQueryCollection({\n startSync: true,\n gcTime: 0, // Live queries created by useLiveQuery are cleaned up immediately\n ...configOrQueryOrCollection,\n })\n }\n depsRef.current = [...deps]\n }\n }\n\n // Use refs to track version and memoized snapshot\n const versionRef = useRef(0)\n const snapshotRef = useRef<{\n state: Map<any, any>\n data: Array<any>\n collection: Collection<any, any, any>\n _version: number\n } | null>(null)\n\n // Reset refs when collection changes\n if (needsNewCollection) {\n versionRef.current = 0\n snapshotRef.current = null\n }\n\n // Create stable subscribe function using ref\n const subscribeRef = useRef<\n ((onStoreChange: () => void) => () => void) | null\n >(null)\n if (!subscribeRef.current || needsNewCollection) {\n subscribeRef.current = (onStoreChange: () => void) => {\n const unsubscribe = collectionRef.current!.subscribeChanges(() => {\n versionRef.current += 1\n onStoreChange()\n })\n return () => {\n unsubscribe()\n }\n }\n }\n\n // Create stable getSnapshot function using ref\n const getSnapshotRef = useRef<\n | (() => {\n state: Map<any, any>\n data: Array<any>\n collection: Collection<any, any, any>\n })\n | null\n >(null)\n if (!getSnapshotRef.current || needsNewCollection) {\n getSnapshotRef.current = () => {\n const currentVersion = versionRef.current\n const currentCollection = collectionRef.current!\n\n // If we don't have a snapshot or the version changed, create a new one\n if (\n !snapshotRef.current ||\n snapshotRef.current._version !== currentVersion\n ) {\n snapshotRef.current = {\n get state() {\n return new Map(currentCollection.entries())\n },\n get data() {\n return Array.from(currentCollection.values())\n },\n collection: currentCollection,\n _version: currentVersion,\n }\n }\n\n return snapshotRef.current\n }\n }\n\n // Use useSyncExternalStore to subscribe to collection changes\n const snapshot = useSyncExternalStore(\n subscribeRef.current,\n getSnapshotRef.current\n )\n\n return {\n state: snapshot.state,\n data: snapshot.data,\n collection: snapshot.collection,\n status: snapshot.collection.status,\n isLoading:\n snapshot.collection.status === `loading` ||\n snapshot.collection.status === `initialCommit`,\n isReady: snapshot.collection.status === `ready`,\n isIdle: snapshot.collection.status === `idle`,\n isError: snapshot.collection.status === `error`,\n isCleanedUp: snapshot.collection.status === `cleaned-up`,\n }\n}\n"],"names":[],"mappings":";;AAgEO,SAAS,aACd,2BACA,OAAuB,IACvB;AAEA,QAAM,eACJ,6BACA,OAAO,8BAA8B,YACrC,OAAO,0BAA0B,qBAAqB,cACtD,OAAO,0BAA0B,uBAAuB,cACxD,OAAO,0BAA0B,OAAO;AAGpC,QAAA,gBAAgB,OAAY,IAAI;AAChC,QAAA,UAAU,OAA8B,IAAI;AAC5C,QAAA,YAAY,OAAY,IAAI;AAG5B,QAAA,qBACJ,CAAC,cAAc,WACd,gBAAgB,UAAU,YAAY,6BACtC,CAAC,iBACC,QAAQ,YAAY,QACnB,QAAQ,QAAQ,WAAW,KAAK,UAChC,QAAQ,QAAQ,KAAK,CAAC,KAAK,MAAM,QAAQ,KAAK,CAAC,CAAC;AAEtD,MAAI,oBAAoB;AACtB,QAAI,cAAc;AAEhB,gCAA0B,mBAAmB;AAC7C,oBAAc,UAAU;AACxB,gBAAU,UAAU;AAAA,IAAA,OACf;AAGD,UAAA,OAAO,8BAA8B,YAAY;AACnD,sBAAc,UAAU,0BAA0B;AAAA,UAChD,OAAO;AAAA,UACP,WAAW;AAAA,UACX,QAAQ;AAAA;AAAA,QAAA,CACT;AAAA,MAAA,OACI;AACL,sBAAc,UAAU,0BAA0B;AAAA,UAChD,WAAW;AAAA,UACX,QAAQ;AAAA;AAAA,UACR,GAAG;AAAA,QAAA,CACJ;AAAA,MAAA;AAEK,cAAA,UAAU,CAAC,GAAG,IAAI;AAAA,IAAA;AAAA,EAC5B;AAII,QAAA,aAAa,OAAO,CAAC;AACrB,QAAA,cAAc,OAKV,IAAI;AAGd,MAAI,oBAAoB;AACtB,eAAW,UAAU;AACrB,gBAAY,UAAU;AAAA,EAAA;AAIlB,QAAA,eAAe,OAEnB,IAAI;AACF,MAAA,CAAC,aAAa,WAAW,oBAAoB;AAClC,iBAAA,UAAU,CAAC,kBAA8B;AACpD,YAAM,cAAc,cAAc,QAAS,iBAAiB,MAAM;AAChE,mBAAW,WAAW;AACR,sBAAA;AAAA,MAAA,CACf;AACD,aAAO,MAAM;AACC,oBAAA;AAAA,MACd;AAAA,IACF;AAAA,EAAA;AAII,QAAA,iBAAiB,OAOrB,IAAI;AACF,MAAA,CAAC,eAAe,WAAW,oBAAoB;AACjD,mBAAe,UAAU,MAAM;AAC7B,YAAM,iBAAiB,WAAW;AAClC,YAAM,oBAAoB,cAAc;AAGxC,UACE,CAAC,YAAY,WACb,YAAY,QAAQ,aAAa,gBACjC;AACA,oBAAY,UAAU;AAAA,UACpB,IAAI,QAAQ;AACV,mBAAO,IAAI,IAAI,kBAAkB,SAAS;AAAA,UAC5C;AAAA,UACA,IAAI,OAAO;AACT,mBAAO,MAAM,KAAK,kBAAkB,OAAA,CAAQ;AAAA,UAC9C;AAAA,UACA,YAAY;AAAA,UACZ,UAAU;AAAA,QACZ;AAAA,MAAA;AAGF,aAAO,YAAY;AAAA,IACrB;AAAA,EAAA;AAIF,QAAM,WAAW;AAAA,IACf,aAAa;AAAA,IACb,eAAe;AAAA,EACjB;AAEO,SAAA;AAAA,IACL,OAAO,SAAS;AAAA,IAChB,MAAM,SAAS;AAAA,IACf,YAAY,SAAS;AAAA,IACrB,QAAQ,SAAS,WAAW;AAAA,IAC5B,WACE,SAAS,WAAW,WAAW,aAC/B,SAAS,WAAW,WAAW;AAAA,IACjC,SAAS,SAAS,WAAW,WAAW;AAAA,IACxC,QAAQ,SAAS,WAAW,WAAW;AAAA,IACvC,SAAS,SAAS,WAAW,WAAW;AAAA,IACxC,aAAa,SAAS,WAAW,WAAW;AAAA,EAC9C;AACF;"}
1
+ {"version":3,"file":"useLiveQuery.js","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["import { useRef, useSyncExternalStore } from \"react\"\nimport { createLiveQueryCollection } from \"@tanstack/db\"\nimport type {\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n QueryBuilder,\n} from \"@tanstack/db\"\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 - Array of dependencies that trigger query re-execution when changed\n * @returns Object with reactive data, state, and status information\n * @example\n * // Basic query with object syntax\n * const { data, isLoading } = useLiveQuery((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 * // With dependencies that trigger re-execution\n * const { data, state } = useLiveQuery(\n * (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => gt(todos.priority, minPriority)),\n * [minPriority] // Re-run when minPriority changes\n * )\n *\n * @example\n * // Join pattern\n * const { data } = useLiveQuery((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((q) =>\n * 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 just the query function\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: Array<GetResult<TContext>>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n}\n\n/**\n * Create a live query using configuration object\n * @param config - Configuration object with query and options\n * @param deps - 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({ query: queryBuilder })\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 2: Accept config object\nexport function useLiveQuery<TContext extends Context>(\n config: LiveQueryCollectionConfig<TContext>,\n deps?: Array<unknown>\n): {\n state: Map<string | number, GetResult<TContext>>\n data: Array<GetResult<TContext>>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\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 3: 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>\n): {\n state: Map<TKey, TResult>\n data: Array<TResult>\n collection: Collection<TResult, TKey, TUtils>\n status: CollectionStatus\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\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 // Check if it's already a collection by checking for specific collection methods\n const isCollection =\n configOrQueryOrCollection &&\n typeof configOrQueryOrCollection === `object` &&\n typeof configOrQueryOrCollection.subscribeChanges === `function` &&\n typeof configOrQueryOrCollection.startSyncImmediate === `function` &&\n typeof configOrQueryOrCollection.id === `string`\n\n // Use refs to cache collection and track dependencies\n const collectionRef = useRef<any>(null)\n const depsRef = useRef<Array<unknown> | null>(null)\n const configRef = useRef<any>(null)\n\n // Check if we need to create/recreate the collection\n const needsNewCollection =\n !collectionRef.current ||\n (isCollection && configRef.current !== configOrQueryOrCollection) ||\n (!isCollection &&\n (depsRef.current === null ||\n depsRef.current.length !== deps.length ||\n depsRef.current.some((dep, i) => dep !== deps[i])))\n\n if (needsNewCollection) {\n if (isCollection) {\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 // Original logic for creating collections\n // Ensure we always start sync for React hooks\n if (typeof configOrQueryOrCollection === `function`) {\n collectionRef.current = createLiveQueryCollection({\n query: configOrQueryOrCollection,\n startSync: true,\n gcTime: 0, // Live queries created by useLiveQuery are cleaned up immediately\n })\n } else {\n collectionRef.current = createLiveQueryCollection({\n startSync: true,\n gcTime: 0, // Live queries created by useLiveQuery are cleaned up immediately\n ...configOrQueryOrCollection,\n })\n }\n depsRef.current = [...deps]\n }\n }\n\n // Use refs to track version and memoized snapshot\n const versionRef = useRef(0)\n const snapshotRef = useRef<{\n state: Map<any, any>\n data: Array<any>\n collection: Collection<any, any, any>\n _version: number\n } | null>(null)\n\n // Reset refs when collection changes\n if (needsNewCollection) {\n versionRef.current = 0\n snapshotRef.current = null\n }\n\n // Create stable subscribe function using ref\n const subscribeRef = useRef<\n ((onStoreChange: () => void) => () => void) | null\n >(null)\n if (!subscribeRef.current || needsNewCollection) {\n subscribeRef.current = (onStoreChange: () => void) => {\n const unsubscribe = collectionRef.current!.subscribeChanges(() => {\n versionRef.current += 1\n onStoreChange()\n })\n return () => {\n unsubscribe()\n }\n }\n }\n\n // Create stable getSnapshot function using ref\n const getSnapshotRef = useRef<\n | (() => {\n state: Map<any, any>\n data: Array<any>\n collection: Collection<any, any, any>\n })\n | null\n >(null)\n if (!getSnapshotRef.current || needsNewCollection) {\n getSnapshotRef.current = () => {\n const currentVersion = versionRef.current\n const currentCollection = collectionRef.current!\n\n // If we don't have a snapshot or the version changed, create a new one\n if (\n !snapshotRef.current ||\n snapshotRef.current._version !== currentVersion\n ) {\n snapshotRef.current = {\n get state() {\n return new Map(currentCollection.entries())\n },\n get data() {\n return Array.from(currentCollection.values())\n },\n collection: currentCollection,\n _version: currentVersion,\n }\n }\n\n return snapshotRef.current\n }\n }\n\n // Use useSyncExternalStore to subscribe to collection changes\n const snapshot = useSyncExternalStore(\n subscribeRef.current,\n getSnapshotRef.current\n )\n\n return {\n state: snapshot.state,\n data: snapshot.data,\n collection: snapshot.collection,\n status: snapshot.collection.status,\n isLoading:\n snapshot.collection.status === `loading` ||\n snapshot.collection.status === `initialCommit`,\n isReady: snapshot.collection.status === `ready`,\n isIdle: snapshot.collection.status === `idle`,\n isError: snapshot.collection.status === `error`,\n isCleanedUp: snapshot.collection.status === `cleaned-up`,\n }\n}\n"],"names":[],"mappings":";;AAgLO,SAAS,aACd,2BACA,OAAuB,IACvB;AAEA,QAAM,eACJ,6BACA,OAAO,8BAA8B,YACrC,OAAO,0BAA0B,qBAAqB,cACtD,OAAO,0BAA0B,uBAAuB,cACxD,OAAO,0BAA0B,OAAO;AAG1C,QAAM,gBAAgB,OAAY,IAAI;AACtC,QAAM,UAAU,OAA8B,IAAI;AAClD,QAAM,YAAY,OAAY,IAAI;AAGlC,QAAM,qBACJ,CAAC,cAAc,WACd,gBAAgB,UAAU,YAAY,6BACtC,CAAC,iBACC,QAAQ,YAAY,QACnB,QAAQ,QAAQ,WAAW,KAAK,UAChC,QAAQ,QAAQ,KAAK,CAAC,KAAK,MAAM,QAAQ,KAAK,CAAC,CAAC;AAEtD,MAAI,oBAAoB;AACtB,QAAI,cAAc;AAEhB,gCAA0B,mBAAA;AAC1B,oBAAc,UAAU;AACxB,gBAAU,UAAU;AAAA,IACtB,OAAO;AAGL,UAAI,OAAO,8BAA8B,YAAY;AACnD,sBAAc,UAAU,0BAA0B;AAAA,UAChD,OAAO;AAAA,UACP,WAAW;AAAA,UACX,QAAQ;AAAA;AAAA,QAAA,CACT;AAAA,MACH,OAAO;AACL,sBAAc,UAAU,0BAA0B;AAAA,UAChD,WAAW;AAAA,UACX,QAAQ;AAAA;AAAA,UACR,GAAG;AAAA,QAAA,CACJ;AAAA,MACH;AACA,cAAQ,UAAU,CAAC,GAAG,IAAI;AAAA,IAC5B;AAAA,EACF;AAGA,QAAM,aAAa,OAAO,CAAC;AAC3B,QAAM,cAAc,OAKV,IAAI;AAGd,MAAI,oBAAoB;AACtB,eAAW,UAAU;AACrB,gBAAY,UAAU;AAAA,EACxB;AAGA,QAAM,eAAe,OAEnB,IAAI;AACN,MAAI,CAAC,aAAa,WAAW,oBAAoB;AAC/C,iBAAa,UAAU,CAAC,kBAA8B;AACpD,YAAM,cAAc,cAAc,QAAS,iBAAiB,MAAM;AAChE,mBAAW,WAAW;AACtB,sBAAA;AAAA,MACF,CAAC;AACD,aAAO,MAAM;AACX,oBAAA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,iBAAiB,OAOrB,IAAI;AACN,MAAI,CAAC,eAAe,WAAW,oBAAoB;AACjD,mBAAe,UAAU,MAAM;AAC7B,YAAM,iBAAiB,WAAW;AAClC,YAAM,oBAAoB,cAAc;AAGxC,UACE,CAAC,YAAY,WACb,YAAY,QAAQ,aAAa,gBACjC;AACA,oBAAY,UAAU;AAAA,UACpB,IAAI,QAAQ;AACV,mBAAO,IAAI,IAAI,kBAAkB,SAAS;AAAA,UAC5C;AAAA,UACA,IAAI,OAAO;AACT,mBAAO,MAAM,KAAK,kBAAkB,OAAA,CAAQ;AAAA,UAC9C;AAAA,UACA,YAAY;AAAA,UACZ,UAAU;AAAA,QAAA;AAAA,MAEd;AAEA,aAAO,YAAY;AAAA,IACrB;AAAA,EACF;AAGA,QAAM,WAAW;AAAA,IACf,aAAa;AAAA,IACb,eAAe;AAAA,EAAA;AAGjB,SAAO;AAAA,IACL,OAAO,SAAS;AAAA,IAChB,MAAM,SAAS;AAAA,IACf,YAAY,SAAS;AAAA,IACrB,QAAQ,SAAS,WAAW;AAAA,IAC5B,WACE,SAAS,WAAW,WAAW,aAC/B,SAAS,WAAW,WAAW;AAAA,IACjC,SAAS,SAAS,WAAW,WAAW;AAAA,IACxC,QAAQ,SAAS,WAAW,WAAW;AAAA,IACvC,SAAS,SAAS,WAAW,WAAW;AAAA,IACxC,aAAa,SAAS,WAAW,WAAW;AAAA,EAAA;AAEhD;"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tanstack/react-db",
3
3
  "description": "React integration for @tanstack/db",
4
- "version": "0.0.17",
4
+ "version": "0.0.19",
5
5
  "author": "Kyle Mathews",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -17,7 +17,7 @@
17
17
  ],
18
18
  "dependencies": {
19
19
  "use-sync-external-store": "^1.2.0",
20
- "@tanstack/db": "0.0.17"
20
+ "@tanstack/db": "0.0.19"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@electric-sql/client": "1.0.0",
@@ -10,6 +10,56 @@ import type {
10
10
  QueryBuilder,
11
11
  } from "@tanstack/db"
12
12
 
13
+ /**
14
+ * Create a live query using a query function
15
+ * @param queryFn - Query function that defines what data to fetch
16
+ * @param deps - Array of dependencies that trigger query re-execution when changed
17
+ * @returns Object with reactive data, state, and status information
18
+ * @example
19
+ * // Basic query with object syntax
20
+ * const { data, isLoading } = useLiveQuery((q) =>
21
+ * q.from({ todos: todosCollection })
22
+ * .where(({ todos }) => eq(todos.completed, false))
23
+ * .select(({ todos }) => ({ id: todos.id, text: todos.text }))
24
+ * )
25
+ *
26
+ * @example
27
+ * // With dependencies that trigger re-execution
28
+ * const { data, state } = useLiveQuery(
29
+ * (q) => q.from({ todos: todosCollection })
30
+ * .where(({ todos }) => gt(todos.priority, minPriority)),
31
+ * [minPriority] // Re-run when minPriority changes
32
+ * )
33
+ *
34
+ * @example
35
+ * // Join pattern
36
+ * const { data } = useLiveQuery((q) =>
37
+ * q.from({ issues: issueCollection })
38
+ * .join({ persons: personCollection }, ({ issues, persons }) =>
39
+ * eq(issues.userId, persons.id)
40
+ * )
41
+ * .select(({ issues, persons }) => ({
42
+ * id: issues.id,
43
+ * title: issues.title,
44
+ * userName: persons.name
45
+ * }))
46
+ * )
47
+ *
48
+ * @example
49
+ * // Handle loading and error states
50
+ * const { data, isLoading, isError, status } = useLiveQuery((q) =>
51
+ * q.from({ todos: todoCollection })
52
+ * )
53
+ *
54
+ * if (isLoading) return <div>Loading...</div>
55
+ * if (isError) return <div>Error: {status}</div>
56
+ *
57
+ * return (
58
+ * <ul>
59
+ * {data.map(todo => <li key={todo.id}>{todo.text}</li>)}
60
+ * </ul>
61
+ * )
62
+ */
13
63
  // Overload 1: Accept just the query function
14
64
  export function useLiveQuery<TContext extends Context>(
15
65
  queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,
@@ -26,6 +76,39 @@ export function useLiveQuery<TContext extends Context>(
26
76
  isCleanedUp: boolean
27
77
  }
28
78
 
79
+ /**
80
+ * Create a live query using configuration object
81
+ * @param config - Configuration object with query and options
82
+ * @param deps - Array of dependencies that trigger query re-execution when changed
83
+ * @returns Object with reactive data, state, and status information
84
+ * @example
85
+ * // Basic config object usage
86
+ * const { data, status } = useLiveQuery({
87
+ * query: (q) => q.from({ todos: todosCollection }),
88
+ * gcTime: 60000
89
+ * })
90
+ *
91
+ * @example
92
+ * // With query builder and options
93
+ * const queryBuilder = new Query()
94
+ * .from({ persons: collection })
95
+ * .where(({ persons }) => gt(persons.age, 30))
96
+ * .select(({ persons }) => ({ id: persons.id, name: persons.name }))
97
+ *
98
+ * const { data, isReady } = useLiveQuery({ query: queryBuilder })
99
+ *
100
+ * @example
101
+ * // Handle all states uniformly
102
+ * const { data, isLoading, isReady, isError } = useLiveQuery({
103
+ * query: (q) => q.from({ items: itemCollection })
104
+ * })
105
+ *
106
+ * if (isLoading) return <div>Loading...</div>
107
+ * if (isError) return <div>Something went wrong</div>
108
+ * if (!isReady) return <div>Preparing...</div>
109
+ *
110
+ * return <div>{data.length} items loaded</div>
111
+ */
29
112
  // Overload 2: Accept config object
30
113
  export function useLiveQuery<TContext extends Context>(
31
114
  config: LiveQueryCollectionConfig<TContext>,
@@ -42,6 +125,35 @@ export function useLiveQuery<TContext extends Context>(
42
125
  isCleanedUp: boolean
43
126
  }
44
127
 
128
+ /**
129
+ * Subscribe to an existing live query collection
130
+ * @param liveQueryCollection - Pre-created live query collection to subscribe to
131
+ * @returns Object with reactive data, state, and status information
132
+ * @example
133
+ * // Using pre-created live query collection
134
+ * const myLiveQuery = createLiveQueryCollection((q) =>
135
+ * q.from({ todos: todosCollection }).where(({ todos }) => eq(todos.active, true))
136
+ * )
137
+ * const { data, collection } = useLiveQuery(myLiveQuery)
138
+ *
139
+ * @example
140
+ * // Access collection methods directly
141
+ * const { data, collection, isReady } = useLiveQuery(existingCollection)
142
+ *
143
+ * // Use collection for mutations
144
+ * const handleToggle = (id) => {
145
+ * collection.update(id, draft => { draft.completed = !draft.completed })
146
+ * }
147
+ *
148
+ * @example
149
+ * // Handle states consistently
150
+ * const { data, isLoading, isError } = useLiveQuery(sharedCollection)
151
+ *
152
+ * if (isLoading) return <div>Loading...</div>
153
+ * if (isError) return <div>Error loading data</div>
154
+ *
155
+ * return <div>{data.map(item => <Item key={item.id} {...item} />)}</div>
156
+ */
45
157
  // Overload 3: Accept pre-created live query collection
46
158
  export function useLiveQuery<
47
159
  TResult extends object,