@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,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
|
-

|
|
@@ -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
|
-

|
|
173
|
-
|
|
174
|
-
Because the `completed` field is also being persisted in the database, it also auto-syncs across browser sessions and devices.
|