@tanstack/react-db 0.0.14 → 0.0.16

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,32 +1,83 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const react = require("react");
4
- const reactStore = require("@tanstack/react-store");
5
4
  const db = require("@tanstack/db");
6
- function useLiveQuery(queryFn, deps = []) {
7
- const [restart, forceRestart] = react.useState(0);
8
- const compiledQuery = react.useMemo(() => {
9
- const query = queryFn(db.queryBuilder());
10
- const compiled = db.compileQuery(query);
11
- compiled.start();
12
- return compiled;
13
- }, [...deps, restart]);
14
- const state = reactStore.useStore(compiledQuery.results.asStoreMap());
15
- const data = reactStore.useStore(compiledQuery.results.asStoreArray());
16
- react.useEffect(() => {
17
- if (compiledQuery.state === `stopped`) {
18
- forceRestart((count) => {
19
- return count += 1;
20
- });
5
+ function useLiveQuery(configOrQueryOrCollection, deps = []) {
6
+ const isCollection = configOrQueryOrCollection && typeof configOrQueryOrCollection === `object` && typeof configOrQueryOrCollection.subscribeChanges === `function` && typeof configOrQueryOrCollection.startSyncImmediate === `function` && typeof configOrQueryOrCollection.id === `string`;
7
+ const collectionRef = react.useRef(null);
8
+ const depsRef = react.useRef(null);
9
+ const configRef = react.useRef(null);
10
+ const needsNewCollection = !collectionRef.current || isCollection && configRef.current !== configOrQueryOrCollection || !isCollection && (depsRef.current === null || depsRef.current.length !== deps.length || depsRef.current.some((dep, i) => dep !== deps[i]));
11
+ if (needsNewCollection) {
12
+ if (isCollection) {
13
+ configOrQueryOrCollection.startSyncImmediate();
14
+ collectionRef.current = configOrQueryOrCollection;
15
+ configRef.current = configOrQueryOrCollection;
16
+ } else {
17
+ if (typeof configOrQueryOrCollection === `function`) {
18
+ collectionRef.current = db.createLiveQueryCollection({
19
+ query: configOrQueryOrCollection,
20
+ startSync: true,
21
+ gcTime: 0
22
+ // Live queries created by useLiveQuery are cleaned up immediately
23
+ });
24
+ } else {
25
+ collectionRef.current = db.createLiveQueryCollection({
26
+ startSync: true,
27
+ gcTime: 0,
28
+ // Live queries created by useLiveQuery are cleaned up immediately
29
+ ...configOrQueryOrCollection
30
+ });
31
+ }
32
+ depsRef.current = [...deps];
21
33
  }
22
- return () => {
23
- compiledQuery.stop();
34
+ }
35
+ const versionRef = react.useRef(0);
36
+ const snapshotRef = react.useRef(null);
37
+ if (needsNewCollection) {
38
+ versionRef.current = 0;
39
+ snapshotRef.current = null;
40
+ }
41
+ const subscribeRef = react.useRef(null);
42
+ if (!subscribeRef.current || needsNewCollection) {
43
+ subscribeRef.current = (onStoreChange) => {
44
+ const unsubscribe = collectionRef.current.subscribeChanges(() => {
45
+ versionRef.current += 1;
46
+ onStoreChange();
47
+ });
48
+ return () => {
49
+ unsubscribe();
50
+ };
51
+ };
52
+ }
53
+ const getSnapshotRef = react.useRef(null);
54
+ if (!getSnapshotRef.current || needsNewCollection) {
55
+ getSnapshotRef.current = () => {
56
+ const currentVersion = versionRef.current;
57
+ const currentCollection = collectionRef.current;
58
+ if (!snapshotRef.current || snapshotRef.current._version !== currentVersion) {
59
+ snapshotRef.current = {
60
+ get state() {
61
+ return new Map(currentCollection.entries());
62
+ },
63
+ get data() {
64
+ return Array.from(currentCollection.values());
65
+ },
66
+ collection: currentCollection,
67
+ _version: currentVersion
68
+ };
69
+ }
70
+ return snapshotRef.current;
24
71
  };
25
- }, [compiledQuery]);
72
+ }
73
+ const snapshot = react.useSyncExternalStore(
74
+ subscribeRef.current,
75
+ getSnapshotRef.current
76
+ );
26
77
  return {
27
- state,
28
- data,
29
- collection: compiledQuery.results
78
+ state: snapshot.state,
79
+ data: snapshot.data,
80
+ collection: snapshot.collection
30
81
  };
31
82
  }
32
83
  exports.useLiveQuery = useLiveQuery;
@@ -1 +1 @@
1
- {"version":3,"file":"useLiveQuery.cjs","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from \"react\"\nimport { useStore } from \"@tanstack/react-store\"\nimport { compileQuery, queryBuilder } from \"@tanstack/db\"\nimport type {\n Collection,\n Context,\n InitialQueryBuilder,\n QueryBuilder,\n ResultsFromContext,\n Schema,\n} from \"@tanstack/db\"\n\nexport interface UseLiveQueryReturn<T extends object> {\n state: Map<string | number, T>\n data: Array<T>\n collection: Collection<T>\n}\n\nexport function useLiveQuery<\n TResultContext extends Context<Schema> = Context<Schema>,\n>(\n queryFn: (\n q: InitialQueryBuilder<Context<Schema>>\n ) => QueryBuilder<TResultContext>,\n deps: Array<unknown> = []\n): UseLiveQueryReturn<ResultsFromContext<TResultContext>> {\n const [restart, forceRestart] = useState(0)\n\n const compiledQuery = useMemo(() => {\n const query = queryFn(queryBuilder())\n const compiled = compileQuery(query)\n compiled.start()\n return compiled\n }, [...deps, restart])\n\n const state = useStore(compiledQuery.results.asStoreMap())\n const data = useStore(compiledQuery.results.asStoreArray())\n\n // Clean up on unmount\n useEffect(() => {\n if (compiledQuery.state === `stopped`) {\n forceRestart((count) => {\n return (count += 1)\n })\n }\n\n return () => {\n compiledQuery.stop()\n }\n }, [compiledQuery])\n\n return {\n state,\n data,\n collection: compiledQuery.results,\n }\n}\n"],"names":["useState","useMemo","queryBuilder","compileQuery","useStore","useEffect"],"mappings":";;;;;AAkBO,SAAS,aAGd,SAGA,OAAuB,IACiC;AACxD,QAAM,CAAC,SAAS,YAAY,IAAIA,MAAAA,SAAS,CAAC;AAEpC,QAAA,gBAAgBC,MAAAA,QAAQ,MAAM;AAC5B,UAAA,QAAQ,QAAQC,GAAAA,cAAc;AAC9B,UAAA,WAAWC,gBAAa,KAAK;AACnC,aAAS,MAAM;AACR,WAAA;AAAA,EAAA,GACN,CAAC,GAAG,MAAM,OAAO,CAAC;AAErB,QAAM,QAAQC,WAAA,SAAS,cAAc,QAAQ,YAAY;AACzD,QAAM,OAAOA,WAAA,SAAS,cAAc,QAAQ,cAAc;AAG1DC,QAAAA,UAAU,MAAM;AACV,QAAA,cAAc,UAAU,WAAW;AACrC,mBAAa,CAAC,UAAU;AACtB,eAAQ,SAAS;AAAA,MAAA,CAClB;AAAA,IAAA;AAGH,WAAO,MAAM;AACX,oBAAc,KAAK;AAAA,IACrB;AAAA,EAAA,GACC,CAAC,aAAa,CAAC;AAEX,SAAA;AAAA,IACL;AAAA,IACA;AAAA,IACA,YAAY,cAAc;AAAA,EAC5B;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 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,7 +1,16 @@
1
- import { Collection, Context, InitialQueryBuilder, QueryBuilder, ResultsFromContext, Schema } from '@tanstack/db';
2
- export interface UseLiveQueryReturn<T extends object> {
3
- state: Map<string | number, T>;
4
- data: Array<T>;
5
- collection: Collection<T>;
6
- }
7
- export declare function useLiveQuery<TResultContext extends Context<Schema> = Context<Schema>>(queryFn: (q: InitialQueryBuilder<Context<Schema>>) => QueryBuilder<TResultContext>, deps?: Array<unknown>): UseLiveQueryReturn<ResultsFromContext<TResultContext>>;
1
+ import { Collection, Context, GetResult, InitialQueryBuilder, LiveQueryCollectionConfig, QueryBuilder } from '@tanstack/db';
2
+ export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>, deps?: Array<unknown>): {
3
+ state: Map<string | number, GetResult<TContext>>;
4
+ data: Array<GetResult<TContext>>;
5
+ collection: Collection<GetResult<TContext>, string | number, {}>;
6
+ };
7
+ export declare function useLiveQuery<TContext extends Context>(config: LiveQueryCollectionConfig<TContext>, deps?: Array<unknown>): {
8
+ state: Map<string | number, GetResult<TContext>>;
9
+ data: Array<GetResult<TContext>>;
10
+ collection: Collection<GetResult<TContext>, string | number, {}>;
11
+ };
12
+ export declare function useLiveQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: Collection<TResult, TKey, TUtils>): {
13
+ state: Map<TKey, TResult>;
14
+ data: Array<TResult>;
15
+ collection: Collection<TResult, TKey, TUtils>;
16
+ };
@@ -1,7 +1,16 @@
1
- import { Collection, Context, InitialQueryBuilder, QueryBuilder, ResultsFromContext, Schema } from '@tanstack/db';
2
- export interface UseLiveQueryReturn<T extends object> {
3
- state: Map<string | number, T>;
4
- data: Array<T>;
5
- collection: Collection<T>;
6
- }
7
- export declare function useLiveQuery<TResultContext extends Context<Schema> = Context<Schema>>(queryFn: (q: InitialQueryBuilder<Context<Schema>>) => QueryBuilder<TResultContext>, deps?: Array<unknown>): UseLiveQueryReturn<ResultsFromContext<TResultContext>>;
1
+ import { Collection, Context, GetResult, InitialQueryBuilder, LiveQueryCollectionConfig, QueryBuilder } from '@tanstack/db';
2
+ export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>, deps?: Array<unknown>): {
3
+ state: Map<string | number, GetResult<TContext>>;
4
+ data: Array<GetResult<TContext>>;
5
+ collection: Collection<GetResult<TContext>, string | number, {}>;
6
+ };
7
+ export declare function useLiveQuery<TContext extends Context>(config: LiveQueryCollectionConfig<TContext>, deps?: Array<unknown>): {
8
+ state: Map<string | number, GetResult<TContext>>;
9
+ data: Array<GetResult<TContext>>;
10
+ collection: Collection<GetResult<TContext>, string | number, {}>;
11
+ };
12
+ export declare function useLiveQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: Collection<TResult, TKey, TUtils>): {
13
+ state: Map<TKey, TResult>;
14
+ data: Array<TResult>;
15
+ collection: Collection<TResult, TKey, TUtils>;
16
+ };
@@ -1,30 +1,81 @@
1
- import { useState, useMemo, useEffect } from "react";
2
- import { useStore } from "@tanstack/react-store";
3
- import { queryBuilder, compileQuery } from "@tanstack/db";
4
- function useLiveQuery(queryFn, deps = []) {
5
- const [restart, forceRestart] = useState(0);
6
- const compiledQuery = useMemo(() => {
7
- const query = queryFn(queryBuilder());
8
- const compiled = compileQuery(query);
9
- compiled.start();
10
- return compiled;
11
- }, [...deps, restart]);
12
- const state = useStore(compiledQuery.results.asStoreMap());
13
- const data = useStore(compiledQuery.results.asStoreArray());
14
- useEffect(() => {
15
- if (compiledQuery.state === `stopped`) {
16
- forceRestart((count) => {
17
- return count += 1;
18
- });
1
+ import { useRef, useSyncExternalStore } from "react";
2
+ import { createLiveQueryCollection } from "@tanstack/db";
3
+ function useLiveQuery(configOrQueryOrCollection, deps = []) {
4
+ const isCollection = configOrQueryOrCollection && typeof configOrQueryOrCollection === `object` && typeof configOrQueryOrCollection.subscribeChanges === `function` && typeof configOrQueryOrCollection.startSyncImmediate === `function` && typeof configOrQueryOrCollection.id === `string`;
5
+ const collectionRef = useRef(null);
6
+ const depsRef = useRef(null);
7
+ const configRef = useRef(null);
8
+ const needsNewCollection = !collectionRef.current || isCollection && configRef.current !== configOrQueryOrCollection || !isCollection && (depsRef.current === null || depsRef.current.length !== deps.length || depsRef.current.some((dep, i) => dep !== deps[i]));
9
+ if (needsNewCollection) {
10
+ if (isCollection) {
11
+ configOrQueryOrCollection.startSyncImmediate();
12
+ collectionRef.current = configOrQueryOrCollection;
13
+ configRef.current = configOrQueryOrCollection;
14
+ } else {
15
+ if (typeof configOrQueryOrCollection === `function`) {
16
+ collectionRef.current = createLiveQueryCollection({
17
+ query: configOrQueryOrCollection,
18
+ startSync: true,
19
+ gcTime: 0
20
+ // Live queries created by useLiveQuery are cleaned up immediately
21
+ });
22
+ } else {
23
+ collectionRef.current = createLiveQueryCollection({
24
+ startSync: true,
25
+ gcTime: 0,
26
+ // Live queries created by useLiveQuery are cleaned up immediately
27
+ ...configOrQueryOrCollection
28
+ });
29
+ }
30
+ depsRef.current = [...deps];
19
31
  }
20
- return () => {
21
- compiledQuery.stop();
32
+ }
33
+ const versionRef = useRef(0);
34
+ const snapshotRef = useRef(null);
35
+ if (needsNewCollection) {
36
+ versionRef.current = 0;
37
+ snapshotRef.current = null;
38
+ }
39
+ const subscribeRef = useRef(null);
40
+ if (!subscribeRef.current || needsNewCollection) {
41
+ subscribeRef.current = (onStoreChange) => {
42
+ const unsubscribe = collectionRef.current.subscribeChanges(() => {
43
+ versionRef.current += 1;
44
+ onStoreChange();
45
+ });
46
+ return () => {
47
+ unsubscribe();
48
+ };
49
+ };
50
+ }
51
+ const getSnapshotRef = useRef(null);
52
+ if (!getSnapshotRef.current || needsNewCollection) {
53
+ getSnapshotRef.current = () => {
54
+ const currentVersion = versionRef.current;
55
+ const currentCollection = collectionRef.current;
56
+ if (!snapshotRef.current || snapshotRef.current._version !== currentVersion) {
57
+ snapshotRef.current = {
58
+ get state() {
59
+ return new Map(currentCollection.entries());
60
+ },
61
+ get data() {
62
+ return Array.from(currentCollection.values());
63
+ },
64
+ collection: currentCollection,
65
+ _version: currentVersion
66
+ };
67
+ }
68
+ return snapshotRef.current;
22
69
  };
23
- }, [compiledQuery]);
70
+ }
71
+ const snapshot = useSyncExternalStore(
72
+ subscribeRef.current,
73
+ getSnapshotRef.current
74
+ );
24
75
  return {
25
- state,
26
- data,
27
- collection: compiledQuery.results
76
+ state: snapshot.state,
77
+ data: snapshot.data,
78
+ collection: snapshot.collection
28
79
  };
29
80
  }
30
81
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"useLiveQuery.js","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from \"react\"\nimport { useStore } from \"@tanstack/react-store\"\nimport { compileQuery, queryBuilder } from \"@tanstack/db\"\nimport type {\n Collection,\n Context,\n InitialQueryBuilder,\n QueryBuilder,\n ResultsFromContext,\n Schema,\n} from \"@tanstack/db\"\n\nexport interface UseLiveQueryReturn<T extends object> {\n state: Map<string | number, T>\n data: Array<T>\n collection: Collection<T>\n}\n\nexport function useLiveQuery<\n TResultContext extends Context<Schema> = Context<Schema>,\n>(\n queryFn: (\n q: InitialQueryBuilder<Context<Schema>>\n ) => QueryBuilder<TResultContext>,\n deps: Array<unknown> = []\n): UseLiveQueryReturn<ResultsFromContext<TResultContext>> {\n const [restart, forceRestart] = useState(0)\n\n const compiledQuery = useMemo(() => {\n const query = queryFn(queryBuilder())\n const compiled = compileQuery(query)\n compiled.start()\n return compiled\n }, [...deps, restart])\n\n const state = useStore(compiledQuery.results.asStoreMap())\n const data = useStore(compiledQuery.results.asStoreArray())\n\n // Clean up on unmount\n useEffect(() => {\n if (compiledQuery.state === `stopped`) {\n forceRestart((count) => {\n return (count += 1)\n })\n }\n\n return () => {\n compiledQuery.stop()\n }\n }, [compiledQuery])\n\n return {\n state,\n data,\n collection: compiledQuery.results,\n }\n}\n"],"names":[],"mappings":";;;AAkBO,SAAS,aAGd,SAGA,OAAuB,IACiC;AACxD,QAAM,CAAC,SAAS,YAAY,IAAI,SAAS,CAAC;AAEpC,QAAA,gBAAgB,QAAQ,MAAM;AAC5B,UAAA,QAAQ,QAAQ,cAAc;AAC9B,UAAA,WAAW,aAAa,KAAK;AACnC,aAAS,MAAM;AACR,WAAA;AAAA,EAAA,GACN,CAAC,GAAG,MAAM,OAAO,CAAC;AAErB,QAAM,QAAQ,SAAS,cAAc,QAAQ,YAAY;AACzD,QAAM,OAAO,SAAS,cAAc,QAAQ,cAAc;AAG1D,YAAU,MAAM;AACV,QAAA,cAAc,UAAU,WAAW;AACrC,mBAAa,CAAC,UAAU;AACtB,eAAQ,SAAS;AAAA,MAAA,CAClB;AAAA,IAAA;AAGH,WAAO,MAAM;AACX,oBAAc,KAAK;AAAA,IACrB;AAAA,EAAA,GACC,CAAC,aAAa,CAAC;AAEX,SAAA;AAAA,IACL;AAAA,IACA;AAAA,IACA,YAAY,cAAc;AAAA,EAC5B;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 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;"}
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.14",
4
+ "version": "0.0.16",
5
5
  "author": "Kyle Mathews",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -16,9 +16,8 @@
16
16
  "typescript"
17
17
  ],
18
18
  "dependencies": {
19
- "@tanstack/react-store": "^0.7.0",
20
19
  "use-sync-external-store": "^1.2.0",
21
- "@tanstack/db": "0.0.14"
20
+ "@tanstack/db": "0.0.16"
22
21
  },
23
22
  "devDependencies": {
24
23
  "@electric-sql/client": "1.0.0",
@@ -1,57 +1,175 @@
1
- import { useEffect, useMemo, useState } from "react"
2
- import { useStore } from "@tanstack/react-store"
3
- import { compileQuery, queryBuilder } from "@tanstack/db"
1
+ import { useRef, useSyncExternalStore } from "react"
2
+ import { createLiveQueryCollection } from "@tanstack/db"
4
3
  import type {
5
4
  Collection,
6
5
  Context,
6
+ GetResult,
7
7
  InitialQueryBuilder,
8
+ LiveQueryCollectionConfig,
8
9
  QueryBuilder,
9
- ResultsFromContext,
10
- Schema,
11
10
  } from "@tanstack/db"
12
11
 
13
- export interface UseLiveQueryReturn<T extends object> {
14
- state: Map<string | number, T>
15
- data: Array<T>
16
- collection: Collection<T>
12
+ // Overload 1: Accept just the query function
13
+ export function useLiveQuery<TContext extends Context>(
14
+ queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,
15
+ deps?: Array<unknown>
16
+ ): {
17
+ state: Map<string | number, GetResult<TContext>>
18
+ data: Array<GetResult<TContext>>
19
+ collection: Collection<GetResult<TContext>, string | number, {}>
17
20
  }
18
21
 
22
+ // Overload 2: Accept config object
23
+ export function useLiveQuery<TContext extends Context>(
24
+ config: LiveQueryCollectionConfig<TContext>,
25
+ deps?: Array<unknown>
26
+ ): {
27
+ state: Map<string | number, GetResult<TContext>>
28
+ data: Array<GetResult<TContext>>
29
+ collection: Collection<GetResult<TContext>, string | number, {}>
30
+ }
31
+
32
+ // Overload 3: Accept pre-created live query collection
19
33
  export function useLiveQuery<
20
- TResultContext extends Context<Schema> = Context<Schema>,
34
+ TResult extends object,
35
+ TKey extends string | number,
36
+ TUtils extends Record<string, any>,
21
37
  >(
22
- queryFn: (
23
- q: InitialQueryBuilder<Context<Schema>>
24
- ) => QueryBuilder<TResultContext>,
38
+ liveQueryCollection: Collection<TResult, TKey, TUtils>
39
+ ): {
40
+ state: Map<TKey, TResult>
41
+ data: Array<TResult>
42
+ collection: Collection<TResult, TKey, TUtils>
43
+ }
44
+
45
+ // Implementation - use function overloads to infer the actual collection type
46
+ export function useLiveQuery(
47
+ configOrQueryOrCollection: any,
25
48
  deps: Array<unknown> = []
26
- ): UseLiveQueryReturn<ResultsFromContext<TResultContext>> {
27
- const [restart, forceRestart] = useState(0)
28
-
29
- const compiledQuery = useMemo(() => {
30
- const query = queryFn(queryBuilder())
31
- const compiled = compileQuery(query)
32
- compiled.start()
33
- return compiled
34
- }, [...deps, restart])
35
-
36
- const state = useStore(compiledQuery.results.asStoreMap())
37
- const data = useStore(compiledQuery.results.asStoreArray())
38
-
39
- // Clean up on unmount
40
- useEffect(() => {
41
- if (compiledQuery.state === `stopped`) {
42
- forceRestart((count) => {
43
- return (count += 1)
49
+ ) {
50
+ // Check if it's already a collection by checking for specific collection methods
51
+ const isCollection =
52
+ configOrQueryOrCollection &&
53
+ typeof configOrQueryOrCollection === `object` &&
54
+ typeof configOrQueryOrCollection.subscribeChanges === `function` &&
55
+ typeof configOrQueryOrCollection.startSyncImmediate === `function` &&
56
+ typeof configOrQueryOrCollection.id === `string`
57
+
58
+ // Use refs to cache collection and track dependencies
59
+ const collectionRef = useRef<any>(null)
60
+ const depsRef = useRef<Array<unknown> | null>(null)
61
+ const configRef = useRef<any>(null)
62
+
63
+ // Check if we need to create/recreate the collection
64
+ const needsNewCollection =
65
+ !collectionRef.current ||
66
+ (isCollection && configRef.current !== configOrQueryOrCollection) ||
67
+ (!isCollection &&
68
+ (depsRef.current === null ||
69
+ depsRef.current.length !== deps.length ||
70
+ depsRef.current.some((dep, i) => dep !== deps[i])))
71
+
72
+ if (needsNewCollection) {
73
+ if (isCollection) {
74
+ // It's already a collection, ensure sync is started for React hooks
75
+ configOrQueryOrCollection.startSyncImmediate()
76
+ collectionRef.current = configOrQueryOrCollection
77
+ configRef.current = configOrQueryOrCollection
78
+ } else {
79
+ // Original logic for creating collections
80
+ // Ensure we always start sync for React hooks
81
+ if (typeof configOrQueryOrCollection === `function`) {
82
+ collectionRef.current = createLiveQueryCollection({
83
+ query: configOrQueryOrCollection,
84
+ startSync: true,
85
+ gcTime: 0, // Live queries created by useLiveQuery are cleaned up immediately
86
+ })
87
+ } else {
88
+ collectionRef.current = createLiveQueryCollection({
89
+ startSync: true,
90
+ gcTime: 0, // Live queries created by useLiveQuery are cleaned up immediately
91
+ ...configOrQueryOrCollection,
92
+ })
93
+ }
94
+ depsRef.current = [...deps]
95
+ }
96
+ }
97
+
98
+ // Use refs to track version and memoized snapshot
99
+ const versionRef = useRef(0)
100
+ const snapshotRef = useRef<{
101
+ state: Map<any, any>
102
+ data: Array<any>
103
+ collection: Collection<any, any, any>
104
+ _version: number
105
+ } | null>(null)
106
+
107
+ // Reset refs when collection changes
108
+ if (needsNewCollection) {
109
+ versionRef.current = 0
110
+ snapshotRef.current = null
111
+ }
112
+
113
+ // Create stable subscribe function using ref
114
+ const subscribeRef = useRef<
115
+ ((onStoreChange: () => void) => () => void) | null
116
+ >(null)
117
+ if (!subscribeRef.current || needsNewCollection) {
118
+ subscribeRef.current = (onStoreChange: () => void) => {
119
+ const unsubscribe = collectionRef.current!.subscribeChanges(() => {
120
+ versionRef.current += 1
121
+ onStoreChange()
44
122
  })
123
+ return () => {
124
+ unsubscribe()
125
+ }
45
126
  }
127
+ }
46
128
 
47
- return () => {
48
- compiledQuery.stop()
129
+ // Create stable getSnapshot function using ref
130
+ const getSnapshotRef = useRef<
131
+ | (() => {
132
+ state: Map<any, any>
133
+ data: Array<any>
134
+ collection: Collection<any, any, any>
135
+ })
136
+ | null
137
+ >(null)
138
+ if (!getSnapshotRef.current || needsNewCollection) {
139
+ getSnapshotRef.current = () => {
140
+ const currentVersion = versionRef.current
141
+ const currentCollection = collectionRef.current!
142
+
143
+ // If we don't have a snapshot or the version changed, create a new one
144
+ if (
145
+ !snapshotRef.current ||
146
+ snapshotRef.current._version !== currentVersion
147
+ ) {
148
+ snapshotRef.current = {
149
+ get state() {
150
+ return new Map(currentCollection.entries())
151
+ },
152
+ get data() {
153
+ return Array.from(currentCollection.values())
154
+ },
155
+ collection: currentCollection,
156
+ _version: currentVersion,
157
+ }
158
+ }
159
+
160
+ return snapshotRef.current
49
161
  }
50
- }, [compiledQuery])
162
+ }
163
+
164
+ // Use useSyncExternalStore to subscribe to collection changes
165
+ const snapshot = useSyncExternalStore(
166
+ subscribeRef.current,
167
+ getSnapshotRef.current
168
+ )
51
169
 
52
170
  return {
53
- state,
54
- data,
55
- collection: compiledQuery.results,
171
+ state: snapshot.state,
172
+ data: snapshot.data,
173
+ collection: snapshot.collection,
56
174
  }
57
175
  }