@tanstack/react-db 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/cjs/DbProvider.cjs +23 -0
- package/dist/cjs/DbProvider.cjs.map +1 -0
- package/dist/cjs/DbProvider.d.cts +9 -0
- package/dist/cjs/HydrationBoundary.cjs +16 -0
- package/dist/cjs/HydrationBoundary.cjs.map +1 -0
- package/dist/cjs/HydrationBoundary.d.cts +7 -0
- package/dist/cjs/index.cjs +6 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +4 -1
- package/dist/cjs/live-query-internals.cjs +19 -0
- package/dist/cjs/live-query-internals.cjs.map +1 -0
- package/dist/cjs/live-query-internals.d.cts +9 -0
- package/dist/cjs/useLiveInfiniteQuery.cjs +76 -8
- package/dist/cjs/useLiveInfiniteQuery.cjs.map +1 -1
- package/dist/cjs/useLiveInfiniteQuery.d.cts +12 -58
- package/dist/cjs/useLiveQuery.cjs +263 -41
- package/dist/cjs/useLiveQuery.cjs.map +1 -1
- package/dist/cjs/useLiveQuery.d.cts +79 -33
- package/dist/cjs/useLiveSuspenseQuery.cjs +19 -5
- package/dist/cjs/useLiveSuspenseQuery.cjs.map +1 -1
- package/dist/cjs/useLiveSuspenseQuery.d.cts +22 -19
- package/dist/esm/DbProvider.d.ts +9 -0
- package/dist/esm/DbProvider.js +23 -0
- package/dist/esm/DbProvider.js.map +1 -0
- package/dist/esm/HydrationBoundary.d.ts +7 -0
- package/dist/esm/HydrationBoundary.js +16 -0
- package/dist/esm/HydrationBoundary.js.map +1 -0
- package/dist/esm/index.d.ts +4 -1
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/live-query-internals.d.ts +9 -0
- package/dist/esm/live-query-internals.js +19 -0
- package/dist/esm/live-query-internals.js.map +1 -0
- package/dist/esm/useLiveInfiniteQuery.d.ts +12 -58
- package/dist/esm/useLiveInfiniteQuery.js +76 -8
- package/dist/esm/useLiveInfiniteQuery.js.map +1 -1
- package/dist/esm/useLiveQuery.d.ts +79 -33
- package/dist/esm/useLiveQuery.js +265 -43
- package/dist/esm/useLiveQuery.js.map +1 -1
- package/dist/esm/useLiveSuspenseQuery.d.ts +22 -19
- package/dist/esm/useLiveSuspenseQuery.js +19 -5
- package/dist/esm/useLiveSuspenseQuery.js.map +1 -1
- package/package.json +2 -2
- package/skills/react-db/SKILL.md +107 -84
- package/src/DbProvider.tsx +32 -0
- package/src/HydrationBoundary.tsx +25 -0
- package/src/index.ts +8 -1
- package/src/live-query-internals.ts +36 -0
- package/src/useLiveInfiniteQuery.ts +119 -65
- package/src/useLiveQuery.ts +441 -92
- package/src/useLiveSuspenseQuery.ts +56 -29
package/README.md
CHANGED
|
@@ -18,3 +18,15 @@
|
|
|
18
18
|
# @tanstack/react-db
|
|
19
19
|
|
|
20
20
|
React hooks for TanStack DB. See [TanStack/db](https://github.com/TanStack/db) for more details.
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { useLiveQuery } from '@tanstack/react-db'
|
|
24
|
+
|
|
25
|
+
function TodoList() {
|
|
26
|
+
const { data: todos } = useLiveQuery({
|
|
27
|
+
query: (q) => q.from({ todo: todoCollection }),
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
return todos.map((todo) => <div key={todo.id}>{todo.text}</div>)
|
|
31
|
+
}
|
|
32
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
4
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
5
|
+
const react = require("react");
|
|
6
|
+
const DbContext = react.createContext(void 0);
|
|
7
|
+
function DbProvider(props) {
|
|
8
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DbContext.Provider, { value: props.client, children: props.children });
|
|
9
|
+
}
|
|
10
|
+
function useDbClient() {
|
|
11
|
+
const client = react.useContext(DbContext);
|
|
12
|
+
if (!client) {
|
|
13
|
+
throw new Error(`useDbClient must be used within a DbProvider.`);
|
|
14
|
+
}
|
|
15
|
+
return client;
|
|
16
|
+
}
|
|
17
|
+
function useOptionalDbClient() {
|
|
18
|
+
return react.useContext(DbContext);
|
|
19
|
+
}
|
|
20
|
+
exports.DbProvider = DbProvider;
|
|
21
|
+
exports.useDbClient = useDbClient;
|
|
22
|
+
exports.useOptionalDbClient = useOptionalDbClient;
|
|
23
|
+
//# sourceMappingURL=DbProvider.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DbProvider.cjs","sources":["../../src/DbProvider.tsx"],"sourcesContent":["'use client'\n\nimport { createContext, useContext } from 'react'\nimport type { DbClient } from '@tanstack/db'\nimport type { ReactNode } from 'react'\n\nconst DbContext = createContext<DbClient | undefined>(undefined)\n\nexport type DbProviderProps = {\n client: DbClient\n children?: ReactNode\n}\n\nexport function DbProvider(props: DbProviderProps) {\n return (\n <DbContext.Provider value={props.client}>\n {props.children}\n </DbContext.Provider>\n )\n}\n\nexport function useDbClient(): DbClient {\n const client = useContext(DbContext)\n if (!client) {\n throw new Error(`useDbClient must be used within a DbProvider.`)\n }\n return client\n}\n\nexport function useOptionalDbClient(): DbClient | undefined {\n return useContext(DbContext)\n}\n"],"names":[],"mappings":";;;;;AAMA;AAOO;AACL;AAKF;AAEO;AACL;AACA;AACE;AAA+D;AAEjE;AACF;AAEO;AACL;AACF;;;;"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DbClient } from '@tanstack/db';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
export type DbProviderProps = {
|
|
4
|
+
client: DbClient;
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
};
|
|
7
|
+
export declare function DbProvider(props: DbProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export declare function useDbClient(): DbClient;
|
|
9
|
+
export declare function useOptionalDbClient(): DbClient | undefined;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
4
|
+
const react = require("react");
|
|
5
|
+
const DbProvider = require("./DbProvider.cjs");
|
|
6
|
+
function HydrationBoundary({ state, children }) {
|
|
7
|
+
const client = DbProvider.useDbClient();
|
|
8
|
+
const hydrated = react.useRef(void 0);
|
|
9
|
+
if (hydrated.current?.client !== client || hydrated.current.state !== state) {
|
|
10
|
+
client.hydrate(state);
|
|
11
|
+
hydrated.current = { client, state };
|
|
12
|
+
}
|
|
13
|
+
return children;
|
|
14
|
+
}
|
|
15
|
+
exports.HydrationBoundary = HydrationBoundary;
|
|
16
|
+
//# sourceMappingURL=HydrationBoundary.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HydrationBoundary.cjs","sources":["../../src/HydrationBoundary.tsx"],"sourcesContent":["'use client'\n\nimport { useRef } from 'react'\nimport { useDbClient } from './DbProvider'\nimport type { DehydratedDbState } from '@tanstack/db'\nimport type { ReactNode } from 'react'\n\nexport type HydrationBoundaryProps = {\n state: DehydratedDbState\n children?: ReactNode\n}\n\nexport function HydrationBoundary({ state, children }: HydrationBoundaryProps) {\n const client = useDbClient()\n const hydrated = useRef<\n { client: typeof client; state: DehydratedDbState } | undefined\n >(undefined)\n\n if (hydrated.current?.client !== client || hydrated.current.state !== state) {\n client.hydrate(state)\n hydrated.current = { client, state }\n }\n\n return children\n}\n"],"names":[],"mappings":";;;;;AAYO;AACL;AACA;AAIA;AACE;AACA;AAA6B;AAG/B;AACF;;"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { DehydratedDbState } from '@tanstack/db';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
export type HydrationBoundaryProps = {
|
|
4
|
+
state: DehydratedDbState;
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
};
|
|
7
|
+
export declare function HydrationBoundary({ state, children }: HydrationBoundaryProps): ReactNode;
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const useLiveQuery = require("./useLiveQuery.cjs");
|
|
4
|
+
const DbProvider = require("./DbProvider.cjs");
|
|
5
|
+
const HydrationBoundary = require("./HydrationBoundary.cjs");
|
|
4
6
|
const useLiveSuspenseQuery = require("./useLiveSuspenseQuery.cjs");
|
|
5
7
|
const usePacedMutations = require("./usePacedMutations.cjs");
|
|
6
8
|
const useLiveInfiniteQuery = require("./useLiveInfiniteQuery.cjs");
|
|
7
9
|
const useLiveQueryEffect = require("./useLiveQueryEffect.cjs");
|
|
8
10
|
const db = require("@tanstack/db");
|
|
9
11
|
exports.useLiveQuery = useLiveQuery.useLiveQuery;
|
|
12
|
+
exports.DbProvider = DbProvider.DbProvider;
|
|
13
|
+
exports.useDbClient = DbProvider.useDbClient;
|
|
14
|
+
exports.useOptionalDbClient = DbProvider.useOptionalDbClient;
|
|
15
|
+
exports.HydrationBoundary = HydrationBoundary.HydrationBoundary;
|
|
10
16
|
exports.useLiveSuspenseQuery = useLiveSuspenseQuery.useLiveSuspenseQuery;
|
|
11
17
|
exports.usePacedMutations = usePacedMutations.usePacedMutations;
|
|
12
18
|
exports.useLiveInfiniteQuery = useLiveInfiniteQuery.useLiveInfiniteQuery;
|
package/dist/cjs/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/cjs/index.d.cts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { useLiveQuery } from './useLiveQuery.cjs';
|
|
2
|
+
export type { LiveQueryKey, UseLiveQueryConfig, UseLiveQueryStatus, } from './useLiveQuery.cjs';
|
|
3
|
+
export * from './DbProvider.cjs';
|
|
4
|
+
export * from './HydrationBoundary.cjs';
|
|
2
5
|
export * from './useLiveSuspenseQuery.cjs';
|
|
3
6
|
export * from './usePacedMutations.cjs';
|
|
4
7
|
export * from './useLiveInfiniteQuery.cjs';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const liveQueryResultInfo = /* @__PURE__ */ Symbol(`liveQueryResultInfo`);
|
|
4
|
+
function setLiveQueryResultInfo(result, info) {
|
|
5
|
+
Object.defineProperty(result, liveQueryResultInfo, {
|
|
6
|
+
configurable: true,
|
|
7
|
+
value: info
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
function getLiveQueryResultInfo(result) {
|
|
11
|
+
const info = result[liveQueryResultInfo];
|
|
12
|
+
if (!info) {
|
|
13
|
+
throw new Error(`Missing internal live query result information.`);
|
|
14
|
+
}
|
|
15
|
+
return info;
|
|
16
|
+
}
|
|
17
|
+
exports.getLiveQueryResultInfo = getLiveQueryResultInfo;
|
|
18
|
+
exports.setLiveQueryResultInfo = setLiveQueryResultInfo;
|
|
19
|
+
//# sourceMappingURL=live-query-internals.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live-query-internals.cjs","sources":["../../src/live-query-internals.ts"],"sourcesContent":["import type {\n DbClient,\n LiveQueryObserver,\n UnhashableQueryIRError,\n} from '@tanstack/db'\n\nconst liveQueryResultInfo = Symbol(`liveQueryResultInfo`)\n\nexport type LiveQueryResultInfo = {\n client: DbClient | undefined\n queryHash: string | undefined\n identityError: UnhashableQueryIRError | undefined\n observer: LiveQueryObserver<object, string | number>\n}\n\ntype ResultWithInfo = {\n [liveQueryResultInfo]?: LiveQueryResultInfo\n}\n\nexport function setLiveQueryResultInfo(\n result: object,\n info: LiveQueryResultInfo,\n): void {\n Object.defineProperty(result, liveQueryResultInfo, {\n configurable: true,\n value: info,\n })\n}\n\nexport function getLiveQueryResultInfo(result: object): LiveQueryResultInfo {\n const info = (result as ResultWithInfo)[liveQueryResultInfo]\n if (!info) {\n throw new Error(`Missing internal live query result information.`)\n }\n return info\n}\n"],"names":[],"mappings":";;AAMA,MAAM,6CAA6B,qBAAqB;AAajD,SAAS,uBACd,QACA,MACM;AACN,SAAO,eAAe,QAAQ,qBAAqB;AAAA,IACjD,cAAc;AAAA,IACd,OAAO;AAAA,EAAA,CACR;AACH;AAEO,SAAS,uBAAuB,QAAqC;AAC1E,QAAM,OAAQ,OAA0B,mBAAmB;AAC3D,MAAI,CAAC,MAAM;AACT,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AACA,SAAO;AACT;;;"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DbClient, LiveQueryObserver, UnhashableQueryIRError } from '@tanstack/db';
|
|
2
|
+
export type LiveQueryResultInfo = {
|
|
3
|
+
client: DbClient | undefined;
|
|
4
|
+
queryHash: string | undefined;
|
|
5
|
+
identityError: UnhashableQueryIRError | undefined;
|
|
6
|
+
observer: LiveQueryObserver<object, string | number>;
|
|
7
|
+
};
|
|
8
|
+
export declare function setLiveQueryResultInfo(result: object, info: LiveQueryResultInfo): void;
|
|
9
|
+
export declare function getLiveQueryResultInfo(result: object): LiveQueryResultInfo;
|
|
@@ -1,21 +1,70 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
"use strict";
|
|
2
3
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
4
|
const react = require("react");
|
|
4
5
|
const db = require("@tanstack/db");
|
|
6
|
+
const DbProvider = require("./DbProvider.cjs");
|
|
7
|
+
const useLiveQuery = require("./useLiveQuery.cjs");
|
|
5
8
|
const DEFAULT_GC_TIME_MS = 1;
|
|
6
|
-
|
|
9
|
+
const unpreparedQueryValue = /* @__PURE__ */ Symbol(`unpreparedQueryValue`);
|
|
10
|
+
function useLiveInfiniteQuery(queryFnOrCollection, config, deps) {
|
|
7
11
|
const pageSize = db.normalizeLiveQueryWindowPageSize(config.pageSize);
|
|
8
12
|
const initialPageParam = config.initialPageParam ?? 0;
|
|
13
|
+
const contextDbClient = DbProvider.useOptionalDbClient();
|
|
14
|
+
const dbClient = config.client ?? contextDbClient;
|
|
9
15
|
const inputIsCollection = db.getLiveQueryWindowInputKind(queryFnOrCollection) === `collection`;
|
|
10
16
|
const committedRef = react.useRef(null);
|
|
11
17
|
const committed = committedRef.current;
|
|
12
18
|
const inputKind = inputIsCollection ? `collection` : `query`;
|
|
19
|
+
const derivedIdentityProfilerRef = react.useRef({
|
|
20
|
+
renderCount: 0,
|
|
21
|
+
totalMs: 0,
|
|
22
|
+
maxMs: 0,
|
|
23
|
+
warned: false
|
|
24
|
+
});
|
|
25
|
+
const legacyUnhashableIdentityRef = react.useRef([
|
|
26
|
+
`legacy-unhashable`
|
|
27
|
+
]);
|
|
28
|
+
const deferredCollections = /* @__PURE__ */ new Set();
|
|
29
|
+
let preparedQueryValue = unpreparedQueryValue;
|
|
30
|
+
let identityDeps = [];
|
|
31
|
+
let identityMode = `collection`;
|
|
32
|
+
if (!inputIsCollection) {
|
|
33
|
+
if (config.queryKey !== void 0) {
|
|
34
|
+
identityMode = `queryKey`;
|
|
35
|
+
identityDeps = config.queryKey;
|
|
36
|
+
} else if (deps !== void 0) {
|
|
37
|
+
identityMode = `legacyDeps`;
|
|
38
|
+
identityDeps = deps;
|
|
39
|
+
useLiveQuery.warnDeprecatedDepsArray(`useLiveInfiniteQuery`);
|
|
40
|
+
} else if (committed?.identityMode === `derived` && committed.inputQuery === queryFnOrCollection && committed.client === dbClient) {
|
|
41
|
+
identityMode = `derived`;
|
|
42
|
+
identityDeps = committed.dependencies ?? [];
|
|
43
|
+
} else {
|
|
44
|
+
identityMode = `derived`;
|
|
45
|
+
const preparation = useLiveQuery.prepareDerivedQuery(
|
|
46
|
+
queryFnOrCollection,
|
|
47
|
+
dbClient,
|
|
48
|
+
derivedIdentityProfilerRef.current,
|
|
49
|
+
deferredCollections
|
|
50
|
+
);
|
|
51
|
+
preparedQueryValue = preparation.value;
|
|
52
|
+
if (preparation.status === `hashable`) {
|
|
53
|
+
identityDeps = preparation.identityDeps;
|
|
54
|
+
} else {
|
|
55
|
+
useLiveQuery.warnUnhashableDerivedIdentity(preparation.error);
|
|
56
|
+
identityDeps = legacyUnhashableIdentityRef.current;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const usesLegacyDeps = !inputIsCollection && config.queryKey === void 0 && deps !== void 0;
|
|
13
61
|
const dependencyComparison = db.compareLiveQueryWindowDependencies(
|
|
14
62
|
committed?.dependencies,
|
|
15
|
-
|
|
63
|
+
identityDeps
|
|
16
64
|
);
|
|
17
|
-
const
|
|
18
|
-
const
|
|
65
|
+
const sameClient = committed?.client === dbClient;
|
|
66
|
+
const dependenciesChanged = !inputIsCollection && (!sameClient || (usesLegacyDeps ? dependencyComparison.changed : !dependencyComparison.structurallyEqual));
|
|
67
|
+
const dependenciesStructurallyEqual = usesLegacyDeps && sameClient && dependencyComparison.structurallyEqual;
|
|
19
68
|
const needsNewCollection = committed === null || committed.inputKind !== inputKind || inputIsCollection && committed.inputCollection !== queryFnOrCollection || dependenciesChanged;
|
|
20
69
|
const pageShapeChanged = committed === null || committed.pageSize !== pageSize || committed.initialPageParam !== initialPageParam;
|
|
21
70
|
const needsNewController = committed === null || needsNewCollection || pageShapeChanged;
|
|
@@ -24,7 +73,18 @@ function useLiveInfiniteQuery(queryFnOrCollection, config, deps = []) {
|
|
|
24
73
|
let collection = committed?.collection;
|
|
25
74
|
let warning = null;
|
|
26
75
|
if (needsNewCollection) {
|
|
27
|
-
|
|
76
|
+
let inputValue = queryFnOrCollection;
|
|
77
|
+
if (!inputIsCollection) {
|
|
78
|
+
if (preparedQueryValue === unpreparedQueryValue) {
|
|
79
|
+
preparedQueryValue = useLiveQuery.prepareQueryValue(
|
|
80
|
+
queryFnOrCollection,
|
|
81
|
+
dbClient,
|
|
82
|
+
deferredCollections
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
inputValue = () => preparedQueryValue;
|
|
86
|
+
}
|
|
87
|
+
const input = db.resolveLiveQueryWindowInput(inputValue);
|
|
28
88
|
if (input.kind === `collection`) {
|
|
29
89
|
collection = input.collection;
|
|
30
90
|
} else {
|
|
@@ -59,7 +119,10 @@ function useLiveInfiniteQuery(queryFnOrCollection, config, deps = []) {
|
|
|
59
119
|
renderState = {
|
|
60
120
|
inputKind,
|
|
61
121
|
inputCollection: inputIsCollection ? collection : null,
|
|
62
|
-
|
|
122
|
+
inputQuery: inputIsCollection ? null : queryFnOrCollection,
|
|
123
|
+
client: dbClient,
|
|
124
|
+
identityMode,
|
|
125
|
+
dependencies: inputIsCollection ? null : [...identityDeps],
|
|
63
126
|
pageSize,
|
|
64
127
|
initialPageParam,
|
|
65
128
|
collection,
|
|
@@ -69,7 +132,8 @@ function useLiveInfiniteQuery(queryFnOrCollection, config, deps = []) {
|
|
|
69
132
|
initialPageCount
|
|
70
133
|
}),
|
|
71
134
|
warning,
|
|
72
|
-
warned: false
|
|
135
|
+
warned: false,
|
|
136
|
+
deferredCollections
|
|
73
137
|
};
|
|
74
138
|
}
|
|
75
139
|
const currentRenderState = renderState;
|
|
@@ -82,12 +146,16 @@ function useLiveInfiniteQuery(queryFnOrCollection, config, deps = []) {
|
|
|
82
146
|
currentRenderState.warned = true;
|
|
83
147
|
console.warn(currentRenderState.warning);
|
|
84
148
|
}
|
|
149
|
+
for (const collection of currentRenderState.deferredCollections) {
|
|
150
|
+
collection._resumeSyncStart();
|
|
151
|
+
}
|
|
152
|
+
currentRenderState.deferredCollections.clear();
|
|
85
153
|
return unsubscribe;
|
|
86
154
|
},
|
|
87
155
|
[controller, currentRenderState]
|
|
88
156
|
);
|
|
89
157
|
const getSnapshot = react.useCallback(() => controller.getSnapshot(), [controller]);
|
|
90
|
-
const snapshot = react.useSyncExternalStore(subscribe, getSnapshot);
|
|
158
|
+
const snapshot = react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
91
159
|
const fetchNextPage = react.useCallback(
|
|
92
160
|
() => db.fetchNextLiveQueryWindowPage(controller),
|
|
93
161
|
[controller]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLiveInfiniteQuery.cjs","sources":["../../src/useLiveInfiniteQuery.ts"],"sourcesContent":["import { useCallback, useRef, useSyncExternalStore } from 'react'\nimport {\n assertLiveQueryWindowManyResult,\n compareLiveQueryWindowDependencies,\n createLiveQueryCollection,\n createLiveQueryWindowController,\n fetchNextLiveQueryWindowPage,\n getLiveQueryWindowCollectionWarning,\n getLiveQueryWindowInputKind,\n normalizeLiveQueryWindowPageSize,\n resolveLiveQueryWindowInput,\n shouldPreserveLiveQueryWindowPageCount,\n} from '@tanstack/db'\n// Type-only: used in `ReturnType<typeof useLiveQuery>` in UseLiveInfiniteQueryReturn.\nimport type { useLiveQuery } from './useLiveQuery'\nimport type {\n Collection,\n Context,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryWindowController,\n NonSingleResult,\n QueryBuilder,\n} from '@tanstack/db'\n\n// Live queries created here are cleaned up immediately (0 disables GC).\nconst DEFAULT_GC_TIME_MS = 1\n\nexport type UseLiveInfiniteQueryConfig<TContext extends Context> = {\n pageSize?: number\n initialPageParam?: number\n /**\n * @deprecated This callback is not used by the current implementation.\n * Pagination is determined internally via a peek-ahead strategy.\n * Provided for API compatibility with TanStack Query conventions.\n */\n getNextPageParam?: (\n lastPage: Array<InferResultType<TContext>[number]>,\n allPages: Array<Array<InferResultType<TContext>[number]>>,\n lastPageParam: number,\n allPageParams: Array<number>,\n ) => number | undefined\n}\n\nexport type UseLiveInfiniteQueryReturn<TContext extends Context> = Omit<\n ReturnType<typeof useLiveQuery<TContext>>,\n `data`\n> & {\n data: InferResultType<TContext>\n pages: Array<Array<InferResultType<TContext>[number]>>\n pageParams: Array<number>\n fetchNextPage: () => Promise<void>\n hasNextPage: boolean\n isFetchingNextPage: boolean\n error: unknown\n}\n\ntype EnabledLiveQueryReturn<TContext extends Context> = ReturnType<\n typeof useLiveQuery<TContext>\n>\n\ntype InfiniteQueryRenderState = {\n inputKind: `collection` | `query`\n inputCollection: Collection<any, any, any> | null\n dependencies: Array<unknown> | null\n pageSize: number\n initialPageParam: number\n collection: Collection<any, any, any>\n controller: LiveQueryWindowController<any, any>\n warning: string | null\n warned: boolean\n}\n\n/**\n * Create an infinite query using a query function with live updates\n *\n * Uses `utils.setWindow()` to dynamically adjust the limit/offset window\n * without recreating the live query collection on each page change.\n *\n * @param queryFn - Query function that defines what data to fetch. Must include `.orderBy()` for setWindow to work.\n * @param config - Configuration including pageSize and getNextPageParam\n * @param deps - Array of dependencies that trigger query re-execution when changed\n * @returns Object with pages, data, and pagination controls\n *\n * @example\n * // Basic infinite query\n * const { data, pages, fetchNextPage, hasNextPage } = useLiveInfiniteQuery(\n * (q) => q\n * .from({ posts: postsCollection })\n * .orderBy(({ posts }) => posts.createdAt, 'desc')\n * .select(({ posts }) => ({\n * id: posts.id,\n * title: posts.title\n * })),\n * {\n * pageSize: 20,\n * getNextPageParam: (lastPage, allPages) =>\n * lastPage.length === 20 ? allPages.length : undefined\n * }\n * )\n *\n * @example\n * // With dependencies\n * const { pages, fetchNextPage } = useLiveInfiniteQuery(\n * (q) => q\n * .from({ posts: postsCollection })\n * .where(({ posts }) => eq(posts.category, category))\n * .orderBy(({ posts }) => posts.createdAt, 'desc'),\n * {\n * pageSize: 10,\n * getNextPageParam: (lastPage) =>\n * lastPage.length === 10 ? lastPage.length : undefined\n * },\n * [category]\n * )\n *\n * @example\n * // Router loader pattern with pre-created collection\n * // In loader:\n * const postsQuery = createLiveQueryCollection({\n * query: (q) => q\n * .from({ posts: postsCollection })\n * .orderBy(({ posts }) => posts.createdAt, 'desc')\n * .limit(20)\n * })\n * await postsQuery.preload()\n * return { postsQuery }\n *\n * // In component:\n * const { postsQuery } = useLoaderData()\n * const { data, fetchNextPage, hasNextPage } = useLiveInfiniteQuery(\n * postsQuery,\n * {\n * pageSize: 20,\n * getNextPageParam: (lastPage) => lastPage.length === 20 ? lastPage.length : undefined\n * }\n * )\n */\n\n// Overload for pre-created collection (non-single result)\nexport function useLiveInfiniteQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult,\n config: UseLiveInfiniteQueryConfig<any>,\n): UseLiveInfiniteQueryReturn<any>\n\n// Overload for query function\nexport function useLiveInfiniteQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n config: UseLiveInfiniteQueryConfig<TContext>,\n deps?: Array<unknown>,\n): UseLiveInfiniteQueryReturn<TContext>\n\n// Implementation\nexport function useLiveInfiniteQuery<TContext extends Context>(\n queryFnOrCollection: any,\n config: UseLiveInfiniteQueryConfig<TContext>,\n deps: Array<unknown> = [],\n): UseLiveInfiniteQueryReturn<TContext> {\n const pageSize = normalizeLiveQueryWindowPageSize(config.pageSize)\n const initialPageParam = config.initialPageParam ?? 0\n\n const inputIsCollection =\n getLiveQueryWindowInputKind(queryFnOrCollection) === `collection`\n\n const committedRef = useRef<InfiniteQueryRenderState | null>(null)\n const committed = committedRef.current\n const inputKind = inputIsCollection ? `collection` : `query`\n\n const dependencyComparison = compareLiveQueryWindowDependencies(\n committed?.dependencies,\n deps,\n )\n const dependenciesChanged = !inputIsCollection && dependencyComparison.changed\n const dependenciesStructurallyEqual =\n !inputIsCollection && dependencyComparison.structurallyEqual\n const needsNewCollection =\n committed === null ||\n committed.inputKind !== inputKind ||\n (inputIsCollection && committed.inputCollection !== queryFnOrCollection) ||\n dependenciesChanged\n const pageShapeChanged =\n committed === null ||\n committed.pageSize !== pageSize ||\n committed.initialPageParam !== initialPageParam\n const needsNewController =\n committed === null || needsNewCollection || pageShapeChanged\n\n let renderState = committed\n if (needsNewController) {\n let collection = committed?.collection\n let warning: string | null = null\n\n if (needsNewCollection) {\n const input = resolveLiveQueryWindowInput<TContext>(queryFnOrCollection)\n if (input.kind === `collection`) {\n collection = input.collection\n } else {\n // Wrap the query with the first page's peek-ahead window; the controller\n // grows the limit from here via setWindow.\n collection = createLiveQueryCollection({\n query: input.query.limit(pageSize + 1).offset(0),\n // Construction happens during render. Synchronization starts only when\n // useSyncExternalStore commits the controller subscription.\n startSync: false,\n gcTime: DEFAULT_GC_TIME_MS,\n })\n }\n }\n\n if (!collection) {\n throw new Error(`useLiveInfiniteQuery: Failed to create a collection.`)\n }\n\n if (inputIsCollection) {\n warning =\n getLiveQueryWindowCollectionWarning(collection, pageSize + 1) ?? null\n } else {\n assertLiveQueryWindowManyResult(collection)\n }\n\n const canPreservePageCount = shouldPreserveLiveQueryWindowPageCount({\n hasPreviousController: committed !== null,\n previousInputKind: committed?.inputKind,\n inputKind,\n sameCollection:\n inputIsCollection && committed?.inputCollection === collection,\n dependenciesChanged,\n dependenciesStructurallyEqual,\n pageShapeChanged,\n })\n const previousPageCount = committed\n ? Math.max(1, committed.controller.getSnapshot().pages.length)\n : 1\n const initialPageCount = canPreservePageCount ? previousPageCount : 1\n renderState = {\n inputKind,\n inputCollection: inputIsCollection ? collection : null,\n dependencies: inputIsCollection ? null : [...deps],\n pageSize,\n initialPageParam,\n collection,\n controller: createLiveQueryWindowController(collection, {\n pageSize,\n initialPageParam,\n initialPageCount,\n }),\n warning,\n warned: false,\n }\n }\n const currentRenderState = renderState!\n const controller = currentRenderState.controller\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n const unsubscribe = controller.subscribe(onStoreChange)\n committedRef.current = currentRenderState\n if (currentRenderState.warning && !currentRenderState.warned) {\n currentRenderState.warned = true\n console.warn(currentRenderState.warning)\n }\n return unsubscribe\n },\n [controller, currentRenderState],\n )\n const getSnapshot = useCallback(() => controller.getSnapshot(), [controller])\n const snapshot = useSyncExternalStore(subscribe, getSnapshot)\n\n const fetchNextPage = useCallback(\n () => fetchNextLiveQueryWindowPage(controller),\n [controller],\n )\n\n return {\n data: snapshot.data as InferResultType<TContext>,\n state: snapshot.state as EnabledLiveQueryReturn<TContext>[`state`],\n status: snapshot.status as EnabledLiveQueryReturn<TContext>[`status`],\n isLoading: snapshot.isLoading,\n isReady: snapshot.isReady,\n isIdle: snapshot.isIdle,\n isError: snapshot.isError,\n isCleanedUp: snapshot.isCleanedUp,\n collection:\n snapshot.collection as EnabledLiveQueryReturn<TContext>[`collection`],\n isEnabled:\n snapshot.isEnabled as EnabledLiveQueryReturn<TContext>[`isEnabled`],\n pages: snapshot.pages as Array<Array<InferResultType<TContext>[number]>>,\n pageParams: snapshot.pageParams as Array<number>,\n fetchNextPage,\n hasNextPage: snapshot.hasNextPage,\n isFetchingNextPage: snapshot.isFetchingNextPage,\n error: snapshot.error,\n }\n}\n"],"names":["normalizeLiveQueryWindowPageSize","getLiveQueryWindowInputKind","useRef","compareLiveQueryWindowDependencies","resolveLiveQueryWindowInput","createLiveQueryCollection","getLiveQueryWindowCollectionWarning","assertLiveQueryWindowManyResult","shouldPreserveLiveQueryWindowPageCount","createLiveQueryWindowController","useCallback","useSyncExternalStore","fetchNextLiveQueryWindowPage"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useLiveInfiniteQuery.cjs","sources":["../../src/useLiveInfiniteQuery.ts"],"sourcesContent":["'use client'\n\nimport { useCallback, useRef, useSyncExternalStore } from 'react'\nimport {\n assertLiveQueryWindowManyResult,\n compareLiveQueryWindowDependencies,\n createLiveQueryCollection,\n createLiveQueryWindowController,\n fetchNextLiveQueryWindowPage,\n getLiveQueryWindowCollectionWarning,\n getLiveQueryWindowInputKind,\n normalizeLiveQueryWindowPageSize,\n resolveLiveQueryWindowInput,\n shouldPreserveLiveQueryWindowPageCount,\n} from '@tanstack/db'\nimport { useOptionalDbClient } from './DbProvider'\nimport {\n prepareDerivedQuery,\n prepareQueryValue,\n warnDeprecatedDepsArray,\n warnUnhashableDerivedIdentity,\n} from './useLiveQuery'\nimport type {\n DerivedIdentityProfiler,\n LiveQueryKey,\n useLiveQuery,\n} from './useLiveQuery'\nimport type {\n Collection,\n CollectionImpl as CollectionImplType,\n Context,\n DbClient,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryWindowController,\n NonSingleResult,\n QueryBuilder,\n} from '@tanstack/db'\n\n// Live queries created here are cleaned up immediately (0 disables GC).\nconst DEFAULT_GC_TIME_MS = 1\nconst unpreparedQueryValue = Symbol(`unpreparedQueryValue`)\n\nexport type UseLiveInfiniteQueryConfig<TContext extends Context> = {\n /**\n * Explicit identity for queries that contain opaque functional variants or\n * are hot enough that deriving identity from structured IR is too expensive.\n * Structured queries should omit this so DB can derive identity directly.\n */\n queryKey?: LiveQueryKey\n /** Override the nearest DbProvider for this query. */\n client?: DbClient\n pageSize?: number\n initialPageParam?: number\n /**\n * @deprecated This callback is not used by the current implementation.\n * Pagination is determined internally via a peek-ahead strategy.\n * Provided for API compatibility with TanStack Query conventions.\n */\n getNextPageParam?: (\n lastPage: Array<InferResultType<TContext>[number]>,\n allPages: Array<Array<InferResultType<TContext>[number]>>,\n lastPageParam: number,\n allPageParams: Array<number>,\n ) => number | undefined\n}\n\nexport type UseLiveInfiniteQueryReturn<TContext extends Context> = Omit<\n ReturnType<typeof useLiveQuery<TContext>>,\n `data`\n> & {\n data: InferResultType<TContext>\n pages: Array<Array<InferResultType<TContext>[number]>>\n pageParams: Array<number>\n fetchNextPage: () => Promise<void>\n hasNextPage: boolean\n isFetchingNextPage: boolean\n error: unknown\n}\n\ntype EnabledLiveQueryReturn<TContext extends Context> = ReturnType<\n typeof useLiveQuery<TContext>\n>\n\ntype InfiniteQueryRenderState = {\n inputKind: `collection` | `query`\n inputCollection: Collection<any, any, any> | null\n inputQuery: unknown\n client: DbClient | undefined\n identityMode: `collection` | `queryKey` | `legacyDeps` | `derived`\n dependencies: Array<unknown> | null\n pageSize: number\n initialPageParam: number\n collection: Collection<any, any, any>\n controller: LiveQueryWindowController<any, any>\n warning: string | null\n warned: boolean\n deferredCollections: Set<\n CollectionImplType<any, string | number, any, any, any>\n >\n}\n\n/**\n * Create an infinite query using a query function with live updates.\n *\n * Uses `utils.setWindow()` to dynamically adjust the limit/offset window\n * without recreating the live query collection on each page change.\n *\n * @param queryFn - Query function that defines what data to fetch. Must include `.orderBy()` for setWindow to work.\n * @param config - Configuration including pageSize and getNextPageParam\n * @param deps - Deprecated array of dependencies that trigger query re-execution when changed\n * @returns Object with pages, data, and pagination controls\n */\n\n// Overload for pre-created collection (non-single result)\nexport function useLiveInfiniteQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult,\n config: UseLiveInfiniteQueryConfig<any>,\n): UseLiveInfiniteQueryReturn<any>\n\n// Overload for query function\nexport function useLiveInfiniteQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n config: UseLiveInfiniteQueryConfig<TContext>,\n deps?: Array<unknown>,\n): UseLiveInfiniteQueryReturn<TContext>\n\n// Implementation\nexport function useLiveInfiniteQuery<TContext extends Context>(\n queryFnOrCollection: any,\n config: UseLiveInfiniteQueryConfig<TContext>,\n deps?: Array<unknown>,\n): UseLiveInfiniteQueryReturn<TContext> {\n const pageSize = normalizeLiveQueryWindowPageSize(config.pageSize)\n const initialPageParam = config.initialPageParam ?? 0\n const contextDbClient = useOptionalDbClient()\n const dbClient = config.client ?? contextDbClient\n\n const inputIsCollection =\n getLiveQueryWindowInputKind(queryFnOrCollection) === `collection`\n\n const committedRef = useRef<InfiniteQueryRenderState | null>(null)\n const committed = committedRef.current\n const inputKind = inputIsCollection ? `collection` : `query`\n const derivedIdentityProfilerRef = useRef<DerivedIdentityProfiler>({\n renderCount: 0,\n totalMs: 0,\n maxMs: 0,\n warned: false,\n })\n const legacyUnhashableIdentityRef = useRef<Array<unknown>>([\n `legacy-unhashable`,\n ])\n const deferredCollections = new Set<\n CollectionImplType<any, string | number, any, any, any>\n >()\n\n let preparedQueryValue: unknown | typeof unpreparedQueryValue =\n unpreparedQueryValue\n let identityDeps: ReadonlyArray<unknown> = []\n let identityMode: InfiniteQueryRenderState[`identityMode`] = `collection`\n\n if (!inputIsCollection) {\n if (config.queryKey !== undefined) {\n identityMode = `queryKey`\n identityDeps = config.queryKey\n } else if (deps !== undefined) {\n identityMode = `legacyDeps`\n identityDeps = deps\n warnDeprecatedDepsArray(`useLiveInfiniteQuery`)\n } else if (\n committed?.identityMode === `derived` &&\n committed.inputQuery === queryFnOrCollection &&\n committed.client === dbClient\n ) {\n identityMode = `derived`\n identityDeps = committed.dependencies ?? []\n } else {\n identityMode = `derived`\n const preparation = prepareDerivedQuery(\n queryFnOrCollection,\n dbClient,\n derivedIdentityProfilerRef.current,\n deferredCollections,\n )\n preparedQueryValue = preparation.value\n if (preparation.status === `hashable`) {\n identityDeps = preparation.identityDeps\n } else {\n warnUnhashableDerivedIdentity(preparation.error)\n identityDeps = legacyUnhashableIdentityRef.current\n }\n }\n }\n\n const usesLegacyDeps =\n !inputIsCollection && config.queryKey === undefined && deps !== undefined\n const dependencyComparison = compareLiveQueryWindowDependencies(\n committed?.dependencies,\n identityDeps,\n )\n const sameClient = committed?.client === dbClient\n const dependenciesChanged =\n !inputIsCollection &&\n (!sameClient ||\n (usesLegacyDeps\n ? dependencyComparison.changed\n : !dependencyComparison.structurallyEqual))\n const dependenciesStructurallyEqual =\n usesLegacyDeps && sameClient && dependencyComparison.structurallyEqual\n const needsNewCollection =\n committed === null ||\n committed.inputKind !== inputKind ||\n (inputIsCollection && committed.inputCollection !== queryFnOrCollection) ||\n dependenciesChanged\n const pageShapeChanged =\n committed === null ||\n committed.pageSize !== pageSize ||\n committed.initialPageParam !== initialPageParam\n const needsNewController =\n committed === null || needsNewCollection || pageShapeChanged\n\n let renderState = committed\n if (needsNewController) {\n let collection = committed?.collection\n let warning: string | null = null\n\n if (needsNewCollection) {\n let inputValue = queryFnOrCollection\n if (!inputIsCollection) {\n if (preparedQueryValue === unpreparedQueryValue) {\n preparedQueryValue = prepareQueryValue(\n queryFnOrCollection,\n dbClient,\n deferredCollections,\n )\n }\n inputValue = () => preparedQueryValue\n }\n const input = resolveLiveQueryWindowInput<TContext>(inputValue)\n if (input.kind === `collection`) {\n collection = input.collection\n } else {\n // Wrap the query with the first page's peek-ahead window; the controller\n // grows the limit from here via setWindow.\n collection = createLiveQueryCollection({\n query: input.query.limit(pageSize + 1).offset(0),\n // Construction happens during render. Synchronization starts only when\n // useSyncExternalStore commits the controller subscription.\n startSync: false,\n gcTime: DEFAULT_GC_TIME_MS,\n })\n }\n }\n\n if (!collection) {\n throw new Error(`useLiveInfiniteQuery: Failed to create a collection.`)\n }\n\n if (inputIsCollection) {\n warning =\n getLiveQueryWindowCollectionWarning(collection, pageSize + 1) ?? null\n } else {\n assertLiveQueryWindowManyResult(collection)\n }\n\n const canPreservePageCount = shouldPreserveLiveQueryWindowPageCount({\n hasPreviousController: committed !== null,\n previousInputKind: committed?.inputKind,\n inputKind,\n sameCollection:\n inputIsCollection && committed?.inputCollection === collection,\n dependenciesChanged,\n dependenciesStructurallyEqual,\n pageShapeChanged,\n })\n const previousPageCount = committed\n ? Math.max(1, committed.controller.getSnapshot().pages.length)\n : 1\n const initialPageCount = canPreservePageCount ? previousPageCount : 1\n renderState = {\n inputKind,\n inputCollection: inputIsCollection ? collection : null,\n inputQuery: inputIsCollection ? null : queryFnOrCollection,\n client: dbClient,\n identityMode,\n dependencies: inputIsCollection ? null : [...identityDeps],\n pageSize,\n initialPageParam,\n collection,\n controller: createLiveQueryWindowController(collection, {\n pageSize,\n initialPageParam,\n initialPageCount,\n }),\n warning,\n warned: false,\n deferredCollections,\n }\n }\n const currentRenderState = renderState!\n const controller = currentRenderState.controller\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n const unsubscribe = controller.subscribe(onStoreChange)\n committedRef.current = currentRenderState\n if (currentRenderState.warning && !currentRenderState.warned) {\n currentRenderState.warned = true\n console.warn(currentRenderState.warning)\n }\n for (const collection of currentRenderState.deferredCollections) {\n collection._resumeSyncStart()\n }\n currentRenderState.deferredCollections.clear()\n return unsubscribe\n },\n [controller, currentRenderState],\n )\n const getSnapshot = useCallback(() => controller.getSnapshot(), [controller])\n const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n\n const fetchNextPage = useCallback(\n () => fetchNextLiveQueryWindowPage(controller),\n [controller],\n )\n\n return {\n data: snapshot.data as InferResultType<TContext>,\n state: snapshot.state as EnabledLiveQueryReturn<TContext>[`state`],\n status: snapshot.status as EnabledLiveQueryReturn<TContext>[`status`],\n isLoading: snapshot.isLoading,\n isReady: snapshot.isReady,\n isIdle: snapshot.isIdle,\n isError: snapshot.isError,\n isCleanedUp: snapshot.isCleanedUp,\n collection:\n snapshot.collection as EnabledLiveQueryReturn<TContext>[`collection`],\n isEnabled:\n snapshot.isEnabled as EnabledLiveQueryReturn<TContext>[`isEnabled`],\n pages: snapshot.pages as Array<Array<InferResultType<TContext>[number]>>,\n pageParams: snapshot.pageParams as Array<number>,\n fetchNextPage,\n hasNextPage: snapshot.hasNextPage,\n isFetchingNextPage: snapshot.isFetchingNextPage,\n error: snapshot.error,\n }\n}\n"],"names":["warnDeprecatedDepsArray","prepareDerivedQuery","warnUnhashableDerivedIdentity","compareLiveQueryWindowDependencies","prepareQueryValue","assertLiveQueryWindowManyResult","useCallback"],"mappings":";;;;;;;AAwCA;AACA;AA2FO;AAKL;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AAAmE;AACpD;AACJ;AACF;AACC;AAEV;AAA2D;AACzD;AAEF;AAIA;AAEA;AACA;AAEA;AACE;AACE;AACA;AAAsB;AAEtB;AACA;AACAA;AAA8C;AAM9C;AACA;AAAyC;AAEzC;AACA;AAAoBC;AAClB;AACA;AAC2B;AAC3B;AAEF;AACA;AACE;AAA2B;AAE3BC;AACA;AAA2C;AAC7C;AACF;AAGF;AAEA;AAA6BC;AAChB;AACX;AAEF;AACA;AAMA;AAEA;AAKA;AAIA;AAGA;AACA;AACE;AACA;AAEA;AACE;AACA;AACE;AACE;AAAqBC;AACnB;AACA;AACA;AAAA;AAGJ;AAAmB;AAErB;AACA;AACE;AAAmB;AAInB;AAAuC;AACU;AAAA;AAAA;AAGpC;AACH;AACT;AACH;AAGF;AACE;AAAsE;AAGxE;AACE;AACmE;AAEnEC;AAA0C;AAG5C;AAAoE;AAC7B;AACP;AAC9B;AAEsD;AACtD;AACA;AACA;AAEF;AAGA;AACA;AAAc;AACZ;AACkD;AACX;AAC/B;AACR;AACyD;AACzD;AACA;AACA;AACwD;AACtD;AACA;AACA;AACD;AACD;AACQ;AACR;AAAA;AAGJ;AACA;AAEA;AAAkBC;AAEd;AACA;AACA;AACE;AACA;AAAuC;AAEzC;AACE;AAAW;AAEb;AACA;AAAO;AACT;AAC+B;AAEjC;AACA;AAEA;AAAsBA;AACyB;AAClC;AAGb;AAAO;AACU;AACC;AACC;AACG;AACF;AACD;AACC;AACI;AAEX;AAEA;AACK;AACK;AACrB;AACsB;AACO;AACb;AAEpB;;"}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import { useLiveQuery } from './useLiveQuery.cjs';
|
|
2
|
-
import { Collection, Context, InferResultType, InitialQueryBuilder, NonSingleResult, QueryBuilder } from '@tanstack/db';
|
|
1
|
+
import { LiveQueryKey, useLiveQuery } from './useLiveQuery.cjs';
|
|
2
|
+
import { Collection, Context, DbClient, InferResultType, InitialQueryBuilder, NonSingleResult, QueryBuilder } from '@tanstack/db';
|
|
3
3
|
export type UseLiveInfiniteQueryConfig<TContext extends Context> = {
|
|
4
|
+
/**
|
|
5
|
+
* Explicit identity for queries that contain opaque functional variants or
|
|
6
|
+
* are hot enough that deriving identity from structured IR is too expensive.
|
|
7
|
+
* Structured queries should omit this so DB can derive identity directly.
|
|
8
|
+
*/
|
|
9
|
+
queryKey?: LiveQueryKey;
|
|
10
|
+
/** Override the nearest DbProvider for this query. */
|
|
11
|
+
client?: DbClient;
|
|
4
12
|
pageSize?: number;
|
|
5
13
|
initialPageParam?: number;
|
|
6
14
|
/**
|
|
@@ -20,69 +28,15 @@ export type UseLiveInfiniteQueryReturn<TContext extends Context> = Omit<ReturnTy
|
|
|
20
28
|
error: unknown;
|
|
21
29
|
};
|
|
22
30
|
/**
|
|
23
|
-
* Create an infinite query using a query function with live updates
|
|
31
|
+
* Create an infinite query using a query function with live updates.
|
|
24
32
|
*
|
|
25
33
|
* Uses `utils.setWindow()` to dynamically adjust the limit/offset window
|
|
26
34
|
* without recreating the live query collection on each page change.
|
|
27
35
|
*
|
|
28
36
|
* @param queryFn - Query function that defines what data to fetch. Must include `.orderBy()` for setWindow to work.
|
|
29
37
|
* @param config - Configuration including pageSize and getNextPageParam
|
|
30
|
-
* @param deps -
|
|
38
|
+
* @param deps - Deprecated array of dependencies that trigger query re-execution when changed
|
|
31
39
|
* @returns Object with pages, data, and pagination controls
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* // Basic infinite query
|
|
35
|
-
* const { data, pages, fetchNextPage, hasNextPage } = useLiveInfiniteQuery(
|
|
36
|
-
* (q) => q
|
|
37
|
-
* .from({ posts: postsCollection })
|
|
38
|
-
* .orderBy(({ posts }) => posts.createdAt, 'desc')
|
|
39
|
-
* .select(({ posts }) => ({
|
|
40
|
-
* id: posts.id,
|
|
41
|
-
* title: posts.title
|
|
42
|
-
* })),
|
|
43
|
-
* {
|
|
44
|
-
* pageSize: 20,
|
|
45
|
-
* getNextPageParam: (lastPage, allPages) =>
|
|
46
|
-
* lastPage.length === 20 ? allPages.length : undefined
|
|
47
|
-
* }
|
|
48
|
-
* )
|
|
49
|
-
*
|
|
50
|
-
* @example
|
|
51
|
-
* // With dependencies
|
|
52
|
-
* const { pages, fetchNextPage } = useLiveInfiniteQuery(
|
|
53
|
-
* (q) => q
|
|
54
|
-
* .from({ posts: postsCollection })
|
|
55
|
-
* .where(({ posts }) => eq(posts.category, category))
|
|
56
|
-
* .orderBy(({ posts }) => posts.createdAt, 'desc'),
|
|
57
|
-
* {
|
|
58
|
-
* pageSize: 10,
|
|
59
|
-
* getNextPageParam: (lastPage) =>
|
|
60
|
-
* lastPage.length === 10 ? lastPage.length : undefined
|
|
61
|
-
* },
|
|
62
|
-
* [category]
|
|
63
|
-
* )
|
|
64
|
-
*
|
|
65
|
-
* @example
|
|
66
|
-
* // Router loader pattern with pre-created collection
|
|
67
|
-
* // In loader:
|
|
68
|
-
* const postsQuery = createLiveQueryCollection({
|
|
69
|
-
* query: (q) => q
|
|
70
|
-
* .from({ posts: postsCollection })
|
|
71
|
-
* .orderBy(({ posts }) => posts.createdAt, 'desc')
|
|
72
|
-
* .limit(20)
|
|
73
|
-
* })
|
|
74
|
-
* await postsQuery.preload()
|
|
75
|
-
* return { postsQuery }
|
|
76
|
-
*
|
|
77
|
-
* // In component:
|
|
78
|
-
* const { postsQuery } = useLoaderData()
|
|
79
|
-
* const { data, fetchNextPage, hasNextPage } = useLiveInfiniteQuery(
|
|
80
|
-
* postsQuery,
|
|
81
|
-
* {
|
|
82
|
-
* pageSize: 20,
|
|
83
|
-
* getNextPageParam: (lastPage) => lastPage.length === 20 ? lastPage.length : undefined
|
|
84
|
-
* }
|
|
85
|
-
* )
|
|
86
40
|
*/
|
|
87
41
|
export declare function useLiveInfiniteQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult, config: UseLiveInfiniteQueryConfig<any>): UseLiveInfiniteQueryReturn<any>;
|
|
88
42
|
export declare function useLiveInfiniteQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>, config: UseLiveInfiniteQueryConfig<TContext>, deps?: Array<unknown>): UseLiveInfiniteQueryReturn<TContext>;
|