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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/README.md +0 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/QueryCache.js +1 -1
  4. package/dist/QueryCache.js.map +1 -1
  5. package/dist/SqliteDbWrapper.d.ts +5 -5
  6. package/dist/SqliteDbWrapper.d.ts.map +1 -1
  7. package/dist/SqliteDbWrapper.js +8 -8
  8. package/dist/SqliteDbWrapper.js.map +1 -1
  9. package/dist/SqliteDbWrapper.test.js +2 -2
  10. package/dist/SqliteDbWrapper.test.js.map +1 -1
  11. package/dist/effect/LiveStore.d.ts +14 -7
  12. package/dist/effect/LiveStore.d.ts.map +1 -1
  13. package/dist/effect/LiveStore.js +0 -15
  14. package/dist/effect/LiveStore.js.map +1 -1
  15. package/dist/effect/LiveStore.test.d.ts +2 -0
  16. package/dist/effect/LiveStore.test.d.ts.map +1 -0
  17. package/dist/effect/LiveStore.test.js +42 -0
  18. package/dist/effect/LiveStore.test.js.map +1 -0
  19. package/dist/live-queries/base-class.d.ts +3 -3
  20. package/dist/live-queries/base-class.d.ts.map +1 -1
  21. package/dist/live-queries/base-class.js +2 -2
  22. package/dist/live-queries/base-class.js.map +1 -1
  23. package/dist/live-queries/client-document-get-query.d.ts +1 -1
  24. package/dist/live-queries/client-document-get-query.d.ts.map +1 -1
  25. package/dist/live-queries/client-document-get-query.js +1 -1
  26. package/dist/live-queries/client-document-get-query.js.map +1 -1
  27. package/dist/live-queries/computed.d.ts.map +1 -1
  28. package/dist/live-queries/computed.js +2 -2
  29. package/dist/live-queries/computed.js.map +1 -1
  30. package/dist/live-queries/db-query.js +14 -14
  31. package/dist/live-queries/db-query.js.map +1 -1
  32. package/dist/live-queries/db-query.test.js +2 -2
  33. package/dist/live-queries/db-query.test.js.map +1 -1
  34. package/dist/live-queries/signal.test.js +2 -2
  35. package/dist/live-queries/signal.test.js.map +1 -1
  36. package/dist/mod.d.ts +1 -1
  37. package/dist/mod.d.ts.map +1 -1
  38. package/dist/mod.js.map +1 -1
  39. package/dist/reactive.d.ts +9 -9
  40. package/dist/reactive.d.ts.map +1 -1
  41. package/dist/reactive.js +9 -26
  42. package/dist/reactive.js.map +1 -1
  43. package/dist/reactive.test.js +2 -2
  44. package/dist/reactive.test.js.map +1 -1
  45. package/dist/store/StoreRegistry.d.ts +30 -5
  46. package/dist/store/StoreRegistry.d.ts.map +1 -1
  47. package/dist/store/StoreRegistry.js +54 -31
  48. package/dist/store/StoreRegistry.js.map +1 -1
  49. package/dist/store/StoreRegistry.test.js +251 -250
  50. package/dist/store/StoreRegistry.test.js.map +1 -1
  51. package/dist/store/create-store.d.ts +6 -2
  52. package/dist/store/create-store.d.ts.map +1 -1
  53. package/dist/store/create-store.js +13 -7
  54. package/dist/store/create-store.js.map +1 -1
  55. package/dist/store/devtools.d.ts +1 -1
  56. package/dist/store/devtools.d.ts.map +1 -1
  57. package/dist/store/devtools.js +3 -3
  58. package/dist/store/devtools.js.map +1 -1
  59. package/dist/store/store-eventstream.test.js +2 -2
  60. package/dist/store/store-eventstream.test.js.map +1 -1
  61. package/dist/store/store-types.d.ts +70 -5
  62. package/dist/store/store-types.d.ts.map +1 -1
  63. package/dist/store/store-types.js.map +1 -1
  64. package/dist/store/store-types.test.js +1 -1
  65. package/dist/store/store-types.test.js.map +1 -1
  66. package/dist/store/store.d.ts +81 -2
  67. package/dist/store/store.d.ts.map +1 -1
  68. package/dist/store/store.js +128 -45
  69. package/dist/store/store.js.map +1 -1
  70. package/dist/utils/dev.js.map +1 -1
  71. package/dist/utils/stack-info.js +2 -2
  72. package/dist/utils/stack-info.js.map +1 -1
  73. package/dist/utils/tests/fixture.d.ts +1 -1
  74. package/dist/utils/tests/fixture.d.ts.map +1 -1
  75. package/dist/utils/tests/fixture.js.map +1 -1
  76. package/dist/utils/tests/otel.d.ts.map +1 -1
  77. package/dist/utils/tests/otel.js +5 -5
  78. package/dist/utils/tests/otel.js.map +1 -1
  79. package/package.json +58 -17
  80. package/src/QueryCache.ts +1 -1
  81. package/src/SqliteDbWrapper.test.ts +4 -2
  82. package/src/SqliteDbWrapper.ts +12 -11
  83. package/src/ambient.d.ts +0 -7
  84. package/src/effect/LiveStore.test.ts +61 -0
  85. package/src/effect/LiveStore.ts +17 -26
  86. package/src/live-queries/__snapshots__/db-query.test.ts.snap +336 -231
  87. package/src/live-queries/base-class.ts +7 -6
  88. package/src/live-queries/client-document-get-query.ts +4 -2
  89. package/src/live-queries/computed.ts +3 -2
  90. package/src/live-queries/db-query.test.ts +3 -2
  91. package/src/live-queries/db-query.ts +15 -15
  92. package/src/live-queries/signal.test.ts +3 -2
  93. package/src/mod.ts +1 -0
  94. package/src/reactive.test.ts +3 -2
  95. package/src/reactive.ts +22 -23
  96. package/src/store/StoreRegistry.test.ts +317 -293
  97. package/src/store/StoreRegistry.ts +63 -38
  98. package/src/store/create-store.ts +26 -11
  99. package/src/store/devtools.ts +5 -6
  100. package/src/store/store-eventstream.test.ts +4 -2
  101. package/src/store/store-types.test.ts +3 -1
  102. package/src/store/store-types.ts +47 -8
  103. package/src/store/store.ts +172 -55
  104. package/src/utils/dev.ts +2 -2
  105. package/src/utils/stack-info.ts +2 -2
  106. package/src/utils/tests/fixture.ts +2 -1
  107. package/src/utils/tests/otel.ts +8 -7
  108. package/docs/api/index.md +0 -3
  109. package/docs/building-with-livestore/complex-ui-state/index.md +0 -3
  110. package/docs/building-with-livestore/crud/index.md +0 -3
  111. package/docs/building-with-livestore/data-modeling/index.md +0 -30
  112. package/docs/building-with-livestore/debugging/index.md +0 -17
  113. package/docs/building-with-livestore/devtools/index.md +0 -79
  114. package/docs/building-with-livestore/events/index.md +0 -355
  115. package/docs/building-with-livestore/examples/ai-agent/index.md +0 -5
  116. package/docs/building-with-livestore/examples/todo-workspaces/index.md +0 -885
  117. package/docs/building-with-livestore/examples/turnbased-game/index.md +0 -7
  118. package/docs/building-with-livestore/opentelemetry/index.md +0 -227
  119. package/docs/building-with-livestore/production-checklist/index.md +0 -5
  120. package/docs/building-with-livestore/reactivity-system/index.md +0 -202
  121. package/docs/building-with-livestore/rules-for-ai-agents/index.md +0 -9
  122. package/docs/building-with-livestore/state/materializers/index.md +0 -300
  123. package/docs/building-with-livestore/state/sql-queries/index.md +0 -94
  124. package/docs/building-with-livestore/state/sqlite/index.md +0 -45
  125. package/docs/building-with-livestore/state/sqlite-schema/index.md +0 -306
  126. package/docs/building-with-livestore/state/sqlite-schema-effect/index.md +0 -300
  127. package/docs/building-with-livestore/store/index.md +0 -625
  128. package/docs/building-with-livestore/syncing/index.md +0 -136
  129. package/docs/building-with-livestore/tools/cli/index.md +0 -177
  130. package/docs/building-with-livestore/tools/mcp/index.md +0 -187
  131. package/docs/examples/cloudflare-adapter/index.md +0 -44
  132. package/docs/examples/expo-adapter/index.md +0 -44
  133. package/docs/examples/index.md +0 -55
  134. package/docs/examples/node-adapter/index.md +0 -44
  135. package/docs/examples/web-adapter/index.md +0 -52
  136. package/docs/framework-integrations/custom-elements/index.md +0 -142
  137. package/docs/framework-integrations/react-integration/index.md +0 -937
  138. package/docs/framework-integrations/solid-integration/index.md +0 -293
  139. package/docs/framework-integrations/svelte-integration/index.md +0 -42
  140. package/docs/framework-integrations/vue-integration/index.md +0 -294
  141. package/docs/getting-started/expo/index.md +0 -882
  142. package/docs/getting-started/node/index.md +0 -115
  143. package/docs/getting-started/react-web/index.md +0 -626
  144. package/docs/getting-started/solid/index.md +0 -3
  145. package/docs/getting-started/vue/index.md +0 -471
  146. package/docs/index.md +0 -208
  147. package/docs/llms.txt +0 -146
  148. package/docs/misc/CODE_OF_CONDUCT/index.md +0 -133
  149. package/docs/misc/FAQ/index.md +0 -37
  150. package/docs/misc/community/index.md +0 -88
  151. package/docs/misc/credits/index.md +0 -14
  152. package/docs/misc/design-partners/index.md +0 -13
  153. package/docs/misc/package-management/index.md +0 -21
  154. package/docs/misc/performance/index.md +0 -25
  155. package/docs/misc/resources/index.md +0 -46
  156. package/docs/misc/state-of-the-project/index.md +0 -37
  157. package/docs/misc/troubleshooting/index.md +0 -82
  158. package/docs/overview/concepts/index.md +0 -78
  159. package/docs/overview/how-livestore-works/index.md +0 -56
  160. package/docs/overview/introduction/index.md +0 -413
  161. package/docs/overview/technology-comparison/index.md +0 -40
  162. package/docs/overview/when-livestore/index.md +0 -81
  163. package/docs/overview/why-livestore/index.md +0 -111
  164. package/docs/patterns/ai/index.md +0 -15
  165. package/docs/patterns/anonymous-user-transition/index.md +0 -10
  166. package/docs/patterns/app-evolution/index.md +0 -72
  167. package/docs/patterns/auth/index.md +0 -377
  168. package/docs/patterns/effect/index.md +0 -1505
  169. package/docs/patterns/encryption/index.md +0 -6
  170. package/docs/patterns/external-data/index.md +0 -5
  171. package/docs/patterns/file-management/index.md +0 -11
  172. package/docs/patterns/file-structure/index.md +0 -14
  173. package/docs/patterns/list-ordering/index.md +0 -369
  174. package/docs/patterns/offline/index.md +0 -32
  175. package/docs/patterns/orm/index.md +0 -18
  176. package/docs/patterns/presence/index.md +0 -11
  177. package/docs/patterns/rich-text-editing/index.md +0 -11
  178. package/docs/patterns/server-side-clients/index.md +0 -97
  179. package/docs/patterns/side-effects/index.md +0 -11
  180. package/docs/patterns/state-machines/index.md +0 -11
  181. package/docs/patterns/storybook/index.md +0 -209
  182. package/docs/patterns/undo-redo/index.md +0 -9
  183. package/docs/patterns/version-control/index.md +0 -8
  184. package/docs/platform-adapters/cloudflare-durable-object-adapter/index.md +0 -453
  185. package/docs/platform-adapters/electron-adapter/index.md +0 -15
  186. package/docs/platform-adapters/expo-adapter/index.md +0 -262
  187. package/docs/platform-adapters/node-adapter/index.md +0 -160
  188. package/docs/platform-adapters/tauri-adapter/index.md +0 -15
  189. package/docs/platform-adapters/web-adapter/index.md +0 -287
  190. package/docs/sustainable-open-source/contributing/docs/index.md +0 -94
  191. package/docs/sustainable-open-source/contributing/info/index.md +0 -63
  192. package/docs/sustainable-open-source/contributing/monorepo/index.md +0 -195
  193. package/docs/sustainable-open-source/sponsoring/index.md +0 -104
  194. package/docs/sync-providers/cloudflare/index.md +0 -773
  195. package/docs/sync-providers/custom/index.md +0 -65
  196. package/docs/sync-providers/electricsql/index.md +0 -159
  197. package/docs/sync-providers/s2/index.md +0 -230
  198. package/docs/tutorial/0-welcome/index.md +0 -48
  199. package/docs/tutorial/1-setup-starter-project/index.md +0 -105
  200. package/docs/tutorial/2-deploy-to-cloudflare/index.md +0 -195
  201. package/docs/tutorial/3-read-and-write-todos-via-livestore/index.md +0 -530
  202. package/docs/tutorial/4-sync-data-via-cloudflare/index.md +0 -210
  203. package/docs/tutorial/5-expand-business-logic/index.md +0 -174
  204. package/docs/tutorial/6-persist-ui-state/index.md +0 -453
  205. package/docs/tutorial/7-next-steps/index.md +0 -22
  206. package/docs/understanding-livestore/design-decisions/index.md +0 -33
  207. package/docs/understanding-livestore/event-sourcing/index.md +0 -40
@@ -1,78 +0,0 @@
1
- # Concepts
2
-
3
- ![](https://share.cleanshot.com/sv62BGww+)
4
-
5
- ## Overview
6
-
7
- - Adapter (platform adapter)
8
- - An adapter can instantiate a client session for a given platform (e.g. web, Expo)
9
- - Client
10
- - A logical group of client sessions
11
- - Identified by a `clientId` - a randomly generated 6-char nanoid
12
- - Each client has at least one client session
13
- - Sessions within a client share local data
14
- - Client session
15
- - An instance within a client
16
- - Identified by a `sessionId`
17
- - In web: sessionId can persist across tab reloads
18
- - Multiple sessions can exist within a single client (e.g., multiple browser tabs)
19
- - Store
20
- - Reactivity graph
21
- - [Devtools](/building-with-livestore/devtools)
22
- - [Events](/building-with-livestore/events)
23
- - Event definition
24
- - Eventlog
25
- - Synced vs client-only events
26
- - Framework integration
27
- - A framework integration is a package that provides a way to integrate LiveStore with a framework (e.g. React, Solid, Svelte, etc.)
28
- - [Reactivity system](/building-with-livestore/reactivity-system)
29
- - Db queries `queryDb()`
30
- - Computed queries `computed()`
31
- - Signals `signal()`
32
- - Schema
33
- - LiveStore uses schema definitions for the following cases:
34
- - [Event definitions](/building-with-livestore/events)
35
- - [SQLite state schema](/building-with-livestore/state/sqlite-schema)
36
- - [Query result schemas](/building-with-livestore/state/sql-queries)
37
- - LiveStore uses the [Effect Schema module](/patterns/effect) to define fine-granular schemas
38
- - State
39
- - Derived from the eventlog via materializers
40
- - Materializer
41
- - Event handler function that maps an event to a state change
42
- - SQLite state / database
43
- - In-memory SQLite database within the client session thread (usually main thread)
44
- - Used by the reactivity graph
45
- - Persisted SQLite database (usually running on the leader thread)
46
- - Fully derived from the eventlog
47
- - [Store](/building-with-livestore/store)
48
- - A store exposes most of LiveStore's functionality to the application layer and is the main entry point for using LiveStore.
49
- - To create a store you need to provide a schema and a platform adapter which creates a client session.
50
- - A store is often created, managed and accessed through a framework integration (like React).
51
- - A store is identified by a `storeId` which is also used for syncing events between clients.
52
- - Sync provider
53
- - A sync provider is a package that provides a sync backend and a sync client.
54
- - Sync backend
55
- - A central server that is responsible for syncing the eventlog between clients
56
-
57
- ### Implementation details
58
-
59
- - Leader thread
60
- - Responsible for syncing and persisting of data
61
- - Sync processor
62
- - LeaderSyncProcessor
63
- - ClientSessionSyncProcessor
64
-
65
- ## Pluggable architecture
66
-
67
- LiveStore is designed to be pluggable in various ways:
68
-
69
- - Platform adapters
70
- - Sync providers
71
- - Framework integrations
72
-
73
- ## Important notes on identity
74
-
75
- - LiveStore does not have built-in concepts of "users" or "devices"
76
- - User identity must be modeled within your application domain through events and application logic
77
- - The `clientId` identifies a client instance, not a user
78
- - Multiple clients can represent the same user (e.g., different browsers or devices)
@@ -1,56 +0,0 @@
1
- # How LiveStore works
2
-
3
- ### TLDR
4
-
5
- LiveStore uses event sourcing to sync events across clients and materialize state into a local, reactive SQLite database.
6
-
7
- <HowItWorksDiagram class="my-8" />
8
-
9
- ## How LiveStore works client-side
10
-
11
- On the client, LiveStore provides a reactive SQLite database for application state, which is kept consistent through an underlying event sourcing mechanism.
12
-
13
- #### Local reactive SQLite
14
-
15
- Application state is materialized into a local SQLite database, offering high-performance, offline-capable data access. This SQLite database is reactive: UI components subscribe to data changes and update automatically when the state changes. LiveStore uses in-memory SQLite for sub-millisecond queries and persistent SQLite for durable storage across application sessions.
16
-
17
- #### Event sourcing
18
-
19
- Underpinning the reactive state, LiveStore implements the event sourcing pattern. All data modifications are captured as an immutable, ordered sequence of events. This eventlog serves as the canonical history, enabling reliable state reconstruction and providing inherent auditability, which aids in debugging. The reactive SQLite state is a projection of this eventlog.
20
-
21
- #### Client-side event flow
22
-
23
- 1. **Event Committing:** User interactions within the application generate events detailing the specific action (e.g., `TodoCreated`, `TaskCompleted`).
24
- 2. **Local Persistence & Materialization:** The committed event is atomically persisted to the local eventlog and immediately materialized as state into the SQLite database.
25
- 3. **UI Reactivity:** Changes to the SQLite database trigger the reactivity system, causing subscribed UI components (e.g. React components) to automatically update and reflect the new state.
26
-
27
- ## How LiveStore syncing works
28
-
29
- LiveStore extends its local event-sourcing model globally by synchronizing events across all clients, typically through a central sync backend. This ensures that the eventlog, serving as the single source of truth, is consistently replicated, leading to an eventually consistent state for all participants.
30
-
31
- #### Push/pull event synchronization
32
-
33
- Inspired by Git, LiveStore employs a push/pull model for event synchronization. Clients must first pull the latest events from the sync backend to ensure their local eventlog is up-to-date before they can push their own newly committed local events. This model helps maintain a global total order of events. Local pending events that haven't been pushed are rebased on top of the latest upstream events before being pushed.
34
-
35
- #### Sync provider integration
36
-
37
- LiveStore supports various sync backend implementations, and it's straightforward for developers to create their own. The sync backend is responsible for storing events, enforcing the total event order, and notifying clients of new events.
38
-
39
- #### Conflict resolution
40
-
41
- When concurrent operations from different clients lead to conflicting events, LiveStore defaults to a "last-write-wins" strategy. However, it also provides the capability for developers to implement custom merge conflict resolution logic tailored to their application's specific needs.
42
-
43
- #### Overall syncing data flow
44
-
45
- After a local event is committed and materialized (as per the client-side flow), LiveStore attempts to push this event to the sync backend. Simultaneously, LiveStore is pulling events from the sync backend in the background.
46
-
47
- Two main scenarios can occur during a push attempt:
48
-
49
- 1. **Client In Sync:** If the client's local eventlog is already up-to-date with the sync backend (i.e., no new remote events have arrived since the last pull/push), the local event is pushed directly.
50
- 2. **Concurrent Incoming Events:** If new remote events have been pulled in the background, or are discovered during the push attempt, the client first processes these incoming remote events. Any local, unpushed events are then rebased on top of these new remote events before being pushed to the sync backend.
51
-
52
- In both scenarios, once remote events are received (either through background pulling or during a push cycle), they are persisted to the local eventlog, materialized into the local SQLite database, and the UI reacts to the new state, ensuring eventual consistency.
53
-
54
- ## Platform adapters
55
-
56
- LiveStore includes platform adapters to integrate with various environments, such as web browsers, mobile applications (iOS/Android), desktop applications, and Node.js.
@@ -1,413 +0,0 @@
1
- # Introduction
2
-
3
- ## What is LiveStore?
4
-
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,40 +0,0 @@
1
- # Technology comparison
2
-
3
- ## TLDR of what sets LiveStore apart
4
-
5
- - Uses combination of reactive, in-memory + synced, persisted SQLite for instant, synchronous queries
6
- - Based on event-sourcing methodologies
7
- - Client-centric (with great devtools)
8
-
9
- ## Other local-first/syncing technologies
10
-
11
- To compare LiveStore with other local-first/syncing technologies, please see the [Local-First Landscape](https://www.localfirst.fm/landscape) resource.
12
-
13
- ## LiveStore vs Redux
14
-
15
- LiveStore shares a lot of similarities with Redux in that sense that both are based on event-sourcing methodologies. Let's compare some of the core concepts:
16
-
17
- - Redux actions are similar to LiveStore events: Both are used to describe "things that have happened"
18
- - Redux views are similar to LiveStore's state (e.g. SQLite tables): Both are derived from the history of events/actions.
19
- - A major difference here is that LiveStore's state materialized as a SQLite database allows for a lot more flexibility via dynamic queries and aggregations vs Redux's static views.
20
- - Redux reducers are similar to LiveStore's materializers: Both are used to transform events/actions into a final state.
21
- - Both Redux and LiveStore are client-centric.
22
- - Both Redux and LiveStore provide powerful [devtools](/building-with-livestore/devtools).
23
-
24
- While LiveStore can be used for the same use cases as Redux, LiveStore goes far beyond Redux in the following ways:
25
-
26
- - LiveStore leverages SQLite for a more powerful state model allowing for flexible queries and aggregations with much simpler materialization logic.
27
- - LiveStore supports client-persistence out of the box.
28
- - LiveStore comes with a built-in [sync engine](/building-with-livestore/syncing) syncing events between clients.
29
-
30
- As a downside compared to Redux, LiveStore has a slightly larger bundle size.
31
-
32
- ## Other state management libraries
33
-
34
- - Zustand
35
- - Redux Toolkit (RTK)
36
- - MobX
37
- - Jotai
38
- - Xstate
39
- - Recoil
40
- - TanStack Query
@@ -1,81 +0,0 @@
1
- # When to use LiveStore (and when not)
2
-
3
- Choosing a data layer for a local-first app is a big decision and should be considered carefully. On a high level, LiveStore can be a good fit if ...
4
- - you are looking for a principled data layer that works across platforms
5
- - you want to use SQLite for your queries
6
- - you like [event sourcing](/understanding-livestore/event-sourcing) to model data changes
7
- - you are working on a new app as LiveStore doesn't yet provide a way to [re-use an existing database](/misc/faq#existing-database)
8
- - the current [state of the project](/misc/state-of-the-project) aligns with your own timeline and requirements
9
-
10
- ## Evaluation exercise
11
-
12
- A great way to evaluate whether LiveStore is a good fit for your application, is by trying to model your application events (and optionally state) schema. This exercise can be done in a few minutes and can give you a good indication of whether LiveStore is a good fit for your application.
13
-
14
- ### Example: Calendar/scheduling app
15
-
16
- Let's say you are building a calendar/scheduling app, your events might include:
17
-
18
- - `AppointmentScheduled`
19
- - `AppointmentRescheduled`
20
- - `AppointmentCancelled`
21
- - `ParticipantInvitedToAppointment`
22
- - `ParticipantRespondedToInvite`
23
-
24
- From this you might want to derive the following state (modeled as SQLite tables):
25
-
26
- - `Appointment`
27
- - `id`
28
- - `title`
29
- - `description`
30
- - `participants`
31
- - `Participant`
32
- - `id`
33
- - `name`
34
- - `email`
35
-
36
- ## Great use cases for LiveStore
37
-
38
- - High-performance desktop/web/mobile apps
39
- - e.g. productivity apps like
40
- - AI agents
41
- - Apps that need ...
42
- - solid offline support
43
- - audit logs
44
-
45
- ## Benefits of LiveStore
46
-
47
- - Unified data layer combining local reactive state with globally synced data
48
- - Easy to ...
49
- - reason about
50
- - debug
51
- - test
52
- - evolve
53
- - operate
54
-
55
- ## Reasons when not to use LiveStore
56
-
57
- - You have an existing database which is the source of truth of your data. (Better use [Zero](https://zero.rocicorp.dev) or [ElectricSQL](https://www.electricsql.com) for this.)
58
- - Your app data is highly connected across users (like a social network / marketplace / etc.) or modeling your data via read-write model separation/event sourcing doesn't seem feasible.
59
- - You want to build a more traditional client-server application with your primary data source being a remote server.
60
- - You want a full-stack batteries-included solution (e.g. auth, storage, etc.). (Technologies like [Jazz](https://jazz.tools) or [Instant](https://instantdb.com) might be a better fit.)
61
- - You don't like to model your data via read-write model separation/event sourcing or the trade-offs it involves.
62
- - You're a new developer and are just getting started. LiveStore is a relatively advanced technology with many design trade-offs that might make most sense after you have already experienced some of the problems LiveStore is trying to solve.
63
- - You want to keep your app bundle size as small as possible. LiveStore adds a few hundred kB to your app bundle size (mostly due to bundling SQLite).
64
-
65
- ## Considerations
66
-
67
- ### Database constraints
68
-
69
- - All the client app data should fit into a in-memory SQLite database
70
- - Depending on the target device having databases up to 1GB in size should be okay.
71
- - If you you have more data, you can consider segmenting your database into multiple SQLite database (e.g. segmented per project, workspace, document, ...).
72
- - You can either use the `storeId` option for the segmentation or there could also be a way to use the [SQLite attach feature](https://www.sqlite.org/lang_attach.html) to dynamically attach/detach databases.
73
-
74
- ### Syncing
75
-
76
- LiveStore's syncing system is designed for small/medium-level concurrency scenarios (e.g. 10s / low 100s of users collaborating on the same thing for a given eventlog).
77
- - Collaboration on multiple different eventlogs concurrently is supported and should be used to "scale horizontally".
78
-
79
- ### Other considerations
80
-
81
- - How data flows / what's the source of truth?