@livestore/react 0.0.0-snapshot-d9d66b354a9f4cfae987725d38971992ff14e4ad → 0.0.0-snapshot-1d99fea7d2ce2c7a5d9ed0a3752f8a7bda6bc3db

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