@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.
Files changed (80) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/LiveStoreContext.d.ts +3 -5
  3. package/dist/LiveStoreContext.d.ts.map +1 -1
  4. package/dist/LiveStoreContext.js +3 -7
  5. package/dist/LiveStoreContext.js.map +1 -1
  6. package/dist/LiveStoreProvider.d.ts +1 -2
  7. package/dist/LiveStoreProvider.d.ts.map +1 -1
  8. package/dist/LiveStoreProvider.js +20 -5
  9. package/dist/LiveStoreProvider.js.map +1 -1
  10. package/dist/__tests__/fixture.d.ts +9 -6
  11. package/dist/__tests__/fixture.d.ts.map +1 -1
  12. package/dist/__tests__/fixture.js +7 -6
  13. package/dist/__tests__/fixture.js.map +1 -1
  14. package/dist/experimental/components/LiveList.d.ts +2 -2
  15. package/dist/experimental/components/LiveList.d.ts.map +1 -1
  16. package/dist/experimental/components/LiveList.js +4 -5
  17. package/dist/experimental/components/LiveList.js.map +1 -1
  18. package/dist/mod.d.ts +1 -0
  19. package/dist/mod.d.ts.map +1 -1
  20. package/dist/mod.js +1 -0
  21. package/dist/mod.js.map +1 -1
  22. package/dist/useAtom.d.ts +2 -4
  23. package/dist/useAtom.d.ts.map +1 -1
  24. package/dist/useAtom.js +28 -32
  25. package/dist/useAtom.js.map +1 -1
  26. package/dist/useQuery.d.ts +3 -26
  27. package/dist/useQuery.d.ts.map +1 -1
  28. package/dist/useQuery.js +45 -60
  29. package/dist/useQuery.js.map +1 -1
  30. package/dist/useQuery.test.js +16 -70
  31. package/dist/useQuery.test.js.map +1 -1
  32. package/dist/useRow.d.ts +7 -10
  33. package/dist/useRow.d.ts.map +1 -1
  34. package/dist/useRow.js +19 -16
  35. package/dist/useRow.js.map +1 -1
  36. package/dist/useRow.test.js +96 -74
  37. package/dist/useRow.test.js.map +1 -1
  38. package/dist/useScopedQuery.d.ts +4 -10
  39. package/dist/useScopedQuery.d.ts.map +1 -1
  40. package/dist/useScopedQuery.js +52 -97
  41. package/dist/useScopedQuery.js.map +1 -1
  42. package/dist/useScopedQuery.test.js +12 -13
  43. package/dist/useScopedQuery.test.js.map +1 -1
  44. package/dist/utils/useStateRefWithReactiveInput.d.ts +1 -1
  45. package/dist/utils/useStateRefWithReactiveInput.d.ts.map +1 -1
  46. package/dist/utils/useStateRefWithReactiveInput.js.map +1 -1
  47. package/package.json +17 -18
  48. package/src/LiveStoreContext.ts +6 -10
  49. package/src/LiveStoreProvider.tsx +21 -7
  50. package/src/__snapshots__/useRow.test.tsx.snap +149 -337
  51. package/src/__tests__/fixture.tsx +10 -7
  52. package/src/experimental/components/LiveList.tsx +7 -8
  53. package/src/mod.ts +1 -0
  54. package/src/useAtom.ts +11 -22
  55. package/src/useQuery.test.tsx +67 -165
  56. package/src/useQuery.ts +54 -84
  57. package/src/useRow.test.tsx +163 -130
  58. package/src/useRow.ts +35 -32
  59. package/src/useScopedQuery.test.tsx +96 -0
  60. package/src/useScopedQuery.ts +142 -0
  61. package/src/utils/useStateRefWithReactiveInput.ts +1 -1
  62. package/dist/useRcRef.d.ts +0 -72
  63. package/dist/useRcRef.d.ts.map +0 -1
  64. package/dist/useRcRef.js +0 -146
  65. package/dist/useRcRef.js.map +0 -1
  66. package/dist/useRcRef.test.d.ts +0 -2
  67. package/dist/useRcRef.test.d.ts.map +0 -1
  68. package/dist/useRcRef.test.js +0 -128
  69. package/dist/useRcRef.test.js.map +0 -1
  70. package/dist/useRcResource.d.ts +0 -76
  71. package/dist/useRcResource.d.ts.map +0 -1
  72. package/dist/useRcResource.js +0 -150
  73. package/dist/useRcResource.js.map +0 -1
  74. package/dist/useRcResource.test.d.ts +0 -2
  75. package/dist/useRcResource.test.d.ts.map +0 -1
  76. package/dist/useRcResource.test.js +0 -122
  77. package/dist/useRcResource.test.js.map +0 -1
  78. package/src/__snapshots__/useQuery.test.tsx.snap +0 -2011
  79. package/src/useRcResource.test.tsx +0 -167
  80. package/src/useRcResource.ts +0 -180
@@ -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 * as ReactTesting from '@testing-library/react'
6
+ import { render, renderHook } from '@testing-library/react'
8
7
  import React from 'react'
9
- import { beforeEach, expect, it } from 'vitest'
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
- Vitest.describe('useRow', () => {
19
- beforeEach(() => {
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, renderCount } = yield* makeTodoMvcReact({})
17
+ const { wrapper, store, reactivityGraph, makeRenderCount } = yield* makeTodoMvcReact({
18
+ useGlobalReactivityGraph: false,
19
+ })
26
20
 
27
- const { result, rerender } = ReactTesting.renderHook(
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(tables.userInfo, userId)
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
- expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
41
- store.mutate(tables.userInfo.insert({ id: 'u2', username: 'username_u2' }))
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
- Vitest.scopedLive('should update the data reactively - via setState', () =>
54
+ it('should update the data reactively - via setState', () =>
55
55
  Effect.gen(function* () {
56
- const { wrapper, renderCount } = yield* makeTodoMvcReact({})
56
+ const { wrapper, reactivityGraph, makeRenderCount } = yield* makeTodoMvcReact({
57
+ useGlobalReactivityGraph: false,
58
+ })
59
+
60
+ const renderCount = makeRenderCount()
57
61
 
58
- const { result } = ReactTesting.renderHook(
62
+ const { result } = renderHook(
59
63
  (userId: string) => {
60
64
  renderCount.inc()
61
65
 
62
- const [state, setState] = LiveStoreReact.useRow(tables.userInfo, userId)
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
- ReactTesting.act(() => result.current.setState.username('username_u1_hello'))
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
- Vitest.scopedLive('should update the data reactively - via raw store mutation', () =>
83
+ it('should update the data reactively - via raw store mutation', () =>
81
84
  Effect.gen(function* () {
82
- const { wrapper, store, renderCount } = yield* makeTodoMvcReact({})
85
+ const { wrapper, store, reactivityGraph, makeRenderCount } = yield* makeTodoMvcReact({
86
+ useGlobalReactivityGraph: false,
87
+ })
83
88
 
84
- const { result } = ReactTesting.renderHook(
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(tables.userInfo, userId)
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
- ReactTesting.act(() =>
99
- store.mutate(tables.userInfo.update({ where: { id: 'u1' }, values: { username: 'username_u1_hello' } })),
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
- Vitest.scopedLive('should work for a larger app', () =>
118
+ it('should work for a larger app', () =>
109
119
  Effect.gen(function* () {
110
- const { wrapper, store, renderCount } = yield* makeTodoMvcReact({})
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
- renderCount.inc()
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 = ReactTesting.render(<AppRouter />, { wrapper })
166
+ const renderResult = render(<AppRouter />, { wrapper })
154
167
 
155
- expect(renderCount.val).toBe(1)
168
+ expect(appRouterRenderCount.val).toBe(1)
156
169
 
157
- ReactTesting.act(() =>
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(renderCount.val).toBe(1)
178
+ expect(appRouterRenderCount.val).toBe(1)
166
179
  expect(renderResult.getByRole('current-id').innerHTML).toMatchInlineSnapshot('"Current Task Id: -"')
167
180
 
168
- ReactTesting.act(() => globalSetState!.currentTaskId('t1'))
181
+ React.act(() => globalSetState!.currentTaskId('t1'))
169
182
 
170
- expect(renderCount.val).toBe(2)
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
- ReactTesting.act(() =>
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(renderCount.val).toBe(3)
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
- Vitest.scopedLive('should work for a useRow query chained with a useTemporary query', () =>
206
+ it('should work for a useRow query chained with a useTemporary query', () =>
195
207
  Effect.gen(function* () {
196
- const { store, wrapper, renderCount } = yield* makeTodoMvcReact({})
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 } = ReactTesting.renderHook(
218
+ const { result, unmount, rerender } = renderHook(
204
219
  (userId: string) => {
205
220
  renderCount.inc()
206
221
 
207
- const [_row, _setRow, rowState$] = LiveStoreReact.useRow(tables.userInfo, userId)
208
- const todos = LiveStoreReact.useQuery(
209
- LiveStore.queryDb(
210
- (get) => tables.todos.query.where('text', 'LIKE', `%${get(rowState$).text}%`),
211
- // TODO find a way where explicit `userId` is not needed here
212
- // possibly by automatically understanding the `get(rowState$)` dependency
213
- { label: 'todosFiltered', deps: userId },
214
- ),
215
- // TODO introduce a `deps` array which is only needed when a query is parametric
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
- ReactTesting.act(() => store.mutate(tables.userInfo.insert({ id: 'u2', username: 'username_u2', text: 'milk' })))
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
- Vitest.describe('otel', () => {
238
- const provider = new BasicTracerProvider({})
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
- it.each([{ strictMode: true }, { strictMode: false }])(
242
- 'should update the data based on component key strictMode=%s',
243
- async ({ strictMode }) => {
244
- const exporter = new InMemorySpanExporter()
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
- // const provider = cachedProvider ?? new BasicTracerProvider({ spanProcessors: [new SimpleSpanProcessor(exporter)] })
247
- provider.addSpanProcessor(new SimpleSpanProcessor(exporter))
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 otelTracer = otel.trace.getTracer(`testing-${strictMode ? 'strict' : 'non-strict'}`)
282
+ const renderCount = makeRenderCount()
250
283
 
251
- const span = otelTracer.startSpan('test-root')
252
- const otelContext = otel.trace.setSpan(otel.context.active(), span)
284
+ const { result, rerender, unmount } = renderHook(
285
+ (userId: string) => {
286
+ renderCount.inc()
253
287
 
254
- await Effect.gen(function* () {
255
- const { wrapper, store, renderCount } = yield* makeTodoMvcReact({
256
- otelContext,
257
- otelTracer,
258
- strictMode,
259
- })
288
+ const [state, setState] = LiveStoreReact.useRow(AppComponentSchema, userId, { reactivityGraph })
289
+ return { state, setState }
290
+ },
291
+ { wrapper, initialProps: 'u1' },
292
+ )
260
293
 
261
- const { result, rerender, unmount } = ReactTesting.renderHook(
262
- (userId: string) => {
263
- renderCount.inc()
294
+ expect(result.current.state.id).toBe('u1')
295
+ expect(result.current.state.username).toBe('')
296
+ expect(renderCount.val).toBe(1)
264
297
 
265
- const [state, setState] = LiveStoreReact.useRow(tables.userInfo, userId)
266
- return { state, setState }
267
- },
268
- { wrapper, initialProps: 'u1' },
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
- expect(result.current.state.id).toBe('u1')
272
- expect(result.current.state.username).toBe('')
273
- expect(renderCount.val).toBe(1)
274
-
275
- // For u2 we'll make sure that the row already exists,
276
- // so the lazy `insert` will be skipped
277
- ReactTesting.act(() => store.mutate(tables.userInfo.insert({ id: 'u2', username: 'username_u2' })))
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, LiveQueryDef, Store } from '@livestore/livestore'
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?: { store?: Store },
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>> & { store?: Store },
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> & { store?: Store },
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 | { store?: Store },
78
- options_?: Partial<RowQuery.RequiredColumnsOptions<TTableDef>> & { store?: Store },
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>> & { store?: Store }) | undefined =
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({ store: options?.store })
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 QueryDef = LiveQueryDef<RowQuery.Result<TTableDef>, QueryInfo.Row>
109
- const queryDef: QueryDef = React.useMemo(
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! }), { deps: idStr! }),
114
- [id, insertValues, rowQuery, table, idStr],
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 queryRef = useQueryRef(queryDef, {
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(queryRef.valueRef.current) : newValueOrFn
126
- if (queryRef.valueRef.current === newValue) return
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(queryRef.valueRef.current[columnName]) : 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 (queryRef.valueRef.current[columnName] === newValue) return
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(queryRef.valueRef.current) : 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]) => queryRef.valueRef.current[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, queryRef.valueRef, sqliteTableDef.columns, store, table])
174
+ }, [id, query$Ref, sqliteTableDef.columns, store, table])
172
175
 
173
- return [queryRef.valueRef.current, setState, queryRef.queryRcRef.value]
176
+ return [query$Ref.current, setState, query$]
174
177
  }
175
178
 
176
179
  export type Dispatch<A> = (action: A) => void