@tanstack/react-db 0.5.12 → 1.0.0-rc.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/dist/cjs/live-query-internals.cjs.map +1 -1
- package/dist/cjs/live-query-internals.d.cts +3 -1
- package/dist/cjs/source-id-bindings.cjs +9 -42
- package/dist/cjs/source-id-bindings.cjs.map +1 -1
- package/dist/cjs/source-id-bindings.d.cts +6 -17
- package/dist/cjs/useLiveInfiniteQuery.cjs +93 -173
- package/dist/cjs/useLiveInfiniteQuery.cjs.map +1 -1
- package/dist/cjs/useLiveInfiniteQuery.d.cts +3 -14
- package/dist/cjs/useLiveQuery.cjs +187 -224
- package/dist/cjs/useLiveQuery.cjs.map +1 -1
- package/dist/cjs/useLiveQuery.d.cts +37 -136
- package/dist/cjs/useLiveSuspenseQuery.cjs +71 -64
- package/dist/cjs/useLiveSuspenseQuery.cjs.map +1 -1
- package/dist/cjs/useLiveSuspenseQuery.d.cts +10 -9
- package/dist/cjs/usePacedMutations.cjs.map +1 -1
- package/dist/cjs/usePacedMutations.d.cts +1 -1
- package/dist/esm/live-query-internals.d.ts +3 -1
- package/dist/esm/live-query-internals.js.map +1 -1
- package/dist/esm/source-id-bindings.d.ts +6 -17
- package/dist/esm/source-id-bindings.js +9 -40
- package/dist/esm/source-id-bindings.js.map +1 -1
- package/dist/esm/useLiveInfiniteQuery.d.ts +3 -14
- package/dist/esm/useLiveInfiniteQuery.js +95 -175
- package/dist/esm/useLiveInfiniteQuery.js.map +1 -1
- package/dist/esm/useLiveQuery.d.ts +37 -136
- package/dist/esm/useLiveQuery.js +189 -226
- package/dist/esm/useLiveQuery.js.map +1 -1
- package/dist/esm/useLiveSuspenseQuery.d.ts +10 -9
- package/dist/esm/useLiveSuspenseQuery.js +71 -64
- package/dist/esm/useLiveSuspenseQuery.js.map +1 -1
- package/dist/esm/usePacedMutations.d.ts +1 -1
- package/dist/esm/usePacedMutations.js.map +1 -1
- package/package.json +3 -3
- package/skills/react-db/SKILL.md +24 -15
- package/src/live-query-internals.ts +3 -0
- package/src/source-id-bindings.ts +16 -62
- package/src/useLiveInfiniteQuery.ts +138 -296
- package/src/useLiveQuery.ts +329 -495
- package/src/useLiveSuspenseQuery.ts +122 -132
- package/src/usePacedMutations.ts +1 -1
|
@@ -1 +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":";;
|
|
1
|
+
{"version":3,"file":"live-query-internals.cjs","sources":["../../src/live-query-internals.ts"],"sourcesContent":["import type {\n DbClient,\n DeferredLiveQueryCollections,\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 /** Sync starts this render deferred, released if the render throws. */\n deferredCollections: DeferredLiveQueryCollections\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":";;AAOA,MAAM,6CAA6B,qBAAqB;AAejD,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;;;"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { DbClient, LiveQueryObserver, UnhashableQueryIRError } from '@tanstack/db';
|
|
1
|
+
import { DbClient, DeferredLiveQueryCollections, LiveQueryObserver, UnhashableQueryIRError } from '@tanstack/db';
|
|
2
2
|
export type LiveQueryResultInfo = {
|
|
3
3
|
client: DbClient | undefined;
|
|
4
4
|
queryHash: string | undefined;
|
|
5
5
|
identityError: UnhashableQueryIRError | undefined;
|
|
6
6
|
observer: LiveQueryObserver<object, string | number>;
|
|
7
|
+
/** Sync starts this render deferred, released if the render throws. */
|
|
8
|
+
deferredCollections: DeferredLiveQueryCollections;
|
|
7
9
|
};
|
|
8
10
|
export declare function setLiveQueryResultInfo(result: object, info: LiveQueryResultInfo): void;
|
|
9
11
|
export declare function getLiveQueryResultInfo(result: object): LiveQueryResultInfo;
|
|
@@ -1,53 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const db = require("@tanstack/db");
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
return id;
|
|
4
|
+
function getUnboundDescriptorReporter(client, hookName) {
|
|
5
|
+
if (client) return void 0;
|
|
6
|
+
return (alias) => {
|
|
7
|
+
throw new Error(
|
|
8
|
+
`[${hookName}] Collection descriptor "${alias}" requires a DbClient when the query is consumed. Wrap this component in <DbProvider client={client}> or use a concrete Collection.`
|
|
9
|
+
);
|
|
10
|
+
};
|
|
13
11
|
}
|
|
14
12
|
function getPreparedSources(preparedValue, client, hookName) {
|
|
15
13
|
return db.getPreparedLiveQuerySources(
|
|
16
14
|
preparedValue,
|
|
17
|
-
|
|
18
|
-
throw new Error(
|
|
19
|
-
`[${hookName}] Collection descriptor "${alias}" requires a DbClient when the query is consumed. Wrap this component in <DbProvider client={client}> or use a concrete Collection.`
|
|
20
|
-
);
|
|
21
|
-
} : void 0
|
|
15
|
+
getUnboundDescriptorReporter(client, hookName)
|
|
22
16
|
);
|
|
23
17
|
}
|
|
24
|
-
function createSourceIdBindings() {
|
|
25
|
-
return { unscoped: /* @__PURE__ */ new Map(), byClient: /* @__PURE__ */ new WeakMap() };
|
|
26
|
-
}
|
|
27
|
-
function claimSourceIds(bindings, preparedValue, client, hookName, release) {
|
|
28
|
-
const prior = client ? bindings.byClient.get(client) : bindings.unscoped;
|
|
29
|
-
const seen = /* @__PURE__ */ new Map();
|
|
30
|
-
for (const source of getPreparedSources(preparedValue, client, hookName)) {
|
|
31
|
-
const token = getSourceObjectToken(source);
|
|
32
|
-
const previous = seen.get(source.id) ?? prior?.get(source.id);
|
|
33
|
-
if (previous !== void 0 && previous !== token) {
|
|
34
|
-
release();
|
|
35
|
-
throw new Error(
|
|
36
|
-
`[${hookName}] Source Collection "${source.id}" was replaced by a different Collection with the same ID while this hook is mounted. Unmount the hook and clean up its previous source and client scope before reusing the ID.`
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
seen.set(source.id, token);
|
|
40
|
-
}
|
|
41
|
-
const claimed = prior ?? /* @__PURE__ */ new Map();
|
|
42
|
-
for (const [id, token] of seen) claimed.set(id, token);
|
|
43
|
-
if (client) bindings.byClient.set(client, claimed);
|
|
44
|
-
}
|
|
45
|
-
Object.defineProperty(exports, "resumeSyncStarts", {
|
|
46
|
-
enumerable: true,
|
|
47
|
-
get: () => db.resumeDeferredLiveQueryCollections
|
|
48
|
-
});
|
|
49
|
-
exports.claimSourceIds = claimSourceIds;
|
|
50
|
-
exports.createSourceIdBindings = createSourceIdBindings;
|
|
51
18
|
exports.getPreparedSources = getPreparedSources;
|
|
52
|
-
exports.
|
|
19
|
+
exports.getUnboundDescriptorReporter = getUnboundDescriptorReporter;
|
|
53
20
|
//# sourceMappingURL=source-id-bindings.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source-id-bindings.cjs","sources":["../../src/source-id-bindings.ts"],"sourcesContent":["import { getPreparedLiveQuerySources } from '@tanstack/db'\nimport type { DbClient } from '@tanstack/db'\n\
|
|
1
|
+
{"version":3,"file":"source-id-bindings.cjs","sources":["../../src/source-id-bindings.ts"],"sourcesContent":["import { getPreparedLiveQuerySources } from '@tanstack/db'\nimport type { DbClient } from '@tanstack/db'\n\n/**\n * Reports a descriptor source that no DbClient bound, naming the provider a\n * React tree needs. Returns `undefined` when a client can bind descriptors.\n */\nexport function getUnboundDescriptorReporter(\n client: DbClient | undefined,\n hookName: string,\n): ((alias: string) => never) | undefined {\n if (client) return undefined\n return (alias) => {\n throw new Error(\n `[${hookName}] Collection descriptor \"${alias}\" requires a DbClient when the query is consumed. Wrap this component in <DbProvider client={client}> or use a concrete Collection.`,\n )\n }\n}\n\nexport function getPreparedSources(\n preparedValue: unknown,\n client: DbClient | undefined,\n hookName: string,\n): Array<{ id: string }> {\n return getPreparedLiveQuerySources(\n preparedValue,\n getUnboundDescriptorReporter(client, hookName),\n )\n}\n"],"names":["getPreparedLiveQuerySources"],"mappings":";;;AAOO,SAAS,6BACd,QACA,UACwC;AACxC,MAAI,OAAQ,QAAO;AACnB,SAAO,CAAC,UAAU;AAChB,UAAM,IAAI;AAAA,MACR,IAAI,QAAQ,4BAA4B,KAAK;AAAA,IAAA;AAAA,EAEjD;AACF;AAEO,SAAS,mBACd,eACA,QACA,UACuB;AACvB,SAAOA,GAAAA;AAAAA,IACL;AAAA,IACA,6BAA6B,QAAQ,QAAQ;AAAA,EAAA;AAEjD;;;"}
|
|
@@ -1,20 +1,9 @@
|
|
|
1
1
|
import { DbClient } from '@tanstack/db';
|
|
2
|
-
export { resumeDeferredLiveQueryCollections as resumeSyncStarts } from '@tanstack/db';
|
|
3
|
-
export declare function getSourceObjectToken(source: object): number;
|
|
4
|
-
export declare function getPreparedSources(preparedValue: unknown, client?: DbClient, hookName?: string): Array<{
|
|
5
|
-
id: string;
|
|
6
|
-
}>;
|
|
7
|
-
/** The source object each Collection ID named while one hook is mounted. */
|
|
8
|
-
export type SourceIdBindings = {
|
|
9
|
-
unscoped: Map<string, number>;
|
|
10
|
-
byClient: WeakMap<DbClient, Map<string, number>>;
|
|
11
|
-
};
|
|
12
|
-
export declare function createSourceIdBindings(): SourceIdBindings;
|
|
13
2
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* or the hook could reuse a live query that reads the old source. Claims the
|
|
17
|
-
* prepared value's sources, or calls `release` and throws on a collision so a
|
|
18
|
-
* rejected render keeps no sync-start deferral.
|
|
3
|
+
* Reports a descriptor source that no DbClient bound, naming the provider a
|
|
4
|
+
* React tree needs. Returns `undefined` when a client can bind descriptors.
|
|
19
5
|
*/
|
|
20
|
-
export declare function
|
|
6
|
+
export declare function getUnboundDescriptorReporter(client: DbClient | undefined, hookName: string): ((alias: string) => never) | undefined;
|
|
7
|
+
export declare function getPreparedSources(preparedValue: unknown, client: DbClient | undefined, hookName: string): Array<{
|
|
8
|
+
id: string;
|
|
9
|
+
}>;
|
|
@@ -4,205 +4,126 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
4
4
|
const react = require("react");
|
|
5
5
|
const db = require("@tanstack/db");
|
|
6
6
|
const DbProvider = require("./DbProvider.cjs");
|
|
7
|
-
const sourceIdBindings = require("./source-id-bindings.cjs");
|
|
8
7
|
const useLiveQuery = require("./useLiveQuery.cjs");
|
|
9
|
-
const DEFAULT_GC_TIME_MS = 1;
|
|
10
8
|
const unpreparedQueryValue = /* @__PURE__ */ Symbol(`unpreparedQueryValue`);
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
function useLiveInfiniteQuery(queryFnOrCollection, config, deps) {
|
|
15
|
-
if (`getNextPageParam` in config) {
|
|
16
|
-
throw new Error(
|
|
17
|
-
`getNextPageParam is not supported by useLiveInfiniteQuery. Use an on-demand collection and fulfill meta.loadSubsetOptions in queryFn for server pagination.`
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
const pageSize = db.normalizeLiveQueryWindowPageSize(config.pageSize);
|
|
21
|
-
const initialPageParam = config.initialPageParam ?? 0;
|
|
9
|
+
function useLiveInfiniteQuery(queryFnOrCollection, config) {
|
|
10
|
+
const pageShape = db.readLiveQueryWindowPageShape(config);
|
|
22
11
|
const contextDbClient = DbProvider.useOptionalDbClient();
|
|
23
12
|
const dbClient = config.client ?? contextDbClient;
|
|
24
|
-
const inputIsCollection = db.
|
|
13
|
+
const inputIsCollection = db.isCollection(queryFnOrCollection);
|
|
25
14
|
const committedRef = react.useRef(null);
|
|
26
15
|
const committed = committedRef.current;
|
|
27
|
-
const
|
|
16
|
+
const renderedRef = react.useRef(null);
|
|
28
17
|
const sourceIdsRef = react.useRef(null);
|
|
29
|
-
const inputKind = inputIsCollection ? `collection` : `query`;
|
|
30
18
|
const derivedIdentityProfilerRef = react.useRef({
|
|
31
19
|
renderCount: 0,
|
|
32
20
|
totalMs: 0,
|
|
33
21
|
maxMs: 0,
|
|
34
22
|
warned: false
|
|
35
23
|
});
|
|
36
|
-
const legacyUnhashableIdentityRef = react.useRef([
|
|
37
|
-
`legacy-unhashable`
|
|
38
|
-
]);
|
|
39
24
|
const deferredCollections = /* @__PURE__ */ new Set();
|
|
25
|
+
const identityScope = () => useLiveQuery.getIdentityScope(
|
|
26
|
+
sourceIdsRef.current ??= db.createLiveQuerySourceIdClaims(),
|
|
27
|
+
dbClient,
|
|
28
|
+
`useLiveInfiniteQuery`
|
|
29
|
+
);
|
|
30
|
+
const queryKey = db.getLiveQueryKey(config);
|
|
40
31
|
let preparedQueryValue = unpreparedQueryValue;
|
|
41
|
-
let
|
|
42
|
-
let
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
identityMode = `derived`;
|
|
53
|
-
identityDeps = committed.dependencies ?? [];
|
|
54
|
-
} else {
|
|
55
|
-
identityMode = `derived`;
|
|
56
|
-
const preparation = useLiveQuery.prepareDerivedQuery(
|
|
57
|
-
queryFnOrCollection,
|
|
58
|
-
dbClient,
|
|
59
|
-
derivedIdentityProfilerRef.current,
|
|
60
|
-
deferredCollections
|
|
61
|
-
);
|
|
62
|
-
preparedQueryValue = preparation.value;
|
|
63
|
-
sourceIdBindings.claimSourceIds(
|
|
64
|
-
sourceIdsRef.current ??= sourceIdBindings.createSourceIdBindings(),
|
|
65
|
-
preparation.value,
|
|
66
|
-
dbClient,
|
|
67
|
-
`useLiveInfiniteQuery`,
|
|
68
|
-
() => db.resumeDeferredLiveQueryCollections(deferredCollections)
|
|
69
|
-
);
|
|
70
|
-
if (preparation.status === `hashable`) {
|
|
71
|
-
identityDeps = preparation.identityDeps;
|
|
32
|
+
let dependencies;
|
|
33
|
+
let derivedQuery = void 0;
|
|
34
|
+
const rendered = renderedRef.current;
|
|
35
|
+
let binding;
|
|
36
|
+
try {
|
|
37
|
+
if (!inputIsCollection) {
|
|
38
|
+
if (queryKey !== void 0) {
|
|
39
|
+
dependencies = queryKey;
|
|
40
|
+
} else if (committed !== null && committed.derivedQuery === queryFnOrCollection && committed.binding.scope === dbClient) {
|
|
41
|
+
derivedQuery = queryFnOrCollection;
|
|
42
|
+
dependencies = committed.binding.dependencies;
|
|
72
43
|
} else {
|
|
73
|
-
useLiveQuery.
|
|
74
|
-
|
|
44
|
+
const preparation = useLiveQuery.prepareDerivedQuery(
|
|
45
|
+
queryFnOrCollection,
|
|
46
|
+
derivedIdentityProfilerRef.current,
|
|
47
|
+
identityScope(),
|
|
48
|
+
deferredCollections
|
|
49
|
+
);
|
|
50
|
+
preparedQueryValue = preparation.value;
|
|
51
|
+
derivedQuery = queryFnOrCollection;
|
|
52
|
+
dependencies = preparation.identityDeps;
|
|
75
53
|
}
|
|
76
54
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const dependenciesChanged = !inputIsCollection && !queryIdentityMatches(dependencyComparison, sameClient, usesLegacyDeps);
|
|
85
|
-
const dependenciesStructurallyEqual = usesLegacyDeps && sameClient && dependencyComparison.structurallyEqual;
|
|
86
|
-
const needsNewCollection = committed === null || committed.inputKind !== inputKind || inputIsCollection && committed.inputCollection !== queryFnOrCollection || dependenciesChanged;
|
|
87
|
-
const pageShapeChanged = committed === null || committed.pageSize !== pageSize || committed.initialPageParam !== initialPageParam;
|
|
88
|
-
const needsNewController = committed === null || needsNewCollection || pageShapeChanged;
|
|
89
|
-
let renderState = committed;
|
|
90
|
-
if (needsNewController) {
|
|
91
|
-
let collection = committed?.collection;
|
|
92
|
-
let warning = null;
|
|
93
|
-
let startInRender = false;
|
|
94
|
-
let suppliedCollection = false;
|
|
95
|
-
let createdCollection = false;
|
|
96
|
-
const canPreservePageCount = db.shouldPreserveLiveQueryWindowPageCount({
|
|
97
|
-
hasPreviousController: committed !== null,
|
|
98
|
-
previousInputKind: committed?.inputKind,
|
|
99
|
-
inputKind,
|
|
100
|
-
sameCollection: inputIsCollection && committed?.inputCollection === queryFnOrCollection,
|
|
101
|
-
dependenciesChanged,
|
|
102
|
-
dependenciesStructurallyEqual,
|
|
103
|
-
pageShapeChanged
|
|
104
|
-
});
|
|
105
|
-
const previousPageCount = committed ? Math.max(1, committed.controller.getSnapshot().pages.length) : 1;
|
|
106
|
-
const initialPageCount = canPreservePageCount ? previousPageCount : 1;
|
|
107
|
-
const requiredLimit = initialPageCount * pageSize + 1;
|
|
108
|
-
if (needsNewCollection) {
|
|
109
|
-
let inputValue = queryFnOrCollection;
|
|
110
|
-
if (!inputIsCollection) {
|
|
111
|
-
if (preparedQueryValue === unpreparedQueryValue) {
|
|
112
|
-
preparedQueryValue = useLiveQuery.prepareQueryValue(
|
|
113
|
-
queryFnOrCollection,
|
|
114
|
-
dbClient,
|
|
115
|
-
deferredCollections
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
inputValue = () => preparedQueryValue;
|
|
119
|
-
}
|
|
120
|
-
const rendered = renderedCollectionRef.current;
|
|
121
|
-
if (!inputIsCollection && rendered !== null && queryIdentityMatches(
|
|
122
|
-
db.compareLiveQueryWindowDependencies(
|
|
123
|
-
rendered.dependencies,
|
|
124
|
-
identityDeps
|
|
125
|
-
),
|
|
126
|
-
rendered.client === dbClient,
|
|
127
|
-
usesLegacyDeps
|
|
128
|
-
) && rendered.collection.status !== `cleaned-up` && rendered.collection.status !== `error` && db.liveQueryWindowMatches(rendered.collection, requiredLimit)) {
|
|
129
|
-
collection = rendered.collection;
|
|
130
|
-
for (const deferred of rendered.deferredCollections) {
|
|
131
|
-
deferredCollections.add(deferred);
|
|
132
|
-
}
|
|
133
|
-
} else {
|
|
134
|
-
const input = db.resolveLiveQueryWindowInput(inputValue);
|
|
135
|
-
if (input.kind === `collection`) {
|
|
136
|
-
collection = input.collection;
|
|
137
|
-
suppliedCollection = true;
|
|
138
|
-
renderedCollectionRef.current = null;
|
|
139
|
-
} else {
|
|
140
|
-
collection = db.createLiveQueryCollection({
|
|
141
|
-
query: input.query.limit(requiredLimit).offset(0),
|
|
142
|
-
gcTime: DEFAULT_GC_TIME_MS
|
|
143
|
-
});
|
|
144
|
-
startInRender = true;
|
|
145
|
-
createdCollection = true;
|
|
146
|
-
}
|
|
55
|
+
const compileQuery = () => {
|
|
56
|
+
if (preparedQueryValue === unpreparedQueryValue) {
|
|
57
|
+
preparedQueryValue = useLiveQuery.prepareQueryValue(
|
|
58
|
+
queryFnOrCollection,
|
|
59
|
+
dbClient,
|
|
60
|
+
deferredCollections
|
|
61
|
+
);
|
|
147
62
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (suppliedCollection) {
|
|
158
|
-
startInRender = db.liveQueryWindowMatches(collection, requiredLimit);
|
|
159
|
-
}
|
|
160
|
-
if (startInRender) collection.startSyncImmediate();
|
|
161
|
-
if (createdCollection) {
|
|
162
|
-
renderedCollectionRef.current = {
|
|
63
|
+
return preparedQueryValue;
|
|
64
|
+
};
|
|
65
|
+
binding = db.bindLiveQueryWindow(
|
|
66
|
+
committed?.binding ?? null,
|
|
67
|
+
{
|
|
68
|
+
...pageShape,
|
|
69
|
+
input: typeof queryFnOrCollection === `function` ? compileQuery : queryFnOrCollection,
|
|
70
|
+
dependencies,
|
|
71
|
+
scope: dbClient,
|
|
163
72
|
client: dbClient,
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
73
|
+
queryKey,
|
|
74
|
+
recompile: `identity`
|
|
75
|
+
},
|
|
76
|
+
rendered?.binding
|
|
77
|
+
);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
db.rejectLiveQueryInput(deferredCollections, error);
|
|
80
|
+
}
|
|
81
|
+
let currentRenderState = committed;
|
|
82
|
+
if (binding !== committed?.binding) {
|
|
83
|
+
const newCollection = binding.collection !== committed?.binding.collection;
|
|
84
|
+
if (newCollection && binding.collection === rendered?.binding.collection) {
|
|
85
|
+
for (const deferred of rendered.deferredCollections) {
|
|
86
|
+
deferredCollections.add(deferred);
|
|
87
|
+
}
|
|
168
88
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
controller: db.createLiveQueryWindowController(collection, {
|
|
180
|
-
pageSize,
|
|
181
|
-
initialPageParam,
|
|
182
|
-
initialPageCount
|
|
183
|
-
}),
|
|
184
|
-
warning,
|
|
185
|
-
warned: false,
|
|
186
|
-
deferredCollections
|
|
89
|
+
const reusesRendered = newCollection && binding.collection === rendered?.binding.collection;
|
|
90
|
+
currentRenderState = {
|
|
91
|
+
binding,
|
|
92
|
+
derivedQuery,
|
|
93
|
+
deferredCollections,
|
|
94
|
+
// A render that never commits releases its deferred sync starts when
|
|
95
|
+
// garbage collection reclaims the window collection it built. A reused
|
|
96
|
+
// collection keeps the release its first render registered.
|
|
97
|
+
cancelReclaimRelease: reusesRendered ? rendered.cancelReclaimRelease : newCollection && binding.ownsCollection ? db.releaseDeferredOnReclaim(binding.collection, deferredCollections) : void 0,
|
|
98
|
+
warned: false
|
|
187
99
|
};
|
|
100
|
+
if (binding.supplied) renderedRef.current = null;
|
|
101
|
+
else if (newCollection && binding.ownsCollection) {
|
|
102
|
+
renderedRef.current = currentRenderState;
|
|
103
|
+
}
|
|
188
104
|
}
|
|
189
|
-
const
|
|
190
|
-
const controller =
|
|
105
|
+
const renderState = currentRenderState;
|
|
106
|
+
const controller = renderState.binding.controller;
|
|
191
107
|
const subscribe = react.useCallback(
|
|
192
108
|
(onStoreChange) => {
|
|
193
109
|
const unsubscribe = controller.subscribe(onStoreChange);
|
|
194
|
-
committedRef.current =
|
|
195
|
-
if (
|
|
196
|
-
|
|
197
|
-
console.warn(
|
|
110
|
+
committedRef.current = renderState;
|
|
111
|
+
if (renderState.binding.warning && !renderState.warned) {
|
|
112
|
+
renderState.warned = true;
|
|
113
|
+
console.warn(renderState.binding.warning);
|
|
198
114
|
}
|
|
199
|
-
|
|
115
|
+
renderState.cancelReclaimRelease?.();
|
|
116
|
+
db.resumeDeferredLiveQueryCollections(renderState.deferredCollections);
|
|
200
117
|
return unsubscribe;
|
|
201
118
|
},
|
|
202
|
-
[controller,
|
|
119
|
+
[controller, renderState]
|
|
203
120
|
);
|
|
204
121
|
const getSnapshot = react.useCallback(() => controller.getSnapshot(), [controller]);
|
|
205
|
-
const snapshot = react.useSyncExternalStore(
|
|
122
|
+
const snapshot = react.useSyncExternalStore(
|
|
123
|
+
subscribe,
|
|
124
|
+
getSnapshot,
|
|
125
|
+
() => controller.getServerSnapshot()
|
|
126
|
+
);
|
|
206
127
|
const fetchNextPage = react.useCallback(
|
|
207
128
|
() => db.fetchNextLiveQueryWindowPage(controller),
|
|
208
129
|
[controller]
|
|
@@ -215,12 +136,11 @@ function useLiveInfiniteQuery(queryFnOrCollection, config, deps) {
|
|
|
215
136
|
status: snapshot.status,
|
|
216
137
|
isLoading: snapshot.isLoading,
|
|
217
138
|
isReady: snapshot.isReady,
|
|
218
|
-
persistedStatus: snapshot.persistedStatus,
|
|
219
|
-
isPersistedReady: snapshot.isPersistedReady,
|
|
220
|
-
persistedError: snapshot.persistedError,
|
|
221
139
|
isIdle: snapshot.isIdle,
|
|
222
|
-
|
|
140
|
+
isLoadingError: snapshot.isLoadingError,
|
|
141
|
+
isCrashed: snapshot.isCrashed,
|
|
223
142
|
isCleanedUp: snapshot.isCleanedUp,
|
|
143
|
+
isSyncError: snapshot.isSyncError,
|
|
224
144
|
collection: snapshot.collection,
|
|
225
145
|
isEnabled: snapshot.isEnabled,
|
|
226
146
|
pages: snapshot.pages,
|
|
@@ -228,7 +148,7 @@ function useLiveInfiniteQuery(queryFnOrCollection, config, deps) {
|
|
|
228
148
|
fetchNextPage,
|
|
229
149
|
hasNextPage: snapshot.hasNextPage,
|
|
230
150
|
isFetchingNextPage: snapshot.isFetchingNextPage,
|
|
231
|
-
|
|
151
|
+
health: snapshot.health
|
|
232
152
|
};
|
|
233
153
|
}
|
|
234
154
|
exports.useLiveInfiniteQuery = useLiveInfiniteQuery;
|
|
@@ -1 +1 @@
|
|
|
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 liveQueryWindowMatches,\n normalizeLiveQueryWindowPageSize,\n resolveLiveQueryWindowInput,\n shouldPreserveLiveQueryWindowPageCount,\n} from '@tanstack/db'\nimport { useOptionalDbClient } from './DbProvider'\nimport {\n claimSourceIds,\n createSourceIdBindings,\n resumeSyncStarts,\n} from './source-id-bindings'\nimport {\n prepareDerivedQuery,\n prepareQueryValue,\n warnDeprecatedDepsArray,\n warnUnhashableDerivedIdentity,\n} from './useLiveQuery'\nimport type { SourceIdBindings } from './source-id-bindings'\nimport type {\n DerivedIdentityProfiler,\n LiveQueryKey,\n useLiveQuery,\n} from './useLiveQuery'\nimport type {\n Collection,\n CollectionImpl as CollectionImplType,\n CollectionStatus,\n Context,\n DbClient,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryPersistedStatus,\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\n// Keep the generic parameter for existing typed config wrappers.\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 /** First result-page label, not a server cursor or remote offset. */\n initialPageParam?: number\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\nexport type UseLiveInfiniteQueryReturnWithCollection<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n> = {\n data: Array<TResult>\n state: Map<TKey, TResult>\n collection: Collection<TResult, TKey, TUtils> & NonSingleResult\n status: CollectionStatus\n isLoading: boolean\n isReady: boolean\n persistedStatus: LiveQueryPersistedStatus\n isPersistedReady: boolean\n persistedError: unknown | undefined\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: true\n pages: Array<Array<TResult>>\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\ntype RenderedWindowCollection = {\n client: DbClient | undefined\n dependencies: Array<unknown>\n collection: Collection<any, any, any>\n deferredCollections: Set<\n CollectionImplType<any, string | number, any, any, any>\n >\n}\n\n/**\n * Whether a recorded query identity matches the current one. Legacy deps\n * compare by reference. Derived identities and query keys compare by structure.\n */\nfunction queryIdentityMatches(\n comparison: { changed: boolean; structurallyEqual: boolean },\n sameClient: boolean,\n usesLegacyDeps: boolean,\n): boolean {\n return (\n sameClient &&\n (usesLegacyDeps ? !comparison.changed : comparison.structurallyEqual)\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 an optional initial page label\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): UseLiveInfiniteQueryReturnWithCollection<TResult, TKey, TUtils>\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 if (`getNextPageParam` in config) {\n throw new Error(\n `getNextPageParam is not supported by useLiveInfiniteQuery. Use an on-demand collection and fulfill meta.loadSubsetOptions in queryFn for server pagination.`,\n )\n }\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 // Like useLiveQuery's instance memo, keep the window collection built by the\n // latest render. A second render before commit, such as a StrictMode double\n // render, reuses it instead of starting another one. Only the collection is\n // kept. Each render still builds its own controller and page count from the\n // committed state, which an uncommitted render never changes.\n const renderedCollectionRef = useRef<RenderedWindowCollection | null>(null)\n const sourceIdsRef = useRef<SourceIdBindings | null>(null)\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 claimSourceIds(\n (sourceIdsRef.current ??= createSourceIdBindings()),\n preparation.value,\n dbClient,\n `useLiveInfiniteQuery`,\n () => resumeSyncStarts(deferredCollections),\n )\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 !queryIdentityMatches(dependencyComparison, sameClient, usesLegacyDeps)\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 let startInRender = false\n let suppliedCollection = false\n let createdCollection = false\n\n const canPreservePageCount = shouldPreserveLiveQueryWindowPageCount({\n hasPreviousController: committed !== null,\n previousInputKind: committed?.inputKind,\n inputKind,\n sameCollection:\n inputIsCollection && committed?.inputCollection === queryFnOrCollection,\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 // The peek-ahead window for every retained page.\n const requiredLimit = initialPageCount * pageSize + 1\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 rendered = renderedCollectionRef.current\n if (\n !inputIsCollection &&\n rendered !== null &&\n queryIdentityMatches(\n compareLiveQueryWindowDependencies(\n rendered.dependencies,\n identityDeps,\n ),\n rendered.client === dbClient,\n usesLegacyDeps,\n ) &&\n rendered.collection.status !== `cleaned-up` &&\n rendered.collection.status !== `error` &&\n liveQueryWindowMatches(rendered.collection, requiredLimit)\n ) {\n // An earlier render built this collection and nothing has committed\n // since. Its window is exactly the retained pages, so its first\n // rows are correct. Sources it deferred still resume at commit. A\n // collection in terminal error, such as after its source restarted,\n // cannot serve the query again, so a new one replaces it.\n collection = rendered.collection\n for (const deferred of rendered.deferredCollections) {\n deferredCollections.add(deferred)\n }\n } else {\n const input = resolveLiveQueryWindowInput<TContext>(inputValue)\n if (input.kind === `collection`) {\n collection = input.collection\n suppliedCollection = true\n // A supplied collection is never reused, so release the last one.\n renderedCollectionRef.current = null\n } else {\n // Wrap the query with the peek-ahead window for every retained page,\n // so a collection that starts syncing now never publishes fewer rows\n // than the controller's pages. The controller grows the limit via\n // setWindow.\n collection = createLiveQueryCollection({\n query: input.query.limit(requiredLimit).offset(0),\n gcTime: DEFAULT_GC_TIME_MS,\n })\n startInRender = true\n createdCollection = true\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 // Like useLiveQuery, start sync during render once the input is valid. A\n // render reads only rows the sources already hold, such as a preloaded\n // collection's, and sends no request until the subscription commits. GC\n // reclaims a render that never commits. A supplied window that the\n // controller must still adjust waits for the subscription.\n if (suppliedCollection) {\n startInRender = liveQueryWindowMatches(collection, requiredLimit)\n }\n if (startInRender) collection.startSyncImmediate()\n // Keep a created collection for the next render only once it validated and\n // started, so a retry after a startup error builds a new one and throws.\n if (createdCollection) {\n renderedCollectionRef.current = {\n client: dbClient,\n dependencies: [...identityDeps],\n collection,\n deferredCollections,\n }\n }\n\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 resumeSyncStarts(currentRenderState.deferredCollections)\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 get state() {\n return snapshot.state as EnabledLiveQueryReturn<TContext>[`state`]\n },\n status: snapshot.status as EnabledLiveQueryReturn<TContext>[`status`],\n isLoading: snapshot.isLoading,\n isReady: snapshot.isReady,\n persistedStatus: snapshot.persistedStatus,\n isPersistedReady: snapshot.isPersistedReady,\n persistedError: snapshot.persistedError,\n isIdle: snapshot.isIdle,\n isError: snapshot.isError,\n isCleanedUp: snapshot.isCleanedUp,\n collection:\n snapshot.collection as EnabledLiveQueryReturn<TContext>[`collection`],\n isEnabled: snapshot.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","claimSourceIds","warnUnhashableDerivedIdentity","compareLiveQueryWindowDependencies","prepareQueryValue","assertLiveQueryWindowManyResult","useCallback","resumeSyncStarts"],"mappings":";;;;;;;;AAiDA;AACA;AA2FA;AAKE;AAIF;AAgCO;AAKL;AACE;AAAU;AACR;AAAA;AAGJ;AACA;AACA;AACA;AAEA;AAGA;AACA;AAMA;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;AACAC;AAAAA;AAC4B;AACd;AACZ;AACA;AAC0C;AAE5C;AACE;AAA2B;AAE3BC;AACA;AAA2C;AAC7C;AACF;AAGF;AAEA;AAA6BC;AAChB;AACX;AAEF;AACA;AAGA;AAEA;AAKA;AAIA;AAGA;AACA;AACE;AACA;AACA;AACA;AACA;AAEA;AAAoE;AAC7B;AACP;AAC9B;AAEsD;AACtD;AACA;AACA;AAEF;AAGA;AAEA;AAEA;AACE;AACA;AACE;AACE;AAAqBC;AACnB;AACA;AACA;AAAA;AAGJ;AAAmB;AAErB;AACA;AAGE;AACED;AACW;AACT;AAAA;AAEkB;AACpB;AAWF;AACA;AACE;AAAgC;AAClC;AAEA;AACA;AACE;AACA;AAEA;AAAgC;AAMhC;AAAuC;AACW;AACxC;AAEV;AACA;AAAoB;AACtB;AACF;AAGF;AACE;AAAsE;AAGxE;AACE;AACmE;AAEnEE;AAA0C;AAO5C;AACE;AAAgE;AAElE;AAGA;AACE;AAAgC;AACtB;AACsB;AAC9B;AACA;AAAA;AAIJ;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;AAEzCC;AACA;AAAO;AACT;AAC+B;AAEjC;AACA;AAEA;AAAsBD;AACyB;AAClC;AAGb;AAAO;AACU;AAEb;AAAgB;AAClB;AACiB;AACG;AACF;AACQ;AACC;AACF;AACR;AACC;AACI;AAEX;AACS;AACJ;AACK;AACrB;AACsB;AACO;AACb;AAEpB;;"}
|
|
1
|
+
{"version":3,"file":"useLiveInfiniteQuery.cjs","sources":["../../src/useLiveInfiniteQuery.ts"],"sourcesContent":["'use client'\n\nimport { useCallback, useRef, useSyncExternalStore } from 'react'\nimport {\n bindLiveQueryWindow,\n createLiveQuerySourceIdClaims,\n fetchNextLiveQueryWindowPage,\n getLiveQueryKey,\n isCollection,\n readLiveQueryWindowPageShape,\n rejectLiveQueryInput,\n releaseDeferredOnReclaim,\n resumeDeferredLiveQueryCollections,\n} from '@tanstack/db'\nimport { useOptionalDbClient } from './DbProvider'\nimport {\n getIdentityScope,\n prepareDerivedQuery,\n prepareQueryValue,\n} from './useLiveQuery'\nimport type {\n DerivedIdentityProfiler,\n LiveQueryKey,\n useLiveQuery,\n} from './useLiveQuery'\nimport type {\n Collection,\n CollectionImpl as CollectionImplType,\n CollectionStatus,\n Context,\n DbClient,\n InferResultType,\n InitialQueryBuilder,\n LiveQuerySourceIdClaims,\n LiveQueryStatusFlags,\n LiveQueryWindowBinding,\n NonSingleResult,\n QueryBuilder,\n} from '@tanstack/db'\n\nconst unpreparedQueryValue = Symbol(`unpreparedQueryValue`)\n\n// Keep the generic parameter for existing typed config wrappers.\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 /** First result-page label, not a server cursor or remote offset. */\n initialPageParam?: number\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}\n\nexport type UseLiveInfiniteQueryReturnWithCollection<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n> = LiveQueryStatusFlags & {\n data: Array<TResult>\n state: Map<TKey, TResult>\n collection: Collection<TResult, TKey, TUtils> & NonSingleResult\n status: CollectionStatus\n isEnabled: true\n pages: Array<Array<TResult>>\n pageParams: Array<number>\n fetchNextPage: () => Promise<void>\n hasNextPage: boolean\n isFetchingNextPage: boolean\n}\n\ntype EnabledLiveQueryReturn<TContext extends Context> = ReturnType<\n typeof useLiveQuery<TContext>\n>\n\n/** One render's window binding and what its commit still owes. */\ntype InfiniteQueryRender = {\n binding: LiveQueryWindowBinding\n /** The query callback whose identity this render derived, if any. */\n derivedQuery: unknown\n deferredCollections: Set<\n CollectionImplType<any, string | number, any, any, any>\n >\n /** Drops the reclaim release when this render commits. */\n cancelReclaimRelease: (() => void) | undefined\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 an optional initial page label\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): UseLiveInfiniteQueryReturnWithCollection<TResult, TKey, TUtils>\n\n// Overload for query function\nexport function useLiveInfiniteQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n config: UseLiveInfiniteQueryConfig<TContext>,\n): UseLiveInfiniteQueryReturn<TContext>\n\n// Implementation\nexport function useLiveInfiniteQuery<TContext extends Context>(\n queryFnOrCollection: any,\n config: UseLiveInfiniteQueryConfig<TContext>,\n): UseLiveInfiniteQueryReturn<TContext> {\n const pageShape = readLiveQueryWindowPageShape(config)\n const contextDbClient = useOptionalDbClient()\n const dbClient = config.client ?? contextDbClient\n const inputIsCollection = isCollection(queryFnOrCollection)\n\n const committedRef = useRef<InfiniteQueryRender | null>(null)\n const committed = committedRef.current\n // Like useLiveQuery's instance memo, keep the latest render whose binding\n // built a window collection. A second render before commit, such as a\n // StrictMode double render, reuses that collection instead of starting\n // another. Each render still builds its own controller and page count from\n // the committed binding, which an uncommitted render never changes.\n const renderedRef = useRef<InfiniteQueryRender | null>(null)\n const sourceIdsRef = useRef<LiveQuerySourceIdClaims | null>(null)\n const derivedIdentityProfilerRef = useRef<DerivedIdentityProfiler>({\n renderCount: 0,\n totalMs: 0,\n maxMs: 0,\n warned: false,\n })\n const deferredCollections = new Set<\n CollectionImplType<any, string | number, any, any, any>\n >()\n const identityScope = () =>\n getIdentityScope(\n (sourceIdsRef.current ??= createLiveQuerySourceIdClaims()),\n dbClient,\n `useLiveInfiniteQuery`,\n )\n\n const queryKey = getLiveQueryKey(config)\n let preparedQueryValue: unknown | typeof unpreparedQueryValue =\n unpreparedQueryValue\n let dependencies: ReadonlyArray<unknown> | undefined\n let derivedQuery: unknown = undefined\n\n const rendered = renderedRef.current\n let binding: LiveQueryWindowBinding\n // The render boundary: a render that throws anywhere after preparation,\n // such as an unhashable, single-result, or uncompilable query, never\n // commits, so it releases the sync starts it deferred here.\n try {\n if (!inputIsCollection) {\n if (queryKey !== undefined) {\n dependencies = queryKey\n } else if (\n committed !== null &&\n committed.derivedQuery === queryFnOrCollection &&\n committed.binding.scope === dbClient\n ) {\n // The same callback in the same client scope keeps its identity.\n derivedQuery = queryFnOrCollection\n dependencies = committed.binding.dependencies\n } else {\n const preparation = prepareDerivedQuery(\n queryFnOrCollection,\n derivedIdentityProfilerRef.current,\n identityScope(),\n deferredCollections,\n )\n preparedQueryValue = preparation.value\n derivedQuery = queryFnOrCollection\n dependencies = preparation.identityDeps\n }\n }\n\n // The binding calls this only when it compiles a new window collection.\n const compileQuery = () => {\n if (preparedQueryValue === unpreparedQueryValue) {\n preparedQueryValue = prepareQueryValue(\n queryFnOrCollection,\n dbClient,\n deferredCollections,\n )\n }\n return preparedQueryValue\n }\n\n binding = bindLiveQueryWindow(\n committed?.binding ?? null,\n {\n ...pageShape,\n input:\n typeof queryFnOrCollection === `function`\n ? compileQuery\n : queryFnOrCollection,\n dependencies,\n scope: dbClient,\n client: dbClient,\n queryKey,\n recompile: `identity`,\n },\n rendered?.binding,\n )\n } catch (error) {\n rejectLiveQueryInput(deferredCollections, error)\n }\n\n let currentRenderState = committed\n if (binding !== committed?.binding) {\n const newCollection = binding.collection !== committed?.binding.collection\n if (newCollection && binding.collection === rendered?.binding.collection) {\n // Sources the earlier render deferred still resume at this commit.\n for (const deferred of rendered.deferredCollections) {\n deferredCollections.add(deferred)\n }\n }\n const reusesRendered =\n newCollection && binding.collection === rendered?.binding.collection\n currentRenderState = {\n binding,\n derivedQuery,\n deferredCollections,\n // A render that never commits releases its deferred sync starts when\n // garbage collection reclaims the window collection it built. A reused\n // collection keeps the release its first render registered.\n cancelReclaimRelease: reusesRendered\n ? rendered.cancelReclaimRelease\n : newCollection && binding.ownsCollection\n ? releaseDeferredOnReclaim(binding.collection, deferredCollections)\n : undefined,\n warned: false,\n }\n // Keep a created collection for the next render only once it validated\n // and started, so a retry after a startup error builds a new one.\n if (binding.supplied) renderedRef.current = null\n else if (newCollection && binding.ownsCollection) {\n renderedRef.current = currentRenderState\n }\n }\n const renderState = currentRenderState!\n const controller = renderState.binding.controller\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n const unsubscribe = controller.subscribe(onStoreChange)\n committedRef.current = renderState\n if (renderState.binding.warning && !renderState.warned) {\n renderState.warned = true\n console.warn(renderState.binding.warning)\n }\n // A commit releases the starts itself, so it drops the reclaim release.\n renderState.cancelReclaimRelease?.()\n resumeDeferredLiveQueryCollections(renderState.deferredCollections)\n return unsubscribe\n },\n [controller, renderState],\n )\n const getSnapshot = useCallback(() => controller.getSnapshot(), [controller])\n const snapshot = useSyncExternalStore(subscribe, getSnapshot, () =>\n controller.getServerSnapshot(),\n )\n\n const fetchNextPage = useCallback(\n () => fetchNextLiveQueryWindowPage(controller),\n [controller],\n )\n\n return {\n data: snapshot.data as InferResultType<TContext>,\n get state() {\n return snapshot.state as EnabledLiveQueryReturn<TContext>[`state`]\n },\n status: snapshot.status as EnabledLiveQueryReturn<TContext>[`status`],\n isLoading: snapshot.isLoading,\n isReady: snapshot.isReady,\n isIdle: snapshot.isIdle,\n isLoadingError: snapshot.isLoadingError,\n isCrashed: snapshot.isCrashed,\n isCleanedUp: snapshot.isCleanedUp,\n isSyncError: snapshot.isSyncError,\n collection:\n snapshot.collection as EnabledLiveQueryReturn<TContext>[`collection`],\n isEnabled: snapshot.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 health: snapshot.health,\n }\n}\n"],"names":["getIdentityScope","prepareDerivedQuery","prepareQueryValue","bindLiveQueryWindow","rejectLiveQueryInput","useCallback","resumeDeferredLiveQueryCollections","useSyncExternalStore"],"mappings":";;;;;;;AAwCA;AA2FO;AAIL;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AAAmE;AACpD;AACJ;AACF;AACC;AAEV;AAGA;AACEA;AAC4B;AAC1B;AACA;AAGJ;AACA;AAEA;AACA;AAEA;AACA;AAIA;AACE;AACE;AACE;AAAe;AAOf;AACA;AAAiC;AAEjC;AAAoBC;AAClB;AAC2B;AAC3B;AACA;AAEF;AACA;AACA;AAA2B;AAC7B;AAIF;AACE;AACE;AAAqBC;AACnB;AACA;AACA;AAAA;AAGJ;AAAO;AAGT;AAAUC;AACc;AACtB;AACK;AAIG;AACN;AACO;AACC;AACR;AACW;AAAA;AAEH;AAAA;AAGZC;AAA+C;AAGjD;AACA;AACE;AACA;AAEE;AACE;AAAgC;AAClC;AAEF;AAEA;AAAqB;AACnB;AACA;AACA;AAAA;AAAA;AAAA;AAQM;AACE;AAIV;AAA4C;AAE1C;AAAsB;AACxB;AAEF;AACA;AAEA;AAAkBC;AAEd;AACA;AACA;AACE;AACA;AAAwC;AAG1C;AACAC;AACA;AAAO;AACT;AACwB;AAE1B;AACA;AAAiBC;AAAqB;AAAW;AACpC;AAGb;AAAsBF;AACyB;AAClC;AAGb;AAAO;AACU;AAEb;AAAgB;AAClB;AACiB;AACG;AACF;AACD;AACQ;AACL;AACE;AACA;AAEX;AACS;AACJ;AACK;AACrB;AACsB;AACO;AACZ;AAErB;;"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LiveQueryKey, useLiveQuery } from './useLiveQuery.cjs';
|
|
2
|
-
import { Collection, CollectionStatus, Context, DbClient, InferResultType, InitialQueryBuilder,
|
|
2
|
+
import { Collection, CollectionStatus, Context, DbClient, InferResultType, InitialQueryBuilder, LiveQueryStatusFlags, NonSingleResult, QueryBuilder } from '@tanstack/db';
|
|
3
3
|
export type UseLiveInfiniteQueryConfig<_TContext extends Context> = {
|
|
4
4
|
/**
|
|
5
5
|
* Explicit identity for queries that contain opaque functional variants or
|
|
@@ -20,28 +20,18 @@ export type UseLiveInfiniteQueryReturn<TContext extends Context> = Omit<ReturnTy
|
|
|
20
20
|
fetchNextPage: () => Promise<void>;
|
|
21
21
|
hasNextPage: boolean;
|
|
22
22
|
isFetchingNextPage: boolean;
|
|
23
|
-
error: unknown;
|
|
24
23
|
};
|
|
25
|
-
export type UseLiveInfiniteQueryReturnWithCollection<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>> = {
|
|
24
|
+
export type UseLiveInfiniteQueryReturnWithCollection<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>> = LiveQueryStatusFlags & {
|
|
26
25
|
data: Array<TResult>;
|
|
27
26
|
state: Map<TKey, TResult>;
|
|
28
27
|
collection: Collection<TResult, TKey, TUtils> & NonSingleResult;
|
|
29
28
|
status: CollectionStatus;
|
|
30
|
-
isLoading: boolean;
|
|
31
|
-
isReady: boolean;
|
|
32
|
-
persistedStatus: LiveQueryPersistedStatus;
|
|
33
|
-
isPersistedReady: boolean;
|
|
34
|
-
persistedError: unknown | undefined;
|
|
35
|
-
isIdle: boolean;
|
|
36
|
-
isError: boolean;
|
|
37
|
-
isCleanedUp: boolean;
|
|
38
29
|
isEnabled: true;
|
|
39
30
|
pages: Array<Array<TResult>>;
|
|
40
31
|
pageParams: Array<number>;
|
|
41
32
|
fetchNextPage: () => Promise<void>;
|
|
42
33
|
hasNextPage: boolean;
|
|
43
34
|
isFetchingNextPage: boolean;
|
|
44
|
-
error: unknown;
|
|
45
35
|
};
|
|
46
36
|
/**
|
|
47
37
|
* Create an infinite query using a query function with live updates.
|
|
@@ -51,8 +41,7 @@ export type UseLiveInfiniteQueryReturnWithCollection<TResult extends object, TKe
|
|
|
51
41
|
*
|
|
52
42
|
* @param queryFn - Query function that defines what data to fetch. Must include `.orderBy()` for setWindow to work.
|
|
53
43
|
* @param config - Configuration including pageSize and an optional initial page label
|
|
54
|
-
* @param deps - Deprecated array of dependencies that trigger query re-execution when changed
|
|
55
44
|
* @returns Object with pages, data, and pagination controls
|
|
56
45
|
*/
|
|
57
46
|
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>): UseLiveInfiniteQueryReturnWithCollection<TResult, TKey, TUtils>;
|
|
58
|
-
export declare function useLiveInfiniteQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>, config: UseLiveInfiniteQueryConfig<TContext
|
|
47
|
+
export declare function useLiveInfiniteQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>, config: UseLiveInfiniteQueryConfig<TContext>): UseLiveInfiniteQueryReturn<TContext>;
|