@livestore/livestore 0.4.0-dev.22 → 0.4.0-dev.23

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 (207) hide show
  1. package/README.md +0 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/QueryCache.js +1 -1
  4. package/dist/QueryCache.js.map +1 -1
  5. package/dist/SqliteDbWrapper.d.ts +5 -5
  6. package/dist/SqliteDbWrapper.d.ts.map +1 -1
  7. package/dist/SqliteDbWrapper.js +8 -8
  8. package/dist/SqliteDbWrapper.js.map +1 -1
  9. package/dist/SqliteDbWrapper.test.js +2 -2
  10. package/dist/SqliteDbWrapper.test.js.map +1 -1
  11. package/dist/effect/LiveStore.d.ts +14 -7
  12. package/dist/effect/LiveStore.d.ts.map +1 -1
  13. package/dist/effect/LiveStore.js +0 -15
  14. package/dist/effect/LiveStore.js.map +1 -1
  15. package/dist/effect/LiveStore.test.d.ts +2 -0
  16. package/dist/effect/LiveStore.test.d.ts.map +1 -0
  17. package/dist/effect/LiveStore.test.js +42 -0
  18. package/dist/effect/LiveStore.test.js.map +1 -0
  19. package/dist/live-queries/base-class.d.ts +3 -3
  20. package/dist/live-queries/base-class.d.ts.map +1 -1
  21. package/dist/live-queries/base-class.js +2 -2
  22. package/dist/live-queries/base-class.js.map +1 -1
  23. package/dist/live-queries/client-document-get-query.d.ts +1 -1
  24. package/dist/live-queries/client-document-get-query.d.ts.map +1 -1
  25. package/dist/live-queries/client-document-get-query.js +1 -1
  26. package/dist/live-queries/client-document-get-query.js.map +1 -1
  27. package/dist/live-queries/computed.d.ts.map +1 -1
  28. package/dist/live-queries/computed.js +2 -2
  29. package/dist/live-queries/computed.js.map +1 -1
  30. package/dist/live-queries/db-query.js +14 -14
  31. package/dist/live-queries/db-query.js.map +1 -1
  32. package/dist/live-queries/db-query.test.js +2 -2
  33. package/dist/live-queries/db-query.test.js.map +1 -1
  34. package/dist/live-queries/signal.test.js +2 -2
  35. package/dist/live-queries/signal.test.js.map +1 -1
  36. package/dist/mod.d.ts +1 -1
  37. package/dist/mod.d.ts.map +1 -1
  38. package/dist/mod.js.map +1 -1
  39. package/dist/reactive.d.ts +9 -9
  40. package/dist/reactive.d.ts.map +1 -1
  41. package/dist/reactive.js +9 -26
  42. package/dist/reactive.js.map +1 -1
  43. package/dist/reactive.test.js +2 -2
  44. package/dist/reactive.test.js.map +1 -1
  45. package/dist/store/StoreRegistry.d.ts +30 -5
  46. package/dist/store/StoreRegistry.d.ts.map +1 -1
  47. package/dist/store/StoreRegistry.js +54 -31
  48. package/dist/store/StoreRegistry.js.map +1 -1
  49. package/dist/store/StoreRegistry.test.js +251 -250
  50. package/dist/store/StoreRegistry.test.js.map +1 -1
  51. package/dist/store/create-store.d.ts +6 -2
  52. package/dist/store/create-store.d.ts.map +1 -1
  53. package/dist/store/create-store.js +13 -7
  54. package/dist/store/create-store.js.map +1 -1
  55. package/dist/store/devtools.d.ts +1 -1
  56. package/dist/store/devtools.d.ts.map +1 -1
  57. package/dist/store/devtools.js +3 -3
  58. package/dist/store/devtools.js.map +1 -1
  59. package/dist/store/store-eventstream.test.js +2 -2
  60. package/dist/store/store-eventstream.test.js.map +1 -1
  61. package/dist/store/store-types.d.ts +70 -5
  62. package/dist/store/store-types.d.ts.map +1 -1
  63. package/dist/store/store-types.js.map +1 -1
  64. package/dist/store/store-types.test.js +1 -1
  65. package/dist/store/store-types.test.js.map +1 -1
  66. package/dist/store/store.d.ts +81 -2
  67. package/dist/store/store.d.ts.map +1 -1
  68. package/dist/store/store.js +128 -45
  69. package/dist/store/store.js.map +1 -1
  70. package/dist/utils/dev.js.map +1 -1
  71. package/dist/utils/stack-info.js +2 -2
  72. package/dist/utils/stack-info.js.map +1 -1
  73. package/dist/utils/tests/fixture.d.ts +1 -1
  74. package/dist/utils/tests/fixture.d.ts.map +1 -1
  75. package/dist/utils/tests/fixture.js.map +1 -1
  76. package/dist/utils/tests/otel.d.ts.map +1 -1
  77. package/dist/utils/tests/otel.js +5 -5
  78. package/dist/utils/tests/otel.js.map +1 -1
  79. package/package.json +58 -17
  80. package/src/QueryCache.ts +1 -1
  81. package/src/SqliteDbWrapper.test.ts +4 -2
  82. package/src/SqliteDbWrapper.ts +12 -11
  83. package/src/ambient.d.ts +0 -7
  84. package/src/effect/LiveStore.test.ts +61 -0
  85. package/src/effect/LiveStore.ts +17 -26
  86. package/src/live-queries/__snapshots__/db-query.test.ts.snap +336 -231
  87. package/src/live-queries/base-class.ts +7 -6
  88. package/src/live-queries/client-document-get-query.ts +4 -2
  89. package/src/live-queries/computed.ts +3 -2
  90. package/src/live-queries/db-query.test.ts +3 -2
  91. package/src/live-queries/db-query.ts +15 -15
  92. package/src/live-queries/signal.test.ts +3 -2
  93. package/src/mod.ts +1 -0
  94. package/src/reactive.test.ts +3 -2
  95. package/src/reactive.ts +22 -23
  96. package/src/store/StoreRegistry.test.ts +317 -293
  97. package/src/store/StoreRegistry.ts +63 -38
  98. package/src/store/create-store.ts +26 -11
  99. package/src/store/devtools.ts +5 -6
  100. package/src/store/store-eventstream.test.ts +4 -2
  101. package/src/store/store-types.test.ts +3 -1
  102. package/src/store/store-types.ts +47 -8
  103. package/src/store/store.ts +172 -55
  104. package/src/utils/dev.ts +2 -2
  105. package/src/utils/stack-info.ts +2 -2
  106. package/src/utils/tests/fixture.ts +2 -1
  107. package/src/utils/tests/otel.ts +8 -7
  108. package/docs/api/index.md +0 -3
  109. package/docs/building-with-livestore/complex-ui-state/index.md +0 -3
  110. package/docs/building-with-livestore/crud/index.md +0 -3
  111. package/docs/building-with-livestore/data-modeling/index.md +0 -30
  112. package/docs/building-with-livestore/debugging/index.md +0 -17
  113. package/docs/building-with-livestore/devtools/index.md +0 -79
  114. package/docs/building-with-livestore/events/index.md +0 -355
  115. package/docs/building-with-livestore/examples/ai-agent/index.md +0 -5
  116. package/docs/building-with-livestore/examples/todo-workspaces/index.md +0 -885
  117. package/docs/building-with-livestore/examples/turnbased-game/index.md +0 -7
  118. package/docs/building-with-livestore/opentelemetry/index.md +0 -227
  119. package/docs/building-with-livestore/production-checklist/index.md +0 -5
  120. package/docs/building-with-livestore/reactivity-system/index.md +0 -202
  121. package/docs/building-with-livestore/rules-for-ai-agents/index.md +0 -9
  122. package/docs/building-with-livestore/state/materializers/index.md +0 -300
  123. package/docs/building-with-livestore/state/sql-queries/index.md +0 -94
  124. package/docs/building-with-livestore/state/sqlite/index.md +0 -45
  125. package/docs/building-with-livestore/state/sqlite-schema/index.md +0 -306
  126. package/docs/building-with-livestore/state/sqlite-schema-effect/index.md +0 -300
  127. package/docs/building-with-livestore/store/index.md +0 -625
  128. package/docs/building-with-livestore/syncing/index.md +0 -136
  129. package/docs/building-with-livestore/tools/cli/index.md +0 -177
  130. package/docs/building-with-livestore/tools/mcp/index.md +0 -187
  131. package/docs/examples/cloudflare-adapter/index.md +0 -44
  132. package/docs/examples/expo-adapter/index.md +0 -44
  133. package/docs/examples/index.md +0 -55
  134. package/docs/examples/node-adapter/index.md +0 -44
  135. package/docs/examples/web-adapter/index.md +0 -52
  136. package/docs/framework-integrations/custom-elements/index.md +0 -142
  137. package/docs/framework-integrations/react-integration/index.md +0 -937
  138. package/docs/framework-integrations/solid-integration/index.md +0 -293
  139. package/docs/framework-integrations/svelte-integration/index.md +0 -42
  140. package/docs/framework-integrations/vue-integration/index.md +0 -294
  141. package/docs/getting-started/expo/index.md +0 -882
  142. package/docs/getting-started/node/index.md +0 -115
  143. package/docs/getting-started/react-web/index.md +0 -626
  144. package/docs/getting-started/solid/index.md +0 -3
  145. package/docs/getting-started/vue/index.md +0 -471
  146. package/docs/index.md +0 -208
  147. package/docs/llms.txt +0 -146
  148. package/docs/misc/CODE_OF_CONDUCT/index.md +0 -133
  149. package/docs/misc/FAQ/index.md +0 -37
  150. package/docs/misc/community/index.md +0 -88
  151. package/docs/misc/credits/index.md +0 -14
  152. package/docs/misc/design-partners/index.md +0 -13
  153. package/docs/misc/package-management/index.md +0 -21
  154. package/docs/misc/performance/index.md +0 -25
  155. package/docs/misc/resources/index.md +0 -46
  156. package/docs/misc/state-of-the-project/index.md +0 -37
  157. package/docs/misc/troubleshooting/index.md +0 -82
  158. package/docs/overview/concepts/index.md +0 -78
  159. package/docs/overview/how-livestore-works/index.md +0 -56
  160. package/docs/overview/introduction/index.md +0 -413
  161. package/docs/overview/technology-comparison/index.md +0 -40
  162. package/docs/overview/when-livestore/index.md +0 -81
  163. package/docs/overview/why-livestore/index.md +0 -111
  164. package/docs/patterns/ai/index.md +0 -15
  165. package/docs/patterns/anonymous-user-transition/index.md +0 -10
  166. package/docs/patterns/app-evolution/index.md +0 -72
  167. package/docs/patterns/auth/index.md +0 -377
  168. package/docs/patterns/effect/index.md +0 -1505
  169. package/docs/patterns/encryption/index.md +0 -6
  170. package/docs/patterns/external-data/index.md +0 -5
  171. package/docs/patterns/file-management/index.md +0 -11
  172. package/docs/patterns/file-structure/index.md +0 -14
  173. package/docs/patterns/list-ordering/index.md +0 -369
  174. package/docs/patterns/offline/index.md +0 -32
  175. package/docs/patterns/orm/index.md +0 -18
  176. package/docs/patterns/presence/index.md +0 -11
  177. package/docs/patterns/rich-text-editing/index.md +0 -11
  178. package/docs/patterns/server-side-clients/index.md +0 -97
  179. package/docs/patterns/side-effects/index.md +0 -11
  180. package/docs/patterns/state-machines/index.md +0 -11
  181. package/docs/patterns/storybook/index.md +0 -209
  182. package/docs/patterns/undo-redo/index.md +0 -9
  183. package/docs/patterns/version-control/index.md +0 -8
  184. package/docs/platform-adapters/cloudflare-durable-object-adapter/index.md +0 -453
  185. package/docs/platform-adapters/electron-adapter/index.md +0 -15
  186. package/docs/platform-adapters/expo-adapter/index.md +0 -262
  187. package/docs/platform-adapters/node-adapter/index.md +0 -160
  188. package/docs/platform-adapters/tauri-adapter/index.md +0 -15
  189. package/docs/platform-adapters/web-adapter/index.md +0 -287
  190. package/docs/sustainable-open-source/contributing/docs/index.md +0 -94
  191. package/docs/sustainable-open-source/contributing/info/index.md +0 -63
  192. package/docs/sustainable-open-source/contributing/monorepo/index.md +0 -195
  193. package/docs/sustainable-open-source/sponsoring/index.md +0 -104
  194. package/docs/sync-providers/cloudflare/index.md +0 -773
  195. package/docs/sync-providers/custom/index.md +0 -65
  196. package/docs/sync-providers/electricsql/index.md +0 -159
  197. package/docs/sync-providers/s2/index.md +0 -230
  198. package/docs/tutorial/0-welcome/index.md +0 -48
  199. package/docs/tutorial/1-setup-starter-project/index.md +0 -105
  200. package/docs/tutorial/2-deploy-to-cloudflare/index.md +0 -195
  201. package/docs/tutorial/3-read-and-write-todos-via-livestore/index.md +0 -530
  202. package/docs/tutorial/4-sync-data-via-cloudflare/index.md +0 -210
  203. package/docs/tutorial/5-expand-business-logic/index.md +0 -174
  204. package/docs/tutorial/6-persist-ui-state/index.md +0 -453
  205. package/docs/tutorial/7-next-steps/index.md +0 -22
  206. package/docs/understanding-livestore/design-decisions/index.md +0 -33
  207. package/docs/understanding-livestore/event-sourcing/index.md +0 -40
@@ -1,937 +0,0 @@
1
- # React integration for LiveStore
2
-
3
- While LiveStore is framework agnostic, the `@livestore/react` package provides a first-class integration with React.
4
-
5
- ## Features
6
-
7
- - High performance
8
- - Fine-grained reactivity (using LiveStore's signals-based reactivity system)
9
- - Instant, synchronous query results (without the need for `useEffect` and `isLoading` checks)
10
- - Supports multiple store instances
11
- - Transactional state transitions (via `batchUpdates`)
12
- - Also supports Expo / React Native via `@livestore/adapter-expo`
13
-
14
- ## Core Concepts
15
-
16
- When using LiveStore in React, you'll primarily interact with these fundamental components:
17
-
18
- - [**`StoreRegistry`**](#new-storeregistryconfig) - Manages store instances with automatic caching and disposal
19
- - [**`<StoreRegistryProvider>`**](#storeregistryprovider) - React context provider that supplies the registry to components
20
- - [**`useStore()`**](#usestoreoptions) - Suspense-enabled hook for accessing store instances
21
-
22
- Stores are cached by their `storeId` and automatically disposed after being unused for a configurable duration (`unusedCacheTime`).
23
-
24
- ## `reference/framework-integrations/react/multi-store/minimal.tsx`
25
-
26
- ```tsx filename="reference/framework-integrations/react/multi-store/minimal.tsx"
27
-
28
- const issueStoreOptions = (issueId: string) =>
29
- storeOptions({
30
- storeId: `issue-${issueId}`,
31
- schema,
32
- adapter: makeInMemoryAdapter(),
33
- })
34
-
35
- export function App() {
36
- const [storeRegistry] = useState(() => new StoreRegistry({ defaultOptions: { batchUpdates } }))
37
- return (
38
- <StoreRegistryProvider storeRegistry={storeRegistry}>
39
- <IssueView />
40
- </StoreRegistryProvider>
41
- )
42
- }
43
-
44
- function IssueView() {
45
- const store = useStore(issueStoreOptions('abc123'))
46
- const [issue] = store.useQuery(queryDb(tables.issue.select()))
47
- return <div>{issue?.title}</div>
48
- }
49
- ```
50
-
51
- ### `reference/framework-integrations/react/multi-store/schema.ts`
52
-
53
- ```ts filename="reference/framework-integrations/react/multi-store/schema.ts"
54
-
55
- // Event definitions
56
- export const events = {
57
- issueCreated: Events.synced({
58
- name: 'v1.IssueCreated',
59
- schema: Schema.Struct({
60
- id: Schema.String,
61
- title: Schema.String,
62
- status: Schema.Literal('todo', 'done'),
63
- }),
64
- }),
65
- issueStatusChanged: Events.synced({
66
- name: 'v1.IssueStatusChanged',
67
- schema: Schema.Struct({
68
- id: Schema.String,
69
- status: Schema.Literal('todo', 'done'),
70
- }),
71
- }),
72
- }
73
-
74
- // State definition
75
- export const tables = {
76
- issue: State.SQLite.table({
77
- name: 'issue',
78
- columns: {
79
- id: State.SQLite.text({ primaryKey: true }),
80
- title: State.SQLite.text(),
81
- status: State.SQLite.text(),
82
- },
83
- }),
84
- }
85
-
86
- const materializers = State.SQLite.materializers(events, {
87
- 'v1.IssueCreated': ({ id, title, status }) => tables.issue.insert({ id, title, status }),
88
- 'v1.IssueStatusChanged': ({ id, status }) => tables.issue.update({ status }).where({ id }),
89
- })
90
-
91
- const state = State.SQLite.makeState({ tables, materializers })
92
-
93
- export const schema = makeSchema({ events, state })
94
- ```
95
-
96
- ## Setting Up
97
-
98
- ### 1. Configure the Store
99
-
100
- Create a store configuration file that exports a custom hook wrapping [`useStore()`](#usestoreoptions):
101
-
102
- ## `reference/framework-integrations/react/store.ts`
103
-
104
- ```ts filename="reference/framework-integrations/react/store.ts"
105
-
106
- const adapter = makeInMemoryAdapter()
107
-
108
- export const useAppStore = () =>
109
- useStore({
110
- storeId: 'app-root',
111
- schema,
112
- adapter,
113
- batchUpdates,
114
- })
115
- ```
116
-
117
- ### `reference/framework-integrations/react/schema.ts`
118
-
119
- ```ts filename="reference/framework-integrations/react/schema.ts"
120
-
121
- export const tables = {
122
- todos: State.SQLite.table({
123
- name: 'todos',
124
- columns: {
125
- id: State.SQLite.text({ primaryKey: true }),
126
- text: State.SQLite.text(),
127
- completed: State.SQLite.boolean({ default: false }),
128
- },
129
- }),
130
- uiState: State.SQLite.clientDocument({
131
- name: 'UiState',
132
- schema: Schema.Struct({ text: Schema.String }),
133
- default: { value: { text: '' } },
134
- }),
135
- } as const
136
-
137
- export const events = {
138
- todoCreated: Events.synced({
139
- name: 'v1.TodoCreated',
140
- schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
141
- }),
142
- } as const
143
-
144
- const materializers = State.SQLite.materializers(events, {
145
- [events.todoCreated.name]: defineMaterializer(events.todoCreated, ({ id, text }) =>
146
- tables.todos.insert({ id, text, completed: false }),
147
- ),
148
- })
149
-
150
- const state = State.SQLite.makeState({ tables, materializers })
151
-
152
- export const schema = makeSchema({ events, state })
153
- ```
154
-
155
- The [`useStore()`](#usestoreoptions) hook accepts store configuration options and returns a store instance. It suspends while the store is loading, so components using it need to be wrapped in a `Suspense` boundary.
156
-
157
- ### 2. Set Up the Registry
158
-
159
- Create a [`StoreRegistry`](#new-storeregistryconfig) and provide it via [`<StoreRegistryProvider>`](#storeregistryprovider). Wrap in a `<Suspense>` to handle loading states and a `<ErrorBoundary>` to handle errors:
160
-
161
- ## `reference/framework-integrations/react/provider.tsx`
162
-
163
- ```tsx filename="reference/framework-integrations/react/provider.tsx"
164
-
165
- export const Root: FC<{ children: ReactNode }> = ({ children }) => {
166
- const [storeRegistry] = useState(() => new StoreRegistry())
167
-
168
- return (
169
- <ErrorBoundary fallback={<div>Something went wrong</div>}>
170
- <Suspense fallback={<div>Loading LiveStore...</div>}>
171
- <StoreRegistryProvider storeRegistry={storeRegistry}>{children}</StoreRegistryProvider>
172
- </Suspense>
173
- </ErrorBoundary>
174
- )
175
- }
176
- ```
177
-
178
- ### 3. Use the Store
179
-
180
- Components can now access the store via your custom hook:
181
-
182
- ## `reference/framework-integrations/react/use-store.tsx`
183
-
184
- ```tsx filename="reference/framework-integrations/react/use-store.tsx"
185
-
186
- export const MyComponent: FC = () => {
187
- const store = useAppStore()
188
-
189
- useEffect(() => {
190
- store.commit(events.todoCreated({ id: '1', text: 'Hello, world!' }))
191
- }, [store])
192
-
193
- return <div>...</div>
194
- }
195
- ```
196
-
197
- ### `reference/framework-integrations/react/schema.ts`
198
-
199
- ```ts filename="reference/framework-integrations/react/schema.ts"
200
-
201
- export const tables = {
202
- todos: State.SQLite.table({
203
- name: 'todos',
204
- columns: {
205
- id: State.SQLite.text({ primaryKey: true }),
206
- text: State.SQLite.text(),
207
- completed: State.SQLite.boolean({ default: false }),
208
- },
209
- }),
210
- uiState: State.SQLite.clientDocument({
211
- name: 'UiState',
212
- schema: Schema.Struct({ text: Schema.String }),
213
- default: { value: { text: '' } },
214
- }),
215
- } as const
216
-
217
- export const events = {
218
- todoCreated: Events.synced({
219
- name: 'v1.TodoCreated',
220
- schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
221
- }),
222
- } as const
223
-
224
- const materializers = State.SQLite.materializers(events, {
225
- [events.todoCreated.name]: defineMaterializer(events.todoCreated, ({ id, text }) =>
226
- tables.todos.insert({ id, text, completed: false }),
227
- ),
228
- })
229
-
230
- const state = State.SQLite.makeState({ tables, materializers })
231
-
232
- export const schema = makeSchema({ events, state })
233
- ```
234
-
235
- ### `reference/framework-integrations/react/store.ts`
236
-
237
- ```ts filename="reference/framework-integrations/react/store.ts"
238
-
239
- const adapter = makeInMemoryAdapter()
240
-
241
- export const useAppStore = () =>
242
- useStore({
243
- storeId: 'app-root',
244
- schema,
245
- adapter,
246
- batchUpdates,
247
- })
248
- ```
249
-
250
- ## Querying Data
251
-
252
- Use [`store.useQuery()`](#storeusequeryqueryable) to subscribe to reactive queries:
253
-
254
- ## `reference/framework-integrations/react/use-query.tsx`
255
-
256
- ```tsx filename="reference/framework-integrations/react/use-query.tsx"
257
-
258
- const query$ = queryDb(tables.todos.where({ completed: true }).orderBy('id', 'desc'), {
259
- label: 'completedTodos',
260
- })
261
-
262
- export const CompletedTodos: FC = () => {
263
- const store = useAppStore()
264
- const todos = store.useQuery(query$)
265
-
266
- return (
267
- <div>
268
- {todos.map((todo) => (
269
- <div key={todo.id}>{todo.text}</div>
270
- ))}
271
- </div>
272
- )
273
- }
274
- ```
275
-
276
- ### `reference/framework-integrations/react/schema.ts`
277
-
278
- ```ts filename="reference/framework-integrations/react/schema.ts"
279
-
280
- export const tables = {
281
- todos: State.SQLite.table({
282
- name: 'todos',
283
- columns: {
284
- id: State.SQLite.text({ primaryKey: true }),
285
- text: State.SQLite.text(),
286
- completed: State.SQLite.boolean({ default: false }),
287
- },
288
- }),
289
- uiState: State.SQLite.clientDocument({
290
- name: 'UiState',
291
- schema: Schema.Struct({ text: Schema.String }),
292
- default: { value: { text: '' } },
293
- }),
294
- } as const
295
-
296
- export const events = {
297
- todoCreated: Events.synced({
298
- name: 'v1.TodoCreated',
299
- schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
300
- }),
301
- } as const
302
-
303
- const materializers = State.SQLite.materializers(events, {
304
- [events.todoCreated.name]: defineMaterializer(events.todoCreated, ({ id, text }) =>
305
- tables.todos.insert({ id, text, completed: false }),
306
- ),
307
- })
308
-
309
- const state = State.SQLite.makeState({ tables, materializers })
310
-
311
- export const schema = makeSchema({ events, state })
312
- ```
313
-
314
- ### `reference/framework-integrations/react/store.ts`
315
-
316
- ```ts filename="reference/framework-integrations/react/store.ts"
317
-
318
- const adapter = makeInMemoryAdapter()
319
-
320
- export const useAppStore = () =>
321
- useStore({
322
- storeId: 'app-root',
323
- schema,
324
- adapter,
325
- batchUpdates,
326
- })
327
- ```
328
-
329
- ## Client Documents
330
-
331
- Use [`store.useClientDocument()`](#storeuseclientdocumenttable-id-options) for client-specific state:
332
-
333
- ## `reference/framework-integrations/react/use-client-document.tsx`
334
-
335
- ```tsx filename="reference/framework-integrations/react/use-client-document.tsx"
336
-
337
- export const TodoItem: FC<{ id: string }> = ({ id }) => {
338
- const store = useAppStore()
339
- const [todo, updateTodo] = store.useClientDocument(tables.uiState, id)
340
-
341
- return (
342
- <button type="button" onClick={() => updateTodo({ text: 'Hello, world!' })}>
343
- {todo.text}
344
- </button>
345
- )
346
- }
347
- ```
348
-
349
- ### `reference/framework-integrations/react/schema.ts`
350
-
351
- ```ts filename="reference/framework-integrations/react/schema.ts"
352
-
353
- export const tables = {
354
- todos: State.SQLite.table({
355
- name: 'todos',
356
- columns: {
357
- id: State.SQLite.text({ primaryKey: true }),
358
- text: State.SQLite.text(),
359
- completed: State.SQLite.boolean({ default: false }),
360
- },
361
- }),
362
- uiState: State.SQLite.clientDocument({
363
- name: 'UiState',
364
- schema: Schema.Struct({ text: Schema.String }),
365
- default: { value: { text: '' } },
366
- }),
367
- } as const
368
-
369
- export const events = {
370
- todoCreated: Events.synced({
371
- name: 'v1.TodoCreated',
372
- schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
373
- }),
374
- } as const
375
-
376
- const materializers = State.SQLite.materializers(events, {
377
- [events.todoCreated.name]: defineMaterializer(events.todoCreated, ({ id, text }) =>
378
- tables.todos.insert({ id, text, completed: false }),
379
- ),
380
- })
381
-
382
- const state = State.SQLite.makeState({ tables, materializers })
383
-
384
- export const schema = makeSchema({ events, state })
385
- ```
386
-
387
- ### `reference/framework-integrations/react/store.ts`
388
-
389
- ```ts filename="reference/framework-integrations/react/store.ts"
390
-
391
- const adapter = makeInMemoryAdapter()
392
-
393
- export const useAppStore = () =>
394
- useStore({
395
- storeId: 'app-root',
396
- schema,
397
- adapter,
398
- batchUpdates,
399
- })
400
- ```
401
-
402
- ## Advanced Patterns
403
-
404
- ### Multiple Stores
405
-
406
- You can have multiple stores within a single React application. This is useful for:
407
-
408
- - **Partial data synchronization** - Load only the data you need, when you need it
409
- - **Multi-tenant applications** - Separate stores for each workspace, organization, or team (like Slack workspaces or Linear teams)
410
-
411
- Use the [`storeOptions()`](#storeoptionsoptions) helper for type-safe, reusable configurations, and then create multiple instances for the same store configuration by using different `storeId` values:
412
-
413
- ## `reference/framework-integrations/react/multi-store/store.ts`
414
-
415
- ```ts filename="reference/framework-integrations/react/multi-store/store.ts"
416
-
417
- // Define reusable store configuration with storeOptions()
418
- // This helper provides type safety and can be reused across your app
419
- export const issueStoreOptions = (issueId: string) =>
420
- storeOptions({
421
- storeId: `issue-${issueId}`,
422
- schema,
423
- adapter: makeInMemoryAdapter(),
424
- })
425
- ```
426
-
427
- ### `reference/framework-integrations/react/multi-store/schema.ts`
428
-
429
- ```ts filename="reference/framework-integrations/react/multi-store/schema.ts"
430
-
431
- // Event definitions
432
- export const events = {
433
- issueCreated: Events.synced({
434
- name: 'v1.IssueCreated',
435
- schema: Schema.Struct({
436
- id: Schema.String,
437
- title: Schema.String,
438
- status: Schema.Literal('todo', 'done'),
439
- }),
440
- }),
441
- issueStatusChanged: Events.synced({
442
- name: 'v1.IssueStatusChanged',
443
- schema: Schema.Struct({
444
- id: Schema.String,
445
- status: Schema.Literal('todo', 'done'),
446
- }),
447
- }),
448
- }
449
-
450
- // State definition
451
- export const tables = {
452
- issue: State.SQLite.table({
453
- name: 'issue',
454
- columns: {
455
- id: State.SQLite.text({ primaryKey: true }),
456
- title: State.SQLite.text(),
457
- status: State.SQLite.text(),
458
- },
459
- }),
460
- }
461
-
462
- const materializers = State.SQLite.materializers(events, {
463
- 'v1.IssueCreated': ({ id, title, status }) => tables.issue.insert({ id, title, status }),
464
- 'v1.IssueStatusChanged': ({ id, status }) => tables.issue.update({ status }).where({ id }),
465
- })
466
-
467
- const state = State.SQLite.makeState({ tables, materializers })
468
-
469
- export const schema = makeSchema({ events, state })
470
- ```
471
-
472
- ## `reference/framework-integrations/react/multi-store/IssueView.tsx`
473
-
474
- ```tsx filename="reference/framework-integrations/react/multi-store/IssueView.tsx"
475
-
476
- export function IssueView({ issueId }: { issueId: string }) {
477
- // useStore() suspends the component until the store is loaded
478
- // If the same store was already loaded, it returns immediately
479
- const issueStore = useStore(issueStoreOptions(issueId))
480
-
481
- // Query data from the store
482
- const [issue] = issueStore.useQuery(queryDb(tables.issue.select().where({ id: issueId })))
483
-
484
- if (!issue) return <div>Issue not found</div>
485
-
486
- return (
487
- <div>
488
- <h3>{issue.title}</h3>
489
- <p>Status: {issue.status}</p>
490
- </div>
491
- )
492
- }
493
-
494
- // Wrap with Suspense and ErrorBoundary for loading and error states
495
- export function IssueViewWithSuspense({ issueId }: { issueId: string }) {
496
- return (
497
- <ErrorBoundary fallback={<div>Error loading issue</div>}>
498
- <Suspense fallback={<div>Loading issue...</div>}>
499
- <IssueView issueId={issueId} />
500
- </Suspense>
501
- </ErrorBoundary>
502
- )
503
- }
504
- ```
505
-
506
- ### `reference/framework-integrations/react/multi-store/schema.ts`
507
-
508
- ```ts filename="reference/framework-integrations/react/multi-store/schema.ts"
509
-
510
- // Event definitions
511
- export const events = {
512
- issueCreated: Events.synced({
513
- name: 'v1.IssueCreated',
514
- schema: Schema.Struct({
515
- id: Schema.String,
516
- title: Schema.String,
517
- status: Schema.Literal('todo', 'done'),
518
- }),
519
- }),
520
- issueStatusChanged: Events.synced({
521
- name: 'v1.IssueStatusChanged',
522
- schema: Schema.Struct({
523
- id: Schema.String,
524
- status: Schema.Literal('todo', 'done'),
525
- }),
526
- }),
527
- }
528
-
529
- // State definition
530
- export const tables = {
531
- issue: State.SQLite.table({
532
- name: 'issue',
533
- columns: {
534
- id: State.SQLite.text({ primaryKey: true }),
535
- title: State.SQLite.text(),
536
- status: State.SQLite.text(),
537
- },
538
- }),
539
- }
540
-
541
- const materializers = State.SQLite.materializers(events, {
542
- 'v1.IssueCreated': ({ id, title, status }) => tables.issue.insert({ id, title, status }),
543
- 'v1.IssueStatusChanged': ({ id, status }) => tables.issue.update({ status }).where({ id }),
544
- })
545
-
546
- const state = State.SQLite.makeState({ tables, materializers })
547
-
548
- export const schema = makeSchema({ events, state })
549
- ```
550
-
551
- ### `reference/framework-integrations/react/multi-store/store.ts`
552
-
553
- ```ts filename="reference/framework-integrations/react/multi-store/store.ts"
554
-
555
- // Define reusable store configuration with storeOptions()
556
- // This helper provides type safety and can be reused across your app
557
- export const issueStoreOptions = (issueId: string) =>
558
- storeOptions({
559
- storeId: `issue-${issueId}`,
560
- schema,
561
- adapter: makeInMemoryAdapter(),
562
- })
563
- ```
564
-
565
- Each store instance is completely isolated with its own data, event log, and synchronization state.
566
-
567
- ### Preloading Stores
568
-
569
- When you know a store will be needed soon, preload it in advance to warm up the cache:
570
-
571
- ## `reference/framework-integrations/react/multi-store/PreloadedIssue.tsx`
572
-
573
- ```tsx filename="reference/framework-integrations/react/multi-store/PreloadedIssue.tsx"
574
-
575
- export function PreloadedIssue({ issueId }: { issueId: string }) {
576
- const [showIssue, setShowIssue] = useState(false)
577
- const storeRegistry = useStoreRegistry()
578
-
579
- // Preload the store when the user hovers (before they click)
580
- const handleMouseEnter = () => {
581
- storeRegistry.preload({
582
- ...issueStoreOptions(issueId),
583
- unusedCacheTime: 10_000, // Optionally override options
584
- })
585
- }
586
-
587
- return (
588
- <div>
589
- {!showIssue ? (
590
- <button type="button" onMouseEnter={handleMouseEnter} onClick={() => setShowIssue(true)}>
591
- Show Issue
592
- </button>
593
- ) : (
594
- <ErrorBoundary fallback={<div>Error loading issue</div>}>
595
- <Suspense fallback={<div>Loading issue...</div>}>
596
- <IssueView issueId={issueId} />
597
- </Suspense>
598
- </ErrorBoundary>
599
- )}
600
- </div>
601
- )
602
- }
603
- ```
604
-
605
- ### `reference/framework-integrations/react/multi-store/IssueView.tsx`
606
-
607
- ```tsx filename="reference/framework-integrations/react/multi-store/IssueView.tsx"
608
-
609
- export function IssueView({ issueId }: { issueId: string }) {
610
- // useStore() suspends the component until the store is loaded
611
- // If the same store was already loaded, it returns immediately
612
- const issueStore = useStore(issueStoreOptions(issueId))
613
-
614
- // Query data from the store
615
- const [issue] = issueStore.useQuery(queryDb(tables.issue.select().where({ id: issueId })))
616
-
617
- if (!issue) return <div>Issue not found</div>
618
-
619
- return (
620
- <div>
621
- <h3>{issue.title}</h3>
622
- <p>Status: {issue.status}</p>
623
- </div>
624
- )
625
- }
626
-
627
- // Wrap with Suspense and ErrorBoundary for loading and error states
628
- export function IssueViewWithSuspense({ issueId }: { issueId: string }) {
629
- return (
630
- <ErrorBoundary fallback={<div>Error loading issue</div>}>
631
- <Suspense fallback={<div>Loading issue...</div>}>
632
- <IssueView issueId={issueId} />
633
- </Suspense>
634
- </ErrorBoundary>
635
- )
636
- }
637
- ```
638
-
639
- ### `reference/framework-integrations/react/multi-store/schema.ts`
640
-
641
- ```ts filename="reference/framework-integrations/react/multi-store/schema.ts"
642
-
643
- // Event definitions
644
- export const events = {
645
- issueCreated: Events.synced({
646
- name: 'v1.IssueCreated',
647
- schema: Schema.Struct({
648
- id: Schema.String,
649
- title: Schema.String,
650
- status: Schema.Literal('todo', 'done'),
651
- }),
652
- }),
653
- issueStatusChanged: Events.synced({
654
- name: 'v1.IssueStatusChanged',
655
- schema: Schema.Struct({
656
- id: Schema.String,
657
- status: Schema.Literal('todo', 'done'),
658
- }),
659
- }),
660
- }
661
-
662
- // State definition
663
- export const tables = {
664
- issue: State.SQLite.table({
665
- name: 'issue',
666
- columns: {
667
- id: State.SQLite.text({ primaryKey: true }),
668
- title: State.SQLite.text(),
669
- status: State.SQLite.text(),
670
- },
671
- }),
672
- }
673
-
674
- const materializers = State.SQLite.materializers(events, {
675
- 'v1.IssueCreated': ({ id, title, status }) => tables.issue.insert({ id, title, status }),
676
- 'v1.IssueStatusChanged': ({ id, status }) => tables.issue.update({ status }).where({ id }),
677
- })
678
-
679
- const state = State.SQLite.makeState({ tables, materializers })
680
-
681
- export const schema = makeSchema({ events, state })
682
- ```
683
-
684
- ### `reference/framework-integrations/react/multi-store/store.ts`
685
-
686
- ```ts filename="reference/framework-integrations/react/multi-store/store.ts"
687
-
688
- // Define reusable store configuration with storeOptions()
689
- // This helper provides type safety and can be reused across your app
690
- export const issueStoreOptions = (issueId: string) =>
691
- storeOptions({
692
- storeId: `issue-${issueId}`,
693
- schema,
694
- adapter: makeInMemoryAdapter(),
695
- })
696
- ```
697
-
698
- ### StoreId Guidelines
699
-
700
- When creating `storeId` values:
701
-
702
- - **Valid characters** - Only alphanumeric characters, underscores (`_`), and hyphens (`-`) are allowed (regex: `/^[a-zA-Z0-9_-]+$/`)
703
- - **Globally unique** - Prefer globally unique IDs (e.g., nanoid) to prevent collisions
704
- - **Use namespaces** - Prefix with the entity type (e.g., `workspace-abc123`, `issue-456`) to avoid collisions and easier identification when debugging
705
- - **Keep them stable** - The same entity should always use the same `storeId` across renders
706
- - **Sanitize user input** - If incorporating user data, validate/sanitize to prevent injection attacks
707
- - **Document your conventions** - Document special IDs like `user-current` as they're part of your API contract
708
-
709
- ### Logging
710
-
711
- You can customize the logger and log level for debugging:
712
-
713
- ## `reference/framework-integrations/react/store-with-logging.ts`
714
-
715
- ```ts filename="reference/framework-integrations/react/store-with-logging.ts"
716
-
717
- const adapter = makeInMemoryAdapter()
718
-
719
- // ---cut---
720
- export const useAppStore = () =>
721
- useStore({
722
- storeId: 'app-root',
723
- schema,
724
- adapter,
725
- batchUpdates,
726
- // Optional: swap the logger implementation
727
- logger: Logger.prettyWithThread('app'),
728
- // Optional: set minimum log level (use LogLevel.None to disable)
729
- logLevel: LogLevel.Info,
730
- })
731
- ```
732
-
733
- ### `reference/framework-integrations/react/schema.ts`
734
-
735
- ```ts filename="reference/framework-integrations/react/schema.ts"
736
-
737
- export const tables = {
738
- todos: State.SQLite.table({
739
- name: 'todos',
740
- columns: {
741
- id: State.SQLite.text({ primaryKey: true }),
742
- text: State.SQLite.text(),
743
- completed: State.SQLite.boolean({ default: false }),
744
- },
745
- }),
746
- uiState: State.SQLite.clientDocument({
747
- name: 'UiState',
748
- schema: Schema.Struct({ text: Schema.String }),
749
- default: { value: { text: '' } },
750
- }),
751
- } as const
752
-
753
- export const events = {
754
- todoCreated: Events.synced({
755
- name: 'v1.TodoCreated',
756
- schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
757
- }),
758
- } as const
759
-
760
- const materializers = State.SQLite.materializers(events, {
761
- [events.todoCreated.name]: defineMaterializer(events.todoCreated, ({ id, text }) =>
762
- tables.todos.insert({ id, text, completed: false }),
763
- ),
764
- })
765
-
766
- const state = State.SQLite.makeState({ tables, materializers })
767
-
768
- export const schema = makeSchema({ events, state })
769
- ```
770
-
771
- Use `LogLevel.None` to disable logging entirely.
772
-
773
- ## API Reference
774
-
775
- ### `storeOptions(options)`
776
-
777
- Helper for defining reusable store options with full type inference. Returns options that can be passed to [`useStore()`](#usestoreoptions) or [`storeRegistry.preload()`](#storeregistrypreloadoptions).
778
-
779
- Options:
780
- - `storeId` - Unique identifier for this store instance
781
- - `schema` - The LiveStore schema
782
- - `adapter` - The platform adapter
783
- - `unusedCacheTime?` - Time in ms to keep unused stores in cache (default: `60_000` in browser, `Infinity` in non-browser environments)
784
- - `batchUpdates?` - Function for batching React updates (recommended)
785
- - `boot?` - Function called when the store is loaded
786
- - `onBootStatus?` - Callback for boot status updates
787
- - `context?` - User-defined context for dependency injection
788
- - `syncPayload?` - Payload sent to sync backend (e.g., auth tokens)
789
- - `syncPayloadSchema?` - Schema for type-safe sync payload validation
790
- - `confirmUnsavedChanges?` - Register beforeunload handler (default: `true`, web only)
791
- - `logger?` - Custom logger implementation
792
- - `logLevel?` - Log level (e.g., `LogLevel.Info`, `LogLevel.Debug`, `LogLevel.None`)
793
- - `otelOptions?` - OpenTelemetry configuration (`{ tracer, rootSpanContext }`)
794
- - `disableDevtools?` - Whether to disable devtools (`boolean | 'auto'`, default: `'auto'`)
795
- - `debug?` - Debug options (`{ instanceId?: string }`)
796
-
797
- ### `useStore(options)`
798
-
799
- Returns a store instance augmented with hooks ([`store.useQuery()`](#storeusequeryqueryable) and [`store.useClientDocument()`](#storeuseclientdocumenttable-id-options)) for reactive queries.
800
-
801
- - Suspends until the store is loaded.
802
- - Throws an error if loading fails.
803
- - Store gets cached by its `storeId` in the [`StoreRegistry`](#new-storeregistryconfig). Multiple calls with the same `storeId` return the same store instance.
804
- - Store is cached as long as it's being used, and after `unusedCacheTime` expires (default `60_000` ms in browser, `Infinity` in non-browser)
805
- - Default store options can be configured in [`StoreRegistry`](#new-storeregistryconfig) constructor.
806
- - Store options are only applied when the store is loaded. Subsequent calls with different options will not affect the store if it's already loaded and cached in the registry.
807
-
808
- ### `store.commit(...events)` / `store.commit(txnFn)`
809
-
810
- Commits events to the store. Supports multiple calling patterns:
811
-
812
- - `store.commit(...events)` - Commit one or more events
813
- - `store.commit(txnFn)` - Commit events via a transaction function
814
- - `store.commit(options, ...events)` - Commit with options
815
- - `store.commit(options, txnFn)` - Options with transaction function
816
-
817
- Options:
818
- - `skipRefresh` - Skip refreshing reactive queries after commit (advanced)
819
-
820
- ### `store.useQuery(queryable)`
821
-
822
- Subscribes to a reactive query. Re-renders the component when the result changes.
823
-
824
- - Takes any `Queryable`: `QueryBuilder`, `LiveQueryDef`, `SignalDef`, or `LiveQuery` instance
825
- - Returns the query result
826
-
827
- ### `store.useClientDocument(table, id?, options?)`
828
-
829
- React.useState-like hook for client-document tables.
830
-
831
- - Returns `[row, setRow, id, query$]` tuple
832
- - Works only with tables defined via `State.SQLite.clientDocument()`
833
- - If the table has a default id, the `id` argument is optional
834
-
835
- ### `new StoreRegistry(config?)`
836
-
837
- Creates a registry that coordinates store loading, caching, and retention.
838
-
839
- Config:
840
- - `defaultOptions?` - Default options that are applied to all stores when they are loaded.:
841
- - `batchUpdates?` - Function for batching React updates
842
- - `unusedCacheTime?` - Cache time for unused stores
843
- - `disableDevtools?` - Whether to disable devtools
844
- - `confirmUnsavedChanges?` - beforeunload confirmation
845
- - `otelOptions?` - OpenTelemetry configuration
846
- - `debug?` - Debug options
847
- - `runtime?` - Effect runtime for registry operations
848
-
849
- ### `<StoreRegistryProvider>`
850
-
851
- React context provider that makes a [`StoreRegistry`](#new-storeregistryconfig) available to descendant components.
852
-
853
- Props:
854
- - `storeRegistry` - The registry instance
855
-
856
- ### `useStoreRegistry(override?)`
857
-
858
- Hook that returns the [`StoreRegistry`](#new-storeregistryconfig) provided by the nearest [`<StoreRegistryProvider>`](#storeregistryprovider) ancestor, or the `override` if provided.
859
-
860
- ### `storeRegistry.preload(options)`
861
-
862
- Loads a store (without suspending) to warm up the cache. Returns a Promise that resolves when loading completes. This is a fire-and-forget operation useful for warming up the cache.
863
-
864
- ## Framework-Specific Notes
865
-
866
- ### Vite
867
-
868
- LiveStore works with Vite out of the box.
869
-
870
- ### Tanstack Start
871
-
872
- LiveStore works with Tanstack Start out of the box.
873
-
874
- #### Provider Placement
875
-
876
- When using LiveStore with TanStack Start, place [`<StoreRegistryProvider>`](#storeregistryprovider) in the correct location to avoid remounting on navigation.
877
-
878
- :::caution
879
- **Do NOT place `<StoreRegistryProvider>` inside `shellComponent`**. The `shellComponent` can be re-rendered on navigation, causing LiveStore to remount and show the loading screen on every page transition.
880
- :::
881
-
882
- Use the `component` prop on `createRootRoute` for `<StoreRegistryProvider>`:
883
-
884
- ```tsx
885
-
886
- export const Route = createRootRoute({
887
- shellComponent: RootShell, // HTML structure only - NO state or providers
888
- component: RootComponent, // App shell - StoreRegistryProvider goes HERE
889
- })
890
-
891
- // HTML document shell - keep this stateless
892
- function RootShell({ children }: { children: React.ReactNode }) {
893
- return (
894
- <html lang="en">
895
- <head><HeadContent /></head>
896
- <body>
897
- {children}
898
- <Scripts />
899
- </body>
900
- </html>
901
- )
902
- }
903
-
904
- // App shell - persists across SPA navigation
905
- function RootComponent() {
906
- const [storeRegistry] = useState(() => new StoreRegistry())
907
-
908
- return (
909
- <Suspense fallback={<div>Loading LiveStore...</div>}>
910
- <StoreRegistryProvider storeRegistry={storeRegistry}>
911
- <Outlet />
912
- </StoreRegistryProvider>
913
- </Suspense>
914
- )
915
- }
916
- ```
917
-
918
- TanStack Start's `shellComponent` is designed for SSR HTML streaming and may be re-evaluated on server requests during navigation. When `<StoreRegistryProvider>` is placed there, the WebSocket connection is re-established and all LiveStore state is re-initialized on each navigation.
919
-
920
- If you see the loading screen on every navigation, check your server logs for multiple "Launching WebSocket" messages.
921
-
922
- ### Expo / React Native
923
-
924
- LiveStore has a first-class integration with Expo / React Native via `@livestore/adapter-expo`. See the [Expo Adapter documentation](/platform-adapters/expo-adapter).
925
-
926
- ### Next.js
927
-
928
- Given various Next.js limitations, LiveStore doesn't yet work with Next.js out of the box.
929
-
930
- ### Complete Example
931
-
932
- See the <a href={`https://github.com/livestorejs/livestore/tree/${getBranchName()}/examples/web-multi-store`}>Multi-Store example</a> for a complete working application demonstrating various multi-store patterns.
933
-
934
- ## Technical Notes
935
-
936
- - `@livestore/react` uses `React.useState()` under the hood for `useQuery()` / `useClientDocument()` to bind LiveStore's reactivity to React's reactivity. Some libraries use `React.useSyncExternalStore()` for similar purposes but `React.useState()` is more efficient for LiveStore's architecture.
937
- - `@livestore/react` supports React Strict Mode.