@livestore/livestore 0.4.0-dev.21 → 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.
- package/README.md +0 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/QueryCache.js +1 -1
- package/dist/QueryCache.js.map +1 -1
- package/dist/SqliteDbWrapper.d.ts +5 -5
- package/dist/SqliteDbWrapper.d.ts.map +1 -1
- package/dist/SqliteDbWrapper.js +8 -8
- package/dist/SqliteDbWrapper.js.map +1 -1
- package/dist/SqliteDbWrapper.test.js +2 -2
- package/dist/SqliteDbWrapper.test.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +130 -2
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +185 -6
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/effect/LiveStore.test.d.ts +2 -0
- package/dist/effect/LiveStore.test.d.ts.map +1 -0
- package/dist/effect/LiveStore.test.js +42 -0
- package/dist/effect/LiveStore.test.js.map +1 -0
- package/dist/effect/mod.d.ts +1 -1
- package/dist/effect/mod.d.ts.map +1 -1
- package/dist/effect/mod.js +3 -1
- package/dist/effect/mod.js.map +1 -1
- package/dist/live-queries/base-class.d.ts +3 -3
- package/dist/live-queries/base-class.d.ts.map +1 -1
- package/dist/live-queries/base-class.js +2 -2
- package/dist/live-queries/base-class.js.map +1 -1
- package/dist/live-queries/client-document-get-query.d.ts +1 -1
- package/dist/live-queries/client-document-get-query.d.ts.map +1 -1
- package/dist/live-queries/client-document-get-query.js +1 -1
- package/dist/live-queries/client-document-get-query.js.map +1 -1
- package/dist/live-queries/computed.d.ts.map +1 -1
- package/dist/live-queries/computed.js +2 -2
- package/dist/live-queries/computed.js.map +1 -1
- package/dist/live-queries/db-query.js +14 -14
- package/dist/live-queries/db-query.js.map +1 -1
- package/dist/live-queries/db-query.test.js +2 -2
- package/dist/live-queries/db-query.test.js.map +1 -1
- package/dist/live-queries/signal.test.js +2 -2
- package/dist/live-queries/signal.test.js.map +1 -1
- package/dist/mod.d.ts +2 -1
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +1 -0
- package/dist/mod.js.map +1 -1
- package/dist/reactive.d.ts +9 -9
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +9 -26
- package/dist/reactive.js.map +1 -1
- package/dist/reactive.test.js +2 -2
- package/dist/reactive.test.js.map +1 -1
- package/dist/store/StoreRegistry.d.ts +215 -0
- package/dist/store/StoreRegistry.d.ts.map +1 -0
- package/dist/store/StoreRegistry.js +267 -0
- package/dist/store/StoreRegistry.js.map +1 -0
- package/dist/store/StoreRegistry.test.d.ts +2 -0
- package/dist/store/StoreRegistry.test.d.ts.map +1 -0
- package/dist/store/StoreRegistry.test.js +381 -0
- package/dist/store/StoreRegistry.test.js.map +1 -0
- package/dist/store/create-store.d.ts +56 -6
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +32 -7
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/devtools.d.ts +1 -1
- package/dist/store/devtools.d.ts.map +1 -1
- package/dist/store/devtools.js +16 -3
- package/dist/store/devtools.js.map +1 -1
- package/dist/store/store-eventstream.test.js +2 -2
- package/dist/store/store-eventstream.test.js.map +1 -1
- package/dist/store/store-types.d.ts +59 -9
- package/dist/store/store-types.d.ts.map +1 -1
- package/dist/store/store-types.js.map +1 -1
- package/dist/store/store-types.test.js +1 -1
- package/dist/store/store-types.test.js.map +1 -1
- package/dist/store/store.d.ts +102 -6
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +148 -47
- package/dist/store/store.js.map +1 -1
- package/dist/utils/dev.js.map +1 -1
- package/dist/utils/stack-info.js +2 -2
- package/dist/utils/stack-info.js.map +1 -1
- package/dist/utils/tests/fixture.d.ts +1 -1
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/dist/utils/tests/fixture.js.map +1 -1
- package/dist/utils/tests/otel.d.ts.map +1 -1
- package/dist/utils/tests/otel.js +5 -5
- package/dist/utils/tests/otel.js.map +1 -1
- package/package.json +59 -18
- package/src/QueryCache.ts +1 -1
- package/src/SqliteDbWrapper.test.ts +4 -2
- package/src/SqliteDbWrapper.ts +12 -11
- package/src/ambient.d.ts +0 -7
- package/src/effect/LiveStore.test.ts +61 -0
- package/src/effect/LiveStore.ts +381 -8
- package/src/effect/mod.ts +13 -1
- package/src/live-queries/__snapshots__/db-query.test.ts.snap +336 -231
- package/src/live-queries/base-class.ts +7 -6
- package/src/live-queries/client-document-get-query.ts +4 -2
- package/src/live-queries/computed.ts +3 -2
- package/src/live-queries/db-query.test.ts +3 -2
- package/src/live-queries/db-query.ts +15 -15
- package/src/live-queries/signal.test.ts +3 -2
- package/src/mod.ts +2 -0
- package/src/reactive.test.ts +3 -2
- package/src/reactive.ts +22 -23
- package/src/store/StoreRegistry.test.ts +540 -0
- package/src/store/StoreRegistry.ts +418 -0
- package/src/store/create-store.ts +76 -15
- package/src/store/devtools.ts +20 -6
- package/src/store/store-eventstream.test.ts +4 -2
- package/src/store/store-types.test.ts +3 -1
- package/src/store/store-types.ts +64 -13
- package/src/store/store.ts +197 -60
- package/src/utils/dev.ts +2 -2
- package/src/utils/stack-info.ts +2 -2
- package/src/utils/tests/fixture.ts +2 -1
- package/src/utils/tests/otel.ts +8 -7
- package/docs/api/index.md +0 -3
- package/docs/building-with-livestore/complex-ui-state/index.md +0 -5
- package/docs/building-with-livestore/crud/index.md +0 -5
- package/docs/building-with-livestore/data-modeling/index.md +0 -1
- package/docs/building-with-livestore/debugging/index.md +0 -17
- package/docs/building-with-livestore/devtools/index.md +0 -79
- package/docs/building-with-livestore/events/index.md +0 -355
- package/docs/building-with-livestore/examples/ai-agent/index.md +0 -5
- package/docs/building-with-livestore/examples/index.md +0 -30
- package/docs/building-with-livestore/examples/todo-workspaces/index.md +0 -891
- package/docs/building-with-livestore/examples/turnbased-game/index.md +0 -7
- package/docs/building-with-livestore/opentelemetry/index.md +0 -208
- package/docs/building-with-livestore/production-checklist/index.md +0 -5
- package/docs/building-with-livestore/reactivity-system/index.md +0 -202
- package/docs/building-with-livestore/rules-for-ai-agents/index.md +0 -9
- package/docs/building-with-livestore/state/materializers/index.md +0 -300
- package/docs/building-with-livestore/state/sql-queries/index.md +0 -72
- package/docs/building-with-livestore/state/sqlite/index.md +0 -45
- package/docs/building-with-livestore/state/sqlite-schema/index.md +0 -306
- package/docs/building-with-livestore/state/sqlite-schema-effect/index.md +0 -300
- package/docs/building-with-livestore/store/index.md +0 -281
- package/docs/building-with-livestore/syncing/index.md +0 -136
- package/docs/building-with-livestore/tools/cli/index.md +0 -177
- package/docs/building-with-livestore/tools/mcp/index.md +0 -187
- package/docs/examples/cloudflare-adapter/index.md +0 -44
- package/docs/examples/expo-adapter/index.md +0 -44
- package/docs/examples/index.md +0 -55
- package/docs/examples/node-adapter/index.md +0 -44
- package/docs/examples/web-adapter/index.md +0 -52
- package/docs/framework-integrations/custom-elements/index.md +0 -142
- package/docs/framework-integrations/react-integration/index.md +0 -918
- package/docs/framework-integrations/solid-integration/index.md +0 -293
- package/docs/framework-integrations/svelte-integration/index.md +0 -42
- package/docs/framework-integrations/vue-integration/index.md +0 -294
- package/docs/getting-started/expo/index.md +0 -736
- package/docs/getting-started/node/index.md +0 -115
- package/docs/getting-started/react-web/index.md +0 -573
- package/docs/getting-started/solid/index.md +0 -3
- package/docs/getting-started/vue/index.md +0 -471
- package/docs/index.md +0 -209
- package/docs/llms.txt +0 -147
- package/docs/misc/CODE_OF_CONDUCT/index.md +0 -133
- package/docs/misc/FAQ/index.md +0 -37
- package/docs/misc/community/index.md +0 -88
- package/docs/misc/credits/index.md +0 -14
- package/docs/misc/design-partners/index.md +0 -13
- package/docs/misc/package-management/index.md +0 -21
- package/docs/misc/performance/index.md +0 -25
- package/docs/misc/resources/index.md +0 -46
- package/docs/misc/state-of-the-project/index.md +0 -37
- package/docs/misc/troubleshooting/index.md +0 -82
- package/docs/overview/concepts/index.md +0 -78
- package/docs/overview/how-livestore-works/index.md +0 -56
- package/docs/overview/introduction/index.md +0 -5
- package/docs/overview/technology-comparison/index.md +0 -40
- package/docs/overview/when-livestore/index.md +0 -81
- package/docs/overview/why-livestore/index.md +0 -5
- package/docs/patterns/ai/index.md +0 -15
- package/docs/patterns/anonymous-user-transition/index.md +0 -10
- package/docs/patterns/app-evolution/index.md +0 -72
- package/docs/patterns/auth/index.md +0 -226
- package/docs/patterns/effect/index.md +0 -1495
- package/docs/patterns/encryption/index.md +0 -6
- package/docs/patterns/external-data/index.md +0 -5
- package/docs/patterns/file-management/index.md +0 -11
- package/docs/patterns/file-structure/index.md +0 -14
- package/docs/patterns/list-ordering/index.md +0 -369
- package/docs/patterns/offline/index.md +0 -32
- package/docs/patterns/orm/index.md +0 -18
- package/docs/patterns/presence/index.md +0 -11
- package/docs/patterns/rich-text-editing/index.md +0 -11
- package/docs/patterns/server-side-clients/index.md +0 -97
- package/docs/patterns/side-effects/index.md +0 -11
- package/docs/patterns/state-machines/index.md +0 -11
- package/docs/patterns/storybook/index.md +0 -192
- package/docs/patterns/undo-redo/index.md +0 -9
- package/docs/patterns/version-control/index.md +0 -8
- package/docs/platform-adapters/cloudflare-durable-object-adapter/index.md +0 -453
- package/docs/platform-adapters/electron-adapter/index.md +0 -15
- package/docs/platform-adapters/expo-adapter/index.md +0 -245
- package/docs/platform-adapters/node-adapter/index.md +0 -160
- package/docs/platform-adapters/tauri-adapter/index.md +0 -15
- package/docs/platform-adapters/web-adapter/index.md +0 -218
- package/docs/sustainable-open-source/contributing/docs/index.md +0 -94
- package/docs/sustainable-open-source/contributing/info/index.md +0 -63
- package/docs/sustainable-open-source/contributing/monorepo/index.md +0 -195
- package/docs/sustainable-open-source/sponsoring/index.md +0 -104
- package/docs/sync-providers/cloudflare/index.md +0 -773
- package/docs/sync-providers/custom/index.md +0 -65
- package/docs/sync-providers/electricsql/index.md +0 -159
- package/docs/sync-providers/s2/index.md +0 -230
- package/docs/tutorial/0-welcome/index.md +0 -48
- package/docs/tutorial/1-setup-starter-project/index.md +0 -105
- package/docs/tutorial/2-deploy-to-cloudflare/index.md +0 -195
- package/docs/tutorial/3-read-and-write-todos-via-livestore/index.md +0 -511
- package/docs/tutorial/4-sync-data-via-cloudflare/index.md +0 -210
- package/docs/tutorial/5-expand-business-logic/index.md +0 -174
- package/docs/tutorial/6-persist-ui-state/index.md +0 -453
- package/docs/tutorial/7-next-steps/index.md +0 -22
- package/docs/understanding-livestore/design-decisions/index.md +0 -33
- 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,72 +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
|
-
},
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
// Read queries
|
|
20
|
-
table.select('name')
|
|
21
|
-
table.where('name', '=', 'Alice')
|
|
22
|
-
table.where({ name: 'Alice' })
|
|
23
|
-
table.orderBy('name', 'desc').offset(10).limit(10)
|
|
24
|
-
table.count().where('name', 'LIKE', '%Ali%')
|
|
25
|
-
|
|
26
|
-
// Write queries
|
|
27
|
-
table.insert({ id: '123', name: 'Bob' })
|
|
28
|
-
table.update({ name: 'Alice' }).where({ id: '123' })
|
|
29
|
-
table.delete().where({ id: '123' })
|
|
30
|
-
|
|
31
|
-
// Upserts (insert or update on conflict)
|
|
32
|
-
table.insert({ id: '123', name: 'Charlie' }).onConflict('id', 'replace')
|
|
33
|
-
table.insert({ id: '456', name: 'Diana' }).onConflict('id', 'update', { name: 'Diana Updated' })
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Raw SQL queries
|
|
37
|
-
|
|
38
|
-
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.
|
|
39
|
-
|
|
40
|
-
## `reference/state/sql-queries/raw-sql.ts`
|
|
41
|
-
|
|
42
|
-
```ts filename="reference/state/sql-queries/raw-sql.ts"
|
|
43
|
-
/** biome-ignore-all lint/correctness/noUnusedVariables: docs snippet keeps reactive references */
|
|
44
|
-
// ---cut---
|
|
45
|
-
|
|
46
|
-
const table = State.SQLite.table({
|
|
47
|
-
name: 'my_table',
|
|
48
|
-
columns: {
|
|
49
|
-
id: State.SQLite.text({ primaryKey: true }),
|
|
50
|
-
name: State.SQLite.text(),
|
|
51
|
-
},
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
const filtered$ = queryDb({
|
|
55
|
-
query: sql`select * from my_table where name = 'Alice'`,
|
|
56
|
-
schema: Schema.Array(table.rowSchema),
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
const count$ = queryDb({
|
|
60
|
-
query: sql`select count(*) as count from my_table`,
|
|
61
|
-
schema: Schema.Struct({ count: Schema.Number }).pipe(Schema.pluck('count'), Schema.Array, Schema.headOrElse()),
|
|
62
|
-
})
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## Best practices
|
|
66
|
-
|
|
67
|
-
- Query results should be treated as immutable/read-only
|
|
68
|
-
- For queries which could return many rows, it's recommended to paginate the results
|
|
69
|
-
- Usually both via paginated/virtualized rendering as well as paginated queries
|
|
70
|
-
- 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
|
|
71
|
-
- For very large/complex queries, it can also make sense to implement incremental view maintenance (IVM) for your queries
|
|
72
|
-
- 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.
|