@livestore/react 0.3.0-dev.9 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/LiveStoreContext.d.ts +10 -4
  3. package/dist/LiveStoreContext.d.ts.map +1 -1
  4. package/dist/LiveStoreContext.js +1 -11
  5. package/dist/LiveStoreContext.js.map +1 -1
  6. package/dist/LiveStoreProvider.d.ts +29 -12
  7. package/dist/LiveStoreProvider.d.ts.map +1 -1
  8. package/dist/LiveStoreProvider.js +84 -55
  9. package/dist/LiveStoreProvider.js.map +1 -1
  10. package/dist/LiveStoreProvider.test.js +80 -29
  11. package/dist/LiveStoreProvider.test.js.map +1 -1
  12. package/dist/__tests__/fixture.d.ts +122 -556
  13. package/dist/__tests__/fixture.d.ts.map +1 -1
  14. package/dist/__tests__/fixture.js +71 -30
  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 +10 -6
  19. package/dist/experimental/components/LiveList.js.map +1 -1
  20. package/dist/mod.d.ts +4 -5
  21. package/dist/mod.d.ts.map +1 -1
  22. package/dist/mod.js +4 -5
  23. package/dist/mod.js.map +1 -1
  24. package/dist/useClientDocument.d.ts +61 -0
  25. package/dist/useClientDocument.d.ts.map +1 -0
  26. package/dist/useClientDocument.js +79 -0
  27. package/dist/useClientDocument.js.map +1 -0
  28. package/dist/useClientDocument.test.d.ts +2 -0
  29. package/dist/useClientDocument.test.d.ts.map +1 -0
  30. package/dist/useClientDocument.test.js +175 -0
  31. package/dist/useClientDocument.test.js.map +1 -0
  32. package/dist/useQuery.d.ts +25 -3
  33. package/dist/useQuery.d.ts.map +1 -1
  34. package/dist/useQuery.js +67 -47
  35. package/dist/useQuery.js.map +1 -1
  36. package/dist/useQuery.test.d.ts +1 -1
  37. package/dist/useQuery.test.d.ts.map +1 -1
  38. package/dist/useQuery.test.js +86 -24
  39. package/dist/useQuery.test.js.map +1 -1
  40. package/dist/useRcResource.d.ts +76 -0
  41. package/dist/useRcResource.d.ts.map +1 -0
  42. package/dist/useRcResource.js +152 -0
  43. package/dist/useRcResource.js.map +1 -0
  44. package/dist/useRcResource.test.d.ts +2 -0
  45. package/dist/useRcResource.test.d.ts.map +1 -0
  46. package/dist/useRcResource.test.js +122 -0
  47. package/dist/useRcResource.test.js.map +1 -0
  48. package/dist/useStore.d.ts +9 -0
  49. package/dist/useStore.d.ts.map +1 -0
  50. package/dist/useStore.js +28 -0
  51. package/dist/useStore.js.map +1 -0
  52. package/dist/utils/useStateRefWithReactiveInput.d.ts.map +1 -1
  53. package/package.json +19 -13
  54. package/src/LiveStoreContext.ts +11 -16
  55. package/src/LiveStoreProvider.test.tsx +176 -37
  56. package/src/LiveStoreProvider.tsx +156 -81
  57. package/src/__snapshots__/useClientDocument.test.tsx.snap +613 -0
  58. package/src/__snapshots__/useQuery.test.tsx.snap +2011 -0
  59. package/src/__tests__/fixture.tsx +74 -47
  60. package/src/experimental/components/LiveList.tsx +10 -7
  61. package/src/mod.ts +5 -6
  62. package/src/useClientDocument.test.tsx +306 -0
  63. package/src/useClientDocument.ts +157 -0
  64. package/src/useQuery.test.tsx +182 -71
  65. package/src/useQuery.ts +95 -58
  66. package/src/useRcResource.test.tsx +167 -0
  67. package/src/useRcResource.ts +182 -0
  68. package/src/useStore.ts +36 -0
  69. package/dist/useAtom.d.ts +0 -5
  70. package/dist/useAtom.d.ts.map +0 -1
  71. package/dist/useAtom.js +0 -38
  72. package/dist/useAtom.js.map +0 -1
  73. package/dist/useRow.d.ts +0 -50
  74. package/dist/useRow.d.ts.map +0 -1
  75. package/dist/useRow.js +0 -93
  76. package/dist/useRow.js.map +0 -1
  77. package/dist/useRow.test.d.ts +0 -2
  78. package/dist/useRow.test.d.ts.map +0 -1
  79. package/dist/useRow.test.js +0 -202
  80. package/dist/useRow.test.js.map +0 -1
  81. package/dist/useScopedQuery.d.ts +0 -33
  82. package/dist/useScopedQuery.d.ts.map +0 -1
  83. package/dist/useScopedQuery.js +0 -87
  84. package/dist/useScopedQuery.js.map +0 -1
  85. package/dist/useScopedQuery.test.d.ts +0 -2
  86. package/dist/useScopedQuery.test.d.ts.map +0 -1
  87. package/dist/useScopedQuery.test.js +0 -60
  88. package/dist/useScopedQuery.test.js.map +0 -1
  89. package/src/__snapshots__/useRow.test.tsx.snap +0 -360
  90. package/src/useAtom.ts +0 -52
  91. package/src/useRow.test.tsx +0 -344
  92. package/src/useRow.ts +0 -188
  93. package/src/useScopedQuery.test.tsx +0 -96
  94. package/src/useScopedQuery.ts +0 -143
  95. package/tsconfig.json +0 -20
  96. package/vitest.config.js +0 -17
@@ -1,27 +1,30 @@
1
+ import { makeInMemoryAdapter } from '@livestore/adapter-web'
1
2
  import { sql } from '@livestore/common'
2
- import { rawSqlMutation } from '@livestore/common/schema'
3
+ import { rawSqlEvent } from '@livestore/common/schema'
3
4
  import { queryDb, type Store } from '@livestore/livestore'
4
5
  import { Schema } from '@livestore/utils/effect'
5
- import { makeInMemoryAdapter } from '@livestore/web'
6
- import { render, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react'
6
+ import * as ReactTesting from '@testing-library/react'
7
7
  import React from 'react'
8
8
  import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
9
9
  import { describe, expect, it } from 'vitest'
10
10
 
11
- import { schema, tables } from './__tests__/fixture.js'
11
+ import { events, schema, tables } from './__tests__/fixture.js'
12
12
  import { LiveStoreProvider } from './LiveStoreProvider.js'
13
13
  import * as LiveStoreReact from './mod.js'
14
14
 
15
- describe('LiveStoreProvider', () => {
15
+ describe.each([true, false])('LiveStoreProvider (strictMode: %s)', (strictMode) => {
16
+ const WithStrictMode = strictMode ? React.StrictMode : React.Fragment
17
+
16
18
  it('simple', async () => {
17
19
  let appRenderCount = 0
18
20
 
19
- const allTodos$ = queryDb({ query: `select * from todos`, schema: Schema.Array(tables.todos.schema) })
21
+ const allTodos$ = queryDb({ query: `select * from todos`, schema: Schema.Array(tables.todos.rowSchema) })
20
22
 
21
23
  const App = () => {
22
24
  appRenderCount++
25
+ const { store } = LiveStoreReact.useStore()
23
26
 
24
- const todos = LiveStoreReact.useQuery(allTodos$)
27
+ const todos = store.useQuery(allTodos$)
25
28
 
26
29
  return <div>{JSON.stringify(todos)}</div>
27
30
  }
@@ -31,8 +34,8 @@ describe('LiveStoreProvider', () => {
31
34
  const Root = ({ forceUpdate }: { forceUpdate: number }) => {
32
35
  const bootCb = React.useCallback(
33
36
  (store: Store) =>
34
- store.mutate(
35
- rawSqlMutation({
37
+ store.commit(
38
+ rawSqlEvent({
36
39
  sql: sql`INSERT OR IGNORE INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)`,
37
40
  }),
38
41
  ),
@@ -41,37 +44,43 @@ describe('LiveStoreProvider', () => {
41
44
  // eslint-disable-next-line react-hooks/exhaustive-deps
42
45
  const adapterMemo = React.useMemo(() => makeInMemoryAdapter(), [forceUpdate])
43
46
  return (
44
- <LiveStoreProvider
45
- schema={schema}
46
- renderLoading={(status) => <div>Loading LiveStore: {status.stage}</div>}
47
- adapter={adapterMemo}
48
- boot={bootCb}
49
- signal={abortController.signal}
50
- batchUpdates={batchUpdates}
51
- >
52
- <App />
53
- </LiveStoreProvider>
47
+ <WithStrictMode>
48
+ <LiveStoreProvider
49
+ schema={schema}
50
+ renderLoading={(status) => <div>Loading LiveStore: {status.stage}</div>}
51
+ adapter={adapterMemo}
52
+ boot={bootCb}
53
+ signal={abortController.signal}
54
+ batchUpdates={batchUpdates}
55
+ >
56
+ <App />
57
+ </LiveStoreProvider>
58
+ </WithStrictMode>
54
59
  )
55
60
  }
56
61
 
57
- const { rerender } = render(<Root forceUpdate={1} />)
62
+ const { rerender } = ReactTesting.render(<Root forceUpdate={1} />)
58
63
 
59
64
  expect(appRenderCount).toBe(0)
60
65
 
61
- await waitForElementToBeRemoved(() => screen.getByText((_) => _.startsWith('Loading LiveStore')))
66
+ await ReactTesting.waitForElementToBeRemoved(() =>
67
+ ReactTesting.screen.getByText((_) => _.startsWith('Loading LiveStore')),
68
+ )
62
69
 
63
- expect(appRenderCount).toBe(1)
70
+ expect(appRenderCount).toBe(strictMode ? 2 : 1)
64
71
 
65
72
  rerender(<Root forceUpdate={2} />)
66
73
 
67
- await waitFor(() => screen.getByText('Loading LiveStore: loading'))
68
- await waitFor(() => screen.getByText((_) => _.includes('buy milk')))
74
+ await ReactTesting.waitFor(() => ReactTesting.screen.getByText('Loading LiveStore: loading'))
75
+ await ReactTesting.waitFor(() => ReactTesting.screen.getByText((_) => _.includes('buy milk')))
69
76
 
70
- expect(appRenderCount).toBe(2)
77
+ expect(appRenderCount).toBe(strictMode ? 4 : 2)
71
78
 
72
79
  abortController.abort()
73
80
 
74
- await waitFor(() => screen.getByText('LiveStore Shutdown due to abort signal'))
81
+ await ReactTesting.waitFor(() =>
82
+ ReactTesting.screen.getByText('LiveStore Shutdown due to interrupted', { exact: false }),
83
+ )
75
84
  })
76
85
 
77
86
  // TODO test aborting during boot
@@ -88,8 +97,8 @@ describe('LiveStoreProvider', () => {
88
97
  const Root = ({ forceUpdate }: { forceUpdate: number }) => {
89
98
  const bootCb = React.useCallback(
90
99
  (store: Store) =>
91
- store.mutate(
92
- rawSqlMutation({
100
+ store.commit(
101
+ rawSqlEvent({
93
102
  sql: sql`INSERT OR IGNORE INTO todos_missing_table (id, text, completed) VALUES ('t1', 'buy milk', 0)`,
94
103
  }),
95
104
  ),
@@ -98,22 +107,152 @@ describe('LiveStoreProvider', () => {
98
107
  // eslint-disable-next-line react-hooks/exhaustive-deps
99
108
  const adapterMemo = React.useMemo(() => makeInMemoryAdapter(), [forceUpdate])
100
109
  return (
110
+ <WithStrictMode>
111
+ <LiveStoreProvider
112
+ schema={schema}
113
+ renderLoading={(status) => <div>Loading LiveStore: {status.stage}</div>}
114
+ adapter={adapterMemo}
115
+ boot={bootCb}
116
+ batchUpdates={batchUpdates}
117
+ >
118
+ <App />
119
+ </LiveStoreProvider>
120
+ </WithStrictMode>
121
+ )
122
+ }
123
+
124
+ ReactTesting.render(<Root forceUpdate={1} />)
125
+
126
+ expect(appRenderCount).toBe(0)
127
+
128
+ await ReactTesting.waitFor(() => ReactTesting.screen.getByText((_) => _.startsWith('LiveStore.UnexpectedError')))
129
+ })
130
+
131
+ it('unmounts when store is shutdown', async () => {
132
+ let appRenderCount = 0
133
+
134
+ const allTodos$ = queryDb({ query: `select * from todos`, schema: Schema.Array(tables.todos.rowSchema) })
135
+
136
+ const shutdownDeferred = Promise.withResolvers<void>()
137
+
138
+ const App = () => {
139
+ appRenderCount++
140
+ const { store } = LiveStoreReact.useStore()
141
+
142
+ React.useEffect(() => {
143
+ shutdownDeferred.promise.then(() => {
144
+ console.log('shutdown')
145
+ return store.shutdown()
146
+ })
147
+ }, [store])
148
+
149
+ const todos = store.useQuery(allTodos$)
150
+
151
+ return <div>{JSON.stringify(todos)}</div>
152
+ }
153
+
154
+ const adapter = makeInMemoryAdapter()
155
+
156
+ const Root = () => {
157
+ return (
158
+ <WithStrictMode>
159
+ <LiveStoreProvider
160
+ schema={schema}
161
+ renderLoading={(status) => <div>Loading LiveStore: {status.stage}</div>}
162
+ adapter={adapter}
163
+ batchUpdates={batchUpdates}
164
+ >
165
+ <App />
166
+ </LiveStoreProvider>
167
+ </WithStrictMode>
168
+ )
169
+ }
170
+
171
+ ReactTesting.render(<Root />)
172
+
173
+ expect(appRenderCount).toBe(0)
174
+
175
+ await ReactTesting.waitFor(() => ReactTesting.screen.getByText('[]'))
176
+
177
+ React.act(() => shutdownDeferred.resolve())
178
+
179
+ expect(appRenderCount).toBe(strictMode ? 2 : 1)
180
+
181
+ await ReactTesting.waitFor(() =>
182
+ ReactTesting.screen.getByText('LiveStore Shutdown due to manual shutdown', { exact: false }),
183
+ )
184
+ })
185
+ })
186
+
187
+ it('should work two stores with the same storeId', async () => {
188
+ const allTodos$ = queryDb({ query: `select * from todos`, schema: Schema.Array(tables.todos.rowSchema) })
189
+
190
+ const appRenderCount = {
191
+ store1: 0,
192
+ store2: 0,
193
+ }
194
+
195
+ const App = () => {
196
+ const { store } = LiveStoreReact.useStore()
197
+ const instanceId = store.clientSession.debugInstanceId as 'store1' | 'store2'
198
+ appRenderCount[instanceId]!++
199
+
200
+ const todos = store.useQuery(allTodos$)
201
+
202
+ return (
203
+ <div id={instanceId}>
204
+ <div role="heading">{instanceId}</div>
205
+ <div role="content">{JSON.stringify(todos)}</div>
206
+ <button onClick={() => store.commit(events.todoCreated({ id: 't1', text: 'buy milk', completed: false }))}>
207
+ create todo {instanceId}
208
+ </button>
209
+ </div>
210
+ )
211
+ }
212
+
213
+ const Root = () => {
214
+ const storeId = 'fixed-store-id'
215
+ return (
216
+ <div>
101
217
  <LiveStoreProvider
218
+ storeId={storeId}
219
+ debug={{ instanceId: 'store1' }}
102
220
  schema={schema}
103
- renderLoading={(status) => <div>Loading LiveStore: {status.stage}</div>}
104
- adapter={adapterMemo}
105
- boot={bootCb}
221
+ adapter={makeInMemoryAdapter()}
106
222
  batchUpdates={batchUpdates}
107
223
  >
108
224
  <App />
109
225
  </LiveStoreProvider>
110
- )
111
- }
226
+ <LiveStoreProvider
227
+ storeId={storeId}
228
+ debug={{ instanceId: 'store2' }}
229
+ schema={schema}
230
+ adapter={makeInMemoryAdapter()}
231
+ batchUpdates={batchUpdates}
232
+ >
233
+ <App />
234
+ </LiveStoreProvider>
235
+ </div>
236
+ )
237
+ }
112
238
 
113
- render(<Root forceUpdate={1} />)
239
+ const { container } = ReactTesting.render(<Root />)
114
240
 
115
- expect(appRenderCount).toBe(0)
241
+ await ReactTesting.waitFor(() => ReactTesting.screen.getByRole('heading', { name: 'store1' }))
242
+ await ReactTesting.waitFor(() => ReactTesting.screen.getByRole('heading', { name: 'store2' }))
116
243
 
117
- await waitFor(() => screen.getByText((_) => _.startsWith('LiveStore.UnexpectedError')))
118
- })
244
+ expect(appRenderCount.store1).toBe(1)
245
+ expect(appRenderCount.store2).toBe(1)
246
+
247
+ ReactTesting.fireEvent.click(ReactTesting.screen.getByText('create todo store1'))
248
+
249
+ expect(appRenderCount.store1).toBe(2)
250
+
251
+ expect(container.querySelector('#store1 > div[role="content"]')?.textContent).toBe(
252
+ '[{"id":"t1","text":"buy milk","completed":false}]',
253
+ )
254
+
255
+ expect(container.querySelector('#store2 > div[role="content"]')?.textContent).toBe('[]')
119
256
  })
257
+
258
+ // TODO test that checks that there are no two exact same instances (i.e. same storeId, clientId, sessionId)