clientOnly: Events that are only processed locally on the client (but still synced across client sessions e.g. across browser tabs/windows)
An event definition consists of a unique name of the event and a schema for the event arguments. It’s recommended to version event definitions to make it easier to evolve them over time.
Events will be synced across clients and materialized into state (i.e. SQLite tables) via materializers.
Synced events are sent to the sync backend and distributed to all connected
clients. Use this for collaborative data that should be shared across users
and devices.
Event names should be versioned (e.g., v1.TodoCreated) to support
schema evolution over time.
Synced events are sent to the sync backend and distributed to all connected
clients. Use this for collaborative data that should be shared across users
and devices.
Event names should be versioned (e.g., v1.TodoCreated) to support
schema evolution over time.
Central interface to a LiveStore database providing reactive queries, event commits, and sync.
A Store instance wraps a local SQLite database that is kept in sync with other clients via
an event log. Instead of mutating state directly, you commit events that get materialized
into database rows. Queries automatically re-run when their underlying tables change.
Creating a Store
Use createStore (Effect-based) or createStorePromise to obtain a Store instance.
In React applications, use StoreRegistry with <StoreRegistryProvider> and the useStore() hook
which manages the Store lifecycle.
Querying Data
Use
Store.query
for one-shot reads or
Store.subscribe
for reactive subscriptions.
Both accept query builders (e.g. tables.todo.where({ complete: true })) or custom LiveQueryDefs.
Committing Events
Use
Store.commit
to persist events. Events are immediately materialized locally and
asynchronously synced to other clients. Multiple events can be committed atomically.
Lifecycle
The Store must be shut down when no longer needed via
Store.shutdown
or
Store.shutdownPromise
. Framework integrations (React, Effect) handle this automatically.
Central interface to a LiveStore database providing reactive queries, event commits, and sync.
A Store instance wraps a local SQLite database that is kept in sync with other clients via
an event log. Instead of mutating state directly, you commit events that get materialized
into database rows. Queries automatically re-run when their underlying tables change.
Creating a Store
Use createStore (Effect-based) or createStorePromise to obtain a Store instance.
In React applications, use StoreRegistry with <StoreRegistryProvider> and the useStore() hook
which manages the Store lifecycle.
Querying Data
Use
Store.query
for one-shot reads or
Store.subscribe
for reactive subscriptions.
Both accept query builders (e.g. tables.todo.where({ complete: true })) or custom LiveQueryDefs.
Committing Events
Use
Store.commit
to persist events. Events are immediately materialized locally and
asynchronously synced to other clients. Multiple events can be committed atomically.
Lifecycle
The Store must be shut down when no longer needed via
Store.shutdown
or
Store.shutdownPromise
. Framework integrations (React, Effect) handle this automatically.
Synced events are sent to the sync backend and distributed to all connected
clients. Use this for collaborative data that should be shared across users
and devices.
Event names should be versioned (e.g., v1.TodoCreated) to support
schema evolution over time.
Synced events are sent to the sync backend and distributed to all connected
clients. Use this for collaborative data that should be shared across users
and devices.
Event names should be versioned (e.g., v1.TodoCreated) to support
schema evolution over time.
Central interface to a LiveStore database providing reactive queries, event commits, and sync.
A Store instance wraps a local SQLite database that is kept in sync with other clients via
an event log. Instead of mutating state directly, you commit events that get materialized
into database rows. Queries automatically re-run when their underlying tables change.
Creating a Store
Use createStore (Effect-based) or createStorePromise to obtain a Store instance.
In React applications, use StoreRegistry with <StoreRegistryProvider> and the useStore() hook
which manages the Store lifecycle.
Querying Data
Use
Store.query
for one-shot reads or
Store.subscribe
for reactive subscriptions.
Both accept query builders (e.g. tables.todo.where({ complete: true })) or custom LiveQueryDefs.
Committing Events
Use
Store.commit
to persist events. Events are immediately materialized locally and
asynchronously synced to other clients. Multiple events can be committed atomically.
Lifecycle
The Store must be shut down when no longer needed via
Store.shutdown
or
Store.shutdownPromise
. Framework integrations (React, Effect) handle this automatically.
Central interface to a LiveStore database providing reactive queries, event commits, and sync.
A Store instance wraps a local SQLite database that is kept in sync with other clients via
an event log. Instead of mutating state directly, you commit events that get materialized
into database rows. Queries automatically re-run when their underlying tables change.
Creating a Store
Use createStore (Effect-based) or createStorePromise to obtain a Store instance.
In React applications, use StoreRegistry with <StoreRegistryProvider> and the useStore() hook
which manages the Store lifecycle.
Querying Data
Use
Store.query
for one-shot reads or
Store.subscribe
for reactive subscriptions.
Both accept query builders (e.g. tables.todo.where({ complete: true })) or custom LiveQueryDefs.
Committing Events
Use
Store.commit
to persist events. Events are immediately materialized locally and
asynchronously synced to other clients. Multiple events can be committed atomically.
Lifecycle
The Store must be shut down when no longer needed via
Store.shutdown
or
Store.shutdownPromise
. Framework integrations (React, Effect) handle this automatically.
Returns an async iterable of events from the eventlog.
Currently only events confirmed by the sync backend is supported.
Defaults to tracking upstreamHead as it advances. If an until event is
supplied the stream finalizes upon reaching it.
To start streaming from a specific point in the eventlog
you can provide a since event.
Allows filtering by:
filter: event types
clientIds: client identifiers
sessionIds: session identifiers
The batchSize option controls the maximum amount of events that are fetched
from the eventlog in each query. Defaults to 100 and has a max allowed
value of 1000.
It’s strongly recommended to use past-tense event names (e.g. todoCreated/createdTodo instead of todoCreate/createTodo) to indicate something already occurred.
When generating IDs for events (e.g. for the todo in the example above), it’s recommended to use a globally unique ID generator (e.g. UUID, nanoid, etc.) to avoid conflicts. For convenience, @livestore/livestore re-exports the nanoid function.
TODO: write down more best practices
TODO: mention AI linting (either manually or via a CI step)
core idea: feed list of best practices to AI and check if events adhere to them + get suggestions if not
It’s recommended to avoid DELETE events and instead use soft-deletes (e.g. add a deleted date/boolean column with a default value of null). This helps avoid some common concurrency issues.
This example shows how a client session rebases its pending events when new authoritative events arrive from upstream. Client A owns the local work that gets rebased, while client B introduces the authoritative change. Colors follow the client IDs so lineage remains visible, and origin notation tracks the rebased event.
Client session has local pending work while upstream advances
Client session holds pending event A:e3'{todoRenamed} built on top of shared history e1 → e2
Sync backend publishes authoritative event B:e3{todoRenamed} that replaces the client’s local change
Client Session
e1
Global sequence:
1
Confirmed:
Yes
e2
Global sequence:
2
Confirmed:
Yes
e3
Context:
todoRenamed
Client label:
A
Global sequence:
3
Confirmed:
Yes
Client Leader
e1
Global sequence:
1
Confirmed:
Yes
e2
Global sequence:
2
Confirmed:
Yes
e3'
Context:
todoRenamed
Client label:
A
Global sequence:
3
Confirmed:
No
Sync Backend
e1
Global sequence:
1
Confirmed:
Yes
e2
Global sequence:
2
Confirmed:
Yes
e3
Context:
todoRenamed
Client label:
B
Global sequence:
3
Confirmed:
Yes
Client leader pulls authoritative events from the sync backend
Client compares its pending chain with upstream events and spots the divergence at e2
Client rolls back events and state to the point of divergence
Client Session
e1
Global sequence:
1
Confirmed:
Yes
e2
Global sequence:
2
Confirmed:
Yes
Client Leader
e1
Global sequence:
1
Confirmed:
Yes
e2
Global sequence:
2
Confirmed:
Yes
Sync Backend
e1
Global sequence:
1
Confirmed:
Yes
e2
Global sequence:
2
Confirmed:
Yes
e3
Context:
todoRenamed
Client label:
B
Global sequence:
3
Confirmed:
Yes
Client applies authoritative upstream events
Client session and leader apply the authoritative upstream events and advances their heads to e3
Client Session
e1
Global sequence:
1
Confirmed:
Yes
e2
Global sequence:
2
Confirmed:
Yes
e3
Context:
todoRenamed
Client label:
B
Global sequence:
3
Confirmed:
Yes
Client Leader
e1
Global sequence:
1
Confirmed:
Yes
e2
Global sequence:
2
Confirmed:
Yes
e3
Context:
todoRenamed
Client label:
B
Global sequence:
3
Confirmed:
Yes
Sync Backend
e1
Global sequence:
1
Confirmed:
Yes
e2
Global sequence:
2
Confirmed:
Yes
e3
Context:
todoRenamed
Client label:
B
Global sequence:
3
Confirmed:
Yes
Client replays its local pending events on top of the new head
Stored original events keep their payload but their sequence number gets updated to follow upstream head
Each newly numbered event is re-appplied and materialized to state in both client session and client leader
Client Session
e1
Global sequence:
1
Confirmed:
Yes
e2
Global sequence:
2
Confirmed:
Yes
e3
Context:
todoRenamed
Client label:
B
Global sequence:
3
Confirmed:
Yes
e4
Context:
todoRenamed
Client label:
A
Global sequence:
4
Confirmed:
Yes
Origin chain:
e3
Client Leader
e1
Global sequence:
1
Confirmed:
Yes
e2
Global sequence:
2
Confirmed:
Yes
e3
Context:
todoRenamed
Client label:
B
Global sequence:
3
Confirmed:
Yes
e4'
Context:
todoRenamed
Client label:
A
Global sequence:
4
Confirmed:
No
Origin chain:
e3
Sync Backend
e1
Global sequence:
1
Confirmed:
Yes
e2
Global sequence:
2
Confirmed:
Yes
e3
Context:
todoRenamed
Client label:
B
Global sequence:
3
Confirmed:
Yes
Client pushes its local pending events to sync backend
Upon receipt local pending events are marked as confirmed and the client leader advances its head to e4
Older clients might receive events that were introduced in newer app versions. Configure the behaviour centrally via unknownEventHandling when constructing the schema:
Pick 'warn' (default) to log every occurrence, 'ignore' to silently drop new events until the client updates, 'fail' to halt immediately, or 'callback' to delegate to custom logging/telemetry while continuing to process the log.
The args field is encoded according to the event’s schema (e.g., Date objects become ISO strings, binary data becomes base64). LiveStore handles encoding/decoding automatically.
Composite event sequence number consisting of global + client + rebaseGeneration.
Used for client-side event tracking with support for unconfirmed local events.
For event notation documentation, see: contributor-docs/events-notation.md
Effect Schema for the composite event sequence number (global + client + rebaseGeneration).
Also includes a make helper for creating validated Composite values.
Composite event sequence number consisting of global + client + rebaseGeneration.
Used for client-side event tracking with support for unconfirmed local events.
For event notation documentation, see: contributor-docs/events-notation.md
Effect Schema for the composite event sequence number (global + client + rebaseGeneration).
Also includes a make helper for creating validated Composite values.