@livestore/react 0.4.0-dev.2 → 0.4.0-dev.21

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 (96) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/LiveStoreContext.d.ts +27 -0
  3. package/dist/LiveStoreContext.d.ts.map +1 -1
  4. package/dist/LiveStoreContext.js +18 -0
  5. package/dist/LiveStoreContext.js.map +1 -1
  6. package/dist/LiveStoreProvider.d.ts +14 -8
  7. package/dist/LiveStoreProvider.d.ts.map +1 -1
  8. package/dist/LiveStoreProvider.js +40 -24
  9. package/dist/LiveStoreProvider.js.map +1 -1
  10. package/dist/LiveStoreProvider.test.js +7 -7
  11. package/dist/LiveStoreProvider.test.js.map +1 -1
  12. package/dist/__tests__/fixture.d.ts +34 -12
  13. package/dist/__tests__/fixture.d.ts.map +1 -1
  14. package/dist/__tests__/fixture.js +13 -5
  15. package/dist/__tests__/fixture.js.map +1 -1
  16. package/dist/experimental/components/LiveList.js +1 -1
  17. package/dist/experimental/mod.d.ts +1 -0
  18. package/dist/experimental/mod.d.ts.map +1 -1
  19. package/dist/experimental/mod.js +1 -0
  20. package/dist/experimental/mod.js.map +1 -1
  21. package/dist/experimental/multi-store/StoreRegistry.d.ts +105 -0
  22. package/dist/experimental/multi-store/StoreRegistry.d.ts.map +1 -0
  23. package/dist/experimental/multi-store/StoreRegistry.js +184 -0
  24. package/dist/experimental/multi-store/StoreRegistry.js.map +1 -0
  25. package/dist/experimental/multi-store/StoreRegistry.test.d.ts +2 -0
  26. package/dist/experimental/multi-store/StoreRegistry.test.d.ts.map +1 -0
  27. package/dist/experimental/multi-store/StoreRegistry.test.js +381 -0
  28. package/dist/experimental/multi-store/StoreRegistry.test.js.map +1 -0
  29. package/dist/experimental/multi-store/StoreRegistryContext.d.ts +10 -0
  30. package/dist/experimental/multi-store/StoreRegistryContext.d.ts.map +1 -0
  31. package/dist/experimental/multi-store/StoreRegistryContext.js +15 -0
  32. package/dist/experimental/multi-store/StoreRegistryContext.js.map +1 -0
  33. package/dist/experimental/multi-store/mod.d.ts +6 -0
  34. package/dist/experimental/multi-store/mod.d.ts.map +1 -0
  35. package/dist/experimental/multi-store/mod.js +6 -0
  36. package/dist/experimental/multi-store/mod.js.map +1 -0
  37. package/dist/experimental/multi-store/storeOptions.d.ts +4 -0
  38. package/dist/experimental/multi-store/storeOptions.d.ts.map +1 -0
  39. package/dist/experimental/multi-store/storeOptions.js +4 -0
  40. package/dist/experimental/multi-store/storeOptions.js.map +1 -0
  41. package/dist/experimental/multi-store/types.d.ts +25 -0
  42. package/dist/experimental/multi-store/types.d.ts.map +1 -0
  43. package/dist/experimental/multi-store/types.js +2 -0
  44. package/dist/experimental/multi-store/types.js.map +1 -0
  45. package/dist/experimental/multi-store/useStore.d.ts +11 -0
  46. package/dist/experimental/multi-store/useStore.d.ts.map +1 -0
  47. package/dist/experimental/multi-store/useStore.js +16 -0
  48. package/dist/experimental/multi-store/useStore.js.map +1 -0
  49. package/dist/experimental/multi-store/useStore.test.d.ts +2 -0
  50. package/dist/experimental/multi-store/useStore.test.d.ts.map +1 -0
  51. package/dist/experimental/multi-store/useStore.test.js +198 -0
  52. package/dist/experimental/multi-store/useStore.test.js.map +1 -0
  53. package/dist/mod.d.ts +1 -1
  54. package/dist/mod.d.ts.map +1 -1
  55. package/dist/mod.js.map +1 -1
  56. package/dist/useClientDocument.d.ts +43 -13
  57. package/dist/useClientDocument.d.ts.map +1 -1
  58. package/dist/useClientDocument.js +4 -5
  59. package/dist/useClientDocument.js.map +1 -1
  60. package/dist/useClientDocument.test.js +29 -7
  61. package/dist/useClientDocument.test.js.map +1 -1
  62. package/dist/useQuery.d.ts +28 -6
  63. package/dist/useQuery.d.ts.map +1 -1
  64. package/dist/useQuery.js +63 -18
  65. package/dist/useQuery.js.map +1 -1
  66. package/dist/useQuery.test.js +35 -11
  67. package/dist/useQuery.test.js.map +1 -1
  68. package/dist/useRcResource.test.js +1 -1
  69. package/dist/useStore.d.ts +53 -1
  70. package/dist/useStore.d.ts.map +1 -1
  71. package/dist/useStore.js +52 -1
  72. package/dist/useStore.js.map +1 -1
  73. package/package.json +14 -14
  74. package/src/LiveStoreContext.ts +27 -0
  75. package/src/LiveStoreProvider.test.tsx +7 -7
  76. package/src/LiveStoreProvider.tsx +67 -45
  77. package/src/__snapshots__/useClientDocument.test.tsx.snap +208 -100
  78. package/src/__snapshots__/useQuery.test.tsx.snap +400 -128
  79. package/src/__tests__/fixture.tsx +23 -24
  80. package/src/experimental/components/LiveList.tsx +1 -1
  81. package/src/experimental/mod.ts +1 -0
  82. package/src/experimental/multi-store/StoreRegistry.test.ts +518 -0
  83. package/src/experimental/multi-store/StoreRegistry.ts +253 -0
  84. package/src/experimental/multi-store/StoreRegistryContext.tsx +23 -0
  85. package/src/experimental/multi-store/mod.ts +5 -0
  86. package/src/experimental/multi-store/storeOptions.ts +8 -0
  87. package/src/experimental/multi-store/types.ts +37 -0
  88. package/src/experimental/multi-store/useStore.test.tsx +269 -0
  89. package/src/experimental/multi-store/useStore.ts +26 -0
  90. package/src/mod.ts +2 -1
  91. package/src/useClientDocument.test.tsx +105 -75
  92. package/src/useClientDocument.ts +58 -13
  93. package/src/useQuery.test.tsx +62 -11
  94. package/src/useQuery.ts +98 -27
  95. package/src/useRcResource.test.tsx +1 -1
  96. package/src/useStore.ts +55 -3
@@ -1,7 +1,8 @@
1
1
  /** biome-ignore-all lint/a11y/useValidAriaRole: not needed for testing */
2
2
  /** biome-ignore-all lint/a11y/noStaticElementInteractions: not needed for testing */
3
3
  import * as LiveStore from '@livestore/livestore'
4
- import { getSimplifiedRootSpan } from '@livestore/livestore/internal/testing-utils'
4
+ import { StoreInternalsSymbol } from '@livestore/livestore'
5
+ import { getAllSimplifiedRootSpans, getSimplifiedRootSpan } from '@livestore/livestore/internal/testing-utils'
5
6
  import { Effect, ReadonlyRecord, Schema } from '@livestore/utils/effect'
6
7
  import { Vitest } from '@livestore/utils-dev/node-vitest'
7
8
  import * as otel from '@opentelemetry/api'
@@ -10,9 +11,9 @@ import * as ReactTesting from '@testing-library/react'
10
11
  import type React from 'react'
11
12
  import { beforeEach, expect, it } from 'vitest'
12
13
 
13
- import { events, makeTodoMvcReact, tables } from './__tests__/fixture.js'
14
- import type * as LiveStoreReact from './mod.js'
15
- import { __resetUseRcResourceCache } from './useRcResource.js'
14
+ import { events, makeTodoMvcReact, tables } from './__tests__/fixture.tsx'
15
+ import type * as LiveStoreReact from './mod.ts'
16
+ import { __resetUseRcResourceCache } from './useRcResource.ts'
16
17
 
17
18
  // const strictMode = process.env.REACT_STRICT_MODE !== undefined
18
19
 
@@ -39,12 +40,12 @@ Vitest.describe('useClientDocument', () => {
39
40
  expect(result.current.id).toBe('u1')
40
41
  expect(result.current.state.username).toBe('')
41
42
  expect(renderCount.val).toBe(1)
42
- expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
43
+ expect(store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
43
44
  store.commit(tables.userInfo.set({ username: 'username_u2' }, 'u2'))
44
45
 
45
46
  rerender('u2')
46
47
 
47
- expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
48
+ expect(store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
48
49
  expect(result.current.id).toBe('u2')
49
50
  expect(result.current.state.username).toBe('username_u2')
50
51
  expect(renderCount.val).toBe(2)
@@ -224,81 +225,110 @@ Vitest.describe('useClientDocument', () => {
224
225
  }),
225
226
  )
226
227
 
227
- Vitest.describe('otel', () => {
228
- it.each([{ strictMode: true }, { strictMode: false }])(
229
- 'should update the data based on component key strictMode=%s',
230
- async ({ strictMode }) => {
231
- const exporter = new InMemorySpanExporter()
228
+ Vitest.scopedLive('kv client document overwrites value (Schema.Any, no partial merge)', () =>
229
+ Effect.gen(function* () {
230
+ const { wrapper, store, renderCount } = yield* makeTodoMvcReact({})
232
231
 
233
- const provider = new BasicTracerProvider({
234
- spanProcessors: [new SimpleSpanProcessor(exporter)],
235
- })
232
+ const { result } = ReactTesting.renderHook(
233
+ (id: string) => {
234
+ renderCount.inc()
235
+
236
+ const [state, setState] = store.useClientDocument(tables.kv, id)
237
+ return { state, setState, id }
238
+ },
239
+ { wrapper, initialProps: 'k1' },
240
+ )
241
+
242
+ expect(result.current.id).toBe('k1')
243
+ expect(result.current.state).toBe(null)
244
+ expect(renderCount.val).toBe(1)
236
245
 
237
- const otelTracer = provider.getTracer(`testing-${strictMode ? 'strict' : 'non-strict'}`)
246
+ ReactTesting.act(() => result.current.setState(1))
247
+ expect(result.current.state).toEqual(1)
248
+ expect(renderCount.val).toBe(2)
249
+
250
+ ReactTesting.act(() => result.current.setState({ b: 2 }))
251
+ expect(result.current.state).toEqual({ b: 2 })
252
+ expect(renderCount.val).toBe(3)
253
+ }),
254
+ )
255
+
256
+ Vitest.describe('otel', () => {
257
+ it.each([
258
+ { strictMode: true },
259
+ { strictMode: false },
260
+ ])('should update the data based on component key strictMode=%s', async ({ strictMode }) => {
261
+ const exporter = new InMemorySpanExporter()
262
+
263
+ const provider = new BasicTracerProvider({
264
+ spanProcessors: [new SimpleSpanProcessor(exporter)],
265
+ })
266
+
267
+ const otelTracer = provider.getTracer(`testing-${strictMode ? 'strict' : 'non-strict'}`)
268
+
269
+ const span = otelTracer.startSpan('test-root')
270
+ const otelContext = otel.trace.setSpan(otel.context.active(), span)
271
+
272
+ await Effect.gen(function* () {
273
+ const { wrapper, store, renderCount } = yield* makeTodoMvcReact({
274
+ otelContext,
275
+ otelTracer,
276
+ strictMode,
277
+ })
238
278
 
239
- const span = otelTracer.startSpan('test-root')
240
- const otelContext = otel.trace.setSpan(otel.context.active(), span)
279
+ const { result, rerender, unmount } = ReactTesting.renderHook(
280
+ (userId: string) => {
281
+ renderCount.inc()
241
282
 
242
- await Effect.gen(function* () {
243
- const { wrapper, store, renderCount } = yield* makeTodoMvcReact({
244
- otelContext,
245
- otelTracer,
246
- strictMode,
247
- })
283
+ const [state, setState, id] = store.useClientDocument(tables.userInfo, userId)
284
+ return { state, setState, id }
285
+ },
286
+ { wrapper, initialProps: 'u1' },
287
+ )
248
288
 
249
- const { result, rerender, unmount } = ReactTesting.renderHook(
250
- (userId: string) => {
251
- renderCount.inc()
289
+ expect(result.current.id).toBe('u1')
290
+ expect(result.current.state.username).toBe('')
291
+ expect(renderCount.val).toBe(1)
292
+
293
+ // For u2 we'll make sure that the row already exists,
294
+ // so the lazy `insert` will be skipped
295
+ ReactTesting.act(() => store.commit(events.UserInfoSet({ username: 'username_u2' }, 'u2')))
296
+
297
+ rerender('u2')
298
+
299
+ expect(result.current.id).toBe('u2')
300
+ expect(result.current.state.username).toBe('username_u2')
301
+ expect(renderCount.val).toBe(2)
302
+
303
+ unmount()
304
+ span.end()
305
+ }).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise)
306
+
307
+ await provider.forceFlush()
308
+
309
+ const mapAttributes = (attributes: otel.Attributes) => {
310
+ return ReadonlyRecord.map(attributes, (val, key) => {
311
+ if (key === 'code.stacktrace') {
312
+ return '<STACKTRACE>'
313
+ } else if (key === 'firstStackInfo') {
314
+ const stackInfo = JSON.parse(val as string) as LiveStore.StackInfo
315
+ // stackInfo.frames.shift() // Removes `renderHook.wrapper` from the stack
316
+ stackInfo.frames.forEach((_) => {
317
+ if (_.name.includes('renderHook.wrapper')) {
318
+ _.name = 'renderHook.wrapper'
319
+ }
320
+ _.filePath = '__REPLACED_FOR_SNAPSHOT__'
321
+ })
322
+ return JSON.stringify(stackInfo)
323
+ }
324
+ return val
325
+ })
326
+ }
252
327
 
253
- const [state, setState, id] = store.useClientDocument(tables.userInfo, userId)
254
- return { state, setState, id }
255
- },
256
- { wrapper, initialProps: 'u1' },
257
- )
328
+ expect(getSimplifiedRootSpan(exporter, 'createStore', mapAttributes)).toMatchSnapshot()
329
+ expect(getAllSimplifiedRootSpans(exporter, 'LiveStore:commit', mapAttributes)).toMatchSnapshot()
258
330
 
259
- expect(result.current.id).toBe('u1')
260
- expect(result.current.state.username).toBe('')
261
- expect(renderCount.val).toBe(1)
262
-
263
- // For u2 we'll make sure that the row already exists,
264
- // so the lazy `insert` will be skipped
265
- ReactTesting.act(() => store.commit(events.UserInfoSet({ username: 'username_u2' }, 'u2')))
266
-
267
- rerender('u2')
268
-
269
- expect(result.current.id).toBe('u2')
270
- expect(result.current.state.username).toBe('username_u2')
271
- expect(renderCount.val).toBe(2)
272
-
273
- unmount()
274
- span.end()
275
- }).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise)
276
-
277
- await provider.forceFlush()
278
-
279
- const mapAttributes = (attributes: otel.Attributes) => {
280
- return ReadonlyRecord.map(attributes, (val, key) => {
281
- if (key === 'code.stacktrace') {
282
- return '<STACKTRACE>'
283
- } else if (key === 'firstStackInfo') {
284
- const stackInfo = JSON.parse(val as string) as LiveStore.StackInfo
285
- // stackInfo.frames.shift() // Removes `renderHook.wrapper` from the stack
286
- stackInfo.frames.forEach((_) => {
287
- if (_.name.includes('renderHook.wrapper')) {
288
- _.name = 'renderHook.wrapper'
289
- }
290
- _.filePath = '__REPLACED_FOR_SNAPSHOT__'
291
- })
292
- return JSON.stringify(stackInfo)
293
- }
294
- return val
295
- })
296
- }
297
-
298
- expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot()
299
-
300
- await provider.shutdown()
301
- },
302
- )
331
+ await provider.shutdown()
332
+ })
303
333
  })
304
334
  })
@@ -3,13 +3,24 @@ import { SessionIdSymbol } from '@livestore/common'
3
3
  import { State } from '@livestore/common/schema'
4
4
  import type { LiveQuery, LiveQueryDef, Store } from '@livestore/livestore'
5
5
  import { queryDb } from '@livestore/livestore'
6
- import { shouldNeverHappen } from '@livestore/utils'
6
+ import { omitUndefineds, shouldNeverHappen } from '@livestore/utils'
7
7
  import React from 'react'
8
8
 
9
9
  import { LiveStoreContext } from './LiveStoreContext.ts'
10
10
  import { useQueryRef } from './useQuery.ts'
11
11
 
12
- export type UseRowResult<TTableDef extends State.SQLite.ClientDocumentTableDef.TraitAny> = [
12
+ /**
13
+ * Return type of `useClientDocument` hook.
14
+ *
15
+ * A tuple providing React-style state access to a client-document table row:
16
+ * - `[0]` row: The current value (decoded according to the table schema)
17
+ * - `[1]` setRow: Setter function to update the document
18
+ * - `[2]` id: The document's ID (resolved from `SessionIdSymbol` if applicable)
19
+ * - `[3]` query$: The underlying `LiveQuery` for advanced use cases
20
+ *
21
+ * @typeParam TTableDef - The client-document table definition type
22
+ */
23
+ export type UseClientDocumentResult<TTableDef extends State.SQLite.ClientDocumentTableDef.TraitAny> = [
13
24
  row: TTableDef['Value'],
14
25
  setRow: StateSetters<TTableDef>,
15
26
  id: string,
@@ -54,13 +65,17 @@ export const useClientDocument: {
54
65
  any,
55
66
  any,
56
67
  any,
57
- { partialSet: boolean; default: { id: string | SessionIdSymbol; value: any } }
68
+ {
69
+ partialSet: boolean
70
+ /** Default value to use instead of the default value from the table definition */
71
+ default: any
72
+ }
58
73
  >,
59
74
  >(
60
75
  table: TTableDef,
61
76
  id?: State.SQLite.ClientDocumentTableDef.DefaultIdType<TTableDef> | SessionIdSymbol,
62
77
  options?: Partial<RowQuery.GetOrCreateOptions<TTableDef>>,
63
- ): UseRowResult<TTableDef>
78
+ ): UseClientDocumentResult<TTableDef>
64
79
 
65
80
  // case: no default id → id arg is required
66
81
  <
@@ -68,20 +83,24 @@ export const useClientDocument: {
68
83
  any,
69
84
  any,
70
85
  any,
71
- { partialSet: boolean; default: { id: string | SessionIdSymbol | undefined; value: any } }
86
+ {
87
+ partialSet: boolean
88
+ /** Default value to use instead of the default value from the table definition */
89
+ default: any
90
+ }
72
91
  >,
73
92
  >(
74
93
  table: TTableDef,
75
94
  // TODO adjust so it works with arbitrary primary keys or unique constraints
76
95
  id: State.SQLite.ClientDocumentTableDef.DefaultIdType<TTableDef> | string | SessionIdSymbol,
77
96
  options?: Partial<RowQuery.GetOrCreateOptions<TTableDef>>,
78
- ): UseRowResult<TTableDef>
97
+ ): UseClientDocumentResult<TTableDef>
79
98
  } = <TTableDef extends State.SQLite.ClientDocumentTableDef.Any>(
80
99
  table: TTableDef,
81
100
  idOrOptions?: string | SessionIdSymbol,
82
101
  options_?: Partial<RowQuery.GetOrCreateOptions<TTableDef>>,
83
102
  storeArg?: { store?: Store },
84
- ): UseRowResult<TTableDef> => {
103
+ ): UseClientDocumentResult<TTableDef> => {
85
104
  const id =
86
105
  typeof idOrOptions === 'string' || idOrOptions === SessionIdSymbol
87
106
  ? idOrOptions
@@ -97,14 +116,13 @@ export const useClientDocument: {
97
116
  const tableName = table.sqliteDef.name
98
117
 
99
118
  const store =
100
- storeArg?.store ??
101
- // biome-ignore lint/correctness/useHookAtTopLevel: store is stable
119
+ storeArg?.store ?? // biome-ignore lint/correctness/useHookAtTopLevel: store is stable
102
120
  React.useContext(LiveStoreContext)?.store ??
103
121
  shouldNeverHappen(`No store provided to useClientDocument`)
104
122
 
105
123
  // console.debug('useClientDocument', tableName, id)
106
124
 
107
- const idStr: string = id === SessionIdSymbol ? store.clientSession.sessionId : id
125
+ const idStr: string = id === SessionIdSymbol ? store.sessionId : id
108
126
 
109
127
  type QueryDef = LiveQueryDef<TTableDef['Value']>
110
128
  const queryDef: QueryDef = React.useMemo(
@@ -117,7 +135,7 @@ export const useClientDocument: {
117
135
 
118
136
  const queryRef = useQueryRef(queryDef, {
119
137
  otelSpanName: `LiveStore:useClientDocument:${tableName}:${idStr}`,
120
- store: storeArg?.store,
138
+ ...omitUndefineds({ store: storeArg?.store }),
121
139
  })
122
140
 
123
141
  const setState = React.useMemo<StateSetters<TTableDef>>(
@@ -133,11 +151,38 @@ export const useClientDocument: {
133
151
  return [queryRef.valueRef.current, setState, idStr, queryRef.queryRcRef.value]
134
152
  }
135
153
 
154
+ /**
155
+ * A function that dispatches an action. Mirrors React's `Dispatch` type.
156
+ * @typeParam A - The action type
157
+ */
136
158
  export type Dispatch<A> = (action: A) => void
137
- export type SetStateAction<S> = Partial<S> | ((previousValue: S) => Partial<S>)
138
159
 
160
+ /**
161
+ * A state update that can be either a partial value or a function returning a partial value.
162
+ * Used when the client-document table has `partialSet: true`.
163
+ * @typeParam S - The state type
164
+ */
165
+ export type SetStateActionPartial<S> = Partial<S> | ((previousValue: S) => Partial<S>)
166
+
167
+ /**
168
+ * A state update that can be either a full value or a function returning a full value.
169
+ * Mirrors React's `SetStateAction` type.
170
+ * @typeParam S - The state type
171
+ */
172
+ export type SetStateAction<S> = S | ((previousValue: S) => S)
173
+
174
+ /**
175
+ * The setter function type for `useClientDocument`, determined by the table's `partialSet` option.
176
+ *
177
+ * - If `partialSet: false` (default), requires full state replacement
178
+ * - If `partialSet: true`, accepts partial updates merged with existing state
179
+ *
180
+ * @typeParam TTableDef - The client-document table definition type
181
+ */
139
182
  export type StateSetters<TTableDef extends State.SQLite.ClientDocumentTableDef.TraitAny> = Dispatch<
140
- SetStateAction<TTableDef['Value']>
183
+ TTableDef[State.SQLite.ClientDocumentTableDefSymbol]['options']['partialSet'] extends false
184
+ ? SetStateAction<TTableDef['Value']>
185
+ : SetStateActionPartial<TTableDef['Value']>
141
186
  >
142
187
 
143
188
  const validateTableOptions = (table: State.SQLite.TableDef<any, any>) => {
@@ -1,6 +1,6 @@
1
1
  /** biome-ignore-all lint/a11y: test */
2
2
  import * as LiveStore from '@livestore/livestore'
3
- import { queryDb, signal } from '@livestore/livestore'
3
+ import { queryDb, StoreInternalsSymbol, signal } from '@livestore/livestore'
4
4
  import { RG } from '@livestore/livestore/internal/testing-utils'
5
5
  import { Effect, Schema } from '@livestore/utils/effect'
6
6
  import { Vitest } from '@livestore/utils-dev/node-vitest'
@@ -10,8 +10,8 @@ import React from 'react'
10
10
  import * as ReactWindow from 'react-window'
11
11
  import { expect } from 'vitest'
12
12
 
13
- import { events, makeTodoMvcReact, tables } from './__tests__/fixture.js'
14
- import { __resetUseRcResourceCache } from './useRcResource.js'
13
+ import { events, makeTodoMvcReact, tables } from './__tests__/fixture.tsx'
14
+ import { __resetUseRcResourceCache } from './useRcResource.ts'
15
15
 
16
16
  Vitest.describe.each([{ strictMode: true }, { strictMode: false }] as const)(
17
17
  'useQuery (strictMode=%s)',
@@ -38,14 +38,14 @@ Vitest.describe.each([{ strictMode: true }, { strictMode: false }] as const)(
38
38
 
39
39
  expect(result.current.length).toBe(0)
40
40
  expect(renderCount.val).toBe(1)
41
- expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
41
+ expect(store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
42
42
 
43
43
  ReactTesting.act(() => store.commit(events.todoCreated({ id: 't1', text: 'buy milk', completed: false })))
44
44
 
45
45
  expect(result.current.length).toBe(1)
46
46
  expect(result.current[0]!.text).toBe('buy milk')
47
47
  expect(renderCount.val).toBe(2)
48
- expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
48
+ expect(store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
49
49
  }),
50
50
  )
51
51
 
@@ -80,19 +80,25 @@ Vitest.describe.each([{ strictMode: true }, { strictMode: false }] as const)(
80
80
 
81
81
  expect(result.current).toBe('buy milk')
82
82
  expect(renderCount.val).toBe(1)
83
- expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot('1: after first render')
83
+ expect(store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot(
84
+ '1: after first render',
85
+ )
84
86
 
85
87
  ReactTesting.act(() => store.commit(events.todoUpdated({ id: 't1', text: 'buy soy milk' })))
86
88
 
87
89
  expect(result.current).toBe('buy soy milk')
88
90
  expect(renderCount.val).toBe(2)
89
- expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot('2: after first commit')
91
+ expect(store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot(
92
+ '2: after first commit',
93
+ )
90
94
 
91
95
  rerender('t2')
92
96
 
93
97
  expect(result.current).toBe('buy eggs')
94
98
  expect(renderCount.val).toBe(3)
95
- expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot('3: after forced rerender')
99
+ expect(store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot(
100
+ '3: after forced rerender',
101
+ )
96
102
  }),
97
103
  )
98
104
 
@@ -120,19 +126,19 @@ Vitest.describe.each([{ strictMode: true }, { strictMode: false }] as const)(
120
126
 
121
127
  expect(result.current).toBe('buy milk')
122
128
  expect(renderCount.val).toBe(1)
123
- expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
129
+ expect(store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
124
130
 
125
131
  ReactTesting.act(() => store.commit(events.todoUpdated({ id: 't1', text: 'buy soy milk' })))
126
132
 
127
133
  expect(result.current).toBe('buy soy milk')
128
134
  expect(renderCount.val).toBe(2)
129
- expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
135
+ expect(store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
130
136
 
131
137
  ReactTesting.act(() => store.setSignal(filter$, 't2'))
132
138
 
133
139
  expect(result.current).toBe('buy eggs')
134
140
  expect(renderCount.val).toBe(3)
135
- expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
141
+ expect(store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
136
142
  }),
137
143
  )
138
144
 
@@ -187,5 +193,50 @@ Vitest.describe.each([{ strictMode: true }, { strictMode: false }] as const)(
187
193
  expect(result.current).toBe(1)
188
194
  }),
189
195
  )
196
+
197
+ Vitest.scopedLive('supports query builders directly', () =>
198
+ Effect.gen(function* () {
199
+ const { wrapper, store } = yield* makeTodoMvcReact({ strictMode })
200
+
201
+ store.commit(
202
+ events.todoCreated({ id: 't1', text: 'buy milk', completed: false }),
203
+ events.todoCreated({ id: 't2', text: 'buy eggs', completed: true }),
204
+ )
205
+
206
+ const todosWhereIncomplete = tables.todos.where({ completed: false })
207
+
208
+ const { result } = ReactTesting.renderHook(() => store.useQuery(todosWhereIncomplete).map((todo) => todo.id), {
209
+ wrapper,
210
+ })
211
+
212
+ expect(result.current).toEqual(['t1'])
213
+ }),
214
+ )
215
+
216
+ Vitest.scopedLive('union of different result types with useQuery', () =>
217
+ Effect.gen(function* () {
218
+ const { wrapper, store, renderCount } = yield* makeTodoMvcReact({ strictMode })
219
+
220
+ const str$ = signal('hello', { label: 'str' })
221
+ const num$ = signal(123, { label: 'num' })
222
+
223
+ const { result, rerender } = ReactTesting.renderHook(
224
+ (useNum: boolean) => {
225
+ renderCount.inc()
226
+ const query$ = React.useMemo(() => (useNum ? num$ : str$), [useNum])
227
+ return store.useQuery(query$)
228
+ },
229
+ { wrapper, initialProps: false },
230
+ )
231
+
232
+ expect(result.current).toBe('hello')
233
+ expect(renderCount.val).toBe(1)
234
+
235
+ rerender(true)
236
+
237
+ expect(result.current).toBe(123)
238
+ expect(renderCount.val).toBe(2)
239
+ }),
240
+ )
190
241
  },
191
242
  )