@tanstack/react-db 0.0.16 → 0.0.18

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.
@@ -77,7 +77,13 @@ function useLiveQuery(configOrQueryOrCollection, deps = []) {
77
77
  return {
78
78
  state: snapshot.state,
79
79
  data: snapshot.data,
80
- collection: snapshot.collection
80
+ collection: snapshot.collection,
81
+ status: snapshot.collection.status,
82
+ isLoading: snapshot.collection.status === `loading` || snapshot.collection.status === `initialCommit`,
83
+ isReady: snapshot.collection.status === `ready`,
84
+ isIdle: snapshot.collection.status === `idle`,
85
+ isError: snapshot.collection.status === `error`,
86
+ isCleanedUp: snapshot.collection.status === `cleaned-up`
81
87
  };
82
88
  }
83
89
  exports.useLiveQuery = useLiveQuery;
@@ -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 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}\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}\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}\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 }\n}\n"],"names":["useRef","createLiveQueryCollection","useSyncExternalStore"],"mappings":";;;;AA6CO,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,EACvB;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,16 +1,146 @@
1
- import { Collection, Context, GetResult, InitialQueryBuilder, LiveQueryCollectionConfig, QueryBuilder } from '@tanstack/db';
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>>;
5
55
  collection: Collection<GetResult<TContext>, string | number, {}>;
56
+ status: CollectionStatus;
57
+ isLoading: boolean;
58
+ isReady: boolean;
59
+ isIdle: boolean;
60
+ isError: boolean;
61
+ isCleanedUp: boolean;
6
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
+ */
7
96
  export declare function useLiveQuery<TContext extends Context>(config: LiveQueryCollectionConfig<TContext>, deps?: Array<unknown>): {
8
97
  state: Map<string | number, GetResult<TContext>>;
9
98
  data: Array<GetResult<TContext>>;
10
99
  collection: Collection<GetResult<TContext>, string | number, {}>;
100
+ status: CollectionStatus;
101
+ isLoading: boolean;
102
+ isReady: boolean;
103
+ isIdle: boolean;
104
+ isError: boolean;
105
+ isCleanedUp: boolean;
11
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
+ */
12
136
  export declare function useLiveQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: Collection<TResult, TKey, TUtils>): {
13
137
  state: Map<TKey, TResult>;
14
138
  data: Array<TResult>;
15
139
  collection: Collection<TResult, TKey, TUtils>;
140
+ status: CollectionStatus;
141
+ isLoading: boolean;
142
+ isReady: boolean;
143
+ isIdle: boolean;
144
+ isError: boolean;
145
+ isCleanedUp: boolean;
16
146
  };
@@ -1,16 +1,146 @@
1
- import { Collection, Context, GetResult, InitialQueryBuilder, LiveQueryCollectionConfig, QueryBuilder } from '@tanstack/db';
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>>;
5
55
  collection: Collection<GetResult<TContext>, string | number, {}>;
56
+ status: CollectionStatus;
57
+ isLoading: boolean;
58
+ isReady: boolean;
59
+ isIdle: boolean;
60
+ isError: boolean;
61
+ isCleanedUp: boolean;
6
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
+ */
7
96
  export declare function useLiveQuery<TContext extends Context>(config: LiveQueryCollectionConfig<TContext>, deps?: Array<unknown>): {
8
97
  state: Map<string | number, GetResult<TContext>>;
9
98
  data: Array<GetResult<TContext>>;
10
99
  collection: Collection<GetResult<TContext>, string | number, {}>;
100
+ status: CollectionStatus;
101
+ isLoading: boolean;
102
+ isReady: boolean;
103
+ isIdle: boolean;
104
+ isError: boolean;
105
+ isCleanedUp: boolean;
11
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
+ */
12
136
  export declare function useLiveQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: Collection<TResult, TKey, TUtils>): {
13
137
  state: Map<TKey, TResult>;
14
138
  data: Array<TResult>;
15
139
  collection: Collection<TResult, TKey, TUtils>;
140
+ status: CollectionStatus;
141
+ isLoading: boolean;
142
+ isReady: boolean;
143
+ isIdle: boolean;
144
+ isError: boolean;
145
+ isCleanedUp: boolean;
16
146
  };
@@ -75,7 +75,13 @@ function useLiveQuery(configOrQueryOrCollection, deps = []) {
75
75
  return {
76
76
  state: snapshot.state,
77
77
  data: snapshot.data,
78
- collection: snapshot.collection
78
+ collection: snapshot.collection,
79
+ status: snapshot.collection.status,
80
+ isLoading: snapshot.collection.status === `loading` || snapshot.collection.status === `initialCommit`,
81
+ isReady: snapshot.collection.status === `ready`,
82
+ isIdle: snapshot.collection.status === `idle`,
83
+ isError: snapshot.collection.status === `error`,
84
+ isCleanedUp: snapshot.collection.status === `cleaned-up`
79
85
  };
80
86
  }
81
87
  export {
@@ -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 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}\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}\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}\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 }\n}\n"],"names":[],"mappings":";;AA6CO,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,EACvB;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.16",
4
+ "version": "0.0.18",
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.16"
20
+ "@tanstack/db": "0.0.18"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@electric-sql/client": "1.0.0",
@@ -2,6 +2,7 @@ import { useRef, useSyncExternalStore } from "react"
2
2
  import { createLiveQueryCollection } from "@tanstack/db"
3
3
  import type {
4
4
  Collection,
5
+ CollectionStatus,
5
6
  Context,
6
7
  GetResult,
7
8
  InitialQueryBuilder,
@@ -9,6 +10,56 @@ import type {
9
10
  QueryBuilder,
10
11
  } from "@tanstack/db"
11
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
+ */
12
63
  // Overload 1: Accept just the query function
13
64
  export function useLiveQuery<TContext extends Context>(
14
65
  queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,
@@ -17,8 +68,47 @@ export function useLiveQuery<TContext extends Context>(
17
68
  state: Map<string | number, GetResult<TContext>>
18
69
  data: Array<GetResult<TContext>>
19
70
  collection: Collection<GetResult<TContext>, string | number, {}>
71
+ status: CollectionStatus
72
+ isLoading: boolean
73
+ isReady: boolean
74
+ isIdle: boolean
75
+ isError: boolean
76
+ isCleanedUp: boolean
20
77
  }
21
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
+ */
22
112
  // Overload 2: Accept config object
23
113
  export function useLiveQuery<TContext extends Context>(
24
114
  config: LiveQueryCollectionConfig<TContext>,
@@ -27,8 +117,43 @@ export function useLiveQuery<TContext extends Context>(
27
117
  state: Map<string | number, GetResult<TContext>>
28
118
  data: Array<GetResult<TContext>>
29
119
  collection: Collection<GetResult<TContext>, string | number, {}>
120
+ status: CollectionStatus
121
+ isLoading: boolean
122
+ isReady: boolean
123
+ isIdle: boolean
124
+ isError: boolean
125
+ isCleanedUp: boolean
30
126
  }
31
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
+ */
32
157
  // Overload 3: Accept pre-created live query collection
33
158
  export function useLiveQuery<
34
159
  TResult extends object,
@@ -40,6 +165,12 @@ export function useLiveQuery<
40
165
  state: Map<TKey, TResult>
41
166
  data: Array<TResult>
42
167
  collection: Collection<TResult, TKey, TUtils>
168
+ status: CollectionStatus
169
+ isLoading: boolean
170
+ isReady: boolean
171
+ isIdle: boolean
172
+ isError: boolean
173
+ isCleanedUp: boolean
43
174
  }
44
175
 
45
176
  // Implementation - use function overloads to infer the actual collection type
@@ -171,5 +302,13 @@ export function useLiveQuery(
171
302
  state: snapshot.state,
172
303
  data: snapshot.data,
173
304
  collection: snapshot.collection,
305
+ status: snapshot.collection.status,
306
+ isLoading:
307
+ snapshot.collection.status === `loading` ||
308
+ snapshot.collection.status === `initialCommit`,
309
+ isReady: snapshot.collection.status === `ready`,
310
+ isIdle: snapshot.collection.status === `idle`,
311
+ isError: snapshot.collection.status === `error`,
312
+ isCleanedUp: snapshot.collection.status === `cleaned-up`,
174
313
  }
175
314
  }