@livestore/react 0.4.0-dev.12 → 0.4.0-dev.14
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/.tsbuildinfo +1 -1
- package/dist/__tests__/fixture.d.ts +3 -2
- package/dist/__tests__/fixture.d.ts.map +1 -1
- package/dist/__tests__/fixture.js +3 -2
- package/dist/__tests__/fixture.js.map +1 -1
- package/dist/experimental/mod.d.ts +1 -0
- package/dist/experimental/mod.d.ts.map +1 -1
- package/dist/experimental/mod.js +1 -0
- package/dist/experimental/mod.js.map +1 -1
- package/dist/experimental/multi-store/StoreRegistry.d.ts +75 -0
- package/dist/experimental/multi-store/StoreRegistry.d.ts.map +1 -0
- package/dist/experimental/multi-store/StoreRegistry.js +286 -0
- package/dist/experimental/multi-store/StoreRegistry.js.map +1 -0
- package/dist/experimental/multi-store/StoreRegistryContext.d.ts +10 -0
- package/dist/experimental/multi-store/StoreRegistryContext.d.ts.map +1 -0
- package/dist/experimental/multi-store/StoreRegistryContext.js +15 -0
- package/dist/experimental/multi-store/StoreRegistryContext.js.map +1 -0
- package/dist/experimental/multi-store/mod.d.ts +6 -0
- package/dist/experimental/multi-store/mod.d.ts.map +1 -0
- package/dist/experimental/multi-store/mod.js +6 -0
- package/dist/experimental/multi-store/mod.js.map +1 -0
- package/dist/experimental/multi-store/storeOptions.d.ts +4 -0
- package/dist/experimental/multi-store/storeOptions.d.ts.map +1 -0
- package/dist/experimental/multi-store/storeOptions.js +4 -0
- package/dist/experimental/multi-store/storeOptions.js.map +1 -0
- package/dist/experimental/multi-store/types.d.ts +44 -0
- package/dist/experimental/multi-store/types.d.ts.map +1 -0
- package/dist/experimental/multi-store/types.js +2 -0
- package/dist/experimental/multi-store/types.js.map +1 -0
- package/dist/experimental/multi-store/useStore.d.ts +11 -0
- package/dist/experimental/multi-store/useStore.d.ts.map +1 -0
- package/dist/experimental/multi-store/useStore.js +21 -0
- package/dist/experimental/multi-store/useStore.js.map +1 -0
- package/dist/useQuery.d.ts +28 -6
- package/dist/useQuery.d.ts.map +1 -1
- package/dist/useQuery.js +54 -7
- package/dist/useQuery.js.map +1 -1
- package/dist/useQuery.test.js +24 -0
- package/dist/useQuery.test.js.map +1 -1
- package/dist/useStore.d.ts +2 -1
- package/dist/useStore.d.ts.map +1 -1
- package/dist/useStore.js +1 -1
- package/dist/useStore.js.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/fixture.tsx +7 -16
- package/src/experimental/mod.ts +1 -0
- package/src/experimental/multi-store/StoreRegistry.ts +356 -0
- package/src/experimental/multi-store/StoreRegistryContext.tsx +23 -0
- package/src/experimental/multi-store/mod.ts +5 -0
- package/src/experimental/multi-store/storeOptions.ts +8 -0
- package/src/experimental/multi-store/types.ts +55 -0
- package/src/experimental/multi-store/useStore.ts +39 -0
- package/src/useQuery.test.tsx +45 -0
- package/src/useQuery.ts +84 -16
- package/src/useStore.ts +4 -3
package/src/useQuery.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
import { isQueryBuilder } from '@livestore/common'
|
|
1
2
|
import type { LiveQuery, LiveQueryDef, Store } from '@livestore/livestore'
|
|
2
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
extractStackInfoFromStackTrace,
|
|
5
|
+
isQueryable,
|
|
6
|
+
type Queryable,
|
|
7
|
+
queryDb,
|
|
8
|
+
type SignalDef,
|
|
9
|
+
stackInfoToString,
|
|
10
|
+
} from '@livestore/livestore'
|
|
3
11
|
import type { LiveQueries } from '@livestore/livestore/internal'
|
|
4
12
|
import { deepEqual, indent, shouldNeverHappen } from '@livestore/utils'
|
|
5
13
|
import * as otel from '@opentelemetry/api'
|
|
@@ -21,15 +29,36 @@ import { useStateRefWithReactiveInput } from './utils/useStateRefWithReactiveInp
|
|
|
21
29
|
* }
|
|
22
30
|
* ```
|
|
23
31
|
*/
|
|
24
|
-
export const useQuery = <
|
|
25
|
-
|
|
32
|
+
export const useQuery = <TQueryable extends Queryable<any>>(
|
|
33
|
+
queryable: TQueryable,
|
|
26
34
|
options?: { store?: Store },
|
|
27
|
-
):
|
|
35
|
+
): Queryable.Result<TQueryable> => useQueryRef(queryable, options).valueRef.current
|
|
28
36
|
|
|
29
37
|
/**
|
|
38
|
+
* Like `useQuery`, but also returns a reference to the underlying LiveQuery instance.
|
|
39
|
+
*
|
|
40
|
+
* Usage
|
|
41
|
+
* - Accepts any `Queryable<TResult>`: a `LiveQueryDef`, `SignalDef`, a `LiveQuery` instance
|
|
42
|
+
* or a SQL `QueryBuilder`. Unions of queryables are supported and the result type is
|
|
43
|
+
* inferred via `Queryable.Result<TQueryable>`.
|
|
44
|
+
* - Creates an OpenTelemetry span per unique query, reusing it while the ref-counted
|
|
45
|
+
* resource is alive. The span name is updated once the dynamic label is known.
|
|
46
|
+
* - Manages a reference-counted resource under-the-hood so query instances are shared
|
|
47
|
+
* across re-renders and properly disposed once no longer referenced.
|
|
48
|
+
*
|
|
49
|
+
* Parameters
|
|
50
|
+
* - `queryable`: The query definition/instance/builder to run and subscribe to.
|
|
51
|
+
* - `options.store`: Optional store to use; by default the store from `LiveStoreContext` is used.
|
|
52
|
+
* - `options.otelContext`: Optional parent otel context for the query span.
|
|
53
|
+
* - `options.otelSpanName`: Optional explicit span name; otherwise derived from the query label.
|
|
54
|
+
*
|
|
55
|
+
* Returns
|
|
56
|
+
* - `valueRef`: A React ref whose `current` holds the latest query result. The type is
|
|
57
|
+
* `Queryable.Result<TQueryable>` with full inference for unions.
|
|
58
|
+
* - `queryRcRef`: The underlying reference-counted `LiveQuery` instance used by the store.
|
|
30
59
|
*/
|
|
31
|
-
export const useQueryRef = <
|
|
32
|
-
|
|
60
|
+
export const useQueryRef = <TQueryable extends Queryable<any>>(
|
|
61
|
+
queryable: TQueryable,
|
|
33
62
|
options?: {
|
|
34
63
|
store?: Store
|
|
35
64
|
/** Parent otel context for the query */
|
|
@@ -38,16 +67,50 @@ export const useQueryRef = <TQuery extends LiveQueryDef.Any>(
|
|
|
38
67
|
otelSpanName?: string
|
|
39
68
|
},
|
|
40
69
|
): {
|
|
41
|
-
valueRef: React.RefObject<
|
|
42
|
-
queryRcRef: LiveQueries.RcRef<LiveQuery<
|
|
70
|
+
valueRef: React.RefObject<Queryable.Result<TQueryable>>
|
|
71
|
+
queryRcRef: LiveQueries.RcRef<LiveQuery<Queryable.Result<TQueryable>>>
|
|
43
72
|
} => {
|
|
44
73
|
const store =
|
|
45
74
|
options?.store ?? // biome-ignore lint/correctness/useHookAtTopLevel: store is stable
|
|
46
75
|
React.useContext(LiveStoreContext)?.store ??
|
|
47
76
|
shouldNeverHappen(`No store provided to useQuery`)
|
|
48
77
|
|
|
78
|
+
type TResult = Queryable.Result<TQueryable>
|
|
79
|
+
type NormalizedQueryable =
|
|
80
|
+
| { _tag: 'definition'; def: LiveQueryDef<TResult> | SignalDef<TResult> }
|
|
81
|
+
| { _tag: 'live-query'; query$: LiveQuery<TResult> }
|
|
82
|
+
|
|
83
|
+
const normalized = React.useMemo<NormalizedQueryable>(() => {
|
|
84
|
+
if (!isQueryable(queryable)) {
|
|
85
|
+
return shouldNeverHappen('useQuery expected a Queryable value')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (isQueryBuilder(queryable)) {
|
|
89
|
+
return { _tag: 'definition', def: queryDb(queryable) }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (
|
|
93
|
+
(queryable as LiveQueryDef<TResult> | SignalDef<TResult>)._tag === 'def' ||
|
|
94
|
+
(queryable as LiveQueryDef<TResult> | SignalDef<TResult>)._tag === 'signal-def'
|
|
95
|
+
) {
|
|
96
|
+
return { _tag: 'definition', def: queryable as LiveQueryDef<TResult> | SignalDef<TResult> }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { _tag: 'live-query', query$: queryable as LiveQuery<TResult> }
|
|
100
|
+
}, [queryable])
|
|
101
|
+
|
|
49
102
|
// It's important to use all "aspects" of a store instance here, otherwise we get unexpected cache mappings
|
|
50
|
-
const rcRefKey =
|
|
103
|
+
const rcRefKey = React.useMemo(() => {
|
|
104
|
+
const base = `${store.storeId}_${store.clientId}_${store.sessionId}`
|
|
105
|
+
|
|
106
|
+
if (normalized._tag === 'definition') {
|
|
107
|
+
return `${base}:def:${normalized.def.hash}`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return `${base}:instance:${normalized.query$.id}`
|
|
111
|
+
}, [normalized, store.clientId, store.sessionId, store.storeId])
|
|
112
|
+
|
|
113
|
+
const resourceLabel = normalized._tag === 'definition' ? normalized.def.label : normalized.query$.label
|
|
51
114
|
|
|
52
115
|
const stackInfo = React.useMemo(() => {
|
|
53
116
|
Error.stackTraceLimit = 10
|
|
@@ -59,17 +122,22 @@ export const useQueryRef = <TQuery extends LiveQueryDef.Any>(
|
|
|
59
122
|
const { queryRcRef, span, otelContext } = useRcResource(
|
|
60
123
|
rcRefKey,
|
|
61
124
|
() => {
|
|
62
|
-
const queryDefLabel = queryDef.label
|
|
63
|
-
|
|
64
125
|
const span = store.otel.tracer.startSpan(
|
|
65
|
-
options?.otelSpanName ?? `LiveStore:useQuery:${
|
|
66
|
-
{ attributes: { label:
|
|
126
|
+
options?.otelSpanName ?? `LiveStore:useQuery:${resourceLabel}`,
|
|
127
|
+
{ attributes: { label: resourceLabel, firstStackInfo: JSON.stringify(stackInfo) } },
|
|
67
128
|
options?.otelContext ?? store.otel.queriesSpanContext,
|
|
68
129
|
)
|
|
69
130
|
|
|
70
131
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
71
132
|
|
|
72
|
-
const queryRcRef =
|
|
133
|
+
const queryRcRef =
|
|
134
|
+
normalized._tag === 'definition'
|
|
135
|
+
? normalized.def.make(store.reactivityGraph.context!, otelContext)
|
|
136
|
+
: ({
|
|
137
|
+
value: normalized.query$,
|
|
138
|
+
deref: () => {},
|
|
139
|
+
rc: Number.POSITIVE_INFINITY,
|
|
140
|
+
} satisfies LiveQueries.RcRef<LiveQuery<TResult>>)
|
|
73
141
|
|
|
74
142
|
return { queryRcRef, span, otelContext }
|
|
75
143
|
},
|
|
@@ -82,7 +150,7 @@ export const useQueryRef = <TQuery extends LiveQueryDef.Any>(
|
|
|
82
150
|
// const queryRcRef.value.get()
|
|
83
151
|
// }
|
|
84
152
|
|
|
85
|
-
const query$ = queryRcRef.value as LiveQuery<
|
|
153
|
+
const query$ = queryRcRef.value as LiveQuery<TResult>
|
|
86
154
|
|
|
87
155
|
React.useDebugValue(`LiveStore:useQuery:${query$.id}:${query$.label}`)
|
|
88
156
|
// console.debug(`LiveStore:useQuery:${query$.id}:${query$.label}`)
|
|
@@ -118,7 +186,7 @@ Stack trace:
|
|
|
118
186
|
}, [otelContext, query$, stackInfo])
|
|
119
187
|
|
|
120
188
|
// We know the query has a result by the time we use it; so we can synchronously populate a default state
|
|
121
|
-
const [valueRef, setValue] = useStateRefWithReactiveInput<
|
|
189
|
+
const [valueRef, setValue] = useStateRefWithReactiveInput<TResult>(initialResult)
|
|
122
190
|
|
|
123
191
|
// TODO we probably need to change the order of `useEffect` calls, so we destroy the query at the end
|
|
124
192
|
// before calling the LS `onEffect` on it
|
package/src/useStore.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { LiveStoreSchema } from '@livestore/common/schema'
|
|
1
2
|
import type { Store } from '@livestore/livestore'
|
|
2
3
|
import React from 'react'
|
|
3
4
|
|
|
@@ -6,14 +7,14 @@ import { LiveStoreContext } from './LiveStoreContext.ts'
|
|
|
6
7
|
import { useClientDocument } from './useClientDocument.ts'
|
|
7
8
|
import { useQuery } from './useQuery.ts'
|
|
8
9
|
|
|
9
|
-
export const withReactApi = (store: Store): Store & ReactApi => {
|
|
10
|
+
export const withReactApi = <TSchema extends LiveStoreSchema>(store: Store<TSchema>): Store<TSchema> & ReactApi => {
|
|
10
11
|
// @ts-expect-error TODO properly implement this
|
|
11
12
|
|
|
12
|
-
store.useQuery = (
|
|
13
|
+
store.useQuery = (queryable) => useQuery(queryable, { store })
|
|
13
14
|
// @ts-expect-error TODO properly implement this
|
|
14
15
|
|
|
15
16
|
store.useClientDocument = (table, idOrOptions, options) => useClientDocument(table, idOrOptions, options, { store })
|
|
16
|
-
return store as Store & ReactApi
|
|
17
|
+
return store as Store<TSchema> & ReactApi
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export const useStore = (options?: { store?: Store }): { store: Store & ReactApi } => {
|