@tanstack/react-db 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/development.cjs +16 -0
- package/dist/cjs/development.cjs.map +1 -0
- package/dist/cjs/development.d.cts +1 -0
- package/dist/cjs/useLiveQuery.cjs +89 -117
- package/dist/cjs/useLiveQuery.cjs.map +1 -1
- package/dist/esm/development.d.ts +1 -0
- package/dist/esm/development.js +16 -0
- package/dist/esm/development.js.map +1 -0
- package/dist/esm/useLiveQuery.js +85 -113
- package/dist/esm/useLiveQuery.js.map +1 -1
- package/package.json +2 -2
- package/src/development.ts +15 -0
- package/src/useLiveQuery.ts +107 -137
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,
|
|
@@ -172,16 +173,6 @@ export function warnDeprecatedDepsArray(
|
|
|
172
173
|
)
|
|
173
174
|
}
|
|
174
175
|
|
|
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
176
|
function getCurrentTime(): number {
|
|
186
177
|
return typeof performance !== `undefined` &&
|
|
187
178
|
typeof performance.now === `function`
|
|
@@ -336,40 +327,6 @@ export function warnUnhashableDerivedIdentity(
|
|
|
336
327
|
)
|
|
337
328
|
}
|
|
338
329
|
|
|
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
330
|
/**
|
|
374
331
|
* Create a live query using a query function.
|
|
375
332
|
* @param queryFn - Query function that defines what data to fetch
|
|
@@ -771,6 +728,32 @@ export function useLiveQueryForSuspense(
|
|
|
771
728
|
return useLiveQueryImpl(configOrQueryOrCollection, deps, true)
|
|
772
729
|
}
|
|
773
730
|
|
|
731
|
+
// What one hook instance keeps across renders. It lives in a single ref
|
|
732
|
+
// slot, so a render neither looks up nor allocates more.
|
|
733
|
+
function createHookInstance(dbClient: DbClient | undefined) {
|
|
734
|
+
return {
|
|
735
|
+
collection: null as Collection<object, string | number, {}> | null,
|
|
736
|
+
deps: null as Array<unknown> | null,
|
|
737
|
+
config: null as unknown,
|
|
738
|
+
client: dbClient,
|
|
739
|
+
legacyUnhashableIdentity: [`legacy-unhashable`] as Array<unknown>,
|
|
740
|
+
derivedIdentityProfiler: {
|
|
741
|
+
renderCount: 0,
|
|
742
|
+
totalMs: 0,
|
|
743
|
+
maxMs: 0,
|
|
744
|
+
warned: false,
|
|
745
|
+
} as DerivedIdentityProfiler,
|
|
746
|
+
deferredCollections: new Set<
|
|
747
|
+
CollectionImpl<any, string | number, any, any, any>
|
|
748
|
+
>(),
|
|
749
|
+
observer: null as LiveQueryObserver<object, string | number> | null,
|
|
750
|
+
queryHash: undefined as string | undefined,
|
|
751
|
+
suspenseKey: undefined as string | undefined,
|
|
752
|
+
identityError: undefined as UnhashableQueryIRError | undefined,
|
|
753
|
+
subscribe: null as ((onStoreChange: () => void) => () => void) | null,
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
774
757
|
function useLiveQueryImpl(
|
|
775
758
|
configOrQueryOrCollection: any,
|
|
776
759
|
deps: Array<unknown> | undefined,
|
|
@@ -784,32 +767,8 @@ function useLiveQueryImpl(
|
|
|
784
767
|
: (getExplicitDbClient(configOrQueryOrCollection) ?? contextDbClient)
|
|
785
768
|
const resolvedDeps = deps ?? []
|
|
786
769
|
|
|
787
|
-
|
|
788
|
-
const
|
|
789
|
-
null,
|
|
790
|
-
)
|
|
791
|
-
const depsRef = useRef<Array<unknown> | null>(null)
|
|
792
|
-
const configRef = useRef<unknown>(null)
|
|
793
|
-
const clientRef = useRef(dbClient)
|
|
794
|
-
const legacyUnhashableIdentityRef = useRef<Array<unknown>>([
|
|
795
|
-
`legacy-unhashable`,
|
|
796
|
-
])
|
|
797
|
-
|
|
798
|
-
const derivedIdentityProfilerRef = useRef<DerivedIdentityProfiler>({
|
|
799
|
-
renderCount: 0,
|
|
800
|
-
totalMs: 0,
|
|
801
|
-
maxMs: 0,
|
|
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)
|
|
770
|
+
const instanceRef = useRef<ReturnType<typeof createHookInstance> | null>(null)
|
|
771
|
+
const instance = (instanceRef.current ??= createHookInstance(dbClient))
|
|
813
772
|
|
|
814
773
|
const queryKey = !inputIsCollection
|
|
815
774
|
? getExplicitQueryKey(configOrQueryOrCollection)
|
|
@@ -825,22 +784,29 @@ function useLiveQueryImpl(
|
|
|
825
784
|
streamIdentity = [`queryKey`, queryKey]
|
|
826
785
|
} else if (deps !== undefined) {
|
|
827
786
|
identityDeps = resolvedDeps
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
)
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
787
|
+
// Deps decide reuse. Only hydration and Suspense read the query hash; the
|
|
788
|
+
// development warning about unhashable queries still derives it.
|
|
789
|
+
if (
|
|
790
|
+
dbClient ||
|
|
791
|
+
forSuspense ||
|
|
792
|
+
shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`)
|
|
793
|
+
)
|
|
794
|
+
try {
|
|
795
|
+
preparedQueryValue = prepareQueryValue(
|
|
796
|
+
configOrQueryOrCollection,
|
|
797
|
+
dbClient,
|
|
798
|
+
instance.deferredCollections,
|
|
799
|
+
)
|
|
800
|
+
streamIdentity = [
|
|
801
|
+
`deps`,
|
|
802
|
+
resolvedDeps,
|
|
803
|
+
getPreparedLiveQueryIdentity(preparedQueryValue),
|
|
804
|
+
]
|
|
805
|
+
} catch (error) {
|
|
806
|
+
if (!(error instanceof UnhashableQueryIRError)) throw error
|
|
807
|
+
warnUnhashableDerivedIdentity(error)
|
|
808
|
+
identityError = error
|
|
809
|
+
}
|
|
844
810
|
} else if (inputIsCollection) {
|
|
845
811
|
identityDeps = []
|
|
846
812
|
streamIdentity = [`collection`, configOrQueryOrCollection.id]
|
|
@@ -848,8 +814,8 @@ function useLiveQueryImpl(
|
|
|
848
814
|
const preparation = prepareDerivedQuery(
|
|
849
815
|
configOrQueryOrCollection,
|
|
850
816
|
dbClient,
|
|
851
|
-
|
|
852
|
-
|
|
817
|
+
instance.derivedIdentityProfiler,
|
|
818
|
+
instance.deferredCollections,
|
|
853
819
|
)
|
|
854
820
|
preparedQueryValue = preparation.value
|
|
855
821
|
if (preparation.status === `hashable`) {
|
|
@@ -857,7 +823,7 @@ function useLiveQueryImpl(
|
|
|
857
823
|
streamIdentity = preparation.identityDeps
|
|
858
824
|
} else {
|
|
859
825
|
warnUnhashableDerivedIdentity(preparation.error)
|
|
860
|
-
identityDeps =
|
|
826
|
+
identityDeps = instance.legacyUnhashableIdentity
|
|
861
827
|
identityError = preparation.error
|
|
862
828
|
}
|
|
863
829
|
}
|
|
@@ -885,10 +851,10 @@ function useLiveQueryImpl(
|
|
|
885
851
|
!inputIsCollection &&
|
|
886
852
|
queryHash !== undefined &&
|
|
887
853
|
!dbClient &&
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
854
|
+
instance.collection !== null &&
|
|
855
|
+
instance.client === dbClient &&
|
|
856
|
+
instance.queryHash === queryHash &&
|
|
857
|
+
instance.suspenseKey !== undefined
|
|
892
858
|
|
|
893
859
|
if (
|
|
894
860
|
forSuspense &&
|
|
@@ -901,14 +867,14 @@ function useLiveQueryImpl(
|
|
|
901
867
|
preparedQueryValue = prepareQueryValue(
|
|
902
868
|
configOrQueryOrCollection,
|
|
903
869
|
dbClient,
|
|
904
|
-
|
|
870
|
+
instance.deferredCollections,
|
|
905
871
|
)
|
|
906
872
|
}
|
|
907
873
|
|
|
908
874
|
const suspenseKey =
|
|
909
875
|
queryHash && !dbClient
|
|
910
876
|
? canReuseSuspenseKey
|
|
911
|
-
?
|
|
877
|
+
? instance.suspenseKey
|
|
912
878
|
: getUnscopedSuspenseKey(preparedQueryValue, queryHash)
|
|
913
879
|
: queryHash
|
|
914
880
|
|
|
@@ -922,23 +888,24 @@ function useLiveQueryImpl(
|
|
|
922
888
|
const suspenseCollection = suspenseEntry?.collection
|
|
923
889
|
|
|
924
890
|
const identityChanged =
|
|
925
|
-
|
|
891
|
+
instance.deps === null ||
|
|
926
892
|
(deps !== undefined
|
|
927
|
-
?
|
|
928
|
-
|
|
929
|
-
: !deepEquals(
|
|
893
|
+
? instance.deps.length !== identityDeps.length ||
|
|
894
|
+
instance.deps.some((dep, index) => dep !== identityDeps[index])
|
|
895
|
+
: !deepEquals(instance.deps, identityDeps))
|
|
930
896
|
|
|
931
897
|
// Check if we need to create/recreate the collection
|
|
932
898
|
const needsNewCollection =
|
|
933
|
-
!
|
|
934
|
-
(inputIsCollection &&
|
|
935
|
-
(!inputIsCollection && (
|
|
899
|
+
!instance.collection ||
|
|
900
|
+
(inputIsCollection && instance.config !== configOrQueryOrCollection) ||
|
|
901
|
+
(!inputIsCollection && (instance.client !== dbClient || identityChanged))
|
|
936
902
|
|
|
937
903
|
const resumeDeferredCollections = () => {
|
|
938
|
-
|
|
904
|
+
if (instance.deferredCollections.size === 0) return
|
|
905
|
+
for (const collection of instance.deferredCollections) {
|
|
939
906
|
collection._resumeSyncStart()
|
|
940
907
|
}
|
|
941
|
-
|
|
908
|
+
instance.deferredCollections.clear()
|
|
942
909
|
}
|
|
943
910
|
|
|
944
911
|
if (needsNewCollection) {
|
|
@@ -963,25 +930,28 @@ function useLiveQueryImpl(
|
|
|
963
930
|
}
|
|
964
931
|
// It's already a collection, ensure sync is started for React hooks
|
|
965
932
|
configOrQueryOrCollection.startSyncImmediate()
|
|
966
|
-
|
|
967
|
-
|
|
933
|
+
instance.collection = configOrQueryOrCollection
|
|
934
|
+
instance.config = configOrQueryOrCollection
|
|
968
935
|
} else {
|
|
969
936
|
if (suspenseCollection) {
|
|
970
|
-
|
|
937
|
+
instance.collection = suspenseCollection
|
|
971
938
|
} else {
|
|
972
939
|
if (preparedQueryValue === unpreparedQueryValue) {
|
|
973
940
|
preparedQueryValue = prepareQueryValue(
|
|
974
941
|
configOrQueryOrCollection,
|
|
975
942
|
dbClient,
|
|
976
|
-
|
|
943
|
+
instance.deferredCollections,
|
|
977
944
|
)
|
|
978
945
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
946
|
+
instance.collection = resolveLiveQueryValue(preparedQueryValue, {
|
|
947
|
+
gcTime: forSuspense
|
|
948
|
+
? DEFAULT_SUSPENSE_GC_TIME_MS
|
|
949
|
+
: DEFAULT_GC_TIME_MS,
|
|
950
|
+
// Hydration and Suspense key the live-query Collection by identity.
|
|
951
|
+
pool: !forSuspense && !dbClient,
|
|
952
|
+
}) as SuspenseCollection | null
|
|
953
|
+
if (suspenseCollections && suspenseKey && instance.collection) {
|
|
954
|
+
const collection = instance.collection
|
|
985
955
|
const removeCleanupListener = collection.on(`status:cleaned-up`, () =>
|
|
986
956
|
releaseSuspenseCollection(
|
|
987
957
|
suspenseCollections,
|
|
@@ -1013,13 +983,13 @@ function useLiveQueryImpl(
|
|
|
1013
983
|
suspenseCollections.set(suspenseKey, entry)
|
|
1014
984
|
}
|
|
1015
985
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
986
|
+
instance.config = configOrQueryOrCollection
|
|
987
|
+
instance.deps = [...identityDeps]
|
|
1018
988
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
989
|
+
instance.client = dbClient
|
|
990
|
+
instance.queryHash = queryHash
|
|
991
|
+
instance.suspenseKey = suspenseKey
|
|
992
|
+
instance.identityError = identityError
|
|
1023
993
|
}
|
|
1024
994
|
|
|
1025
995
|
// Recreate the observer when the underlying collection changes. The observer
|
|
@@ -1035,38 +1005,38 @@ function useLiveQueryImpl(
|
|
|
1035
1005
|
// hook's pre-observer loading policy, and — because wholesale delivers
|
|
1036
1006
|
// nothing synchronously during subscribe — never notifies
|
|
1037
1007
|
// useSyncExternalStore inside its own subscribe call.
|
|
1038
|
-
|
|
1008
|
+
instance.observer = createLiveQueryObserver(instance.collection, {
|
|
1039
1009
|
mode: `wholesale`,
|
|
1040
1010
|
client: dbClient,
|
|
1041
|
-
queryHash:
|
|
1011
|
+
queryHash: instance.queryHash,
|
|
1042
1012
|
onPreload: resumeDeferredCollections,
|
|
1043
1013
|
})
|
|
1044
1014
|
}
|
|
1045
|
-
const observer =
|
|
1015
|
+
const observer = instance.observer!
|
|
1046
1016
|
|
|
1047
1017
|
// Stable subscribe bound to the current observer; the observer owns the
|
|
1048
1018
|
// subscription, ready-race, and disposal.
|
|
1049
|
-
|
|
1050
|
-
(
|
|
1051
|
-
|
|
1052
|
-
if (!subscribeRef.current || needsNewCollection) {
|
|
1053
|
-
subscribeRef.current = (onStoreChange: () => void) => {
|
|
1054
|
-
const unsubscribe = observer.subscribe(() => onStoreChange())
|
|
1019
|
+
if (!instance.subscribe || needsNewCollection) {
|
|
1020
|
+
instance.subscribe = (onStoreChange: () => void) => {
|
|
1021
|
+
const unsubscribe = observer.subscribe(onStoreChange)
|
|
1055
1022
|
resumeDeferredCollections()
|
|
1056
1023
|
return unsubscribe
|
|
1057
1024
|
}
|
|
1058
1025
|
}
|
|
1059
1026
|
|
|
1060
1027
|
const returned = useSyncExternalStore(
|
|
1061
|
-
|
|
1028
|
+
instance.subscribe,
|
|
1062
1029
|
() => observer.getSnapshot(),
|
|
1063
1030
|
() => observer.getServerSnapshot(),
|
|
1064
1031
|
)
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1032
|
+
// Only useLiveSuspenseQuery reads this, and it costs a define per render.
|
|
1033
|
+
if (forSuspense) {
|
|
1034
|
+
setLiveQueryResultInfo(returned, {
|
|
1035
|
+
client: dbClient,
|
|
1036
|
+
queryHash: instance.queryHash,
|
|
1037
|
+
identityError: instance.identityError,
|
|
1038
|
+
observer,
|
|
1039
|
+
})
|
|
1040
|
+
}
|
|
1071
1041
|
return returned as any
|
|
1072
1042
|
}
|