@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,210 +0,0 @@
1
- # 4. Sync data to Cloudflare
2
-
3
- In this step, you're going to introduce a [sync](/building-with-livestore/syncing) backend. This sync backend will:
4
-
5
- - Have a "live connection" (via WebSockets) to _all_ the clients that are running your app.
6
- - Propagate events to _all_ other clients whenever a particular client emits an event.
7
-
8
- Notice how you'll achieve this by _only_ changing the data layer of your application!
9
-
10
- You won't need to touch the code that you've already written in `main.tsx` and `App.tsx`—all the syncing is handled by LiveStore under the hood without you needing to worry about it in your application code.
11
-
12
- You're going to implement the sync backend using [Cloudflare Workers](https://developers.cloudflare.com/workers/) and [Durable Objects](https://developers.cloudflare.com/durable-objects/), using the [@livestore/sync-cf](/sync-providers/cloudflare) package.
13
-
14
- ## Install the Cloudflare sync provider package
15
-
16
- Run the following command in your terminal:
17
-
18
- <Tabs syncKey="package-manager">
19
-
20
- <TabItem label="bun">
21
-
22
- <Code code={`bun add @livestore/sync-cf@0.4.0-dev.14`} lang="sh" />
23
-
24
- </TabItem>
25
-
26
- <TabItem label="pnpm">
27
-
28
- <Code code={`pnpm add @livestore/sync-cf@0.4.0-dev.14`} lang="sh" />
29
-
30
- </TabItem>
31
-
32
- </Tabs>
33
-
34
- ## Create the sync backend
35
-
36
- Now, create a new `sync` directory and a new file for the sync backend:
37
-
38
- ```bash
39
- mkdir src/sync
40
- touch src/sync/client-ws.ts
41
- ```
42
-
43
- Now, add the following code to it:
44
-
45
- ```ts title="src/sync/client-ws.ts"
46
-
47
- export class SyncBackendDO extends makeDurableObject({
48
- onPush: async (message, context) => {
49
- console.log('client-ws.ts: onPush', message, context)
50
- },
51
- onPull: async (message, context) => {
52
- console.log('client-ws.ts: onPull', message, context)
53
- },
54
- }) {}
55
-
56
- export default {
57
- async fetch(request: CfTypes.Request, _env: SyncBackend.Env, ctx: CfTypes.ExecutionContext) {
58
- const searchParams = SyncBackend.matchSyncRequest(request)
59
- console.log('client-ws.ts: fetch in with searchParams', searchParams)
60
- if (searchParams !== undefined) {
61
- return SyncBackend.handleSyncRequest({
62
- request,
63
- searchParams,
64
- ctx,
65
- syncBackendBinding: 'SYNC_BACKEND_DO',
66
- })
67
- }
68
-
69
- return new Response('Not Found', { status: 404 })
70
- },
71
- }
72
- ```
73
-
74
- This code will be deployed as a Durable Object (think of it as a "Cloudflare Worker with WebSocket capabilities and attached storage").
75
-
76
- In this tutorial, you won't need to go beyond the following basic boilerplate code for syncing.
77
-
78
- However, in more advanced scenarios, there are ways for you to hook into the connection between your client apps and the sync backend. This could e.g. be useful when your app requires authentication and you need to send along an auth token.
79
-
80
- Next, you need to slightly modify the code for your LiveStore web worker in `/src/livestore/livestore.worker.ts`:
81
-
82
- ```diff title="/src/livestore/livestore.worker.ts" lang="ts"
83
-
84
- +import { makeWsSync } from '@livestore/sync-cf/client'
85
-
86
- makeWorker({
87
- schema,
88
- + sync: {
89
- + backend: makeWsSync({ url: `${location.origin}/sync` }),
90
- + }
91
- })
92
- ```
93
-
94
- Finally, you need to update your `wrangler.toml` to ensure Vite and Cloudflare know about the new sync backend:
95
-
96
- ```diff title="wrangler.toml" lang="toml"
97
- name = "livestore-todo-app"
98
- compatibility_date = "2024-10-28"
99
- +main = "./src/sync/client-ws.ts"
100
- +compatibility_flags = [
101
- + "nodejs_compat",
102
- +]
103
-
104
- [observability]
105
- enabled = true
106
-
107
- +[[durable_objects.bindings]]
108
- +name = "SYNC_BACKEND_DO"
109
- +class_name = "SyncBackendDO"
110
-
111
- +[[migrations]]
112
- +tag = "v1"
113
- +new_sqlite_classes = ["SyncBackendDO"]
114
- ```
115
-
116
- Here's a quick rundown of the changes:
117
-
118
- - `main`: Since you're now introducing a "backend", your app needs to be reconfigured. It now has a dedicated entry point in `./src/sync/client-ws.ts`. When your Worker receives a request, this file's exported handlers are executed. In your case, it points to the WebSocket sync backend implementation.
119
- - `durable_objects.bindings`: This configures the bindings for your Durable Object. You'll be able to inspect it in the Cloudflare Dashboard by its name `SYNC_BACKEND_DO`.
120
- - `migrations`: Whenever you introduce a new Durable Object that supports [SQLite storage](https://developers.cloudflare.com/durable-objects/best-practices/access-durable-objects-storage/#create-sqlite-backed-durable-object-class) (or update an existing one), you need to add a migration referencing the new or updated Durable Object class.
121
-
122
- You're now ready to run and deploy the app:
123
-
124
- <Tabs syncKey="package-manager">
125
-
126
- <TabItem label="bun">
127
-
128
- <Code code={`bun run deploy`} lang="sh" />
129
-
130
- </TabItem>
131
-
132
- <TabItem label="pnpm">
133
-
134
- <Code code={`pnpm run deploy`} lang="sh" />
135
-
136
- </TabItem>
137
-
138
- </Tabs>
139
-
140
- <details>
141
- <summary>Expand to view the expected output</summary>
142
-
143
- ```
144
- ✗ pnpm run deploy
145
-
146
- > livestore-todo-app@0.0.0 deploy /Users/nikolasburk/Projects/LiveStore/plain-react-tutorial/livestore-todo-app
147
- > pnpm run build && wrangler deploy
148
-
149
- > livestore-todo-app@0.0.0 build /Users/nikolasburk/Projects/LiveStore/plain-react-tutorial/livestore-todo-app
150
- > tsc -b && vite build
151
-
152
- vite v7.1.12 building SSR bundle for production...
153
- ✓ 1085 modules transformed.
154
- dist/livestore_todo_app/.vite/manifest.json 0.16 kB
155
- dist/livestore_todo_app/wrangler.json 1.37 kB
156
- dist/livestore_todo_app/index.js 1,021.14 kB
157
- ✓ built in 1.31s
158
- vite v7.1.12 building for production...
159
- ✓ 1119 modules transformed.
160
- dist/client/index.html 0.47 kB │ gzip: 0.30 kB
161
- dist/client/assets/make-shared-worker-CbM93UVL.js 363.01 kB
162
- dist/client/assets/livestore.worker-DbNV_so_.js 594.87 kB
163
- dist/client/assets/wa-sqlite-CLgeTS2u.wasm 618.93 kB │ gzip: 303.78 kB
164
- dist/client/assets/index-DNxhg8nM.css 11.76 kB │ gzip: 3.12 kB
165
- dist/client/assets/index-BhsI_Uw7.js 760.04 kB │ gzip: 238.67 kB
166
-
167
- (!) Some chunks are larger than 500 kB after minification. Consider:
168
- - Using dynamic import() to code-split the application
169
- - Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks
170
- - Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
171
- ✓ built in 4.29s
172
-
173
- ⛅️ wrangler 4.45.1
174
- ───────────────────
175
- Using redirected Wrangler configuration.
176
- - Configuration being used: "dist/livestore_todo_app/wrangler.json"
177
- - Original user's configuration: "wrangler.toml"
178
- - Deploy configuration file: ".wrangler/deploy/config.json"
179
- 🌀 Building list of assets...
180
- ✨ Read 8 files from the assets directory /Users/nikolasburk/Projects/LiveStore/plain-react-tutorial/livestore-todo-app/dist/client
181
- 🌀 Starting asset upload...
182
- 🌀 Found 4 new or modified static assets to upload. Proceeding with upload...
183
- + /index.html
184
- + /assets/index-BhsI_Uw7.js
185
- + /assets/index-DNxhg8nM.css
186
- + /assets/livestore.worker-DbNV_so_.js
187
- Uploaded 1 of 4 assets
188
- Uploaded 2 of 4 assets
189
- Uploaded 4 of 4 assets
190
- ✨ Success! Uploaded 4 files (3 already uploaded) (4.31 sec)
191
-
192
- Total Upload: 997.21 KiB / gzip: 208.73 KiB
193
- Worker Startup Time: 47 ms
194
- Your Worker has access to the following bindings:
195
- Binding Resource
196
- env.SYNC_BACKEND_DO (SyncBackendDO) Durable Object
197
-
198
- Uploaded livestore-todo-app (17.72 sec)
199
- Deployed livestore-todo-app triggers (9.82 sec)
200
- https://livestore-todo-app.nikolas-burk.workers.dev
201
- Current Version ID: 8860dd68-6082-4da9-9407-dea670cd8b23
202
- ```
203
-
204
- </details>
205
-
206
- Feel free to test the new behaviour locally or using the deployed version. This time, data syncing will work across browsers and even devices!
207
-
208
- Here's a GIF demonstrating how a regular Chrome tab, an incognito Chrome tab and a Safari tab are staying in sync automatically:
209
-
210
- ![](../../../assets/tutorial/chapter-4/1-livestore-cf-sync.gif)
@@ -1,174 +0,0 @@
1
- # 5. Expand business logic with more events
2
-
3
- In this chapter, you'll expand the business logic of the app by adding a "todo completed" feature.
4
-
5
- You'll do this in these steps:
6
-
7
- 1. Update your schema to:
8
- 1. Add a `boolean` column to your SQLite table.
9
- 2. Add events to mark a todo as completed and uncompleted.
10
- 3. Add a materializer to update the DB when one of these events happens.
11
- 2. Update the UI with a checkbox element for each todo which sends these events when toggled.
12
-
13
- ## Update your LiveStore schema
14
-
15
- Open your `schema.ts` file and update it with a new column, and two events (for completing and uncompleting a todo) with two corresponding materializers:
16
-
17
- ```diff title="src/livestore/schema.ts" lang="ts"
18
-
19
- export const tables = {
20
- todos: State.SQLite.table({
21
- name: 'todos',
22
- columns: {
23
- id: State.SQLite.integer({ primaryKey: true }),
24
- text: State.SQLite.text({ default: '' }),
25
- + completed: State.SQLite.boolean({ default: false }),
26
- },
27
- })
28
- }
29
-
30
- export const events = {
31
- todoCreated: Events.synced({
32
- name: 'v1.TodoCreated',
33
- schema: Schema.Struct({ id: Schema.Number, text: Schema.String }),
34
- }),
35
- todoDeleted: Events.synced({
36
- name: 'v1.TodoDeleted',
37
- schema: Schema.Struct({ id: Schema.Number }),
38
- }),
39
- + todoCompleted: Events.synced({
40
- + name: 'v1.TodoCompleted',
41
- + schema: Schema.Struct({ id: Schema.Number }),
42
- + }),
43
- + todoUncompleted: Events.synced({
44
- + name: 'v1.TodoUncompleted',
45
- + schema: Schema.Struct({ id: Schema.Number }),
46
- + }),
47
- }
48
-
49
- const materializers = State.SQLite.materializers(events, {
50
- 'v1.TodoCreated': ({ id, text }) => tables.todos.insert({ id, text }),
51
- 'v1.TodoDeleted': ({ id }) => tables.todos.delete().where({ id: id }),
52
-
53
- + 'v1.TodoCompleted': ({ id }) => tables.todos.update({ completed: true }).where({ id }),
54
- + 'v1.TodoUncompleted': ({ id }) => tables.todos.update({ completed: false }).where({ id }),
55
- })
56
-
57
- const state = State.SQLite.makeState({ tables, materializers })
58
- export const schema = makeSchema({ events, state })
59
- ```
60
-
61
- ## Update the UI and business logic
62
-
63
- Next, add a `toggleTodo` function in `App.tsx` in which you're emitting the events that you've just defined and invoke it from a new checkbox element that's added to each todo element:
64
-
65
- ```diff title="src/App.tsx" lang="ts"
66
-
67
- function App() {
68
-
69
- const { store } = useStore()
70
-
71
- const todos$ = queryDb(() => tables.todos.select())
72
-
73
- const todos = store.useQuery(todos$)
74
-
75
- const [input, setInput] = useState('')
76
-
77
- const addTodo = () => {
78
- if (input.trim()) {
79
- store.commit(
80
- events.todoCreated({ id: Date.now(), text: input }),
81
- )
82
- setInput('')
83
- }
84
- }
85
-
86
- const deleteTodo = (id: number) => {
87
- store.commit(
88
- events.todoDeleted({ id }),
89
- )
90
- }
91
-
92
- + const toggleTodo = (id: number, completed: boolean) => {
93
- + store.commit(
94
- + completed ? events.todoUncompleted({ id }) : events.todoCompleted({ id })
95
- + )
96
- + }
97
-
98
- const handleKeyDown = (e: React.KeyboardEvent) => {
99
- if (e.key === 'Enter') {
100
- addTodo()
101
- }
102
- }
103
-
104
- return (
105
- <div className="min-h-screen bg-gray-50 flex items-center justify-center p-6">
106
- <div className="w-full max-w-lg">
107
- <h1 className="text-5xl font-bold text-gray-800 text-center mb-12">
108
- Todo List
109
- </h1>
110
-
111
- <div className="flex gap-3 mb-8">
112
- <input
113
- type="text"
114
- value={input}
115
- onChange={(e) => setInput(e.target.value)}
116
- onKeyDown={handleKeyDown}
117
- placeholder="Enter a todo..."
118
- className="flex-1 px-4 py-2 text-sm border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
119
- />
120
- <button
121
- onClick={addTodo}
122
- className="px-6 py-2 text-sm font-medium text-white bg-blue-500 rounded hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors"
123
- >
124
- Add
125
- </button>
126
- </div>
127
-
128
- <div className="space-y-3">
129
- {todos.map(todo => (
130
- + <div
131
- + key={todo.id}
132
- + className="flex items-center justify-between bg-white px-4 py-3 rounded shadow-sm"
133
- + >
134
- + <div className="flex items-center gap-3 flex-1">
135
- + <input
136
- + type="checkbox"
137
- + checked={todo.completed}
138
- + onChange={() => toggleTodo(todo.id, todo.completed)}
139
- + className="w-4 h-4 cursor-pointer"
140
- + />
141
- + <span className={`text-gray-700 ${todo.completed ? 'line-through text-gray-400' : ''}`}>
142
- + {todo.text}
143
- + </span>
144
- + </div>
145
- + <button
146
- + onClick={() => deleteTodo(todo.id)}
147
- + className="px-4 py-1 text-sm font-medium text-white bg-red-500 rounded hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition-colors"
148
- + >
149
- + Delete
150
- + </button>
151
- + </div>
152
- ))}
153
- </div>
154
-
155
- {todos.length === 0 && (
156
- <p className="text-center text-gray-400 mt-8">
157
- No todos yet. Add one above!
158
- </p>
159
- )}
160
- </div>
161
- </div>
162
- )
163
- }
164
-
165
- export default App
166
- ```
167
-
168
- ## Deploy and test
169
-
170
- If you run or deploy the app, the UI will now look as follows:
171
-
172
- ![](../../../assets/tutorial/chapter-5/0-completed-ui.png)
173
-
174
- Because the `completed` field is also being persisted in the database, it also auto-syncs across browser sessions and devices.