@livestore/livestore 0.4.0-dev.22 → 0.4.0-dev.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +14 -7
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +0 -15
- 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/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 +1 -1
- package/dist/mod.d.ts.map +1 -1
- 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 +30 -5
- package/dist/store/StoreRegistry.d.ts.map +1 -1
- package/dist/store/StoreRegistry.js +54 -31
- package/dist/store/StoreRegistry.js.map +1 -1
- package/dist/store/StoreRegistry.test.js +251 -250
- package/dist/store/StoreRegistry.test.js.map +1 -1
- package/dist/store/create-store.d.ts +6 -2
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +13 -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 +3 -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 +70 -5
- 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 +81 -2
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +128 -45
- 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 +58 -17
- 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 +17 -26
- 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 +1 -0
- package/src/reactive.test.ts +3 -2
- package/src/reactive.ts +22 -23
- package/src/store/StoreRegistry.test.ts +317 -293
- package/src/store/StoreRegistry.ts +63 -38
- package/src/store/create-store.ts +26 -11
- package/src/store/devtools.ts +5 -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 +47 -8
- package/src/store/store.ts +172 -55
- 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 -3
- package/docs/building-with-livestore/crud/index.md +0 -3
- package/docs/building-with-livestore/data-modeling/index.md +0 -30
- 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/todo-workspaces/index.md +0 -885
- package/docs/building-with-livestore/examples/turnbased-game/index.md +0 -7
- package/docs/building-with-livestore/opentelemetry/index.md +0 -227
- 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 -94
- 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 -625
- 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 -937
- 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 -882
- package/docs/getting-started/node/index.md +0 -115
- package/docs/getting-started/react-web/index.md +0 -626
- package/docs/getting-started/solid/index.md +0 -3
- package/docs/getting-started/vue/index.md +0 -471
- package/docs/index.md +0 -208
- package/docs/llms.txt +0 -146
- 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 -413
- 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 -111
- 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 -377
- package/docs/patterns/effect/index.md +0 -1505
- 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 -209
- 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 -262
- 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 -287
- 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 -530
- 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,882 +0,0 @@
|
|
|
1
|
-
# Expo
|
|
2
|
-
|
|
3
|
-
export const CODE = {
|
|
4
|
-
babelConfig: babelConfigCode,
|
|
5
|
-
metroConfig: metroConfigCode,
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
{/* We're adjusting the package to use the dev version on the dev branch */}
|
|
9
|
-
export const manualInstallDepsStr = [
|
|
10
|
-
'@livestore/devtools-expo' + versionNpmSuffix,
|
|
11
|
-
'@livestore/adapter-expo' + versionNpmSuffix,
|
|
12
|
-
'@livestore/livestore' + versionNpmSuffix,
|
|
13
|
-
'@livestore/react' + versionNpmSuffix,
|
|
14
|
-
'@livestore/sync-cf/client' + versionNpmSuffix,
|
|
15
|
-
'@livestore/peer-deps' + versionNpmSuffix,
|
|
16
|
-
'expo-sqlite',
|
|
17
|
-
].join(' ')
|
|
18
|
-
|
|
19
|
-
### Prerequisites
|
|
20
|
-
|
|
21
|
-
- Recommended: Bun 1.2 or higher
|
|
22
|
-
- Node.js {MIN_NODE_VERSION} or higher
|
|
23
|
-
|
|
24
|
-
To use [LiveStore](/) with [Expo](https://docs.expo.dev/), ensure your project has the [New Architecture](https://docs.expo.dev/guides/new-architecture/) enabled. This is required for transactional state updates.
|
|
25
|
-
|
|
26
|
-
### Option A: Quick start
|
|
27
|
-
|
|
28
|
-
For a quick start we recommend using our template app following the steps below.
|
|
29
|
-
|
|
30
|
-
For existing projects see [Existing project setup](#existing-project-setup).
|
|
31
|
-
|
|
32
|
-
<Steps>
|
|
33
|
-
|
|
34
|
-
1. **Set up project from template**
|
|
35
|
-
|
|
36
|
-
<Tabs syncKey="package-manager">
|
|
37
|
-
<TabItem label="bun">
|
|
38
|
-
<Code code={makeCreate('expo-todomvc-sync-cf', 'bunx')} lang="sh" />
|
|
39
|
-
</TabItem>
|
|
40
|
-
<TabItem label="pnpm">
|
|
41
|
-
<Code code={makeCreate('expo-todomvc-sync-cf', 'pnpm dlx')} lang="sh" />
|
|
42
|
-
</TabItem>
|
|
43
|
-
<TabItem label="npm">
|
|
44
|
-
<Code code={makeCreate('expo-todomvc-sync-cf', 'npx')} lang="sh" />
|
|
45
|
-
</TabItem>
|
|
46
|
-
<TabItem label="yarn">
|
|
47
|
-
<Code code={makeCreate('expo-todomvc-sync-cf', 'yarn dlx')} lang="sh" />
|
|
48
|
-
</TabItem>
|
|
49
|
-
</Tabs>
|
|
50
|
-
|
|
51
|
-
Replace `livestore-app` with your desired app name.
|
|
52
|
-
|
|
53
|
-
2. **Install dependencies**
|
|
54
|
-
|
|
55
|
-
It's strongly recommended to use `bun` or `pnpm` for the simplest and most reliable dependency setup (see [note on package management](/misc/package-management) for more details).
|
|
56
|
-
|
|
57
|
-
<Tabs syncKey="package-manager">
|
|
58
|
-
<TabItem label="bun">
|
|
59
|
-
```bash
|
|
60
|
-
bun install
|
|
61
|
-
```
|
|
62
|
-
</TabItem>
|
|
63
|
-
<TabItem label="pnpm">
|
|
64
|
-
```bash
|
|
65
|
-
pnpm install --node-linker=hoisted
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
Make sure to use `--node-linker=hoisted` when installing dependencies in your project or add it to your `.npmrc` file.
|
|
69
|
-
```
|
|
70
|
-
# .npmrc
|
|
71
|
-
nodeLinker=hoisted
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
Hopefully Expo will also support non-hoisted setups in the future.
|
|
75
|
-
</TabItem>
|
|
76
|
-
<TabItem label="npm">
|
|
77
|
-
```bash
|
|
78
|
-
npm install
|
|
79
|
-
```
|
|
80
|
-
</TabItem>
|
|
81
|
-
<TabItem label="yarn">
|
|
82
|
-
When using `yarn`, make sure you're using Yarn 4 or higher with the `node-modules` linker.
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
yarn set version stable
|
|
86
|
-
yarn config set nodeLinker node-modules
|
|
87
|
-
yarn install
|
|
88
|
-
```
|
|
89
|
-
</TabItem>
|
|
90
|
-
</Tabs>
|
|
91
|
-
|
|
92
|
-
Pro tip: You can use [direnv](https://direnv.net/) to manage environment variables.
|
|
93
|
-
|
|
94
|
-
3. **Run the app**
|
|
95
|
-
|
|
96
|
-
<Tabs syncKey="package-manager">
|
|
97
|
-
<TabItem label="bun">
|
|
98
|
-
<Code code="bun start" lang="sh" />
|
|
99
|
-
</TabItem>
|
|
100
|
-
<TabItem label="pnpm">
|
|
101
|
-
<Code code="pnpm start" lang="sh" />
|
|
102
|
-
</TabItem>
|
|
103
|
-
<TabItem label="npm">
|
|
104
|
-
<Code code="npm run start" lang="sh" />
|
|
105
|
-
</TabItem>
|
|
106
|
-
<TabItem label="yarn">
|
|
107
|
-
<Code code="yarn start" lang="sh" />
|
|
108
|
-
</TabItem>
|
|
109
|
-
</Tabs>
|
|
110
|
-
|
|
111
|
-
In a new terminal, start the Cloudflare Worker (for the sync backend):
|
|
112
|
-
|
|
113
|
-
<Tabs syncKey="package-manager">
|
|
114
|
-
<TabItem label="bun">
|
|
115
|
-
<Code code="bun wrangler:dev" lang="sh" />
|
|
116
|
-
</TabItem>
|
|
117
|
-
<TabItem label="pnpm">
|
|
118
|
-
<Code code="pnpm wrangler:dev" lang="sh" />
|
|
119
|
-
</TabItem>
|
|
120
|
-
<TabItem label="npm">
|
|
121
|
-
<Code code="npm run wrangler:dev" lang="sh" />
|
|
122
|
-
</TabItem>
|
|
123
|
-
<TabItem label="yarn">
|
|
124
|
-
<Code code="yarn wrangler:dev" lang="sh" />
|
|
125
|
-
</TabItem>
|
|
126
|
-
</Tabs>
|
|
127
|
-
</Steps>
|
|
128
|
-
|
|
129
|
-
### Option B: Existing project setup \{#existing-project-setup\}
|
|
130
|
-
|
|
131
|
-
<Steps>
|
|
132
|
-
|
|
133
|
-
1. **Install dependencies**
|
|
134
|
-
|
|
135
|
-
<Tabs syncKey="package-manager">
|
|
136
|
-
<TabItem label="bun">
|
|
137
|
-
<Code code={'bun install ' + manualInstallDepsStr} lang="sh" />
|
|
138
|
-
</TabItem>
|
|
139
|
-
<TabItem label="pnpm">
|
|
140
|
-
<Code code={'pnpm install ' + manualInstallDepsStr} lang="sh" />
|
|
141
|
-
</TabItem>
|
|
142
|
-
<TabItem label="npm">
|
|
143
|
-
<Code code={'npm install ' + manualInstallDepsStr} lang="sh" />
|
|
144
|
-
</TabItem>
|
|
145
|
-
<TabItem label="yarn">
|
|
146
|
-
<Code code={'yarn add ' + manualInstallDepsStr} lang="sh" />
|
|
147
|
-
</TabItem>
|
|
148
|
-
</Tabs>
|
|
149
|
-
|
|
150
|
-
2. **Add Vite meta plugin to babel config file**
|
|
151
|
-
|
|
152
|
-
LiveStore Devtools uses Vite. This plugin emulates Vite's `import.meta.env` functionality.
|
|
153
|
-
|
|
154
|
-
<Tabs syncKey="package-manager">
|
|
155
|
-
<TabItem label="bun">
|
|
156
|
-
<Code code="bun add -d babel-plugin-transform-vite-meta-env" lang="sh" />
|
|
157
|
-
</TabItem>
|
|
158
|
-
<TabItem label="pnpm">
|
|
159
|
-
<Code code="pnpm add -D babel-plugin-transform-vite-meta-env" lang="sh" />
|
|
160
|
-
</TabItem>
|
|
161
|
-
<TabItem label="yarn">
|
|
162
|
-
<Code code="yarn add -D babel-plugin-transform-vite-meta-env" lang="sh" />
|
|
163
|
-
</TabItem>
|
|
164
|
-
<TabItem label="npm">
|
|
165
|
-
<Code code="npm install --save-dev babel-plugin-transform-vite-meta-env" lang="sh" />
|
|
166
|
-
</TabItem>
|
|
167
|
-
</Tabs>
|
|
168
|
-
|
|
169
|
-
In your `babel.config.js` file, add the plugin as follows:
|
|
170
|
-
|
|
171
|
-
<Code code={CODE.babelConfig} lang="js" title="babel.config.js" />
|
|
172
|
-
|
|
173
|
-
3. **Update Metro config**
|
|
174
|
-
|
|
175
|
-
Add the following code to your `metro.config.js` file:
|
|
176
|
-
|
|
177
|
-
<Code code={CODE.metroConfig} lang="js" title="metro.config.js" />
|
|
178
|
-
|
|
179
|
-
</Steps>
|
|
180
|
-
|
|
181
|
-
## Define your schema
|
|
182
|
-
|
|
183
|
-
Create a file named `schema.ts` inside the `src/livestore` folder. This file defines your LiveStore schema consisting of your app's event definitions (describing how data changes), derived state (i.e. SQLite tables), and materializers (how state is derived from events).
|
|
184
|
-
|
|
185
|
-
Here's an example schema:
|
|
186
|
-
|
|
187
|
-
## `getting-started/expo/livestore/schema.ts`
|
|
188
|
-
|
|
189
|
-
```ts filename="getting-started/expo/livestore/schema.ts"
|
|
190
|
-
|
|
191
|
-
export const tables = {
|
|
192
|
-
todos: State.SQLite.table({
|
|
193
|
-
name: 'todos',
|
|
194
|
-
columns: {
|
|
195
|
-
id: State.SQLite.text({ primaryKey: true }),
|
|
196
|
-
text: State.SQLite.text({ default: '' }),
|
|
197
|
-
completed: State.SQLite.boolean({ default: false }),
|
|
198
|
-
deletedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
|
|
199
|
-
},
|
|
200
|
-
}),
|
|
201
|
-
uiState: State.SQLite.clientDocument({
|
|
202
|
-
name: 'uiState',
|
|
203
|
-
schema: Schema.Struct({ newTodoText: Schema.String, filter: Schema.Literal('all', 'active', 'completed') }),
|
|
204
|
-
default: { id: SessionIdSymbol, value: { newTodoText: '', filter: 'all' } },
|
|
205
|
-
}),
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export const events = {
|
|
209
|
-
todoCreated: Events.synced({
|
|
210
|
-
name: 'v1.TodoCreated',
|
|
211
|
-
schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
|
|
212
|
-
}),
|
|
213
|
-
todoCompleted: Events.synced({
|
|
214
|
-
name: 'v1.TodoCompleted',
|
|
215
|
-
schema: Schema.Struct({ id: Schema.String }),
|
|
216
|
-
}),
|
|
217
|
-
todoUncompleted: Events.synced({
|
|
218
|
-
name: 'v1.TodoUncompleted',
|
|
219
|
-
schema: Schema.Struct({ id: Schema.String }),
|
|
220
|
-
}),
|
|
221
|
-
todoDeleted: Events.synced({
|
|
222
|
-
name: 'v1.TodoDeleted',
|
|
223
|
-
schema: Schema.Struct({ id: Schema.String, deletedAt: Schema.Date }),
|
|
224
|
-
}),
|
|
225
|
-
todoClearedCompleted: Events.synced({
|
|
226
|
-
name: 'v1.TodoClearedCompleted',
|
|
227
|
-
schema: Schema.Struct({ deletedAt: Schema.Date }),
|
|
228
|
-
}),
|
|
229
|
-
uiStateSet: tables.uiState.set,
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const materializers = State.SQLite.materializers(events, {
|
|
233
|
-
'v1.TodoCreated': ({ id, text }) => tables.todos.insert({ id, text, completed: false }),
|
|
234
|
-
'v1.TodoCompleted': ({ id }) => tables.todos.update({ completed: true }).where({ id }),
|
|
235
|
-
'v1.TodoUncompleted': ({ id }) => tables.todos.update({ completed: false }).where({ id }),
|
|
236
|
-
'v1.TodoDeleted': ({ id, deletedAt }) => tables.todos.update({ deletedAt }).where({ id }),
|
|
237
|
-
'v1.TodoClearedCompleted': ({ deletedAt }) => tables.todos.update({ deletedAt }).where({ completed: true }),
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
const state = State.SQLite.makeState({ tables, materializers })
|
|
241
|
-
|
|
242
|
-
export const schema = makeSchema({ events, state })
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
## Configure the store
|
|
246
|
-
|
|
247
|
-
Create a `store.ts` file in the `src/livestore` folder. This file configures the store adapter and exports a custom hook that components will use to access the store.
|
|
248
|
-
|
|
249
|
-
The `useStore()` hook accepts store configuration options (schema, adapter, store ID) and returns a store instance. It suspends while the store is loading, so make sure to use a `Suspense` boundary to handle the loading state.
|
|
250
|
-
|
|
251
|
-
## `getting-started/expo/livestore/store.ts`
|
|
252
|
-
|
|
253
|
-
```ts filename="getting-started/expo/livestore/store.ts"
|
|
254
|
-
|
|
255
|
-
const syncUrl = 'https://example.org/sync'
|
|
256
|
-
|
|
257
|
-
const adapter = makePersistedAdapter({
|
|
258
|
-
sync: { backend: makeWsSync({ url: syncUrl }) },
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
export const useAppStore = () =>
|
|
262
|
-
useStore({
|
|
263
|
-
storeId: 'expo-todomvc',
|
|
264
|
-
schema,
|
|
265
|
-
adapter,
|
|
266
|
-
batchUpdates,
|
|
267
|
-
boot: (store) => {
|
|
268
|
-
if (store.query(tables.todos.count()) === 0) {
|
|
269
|
-
store.commit(events.todoCreated({ id: crypto.randomUUID(), text: 'Make coffee' }))
|
|
270
|
-
}
|
|
271
|
-
},
|
|
272
|
-
})
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### `getting-started/expo/livestore/schema.ts`
|
|
276
|
-
|
|
277
|
-
```ts filename="getting-started/expo/livestore/schema.ts"
|
|
278
|
-
|
|
279
|
-
export const tables = {
|
|
280
|
-
todos: State.SQLite.table({
|
|
281
|
-
name: 'todos',
|
|
282
|
-
columns: {
|
|
283
|
-
id: State.SQLite.text({ primaryKey: true }),
|
|
284
|
-
text: State.SQLite.text({ default: '' }),
|
|
285
|
-
completed: State.SQLite.boolean({ default: false }),
|
|
286
|
-
deletedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
|
|
287
|
-
},
|
|
288
|
-
}),
|
|
289
|
-
uiState: State.SQLite.clientDocument({
|
|
290
|
-
name: 'uiState',
|
|
291
|
-
schema: Schema.Struct({ newTodoText: Schema.String, filter: Schema.Literal('all', 'active', 'completed') }),
|
|
292
|
-
default: { id: SessionIdSymbol, value: { newTodoText: '', filter: 'all' } },
|
|
293
|
-
}),
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
export const events = {
|
|
297
|
-
todoCreated: Events.synced({
|
|
298
|
-
name: 'v1.TodoCreated',
|
|
299
|
-
schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
|
|
300
|
-
}),
|
|
301
|
-
todoCompleted: Events.synced({
|
|
302
|
-
name: 'v1.TodoCompleted',
|
|
303
|
-
schema: Schema.Struct({ id: Schema.String }),
|
|
304
|
-
}),
|
|
305
|
-
todoUncompleted: Events.synced({
|
|
306
|
-
name: 'v1.TodoUncompleted',
|
|
307
|
-
schema: Schema.Struct({ id: Schema.String }),
|
|
308
|
-
}),
|
|
309
|
-
todoDeleted: Events.synced({
|
|
310
|
-
name: 'v1.TodoDeleted',
|
|
311
|
-
schema: Schema.Struct({ id: Schema.String, deletedAt: Schema.Date }),
|
|
312
|
-
}),
|
|
313
|
-
todoClearedCompleted: Events.synced({
|
|
314
|
-
name: 'v1.TodoClearedCompleted',
|
|
315
|
-
schema: Schema.Struct({ deletedAt: Schema.Date }),
|
|
316
|
-
}),
|
|
317
|
-
uiStateSet: tables.uiState.set,
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const materializers = State.SQLite.materializers(events, {
|
|
321
|
-
'v1.TodoCreated': ({ id, text }) => tables.todos.insert({ id, text, completed: false }),
|
|
322
|
-
'v1.TodoCompleted': ({ id }) => tables.todos.update({ completed: true }).where({ id }),
|
|
323
|
-
'v1.TodoUncompleted': ({ id }) => tables.todos.update({ completed: false }).where({ id }),
|
|
324
|
-
'v1.TodoDeleted': ({ id, deletedAt }) => tables.todos.update({ deletedAt }).where({ id }),
|
|
325
|
-
'v1.TodoClearedCompleted': ({ deletedAt }) => tables.todos.update({ deletedAt }).where({ completed: true }),
|
|
326
|
-
})
|
|
327
|
-
|
|
328
|
-
const state = State.SQLite.makeState({ tables, materializers })
|
|
329
|
-
|
|
330
|
-
export const schema = makeSchema({ events, state })
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
## Set up the store registry
|
|
334
|
-
|
|
335
|
-
To enable store management throughout your app, create a `StoreRegistry` and provide it with a `<StoreRegistryProvider>`. The registry manages store instance lifecycles (loading, caching, disposal).
|
|
336
|
-
|
|
337
|
-
Wrap the provider in a `Suspense` boundary to handle the loading state for when the store is loading.
|
|
338
|
-
|
|
339
|
-
## `getting-started/expo/Root.tsx`
|
|
340
|
-
|
|
341
|
-
```tsx filename="getting-started/expo/Root.tsx"
|
|
342
|
-
|
|
343
|
-
const AppContent: FC = () => (
|
|
344
|
-
<View style={{ flex: 1, gap: 24, padding: 24 }}>
|
|
345
|
-
<NewTodo />
|
|
346
|
-
<ListTodos />
|
|
347
|
-
</View>
|
|
348
|
-
)
|
|
349
|
-
|
|
350
|
-
export const Root: FC = () => {
|
|
351
|
-
const [storeRegistry] = useState(() => new StoreRegistry())
|
|
352
|
-
|
|
353
|
-
return (
|
|
354
|
-
<SafeAreaView style={{ flex: 1 }}>
|
|
355
|
-
<Suspense fallback={<Text>Loading LiveStore...</Text>}>
|
|
356
|
-
<StoreRegistryProvider storeRegistry={storeRegistry}>
|
|
357
|
-
<AppContent />
|
|
358
|
-
</StoreRegistryProvider>
|
|
359
|
-
</Suspense>
|
|
360
|
-
<StatusBar style="auto" />
|
|
361
|
-
</SafeAreaView>
|
|
362
|
-
)
|
|
363
|
-
}
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
### `getting-started/expo/components/ListTodos.tsx`
|
|
367
|
-
|
|
368
|
-
```tsx filename="getting-started/expo/components/ListTodos.tsx"
|
|
369
|
-
|
|
370
|
-
export const ListTodos: FC = () => {
|
|
371
|
-
const store = useAppStore()
|
|
372
|
-
const todos = store.useQuery(visibleTodos$)
|
|
373
|
-
|
|
374
|
-
const toggleTodo = useCallback(
|
|
375
|
-
({ id, completed }: typeof tables.todos.Type) => {
|
|
376
|
-
store.commit(completed ? events.todoUncompleted({ id }) : events.todoCompleted({ id }))
|
|
377
|
-
},
|
|
378
|
-
[store],
|
|
379
|
-
)
|
|
380
|
-
|
|
381
|
-
const clearCompleted = () => store.commit(events.todoClearedCompleted({ deletedAt: new Date() }))
|
|
382
|
-
|
|
383
|
-
return (
|
|
384
|
-
<View style={{ flex: 1, gap: 16 }}>
|
|
385
|
-
<ScrollView contentContainerStyle={{ gap: 12 }}>
|
|
386
|
-
{todos.map((todo) => (
|
|
387
|
-
<View
|
|
388
|
-
key={todo.id}
|
|
389
|
-
style={{
|
|
390
|
-
borderRadius: 12,
|
|
391
|
-
borderColor: '#d4d4d8',
|
|
392
|
-
borderWidth: 1,
|
|
393
|
-
padding: 16,
|
|
394
|
-
gap: 8,
|
|
395
|
-
}}
|
|
396
|
-
>
|
|
397
|
-
<Text style={{ fontSize: 16, fontWeight: '600' }}>{todo.text}</Text>
|
|
398
|
-
<Text>{todo.completed ? 'Completed' : 'Pending'}</Text>
|
|
399
|
-
<View style={{ flexDirection: 'row', gap: 12 }}>
|
|
400
|
-
<Button title={todo.completed ? 'Mark pending' : 'Mark done'} onPress={() => toggleTodo(todo)} />
|
|
401
|
-
<Button
|
|
402
|
-
title="Delete"
|
|
403
|
-
onPress={() => store.commit(events.todoDeleted({ id: todo.id, deletedAt: new Date() }))}
|
|
404
|
-
/>
|
|
405
|
-
</View>
|
|
406
|
-
</View>
|
|
407
|
-
))}
|
|
408
|
-
</ScrollView>
|
|
409
|
-
<Button title="Clear completed" onPress={clearCompleted} />
|
|
410
|
-
</View>
|
|
411
|
-
)
|
|
412
|
-
}
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
### `getting-started/expo/components/NewTodo.tsx`
|
|
416
|
-
|
|
417
|
-
```tsx filename="getting-started/expo/components/NewTodo.tsx"
|
|
418
|
-
|
|
419
|
-
export const NewTodo: FC = () => {
|
|
420
|
-
const store = useAppStore()
|
|
421
|
-
const { newTodoText } = store.useQuery(uiState$)
|
|
422
|
-
|
|
423
|
-
const updateText = (text: string) => store.commit(events.uiStateSet({ newTodoText: text }))
|
|
424
|
-
const createTodo = () =>
|
|
425
|
-
store.commit(
|
|
426
|
-
events.todoCreated({ id: crypto.randomUUID(), text: newTodoText }),
|
|
427
|
-
events.uiStateSet({ newTodoText: '' }),
|
|
428
|
-
)
|
|
429
|
-
|
|
430
|
-
const addSampleTodos = () => {
|
|
431
|
-
const todos = Array.from({ length: 5 }, (_, index) => ({
|
|
432
|
-
id: crypto.randomUUID(),
|
|
433
|
-
text: `Todo ${index + 1}`,
|
|
434
|
-
}))
|
|
435
|
-
store.commit(...todos.map((todo) => events.todoCreated(todo)))
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
return (
|
|
439
|
-
<View style={{ gap: 12 }}>
|
|
440
|
-
<TextInput value={newTodoText} onChangeText={updateText} placeholder="What needs to be done?" />
|
|
441
|
-
<Button title="Add todo" onPress={createTodo} />
|
|
442
|
-
<Button title="Add sample todos" onPress={addSampleTodos} />
|
|
443
|
-
</View>
|
|
444
|
-
)
|
|
445
|
-
}
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
### `getting-started/expo/livestore/queries.ts`
|
|
449
|
-
|
|
450
|
-
```ts filename="getting-started/expo/livestore/queries.ts"
|
|
451
|
-
|
|
452
|
-
export const uiState$ = queryDb(tables.uiState.get(), { label: 'uiState' })
|
|
453
|
-
|
|
454
|
-
export const visibleTodos$ = queryDb(
|
|
455
|
-
(get) => {
|
|
456
|
-
const { filter } = get(uiState$)
|
|
457
|
-
|
|
458
|
-
return tables.todos.where({
|
|
459
|
-
deletedAt: null,
|
|
460
|
-
completed: filter === 'all' ? undefined : filter === 'completed',
|
|
461
|
-
})
|
|
462
|
-
},
|
|
463
|
-
{ label: 'visibleTodos' },
|
|
464
|
-
)
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
### `getting-started/expo/livestore/schema.ts`
|
|
468
|
-
|
|
469
|
-
```ts filename="getting-started/expo/livestore/schema.ts"
|
|
470
|
-
|
|
471
|
-
export const tables = {
|
|
472
|
-
todos: State.SQLite.table({
|
|
473
|
-
name: 'todos',
|
|
474
|
-
columns: {
|
|
475
|
-
id: State.SQLite.text({ primaryKey: true }),
|
|
476
|
-
text: State.SQLite.text({ default: '' }),
|
|
477
|
-
completed: State.SQLite.boolean({ default: false }),
|
|
478
|
-
deletedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
|
|
479
|
-
},
|
|
480
|
-
}),
|
|
481
|
-
uiState: State.SQLite.clientDocument({
|
|
482
|
-
name: 'uiState',
|
|
483
|
-
schema: Schema.Struct({ newTodoText: Schema.String, filter: Schema.Literal('all', 'active', 'completed') }),
|
|
484
|
-
default: { id: SessionIdSymbol, value: { newTodoText: '', filter: 'all' } },
|
|
485
|
-
}),
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
export const events = {
|
|
489
|
-
todoCreated: Events.synced({
|
|
490
|
-
name: 'v1.TodoCreated',
|
|
491
|
-
schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
|
|
492
|
-
}),
|
|
493
|
-
todoCompleted: Events.synced({
|
|
494
|
-
name: 'v1.TodoCompleted',
|
|
495
|
-
schema: Schema.Struct({ id: Schema.String }),
|
|
496
|
-
}),
|
|
497
|
-
todoUncompleted: Events.synced({
|
|
498
|
-
name: 'v1.TodoUncompleted',
|
|
499
|
-
schema: Schema.Struct({ id: Schema.String }),
|
|
500
|
-
}),
|
|
501
|
-
todoDeleted: Events.synced({
|
|
502
|
-
name: 'v1.TodoDeleted',
|
|
503
|
-
schema: Schema.Struct({ id: Schema.String, deletedAt: Schema.Date }),
|
|
504
|
-
}),
|
|
505
|
-
todoClearedCompleted: Events.synced({
|
|
506
|
-
name: 'v1.TodoClearedCompleted',
|
|
507
|
-
schema: Schema.Struct({ deletedAt: Schema.Date }),
|
|
508
|
-
}),
|
|
509
|
-
uiStateSet: tables.uiState.set,
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
const materializers = State.SQLite.materializers(events, {
|
|
513
|
-
'v1.TodoCreated': ({ id, text }) => tables.todos.insert({ id, text, completed: false }),
|
|
514
|
-
'v1.TodoCompleted': ({ id }) => tables.todos.update({ completed: true }).where({ id }),
|
|
515
|
-
'v1.TodoUncompleted': ({ id }) => tables.todos.update({ completed: false }).where({ id }),
|
|
516
|
-
'v1.TodoDeleted': ({ id, deletedAt }) => tables.todos.update({ deletedAt }).where({ id }),
|
|
517
|
-
'v1.TodoClearedCompleted': ({ deletedAt }) => tables.todos.update({ deletedAt }).where({ completed: true }),
|
|
518
|
-
})
|
|
519
|
-
|
|
520
|
-
const state = State.SQLite.makeState({ tables, materializers })
|
|
521
|
-
|
|
522
|
-
export const schema = makeSchema({ events, state })
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
### `getting-started/expo/livestore/store.ts`
|
|
526
|
-
|
|
527
|
-
```ts filename="getting-started/expo/livestore/store.ts"
|
|
528
|
-
|
|
529
|
-
const syncUrl = 'https://example.org/sync'
|
|
530
|
-
|
|
531
|
-
const adapter = makePersistedAdapter({
|
|
532
|
-
sync: { backend: makeWsSync({ url: syncUrl }) },
|
|
533
|
-
})
|
|
534
|
-
|
|
535
|
-
export const useAppStore = () =>
|
|
536
|
-
useStore({
|
|
537
|
-
storeId: 'expo-todomvc',
|
|
538
|
-
schema,
|
|
539
|
-
adapter,
|
|
540
|
-
batchUpdates,
|
|
541
|
-
boot: (store) => {
|
|
542
|
-
if (store.query(tables.todos.count()) === 0) {
|
|
543
|
-
store.commit(events.todoCreated({ id: crypto.randomUUID(), text: 'Make coffee' }))
|
|
544
|
-
}
|
|
545
|
-
},
|
|
546
|
-
})
|
|
547
|
-
```
|
|
548
|
-
|
|
549
|
-
## Commit events
|
|
550
|
-
|
|
551
|
-
After setting up the registry, use the `useAppStore()` hook from any component to access the store and commit events.
|
|
552
|
-
|
|
553
|
-
## `getting-started/expo/components/NewTodo.tsx`
|
|
554
|
-
|
|
555
|
-
```tsx filename="getting-started/expo/components/NewTodo.tsx"
|
|
556
|
-
|
|
557
|
-
export const NewTodo: FC = () => {
|
|
558
|
-
const store = useAppStore()
|
|
559
|
-
const { newTodoText } = store.useQuery(uiState$)
|
|
560
|
-
|
|
561
|
-
const updateText = (text: string) => store.commit(events.uiStateSet({ newTodoText: text }))
|
|
562
|
-
const createTodo = () =>
|
|
563
|
-
store.commit(
|
|
564
|
-
events.todoCreated({ id: crypto.randomUUID(), text: newTodoText }),
|
|
565
|
-
events.uiStateSet({ newTodoText: '' }),
|
|
566
|
-
)
|
|
567
|
-
|
|
568
|
-
const addSampleTodos = () => {
|
|
569
|
-
const todos = Array.from({ length: 5 }, (_, index) => ({
|
|
570
|
-
id: crypto.randomUUID(),
|
|
571
|
-
text: `Todo ${index + 1}`,
|
|
572
|
-
}))
|
|
573
|
-
store.commit(...todos.map((todo) => events.todoCreated(todo)))
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
return (
|
|
577
|
-
<View style={{ gap: 12 }}>
|
|
578
|
-
<TextInput value={newTodoText} onChangeText={updateText} placeholder="What needs to be done?" />
|
|
579
|
-
<Button title="Add todo" onPress={createTodo} />
|
|
580
|
-
<Button title="Add sample todos" onPress={addSampleTodos} />
|
|
581
|
-
</View>
|
|
582
|
-
)
|
|
583
|
-
}
|
|
584
|
-
```
|
|
585
|
-
|
|
586
|
-
### `getting-started/expo/livestore/queries.ts`
|
|
587
|
-
|
|
588
|
-
```ts filename="getting-started/expo/livestore/queries.ts"
|
|
589
|
-
|
|
590
|
-
export const uiState$ = queryDb(tables.uiState.get(), { label: 'uiState' })
|
|
591
|
-
|
|
592
|
-
export const visibleTodos$ = queryDb(
|
|
593
|
-
(get) => {
|
|
594
|
-
const { filter } = get(uiState$)
|
|
595
|
-
|
|
596
|
-
return tables.todos.where({
|
|
597
|
-
deletedAt: null,
|
|
598
|
-
completed: filter === 'all' ? undefined : filter === 'completed',
|
|
599
|
-
})
|
|
600
|
-
},
|
|
601
|
-
{ label: 'visibleTodos' },
|
|
602
|
-
)
|
|
603
|
-
```
|
|
604
|
-
|
|
605
|
-
### `getting-started/expo/livestore/schema.ts`
|
|
606
|
-
|
|
607
|
-
```ts filename="getting-started/expo/livestore/schema.ts"
|
|
608
|
-
|
|
609
|
-
export const tables = {
|
|
610
|
-
todos: State.SQLite.table({
|
|
611
|
-
name: 'todos',
|
|
612
|
-
columns: {
|
|
613
|
-
id: State.SQLite.text({ primaryKey: true }),
|
|
614
|
-
text: State.SQLite.text({ default: '' }),
|
|
615
|
-
completed: State.SQLite.boolean({ default: false }),
|
|
616
|
-
deletedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
|
|
617
|
-
},
|
|
618
|
-
}),
|
|
619
|
-
uiState: State.SQLite.clientDocument({
|
|
620
|
-
name: 'uiState',
|
|
621
|
-
schema: Schema.Struct({ newTodoText: Schema.String, filter: Schema.Literal('all', 'active', 'completed') }),
|
|
622
|
-
default: { id: SessionIdSymbol, value: { newTodoText: '', filter: 'all' } },
|
|
623
|
-
}),
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
export const events = {
|
|
627
|
-
todoCreated: Events.synced({
|
|
628
|
-
name: 'v1.TodoCreated',
|
|
629
|
-
schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
|
|
630
|
-
}),
|
|
631
|
-
todoCompleted: Events.synced({
|
|
632
|
-
name: 'v1.TodoCompleted',
|
|
633
|
-
schema: Schema.Struct({ id: Schema.String }),
|
|
634
|
-
}),
|
|
635
|
-
todoUncompleted: Events.synced({
|
|
636
|
-
name: 'v1.TodoUncompleted',
|
|
637
|
-
schema: Schema.Struct({ id: Schema.String }),
|
|
638
|
-
}),
|
|
639
|
-
todoDeleted: Events.synced({
|
|
640
|
-
name: 'v1.TodoDeleted',
|
|
641
|
-
schema: Schema.Struct({ id: Schema.String, deletedAt: Schema.Date }),
|
|
642
|
-
}),
|
|
643
|
-
todoClearedCompleted: Events.synced({
|
|
644
|
-
name: 'v1.TodoClearedCompleted',
|
|
645
|
-
schema: Schema.Struct({ deletedAt: Schema.Date }),
|
|
646
|
-
}),
|
|
647
|
-
uiStateSet: tables.uiState.set,
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
const materializers = State.SQLite.materializers(events, {
|
|
651
|
-
'v1.TodoCreated': ({ id, text }) => tables.todos.insert({ id, text, completed: false }),
|
|
652
|
-
'v1.TodoCompleted': ({ id }) => tables.todos.update({ completed: true }).where({ id }),
|
|
653
|
-
'v1.TodoUncompleted': ({ id }) => tables.todos.update({ completed: false }).where({ id }),
|
|
654
|
-
'v1.TodoDeleted': ({ id, deletedAt }) => tables.todos.update({ deletedAt }).where({ id }),
|
|
655
|
-
'v1.TodoClearedCompleted': ({ deletedAt }) => tables.todos.update({ deletedAt }).where({ completed: true }),
|
|
656
|
-
})
|
|
657
|
-
|
|
658
|
-
const state = State.SQLite.makeState({ tables, materializers })
|
|
659
|
-
|
|
660
|
-
export const schema = makeSchema({ events, state })
|
|
661
|
-
```
|
|
662
|
-
|
|
663
|
-
### `getting-started/expo/livestore/store.ts`
|
|
664
|
-
|
|
665
|
-
```ts filename="getting-started/expo/livestore/store.ts"
|
|
666
|
-
|
|
667
|
-
const syncUrl = 'https://example.org/sync'
|
|
668
|
-
|
|
669
|
-
const adapter = makePersistedAdapter({
|
|
670
|
-
sync: { backend: makeWsSync({ url: syncUrl }) },
|
|
671
|
-
})
|
|
672
|
-
|
|
673
|
-
export const useAppStore = () =>
|
|
674
|
-
useStore({
|
|
675
|
-
storeId: 'expo-todomvc',
|
|
676
|
-
schema,
|
|
677
|
-
adapter,
|
|
678
|
-
batchUpdates,
|
|
679
|
-
boot: (store) => {
|
|
680
|
-
if (store.query(tables.todos.count()) === 0) {
|
|
681
|
-
store.commit(events.todoCreated({ id: crypto.randomUUID(), text: 'Make coffee' }))
|
|
682
|
-
}
|
|
683
|
-
},
|
|
684
|
-
})
|
|
685
|
-
```
|
|
686
|
-
|
|
687
|
-
## Queries
|
|
688
|
-
|
|
689
|
-
To retrieve data from the database, define a query using `queryDb` from `@livestore/livestore`, then execute it with `store.useQuery()`.
|
|
690
|
-
|
|
691
|
-
Consider abstracting queries into a separate file to keep your code organized, though you can also define them directly within components if preferred.
|
|
692
|
-
|
|
693
|
-
## `getting-started/expo/components/ListTodos.tsx`
|
|
694
|
-
|
|
695
|
-
```tsx filename="getting-started/expo/components/ListTodos.tsx"
|
|
696
|
-
|
|
697
|
-
export const ListTodos: FC = () => {
|
|
698
|
-
const store = useAppStore()
|
|
699
|
-
const todos = store.useQuery(visibleTodos$)
|
|
700
|
-
|
|
701
|
-
const toggleTodo = useCallback(
|
|
702
|
-
({ id, completed }: typeof tables.todos.Type) => {
|
|
703
|
-
store.commit(completed ? events.todoUncompleted({ id }) : events.todoCompleted({ id }))
|
|
704
|
-
},
|
|
705
|
-
[store],
|
|
706
|
-
)
|
|
707
|
-
|
|
708
|
-
const clearCompleted = () => store.commit(events.todoClearedCompleted({ deletedAt: new Date() }))
|
|
709
|
-
|
|
710
|
-
return (
|
|
711
|
-
<View style={{ flex: 1, gap: 16 }}>
|
|
712
|
-
<ScrollView contentContainerStyle={{ gap: 12 }}>
|
|
713
|
-
{todos.map((todo) => (
|
|
714
|
-
<View
|
|
715
|
-
key={todo.id}
|
|
716
|
-
style={{
|
|
717
|
-
borderRadius: 12,
|
|
718
|
-
borderColor: '#d4d4d8',
|
|
719
|
-
borderWidth: 1,
|
|
720
|
-
padding: 16,
|
|
721
|
-
gap: 8,
|
|
722
|
-
}}
|
|
723
|
-
>
|
|
724
|
-
<Text style={{ fontSize: 16, fontWeight: '600' }}>{todo.text}</Text>
|
|
725
|
-
<Text>{todo.completed ? 'Completed' : 'Pending'}</Text>
|
|
726
|
-
<View style={{ flexDirection: 'row', gap: 12 }}>
|
|
727
|
-
<Button title={todo.completed ? 'Mark pending' : 'Mark done'} onPress={() => toggleTodo(todo)} />
|
|
728
|
-
<Button
|
|
729
|
-
title="Delete"
|
|
730
|
-
onPress={() => store.commit(events.todoDeleted({ id: todo.id, deletedAt: new Date() }))}
|
|
731
|
-
/>
|
|
732
|
-
</View>
|
|
733
|
-
</View>
|
|
734
|
-
))}
|
|
735
|
-
</ScrollView>
|
|
736
|
-
<Button title="Clear completed" onPress={clearCompleted} />
|
|
737
|
-
</View>
|
|
738
|
-
)
|
|
739
|
-
}
|
|
740
|
-
```
|
|
741
|
-
|
|
742
|
-
### `getting-started/expo/livestore/queries.ts`
|
|
743
|
-
|
|
744
|
-
```ts filename="getting-started/expo/livestore/queries.ts"
|
|
745
|
-
|
|
746
|
-
export const uiState$ = queryDb(tables.uiState.get(), { label: 'uiState' })
|
|
747
|
-
|
|
748
|
-
export const visibleTodos$ = queryDb(
|
|
749
|
-
(get) => {
|
|
750
|
-
const { filter } = get(uiState$)
|
|
751
|
-
|
|
752
|
-
return tables.todos.where({
|
|
753
|
-
deletedAt: null,
|
|
754
|
-
completed: filter === 'all' ? undefined : filter === 'completed',
|
|
755
|
-
})
|
|
756
|
-
},
|
|
757
|
-
{ label: 'visibleTodos' },
|
|
758
|
-
)
|
|
759
|
-
```
|
|
760
|
-
|
|
761
|
-
### `getting-started/expo/livestore/schema.ts`
|
|
762
|
-
|
|
763
|
-
```ts filename="getting-started/expo/livestore/schema.ts"
|
|
764
|
-
|
|
765
|
-
export const tables = {
|
|
766
|
-
todos: State.SQLite.table({
|
|
767
|
-
name: 'todos',
|
|
768
|
-
columns: {
|
|
769
|
-
id: State.SQLite.text({ primaryKey: true }),
|
|
770
|
-
text: State.SQLite.text({ default: '' }),
|
|
771
|
-
completed: State.SQLite.boolean({ default: false }),
|
|
772
|
-
deletedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
|
|
773
|
-
},
|
|
774
|
-
}),
|
|
775
|
-
uiState: State.SQLite.clientDocument({
|
|
776
|
-
name: 'uiState',
|
|
777
|
-
schema: Schema.Struct({ newTodoText: Schema.String, filter: Schema.Literal('all', 'active', 'completed') }),
|
|
778
|
-
default: { id: SessionIdSymbol, value: { newTodoText: '', filter: 'all' } },
|
|
779
|
-
}),
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
export const events = {
|
|
783
|
-
todoCreated: Events.synced({
|
|
784
|
-
name: 'v1.TodoCreated',
|
|
785
|
-
schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
|
|
786
|
-
}),
|
|
787
|
-
todoCompleted: Events.synced({
|
|
788
|
-
name: 'v1.TodoCompleted',
|
|
789
|
-
schema: Schema.Struct({ id: Schema.String }),
|
|
790
|
-
}),
|
|
791
|
-
todoUncompleted: Events.synced({
|
|
792
|
-
name: 'v1.TodoUncompleted',
|
|
793
|
-
schema: Schema.Struct({ id: Schema.String }),
|
|
794
|
-
}),
|
|
795
|
-
todoDeleted: Events.synced({
|
|
796
|
-
name: 'v1.TodoDeleted',
|
|
797
|
-
schema: Schema.Struct({ id: Schema.String, deletedAt: Schema.Date }),
|
|
798
|
-
}),
|
|
799
|
-
todoClearedCompleted: Events.synced({
|
|
800
|
-
name: 'v1.TodoClearedCompleted',
|
|
801
|
-
schema: Schema.Struct({ deletedAt: Schema.Date }),
|
|
802
|
-
}),
|
|
803
|
-
uiStateSet: tables.uiState.set,
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
const materializers = State.SQLite.materializers(events, {
|
|
807
|
-
'v1.TodoCreated': ({ id, text }) => tables.todos.insert({ id, text, completed: false }),
|
|
808
|
-
'v1.TodoCompleted': ({ id }) => tables.todos.update({ completed: true }).where({ id }),
|
|
809
|
-
'v1.TodoUncompleted': ({ id }) => tables.todos.update({ completed: false }).where({ id }),
|
|
810
|
-
'v1.TodoDeleted': ({ id, deletedAt }) => tables.todos.update({ deletedAt }).where({ id }),
|
|
811
|
-
'v1.TodoClearedCompleted': ({ deletedAt }) => tables.todos.update({ deletedAt }).where({ completed: true }),
|
|
812
|
-
})
|
|
813
|
-
|
|
814
|
-
const state = State.SQLite.makeState({ tables, materializers })
|
|
815
|
-
|
|
816
|
-
export const schema = makeSchema({ events, state })
|
|
817
|
-
```
|
|
818
|
-
|
|
819
|
-
### `getting-started/expo/livestore/store.ts`
|
|
820
|
-
|
|
821
|
-
```ts filename="getting-started/expo/livestore/store.ts"
|
|
822
|
-
|
|
823
|
-
const syncUrl = 'https://example.org/sync'
|
|
824
|
-
|
|
825
|
-
const adapter = makePersistedAdapter({
|
|
826
|
-
sync: { backend: makeWsSync({ url: syncUrl }) },
|
|
827
|
-
})
|
|
828
|
-
|
|
829
|
-
export const useAppStore = () =>
|
|
830
|
-
useStore({
|
|
831
|
-
storeId: 'expo-todomvc',
|
|
832
|
-
schema,
|
|
833
|
-
adapter,
|
|
834
|
-
batchUpdates,
|
|
835
|
-
boot: (store) => {
|
|
836
|
-
if (store.query(tables.todos.count()) === 0) {
|
|
837
|
-
store.commit(events.todoCreated({ id: crypto.randomUUID(), text: 'Make coffee' }))
|
|
838
|
-
}
|
|
839
|
-
},
|
|
840
|
-
})
|
|
841
|
-
```
|
|
842
|
-
|
|
843
|
-
## Devtools
|
|
844
|
-
|
|
845
|
-
To open the devtools, run the app and from your terminal press `shift + m`, then select LiveStore Devtools and press `Enter`.
|
|
846
|
-
|
|
847
|
-

|
|
848
|
-
|
|
849
|
-
This will open the devtools in a new tab in your default browser.
|
|
850
|
-
|
|
851
|
-

|
|
852
|
-
|
|
853
|
-
Use the devtools to inspect the state of your LiveStore database, execute events, track performance, and more.
|
|
854
|
-
|
|
855
|
-
## Database location
|
|
856
|
-
|
|
857
|
-
### With Expo Go
|
|
858
|
-
|
|
859
|
-
To open the database in Finder, run the following command in your terminal:
|
|
860
|
-
|
|
861
|
-
```bash
|
|
862
|
-
open $(find $(xcrun simctl get_app_container booted host.exp.Exponent data) -path "*/Documents/ExponentExperienceData/*livestore-expo*" -print -quit)/SQLite
|
|
863
|
-
```
|
|
864
|
-
|
|
865
|
-
### With development builds
|
|
866
|
-
|
|
867
|
-
For development builds, the app SQLite database is stored in the app's Library directory.
|
|
868
|
-
|
|
869
|
-
Example:
|
|
870
|
-
`/Users/<USERNAME>/Library/Developer/CoreSimulator/Devices/<DEVICE_ID>/data/Containers/Data/Application/<APP_ID>/Documents/SQLite/app.db`
|
|
871
|
-
|
|
872
|
-
To open the database in Finder, run the following command in your terminal:
|
|
873
|
-
|
|
874
|
-
```bash
|
|
875
|
-
open $(xcrun simctl get_app_container booted [APP_BUNDLE_ID] data)/Documents/SQLite
|
|
876
|
-
```
|
|
877
|
-
|
|
878
|
-
Replace `[APP_BUNDLE_ID]` with your app's bundle ID. e.g. `dev.livestore.livestore-expo`.
|
|
879
|
-
|
|
880
|
-
## Further notes
|
|
881
|
-
|
|
882
|
-
- LiveStore doesn't yet support Expo Web (see [#130](https://github.com/livestorejs/livestore/issues/130))
|