@livestore/react 0.3.0-dev.11 → 0.3.0-dev.5
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/LiveStoreContext.d.ts +3 -5
- package/dist/LiveStoreContext.d.ts.map +1 -1
- package/dist/LiveStoreContext.js +3 -7
- package/dist/LiveStoreContext.js.map +1 -1
- package/dist/LiveStoreProvider.d.ts +1 -2
- package/dist/LiveStoreProvider.d.ts.map +1 -1
- package/dist/LiveStoreProvider.js +20 -5
- package/dist/LiveStoreProvider.js.map +1 -1
- package/dist/__tests__/fixture.d.ts +9 -6
- package/dist/__tests__/fixture.d.ts.map +1 -1
- package/dist/__tests__/fixture.js +7 -6
- package/dist/__tests__/fixture.js.map +1 -1
- package/dist/experimental/components/LiveList.d.ts +2 -2
- package/dist/experimental/components/LiveList.d.ts.map +1 -1
- package/dist/experimental/components/LiveList.js +4 -5
- package/dist/experimental/components/LiveList.js.map +1 -1
- package/dist/mod.d.ts +1 -0
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +1 -0
- package/dist/mod.js.map +1 -1
- package/dist/useAtom.d.ts +2 -4
- package/dist/useAtom.d.ts.map +1 -1
- package/dist/useAtom.js +28 -32
- package/dist/useAtom.js.map +1 -1
- package/dist/useQuery.d.ts +3 -26
- package/dist/useQuery.d.ts.map +1 -1
- package/dist/useQuery.js +45 -60
- package/dist/useQuery.js.map +1 -1
- package/dist/useQuery.test.js +16 -70
- package/dist/useQuery.test.js.map +1 -1
- package/dist/useRow.d.ts +7 -10
- package/dist/useRow.d.ts.map +1 -1
- package/dist/useRow.js +19 -16
- package/dist/useRow.js.map +1 -1
- package/dist/useRow.test.js +96 -74
- package/dist/useRow.test.js.map +1 -1
- package/dist/useScopedQuery.d.ts +4 -10
- package/dist/useScopedQuery.d.ts.map +1 -1
- package/dist/useScopedQuery.js +52 -97
- package/dist/useScopedQuery.js.map +1 -1
- package/dist/useScopedQuery.test.js +12 -13
- package/dist/useScopedQuery.test.js.map +1 -1
- package/dist/utils/useStateRefWithReactiveInput.d.ts +1 -1
- package/dist/utils/useStateRefWithReactiveInput.d.ts.map +1 -1
- package/dist/utils/useStateRefWithReactiveInput.js.map +1 -1
- package/package.json +17 -18
- package/src/LiveStoreContext.ts +6 -10
- package/src/LiveStoreProvider.tsx +21 -7
- package/src/__snapshots__/useRow.test.tsx.snap +149 -337
- package/src/__tests__/fixture.tsx +10 -7
- package/src/experimental/components/LiveList.tsx +7 -8
- package/src/mod.ts +1 -0
- package/src/useAtom.ts +11 -22
- package/src/useQuery.test.tsx +67 -165
- package/src/useQuery.ts +54 -84
- package/src/useRow.test.tsx +163 -130
- package/src/useRow.ts +35 -32
- package/src/useScopedQuery.test.tsx +96 -0
- package/src/useScopedQuery.ts +142 -0
- package/src/utils/useStateRefWithReactiveInput.ts +1 -1
- package/dist/useRcRef.d.ts +0 -72
- package/dist/useRcRef.d.ts.map +0 -1
- package/dist/useRcRef.js +0 -146
- package/dist/useRcRef.js.map +0 -1
- package/dist/useRcRef.test.d.ts +0 -2
- package/dist/useRcRef.test.d.ts.map +0 -1
- package/dist/useRcRef.test.js +0 -128
- package/dist/useRcRef.test.js.map +0 -1
- package/dist/useRcResource.d.ts +0 -76
- package/dist/useRcResource.d.ts.map +0 -1
- package/dist/useRcResource.js +0 -150
- package/dist/useRcResource.js.map +0 -1
- package/dist/useRcResource.test.d.ts +0 -2
- package/dist/useRcResource.test.d.ts.map +0 -1
- package/dist/useRcResource.test.js +0 -122
- package/dist/useRcResource.test.js.map +0 -1
- package/src/__snapshots__/useQuery.test.tsx.snap +0 -2011
- package/src/useRcResource.test.tsx +0 -167
- package/src/useRcResource.ts +0 -180
package/src/useRow.test.tsx
CHANGED
|
@@ -1,34 +1,30 @@
|
|
|
1
1
|
import * as LiveStore from '@livestore/livestore'
|
|
2
2
|
import { getSimplifiedRootSpan } from '@livestore/livestore/internal/testing-utils'
|
|
3
3
|
import { Effect, ReadonlyRecord, Schema } from '@livestore/utils/effect'
|
|
4
|
-
import { Vitest } from '@livestore/utils/node-vitest'
|
|
5
4
|
import * as otel from '@opentelemetry/api'
|
|
6
5
|
import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
|
7
|
-
import
|
|
6
|
+
import { render, renderHook } from '@testing-library/react'
|
|
8
7
|
import React from 'react'
|
|
9
|
-
import {
|
|
8
|
+
import { describe, expect, it } from 'vitest'
|
|
10
9
|
|
|
11
|
-
import { AppRouterSchema, makeTodoMvcReact, tables, todos } from './__tests__/fixture.js'
|
|
10
|
+
import { AppComponentSchema, AppRouterSchema, makeTodoMvcReact, tables, todos } from './__tests__/fixture.js'
|
|
12
11
|
import * as LiveStoreReact from './mod.js'
|
|
13
|
-
import { __resetUseRcResourceCache } from './useRcResource.js'
|
|
14
|
-
|
|
15
|
-
// const strictMode = process.env.REACT_STRICT_MODE !== undefined
|
|
16
12
|
|
|
17
13
|
// NOTE running tests concurrently doesn't work with the default global db graph
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
__resetUseRcResourceCache()
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
Vitest.scopedLive('should update the data based on component key', () =>
|
|
14
|
+
describe('useRow', () => {
|
|
15
|
+
it('should update the data based on component key', () =>
|
|
24
16
|
Effect.gen(function* () {
|
|
25
|
-
const { wrapper, store,
|
|
17
|
+
const { wrapper, store, reactivityGraph, makeRenderCount } = yield* makeTodoMvcReact({
|
|
18
|
+
useGlobalReactivityGraph: false,
|
|
19
|
+
})
|
|
26
20
|
|
|
27
|
-
const
|
|
21
|
+
const renderCount = makeRenderCount()
|
|
22
|
+
|
|
23
|
+
const { result, rerender } = renderHook(
|
|
28
24
|
(userId: string) => {
|
|
29
25
|
renderCount.inc()
|
|
30
26
|
|
|
31
|
-
const [state, setState] = LiveStoreReact.useRow(
|
|
27
|
+
const [state, setState] = LiveStoreReact.useRow(AppComponentSchema, userId, { reactivityGraph })
|
|
32
28
|
return { state, setState }
|
|
33
29
|
},
|
|
34
30
|
{ wrapper, initialProps: 'u1' },
|
|
@@ -37,29 +33,37 @@ Vitest.describe('useRow', () => {
|
|
|
37
33
|
expect(result.current.state.id).toBe('u1')
|
|
38
34
|
expect(result.current.state.username).toBe('')
|
|
39
35
|
expect(renderCount.val).toBe(1)
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
|
|
37
|
+
React.act(() =>
|
|
38
|
+
store.mutate(
|
|
39
|
+
LiveStore.rawSqlMutation({
|
|
40
|
+
sql: LiveStore.sql`INSERT INTO UserInfo (id, username) VALUES ('u2', 'username_u2')`,
|
|
41
|
+
}),
|
|
42
|
+
),
|
|
43
|
+
)
|
|
42
44
|
|
|
43
45
|
rerender('u2')
|
|
44
46
|
|
|
45
|
-
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
46
47
|
expect(result.current.state.id).toBe('u2')
|
|
47
48
|
expect(result.current.state.username).toBe('username_u2')
|
|
48
49
|
expect(renderCount.val).toBe(2)
|
|
49
|
-
}),
|
|
50
|
-
)
|
|
50
|
+
}).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise))
|
|
51
51
|
|
|
52
52
|
// TODO add a test that makes sure React doesn't re-render when a setter is used to set the same value
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
it('should update the data reactively - via setState', () =>
|
|
55
55
|
Effect.gen(function* () {
|
|
56
|
-
const { wrapper,
|
|
56
|
+
const { wrapper, reactivityGraph, makeRenderCount } = yield* makeTodoMvcReact({
|
|
57
|
+
useGlobalReactivityGraph: false,
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const renderCount = makeRenderCount()
|
|
57
61
|
|
|
58
|
-
const { result } =
|
|
62
|
+
const { result } = renderHook(
|
|
59
63
|
(userId: string) => {
|
|
60
64
|
renderCount.inc()
|
|
61
65
|
|
|
62
|
-
const [state, setState] = LiveStoreReact.useRow(
|
|
66
|
+
const [state, setState] = LiveStoreReact.useRow(AppComponentSchema, userId, { reactivityGraph })
|
|
63
67
|
return { state, setState }
|
|
64
68
|
},
|
|
65
69
|
{ wrapper, initialProps: 'u1' },
|
|
@@ -69,23 +73,26 @@ Vitest.describe('useRow', () => {
|
|
|
69
73
|
expect(result.current.state.username).toBe('')
|
|
70
74
|
expect(renderCount.val).toBe(1)
|
|
71
75
|
|
|
72
|
-
|
|
76
|
+
React.act(() => result.current.setState.username('username_u1_hello'))
|
|
73
77
|
|
|
74
78
|
expect(result.current.state.id).toBe('u1')
|
|
75
79
|
expect(result.current.state.username).toBe('username_u1_hello')
|
|
76
80
|
expect(renderCount.val).toBe(2)
|
|
77
|
-
}),
|
|
78
|
-
)
|
|
81
|
+
}).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise))
|
|
79
82
|
|
|
80
|
-
|
|
83
|
+
it('should update the data reactively - via raw store mutation', () =>
|
|
81
84
|
Effect.gen(function* () {
|
|
82
|
-
const { wrapper, store,
|
|
85
|
+
const { wrapper, store, reactivityGraph, makeRenderCount } = yield* makeTodoMvcReact({
|
|
86
|
+
useGlobalReactivityGraph: false,
|
|
87
|
+
})
|
|
83
88
|
|
|
84
|
-
const
|
|
89
|
+
const renderCount = makeRenderCount()
|
|
90
|
+
|
|
91
|
+
const { result } = renderHook(
|
|
85
92
|
(userId: string) => {
|
|
86
93
|
renderCount.inc()
|
|
87
94
|
|
|
88
|
-
const [state, setState] = LiveStoreReact.useRow(
|
|
95
|
+
const [state, setState] = LiveStoreReact.useRow(AppComponentSchema, userId, { reactivityGraph })
|
|
89
96
|
return { state, setState }
|
|
90
97
|
},
|
|
91
98
|
{ wrapper, initialProps: 'u1' },
|
|
@@ -95,30 +102,36 @@ Vitest.describe('useRow', () => {
|
|
|
95
102
|
expect(result.current.state.username).toBe('')
|
|
96
103
|
expect(renderCount.val).toBe(1)
|
|
97
104
|
|
|
98
|
-
|
|
99
|
-
store.mutate(
|
|
105
|
+
React.act(() =>
|
|
106
|
+
store.mutate(
|
|
107
|
+
LiveStore.rawSqlMutation({
|
|
108
|
+
sql: LiveStore.sql`UPDATE UserInfo SET username = 'username_u1_hello' WHERE id = 'u1';`,
|
|
109
|
+
}),
|
|
110
|
+
),
|
|
100
111
|
)
|
|
101
112
|
|
|
102
113
|
expect(result.current.state.id).toBe('u1')
|
|
103
114
|
expect(result.current.state.username).toBe('username_u1_hello')
|
|
104
115
|
expect(renderCount.val).toBe(2)
|
|
105
|
-
}),
|
|
106
|
-
)
|
|
116
|
+
}).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise))
|
|
107
117
|
|
|
108
|
-
|
|
118
|
+
it('should work for a larger app', () =>
|
|
109
119
|
Effect.gen(function* () {
|
|
110
|
-
const { wrapper, store,
|
|
120
|
+
const { wrapper, store, reactivityGraph, makeRenderCount } = yield* makeTodoMvcReact({
|
|
121
|
+
useGlobalReactivityGraph: false,
|
|
122
|
+
})
|
|
111
123
|
|
|
112
124
|
const allTodos$ = LiveStore.queryDb(
|
|
113
125
|
{ query: `select * from todos`, schema: Schema.Array(tables.todos.schema) },
|
|
114
|
-
{ label: 'allTodos' },
|
|
126
|
+
{ label: 'allTodos', reactivityGraph },
|
|
115
127
|
)
|
|
116
128
|
|
|
129
|
+
const appRouterRenderCount = makeRenderCount()
|
|
117
130
|
let globalSetState: LiveStoreReact.StateSetters<typeof AppRouterSchema> | undefined
|
|
118
131
|
const AppRouter: React.FC = () => {
|
|
119
|
-
|
|
132
|
+
appRouterRenderCount.inc()
|
|
120
133
|
|
|
121
|
-
const [state, setState] = LiveStoreReact.useRow(AppRouterSchema)
|
|
134
|
+
const [state, setState] = LiveStoreReact.useRow(AppRouterSchema, { reactivityGraph })
|
|
122
135
|
|
|
123
136
|
globalSetState = setState
|
|
124
137
|
|
|
@@ -146,15 +159,15 @@ Vitest.describe('useRow', () => {
|
|
|
146
159
|
}
|
|
147
160
|
|
|
148
161
|
const TaskDetails: React.FC<{ id: string }> = ({ id }) => {
|
|
149
|
-
const [todo] = LiveStoreReact.useRow(todos, id)
|
|
162
|
+
const [todo] = LiveStoreReact.useRow(todos, id, { reactivityGraph })
|
|
150
163
|
return <div role="content">{JSON.stringify(todo)}</div>
|
|
151
164
|
}
|
|
152
165
|
|
|
153
|
-
const renderResult =
|
|
166
|
+
const renderResult = render(<AppRouter />, { wrapper })
|
|
154
167
|
|
|
155
|
-
expect(
|
|
168
|
+
expect(appRouterRenderCount.val).toBe(1)
|
|
156
169
|
|
|
157
|
-
|
|
170
|
+
React.act(() =>
|
|
158
171
|
store.mutate(
|
|
159
172
|
LiveStore.rawSqlMutation({
|
|
160
173
|
sql: LiveStore.sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)`,
|
|
@@ -162,19 +175,19 @@ Vitest.describe('useRow', () => {
|
|
|
162
175
|
),
|
|
163
176
|
)
|
|
164
177
|
|
|
165
|
-
expect(
|
|
178
|
+
expect(appRouterRenderCount.val).toBe(1)
|
|
166
179
|
expect(renderResult.getByRole('current-id').innerHTML).toMatchInlineSnapshot('"Current Task Id: -"')
|
|
167
180
|
|
|
168
|
-
|
|
181
|
+
React.act(() => globalSetState!.currentTaskId('t1'))
|
|
169
182
|
|
|
170
|
-
expect(
|
|
183
|
+
expect(appRouterRenderCount.val).toBe(2)
|
|
171
184
|
expect(renderResult.getByRole('content').innerHTML).toMatchInlineSnapshot(
|
|
172
185
|
`"{"id":"t1","text":"buy milk","completed":false}"`,
|
|
173
186
|
)
|
|
174
187
|
|
|
175
188
|
expect(renderResult.getByRole('current-id').innerHTML).toMatchInlineSnapshot('"Current Task Id: t1"')
|
|
176
189
|
|
|
177
|
-
|
|
190
|
+
React.act(() =>
|
|
178
191
|
store.mutate(
|
|
179
192
|
LiveStore.rawSqlMutation({
|
|
180
193
|
sql: LiveStore.sql`INSERT INTO todos (id, text, completed) VALUES ('t2', 'buy eggs', 0)`,
|
|
@@ -186,33 +199,37 @@ Vitest.describe('useRow', () => {
|
|
|
186
199
|
),
|
|
187
200
|
)
|
|
188
201
|
|
|
189
|
-
expect(
|
|
202
|
+
expect(appRouterRenderCount.val).toBe(3)
|
|
190
203
|
expect(renderResult.getByRole('current-id').innerHTML).toMatchInlineSnapshot('"Current Task Id: t2"')
|
|
191
|
-
}),
|
|
192
|
-
)
|
|
204
|
+
}).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise))
|
|
193
205
|
|
|
194
|
-
|
|
206
|
+
it('should work for a useRow query chained with a useTemporary query', () =>
|
|
195
207
|
Effect.gen(function* () {
|
|
196
|
-
const { store, wrapper,
|
|
208
|
+
const { store, wrapper, reactivityGraph, makeRenderCount } = yield* makeTodoMvcReact({
|
|
209
|
+
useGlobalReactivityGraph: false,
|
|
210
|
+
})
|
|
211
|
+
const renderCount = makeRenderCount()
|
|
197
212
|
|
|
198
213
|
store.mutate(
|
|
199
214
|
todos.insert({ id: 't1', text: 'buy milk', completed: false }),
|
|
200
215
|
todos.insert({ id: 't2', text: 'buy bread', completed: false }),
|
|
201
216
|
)
|
|
202
217
|
|
|
203
|
-
const { result, unmount, rerender } =
|
|
218
|
+
const { result, unmount, rerender } = renderHook(
|
|
204
219
|
(userId: string) => {
|
|
205
220
|
renderCount.inc()
|
|
206
221
|
|
|
207
|
-
const [_row, _setRow, rowState$] = LiveStoreReact.useRow(
|
|
208
|
-
const todos = LiveStoreReact.
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
222
|
+
const [_row, _setRow, rowState$] = LiveStoreReact.useRow(AppComponentSchema, userId, { reactivityGraph })
|
|
223
|
+
const todos = LiveStoreReact.useScopedQuery(
|
|
224
|
+
() =>
|
|
225
|
+
LiveStore.queryDb(
|
|
226
|
+
(get) => ({
|
|
227
|
+
query: LiveStore.sql`select * from todos where text like '%${get(rowState$).text}%'`,
|
|
228
|
+
schema: Schema.Array(tables.todos.schema),
|
|
229
|
+
}),
|
|
230
|
+
{ reactivityGraph, label: 'todosFiltered' },
|
|
231
|
+
),
|
|
232
|
+
userId,
|
|
216
233
|
)
|
|
217
234
|
|
|
218
235
|
return { todos }
|
|
@@ -220,9 +237,16 @@ Vitest.describe('useRow', () => {
|
|
|
220
237
|
{ wrapper, initialProps: 'u1' },
|
|
221
238
|
)
|
|
222
239
|
|
|
223
|
-
|
|
240
|
+
React.act(() =>
|
|
241
|
+
store.mutate(
|
|
242
|
+
LiveStore.rawSqlMutation({
|
|
243
|
+
sql: LiveStore.sql`INSERT INTO UserInfo (id, username, text) VALUES ('u2', 'username_u2', 'milk')`,
|
|
244
|
+
}),
|
|
245
|
+
),
|
|
246
|
+
)
|
|
224
247
|
|
|
225
248
|
expect(result.current.todos.length).toBe(2)
|
|
249
|
+
// expect(result.current.state.username).toBe('')
|
|
226
250
|
expect(renderCount.val).toBe(1)
|
|
227
251
|
|
|
228
252
|
rerender('u2')
|
|
@@ -231,80 +255,89 @@ Vitest.describe('useRow', () => {
|
|
|
231
255
|
expect(renderCount.val).toBe(2)
|
|
232
256
|
|
|
233
257
|
unmount()
|
|
234
|
-
}),
|
|
235
|
-
|
|
258
|
+
}).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise))
|
|
259
|
+
|
|
260
|
+
let cachedProvider: BasicTracerProvider | undefined
|
|
236
261
|
|
|
237
|
-
|
|
238
|
-
const
|
|
262
|
+
describe('otel', () => {
|
|
263
|
+
const exporter = new InMemorySpanExporter()
|
|
264
|
+
|
|
265
|
+
const provider = cachedProvider ?? new BasicTracerProvider({ spanProcessors: [new SimpleSpanProcessor(exporter)] })
|
|
266
|
+
cachedProvider = provider
|
|
239
267
|
provider.register()
|
|
240
268
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
269
|
+
const otelTracer = otel.trace.getTracer('test')
|
|
270
|
+
|
|
271
|
+
const span = otelTracer.startSpan('test-root')
|
|
272
|
+
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
245
273
|
|
|
246
|
-
|
|
247
|
-
|
|
274
|
+
it('should update the data based on component key', async () => {
|
|
275
|
+
const { strictMode } = await Effect.gen(function* () {
|
|
276
|
+
const { wrapper, store, reactivityGraph, makeRenderCount, strictMode } = yield* makeTodoMvcReact({
|
|
277
|
+
useGlobalReactivityGraph: false,
|
|
278
|
+
otelContext,
|
|
279
|
+
otelTracer,
|
|
280
|
+
})
|
|
248
281
|
|
|
249
|
-
const
|
|
282
|
+
const renderCount = makeRenderCount()
|
|
250
283
|
|
|
251
|
-
const
|
|
252
|
-
|
|
284
|
+
const { result, rerender, unmount } = renderHook(
|
|
285
|
+
(userId: string) => {
|
|
286
|
+
renderCount.inc()
|
|
253
287
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
})
|
|
288
|
+
const [state, setState] = LiveStoreReact.useRow(AppComponentSchema, userId, { reactivityGraph })
|
|
289
|
+
return { state, setState }
|
|
290
|
+
},
|
|
291
|
+
{ wrapper, initialProps: 'u1' },
|
|
292
|
+
)
|
|
260
293
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
294
|
+
expect(result.current.state.id).toBe('u1')
|
|
295
|
+
expect(result.current.state.username).toBe('')
|
|
296
|
+
expect(renderCount.val).toBe(1)
|
|
264
297
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
298
|
+
React.act(() =>
|
|
299
|
+
store.mutate(
|
|
300
|
+
LiveStore.rawSqlMutation({
|
|
301
|
+
sql: LiveStore.sql`INSERT INTO UserInfo (id, username) VALUES ('u2', 'username_u2')`,
|
|
302
|
+
}),
|
|
303
|
+
),
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
rerender('u2')
|
|
307
|
+
|
|
308
|
+
expect(result.current.state.id).toBe('u2')
|
|
309
|
+
expect(result.current.state.username).toBe('username_u2')
|
|
310
|
+
expect(renderCount.val).toBe(2)
|
|
311
|
+
|
|
312
|
+
unmount()
|
|
313
|
+
span.end()
|
|
314
|
+
|
|
315
|
+
return { strictMode }
|
|
316
|
+
}).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise)
|
|
317
|
+
|
|
318
|
+
const mapAttributes = (attributes: otel.Attributes) => {
|
|
319
|
+
return ReadonlyRecord.map(attributes, (val, key) => {
|
|
320
|
+
if (key === 'stackInfo') {
|
|
321
|
+
const stackInfo = JSON.parse(val as string) as LiveStore.StackInfo
|
|
322
|
+
// stackInfo.frames.shift() // Removes `renderHook.wrapper` from the stack
|
|
323
|
+
stackInfo.frames.forEach((_) => {
|
|
324
|
+
if (_.name.includes('renderHook.wrapper')) {
|
|
325
|
+
_.name = 'renderHook.wrapper'
|
|
326
|
+
}
|
|
327
|
+
_.filePath = '__REPLACED_FOR_SNAPSHOT__'
|
|
328
|
+
})
|
|
329
|
+
return JSON.stringify(stackInfo)
|
|
330
|
+
}
|
|
331
|
+
return val
|
|
332
|
+
})
|
|
333
|
+
}
|
|
270
334
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
rerender('u2')
|
|
280
|
-
|
|
281
|
-
expect(result.current.state.id).toBe('u2')
|
|
282
|
-
expect(result.current.state.username).toBe('username_u2')
|
|
283
|
-
expect(renderCount.val).toBe(2)
|
|
284
|
-
|
|
285
|
-
unmount()
|
|
286
|
-
span.end()
|
|
287
|
-
}).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise)
|
|
288
|
-
|
|
289
|
-
const mapAttributes = (attributes: otel.Attributes) => {
|
|
290
|
-
return ReadonlyRecord.map(attributes, (val, key) => {
|
|
291
|
-
if (key === 'firstStackInfo') {
|
|
292
|
-
const stackInfo = JSON.parse(val as string) as LiveStore.StackInfo
|
|
293
|
-
// stackInfo.frames.shift() // Removes `renderHook.wrapper` from the stack
|
|
294
|
-
stackInfo.frames.forEach((_) => {
|
|
295
|
-
if (_.name.includes('renderHook.wrapper')) {
|
|
296
|
-
_.name = 'renderHook.wrapper'
|
|
297
|
-
}
|
|
298
|
-
_.filePath = '__REPLACED_FOR_SNAPSHOT__'
|
|
299
|
-
})
|
|
300
|
-
return JSON.stringify(stackInfo)
|
|
301
|
-
}
|
|
302
|
-
return val
|
|
303
|
-
})
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot()
|
|
307
|
-
},
|
|
308
|
-
)
|
|
335
|
+
// TODO improve testing setup so "obsolete" warning is avoided
|
|
336
|
+
if (strictMode) {
|
|
337
|
+
expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot('strictMode=true')
|
|
338
|
+
} else {
|
|
339
|
+
expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot('strictMode=false')
|
|
340
|
+
}
|
|
341
|
+
})
|
|
309
342
|
})
|
|
310
343
|
})
|
package/src/useRow.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { QueryInfo, RowQuery } from '@livestore/common'
|
|
|
2
2
|
import { SessionIdSymbol } from '@livestore/common'
|
|
3
3
|
import { DbSchema } from '@livestore/common/schema'
|
|
4
4
|
import type { SqliteDsl } from '@livestore/db-schema'
|
|
5
|
-
import type { LiveQuery,
|
|
5
|
+
import type { LiveQuery, ReactivityGraph } from '@livestore/livestore'
|
|
6
6
|
import { queryDb } from '@livestore/livestore'
|
|
7
7
|
import { shouldNeverHappen } from '@livestore/utils'
|
|
8
8
|
import { ReadonlyRecord } from '@livestore/utils/effect'
|
|
@@ -10,6 +10,7 @@ import React from 'react'
|
|
|
10
10
|
|
|
11
11
|
import { useStore } from './LiveStoreContext.js'
|
|
12
12
|
import { useQueryRef } from './useQuery.js'
|
|
13
|
+
import { useMakeScopedQuery } from './useScopedQuery.js'
|
|
13
14
|
|
|
14
15
|
export type UseRowResult<TTableDef extends DbSchema.TableDefBase> = [
|
|
15
16
|
row: RowQuery.Result<TTableDef>,
|
|
@@ -17,6 +18,10 @@ export type UseRowResult<TTableDef extends DbSchema.TableDefBase> = [
|
|
|
17
18
|
query$: LiveQuery<RowQuery.Result<TTableDef>, QueryInfo>,
|
|
18
19
|
]
|
|
19
20
|
|
|
21
|
+
export type UseRowOptionsBase = {
|
|
22
|
+
reactivityGraph?: ReactivityGraph
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
/**
|
|
21
26
|
* Similar to `React.useState` but returns a tuple of `[row, setRow, query$]` for a given table where ...
|
|
22
27
|
*
|
|
@@ -27,7 +32,6 @@ export type UseRowResult<TTableDef extends DbSchema.TableDefBase> = [
|
|
|
27
32
|
* If the table is a singleton table, `useRow` can be called without an `id` argument. Otherwise, the `id` argument is required.
|
|
28
33
|
*/
|
|
29
34
|
export const useRow: {
|
|
30
|
-
// isSingleton: true
|
|
31
35
|
<
|
|
32
36
|
TTableDef extends DbSchema.TableDef<
|
|
33
37
|
DbSchema.DefaultSqliteTableDef,
|
|
@@ -35,10 +39,8 @@ export const useRow: {
|
|
|
35
39
|
>,
|
|
36
40
|
>(
|
|
37
41
|
table: TTableDef,
|
|
38
|
-
options?:
|
|
42
|
+
options?: UseRowOptionsBase,
|
|
39
43
|
): UseRowResult<TTableDef>
|
|
40
|
-
|
|
41
|
-
// isSingleton: false with requiredInsertColumnNames: 'id'
|
|
42
44
|
<
|
|
43
45
|
TTableDef extends DbSchema.TableDef<
|
|
44
46
|
DbSchema.DefaultSqliteTableDef,
|
|
@@ -52,10 +54,8 @@ export const useRow: {
|
|
|
52
54
|
table: TTableDef,
|
|
53
55
|
// TODO adjust so it works with arbitrary primary keys or unique constraints
|
|
54
56
|
id: string | SessionIdSymbol,
|
|
55
|
-
options?: Partial<RowQuery.RequiredColumnsOptions<TTableDef
|
|
57
|
+
options?: UseRowOptionsBase & Partial<RowQuery.RequiredColumnsOptions<TTableDef>>,
|
|
56
58
|
): UseRowResult<TTableDef>
|
|
57
|
-
|
|
58
|
-
// isSingleton: false
|
|
59
59
|
<
|
|
60
60
|
TTableDef extends DbSchema.TableDef<
|
|
61
61
|
DbSchema.DefaultSqliteTableDef,
|
|
@@ -65,7 +65,7 @@ export const useRow: {
|
|
|
65
65
|
table: TTableDef,
|
|
66
66
|
// TODO adjust so it works with arbitrary primary keys or unique constraints
|
|
67
67
|
id: string | SessionIdSymbol,
|
|
68
|
-
options: RowQuery.RequiredColumnsOptions<TTableDef
|
|
68
|
+
options: UseRowOptionsBase & RowQuery.RequiredColumnsOptions<TTableDef>,
|
|
69
69
|
): UseRowResult<TTableDef>
|
|
70
70
|
} = <
|
|
71
71
|
TTableDef extends DbSchema.TableDef<
|
|
@@ -74,14 +74,14 @@ export const useRow: {
|
|
|
74
74
|
>,
|
|
75
75
|
>(
|
|
76
76
|
table: TTableDef,
|
|
77
|
-
idOrOptions?: string | SessionIdSymbol |
|
|
78
|
-
options_?: Partial<RowQuery.RequiredColumnsOptions<TTableDef
|
|
77
|
+
idOrOptions?: string | SessionIdSymbol | UseRowOptionsBase,
|
|
78
|
+
options_?: UseRowOptionsBase & Partial<RowQuery.RequiredColumnsOptions<TTableDef>>,
|
|
79
79
|
): UseRowResult<TTableDef> => {
|
|
80
80
|
const sqliteTableDef = table.sqliteDef
|
|
81
81
|
const id = typeof idOrOptions === 'string' || idOrOptions === SessionIdSymbol ? idOrOptions : undefined
|
|
82
|
-
const options: (Partial<RowQuery.RequiredColumnsOptions<TTableDef>>
|
|
82
|
+
const options: (UseRowOptionsBase & Partial<RowQuery.RequiredColumnsOptions<TTableDef>>) | undefined =
|
|
83
83
|
typeof idOrOptions === 'string' || idOrOptions === SessionIdSymbol ? options_ : idOrOptions
|
|
84
|
-
const { insertValues } = options ?? {}
|
|
84
|
+
const { insertValues, reactivityGraph } = options ?? {}
|
|
85
85
|
|
|
86
86
|
type TComponentState = SqliteDsl.FromColumns.RowDecoded<TTableDef['sqliteDef']['columns']>
|
|
87
87
|
|
|
@@ -91,7 +91,7 @@ export const useRow: {
|
|
|
91
91
|
shouldNeverHappen(`useRow called on table "${tableName}" which does not have 'deriveMutations: true' set`)
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
const { store } = useStore(
|
|
94
|
+
const { store } = useStore()
|
|
95
95
|
|
|
96
96
|
if (
|
|
97
97
|
store.schema.tables.has(table.sqliteDef.name) === false &&
|
|
@@ -105,25 +105,28 @@ export const useRow: {
|
|
|
105
105
|
const idStr = id === SessionIdSymbol ? 'session' : id
|
|
106
106
|
const rowQuery = table.query.row as any
|
|
107
107
|
|
|
108
|
-
type
|
|
109
|
-
const
|
|
110
|
-
() =>
|
|
108
|
+
type Query$ = LiveQuery<RowQuery.Result<TTableDef>, QueryInfo.Row>
|
|
109
|
+
const { query$, otelContext } = useMakeScopedQuery(
|
|
110
|
+
(otelContext) =>
|
|
111
111
|
DbSchema.tableIsSingleton(table)
|
|
112
|
-
? queryDb(rowQuery(), {})
|
|
113
|
-
: queryDb(rowQuery(id!, { insertValues: insertValues! }), {
|
|
114
|
-
[
|
|
112
|
+
? (queryDb(rowQuery(), { reactivityGraph, otelContext }) as any as Query$)
|
|
113
|
+
: (queryDb(rowQuery(id!, { insertValues: insertValues! }), { reactivityGraph, otelContext }) as any as Query$),
|
|
114
|
+
[idStr!, tableName],
|
|
115
|
+
{
|
|
116
|
+
otel: {
|
|
117
|
+
spanName: `LiveStore:useRow:${tableName}${idStr === undefined ? '' : `:${idStr}`}`,
|
|
118
|
+
attributes: { id: idStr },
|
|
119
|
+
},
|
|
120
|
+
},
|
|
115
121
|
)
|
|
116
122
|
|
|
117
|
-
const
|
|
118
|
-
otelSpanName: `LiveStore:useRow:${tableName}${idStr === undefined ? '' : `:${idStr}`}`,
|
|
119
|
-
store: options?.store,
|
|
120
|
-
})
|
|
123
|
+
const query$Ref = useQueryRef(query$, otelContext) as React.MutableRefObject<RowQuery.Result<TTableDef>>
|
|
121
124
|
|
|
122
125
|
const setState = React.useMemo<StateSetters<TTableDef>>(() => {
|
|
123
126
|
if (table.options.isSingleColumn) {
|
|
124
127
|
return (newValueOrFn: RowQuery.Result<TTableDef>) => {
|
|
125
|
-
const newValue = typeof newValueOrFn === 'function' ? newValueOrFn(
|
|
126
|
-
if (
|
|
128
|
+
const newValue = typeof newValueOrFn === 'function' ? newValueOrFn(query$Ref.current) : newValueOrFn
|
|
129
|
+
if (query$Ref.current === newValue) return
|
|
127
130
|
|
|
128
131
|
// NOTE we need to account for the short-hand syntax for single-column+singleton tables
|
|
129
132
|
if (table.options.isSingleton) {
|
|
@@ -138,11 +141,11 @@ export const useRow: {
|
|
|
138
141
|
ReadonlyRecord.map(sqliteTableDef.columns, (column, columnName) => (newValueOrFn: any) => {
|
|
139
142
|
const newValue =
|
|
140
143
|
// @ts-expect-error TODO fix typing
|
|
141
|
-
typeof newValueOrFn === 'function' ? newValueOrFn(
|
|
144
|
+
typeof newValueOrFn === 'function' ? newValueOrFn(query$Ref.current[columnName]) : newValueOrFn
|
|
142
145
|
|
|
143
146
|
// Don't update the state if it's the same as the value already seen in the component
|
|
144
147
|
// @ts-expect-error TODO fix typing
|
|
145
|
-
if (
|
|
148
|
+
if (query$Ref.current[columnName] === newValue) return
|
|
146
149
|
|
|
147
150
|
store.mutate(table.update({ where: { id: id ?? 'singleton' }, values: { [columnName]: newValue } }))
|
|
148
151
|
// store.mutate(updateMutationForQueryInfo(query$.queryInfo!, { [columnName]: newValue }))
|
|
@@ -151,13 +154,13 @@ export const useRow: {
|
|
|
151
154
|
setState.setMany = (columnValuesOrFn: Partial<TComponentState>) => {
|
|
152
155
|
const columnValues =
|
|
153
156
|
// @ts-expect-error TODO fix typing
|
|
154
|
-
typeof columnValuesOrFn === 'function' ? columnValuesOrFn(
|
|
157
|
+
typeof columnValuesOrFn === 'function' ? columnValuesOrFn(query$Ref.current) : columnValuesOrFn
|
|
155
158
|
|
|
156
159
|
// TODO use hashing instead
|
|
157
160
|
// Don't update the state if it's the same as the value already seen in the component
|
|
158
161
|
if (
|
|
159
162
|
// @ts-expect-error TODO fix typing
|
|
160
|
-
Object.entries(columnValues).every(([columnName, value]) =>
|
|
163
|
+
Object.entries(columnValues).every(([columnName, value]) => query$Ref.current[columnName] === value)
|
|
161
164
|
) {
|
|
162
165
|
return
|
|
163
166
|
}
|
|
@@ -168,9 +171,9 @@ export const useRow: {
|
|
|
168
171
|
|
|
169
172
|
return setState as any
|
|
170
173
|
}
|
|
171
|
-
}, [id,
|
|
174
|
+
}, [id, query$Ref, sqliteTableDef.columns, store, table])
|
|
172
175
|
|
|
173
|
-
return [
|
|
176
|
+
return [query$Ref.current, setState, query$]
|
|
174
177
|
}
|
|
175
178
|
|
|
176
179
|
export type Dispatch<A> = (action: A) => void
|