@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.
Files changed (40) hide show
  1. package/dist/cjs/live-query-internals.cjs.map +1 -1
  2. package/dist/cjs/live-query-internals.d.cts +3 -1
  3. package/dist/cjs/source-id-bindings.cjs +9 -42
  4. package/dist/cjs/source-id-bindings.cjs.map +1 -1
  5. package/dist/cjs/source-id-bindings.d.cts +6 -17
  6. package/dist/cjs/useLiveInfiniteQuery.cjs +93 -173
  7. package/dist/cjs/useLiveInfiniteQuery.cjs.map +1 -1
  8. package/dist/cjs/useLiveInfiniteQuery.d.cts +3 -14
  9. package/dist/cjs/useLiveQuery.cjs +187 -224
  10. package/dist/cjs/useLiveQuery.cjs.map +1 -1
  11. package/dist/cjs/useLiveQuery.d.cts +37 -136
  12. package/dist/cjs/useLiveSuspenseQuery.cjs +71 -64
  13. package/dist/cjs/useLiveSuspenseQuery.cjs.map +1 -1
  14. package/dist/cjs/useLiveSuspenseQuery.d.cts +10 -9
  15. package/dist/cjs/usePacedMutations.cjs.map +1 -1
  16. package/dist/cjs/usePacedMutations.d.cts +1 -1
  17. package/dist/esm/live-query-internals.d.ts +3 -1
  18. package/dist/esm/live-query-internals.js.map +1 -1
  19. package/dist/esm/source-id-bindings.d.ts +6 -17
  20. package/dist/esm/source-id-bindings.js +9 -40
  21. package/dist/esm/source-id-bindings.js.map +1 -1
  22. package/dist/esm/useLiveInfiniteQuery.d.ts +3 -14
  23. package/dist/esm/useLiveInfiniteQuery.js +95 -175
  24. package/dist/esm/useLiveInfiniteQuery.js.map +1 -1
  25. package/dist/esm/useLiveQuery.d.ts +37 -136
  26. package/dist/esm/useLiveQuery.js +189 -226
  27. package/dist/esm/useLiveQuery.js.map +1 -1
  28. package/dist/esm/useLiveSuspenseQuery.d.ts +10 -9
  29. package/dist/esm/useLiveSuspenseQuery.js +71 -64
  30. package/dist/esm/useLiveSuspenseQuery.js.map +1 -1
  31. package/dist/esm/usePacedMutations.d.ts +1 -1
  32. package/dist/esm/usePacedMutations.js.map +1 -1
  33. package/package.json +3 -3
  34. package/skills/react-db/SKILL.md +24 -15
  35. package/src/live-query-internals.ts +3 -0
  36. package/src/source-id-bindings.ts +16 -62
  37. package/src/useLiveInfiniteQuery.ts +138 -296
  38. package/src/useLiveQuery.ts +329 -495
  39. package/src/useLiveSuspenseQuery.ts +122 -132
  40. package/src/usePacedMutations.ts +1 -1
@@ -1,18 +1,16 @@
1
1
  "use client";
2
2
  import { useRef, useSyncExternalStore } from "react";
3
- import { getPreparedLiveQueryIdentity, UnhashableQueryIRError, prepareLiveQueryValue, isCollection, getStableValueHash, deepEquals, resolveLiveQueryValue, createLiveQueryObserver, resumeDeferredLiveQueryCollections } from "@tanstack/db";
3
+ import { identifyLiveQuery, prepareLiveQueryValue, isCollection, getLiveQueryKey, getStableValueHash, UnhashableQueryIRError, deepEquals, resolveLiveQueryValue, releaseDeferredOnReclaim, createLiveQueryObserver, rejectLiveQueryInput, createLiveQuerySourceIdClaims, getSourceObjectToken, resumeDeferredLiveQueryCollections } from "@tanstack/db";
4
4
  import { useOptionalDbClient } from "./DbProvider.js";
5
5
  import { setLiveQueryResultInfo } from "./live-query-internals.js";
6
6
  import { shouldWarnInDevelopment } from "./development.js";
7
- import { claimSourceIds, getPreparedSources, createSourceIdBindings, getSourceObjectToken } from "./source-id-bindings.js";
7
+ import { getPreparedSources, getUnboundDescriptorReporter } from "./source-id-bindings.js";
8
8
  const DEFAULT_GC_TIME_MS = 1;
9
9
  const DEFAULT_SUSPENSE_GC_TIME_MS = 5e3;
10
10
  const DERIVED_IDENTITY_SINGLE_RENDER_WARN_MS = 16;
11
11
  const DERIVED_IDENTITY_RENDER_COUNT_WARN_THRESHOLD = 10;
12
12
  const DERIVED_IDENTITY_TOTAL_WARN_MS = 50;
13
- const warnedDepsCallsites = /* @__PURE__ */ new Set();
14
13
  const warnedDerivedIdentityCallsites = /* @__PURE__ */ new Set();
15
- const warnedUnhashableIdentityCallsites = /* @__PURE__ */ new Set();
16
14
  const unpreparedQueryValue = /* @__PURE__ */ Symbol(`unpreparedQueryValue`);
17
15
  const unscopedSuspenseCollections = /* @__PURE__ */ new Map();
18
16
  const suspenseCollectionsByClient = /* @__PURE__ */ new WeakMap();
@@ -40,20 +38,6 @@ function releaseSuspenseCollection(collections, queryHash, collection) {
40
38
  collections.delete(queryHash);
41
39
  entry.removeStatusListeners();
42
40
  }
43
- function warnDeprecatedDepsArray(hookName = `useLiveQuery`) {
44
- if (!shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_DEPRECATION_WARNINGS`)) {
45
- return;
46
- }
47
- const callsite = getWarningCallsite(4);
48
- if (warnedDepsCallsites.has(callsite)) {
49
- return;
50
- }
51
- warnedDepsCallsites.add(callsite);
52
- const replacement = hookName === `useLiveQuery` ? `useLiveQuery({ query })` : `useLiveInfiniteQuery(query, { queryKey })`;
53
- console.warn(
54
- `[${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.`
55
- );
56
- }
57
41
  function getCurrentTime() {
58
42
  return typeof performance !== `undefined` && typeof performance.now === `function` ? performance.now() : Date.now();
59
43
  }
@@ -67,7 +51,7 @@ function getWarningCallsite(stackIndex) {
67
51
  return userFrame?.trim() ?? lines[stackIndex]?.trim() ?? stack;
68
52
  }
69
53
  function warnDerivedIdentityHotPath(profiler, durationMs) {
70
- if (profiler.warned || !shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)) {
54
+ if (profiler.warned || !(shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`) && process.env.NODE_ENV !== `production`)) {
71
55
  return;
72
56
  }
73
57
  const isSlowSingleRender = durationMs >= DERIVED_IDENTITY_SINGLE_RENDER_WARN_MS;
@@ -87,33 +71,33 @@ function warnDerivedIdentityHotPath(profiler, durationMs) {
87
71
  `[useLiveQuery] Deriving live query identity from structured query IR is running on a hot render path (${reason}, max ${profiler.maxMs.toFixed(1)}ms). Provide an explicit queryKey to skip rebuilding and hashing the IR on every render: useLiveQuery({ queryKey: [...], query }).`
88
72
  );
89
73
  }
90
- function getExplicitQueryKey(value) {
91
- return value && typeof value === `object` && Array.isArray(value.queryKey) ? value.queryKey : void 0;
92
- }
93
74
  function getExplicitDbClient(value) {
94
75
  return value && typeof value === `object` && `client` in value && value.client !== void 0 ? value.client : void 0;
95
76
  }
96
77
  function prepareQueryValue(value, dbClient, deferredCollections) {
97
78
  return prepareLiveQueryValue(value, dbClient, deferredCollections);
98
79
  }
99
- function prepareDerivedQuery(value, dbClient, profiler, deferredCollections) {
100
- const shouldProfile = shouldWarnInDevelopment(
101
- `TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`
102
- );
80
+ function getIdentityScope(claims, dbClient, hookName) {
81
+ return {
82
+ claims,
83
+ client: dbClient,
84
+ bindingName: hookName,
85
+ onUnboundDescriptor: getUnboundDescriptorReporter(dbClient, hookName)
86
+ };
87
+ }
88
+ function prepareDerivedQuery(value, profiler, scope, deferredCollections) {
89
+ const shouldProfile = shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`) && process.env.NODE_ENV !== `production`;
103
90
  const start = shouldProfile ? getCurrentTime() : 0;
104
- const preparedValue = prepareQueryValue(value, dbClient, deferredCollections);
91
+ const preparedValue = prepareQueryValue(
92
+ value,
93
+ scope.client,
94
+ deferredCollections
95
+ );
105
96
  try {
106
- const identity = getPreparedLiveQueryIdentity(preparedValue);
107
97
  return {
108
- status: `hashable`,
109
98
  value: preparedValue,
110
- identityDeps: [`derived`, identity]
99
+ identityDeps: [`derived`, identifyLiveQuery(preparedValue, scope)]
111
100
  };
112
- } catch (error) {
113
- if (error instanceof UnhashableQueryIRError) {
114
- return { status: `unhashable`, value: preparedValue, error };
115
- }
116
- throw error;
117
101
  } finally {
118
102
  if (shouldProfile) {
119
103
  const durationMs = getCurrentTime() - start;
@@ -124,32 +108,18 @@ function prepareDerivedQuery(value, dbClient, profiler, deferredCollections) {
124
108
  }
125
109
  }
126
110
  }
127
- function warnUnhashableDerivedIdentity(error) {
128
- if (!shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)) {
129
- return;
130
- }
131
- const callsite = getWarningCallsite(4);
132
- if (warnedUnhashableIdentityCallsites.has(callsite)) {
133
- return;
134
- }
135
- warnedUnhashableIdentityCallsites.add(callsite);
136
- console.warn(
137
- `[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.`
138
- );
139
- }
140
- function useLiveQuery(configOrQueryOrCollection, deps) {
141
- return useLiveQueryImpl(configOrQueryOrCollection, deps);
111
+ function useLiveQuery(configOrQueryOrCollection) {
112
+ return useLiveQueryImpl(configOrQueryOrCollection);
142
113
  }
143
- function useLiveQueryForSuspense(configOrQueryOrCollection, deps) {
144
- return useLiveQueryImpl(configOrQueryOrCollection, deps, true);
114
+ function useLiveQueryForSuspense(configOrQueryOrCollection) {
115
+ return useLiveQueryImpl(configOrQueryOrCollection, true);
145
116
  }
146
117
  function createHookInstance(dbClient) {
147
118
  return {
148
119
  collection: null,
149
- deps: null,
120
+ identityDeps: null,
150
121
  config: null,
151
122
  client: dbClient,
152
- legacyUnhashableIdentity: [`legacy-unhashable`],
153
123
  derivedIdentityProfiler: {
154
124
  renderCount: 0,
155
125
  totalMs: 0,
@@ -157,7 +127,8 @@ function createHookInstance(dbClient) {
157
127
  warned: false
158
128
  },
159
129
  deferredCollections: /* @__PURE__ */ new Set(),
160
- sourceIds: createSourceIdBindings(),
130
+ sourceIds: createLiveQuerySourceIdClaims(),
131
+ cancelReclaimRelease: void 0,
161
132
  observer: null,
162
133
  queryHash: void 0,
163
134
  suspenseKey: void 0,
@@ -165,119 +136,89 @@ function createHookInstance(dbClient) {
165
136
  subscribe: null
166
137
  };
167
138
  }
168
- function useLiveQueryImpl(configOrQueryOrCollection, deps, forSuspense = false) {
139
+ function useLiveQueryImpl(configOrQueryOrCollection, forSuspense = false) {
169
140
  const contextDbClient = useOptionalDbClient();
170
141
  const inputIsCollection = isCollection(configOrQueryOrCollection);
171
142
  const dbClient = inputIsCollection ? contextDbClient : getExplicitDbClient(configOrQueryOrCollection) ?? contextDbClient;
172
- const resolvedDeps = deps ?? [];
173
143
  const instanceRef = useRef(null);
174
144
  const instance = instanceRef.current ??= createHookInstance(dbClient);
175
- const resumeDeferredCollections = () => resumeDeferredLiveQueryCollections(instance.deferredCollections);
176
- const queryKey = !inputIsCollection ? getExplicitQueryKey(configOrQueryOrCollection) : void 0;
177
- let preparedQueryValue = unpreparedQueryValue;
178
- let identityDeps;
179
- let streamIdentity = void 0;
180
- let identityError;
181
- if (queryKey) {
182
- identityDeps = queryKey;
183
- streamIdentity = [`queryKey`, queryKey];
184
- } else if (deps !== void 0) {
185
- identityDeps = resolvedDeps;
186
- if (dbClient || forSuspense || shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`))
187
- try {
188
- preparedQueryValue = prepareQueryValue(
189
- configOrQueryOrCollection,
190
- dbClient,
191
- instance.deferredCollections
192
- );
193
- streamIdentity = [
194
- `deps`,
195
- resolvedDeps,
196
- getPreparedLiveQueryIdentity(preparedQueryValue)
197
- ];
198
- } catch (error) {
199
- if (!(error instanceof UnhashableQueryIRError)) throw error;
200
- warnUnhashableDerivedIdentity(error);
201
- identityError = error;
202
- }
203
- } else if (inputIsCollection) {
204
- identityDeps = [];
205
- streamIdentity = [`collection`, configOrQueryOrCollection.id];
206
- } else {
207
- const preparation = prepareDerivedQuery(
208
- configOrQueryOrCollection,
209
- dbClient,
210
- instance.derivedIdentityProfiler,
211
- instance.deferredCollections
212
- );
213
- preparedQueryValue = preparation.value;
214
- if (preparation.status === `hashable`) {
145
+ const resumeDeferredCollections = () => {
146
+ instance.cancelReclaimRelease?.();
147
+ instance.cancelReclaimRelease = void 0;
148
+ resumeDeferredLiveQueryCollections(instance.deferredCollections);
149
+ };
150
+ try {
151
+ const queryKey = !inputIsCollection ? getLiveQueryKey(configOrQueryOrCollection) : void 0;
152
+ let preparedQueryValue = unpreparedQueryValue;
153
+ let identityDeps;
154
+ let streamIdentity = void 0;
155
+ let identityError;
156
+ if (queryKey) {
157
+ identityDeps = queryKey;
158
+ streamIdentity = [`queryKey`, queryKey];
159
+ } else if (inputIsCollection) {
160
+ identityDeps = [];
161
+ streamIdentity = [`collection`, configOrQueryOrCollection.id];
162
+ } else {
163
+ const preparation = prepareDerivedQuery(
164
+ configOrQueryOrCollection,
165
+ instance.derivedIdentityProfiler,
166
+ getIdentityScope(instance.sourceIds, dbClient, `useLiveQuery`),
167
+ instance.deferredCollections
168
+ );
169
+ preparedQueryValue = preparation.value;
215
170
  identityDeps = preparation.identityDeps;
216
171
  streamIdentity = preparation.identityDeps;
217
- } else {
218
- warnUnhashableDerivedIdentity(preparation.error);
219
- identityDeps = instance.legacyUnhashableIdentity;
220
- identityError = preparation.error;
221
172
  }
222
- }
223
- let queryHash;
224
- if (streamIdentity !== void 0) {
225
- try {
226
- queryHash = getStableValueHash(streamIdentity, `queryKey`);
227
- } catch (error) {
228
- if (error instanceof UnhashableQueryIRError) {
229
- if (queryKey !== void 0) throw error;
230
- identityError = error;
231
- } else {
232
- throw error;
173
+ let queryHash;
174
+ if (streamIdentity !== void 0) {
175
+ try {
176
+ queryHash = getStableValueHash(streamIdentity, `queryKey`);
177
+ } catch (error) {
178
+ if (error instanceof UnhashableQueryIRError) {
179
+ if (queryKey !== void 0) throw error;
180
+ identityError = error;
181
+ } else {
182
+ throw error;
183
+ }
233
184
  }
234
185
  }
235
- }
236
- if (deps !== void 0) {
237
- warnDeprecatedDepsArray();
238
- }
239
- if (queryKey === void 0 && deps === void 0 && preparedQueryValue !== unpreparedQueryValue) {
240
- claimSourceIds(
241
- instance.sourceIds,
186
+ const canReuseSuspenseKey = forSuspense && !inputIsCollection && queryHash !== void 0 && instance.collection !== null && instance.client === dbClient && instance.queryHash === queryHash && instance.suspenseKey !== void 0;
187
+ if (forSuspense && !inputIsCollection && queryHash && !canReuseSuspenseKey && preparedQueryValue === unpreparedQueryValue) {
188
+ preparedQueryValue = prepareQueryValue(
189
+ configOrQueryOrCollection,
190
+ dbClient,
191
+ instance.deferredCollections
192
+ );
193
+ }
194
+ const suspenseKey = queryHash && forSuspense && !inputIsCollection ? canReuseSuspenseKey ? instance.suspenseKey : getSourceQualifiedSuspenseKey(
242
195
  preparedQueryValue,
243
- dbClient,
244
- `useLiveQuery`,
245
- resumeDeferredCollections
246
- );
247
- }
248
- const canReuseSuspenseKey = forSuspense && !inputIsCollection && queryHash !== void 0 && instance.collection !== null && instance.client === dbClient && instance.queryHash === queryHash && instance.suspenseKey !== void 0;
249
- if (forSuspense && !inputIsCollection && queryHash && !canReuseSuspenseKey && preparedQueryValue === unpreparedQueryValue) {
250
- preparedQueryValue = prepareQueryValue(
251
- configOrQueryOrCollection,
252
- dbClient,
253
- instance.deferredCollections
254
- );
255
- }
256
- const suspenseKey = queryHash && forSuspense && !inputIsCollection ? canReuseSuspenseKey ? instance.suspenseKey : getSourceQualifiedSuspenseKey(preparedQueryValue, queryHash, dbClient) : queryHash;
257
- const suspenseCollections = forSuspense && !inputIsCollection && suspenseKey ? getSuspenseCollections(dbClient) : void 0;
258
- const suspenseEntry = suspenseKey ? suspenseCollections?.get(suspenseKey) : void 0;
259
- const suspenseCollection = suspenseEntry?.collection;
260
- const identityChanged = instance.deps === null || (deps !== void 0 ? instance.deps.length !== identityDeps.length || instance.deps.some((dep, index) => dep !== identityDeps[index]) : !deepEquals(instance.deps, identityDeps));
261
- const needsNewCollection = !instance.collection || inputIsCollection && instance.config !== configOrQueryOrCollection || !inputIsCollection && (instance.client !== dbClient || identityChanged);
262
- if (needsNewCollection) {
263
- if (inputIsCollection) {
264
- const syncMode = configOrQueryOrCollection.config?.syncMode;
265
- if (syncMode === `on-demand` && shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)) {
266
- console.warn(
267
- `[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.
196
+ queryHash,
197
+ dbClient
198
+ ) : queryHash;
199
+ const suspenseCollections = forSuspense && !inputIsCollection && suspenseKey ? getSuspenseCollections(dbClient) : void 0;
200
+ const suspenseEntry = suspenseKey ? suspenseCollections?.get(suspenseKey) : void 0;
201
+ const suspenseCollection = suspenseEntry?.collection;
202
+ const identityChanged = instance.identityDeps === null || !deepEquals(instance.identityDeps, identityDeps);
203
+ const needsNewCollection = !instance.collection || inputIsCollection && instance.config !== configOrQueryOrCollection || !inputIsCollection && (instance.client !== dbClient || identityChanged);
204
+ if (needsNewCollection) {
205
+ if (inputIsCollection) {
206
+ const syncMode = configOrQueryOrCollection.config?.syncMode;
207
+ if (syncMode === `on-demand` && shouldWarnInDevelopment(
208
+ `TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`
209
+ ) && process.env.NODE_ENV !== `production`) {
210
+ console.warn(
211
+ `[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.
268
212
 
269
213
  Instead, use a query builder function:
270
214
  const { data } = useLiveQuery({ query: (q) => q.from({ c: myCollection }).select(({ c }) => c) })
271
215
 
272
216
  Or switch to syncMode "eager" if you want all data to sync automatically.`
273
- );
274
- }
275
- configOrQueryOrCollection.startSyncImmediate();
276
- instance.collection = configOrQueryOrCollection;
277
- instance.config = configOrQueryOrCollection;
278
- } else {
279
- if (suspenseCollection) {
280
- instance.collection = suspenseCollection;
217
+ );
218
+ }
219
+ configOrQueryOrCollection.startSyncImmediate();
220
+ instance.collection = configOrQueryOrCollection;
221
+ instance.config = configOrQueryOrCollection;
281
222
  } else {
282
223
  if (preparedQueryValue === unpreparedQueryValue) {
283
224
  preparedQueryValue = prepareQueryValue(
@@ -286,28 +227,38 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
286
227
  instance.deferredCollections
287
228
  );
288
229
  }
289
- if (!dbClient) {
290
- getPreparedSources(preparedQueryValue, dbClient, `useLiveQuery`);
291
- }
292
- instance.collection = resolveLiveQueryValue(preparedQueryValue, {
293
- gcTime: forSuspense ? DEFAULT_SUSPENSE_GC_TIME_MS : DEFAULT_GC_TIME_MS,
294
- // Hydration and Suspense key the live-query Collection by identity.
295
- pool: !forSuspense && !dbClient,
296
- // Without a DbClient, Suspense starts it through a preload below.
297
- startSync: !forSuspense || dbClient !== void 0
298
- });
299
- if (suspenseCollections && suspenseKey && instance.collection) {
300
- const collection = instance.collection;
301
- const removeCleanupListener = collection.on(
302
- `status:cleaned-up`,
303
- () => releaseSuspenseCollection(
304
- suspenseCollections,
305
- suspenseKey,
306
- collection
307
- )
308
- );
309
- const removeErrorListener = collection.on(`status:error`, () => {
310
- setTimeout(
230
+ if (suspenseCollection) {
231
+ instance.collection = suspenseCollection;
232
+ } else {
233
+ if (!dbClient && queryKey !== void 0) {
234
+ getPreparedSources(preparedQueryValue, dbClient, `useLiveQuery`);
235
+ }
236
+ instance.collection = resolveLiveQueryValue(preparedQueryValue, {
237
+ gcTime: forSuspense ? DEFAULT_SUSPENSE_GC_TIME_MS : DEFAULT_GC_TIME_MS,
238
+ // Hydration and Suspense key the live-query Collection by identity,
239
+ // so they never pool, even when the config passes a pool.
240
+ pool: forSuspense || dbClient ? false : void 0,
241
+ // Without a DbClient, Suspense starts it through a preload below.
242
+ startSync: !forSuspense || dbClient !== void 0
243
+ });
244
+ if (instance.collection && !isCollection(preparedQueryValue)) {
245
+ instance.cancelReclaimRelease?.();
246
+ instance.cancelReclaimRelease = releaseDeferredOnReclaim(
247
+ instance.collection,
248
+ instance.deferredCollections
249
+ );
250
+ }
251
+ if (suspenseCollections && suspenseKey && instance.collection) {
252
+ const collection = instance.collection;
253
+ const removeCleanupListener = collection.on(
254
+ `status:cleaned-up`,
255
+ () => releaseSuspenseCollection(
256
+ suspenseCollections,
257
+ suspenseKey,
258
+ collection
259
+ )
260
+ );
261
+ const retireAfterRetry = () => setTimeout(
311
262
  () => releaseSuspenseCollection(
312
263
  suspenseCollections,
313
264
  suspenseKey,
@@ -315,66 +266,78 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
315
266
  ),
316
267
  0
317
268
  );
318
- });
319
- const entry = {
320
- collection,
321
- removeStatusListeners: () => {
322
- removeCleanupListener();
323
- removeErrorListener();
324
- }
325
- };
326
- suspenseCollections.set(suspenseKey, entry);
269
+ const removeErrorListener = collection.on(
270
+ `status:error`,
271
+ retireAfterRetry
272
+ );
273
+ const removeCrashListener = collection.on(
274
+ `status:crashed`,
275
+ retireAfterRetry
276
+ );
277
+ const entry = {
278
+ collection,
279
+ removeStatusListeners: () => {
280
+ removeCleanupListener();
281
+ removeErrorListener();
282
+ removeCrashListener();
283
+ }
284
+ };
285
+ suspenseCollections.set(suspenseKey, entry);
286
+ }
327
287
  }
288
+ instance.config = configOrQueryOrCollection;
289
+ instance.identityDeps = [...identityDeps];
328
290
  }
329
- instance.config = configOrQueryOrCollection;
330
- instance.deps = [...identityDeps];
291
+ instance.client = dbClient;
292
+ instance.queryHash = queryHash;
293
+ instance.suspenseKey = suspenseKey;
294
+ instance.identityError = identityError;
331
295
  }
332
- instance.client = dbClient;
333
- instance.queryHash = queryHash;
334
- instance.suspenseKey = suspenseKey;
335
- instance.identityError = identityError;
336
- }
337
- if (needsNewCollection) {
338
- instance.observer = createLiveQueryObserver(instance.collection, {
339
- mode: `wholesale`,
340
- client: dbClient,
341
- queryHash: instance.queryHash,
342
- onPreload: resumeDeferredCollections
343
- });
344
- }
345
- const observer = instance.observer;
346
- if (forSuspense && !dbClient && (instance.collection?.status === `idle` || instance.collection?.status === `cleaned-up`)) {
347
- observer.preloadForInitialRender().catch(() => {
348
- });
349
- }
350
- if (!instance.subscribe || needsNewCollection) {
351
- instance.subscribe = (onStoreChange) => {
352
- const unsubscribe = observer.subscribe(onStoreChange);
353
- resumeDeferredCollections();
354
- return unsubscribe;
355
- };
356
- }
357
- const returned = useSyncExternalStore(
358
- instance.subscribe,
359
- () => observer.getSnapshot(),
360
- () => observer.getServerSnapshot()
361
- );
362
- if (forSuspense) {
363
- setLiveQueryResultInfo(returned, {
364
- client: dbClient,
365
- queryHash: instance.queryHash,
366
- identityError: instance.identityError,
367
- observer
368
- });
296
+ if (needsNewCollection) {
297
+ instance.observer = createLiveQueryObserver(instance.collection, {
298
+ mode: `wholesale`,
299
+ client: dbClient,
300
+ queryHash: instance.queryHash,
301
+ onPreload: resumeDeferredCollections,
302
+ suspendUntilReady: forSuspense
303
+ });
304
+ }
305
+ const observer = instance.observer;
306
+ if (forSuspense && !dbClient && (instance.collection?.status === `idle` || instance.collection?.status === `cleaned-up`)) {
307
+ observer.preload().catch(() => {
308
+ });
309
+ }
310
+ if (!instance.subscribe || needsNewCollection) {
311
+ instance.subscribe = (onStoreChange) => {
312
+ const unsubscribe = observer.subscribe(onStoreChange);
313
+ resumeDeferredCollections();
314
+ return unsubscribe;
315
+ };
316
+ }
317
+ const returned = useSyncExternalStore(
318
+ instance.subscribe,
319
+ () => observer.getSnapshot(),
320
+ () => observer.getServerSnapshot(forSuspense)
321
+ );
322
+ if (forSuspense) {
323
+ setLiveQueryResultInfo(returned, {
324
+ client: dbClient,
325
+ queryHash: instance.queryHash,
326
+ identityError: instance.identityError,
327
+ observer,
328
+ deferredCollections: instance.deferredCollections
329
+ });
330
+ }
331
+ return returned;
332
+ } catch (error) {
333
+ return rejectLiveQueryInput(instance.deferredCollections, error);
369
334
  }
370
- return returned;
371
335
  }
372
336
  export {
337
+ getIdentityScope,
373
338
  prepareDerivedQuery,
374
339
  prepareQueryValue,
375
340
  useLiveQuery,
376
- useLiveQueryForSuspense,
377
- warnDeprecatedDepsArray,
378
- warnUnhashableDerivedIdentity
341
+ useLiveQueryForSuspense
379
342
  };
380
343
  //# sourceMappingURL=useLiveQuery.js.map