@tanstack/react-db 0.5.12 → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/live-query-internals.cjs.map +1 -1
- package/dist/cjs/live-query-internals.d.cts +3 -1
- package/dist/cjs/source-id-bindings.cjs +9 -42
- package/dist/cjs/source-id-bindings.cjs.map +1 -1
- package/dist/cjs/source-id-bindings.d.cts +6 -17
- package/dist/cjs/useLiveInfiniteQuery.cjs +93 -173
- package/dist/cjs/useLiveInfiniteQuery.cjs.map +1 -1
- package/dist/cjs/useLiveInfiniteQuery.d.cts +3 -14
- package/dist/cjs/useLiveQuery.cjs +187 -224
- package/dist/cjs/useLiveQuery.cjs.map +1 -1
- package/dist/cjs/useLiveQuery.d.cts +37 -136
- package/dist/cjs/useLiveSuspenseQuery.cjs +71 -64
- package/dist/cjs/useLiveSuspenseQuery.cjs.map +1 -1
- package/dist/cjs/useLiveSuspenseQuery.d.cts +10 -9
- package/dist/cjs/usePacedMutations.cjs.map +1 -1
- package/dist/cjs/usePacedMutations.d.cts +1 -1
- package/dist/esm/live-query-internals.d.ts +3 -1
- package/dist/esm/live-query-internals.js.map +1 -1
- package/dist/esm/source-id-bindings.d.ts +6 -17
- package/dist/esm/source-id-bindings.js +9 -40
- package/dist/esm/source-id-bindings.js.map +1 -1
- package/dist/esm/useLiveInfiniteQuery.d.ts +3 -14
- package/dist/esm/useLiveInfiniteQuery.js +95 -175
- package/dist/esm/useLiveInfiniteQuery.js.map +1 -1
- package/dist/esm/useLiveQuery.d.ts +37 -136
- package/dist/esm/useLiveQuery.js +189 -226
- package/dist/esm/useLiveQuery.js.map +1 -1
- package/dist/esm/useLiveSuspenseQuery.d.ts +10 -9
- package/dist/esm/useLiveSuspenseQuery.js +71 -64
- package/dist/esm/useLiveSuspenseQuery.js.map +1 -1
- package/dist/esm/usePacedMutations.d.ts +1 -1
- package/dist/esm/usePacedMutations.js.map +1 -1
- package/package.json +3 -3
- package/skills/react-db/SKILL.md +24 -15
- package/src/live-query-internals.ts +3 -0
- package/src/source-id-bindings.ts +16 -62
- package/src/useLiveInfiniteQuery.ts +138 -296
- package/src/useLiveQuery.ts +329 -495
- package/src/useLiveSuspenseQuery.ts +122 -132
- package/src/usePacedMutations.ts +1 -1
|
@@ -2,31 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback, useRef, useSyncExternalStore } from 'react'
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
createLiveQueryCollection,
|
|
8
|
-
createLiveQueryWindowController,
|
|
5
|
+
bindLiveQueryWindow,
|
|
6
|
+
createLiveQuerySourceIdClaims,
|
|
9
7
|
fetchNextLiveQueryWindowPage,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
204
|
-
//
|
|
205
|
-
// render, reuses
|
|
206
|
-
//
|
|
207
|
-
// committed
|
|
208
|
-
const
|
|
209
|
-
const sourceIdsRef = useRef<
|
|
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
|
|
227
|
-
let
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
//
|
|
347
|
-
|
|
348
|
-
|
|
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
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
|
|
378
|
-
|
|
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
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
|
428
|
-
const 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 =
|
|
434
|
-
if (
|
|
435
|
-
|
|
436
|
-
console.warn(
|
|
272
|
+
committedRef.current = renderState
|
|
273
|
+
if (renderState.binding.warning && !renderState.warned) {
|
|
274
|
+
renderState.warned = true
|
|
275
|
+
console.warn(renderState.binding.warning)
|
|
437
276
|
}
|
|
438
|
-
|
|
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,
|
|
282
|
+
[controller, renderState],
|
|
442
283
|
)
|
|
443
284
|
const getSnapshot = useCallback(() => controller.getSnapshot(), [controller])
|
|
444
|
-
const snapshot = useSyncExternalStore(subscribe, 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
|
-
|
|
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
|
-
|
|
315
|
+
health: snapshot.health,
|
|
474
316
|
}
|
|
475
317
|
}
|