@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,300 +0,0 @@
1
- # Materializers
2
-
3
- Materializers are functions that allow you to write to your database in response to events. Materializers are executed in the order of the events in the eventlog.
4
-
5
- ## Example
6
-
7
- ## `reference/state/materializers/example.ts`
8
-
9
- ```ts filename="reference/state/materializers/example.ts"
10
-
11
- export const todos = State.SQLite.table({
12
- name: 'todos',
13
- columns: {
14
- id: State.SQLite.text({ primaryKey: true }),
15
- text: State.SQLite.text(),
16
- completed: State.SQLite.boolean({ default: false }),
17
- previousIds: State.SQLite.json({
18
- schema: Schema.Array(Schema.String),
19
- nullable: true,
20
- }),
21
- },
22
- })
23
-
24
- export const table1 = State.SQLite.table({
25
- name: 'settings',
26
- columns: {
27
- id: State.SQLite.text({ primaryKey: true }),
28
- someVal: State.SQLite.integer({ default: 0 }),
29
- },
30
- })
31
-
32
- export const table2 = State.SQLite.table({
33
- name: 'preferences',
34
- columns: {
35
- id: State.SQLite.text({ primaryKey: true }),
36
- otherVal: State.SQLite.text({ default: 'default' }),
37
- },
38
- })
39
-
40
- export const events = {
41
- todoCreated: Events.synced({
42
- name: 'todoCreated',
43
- schema: Schema.Struct({
44
- id: Schema.String,
45
- text: Schema.String,
46
- completed: Schema.Boolean.pipe(Schema.optional),
47
- }),
48
- }),
49
- userPreferencesUpdated: Events.synced({
50
- name: 'userPreferencesUpdated',
51
- schema: Schema.Struct({ userId: Schema.String, theme: Schema.String }),
52
- }),
53
- factoryResetApplied: Events.synced({
54
- name: 'factoryResetApplied',
55
- schema: Schema.Struct({}),
56
- }),
57
- } as const
58
-
59
- export const materializers = State.SQLite.materializers(events, {
60
- [events.todoCreated.name]: defineMaterializer(events.todoCreated, ({ id, text, completed }) =>
61
- todos.insert({ id, text, completed: completed ?? false }),
62
- ),
63
- [events.userPreferencesUpdated.name]: defineMaterializer(events.userPreferencesUpdated, ({ userId, theme }) => {
64
- console.log(`User ${userId} updated theme to ${theme}.`)
65
- return []
66
- }),
67
- [events.factoryResetApplied.name]: defineMaterializer(events.factoryResetApplied, () => [
68
- table1.update({ someVal: 0 }),
69
- table2.update({ otherVal: 'default' }),
70
- ]),
71
- })
72
- ```
73
-
74
- ## Reading from the database in materializers
75
-
76
- Sometimes it can be useful to query your current state when executing a materializer. This can be done by using `ctx.query` in your materializer function.
77
-
78
- ## `reference/state/materializers/with-query.ts`
79
-
80
- ```ts filename="reference/state/materializers/with-query.ts"
81
-
82
- const events = {
83
- todoCreated: Events.synced({
84
- name: 'todoCreated',
85
- schema: Schema.Struct({
86
- id: Schema.String,
87
- text: Schema.String,
88
- completed: Schema.Boolean.pipe(Schema.optional),
89
- }),
90
- }),
91
- } as const
92
-
93
- export const materializers = State.SQLite.materializers(events, {
94
- [events.todoCreated.name]: defineMaterializer(events.todoCreated, ({ id, text, completed }, ctx) => {
95
- const previousIds = ctx.query(todos.select('id'))
96
- return todos.insert({ id, text, completed: completed ?? false, previousIds })
97
- }),
98
- })
99
- ```
100
-
101
- ### `reference/state/materializers/example.ts`
102
-
103
- ```ts filename="reference/state/materializers/example.ts"
104
-
105
- export const todos = State.SQLite.table({
106
- name: 'todos',
107
- columns: {
108
- id: State.SQLite.text({ primaryKey: true }),
109
- text: State.SQLite.text(),
110
- completed: State.SQLite.boolean({ default: false }),
111
- previousIds: State.SQLite.json({
112
- schema: Schema.Array(Schema.String),
113
- nullable: true,
114
- }),
115
- },
116
- })
117
-
118
- export const table1 = State.SQLite.table({
119
- name: 'settings',
120
- columns: {
121
- id: State.SQLite.text({ primaryKey: true }),
122
- someVal: State.SQLite.integer({ default: 0 }),
123
- },
124
- })
125
-
126
- export const table2 = State.SQLite.table({
127
- name: 'preferences',
128
- columns: {
129
- id: State.SQLite.text({ primaryKey: true }),
130
- otherVal: State.SQLite.text({ default: 'default' }),
131
- },
132
- })
133
-
134
- export const events = {
135
- todoCreated: Events.synced({
136
- name: 'todoCreated',
137
- schema: Schema.Struct({
138
- id: Schema.String,
139
- text: Schema.String,
140
- completed: Schema.Boolean.pipe(Schema.optional),
141
- }),
142
- }),
143
- userPreferencesUpdated: Events.synced({
144
- name: 'userPreferencesUpdated',
145
- schema: Schema.Struct({ userId: Schema.String, theme: Schema.String }),
146
- }),
147
- factoryResetApplied: Events.synced({
148
- name: 'factoryResetApplied',
149
- schema: Schema.Struct({}),
150
- }),
151
- } as const
152
-
153
- export const materializers = State.SQLite.materializers(events, {
154
- [events.todoCreated.name]: defineMaterializer(events.todoCreated, ({ id, text, completed }) =>
155
- todos.insert({ id, text, completed: completed ?? false }),
156
- ),
157
- [events.userPreferencesUpdated.name]: defineMaterializer(events.userPreferencesUpdated, ({ userId, theme }) => {
158
- console.log(`User ${userId} updated theme to ${theme}.`)
159
- return []
160
- }),
161
- [events.factoryResetApplied.name]: defineMaterializer(events.factoryResetApplied, () => [
162
- table1.update({ someVal: 0 }),
163
- table2.update({ otherVal: 'default' }),
164
- ]),
165
- })
166
- ```
167
-
168
- ## Transactional behaviour
169
-
170
- A materializer is always executed in a transaction. This transaction applies to:
171
- - All database write operations returned by the materializer.
172
- - Any `ctx.query` calls made within the materializer, ensuring a consistent view of the data.
173
-
174
- Materializers can return:
175
- - A single database write operation.
176
- - An array of database write operations.
177
- - `void` (i.e., no return value) if no database modifications are needed.
178
- - An `Effect` that resolves to one of the above (e.g., `Effect.succeed(writeOp)` or `Effect.void`).
179
-
180
- The `context` object passed to each materializer provides `query` for database reads, `db` for direct database access if needed, and `event` for the full event details.
181
-
182
- ## Error handling
183
-
184
- If a materializer function throws an error, or if an `Effect` returned by a materializer fails, the entire transaction for that event will be rolled back. This means any database changes attempted by that materializer for the failing event will not be persisted. The error will be logged, and the system will typically halt or flag the event as problematic, depending on the specific LiveStore setup.
185
-
186
- If the error happens on the client which tries to commit the event, the event will never be committed and pushed to the sync backend.
187
-
188
- In the future there will be ways to configure the error-handling behaviour, e.g. to allow skipping an incoming event when a materializer fails in order to avoid the app getting stuck. However, skipping events might also lead to diverging state across clients and should be used with caution.
189
-
190
- ## Best practices
191
-
192
- ### Side-effect free / deterministic
193
-
194
- It's strongly recommended to make sure your materializers are side-effect free and deterministic. This also implies passing in all necessary data via the event payload.
195
-
196
- Example:
197
-
198
- ## `reference/state/materializers/deterministic.ts`
199
-
200
- ```ts filename="reference/state/materializers/deterministic.ts"
201
-
202
- declare const store: Store
203
-
204
- export const nondeterministicEvents = {
205
- todoCreated: Events.synced({
206
- name: 'v1.TodoCreated',
207
- schema: Schema.Struct({ text: Schema.String }),
208
- }),
209
- } as const
210
-
211
- export const nondeterministicMaterializers = State.SQLite.materializers(nondeterministicEvents, {
212
- [nondeterministicEvents.todoCreated.name]: defineMaterializer(nondeterministicEvents.todoCreated, ({ text }) =>
213
- todos.insert({ id: randomUUID(), text }),
214
- ),
215
- })
216
-
217
- store.commit(nondeterministicEvents.todoCreated({ text: 'Buy groceries' }))
218
-
219
- export const deterministicEvents = {
220
- todoCreated: Events.synced({
221
- name: 'v1.TodoCreated',
222
- schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
223
- }),
224
- } as const
225
-
226
- export const deterministicMaterializers = State.SQLite.materializers(deterministicEvents, {
227
- [deterministicEvents.todoCreated.name]: defineMaterializer(deterministicEvents.todoCreated, ({ id, text }) =>
228
- todos.insert({ id, text }),
229
- ),
230
- })
231
-
232
- store.commit(deterministicEvents.todoCreated({ id: nanoid(), text: 'Buy groceries' }))
233
- ```
234
-
235
- ### `reference/state/materializers/example.ts`
236
-
237
- ```ts filename="reference/state/materializers/example.ts"
238
-
239
- export const todos = State.SQLite.table({
240
- name: 'todos',
241
- columns: {
242
- id: State.SQLite.text({ primaryKey: true }),
243
- text: State.SQLite.text(),
244
- completed: State.SQLite.boolean({ default: false }),
245
- previousIds: State.SQLite.json({
246
- schema: Schema.Array(Schema.String),
247
- nullable: true,
248
- }),
249
- },
250
- })
251
-
252
- export const table1 = State.SQLite.table({
253
- name: 'settings',
254
- columns: {
255
- id: State.SQLite.text({ primaryKey: true }),
256
- someVal: State.SQLite.integer({ default: 0 }),
257
- },
258
- })
259
-
260
- export const table2 = State.SQLite.table({
261
- name: 'preferences',
262
- columns: {
263
- id: State.SQLite.text({ primaryKey: true }),
264
- otherVal: State.SQLite.text({ default: 'default' }),
265
- },
266
- })
267
-
268
- export const events = {
269
- todoCreated: Events.synced({
270
- name: 'todoCreated',
271
- schema: Schema.Struct({
272
- id: Schema.String,
273
- text: Schema.String,
274
- completed: Schema.Boolean.pipe(Schema.optional),
275
- }),
276
- }),
277
- userPreferencesUpdated: Events.synced({
278
- name: 'userPreferencesUpdated',
279
- schema: Schema.Struct({ userId: Schema.String, theme: Schema.String }),
280
- }),
281
- factoryResetApplied: Events.synced({
282
- name: 'factoryResetApplied',
283
- schema: Schema.Struct({}),
284
- }),
285
- } as const
286
-
287
- export const materializers = State.SQLite.materializers(events, {
288
- [events.todoCreated.name]: defineMaterializer(events.todoCreated, ({ id, text, completed }) =>
289
- todos.insert({ id, text, completed: completed ?? false }),
290
- ),
291
- [events.userPreferencesUpdated.name]: defineMaterializer(events.userPreferencesUpdated, ({ userId, theme }) => {
292
- console.log(`User ${userId} updated theme to ${theme}.`)
293
- return []
294
- }),
295
- [events.factoryResetApplied.name]: defineMaterializer(events.factoryResetApplied, () => [
296
- table1.update({ someVal: 0 }),
297
- table2.update({ otherVal: 'default' }),
298
- ]),
299
- })
300
- ```
@@ -1,94 +0,0 @@
1
- # SQL queries
2
-
3
- ## Query builder
4
-
5
- LiveStore also provides a small query builder for the most common queries. The query builder automatically derives the appropriate result schema internally.
6
-
7
- ## `reference/state/sql-queries/query-builder.ts`
8
-
9
- ```ts filename="reference/state/sql-queries/query-builder.ts"
10
-
11
- const table = State.SQLite.table({
12
- name: 'my_table',
13
- columns: {
14
- id: State.SQLite.text({ primaryKey: true }),
15
- name: State.SQLite.text(),
16
- tags: State.SQLite.json({ schema: Schema.Array(Schema.String), default: [] }),
17
- },
18
- })
19
-
20
- // Read queries
21
- table.select('name')
22
- table.where('name', '=', 'Alice')
23
- table.where({ name: 'Alice' })
24
- table.orderBy('name', 'desc').offset(10).limit(10)
25
- table.count().where('name', 'LIKE', '%Ali%')
26
-
27
- // JSON array containment queries
28
- // NOTE: These use SQLite's json_each() which cannot be indexed
29
- table.where({ tags: { op: 'JSON_CONTAINS', value: 'important' } })
30
- table.where({ tags: { op: 'JSON_NOT_CONTAINS', value: 'archived' } })
31
-
32
- // Write queries
33
- table.insert({ id: '123', name: 'Bob' })
34
- table.update({ name: 'Alice' }).where({ id: '123' })
35
- table.delete().where({ id: '123' })
36
-
37
- // Upserts (insert or update on conflict)
38
- table.insert({ id: '123', name: 'Charlie' }).onConflict('id', 'replace')
39
- table.insert({ id: '456', name: 'Diana' }).onConflict('id', 'update', { name: 'Diana Updated' })
40
- ```
41
-
42
- ## Raw SQL queries
43
-
44
- LiveStore supports arbitrary SQL queries on top of SQLite. In order for LiveStore to handle the query results correctly, you need to provide the result schema.
45
-
46
- ## `reference/state/sql-queries/raw-sql.ts`
47
-
48
- ```ts filename="reference/state/sql-queries/raw-sql.ts"
49
- /** biome-ignore-all lint/correctness/noUnusedVariables: docs snippet keeps reactive references */
50
- // ---cut---
51
-
52
- const table = State.SQLite.table({
53
- name: 'my_table',
54
- columns: {
55
- id: State.SQLite.text({ primaryKey: true }),
56
- name: State.SQLite.text(),
57
- },
58
- })
59
-
60
- const filtered$ = queryDb({
61
- query: sql`select * from my_table where name = 'Alice'`,
62
- schema: Schema.Array(table.rowSchema),
63
- })
64
-
65
- const count$ = queryDb({
66
- query: sql`select count(*) as count from my_table`,
67
- schema: Schema.Struct({ count: Schema.Number }).pipe(Schema.pluck('count'), Schema.Array, Schema.headOrElse()),
68
- })
69
- ```
70
-
71
- ## JSON array containment
72
-
73
- For JSON array columns, you can use `JSON_CONTAINS` and `JSON_NOT_CONTAINS` operators to check if an array contains (or doesn't contain) a specific value:
74
-
75
- ```ts
76
- // Find items with a specific tag
77
- table.where({ tags: { op: 'JSON_CONTAINS', value: 'important' } })
78
-
79
- // Find items without a specific tag
80
- table.where({ tags: { op: 'JSON_NOT_CONTAINS', value: 'archived' } })
81
- ```
82
-
83
- :::caution[Performance consideration]
84
- These operators use SQLite's `json_each()` table-valued function which **cannot be indexed** and requires a full table scan. For large tables with frequent lookups, consider denormalizing the data into a separate indexed table.
85
- :::
86
-
87
- ## Best practices
88
-
89
- - Query results should be treated as immutable/read-only
90
- - For queries which could return many rows, it's recommended to paginate the results
91
- - Usually both via paginated/virtualized rendering as well as paginated queries
92
- - You'll get best query performance by using a `WHERE` clause over an indexed column combined with a `LIMIT` clause. Avoid `OFFSET` as it can be slow on large tables
93
- - For very large/complex queries, it can also make sense to implement incremental view maintenance (IVM) for your queries
94
- - You can for example do this by have a separate table which is a materialized version of your query results which you update manually (and ideally incrementally) as the underlying data changes.
@@ -1,45 +0,0 @@
1
- # SQLite in LiveStore
2
-
3
- LiveStore heavily uses SQLite as its default state/read model.
4
-
5
- ## Implementation notes
6
-
7
- - LiveStore relies on the following SQLite extensions to be available: `-DSQLITE_ENABLE_BYTECODE_VTAB -DSQLITE_ENABLE_SESSION -DSQLITE_ENABLE_PREUPDATE_HOOK`
8
- - [bytecode](https://www.sqlite.org/bytecodevtab.html)
9
- - [session](https://www.sqlite.org/sessionintro.html) (incl. preupdate)
10
-
11
- - For web / node adapter:
12
- - LiveStore uses [a fork](https://github.com/livestorejs/wa-sqlite) of the [wa-sqlite](https://github.com/rhashimoto/wa-sqlite) SQLite WASM library.
13
- - Write‑ahead logging (WAL) is currently not supported/enabled for the web adapter using OPFS (AccessHandlePoolVFS). The underlying VFS does not support WAL reliably in this setup; we disable it until it’s safe to use. See our tracking issue and upstream notes:
14
- - LiveStore: https://github.com/livestorejs/livestore/issues/258
15
- - wa‑sqlite examples (comparison table shows WAL unsupported for AccessHandlePoolVFS): https://github.com/rhashimoto/wa-sqlite/blob/master/src/examples/README.md
16
- - Related discussion on single‑connection OPFS and locking: https://github.com/rhashimoto/wa-sqlite/discussions/81
17
- - In the future LiveStore might use a non‑WASM build for Node/Bun/Deno/etc.
18
- - For Expo adapter:
19
- - LiveStore uses the official expo-sqlite library which supports LiveStore's SQLite requirements.
20
-
21
- - LiveStore uses the `session` extension to enable efficient database rollback which is needed when the eventlog is rolled back as part of a rebase. An alternative implementation strategy would be to rely on snapshotting (i.e. periodically create database snapshots and roll back to the latest snapshot + applied missing mutations).
22
-
23
- ## Default tables
24
-
25
- LiveStore operates two SQLite databases by default: a state database (your materialized tables) and an event log database (the immutable event stream and sync metadata). In addition to your own application tables, LiveStore creates a small set of internal tables in each database.
26
-
27
- ### State database
28
-
29
- - `__livestore_schema`
30
- - Tracks the schema hash and last update time per materialized table. Used for migrations and compatibility checks.
31
- - `__livestore_schema_event_defs`
32
- - Tracks the schema hash and last update time per event definition. Used to detect incompatible event schema changes during rematerialization.
33
- - `__livestore_session_changeset`
34
- - Stores SQLite session changeset blobs keyed by event sequence numbers. Used to efficiently roll back and re‑apply state during rebases.
35
- - Your application tables
36
- - All tables you define via `State.SQLite.table(...)` live in the state database.
37
-
38
- ### Eventlog database
39
-
40
- - `eventlog`
41
- - Append‑only table containing all events (sequence numbers, parent links, event name, encoded args, client/session IDs, schema hash, optional sync metadata). Used to reconstruct state and for sync.
42
- - `__livestore_sync_status`
43
- - Stores the current head and optional backend identity for synchronization bookkeeping.
44
-
45
- Note: The event log database’s use of SQLite is an implementation detail. It is not a public interface and is not intended for direct reads or writes. Query state via your materialized tables and LiveStore APIs; do not depend on the event log database layout or mutate it directly.