@livestore/livestore 0.4.0-dev.21 → 0.4.0-dev.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/QueryCache.js +1 -1
- package/dist/QueryCache.js.map +1 -1
- package/dist/SqliteDbWrapper.d.ts +5 -5
- package/dist/SqliteDbWrapper.d.ts.map +1 -1
- package/dist/SqliteDbWrapper.js +8 -8
- package/dist/SqliteDbWrapper.js.map +1 -1
- package/dist/SqliteDbWrapper.test.js +2 -2
- package/dist/SqliteDbWrapper.test.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +130 -2
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +185 -6
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/effect/LiveStore.test.d.ts +2 -0
- package/dist/effect/LiveStore.test.d.ts.map +1 -0
- package/dist/effect/LiveStore.test.js +42 -0
- package/dist/effect/LiveStore.test.js.map +1 -0
- package/dist/effect/mod.d.ts +1 -1
- package/dist/effect/mod.d.ts.map +1 -1
- package/dist/effect/mod.js +3 -1
- package/dist/effect/mod.js.map +1 -1
- package/dist/live-queries/base-class.d.ts +3 -3
- package/dist/live-queries/base-class.d.ts.map +1 -1
- package/dist/live-queries/base-class.js +2 -2
- package/dist/live-queries/base-class.js.map +1 -1
- package/dist/live-queries/client-document-get-query.d.ts +1 -1
- package/dist/live-queries/client-document-get-query.d.ts.map +1 -1
- package/dist/live-queries/client-document-get-query.js +1 -1
- package/dist/live-queries/client-document-get-query.js.map +1 -1
- package/dist/live-queries/computed.d.ts.map +1 -1
- package/dist/live-queries/computed.js +2 -2
- package/dist/live-queries/computed.js.map +1 -1
- package/dist/live-queries/db-query.js +14 -14
- package/dist/live-queries/db-query.js.map +1 -1
- package/dist/live-queries/db-query.test.js +2 -2
- package/dist/live-queries/db-query.test.js.map +1 -1
- package/dist/live-queries/signal.test.js +2 -2
- package/dist/live-queries/signal.test.js.map +1 -1
- package/dist/mod.d.ts +2 -1
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +1 -0
- package/dist/mod.js.map +1 -1
- package/dist/reactive.d.ts +9 -9
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +9 -26
- package/dist/reactive.js.map +1 -1
- package/dist/reactive.test.js +2 -2
- package/dist/reactive.test.js.map +1 -1
- package/dist/store/StoreRegistry.d.ts +215 -0
- package/dist/store/StoreRegistry.d.ts.map +1 -0
- package/dist/store/StoreRegistry.js +267 -0
- package/dist/store/StoreRegistry.js.map +1 -0
- package/dist/store/StoreRegistry.test.d.ts +2 -0
- package/dist/store/StoreRegistry.test.d.ts.map +1 -0
- package/dist/store/StoreRegistry.test.js +381 -0
- package/dist/store/StoreRegistry.test.js.map +1 -0
- package/dist/store/create-store.d.ts +56 -6
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +32 -7
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/devtools.d.ts +1 -1
- package/dist/store/devtools.d.ts.map +1 -1
- package/dist/store/devtools.js +16 -3
- package/dist/store/devtools.js.map +1 -1
- package/dist/store/store-eventstream.test.js +2 -2
- package/dist/store/store-eventstream.test.js.map +1 -1
- package/dist/store/store-types.d.ts +59 -9
- package/dist/store/store-types.d.ts.map +1 -1
- package/dist/store/store-types.js.map +1 -1
- package/dist/store/store-types.test.js +1 -1
- package/dist/store/store-types.test.js.map +1 -1
- package/dist/store/store.d.ts +102 -6
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +148 -47
- package/dist/store/store.js.map +1 -1
- package/dist/utils/dev.js.map +1 -1
- package/dist/utils/stack-info.js +2 -2
- package/dist/utils/stack-info.js.map +1 -1
- package/dist/utils/tests/fixture.d.ts +1 -1
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/dist/utils/tests/fixture.js.map +1 -1
- package/dist/utils/tests/otel.d.ts.map +1 -1
- package/dist/utils/tests/otel.js +5 -5
- package/dist/utils/tests/otel.js.map +1 -1
- package/package.json +59 -18
- package/src/QueryCache.ts +1 -1
- package/src/SqliteDbWrapper.test.ts +4 -2
- package/src/SqliteDbWrapper.ts +12 -11
- package/src/ambient.d.ts +0 -7
- package/src/effect/LiveStore.test.ts +61 -0
- package/src/effect/LiveStore.ts +381 -8
- package/src/effect/mod.ts +13 -1
- package/src/live-queries/__snapshots__/db-query.test.ts.snap +336 -231
- package/src/live-queries/base-class.ts +7 -6
- package/src/live-queries/client-document-get-query.ts +4 -2
- package/src/live-queries/computed.ts +3 -2
- package/src/live-queries/db-query.test.ts +3 -2
- package/src/live-queries/db-query.ts +15 -15
- package/src/live-queries/signal.test.ts +3 -2
- package/src/mod.ts +2 -0
- package/src/reactive.test.ts +3 -2
- package/src/reactive.ts +22 -23
- package/src/store/StoreRegistry.test.ts +540 -0
- package/src/store/StoreRegistry.ts +418 -0
- package/src/store/create-store.ts +76 -15
- package/src/store/devtools.ts +20 -6
- package/src/store/store-eventstream.test.ts +4 -2
- package/src/store/store-types.test.ts +3 -1
- package/src/store/store-types.ts +64 -13
- package/src/store/store.ts +197 -60
- package/src/utils/dev.ts +2 -2
- package/src/utils/stack-info.ts +2 -2
- package/src/utils/tests/fixture.ts +2 -1
- package/src/utils/tests/otel.ts +8 -7
- package/docs/api/index.md +0 -3
- package/docs/building-with-livestore/complex-ui-state/index.md +0 -5
- package/docs/building-with-livestore/crud/index.md +0 -5
- package/docs/building-with-livestore/data-modeling/index.md +0 -1
- package/docs/building-with-livestore/debugging/index.md +0 -17
- package/docs/building-with-livestore/devtools/index.md +0 -79
- package/docs/building-with-livestore/events/index.md +0 -355
- package/docs/building-with-livestore/examples/ai-agent/index.md +0 -5
- package/docs/building-with-livestore/examples/index.md +0 -30
- package/docs/building-with-livestore/examples/todo-workspaces/index.md +0 -891
- package/docs/building-with-livestore/examples/turnbased-game/index.md +0 -7
- package/docs/building-with-livestore/opentelemetry/index.md +0 -208
- package/docs/building-with-livestore/production-checklist/index.md +0 -5
- package/docs/building-with-livestore/reactivity-system/index.md +0 -202
- package/docs/building-with-livestore/rules-for-ai-agents/index.md +0 -9
- package/docs/building-with-livestore/state/materializers/index.md +0 -300
- package/docs/building-with-livestore/state/sql-queries/index.md +0 -72
- package/docs/building-with-livestore/state/sqlite/index.md +0 -45
- package/docs/building-with-livestore/state/sqlite-schema/index.md +0 -306
- package/docs/building-with-livestore/state/sqlite-schema-effect/index.md +0 -300
- package/docs/building-with-livestore/store/index.md +0 -281
- package/docs/building-with-livestore/syncing/index.md +0 -136
- package/docs/building-with-livestore/tools/cli/index.md +0 -177
- package/docs/building-with-livestore/tools/mcp/index.md +0 -187
- package/docs/examples/cloudflare-adapter/index.md +0 -44
- package/docs/examples/expo-adapter/index.md +0 -44
- package/docs/examples/index.md +0 -55
- package/docs/examples/node-adapter/index.md +0 -44
- package/docs/examples/web-adapter/index.md +0 -52
- package/docs/framework-integrations/custom-elements/index.md +0 -142
- package/docs/framework-integrations/react-integration/index.md +0 -918
- package/docs/framework-integrations/solid-integration/index.md +0 -293
- package/docs/framework-integrations/svelte-integration/index.md +0 -42
- package/docs/framework-integrations/vue-integration/index.md +0 -294
- package/docs/getting-started/expo/index.md +0 -736
- package/docs/getting-started/node/index.md +0 -115
- package/docs/getting-started/react-web/index.md +0 -573
- package/docs/getting-started/solid/index.md +0 -3
- package/docs/getting-started/vue/index.md +0 -471
- package/docs/index.md +0 -209
- package/docs/llms.txt +0 -147
- package/docs/misc/CODE_OF_CONDUCT/index.md +0 -133
- package/docs/misc/FAQ/index.md +0 -37
- package/docs/misc/community/index.md +0 -88
- package/docs/misc/credits/index.md +0 -14
- package/docs/misc/design-partners/index.md +0 -13
- package/docs/misc/package-management/index.md +0 -21
- package/docs/misc/performance/index.md +0 -25
- package/docs/misc/resources/index.md +0 -46
- package/docs/misc/state-of-the-project/index.md +0 -37
- package/docs/misc/troubleshooting/index.md +0 -82
- package/docs/overview/concepts/index.md +0 -78
- package/docs/overview/how-livestore-works/index.md +0 -56
- package/docs/overview/introduction/index.md +0 -5
- package/docs/overview/technology-comparison/index.md +0 -40
- package/docs/overview/when-livestore/index.md +0 -81
- package/docs/overview/why-livestore/index.md +0 -5
- package/docs/patterns/ai/index.md +0 -15
- package/docs/patterns/anonymous-user-transition/index.md +0 -10
- package/docs/patterns/app-evolution/index.md +0 -72
- package/docs/patterns/auth/index.md +0 -226
- package/docs/patterns/effect/index.md +0 -1495
- package/docs/patterns/encryption/index.md +0 -6
- package/docs/patterns/external-data/index.md +0 -5
- package/docs/patterns/file-management/index.md +0 -11
- package/docs/patterns/file-structure/index.md +0 -14
- package/docs/patterns/list-ordering/index.md +0 -369
- package/docs/patterns/offline/index.md +0 -32
- package/docs/patterns/orm/index.md +0 -18
- package/docs/patterns/presence/index.md +0 -11
- package/docs/patterns/rich-text-editing/index.md +0 -11
- package/docs/patterns/server-side-clients/index.md +0 -97
- package/docs/patterns/side-effects/index.md +0 -11
- package/docs/patterns/state-machines/index.md +0 -11
- package/docs/patterns/storybook/index.md +0 -192
- package/docs/patterns/undo-redo/index.md +0 -9
- package/docs/patterns/version-control/index.md +0 -8
- package/docs/platform-adapters/cloudflare-durable-object-adapter/index.md +0 -453
- package/docs/platform-adapters/electron-adapter/index.md +0 -15
- package/docs/platform-adapters/expo-adapter/index.md +0 -245
- package/docs/platform-adapters/node-adapter/index.md +0 -160
- package/docs/platform-adapters/tauri-adapter/index.md +0 -15
- package/docs/platform-adapters/web-adapter/index.md +0 -218
- package/docs/sustainable-open-source/contributing/docs/index.md +0 -94
- package/docs/sustainable-open-source/contributing/info/index.md +0 -63
- package/docs/sustainable-open-source/contributing/monorepo/index.md +0 -195
- package/docs/sustainable-open-source/sponsoring/index.md +0 -104
- package/docs/sync-providers/cloudflare/index.md +0 -773
- package/docs/sync-providers/custom/index.md +0 -65
- package/docs/sync-providers/electricsql/index.md +0 -159
- package/docs/sync-providers/s2/index.md +0 -230
- package/docs/tutorial/0-welcome/index.md +0 -48
- package/docs/tutorial/1-setup-starter-project/index.md +0 -105
- package/docs/tutorial/2-deploy-to-cloudflare/index.md +0 -195
- package/docs/tutorial/3-read-and-write-todos-via-livestore/index.md +0 -511
- package/docs/tutorial/4-sync-data-via-cloudflare/index.md +0 -210
- package/docs/tutorial/5-expand-business-logic/index.md +0 -174
- package/docs/tutorial/6-persist-ui-state/index.md +0 -453
- package/docs/tutorial/7-next-steps/index.md +0 -22
- package/docs/understanding-livestore/design-decisions/index.md +0 -33
- package/docs/understanding-livestore/event-sourcing/index.md +0 -40
|
@@ -1,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,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.
|