@livestore/react 0.4.0-dev.9 → 0.5.0-dev.0
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/README.md +1 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/StoreRegistryContext.d.ts +56 -0
- package/dist/StoreRegistryContext.d.ts.map +1 -0
- package/dist/StoreRegistryContext.js +61 -0
- package/dist/StoreRegistryContext.js.map +1 -0
- package/dist/__tests__/fixture.d.ts +12 -283
- package/dist/__tests__/fixture.d.ts.map +1 -1
- package/dist/__tests__/fixture.js +12 -86
- package/dist/__tests__/fixture.js.map +1 -1
- package/dist/experimental/components/LiveList.d.ts +4 -2
- package/dist/experimental/components/LiveList.d.ts.map +1 -1
- package/dist/experimental/components/LiveList.js +11 -9
- package/dist/experimental/components/LiveList.js.map +1 -1
- package/dist/mod.d.ts +8 -5
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +6 -4
- package/dist/mod.js.map +1 -1
- package/dist/useClientDocument.d.ts +12 -4
- package/dist/useClientDocument.d.ts.map +1 -1
- package/dist/useClientDocument.js +4 -18
- package/dist/useClientDocument.js.map +1 -1
- package/dist/useClientDocument.test.js +28 -20
- package/dist/useClientDocument.test.js.map +1 -1
- package/dist/useQuery.d.ts +29 -8
- package/dist/useQuery.d.ts.map +1 -1
- package/dist/useQuery.js +43 -70
- package/dist/useQuery.js.map +1 -1
- package/dist/useQuery.test.js +67 -23
- package/dist/useQuery.test.js.map +1 -1
- package/dist/useRcResource.d.ts +1 -1
- package/dist/useRcResource.d.ts.map +1 -1
- package/dist/useRcResource.js +42 -35
- package/dist/useRcResource.js.map +1 -1
- package/dist/useRcResource.test.js +73 -50
- package/dist/useRcResource.test.js.map +1 -1
- package/dist/useStore.d.ts +74 -7
- package/dist/useStore.d.ts.map +1 -1
- package/dist/useStore.js +82 -16
- package/dist/useStore.js.map +1 -1
- package/dist/useStore.test.d.ts +2 -0
- package/dist/useStore.test.d.ts.map +1 -0
- package/dist/useStore.test.js +242 -0
- package/dist/useStore.test.js.map +1 -0
- package/dist/useSyncStatus.d.ts +22 -0
- package/dist/useSyncStatus.d.ts.map +1 -0
- package/dist/useSyncStatus.js +28 -0
- package/dist/useSyncStatus.js.map +1 -0
- package/package.json +60 -27
- package/src/StoreRegistryContext.tsx +70 -0
- package/src/__snapshots__/useClientDocument.test.tsx.snap +152 -78
- package/src/__snapshots__/useQuery.test.tsx.snap +12 -12
- package/src/__tests__/fixture.tsx +32 -135
- package/src/experimental/components/LiveList.tsx +25 -11
- package/src/mod.ts +8 -12
- package/src/useClientDocument.test.tsx +38 -23
- package/src/useClientDocument.ts +19 -38
- package/src/useQuery.test.tsx +110 -23
- package/src/useQuery.ts +74 -89
- package/src/useRcResource.test.tsx +116 -59
- package/src/useRcResource.ts +52 -38
- package/src/useStore.test.tsx +357 -0
- package/src/useStore.ts +115 -22
- package/src/useSyncStatus.ts +34 -0
- package/dist/LiveStoreContext.d.ts +0 -13
- package/dist/LiveStoreContext.d.ts.map +0 -1
- package/dist/LiveStoreContext.js +0 -3
- package/dist/LiveStoreContext.js.map +0 -1
- package/dist/LiveStoreProvider.d.ts +0 -65
- package/dist/LiveStoreProvider.d.ts.map +0 -1
- package/dist/LiveStoreProvider.js +0 -221
- package/dist/LiveStoreProvider.js.map +0 -1
- package/dist/LiveStoreProvider.test.d.ts +0 -2
- package/dist/LiveStoreProvider.test.d.ts.map +0 -1
- package/dist/LiveStoreProvider.test.js +0 -117
- package/dist/LiveStoreProvider.test.js.map +0 -1
- package/dist/utils/stack-info.d.ts +0 -4
- package/dist/utils/stack-info.d.ts.map +0 -1
- package/dist/utils/stack-info.js +0 -10
- package/dist/utils/stack-info.js.map +0 -1
- package/src/LiveStoreContext.ts +0 -14
- package/src/LiveStoreProvider.test.tsx +0 -248
- package/src/LiveStoreProvider.tsx +0 -413
- package/src/ambient.d.ts +0 -1
- package/src/utils/stack-info.ts +0 -13
package/src/useQuery.test.tsx
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
/** biome-ignore-all lint/a11y: test */
|
|
2
|
-
import * as LiveStore from '@livestore/livestore'
|
|
3
|
-
import { queryDb, signal } from '@livestore/livestore'
|
|
4
|
-
import { RG } from '@livestore/livestore/internal/testing-utils'
|
|
5
|
-
import { Effect, Schema } from '@livestore/utils/effect'
|
|
6
|
-
import { Vitest } from '@livestore/utils-dev/node-vitest'
|
|
7
1
|
import * as ReactTesting from '@testing-library/react'
|
|
8
2
|
import React from 'react'
|
|
9
|
-
// @ts-expect-error no types
|
|
10
3
|
import * as ReactWindow from 'react-window'
|
|
11
4
|
import { expect } from 'vitest'
|
|
12
5
|
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
6
|
+
import { makeInMemoryAdapter } from '@livestore/adapter-web'
|
|
7
|
+
import { provideOtel } from '@livestore/common'
|
|
8
|
+
import * as LiveStore from '@livestore/livestore'
|
|
9
|
+
import { createStore, queryDb, StoreInternalsSymbol, signal } from '@livestore/livestore'
|
|
10
|
+
import { RG } from '@livestore/livestore/internal/testing-utils'
|
|
11
|
+
import { Vitest } from '@livestore/utils-dev/node-vitest'
|
|
12
|
+
import { Effect, Schema } from '@livestore/utils/effect'
|
|
15
13
|
|
|
14
|
+
import { events, makeTodoMvcReact, schema, tables } from './__tests__/fixture.tsx'
|
|
15
|
+
import { __resetUseRcResourceCache } from './useRcResource.ts'
|
|
16
|
+
import { withReactApi } from './useStore.ts'
|
|
17
|
+
|
|
18
|
+
/** Verifies: LS.SYS.INT.REACT-R01, LS.SYS.INT.REACT-R04, LS.SYS.INT.REACT-R05 */
|
|
16
19
|
Vitest.describe.each([{ strictMode: true }, { strictMode: false }] as const)(
|
|
17
20
|
'useQuery (strictMode=%s)',
|
|
18
21
|
({ strictMode }) => {
|
|
@@ -21,7 +24,7 @@ Vitest.describe.each([{ strictMode: true }, { strictMode: false }] as const)(
|
|
|
21
24
|
__resetUseRcResourceCache()
|
|
22
25
|
})
|
|
23
26
|
|
|
24
|
-
Vitest.
|
|
27
|
+
Vitest.live('simple', () =>
|
|
25
28
|
Effect.gen(function* () {
|
|
26
29
|
const { wrapper, store, renderCount } = yield* makeTodoMvcReact({ strictMode })
|
|
27
30
|
|
|
@@ -38,18 +41,18 @@ Vitest.describe.each([{ strictMode: true }, { strictMode: false }] as const)(
|
|
|
38
41
|
|
|
39
42
|
expect(result.current.length).toBe(0)
|
|
40
43
|
expect(renderCount.val).toBe(1)
|
|
41
|
-
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
44
|
+
expect(store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
42
45
|
|
|
43
46
|
ReactTesting.act(() => store.commit(events.todoCreated({ id: 't1', text: 'buy milk', completed: false })))
|
|
44
47
|
|
|
45
48
|
expect(result.current.length).toBe(1)
|
|
46
49
|
expect(result.current[0]!.text).toBe('buy milk')
|
|
47
50
|
expect(renderCount.val).toBe(2)
|
|
48
|
-
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
51
|
+
expect(store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
49
52
|
}),
|
|
50
53
|
)
|
|
51
54
|
|
|
52
|
-
Vitest.
|
|
55
|
+
Vitest.live('same `useQuery` hook invoked with different queries', () =>
|
|
53
56
|
Effect.gen(function* () {
|
|
54
57
|
const { wrapper, store, renderCount } = yield* makeTodoMvcReact({ strictMode })
|
|
55
58
|
|
|
@@ -80,23 +83,29 @@ Vitest.describe.each([{ strictMode: true }, { strictMode: false }] as const)(
|
|
|
80
83
|
|
|
81
84
|
expect(result.current).toBe('buy milk')
|
|
82
85
|
expect(renderCount.val).toBe(1)
|
|
83
|
-
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot(
|
|
86
|
+
expect(store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot(
|
|
87
|
+
'1: after first render',
|
|
88
|
+
)
|
|
84
89
|
|
|
85
90
|
ReactTesting.act(() => store.commit(events.todoUpdated({ id: 't1', text: 'buy soy milk' })))
|
|
86
91
|
|
|
87
92
|
expect(result.current).toBe('buy soy milk')
|
|
88
93
|
expect(renderCount.val).toBe(2)
|
|
89
|
-
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot(
|
|
94
|
+
expect(store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot(
|
|
95
|
+
'2: after first commit',
|
|
96
|
+
)
|
|
90
97
|
|
|
91
98
|
rerender('t2')
|
|
92
99
|
|
|
93
100
|
expect(result.current).toBe('buy eggs')
|
|
94
101
|
expect(renderCount.val).toBe(3)
|
|
95
|
-
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot(
|
|
102
|
+
expect(store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot(
|
|
103
|
+
'3: after forced rerender',
|
|
104
|
+
)
|
|
96
105
|
}),
|
|
97
106
|
)
|
|
98
107
|
|
|
99
|
-
Vitest.
|
|
108
|
+
Vitest.live('filtered dependency query', () =>
|
|
100
109
|
Effect.gen(function* () {
|
|
101
110
|
const { wrapper, store, renderCount } = yield* makeTodoMvcReact({ strictMode })
|
|
102
111
|
|
|
@@ -120,26 +129,26 @@ Vitest.describe.each([{ strictMode: true }, { strictMode: false }] as const)(
|
|
|
120
129
|
|
|
121
130
|
expect(result.current).toBe('buy milk')
|
|
122
131
|
expect(renderCount.val).toBe(1)
|
|
123
|
-
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
132
|
+
expect(store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
124
133
|
|
|
125
134
|
ReactTesting.act(() => store.commit(events.todoUpdated({ id: 't1', text: 'buy soy milk' })))
|
|
126
135
|
|
|
127
136
|
expect(result.current).toBe('buy soy milk')
|
|
128
137
|
expect(renderCount.val).toBe(2)
|
|
129
|
-
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
138
|
+
expect(store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
130
139
|
|
|
131
140
|
ReactTesting.act(() => store.setSignal(filter$, 't2'))
|
|
132
141
|
|
|
133
142
|
expect(result.current).toBe('buy eggs')
|
|
134
143
|
expect(renderCount.val).toBe(3)
|
|
135
|
-
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
144
|
+
expect(store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
136
145
|
}),
|
|
137
146
|
)
|
|
138
147
|
|
|
139
148
|
// NOTE this test covers some special react lifecyle paths which I couldn't easily reproduce without react-window
|
|
140
149
|
// it basically causes a "query swap" in the `useMemo` and both a `useEffect` cleanup call.
|
|
141
150
|
// To handle this properly we introduced the `_tag: 'destroyed'` state in the `spanAlreadyStartedCache`.
|
|
142
|
-
Vitest.
|
|
151
|
+
Vitest.live('should work for a list with react-window', () =>
|
|
143
152
|
Effect.gen(function* () {
|
|
144
153
|
const { wrapper, store } = yield* makeTodoMvcReact({ strictMode })
|
|
145
154
|
|
|
@@ -150,7 +159,7 @@ Vitest.describe.each([{ strictMode: true }, { strictMode: false }] as const)(
|
|
|
150
159
|
width={100}
|
|
151
160
|
itemSize={10}
|
|
152
161
|
itemCount={numItems}
|
|
153
|
-
itemData={Array.from({ length: numItems }, (_, i) => i).
|
|
162
|
+
itemData={Array.from({ length: numItems }, (_, i) => i).toReversed()}
|
|
154
163
|
>
|
|
155
164
|
{ListItem}
|
|
156
165
|
</ReactWindow.FixedSizeList>
|
|
@@ -173,7 +182,7 @@ Vitest.describe.each([{ strictMode: true }, { strictMode: false }] as const)(
|
|
|
173
182
|
}),
|
|
174
183
|
)
|
|
175
184
|
|
|
176
|
-
Vitest.
|
|
185
|
+
Vitest.live('should work with signal', () =>
|
|
177
186
|
Effect.gen(function* () {
|
|
178
187
|
const { wrapper, store } = yield* makeTodoMvcReact({ strictMode })
|
|
179
188
|
const num$ = signal(0)
|
|
@@ -187,5 +196,83 @@ Vitest.describe.each([{ strictMode: true }, { strictMode: false }] as const)(
|
|
|
187
196
|
expect(result.current).toBe(1)
|
|
188
197
|
}),
|
|
189
198
|
)
|
|
199
|
+
|
|
200
|
+
Vitest.live('supports query builders directly', () =>
|
|
201
|
+
Effect.gen(function* () {
|
|
202
|
+
const { wrapper, store } = yield* makeTodoMvcReact({ strictMode })
|
|
203
|
+
|
|
204
|
+
store.commit(
|
|
205
|
+
events.todoCreated({ id: 't1', text: 'buy milk', completed: false }),
|
|
206
|
+
events.todoCreated({ id: 't2', text: 'buy eggs', completed: true }),
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
const todosWhereIncomplete = tables.todos.where({ completed: false })
|
|
210
|
+
|
|
211
|
+
const { result } = ReactTesting.renderHook(() => store.useQuery(todosWhereIncomplete).map((todo) => todo.id), {
|
|
212
|
+
wrapper,
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
expect(result.current).toEqual(['t1'])
|
|
216
|
+
}),
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
Vitest.live('derives a fresh LiveQuery per Store instance', () =>
|
|
220
|
+
Effect.gen(function* () {
|
|
221
|
+
const makeStore = () =>
|
|
222
|
+
createStore({
|
|
223
|
+
schema,
|
|
224
|
+
storeId: 'reset-test',
|
|
225
|
+
adapter: makeInMemoryAdapter({ clientId: 'stable-client', sessionId: 'stable-session' }),
|
|
226
|
+
debug: { instanceId: 'test' },
|
|
227
|
+
}).pipe(provideOtel({}))
|
|
228
|
+
|
|
229
|
+
const storeA = withReactApi(yield* makeStore())
|
|
230
|
+
const storeB = withReactApi(yield* makeStore())
|
|
231
|
+
|
|
232
|
+
const allTodos$ = queryDb({ query: `select * from todos`, schema: Schema.Array(tables.todos.rowSchema) })
|
|
233
|
+
|
|
234
|
+
const MaybeStrictMode = strictMode === true ? React.StrictMode : React.Fragment
|
|
235
|
+
const wrapper = ({ children }: any) => <MaybeStrictMode>{children}</MaybeStrictMode>
|
|
236
|
+
|
|
237
|
+
storeA.commit(events.todoCreated({ id: 't1', text: 'buy milk', completed: false }))
|
|
238
|
+
|
|
239
|
+
const { result, rerender } = ReactTesting.renderHook(
|
|
240
|
+
({ store }: { store: typeof storeA }) => store.useQuery(allTodos$).length,
|
|
241
|
+
{ wrapper, initialProps: { store: storeA } },
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
expect(result.current).toBe(1)
|
|
245
|
+
|
|
246
|
+
rerender({ store: storeB })
|
|
247
|
+
|
|
248
|
+
expect(result.current).toBe(0)
|
|
249
|
+
}),
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
Vitest.live('union of different result types with useQuery', () =>
|
|
253
|
+
Effect.gen(function* () {
|
|
254
|
+
const { wrapper, store, renderCount } = yield* makeTodoMvcReact({ strictMode })
|
|
255
|
+
|
|
256
|
+
const str$ = signal('hello', { label: 'str' })
|
|
257
|
+
const num$ = signal(123, { label: 'num' })
|
|
258
|
+
|
|
259
|
+
const { result, rerender } = ReactTesting.renderHook(
|
|
260
|
+
(useNum: boolean) => {
|
|
261
|
+
renderCount.inc()
|
|
262
|
+
const query$ = React.useMemo(() => (useNum === true ? num$ : str$), [useNum])
|
|
263
|
+
return store.useQuery(query$)
|
|
264
|
+
},
|
|
265
|
+
{ wrapper, initialProps: false },
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
expect(result.current).toBe('hello')
|
|
269
|
+
expect(renderCount.val).toBe(1)
|
|
270
|
+
|
|
271
|
+
rerender(true)
|
|
272
|
+
|
|
273
|
+
expect(result.current).toBe(123)
|
|
274
|
+
expect(renderCount.val).toBe(2)
|
|
275
|
+
}),
|
|
276
|
+
)
|
|
190
277
|
},
|
|
191
278
|
)
|
package/src/useQuery.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { extractStackInfoFromStackTrace, stackInfoToString } from '@livestore/livestore'
|
|
3
|
-
import type { LiveQueries } from '@livestore/livestore/internal'
|
|
4
|
-
import { deepEqual, indent, shouldNeverHappen } from '@livestore/utils'
|
|
5
|
-
import * as otel from '@opentelemetry/api'
|
|
1
|
+
import type * as otel from '@opentelemetry/api'
|
|
6
2
|
import React from 'react'
|
|
7
3
|
|
|
8
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
captureStackInfo,
|
|
6
|
+
computeRcRefKey,
|
|
7
|
+
createQueryResource,
|
|
8
|
+
type NormalizedQueryable,
|
|
9
|
+
normalizeQueryable,
|
|
10
|
+
runInitialQuery,
|
|
11
|
+
} from '@livestore/framework-toolkit'
|
|
12
|
+
import type { LiveQuery, Queryable, Store } from '@livestore/livestore'
|
|
13
|
+
import type { LiveQueries } from '@livestore/livestore/internal'
|
|
14
|
+
import { deepEqual, shouldNeverHappen } from '@livestore/utils'
|
|
15
|
+
|
|
9
16
|
import { useRcResource } from './useRcResource.ts'
|
|
10
|
-
import { originalStackLimit } from './utils/stack-info.ts'
|
|
11
17
|
import { useStateRefWithReactiveInput } from './utils/useStateRefWithReactiveInput.ts'
|
|
12
18
|
|
|
13
19
|
/**
|
|
@@ -21,15 +27,36 @@ import { useStateRefWithReactiveInput } from './utils/useStateRefWithReactiveInp
|
|
|
21
27
|
* }
|
|
22
28
|
* ```
|
|
23
29
|
*/
|
|
24
|
-
export const useQuery = <
|
|
25
|
-
|
|
30
|
+
export const useQuery = <TQueryable extends Queryable<any>>(
|
|
31
|
+
queryable: TQueryable,
|
|
26
32
|
options?: { store?: Store },
|
|
27
|
-
):
|
|
33
|
+
): Queryable.Result<TQueryable> => useQueryRef(queryable, options).valueRef.current
|
|
28
34
|
|
|
29
35
|
/**
|
|
36
|
+
* Like `useQuery`, but also returns a reference to the underlying LiveQuery instance.
|
|
37
|
+
*
|
|
38
|
+
* Usage
|
|
39
|
+
* - Accepts any `Queryable<TResult>`: a `LiveQueryDef`, `SignalDef`, a `LiveQuery` instance
|
|
40
|
+
* or a SQL `QueryBuilder`. Unions of queryables are supported and the result type is
|
|
41
|
+
* inferred via `Queryable.Result<TQueryable>`.
|
|
42
|
+
* - Creates an OpenTelemetry span per unique query, reusing it while the ref-counted
|
|
43
|
+
* resource is alive. The span name is updated once the dynamic label is known.
|
|
44
|
+
* - Manages a reference-counted resource under-the-hood so query instances are shared
|
|
45
|
+
* across re-renders and properly disposed once no longer referenced.
|
|
46
|
+
*
|
|
47
|
+
* Parameters
|
|
48
|
+
* - `queryable`: The query definition/instance/builder to run and subscribe to.
|
|
49
|
+
* - `options.store`: The store to use. Required when calling `useQueryRef` directly; automatically provided when using `store.useQuery()`.
|
|
50
|
+
* - `options.otelContext`: Optional parent otel context for the query span.
|
|
51
|
+
* - `options.otelSpanName`: Optional explicit span name; otherwise derived from the query label.
|
|
52
|
+
*
|
|
53
|
+
* Returns
|
|
54
|
+
* - `valueRef`: A React ref whose `current` holds the latest query result. The type is
|
|
55
|
+
* `Queryable.Result<TQueryable>` with full inference for unions.
|
|
56
|
+
* - `queryRcRef`: The underlying reference-counted `LiveQuery` instance used by the store.
|
|
30
57
|
*/
|
|
31
|
-
export const useQueryRef = <
|
|
32
|
-
|
|
58
|
+
export const useQueryRef = <TQueryable extends Queryable<any>>(
|
|
59
|
+
queryable: TQueryable,
|
|
33
60
|
options?: {
|
|
34
61
|
store?: Store
|
|
35
62
|
/** Parent otel context for the query */
|
|
@@ -38,102 +65,58 @@ export const useQueryRef = <TQuery extends LiveQueryDef.Any>(
|
|
|
38
65
|
otelSpanName?: string
|
|
39
66
|
},
|
|
40
67
|
): {
|
|
41
|
-
valueRef: React.RefObject<
|
|
42
|
-
queryRcRef: LiveQueries.RcRef<LiveQuery<
|
|
68
|
+
valueRef: React.RefObject<Queryable.Result<TQueryable>>
|
|
69
|
+
queryRcRef: LiveQueries.RcRef<LiveQuery<Queryable.Result<TQueryable>>>
|
|
43
70
|
} => {
|
|
44
|
-
const store =
|
|
45
|
-
options?.store ?? // biome-ignore lint/correctness/useHookAtTopLevel: store is stable
|
|
46
|
-
React.useContext(LiveStoreContext)?.store ??
|
|
47
|
-
shouldNeverHappen(`No store provided to useQuery`)
|
|
48
|
-
|
|
49
|
-
// It's important to use all "aspects" of a store instance here, otherwise we get unexpected cache mappings
|
|
50
|
-
const rcRefKey = `${store.storeId}_${store.clientId}_${store.sessionId}_${queryDef.hash}`
|
|
71
|
+
const store = options?.store ?? shouldNeverHappen(`No store provided to useQuery`)
|
|
51
72
|
|
|
52
|
-
|
|
53
|
-
Error.stackTraceLimit = 10
|
|
54
|
-
const stack = new Error().stack!
|
|
55
|
-
Error.stackTraceLimit = originalStackLimit
|
|
56
|
-
return extractStackInfoFromStackTrace(stack)
|
|
57
|
-
}, [])
|
|
73
|
+
type TResult = Queryable.Result<TQueryable>
|
|
58
74
|
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const span = store.otel.tracer.startSpan(
|
|
65
|
-
options?.otelSpanName ?? `LiveStore:useQuery:${queryDefLabel}`,
|
|
66
|
-
{ attributes: { label: queryDefLabel, firstStackInfo: JSON.stringify(stackInfo) } },
|
|
67
|
-
options?.otelContext ?? store.otel.queriesSpanContext,
|
|
68
|
-
)
|
|
75
|
+
const normalized = React.useMemo<NormalizedQueryable<TResult>>(
|
|
76
|
+
() => normalizeQueryable(queryable as Queryable<TResult>),
|
|
77
|
+
[queryable],
|
|
78
|
+
)
|
|
69
79
|
|
|
70
|
-
|
|
80
|
+
const rcRefKey = React.useMemo(() => computeRcRefKey(store, normalized), [normalized, store])
|
|
71
81
|
|
|
72
|
-
|
|
82
|
+
const stackInfo = React.useMemo(() => captureStackInfo(), [])
|
|
73
83
|
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
const { queryRcRef, span, otelContext } = useRcResource(
|
|
85
|
+
store,
|
|
86
|
+
rcRefKey,
|
|
87
|
+
() =>
|
|
88
|
+
createQueryResource(store, normalized, stackInfo, {
|
|
89
|
+
otelSpanName: options?.otelSpanName,
|
|
90
|
+
otelContext: options?.otelContext,
|
|
91
|
+
}),
|
|
76
92
|
// We need to keep the queryRcRef alive a bit longer, so we have a second `useRcResource` below
|
|
77
93
|
// which takes care of disposing the queryRcRef
|
|
78
94
|
() => {},
|
|
79
95
|
)
|
|
80
96
|
|
|
81
|
-
|
|
82
|
-
// const queryRcRef.value.get()
|
|
83
|
-
// }
|
|
84
|
-
|
|
85
|
-
const query$ = queryRcRef.value as LiveQuery<LiveQueries.GetResult<TQuery>>
|
|
97
|
+
const query$ = queryRcRef.value
|
|
86
98
|
|
|
87
99
|
React.useDebugValue(`LiveStore:useQuery:${query$.id}:${query$.label}`)
|
|
88
|
-
// console.debug(`LiveStore:useQuery:${query$.id}:${query$.label}`)
|
|
89
|
-
|
|
90
|
-
const initialResult = React.useMemo(() => {
|
|
91
|
-
try {
|
|
92
|
-
return query$.run({
|
|
93
|
-
otelContext,
|
|
94
|
-
debugRefreshReason: {
|
|
95
|
-
_tag: 'react',
|
|
96
|
-
api: 'useQuery',
|
|
97
|
-
label: `useQuery:initial-run:${query$.label}`,
|
|
98
|
-
stackInfo,
|
|
99
|
-
},
|
|
100
|
-
})
|
|
101
|
-
} catch (cause: any) {
|
|
102
|
-
console.error('[@livestore/react:useQuery] Error running query', cause)
|
|
103
|
-
throw new Error(
|
|
104
|
-
`\
|
|
105
|
-
[@livestore/react:useQuery] Error running query: ${cause.name}
|
|
106
|
-
|
|
107
|
-
Query: ${query$.label}
|
|
108
100
|
|
|
109
|
-
React
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
Stack trace:
|
|
114
|
-
`,
|
|
115
|
-
{ cause },
|
|
116
|
-
)
|
|
117
|
-
}
|
|
118
|
-
}, [otelContext, query$, stackInfo])
|
|
101
|
+
const initialResult = React.useMemo(
|
|
102
|
+
() => runInitialQuery(query$, otelContext, stackInfo, 'react'),
|
|
103
|
+
[otelContext, query$, stackInfo],
|
|
104
|
+
)
|
|
119
105
|
|
|
120
106
|
// 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<
|
|
122
|
-
|
|
123
|
-
// TODO we probably need to change the order of `useEffect` calls, so we destroy the query at the end
|
|
124
|
-
// before calling the LS `onEffect` on it
|
|
107
|
+
const [valueRef, setValue] = useStateRefWithReactiveInput<TResult>(initialResult)
|
|
125
108
|
|
|
126
109
|
// Subscribe to future updates for this query
|
|
127
110
|
React.useEffect(() => {
|
|
128
|
-
// TODO double check whether we still need `activeSubscriptions`
|
|
129
111
|
query$.activeSubscriptions.add(stackInfo)
|
|
130
112
|
|
|
131
113
|
// Dynamic queries only set their actual label after they've been run the first time,
|
|
132
114
|
// so we're also updating the span name here.
|
|
133
115
|
span.updateName(options?.otelSpanName ?? `LiveStore:useQuery:${query$.label}`)
|
|
134
116
|
|
|
135
|
-
return store.subscribe(
|
|
136
|
-
|
|
117
|
+
return store.subscribe(
|
|
118
|
+
query$,
|
|
119
|
+
(newValue) => {
|
|
137
120
|
// NOTE: we return a reference to the result object within LiveStore;
|
|
138
121
|
// this implies that app code must not mutate the results, or else
|
|
139
122
|
// there may be weird reactivity bugs.
|
|
@@ -141,19 +124,21 @@ Stack trace:
|
|
|
141
124
|
setValue(newValue)
|
|
142
125
|
}
|
|
143
126
|
},
|
|
144
|
-
|
|
145
|
-
|
|
127
|
+
{
|
|
128
|
+
onUnsubsubscribe: () => {
|
|
129
|
+
query$.activeSubscriptions.delete(stackInfo)
|
|
130
|
+
},
|
|
131
|
+
label: query$.label,
|
|
132
|
+
otelContext,
|
|
146
133
|
},
|
|
147
|
-
|
|
148
|
-
otelContext,
|
|
149
|
-
})
|
|
134
|
+
)
|
|
150
135
|
}, [stackInfo, query$, setValue, store, valueRef, otelContext, span, options?.otelSpanName])
|
|
151
136
|
|
|
152
137
|
useRcResource(
|
|
138
|
+
store,
|
|
153
139
|
rcRefKey,
|
|
154
140
|
() => ({ queryRcRef, span }),
|
|
155
141
|
({ queryRcRef, span }) => {
|
|
156
|
-
// console.debug('deref', queryRcRef.value.id, queryRcRef.value.label)
|
|
157
142
|
queryRcRef.deref()
|
|
158
143
|
span.end()
|
|
159
144
|
},
|