@tanstack/react-db 0.5.1 → 0.5.4
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 +137 -133
- 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 +133 -129
- package/dist/esm/useLiveQuery.js.map +1 -1
- package/package.json +2 -2
- package/skills/react-db/SKILL.md +50 -0
- package/src/development.ts +15 -0
- package/src/useLiveQuery.ts +171 -163
package/skills/react-db/SKILL.md
CHANGED
|
@@ -324,6 +324,56 @@ See meta-framework/SKILL.md for full preloading patterns.
|
|
|
324
324
|
|
|
325
325
|
## Common Mistakes
|
|
326
326
|
|
|
327
|
+
### CRITICAL Using === instead of eq() in .where()
|
|
328
|
+
|
|
329
|
+
Wrong — throws `InvalidWhereExpressionError` at runtime:
|
|
330
|
+
|
|
331
|
+
```tsx
|
|
332
|
+
const { data } = useLiveQuery({
|
|
333
|
+
query: (q) =>
|
|
334
|
+
q
|
|
335
|
+
.from({ todo: todoCollection })
|
|
336
|
+
.where(({ todo }) => todo.completed === true),
|
|
337
|
+
})
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Correct — use expression functions from `@tanstack/react-db`:
|
|
341
|
+
|
|
342
|
+
```tsx
|
|
343
|
+
import { useLiveQuery, eq } from '@tanstack/react-db'
|
|
344
|
+
|
|
345
|
+
const { data } = useLiveQuery({
|
|
346
|
+
query: (q) =>
|
|
347
|
+
q
|
|
348
|
+
.from({ todo: todoCollection })
|
|
349
|
+
.where(({ todo }) => eq(todo.completed, true)),
|
|
350
|
+
})
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
JavaScript `===`, `!==`, `<`, and `>` evaluate immediately and return a boolean. Use expression functions such as `eq`, `gt`, `gte`, `lt`, `lte`, `and`, `or`, and `not` for structured `.where()` predicates. See db-core/live-queries/SKILL.md for the full operator reference.
|
|
354
|
+
|
|
355
|
+
### CRITICAL Assigning useLiveQuery result directly instead of destructuring
|
|
356
|
+
|
|
357
|
+
Wrong — crashes with "map is not a function":
|
|
358
|
+
|
|
359
|
+
```tsx
|
|
360
|
+
const todos = useLiveQuery({
|
|
361
|
+
query: (q) => q.from({ todo: todoCollection }),
|
|
362
|
+
})
|
|
363
|
+
return todos.map((t) => <li>{t.text}</li>) // TypeError: todos.map is not a function
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Correct — destructure `{ data }`:
|
|
367
|
+
|
|
368
|
+
```tsx
|
|
369
|
+
const { data: todos } = useLiveQuery({
|
|
370
|
+
query: (q) => q.from({ todo: todoCollection }),
|
|
371
|
+
})
|
|
372
|
+
return todos.map((t) => <li key={t.id}>{t.text}</li>)
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
`useLiveQuery` returns an object with `data`, `isLoading`, `status`, and other fields. For queries that can be disabled, use `{ data: todos = [] }` to handle `data` being `undefined`.
|
|
376
|
+
|
|
327
377
|
### CRITICAL Using opaque query logic without queryKey
|
|
328
378
|
|
|
329
379
|
Wrong:
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Bundlers inline `process.env.NODE_ENV` even where `process` does not exist,
|
|
2
|
+
// so it is read directly; without either, warnings stay off.
|
|
3
|
+
export function shouldWarnInDevelopment(disableEnvVar: string): boolean {
|
|
4
|
+
try {
|
|
5
|
+
if (process.env.NODE_ENV === `production`) return false
|
|
6
|
+
} catch {
|
|
7
|
+
return false
|
|
8
|
+
}
|
|
9
|
+
// A page may supply a `process` shim without `env`; nothing disabled it.
|
|
10
|
+
try {
|
|
11
|
+
return process.env[disableEnvVar] !== `1`
|
|
12
|
+
} catch {
|
|
13
|
+
return true
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/useLiveQuery.ts
CHANGED
|
@@ -5,16 +5,17 @@ import {
|
|
|
5
5
|
BaseQueryBuilder,
|
|
6
6
|
IR,
|
|
7
7
|
UnhashableQueryIRError,
|
|
8
|
-
createLiveQueryCollection,
|
|
9
8
|
createLiveQueryObserver,
|
|
10
9
|
deepEquals,
|
|
11
10
|
getPreparedLiveQueryIdentity,
|
|
12
11
|
getStableValueHash,
|
|
13
12
|
isCollection,
|
|
14
13
|
prepareLiveQueryValue,
|
|
14
|
+
resolveLiveQueryValue,
|
|
15
15
|
} from '@tanstack/db'
|
|
16
16
|
import { useOptionalDbClient } from './DbProvider'
|
|
17
17
|
import { setLiveQueryResultInfo } from './live-query-internals'
|
|
18
|
+
import { shouldWarnInDevelopment } from './development'
|
|
18
19
|
import type {
|
|
19
20
|
Collection,
|
|
20
21
|
CollectionImpl,
|
|
@@ -55,22 +56,20 @@ const suspenseCollectionsByClient = new WeakMap<
|
|
|
55
56
|
DbClient,
|
|
56
57
|
Map<string, SuspenseCollectionEntry>
|
|
57
58
|
>()
|
|
58
|
-
const
|
|
59
|
-
let
|
|
59
|
+
const sourceObjectTokens = new WeakMap<object, number>()
|
|
60
|
+
let nextSourceObjectToken = 0
|
|
60
61
|
|
|
61
|
-
function
|
|
62
|
-
let id =
|
|
62
|
+
function getSourceObjectToken(source: object): number {
|
|
63
|
+
let id = sourceObjectTokens.get(source)
|
|
63
64
|
if (id === undefined) {
|
|
64
|
-
id = ++
|
|
65
|
-
|
|
65
|
+
id = ++nextSourceObjectToken
|
|
66
|
+
sourceObjectTokens.set(source, id)
|
|
66
67
|
}
|
|
67
68
|
return id
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
function
|
|
71
|
-
preparedValue
|
|
72
|
-
queryHash: string,
|
|
73
|
-
): string {
|
|
71
|
+
function getPreparedSources(preparedValue: unknown): Array<{ id: string }> {
|
|
72
|
+
if (isCollection(preparedValue)) return [preparedValue]
|
|
74
73
|
const query =
|
|
75
74
|
preparedValue instanceof BaseQueryBuilder
|
|
76
75
|
? preparedValue
|
|
@@ -80,10 +79,19 @@ function getUnscopedSuspenseKey(
|
|
|
80
79
|
preparedValue.query instanceof BaseQueryBuilder
|
|
81
80
|
? preparedValue.query
|
|
82
81
|
: undefined
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
return query
|
|
83
|
+
? IR.collectCollectionSources(query._getQuery()).map(
|
|
84
|
+
({ collection }) => collection,
|
|
85
|
+
)
|
|
86
|
+
: []
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getSourceQualifiedSuspenseKey(
|
|
90
|
+
preparedValue: unknown,
|
|
91
|
+
queryHash: string,
|
|
92
|
+
): string {
|
|
93
|
+
const sourceIds = getPreparedSources(preparedValue).map(getSourceObjectToken)
|
|
94
|
+
if (sourceIds.length === 0) return queryHash
|
|
87
95
|
return `${sourceIds.join(`,`)}:${queryHash}`
|
|
88
96
|
}
|
|
89
97
|
|
|
@@ -172,16 +180,6 @@ export function warnDeprecatedDepsArray(
|
|
|
172
180
|
)
|
|
173
181
|
}
|
|
174
182
|
|
|
175
|
-
function shouldWarnInDevelopment(disableEnvVar: string): boolean {
|
|
176
|
-
if (typeof process === `undefined`) {
|
|
177
|
-
return false
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return (
|
|
181
|
-
process.env.NODE_ENV !== `production` && process.env[disableEnvVar] !== `1`
|
|
182
|
-
)
|
|
183
|
-
}
|
|
184
|
-
|
|
185
183
|
function getCurrentTime(): number {
|
|
186
184
|
return typeof performance !== `undefined` &&
|
|
187
185
|
typeof performance.now === `function`
|
|
@@ -336,40 +334,6 @@ export function warnUnhashableDerivedIdentity(
|
|
|
336
334
|
)
|
|
337
335
|
}
|
|
338
336
|
|
|
339
|
-
function createCollectionFromPreparedQuery(
|
|
340
|
-
value: unknown,
|
|
341
|
-
defaultGcTime = DEFAULT_GC_TIME_MS,
|
|
342
|
-
) {
|
|
343
|
-
if (value === undefined || value === null) {
|
|
344
|
-
return null
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if (isCollection(value)) {
|
|
348
|
-
value.startSyncImmediate()
|
|
349
|
-
return value
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (value instanceof BaseQueryBuilder) {
|
|
353
|
-
return createLiveQueryCollection({
|
|
354
|
-
query: value,
|
|
355
|
-
startSync: true,
|
|
356
|
-
gcTime: defaultGcTime,
|
|
357
|
-
})
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
if (typeof value === `object`) {
|
|
361
|
-
return createLiveQueryCollection({
|
|
362
|
-
startSync: true,
|
|
363
|
-
gcTime: defaultGcTime,
|
|
364
|
-
...(value as LiveQueryCollectionConfig<any>),
|
|
365
|
-
})
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
throw new Error(
|
|
369
|
-
`useLiveQuery callback must return a QueryBuilder, LiveQueryCollectionConfig, Collection, undefined, or null. Got: ${typeof value}`,
|
|
370
|
-
)
|
|
371
|
-
}
|
|
372
|
-
|
|
373
337
|
/**
|
|
374
338
|
* Create a live query using a query function.
|
|
375
339
|
* @param queryFn - Query function that defines what data to fetch
|
|
@@ -547,9 +511,7 @@ export function useLiveQuery<
|
|
|
547
511
|
deps?: Array<unknown>,
|
|
548
512
|
): {
|
|
549
513
|
state:
|
|
550
|
-
|
|
551
|
-
| Map<TKey, TResult>
|
|
552
|
-
| undefined
|
|
514
|
+
Map<string | number, GetResult<TContext>> | Map<TKey, TResult> | undefined
|
|
553
515
|
data: InferResultType<TContext> | Array<TResult> | undefined
|
|
554
516
|
collection:
|
|
555
517
|
| Collection<GetResult<TContext>, string | number, {}>
|
|
@@ -771,6 +733,36 @@ export function useLiveQueryForSuspense(
|
|
|
771
733
|
return useLiveQueryImpl(configOrQueryOrCollection, deps, true)
|
|
772
734
|
}
|
|
773
735
|
|
|
736
|
+
// What one hook instance keeps across renders. It lives in a single ref
|
|
737
|
+
// slot, so a render neither looks up nor allocates more.
|
|
738
|
+
function createHookInstance(dbClient: DbClient | undefined) {
|
|
739
|
+
return {
|
|
740
|
+
collection: null as Collection<object, string | number, {}> | null,
|
|
741
|
+
deps: null as Array<unknown> | null,
|
|
742
|
+
config: null as unknown,
|
|
743
|
+
client: dbClient,
|
|
744
|
+
legacyUnhashableIdentity: [`legacy-unhashable`] as Array<unknown>,
|
|
745
|
+
derivedIdentityProfiler: {
|
|
746
|
+
renderCount: 0,
|
|
747
|
+
totalMs: 0,
|
|
748
|
+
maxMs: 0,
|
|
749
|
+
warned: false,
|
|
750
|
+
} as DerivedIdentityProfiler,
|
|
751
|
+
deferredCollections: new Set<
|
|
752
|
+
CollectionImpl<any, string | number, any, any, any>
|
|
753
|
+
>(),
|
|
754
|
+
sourceIds: {
|
|
755
|
+
unscoped: new Map<string, number>(),
|
|
756
|
+
byClient: new WeakMap<DbClient, Map<string, number>>(),
|
|
757
|
+
},
|
|
758
|
+
observer: null as LiveQueryObserver<object, string | number> | null,
|
|
759
|
+
queryHash: undefined as string | undefined,
|
|
760
|
+
suspenseKey: undefined as string | undefined,
|
|
761
|
+
identityError: undefined as UnhashableQueryIRError | undefined,
|
|
762
|
+
subscribe: null as ((onStoreChange: () => void) => () => void) | null,
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
774
766
|
function useLiveQueryImpl(
|
|
775
767
|
configOrQueryOrCollection: any,
|
|
776
768
|
deps: Array<unknown> | undefined,
|
|
@@ -784,32 +776,21 @@ function useLiveQueryImpl(
|
|
|
784
776
|
: (getExplicitDbClient(configOrQueryOrCollection) ?? contextDbClient)
|
|
785
777
|
const resolvedDeps = deps ?? []
|
|
786
778
|
|
|
787
|
-
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
warned: false,
|
|
803
|
-
})
|
|
804
|
-
const deferredCollectionsRef = useRef(
|
|
805
|
-
new Set<CollectionImpl<any, string | number, any, any, any>>(),
|
|
806
|
-
)
|
|
807
|
-
const observerRef = useRef<LiveQueryObserver<object, string | number> | null>(
|
|
808
|
-
null,
|
|
809
|
-
)
|
|
810
|
-
const queryHashRef = useRef<string | undefined>(undefined)
|
|
811
|
-
const suspenseKeyRef = useRef<string | undefined>(undefined)
|
|
812
|
-
const identityErrorRef = useRef<UnhashableQueryIRError | undefined>(undefined)
|
|
779
|
+
const instanceRef = useRef<ReturnType<typeof createHookInstance> | null>(null)
|
|
780
|
+
const instance = (instanceRef.current ??= createHookInstance(dbClient))
|
|
781
|
+
const resumeDeferredCollections = () => {
|
|
782
|
+
const collections = Array.from(instance.deferredCollections)
|
|
783
|
+
instance.deferredCollections.clear()
|
|
784
|
+
const errors: Array<unknown> = []
|
|
785
|
+
for (const collection of collections) {
|
|
786
|
+
try {
|
|
787
|
+
collection._resumeSyncStart()
|
|
788
|
+
} catch (error) {
|
|
789
|
+
errors.push(error)
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
if (errors.length) throw errors[0]
|
|
793
|
+
}
|
|
813
794
|
|
|
814
795
|
const queryKey = !inputIsCollection
|
|
815
796
|
? getExplicitQueryKey(configOrQueryOrCollection)
|
|
@@ -825,22 +806,29 @@ function useLiveQueryImpl(
|
|
|
825
806
|
streamIdentity = [`queryKey`, queryKey]
|
|
826
807
|
} else if (deps !== undefined) {
|
|
827
808
|
identityDeps = resolvedDeps
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
)
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
809
|
+
// Deps decide reuse. Only hydration and Suspense read the query hash; the
|
|
810
|
+
// development warning about unhashable queries still derives it.
|
|
811
|
+
if (
|
|
812
|
+
dbClient ||
|
|
813
|
+
forSuspense ||
|
|
814
|
+
shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)
|
|
815
|
+
)
|
|
816
|
+
try {
|
|
817
|
+
preparedQueryValue = prepareQueryValue(
|
|
818
|
+
configOrQueryOrCollection,
|
|
819
|
+
dbClient,
|
|
820
|
+
instance.deferredCollections,
|
|
821
|
+
)
|
|
822
|
+
streamIdentity = [
|
|
823
|
+
`deps`,
|
|
824
|
+
resolvedDeps,
|
|
825
|
+
getPreparedLiveQueryIdentity(preparedQueryValue),
|
|
826
|
+
]
|
|
827
|
+
} catch (error) {
|
|
828
|
+
if (!(error instanceof UnhashableQueryIRError)) throw error
|
|
829
|
+
warnUnhashableDerivedIdentity(error)
|
|
830
|
+
identityError = error
|
|
831
|
+
}
|
|
844
832
|
} else if (inputIsCollection) {
|
|
845
833
|
identityDeps = []
|
|
846
834
|
streamIdentity = [`collection`, configOrQueryOrCollection.id]
|
|
@@ -848,8 +836,8 @@ function useLiveQueryImpl(
|
|
|
848
836
|
const preparation = prepareDerivedQuery(
|
|
849
837
|
configOrQueryOrCollection,
|
|
850
838
|
dbClient,
|
|
851
|
-
|
|
852
|
-
|
|
839
|
+
instance.derivedIdentityProfiler,
|
|
840
|
+
instance.deferredCollections,
|
|
853
841
|
)
|
|
854
842
|
preparedQueryValue = preparation.value
|
|
855
843
|
if (preparation.status === `hashable`) {
|
|
@@ -857,7 +845,7 @@ function useLiveQueryImpl(
|
|
|
857
845
|
streamIdentity = preparation.identityDeps
|
|
858
846
|
} else {
|
|
859
847
|
warnUnhashableDerivedIdentity(preparation.error)
|
|
860
|
-
identityDeps =
|
|
848
|
+
identityDeps = instance.legacyUnhashableIdentity
|
|
861
849
|
identityError = preparation.error
|
|
862
850
|
}
|
|
863
851
|
}
|
|
@@ -880,36 +868,60 @@ function useLiveQueryImpl(
|
|
|
880
868
|
warnDeprecatedDepsArray()
|
|
881
869
|
}
|
|
882
870
|
|
|
871
|
+
if (
|
|
872
|
+
queryKey === undefined &&
|
|
873
|
+
deps === undefined &&
|
|
874
|
+
preparedQueryValue !== unpreparedQueryValue
|
|
875
|
+
) {
|
|
876
|
+
const prior = dbClient
|
|
877
|
+
? instance.sourceIds.byClient.get(dbClient)
|
|
878
|
+
: instance.sourceIds.unscoped
|
|
879
|
+
const seen = new Map<string, number>()
|
|
880
|
+
for (const source of getPreparedSources(preparedQueryValue)) {
|
|
881
|
+
const token = getSourceObjectToken(source)
|
|
882
|
+
const previous = seen.get(source.id) ?? prior?.get(source.id)
|
|
883
|
+
if (previous !== undefined && previous !== token) {
|
|
884
|
+
// The rejected render must not retain a shared Collection's sync deferral.
|
|
885
|
+
resumeDeferredCollections()
|
|
886
|
+
throw new Error(
|
|
887
|
+
`[useLiveQuery] Source Collection "${source.id}" was replaced by a different Collection with the same ID while this hook is mounted. Unmount the hook and clean up its previous source and client scope before reusing the ID.`,
|
|
888
|
+
)
|
|
889
|
+
}
|
|
890
|
+
seen.set(source.id, token)
|
|
891
|
+
}
|
|
892
|
+
const bindings = prior ?? new Map<string, number>()
|
|
893
|
+
for (const [id, token] of seen) bindings.set(id, token)
|
|
894
|
+
if (dbClient) instance.sourceIds.byClient.set(dbClient, bindings)
|
|
895
|
+
}
|
|
896
|
+
|
|
883
897
|
const canReuseSuspenseKey =
|
|
884
898
|
forSuspense &&
|
|
885
899
|
!inputIsCollection &&
|
|
886
900
|
queryHash !== undefined &&
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
suspenseKeyRef.current !== undefined
|
|
901
|
+
instance.collection !== null &&
|
|
902
|
+
instance.client === dbClient &&
|
|
903
|
+
instance.queryHash === queryHash &&
|
|
904
|
+
instance.suspenseKey !== undefined
|
|
892
905
|
|
|
893
906
|
if (
|
|
894
907
|
forSuspense &&
|
|
895
908
|
!inputIsCollection &&
|
|
896
909
|
queryHash &&
|
|
897
|
-
!dbClient &&
|
|
898
910
|
!canReuseSuspenseKey &&
|
|
899
911
|
preparedQueryValue === unpreparedQueryValue
|
|
900
912
|
) {
|
|
901
913
|
preparedQueryValue = prepareQueryValue(
|
|
902
914
|
configOrQueryOrCollection,
|
|
903
915
|
dbClient,
|
|
904
|
-
|
|
916
|
+
instance.deferredCollections,
|
|
905
917
|
)
|
|
906
918
|
}
|
|
907
919
|
|
|
908
920
|
const suspenseKey =
|
|
909
|
-
queryHash && !
|
|
921
|
+
queryHash && forSuspense && !inputIsCollection
|
|
910
922
|
? canReuseSuspenseKey
|
|
911
|
-
?
|
|
912
|
-
:
|
|
923
|
+
? instance.suspenseKey
|
|
924
|
+
: getSourceQualifiedSuspenseKey(preparedQueryValue, queryHash)
|
|
913
925
|
: queryHash
|
|
914
926
|
|
|
915
927
|
const suspenseCollections =
|
|
@@ -922,24 +934,17 @@ function useLiveQueryImpl(
|
|
|
922
934
|
const suspenseCollection = suspenseEntry?.collection
|
|
923
935
|
|
|
924
936
|
const identityChanged =
|
|
925
|
-
|
|
937
|
+
instance.deps === null ||
|
|
926
938
|
(deps !== undefined
|
|
927
|
-
?
|
|
928
|
-
|
|
929
|
-
: !deepEquals(
|
|
939
|
+
? instance.deps.length !== identityDeps.length ||
|
|
940
|
+
instance.deps.some((dep, index) => dep !== identityDeps[index])
|
|
941
|
+
: !deepEquals(instance.deps, identityDeps))
|
|
930
942
|
|
|
931
943
|
// Check if we need to create/recreate the collection
|
|
932
944
|
const needsNewCollection =
|
|
933
|
-
!
|
|
934
|
-
(inputIsCollection &&
|
|
935
|
-
(!inputIsCollection && (
|
|
936
|
-
|
|
937
|
-
const resumeDeferredCollections = () => {
|
|
938
|
-
for (const collection of deferredCollectionsRef.current) {
|
|
939
|
-
collection._resumeSyncStart()
|
|
940
|
-
}
|
|
941
|
-
deferredCollectionsRef.current.clear()
|
|
942
|
-
}
|
|
945
|
+
!instance.collection ||
|
|
946
|
+
(inputIsCollection && instance.config !== configOrQueryOrCollection) ||
|
|
947
|
+
(!inputIsCollection && (instance.client !== dbClient || identityChanged))
|
|
943
948
|
|
|
944
949
|
if (needsNewCollection) {
|
|
945
950
|
if (inputIsCollection) {
|
|
@@ -963,25 +968,28 @@ function useLiveQueryImpl(
|
|
|
963
968
|
}
|
|
964
969
|
// It's already a collection, ensure sync is started for React hooks
|
|
965
970
|
configOrQueryOrCollection.startSyncImmediate()
|
|
966
|
-
|
|
967
|
-
|
|
971
|
+
instance.collection = configOrQueryOrCollection
|
|
972
|
+
instance.config = configOrQueryOrCollection
|
|
968
973
|
} else {
|
|
969
974
|
if (suspenseCollection) {
|
|
970
|
-
|
|
975
|
+
instance.collection = suspenseCollection
|
|
971
976
|
} else {
|
|
972
977
|
if (preparedQueryValue === unpreparedQueryValue) {
|
|
973
978
|
preparedQueryValue = prepareQueryValue(
|
|
974
979
|
configOrQueryOrCollection,
|
|
975
980
|
dbClient,
|
|
976
|
-
|
|
981
|
+
instance.deferredCollections,
|
|
977
982
|
)
|
|
978
983
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
984
|
+
instance.collection = resolveLiveQueryValue(preparedQueryValue, {
|
|
985
|
+
gcTime: forSuspense
|
|
986
|
+
? DEFAULT_SUSPENSE_GC_TIME_MS
|
|
987
|
+
: DEFAULT_GC_TIME_MS,
|
|
988
|
+
// Hydration and Suspense key the live-query Collection by identity.
|
|
989
|
+
pool: !forSuspense && !dbClient,
|
|
990
|
+
}) as SuspenseCollection | null
|
|
991
|
+
if (suspenseCollections && suspenseKey && instance.collection) {
|
|
992
|
+
const collection = instance.collection
|
|
985
993
|
const removeCleanupListener = collection.on(`status:cleaned-up`, () =>
|
|
986
994
|
releaseSuspenseCollection(
|
|
987
995
|
suspenseCollections,
|
|
@@ -1013,13 +1021,13 @@ function useLiveQueryImpl(
|
|
|
1013
1021
|
suspenseCollections.set(suspenseKey, entry)
|
|
1014
1022
|
}
|
|
1015
1023
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
1024
|
+
instance.config = configOrQueryOrCollection
|
|
1025
|
+
instance.deps = [...identityDeps]
|
|
1018
1026
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1027
|
+
instance.client = dbClient
|
|
1028
|
+
instance.queryHash = queryHash
|
|
1029
|
+
instance.suspenseKey = suspenseKey
|
|
1030
|
+
instance.identityError = identityError
|
|
1023
1031
|
}
|
|
1024
1032
|
|
|
1025
1033
|
// Recreate the observer when the underlying collection changes. The observer
|
|
@@ -1035,38 +1043,38 @@ function useLiveQueryImpl(
|
|
|
1035
1043
|
// hook's pre-observer loading policy, and — because wholesale delivers
|
|
1036
1044
|
// nothing synchronously during subscribe — never notifies
|
|
1037
1045
|
// useSyncExternalStore inside its own subscribe call.
|
|
1038
|
-
|
|
1046
|
+
instance.observer = createLiveQueryObserver(instance.collection, {
|
|
1039
1047
|
mode: `wholesale`,
|
|
1040
1048
|
client: dbClient,
|
|
1041
|
-
queryHash:
|
|
1049
|
+
queryHash: instance.queryHash,
|
|
1042
1050
|
onPreload: resumeDeferredCollections,
|
|
1043
1051
|
})
|
|
1044
1052
|
}
|
|
1045
|
-
const observer =
|
|
1053
|
+
const observer = instance.observer!
|
|
1046
1054
|
|
|
1047
1055
|
// Stable subscribe bound to the current observer; the observer owns the
|
|
1048
1056
|
// subscription, ready-race, and disposal.
|
|
1049
|
-
|
|
1050
|
-
(
|
|
1051
|
-
|
|
1052
|
-
if (!subscribeRef.current || needsNewCollection) {
|
|
1053
|
-
subscribeRef.current = (onStoreChange: () => void) => {
|
|
1054
|
-
const unsubscribe = observer.subscribe(() => onStoreChange())
|
|
1057
|
+
if (!instance.subscribe || needsNewCollection) {
|
|
1058
|
+
instance.subscribe = (onStoreChange: () => void) => {
|
|
1059
|
+
const unsubscribe = observer.subscribe(onStoreChange)
|
|
1055
1060
|
resumeDeferredCollections()
|
|
1056
1061
|
return unsubscribe
|
|
1057
1062
|
}
|
|
1058
1063
|
}
|
|
1059
1064
|
|
|
1060
1065
|
const returned = useSyncExternalStore(
|
|
1061
|
-
|
|
1066
|
+
instance.subscribe,
|
|
1062
1067
|
() => observer.getSnapshot(),
|
|
1063
1068
|
() => observer.getServerSnapshot(),
|
|
1064
1069
|
)
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1070
|
+
// Only useLiveSuspenseQuery reads this, and it costs a define per render.
|
|
1071
|
+
if (forSuspense) {
|
|
1072
|
+
setLiveQueryResultInfo(returned, {
|
|
1073
|
+
client: dbClient,
|
|
1074
|
+
queryHash: instance.queryHash,
|
|
1075
|
+
identityError: instance.identityError,
|
|
1076
|
+
observer,
|
|
1077
|
+
})
|
|
1078
|
+
}
|
|
1071
1079
|
return returned as any
|
|
1072
1080
|
}
|