@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.
Files changed (216) 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 +130 -2
  12. package/dist/effect/LiveStore.d.ts.map +1 -1
  13. package/dist/effect/LiveStore.js +185 -6
  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/effect/mod.d.ts +1 -1
  20. package/dist/effect/mod.d.ts.map +1 -1
  21. package/dist/effect/mod.js +3 -1
  22. package/dist/effect/mod.js.map +1 -1
  23. package/dist/live-queries/base-class.d.ts +3 -3
  24. package/dist/live-queries/base-class.d.ts.map +1 -1
  25. package/dist/live-queries/base-class.js +2 -2
  26. package/dist/live-queries/base-class.js.map +1 -1
  27. package/dist/live-queries/client-document-get-query.d.ts +1 -1
  28. package/dist/live-queries/client-document-get-query.d.ts.map +1 -1
  29. package/dist/live-queries/client-document-get-query.js +1 -1
  30. package/dist/live-queries/client-document-get-query.js.map +1 -1
  31. package/dist/live-queries/computed.d.ts.map +1 -1
  32. package/dist/live-queries/computed.js +2 -2
  33. package/dist/live-queries/computed.js.map +1 -1
  34. package/dist/live-queries/db-query.js +14 -14
  35. package/dist/live-queries/db-query.js.map +1 -1
  36. package/dist/live-queries/db-query.test.js +2 -2
  37. package/dist/live-queries/db-query.test.js.map +1 -1
  38. package/dist/live-queries/signal.test.js +2 -2
  39. package/dist/live-queries/signal.test.js.map +1 -1
  40. package/dist/mod.d.ts +2 -1
  41. package/dist/mod.d.ts.map +1 -1
  42. package/dist/mod.js +1 -0
  43. package/dist/mod.js.map +1 -1
  44. package/dist/reactive.d.ts +9 -9
  45. package/dist/reactive.d.ts.map +1 -1
  46. package/dist/reactive.js +9 -26
  47. package/dist/reactive.js.map +1 -1
  48. package/dist/reactive.test.js +2 -2
  49. package/dist/reactive.test.js.map +1 -1
  50. package/dist/store/StoreRegistry.d.ts +215 -0
  51. package/dist/store/StoreRegistry.d.ts.map +1 -0
  52. package/dist/store/StoreRegistry.js +267 -0
  53. package/dist/store/StoreRegistry.js.map +1 -0
  54. package/dist/store/StoreRegistry.test.d.ts +2 -0
  55. package/dist/store/StoreRegistry.test.d.ts.map +1 -0
  56. package/dist/store/StoreRegistry.test.js +381 -0
  57. package/dist/store/StoreRegistry.test.js.map +1 -0
  58. package/dist/store/create-store.d.ts +56 -6
  59. package/dist/store/create-store.d.ts.map +1 -1
  60. package/dist/store/create-store.js +32 -7
  61. package/dist/store/create-store.js.map +1 -1
  62. package/dist/store/devtools.d.ts +1 -1
  63. package/dist/store/devtools.d.ts.map +1 -1
  64. package/dist/store/devtools.js +16 -3
  65. package/dist/store/devtools.js.map +1 -1
  66. package/dist/store/store-eventstream.test.js +2 -2
  67. package/dist/store/store-eventstream.test.js.map +1 -1
  68. package/dist/store/store-types.d.ts +59 -9
  69. package/dist/store/store-types.d.ts.map +1 -1
  70. package/dist/store/store-types.js.map +1 -1
  71. package/dist/store/store-types.test.js +1 -1
  72. package/dist/store/store-types.test.js.map +1 -1
  73. package/dist/store/store.d.ts +102 -6
  74. package/dist/store/store.d.ts.map +1 -1
  75. package/dist/store/store.js +148 -47
  76. package/dist/store/store.js.map +1 -1
  77. package/dist/utils/dev.js.map +1 -1
  78. package/dist/utils/stack-info.js +2 -2
  79. package/dist/utils/stack-info.js.map +1 -1
  80. package/dist/utils/tests/fixture.d.ts +1 -1
  81. package/dist/utils/tests/fixture.d.ts.map +1 -1
  82. package/dist/utils/tests/fixture.js.map +1 -1
  83. package/dist/utils/tests/otel.d.ts.map +1 -1
  84. package/dist/utils/tests/otel.js +5 -5
  85. package/dist/utils/tests/otel.js.map +1 -1
  86. package/package.json +59 -18
  87. package/src/QueryCache.ts +1 -1
  88. package/src/SqliteDbWrapper.test.ts +4 -2
  89. package/src/SqliteDbWrapper.ts +12 -11
  90. package/src/ambient.d.ts +0 -7
  91. package/src/effect/LiveStore.test.ts +61 -0
  92. package/src/effect/LiveStore.ts +381 -8
  93. package/src/effect/mod.ts +13 -1
  94. package/src/live-queries/__snapshots__/db-query.test.ts.snap +336 -231
  95. package/src/live-queries/base-class.ts +7 -6
  96. package/src/live-queries/client-document-get-query.ts +4 -2
  97. package/src/live-queries/computed.ts +3 -2
  98. package/src/live-queries/db-query.test.ts +3 -2
  99. package/src/live-queries/db-query.ts +15 -15
  100. package/src/live-queries/signal.test.ts +3 -2
  101. package/src/mod.ts +2 -0
  102. package/src/reactive.test.ts +3 -2
  103. package/src/reactive.ts +22 -23
  104. package/src/store/StoreRegistry.test.ts +540 -0
  105. package/src/store/StoreRegistry.ts +418 -0
  106. package/src/store/create-store.ts +76 -15
  107. package/src/store/devtools.ts +20 -6
  108. package/src/store/store-eventstream.test.ts +4 -2
  109. package/src/store/store-types.test.ts +3 -1
  110. package/src/store/store-types.ts +64 -13
  111. package/src/store/store.ts +197 -60
  112. package/src/utils/dev.ts +2 -2
  113. package/src/utils/stack-info.ts +2 -2
  114. package/src/utils/tests/fixture.ts +2 -1
  115. package/src/utils/tests/otel.ts +8 -7
  116. package/docs/api/index.md +0 -3
  117. package/docs/building-with-livestore/complex-ui-state/index.md +0 -5
  118. package/docs/building-with-livestore/crud/index.md +0 -5
  119. package/docs/building-with-livestore/data-modeling/index.md +0 -1
  120. package/docs/building-with-livestore/debugging/index.md +0 -17
  121. package/docs/building-with-livestore/devtools/index.md +0 -79
  122. package/docs/building-with-livestore/events/index.md +0 -355
  123. package/docs/building-with-livestore/examples/ai-agent/index.md +0 -5
  124. package/docs/building-with-livestore/examples/index.md +0 -30
  125. package/docs/building-with-livestore/examples/todo-workspaces/index.md +0 -891
  126. package/docs/building-with-livestore/examples/turnbased-game/index.md +0 -7
  127. package/docs/building-with-livestore/opentelemetry/index.md +0 -208
  128. package/docs/building-with-livestore/production-checklist/index.md +0 -5
  129. package/docs/building-with-livestore/reactivity-system/index.md +0 -202
  130. package/docs/building-with-livestore/rules-for-ai-agents/index.md +0 -9
  131. package/docs/building-with-livestore/state/materializers/index.md +0 -300
  132. package/docs/building-with-livestore/state/sql-queries/index.md +0 -72
  133. package/docs/building-with-livestore/state/sqlite/index.md +0 -45
  134. package/docs/building-with-livestore/state/sqlite-schema/index.md +0 -306
  135. package/docs/building-with-livestore/state/sqlite-schema-effect/index.md +0 -300
  136. package/docs/building-with-livestore/store/index.md +0 -281
  137. package/docs/building-with-livestore/syncing/index.md +0 -136
  138. package/docs/building-with-livestore/tools/cli/index.md +0 -177
  139. package/docs/building-with-livestore/tools/mcp/index.md +0 -187
  140. package/docs/examples/cloudflare-adapter/index.md +0 -44
  141. package/docs/examples/expo-adapter/index.md +0 -44
  142. package/docs/examples/index.md +0 -55
  143. package/docs/examples/node-adapter/index.md +0 -44
  144. package/docs/examples/web-adapter/index.md +0 -52
  145. package/docs/framework-integrations/custom-elements/index.md +0 -142
  146. package/docs/framework-integrations/react-integration/index.md +0 -918
  147. package/docs/framework-integrations/solid-integration/index.md +0 -293
  148. package/docs/framework-integrations/svelte-integration/index.md +0 -42
  149. package/docs/framework-integrations/vue-integration/index.md +0 -294
  150. package/docs/getting-started/expo/index.md +0 -736
  151. package/docs/getting-started/node/index.md +0 -115
  152. package/docs/getting-started/react-web/index.md +0 -573
  153. package/docs/getting-started/solid/index.md +0 -3
  154. package/docs/getting-started/vue/index.md +0 -471
  155. package/docs/index.md +0 -209
  156. package/docs/llms.txt +0 -147
  157. package/docs/misc/CODE_OF_CONDUCT/index.md +0 -133
  158. package/docs/misc/FAQ/index.md +0 -37
  159. package/docs/misc/community/index.md +0 -88
  160. package/docs/misc/credits/index.md +0 -14
  161. package/docs/misc/design-partners/index.md +0 -13
  162. package/docs/misc/package-management/index.md +0 -21
  163. package/docs/misc/performance/index.md +0 -25
  164. package/docs/misc/resources/index.md +0 -46
  165. package/docs/misc/state-of-the-project/index.md +0 -37
  166. package/docs/misc/troubleshooting/index.md +0 -82
  167. package/docs/overview/concepts/index.md +0 -78
  168. package/docs/overview/how-livestore-works/index.md +0 -56
  169. package/docs/overview/introduction/index.md +0 -5
  170. package/docs/overview/technology-comparison/index.md +0 -40
  171. package/docs/overview/when-livestore/index.md +0 -81
  172. package/docs/overview/why-livestore/index.md +0 -5
  173. package/docs/patterns/ai/index.md +0 -15
  174. package/docs/patterns/anonymous-user-transition/index.md +0 -10
  175. package/docs/patterns/app-evolution/index.md +0 -72
  176. package/docs/patterns/auth/index.md +0 -226
  177. package/docs/patterns/effect/index.md +0 -1495
  178. package/docs/patterns/encryption/index.md +0 -6
  179. package/docs/patterns/external-data/index.md +0 -5
  180. package/docs/patterns/file-management/index.md +0 -11
  181. package/docs/patterns/file-structure/index.md +0 -14
  182. package/docs/patterns/list-ordering/index.md +0 -369
  183. package/docs/patterns/offline/index.md +0 -32
  184. package/docs/patterns/orm/index.md +0 -18
  185. package/docs/patterns/presence/index.md +0 -11
  186. package/docs/patterns/rich-text-editing/index.md +0 -11
  187. package/docs/patterns/server-side-clients/index.md +0 -97
  188. package/docs/patterns/side-effects/index.md +0 -11
  189. package/docs/patterns/state-machines/index.md +0 -11
  190. package/docs/patterns/storybook/index.md +0 -192
  191. package/docs/patterns/undo-redo/index.md +0 -9
  192. package/docs/patterns/version-control/index.md +0 -8
  193. package/docs/platform-adapters/cloudflare-durable-object-adapter/index.md +0 -453
  194. package/docs/platform-adapters/electron-adapter/index.md +0 -15
  195. package/docs/platform-adapters/expo-adapter/index.md +0 -245
  196. package/docs/platform-adapters/node-adapter/index.md +0 -160
  197. package/docs/platform-adapters/tauri-adapter/index.md +0 -15
  198. package/docs/platform-adapters/web-adapter/index.md +0 -218
  199. package/docs/sustainable-open-source/contributing/docs/index.md +0 -94
  200. package/docs/sustainable-open-source/contributing/info/index.md +0 -63
  201. package/docs/sustainable-open-source/contributing/monorepo/index.md +0 -195
  202. package/docs/sustainable-open-source/sponsoring/index.md +0 -104
  203. package/docs/sync-providers/cloudflare/index.md +0 -773
  204. package/docs/sync-providers/custom/index.md +0 -65
  205. package/docs/sync-providers/electricsql/index.md +0 -159
  206. package/docs/sync-providers/s2/index.md +0 -230
  207. package/docs/tutorial/0-welcome/index.md +0 -48
  208. package/docs/tutorial/1-setup-starter-project/index.md +0 -105
  209. package/docs/tutorial/2-deploy-to-cloudflare/index.md +0 -195
  210. package/docs/tutorial/3-read-and-write-todos-via-livestore/index.md +0 -511
  211. package/docs/tutorial/4-sync-data-via-cloudflare/index.md +0 -210
  212. package/docs/tutorial/5-expand-business-logic/index.md +0 -174
  213. package/docs/tutorial/6-persist-ui-state/index.md +0 -453
  214. package/docs/tutorial/7-next-steps/index.md +0 -22
  215. package/docs/understanding-livestore/design-decisions/index.md +0 -33
  216. package/docs/understanding-livestore/event-sourcing/index.md +0 -40
@@ -1,306 +0,0 @@
1
- # SQLite state schema
2
-
3
- LiveStore provides a schema definition language for defining your database tables and mutation definitions using explicit column configurations. LiveStore automatically migrates your database schema when you change your schema definitions.
4
-
5
- > **Alternative Approach**: You can also define tables using [Effect Schema with annotations](/building-with-livestore/state/sqlite-schema-effect) for type-safe schema definitions.
6
-
7
- ### Example
8
-
9
- <Tabs syncKey="package-manager">
10
- <TabItem label="schema.ts">
11
-
12
- ## `reference/state/sqlite-schema/schema.ts`
13
-
14
- ```ts filename="reference/state/sqlite-schema/schema.ts"
15
-
16
- // You can model your state as SQLite tables (https://docs.livestore.dev/reference/state/sqlite-schema)
17
- export const tables = {
18
- todos: State.SQLite.table({
19
- name: 'todos',
20
- columns: {
21
- id: State.SQLite.text({ primaryKey: true }),
22
- text: State.SQLite.text({ default: '' }),
23
- completed: State.SQLite.boolean({ default: false }),
24
- deletedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
25
- },
26
- }),
27
- // Client documents can be used for local-only state (e.g. form inputs)
28
- uiState: State.SQLite.clientDocument({
29
- name: 'uiState',
30
- schema: Schema.Struct({ newTodoText: Schema.String, filter: Schema.Literal('all', 'active', 'completed') }),
31
- default: { id: SessionIdSymbol, value: { newTodoText: '', filter: 'all' } },
32
- }),
33
- }
34
-
35
- // Events describe data changes (https://docs.livestore.dev/reference/events)
36
- export const events = {
37
- todoCreated: Events.synced({
38
- name: 'v1.TodoCreated',
39
- schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
40
- }),
41
- todoCompleted: Events.synced({
42
- name: 'v1.TodoCompleted',
43
- schema: Schema.Struct({ id: Schema.String }),
44
- }),
45
- todoUncompleted: Events.synced({
46
- name: 'v1.TodoUncompleted',
47
- schema: Schema.Struct({ id: Schema.String }),
48
- }),
49
- todoDeleted: Events.synced({
50
- name: 'v1.TodoDeleted',
51
- schema: Schema.Struct({ id: Schema.String, deletedAt: Schema.Date }),
52
- }),
53
- todoClearedCompleted: Events.synced({
54
- name: 'v1.TodoClearedCompleted',
55
- schema: Schema.Struct({ deletedAt: Schema.Date }),
56
- }),
57
- uiStateSet: tables.uiState.set,
58
- }
59
-
60
- // Materializers are used to map events to state (https://docs.livestore.dev/reference/state/materializers)
61
- const materializers = State.SQLite.materializers(events, {
62
- 'v1.TodoCreated': ({ id, text }) => tables.todos.insert({ id, text, completed: false }),
63
- 'v1.TodoCompleted': ({ id }) => tables.todos.update({ completed: true }).where({ id }),
64
- 'v1.TodoUncompleted': ({ id }) => tables.todos.update({ completed: false }).where({ id }),
65
- 'v1.TodoDeleted': ({ id, deletedAt }) => tables.todos.update({ deletedAt }).where({ id }),
66
- 'v1.TodoClearedCompleted': ({ deletedAt }) => tables.todos.update({ deletedAt }).where({ completed: true }),
67
- })
68
-
69
- const state = State.SQLite.makeState({ tables, materializers })
70
-
71
- export const schema = makeSchema({ events, state })
72
- ```
73
-
74
- </TabItem>
75
- </Tabs>
76
-
77
- ## Defining tables
78
-
79
- Define SQLite tables using explicit column definitions:
80
-
81
- ## `reference/state/sqlite-schema/columns/table-basic.ts`
82
-
83
- ```ts filename="reference/state/sqlite-schema/columns/table-basic.ts"
84
-
85
- export const userTable = State.SQLite.table({
86
- name: 'users',
87
- columns: {
88
- id: State.SQLite.text({ primaryKey: true }),
89
- email: State.SQLite.text(),
90
- name: State.SQLite.text(),
91
- age: State.SQLite.integer({ default: 0 }),
92
- isActive: State.SQLite.boolean({ default: true }),
93
- metadata: State.SQLite.json({ nullable: true }),
94
- },
95
- indexes: [{ name: 'idx_users_email', columns: ['email'], isUnique: true }],
96
- })
97
- ```
98
-
99
- Use the optional `indexes` array to declare secondary indexes or enforce uniqueness (set `isUnique: true`).
100
-
101
- ## Column types
102
-
103
- You can use these column types when defining tables:
104
-
105
- ### Core SQLite column types
106
-
107
- - `State.SQLite.text`: A text field, returns `string`.
108
- - `State.SQLite.integer`: An integer field, returns `number`.
109
- - `State.SQLite.real`: A real field (floating point number), returns `number`.
110
- - `State.SQLite.blob`: A blob field (binary data), returns `Uint8Array`.
111
-
112
- ### Higher level column types
113
-
114
- - `State.SQLite.boolean`: An integer field that stores `0` for `false` and `1` for `true` and returns a `boolean`.
115
- - `State.SQLite.json`: A text field that stores a stringified JSON object and returns a decoded JSON value.
116
- - `State.SQLite.datetime`: A text field that stores dates as ISO 8601 strings and returns a `Date`.
117
- - `State.SQLite.datetimeInteger`: A integer field that stores dates as the number of milliseconds since the epoch and returns a `Date`.
118
-
119
- ### Custom column schemas
120
-
121
- You can also provide a custom schema for a column which is used to automatically encode and decode the column value.
122
-
123
- ### Example: JSON-encoded struct
124
-
125
- ## `reference/state/sqlite-schema/columns/json-struct.ts`
126
-
127
- ```ts filename="reference/state/sqlite-schema/columns/json-struct.ts"
128
-
129
- export const UserMetadata = Schema.Struct({
130
- petName: Schema.String,
131
- favoriteColor: Schema.Literal('red', 'blue', 'green'),
132
- })
133
-
134
- export const userTable = State.SQLite.table({
135
- name: 'user',
136
- columns: {
137
- id: State.SQLite.text({ primaryKey: true }),
138
- name: State.SQLite.text(),
139
- metadata: State.SQLite.json({ schema: UserMetadata }),
140
- },
141
- })
142
- ```
143
-
144
- ### Schema migrations
145
-
146
- Migration strategies:
147
-
148
- - `auto`: Automatically migrate the database to the newest schema and rematerializes the state from the eventlog.
149
- - `manual`: Manually migrate the database to the newest schema.
150
-
151
- ## Client documents
152
-
153
- - Meant for convenience
154
- - Client-only
155
- - Goal: Similar ease of use as `React.useState`
156
- - When schema changes in a non-backwards compatible way, previous events are dropped and the state is reset
157
- - Don't use client documents for sensitive data which must not be lost
158
- - Implies
159
- - Table with `id` and `value` columns
160
- - `${MyTable}Set` event + materializer (which are auto-registered)
161
-
162
- ### Basic usage
163
-
164
- ## `reference/state/sqlite-schema/columns/client-document-basic.tsx`
165
-
166
- ```tsx filename="reference/state/sqlite-schema/columns/client-document-basic.tsx"
167
-
168
- export const uiState = State.SQLite.clientDocument({
169
- name: 'UiState',
170
- schema: Schema.Struct({
171
- newTodoText: Schema.String,
172
- filter: Schema.Literal('all', 'active', 'completed'),
173
- }),
174
- default: { id: SessionIdSymbol, value: { newTodoText: '', filter: 'all' } },
175
- })
176
-
177
- export const readUiState = (store: Store): { newTodoText: string; filter: 'all' | 'active' | 'completed' } =>
178
- store.query(uiState.get())
179
-
180
- export const setNewTodoText = (store: Store, newTodoText: string): void => {
181
- store.commit(uiState.set({ newTodoText }))
182
- }
183
-
184
- export const UiStateFilter: React.FC = () => {
185
- const [state, setState] = useClientDocument(uiState)
186
-
187
- const showActive = React.useCallback(() => {
188
- setState({ filter: 'active' })
189
- }, [setState])
190
-
191
- const showAll = React.useCallback(() => {
192
- setState({ filter: 'all' })
193
- }, [setState])
194
-
195
- return (
196
- <div>
197
- <button type="button" onClick={showAll}>
198
- All
199
- </button>
200
- <button type="button" onClick={showActive}>
201
- Active ({state.filter === 'active' ? 'selected' : 'select'})
202
- </button>
203
- </div>
204
- )
205
- }
206
- ```
207
-
208
- ### KV-style client document
209
-
210
- Sometimes you want a simple key-value store for arbitrary values without partial merging. You can model this by using `Schema.Any` as the value schema. With `Schema.Any`, updates fully replace the stored value (no partial merge semantics).
211
-
212
- ## `reference/state/sqlite-schema/columns/client-document-kv.tsx`
213
-
214
- ```tsx filename="reference/state/sqlite-schema/columns/client-document-kv.tsx"
215
-
216
- export const kv = State.SQLite.clientDocument({
217
- name: 'Kv',
218
- schema: Schema.Any,
219
- default: { value: null },
220
- })
221
-
222
- export const readKvValue = (store: Store, id: string): unknown => store.query(kv.get(id))
223
-
224
- export const setKvValue = (store: Store, id: string, value: unknown): void => {
225
- store.commit(kv.set(value, id))
226
- }
227
-
228
- export const KvViewer: React.FC<{ id: string }> = ({ id }) => {
229
- const [value, setValue] = useClientDocument(kv, id)
230
-
231
- return (
232
- <button type="button" onClick={() => setValue('hello')}>
233
- Current value: {JSON.stringify(value)}
234
- </button>
235
- )
236
- }
237
- ```
238
-
239
- ## Column types
240
-
241
- You can use these column types:
242
-
243
- #### Core SQLite column types
244
-
245
- - `State.SQLite.text`: A text field, returns `string`.
246
- - `State.SQLite.integer`: An integer field, returns `number`.
247
- - `State.SQLite.real`: A real field (floating point number), returns `number`.
248
- - `State.SQLite.blob`: A blob field (binary data), returns `Uint8Array`.
249
-
250
- #### Higher level column types
251
-
252
- - `State.SQLite.boolean`: An integer field that stores `0` for `false` and `1` for `true` and returns a `boolean`.
253
- - `State.SQLite.json`: A text field that stores a stringified JSON object and returns a decoded JSON value.
254
- - `State.SQLite.datetime`: A text field that stores dates as ISO 8601 strings and returns a `Date`.
255
- - `State.SQLite.datetimeInteger`: A integer field that stores dates as the number of milliseconds since the epoch and returns a `Date`.
256
-
257
- #### Custom column schemas
258
-
259
- You can also provide a custom schema for a column which is used to automatically encode and decode the column value.
260
-
261
- #### Example: JSON-encoded struct
262
-
263
- ## `reference/state/sqlite-schema/columns/json-struct.ts`
264
-
265
- ```ts filename="reference/state/sqlite-schema/columns/json-struct.ts"
266
-
267
- export const UserMetadata = Schema.Struct({
268
- petName: Schema.String,
269
- favoriteColor: Schema.Literal('red', 'blue', 'green'),
270
- })
271
-
272
- export const userTable = State.SQLite.table({
273
- name: 'user',
274
- columns: {
275
- id: State.SQLite.text({ primaryKey: true }),
276
- name: State.SQLite.text(),
277
- metadata: State.SQLite.json({ schema: UserMetadata }),
278
- },
279
- })
280
- ```
281
-
282
- ## Best practices
283
-
284
- ### Column configuration
285
-
286
- - Use appropriate SQLite column types for your data (text, integer, real, blob)
287
- - Set `primaryKey: true` for primary key columns
288
- - Use `nullable: true` for columns that can contain NULL values
289
- - Provide meaningful `default` values where appropriate
290
- - Add unique constraints via table `indexes` using `isUnique: true`
291
-
292
- ### Schema design
293
-
294
- - Choose column types that match your data requirements
295
- - Use custom schemas with `State.SQLite.json()` for complex data structures
296
- - Group related table definitions in the same module
297
- - Use descriptive table and column names
298
-
299
- ### General practices
300
-
301
- - It's usually recommend to **not distinguish** between app state vs app data but rather keep all state in LiveStore.
302
- - This means you'll rarely use `React.useState` when using LiveStore
303
- - In some cases for "fast changing values" it can make sense to keep a version of a state value outside of LiveStore with a reactive setter for React and a debounced setter for LiveStore to avoid excessive LiveStore mutations. Cases where this can make sense can include:
304
- - Text input / rich text editing
305
- - Scroll position tracking, resize events, move/drag events
306
- - ...
@@ -1,300 +0,0 @@
1
- # SQLite state schema (Effect schema)
2
-
3
- LiveStore supports defining SQLite tables using Effect Schema with annotations for database constraints. This approach provides strong type safety, composability, and automatic type mapping from TypeScript to SQLite.
4
-
5
- > **Note**: This approach will become the default once Effect Schema v4 is released. See [livestore#382](https://github.com/livestorejs/livestore/issues/382) for details.
6
- >
7
- > For the traditional column-based approach, see [SQLite State Schema](/building-with-livestore/state/sqlite-schema).
8
-
9
- ## Basic usage
10
-
11
- Define tables using Effect Schema with database constraint annotations:
12
-
13
- ## `reference/state/sqlite-schema/effect/basic.ts`
14
-
15
- ```ts filename="reference/state/sqlite-schema/effect/basic.ts"
16
-
17
- const UserSchema = Schema.Struct({
18
- id: Schema.String.pipe(State.SQLite.withPrimaryKey),
19
- email: Schema.String.pipe(State.SQLite.withUnique),
20
- name: Schema.String,
21
- age: Schema.Int.pipe(State.SQLite.withDefault(0)),
22
- isActive: Schema.Boolean.pipe(State.SQLite.withDefault(true)),
23
- metadata: Schema.optional(
24
- Schema.Record({
25
- key: Schema.String,
26
- value: Schema.Unknown,
27
- }),
28
- ),
29
- }).annotations({ title: 'users' })
30
-
31
- export const userTable = State.SQLite.table({ schema: UserSchema })
32
- ```
33
-
34
- ## Schema annotations
35
-
36
- You can annotate schema fields with database constraints:
37
-
38
- ### Primary keys
39
-
40
- ## `reference/state/sqlite-schema/effect/primary-key.ts`
41
-
42
- ```ts filename="reference/state/sqlite-schema/effect/primary-key.ts"
43
-
44
- const _schema = Schema.Struct({
45
- id: Schema.String.pipe(State.SQLite.withPrimaryKey),
46
- // Other fields...
47
- })
48
- ```
49
-
50
- **Important**: Primary key columns cannot be nullable. This will throw an error:
51
-
52
- ## `reference/state/sqlite-schema/effect/primary-key-nullable.ts`
53
-
54
- ```ts filename="reference/state/sqlite-schema/effect/primary-key-nullable.ts"
55
-
56
- // ❌ This will throw an error at runtime because primary keys cannot be nullable
57
- const _badSchema = Schema.Struct({
58
- id: Schema.NullOr(Schema.String).pipe(State.SQLite.withPrimaryKey),
59
- })
60
- ```
61
-
62
- ### Auto-Increment
63
-
64
- ## `reference/state/sqlite-schema/effect/auto-increment.ts`
65
-
66
- ```ts filename="reference/state/sqlite-schema/effect/auto-increment.ts"
67
-
68
- const _schema = Schema.Struct({
69
- id: Schema.Int.pipe(State.SQLite.withPrimaryKey, State.SQLite.withAutoIncrement),
70
- // Other fields...
71
- })
72
- ```
73
-
74
- ### Default values
75
-
76
- ## `reference/state/sqlite-schema/effect/default-values.ts`
77
-
78
- ```ts filename="reference/state/sqlite-schema/effect/default-values.ts"
79
-
80
- const _schema = Schema.Struct({
81
- status: Schema.String.pipe(State.SQLite.withDefault('active')),
82
- createdAt: Schema.String.pipe(State.SQLite.withDefault('CURRENT_TIMESTAMP')),
83
- count: Schema.Int.pipe(State.SQLite.withDefault(0)),
84
- })
85
- ```
86
-
87
- ### Unique constraints
88
-
89
- ## `reference/state/sqlite-schema/effect/unique-constraints.ts`
90
-
91
- ```ts filename="reference/state/sqlite-schema/effect/unique-constraints.ts"
92
-
93
- const _schema = Schema.Struct({
94
- email: Schema.String.pipe(State.SQLite.withUnique),
95
- username: Schema.String.pipe(State.SQLite.withUnique),
96
- })
97
- ```
98
-
99
- Unique annotations automatically create unique indexes.
100
-
101
- ### Custom column types
102
-
103
- Override the automatically inferred SQLite column type:
104
-
105
- ## `reference/state/sqlite-schema/effect/custom-column-types.ts`
106
-
107
- ```ts filename="reference/state/sqlite-schema/effect/custom-column-types.ts"
108
-
109
- const _schema = Schema.Struct({
110
- // Store a number as text instead of real
111
- version: Schema.Number.pipe(State.SQLite.withColumnType('text')),
112
- // Store binary data as blob
113
- data: Schema.Uint8Array.pipe(State.SQLite.withColumnType('blob')),
114
- })
115
- ```
116
-
117
- ### Combining annotations
118
-
119
- Annotations can be chained together:
120
-
121
- ## `reference/state/sqlite-schema/effect/combining-annotations.ts`
122
-
123
- ```ts filename="reference/state/sqlite-schema/effect/combining-annotations.ts"
124
-
125
- const _schema = Schema.Struct({
126
- id: Schema.Int.pipe(State.SQLite.withPrimaryKey, State.SQLite.withAutoIncrement),
127
- email: Schema.String.pipe(State.SQLite.withUnique, State.SQLite.withColumnType('text')),
128
- })
129
- ```
130
-
131
- ## Table naming
132
-
133
- You can specify table names in several ways:
134
-
135
- ### Using schema annotations
136
-
137
- ## `reference/state/sqlite-schema/effect/table-name-annotations.ts`
138
-
139
- ```ts filename="reference/state/sqlite-schema/effect/table-name-annotations.ts"
140
-
141
- // Using title annotation
142
- const UserSchema = Schema.Struct({
143
- id: Schema.String.pipe(State.SQLite.withPrimaryKey),
144
- name: Schema.String,
145
- }).annotations({ title: 'users' })
146
-
147
- export const userTable = State.SQLite.table({ schema: UserSchema })
148
-
149
- // Using identifier annotation
150
- const PostSchema = Schema.Struct({
151
- id: Schema.String.pipe(State.SQLite.withPrimaryKey),
152
- title: Schema.String,
153
- }).annotations({ identifier: 'posts' })
154
-
155
- export const postTable = State.SQLite.table({ schema: PostSchema })
156
- ```
157
-
158
- ### Explicit name
159
-
160
- ## `reference/state/sqlite-schema/effect/table-name-explicit.ts`
161
-
162
- ```ts filename="reference/state/sqlite-schema/effect/table-name-explicit.ts"
163
-
164
- const UserSchema = Schema.Struct({
165
- id: Schema.String.pipe(State.SQLite.withPrimaryKey),
166
- name: Schema.String,
167
- })
168
-
169
- export const userTable = State.SQLite.table({
170
- name: 'users',
171
- schema: UserSchema,
172
- })
173
- ```
174
-
175
- **Note**: Title annotation takes precedence over identifier annotation.
176
-
177
- ## Type mapping
178
-
179
- Effect Schema types are automatically mapped to SQLite column types:
180
-
181
- | Schema Type | SQLite Type | TypeScript Type |
182
- |-------------|-------------|-----------------|
183
- | `Schema.String` | `text` | `string` |
184
- | `Schema.Number` | `real` | `number` |
185
- | `Schema.Int` | `integer` | `number` |
186
- | `Schema.Boolean` | `integer` | `boolean` |
187
- | `Schema.Date` | `text` | `Date` |
188
- | `Schema.BigInt` | `text` | `bigint` |
189
- | Complex types (Struct, Array, etc.) | `text` (JSON encoded) | Decoded type |
190
- | `Schema.optional(T)` | Nullable column | `T \| undefined` |
191
- | `Schema.NullOr(T)` | Nullable column | `T \| null` |
192
-
193
- ## Advanced examples
194
-
195
- ### Complex schema with multiple constraints
196
-
197
- ## `reference/state/sqlite-schema/effect/advanced-product.ts`
198
-
199
- ```ts filename="reference/state/sqlite-schema/effect/advanced-product.ts"
200
-
201
- const ProductSchema = Schema.Struct({
202
- id: Schema.Int.pipe(State.SQLite.withPrimaryKey, State.SQLite.withAutoIncrement),
203
- sku: Schema.String.pipe(State.SQLite.withUnique),
204
- name: Schema.String,
205
- price: Schema.Number.pipe(State.SQLite.withDefault(0)),
206
- category: Schema.Literal('electronics', 'clothing', 'books'),
207
- metadata: Schema.optional(
208
- Schema.Struct({
209
- weight: Schema.Number,
210
- dimensions: Schema.Struct({
211
- width: Schema.Number,
212
- height: Schema.Number,
213
- depth: Schema.Number,
214
- }),
215
- }),
216
- ),
217
- isActive: Schema.Boolean.pipe(State.SQLite.withDefault(true)),
218
- createdAt: Schema.Date.pipe(State.SQLite.withDefault('CURRENT_TIMESTAMP')),
219
- }).annotations({ title: 'products' })
220
-
221
- export const productTable = State.SQLite.table({ schema: ProductSchema })
222
- ```
223
-
224
- ### Working with Schema.Class
225
-
226
- ## `reference/state/sqlite-schema/effect/schema-class.ts`
227
-
228
- ```ts filename="reference/state/sqlite-schema/effect/schema-class.ts"
229
-
230
- class User extends Schema.Class<User>('User')({
231
- id: Schema.String.pipe(State.SQLite.withPrimaryKey),
232
- email: Schema.String.pipe(State.SQLite.withUnique),
233
- name: Schema.String,
234
- age: Schema.Int,
235
- }) {}
236
-
237
- export const userTable = State.SQLite.table({
238
- name: 'users',
239
- schema: User,
240
- })
241
- ```
242
-
243
- ### Custom indexes
244
-
245
- ## `reference/state/sqlite-schema/effect/custom-indexes.ts`
246
-
247
- ```ts filename="reference/state/sqlite-schema/effect/custom-indexes.ts"
248
-
249
- const PostSchema = Schema.Struct({
250
- id: Schema.String.pipe(State.SQLite.withPrimaryKey),
251
- title: Schema.String,
252
- authorId: Schema.String,
253
- createdAt: Schema.Date,
254
- }).annotations({ title: 'posts' })
255
-
256
- export const postTable = State.SQLite.table({
257
- schema: PostSchema,
258
- indexes: [
259
- { name: 'idx_posts_author', columns: ['authorId'] },
260
- { name: 'idx_posts_created', columns: ['createdAt'] },
261
- ],
262
- })
263
- ```
264
-
265
- ## Best Practices
266
-
267
- ### Schema Design
268
-
269
- - Always use `withPrimaryKey` for primary key columns - never combine it with nullable types
270
- - Use `Schema.optional()` for truly optional fields that can be undefined
271
- - Use `Schema.NullOr()` for fields that can explicitly be set to null
272
- - Leverage schema annotations like `title` or `identifier` to avoid repeating table names
273
- - Group related schemas in the same module for better organization
274
-
275
- ### Type safety
276
-
277
- - Let TypeScript infer table types rather than explicitly typing them
278
- - Use Effect Schema's refinements and transformations for data validation
279
- - Prefer Effect Schema's built-in types (`Schema.Int`, `Schema.Date`) over generic types where appropriate
280
-
281
- ### Performance
282
-
283
- - Be mindful of complex types stored as JSON - they can impact query performance
284
- - Use appropriate indexes for frequently queried columns
285
- - Consider using `withColumnType` to optimize storage for specific use cases
286
-
287
- ## When to Use This Approach
288
-
289
- **Use Effect Schema-based tables when:**
290
- - You already have Effect Schema definitions to reuse
291
- - You prefer Effect Schema's composability and transformations
292
- - Your schemas are shared across different parts of your application
293
- - You want automatic type mapping and strong type safety
294
- - You plan to migrate to Effect Schema v4 when it becomes available
295
-
296
- **Consider column-based tables when:**
297
- - You need precise control over SQLite column types
298
- - You're migrating from existing SQLite schemas
299
- - You prefer explicit column configuration
300
- - You're not already using Effect Schema extensively in your project