@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,736 +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
- ## Add the LiveStore Provider
246
-
247
- To make the LiveStore available throughout your app, wrap your app's root component with the `LiveStoreProvider` component from `@livestore/react`. This provider manages your app’s data store, loading, and error states.
248
-
249
- Here's an example:
250
-
251
- ## `getting-started/expo/Root.tsx`
252
-
253
- ```tsx filename="getting-started/expo/Root.tsx"
254
-
255
- const storeId = 'expo-todomvc'
256
- const syncUrl = 'https://example.org/sync'
257
-
258
- const adapter = makePersistedAdapter({
259
- sync: { backend: makeWsSync({ url: syncUrl }) },
260
- })
261
-
262
- export const Root: FC = () => (
263
- <SafeAreaView style={{ flex: 1 }}>
264
- <LiveStoreProvider
265
- schema={schema}
266
- adapter={adapter}
267
- storeId={storeId}
268
- batchUpdates={batchUpdates}
269
- renderLoading={(status) => <Text>Loading LiveStore ({status.stage})...</Text>}
270
- renderError={(error) => <Text>Error: {String(error)}</Text>}
271
- renderShutdown={() => <Text>LiveStore shutdown</Text>}
272
- boot={(store) => {
273
- if (store.query(tables.todos.count()) === 0) {
274
- store.commit(events.todoCreated({ id: crypto.randomUUID(), text: 'Make coffee' }))
275
- }
276
- }}
277
- >
278
- <View style={{ flex: 1, gap: 24, padding: 24 }}>
279
- <NewTodo />
280
- <ListTodos />
281
- </View>
282
- </LiveStoreProvider>
283
- <StatusBar style="auto" />
284
- </SafeAreaView>
285
- )
286
- ```
287
-
288
- ### `getting-started/expo/components/ListTodos.tsx`
289
-
290
- ```tsx filename="getting-started/expo/components/ListTodos.tsx"
291
-
292
- export const ListTodos: FC = () => {
293
- const { store } = useStore()
294
- const todos = useQuery(visibleTodos$)
295
-
296
- const toggleTodo = useCallback(
297
- ({ id, completed }: typeof tables.todos.Type) => {
298
- store.commit(completed ? events.todoUncompleted({ id }) : events.todoCompleted({ id }))
299
- },
300
- [store],
301
- )
302
-
303
- const clearCompleted = () => store.commit(events.todoClearedCompleted({ deletedAt: new Date() }))
304
-
305
- return (
306
- <View style={{ flex: 1, gap: 16 }}>
307
- <ScrollView contentContainerStyle={{ gap: 12 }}>
308
- {todos.map((todo) => (
309
- <View
310
- key={todo.id}
311
- style={{
312
- borderRadius: 12,
313
- borderColor: '#d4d4d8',
314
- borderWidth: 1,
315
- padding: 16,
316
- gap: 8,
317
- }}
318
- >
319
- <Text style={{ fontSize: 16, fontWeight: '600' }}>{todo.text}</Text>
320
- <Text>{todo.completed ? 'Completed' : 'Pending'}</Text>
321
- <View style={{ flexDirection: 'row', gap: 12 }}>
322
- <Button title={todo.completed ? 'Mark pending' : 'Mark done'} onPress={() => toggleTodo(todo)} />
323
- <Button
324
- title="Delete"
325
- onPress={() => store.commit(events.todoDeleted({ id: todo.id, deletedAt: new Date() }))}
326
- />
327
- </View>
328
- </View>
329
- ))}
330
- </ScrollView>
331
- <Button title="Clear completed" onPress={clearCompleted} />
332
- </View>
333
- )
334
- }
335
- ```
336
-
337
- ### `getting-started/expo/components/NewTodo.tsx`
338
-
339
- ```tsx filename="getting-started/expo/components/NewTodo.tsx"
340
-
341
- export const NewTodo: FC = () => {
342
- const { store } = useStore()
343
- const { newTodoText } = useQuery(uiState$)
344
-
345
- const updateText = (text: string) => store.commit(events.uiStateSet({ newTodoText: text }))
346
- const createTodo = () =>
347
- store.commit(
348
- events.todoCreated({ id: crypto.randomUUID(), text: newTodoText }),
349
- events.uiStateSet({ newTodoText: '' }),
350
- )
351
-
352
- const addSampleTodos = () => {
353
- const todos = Array.from({ length: 5 }, (_, index) => ({
354
- id: crypto.randomUUID(),
355
- text: `Todo ${index + 1}`,
356
- }))
357
- store.commit(...todos.map((todo) => events.todoCreated(todo)))
358
- }
359
-
360
- return (
361
- <View style={{ gap: 12 }}>
362
- <TextInput value={newTodoText} onChangeText={updateText} placeholder="What needs to be done?" />
363
- <Button title="Add todo" onPress={createTodo} />
364
- <Button title="Add sample todos" onPress={addSampleTodos} />
365
- </View>
366
- )
367
- }
368
- ```
369
-
370
- ### `getting-started/expo/livestore/queries.ts`
371
-
372
- ```ts filename="getting-started/expo/livestore/queries.ts"
373
-
374
- export const uiState$ = queryDb(tables.uiState.get(), { label: 'uiState' })
375
-
376
- export const visibleTodos$ = queryDb(
377
- (get) => {
378
- const { filter } = get(uiState$)
379
-
380
- return tables.todos.where({
381
- deletedAt: null,
382
- completed: filter === 'all' ? undefined : filter === 'completed',
383
- })
384
- },
385
- { label: 'visibleTodos' },
386
- )
387
- ```
388
-
389
- ### `getting-started/expo/livestore/schema.ts`
390
-
391
- ```ts filename="getting-started/expo/livestore/schema.ts"
392
-
393
- export const tables = {
394
- todos: State.SQLite.table({
395
- name: 'todos',
396
- columns: {
397
- id: State.SQLite.text({ primaryKey: true }),
398
- text: State.SQLite.text({ default: '' }),
399
- completed: State.SQLite.boolean({ default: false }),
400
- deletedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
401
- },
402
- }),
403
- uiState: State.SQLite.clientDocument({
404
- name: 'uiState',
405
- schema: Schema.Struct({ newTodoText: Schema.String, filter: Schema.Literal('all', 'active', 'completed') }),
406
- default: { id: SessionIdSymbol, value: { newTodoText: '', filter: 'all' } },
407
- }),
408
- }
409
-
410
- export const events = {
411
- todoCreated: Events.synced({
412
- name: 'v1.TodoCreated',
413
- schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
414
- }),
415
- todoCompleted: Events.synced({
416
- name: 'v1.TodoCompleted',
417
- schema: Schema.Struct({ id: Schema.String }),
418
- }),
419
- todoUncompleted: Events.synced({
420
- name: 'v1.TodoUncompleted',
421
- schema: Schema.Struct({ id: Schema.String }),
422
- }),
423
- todoDeleted: Events.synced({
424
- name: 'v1.TodoDeleted',
425
- schema: Schema.Struct({ id: Schema.String, deletedAt: Schema.Date }),
426
- }),
427
- todoClearedCompleted: Events.synced({
428
- name: 'v1.TodoClearedCompleted',
429
- schema: Schema.Struct({ deletedAt: Schema.Date }),
430
- }),
431
- uiStateSet: tables.uiState.set,
432
- }
433
-
434
- const materializers = State.SQLite.materializers(events, {
435
- 'v1.TodoCreated': ({ id, text }) => tables.todos.insert({ id, text, completed: false }),
436
- 'v1.TodoCompleted': ({ id }) => tables.todos.update({ completed: true }).where({ id }),
437
- 'v1.TodoUncompleted': ({ id }) => tables.todos.update({ completed: false }).where({ id }),
438
- 'v1.TodoDeleted': ({ id, deletedAt }) => tables.todos.update({ deletedAt }).where({ id }),
439
- 'v1.TodoClearedCompleted': ({ deletedAt }) => tables.todos.update({ deletedAt }).where({ completed: true }),
440
- })
441
-
442
- const state = State.SQLite.makeState({ tables, materializers })
443
-
444
- export const schema = makeSchema({ events, state })
445
- ```
446
-
447
- ### Commit events
448
-
449
- After wrapping your app with the `LiveStoreProvider`, you can use the `useStore` hook from any component to commit events.
450
-
451
- Here's an example:
452
-
453
- ## `getting-started/expo/components/NewTodo.tsx`
454
-
455
- ```tsx filename="getting-started/expo/components/NewTodo.tsx"
456
-
457
- export const NewTodo: FC = () => {
458
- const { store } = useStore()
459
- const { newTodoText } = useQuery(uiState$)
460
-
461
- const updateText = (text: string) => store.commit(events.uiStateSet({ newTodoText: text }))
462
- const createTodo = () =>
463
- store.commit(
464
- events.todoCreated({ id: crypto.randomUUID(), text: newTodoText }),
465
- events.uiStateSet({ newTodoText: '' }),
466
- )
467
-
468
- const addSampleTodos = () => {
469
- const todos = Array.from({ length: 5 }, (_, index) => ({
470
- id: crypto.randomUUID(),
471
- text: `Todo ${index + 1}`,
472
- }))
473
- store.commit(...todos.map((todo) => events.todoCreated(todo)))
474
- }
475
-
476
- return (
477
- <View style={{ gap: 12 }}>
478
- <TextInput value={newTodoText} onChangeText={updateText} placeholder="What needs to be done?" />
479
- <Button title="Add todo" onPress={createTodo} />
480
- <Button title="Add sample todos" onPress={addSampleTodos} />
481
- </View>
482
- )
483
- }
484
- ```
485
-
486
- ### `getting-started/expo/livestore/queries.ts`
487
-
488
- ```ts filename="getting-started/expo/livestore/queries.ts"
489
-
490
- export const uiState$ = queryDb(tables.uiState.get(), { label: 'uiState' })
491
-
492
- export const visibleTodos$ = queryDb(
493
- (get) => {
494
- const { filter } = get(uiState$)
495
-
496
- return tables.todos.where({
497
- deletedAt: null,
498
- completed: filter === 'all' ? undefined : filter === 'completed',
499
- })
500
- },
501
- { label: 'visibleTodos' },
502
- )
503
- ```
504
-
505
- ### `getting-started/expo/livestore/schema.ts`
506
-
507
- ```ts filename="getting-started/expo/livestore/schema.ts"
508
-
509
- export const tables = {
510
- todos: State.SQLite.table({
511
- name: 'todos',
512
- columns: {
513
- id: State.SQLite.text({ primaryKey: true }),
514
- text: State.SQLite.text({ default: '' }),
515
- completed: State.SQLite.boolean({ default: false }),
516
- deletedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
517
- },
518
- }),
519
- uiState: State.SQLite.clientDocument({
520
- name: 'uiState',
521
- schema: Schema.Struct({ newTodoText: Schema.String, filter: Schema.Literal('all', 'active', 'completed') }),
522
- default: { id: SessionIdSymbol, value: { newTodoText: '', filter: 'all' } },
523
- }),
524
- }
525
-
526
- export const events = {
527
- todoCreated: Events.synced({
528
- name: 'v1.TodoCreated',
529
- schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
530
- }),
531
- todoCompleted: Events.synced({
532
- name: 'v1.TodoCompleted',
533
- schema: Schema.Struct({ id: Schema.String }),
534
- }),
535
- todoUncompleted: Events.synced({
536
- name: 'v1.TodoUncompleted',
537
- schema: Schema.Struct({ id: Schema.String }),
538
- }),
539
- todoDeleted: Events.synced({
540
- name: 'v1.TodoDeleted',
541
- schema: Schema.Struct({ id: Schema.String, deletedAt: Schema.Date }),
542
- }),
543
- todoClearedCompleted: Events.synced({
544
- name: 'v1.TodoClearedCompleted',
545
- schema: Schema.Struct({ deletedAt: Schema.Date }),
546
- }),
547
- uiStateSet: tables.uiState.set,
548
- }
549
-
550
- const materializers = State.SQLite.materializers(events, {
551
- 'v1.TodoCreated': ({ id, text }) => tables.todos.insert({ id, text, completed: false }),
552
- 'v1.TodoCompleted': ({ id }) => tables.todos.update({ completed: true }).where({ id }),
553
- 'v1.TodoUncompleted': ({ id }) => tables.todos.update({ completed: false }).where({ id }),
554
- 'v1.TodoDeleted': ({ id, deletedAt }) => tables.todos.update({ deletedAt }).where({ id }),
555
- 'v1.TodoClearedCompleted': ({ deletedAt }) => tables.todos.update({ deletedAt }).where({ completed: true }),
556
- })
557
-
558
- const state = State.SQLite.makeState({ tables, materializers })
559
-
560
- export const schema = makeSchema({ events, state })
561
- ```
562
-
563
- ## Queries
564
-
565
- To retrieve data from the database, first define a query using `queryDb` from `@livestore/livestore`. Then, execute the query with the `useQuery` hook from `@livestore/react`.
566
-
567
- Consider abstracting queries into a separate file to keep your code organized, though you can also define them directly within components if preferred.
568
-
569
- Here's an example:
570
-
571
- ## `getting-started/expo/components/ListTodos.tsx`
572
-
573
- ```tsx filename="getting-started/expo/components/ListTodos.tsx"
574
-
575
- export const ListTodos: FC = () => {
576
- const { store } = useStore()
577
- const todos = useQuery(visibleTodos$)
578
-
579
- const toggleTodo = useCallback(
580
- ({ id, completed }: typeof tables.todos.Type) => {
581
- store.commit(completed ? events.todoUncompleted({ id }) : events.todoCompleted({ id }))
582
- },
583
- [store],
584
- )
585
-
586
- const clearCompleted = () => store.commit(events.todoClearedCompleted({ deletedAt: new Date() }))
587
-
588
- return (
589
- <View style={{ flex: 1, gap: 16 }}>
590
- <ScrollView contentContainerStyle={{ gap: 12 }}>
591
- {todos.map((todo) => (
592
- <View
593
- key={todo.id}
594
- style={{
595
- borderRadius: 12,
596
- borderColor: '#d4d4d8',
597
- borderWidth: 1,
598
- padding: 16,
599
- gap: 8,
600
- }}
601
- >
602
- <Text style={{ fontSize: 16, fontWeight: '600' }}>{todo.text}</Text>
603
- <Text>{todo.completed ? 'Completed' : 'Pending'}</Text>
604
- <View style={{ flexDirection: 'row', gap: 12 }}>
605
- <Button title={todo.completed ? 'Mark pending' : 'Mark done'} onPress={() => toggleTodo(todo)} />
606
- <Button
607
- title="Delete"
608
- onPress={() => store.commit(events.todoDeleted({ id: todo.id, deletedAt: new Date() }))}
609
- />
610
- </View>
611
- </View>
612
- ))}
613
- </ScrollView>
614
- <Button title="Clear completed" onPress={clearCompleted} />
615
- </View>
616
- )
617
- }
618
- ```
619
-
620
- ### `getting-started/expo/livestore/queries.ts`
621
-
622
- ```ts filename="getting-started/expo/livestore/queries.ts"
623
-
624
- export const uiState$ = queryDb(tables.uiState.get(), { label: 'uiState' })
625
-
626
- export const visibleTodos$ = queryDb(
627
- (get) => {
628
- const { filter } = get(uiState$)
629
-
630
- return tables.todos.where({
631
- deletedAt: null,
632
- completed: filter === 'all' ? undefined : filter === 'completed',
633
- })
634
- },
635
- { label: 'visibleTodos' },
636
- )
637
- ```
638
-
639
- ### `getting-started/expo/livestore/schema.ts`
640
-
641
- ```ts filename="getting-started/expo/livestore/schema.ts"
642
-
643
- export const tables = {
644
- todos: State.SQLite.table({
645
- name: 'todos',
646
- columns: {
647
- id: State.SQLite.text({ primaryKey: true }),
648
- text: State.SQLite.text({ default: '' }),
649
- completed: State.SQLite.boolean({ default: false }),
650
- deletedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
651
- },
652
- }),
653
- uiState: State.SQLite.clientDocument({
654
- name: 'uiState',
655
- schema: Schema.Struct({ newTodoText: Schema.String, filter: Schema.Literal('all', 'active', 'completed') }),
656
- default: { id: SessionIdSymbol, value: { newTodoText: '', filter: 'all' } },
657
- }),
658
- }
659
-
660
- export const events = {
661
- todoCreated: Events.synced({
662
- name: 'v1.TodoCreated',
663
- schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
664
- }),
665
- todoCompleted: Events.synced({
666
- name: 'v1.TodoCompleted',
667
- schema: Schema.Struct({ id: Schema.String }),
668
- }),
669
- todoUncompleted: Events.synced({
670
- name: 'v1.TodoUncompleted',
671
- schema: Schema.Struct({ id: Schema.String }),
672
- }),
673
- todoDeleted: Events.synced({
674
- name: 'v1.TodoDeleted',
675
- schema: Schema.Struct({ id: Schema.String, deletedAt: Schema.Date }),
676
- }),
677
- todoClearedCompleted: Events.synced({
678
- name: 'v1.TodoClearedCompleted',
679
- schema: Schema.Struct({ deletedAt: Schema.Date }),
680
- }),
681
- uiStateSet: tables.uiState.set,
682
- }
683
-
684
- const materializers = State.SQLite.materializers(events, {
685
- 'v1.TodoCreated': ({ id, text }) => tables.todos.insert({ id, text, completed: false }),
686
- 'v1.TodoCompleted': ({ id }) => tables.todos.update({ completed: true }).where({ id }),
687
- 'v1.TodoUncompleted': ({ id }) => tables.todos.update({ completed: false }).where({ id }),
688
- 'v1.TodoDeleted': ({ id, deletedAt }) => tables.todos.update({ deletedAt }).where({ id }),
689
- 'v1.TodoClearedCompleted': ({ deletedAt }) => tables.todos.update({ deletedAt }).where({ completed: true }),
690
- })
691
-
692
- const state = State.SQLite.makeState({ tables, materializers })
693
-
694
- export const schema = makeSchema({ events, state })
695
- ```
696
-
697
- ## Devtools
698
-
699
- To open the devtools, run the app and from your terminal press `shift + m`, then select LiveStore Devtools and press `Enter`.
700
-
701
- ![Expo Terminal Screenshot](../../../assets/devtools-terminal-expo.png)
702
-
703
- This will open the devtools in a new tab in your default browser.
704
-
705
- ![Devtools Browser Screenshot](../../../assets/devtools-browser-view.png)
706
-
707
- Use the devtools to inspect the state of your LiveStore database, execute events, track performance, and more.
708
-
709
- ## Database location
710
-
711
- ### With Expo Go
712
-
713
- To open the database in Finder, run the following command in your terminal:
714
-
715
- ```bash
716
- open $(find $(xcrun simctl get_app_container booted host.exp.Exponent data) -path "*/Documents/ExponentExperienceData/*livestore-expo*" -print -quit)/SQLite
717
- ```
718
-
719
- ### With development builds
720
-
721
- For development builds, the app SQLite database is stored in the app's Library directory.
722
-
723
- Example:
724
- `/Users/<USERNAME>/Library/Developer/CoreSimulator/Devices/<DEVICE_ID>/data/Containers/Data/Application/<APP_ID>/Documents/SQLite/app.db`
725
-
726
- To open the database in Finder, run the following command in your terminal:
727
-
728
- ```bash
729
- open $(xcrun simctl get_app_container booted [APP_BUNDLE_ID] data)/Documents/SQLite
730
- ```
731
-
732
- Replace `[APP_BUNDLE_ID]` with your app's bundle ID. e.g. `dev.livestore.livestore-expo`.
733
-
734
- ## Further notes
735
-
736
- - LiveStore doesn't yet support Expo Web (see [#130](https://github.com/livestorejs/livestore/issues/130))