Skip to content

Design decisions

This page summarizes LiveStore’s major design decisions. The full decision records — including the alternatives that were considered and rejected — live in the repository’s intent layer.

  • Fast, synchronous, transactional, and reactive state management
  • Global state is eventually consistent
  • Persistent storage
  • Syncing
  • Convenient schema migrations
  • Great devtools
  • Event sourcing with read/write-model separation is the foundational model: an append-only log of events is the source of truth, and all queryable state derives from it — so evolving the read model never requires migrating source data. (decision record, event sourcing)
  • SQLite is the primary read model — a real database rather than a cache: synchronous reads with full SQL power. Further read-model realizations (including plain JavaScript structures) are a planned direction behind the same contract. (decision record)
  • An in-memory SQLite database runs in each client session (usually the main thread) so queries are synchronous, while a persisted database lives with the leader in a separate thread. This costs some memory per tab and assumes app data fits in memory. (decision record)
  • One leader per client owns persistence and sync; browser tabs and other sessions hold in-memory mirrors and talk to the leader through a proxy. Multi-tab consistency becomes a leader-election problem instead of a write-conflict problem. (decision record)
  • Reactivity is a signals-based eager graph built on the ideas of Adapton for incremental computation — refreshed synchronously in topological order with equality cutoff (lazy recomputation is deliberately not implemented). (decision record)
  • Conflicts resolve by deterministic total-order rebase: the sync backend arbitrates one event order, clients rebase and replay — the same events always produce the same state. Richer conflict semantics are an active design direction (see the command replay RFC). (decision record)
  • Devtools are protocol-first: all inspection and control flows through a versioned message protocol, so the separately shipped devtools UI tolerates version skew and holds no privileged access to engine internals. (decision record)
  • Sync-provider agnostic: the sync backend is a contract with multiple realizations so you can use the right provider for your use case (see sync provider contract).
  • Deliberately minimal scope: LiveStore stays focused on core data management and syncing, leaving authentication, file uploads, and business logic to application code — a composable, Unix-like building block.
  • Build most of the library in TypeScript; parts may move to Rust in the future.
  • Embrace Effect as a library of powerful primitives, particularly for the IO/concurrency-heavy parts. (decision record)
  • Frustration with database schema migrations → event sourcing to separate read and write models (no schema migrations for the read model).
  • Applying the “make the right thing easy” principle to app data management.