Store
The Store is the most common way to interact with LiveStore from your application code. It provides a way to query data, commit events, and subscribe to data changes.
Creating a store
Section titled “Creating a store”For how to create a store in React, see the React integration docs. The following example shows how to create a store manually:
import { const makeAdapter: ({ sync, ...options }: NodeAdapterOptions & { sync?: SyncOptions;}) => Adapter
Creates a single-threaded LiveStore adapter for Node.js applications.
This adapter runs the leader thread (persistence and sync) in the same thread as
your application. Suitable for CLI tools, scripts, and applications where simplicity
is preferred over maximum performance.
For production servers or performance-critical applications, consider makeWorkerAdapter
which runs persistence/sync in a separate worker thread.
makeAdapter } from '@livestore/adapter-node'import { const createStorePromise: <TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>({ signal, otelOptions, ...options }: CreateStoreOptionsPromise<TSchema, TContext, TSyncPayloadSchema>) => Promise<Store<TSchema, TContext>>
Create a new LiveStore Store
createStorePromise } from '@livestore/livestore'
import { import schema
schema } from './schema.ts'
const const adapter: Adapter
adapter = function makeAdapter({ sync, ...options }: NodeAdapterOptions & { sync?: SyncOptions;}): Adapter
Creates a single-threaded LiveStore adapter for Node.js applications.
This adapter runs the leader thread (persistence and sync) in the same thread as
your application. Suitable for CLI tools, scripts, and applications where simplicity
is preferred over maximum performance.
For production servers or performance-critical applications, consider makeWorkerAdapter
which runs persistence/sync in a separate worker thread.
makeAdapter({ NodeAdapterOptions.storage: { readonly type: ["in-memory"]; readonly importSnapshot?: any;} | { readonly type: ["fs"]; readonly baseDirectory?: string | undefined;}
storage: { type: string
type: 'fs' }, // sync: { backend: makeWsSync({ url: '...' }) },})
export const const bootstrap: () => Promise<Store<any, {}>>
bootstrap = async () => { const const store: Store<any, {}>
store = await createStorePromise<any, {}, Codec<Json, Json, never, never>>({ signal, otelOptions, ...options }: CreateStoreOptionsPromise<any, {}, Codec<Json, Json, never, never>>): Promise<Store<any, {}>>
Create a new LiveStore Store
createStorePromise({ CreateStoreOptions<any, {}, Codec<Json, Json, never, never>>.schema: any
The LiveStore schema defining tables, events, and materializers.
schema, CreateStoreOptions<TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>.adapter: Adapter
Adapter used for data storage and synchronization.
adapter, CreateStoreOptions<TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>.storeId: string
Unique identifier for the Store instance, stable for its lifetime.
- Valid characters: Only alphanumeric characters, underscores (
_), and hyphens (-)
are allowed. Must match /^[a-zA-Z0-9_-]+$/.
- Globally unique: Use globally unique IDs (e.g., nanoid) to prevent collisions across stores.
- Use namespaces: Prefix to avoid collisions and for easier identification when debugging
(e.g.,
app-root, workspace-abc123, issue-456)
storeId: 'some-store-id', })
return const store: Store<any, {}>
store}import { const defineMaterializer: <TEventDef extends State.SQLite.EventDef.AnyWithoutFn>(_eventDef: TEventDef, materializer: State.SQLite.Materializer<TEventDef>) => State.SQLite.Materializer<TEventDef>
Type-safe wrapper for defining a single materializer.
Useful when defining materializers separately from the materializers() builder.
The first argument provides type inference for the second.
defineMaterializer, import Events
Events, const makeSchema: <TInputSchema extends InputSchema>(inputSchema: TInputSchema) => FromInputSchema.DeriveSchema<TInputSchema>
makeSchema, import Schema
Schema, import State
State } from '@livestore/livestore'
const const tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables = { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos: import State
State.import SQLite
SQLite.function table<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}, Partial<...>>(args: { ...;} & Partial<...>): State.SQLite.TableDef<...> (+2 overloads)
Creates a SQLite table definition from columns or an Effect Schema.
This function supports two main ways to define a table:
- Using explicit column definitions
- Using an Effect Schema (either the
name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columnsconst usersTable = State.SQLite.table({ name: 'users', columns: { id: State.SQLite.text({ primaryKey: true }), name: State.SQLite.text({ nullable: false }), email: State.SQLite.text({ nullable: false }), age: State.SQLite.integer({ nullable: true }), },})
// Using Effect Schema with annotationsimport { Schema } from '@livestore/utils/effect'
const UserSchema = Schema.Struct({ id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement), email: Schema.String.pipe(State.SQLite.withUnique), name: Schema.String, active: Schema.Boolean.pipe(State.SQLite.withDefault(true)), createdAt: Schema.optional(Schema.Date),})
// Option 1: With explicit nameconst usersTable = State.SQLite.table({ name: 'users', schema: UserSchema,})
// Option 2: With name from schema annotation (title or identifier)const AnnotatedUserSchema = UserSchema.annotate({ title: 'users' })const usersTable2 = State.SQLite.table({ schema: AnnotatedUserSchema,})
// Adding indexesconst PostSchema = Schema.Struct({ id: Schema.String.pipe(State.SQLite.withPrimaryKey), title: Schema.String, authorId: Schema.String, createdAt: Schema.Date,}).annotate({ identifier: 'posts' })
const postsTable = State.SQLite.table({ schema: PostSchema, indexes: [ { name: 'idx_posts_author', columns: ['authorId'] }, { name: 'idx_posts_created', columns: ['createdAt'], isUnique: false }, ],})
table({ name: "todos"
name: 'todos', columns: { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}
columns: { id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;}
id: import State
State.import SQLite
SQLite.const text: <string, string, false, typeof NoDefault, true, false>(args: { schema?: Schema.Codec<string, string, never, never>; default?: typeof NoDefault; nullable?: false; primaryKey?: true; autoIncrement?: false;}) => { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;} (+1 overload)
text({ primaryKey?: true
primaryKey: true }), text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;}
text: import State
State.import SQLite
SQLite.const text: () => { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;} (+1 overload)
text(), completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false;}
completed: import State
State.import SQLite
SQLite.const boolean: <boolean, false, false, false, false>(args: { default?: false; nullable?: false; primaryKey?: false; autoIncrement?: false;}) => { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false;} (+1 overload)
boolean({ default?: false
default: false }), }, }),} as type const = { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
const
const const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
events = { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>
todoCreated: import Events
Events.synced<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>(args: { name: "v1.TodoCreated"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">, never, never>;} & Omit<...>): State.SQLite.EventDef<...>export synced
Creates a synced event definition.
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({ name: "v1.TodoCreated"
name: 'v1.TodoCreated', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String; readonly text: Schema.String;}>(fields: { readonly id: Schema.String; readonly text: Schema.String;}): Schema.Struct<{ readonly id: Schema.String; readonly text: Schema.String;}>
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Example (Defining a basic struct)
import { Schema } from "effect"
const Person = Schema.Struct({ name: Schema.String, age: Schema.Number, email: Schema.optionalKey(Schema.String)})
// { readonly name: string; readonly age: number; readonly email?: string }type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })console.log(alice)// { name: 'Alice', age: 30 }
Struct({ id: Schema.String
id: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String, text: Schema.String
text: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String }), }),} as type const = { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
const
const const materializers: { "v1.TodoCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>>;}
materializers = import State
State.import SQLite
SQLite.const materializers: <{ readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}>(_eventDefRecord: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}, handlers: { ...;}) => { ...;}
Builder function for creating a type-safe materializer map.
This is the primary way to define materializers in LiveStore. It ensures:
- Every non-derived event has a corresponding materializer
- Materializer argument types match their event schemas
- Derived events are excluded from the required handlers
materializers(const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
events, { [const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
events.todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>
todoCreated.name: "v1.TodoCreated"
Unique identifier for this event type. Conventionally versioned (e.g., v1.TodoCreated).
name]: defineMaterializer<State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>>(_eventDef: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>, materializer: State.SQLite.Materializer<...>): State.SQLite.Materializer<...>
Type-safe wrapper for defining a single materializer.
Useful when defining materializers separately from the materializers() builder.
The first argument provides type inference for the second.
defineMaterializer(const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
events.todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>
todoCreated, ({ id: string
id, text: string
text }) => const tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos.insert: (values: { readonly text: string; readonly id: string; readonly completed?: boolean;}) => QueryBuilder<readonly Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>;}, "Type">[], State.SQLite.TableDefBase<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { ...; }; readonly completed: { ...; };}>, State.SQLite.WithDefaults<...>>, "select" | ... 6 more ... | "row">
Insert a new row into the table.
insert({ id: string
id, text: string
text, completed?: boolean
completed: false }), ),})
const const state: InternalState
state = import State
State.import SQLite
SQLite.const makeState: <{ tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; }; materializers: { ...; };}>(inputSchema: { tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; }; materializers: { ...; };}) => InternalState
makeState({ tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables, materializers: { "v1.TodoCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>>;}
materializers })
export const const schema: FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; }; state: InternalState;}>
schema = makeSchema<{ events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; }; state: InternalState;}>(inputSchema: { events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; }; state: InternalState;}): FromInputSchema.DeriveSchema<...>
makeSchema({ events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
events, state: InternalState
state })export const const storeTables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
storeTables = const tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tablesexport const const storeEvents: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
storeEvents = const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
eventsUsing a store
Section titled “Using a store”Querying data
Section titled “Querying data”import type { class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
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.
Store } from '@livestore/livestore'import { import storeTables
storeTables } from './schema.ts'
declare const const store: Store<LiveStoreSchema.Any, {}>
store: class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
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.
Store
const const todos: unknown
todos = const store: Store<LiveStoreSchema.Any, {}>
store.Store<LiveStoreSchema<TDbSchema extends DbSchema = DbSchema, TEventsDefRecord extends EventDefRecord = EventDefRecord>.Any, {}>.query: <unknown>(query: Queryable<unknown> | { query: string; bindValues: Bindable; schema?: Decoder<unknown, never>;}, options?: { otelContext?: Context; debugRefreshReason?: RefreshReason;}) => unknown
Synchronously queries the database without creating a LiveQuery.
This is useful for queries that don't need to be reactive.
Example: Query builder
const completedTodos = store.query(tables.todo.where({ complete: true }))
Example: Raw SQL query
const completedTodos = store.query({ query: 'SELECT * FROM todo WHERE complete = 1', bindValues: {} })
query(import storeTables
storeTables.any
todos)var console: Console
console.Console.log(...data: any[]): void (+2 overloads)
The console.log() static method outputs a message to the console.
log(const todos: unknown
todos)
import type { class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
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.
Store } from '@livestore/livestore'
import { const storeTables: { readonly todos: TableDef<SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, WithDefaults<...>, Struct<...>>;}
storeTables } from './schema.ts'
declare const const store: Store<LiveStoreSchema.Any, {}>
store: class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
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.
Store
const const todos: readonly Struct.ReadonlySide<{ readonly id: Codec<string, string, never, never>; readonly text: Codec<string, string, never, never>; readonly completed: Codec<boolean, number, never, never>;}, "Type">[]
todos = const store: Store<LiveStoreSchema.Any, {}>
store.Store<LiveStoreSchema<TDbSchema extends DbSchema = DbSchema, TEventsDefRecord extends EventDefRecord = EventDefRecord>.Any, {}>.query: <readonly Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: Codec<string, string, never, never>; readonly text: Codec<string, string, never, never>; readonly completed: Codec<boolean, number, never, never>;}, "Type">[]>(query: Queryable<readonly Struct.ReadonlySide<{ readonly id: Codec<string, string, never, never>; readonly text: Codec<string, string, never, never>; readonly completed: Codec<boolean, number, never, never>;}, "Type">[]> | { query: string; bindValues: Bindable; schema?: Decoder<...>;}, options?: { otelContext?: Context; debugRefreshReason?: RefreshReason;}) => readonly Struct.ReadonlySide<...>[]
Synchronously queries the database without creating a LiveQuery.
This is useful for queries that don't need to be reactive.
Example: Query builder
const completedTodos = store.query(tables.todo.where({ complete: true }))
Example: Raw SQL query
const completedTodos = store.query({ query: 'SELECT * FROM todo WHERE complete = 1', bindValues: {} })
query(const storeTables: { readonly todos: TableDef<SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, WithDefaults<...>, Struct<...>>;}
storeTables.todos: TableDef<SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}>, WithDefaults<...>, Struct<...>>
todos)var console: Console
console.Console.log(...data: any[]): void (+2 overloads)
The console.log() static method outputs a message to the console.
log(const todos: readonly Struct.ReadonlySide<{ readonly id: Codec<string, string, never, never>; readonly text: Codec<string, string, never, never>; readonly completed: Codec<boolean, number, never, never>;}, "Type">[]
todos)
Subscribing to data
Section titled “Subscribing to data”import type { class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
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.
Store } from '@livestore/livestore'
import { import storeTables
storeTables } from './schema.ts'
declare const const store: Store<LiveStoreSchema.Any, {}>
store: class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
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.
Store
const const unsubscribe: Unsubscribe
unsubscribe = const store: Store<LiveStoreSchema.Any, {}>
store.Store<LiveStoreSchema<TDbSchema extends DbSchema = DbSchema, TEventsDefRecord extends EventDefRecord = EventDefRecord>.Any, {}>.subscribe: <unknown>(query: Queryable<unknown>, onUpdate: (value: unknown) => void, options?: SubscribeOptions<unknown> | undefined) => Unsubscribe (+1 overload)
subscribe(import storeTables
storeTables.any
todos, (todos: unknown
todos) => { var console: Console
console.Console.log(...data: any[]): void (+2 overloads)
The console.log() static method outputs a message to the console.
log(todos: unknown
todos)})
const unsubscribe: () => void
unsubscribe()import { const defineMaterializer: <TEventDef extends State.SQLite.EventDef.AnyWithoutFn>(_eventDef: TEventDef, materializer: State.SQLite.Materializer<TEventDef>) => State.SQLite.Materializer<TEventDef>
Type-safe wrapper for defining a single materializer.
Useful when defining materializers separately from the materializers() builder.
The first argument provides type inference for the second.
defineMaterializer, import Events
Events, const makeSchema: <TInputSchema extends InputSchema>(inputSchema: TInputSchema) => FromInputSchema.DeriveSchema<TInputSchema>
makeSchema, import Schema
Schema, import State
State } from '@livestore/livestore'
const const tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables = { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos: import State
State.import SQLite
SQLite.function table<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}, Partial<...>>(args: { ...;} & Partial<...>): State.SQLite.TableDef<...> (+2 overloads)
Creates a SQLite table definition from columns or an Effect Schema.
This function supports two main ways to define a table:
- Using explicit column definitions
- Using an Effect Schema (either the
name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columnsconst usersTable = State.SQLite.table({ name: 'users', columns: { id: State.SQLite.text({ primaryKey: true }), name: State.SQLite.text({ nullable: false }), email: State.SQLite.text({ nullable: false }), age: State.SQLite.integer({ nullable: true }), },})
// Using Effect Schema with annotationsimport { Schema } from '@livestore/utils/effect'
const UserSchema = Schema.Struct({ id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement), email: Schema.String.pipe(State.SQLite.withUnique), name: Schema.String, active: Schema.Boolean.pipe(State.SQLite.withDefault(true)), createdAt: Schema.optional(Schema.Date),})
// Option 1: With explicit nameconst usersTable = State.SQLite.table({ name: 'users', schema: UserSchema,})
// Option 2: With name from schema annotation (title or identifier)const AnnotatedUserSchema = UserSchema.annotate({ title: 'users' })const usersTable2 = State.SQLite.table({ schema: AnnotatedUserSchema,})
// Adding indexesconst PostSchema = Schema.Struct({ id: Schema.String.pipe(State.SQLite.withPrimaryKey), title: Schema.String, authorId: Schema.String, createdAt: Schema.Date,}).annotate({ identifier: 'posts' })
const postsTable = State.SQLite.table({ schema: PostSchema, indexes: [ { name: 'idx_posts_author', columns: ['authorId'] }, { name: 'idx_posts_created', columns: ['createdAt'], isUnique: false }, ],})
table({ name: "todos"
name: 'todos', columns: { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}
columns: { id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;}
id: import State
State.import SQLite
SQLite.const text: <string, string, false, typeof NoDefault, true, false>(args: { schema?: Schema.Codec<string, string, never, never>; default?: typeof NoDefault; nullable?: false; primaryKey?: true; autoIncrement?: false;}) => { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;} (+1 overload)
text({ primaryKey?: true
primaryKey: true }), text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;}
text: import State
State.import SQLite
SQLite.const text: () => { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;} (+1 overload)
text(), completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false;}
completed: import State
State.import SQLite
SQLite.const boolean: <boolean, false, false, false, false>(args: { default?: false; nullable?: false; primaryKey?: false; autoIncrement?: false;}) => { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false;} (+1 overload)
boolean({ default?: false
default: false }), }, }),} as type const = { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
const
const const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
events = { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>
todoCreated: import Events
Events.synced<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>(args: { name: "v1.TodoCreated"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">, never, never>;} & Omit<...>): State.SQLite.EventDef<...>export synced
Creates a synced event definition.
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({ name: "v1.TodoCreated"
name: 'v1.TodoCreated', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String; readonly text: Schema.String;}>(fields: { readonly id: Schema.String; readonly text: Schema.String;}): Schema.Struct<{ readonly id: Schema.String; readonly text: Schema.String;}>
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Example (Defining a basic struct)
import { Schema } from "effect"
const Person = Schema.Struct({ name: Schema.String, age: Schema.Number, email: Schema.optionalKey(Schema.String)})
// { readonly name: string; readonly age: number; readonly email?: string }type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })console.log(alice)// { name: 'Alice', age: 30 }
Struct({ id: Schema.String
id: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String, text: Schema.String
text: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String }), }),} as type const = { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
const
const const materializers: { "v1.TodoCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>>;}
materializers = import State
State.import SQLite
SQLite.const materializers: <{ readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}>(_eventDefRecord: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}, handlers: { ...;}) => { ...;}
Builder function for creating a type-safe materializer map.
This is the primary way to define materializers in LiveStore. It ensures:
- Every non-derived event has a corresponding materializer
- Materializer argument types match their event schemas
- Derived events are excluded from the required handlers
materializers(const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
events, { [const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
events.todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>
todoCreated.name: "v1.TodoCreated"
Unique identifier for this event type. Conventionally versioned (e.g., v1.TodoCreated).
name]: defineMaterializer<State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>>(_eventDef: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>, materializer: State.SQLite.Materializer<...>): State.SQLite.Materializer<...>
Type-safe wrapper for defining a single materializer.
Useful when defining materializers separately from the materializers() builder.
The first argument provides type inference for the second.
defineMaterializer(const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
events.todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>
todoCreated, ({ id: string
id, text: string
text }) => const tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos.insert: (values: { readonly text: string; readonly id: string; readonly completed?: boolean;}) => QueryBuilder<readonly Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>;}, "Type">[], State.SQLite.TableDefBase<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { ...; }; readonly completed: { ...; };}>, State.SQLite.WithDefaults<...>>, "select" | ... 6 more ... | "row">
Insert a new row into the table.
insert({ id: string
id, text: string
text, completed?: boolean
completed: false }), ),})
const const state: InternalState
state = import State
State.import SQLite
SQLite.const makeState: <{ tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; }; materializers: { ...; };}>(inputSchema: { tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; }; materializers: { ...; };}) => InternalState
makeState({ tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables, materializers: { "v1.TodoCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>>;}
materializers })
export const const schema: FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; }; state: InternalState;}>
schema = makeSchema<{ events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; }; state: InternalState;}>(inputSchema: { events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; }; state: InternalState;}): FromInputSchema.DeriveSchema<...>
makeSchema({ events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
events, state: InternalState
state })export const const storeTables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
storeTables = const tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tablesexport const const storeEvents: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
storeEvents = const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
eventsCommitting events
Section titled “Committing events”import type { class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
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.
Store } from '@livestore/livestore'
import { import storeEvents
storeEvents } from './schema.ts'
declare const const store: Store<LiveStoreSchema.Any, {}>
store: class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
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.
Store
const store: Store<LiveStoreSchema.Any, {}>
store.Store<LiveStoreSchema<TDbSchema extends DbSchema = DbSchema, TEventsDefRecord extends EventDefRecord = EventDefRecord>.Any, {}>.commit: <readonly [any]>(list_0: any) => void (+3 overloads)
commit(import storeEvents
storeEvents.any
todoCreated({ id: string
id: '1', text: string
text: 'Buy milk' }))import { const defineMaterializer: <TEventDef extends State.SQLite.EventDef.AnyWithoutFn>(_eventDef: TEventDef, materializer: State.SQLite.Materializer<TEventDef>) => State.SQLite.Materializer<TEventDef>
Type-safe wrapper for defining a single materializer.
Useful when defining materializers separately from the materializers() builder.
The first argument provides type inference for the second.
defineMaterializer, import Events
Events, const makeSchema: <TInputSchema extends InputSchema>(inputSchema: TInputSchema) => FromInputSchema.DeriveSchema<TInputSchema>
makeSchema, import Schema
Schema, import State
State } from '@livestore/livestore'
const const tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables = { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos: import State
State.import SQLite
SQLite.function table<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}, Partial<...>>(args: { ...;} & Partial<...>): State.SQLite.TableDef<...> (+2 overloads)
Creates a SQLite table definition from columns or an Effect Schema.
This function supports two main ways to define a table:
- Using explicit column definitions
- Using an Effect Schema (either the
name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columnsconst usersTable = State.SQLite.table({ name: 'users', columns: { id: State.SQLite.text({ primaryKey: true }), name: State.SQLite.text({ nullable: false }), email: State.SQLite.text({ nullable: false }), age: State.SQLite.integer({ nullable: true }), },})
// Using Effect Schema with annotationsimport { Schema } from '@livestore/utils/effect'
const UserSchema = Schema.Struct({ id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement), email: Schema.String.pipe(State.SQLite.withUnique), name: Schema.String, active: Schema.Boolean.pipe(State.SQLite.withDefault(true)), createdAt: Schema.optional(Schema.Date),})
// Option 1: With explicit nameconst usersTable = State.SQLite.table({ name: 'users', schema: UserSchema,})
// Option 2: With name from schema annotation (title or identifier)const AnnotatedUserSchema = UserSchema.annotate({ title: 'users' })const usersTable2 = State.SQLite.table({ schema: AnnotatedUserSchema,})
// Adding indexesconst PostSchema = Schema.Struct({ id: Schema.String.pipe(State.SQLite.withPrimaryKey), title: Schema.String, authorId: Schema.String, createdAt: Schema.Date,}).annotate({ identifier: 'posts' })
const postsTable = State.SQLite.table({ schema: PostSchema, indexes: [ { name: 'idx_posts_author', columns: ['authorId'] }, { name: 'idx_posts_created', columns: ['createdAt'], isUnique: false }, ],})
table({ name: "todos"
name: 'todos', columns: { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}
columns: { id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;}
id: import State
State.import SQLite
SQLite.const text: <string, string, false, typeof NoDefault, true, false>(args: { schema?: Schema.Codec<string, string, never, never>; default?: typeof NoDefault; nullable?: false; primaryKey?: true; autoIncrement?: false;}) => { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;} (+1 overload)
text({ primaryKey?: true
primaryKey: true }), text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;}
text: import State
State.import SQLite
SQLite.const text: () => { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;} (+1 overload)
text(), completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false;}
completed: import State
State.import SQLite
SQLite.const boolean: <boolean, false, false, false, false>(args: { default?: false; nullable?: false; primaryKey?: false; autoIncrement?: false;}) => { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false;} (+1 overload)
boolean({ default?: false
default: false }), }, }),} as type const = { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
const
const const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
events = { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>
todoCreated: import Events
Events.synced<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>(args: { name: "v1.TodoCreated"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">, never, never>;} & Omit<...>): State.SQLite.EventDef<...>export synced
Creates a synced event definition.
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({ name: "v1.TodoCreated"
name: 'v1.TodoCreated', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String; readonly text: Schema.String;}>(fields: { readonly id: Schema.String; readonly text: Schema.String;}): Schema.Struct<{ readonly id: Schema.String; readonly text: Schema.String;}>
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Example (Defining a basic struct)
import { Schema } from "effect"
const Person = Schema.Struct({ name: Schema.String, age: Schema.Number, email: Schema.optionalKey(Schema.String)})
// { readonly name: string; readonly age: number; readonly email?: string }type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })console.log(alice)// { name: 'Alice', age: 30 }
Struct({ id: Schema.String
id: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String, text: Schema.String
text: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String }), }),} as type const = { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
const
const const materializers: { "v1.TodoCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>>;}
materializers = import State
State.import SQLite
SQLite.const materializers: <{ readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}>(_eventDefRecord: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}, handlers: { ...;}) => { ...;}
Builder function for creating a type-safe materializer map.
This is the primary way to define materializers in LiveStore. It ensures:
- Every non-derived event has a corresponding materializer
- Materializer argument types match their event schemas
- Derived events are excluded from the required handlers
materializers(const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
events, { [const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
events.todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>
todoCreated.name: "v1.TodoCreated"
Unique identifier for this event type. Conventionally versioned (e.g., v1.TodoCreated).
name]: defineMaterializer<State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>>(_eventDef: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>, materializer: State.SQLite.Materializer<...>): State.SQLite.Materializer<...>
Type-safe wrapper for defining a single materializer.
Useful when defining materializers separately from the materializers() builder.
The first argument provides type inference for the second.
defineMaterializer(const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
events.todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>
todoCreated, ({ id: string
id, text: string
text }) => const tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos.insert: (values: { readonly text: string; readonly id: string; readonly completed?: boolean;}) => QueryBuilder<readonly Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>;}, "Type">[], State.SQLite.TableDefBase<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { ...; }; readonly completed: { ...; };}>, State.SQLite.WithDefaults<...>>, "select" | ... 6 more ... | "row">
Insert a new row into the table.
insert({ id: string
id, text: string
text, completed?: boolean
completed: false }), ),})
const const state: InternalState
state = import State
State.import SQLite
SQLite.const makeState: <{ tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; }; materializers: { ...; };}>(inputSchema: { tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; }; materializers: { ...; };}) => InternalState
makeState({ tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables, materializers: { "v1.TodoCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>>;}
materializers })
export const const schema: FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; }; state: InternalState;}>
schema = makeSchema<{ events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; }; state: InternalState;}>(inputSchema: { events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; }; state: InternalState;}): FromInputSchema.DeriveSchema<...>
makeSchema({ events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
events, state: InternalState
state })export const const storeTables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
storeTables = const tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tablesexport const const storeEvents: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
storeEvents = const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>;}
eventsStreaming events
Section titled “Streaming events”Currently only events confirmed by the sync backend are supported.
import type { class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
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.
Store } from '@livestore/livestore'
declare const const store: Store<LiveStoreSchema.Any, {}>
store: class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
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.
Store
// Run oncefor await (const const event: Decoded<any>
event of const store: Store<LiveStoreSchema.Any, {}>
store.Store<LiveStoreSchema<TDbSchema extends DbSchema = DbSchema, TEventsDefRecord extends EventDefRecord = EventDefRecord>.Any, {}>.events: (options?: StoreEventsOptions<LiveStoreSchema<TDbSchema extends DbSchema = DbSchema, TEventsDefRecord extends EventDefRecord = EventDefRecord>.Any> | undefined) => AsyncIterable<Decoded<any>>
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.
TODO:
- Support streaming unconfirmed events
- Leader level
- Session level
- Support streaming client-only events
events()) { var console: Console
console.Console.log(...data: any[]): void (+2 overloads)
The console.log() static method outputs a message to the console.
log('event from leader', const event: Decoded<any>
event)}
// Continuos streamconst const iterator: AsyncIterator<Decoded<any>, any, any>
iterator = const store: Store<LiveStoreSchema.Any, {}>
store.Store<LiveStoreSchema<TDbSchema extends DbSchema = DbSchema, TEventsDefRecord extends EventDefRecord = EventDefRecord>.Any, {}>.events: (options?: StoreEventsOptions<LiveStoreSchema<TDbSchema extends DbSchema = DbSchema, TEventsDefRecord extends EventDefRecord = EventDefRecord>.Any> | undefined) => AsyncIterable<Decoded<any>>
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.
TODO:
- Support streaming unconfirmed events
- Leader level
- Session level
- Support streaming client-only events
events()[var Symbol: SymbolConstructor
Symbol.SymbolConstructor.asyncIterator: typeof Symbol.asyncIterator
A method that returns the default async iterator for an object. Called by the semantics of
the for-await-of statement.
asyncIterator]()try { while (true) { const { const value: any
value, const done: boolean | undefined
done } = await const iterator: AsyncIterator<Decoded<any>, any, any>
iterator.AsyncIterator<Decoded<any>, any, any>.next(...[value]: [] | [any]): Promise<IteratorResult<Decoded<any>, any>>
next() if (const done: boolean | undefined
done === true) break var console: Console
console.Console.log(...data: any[]): void (+2 overloads)
The console.log() static method outputs a message to the console.
log('event from stream:', const value: Decoded<any>
value) }} finally { await const iterator: AsyncIterator<Decoded<any>, any, any>
iterator.AsyncIterator<Decoded<any>, any, any>.return?(value?: any): Promise<IteratorResult<Decoded<any>, any>>
return?.()}Shutting down a store
Section titled “Shutting down a store”LiveStore provides two APIs for shutting down a store:
import type { class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
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.
Store } from '@livestore/livestore'
declare const const store: Store<LiveStoreSchema.Any, {}>
store: class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
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.
Store
const const effectShutdown: Effect.Effect<void, never, never>
effectShutdown = import Effect
Effect.const gen: <Effect.Effect<void, never, never>, void>(f: () => Generator<Effect.Effect<void, never, never>, void, never>) => Effect.Effect<void, never, never> (+1 overload)
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to use
Use when you want to write effectful code that looks and behaves like
synchronous code, while still handling asynchronous tasks, errors, and complex
control flow such as loops and conditions.
Generator functions work similarly to async/await but keep errors,
requirements, and interruption in the Effect type. You can yield* values
from effects and return the final result at the end.
Example (Sequencing effects with generators)
import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = ( total: number, discountRate: number): Effect.Effect<number, DiscountRateError> => discountRate === 0 ? Effect.fail(new DiscountRateError()) : Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() { const transactionAmount = yield* fetchTransactionAmount const discountRate = yield* fetchDiscountRate const discountedAmount = yield* applyDiscount( transactionAmount, discountRate ) const finalAmount = addServiceCharge(discountedAmount) return `Final amount to charge: ${finalAmount}`})
gen(function* () { yield* import Effect
Effect.const log: (...message: ReadonlyArray<any>) => Effect.Effect<void>
Logs one or more messages using the default log level.
Example (Logging at the default level)
import { Effect } from "effect"
const program = Effect.gen(function*() { yield* Effect.log("Starting computation") const result = 2 + 2 yield* Effect.log("Result:", result) yield* Effect.log("Multiple", "values", "can", "be", "logged") return result})
Effect.runPromise(program).then(console.log)// Output:// timestamp=2023-... level=INFO message="Starting computation"// timestamp=2023-... level=INFO message="Result: 4"// timestamp=2023-... level=INFO message="Multiple values can be logged"// 4
log('Shutting down store') yield* const store: Store<LiveStoreSchema.Any, {}>
store.Store<LiveStoreSchema<TDbSchema extends DbSchema = DbSchema, TEventsDefRecord extends EventDefRecord = EventDefRecord>.Any, {}>.shutdown: (cause?: Cause<UnknownError | MaterializeError>) => Effect.Effect<void>
Shuts down the store and closes the client session.
This is called automatically when the store was created using the React or Effect API.
shutdown()})
const const shutdownWithPromise: () => Promise<void>
shutdownWithPromise = async () => { await const store: Store<LiveStoreSchema.Any, {}>
store.Store<LiveStoreSchema<TDbSchema extends DbSchema = DbSchema, TEventsDefRecord extends EventDefRecord = EventDefRecord>.Any, {}>.shutdownPromise: (cause?: UnknownError) => Promise<void>
Shuts down the store and closes the client session.
This is called automatically when the store was created using the React or Effect API.
shutdownPromise()}
Effect integration
Section titled “Effect integration”For applications using Effect, LiveStore provides a type-safe way to access stores through the Effect layer system via makeStoreContext().
Creating a typed store context
Section titled “Creating a typed store context”Use makeStoreContext() to create a typed context that preserves your schema types:
// Define a typed store context with your schemaexport const const TodoStore: StoreTagClass<any, "todos">
TodoStore = const Store: { Tag: <TSchema extends LiveStoreSchema, TStoreId extends string>(schema: TSchema, storeId: TStoreId) => StoreTagClass<TSchema, TStoreId>;}
Store utilities for Effect integration.
Store.type Tag: <any, "todos">(schema: any, storeId: "todos") => StoreTagClass<any, "todos">
Create a typed store context class for use with Effect.
Returns a class that extends Context.Service, making it directly yieldable in Effect code.
The class includes static methods for creating layers and accessors for common operations.
Tag(import schema
schema, 'todos')
// Create a layer to initialize the storeconst const adapter: Adapter
adapter = function makeAdapter({ sync, ...options }: NodeAdapterOptions & { sync?: SyncOptions;}): Adapter
Creates a single-threaded LiveStore adapter for Node.js applications.
This adapter runs the leader thread (persistence and sync) in the same thread as
your application. Suitable for CLI tools, scripts, and applications where simplicity
is preferred over maximum performance.
For production servers or performance-critical applications, consider makeWorkerAdapter
which runs persistence/sync in a separate worker thread.
makeAdapter({ NodeAdapterOptions.storage: { readonly type: ["in-memory"]; readonly importSnapshot?: any;} | { readonly type: ["fs"]; readonly baseDirectory?: string | undefined;}
storage: { type: string
type: 'fs' } })
adapter: Adapter
adapter, batchUpdates: (run: () => void) => void
batchUpdates: (cb: () => void
cb) => cb: () => void
cb(), // For Node.js; use React's unstable_batchedUpdates in React apps})
import { const defineMaterializer: <TEventDef extends State.SQLite.EventDef.AnyWithoutFn>(_eventDef: TEventDef, materializer: State.SQLite.Materializer<TEventDef>) => State.SQLite.Materializer<TEventDef>
Type-safe wrapper for defining a single materializer.
Useful when defining materializers separately from the materializers() builder.
The first argument provides type inference for the second.
defineMaterializer, import Events
Events, const makeSchema: <TInputSchema extends InputSchema>(inputSchema: TInputSchema) => FromInputSchema.DeriveSchema<TInputSchema>
makeSchema, import Schema
Schema, import State
State } from '@livestore/livestore'
const const tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables = {// Define a typed store context with your schemaexport const const TodoStore: StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "todos">
TodoStore = const Store: { Tag: <TSchema extends LiveStoreSchema, TStoreId extends string>(schema: TSchema, storeId: TStoreId) => StoreTagClass<TSchema, TStoreId>;}
Store utilities for Effect integration.
Store.type Tag: <FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "todos">(schema: FromInputSchema.DeriveSchema<...>, storeId: "todos") => StoreTagClass<...>
Create a typed store context class for use with Effect.
Returns a class that extends Context.Service, making it directly yieldable in Effect code.
The class includes static methods for creating layers and accessors for common operations.
Tag(const schema: FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>
schema, 'todos')
// Create a layer to initialize the storeconst const adapter: Adapter
adapter = function makeAdapter({ sync, ...options }: NodeAdapterOptions & { sync?: SyncOptions;}): Adapter
Creates a single-threaded LiveStore adapter for Node.js applications.
This adapter runs the leader thread (persistence and sync) in the same thread as
your application. Suitable for CLI tools, scripts, and applications where simplicity
is preferred over maximum performance.
For production servers or performance-critical applications, consider makeWorkerAdapter
which runs persistence/sync in a separate worker thread.
makeAdapter({ NodeAdapterOptions.storage: { readonly type: ["in-memory"]; readonly importSnapshot?: any;} | { readonly type: ["fs"]; readonly baseDirectory?: string | undefined;}
storage: { type: string
type: 'fs' } })
export const const TodoStoreLayer: Layer<StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "todos">, UnknownError | TimeoutError, OtelTracer>
TodoStoreLayer = const TodoStore: StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "todos">
TodoStore.StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<...>>; readonly todoCompleted: EventDef<...>; }; state: InternalState; }>, "todos">.layer<unknown, Codec<Json, Json, never, never>>(props: StoreLayerProps<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, unknown, Codec<...>>): Layer<...>
Creates a layer that initializes the store
layer({ adapter: Adapter
adapter, batchUpdates: (run: () => void) => void
batchUpdates: (cb: () => void
cb) => cb: () => void
cb(), // For Node.js; use React's unstable_batchedUpdates in React apps})
The factory takes your schema type as a generic parameter and returns a StoreContext with:
Tag- Context tag for dependency injectionLayer- Creates a layer that initializes the storeDeferredTag- For async initialization patternsDeferredLayer- Layer providing the deferred contextfromDeferred- Layer that waits for deferred initialization
Using the store in Effect services
Section titled “Using the store in Effect services”Access the store in Effect code with full type safety and autocomplete:
// Define a typed store context with your schemaexport const const TodoStore: StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "todos">
TodoStore = const Store: { Tag: <TSchema extends LiveStoreSchema, TStoreId extends string>(schema: TSchema, storeId: TStoreId) => StoreTagClass<TSchema, TStoreId>;}
Store utilities for Effect integration.
Store.type Tag: <FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "todos">(schema: FromInputSchema.DeriveSchema<...>, storeId: "todos") => StoreTagClass<...>
Create a typed store context class for use with Effect.
Returns a class that extends Context.Service, making it directly yieldable in Effect code.
The class includes static methods for creating layers and accessors for common operations.
Tag(const schema: FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>
schema, 'todos')
// Create a layer to initialize the storeconst const adapter: Adapter
adapter = function makeAdapter({ sync, ...options }: NodeAdapterOptions & { sync?: SyncOptions;}): Adapter
Creates a single-threaded LiveStore adapter for Node.js applications.
This adapter runs the leader thread (persistence and sync) in the same thread as
your application. Suitable for CLI tools, scripts, and applications where simplicity
is preferred over maximum performance.
For production servers or performance-critical applications, consider makeWorkerAdapter
which runs persistence/sync in a separate worker thread.
makeAdapter({ NodeAdapterOptions.storage: { readonly type: ["in-memory"]; readonly importSnapshot?: any;} | { readonly type: ["fs"]; readonly baseDirectory?: string | undefined;}
storage: { type: string
type: 'fs' } })
adapter: Adapter
adapter, batchUpdates: (run: () => void) => void
batchUpdates: (cb: () => void
cb) => cb: () => void
cb(), // For Node.js; use React's unstable_batchedUpdates in React apps})
import { const defineMaterializer: <TEventDef extends State.SQLite.EventDef.AnyWithoutFn>(_eventDef: TEventDef, materializer: State.SQLite.Materializer<TEventDef>) => State.SQLite.Materializer<TEventDef>
Type-safe wrapper for defining a single materializer.
Useful when defining materializers separately from the materializers() builder.
The first argument provides type inference for the second.
defineMaterializer, import Events
Events, const makeSchema: <TInputSchema extends InputSchema>(inputSchema: TInputSchema) => FromInputSchema.DeriveSchema<TInputSchema>
makeSchema, import Schema
Schema, import State
State } from '@livestore/livestore'
const const tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables = { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos: import State
State.import SQLite
SQLite.function table<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}, Partial<...>>(args: { ...;} & Partial<...>): State.SQLite.TableDef<...> (+2 overloads)
Creates a SQLite table definition from columns or an Effect Schema.
This function supports two main ways to define a table:
- Using explicit column definitions
- Using an Effect Schema (either the
name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columnsconst usersTable = State.SQLite.table({ name: 'users', columns: { id: State.SQLite.text({ primaryKey: true }), name: State.SQLite.text({ nullable: false }), email: State.SQLite.text({ nullable: false }), age: State.SQLite.integer({ nullable: true }), },})
// Using Effect Schema with annotationsimport { Schema } from '@livestore/utils/effect'
const UserSchema = Schema.Struct({ id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement), email: Schema.String.pipe(State.SQLite.withUnique), name: Schema.String, active: Schema.Boolean.pipe(State.SQLite.withDefault(true)), createdAt: Schema.optional(Schema.Date),})
// Option 1: With explicit nameconst usersTable = State.SQLite.table({ name: 'users', schema: UserSchema,})
// Option 2: With name from schema annotation (title or identifier)const AnnotatedUserSchema = UserSchema.annotate({ title: 'users' })const usersTable2 = State.SQLite.table({ schema: AnnotatedUserSchema,})
// Adding indexesconst PostSchema = Schema.Struct({ id: Schema.String.pipe(State.SQLite.withPrimaryKey), title: Schema.String, authorId: Schema.String, createdAt: Schema.Date,}).annotate({ identifier: 'posts' })
const postsTable = State.SQLite.table({ schema: PostSchema, indexes: [ { name: 'idx_posts_author', columns: ['authorId'] }, { name: 'idx_posts_created', columns: ['createdAt'], isUnique: false }, ],})
table({ name: "todos"
name: 'todos', columns: { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}
columns: { id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;}
id: import State
State.import SQLite
SQLite.const text: <string, string, false, typeof NoDefault, true, false>(args: { schema?: Schema.Codec<string, string, never, never>; default?: typeof NoDefault; nullable?: false; primaryKey?: true; autoIncrement?: false;}) => { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;} (+1 overload)
text({ primaryKey?: true
primaryKey: true }), text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;}
text: import State
State.import SQLite
SQLite.const text: () => { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;} (+1 overload)
text(), completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false;}
completed: import State
State.import SQLite
SQLite.const boolean: <boolean, false, false, false, false>(args: { default?: false; nullable?: false; primaryKey?: false; autoIncrement?: false;}) => { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false;} (+1 overload)
boolean({ default?: false
default: false }), }, }),} as type const = { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
const
const const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; readonly todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>;}
events = { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>
todoCreated: import Events
Events.synced<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>(args: { name: "v1.TodoCreated"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">, never, never>;} & Omit<...>): State.SQLite.EventDef<...>export synced
Creates a synced event definition.
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({ name: "v1.TodoCreated"
name: 'v1.TodoCreated', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String; readonly text: Schema.String;}>(fields: { readonly id: Schema.String; readonly text: Schema.String;}): Schema.Struct<{ readonly id: Schema.String; readonly text: Schema.String;}>
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Example (Defining a basic struct)
import { Schema } from "effect"
const Person = Schema.Struct({ name: Schema.String, age: Schema.Number, email: Schema.optionalKey(Schema.String)})
// { readonly name: string; readonly age: number; readonly email?: string }type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })console.log(alice)// { name: 'Alice', age: 30 }
Struct({ id: Schema.String
id: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String, text: Schema.String
text: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String }),// Access the store in Effect code with full type safetyconst const _todoService: Effect.Effect<readonly { readonly text: string; readonly id: string; readonly completed: boolean;}[], never, StoreTagClass<any, "todos">>
_todoService = import Effect
Effect.const gen: <Effect.Effect<LiveStoreContextRunning<any>, never, StoreTagClass<any, "todos">>, readonly { readonly text: string; readonly id: string; readonly completed: boolean;}[]>(f: () => Generator<Effect.Effect<LiveStoreContextRunning<any>, never, StoreTagClass<any, "todos">>, readonly { readonly text: string; readonly id: string; readonly completed: boolean;}[], never>) => Effect.Effect<readonly { readonly text: string; readonly id: string; readonly completed: boolean;}[], never, StoreTagClass<...>> (+1 overload)
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to use
Use when you want to write effectful code that looks and behaves like
synchronous code, while still handling asynchronous tasks, errors, and complex
control flow such as loops and conditions.
Generator functions work similarly to async/await but keep errors,
requirements, and interruption in the Effect type. You can yield* values
from effects and return the final result at the end.
Example (Sequencing effects with generators)
import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = ( total: number, discountRate: number): Effect.Effect<number, DiscountRateError> => discountRate === 0 ? Effect.fail(new DiscountRateError()) : Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() { const transactionAmount = yield* fetchTransactionAmount const discountRate = yield* fetchDiscountRate const discountedAmount = yield* applyDiscount( transactionAmount, discountRate ) const finalAmount = addServiceCharge(discountedAmount) return `Final amount to charge: ${finalAmount}`})
gen(function* () { // Yield the store directly (it's a Context.Service) const { const store: Store<any, {}>
store } = yield* const TodoStore: StoreTagClass<any, "todos">
TodoStore
// Query with autocomplete for tables
// Commit events const store: Store<any, {}>
store.Store<any, {}>.commit: <readonly [{ name: "v1.TodoCreated"; args: Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">;}]>(list_0: { name: "v1.TodoCreated"; args: Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">;}) => void (+3 overloads)
commit(const storeEvents: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>;}
storeEvents.todoCreated: (args: Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String;}, "Type">) => { name: "v1.TodoCreated"; args: Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">;}
Callable signature - creates a partial event with decoded arguments.
The returned object can be passed directly to store.commit().
todoCreated({ id: string
id: '1', text: string
text: 'Buy milk' }))
return const todos: readonly { readonly text: string; readonly id: string; readonly completed: boolean;}[]
todos})
// Or use static accessors for a more functional styleconst const _todoServiceAlt: Effect.Effect<readonly { readonly text: string; readonly id: string; readonly completed: boolean;}[], never, StoreTagClass<any, "todos">>
_todoServiceAlt = import Effect
Effect.const gen: <Effect.Effect<void, never, StoreTagClass<any, "todos">>, readonly { readonly text: string; readonly id: string; readonly completed: boolean;}[]>(f: () => Generator<Effect.Effect<void, never, StoreTagClass<any, "todos">>, readonly { readonly text: string; readonly id: string; readonly completed: boolean;}[], never>) => Effect.Effect<readonly { readonly text: string; readonly id: string; readonly completed: boolean;}[], never, StoreTagClass<any, "todos">> (+1 overload)
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to use
Use when you want to write effectful code that looks and behaves like
synchronous code, while still handling asynchronous tasks, errors, and complex
control flow such as loops and conditions.
Generator functions work similarly to async/await but keep errors,
requirements, and interruption in the Effect type. You can yield* values
from effects and return the final result at the end.
Example (Sequencing effects with generators)
import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = ( total: number, discountRate: number): Effect.Effect<number, DiscountRateError> => discountRate === 0 ? Effect.fail(new DiscountRateError()) : Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() { const transactionAmount = yield* fetchTransactionAmount const discountRate = yield* fetchDiscountRate const discountedAmount = yield* applyDiscount( transactionAmount, discountRate ) const finalAmount = addServiceCharge(discountedAmount) return `Final amount to charge: ${finalAmount}`})
gen(function* () { // Query using static accessor const const todos: readonly { readonly text: string; readonly id: string; readonly completed: boolean;}[]
todos = yield* const TodoStore: StoreTagClass<any, "todos">
TodoStore.StoreTagClass<any, "todos">.query<readonly { readonly text: string; readonly id: string; readonly completed: boolean;}[]>(query: Queryable<readonly { readonly text: string; readonly id: string; readonly completed: boolean;}[]>): Effect.Effect<readonly { readonly text: string; readonly id: string; readonly completed: boolean;}[], never, StoreTagClass<any, "todos">>
Query the store. Returns an Effect that yields the query result.
query(const storeTables: { readonly todos: TableDef<SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, WithDefaults<...>, Struct<...>>;}
storeTables.todos: TableDef<SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}>, WithDefaults<...>, Struct<...>>
todos.select: <"text" | "id" | "completed">(...columns: ("text" | "id" | "completed")[]) => QueryBuilder<readonly { readonly text: string; readonly id: string; readonly completed: boolean;}[], TableDefBase<SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; ... 4 more ...; autoIncrement: false; };}>, WithDefaults<...>>, "select" | ... 3 more ... | "row"> (+1 overload)
Select multiple columns
select())// Define a typed store context with your schemaexport const const TodoStore: StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "todos">
TodoStore = const Store: { Tag: <TSchema extends LiveStoreSchema, TStoreId extends string>(schema: TSchema, storeId: TStoreId) => StoreTagClass<TSchema, TStoreId>;}
Store utilities for Effect integration.
Store.type Tag: <FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "todos">(schema: FromInputSchema.DeriveSchema<...>, storeId: "todos") => StoreTagClass<...>
Create a typed store context class for use with Effect.
Returns a class that extends Context.Service, making it directly yieldable in Effect code.
The class includes static methods for creating layers and accessors for common operations.
Tag(const schema: FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>
schema, 'todos')
// Create a layer to initialize the storeconst const adapter: Adapter
adapter = function makeAdapter({ sync, ...options }: NodeAdapterOptions & { sync?: SyncOptions;}): Adapter
Creates a single-threaded LiveStore adapter for Node.js applications.
This adapter runs the leader thread (persistence and sync) in the same thread as
your application. Suitable for CLI tools, scripts, and applications where simplicity
is preferred over maximum performance.
For production servers or performance-critical applications, consider makeWorkerAdapter
which runs persistence/sync in a separate worker thread.
makeAdapter({ NodeAdapterOptions.storage: { readonly type: ["in-memory"]; readonly importSnapshot?: any;} | { readonly type: ["fs"]; readonly baseDirectory?: string | undefined;}
storage: { type: string
type: 'fs' } })
export const const TodoStoreLayer: Layer<StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "todos">, UnknownError | TimeoutError, OtelTracer>
TodoStoreLayer = const TodoStore: StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "todos">
TodoStore.StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<...>>; readonly todoCompleted: EventDef<...>; }; state: InternalState; }>, "todos">.layer<unknown, Codec<Json, Json, never, never>>(props: StoreLayerProps<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, unknown, Codec<...>>): Layer<...>
Creates a layer that initializes the store
layer({ adapter: Adapter
adapter, batchUpdates: (run: () => void) => void
batchUpdates: (cb: () => void
cb) => cb: () => void
cb(), // For Node.js; use React's unstable_batchedUpdates in React apps})
Layer composition
Section titled “Layer composition”Compose store layers with your application services:
// Define a typed store context with your schemaexport const const TodoStore: StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "todos">
TodoStore = const Store: { Tag: <TSchema extends LiveStoreSchema, TStoreId extends string>(schema: TSchema, storeId: TStoreId) => StoreTagClass<TSchema, TStoreId>;}
Store utilities for Effect integration.
Store.type Tag: <FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "todos">(schema: FromInputSchema.DeriveSchema<...>, storeId: "todos") => StoreTagClass<...>
Create a typed store context class for use with Effect.
Returns a class that extends Context.Service, making it directly yieldable in Effect code.
The class includes static methods for creating layers and accessors for common operations.
Tag(const schema: FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>
schema, 'todos')
// Create a layer to initialize the storeconst const adapter: Adapter
adapter = function makeAdapter({ sync, ...options }: NodeAdapterOptions & { sync?: SyncOptions;}): Adapter
Creates a single-threaded LiveStore adapter for Node.js applications.
This adapter runs the leader thread (persistence and sync) in the same thread as
your application. Suitable for CLI tools, scripts, and applications where simplicity
is preferred over maximum performance.
For production servers or performance-critical applications, consider makeWorkerAdapter
which runs persistence/sync in a separate worker thread.
makeAdapter({ NodeAdapterOptions.storage: { readonly type: ["in-memory"]; readonly importSnapshot?: any;} | { readonly type: ["fs"]; readonly baseDirectory?: string | undefined;}
storage: { type: string
type: 'fs' } })
adapter: Adapter
adapter, batchUpdates: (run: () => void) => void
batchUpdates: (cb: () => void
cb) => cb: () => void
cb(), // For Node.js; use React's unstable_batchedUpdates in React apps})
import { const defineMaterializer: <TEventDef extends State.SQLite.EventDef.AnyWithoutFn>(_eventDef: TEventDef, materializer: State.SQLite.Materializer<TEventDef>) => State.SQLite.Materializer<TEventDef>
Type-safe wrapper for defining a single materializer.
Useful when defining materializers separately from the materializers() builder.
The first argument provides type inference for the second.
defineMaterializer, import Events
Events, const makeSchema: <TInputSchema extends InputSchema>(inputSchema: TInputSchema) => FromInputSchema.DeriveSchema<TInputSchema>
makeSchema, import Schema
Schema, import State
State } from '@livestore/livestore'
const const tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables = { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos: import State
State.import SQLite
SQLite.function table<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}, Partial<...>>(args: { ...;} & Partial<...>): State.SQLite.TableDef<...> (+2 overloads)
Creates a SQLite table definition from columns or an Effect Schema.
This function supports two main ways to define a table:
- Using explicit column definitions
- Using an Effect Schema (either the
name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columnsconst usersTable = State.SQLite.table({ name: 'users', columns: { id: State.SQLite.text({ primaryKey: true }), name: State.SQLite.text({ nullable: false }), email: State.SQLite.text({ nullable: false }), age: State.SQLite.integer({ nullable: true }), },})
// Using Effect Schema with annotationsimport { Schema } from '@livestore/utils/effect'
const UserSchema = Schema.Struct({ id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement), email: Schema.String.pipe(State.SQLite.withUnique), name: Schema.String, active: Schema.Boolean.pipe(State.SQLite.withDefault(true)), createdAt: Schema.optional(Schema.Date),})
// Option 1: With explicit nameconst usersTable = State.SQLite.table({ name: 'users', schema: UserSchema,})
// Option 2: With name from schema annotation (title or identifier)const AnnotatedUserSchema = UserSchema.annotate({ title: 'users' })const usersTable2 = State.SQLite.table({ schema: AnnotatedUserSchema,})
// Adding indexesconst PostSchema = Schema.Struct({ id: Schema.String.pipe(State.SQLite.withPrimaryKey), title: Schema.String, authorId: Schema.String, createdAt: Schema.Date,}).annotate({ identifier: 'posts' })
const postsTable = State.SQLite.table({ schema: PostSchema, indexes: [ { name: 'idx_posts_author', columns: ['authorId'] }, { name: 'idx_posts_created', columns: ['createdAt'], isUnique: false }, ],})
table({ name: "todos"
name: 'todos', columns: { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}
columns: { id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;}
id: import State
State.import SQLite
SQLite.const text: <string, string, false, typeof NoDefault, true, false>(args: { schema?: Schema.Codec<string, string, never, never>; default?: typeof NoDefault; nullable?: false; primaryKey?: true; autoIncrement?: false;}) => { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;} (+1 overload)
text({ primaryKey?: true
primaryKey: true }), text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;}
text: import State
State.import SQLite
SQLite.const text: () => { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;} (+1 overload)
text(), completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false;}
completed: import State
State.import SQLite
SQLite.const boolean: <boolean, false, false, false, false>(args: { default?: false; nullable?: false; primaryKey?: false; autoIncrement?: false;}) => { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false;} (+1 overload)
boolean({ default?: false
default: false }), }, }),} as type const = { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
const
const const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; readonly todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>;}
events = { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>
todoCreated: import Events
Events.synced<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>(args: { name: "v1.TodoCreated"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">, never, never>;} & Omit<...>): State.SQLite.EventDef<...>export synced
Creates a synced event definition.
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({ name: "v1.TodoCreated"
name: 'v1.TodoCreated', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String; readonly text: Schema.String;}>(fields: { readonly id: Schema.String; readonly text: Schema.String;}): Schema.Struct<{ readonly id: Schema.String; readonly text: Schema.String;}>
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Example (Defining a basic struct)
import { Schema } from "effect"
const Person = Schema.Struct({ name: Schema.String, age: Schema.Number, email: Schema.optionalKey(Schema.String)})
// { readonly name: string; readonly age: number; readonly email?: string }type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })console.log(alice)// { name: 'Alice', age: 30 }
Struct({ id: Schema.String
id: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String, text: Schema.String
text: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String }), }), todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Encoded">>
todoCompleted: import Events
Events.synced<"v1.TodoCompleted", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Encoded">>(args: { name: "v1.TodoCompleted"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">, never, never>;} & Omit<State.SQLite.DefineEventOptions<Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Type">, false>, "derived" | "clientOnly">): State.SQLite.EventDef<...>export synced
Creates a synced event definition.
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({ name: "v1.TodoCompleted"
name: 'v1.TodoCompleted', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String;}>(fields: { readonly id: Schema.String;}): Schema.Struct<{ readonly id: Schema.String;}>
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Example (Defining a basic struct)
import { Schema } from "effect"
const Person = Schema.Struct({ name: Schema.String, age: Schema.Number, email: Schema.optionalKey(Schema.String)})
// { readonly name: string; readonly age: number; readonly email?: string }type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })console.log(alice)// { name: 'Alice', age: 30 }
Struct({ id: Schema.String
id: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String }), }),} as type const = { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; readonly todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>;}
const
const const materializers: { "v1.TodoCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>>; "v1.TodoCompleted": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>>;}
materializers = import State
State.import SQLite
SQLite.const materializers: <{ readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; readonly todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>;}>(_eventDefRecord: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; readonly todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>;}, handlers: { ...;}) => { ...;}
Builder function for creating a type-safe materializer map.
This is the primary way to define materializers in LiveStore. It ensures:
- Every non-derived event has a corresponding materializer
- Materializer argument types match their event schemas
- Derived events are excluded from the required handlers
materializers(const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; readonly todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>;}
events, { [const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; readonly todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>;}
events.todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>
todoCreated.name: "v1.TodoCreated"
Unique identifier for this event type. Conventionally versioned (e.g., v1.TodoCreated).
name]: defineMaterializer<State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>>(_eventDef: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>, materializer: State.SQLite.Materializer<...>): State.SQLite.Materializer<...>
Type-safe wrapper for defining a single materializer.
Useful when defining materializers separately from the materializers() builder.
The first argument provides type inference for the second.
defineMaterializer(const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; readonly todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>;}
events.todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>
todoCreated, ({ id: string
id, text: string
text }) => const tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos.insert: (values: { readonly id: string; readonly text: string; readonly completed?: boolean;}) => QueryBuilder<readonly Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>;}, "Type">[], State.SQLite.TableDefBase<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { ...; }; readonly completed: { ...; };}>, State.SQLite.WithDefaults<...>>, "select" | ... 6 more ... | "row">
Insert a new row into the table.
insert({ id: string
id, text: string
text, completed?: boolean
completed: false }), ),// Define services that depend on the storeclass class TodoService
TodoService extends import Context
Context.const Service: <TodoService, { createTodo: (id: string, text: string) => Effect.Effect<void>; completeTodo: (id: string) => Effect.Effect<void>;}>() => <Identifier, E, R, Args>(id: Identifier, options?: { readonly make: ((...args: Args) => Effect.Effect<{ createTodo: (id: string, text: string) => Effect.Effect<void>; completeTodo: (id: string) => Effect.Effect<void>; }, E, R>) | Effect.Effect<{ createTodo: (id: string, text: string) => Effect.Effect<void>; completeTodo: (id: string) => Effect.Effect<void>; }, E, R> | undefined;} | undefined) => Context.ServiceClass<...> & ([...] extends [...] ? unknown : { ...;}) (+2 overloads)
Creates a Context service key.
When to use
Use when you need to define a context service key for a dependency that must
be provided by the surrounding context.
Details
Call Context.Service("Key") for a function-style key, or use the two-stage
form Context.Service<Self, Shape>()("Key") for class-style service
declarations. The returned key can be yielded as an Effect and passed to
Context.make, Context.add, and the Context getter functions.
Gotchas
The string key is the runtime identity of the service. Reusing the same key
string for unrelated services makes them occupy the same slot in a
Context.
Example (Creating service keys)
import { Context } from "effect"
// Create a simple serviceconst Database = Context.Service<{ query: (sql: string) => string}>("Database")
// Create a service classclass Config extends Context.Service<Config, { port: number}>()("Config") {}
// Use the services to create contextsconst db = Context.make(Database, { query: (sql) => `Result: ${sql}`})const config = Context.make(Config, { port: 8080 })
Service< class TodoService
TodoService, { createTodo: (id: string, text: string) => Effect.Effect<void>
createTodo: (id: string
id: string, text: string
text: string) => import Effect
Effect.interface Effect<out A, out E = never, out R = never>
The Effect interface defines a value that lazily describes a workflow or
job. The workflow requires some context R, and may fail with an error of
type E, or succeed with a value of type A.
When to use
Use when you need to represent a lazy, composable workflow that can require
services, fail with a typed error, or succeed with a typed value.
Details
Effect values model resourceful interaction with the outside world,
including synchronous, asynchronous, concurrent, and parallel interaction.
They use a fiber-based concurrency model, with built-in support for
scheduling, fine-grained interruption, structured concurrency, and high
scalability.
To run an Effect value, you need a Runtime, which is a type that is
capable of executing Effect values.
Effect<void> completeTodo: (id: string) => Effect.Effect<void>
completeTodo: (id: string
id: string) => import Effect
Effect.interface Effect<out A, out E = never, out R = never>
The Effect interface defines a value that lazily describes a workflow or
job. The workflow requires some context R, and may fail with an error of
type E, or succeed with a value of type A.
When to use
Use when you need to represent a lazy, composable workflow that can require
services, fail with a typed error, or succeed with a typed value.
Details
Effect values model resourceful interaction with the outside world,
including synchronous, asynchronous, concurrent, and parallel interaction.
They use a fiber-based concurrency model, with built-in support for
scheduling, fine-grained interruption, structured concurrency, and high
scalability.
To run an Effect value, you need a Runtime, which is a type that is
capable of executing Effect values.
Effect<void>>()('TodoService') { static readonly TodoService.layer: Layer.Layer<TodoService, UnknownError | TimeoutError, OtelTracer>
layer = import Layer
Layer.const effect: <TodoService, { createTodo: (id: string, text: string) => Effect.Effect<void>; completeTodo: (id: string) => Effect.Effect<void>;}, never, StoreTagClass<any, "todos">>(service: Context.Key<TodoService, { createTodo: (id: string, text: string) => Effect.Effect<void>; completeTodo: (id: string) => Effect.Effect<void>;}>, effect: Effect.Effect<{ createTodo: (id: string, text: string) => Effect.Effect<void>; completeTodo: (id: string) => Effect.Effect<void>;}, never, StoreTagClass<any, "todos">>) => Layer.Layer<...> (+1 overload)
Constructs a layer from an effect that produces a single service.
When to use
Use when you need to construct a Layer-provided service with an Effect,
dependencies, or scoped resource acquisition.
Details
This allows you to create a Layer from an Effect that produces a service.
The Effect is executed in the scope of the layer, allowing for proper
resource management.
Example (Creating a layer from an effect)
import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, { readonly query: (sql: string) => Effect.Effect<string>}>()("Database") {}
const layer = Layer.effect(Database, Effect.sync(() => ({ query: (sql: string) => Effect.succeed(`Query: ${sql}`) })))
effect( class TodoService
TodoService, import Effect
Effect.const gen: <Effect.Effect<LiveStoreContextRunning<any>, never, StoreTagClass<any, "todos">>, { createTodo: (id: string, text: string) => Effect.Effect<void>; completeTodo: (id: string) => Effect.Effect<void>;}>(f: () => Generator<Effect.Effect<LiveStoreContextRunning<any>, never, StoreTagClass<any, "todos">>, { createTodo: (id: string, text: string) => Effect.Effect<void>; completeTodo: (id: string) => Effect.Effect<void>;}, never>) => Effect.Effect<...> (+1 overload)
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to use
Use when you want to write effectful code that looks and behaves like
synchronous code, while still handling asynchronous tasks, errors, and complex
control flow such as loops and conditions.
Generator functions work similarly to async/await but keep errors,
requirements, and interruption in the Effect type. You can yield* values
from effects and return the final result at the end.
Example (Sequencing effects with generators)
import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = ( total: number, discountRate: number): Effect.Effect<number, DiscountRateError> => discountRate === 0 ? Effect.fail(new DiscountRateError()) : Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() { const transactionAmount = yield* fetchTransactionAmount const discountRate = yield* fetchDiscountRate const discountedAmount = yield* applyDiscount( transactionAmount, discountRate ) const finalAmount = addServiceCharge(discountedAmount) return `Final amount to charge: ${finalAmount}`})
gen(function* () { const { const store: Store<any, {}>
store } = yield* const TodoStore: StoreTagClass<any, "todos">
TodoStore
const const createTodo: (id: string, text: string) => Effect.Effect<void, never, never>
createTodo = (id: string
id: string, text: string
text: string) => import Effect
Effect.const sync: <void>(thunk: LazyArg<void>) => Effect.Effect<void, never, never>
Creates an Effect that represents a synchronous side-effectful computation.
When to use
Use when you need to wrap a synchronous side-effectful operation that is not
expected to throw.
Details
The provided function is evaluated lazily when the effect runs.
Gotchas
The function must not throw. If it throws, the thrown value is treated as a
defect, not as a typed failure. Use try when throwing is expected.
Example (Capturing synchronous logging in an Effect)
import { Effect } from "effect"
const log = (message: string) => Effect.sync(() => { console.log(message) // side effect })
// ┌─── Effect<void, never, never>// ▼const program = log("Hello, World!")
sync(() => const store: Store<any, {}>
store.Store<any, {}>.commit: <readonly [{ name: "v1.TodoCreated"; args: Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">;}]>(list_0: { name: "v1.TodoCreated"; args: Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">;}) => void (+3 overloads)
commit(const storeEvents: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>;}
storeEvents.todoCreated: (args: Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String;}, "Type">) => { name: "v1.TodoCreated"; args: Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">;}
Callable signature - creates a partial event with decoded arguments.
The returned object can be passed directly to store.commit().
todoCreated({ id: string
id, text: string
text })))
const const completeTodo: (id: string) => Effect.Effect<void, never, never>
completeTodo = (id: string
id: string) => import Effect
Effect.const sync: <void>(thunk: LazyArg<void>) => Effect.Effect<void, never, never>
Creates an Effect that represents a synchronous side-effectful computation.
When to use
Use when you need to wrap a synchronous side-effectful operation that is not
expected to throw.
Details
The provided function is evaluated lazily when the effect runs.
Gotchas
The function must not throw. If it throws, the thrown value is treated as a
defect, not as a typed failure. Use try when throwing is expected.
Example (Capturing synchronous logging in an Effect)
import { Effect } from "effect"
const log = (message: string) => Effect.sync(() => { console.log(message) // side effect })
// ┌─── Effect<void, never, never>// ▼const program = log("Hello, World!")
sync(() => const store: Store<any, {}>
store.Store<any, {}>.commit: <readonly [{ name: "v1.TodoCompleted"; args: Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; }, "Type">;}]>(list_0: { name: "v1.TodoCompleted"; args: Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; }, "Type">;}) => void (+3 overloads)
commit(const storeEvents: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>;}
storeEvents.todoCompleted: (args: Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String;}, "Type">) => { name: "v1.TodoCompleted"; args: Struct.ReadonlySide<{ readonly id: String; }, "Type">;}
Callable signature - creates a partial event with decoded arguments.
The returned object can be passed directly to store.commit().
todoCompleted({ id: string
id })))// Define a typed store context with your schemaexport const const TodoStore: StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "todos">
TodoStore = const Store: { Tag: <TSchema extends LiveStoreSchema, TStoreId extends string>(schema: TSchema, storeId: TStoreId) => StoreTagClass<TSchema, TStoreId>;}
Store utilities for Effect integration.
Store.type Tag: <FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "todos">(schema: FromInputSchema.DeriveSchema<...>, storeId: "todos") => StoreTagClass<...>
Create a typed store context class for use with Effect.
Returns a class that extends Context.Service, making it directly yieldable in Effect code.
The class includes static methods for creating layers and accessors for common operations.
Tag(const schema: FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>
schema, 'todos')
// Create a layer to initialize the storeconst const adapter: Adapter
adapter = function makeAdapter({ sync, ...options }: NodeAdapterOptions & { sync?: SyncOptions;}): Adapter
Creates a single-threaded LiveStore adapter for Node.js applications.
This adapter runs the leader thread (persistence and sync) in the same thread as
your application. Suitable for CLI tools, scripts, and applications where simplicity
is preferred over maximum performance.
For production servers or performance-critical applications, consider makeWorkerAdapter
which runs persistence/sync in a separate worker thread.
makeAdapter({ NodeAdapterOptions.storage: { readonly type: ["in-memory"]; readonly importSnapshot?: any;} | { readonly type: ["fs"]; readonly baseDirectory?: string | undefined;}
storage: { type: string
type: 'fs' } })
export const const TodoStoreLayer: Layer<StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "todos">, UnknownError | TimeoutError, OtelTracer>
TodoStoreLayer = const TodoStore: StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "todos">
TodoStore.StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<...>>; readonly todoCompleted: EventDef<...>; }; state: InternalState; }>, "todos">.layer<unknown, Codec<Json, Json, never, never>>(props: StoreLayerProps<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, unknown, Codec<...>>): Layer<...>
Creates a layer that initializes the store
layer({ adapter: Adapter
adapter, batchUpdates: (run: () => void) => void
batchUpdates: (cb: () => void
cb) => cb: () => void
cb(), // For Node.js; use React's unstable_batchedUpdates in React apps})
Multiple stores with Effect
Section titled “Multiple stores with Effect”Each store gets a unique context tag, allowing multiple stores in the same Effect context:
// Define multiple typed store contextsconst const MainStore: StoreTagClass<any, "main">
MainStore = const Store: { Tag: <TSchema extends LiveStoreSchema, TStoreId extends string>(schema: TSchema, storeId: TStoreId) => StoreTagClass<TSchema, TStoreId>;}
Store utilities for Effect integration.
Store.type Tag: <any, "main">(schema: any, storeId: "main") => StoreTagClass<any, "main">
Create a typed store context class for use with Effect.
Returns a class that extends Context.Service, making it directly yieldable in Effect code.
The class includes static methods for creating layers and accessors for common operations.
Tag(import mainSchema
mainSchema, 'main')const const SettingsStore: StoreTagClass<any, "settings">
SettingsStore = const Store: { Tag: <TSchema extends LiveStoreSchema, TStoreId extends string>(schema: TSchema, storeId: TStoreId) => StoreTagClass<TSchema, TStoreId>;}
Store utilities for Effect integration.
Store.type Tag: <any, "settings">(schema: any, storeId: "settings") => StoreTagClass<any, "settings">
Create a typed store context class for use with Effect.
Returns a class that extends Context.Service, making it directly yieldable in Effect code.
The class includes static methods for creating layers and accessors for common operations.
Tag(const settingsSchema: any
settingsSchema, 'settings')
// Each store has its own layerconst const adapter: Adapter
adapter = function makeAdapter({ sync, ...options }: NodeAdapterOptions & { sync?: SyncOptions;}): Adapter
Creates a single-threaded LiveStore adapter for Node.js applications.
This adapter runs the leader thread (persistence and sync) in the same thread as
your application. Suitable for CLI tools, scripts, and applications where simplicity
is preferred over maximum performance.
For production servers or performance-critical applications, consider makeWorkerAdapter
which runs persistence/sync in a separate worker thread.
makeAdapter({ NodeAdapterOptions.storage: { readonly type: ["in-memory"]; readonly importSnapshot?: any;} | { readonly type: ["fs"]; readonly baseDirectory?: string | undefined;}
storage: { type: string
type: 'fs' } })
const const MainStoreLayer: Layer.Layer<StoreTagClass<any, "main">, UnknownError | TimeoutError, OtelTracer>
MainStoreLayer = const MainStore: StoreTagClass<any, "main">
MainStore.StoreTagClass<any, "main">.layer<unknown, Codec<Json, Json, never, never>>(props: StoreLayerProps<any, unknown, Codec<Json, Json, never, never>>): Layer.Layer<StoreTagClass<any, "main">, UnknownError | TimeoutError, OtelTracer>
Creates a layer that initializes the store
layer({ adapter: Adapter
adapter, batchUpdates: (run: () => void) => void
batchUpdates: (cb: () => void
cb) => cb: () => void
cb() })const const SettingsStoreLayer: Layer.Layer<StoreTagClass<any, "settings">, UnknownError | TimeoutError, OtelTracer>
SettingsStoreLayer = const SettingsStore: StoreTagClass<any, "settings">
SettingsStore.StoreTagClass<any, "settings">.layer<unknown, Codec<Json, Json, never, never>>(props: StoreLayerProps<any, unknown, Codec<Json, Json, never, never>>): Layer.Layer<StoreTagClass<any, "settings">, UnknownError | TimeoutError, OtelTracer>
Creates a layer that initializes the store
layer({ adapter: Adapter
adapter, batchUpdates: (run: () => void) => void
batchUpdates: (cb: () => void
cb) => cb: () => void
cb() })
// Compose layers together
// Both stores available in Effect codeconst const _program: Effect.Effect<{ mainStore: Store<any, {}>; settingsStore: Store<any, {}>;}, never, StoreTagClass<any, "main"> | StoreTagClass<any, "settings">>
_program = import Effect
Effect.const gen: <Effect.Effect<LiveStoreContextRunning<any>, never, StoreTagClass<any, "main">> | Effect.Effect<LiveStoreContextRunning<any>, never, StoreTagClass<any, "settings">>, { mainStore: Store<any, {}>; settingsStore: Store<any, {}>;}>(f: () => Generator<Effect.Effect<LiveStoreContextRunning<any>, never, StoreTagClass<any, "main">> | Effect.Effect<LiveStoreContextRunning<any>, never, StoreTagClass<...>>, { mainStore: Store<any, {}>; settingsStore: Store<any, {}>;}, never>) => Effect.Effect<...> (+1 overload)
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to use
Use when you want to write effectful code that looks and behaves like
synchronous code, while still handling asynchronous tasks, errors, and complex
control flow such as loops and conditions.
Generator functions work similarly to async/await but keep errors,
requirements, and interruption in the Effect type. You can yield* values
from effects and return the final result at the end.
Example (Sequencing effects with generators)
import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = ( total: number, discountRate: number): Effect.Effect<number, DiscountRateError> => discountRate === 0 ? Effect.fail(new DiscountRateError()) : Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() { const transactionAmount = yield* fetchTransactionAmount const discountRate = yield* fetchDiscountRate const discountedAmount = yield* applyDiscount( transactionAmount, discountRate ) const finalAmount = addServiceCharge(discountedAmount) return `Final amount to charge: ${finalAmount}`})
gen(function* () { const { store: Store<any, {}>
store: const mainStore: Store<any, {}>
mainStore } = yield* const MainStore: StoreTagClass<any, "main">
MainStore const { store: Store<any, {}>
store: const settingsStore: Store<any, {}>
settingsStore } = yield* const SettingsStore: StoreTagClass<any, "settings">
SettingsStore
// Each store is independently typed return { mainStore: Store<any, {}>
mainStore, settingsStore: Store<any, {}>
settingsStore }})
import { const defineMaterializer: <TEventDef extends State.SQLite.EventDef.AnyWithoutFn>(_eventDef: TEventDef, materializer: State.SQLite.Materializer<TEventDef>) => State.SQLite.Materializer<TEventDef>
Type-safe wrapper for defining a single materializer.
Useful when defining materializers separately from the materializers() builder.
The first argument provides type inference for the second.
defineMaterializer, import Events
Events, const makeSchema: <TInputSchema extends InputSchema>(inputSchema: TInputSchema) => FromInputSchema.DeriveSchema<TInputSchema>
makeSchema, import Schema
Schema, import State
State } from '@livestore/livestore'
const const tables: { readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables = { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos: import State
State.import SQLite
SQLite.function table<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}, Partial<...>>(args: { ...;} & Partial<...>): State.SQLite.TableDef<...> (+2 overloads)
Creates a SQLite table definition from columns or an Effect Schema.
This function supports two main ways to define a table:
- Using explicit column definitions
- Using an Effect Schema (either the
name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columnsconst usersTable = State.SQLite.table({ name: 'users', columns: { id: State.SQLite.text({ primaryKey: true }), name: State.SQLite.text({ nullable: false }), email: State.SQLite.text({ nullable: false }), age: State.SQLite.integer({ nullable: true }), },})
// Using Effect Schema with annotationsimport { Schema } from '@livestore/utils/effect'
const UserSchema = Schema.Struct({ id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement), email: Schema.String.pipe(State.SQLite.withUnique), name: Schema.String, active: Schema.Boolean.pipe(State.SQLite.withDefault(true)), createdAt: Schema.optional(Schema.Date),})
// Option 1: With explicit nameconst usersTable = State.SQLite.table({ name: 'users', schema: UserSchema,})
// Option 2: With name from schema annotation (title or identifier)const AnnotatedUserSchema = UserSchema.annotate({ title: 'users' })const usersTable2 = State.SQLite.table({ schema: AnnotatedUserSchema,})
// Adding indexesconst PostSchema = Schema.Struct({ id: Schema.String.pipe(State.SQLite.withPrimaryKey), title: Schema.String, authorId: Schema.String, createdAt: Schema.Date,}).annotate({ identifier: 'posts' })
const postsTable = State.SQLite.table({ schema: PostSchema, indexes: [ { name: 'idx_posts_author', columns: ['authorId'] }, { name: 'idx_posts_created', columns: ['createdAt'], isUnique: false }, ],})
table({ name: "todos"
name: 'todos', columns: { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}
columns: { id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;}
id: import State
State.import SQLite
SQLite.const text: <string, string, false, typeof NoDefault, true, false>(args: { schema?: Schema.Codec<string, string, never, never>; default?: typeof NoDefault; nullable?: false; primaryKey?: true; autoIncrement?: false;}) => { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;} (+1 overload)
text({ primaryKey?: true
primaryKey: true }), text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;}
text: import State
State.import SQLite
SQLite.const text: () => { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;} (+1 overload)
text(),// Define multiple typed store contextsconst const MainStore: StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "main">
MainStore = const Store: { Tag: <TSchema extends LiveStoreSchema, TStoreId extends string>(schema: TSchema, storeId: TStoreId) => StoreTagClass<TSchema, TStoreId>;}
Store utilities for Effect integration.
Store.type Tag: <FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "main">(schema: FromInputSchema.DeriveSchema<...>, storeId: "main") => StoreTagClass<...>
Create a typed store context class for use with Effect.
Returns a class that extends Context.Service, making it directly yieldable in Effect code.
The class includes static methods for creating layers and accessors for common operations.
Tag(const mainSchema: FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>
mainSchema, 'main')const const SettingsStore: StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "settings">
SettingsStore = const Store: { Tag: <TSchema extends LiveStoreSchema, TStoreId extends string>(schema: TSchema, storeId: TStoreId) => StoreTagClass<TSchema, TStoreId>;}
Store utilities for Effect integration.
Store.type Tag: <FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "settings">(schema: FromInputSchema.DeriveSchema<...>, storeId: "settings") => StoreTagClass<...>
Create a typed store context class for use with Effect.
Returns a class that extends Context.Service, making it directly yieldable in Effect code.
The class includes static methods for creating layers and accessors for common operations.
Tag(const settingsSchema: FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>
settingsSchema, 'settings')
// Each store has its own layerconst const adapter: Adapter
adapter = function makeAdapter({ sync, ...options }: NodeAdapterOptions & { sync?: SyncOptions;}): Adapter
Creates a single-threaded LiveStore adapter for Node.js applications.
This adapter runs the leader thread (persistence and sync) in the same thread as
your application. Suitable for CLI tools, scripts, and applications where simplicity
is preferred over maximum performance.
For production servers or performance-critical applications, consider makeWorkerAdapter
which runs persistence/sync in a separate worker thread.
makeAdapter({ NodeAdapterOptions.storage: { readonly type: ["in-memory"]; readonly importSnapshot?: any;} | { readonly type: ["fs"]; readonly baseDirectory?: string | undefined;}
storage: { type: string
type: 'fs' } })
const const MainStoreLayer: Layer.Layer<StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "main">, UnknownError | TimeoutError, OtelTracer>
MainStoreLayer = const MainStore: StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "main">
MainStore.StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<...>>; readonly todoCompleted: EventDef<...>; }; state: InternalState; }>, "main">.layer<unknown, Codec<Json, Json, never, never>>(props: StoreLayerProps<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, unknown, Codec<...>>): Layer.Layer<...>
Creates a layer that initializes the store
layer({ adapter: Adapter
adapter, batchUpdates: (run: () => void) => void
batchUpdates: (cb: () => void
cb) => cb: () => void
cb() })const const SettingsStoreLayer: Layer.Layer<StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "settings">, UnknownError | TimeoutError, OtelTracer>
SettingsStoreLayer = const SettingsStore: StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "settings">
SettingsStore.StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<...>>; readonly todoCompleted: EventDef<...>; }; state: InternalState; }>, "settings">.layer<unknown, Codec<Json, Json, never, never>>(props: StoreLayerProps<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, unknown, Codec<...>>): Layer.Layer<...>
Creates a layer that initializes the store
layer({ adapter: Adapter
adapter, batchUpdates: (run: () => void) => void
batchUpdates: (cb: () => void
cb) => cb: () => void
cb() })
// Compose layers togetherconst const _AllStoresLayer: Layer.Layer<StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "main"> | StoreTagClass<...>, UnknownError | TimeoutError, OtelTracer>
_AllStoresLayer = import Layer
Layer.const mergeAll: <[Layer.Layer<StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "main">, UnknownError | TimeoutError, OtelTracer>, Layer.Layer<...>]>(layers_0: Layer.Layer<...>, layers_1: Layer.Layer<...>) => Layer.Layer<...>
Combines all the provided layers concurrently, creating a new layer with
merged input, error, and output types.
When to use
Use when you need to combine multiple independent layers.
Details
All layers are built concurrently, and their outputs are merged into a single layer.
If multiple merged layers depend on the same layer value, that dependency is
shared by default. Reuse a named layer value when you want services to share
the same resource, such as one database pool.
Example (Merging independent layers)
import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, { readonly query: (sql: string) => Effect.Effect<string>}>()("Database") {}
class Logger extends Context.Service<Logger, { readonly log: (msg: string) => Effect.Effect<void>}>()("Logger") {}
const dbLayer = Layer.succeed(Database, { query: Effect.fn("Database.query")((sql: string) => Effect.succeed("result"))})const loggerLayer = Layer.succeed(Logger, { log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => console.log(msg)))})
const mergedLayer = Layer.mergeAll(dbLayer, loggerLayer)
mergeAll(const MainStoreLayer: Layer.Layer<StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "main">, UnknownError | TimeoutError, OtelTracer>
MainStoreLayer, const SettingsStoreLayer: Layer.Layer<StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "settings">, UnknownError | TimeoutError, OtelTracer>
SettingsStoreLayer)
// Both stores available in Effect codeconst const _program: Effect.Effect<{ mainStore: Store<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState; }>, {}>; settingsStore: Store<...>;}, never, StoreTagClass<...> | StoreTagClass<...>>
_program = import Effect
Effect.const gen: <Effect.Effect<LiveStoreContextRunning<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>>, never, StoreTagClass<...>> | Effect.Effect<...>, { ...;}>(f: () => Generator<...>) => Effect.Effect<...> (+1 overload)
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to use
Use when you want to write effectful code that looks and behaves like
synchronous code, while still handling asynchronous tasks, errors, and complex
control flow such as loops and conditions.
Generator functions work similarly to async/await but keep errors,
requirements, and interruption in the Effect type. You can yield* values
from effects and return the final result at the end.
Example (Sequencing effects with generators)
import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = ( total: number, discountRate: number): Effect.Effect<number, DiscountRateError> => discountRate === 0 ? Effect.fail(new DiscountRateError()) : Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() { const transactionAmount = yield* fetchTransactionAmount const discountRate = yield* fetchDiscountRate const discountedAmount = yield* applyDiscount( transactionAmount, discountRate ) const finalAmount = addServiceCharge(discountedAmount) return `Final amount to charge: ${finalAmount}`})
gen(function* () { const { store: Store<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, {}>
store: const mainStore: Store<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, {}>
mainStore } = yield* const MainStore: StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "main">
MainStore const { store: Store<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, {}>
store: const settingsStore: Store<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, {}>
settingsStore } = yield* const SettingsStore: StoreTagClass<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, "settings">
SettingsStore
// Each store is independently typed return { mainStore: Store<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, {}>
mainStore, settingsStore: Store<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; readonly todoCompleted: EventDef<"v1.TodoCompleted", Struct.ReadonlySide<{ readonly id: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; }, "Encoded">>; }; state: InternalState;}>, {}>
settingsStore }})
For more Effect patterns including Effect Atom integration, see the Effect patterns page.
Multiple stores
Section titled “Multiple stores”You can create and use multiple stores in the same app. This can be useful when breaking up your data model into smaller pieces.
Sync Status
Section titled “Sync Status”LiveStore provides APIs to monitor the synchronization status between the client session and the leader thread. This is useful for displaying sync indicators or performing health checks.
SyncStatus type
Section titled “SyncStatus type”type SyncStatus = { localHead: string // e.g., "e5.2" or "e5.2r1" upstreamHead: string // e.g., "e3" pendingCount: number // Number of events pending sync isSynced: boolean // true when pendingCount === 0}store.syncStatus()
Section titled “store.syncStatus()”Returns the current sync status synchronously.
const status = store.syncStatus()if (!status.isSynced) { console.log(`${status.pendingCount} events pending sync`)}store.subscribeSyncStatus(callback)
Section titled “store.subscribeSyncStatus(callback)”Subscribes to sync status changes. The callback is invoked immediately with the current status and whenever the sync state changes.
const unsubscribe = store.subscribeSyncStatus((status) => { updateUI(status.isSynced ? 'Synced' : 'Syncing...')})
// Later, stop listeningunsubscribe()store.syncStatusStream()
Section titled “store.syncStatusStream()”Returns an Effect Stream of sync status updates. For Effect-based workflows.
import { Effect, Stream } from 'effect'
store.syncStatusStream().pipe( Stream.tap((status) => Effect.log(`Sync: ${status.isSynced}`)), Stream.runDrain,)For React, see store.useSyncStatus().
Development/debugging helpers
Section titled “Development/debugging helpers”A store instance also exposes a _dev property that contains some helpful methods for development. For convenience you can access a store on globalThis/window like via __debugLiveStore.default._dev (default is the store id):
// Download the SQLite database__debugLiveStore.default._dev.downloadDb()
// Download the eventlog database__debugLiveStore.default._dev.downloadEventlogDb()
// Reset the store__debugLiveStore.default._dev.hardReset()
// See the current sync state__debugLiveStore.default._dev.syncStates()