@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.
Files changed (207) 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 +14 -7
  12. package/dist/effect/LiveStore.d.ts.map +1 -1
  13. package/dist/effect/LiveStore.js +0 -15
  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/live-queries/base-class.d.ts +3 -3
  20. package/dist/live-queries/base-class.d.ts.map +1 -1
  21. package/dist/live-queries/base-class.js +2 -2
  22. package/dist/live-queries/base-class.js.map +1 -1
  23. package/dist/live-queries/client-document-get-query.d.ts +1 -1
  24. package/dist/live-queries/client-document-get-query.d.ts.map +1 -1
  25. package/dist/live-queries/client-document-get-query.js +1 -1
  26. package/dist/live-queries/client-document-get-query.js.map +1 -1
  27. package/dist/live-queries/computed.d.ts.map +1 -1
  28. package/dist/live-queries/computed.js +2 -2
  29. package/dist/live-queries/computed.js.map +1 -1
  30. package/dist/live-queries/db-query.js +14 -14
  31. package/dist/live-queries/db-query.js.map +1 -1
  32. package/dist/live-queries/db-query.test.js +2 -2
  33. package/dist/live-queries/db-query.test.js.map +1 -1
  34. package/dist/live-queries/signal.test.js +2 -2
  35. package/dist/live-queries/signal.test.js.map +1 -1
  36. package/dist/mod.d.ts +1 -1
  37. package/dist/mod.d.ts.map +1 -1
  38. package/dist/mod.js.map +1 -1
  39. package/dist/reactive.d.ts +9 -9
  40. package/dist/reactive.d.ts.map +1 -1
  41. package/dist/reactive.js +9 -26
  42. package/dist/reactive.js.map +1 -1
  43. package/dist/reactive.test.js +2 -2
  44. package/dist/reactive.test.js.map +1 -1
  45. package/dist/store/StoreRegistry.d.ts +30 -5
  46. package/dist/store/StoreRegistry.d.ts.map +1 -1
  47. package/dist/store/StoreRegistry.js +54 -31
  48. package/dist/store/StoreRegistry.js.map +1 -1
  49. package/dist/store/StoreRegistry.test.js +251 -250
  50. package/dist/store/StoreRegistry.test.js.map +1 -1
  51. package/dist/store/create-store.d.ts +6 -2
  52. package/dist/store/create-store.d.ts.map +1 -1
  53. package/dist/store/create-store.js +13 -7
  54. package/dist/store/create-store.js.map +1 -1
  55. package/dist/store/devtools.d.ts +1 -1
  56. package/dist/store/devtools.d.ts.map +1 -1
  57. package/dist/store/devtools.js +3 -3
  58. package/dist/store/devtools.js.map +1 -1
  59. package/dist/store/store-eventstream.test.js +2 -2
  60. package/dist/store/store-eventstream.test.js.map +1 -1
  61. package/dist/store/store-types.d.ts +70 -5
  62. package/dist/store/store-types.d.ts.map +1 -1
  63. package/dist/store/store-types.js.map +1 -1
  64. package/dist/store/store-types.test.js +1 -1
  65. package/dist/store/store-types.test.js.map +1 -1
  66. package/dist/store/store.d.ts +81 -2
  67. package/dist/store/store.d.ts.map +1 -1
  68. package/dist/store/store.js +128 -45
  69. package/dist/store/store.js.map +1 -1
  70. package/dist/utils/dev.js.map +1 -1
  71. package/dist/utils/stack-info.js +2 -2
  72. package/dist/utils/stack-info.js.map +1 -1
  73. package/dist/utils/tests/fixture.d.ts +1 -1
  74. package/dist/utils/tests/fixture.d.ts.map +1 -1
  75. package/dist/utils/tests/fixture.js.map +1 -1
  76. package/dist/utils/tests/otel.d.ts.map +1 -1
  77. package/dist/utils/tests/otel.js +5 -5
  78. package/dist/utils/tests/otel.js.map +1 -1
  79. package/package.json +58 -17
  80. package/src/QueryCache.ts +1 -1
  81. package/src/SqliteDbWrapper.test.ts +4 -2
  82. package/src/SqliteDbWrapper.ts +12 -11
  83. package/src/ambient.d.ts +0 -7
  84. package/src/effect/LiveStore.test.ts +61 -0
  85. package/src/effect/LiveStore.ts +17 -26
  86. package/src/live-queries/__snapshots__/db-query.test.ts.snap +336 -231
  87. package/src/live-queries/base-class.ts +7 -6
  88. package/src/live-queries/client-document-get-query.ts +4 -2
  89. package/src/live-queries/computed.ts +3 -2
  90. package/src/live-queries/db-query.test.ts +3 -2
  91. package/src/live-queries/db-query.ts +15 -15
  92. package/src/live-queries/signal.test.ts +3 -2
  93. package/src/mod.ts +1 -0
  94. package/src/reactive.test.ts +3 -2
  95. package/src/reactive.ts +22 -23
  96. package/src/store/StoreRegistry.test.ts +317 -293
  97. package/src/store/StoreRegistry.ts +63 -38
  98. package/src/store/create-store.ts +26 -11
  99. package/src/store/devtools.ts +5 -6
  100. package/src/store/store-eventstream.test.ts +4 -2
  101. package/src/store/store-types.test.ts +3 -1
  102. package/src/store/store-types.ts +47 -8
  103. package/src/store/store.ts +172 -55
  104. package/src/utils/dev.ts +2 -2
  105. package/src/utils/stack-info.ts +2 -2
  106. package/src/utils/tests/fixture.ts +2 -1
  107. package/src/utils/tests/otel.ts +8 -7
  108. package/docs/api/index.md +0 -3
  109. package/docs/building-with-livestore/complex-ui-state/index.md +0 -3
  110. package/docs/building-with-livestore/crud/index.md +0 -3
  111. package/docs/building-with-livestore/data-modeling/index.md +0 -30
  112. package/docs/building-with-livestore/debugging/index.md +0 -17
  113. package/docs/building-with-livestore/devtools/index.md +0 -79
  114. package/docs/building-with-livestore/events/index.md +0 -355
  115. package/docs/building-with-livestore/examples/ai-agent/index.md +0 -5
  116. package/docs/building-with-livestore/examples/todo-workspaces/index.md +0 -885
  117. package/docs/building-with-livestore/examples/turnbased-game/index.md +0 -7
  118. package/docs/building-with-livestore/opentelemetry/index.md +0 -227
  119. package/docs/building-with-livestore/production-checklist/index.md +0 -5
  120. package/docs/building-with-livestore/reactivity-system/index.md +0 -202
  121. package/docs/building-with-livestore/rules-for-ai-agents/index.md +0 -9
  122. package/docs/building-with-livestore/state/materializers/index.md +0 -300
  123. package/docs/building-with-livestore/state/sql-queries/index.md +0 -94
  124. package/docs/building-with-livestore/state/sqlite/index.md +0 -45
  125. package/docs/building-with-livestore/state/sqlite-schema/index.md +0 -306
  126. package/docs/building-with-livestore/state/sqlite-schema-effect/index.md +0 -300
  127. package/docs/building-with-livestore/store/index.md +0 -625
  128. package/docs/building-with-livestore/syncing/index.md +0 -136
  129. package/docs/building-with-livestore/tools/cli/index.md +0 -177
  130. package/docs/building-with-livestore/tools/mcp/index.md +0 -187
  131. package/docs/examples/cloudflare-adapter/index.md +0 -44
  132. package/docs/examples/expo-adapter/index.md +0 -44
  133. package/docs/examples/index.md +0 -55
  134. package/docs/examples/node-adapter/index.md +0 -44
  135. package/docs/examples/web-adapter/index.md +0 -52
  136. package/docs/framework-integrations/custom-elements/index.md +0 -142
  137. package/docs/framework-integrations/react-integration/index.md +0 -937
  138. package/docs/framework-integrations/solid-integration/index.md +0 -293
  139. package/docs/framework-integrations/svelte-integration/index.md +0 -42
  140. package/docs/framework-integrations/vue-integration/index.md +0 -294
  141. package/docs/getting-started/expo/index.md +0 -882
  142. package/docs/getting-started/node/index.md +0 -115
  143. package/docs/getting-started/react-web/index.md +0 -626
  144. package/docs/getting-started/solid/index.md +0 -3
  145. package/docs/getting-started/vue/index.md +0 -471
  146. package/docs/index.md +0 -208
  147. package/docs/llms.txt +0 -146
  148. package/docs/misc/CODE_OF_CONDUCT/index.md +0 -133
  149. package/docs/misc/FAQ/index.md +0 -37
  150. package/docs/misc/community/index.md +0 -88
  151. package/docs/misc/credits/index.md +0 -14
  152. package/docs/misc/design-partners/index.md +0 -13
  153. package/docs/misc/package-management/index.md +0 -21
  154. package/docs/misc/performance/index.md +0 -25
  155. package/docs/misc/resources/index.md +0 -46
  156. package/docs/misc/state-of-the-project/index.md +0 -37
  157. package/docs/misc/troubleshooting/index.md +0 -82
  158. package/docs/overview/concepts/index.md +0 -78
  159. package/docs/overview/how-livestore-works/index.md +0 -56
  160. package/docs/overview/introduction/index.md +0 -413
  161. package/docs/overview/technology-comparison/index.md +0 -40
  162. package/docs/overview/when-livestore/index.md +0 -81
  163. package/docs/overview/why-livestore/index.md +0 -111
  164. package/docs/patterns/ai/index.md +0 -15
  165. package/docs/patterns/anonymous-user-transition/index.md +0 -10
  166. package/docs/patterns/app-evolution/index.md +0 -72
  167. package/docs/patterns/auth/index.md +0 -377
  168. package/docs/patterns/effect/index.md +0 -1505
  169. package/docs/patterns/encryption/index.md +0 -6
  170. package/docs/patterns/external-data/index.md +0 -5
  171. package/docs/patterns/file-management/index.md +0 -11
  172. package/docs/patterns/file-structure/index.md +0 -14
  173. package/docs/patterns/list-ordering/index.md +0 -369
  174. package/docs/patterns/offline/index.md +0 -32
  175. package/docs/patterns/orm/index.md +0 -18
  176. package/docs/patterns/presence/index.md +0 -11
  177. package/docs/patterns/rich-text-editing/index.md +0 -11
  178. package/docs/patterns/server-side-clients/index.md +0 -97
  179. package/docs/patterns/side-effects/index.md +0 -11
  180. package/docs/patterns/state-machines/index.md +0 -11
  181. package/docs/patterns/storybook/index.md +0 -209
  182. package/docs/patterns/undo-redo/index.md +0 -9
  183. package/docs/patterns/version-control/index.md +0 -8
  184. package/docs/platform-adapters/cloudflare-durable-object-adapter/index.md +0 -453
  185. package/docs/platform-adapters/electron-adapter/index.md +0 -15
  186. package/docs/platform-adapters/expo-adapter/index.md +0 -262
  187. package/docs/platform-adapters/node-adapter/index.md +0 -160
  188. package/docs/platform-adapters/tauri-adapter/index.md +0 -15
  189. package/docs/platform-adapters/web-adapter/index.md +0 -287
  190. package/docs/sustainable-open-source/contributing/docs/index.md +0 -94
  191. package/docs/sustainable-open-source/contributing/info/index.md +0 -63
  192. package/docs/sustainable-open-source/contributing/monorepo/index.md +0 -195
  193. package/docs/sustainable-open-source/sponsoring/index.md +0 -104
  194. package/docs/sync-providers/cloudflare/index.md +0 -773
  195. package/docs/sync-providers/custom/index.md +0 -65
  196. package/docs/sync-providers/electricsql/index.md +0 -159
  197. package/docs/sync-providers/s2/index.md +0 -230
  198. package/docs/tutorial/0-welcome/index.md +0 -48
  199. package/docs/tutorial/1-setup-starter-project/index.md +0 -105
  200. package/docs/tutorial/2-deploy-to-cloudflare/index.md +0 -195
  201. package/docs/tutorial/3-read-and-write-todos-via-livestore/index.md +0 -530
  202. package/docs/tutorial/4-sync-data-via-cloudflare/index.md +0 -210
  203. package/docs/tutorial/5-expand-business-logic/index.md +0 -174
  204. package/docs/tutorial/6-persist-ui-state/index.md +0 -453
  205. package/docs/tutorial/7-next-steps/index.md +0 -22
  206. package/docs/understanding-livestore/design-decisions/index.md +0 -33
  207. package/docs/understanding-livestore/event-sourcing/index.md +0 -40
@@ -1,209 +0,0 @@
1
- # Storybook testing (React)
2
-
3
- export const CODE = {
4
- todoInputStories: `import type { Meta, StoryObj } from '@storybook/react'
5
-
6
- const meta: Meta<typeof TodoInput> = {
7
- title: 'TodoMVC/TodoInput',
8
- component: TodoInput,
9
- }
10
-
11
- export default meta
12
- type Story = StoryObj<typeof TodoInput>
13
-
14
- export const Default: Story = {}
15
-
16
- export const WithInitialText: Story = {
17
- decorators: [
18
- createLiveStoreDecorator([
19
- events.uiStateSet({ newTodoText: 'Buy groceries' })
20
- ])
21
- ],
22
- }`,
23
-
24
- storybookPreview: `import React from 'react'
25
-
26
- // Default decorator with no seed data
27
- const LiveStoreDecorator = createLiveStoreDecorator()
28
-
29
- export const decorators = [LiveStoreDecorator]`,
30
-
31
- decorator: `import React, { Suspense, useState } from 'react'
32
-
33
- const adapter = makeInMemoryAdapter()
34
-
35
- // Create LiveStore decorator with optional seeding
36
- export const createLiveStoreDecorator = (seedEvents = []) => (Story) => {
37
- const [storeRegistry] = useState(() => new StoreRegistry())
38
-
39
- return (
40
- <Suspense fallback={<div>Loading LiveStore...</div>}>
41
- <StoreRegistryProvider storeRegistry={storeRegistry}>
42
- <StoryWithStore Story={Story} seedEvents={seedEvents} />
43
- </StoreRegistryProvider>
44
- </Suspense>
45
- )
46
- }
47
-
48
- const StoryWithStore = ({ Story, seedEvents }) => {
49
- const store = useStore({
50
- storeId: 'storybook',
51
- schema,
52
- adapter,
53
- batchUpdates,
54
- boot: (store) => {
55
- if (seedEvents.length > 0) {
56
- store.commit(...seedEvents)
57
- }
58
- },
59
- })
60
- return <Story />
61
- }`,
62
-
63
- todoInput: `import { useStore } from '@livestore/react'
64
-
65
- const adapter = makeInMemoryAdapter()
66
-
67
- // Define queries (like in TodoMVC)
68
- const uiState$ = queryDb(tables.uiState.get(), { label: 'uiState' })
69
-
70
- const useAppStore = () => useStore({
71
- storeId: 'todo-app',
72
- schema,
73
- adapter,
74
- batchUpdates,
75
- })
76
-
77
- export const TodoInput = () => {
78
- const store = useAppStore()
79
- const { newTodoText } = store.useQuery(uiState$)
80
-
81
- const updateNewTodoText = (text: string) =>
82
- store.commit(events.uiStateSet({ newTodoText: text }))
83
-
84
- const createTodo = () => {
85
- if (newTodoText.trim()) {
86
- store.commit(
87
- events.todoCreated({ id: crypto.randomUUID(), text: newTodoText }),
88
- events.uiStateSet({ newTodoText: '' }),
89
- )
90
- }
91
- }
92
-
93
- return (
94
- <div>
95
- <h2>Add Todo</h2>
96
- <input
97
- type="text"
98
- placeholder="What needs to be done?"
99
- value={newTodoText}
100
- onChange={(e) => updateNewTodoText(e.target.value)}
101
- onKeyDown={(e) => {
102
- if (e.key === 'Enter') {
103
- createTodo()
104
- }
105
- }}
106
- />
107
- <button onClick={createTodo}>
108
- Add
109
- </button>
110
- </div>
111
- )
112
- }`,
113
-
114
- schema: `import { Events, makeSchema, Schema, SessionIdSymbol, State } from '@livestore/livestore'
115
-
116
- // Define tables (based on TodoMVC example)
117
- export const tables = {
118
- todos: State.SQLite.table({
119
- name: 'todos',
120
- columns: {
121
- id: State.SQLite.text({ primaryKey: true }),
122
- text: State.SQLite.text({ default: '' }),
123
- completed: State.SQLite.boolean({ default: false }),
124
- deletedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
125
- },
126
- }),
127
- // Client document for UI state
128
- uiState: State.SQLite.clientDocument({
129
- name: 'uiState',
130
- schema: Schema.Struct({
131
- newTodoText: Schema.String,
132
- filter: Schema.Literal('all', 'active', 'completed')
133
- }),
134
- default: {
135
- id: SessionIdSymbol,
136
- value: { newTodoText: '', filter: 'all' }
137
- },
138
- }),
139
- }
140
-
141
- // Define events (exactly from TodoMVC)
142
- export const events = {
143
- todoCreated: Events.synced({
144
- name: 'v1.TodoCreated',
145
- schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
146
- }),
147
- todoCompleted: Events.synced({
148
- name: 'v1.TodoCompleted',
149
- schema: Schema.Struct({ id: Schema.String }),
150
- }),
151
- todoUncompleted: Events.synced({
152
- name: 'v1.TodoUncompleted',
153
- schema: Schema.Struct({ id: Schema.String }),
154
- }),
155
- todoDeleted: Events.synced({
156
- name: 'v1.TodoDeleted',
157
- schema: Schema.Struct({ id: Schema.String, deletedAt: Schema.Date }),
158
- }),
159
- todoClearedCompleted: Events.synced({
160
- name: 'v1.TodoClearedCompleted',
161
- schema: Schema.Struct({ deletedAt: Schema.Date }),
162
- }),
163
- // Auto-generated client document event
164
- uiStateSet: tables.uiState.set,
165
- }
166
-
167
- // Define materializers to map events to state
168
- const materializers = State.SQLite.materializers(events, {
169
- 'v1.TodoCreated': ({ id, text }) => tables.todos.insert({ id, text, completed: false }),
170
- 'v1.TodoCompleted': ({ id }) => tables.todos.update({ completed: true }).where({ id }),
171
- 'v1.TodoUncompleted': ({ id }) => tables.todos.update({ completed: false }).where({ id }),
172
- 'v1.TodoDeleted': ({ id, deletedAt }) => tables.todos.update({ deletedAt }).where({ id }),
173
- 'v1.TodoClearedCompleted': ({ deletedAt }) => tables.todos.update({ deletedAt }).where({ completed: true }),
174
- })
175
-
176
- const state = State.SQLite.makeState({ tables, materializers })
177
-
178
- export const schema = makeSchema({ events, state })`
179
- }
180
-
181
- LiveStore works seamlessly with Storybook for React component development and testing.
182
-
183
- **Note:** This guide focuses on React. For other frameworks, adapt patterns accordingly.
184
-
185
- ## Setup
186
-
187
- First, [install Storybook](https://storybook.js.org/docs/get-started/install) in your React project.
188
-
189
- ## Configuration
190
-
191
- Create a decorator that wraps stories with a fresh LiveStore instance and use the TodoMVC schema for realistic examples.
192
-
193
- <Tabs>
194
- <TabItem label="Stories">
195
- <Code code={CODE.todoInputStories} lang="tsx" title="src/TodoInput.stories.tsx" />
196
- </TabItem>
197
- <TabItem label="Storybook Config">
198
- <Code code={CODE.storybookPreview} lang="js" title=".storybook/preview.js" />
199
- </TabItem>
200
- <TabItem label="Decorator">
201
- <Code code={CODE.decorator} lang="js" title="src/decorator.js" />
202
- </TabItem>
203
- <TabItem label="Component">
204
- <Code code={CODE.todoInput} lang="tsx" title="src/TodoInput.tsx" />
205
- </TabItem>
206
- <TabItem label="Schema">
207
- <Code code={CODE.schema} lang="ts" title="src/schema.ts" />
208
- </TabItem>
209
- </Tabs>
@@ -1,9 +0,0 @@
1
- # Undo/Redo
2
-
3
- Undo/redo functionality should be generally modeled through explicit events instead of "removing" events from the event history.
4
-
5
- ## Example
6
-
7
- ```ts
8
- // TODO (contribution welcome)
9
- ```
@@ -1,8 +0,0 @@
1
- # Version control
2
-
3
- LiveStore's event sourcing approach allows you to implement version control functionality in your application (similar to Git but for your application domain). This could include features like:
4
-
5
- - Branching
6
- - Semantic commit messages & grouping
7
- - History tracking
8
- - Semantic/interactive merges
@@ -1,453 +0,0 @@
1
- # Cloudflare Durable Object adapter
2
-
3
- The Cloudflare Durable Object adapter enables running LiveStore applications on Cloudflare Workers with stateful Durable Objects for synchronized real-time data.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- pnpm add @livestore/adapter-cloudflare @livestore/sync-cf
9
- ```
10
-
11
- ## Configuration
12
-
13
- ### Wrangler configuration
14
-
15
- Configure your `wrangler.toml` with the required Durable Object bindings:
16
-
17
- ```toml
18
- name = "my-livestore-app"
19
- main = "./src/worker.ts"
20
- compatibility_date = "2025-05-07"
21
- compatibility_flags = [
22
- "enable_request_signal", # Required for HTTP RPC streams
23
- ]
24
-
25
- [[durable_objects.bindings]]
26
- name = "SYNC_BACKEND_DO"
27
- class_name = "SyncBackendDO"
28
-
29
- [[durable_objects.bindings]]
30
- name = "CLIENT_DO"
31
- class_name = "LiveStoreClientDO"
32
-
33
- [[migrations]]
34
- tag = "v1"
35
- new_sqlite_classes = ["SyncBackendDO", "LiveStoreClientDO"]
36
-
37
- [[d1_databases]]
38
- binding = "DB"
39
- database_name = "my-livestore-db"
40
- database_id = "your-database-id"
41
- ```
42
-
43
- ### Environment types
44
-
45
- Define your Worker bindings so TypeScript can guide you when wiring Durable Objects:
46
-
47
- ## `reference/platform-adapters/cloudflare/env.ts`
48
-
49
- ```ts filename="reference/platform-adapters/cloudflare/env.ts"
50
-
51
- export type Env = {
52
- CLIENT_DO: CfTypes.DurableObjectNamespace<ClientDoWithRpcCallback>
53
- SYNC_BACKEND_DO: CfTypes.DurableObjectNamespace<SyncBackendRpcInterface>
54
- DB: CfTypes.D1Database
55
- }
56
- ```
57
-
58
- We also use a small helper to extract the store identifier from incoming requests:
59
-
60
- ## `reference/platform-adapters/cloudflare/shared.ts`
61
-
62
- ```ts filename="reference/platform-adapters/cloudflare/shared.ts"
63
-
64
- export const storeIdFromRequest = (request: CfTypes.Request) => {
65
- const url = new URL(request.url)
66
- const storeId = url.searchParams.get('storeId')
67
-
68
- if (storeId === null) {
69
- throw new Error('storeId is required in URL search params')
70
- }
71
-
72
- return storeId
73
- }
74
- ```
75
-
76
- ## Basic setup
77
-
78
- ### 1. Create the sync backend Durable Object
79
-
80
- The sync backend handles pushing and pulling events between clients:
81
-
82
- ## `reference/platform-adapters/cloudflare/sync-backend.ts`
83
-
84
- ```ts filename="reference/platform-adapters/cloudflare/sync-backend.ts"
85
-
86
- export class SyncBackendDO extends SyncBackend.makeDurableObject({
87
- // Optional: Handle push events
88
- // onPush: async (message, { storeId }) => {
89
- // console.log(`onPush for store (${storeId})`, message.batch)
90
- // },
91
- }) {}
92
- ```
93
-
94
- ### 2. Create the client Durable Object
95
-
96
- Each client Durable Object hosts a LiveStore instance and exposes DO RPC callbacks:
97
-
98
- ## `reference/platform-adapters/cloudflare/client-do.ts`
99
-
100
- ```ts filename="reference/platform-adapters/cloudflare/client-do.ts"
101
- /// <reference types="@cloudflare/workers-types" />
102
-
103
- type AlarmInfo = {
104
- isRetry: boolean
105
- retryCount: number
106
- }
107
-
108
- export class LiveStoreClientDO extends DurableObject<Env> implements ClientDoWithRpcCallback {
109
- __DURABLE_OBJECT_BRAND: never = undefined as never
110
-
111
- private storeId: string | undefined
112
- private cachedStore: Store<typeof schema> | undefined
113
- private storeSubscription: Unsubscribe | undefined
114
- private readonly todosQuery = tables.todos.select()
115
-
116
- async fetch(request: Request): Promise<Response> {
117
- // @ts-expect-error TODO remove casts once CF types are fixed in https://github.com/cloudflare/workerd/issues/4811
118
- this.storeId = storeIdFromRequest(request)
119
-
120
- const store = await this.getStore()
121
- await this.subscribeToStore()
122
-
123
- const todos = store.query(this.todosQuery)
124
- return new Response(JSON.stringify(todos, null, 2), {
125
- headers: { 'Content-Type': 'application/json' },
126
- })
127
- }
128
-
129
- private async getStore() {
130
- if (this.cachedStore !== undefined) {
131
- return this.cachedStore
132
- }
133
-
134
- const storeId = this.storeId ?? nanoid()
135
-
136
- const store = await createStoreDoPromise({
137
- schema,
138
- storeId,
139
- clientId: 'client-do',
140
- sessionId: nanoid(),
141
- durableObject: {
142
- // @ts-expect-error TODO remove once CF types are fixed in https://github.com/cloudflare/workerd/issues/4811
143
- ctx: this.ctx,
144
- env: this.env,
145
- bindingName: 'CLIENT_DO',
146
- },
147
- syncBackendStub: this.env.SYNC_BACKEND_DO.get(this.env.SYNC_BACKEND_DO.idFromName(storeId)),
148
- livePull: true,
149
- })
150
-
151
- this.cachedStore = store
152
- return store
153
- }
154
-
155
- private async subscribeToStore() {
156
- const store = await this.getStore()
157
-
158
- if (this.storeSubscription === undefined) {
159
- this.storeSubscription = store.subscribe(this.todosQuery, (todos: ReadonlyArray<typeof tables.todos.Type>) => {
160
- console.log(`todos for store (${this.storeId})`, todos)
161
- })
162
- }
163
-
164
- await this.ctx.storage.setAlarm(Date.now() + 1000)
165
- }
166
-
167
- alarm(_alarmInfo?: AlarmInfo): void | Promise<void> {
168
- return this.subscribeToStore()
169
- }
170
-
171
- async syncUpdateRpc(payload: unknown) {
172
- await handleSyncUpdateRpc(payload)
173
- }
174
- }
175
- ```
176
-
177
- ### `reference/platform-adapters/cloudflare/env.ts`
178
-
179
- ```ts filename="reference/platform-adapters/cloudflare/env.ts"
180
-
181
- export type Env = {
182
- CLIENT_DO: CfTypes.DurableObjectNamespace<ClientDoWithRpcCallback>
183
- SYNC_BACKEND_DO: CfTypes.DurableObjectNamespace<SyncBackendRpcInterface>
184
- DB: CfTypes.D1Database
185
- }
186
- ```
187
-
188
- ### `reference/platform-adapters/cloudflare/schema.ts`
189
-
190
- ```ts filename="reference/platform-adapters/cloudflare/schema.ts"
191
-
192
- export const tables = {
193
- todos: State.SQLite.table({
194
- name: 'todos',
195
- columns: {
196
- id: State.SQLite.text({ primaryKey: true }),
197
- text: State.SQLite.text({ default: '' }),
198
- completed: State.SQLite.boolean({ default: false }),
199
- deletedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
200
- },
201
- }),
202
- }
203
-
204
- export const events = {
205
- todoCreated: Events.synced({
206
- name: 'v1.TodoCreated',
207
- schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
208
- }),
209
- todoCompleted: Events.synced({
210
- name: 'v1.TodoCompleted',
211
- schema: Schema.Struct({ id: Schema.String }),
212
- }),
213
- todoUncompleted: Events.synced({
214
- name: 'v1.TodoUncompleted',
215
- schema: Schema.Struct({ id: Schema.String }),
216
- }),
217
- todoDeleted: Events.synced({
218
- name: 'v1.TodoDeleted',
219
- schema: Schema.Struct({ id: Schema.String, deletedAt: Schema.Date }),
220
- }),
221
- todoClearedCompleted: Events.synced({
222
- name: 'v1.TodoClearedCompleted',
223
- schema: Schema.Struct({ deletedAt: Schema.Date }),
224
- }),
225
- }
226
-
227
- const materializers = State.SQLite.materializers(events, {
228
- 'v1.TodoCreated': ({ id, text }) => tables.todos.insert({ id, text, completed: false }),
229
- 'v1.TodoCompleted': ({ id }) => tables.todos.update({ completed: true }).where({ id }),
230
- 'v1.TodoUncompleted': ({ id }) => tables.todos.update({ completed: false }).where({ id }),
231
- 'v1.TodoDeleted': ({ id, deletedAt }) => tables.todos.update({ deletedAt }).where({ id }),
232
- 'v1.TodoClearedCompleted': ({ deletedAt }) => tables.todos.update({ deletedAt }).where({ completed: true }),
233
- })
234
-
235
- const state = State.SQLite.makeState({ tables, materializers })
236
-
237
- export const schema = makeSchema({ events, state })
238
- ```
239
-
240
- ### `reference/platform-adapters/cloudflare/shared.ts`
241
-
242
- ```ts filename="reference/platform-adapters/cloudflare/shared.ts"
243
-
244
- export const storeIdFromRequest = (request: CfTypes.Request) => {
245
- const url = new URL(request.url)
246
- const storeId = url.searchParams.get('storeId')
247
-
248
- if (storeId === null) {
249
- throw new Error('storeId is required in URL search params')
250
- }
251
-
252
- return storeId
253
- }
254
- ```
255
-
256
- ### 3. Worker fetch handler
257
-
258
- The worker routes incoming requests either to the sync backend or to the client Durable Object:
259
-
260
- ## `reference/platform-adapters/cloudflare/worker.ts`
261
-
262
- ```ts filename="reference/platform-adapters/cloudflare/worker.ts"
263
-
264
- export default {
265
- fetch: async (request: CfTypes.Request, env: Env, ctx: CfTypes.ExecutionContext) => {
266
- const url = new URL(request.url)
267
-
268
- const searchParams = SyncBackend.matchSyncRequest(request)
269
- if (searchParams !== undefined) {
270
- return SyncBackend.handleSyncRequest({
271
- request,
272
- searchParams,
273
- env,
274
- ctx,
275
- syncBackendBinding: 'SYNC_BACKEND_DO',
276
- headers: {},
277
- })
278
- }
279
-
280
- if (url.pathname.endsWith('/client-do')) {
281
- const storeId = storeIdFromRequest(request)
282
- const id = env.CLIENT_DO.idFromName(storeId)
283
- return env.CLIENT_DO.get(id).fetch(request)
284
- }
285
-
286
- return new Response('Not found', { status: 404 }) as unknown as CfTypes.Response
287
- },
288
- } satisfies SyncBackend.CFWorker<Env>
289
- ```
290
-
291
- ### `reference/platform-adapters/cloudflare/env.ts`
292
-
293
- ```ts filename="reference/platform-adapters/cloudflare/env.ts"
294
-
295
- export type Env = {
296
- CLIENT_DO: CfTypes.DurableObjectNamespace<ClientDoWithRpcCallback>
297
- SYNC_BACKEND_DO: CfTypes.DurableObjectNamespace<SyncBackendRpcInterface>
298
- DB: CfTypes.D1Database
299
- }
300
- ```
301
-
302
- ### `reference/platform-adapters/cloudflare/shared.ts`
303
-
304
- ```ts filename="reference/platform-adapters/cloudflare/shared.ts"
305
-
306
- export const storeIdFromRequest = (request: CfTypes.Request) => {
307
- const url = new URL(request.url)
308
- const storeId = url.searchParams.get('storeId')
309
-
310
- if (storeId === null) {
311
- throw new Error('storeId is required in URL search params')
312
- }
313
-
314
- return storeId
315
- }
316
- ```
317
-
318
- ## API reference
319
-
320
- ### `createStoreDoPromise(options)`
321
-
322
- Creates a LiveStore instance inside a Durable Object.
323
-
324
- **Options:**
325
- - `schema` – LiveStore schema definition
326
- - `storeId` – Unique identifier for the store
327
- - `clientId` – Client identifier
328
- - `sessionId` – Session identifier (use `nanoid()`)
329
- - `durableObject` – Context about the Durable Object hosting the store:
330
- - `state` – Durable Object state handle (for example `this.ctx`)
331
- - `env` – Environment bindings for the Durable Object
332
- - `bindingName` – Name other workers use to reach this Durable Object
333
- - `syncBackendStub` – Durable Object stub used to reach the sync backend
334
- - `livePull` – Enable real-time updates (default: `false`)
335
- - `resetPersistence` – Drop LiveStore state/eventlog persistence before booting (development only, default: `false`)
336
- - `logger?` – Optional Effect logger layer to customize formatting/output
337
- - `logLevel?` – Optional minimum log level (use `LogLevel.None` to disable logs)
338
-
339
- ### `syncUpdateRpc(payload)`
340
-
341
- Client Durable Objects must implement this method so the sync backend can deliver live updates. `createStoreDoPromise` wires it up automatically—just forward the payload to `handleSyncUpdateRpc` (see the client Durable Object example above).
342
-
343
- ## Resetting LiveStore persistence (development only)
344
-
345
- When iterating locally, you can instruct the adapter to wipe the Durable Object’s LiveStore databases before booting by enabling `resetPersistence`. Guard this behind a protected route or admin token.
346
-
347
- ## `reference/platform-adapters/cloudflare/reset.ts`
348
-
349
- ```ts filename="reference/platform-adapters/cloudflare/reset.ts"
350
-
351
- export const maybeResetStore = async ({
352
- request,
353
- env,
354
- ctx,
355
- }: {
356
- request: Request
357
- env: Env
358
- ctx: CfTypes.DurableObjectState
359
- }) => {
360
- const url = new URL(request.url)
361
- const shouldReset = url.pathname === '/internal/livestore-dev-reset'
362
-
363
- const storeId = url.searchParams.get('storeId') ?? nanoid()
364
-
365
- const store = await createStoreDoPromise({
366
- schema,
367
- storeId,
368
- clientId: 'client-do',
369
- sessionId: nanoid(),
370
- durableObject: { ctx, env, bindingName: 'CLIENT_DO' },
371
- syncBackendStub: env.SYNC_BACKEND_DO.get(env.SYNC_BACKEND_DO.idFromName(storeId)),
372
- livePull: true,
373
- resetPersistence: shouldReset,
374
- })
375
-
376
- return store
377
- }
378
- ```
379
-
380
- ### `reference/platform-adapters/cloudflare/env.ts`
381
-
382
- ```ts filename="reference/platform-adapters/cloudflare/env.ts"
383
-
384
- export type Env = {
385
- CLIENT_DO: CfTypes.DurableObjectNamespace<ClientDoWithRpcCallback>
386
- SYNC_BACKEND_DO: CfTypes.DurableObjectNamespace<SyncBackendRpcInterface>
387
- DB: CfTypes.D1Database
388
- }
389
- ```
390
-
391
- ### `reference/platform-adapters/cloudflare/schema.ts`
392
-
393
- ```ts filename="reference/platform-adapters/cloudflare/schema.ts"
394
-
395
- export const tables = {
396
- todos: State.SQLite.table({
397
- name: 'todos',
398
- columns: {
399
- id: State.SQLite.text({ primaryKey: true }),
400
- text: State.SQLite.text({ default: '' }),
401
- completed: State.SQLite.boolean({ default: false }),
402
- deletedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
403
- },
404
- }),
405
- }
406
-
407
- export const events = {
408
- todoCreated: Events.synced({
409
- name: 'v1.TodoCreated',
410
- schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
411
- }),
412
- todoCompleted: Events.synced({
413
- name: 'v1.TodoCompleted',
414
- schema: Schema.Struct({ id: Schema.String }),
415
- }),
416
- todoUncompleted: Events.synced({
417
- name: 'v1.TodoUncompleted',
418
- schema: Schema.Struct({ id: Schema.String }),
419
- }),
420
- todoDeleted: Events.synced({
421
- name: 'v1.TodoDeleted',
422
- schema: Schema.Struct({ id: Schema.String, deletedAt: Schema.Date }),
423
- }),
424
- todoClearedCompleted: Events.synced({
425
- name: 'v1.TodoClearedCompleted',
426
- schema: Schema.Struct({ deletedAt: Schema.Date }),
427
- }),
428
- }
429
-
430
- const materializers = State.SQLite.materializers(events, {
431
- 'v1.TodoCreated': ({ id, text }) => tables.todos.insert({ id, text, completed: false }),
432
- 'v1.TodoCompleted': ({ id }) => tables.todos.update({ completed: true }).where({ id }),
433
- 'v1.TodoUncompleted': ({ id }) => tables.todos.update({ completed: false }).where({ id }),
434
- 'v1.TodoDeleted': ({ id, deletedAt }) => tables.todos.update({ deletedAt }).where({ id }),
435
- 'v1.TodoClearedCompleted': ({ deletedAt }) => tables.todos.update({ deletedAt }).where({ completed: true }),
436
- })
437
-
438
- const state = State.SQLite.makeState({ tables, materializers })
439
-
440
- export const schema = makeSchema({ events, state })
441
- ```
442
-
443
- :::caution
444
- Resetting persistence deletes all LiveStore state and eventlog data stored inside the Durable Object. Only expose this behaviour in guarded development flows and never to production traffic.
445
- :::
446
-
447
- ## Advanced features
448
-
449
- - Use `livePull: true` to receive push-based updates via Durable Object RPC callbacks.
450
- - Subscribe to data changes inside the Durable Object to trigger side effects (see the client Durable Object example).
451
- - Wire additional routes in the worker fetch handler to expose debugging endpoints or admin operations.
452
-
453
- For sync backend-related APIs like `makeDurableObject`, `handleSyncRequest`, and `matchSyncRequest`, see the [Cloudflare sync provider documentation](/sync-providers/cloudflare).
@@ -1,15 +0,0 @@
1
- # Electron adapter
2
-
3
- ## Using LiveStore with Electron
4
-
5
- LiveStore can already be used in Electron through the [web adapter](/platform-adapters/web-adapter), which works perfectly fine for most use cases. The web adapter leverages the browser APIs available in Electron's renderer process, providing full LiveStore functionality including reactive queries, offline-first operation, and sync capabilities.
6
-
7
- ## Native Electron Adapter
8
-
9
- While the web adapter works well, there is room for further improvement through a dedicated native Electron adapter. A native adapter would leverage Electron's unique capabilities including:
10
-
11
- - **Transparent database file persistence** - Direct file system access instead of browser storage abstractions (IndexedDB/OPFS)
12
- - **Performance improvements** - Native SQLite bindings via Node.js integration
13
- - **Better integration with Electron's main process** - Coordination between main and renderer processes
14
-
15
- Development of the native Electron adapter is tracked in [this GitHub issue](https://github.com/livestorejs/livestore/issues/296). Contributors are welcome to help with the implementation, and sponsorship opportunities are available to accelerate development of this adapter.