@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
@@ -2,31 +2,22 @@
2
2
 
3
3
  import { useCallback, useRef, useSyncExternalStore } from 'react'
4
4
  import {
5
- assertLiveQueryWindowManyResult,
6
- compareLiveQueryWindowDependencies,
7
- createLiveQueryCollection,
8
- createLiveQueryWindowController,
5
+ bindLiveQueryWindow,
6
+ createLiveQuerySourceIdClaims,
9
7
  fetchNextLiveQueryWindowPage,
10
- getLiveQueryWindowCollectionWarning,
11
- getLiveQueryWindowInputKind,
12
- liveQueryWindowMatches,
13
- normalizeLiveQueryWindowPageSize,
14
- resolveLiveQueryWindowInput,
15
- shouldPreserveLiveQueryWindowPageCount,
8
+ getLiveQueryKey,
9
+ isCollection,
10
+ readLiveQueryWindowPageShape,
11
+ rejectLiveQueryInput,
12
+ releaseDeferredOnReclaim,
13
+ resumeDeferredLiveQueryCollections,
16
14
  } from '@tanstack/db'
17
15
  import { useOptionalDbClient } from './DbProvider'
18
16
  import {
19
- claimSourceIds,
20
- createSourceIdBindings,
21
- resumeSyncStarts,
22
- } from './source-id-bindings'
23
- import {
17
+ getIdentityScope,
24
18
  prepareDerivedQuery,
25
19
  prepareQueryValue,
26
- warnDeprecatedDepsArray,
27
- warnUnhashableDerivedIdentity,
28
20
  } from './useLiveQuery'
29
- import type { SourceIdBindings } from './source-id-bindings'
30
21
  import type {
31
22
  DerivedIdentityProfiler,
32
23
  LiveQueryKey,
@@ -40,14 +31,13 @@ import type {
40
31
  DbClient,
41
32
  InferResultType,
42
33
  InitialQueryBuilder,
43
- LiveQueryPersistedStatus,
44
- LiveQueryWindowController,
34
+ LiveQuerySourceIdClaims,
35
+ LiveQueryStatusFlags,
36
+ LiveQueryWindowBinding,
45
37
  NonSingleResult,
46
38
  QueryBuilder,
47
39
  } from '@tanstack/db'
48
40
 
49
- // Live queries created here are cleaned up immediately (0 disables GC).
50
- const DEFAULT_GC_TIME_MS = 1
51
41
  const unpreparedQueryValue = Symbol(`unpreparedQueryValue`)
52
42
 
53
43
  // Keep the generic parameter for existing typed config wrappers.
@@ -75,79 +65,40 @@ export type UseLiveInfiniteQueryReturn<TContext extends Context> = Omit<
75
65
  fetchNextPage: () => Promise<void>
76
66
  hasNextPage: boolean
77
67
  isFetchingNextPage: boolean
78
- error: unknown
79
68
  }
80
69
 
81
70
  export type UseLiveInfiniteQueryReturnWithCollection<
82
71
  TResult extends object,
83
72
  TKey extends string | number,
84
73
  TUtils extends Record<string, any>,
85
- > = {
74
+ > = LiveQueryStatusFlags & {
86
75
  data: Array<TResult>
87
76
  state: Map<TKey, TResult>
88
77
  collection: Collection<TResult, TKey, TUtils> & NonSingleResult
89
78
  status: CollectionStatus
90
- isLoading: boolean
91
- isReady: boolean
92
- persistedStatus: LiveQueryPersistedStatus
93
- isPersistedReady: boolean
94
- persistedError: unknown | undefined
95
- isIdle: boolean
96
- isError: boolean
97
- isCleanedUp: boolean
98
79
  isEnabled: true
99
80
  pages: Array<Array<TResult>>
100
81
  pageParams: Array<number>
101
82
  fetchNextPage: () => Promise<void>
102
83
  hasNextPage: boolean
103
84
  isFetchingNextPage: boolean
104
- error: unknown
105
85
  }
106
86
 
107
87
  type EnabledLiveQueryReturn<TContext extends Context> = ReturnType<
108
88
  typeof useLiveQuery<TContext>
109
89
  >
110
90
 
111
- type InfiniteQueryRenderState = {
112
- inputKind: `collection` | `query`
113
- inputCollection: Collection<any, any, any> | null
114
- inputQuery: unknown
115
- client: DbClient | undefined
116
- identityMode: `collection` | `queryKey` | `legacyDeps` | `derived`
117
- dependencies: Array<unknown> | null
118
- pageSize: number
119
- initialPageParam: number
120
- collection: Collection<any, any, any>
121
- controller: LiveQueryWindowController<any, any>
122
- warning: string | null
123
- warned: boolean
124
- deferredCollections: Set<
125
- CollectionImplType<any, string | number, any, any, any>
126
- >
127
- }
128
-
129
- type RenderedWindowCollection = {
130
- client: DbClient | undefined
131
- dependencies: Array<unknown>
132
- collection: Collection<any, any, any>
91
+ /** One render's window binding and what its commit still owes. */
92
+ type InfiniteQueryRender = {
93
+ binding: LiveQueryWindowBinding
94
+ /** The query callback whose identity this render derived, if any. */
95
+ derivedQuery: unknown
133
96
  deferredCollections: Set<
134
97
  CollectionImplType<any, string | number, any, any, any>
135
98
  >
136
- }
137
-
138
- /**
139
- * Whether a recorded query identity matches the current one. Legacy deps
140
- * compare by reference. Derived identities and query keys compare by structure.
141
- */
142
- function queryIdentityMatches(
143
- comparison: { changed: boolean; structurallyEqual: boolean },
144
- sameClient: boolean,
145
- usesLegacyDeps: boolean,
146
- ): boolean {
147
- return (
148
- sameClient &&
149
- (usesLegacyDeps ? !comparison.changed : comparison.structurallyEqual)
150
- )
99
+ /** Drops the reclaim release when this render commits. */
100
+ cancelReclaimRelease: (() => void) | undefined
101
+ warned: boolean
151
102
  }
152
103
 
153
104
  /**
@@ -158,7 +109,6 @@ function queryIdentityMatches(
158
109
  *
159
110
  * @param queryFn - Query function that defines what data to fetch. Must include `.orderBy()` for setWindow to work.
160
111
  * @param config - Configuration including pageSize and an optional initial page label
161
- * @param deps - Deprecated array of dependencies that trigger query re-execution when changed
162
112
  * @returns Object with pages, data, and pagination controls
163
113
  */
164
114
 
@@ -176,272 +126,165 @@ export function useLiveInfiniteQuery<
176
126
  export function useLiveInfiniteQuery<TContext extends Context>(
177
127
  queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,
178
128
  config: UseLiveInfiniteQueryConfig<TContext>,
179
- deps?: Array<unknown>,
180
129
  ): UseLiveInfiniteQueryReturn<TContext>
181
130
 
182
131
  // Implementation
183
132
  export function useLiveInfiniteQuery<TContext extends Context>(
184
133
  queryFnOrCollection: any,
185
134
  config: UseLiveInfiniteQueryConfig<TContext>,
186
- deps?: Array<unknown>,
187
135
  ): UseLiveInfiniteQueryReturn<TContext> {
188
- if (`getNextPageParam` in config) {
189
- throw new Error(
190
- `getNextPageParam is not supported by useLiveInfiniteQuery. Use an on-demand collection and fulfill meta.loadSubsetOptions in queryFn for server pagination.`,
191
- )
192
- }
193
- const pageSize = normalizeLiveQueryWindowPageSize(config.pageSize)
194
- const initialPageParam = config.initialPageParam ?? 0
136
+ const pageShape = readLiveQueryWindowPageShape(config)
195
137
  const contextDbClient = useOptionalDbClient()
196
138
  const dbClient = config.client ?? contextDbClient
139
+ const inputIsCollection = isCollection(queryFnOrCollection)
197
140
 
198
- const inputIsCollection =
199
- getLiveQueryWindowInputKind(queryFnOrCollection) === `collection`
200
-
201
- const committedRef = useRef<InfiniteQueryRenderState | null>(null)
141
+ const committedRef = useRef<InfiniteQueryRender | null>(null)
202
142
  const committed = committedRef.current
203
- // Like useLiveQuery's instance memo, keep the window collection built by the
204
- // latest render. A second render before commit, such as a StrictMode double
205
- // render, reuses it instead of starting another one. Only the collection is
206
- // kept. Each render still builds its own controller and page count from the
207
- // committed state, which an uncommitted render never changes.
208
- const renderedCollectionRef = useRef<RenderedWindowCollection | null>(null)
209
- const sourceIdsRef = useRef<SourceIdBindings | null>(null)
210
- const inputKind = inputIsCollection ? `collection` : `query`
143
+ // Like useLiveQuery's instance memo, keep the latest render whose binding
144
+ // built a window collection. A second render before commit, such as a
145
+ // StrictMode double render, reuses that collection instead of starting
146
+ // another. Each render still builds its own controller and page count from
147
+ // the committed binding, which an uncommitted render never changes.
148
+ const renderedRef = useRef<InfiniteQueryRender | null>(null)
149
+ const sourceIdsRef = useRef<LiveQuerySourceIdClaims | null>(null)
211
150
  const derivedIdentityProfilerRef = useRef<DerivedIdentityProfiler>({
212
151
  renderCount: 0,
213
152
  totalMs: 0,
214
153
  maxMs: 0,
215
154
  warned: false,
216
155
  })
217
- const legacyUnhashableIdentityRef = useRef<Array<unknown>>([
218
- `legacy-unhashable`,
219
- ])
220
156
  const deferredCollections = new Set<
221
157
  CollectionImplType<any, string | number, any, any, any>
222
158
  >()
159
+ const identityScope = () =>
160
+ getIdentityScope(
161
+ (sourceIdsRef.current ??= createLiveQuerySourceIdClaims()),
162
+ dbClient,
163
+ `useLiveInfiniteQuery`,
164
+ )
223
165
 
166
+ const queryKey = getLiveQueryKey(config)
224
167
  let preparedQueryValue: unknown | typeof unpreparedQueryValue =
225
168
  unpreparedQueryValue
226
- let identityDeps: ReadonlyArray<unknown> = []
227
- let identityMode: InfiniteQueryRenderState[`identityMode`] = `collection`
228
-
229
- if (!inputIsCollection) {
230
- if (config.queryKey !== undefined) {
231
- identityMode = `queryKey`
232
- identityDeps = config.queryKey
233
- } else if (deps !== undefined) {
234
- identityMode = `legacyDeps`
235
- identityDeps = deps
236
- warnDeprecatedDepsArray(`useLiveInfiniteQuery`)
237
- } else if (
238
- committed?.identityMode === `derived` &&
239
- committed.inputQuery === queryFnOrCollection &&
240
- committed.client === dbClient
241
- ) {
242
- identityMode = `derived`
243
- identityDeps = committed.dependencies ?? []
244
- } else {
245
- identityMode = `derived`
246
- const preparation = prepareDerivedQuery(
247
- queryFnOrCollection,
248
- dbClient,
249
- derivedIdentityProfilerRef.current,
250
- deferredCollections,
251
- )
252
- preparedQueryValue = preparation.value
253
- claimSourceIds(
254
- (sourceIdsRef.current ??= createSourceIdBindings()),
255
- preparation.value,
256
- dbClient,
257
- `useLiveInfiniteQuery`,
258
- () => resumeSyncStarts(deferredCollections),
259
- )
260
- if (preparation.status === `hashable`) {
261
- identityDeps = preparation.identityDeps
262
- } else {
263
- warnUnhashableDerivedIdentity(preparation.error)
264
- identityDeps = legacyUnhashableIdentityRef.current
265
- }
266
- }
267
- }
268
-
269
- const usesLegacyDeps =
270
- !inputIsCollection && config.queryKey === undefined && deps !== undefined
271
- const dependencyComparison = compareLiveQueryWindowDependencies(
272
- committed?.dependencies,
273
- identityDeps,
274
- )
275
- const sameClient = committed?.client === dbClient
276
- const dependenciesChanged =
277
- !inputIsCollection &&
278
- !queryIdentityMatches(dependencyComparison, sameClient, usesLegacyDeps)
279
- const dependenciesStructurallyEqual =
280
- usesLegacyDeps && sameClient && dependencyComparison.structurallyEqual
281
- const needsNewCollection =
282
- committed === null ||
283
- committed.inputKind !== inputKind ||
284
- (inputIsCollection && committed.inputCollection !== queryFnOrCollection) ||
285
- dependenciesChanged
286
- const pageShapeChanged =
287
- committed === null ||
288
- committed.pageSize !== pageSize ||
289
- committed.initialPageParam !== initialPageParam
290
- const needsNewController =
291
- committed === null || needsNewCollection || pageShapeChanged
169
+ let dependencies: ReadonlyArray<unknown> | undefined
170
+ let derivedQuery: unknown = undefined
292
171
 
293
- let renderState = committed
294
- if (needsNewController) {
295
- let collection = committed?.collection
296
- let warning: string | null = null
297
- let startInRender = false
298
- let suppliedCollection = false
299
- let createdCollection = false
300
-
301
- const canPreservePageCount = shouldPreserveLiveQueryWindowPageCount({
302
- hasPreviousController: committed !== null,
303
- previousInputKind: committed?.inputKind,
304
- inputKind,
305
- sameCollection:
306
- inputIsCollection && committed?.inputCollection === queryFnOrCollection,
307
- dependenciesChanged,
308
- dependenciesStructurallyEqual,
309
- pageShapeChanged,
310
- })
311
- const previousPageCount = committed
312
- ? Math.max(1, committed.controller.getSnapshot().pages.length)
313
- : 1
314
- const initialPageCount = canPreservePageCount ? previousPageCount : 1
315
- // The peek-ahead window for every retained page.
316
- const requiredLimit = initialPageCount * pageSize + 1
317
-
318
- if (needsNewCollection) {
319
- let inputValue = queryFnOrCollection
320
- if (!inputIsCollection) {
321
- if (preparedQueryValue === unpreparedQueryValue) {
322
- preparedQueryValue = prepareQueryValue(
323
- queryFnOrCollection,
324
- dbClient,
325
- deferredCollections,
326
- )
327
- }
328
- inputValue = () => preparedQueryValue
329
- }
330
- const rendered = renderedCollectionRef.current
331
- if (
332
- !inputIsCollection &&
333
- rendered !== null &&
334
- queryIdentityMatches(
335
- compareLiveQueryWindowDependencies(
336
- rendered.dependencies,
337
- identityDeps,
338
- ),
339
- rendered.client === dbClient,
340
- usesLegacyDeps,
341
- ) &&
342
- rendered.collection.status !== `cleaned-up` &&
343
- rendered.collection.status !== `error` &&
344
- liveQueryWindowMatches(rendered.collection, requiredLimit)
172
+ const rendered = renderedRef.current
173
+ let binding: LiveQueryWindowBinding
174
+ // The render boundary: a render that throws anywhere after preparation,
175
+ // such as an unhashable, single-result, or uncompilable query, never
176
+ // commits, so it releases the sync starts it deferred here.
177
+ try {
178
+ if (!inputIsCollection) {
179
+ if (queryKey !== undefined) {
180
+ dependencies = queryKey
181
+ } else if (
182
+ committed !== null &&
183
+ committed.derivedQuery === queryFnOrCollection &&
184
+ committed.binding.scope === dbClient
345
185
  ) {
346
- // An earlier render built this collection and nothing has committed
347
- // since. Its window is exactly the retained pages, so its first
348
- // rows are correct. Sources it deferred still resume at commit. A
349
- // collection in terminal error, such as after its source restarted,
350
- // cannot serve the query again, so a new one replaces it.
351
- collection = rendered.collection
352
- for (const deferred of rendered.deferredCollections) {
353
- deferredCollections.add(deferred)
354
- }
186
+ // The same callback in the same client scope keeps its identity.
187
+ derivedQuery = queryFnOrCollection
188
+ dependencies = committed.binding.dependencies
355
189
  } else {
356
- const input = resolveLiveQueryWindowInput<TContext>(inputValue)
357
- if (input.kind === `collection`) {
358
- collection = input.collection
359
- suppliedCollection = true
360
- // A supplied collection is never reused, so release the last one.
361
- renderedCollectionRef.current = null
362
- } else {
363
- // Wrap the query with the peek-ahead window for every retained page,
364
- // so a collection that starts syncing now never publishes fewer rows
365
- // than the controller's pages. The controller grows the limit via
366
- // setWindow.
367
- collection = createLiveQueryCollection({
368
- query: input.query.limit(requiredLimit).offset(0),
369
- gcTime: DEFAULT_GC_TIME_MS,
370
- })
371
- startInRender = true
372
- createdCollection = true
373
- }
190
+ const preparation = prepareDerivedQuery(
191
+ queryFnOrCollection,
192
+ derivedIdentityProfilerRef.current,
193
+ identityScope(),
194
+ deferredCollections,
195
+ )
196
+ preparedQueryValue = preparation.value
197
+ derivedQuery = queryFnOrCollection
198
+ dependencies = preparation.identityDeps
374
199
  }
375
200
  }
376
201
 
377
- if (!collection) {
378
- throw new Error(`useLiveInfiniteQuery: Failed to create a collection.`)
202
+ // The binding calls this only when it compiles a new window collection.
203
+ const compileQuery = () => {
204
+ if (preparedQueryValue === unpreparedQueryValue) {
205
+ preparedQueryValue = prepareQueryValue(
206
+ queryFnOrCollection,
207
+ dbClient,
208
+ deferredCollections,
209
+ )
210
+ }
211
+ return preparedQueryValue
379
212
  }
380
213
 
381
- if (inputIsCollection) {
382
- warning =
383
- getLiveQueryWindowCollectionWarning(collection, pageSize + 1) ?? null
384
- } else {
385
- assertLiveQueryWindowManyResult(collection)
386
- }
387
- // Like useLiveQuery, start sync during render once the input is valid. A
388
- // render reads only rows the sources already hold, such as a preloaded
389
- // collection's, and sends no request until the subscription commits. GC
390
- // reclaims a render that never commits. A supplied window that the
391
- // controller must still adjust waits for the subscription.
392
- if (suppliedCollection) {
393
- startInRender = liveQueryWindowMatches(collection, requiredLimit)
394
- }
395
- if (startInRender) collection.startSyncImmediate()
396
- // Keep a created collection for the next render only once it validated and
397
- // started, so a retry after a startup error builds a new one and throws.
398
- if (createdCollection) {
399
- renderedCollectionRef.current = {
214
+ binding = bindLiveQueryWindow(
215
+ committed?.binding ?? null,
216
+ {
217
+ ...pageShape,
218
+ input:
219
+ typeof queryFnOrCollection === `function`
220
+ ? compileQuery
221
+ : queryFnOrCollection,
222
+ dependencies,
223
+ scope: dbClient,
400
224
  client: dbClient,
401
- dependencies: [...identityDeps],
402
- collection,
403
- deferredCollections,
225
+ queryKey,
226
+ recompile: `identity`,
227
+ },
228
+ rendered?.binding,
229
+ )
230
+ } catch (error) {
231
+ rejectLiveQueryInput(deferredCollections, error)
232
+ }
233
+
234
+ let currentRenderState = committed
235
+ if (binding !== committed?.binding) {
236
+ const newCollection = binding.collection !== committed?.binding.collection
237
+ if (newCollection && binding.collection === rendered?.binding.collection) {
238
+ // Sources the earlier render deferred still resume at this commit.
239
+ for (const deferred of rendered.deferredCollections) {
240
+ deferredCollections.add(deferred)
404
241
  }
405
242
  }
406
-
407
- renderState = {
408
- inputKind,
409
- inputCollection: inputIsCollection ? collection : null,
410
- inputQuery: inputIsCollection ? null : queryFnOrCollection,
411
- client: dbClient,
412
- identityMode,
413
- dependencies: inputIsCollection ? null : [...identityDeps],
414
- pageSize,
415
- initialPageParam,
416
- collection,
417
- controller: createLiveQueryWindowController(collection, {
418
- pageSize,
419
- initialPageParam,
420
- initialPageCount,
421
- }),
422
- warning,
423
- warned: false,
243
+ const reusesRendered =
244
+ newCollection && binding.collection === rendered?.binding.collection
245
+ currentRenderState = {
246
+ binding,
247
+ derivedQuery,
424
248
  deferredCollections,
249
+ // A render that never commits releases its deferred sync starts when
250
+ // garbage collection reclaims the window collection it built. A reused
251
+ // collection keeps the release its first render registered.
252
+ cancelReclaimRelease: reusesRendered
253
+ ? rendered.cancelReclaimRelease
254
+ : newCollection && binding.ownsCollection
255
+ ? releaseDeferredOnReclaim(binding.collection, deferredCollections)
256
+ : undefined,
257
+ warned: false,
258
+ }
259
+ // Keep a created collection for the next render only once it validated
260
+ // and started, so a retry after a startup error builds a new one.
261
+ if (binding.supplied) renderedRef.current = null
262
+ else if (newCollection && binding.ownsCollection) {
263
+ renderedRef.current = currentRenderState
425
264
  }
426
265
  }
427
- const currentRenderState = renderState!
428
- const controller = currentRenderState.controller
266
+ const renderState = currentRenderState!
267
+ const controller = renderState.binding.controller
429
268
 
430
269
  const subscribe = useCallback(
431
270
  (onStoreChange: () => void) => {
432
271
  const unsubscribe = controller.subscribe(onStoreChange)
433
- committedRef.current = currentRenderState
434
- if (currentRenderState.warning && !currentRenderState.warned) {
435
- currentRenderState.warned = true
436
- console.warn(currentRenderState.warning)
272
+ committedRef.current = renderState
273
+ if (renderState.binding.warning && !renderState.warned) {
274
+ renderState.warned = true
275
+ console.warn(renderState.binding.warning)
437
276
  }
438
- resumeSyncStarts(currentRenderState.deferredCollections)
277
+ // A commit releases the starts itself, so it drops the reclaim release.
278
+ renderState.cancelReclaimRelease?.()
279
+ resumeDeferredLiveQueryCollections(renderState.deferredCollections)
439
280
  return unsubscribe
440
281
  },
441
- [controller, currentRenderState],
282
+ [controller, renderState],
442
283
  )
443
284
  const getSnapshot = useCallback(() => controller.getSnapshot(), [controller])
444
- const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
285
+ const snapshot = useSyncExternalStore(subscribe, getSnapshot, () =>
286
+ controller.getServerSnapshot(),
287
+ )
445
288
 
446
289
  const fetchNextPage = useCallback(
447
290
  () => fetchNextLiveQueryWindowPage(controller),
@@ -456,12 +299,11 @@ export function useLiveInfiniteQuery<TContext extends Context>(
456
299
  status: snapshot.status as EnabledLiveQueryReturn<TContext>[`status`],
457
300
  isLoading: snapshot.isLoading,
458
301
  isReady: snapshot.isReady,
459
- persistedStatus: snapshot.persistedStatus,
460
- isPersistedReady: snapshot.isPersistedReady,
461
- persistedError: snapshot.persistedError,
462
302
  isIdle: snapshot.isIdle,
463
- isError: snapshot.isError,
303
+ isLoadingError: snapshot.isLoadingError,
304
+ isCrashed: snapshot.isCrashed,
464
305
  isCleanedUp: snapshot.isCleanedUp,
306
+ isSyncError: snapshot.isSyncError,
465
307
  collection:
466
308
  snapshot.collection as EnabledLiveQueryReturn<TContext>[`collection`],
467
309
  isEnabled: snapshot.isEnabled,
@@ -470,6 +312,6 @@ export function useLiveInfiniteQuery<TContext extends Context>(
470
312
  fetchNextPage,
471
313
  hasNextPage: snapshot.hasNextPage,
472
314
  isFetchingNextPage: snapshot.isFetchingNextPage,
473
- error: snapshot.error,
315
+ health: snapshot.health,
474
316
  }
475
317
  }