@livestore/livestore 0.4.0-dev.21 → 0.4.0-dev.22

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 (75) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/effect/LiveStore.d.ts +123 -2
  3. package/dist/effect/LiveStore.d.ts.map +1 -1
  4. package/dist/effect/LiveStore.js +195 -1
  5. package/dist/effect/LiveStore.js.map +1 -1
  6. package/dist/effect/mod.d.ts +1 -1
  7. package/dist/effect/mod.d.ts.map +1 -1
  8. package/dist/effect/mod.js +3 -1
  9. package/dist/effect/mod.js.map +1 -1
  10. package/dist/mod.d.ts +1 -0
  11. package/dist/mod.d.ts.map +1 -1
  12. package/dist/mod.js +1 -0
  13. package/dist/mod.js.map +1 -1
  14. package/dist/store/StoreRegistry.d.ts +190 -0
  15. package/dist/store/StoreRegistry.d.ts.map +1 -0
  16. package/dist/store/StoreRegistry.js +244 -0
  17. package/dist/store/StoreRegistry.js.map +1 -0
  18. package/dist/store/StoreRegistry.test.d.ts +2 -0
  19. package/dist/store/StoreRegistry.test.d.ts.map +1 -0
  20. package/dist/store/StoreRegistry.test.js +380 -0
  21. package/dist/store/StoreRegistry.test.js.map +1 -0
  22. package/dist/store/create-store.d.ts +50 -4
  23. package/dist/store/create-store.d.ts.map +1 -1
  24. package/dist/store/create-store.js +19 -0
  25. package/dist/store/create-store.js.map +1 -1
  26. package/dist/store/devtools.d.ts.map +1 -1
  27. package/dist/store/devtools.js +13 -0
  28. package/dist/store/devtools.js.map +1 -1
  29. package/dist/store/store-types.d.ts +10 -25
  30. package/dist/store/store-types.d.ts.map +1 -1
  31. package/dist/store/store-types.js.map +1 -1
  32. package/dist/store/store.d.ts +23 -6
  33. package/dist/store/store.d.ts.map +1 -1
  34. package/dist/store/store.js +20 -2
  35. package/dist/store/store.js.map +1 -1
  36. package/docs/building-with-livestore/complex-ui-state/index.md +0 -2
  37. package/docs/building-with-livestore/crud/index.md +0 -2
  38. package/docs/building-with-livestore/data-modeling/index.md +29 -0
  39. package/docs/building-with-livestore/examples/todo-workspaces/index.md +0 -6
  40. package/docs/building-with-livestore/opentelemetry/index.md +25 -6
  41. package/docs/building-with-livestore/rules-for-ai-agents/index.md +2 -2
  42. package/docs/building-with-livestore/state/sql-queries/index.md +22 -0
  43. package/docs/building-with-livestore/state/sqlite-schema/index.md +2 -2
  44. package/docs/building-with-livestore/store/index.md +344 -0
  45. package/docs/framework-integrations/react-integration/index.md +380 -361
  46. package/docs/framework-integrations/vue-integration/index.md +2 -2
  47. package/docs/getting-started/expo/index.md +189 -43
  48. package/docs/getting-started/react-web/index.md +77 -24
  49. package/docs/getting-started/vue/index.md +3 -3
  50. package/docs/index.md +1 -2
  51. package/docs/llms.txt +0 -1
  52. package/docs/misc/troubleshooting/index.md +3 -3
  53. package/docs/overview/how-livestore-works/index.md +1 -1
  54. package/docs/overview/introduction/index.md +409 -1
  55. package/docs/overview/why-livestore/index.md +108 -2
  56. package/docs/patterns/auth/index.md +185 -34
  57. package/docs/patterns/effect/index.md +11 -1
  58. package/docs/patterns/storybook/index.md +43 -26
  59. package/docs/platform-adapters/expo-adapter/index.md +36 -19
  60. package/docs/platform-adapters/web-adapter/index.md +71 -2
  61. package/docs/tutorial/1-setup-starter-project/index.md +5 -5
  62. package/docs/tutorial/3-read-and-write-todos-via-livestore/index.md +54 -35
  63. package/docs/tutorial/5-expand-business-logic/index.md +1 -1
  64. package/docs/tutorial/6-persist-ui-state/index.md +12 -12
  65. package/package.json +6 -6
  66. package/src/effect/LiveStore.ts +385 -3
  67. package/src/effect/mod.ts +13 -1
  68. package/src/mod.ts +1 -0
  69. package/src/store/StoreRegistry.test.ts +516 -0
  70. package/src/store/StoreRegistry.ts +393 -0
  71. package/src/store/create-store.ts +50 -4
  72. package/src/store/devtools.ts +15 -0
  73. package/src/store/store-types.ts +17 -5
  74. package/src/store/store.ts +25 -5
  75. package/docs/building-with-livestore/examples/index.md +0 -30
@@ -2,4 +2,412 @@
2
2
 
3
3
  ## What is LiveStore?
4
4
 
5
- TBD
5
+ LiveStore is an event-driven data layer with a built-in sync engine. Its prime use case is building complex, client-side apps like [Linear](http://linear.app/), [Figma](https://www.figma.com/) or [Notion](https://notion.so/) that also work offline.
6
+
7
+ Think of LiveStore as a next-generation state management library (like Zustand or Redux) that also persists and distributes state:
8
+
9
+ <CapabilitiesDiagram class="my-8" />
10
+
11
+ The combination of persisted and distributed state is a giant leap for creating an **amazing user experience** (UX), while also providing a **best-in-class developer experience** (DX). The **immutable eventlog** enables robust testing and tight feedback loops, making it perfect for agentic coding.
12
+
13
+ <div class="not-content grid grid-cols-1 md:grid-cols-3 gap-4 my-8">
14
+ <div class="rounded-xl border border-gray-700 p-5">
15
+ <h3 class="text-base font-medium !mt-0 !mb-2">UX</h3>
16
+ <ul class="!space-y-2 text-sm list-none p-0 m-0">
17
+ <li><strong>Synced</strong>: Real-time updates across devices</li>
18
+ <li><strong>Fast</strong>: No async loading over the network</li>
19
+ <li><strong>Persistent</strong>: Works offline, survives page refresh</li>
20
+ </ul>
21
+ </div>
22
+
23
+ <div class="rounded-xl border border-gray-700 p-5">
24
+ <h3 class="text-base font-medium !mt-0 !mb-2">DX</h3>
25
+ <ul class="!space-y-2 text-sm list-none p-0 m-0">
26
+ <li><strong>Principled</strong>: Event-driven instead of mutable state</li>
27
+ <li><strong>Composable & Type-safe</strong>: Fully typed events & queries</li>
28
+ <li><strong>Reactive</strong>: Automatic UI updates when data changes</li>
29
+ </ul>
30
+ </div>
31
+
32
+ <div class="rounded-xl border border-gray-700 p-5">
33
+ <h3 class="text-base font-medium !mt-0 !mb-2">AX</h3>
34
+ <ul class="!space-y-2 text-sm list-none p-0 m-0">
35
+ <li><strong>Testable</strong>: Immutable eventlog for feedback loop</li>
36
+ <li><strong>Debuggable</strong>: Same events always produce same state</li>
37
+ <li><strong>Evolvable</strong>: Reset & fork state for experiments</li>
38
+ </ul>
39
+ </div>
40
+ </div>
41
+
42
+ LiveStore works cross-platform and can be used for building UI apps (web, mobile, desktop, ...), agents, and any other software like CLIs, scripts or server-to-server applications.
43
+
44
+ See this talk for more info: [Sync different: Event sourcing in local-first apps](https://www.youtube.com/watch?v=nyPl84BopKc&list=PL4isNRKAwz2MabH6AMhUz1yS3j1DqGdtT).
45
+
46
+ <details>
47
+ <summary>Are you an expert? See here for advanced topics.</summary>
48
+ - [How LiveStore deals with merge conflicts?](/overview/how-livestore-works#conflict-resolution)
49
+ - Why event-sourcing instead of CRDTs or query-driven sync?
50
+ - How do schema migrations work?
51
+ - What are limitations of LiveStore?
52
+ - How LiveStore is perfect for coding agents.
53
+ </details>
54
+
55
+ ## The core idea: Synced events -> State -> UI
56
+
57
+ Unlike other sync solutions, LiveStore syncs events—not state!
58
+
59
+ Events are immutable facts that describe what happened ("TodoCreated", "TodoCompleted"), while state is derived by replaying them. This means every client reconstructs the same state from the same event history, making sync predictable and debuggable.
60
+
61
+ The state then changes trigger reaactive UI updates.
62
+
63
+ ### Traditional state management uses ephemeral, in-memory state
64
+
65
+ Traditional state management works like this: you dispatch actions that update an in-memory store, and your UI reacts to changes. But that state vanishes when the user refreshes or closes the browser. Add persistence and you need to manage local storage which essentially serves as a secondary database to the data you've stored in the cloud. Add sync and you're dealing with conflict resolution, offline queues, and backend integration. LiveStore handles all this for you with one simple, event-driven API!
66
+
67
+ ### LiveStore persists events which materialize into state
68
+
69
+ LiveStore handles all of this through one unified pattern: **event sourcing**.
70
+
71
+ <div class="d2-full-width">
72
+
73
+ ```d2
74
+ ...@../../../../src/content/base.d2
75
+
76
+ direction: right
77
+
78
+ "User action": {
79
+ label: "User action"
80
+ shape: rectangle
81
+ }
82
+
83
+ Event: {
84
+ label: "Event"
85
+ shape: rectangle
86
+ }
87
+
88
+ Eventlog: {
89
+ label: "Eventlog"
90
+ shape: rectangle
91
+ }
92
+
93
+ "SQLite state": {
94
+ label: "SQLite state"
95
+ shape: rectangle
96
+ }
97
+
98
+ UI: {
99
+ label: "UI"
100
+ shape: rectangle
101
+ }
102
+
103
+ "Sync to other clients": {
104
+ label: "Sync to other clients"
105
+ shape: rectangle
106
+ }
107
+
108
+ "User action" -> Event -> Eventlog -> "SQLite state" -> UI
109
+ Eventlog -> "Sync to other clients"
110
+ ```
111
+
112
+ </div>
113
+
114
+ Instead of mutating state directly, you commit **events** that _describe_ what happened. These events are persisted to an **eventlog** (like a git history) and automatically materialized into a local **SQLite database** that your UI queries reactively.
115
+
116
+ :::note
117
+ While the majority of apps will probably use SQLite as the data store for persistence, LiveStore is flexible enough to materialize state into other targets as well (e.g. file systems).
118
+ :::
119
+
120
+ If you want to learn more, you can dive deeper into [how LiveStore works](/overview/how-livestore-works).
121
+
122
+ ### Comparison with traditional state management like Redux
123
+
124
+ If you've used Redux, this pattern of "comitting events" will feel familiar: **Events are like actions, materializers are like reducers, and the SQLite state is like your store.**
125
+
126
+ But there are key differences:
127
+
128
+ | Redux | LiveStore |
129
+ | -------------------------------------------------- | ------------------------------------------- |
130
+ | Actions dispatch → reducers update in-memory state | Events commit → materializers update SQLite |
131
+ | State lost on refresh | Events persisted locally |
132
+ | Sync requires external setup | Sync built-in via eventlog |
133
+ | Fixed state shape | Query any shape with SQL |
134
+
135
+ ## A practical example
136
+
137
+ Let's walk through a simple example of a todo list with LiveStore and React.
138
+
139
+ ### Define your schema
140
+
141
+ At the core of every app built with LiveStore, you have a _schema_ which consists of three parts:
142
+
143
+ - **Events**: describe what can happen in your app
144
+ - **State**: defines how data is stored in your app
145
+ - **Materializers**: determines how events are mapped to state in your app
146
+
147
+ Here's an example:
148
+
149
+ ## `reference/overview/introduction/schema.ts`
150
+
151
+ ```ts filename="reference/overview/introduction/schema.ts"
152
+ // schema.ts
153
+
154
+ // 1. Define events (the things that can happen in your app)
155
+ export const events = {
156
+ todoCreated: Events.synced({
157
+ name: 'v1.TodoCreated',
158
+ schema: Schema.Struct({
159
+ id: Schema.String,
160
+ text: Schema.String,
161
+ }),
162
+ }),
163
+ todoCompleted: Events.synced({
164
+ name: 'v1.TodoCompleted',
165
+ schema: Schema.Struct({ id: Schema.String }),
166
+ }),
167
+ }
168
+
169
+ // 2. Define SQLite tables (how to query your state)
170
+ export const tables = {
171
+ todos: State.SQLite.table({
172
+ name: 'todos',
173
+ columns: {
174
+ id: State.SQLite.text({ primaryKey: true }),
175
+ text: State.SQLite.text({ default: '' }),
176
+ completed: State.SQLite.boolean({ default: false }),
177
+ },
178
+ }),
179
+ }
180
+
181
+ // 3. Define materializers (how to turn events into state)
182
+ const materializers = State.SQLite.materializers(events, {
183
+ 'v1.TodoCreated': ({ id, text }) => tables.todos.insert({ id, text }),
184
+ 'v1.TodoCompleted': ({ id }) => tables.todos.update({ completed: true }).where({ id }),
185
+ })
186
+
187
+ const state = State.SQLite.makeState({ tables, materializers })
188
+ export const schema = makeSchema({ events, state })
189
+ ```
190
+
191
+ ### Usage on the frontend
192
+
193
+ LiveStore comes with integrations for all major frontend frameworks, e.g. for [React](/framework-integrations/react-integration) or [Vue](/framework-integrations/vue-integration).
194
+
195
+ The [`queryDb`](/building-with-livestore/reactivity-system#reactive-sql-queries) function creates a [reactive query](/building-with-livestore/reactivity-system) which updates automatically when its data in the database changes. Here's how to use it in React:
196
+
197
+ ## `reference/overview/introduction/todo-app.tsx`
198
+
199
+ ```tsx filename="reference/overview/introduction/todo-app.tsx"
200
+ // TodoApp.tsx
201
+
202
+ const adapter = makeInMemoryAdapter()
203
+
204
+ const useAppStore = () =>
205
+ useStore({
206
+ storeId: 'my-app',
207
+ schema,
208
+ adapter,
209
+ batchUpdates,
210
+ })
211
+
212
+ // Define a reactive query
213
+ const visibleTodos$ = queryDb(() => tables.todos, {
214
+ label: 'visibleTodos',
215
+ })
216
+
217
+ export function TodoApp() {
218
+ const store = useAppStore()
219
+
220
+ // Reactively updates when todos change in the DB
221
+ const todos = store.useQuery(visibleTodos$)
222
+
223
+ const addTodo = (text: string) => {
224
+ // Commit an event to the store
225
+ store.commit(
226
+ events.todoCreated({
227
+ id: crypto.randomUUID(),
228
+ text,
229
+ }),
230
+ )
231
+ }
232
+
233
+ const completeTodo = (id: string) => {
234
+ // Commit an event to the store
235
+ store.commit(events.todoCompleted({ id }))
236
+ }
237
+
238
+ return (
239
+ <div>
240
+ <button type="button" onClick={() => addTodo('New todo')}>
241
+ Add
242
+ </button>
243
+ {todos.map((todo) => (
244
+ <button key={todo.id} type="button" onClick={() => completeTodo(todo.id)}>
245
+ {todo.completed ? '✓' : '○'} {todo.text}
246
+ </button>
247
+ ))}
248
+ </div>
249
+ )
250
+ }
251
+ ```
252
+
253
+ ### `reference/overview/introduction/schema.ts`
254
+
255
+ ```ts filename="reference/overview/introduction/schema.ts"
256
+ // schema.ts
257
+
258
+ // 1. Define events (the things that can happen in your app)
259
+ export const events = {
260
+ todoCreated: Events.synced({
261
+ name: 'v1.TodoCreated',
262
+ schema: Schema.Struct({
263
+ id: Schema.String,
264
+ text: Schema.String,
265
+ }),
266
+ }),
267
+ todoCompleted: Events.synced({
268
+ name: 'v1.TodoCompleted',
269
+ schema: Schema.Struct({ id: Schema.String }),
270
+ }),
271
+ }
272
+
273
+ // 2. Define SQLite tables (how to query your state)
274
+ export const tables = {
275
+ todos: State.SQLite.table({
276
+ name: 'todos',
277
+ columns: {
278
+ id: State.SQLite.text({ primaryKey: true }),
279
+ text: State.SQLite.text({ default: '' }),
280
+ completed: State.SQLite.boolean({ default: false }),
281
+ },
282
+ }),
283
+ }
284
+
285
+ // 3. Define materializers (how to turn events into state)
286
+ const materializers = State.SQLite.materializers(events, {
287
+ 'v1.TodoCreated': ({ id, text }) => tables.todos.insert({ id, text }),
288
+ 'v1.TodoCompleted': ({ id }) => tables.todos.update({ completed: true }).where({ id }),
289
+ })
290
+
291
+ const state = State.SQLite.makeState({ tables, materializers })
292
+ export const schema = makeSchema({ events, state })
293
+ ```
294
+
295
+ This code replaces all the API calls, state management, UI update, caching, and synchronization logic you may be used to from writing apps in a more traditional way. If configured, it also automatically takes care of syncing data to other clients.
296
+
297
+ Also notice how there's no loading state for queries—SQLite runs in-memory on the main thread, so reads are synchronous and instant, making your app super snappy and reactive to user input.
298
+
299
+ ## Why events?
300
+
301
+ But why use events at all, rather than directly mutating state?
302
+
303
+ ### Benefits of using events for state management
304
+
305
+ - **Events capture intent, not just outcome.** When you mutate state directly (`todo.completed = true`), you lose the _why_. Events like `TodoCompleted` preserve the user's intent, which matters for debugging, analytics, undo/redo, and features like activity feeds.
306
+ - **Events decouple what happened from how it's stored.** Your state shape can evolve independently of your event history. Need a new denormalized table for performance? Just add a materializer—no data migration required.
307
+ - **Events make sync tractable.** Syncing mutable state across devices is hard (which field wins?). Syncing an append-only event log is simpler: you're merging histories, not reconciling conflicting states.
308
+
309
+ ### Benefits of LiveStore's eventlog
310
+
311
+ The [eventlog](/building-with-livestore/events/#eventlog) sits the core of LiveStore and gives you several benefits:
312
+
313
+ - **Persistence**: Events survive page refreshes and app restarts
314
+ - **Sync**: Events replay identically across devices
315
+ - **History**: Full audit trail of every change (enables time travel and helps with debugging)
316
+ - **Flexibility**: Change your queries without migrations—just materialize differently
317
+
318
+ ## How syncing works
319
+
320
+ LiveStore includes a **sync engine** which handles [syncing](/building-with-livestore/syncing) for you under the hood.
321
+
322
+ When you can enable syncing, the sync engine distributes your state across various other clients (these can be other browsers, apps, devices, servers—anything that can connect to your store via an [adapter](/overview/how-livestore-works#platform-adapters)).
323
+
324
+ LiveStore uses a push/pull model inspired by git:
325
+
326
+ 1. Local events are committed immediately (optimistic updates by default)
327
+ 1. Events sync to a central backend when online
328
+ 1. Other clients pull new events and materialize them locally
329
+ 1. Conflicts resolve deterministically (last-write-wins by default, or custom logic)
330
+
331
+ Here's an example that syncs your state via Cloudflare (using the [`@livestore/sync-cf`](/sync-providers/cloudflare/) package):
332
+
333
+ ## `reference/overview/introduction/worker.ts`
334
+
335
+ ```ts filename="reference/overview/introduction/worker.ts"
336
+
337
+ makeWorker({
338
+ schema,
339
+ sync: {
340
+ backend: makeWsSync({ url: `${location.origin}/sync` }),
341
+ },
342
+ })
343
+ ```
344
+
345
+ ### `reference/overview/introduction/schema.ts`
346
+
347
+ ```ts filename="reference/overview/introduction/schema.ts"
348
+ // schema.ts
349
+
350
+ // 1. Define events (the things that can happen in your app)
351
+ export const events = {
352
+ todoCreated: Events.synced({
353
+ name: 'v1.TodoCreated',
354
+ schema: Schema.Struct({
355
+ id: Schema.String,
356
+ text: Schema.String,
357
+ }),
358
+ }),
359
+ todoCompleted: Events.synced({
360
+ name: 'v1.TodoCompleted',
361
+ schema: Schema.Struct({ id: Schema.String }),
362
+ }),
363
+ }
364
+
365
+ // 2. Define SQLite tables (how to query your state)
366
+ export const tables = {
367
+ todos: State.SQLite.table({
368
+ name: 'todos',
369
+ columns: {
370
+ id: State.SQLite.text({ primaryKey: true }),
371
+ text: State.SQLite.text({ default: '' }),
372
+ completed: State.SQLite.boolean({ default: false }),
373
+ },
374
+ }),
375
+ }
376
+
377
+ // 3. Define materializers (how to turn events into state)
378
+ const materializers = State.SQLite.materializers(events, {
379
+ 'v1.TodoCreated': ({ id, text }) => tables.todos.insert({ id, text }),
380
+ 'v1.TodoCompleted': ({ id }) => tables.todos.update({ completed: true }).where({ id }),
381
+ })
382
+
383
+ const state = State.SQLite.makeState({ tables, materializers })
384
+ export const schema = makeSchema({ events, state })
385
+ ```
386
+
387
+ That's all the configuration needed—your app works offline automatically. When connectivity returns, LiveStore syncs pending events and reconciles state.
388
+
389
+ ## When LiveStore fits
390
+
391
+ LiveStore works well for:
392
+
393
+ - **Productivity apps** (todo lists, note-taking, project management, ...)
394
+ - **Collaborative tools** with multi-player support
395
+ - **Apps with complex local state** that need SQL-level queries
396
+ - **Local-first** apps with offline support
397
+ - **Cross-platform apps** (web, mobile, desktop, server)
398
+ - ... or any combination of the above
399
+
400
+ LiveStore may not fit if:
401
+
402
+ - Your data must live on an existing server database (consider [ElectricSQL](https://electric-sql.com) or [Zero](https://zero.rocicorp.dev))
403
+ - You're building a traditional client-server app without offline needs
404
+ - You need unbounded data that won't fit in client memory
405
+
406
+ If you're unsure, go through our [evaluation exercise](/overview/when-livestore) to find out whether LiveStore is a good fit for your project or [compare it with similar tools](/overview/technology-comparison).
407
+
408
+ ## Next steps
409
+
410
+ - [Tutorial](/tutorial/0-welcome) — Step-by-step tutorial introducing the main concepts and workflows of LiveStore
411
+ - [Getting started with React](/getting-started/react-web) — Quickstart to set up a React app
412
+ - [How LiveStore works](/overview/how-livestore-works) — Deeper dive into the LiveStore architecture
413
+ - [Examples](/examples) — See LiveStore in action with TodoMVC, Linearlite, and more
@@ -1,5 +1,111 @@
1
1
  # Why LiveStore?
2
2
 
3
- ## Why should you use LiveStore?
3
+ ## The problem LiveStore solves
4
4
 
5
- TBD
5
+ Building modern apps with great UX means handling a lot of complexity:
6
+
7
+ - **State management**: Keeping UI in sync with data
8
+ - **Persistence**: Surviving refreshes and app restarts
9
+ - **Offline support**: Working without a network connection
10
+ - **Real-time sync**: Reflecting changes across devices instantly
11
+ - **Conflict resolution**: Handling concurrent edits gracefully
12
+
13
+ Traditionally, each of these concerns requires separate solutions—a state library, local storage, a sync service, optimistic update logic, retry queues. These pieces don't naturally fit together, leading to complex, fragile code and subtle bugs.
14
+
15
+ LiveStore provides a _unified architecture_ that handles all of these concerns through one coherent model: event sourcing.
16
+
17
+ As a positive side-effect, the simplicity of LiveStore's event-driven abstractions also leads to a superior DX compared to the traditional ways of dealing with state.
18
+
19
+ ## What makes LiveStore different
20
+
21
+ ### A real database, not just a cache
22
+
23
+ Most state management tools treat local data as a _cache_ of server state. This introduces complexity that's hard to manage due to the inherent intricacies of caching.
24
+
25
+ LiveStore flips this: your local SQLite database is the primary data source, and the server is just a sync target to which data is distributed automatically.
26
+
27
+ <SourceOfTruthDiagram class="my-8" />
28
+
29
+ This means:
30
+ - **No loading states for reads**—queries execute synchronously against local SQLite
31
+ - **Full SQL power**—joins, aggregations, complex filters, all instant
32
+ - **Offline by default**—your app works the same whether online or not
33
+
34
+ ### Events as the source of truth
35
+
36
+ Instead of syncing mutable state (which leads to "who wins?" conflicts), LiveStore syncs an append-only log of events. This is fundamentally easier to reason about:
37
+
38
+ - Events merge naturally—you're combining histories, not reconciling states
39
+ - Every change is auditable—you have a complete record of what happened
40
+ - State is always rebuildable—replay events to reconstruct any point in time
41
+
42
+ ### Sync that actually works
43
+
44
+ LiveStore includes a battle-tested sync engine that handles the hard problems:
45
+
46
+ - **Optimistic updates**: Changes appear instantly, sync happens in the background
47
+ - **Automatic offline queue**: Events committed offline sync when connectivity returns
48
+ - **Deterministic conflict resolution**: Same events always produce the same state
49
+ - **Pluggable backends**: Use Cloudflare, ElectricSQL, or build your own
50
+
51
+ ### Built for demanding apps
52
+
53
+ LiveStore was developed as the foundation for [Overtone](https://overtone.pro), a professional music application requiring 120fps performance with complex, real-time collaborative state. It's designed for apps where performance and reliability aren't negotiable.
54
+
55
+ ### Open-source: By developers, for developers
56
+
57
+ LiveStore is entirely open-source. There is no company behind it and it's only possible through its generous [sponsors](/sustainable-open-source/sponsoring).
58
+
59
+ ## LiveStore vs. the alternatives
60
+
61
+ ### vs. State management libraries (Redux, Zustand, MobX)
62
+
63
+ These solve state management but not persistence or sync. You'll need to add:
64
+ - Local storage or IndexedDB for persistence
65
+ - A sync layer for real-time updates
66
+ - Optimistic update logic
67
+ - Offline queue management
68
+
69
+ LiveStore handles all of this out of the box.
70
+
71
+ ### vs. Backend-as-a-Service (Firebase, Supabase)
72
+
73
+ These provide sync but treat the server as the source of truth. The consequences are:
74
+ - Reads require network round-trips (or complex caching)
75
+ - Offline support is limited or requires significant extra work
76
+ - You're locked into their data model and pricing
77
+
78
+ LiveStore is client-first, works fully offline, and syncs via any backend you choose.
79
+
80
+ ### vs. Other local-first solutions (ElectricSQL, Zero, PowerSync)
81
+
82
+ These are excellent if you have an existing Postgres database you want to sync to clients. LiveStore is better when:
83
+ - You're building a new app without legacy data
84
+ - You want event sourcing semantics (not just row-level sync)
85
+ - You need the flexibility to materialize state in different ways
86
+
87
+ See the [local-first landscape](https://localfirst.fm/landscape) for a comprehensive comparison.
88
+
89
+ ## A great developer experience (DX)
90
+
91
+ LiveStore is designed to make the right thing easy:
92
+
93
+ - **Type-safe schema**: Your events, tables, and queries are fully typed
94
+ - **Reactive by default**: UI updates automatically when data changes
95
+ - **Powerful devtools**: Inspect state, browse events, time-travel debug
96
+ - **Cross-platform**: Same mental model on web, mobile, desktop, and server
97
+
98
+ ## When to choose LiveStore
99
+
100
+ **Choose LiveStore if:**
101
+ - You're building a new app that should work offline
102
+ - You want a unified solution for state, persistence, and sync
103
+ - Your app has complex local state that benefits from SQL queries
104
+ - You value auditability and the ability to replay/debug state changes
105
+
106
+ **Consider alternatives if:**
107
+ - You need to sync with an existing server database
108
+ - Your data won't fit in client memory
109
+ - You're building a traditional request/response app without offline needs
110
+
111
+ Still unsure? Try the [evaluation exercise](/overview/when-livestore#evaluation-exercise) to model your app's events and see if it feels natural.