@tanstack/react-db 0.5.1 → 0.5.3
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/development.cjs +16 -0
- package/dist/cjs/development.cjs.map +1 -0
- package/dist/cjs/development.d.cts +1 -0
- package/dist/cjs/useLiveQuery.cjs +89 -117
- package/dist/cjs/useLiveQuery.cjs.map +1 -1
- package/dist/esm/development.d.ts +1 -0
- package/dist/esm/development.js +16 -0
- package/dist/esm/development.js.map +1 -0
- package/dist/esm/useLiveQuery.js +85 -113
- package/dist/esm/useLiveQuery.js.map +1 -1
- package/package.json +2 -2
- package/src/development.ts +15 -0
- package/src/useLiveQuery.ts +107 -137
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
function shouldWarnInDevelopment(disableEnvVar) {
|
|
4
|
+
try {
|
|
5
|
+
if (process.env.NODE_ENV === `production`) return false;
|
|
6
|
+
} catch {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
try {
|
|
10
|
+
return process.env[disableEnvVar] !== `1`;
|
|
11
|
+
} catch {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.shouldWarnInDevelopment = shouldWarnInDevelopment;
|
|
16
|
+
//# sourceMappingURL=development.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"development.cjs","sources":["../../src/development.ts"],"sourcesContent":["// Bundlers inline `process.env.NODE_ENV` even where `process` does not exist,\n// so it is read directly; without either, warnings stay off.\nexport function shouldWarnInDevelopment(disableEnvVar: string): boolean {\n try {\n if (process.env.NODE_ENV === `production`) return false\n } catch {\n return false\n }\n // A page may supply a `process` shim without `env`; nothing disabled it.\n try {\n return process.env[disableEnvVar] !== `1`\n } catch {\n return true\n }\n}\n"],"names":[],"mappings":";;AAEO,SAAS,wBAAwB,eAAgC;AACtE,MAAI;AACF,QAAI,QAAQ,IAAI,aAAa,aAAc,QAAO;AAAA,EACpD,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,MAAI;AACF,WAAO,QAAQ,IAAI,aAAa,MAAM;AAAA,EACxC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function shouldWarnInDevelopment(disableEnvVar: string): boolean;
|
|
@@ -5,6 +5,7 @@ const react = require("react");
|
|
|
5
5
|
const db = require("@tanstack/db");
|
|
6
6
|
const DbProvider = require("./DbProvider.cjs");
|
|
7
7
|
const liveQueryInternals = require("./live-query-internals.cjs");
|
|
8
|
+
const development = require("./development.cjs");
|
|
8
9
|
const DEFAULT_GC_TIME_MS = 1;
|
|
9
10
|
const DEFAULT_SUSPENSE_GC_TIME_MS = 5e3;
|
|
10
11
|
const DERIVED_IDENTITY_SINGLE_RENDER_WARN_MS = 16;
|
|
@@ -50,7 +51,7 @@ function releaseSuspenseCollection(collections, queryHash, collection) {
|
|
|
50
51
|
entry.removeStatusListeners();
|
|
51
52
|
}
|
|
52
53
|
function warnDeprecatedDepsArray(hookName = `useLiveQuery`) {
|
|
53
|
-
if (!shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_DEPRECATION_WARNINGS`)) {
|
|
54
|
+
if (!development.shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_DEPRECATION_WARNINGS`)) {
|
|
54
55
|
return;
|
|
55
56
|
}
|
|
56
57
|
const callsite = getWarningCallsite(4);
|
|
@@ -63,12 +64,6 @@ function warnDeprecatedDepsArray(hookName = `useLiveQuery`) {
|
|
|
63
64
|
`[${hookName}] The dependency-array form is deprecated and will be removed in 1.0. Use ${replacement} instead. Provide queryKey only for functional/opaque queries or to avoid deriving identity from structured query IR on render.`
|
|
64
65
|
);
|
|
65
66
|
}
|
|
66
|
-
function shouldWarnInDevelopment(disableEnvVar) {
|
|
67
|
-
if (typeof process === `undefined`) {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
return process.env.NODE_ENV !== `production` && process.env[disableEnvVar] !== `1`;
|
|
71
|
-
}
|
|
72
67
|
function getCurrentTime() {
|
|
73
68
|
return typeof performance !== `undefined` && typeof performance.now === `function` ? performance.now() : Date.now();
|
|
74
69
|
}
|
|
@@ -82,7 +77,7 @@ function getWarningCallsite(stackIndex) {
|
|
|
82
77
|
return userFrame?.trim() ?? lines[stackIndex]?.trim() ?? stack;
|
|
83
78
|
}
|
|
84
79
|
function warnDerivedIdentityHotPath(profiler, durationMs) {
|
|
85
|
-
if (profiler.warned || !shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)) {
|
|
80
|
+
if (profiler.warned || !development.shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)) {
|
|
86
81
|
return;
|
|
87
82
|
}
|
|
88
83
|
const isSlowSingleRender = durationMs >= DERIVED_IDENTITY_SINGLE_RENDER_WARN_MS;
|
|
@@ -112,7 +107,7 @@ function prepareQueryValue(value, dbClient, deferredCollections) {
|
|
|
112
107
|
return db.prepareLiveQueryValue(value, dbClient, deferredCollections);
|
|
113
108
|
}
|
|
114
109
|
function prepareDerivedQuery(value, dbClient, profiler, deferredCollections) {
|
|
115
|
-
const shouldProfile = shouldWarnInDevelopment(
|
|
110
|
+
const shouldProfile = development.shouldWarnInDevelopment(
|
|
116
111
|
`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`
|
|
117
112
|
);
|
|
118
113
|
const start = shouldProfile ? getCurrentTime() : 0;
|
|
@@ -140,7 +135,7 @@ function prepareDerivedQuery(value, dbClient, profiler, deferredCollections) {
|
|
|
140
135
|
}
|
|
141
136
|
}
|
|
142
137
|
function warnUnhashableDerivedIdentity(error) {
|
|
143
|
-
if (!shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)) {
|
|
138
|
+
if (!development.shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)) {
|
|
144
139
|
return;
|
|
145
140
|
}
|
|
146
141
|
const callsite = getWarningCallsite(4);
|
|
@@ -152,67 +147,40 @@ function warnUnhashableDerivedIdentity(error) {
|
|
|
152
147
|
`[useLiveQuery] This query cannot derive a stable identity because ${error.reason} at ${error.path}. It will keep the legacy mount-stable behavior for now. Add queryKey: [...] to make captured values reactive. Unhashable queries without queryKey will throw in 1.0.`
|
|
153
148
|
);
|
|
154
149
|
}
|
|
155
|
-
function createCollectionFromPreparedQuery(value, defaultGcTime = DEFAULT_GC_TIME_MS) {
|
|
156
|
-
if (value === void 0 || value === null) {
|
|
157
|
-
return null;
|
|
158
|
-
}
|
|
159
|
-
if (db.isCollection(value)) {
|
|
160
|
-
value.startSyncImmediate();
|
|
161
|
-
return value;
|
|
162
|
-
}
|
|
163
|
-
if (value instanceof db.BaseQueryBuilder) {
|
|
164
|
-
return db.createLiveQueryCollection({
|
|
165
|
-
query: value,
|
|
166
|
-
startSync: true,
|
|
167
|
-
gcTime: defaultGcTime
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
if (typeof value === `object`) {
|
|
171
|
-
return db.createLiveQueryCollection({
|
|
172
|
-
startSync: true,
|
|
173
|
-
gcTime: defaultGcTime,
|
|
174
|
-
...value
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
throw new Error(
|
|
178
|
-
`useLiveQuery callback must return a QueryBuilder, LiveQueryCollectionConfig, Collection, undefined, or null. Got: ${typeof value}`
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
150
|
function useLiveQuery(configOrQueryOrCollection, deps) {
|
|
182
151
|
return useLiveQueryImpl(configOrQueryOrCollection, deps);
|
|
183
152
|
}
|
|
184
153
|
function useLiveQueryForSuspense(configOrQueryOrCollection, deps) {
|
|
185
154
|
return useLiveQueryImpl(configOrQueryOrCollection, deps, true);
|
|
186
155
|
}
|
|
156
|
+
function createHookInstance(dbClient) {
|
|
157
|
+
return {
|
|
158
|
+
collection: null,
|
|
159
|
+
deps: null,
|
|
160
|
+
config: null,
|
|
161
|
+
client: dbClient,
|
|
162
|
+
legacyUnhashableIdentity: [`legacy-unhashable`],
|
|
163
|
+
derivedIdentityProfiler: {
|
|
164
|
+
renderCount: 0,
|
|
165
|
+
totalMs: 0,
|
|
166
|
+
maxMs: 0,
|
|
167
|
+
warned: false
|
|
168
|
+
},
|
|
169
|
+
deferredCollections: /* @__PURE__ */ new Set(),
|
|
170
|
+
observer: null,
|
|
171
|
+
queryHash: void 0,
|
|
172
|
+
suspenseKey: void 0,
|
|
173
|
+
identityError: void 0,
|
|
174
|
+
subscribe: null
|
|
175
|
+
};
|
|
176
|
+
}
|
|
187
177
|
function useLiveQueryImpl(configOrQueryOrCollection, deps, forSuspense = false) {
|
|
188
178
|
const contextDbClient = DbProvider.useOptionalDbClient();
|
|
189
179
|
const inputIsCollection = db.isCollection(configOrQueryOrCollection);
|
|
190
180
|
const dbClient = inputIsCollection ? contextDbClient : getExplicitDbClient(configOrQueryOrCollection) ?? contextDbClient;
|
|
191
181
|
const resolvedDeps = deps ?? [];
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
);
|
|
195
|
-
const depsRef = react.useRef(null);
|
|
196
|
-
const configRef = react.useRef(null);
|
|
197
|
-
const clientRef = react.useRef(dbClient);
|
|
198
|
-
const legacyUnhashableIdentityRef = react.useRef([
|
|
199
|
-
`legacy-unhashable`
|
|
200
|
-
]);
|
|
201
|
-
const derivedIdentityProfilerRef = react.useRef({
|
|
202
|
-
renderCount: 0,
|
|
203
|
-
totalMs: 0,
|
|
204
|
-
maxMs: 0,
|
|
205
|
-
warned: false
|
|
206
|
-
});
|
|
207
|
-
const deferredCollectionsRef = react.useRef(
|
|
208
|
-
/* @__PURE__ */ new Set()
|
|
209
|
-
);
|
|
210
|
-
const observerRef = react.useRef(
|
|
211
|
-
null
|
|
212
|
-
);
|
|
213
|
-
const queryHashRef = react.useRef(void 0);
|
|
214
|
-
const suspenseKeyRef = react.useRef(void 0);
|
|
215
|
-
const identityErrorRef = react.useRef(void 0);
|
|
182
|
+
const instanceRef = react.useRef(null);
|
|
183
|
+
const instance = instanceRef.current ??= createHookInstance(dbClient);
|
|
216
184
|
const queryKey = !inputIsCollection ? getExplicitQueryKey(configOrQueryOrCollection) : void 0;
|
|
217
185
|
let preparedQueryValue = unpreparedQueryValue;
|
|
218
186
|
let identityDeps;
|
|
@@ -223,22 +191,23 @@ function useLiveQueryImpl(configOrQueryOrCollection, deps, forSuspense = false)
|
|
|
223
191
|
streamIdentity = [`queryKey`, queryKey];
|
|
224
192
|
} else if (deps !== void 0) {
|
|
225
193
|
identityDeps = resolvedDeps;
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
194
|
+
if (dbClient || forSuspense || development.shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`))
|
|
195
|
+
try {
|
|
196
|
+
preparedQueryValue = prepareQueryValue(
|
|
197
|
+
configOrQueryOrCollection,
|
|
198
|
+
dbClient,
|
|
199
|
+
instance.deferredCollections
|
|
200
|
+
);
|
|
201
|
+
streamIdentity = [
|
|
202
|
+
`deps`,
|
|
203
|
+
resolvedDeps,
|
|
204
|
+
db.getPreparedLiveQueryIdentity(preparedQueryValue)
|
|
205
|
+
];
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if (!(error instanceof db.UnhashableQueryIRError)) throw error;
|
|
208
|
+
warnUnhashableDerivedIdentity(error);
|
|
209
|
+
identityError = error;
|
|
210
|
+
}
|
|
242
211
|
} else if (inputIsCollection) {
|
|
243
212
|
identityDeps = [];
|
|
244
213
|
streamIdentity = [`collection`, configOrQueryOrCollection.id];
|
|
@@ -246,8 +215,8 @@ function useLiveQueryImpl(configOrQueryOrCollection, deps, forSuspense = false)
|
|
|
246
215
|
const preparation = prepareDerivedQuery(
|
|
247
216
|
configOrQueryOrCollection,
|
|
248
217
|
dbClient,
|
|
249
|
-
|
|
250
|
-
|
|
218
|
+
instance.derivedIdentityProfiler,
|
|
219
|
+
instance.deferredCollections
|
|
251
220
|
);
|
|
252
221
|
preparedQueryValue = preparation.value;
|
|
253
222
|
if (preparation.status === `hashable`) {
|
|
@@ -255,7 +224,7 @@ function useLiveQueryImpl(configOrQueryOrCollection, deps, forSuspense = false)
|
|
|
255
224
|
streamIdentity = preparation.identityDeps;
|
|
256
225
|
} else {
|
|
257
226
|
warnUnhashableDerivedIdentity(preparation.error);
|
|
258
|
-
identityDeps =
|
|
227
|
+
identityDeps = instance.legacyUnhashableIdentity;
|
|
259
228
|
identityError = preparation.error;
|
|
260
229
|
}
|
|
261
230
|
}
|
|
@@ -275,30 +244,31 @@ function useLiveQueryImpl(configOrQueryOrCollection, deps, forSuspense = false)
|
|
|
275
244
|
if (deps !== void 0) {
|
|
276
245
|
warnDeprecatedDepsArray();
|
|
277
246
|
}
|
|
278
|
-
const canReuseSuspenseKey = forSuspense && !inputIsCollection && queryHash !== void 0 && !dbClient &&
|
|
247
|
+
const canReuseSuspenseKey = forSuspense && !inputIsCollection && queryHash !== void 0 && !dbClient && instance.collection !== null && instance.client === dbClient && instance.queryHash === queryHash && instance.suspenseKey !== void 0;
|
|
279
248
|
if (forSuspense && !inputIsCollection && queryHash && !dbClient && !canReuseSuspenseKey && preparedQueryValue === unpreparedQueryValue) {
|
|
280
249
|
preparedQueryValue = prepareQueryValue(
|
|
281
250
|
configOrQueryOrCollection,
|
|
282
251
|
dbClient,
|
|
283
|
-
|
|
252
|
+
instance.deferredCollections
|
|
284
253
|
);
|
|
285
254
|
}
|
|
286
|
-
const suspenseKey = queryHash && !dbClient ? canReuseSuspenseKey ?
|
|
255
|
+
const suspenseKey = queryHash && !dbClient ? canReuseSuspenseKey ? instance.suspenseKey : getUnscopedSuspenseKey(preparedQueryValue, queryHash) : queryHash;
|
|
287
256
|
const suspenseCollections = forSuspense && !inputIsCollection && suspenseKey ? getSuspenseCollections(dbClient) : void 0;
|
|
288
257
|
const suspenseEntry = suspenseKey ? suspenseCollections?.get(suspenseKey) : void 0;
|
|
289
258
|
const suspenseCollection = suspenseEntry?.collection;
|
|
290
|
-
const identityChanged =
|
|
291
|
-
const needsNewCollection = !
|
|
259
|
+
const identityChanged = instance.deps === null || (deps !== void 0 ? instance.deps.length !== identityDeps.length || instance.deps.some((dep, index) => dep !== identityDeps[index]) : !db.deepEquals(instance.deps, identityDeps));
|
|
260
|
+
const needsNewCollection = !instance.collection || inputIsCollection && instance.config !== configOrQueryOrCollection || !inputIsCollection && (instance.client !== dbClient || identityChanged);
|
|
292
261
|
const resumeDeferredCollections = () => {
|
|
293
|
-
|
|
262
|
+
if (instance.deferredCollections.size === 0) return;
|
|
263
|
+
for (const collection of instance.deferredCollections) {
|
|
294
264
|
collection._resumeSyncStart();
|
|
295
265
|
}
|
|
296
|
-
|
|
266
|
+
instance.deferredCollections.clear();
|
|
297
267
|
};
|
|
298
268
|
if (needsNewCollection) {
|
|
299
269
|
if (inputIsCollection) {
|
|
300
270
|
const syncMode = configOrQueryOrCollection.config?.syncMode;
|
|
301
|
-
if (syncMode === `on-demand` && shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)) {
|
|
271
|
+
if (syncMode === `on-demand` && development.shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)) {
|
|
302
272
|
console.warn(
|
|
303
273
|
`[useLiveQuery] Warning: Passing a collection with syncMode "on-demand" directly to useLiveQuery will not load any data. In on-demand mode, data is only loaded when queries with predicates request it.
|
|
304
274
|
|
|
@@ -309,25 +279,26 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
|
|
|
309
279
|
);
|
|
310
280
|
}
|
|
311
281
|
configOrQueryOrCollection.startSyncImmediate();
|
|
312
|
-
|
|
313
|
-
|
|
282
|
+
instance.collection = configOrQueryOrCollection;
|
|
283
|
+
instance.config = configOrQueryOrCollection;
|
|
314
284
|
} else {
|
|
315
285
|
if (suspenseCollection) {
|
|
316
|
-
|
|
286
|
+
instance.collection = suspenseCollection;
|
|
317
287
|
} else {
|
|
318
288
|
if (preparedQueryValue === unpreparedQueryValue) {
|
|
319
289
|
preparedQueryValue = prepareQueryValue(
|
|
320
290
|
configOrQueryOrCollection,
|
|
321
291
|
dbClient,
|
|
322
|
-
|
|
292
|
+
instance.deferredCollections
|
|
323
293
|
);
|
|
324
294
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
295
|
+
instance.collection = db.resolveLiveQueryValue(preparedQueryValue, {
|
|
296
|
+
gcTime: forSuspense ? DEFAULT_SUSPENSE_GC_TIME_MS : DEFAULT_GC_TIME_MS,
|
|
297
|
+
// Hydration and Suspense key the live-query Collection by identity.
|
|
298
|
+
pool: !forSuspense && !dbClient
|
|
299
|
+
});
|
|
300
|
+
if (suspenseCollections && suspenseKey && instance.collection) {
|
|
301
|
+
const collection = instance.collection;
|
|
331
302
|
const removeCleanupListener = collection.on(
|
|
332
303
|
`status:cleaned-up`,
|
|
333
304
|
() => releaseSuspenseCollection(
|
|
@@ -356,42 +327,43 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
|
|
|
356
327
|
suspenseCollections.set(suspenseKey, entry);
|
|
357
328
|
}
|
|
358
329
|
}
|
|
359
|
-
|
|
360
|
-
|
|
330
|
+
instance.config = configOrQueryOrCollection;
|
|
331
|
+
instance.deps = [...identityDeps];
|
|
361
332
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
333
|
+
instance.client = dbClient;
|
|
334
|
+
instance.queryHash = queryHash;
|
|
335
|
+
instance.suspenseKey = suspenseKey;
|
|
336
|
+
instance.identityError = identityError;
|
|
366
337
|
}
|
|
367
338
|
if (needsNewCollection) {
|
|
368
|
-
|
|
339
|
+
instance.observer = db.createLiveQueryObserver(instance.collection, {
|
|
369
340
|
mode: `wholesale`,
|
|
370
341
|
client: dbClient,
|
|
371
|
-
queryHash:
|
|
342
|
+
queryHash: instance.queryHash,
|
|
372
343
|
onPreload: resumeDeferredCollections
|
|
373
344
|
});
|
|
374
345
|
}
|
|
375
|
-
const observer =
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const unsubscribe = observer.subscribe(() => onStoreChange());
|
|
346
|
+
const observer = instance.observer;
|
|
347
|
+
if (!instance.subscribe || needsNewCollection) {
|
|
348
|
+
instance.subscribe = (onStoreChange) => {
|
|
349
|
+
const unsubscribe = observer.subscribe(onStoreChange);
|
|
380
350
|
resumeDeferredCollections();
|
|
381
351
|
return unsubscribe;
|
|
382
352
|
};
|
|
383
353
|
}
|
|
384
354
|
const returned = react.useSyncExternalStore(
|
|
385
|
-
|
|
355
|
+
instance.subscribe,
|
|
386
356
|
() => observer.getSnapshot(),
|
|
387
357
|
() => observer.getServerSnapshot()
|
|
388
358
|
);
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
359
|
+
if (forSuspense) {
|
|
360
|
+
liveQueryInternals.setLiveQueryResultInfo(returned, {
|
|
361
|
+
client: dbClient,
|
|
362
|
+
queryHash: instance.queryHash,
|
|
363
|
+
identityError: instance.identityError,
|
|
364
|
+
observer
|
|
365
|
+
});
|
|
366
|
+
}
|
|
395
367
|
return returned;
|
|
396
368
|
}
|
|
397
369
|
exports.prepareDerivedQuery = prepareDerivedQuery;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLiveQuery.cjs","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["'use client'\n\nimport { useRef, useSyncExternalStore } from 'react'\nimport {\n BaseQueryBuilder,\n IR,\n UnhashableQueryIRError,\n createLiveQueryCollection,\n createLiveQueryObserver,\n deepEquals,\n getPreparedLiveQueryIdentity,\n getStableValueHash,\n isCollection,\n prepareLiveQueryValue,\n} from '@tanstack/db'\nimport { useOptionalDbClient } from './DbProvider'\nimport { setLiveQueryResultInfo } from './live-query-internals'\nimport type {\n Collection,\n CollectionImpl,\n CollectionStatus,\n Context,\n DbClient,\n GetResult,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n LiveQueryObserver,\n LiveQueryPersistedStatus,\n NonSingleResult,\n QueryBuilder,\n SingleResult,\n} from '@tanstack/db'\n\nconst DEFAULT_GC_TIME_MS = 1 // Live queries created by useLiveQuery are cleaned up immediately (0 disables GC)\n// Suspense renders can be abandoned before React subscribes. Keep their\n// collection briefly so a nearby retry can commit without starting over.\nconst DEFAULT_SUSPENSE_GC_TIME_MS = 5000\nconst DERIVED_IDENTITY_SINGLE_RENDER_WARN_MS = 16\nconst DERIVED_IDENTITY_RENDER_COUNT_WARN_THRESHOLD = 10\nconst DERIVED_IDENTITY_TOTAL_WARN_MS = 50\nconst warnedDepsCallsites = new Set<string>()\nconst warnedDerivedIdentityCallsites = new Set<string>()\nconst warnedUnhashableIdentityCallsites = new Set<string>()\nconst unpreparedQueryValue = Symbol(`unpreparedQueryValue`)\n\ntype SuspenseCollection = Collection<object, string | number, {}>\ntype SuspenseCollectionEntry = {\n collection: SuspenseCollection\n removeStatusListeners: () => void\n}\n\nconst unscopedSuspenseCollections = new Map<string, SuspenseCollectionEntry>()\nconst suspenseCollectionsByClient = new WeakMap<\n DbClient,\n Map<string, SuspenseCollectionEntry>\n>()\nconst suspenseSourceIds = new WeakMap<object, number>()\nlet nextSuspenseSourceId = 0\n\nfunction getSuspenseSourceId(source: object): number {\n let id = suspenseSourceIds.get(source)\n if (id === undefined) {\n id = ++nextSuspenseSourceId\n suspenseSourceIds.set(source, id)\n }\n return id\n}\n\nfunction getUnscopedSuspenseKey(\n preparedValue: unknown,\n queryHash: string,\n): string {\n const query =\n preparedValue instanceof BaseQueryBuilder\n ? preparedValue\n : preparedValue &&\n typeof preparedValue === `object` &&\n `query` in preparedValue &&\n preparedValue.query instanceof BaseQueryBuilder\n ? preparedValue.query\n : undefined\n if (!query) return queryHash\n const sourceIds = IR.collectCollectionSources(query._getQuery()).map(\n ({ collection }) => getSuspenseSourceId(collection),\n )\n return `${sourceIds.join(`,`)}:${queryHash}`\n}\n\nfunction getSuspenseCollections(\n client: DbClient | undefined,\n): Map<string, SuspenseCollectionEntry> {\n if (!client) return unscopedSuspenseCollections\n let collections = suspenseCollectionsByClient.get(client)\n if (!collections) {\n collections = new Map()\n suspenseCollectionsByClient.set(client, collections)\n }\n return collections\n}\n\nfunction releaseSuspenseCollection(\n collections: Map<string, SuspenseCollectionEntry>,\n queryHash: string,\n collection: SuspenseCollection,\n): void {\n const entry = collections.get(queryHash)\n if (entry?.collection !== collection) return\n collections.delete(queryHash)\n entry.removeStatusListeners()\n}\n\nexport type DerivedIdentityProfiler = {\n renderCount: number\n totalMs: number\n maxMs: number\n warned: boolean\n}\n\nexport type UseLiveQueryStatus = CollectionStatus | `disabled`\nexport type LiveQueryKey = ReadonlyArray<unknown>\ntype UseLiveQueryConfigOptions<TContext extends Context> = Omit<\n LiveQueryCollectionConfig<TContext>,\n `query`\n> & {\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}\n\ntype ConfiguredQueryBuilder<TContext extends Context> = Extract<\n LiveQueryCollectionConfig<TContext>[`query`],\n QueryBuilder<TContext>\n>\n\nexport type UseLiveQueryConfig<TContext extends Context> =\n UseLiveQueryConfigOptions<TContext> &\n Pick<LiveQueryCollectionConfig<TContext>, `query`>\n\nexport type ConditionalUseLiveQueryConfig<TContext extends Context> =\n UseLiveQueryConfigOptions<TContext> & {\n query:\n | ConfiguredQueryBuilder<TContext>\n | ((\n q: InitialQueryBuilder,\n ) => ConfiguredQueryBuilder<TContext> | undefined | null)\n }\n\nexport function warnDeprecatedDepsArray(\n hookName: `useLiveQuery` | `useLiveInfiniteQuery` = `useLiveQuery`,\n): void {\n if (!shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_DEPRECATION_WARNINGS`)) {\n return\n }\n\n const callsite = getWarningCallsite(4)\n if (warnedDepsCallsites.has(callsite)) {\n return\n }\n warnedDepsCallsites.add(callsite)\n const replacement =\n hookName === `useLiveQuery`\n ? `useLiveQuery({ query })`\n : `useLiveInfiniteQuery(query, { queryKey })`\n console.warn(\n `[${hookName}] The dependency-array form is deprecated and will be removed in 1.0. Use ${replacement} instead. Provide queryKey only for functional/opaque queries or to avoid deriving identity from structured query IR on render.`,\n )\n}\n\nfunction shouldWarnInDevelopment(disableEnvVar: string): boolean {\n if (typeof process === `undefined`) {\n return false\n }\n\n return (\n process.env.NODE_ENV !== `production` && process.env[disableEnvVar] !== `1`\n )\n}\n\nfunction getCurrentTime(): number {\n return typeof performance !== `undefined` &&\n typeof performance.now === `function`\n ? performance.now()\n : Date.now()\n}\n\nfunction getWarningCallsite(stackIndex: number): string {\n const stack = new Error().stack ?? `unknown`\n const lines = stack.split(`\\n`)\n const userFrame = lines\n .slice(1)\n .find(\n (line) =>\n !line.includes(`useLiveQuery.ts`) &&\n !line.includes(`useLiveSuspenseQuery.ts`) &&\n !line.includes(`useLiveInfiniteQuery.ts`),\n )\n return userFrame?.trim() ?? lines[stackIndex]?.trim() ?? stack\n}\n\nfunction warnDerivedIdentityHotPath(\n profiler: DerivedIdentityProfiler,\n durationMs: number,\n): void {\n if (\n profiler.warned ||\n !shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)\n ) {\n return\n }\n\n const isSlowSingleRender =\n durationMs >= DERIVED_IDENTITY_SINGLE_RENDER_WARN_MS\n const isHotRenderPath =\n profiler.renderCount >= DERIVED_IDENTITY_RENDER_COUNT_WARN_THRESHOLD &&\n profiler.totalMs >= DERIVED_IDENTITY_TOTAL_WARN_MS\n\n if (!isSlowSingleRender && !isHotRenderPath) {\n return\n }\n\n const callsite = getWarningCallsite(5)\n if (warnedDerivedIdentityCallsites.has(callsite)) {\n profiler.warned = true\n return\n }\n\n warnedDerivedIdentityCallsites.add(callsite)\n profiler.warned = true\n\n const reason = isSlowSingleRender\n ? `one render took ${durationMs.toFixed(1)}ms`\n : `${profiler.renderCount} renders took ${profiler.totalMs.toFixed(1)}ms`\n\n console.warn(\n `[useLiveQuery] Deriving live query identity from structured query IR is running on a hot render path (${reason}, max ${profiler.maxMs.toFixed(1)}ms). ` +\n `Provide an explicit queryKey to skip rebuilding and hashing the IR on every render: useLiveQuery({ queryKey: [...], query }).`,\n )\n}\n\nfunction getExplicitQueryKey(value: unknown): LiveQueryKey | undefined {\n return value &&\n typeof value === `object` &&\n Array.isArray((value as { queryKey?: unknown }).queryKey)\n ? (value as { queryKey: LiveQueryKey }).queryKey\n : undefined\n}\n\nfunction getExplicitDbClient(value: unknown): DbClient | undefined {\n return value &&\n typeof value === `object` &&\n `client` in value &&\n (value as { client?: unknown }).client !== undefined\n ? (value as { client: DbClient }).client\n : undefined\n}\n\nexport function prepareQueryValue(\n value: unknown,\n dbClient: DbClient | undefined,\n deferredCollections: Set<CollectionImpl<any, string | number, any, any, any>>,\n): unknown {\n return prepareLiveQueryValue(value, dbClient, deferredCollections)\n}\n\ntype DerivedQueryPreparation =\n | {\n status: `hashable`\n value: unknown\n identityDeps: Array<unknown>\n }\n | {\n status: `unhashable`\n value: unknown\n error: UnhashableQueryIRError\n }\n\nexport function prepareDerivedQuery(\n value: unknown,\n dbClient: DbClient | undefined,\n profiler: DerivedIdentityProfiler,\n deferredCollections: Set<CollectionImpl<any, string | number, any, any, any>>,\n): DerivedQueryPreparation {\n const shouldProfile = shouldWarnInDevelopment(\n `TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`,\n )\n const start = shouldProfile ? getCurrentTime() : 0\n const preparedValue = prepareQueryValue(value, dbClient, deferredCollections)\n\n try {\n const identity = getPreparedLiveQueryIdentity(preparedValue)\n return {\n status: `hashable`,\n value: preparedValue,\n identityDeps: [`derived`, identity],\n }\n } catch (error) {\n if (error instanceof UnhashableQueryIRError) {\n return { status: `unhashable`, value: preparedValue, error }\n }\n\n throw error\n } finally {\n if (shouldProfile) {\n const durationMs = getCurrentTime() - start\n profiler.renderCount += 1\n profiler.totalMs += durationMs\n profiler.maxMs = Math.max(profiler.maxMs, durationMs)\n warnDerivedIdentityHotPath(profiler, durationMs)\n }\n }\n}\n\nexport function warnUnhashableDerivedIdentity(\n error: UnhashableQueryIRError,\n): void {\n if (!shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)) {\n return\n }\n\n const callsite = getWarningCallsite(4)\n if (warnedUnhashableIdentityCallsites.has(callsite)) {\n return\n }\n warnedUnhashableIdentityCallsites.add(callsite)\n\n console.warn(\n `[useLiveQuery] This query cannot derive a stable identity because ${error.reason} at ${error.path}. ` +\n `It will keep the legacy mount-stable behavior for now. Add queryKey: [...] to make captured values reactive. ` +\n `Unhashable queries without queryKey will throw in 1.0.`,\n )\n}\n\nfunction createCollectionFromPreparedQuery(\n value: unknown,\n defaultGcTime = DEFAULT_GC_TIME_MS,\n) {\n if (value === undefined || value === null) {\n return null\n }\n\n if (isCollection(value)) {\n value.startSyncImmediate()\n return value\n }\n\n if (value instanceof BaseQueryBuilder) {\n return createLiveQueryCollection({\n query: value,\n startSync: true,\n gcTime: defaultGcTime,\n })\n }\n\n if (typeof value === `object`) {\n return createLiveQueryCollection({\n startSync: true,\n gcTime: defaultGcTime,\n ...(value as LiveQueryCollectionConfig<any>),\n })\n }\n\n throw new Error(\n `useLiveQuery callback must return a QueryBuilder, LiveQueryCollectionConfig, Collection, undefined, or null. Got: ${typeof value}`,\n )\n}\n\n/**\n * Create a live query using a query function.\n * @param queryFn - Query function that defines what data to fetch\n * @param deps - Deprecated array of dependencies that trigger query re-execution when changed\n * @returns Object with reactive data, state, and status information\n * @example\n * // Prefer config object syntax\n * const { data, isLoading } = useLiveQuery({\n * query: (q) =>\n * q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.completed, false))\n * .select(({ todos }) => ({ id: todos.id, text: todos.text }))\n * })\n *\n * @example\n * // Single result query\n * const { data } = useLiveQuery({\n * query: (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.id, 1))\n * .findOne()\n * })\n *\n * @example\n * // Structured captured values are included in derived query identity\n * const { data, state } = useLiveQuery({\n * query: (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => gt(todos.priority, minPriority)),\n * })\n *\n * @example\n * // Return undefined or null to disable a query\n * const { data, isEnabled } = useLiveQuery({\n * query: (q) => {\n * if (!userId) return undefined\n * return q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.userId, userId))\n * },\n * })\n *\n * @example\n * // Join pattern\n * const { data } = useLiveQuery({\n * query: (q) =>\n * q.from({ issues: issueCollection })\n * .join({ persons: personCollection }, ({ issues, persons }) =>\n * eq(issues.userId, persons.id)\n * )\n * .select(({ issues, persons }) => ({\n * id: issues.id,\n * title: issues.title,\n * userName: persons.name\n * }))\n * })\n *\n * @example\n * // Handle loading and error states\n * const { data, isLoading, isError, status } = useLiveQuery({\n * query: (q) => q.from({ todos: todoCollection })\n * })\n *\n * if (isLoading) return <div>Loading...</div>\n * if (isError) return <div>Error: {status}</div>\n *\n * return (\n * <ul>\n * {data.map(todo => <li key={todo.id}>{todo.text}</li>)}\n * </ul>\n * )\n */\n// Overload 1: Accept query function that always returns QueryBuilder\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n deps?: Array<unknown>,\n): {\n state: Map<string | number, GetResult<TContext>>\n data: InferResultType<TContext>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus // Can't be disabled if always returns QueryBuilder\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 // Always true if always returns QueryBuilder\n}\n\n// Overload 2: Accept query function that can return undefined/null\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext> | undefined | null,\n deps?: Array<unknown>,\n): {\n state: Map<string | number, GetResult<TContext>> | undefined\n data: InferResultType<TContext> | undefined\n collection: Collection<GetResult<TContext>, string | number, {}> | undefined\n status: UseLiveQueryStatus\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: boolean\n}\n\n// Overload 3: Accept query function that can return LiveQueryCollectionConfig\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => LiveQueryCollectionConfig<TContext> | undefined | null,\n deps?: Array<unknown>,\n): {\n state: Map<string | number, GetResult<TContext>> | undefined\n data: InferResultType<TContext> | undefined\n collection: Collection<GetResult<TContext>, string | number, {}> | undefined\n status: UseLiveQueryStatus\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: boolean\n}\n\n// Overload 4: Accept query function that can return Collection\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => Collection<TResult, TKey, TUtils> | undefined | null,\n deps?: Array<unknown>,\n): {\n state: Map<TKey, TResult> | undefined\n data: Array<TResult> | undefined\n collection: Collection<TResult, TKey, TUtils> | undefined\n status: UseLiveQueryStatus\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: boolean\n}\n\n// Overload 5: Accept query function that can return all types\nexport function useLiveQuery<\n TContext extends Context,\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n queryFn: (\n q: InitialQueryBuilder,\n ) =>\n | QueryBuilder<TContext>\n | LiveQueryCollectionConfig<TContext>\n | Collection<TResult, TKey, TUtils>\n | undefined\n | null,\n deps?: Array<unknown>,\n): {\n state:\n | Map<string | number, GetResult<TContext>>\n | Map<TKey, TResult>\n | undefined\n data: InferResultType<TContext> | Array<TResult> | undefined\n collection:\n | Collection<GetResult<TContext>, string | number, {}>\n | Collection<TResult, TKey, TUtils>\n | undefined\n status: UseLiveQueryStatus\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: boolean\n}\n\n/**\n * Create a live query using configuration object\n * @param config - Configuration object with query and options\n * @param deps - Deprecated array of dependencies that trigger query re-execution when changed\n * @returns Object with reactive data, state, and status information\n * @example\n * // Basic config object usage\n * const { data, status } = useLiveQuery({\n * query: (q) => q.from({ todos: todosCollection }),\n * gcTime: 60000\n * })\n *\n * @example\n * // With query builder and options\n * const queryBuilder = new Query()\n * .from({ persons: collection })\n * .where(({ persons }) => gt(persons.age, 30))\n * .select(({ persons }) => ({ id: persons.id, name: persons.name }))\n *\n * const { data, isReady } = useLiveQuery({\n * query: queryBuilder,\n * })\n *\n * @example\n * // Handle all states uniformly\n * const { data, isLoading, isReady, isError } = useLiveQuery({\n * query: (q) => q.from({ items: itemCollection })\n * })\n *\n * if (isLoading) return <div>Loading...</div>\n * if (isError) return <div>Something went wrong</div>\n * if (!isReady) return <div>Preparing...</div>\n *\n * return <div>{data.length} items loaded</div>\n */\n// Overload 6: Accept config object\nexport function useLiveQuery<TContext extends Context>(\n config: UseLiveQueryConfig<TContext>,\n): {\n state: Map<string | number, GetResult<TContext>>\n data: InferResultType<TContext>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus // Can't be disabled when query always returns a builder\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 // Always true when query always returns a builder\n}\n\n// Overload 7: Accept config object with a query that can return undefined/null\nexport function useLiveQuery<TContext extends Context>(\n config: ConditionalUseLiveQueryConfig<TContext>,\n): {\n state: Map<string | number, GetResult<TContext>> | undefined\n data: InferResultType<TContext> | undefined\n collection: Collection<GetResult<TContext>, string | number, {}> | undefined\n status: UseLiveQueryStatus\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: boolean\n}\n\n// Overload 8: Accept config object with legacy deps\nexport function useLiveQuery<TContext extends Context>(\n config: LiveQueryCollectionConfig<TContext>,\n deps?: Array<unknown>,\n): {\n state: Map<string | number, GetResult<TContext>>\n data: InferResultType<TContext>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus // Can't be disabled when query always returns a builder\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 // Always true when query always returns a builder\n}\n\n// Overload 9: Accept config object with legacy deps and a query that can return undefined/null\nexport function useLiveQuery<TContext extends Context>(\n config: ConditionalUseLiveQueryConfig<TContext>,\n deps: Array<unknown>,\n): {\n state: Map<string | number, GetResult<TContext>> | undefined\n data: InferResultType<TContext> | undefined\n collection: Collection<GetResult<TContext>, string | number, {}> | undefined\n status: UseLiveQueryStatus\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: boolean\n}\n\n/**\n * Subscribe to an existing live query collection\n * @param liveQueryCollection - Pre-created live query collection to subscribe to\n * @returns Object with reactive data, state, and status information\n * @example\n * // Using pre-created live query collection\n * const myLiveQuery = createLiveQueryCollection((q) =>\n * q.from({ todos: todosCollection }).where(({ todos }) => eq(todos.active, true))\n * )\n * const { data, collection } = useLiveQuery(myLiveQuery)\n *\n * @example\n * // Access collection methods directly\n * const { data, collection, isReady } = useLiveQuery(existingCollection)\n *\n * // Use collection for mutations\n * const handleToggle = (id) => {\n * collection.update(id, draft => { draft.completed = !draft.completed })\n * }\n *\n * @example\n * // Handle states consistently\n * const { data, isLoading, isError } = useLiveQuery(sharedCollection)\n *\n * if (isLoading) return <div>Loading...</div>\n * if (isError) return <div>Error loading data</div>\n *\n * return <div>{data.map(item => <Item key={item.id} {...item} />)}</div>\n */\n// Overload 10: Accept pre-created live query collection\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult,\n): {\n state: Map<TKey, TResult>\n data: Array<TResult>\n collection: Collection<TResult, TKey, TUtils>\n status: CollectionStatus // Can't be disabled for pre-created live query collections\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 // Always true for pre-created live query collections\n}\n\n// Overload 10: Accept pre-created live query collection with singleResult: true\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & SingleResult,\n): {\n state: Map<TKey, TResult>\n data: TResult | undefined\n collection: Collection<TResult, TKey, TUtils> & SingleResult\n status: CollectionStatus // Can't be disabled for pre-created live query collections\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 // Always true for pre-created live query collections\n}\n\n// Implementation - use function overloads to infer the actual collection type\nexport function useLiveQuery(\n configOrQueryOrCollection: any,\n deps?: Array<unknown>,\n) {\n return useLiveQueryImpl(configOrQueryOrCollection, deps)\n}\n\n/** @internal Shared implementation for the Suspense wrapper. */\nexport function useLiveQueryForSuspense(\n configOrQueryOrCollection: any,\n deps: Array<unknown> | undefined,\n) {\n return useLiveQueryImpl(configOrQueryOrCollection, deps, true)\n}\n\nfunction useLiveQueryImpl(\n configOrQueryOrCollection: any,\n deps: Array<unknown> | undefined,\n forSuspense = false,\n) {\n const contextDbClient = useOptionalDbClient()\n // Check if it's already a collection\n const inputIsCollection = isCollection(configOrQueryOrCollection)\n const dbClient = inputIsCollection\n ? contextDbClient\n : (getExplicitDbClient(configOrQueryOrCollection) ?? contextDbClient)\n const resolvedDeps = deps ?? []\n\n // Use refs to cache collection and track dependencies\n const collectionRef = useRef<Collection<object, string | number, {}> | null>(\n null,\n )\n const depsRef = useRef<Array<unknown> | null>(null)\n const configRef = useRef<unknown>(null)\n const clientRef = useRef(dbClient)\n const legacyUnhashableIdentityRef = useRef<Array<unknown>>([\n `legacy-unhashable`,\n ])\n\n const derivedIdentityProfilerRef = useRef<DerivedIdentityProfiler>({\n renderCount: 0,\n totalMs: 0,\n maxMs: 0,\n warned: false,\n })\n const deferredCollectionsRef = useRef(\n new Set<CollectionImpl<any, string | number, any, any, any>>(),\n )\n const observerRef = useRef<LiveQueryObserver<object, string | number> | null>(\n null,\n )\n const queryHashRef = useRef<string | undefined>(undefined)\n const suspenseKeyRef = useRef<string | undefined>(undefined)\n const identityErrorRef = useRef<UnhashableQueryIRError | undefined>(undefined)\n\n const queryKey = !inputIsCollection\n ? getExplicitQueryKey(configOrQueryOrCollection)\n : undefined\n let preparedQueryValue: unknown | typeof unpreparedQueryValue =\n unpreparedQueryValue\n let identityDeps: ReadonlyArray<unknown>\n let streamIdentity: unknown = undefined\n let identityError: UnhashableQueryIRError | undefined\n\n if (queryKey) {\n identityDeps = queryKey\n streamIdentity = [`queryKey`, queryKey]\n } else if (deps !== undefined) {\n identityDeps = resolvedDeps\n try {\n preparedQueryValue = prepareQueryValue(\n configOrQueryOrCollection,\n dbClient,\n deferredCollectionsRef.current,\n )\n streamIdentity = [\n `deps`,\n resolvedDeps,\n getPreparedLiveQueryIdentity(preparedQueryValue),\n ]\n } catch (error) {\n if (!(error instanceof UnhashableQueryIRError)) throw error\n warnUnhashableDerivedIdentity(error)\n identityError = error\n }\n } else if (inputIsCollection) {\n identityDeps = []\n streamIdentity = [`collection`, configOrQueryOrCollection.id]\n } else {\n const preparation = prepareDerivedQuery(\n configOrQueryOrCollection,\n dbClient,\n derivedIdentityProfilerRef.current,\n deferredCollectionsRef.current,\n )\n preparedQueryValue = preparation.value\n if (preparation.status === `hashable`) {\n identityDeps = preparation.identityDeps\n streamIdentity = preparation.identityDeps\n } else {\n warnUnhashableDerivedIdentity(preparation.error)\n identityDeps = legacyUnhashableIdentityRef.current\n identityError = preparation.error\n }\n }\n\n let queryHash: string | undefined\n if (streamIdentity !== undefined) {\n try {\n queryHash = getStableValueHash(streamIdentity, `queryKey`)\n } catch (error) {\n if (error instanceof UnhashableQueryIRError) {\n if (queryKey !== undefined) throw error\n identityError = error\n } else {\n throw error\n }\n }\n }\n\n if (deps !== undefined) {\n warnDeprecatedDepsArray()\n }\n\n const canReuseSuspenseKey =\n forSuspense &&\n !inputIsCollection &&\n queryHash !== undefined &&\n !dbClient &&\n collectionRef.current !== null &&\n clientRef.current === dbClient &&\n queryHashRef.current === queryHash &&\n suspenseKeyRef.current !== undefined\n\n if (\n forSuspense &&\n !inputIsCollection &&\n queryHash &&\n !dbClient &&\n !canReuseSuspenseKey &&\n preparedQueryValue === unpreparedQueryValue\n ) {\n preparedQueryValue = prepareQueryValue(\n configOrQueryOrCollection,\n dbClient,\n deferredCollectionsRef.current,\n )\n }\n\n const suspenseKey =\n queryHash && !dbClient\n ? canReuseSuspenseKey\n ? suspenseKeyRef.current\n : getUnscopedSuspenseKey(preparedQueryValue, queryHash)\n : queryHash\n\n const suspenseCollections =\n forSuspense && !inputIsCollection && suspenseKey\n ? getSuspenseCollections(dbClient)\n : undefined\n const suspenseEntry = suspenseKey\n ? suspenseCollections?.get(suspenseKey)\n : undefined\n const suspenseCollection = suspenseEntry?.collection\n\n const identityChanged =\n depsRef.current === null ||\n (deps !== undefined\n ? depsRef.current.length !== identityDeps.length ||\n depsRef.current.some((dep, index) => dep !== identityDeps[index])\n : !deepEquals(depsRef.current, identityDeps))\n\n // Check if we need to create/recreate the collection\n const needsNewCollection =\n !collectionRef.current ||\n (inputIsCollection && configRef.current !== configOrQueryOrCollection) ||\n (!inputIsCollection && (clientRef.current !== dbClient || identityChanged))\n\n const resumeDeferredCollections = () => {\n for (const collection of deferredCollectionsRef.current) {\n collection._resumeSyncStart()\n }\n deferredCollectionsRef.current.clear()\n }\n\n if (needsNewCollection) {\n if (inputIsCollection) {\n // Warn when passing a collection directly with on-demand sync mode\n // In on-demand mode, data is only loaded when queries with predicates request it\n // Passing the collection directly doesn't provide any predicates, so no data loads\n const syncMode = (\n configOrQueryOrCollection as { config?: { syncMode?: string } }\n ).config?.syncMode\n if (\n syncMode === `on-demand` &&\n shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)\n ) {\n console.warn(\n `[useLiveQuery] Warning: Passing a collection with syncMode \"on-demand\" directly to useLiveQuery ` +\n `will not load any data. In on-demand mode, data is only loaded when queries with predicates request it.\\n\\n` +\n `Instead, use a query builder function:\\n` +\n ` const { data } = useLiveQuery({ query: (q) => q.from({ c: myCollection }).select(({ c }) => c) })\\n\\n` +\n `Or switch to syncMode \"eager\" if you want all data to sync automatically.`,\n )\n }\n // It's already a collection, ensure sync is started for React hooks\n configOrQueryOrCollection.startSyncImmediate()\n collectionRef.current = configOrQueryOrCollection\n configRef.current = configOrQueryOrCollection\n } else {\n if (suspenseCollection) {\n collectionRef.current = suspenseCollection\n } else {\n if (preparedQueryValue === unpreparedQueryValue) {\n preparedQueryValue = prepareQueryValue(\n configOrQueryOrCollection,\n dbClient,\n deferredCollectionsRef.current,\n )\n }\n collectionRef.current = createCollectionFromPreparedQuery(\n preparedQueryValue,\n forSuspense ? DEFAULT_SUSPENSE_GC_TIME_MS : DEFAULT_GC_TIME_MS,\n ) as SuspenseCollection | null\n if (suspenseCollections && suspenseKey && collectionRef.current) {\n const collection = collectionRef.current\n const removeCleanupListener = collection.on(`status:cleaned-up`, () =>\n releaseSuspenseCollection(\n suspenseCollections,\n suspenseKey,\n collection,\n ),\n )\n const removeErrorListener = collection.on(`status:error`, () => {\n // Keep the failed collection through React's immediate retry so\n // the hook can throw its actual load error to an ErrorBoundary.\n // Retire it afterward so a later boundary reset starts fresh.\n setTimeout(\n () =>\n releaseSuspenseCollection(\n suspenseCollections,\n suspenseKey,\n collection,\n ),\n 0,\n )\n })\n const entry: SuspenseCollectionEntry = {\n collection,\n removeStatusListeners: () => {\n removeCleanupListener()\n removeErrorListener()\n },\n }\n suspenseCollections.set(suspenseKey, entry)\n }\n }\n configRef.current = configOrQueryOrCollection\n depsRef.current = [...identityDeps]\n }\n clientRef.current = dbClient\n queryHashRef.current = queryHash\n suspenseKeyRef.current = suspenseKey\n identityErrorRef.current = identityError\n }\n\n // Recreate the observer when the underlying collection changes. The observer\n // is not disposed explicitly here or on unmount: `useSyncExternalStore`\n // unsubscribes it when the subscribe changes or the component unmounts, which\n // detaches the collection subscription; the observer is then GC'd. (An unmount\n // effect that disposed it would misfire under StrictMode/offscreen effect\n // replay, leaving a disposed observer in the ref.)\n if (needsNewCollection) {\n // Defer the initial notify: useSyncExternalStore must not be notified\n // synchronously during subscribe.\n // Wholesale mode: React re-reads getSnapshot() on notify, keeps the\n // hook's pre-observer loading policy, and — because wholesale delivers\n // nothing synchronously during subscribe — never notifies\n // useSyncExternalStore inside its own subscribe call.\n observerRef.current = createLiveQueryObserver(collectionRef.current, {\n mode: `wholesale`,\n client: dbClient,\n queryHash: queryHashRef.current,\n onPreload: resumeDeferredCollections,\n })\n }\n const observer = observerRef.current!\n\n // Stable subscribe bound to the current observer; the observer owns the\n // subscription, ready-race, and disposal.\n const subscribeRef = useRef<\n ((onStoreChange: () => void) => () => void) | null\n >(null)\n if (!subscribeRef.current || needsNewCollection) {\n subscribeRef.current = (onStoreChange: () => void) => {\n const unsubscribe = observer.subscribe(() => onStoreChange())\n resumeDeferredCollections()\n return unsubscribe\n }\n }\n\n const returned = useSyncExternalStore(\n subscribeRef.current,\n () => observer.getSnapshot(),\n () => observer.getServerSnapshot(),\n )\n setLiveQueryResultInfo(returned, {\n client: dbClient,\n queryHash: queryHashRef.current,\n identityError: identityErrorRef.current,\n observer,\n })\n return returned as any\n}\n"],"names":["useRef","useSyncExternalStore","setLiveQueryResultInfo"],"mappings":";;;;;;;AAkCA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AAIA;AACA;AAEA;AACE;AACA;AACE;AACA;AAAgC;AAElC;AACF;AAEA;AAIE;AASA;AACA;AAAiE;AACb;AAEpD;AACF;AAEA;AAGE;AACA;AACA;AACE;AACA;AAAmD;AAErD;AACF;AAEA;AAKE;AACA;AACA;AACA;AACF;AA2CO;AAGL;AACE;AAAA;AAGF;AACA;AACE;AAAA;AAEF;AACA;AAIA;AAAQ;AAC8F;AAExG;AAEA;AACE;AACE;AAAO;AAGT;AAGF;AAEA;AACE;AAIF;AAEA;AACE;AACA;AAA0B;AAC1B;AAEG;AAI2C;AAE9C;AACF;AAEA;AAIE;AAIE;AAAA;AAGF;AAEA;AAIA;AACE;AAAA;AAGF;AACA;AACE;AACA;AAAA;AAGF;AACA;AAEA;AAIA;AAAQ;AAC2I;AAGrJ;AAEA;AACE;AAKF;AAEA;AACE;AAMF;AAEO;AAKL;AACF;AAcO;AAML;AAAsB;AACpB;AAEF;AACA;AAEA;AACE;AACA;AAAO;AACG;AACD;AAC2B;AAAA;AAGpC;AACE;AAAqD;AAGvD;AAAM;AAEN;AACE;AACA;AACA;AACA;AACA;AAA+C;AACjD;AAEJ;AAEO;AAGL;AACE;AAAA;AAGF;AACA;AACE;AAAA;AAEF;AAEA;AAAQ;AAC4F;AAItG;AAEA;AAIE;AACE;AAAO;AAGT;AACE;AACA;AAAO;AAGT;AACE;AAAiC;AACxB;AACI;AACH;AACT;AAGH;AACE;AAAiC;AACpB;AACH;AACJ;AACL;AAGH;AAAU;AACyH;AAErI;AAoYO;AAIL;AACF;AAGO;AAIL;AACF;AAEA;AAKE;AAEA;AACA;AAGA;AAGA;AAAsBA;AACpB;AAEF;AACA;AACA;AACA;AAA2D;AACzD;AAGF;AAAmE;AACpD;AACJ;AACF;AACC;AAEV;AAA+BA;AACzB;AAEN;AAAoBA;AAClB;AAEF;AACA;AACA;AAEA;AAGA;AAEA;AACA;AACA;AAEA;AACE;AACA;AAAsC;AAEtC;AACA;AACE;AAAqB;AACnB;AACA;AACuB;AAEzB;AAAiB;AACf;AACA;AAC+C;AAAA;AAGjD;AACA;AACA;AAAgB;AAClB;AAEA;AACA;AAA4D;AAE5D;AAAoB;AAClB;AACA;AAC2B;AACJ;AAEzB;AACA;AACE;AACA;AAA6B;AAE7B;AACA;AACA;AAA4B;AAC9B;AAGF;AACA;AACE;AACE;AAAyD;AAEzD;AACE;AACA;AAAgB;AAEhB;AAAM;AACR;AACF;AAGF;AACE;AAAA;AAGF;AAUA;AAQE;AAAqB;AACnB;AACA;AACuB;AAAA;AAI3B;AAOA;AAIA;AAGA;AAEA;AAQA;AAKA;AACE;AACE;AAAW;AAEb;AAA+B;AAGjC;AACE;AAIE;AAGA;AAIE;AAAQ;;AACN;AAAA;;AAAA;AAAA;AAAA;AAQJ;AACA;AACA;AAAoB;AAEpB;AACE;AAAwB;AAExB;AACE;AAAqB;AACnB;AACA;AACuB;AAAA;AAG3B;AAAwB;AACtB;AAC4C;AAE9C;AACE;AACA;AAAyC;AAAG;AAC1C;AACE;AACA;AACA;AAAA;AAGJ;AAIE;AAAA;AAEI;AACE;AACA;AACA;AAAA;AAEJ;AAAA;AAGJ;AAAuC;AACrC;AAEE;AACA;AAAA;AACF;AAEF;AAA0C;AAC5C;AAEF;AACA;AAAkC;AAEpC;AACA;AACA;AACA;AAA2B;AAS7B;AAOE;AAAqE;AAC7D;AACE;AACgB;AACb;AACZ;AAEH;AAIA;AAGA;AACE;AACE;AACA;AACA;AAAO;AACT;AAGF;AAAiBC;AACF;AACE;AACA;AAEjBC;AAAiC;AACvB;AACgB;AACQ;AAChC;AAEF;AACF;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"useLiveQuery.cjs","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["'use client'\n\nimport { useRef, useSyncExternalStore } from 'react'\nimport {\n BaseQueryBuilder,\n IR,\n UnhashableQueryIRError,\n createLiveQueryObserver,\n deepEquals,\n getPreparedLiveQueryIdentity,\n getStableValueHash,\n isCollection,\n prepareLiveQueryValue,\n resolveLiveQueryValue,\n} from '@tanstack/db'\nimport { useOptionalDbClient } from './DbProvider'\nimport { setLiveQueryResultInfo } from './live-query-internals'\nimport { shouldWarnInDevelopment } from './development'\nimport type {\n Collection,\n CollectionImpl,\n CollectionStatus,\n Context,\n DbClient,\n GetResult,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n LiveQueryObserver,\n LiveQueryPersistedStatus,\n NonSingleResult,\n QueryBuilder,\n SingleResult,\n} from '@tanstack/db'\n\nconst DEFAULT_GC_TIME_MS = 1 // Live queries created by useLiveQuery are cleaned up immediately (0 disables GC)\n// Suspense renders can be abandoned before React subscribes. Keep their\n// collection briefly so a nearby retry can commit without starting over.\nconst DEFAULT_SUSPENSE_GC_TIME_MS = 5000\nconst DERIVED_IDENTITY_SINGLE_RENDER_WARN_MS = 16\nconst DERIVED_IDENTITY_RENDER_COUNT_WARN_THRESHOLD = 10\nconst DERIVED_IDENTITY_TOTAL_WARN_MS = 50\nconst warnedDepsCallsites = new Set<string>()\nconst warnedDerivedIdentityCallsites = new Set<string>()\nconst warnedUnhashableIdentityCallsites = new Set<string>()\nconst unpreparedQueryValue = Symbol(`unpreparedQueryValue`)\n\ntype SuspenseCollection = Collection<object, string | number, {}>\ntype SuspenseCollectionEntry = {\n collection: SuspenseCollection\n removeStatusListeners: () => void\n}\n\nconst unscopedSuspenseCollections = new Map<string, SuspenseCollectionEntry>()\nconst suspenseCollectionsByClient = new WeakMap<\n DbClient,\n Map<string, SuspenseCollectionEntry>\n>()\nconst suspenseSourceIds = new WeakMap<object, number>()\nlet nextSuspenseSourceId = 0\n\nfunction getSuspenseSourceId(source: object): number {\n let id = suspenseSourceIds.get(source)\n if (id === undefined) {\n id = ++nextSuspenseSourceId\n suspenseSourceIds.set(source, id)\n }\n return id\n}\n\nfunction getUnscopedSuspenseKey(\n preparedValue: unknown,\n queryHash: string,\n): string {\n const query =\n preparedValue instanceof BaseQueryBuilder\n ? preparedValue\n : preparedValue &&\n typeof preparedValue === `object` &&\n `query` in preparedValue &&\n preparedValue.query instanceof BaseQueryBuilder\n ? preparedValue.query\n : undefined\n if (!query) return queryHash\n const sourceIds = IR.collectCollectionSources(query._getQuery()).map(\n ({ collection }) => getSuspenseSourceId(collection),\n )\n return `${sourceIds.join(`,`)}:${queryHash}`\n}\n\nfunction getSuspenseCollections(\n client: DbClient | undefined,\n): Map<string, SuspenseCollectionEntry> {\n if (!client) return unscopedSuspenseCollections\n let collections = suspenseCollectionsByClient.get(client)\n if (!collections) {\n collections = new Map()\n suspenseCollectionsByClient.set(client, collections)\n }\n return collections\n}\n\nfunction releaseSuspenseCollection(\n collections: Map<string, SuspenseCollectionEntry>,\n queryHash: string,\n collection: SuspenseCollection,\n): void {\n const entry = collections.get(queryHash)\n if (entry?.collection !== collection) return\n collections.delete(queryHash)\n entry.removeStatusListeners()\n}\n\nexport type DerivedIdentityProfiler = {\n renderCount: number\n totalMs: number\n maxMs: number\n warned: boolean\n}\n\nexport type UseLiveQueryStatus = CollectionStatus | `disabled`\nexport type LiveQueryKey = ReadonlyArray<unknown>\ntype UseLiveQueryConfigOptions<TContext extends Context> = Omit<\n LiveQueryCollectionConfig<TContext>,\n `query`\n> & {\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}\n\ntype ConfiguredQueryBuilder<TContext extends Context> = Extract<\n LiveQueryCollectionConfig<TContext>[`query`],\n QueryBuilder<TContext>\n>\n\nexport type UseLiveQueryConfig<TContext extends Context> =\n UseLiveQueryConfigOptions<TContext> &\n Pick<LiveQueryCollectionConfig<TContext>, `query`>\n\nexport type ConditionalUseLiveQueryConfig<TContext extends Context> =\n UseLiveQueryConfigOptions<TContext> & {\n query:\n | ConfiguredQueryBuilder<TContext>\n | ((\n q: InitialQueryBuilder,\n ) => ConfiguredQueryBuilder<TContext> | undefined | null)\n }\n\nexport function warnDeprecatedDepsArray(\n hookName: `useLiveQuery` | `useLiveInfiniteQuery` = `useLiveQuery`,\n): void {\n if (!shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_DEPRECATION_WARNINGS`)) {\n return\n }\n\n const callsite = getWarningCallsite(4)\n if (warnedDepsCallsites.has(callsite)) {\n return\n }\n warnedDepsCallsites.add(callsite)\n const replacement =\n hookName === `useLiveQuery`\n ? `useLiveQuery({ query })`\n : `useLiveInfiniteQuery(query, { queryKey })`\n console.warn(\n `[${hookName}] The dependency-array form is deprecated and will be removed in 1.0. Use ${replacement} instead. Provide queryKey only for functional/opaque queries or to avoid deriving identity from structured query IR on render.`,\n )\n}\n\nfunction getCurrentTime(): number {\n return typeof performance !== `undefined` &&\n typeof performance.now === `function`\n ? performance.now()\n : Date.now()\n}\n\nfunction getWarningCallsite(stackIndex: number): string {\n const stack = new Error().stack ?? `unknown`\n const lines = stack.split(`\\n`)\n const userFrame = lines\n .slice(1)\n .find(\n (line) =>\n !line.includes(`useLiveQuery.ts`) &&\n !line.includes(`useLiveSuspenseQuery.ts`) &&\n !line.includes(`useLiveInfiniteQuery.ts`),\n )\n return userFrame?.trim() ?? lines[stackIndex]?.trim() ?? stack\n}\n\nfunction warnDerivedIdentityHotPath(\n profiler: DerivedIdentityProfiler,\n durationMs: number,\n): void {\n if (\n profiler.warned ||\n !shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)\n ) {\n return\n }\n\n const isSlowSingleRender =\n durationMs >= DERIVED_IDENTITY_SINGLE_RENDER_WARN_MS\n const isHotRenderPath =\n profiler.renderCount >= DERIVED_IDENTITY_RENDER_COUNT_WARN_THRESHOLD &&\n profiler.totalMs >= DERIVED_IDENTITY_TOTAL_WARN_MS\n\n if (!isSlowSingleRender && !isHotRenderPath) {\n return\n }\n\n const callsite = getWarningCallsite(5)\n if (warnedDerivedIdentityCallsites.has(callsite)) {\n profiler.warned = true\n return\n }\n\n warnedDerivedIdentityCallsites.add(callsite)\n profiler.warned = true\n\n const reason = isSlowSingleRender\n ? `one render took ${durationMs.toFixed(1)}ms`\n : `${profiler.renderCount} renders took ${profiler.totalMs.toFixed(1)}ms`\n\n console.warn(\n `[useLiveQuery] Deriving live query identity from structured query IR is running on a hot render path (${reason}, max ${profiler.maxMs.toFixed(1)}ms). ` +\n `Provide an explicit queryKey to skip rebuilding and hashing the IR on every render: useLiveQuery({ queryKey: [...], query }).`,\n )\n}\n\nfunction getExplicitQueryKey(value: unknown): LiveQueryKey | undefined {\n return value &&\n typeof value === `object` &&\n Array.isArray((value as { queryKey?: unknown }).queryKey)\n ? (value as { queryKey: LiveQueryKey }).queryKey\n : undefined\n}\n\nfunction getExplicitDbClient(value: unknown): DbClient | undefined {\n return value &&\n typeof value === `object` &&\n `client` in value &&\n (value as { client?: unknown }).client !== undefined\n ? (value as { client: DbClient }).client\n : undefined\n}\n\nexport function prepareQueryValue(\n value: unknown,\n dbClient: DbClient | undefined,\n deferredCollections: Set<CollectionImpl<any, string | number, any, any, any>>,\n): unknown {\n return prepareLiveQueryValue(value, dbClient, deferredCollections)\n}\n\ntype DerivedQueryPreparation =\n | {\n status: `hashable`\n value: unknown\n identityDeps: Array<unknown>\n }\n | {\n status: `unhashable`\n value: unknown\n error: UnhashableQueryIRError\n }\n\nexport function prepareDerivedQuery(\n value: unknown,\n dbClient: DbClient | undefined,\n profiler: DerivedIdentityProfiler,\n deferredCollections: Set<CollectionImpl<any, string | number, any, any, any>>,\n): DerivedQueryPreparation {\n const shouldProfile = shouldWarnInDevelopment(\n `TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`,\n )\n const start = shouldProfile ? getCurrentTime() : 0\n const preparedValue = prepareQueryValue(value, dbClient, deferredCollections)\n\n try {\n const identity = getPreparedLiveQueryIdentity(preparedValue)\n return {\n status: `hashable`,\n value: preparedValue,\n identityDeps: [`derived`, identity],\n }\n } catch (error) {\n if (error instanceof UnhashableQueryIRError) {\n return { status: `unhashable`, value: preparedValue, error }\n }\n\n throw error\n } finally {\n if (shouldProfile) {\n const durationMs = getCurrentTime() - start\n profiler.renderCount += 1\n profiler.totalMs += durationMs\n profiler.maxMs = Math.max(profiler.maxMs, durationMs)\n warnDerivedIdentityHotPath(profiler, durationMs)\n }\n }\n}\n\nexport function warnUnhashableDerivedIdentity(\n error: UnhashableQueryIRError,\n): void {\n if (!shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)) {\n return\n }\n\n const callsite = getWarningCallsite(4)\n if (warnedUnhashableIdentityCallsites.has(callsite)) {\n return\n }\n warnedUnhashableIdentityCallsites.add(callsite)\n\n console.warn(\n `[useLiveQuery] This query cannot derive a stable identity because ${error.reason} at ${error.path}. ` +\n `It will keep the legacy mount-stable behavior for now. Add queryKey: [...] to make captured values reactive. ` +\n `Unhashable queries without queryKey will throw in 1.0.`,\n )\n}\n\n/**\n * Create a live query using a query function.\n * @param queryFn - Query function that defines what data to fetch\n * @param deps - Deprecated array of dependencies that trigger query re-execution when changed\n * @returns Object with reactive data, state, and status information\n * @example\n * // Prefer config object syntax\n * const { data, isLoading } = useLiveQuery({\n * query: (q) =>\n * q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.completed, false))\n * .select(({ todos }) => ({ id: todos.id, text: todos.text }))\n * })\n *\n * @example\n * // Single result query\n * const { data } = useLiveQuery({\n * query: (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.id, 1))\n * .findOne()\n * })\n *\n * @example\n * // Structured captured values are included in derived query identity\n * const { data, state } = useLiveQuery({\n * query: (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => gt(todos.priority, minPriority)),\n * })\n *\n * @example\n * // Return undefined or null to disable a query\n * const { data, isEnabled } = useLiveQuery({\n * query: (q) => {\n * if (!userId) return undefined\n * return q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.userId, userId))\n * },\n * })\n *\n * @example\n * // Join pattern\n * const { data } = useLiveQuery({\n * query: (q) =>\n * q.from({ issues: issueCollection })\n * .join({ persons: personCollection }, ({ issues, persons }) =>\n * eq(issues.userId, persons.id)\n * )\n * .select(({ issues, persons }) => ({\n * id: issues.id,\n * title: issues.title,\n * userName: persons.name\n * }))\n * })\n *\n * @example\n * // Handle loading and error states\n * const { data, isLoading, isError, status } = useLiveQuery({\n * query: (q) => q.from({ todos: todoCollection })\n * })\n *\n * if (isLoading) return <div>Loading...</div>\n * if (isError) return <div>Error: {status}</div>\n *\n * return (\n * <ul>\n * {data.map(todo => <li key={todo.id}>{todo.text}</li>)}\n * </ul>\n * )\n */\n// Overload 1: Accept query function that always returns QueryBuilder\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n deps?: Array<unknown>,\n): {\n state: Map<string | number, GetResult<TContext>>\n data: InferResultType<TContext>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus // Can't be disabled if always returns QueryBuilder\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 // Always true if always returns QueryBuilder\n}\n\n// Overload 2: Accept query function that can return undefined/null\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext> | undefined | null,\n deps?: Array<unknown>,\n): {\n state: Map<string | number, GetResult<TContext>> | undefined\n data: InferResultType<TContext> | undefined\n collection: Collection<GetResult<TContext>, string | number, {}> | undefined\n status: UseLiveQueryStatus\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: boolean\n}\n\n// Overload 3: Accept query function that can return LiveQueryCollectionConfig\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => LiveQueryCollectionConfig<TContext> | undefined | null,\n deps?: Array<unknown>,\n): {\n state: Map<string | number, GetResult<TContext>> | undefined\n data: InferResultType<TContext> | undefined\n collection: Collection<GetResult<TContext>, string | number, {}> | undefined\n status: UseLiveQueryStatus\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: boolean\n}\n\n// Overload 4: Accept query function that can return Collection\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => Collection<TResult, TKey, TUtils> | undefined | null,\n deps?: Array<unknown>,\n): {\n state: Map<TKey, TResult> | undefined\n data: Array<TResult> | undefined\n collection: Collection<TResult, TKey, TUtils> | undefined\n status: UseLiveQueryStatus\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: boolean\n}\n\n// Overload 5: Accept query function that can return all types\nexport function useLiveQuery<\n TContext extends Context,\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n queryFn: (\n q: InitialQueryBuilder,\n ) =>\n | QueryBuilder<TContext>\n | LiveQueryCollectionConfig<TContext>\n | Collection<TResult, TKey, TUtils>\n | undefined\n | null,\n deps?: Array<unknown>,\n): {\n state:\n | Map<string | number, GetResult<TContext>>\n | Map<TKey, TResult>\n | undefined\n data: InferResultType<TContext> | Array<TResult> | undefined\n collection:\n | Collection<GetResult<TContext>, string | number, {}>\n | Collection<TResult, TKey, TUtils>\n | undefined\n status: UseLiveQueryStatus\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: boolean\n}\n\n/**\n * Create a live query using configuration object\n * @param config - Configuration object with query and options\n * @param deps - Deprecated array of dependencies that trigger query re-execution when changed\n * @returns Object with reactive data, state, and status information\n * @example\n * // Basic config object usage\n * const { data, status } = useLiveQuery({\n * query: (q) => q.from({ todos: todosCollection }),\n * gcTime: 60000\n * })\n *\n * @example\n * // With query builder and options\n * const queryBuilder = new Query()\n * .from({ persons: collection })\n * .where(({ persons }) => gt(persons.age, 30))\n * .select(({ persons }) => ({ id: persons.id, name: persons.name }))\n *\n * const { data, isReady } = useLiveQuery({\n * query: queryBuilder,\n * })\n *\n * @example\n * // Handle all states uniformly\n * const { data, isLoading, isReady, isError } = useLiveQuery({\n * query: (q) => q.from({ items: itemCollection })\n * })\n *\n * if (isLoading) return <div>Loading...</div>\n * if (isError) return <div>Something went wrong</div>\n * if (!isReady) return <div>Preparing...</div>\n *\n * return <div>{data.length} items loaded</div>\n */\n// Overload 6: Accept config object\nexport function useLiveQuery<TContext extends Context>(\n config: UseLiveQueryConfig<TContext>,\n): {\n state: Map<string | number, GetResult<TContext>>\n data: InferResultType<TContext>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus // Can't be disabled when query always returns a builder\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 // Always true when query always returns a builder\n}\n\n// Overload 7: Accept config object with a query that can return undefined/null\nexport function useLiveQuery<TContext extends Context>(\n config: ConditionalUseLiveQueryConfig<TContext>,\n): {\n state: Map<string | number, GetResult<TContext>> | undefined\n data: InferResultType<TContext> | undefined\n collection: Collection<GetResult<TContext>, string | number, {}> | undefined\n status: UseLiveQueryStatus\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: boolean\n}\n\n// Overload 8: Accept config object with legacy deps\nexport function useLiveQuery<TContext extends Context>(\n config: LiveQueryCollectionConfig<TContext>,\n deps?: Array<unknown>,\n): {\n state: Map<string | number, GetResult<TContext>>\n data: InferResultType<TContext>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus // Can't be disabled when query always returns a builder\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 // Always true when query always returns a builder\n}\n\n// Overload 9: Accept config object with legacy deps and a query that can return undefined/null\nexport function useLiveQuery<TContext extends Context>(\n config: ConditionalUseLiveQueryConfig<TContext>,\n deps: Array<unknown>,\n): {\n state: Map<string | number, GetResult<TContext>> | undefined\n data: InferResultType<TContext> | undefined\n collection: Collection<GetResult<TContext>, string | number, {}> | undefined\n status: UseLiveQueryStatus\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: boolean\n}\n\n/**\n * Subscribe to an existing live query collection\n * @param liveQueryCollection - Pre-created live query collection to subscribe to\n * @returns Object with reactive data, state, and status information\n * @example\n * // Using pre-created live query collection\n * const myLiveQuery = createLiveQueryCollection((q) =>\n * q.from({ todos: todosCollection }).where(({ todos }) => eq(todos.active, true))\n * )\n * const { data, collection } = useLiveQuery(myLiveQuery)\n *\n * @example\n * // Access collection methods directly\n * const { data, collection, isReady } = useLiveQuery(existingCollection)\n *\n * // Use collection for mutations\n * const handleToggle = (id) => {\n * collection.update(id, draft => { draft.completed = !draft.completed })\n * }\n *\n * @example\n * // Handle states consistently\n * const { data, isLoading, isError } = useLiveQuery(sharedCollection)\n *\n * if (isLoading) return <div>Loading...</div>\n * if (isError) return <div>Error loading data</div>\n *\n * return <div>{data.map(item => <Item key={item.id} {...item} />)}</div>\n */\n// Overload 10: Accept pre-created live query collection\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult,\n): {\n state: Map<TKey, TResult>\n data: Array<TResult>\n collection: Collection<TResult, TKey, TUtils>\n status: CollectionStatus // Can't be disabled for pre-created live query collections\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 // Always true for pre-created live query collections\n}\n\n// Overload 10: Accept pre-created live query collection with singleResult: true\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & SingleResult,\n): {\n state: Map<TKey, TResult>\n data: TResult | undefined\n collection: Collection<TResult, TKey, TUtils> & SingleResult\n status: CollectionStatus // Can't be disabled for pre-created live query collections\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 // Always true for pre-created live query collections\n}\n\n// Implementation - use function overloads to infer the actual collection type\nexport function useLiveQuery(\n configOrQueryOrCollection: any,\n deps?: Array<unknown>,\n) {\n return useLiveQueryImpl(configOrQueryOrCollection, deps)\n}\n\n/** @internal Shared implementation for the Suspense wrapper. */\nexport function useLiveQueryForSuspense(\n configOrQueryOrCollection: any,\n deps: Array<unknown> | undefined,\n) {\n return useLiveQueryImpl(configOrQueryOrCollection, deps, true)\n}\n\n// What one hook instance keeps across renders. It lives in a single ref\n// slot, so a render neither looks up nor allocates more.\nfunction createHookInstance(dbClient: DbClient | undefined) {\n return {\n collection: null as Collection<object, string | number, {}> | null,\n deps: null as Array<unknown> | null,\n config: null as unknown,\n client: dbClient,\n legacyUnhashableIdentity: [`legacy-unhashable`] as Array<unknown>,\n derivedIdentityProfiler: {\n renderCount: 0,\n totalMs: 0,\n maxMs: 0,\n warned: false,\n } as DerivedIdentityProfiler,\n deferredCollections: new Set<\n CollectionImpl<any, string | number, any, any, any>\n >(),\n observer: null as LiveQueryObserver<object, string | number> | null,\n queryHash: undefined as string | undefined,\n suspenseKey: undefined as string | undefined,\n identityError: undefined as UnhashableQueryIRError | undefined,\n subscribe: null as ((onStoreChange: () => void) => () => void) | null,\n }\n}\n\nfunction useLiveQueryImpl(\n configOrQueryOrCollection: any,\n deps: Array<unknown> | undefined,\n forSuspense = false,\n) {\n const contextDbClient = useOptionalDbClient()\n // Check if it's already a collection\n const inputIsCollection = isCollection(configOrQueryOrCollection)\n const dbClient = inputIsCollection\n ? contextDbClient\n : (getExplicitDbClient(configOrQueryOrCollection) ?? contextDbClient)\n const resolvedDeps = deps ?? []\n\n const instanceRef = useRef<ReturnType<typeof createHookInstance> | null>(null)\n const instance = (instanceRef.current ??= createHookInstance(dbClient))\n\n const queryKey = !inputIsCollection\n ? getExplicitQueryKey(configOrQueryOrCollection)\n : undefined\n let preparedQueryValue: unknown | typeof unpreparedQueryValue =\n unpreparedQueryValue\n let identityDeps: ReadonlyArray<unknown>\n let streamIdentity: unknown = undefined\n let identityError: UnhashableQueryIRError | undefined\n\n if (queryKey) {\n identityDeps = queryKey\n streamIdentity = [`queryKey`, queryKey]\n } else if (deps !== undefined) {\n identityDeps = resolvedDeps\n // Deps decide reuse. Only hydration and Suspense read the query hash; the\n // development warning about unhashable queries still derives it.\n if (\n dbClient ||\n forSuspense ||\n shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)\n )\n try {\n preparedQueryValue = prepareQueryValue(\n configOrQueryOrCollection,\n dbClient,\n instance.deferredCollections,\n )\n streamIdentity = [\n `deps`,\n resolvedDeps,\n getPreparedLiveQueryIdentity(preparedQueryValue),\n ]\n } catch (error) {\n if (!(error instanceof UnhashableQueryIRError)) throw error\n warnUnhashableDerivedIdentity(error)\n identityError = error\n }\n } else if (inputIsCollection) {\n identityDeps = []\n streamIdentity = [`collection`, configOrQueryOrCollection.id]\n } else {\n const preparation = prepareDerivedQuery(\n configOrQueryOrCollection,\n dbClient,\n instance.derivedIdentityProfiler,\n instance.deferredCollections,\n )\n preparedQueryValue = preparation.value\n if (preparation.status === `hashable`) {\n identityDeps = preparation.identityDeps\n streamIdentity = preparation.identityDeps\n } else {\n warnUnhashableDerivedIdentity(preparation.error)\n identityDeps = instance.legacyUnhashableIdentity\n identityError = preparation.error\n }\n }\n\n let queryHash: string | undefined\n if (streamIdentity !== undefined) {\n try {\n queryHash = getStableValueHash(streamIdentity, `queryKey`)\n } catch (error) {\n if (error instanceof UnhashableQueryIRError) {\n if (queryKey !== undefined) throw error\n identityError = error\n } else {\n throw error\n }\n }\n }\n\n if (deps !== undefined) {\n warnDeprecatedDepsArray()\n }\n\n const canReuseSuspenseKey =\n forSuspense &&\n !inputIsCollection &&\n queryHash !== undefined &&\n !dbClient &&\n instance.collection !== null &&\n instance.client === dbClient &&\n instance.queryHash === queryHash &&\n instance.suspenseKey !== undefined\n\n if (\n forSuspense &&\n !inputIsCollection &&\n queryHash &&\n !dbClient &&\n !canReuseSuspenseKey &&\n preparedQueryValue === unpreparedQueryValue\n ) {\n preparedQueryValue = prepareQueryValue(\n configOrQueryOrCollection,\n dbClient,\n instance.deferredCollections,\n )\n }\n\n const suspenseKey =\n queryHash && !dbClient\n ? canReuseSuspenseKey\n ? instance.suspenseKey\n : getUnscopedSuspenseKey(preparedQueryValue, queryHash)\n : queryHash\n\n const suspenseCollections =\n forSuspense && !inputIsCollection && suspenseKey\n ? getSuspenseCollections(dbClient)\n : undefined\n const suspenseEntry = suspenseKey\n ? suspenseCollections?.get(suspenseKey)\n : undefined\n const suspenseCollection = suspenseEntry?.collection\n\n const identityChanged =\n instance.deps === null ||\n (deps !== undefined\n ? instance.deps.length !== identityDeps.length ||\n instance.deps.some((dep, index) => dep !== identityDeps[index])\n : !deepEquals(instance.deps, identityDeps))\n\n // Check if we need to create/recreate the collection\n const needsNewCollection =\n !instance.collection ||\n (inputIsCollection && instance.config !== configOrQueryOrCollection) ||\n (!inputIsCollection && (instance.client !== dbClient || identityChanged))\n\n const resumeDeferredCollections = () => {\n if (instance.deferredCollections.size === 0) return\n for (const collection of instance.deferredCollections) {\n collection._resumeSyncStart()\n }\n instance.deferredCollections.clear()\n }\n\n if (needsNewCollection) {\n if (inputIsCollection) {\n // Warn when passing a collection directly with on-demand sync mode\n // In on-demand mode, data is only loaded when queries with predicates request it\n // Passing the collection directly doesn't provide any predicates, so no data loads\n const syncMode = (\n configOrQueryOrCollection as { config?: { syncMode?: string } }\n ).config?.syncMode\n if (\n syncMode === `on-demand` &&\n shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)\n ) {\n console.warn(\n `[useLiveQuery] Warning: Passing a collection with syncMode \"on-demand\" directly to useLiveQuery ` +\n `will not load any data. In on-demand mode, data is only loaded when queries with predicates request it.\\n\\n` +\n `Instead, use a query builder function:\\n` +\n ` const { data } = useLiveQuery({ query: (q) => q.from({ c: myCollection }).select(({ c }) => c) })\\n\\n` +\n `Or switch to syncMode \"eager\" if you want all data to sync automatically.`,\n )\n }\n // It's already a collection, ensure sync is started for React hooks\n configOrQueryOrCollection.startSyncImmediate()\n instance.collection = configOrQueryOrCollection\n instance.config = configOrQueryOrCollection\n } else {\n if (suspenseCollection) {\n instance.collection = suspenseCollection\n } else {\n if (preparedQueryValue === unpreparedQueryValue) {\n preparedQueryValue = prepareQueryValue(\n configOrQueryOrCollection,\n dbClient,\n instance.deferredCollections,\n )\n }\n instance.collection = resolveLiveQueryValue(preparedQueryValue, {\n gcTime: forSuspense\n ? DEFAULT_SUSPENSE_GC_TIME_MS\n : DEFAULT_GC_TIME_MS,\n // Hydration and Suspense key the live-query Collection by identity.\n pool: !forSuspense && !dbClient,\n }) as SuspenseCollection | null\n if (suspenseCollections && suspenseKey && instance.collection) {\n const collection = instance.collection\n const removeCleanupListener = collection.on(`status:cleaned-up`, () =>\n releaseSuspenseCollection(\n suspenseCollections,\n suspenseKey,\n collection,\n ),\n )\n const removeErrorListener = collection.on(`status:error`, () => {\n // Keep the failed collection through React's immediate retry so\n // the hook can throw its actual load error to an ErrorBoundary.\n // Retire it afterward so a later boundary reset starts fresh.\n setTimeout(\n () =>\n releaseSuspenseCollection(\n suspenseCollections,\n suspenseKey,\n collection,\n ),\n 0,\n )\n })\n const entry: SuspenseCollectionEntry = {\n collection,\n removeStatusListeners: () => {\n removeCleanupListener()\n removeErrorListener()\n },\n }\n suspenseCollections.set(suspenseKey, entry)\n }\n }\n instance.config = configOrQueryOrCollection\n instance.deps = [...identityDeps]\n }\n instance.client = dbClient\n instance.queryHash = queryHash\n instance.suspenseKey = suspenseKey\n instance.identityError = identityError\n }\n\n // Recreate the observer when the underlying collection changes. The observer\n // is not disposed explicitly here or on unmount: `useSyncExternalStore`\n // unsubscribes it when the subscribe changes or the component unmounts, which\n // detaches the collection subscription; the observer is then GC'd. (An unmount\n // effect that disposed it would misfire under StrictMode/offscreen effect\n // replay, leaving a disposed observer in the ref.)\n if (needsNewCollection) {\n // Defer the initial notify: useSyncExternalStore must not be notified\n // synchronously during subscribe.\n // Wholesale mode: React re-reads getSnapshot() on notify, keeps the\n // hook's pre-observer loading policy, and — because wholesale delivers\n // nothing synchronously during subscribe — never notifies\n // useSyncExternalStore inside its own subscribe call.\n instance.observer = createLiveQueryObserver(instance.collection, {\n mode: `wholesale`,\n client: dbClient,\n queryHash: instance.queryHash,\n onPreload: resumeDeferredCollections,\n })\n }\n const observer = instance.observer!\n\n // Stable subscribe bound to the current observer; the observer owns the\n // subscription, ready-race, and disposal.\n if (!instance.subscribe || needsNewCollection) {\n instance.subscribe = (onStoreChange: () => void) => {\n const unsubscribe = observer.subscribe(onStoreChange)\n resumeDeferredCollections()\n return unsubscribe\n }\n }\n\n const returned = useSyncExternalStore(\n instance.subscribe,\n () => observer.getSnapshot(),\n () => observer.getServerSnapshot(),\n )\n // Only useLiveSuspenseQuery reads this, and it costs a define per render.\n if (forSuspense) {\n setLiveQueryResultInfo(returned, {\n client: dbClient,\n queryHash: instance.queryHash,\n identityError: instance.identityError,\n observer,\n })\n }\n return returned as any\n}\n"],"names":["shouldWarnInDevelopment","useSyncExternalStore","setLiveQueryResultInfo"],"mappings":";;;;;;;;AAmCA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AAIA;AACA;AAEA;AACE;AACA;AACE;AACA;AAAgC;AAElC;AACF;AAEA;AAIE;AASA;AACA;AAAiE;AACb;AAEpD;AACF;AAEA;AAGE;AACA;AACA;AACE;AACA;AAAmD;AAErD;AACF;AAEA;AAKE;AACA;AACA;AACA;AACF;AA2CO;AAGL;AACE;AAAA;AAGF;AACA;AACE;AAAA;AAEF;AACA;AAIA;AAAQ;AAC8F;AAExG;AAEA;AACE;AAIF;AAEA;AACE;AACA;AAA0B;AAC1B;AAEG;AAI2C;AAE9C;AACF;AAEA;AAIE;AAIE;AAAA;AAGF;AAEA;AAIA;AACE;AAAA;AAGF;AACA;AACE;AACA;AAAA;AAGF;AACA;AAEA;AAIA;AAAQ;AAC2I;AAGrJ;AAEA;AACE;AAKF;AAEA;AACE;AAMF;AAEO;AAKL;AACF;AAcO;AAML;AAAsBA;AACpB;AAEF;AACA;AAEA;AACE;AACA;AAAO;AACG;AACD;AAC2B;AAAA;AAGpC;AACE;AAAqD;AAGvD;AAAM;AAEN;AACE;AACA;AACA;AACA;AACA;AAA+C;AACjD;AAEJ;AAEO;AAGL;AACE;AAAA;AAGF;AACA;AACE;AAAA;AAEF;AAEA;AAAQ;AAC4F;AAItG;AAoYO;AAIL;AACF;AAGO;AAIL;AACF;AAIA;AACE;AAAO;AACO;AACN;AACE;AACA;AACsC;AACrB;AACV;AACJ;AACF;AACC;AAAA;AAEe;AAGf;AACC;AACE;AACE;AACJ;AAEf;AAEA;AAKE;AAEA;AACA;AAGA;AAEA;AACA;AAEA;AAGA;AAEA;AACA;AACA;AAEA;AACE;AACA;AAAsC;AAEtC;AAGA;AAKE;AACE;AAAqB;AACnB;AACA;AACS;AAEX;AAAiB;AACf;AACA;AAC+C;AAAA;AAGjD;AACA;AACA;AAAgB;AAClB;AAEF;AACA;AAA4D;AAE5D;AAAoB;AAClB;AACA;AACS;AACA;AAEX;AACA;AACE;AACA;AAA6B;AAE7B;AACA;AACA;AAA4B;AAC9B;AAGF;AACA;AACE;AACE;AAAyD;AAEzD;AACE;AACA;AAAgB;AAEhB;AAAM;AACR;AACF;AAGF;AACE;AAAA;AAGF;AAUA;AAQE;AAAqB;AACnB;AACA;AACS;AAAA;AAIb;AAOA;AAIA;AAGA;AAEA;AAQA;AAKA;AACE;AACA;AACE;AAAW;AAEb;AAA6B;AAG/B;AACE;AAIE;AAGA;AAIE;AAAQ;;AACN;AAAA;;AAAA;AAAA;AAAA;AAQJ;AACA;AACA;AAAkB;AAElB;AACE;AAAsB;AAEtB;AACE;AAAqB;AACnB;AACA;AACS;AAAA;AAGb;AAAgE;AAG1D;AAAA;AAEmB;AAEzB;AACE;AACA;AAAyC;AAAG;AAC1C;AACE;AACA;AACA;AAAA;AAGJ;AAIE;AAAA;AAEI;AACE;AACA;AACA;AAAA;AAEJ;AAAA;AAGJ;AAAuC;AACrC;AAEE;AACA;AAAA;AACF;AAEF;AAA0C;AAC5C;AAEF;AACA;AAAgC;AAElC;AACA;AACA;AACA;AAAyB;AAS3B;AAOE;AAAiE;AACzD;AACE;AACY;AACT;AACZ;AAEH;AAIA;AACE;AACE;AACA;AACA;AAAO;AACT;AAGF;AAAiBC;AACN;AACM;AACA;AAGjB;AACEC;AAAiC;AACvB;AACY;AACI;AACxB;AACD;AAEH;AACF;;;;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function shouldWarnInDevelopment(disableEnvVar: string): boolean;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function shouldWarnInDevelopment(disableEnvVar) {
|
|
2
|
+
try {
|
|
3
|
+
if (process.env.NODE_ENV === `production`) return false;
|
|
4
|
+
} catch {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
try {
|
|
8
|
+
return process.env[disableEnvVar] !== `1`;
|
|
9
|
+
} catch {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
shouldWarnInDevelopment
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=development.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"development.js","sources":["../../src/development.ts"],"sourcesContent":["// Bundlers inline `process.env.NODE_ENV` even where `process` does not exist,\n// so it is read directly; without either, warnings stay off.\nexport function shouldWarnInDevelopment(disableEnvVar: string): boolean {\n try {\n if (process.env.NODE_ENV === `production`) return false\n } catch {\n return false\n }\n // A page may supply a `process` shim without `env`; nothing disabled it.\n try {\n return process.env[disableEnvVar] !== `1`\n } catch {\n return true\n }\n}\n"],"names":[],"mappings":"AAEO,SAAS,wBAAwB,eAAgC;AACtE,MAAI;AACF,QAAI,QAAQ,IAAI,aAAa,aAAc,QAAO;AAAA,EACpD,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,MAAI;AACF,WAAO,QAAQ,IAAI,aAAa,MAAM;AAAA,EACxC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;"}
|