@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,192 +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 from 'react'
32
-
33
- // Create LiveStore decorator with optional seeding
34
- export const createLiveStoreDecorator = (seedEvents = []) => (Story) => {
35
- const onBoot = (store) => {
36
- // Seed data through events during boot
37
- if (seedEvents.length > 0) {
38
- store.commit(...seedEvents)
39
- }
40
- }
41
-
42
- return (
43
- <LiveStoreProvider
44
- schema={schema}
45
- adapter={makeInMemoryAdapter()}
46
- batchUpdates={batchUpdates}
47
- boot={onBoot}
48
- renderLoading={(status) => <div>Loading LiveStore ({status.stage})...</div>}
49
- >
50
- <Story />
51
- </LiveStoreProvider>
52
- )
53
- }`,
54
-
55
- todoInput: `import React from 'react'
56
-
57
- // Define queries (like in TodoMVC)
58
- const uiState$ = queryDb(tables.uiState.get(), { label: 'uiState' })
59
-
60
- export const TodoInput = () => {
61
- const { store } = useStore()
62
- const { newTodoText } = store.useQuery(uiState$)
63
-
64
- const updateNewTodoText = (text: string) =>
65
- store.commit(events.uiStateSet({ newTodoText: text }))
66
-
67
- const createTodo = () => {
68
- if (newTodoText.trim()) {
69
- store.commit(
70
- events.todoCreated({ id: crypto.randomUUID(), text: newTodoText }),
71
- events.uiStateSet({ newTodoText: '' }),
72
- )
73
- }
74
- }
75
-
76
- return (
77
- <div>
78
- <h2>Add Todo</h2>
79
- <input
80
- type="text"
81
- placeholder="What needs to be done?"
82
- value={newTodoText}
83
- onChange={(e) => updateNewTodoText(e.target.value)}
84
- onKeyDown={(e) => {
85
- if (e.key === 'Enter') {
86
- createTodo()
87
- }
88
- }}
89
- />
90
- <button onClick={createTodo}>
91
- Add
92
- </button>
93
- </div>
94
- )
95
- }`,
96
-
97
- schema: `import { Events, makeSchema, Schema, SessionIdSymbol, State } from '@livestore/livestore'
98
-
99
- // Define tables (based on TodoMVC example)
100
- export const tables = {
101
- todos: State.SQLite.table({
102
- name: 'todos',
103
- columns: {
104
- id: State.SQLite.text({ primaryKey: true }),
105
- text: State.SQLite.text({ default: '' }),
106
- completed: State.SQLite.boolean({ default: false }),
107
- deletedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
108
- },
109
- }),
110
- // Client document for UI state
111
- uiState: State.SQLite.clientDocument({
112
- name: 'uiState',
113
- schema: Schema.Struct({
114
- newTodoText: Schema.String,
115
- filter: Schema.Literal('all', 'active', 'completed')
116
- }),
117
- default: {
118
- id: SessionIdSymbol,
119
- value: { newTodoText: '', filter: 'all' }
120
- },
121
- }),
122
- }
123
-
124
- // Define events (exactly from TodoMVC)
125
- export const events = {
126
- todoCreated: Events.synced({
127
- name: 'v1.TodoCreated',
128
- schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
129
- }),
130
- todoCompleted: Events.synced({
131
- name: 'v1.TodoCompleted',
132
- schema: Schema.Struct({ id: Schema.String }),
133
- }),
134
- todoUncompleted: Events.synced({
135
- name: 'v1.TodoUncompleted',
136
- schema: Schema.Struct({ id: Schema.String }),
137
- }),
138
- todoDeleted: Events.synced({
139
- name: 'v1.TodoDeleted',
140
- schema: Schema.Struct({ id: Schema.String, deletedAt: Schema.Date }),
141
- }),
142
- todoClearedCompleted: Events.synced({
143
- name: 'v1.TodoClearedCompleted',
144
- schema: Schema.Struct({ deletedAt: Schema.Date }),
145
- }),
146
- // Auto-generated client document event
147
- uiStateSet: tables.uiState.set,
148
- }
149
-
150
- // Define materializers to map events to state
151
- const materializers = State.SQLite.materializers(events, {
152
- 'v1.TodoCreated': ({ id, text }) => tables.todos.insert({ id, text, completed: false }),
153
- 'v1.TodoCompleted': ({ id }) => tables.todos.update({ completed: true }).where({ id }),
154
- 'v1.TodoUncompleted': ({ id }) => tables.todos.update({ completed: false }).where({ id }),
155
- 'v1.TodoDeleted': ({ id, deletedAt }) => tables.todos.update({ deletedAt }).where({ id }),
156
- 'v1.TodoClearedCompleted': ({ deletedAt }) => tables.todos.update({ deletedAt }).where({ completed: true }),
157
- })
158
-
159
- const state = State.SQLite.makeState({ tables, materializers })
160
-
161
- export const schema = makeSchema({ events, state })`
162
- }
163
-
164
- LiveStore works seamlessly with Storybook for React component development and testing.
165
-
166
- **Note:** This guide focuses on React. For other frameworks, adapt patterns accordingly.
167
-
168
- ## Setup
169
-
170
- First, [install Storybook](https://storybook.js.org/docs/get-started/install) in your React project.
171
-
172
- ## Configuration
173
-
174
- Create a decorator that wraps stories with a fresh LiveStore instance and use the TodoMVC schema for realistic examples.
175
-
176
- <Tabs>
177
- <TabItem label="Stories">
178
- <Code code={CODE.todoInputStories} lang="tsx" title="src/TodoInput.stories.tsx" />
179
- </TabItem>
180
- <TabItem label="Storybook Config">
181
- <Code code={CODE.storybookPreview} lang="js" title=".storybook/preview.js" />
182
- </TabItem>
183
- <TabItem label="Decorator">
184
- <Code code={CODE.decorator} lang="js" title="src/decorator.js" />
185
- </TabItem>
186
- <TabItem label="Component">
187
- <Code code={CODE.todoInput} lang="tsx" title="src/TodoInput.tsx" />
188
- </TabItem>
189
- <TabItem label="Schema">
190
- <Code code={CODE.schema} lang="ts" title="src/schema.ts" />
191
- </TabItem>
192
- </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.