Reactivity system
LiveStore has a high-performance, fine-grained reactivity system built in which is similar to Signals (e.g. in SolidJS).
Defining reactive state
Section titled “Defining reactive state”LiveStore provides 3 types of reactive state:
- Reactive SQL queries on top of SQLite state (
queryDb()) - Reactive state values (
signal()) - Reactive computed values (
computed())
Reactive state variables end on a $ by convention (e.g. todos$). The label option is optional but can be used to identify the reactive state variable in the devtools.
Reactive SQL queries
Section titled “Reactive SQL queries”import { const queryDb: { <TResultSchema, TResult = TResultSchema>(queryInput: QueryInputRaw<TResultSchema, ReadonlyArray<any>> | QueryBuilder<TResultSchema, any, any>, options?: { map?: (rows: TResultSchema) => TResult; label?: string; deps?: DepKey; }): LiveQueryDef<TResult>; <TResultSchema, TResult = TResultSchema>(queryInput: ((get: GetAtomResult) => QueryInputRaw<TResultSchema, ReadonlyArray<any>>) | ((get: GetAtomResult) => QueryBuilder<TResultSchema, any, any>), options?: { map?: (rows: TResultSchema) => TResult; label?: string; deps?: DepKey; }): LiveQueryDef<TResult>;}
NOTE queryDb is only supposed to read data. Don't use it to insert/update/delete data but use events instead.
When using contextual data when constructing the query, please make sure to include it in the deps option.
queryDb, const signal: <T>(defaultValue: T, options?: { label?: string;}) => SignalDef<T>
Creates a reactive signal for ephemeral, local-only state that isn't persisted to the database.
Signals are useful for UI state that needs to trigger query re-evaluation but shouldn't be
synced across clients or stored permanently—such as search filters, selected items, or
temporary form values.
Unlike database-backed state (via events), signals:
- Are not persisted or synced
- Exist only for the lifetime of the Store
- Can hold any value type (primitives, objects, functions)
signal } from '@livestore/livestore'import { import tables
tables } from '../framework-integrations/react/schema.ts'
const const uiState$: SignalDef<{ showCompleted: boolean;}>
uiState$ = signal<{ showCompleted: boolean;}>(defaultValue: { showCompleted: boolean;}, options?: { label?: string;}): SignalDef<{ showCompleted: boolean;}>
Creates a reactive signal for ephemeral, local-only state that isn't persisted to the database.
Signals are useful for UI state that needs to trigger query re-evaluation but shouldn't be
synced across clients or stored permanently—such as search filters, selected items, or
temporary form values.
Unlike database-backed state (via events), signals:
- Are not persisted or synced
- Exist only for the lifetime of the Store
- Can hold any value type (primitives, objects, functions)
signal({ showCompleted: boolean
showCompleted: false }, { label?: string
label: 'uiState$' })
const const todos$: LiveQueryDef<unknown, "def">
todos$ = queryDb<unknown, unknown>(queryInput: QueryInputRaw<unknown, readonly any[]> | QueryBuilder<unknown, any, any>, options?: { map?: (rows: unknown) => unknown; label?: string; deps?: DepKey;} | undefined): LiveQueryDef<unknown, "def"> (+1 overload)
NOTE queryDb is only supposed to read data. Don't use it to insert/update/delete data but use events instead.
When using contextual data when constructing the query, please make sure to include it in the deps option.
queryDb(import tables
tables.any
todos.any
orderBy('createdAt', 'desc'), { label?: string
Used for debugging / devtools
label: 'todos$' })
{ const const todos$: LiveQueryDef<unknown, "def">
todos$ = queryDb<unknown, unknown>(queryInput: ((get: GetAtomResult) => QueryInputRaw<unknown, readonly any[]>) | ((get: GetAtomResult) => QueryBuilder<unknown, any, any>), options?: { map?: (rows: unknown) => unknown; label?: string; deps?: DepKey;} | undefined): LiveQueryDef<unknown, "def"> (+1 overload)
NOTE queryDb is only supposed to read data. Don't use it to insert/update/delete data but use events instead.
When using contextual data when constructing the query, please make sure to include it in the deps option.
queryDb( (get: GetAtomResult
get) => { const { const showCompleted: boolean
showCompleted } = get: <{ showCompleted: boolean;}>(atom: SignalDef<{ showCompleted: boolean;}> | Atom<{ showCompleted: boolean;}, any, RefreshReason> | LiveQueryDef<{ showCompleted: boolean;}, "def"> | LiveQuery<{ showCompleted: boolean;}> | ISignal<{ showCompleted: boolean;}>, otelContext?: Context, debugRefreshReason?: RefreshReason) => { showCompleted: boolean;}
get(const uiState$: SignalDef<{ showCompleted: boolean;}>
uiState$) return import tables
tables.any
todos.any
where(const showCompleted: boolean
showCompleted === true ? { completed: boolean
completed: true } : {}) }, { label?: string
Used for debugging / devtools
label: 'todos$' }, )}
import { const queryDb: { <TResultSchema, TResult = TResultSchema>(queryInput: QueryInputRaw<TResultSchema, ReadonlyArray<any>> | QueryBuilder<TResultSchema, any, any>, options?: { map?: (rows: TResultSchema) => TResult; label?: string; deps?: DepKey; }): LiveQueryDef<TResult>; <TResultSchema, TResult = TResultSchema>(queryInput: ((get: GetAtomResult) => QueryInputRaw<TResultSchema, ReadonlyArray<any>>) | ((get: GetAtomResult) => QueryBuilder<TResultSchema, any, any>), options?: { map?: (rows: TResultSchema) => TResult; label?: string; deps?: DepKey; }): LiveQueryDef<TResult>;}
NOTE queryDb is only supposed to read data. Don't use it to insert/update/delete data but use events instead.
When using contextual data when constructing the query, please make sure to include it in the deps option.
queryDb, const signal: <T>(defaultValue: T, options?: { label?: string;}) => SignalDef<T>
Creates a reactive signal for ephemeral, local-only state that isn't persisted to the database.
Signals are useful for UI state that needs to trigger query re-evaluation but shouldn't be
synced across clients or stored permanently—such as search filters, selected items, or
temporary form values.
Unlike database-backed state (via events), signals:
- Are not persisted or synced
- Exist only for the lifetime of the Store
- Can hold any value type (primitives, objects, functions)
signal } from '@livestore/livestore'
import { const tables: { 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; }; readonly createdAt: { ...; }; }>, WithDefaults<...>, Struct<...>>; readonly uiState: ClientDocumentTableDef<...>;}
tables } from '../framework-integrations/react/schema.ts'
const const uiState$: SignalDef<{ showCompleted: boolean;}>
uiState$ = signal<{ showCompleted: boolean;}>(defaultValue: { showCompleted: boolean;}, options?: { label?: string;}): SignalDef<{ showCompleted: boolean;}>
Creates a reactive signal for ephemeral, local-only state that isn't persisted to the database.
Signals are useful for UI state that needs to trigger query re-evaluation but shouldn't be
synced across clients or stored permanently—such as search filters, selected items, or
temporary form values.
Unlike database-backed state (via events), signals:
- Are not persisted or synced
- Exist only for the lifetime of the Store
- Can hold any value type (primitives, objects, functions)
signal({ showCompleted: boolean
showCompleted: false }, { label?: string
label: 'uiState$' })
const const todos$: LiveQueryDef<readonly Struct.ReadonlySide<{ readonly id: Codec<string, string, never, never>; readonly text: Codec<string, string, never, never>; readonly completed: Codec<boolean, number, never, never>; readonly createdAt: Codec<Date, string, never, never>;}, "Type">[], "def">
todos$ = queryDb<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>; readonly createdAt: Codec<Date, string, never, never>;}, "Type">[], readonly Struct.ReadonlySide<{ readonly id: Codec<string, string, never, never>; readonly text: Codec<string, string, never, never>; readonly completed: Codec<boolean, number, never, never>; readonly createdAt: Codec<Date, string, never, never>;}, "Type">[]>(queryInput: QueryInputRaw<...> | QueryBuilder<...>, options?: { ...;} | undefined): LiveQueryDef<...> (+1 overload)
NOTE queryDb is only supposed to read data. Don't use it to insert/update/delete data but use events instead.
When using contextual data when constructing the query, please make sure to include it in the deps option.
queryDb(const tables: { 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; }; readonly createdAt: { ...; }; }>, WithDefaults<...>, Struct<...>>; readonly uiState: ClientDocumentTableDef<...>;}
tables.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; }; readonly createdAt: { ...; };}>, WithDefaults<...>, Struct<...>>
todos.orderBy: <"createdAt">(col: "createdAt", direction: "asc" | "desc") => QueryBuilder<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>; readonly createdAt: Codec<Date, string, never, never>;}, "Type">[], TableDefBase<SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { ...; }; readonly completed: { ...; }; readonly createdAt: { ...; };}>, WithDefaults<...>>, "onConflict" | "returning"> (+1 overload)
orderBy('createdAt', 'desc'), { label?: string
Used for debugging / devtools
label: 'todos$' })
{ const const todos$: LiveQueryDef<readonly Struct.ReadonlySide<{ readonly id: Codec<string, string, never, never>; readonly text: Codec<string, string, never, never>; readonly completed: Codec<boolean, number, never, never>; readonly createdAt: Codec<Date, string, never, never>;}, "Type">[], "def">
todos$ = queryDb<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>; readonly createdAt: Codec<Date, string, never, never>;}, "Type">[], readonly Struct.ReadonlySide<{ readonly id: Codec<string, string, never, never>; readonly text: Codec<string, string, never, never>; readonly completed: Codec<boolean, number, never, never>; readonly createdAt: Codec<Date, string, never, never>;}, "Type">[]>(queryInput: ((get: GetAtomResult) => QueryInputRaw<...>) | ((get: GetAtomResult) => QueryBuilder<...>), options?: { ...;} | undefined): LiveQueryDef<...> (+1 overload)
NOTE queryDb is only supposed to read data. Don't use it to insert/update/delete data but use events instead.
When using contextual data when constructing the query, please make sure to include it in the deps option.
queryDb( (get: GetAtomResult
get) => { const { const showCompleted: boolean
showCompleted } = get: <{ showCompleted: boolean;}>(atom: SignalDef<{ showCompleted: boolean;}> | Atom<{ showCompleted: boolean;}, any, RefreshReason> | LiveQueryDef<{ showCompleted: boolean;}, "def"> | LiveQuery<{ showCompleted: boolean;}> | ISignal<{ showCompleted: boolean;}>, otelContext?: Context, debugRefreshReason?: RefreshReason) => { showCompleted: boolean;}
get(const uiState$: SignalDef<{ showCompleted: boolean;}>
uiState$) return const tables: { 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; }; readonly createdAt: { ...; }; }>, WithDefaults<...>, Struct<...>>; readonly uiState: ClientDocumentTableDef<...>;}
tables.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; }; readonly createdAt: { ...; };}>, WithDefaults<...>, Struct<...>>
todos.where: (params: Partial<{ readonly id: string | { op: Exclude<QueryBuilder<TResult, TTableDef extends TableDefBase<any, any>, TWithout extends QueryBuilder.ApiFeature = never>.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[]; } | undefined; readonly text: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { ...; } | undefined; readonly completed: boolean | ... 2 more ... | undefined; readonly createdAt: Date | ... 2 more ... | undefined;}>) => QueryBuilder<...> (+3 overloads)
where(const showCompleted: boolean
showCompleted === true ? { completed?: boolean | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: boolean;} | { op: QueryBuilder.WhereOps.MultiValue; value: readonly boolean[];} | undefined
completed: true } : {}) }, { label?: string
Used for debugging / devtools
label: 'todos$' }, )}
Signals
Section titled “Signals”Signals are reactive state values that can be set and get. This can be useful for state that is not materialized from events into SQLite tables.
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, const signal: <T>(defaultValue: T, options?: { label?: string;}) => SignalDef<T>
Creates a reactive signal for ephemeral, local-only state that isn't persisted to the database.
Signals are useful for UI state that needs to trigger query re-evaluation but shouldn't be
synced across clients or stored permanently—such as search filters, selected items, or
temporary form values.
Unlike database-backed state (via events), signals:
- Are not persisted or synced
- Exist only for the lifetime of the Store
- Can hold any value type (primitives, objects, functions)
signal } from '@livestore/livestore'
import type { import schema
schema } from '../framework-integrations/react/schema.ts'
declare const const store: Store<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<typeof import schema
schema>
const const now$: SignalDef<number>
now$ = signal<number>(defaultValue: number, options?: { label?: string;}): SignalDef<number>
Creates a reactive signal for ephemeral, local-only state that isn't persisted to the database.
Signals are useful for UI state that needs to trigger query re-evaluation but shouldn't be
synced across clients or stored permanently—such as search filters, selected items, or
temporary form values.
Unlike database-backed state (via events), signals:
- Are not persisted or synced
- Exist only for the lifetime of the Store
- Can hold any value type (primitives, objects, functions)
signal(var Date: DateConstructor
Enables basic storage and retrieval of dates and times.
Date.DateConstructor.now(): number
Returns the number of milliseconds elapsed since midnight, January 1, 1970 Universal Coordinated Time (UTC).
now(), { label?: string
label: 'now$' })
function setInterval<[]>(callback: () => void, delay?: number): NodeJS.Timeout (+4 overloads)
setInterval(() => { const store: Store<any, {}>
store.Store<any, {}>.setSignal: <number>(signalDef: SignalDef<number>, value: number | ((prev: number) => number)) => void
Set the value of a signal
setSignal(const now$: SignalDef<number>
now$, var Date: DateConstructor
Enables basic storage and retrieval of dates and times.
Date.DateConstructor.now(): number
Returns the number of milliseconds elapsed since midnight, January 1, 1970 Universal Coordinated Time (UTC).
now())}, 1000)
const const num$: SignalDef<number>
num$ = signal<number>(defaultValue: number, options?: { label?: string;}): SignalDef<number>
Creates a reactive signal for ephemeral, local-only state that isn't persisted to the database.
Signals are useful for UI state that needs to trigger query re-evaluation but shouldn't be
synced across clients or stored permanently—such as search filters, selected items, or
temporary form values.
Unlike database-backed state (via events), signals:
- Are not persisted or synced
- Exist only for the lifetime of the Store
- Can hold any value type (primitives, objects, functions)
signal(0, { label?: string
label: 'num$' })const const increment: () => void
increment = () => const store: Store<any, {}>
store.Store<any, {}>.setSignal: <number>(signalDef: SignalDef<number>, value: number | ((prev: number) => number)) => void
Set the value of a signal
setSignal(const num$: SignalDef<number>
num$, (prev: number
prev) => prev: number
prev + 1)
const increment: () => void
increment()const increment: () => void
increment()
var console: Console
console.Console.log(...data: any[]): void (+2 overloads)
The console.log() static method outputs a message to the console.
log(const store: Store<any, {}>
store.Store<any, {}>.query: <number>(query: Queryable<number> | { query: string; bindValues: Bindable; schema?: Decoder<number, never>;}, options?: { otelContext?: Context; debugRefreshReason?: RefreshReason;}) => number
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 num$: SignalDef<number>
num$))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, type SessionIdSymbol = typeof SessionIdSymbolconst SessionIdSymbol: typeof SessionIdSymbol
Can be used in queries to refer to the current session id.
Will be replaced with the actual session id at runtime.
In client document table:
const uiState = State.SQLite.clientDocument({ name: 'ui_state', schema: Schema.Struct({ theme: Schema.Literals(['dark', 'light', 'system']), user: Schema.String, showToolbar: Schema.Boolean, }), default: { value: defaultFrontendState, id: SessionIdSymbol },})
Or in a client document query:
const query$ = queryDb(tables.uiState.get(SessionIdSymbol))
SessionIdSymbol, import State
State } from '@livestore/livestore'
export 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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>;}
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; }; readonly createdAt: { ...; };}>, 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; }; readonly createdAt: { ...; };}, 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; }; readonly createdAt: { ...; };}
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 }), createdAt: { columnType: "text"; schema: Schema.Codec<Date, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;}
createdAt: import State
State.import SQLite
SQLite.const datetime: () => { columnType: "text"; schema: Schema.Codec<Date, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;} (+1 overload)
datetime(), }, }), uiState: State.SQLite.ClientDocumentTableDef<"UiState", Schema.Struct.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Encoded">, { partialSet: true; default: { id: typeof SessionIdSymbol; value: { readonly newTodoText: ""; readonly filter: "all"; }; };}>
uiState: import State
State.import SQLite
SQLite.clientDocument<"UiState", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Encoded">, { readonly name: "UiState"; readonly schema: Schema.Struct<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>; }>; readonly default: { ...; };}>({ name, schema: valueSchema, ...inputOptions }: { ...;} & { readonly name: "UiState"; readonly schema: Schema.Struct<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>; }>; readonly default: { ...; };}): State.SQLite.ClientDocumentTableDef<...>export clientDocument
Special:
- Synced across client sessions (e.g. tabs) but not across different clients
- Derived setters
- Emits client-only events
- Has implicit setter-materializers
- Similar to
React.useState (except it's persisted)
Careful:
- When changing the table definitions in a non-backwards compatible way, the state might be lost without
explicit materializers to handle the old auto-generated events
Usage:
// Querying data// `'some-id'` can be ommited for SessionIdSymbolstore.queryDb(clientDocumentTable.get('some-id'))
// Setting data// Again, `'some-id'` can be ommited for SessionIdSymbolstore.commit(clientDocumentTable.set({ someField: 'some-value' }, 'some-id'))
clientDocument({ name: "UiState"
name: 'UiState', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Encoded">, never, never> & Schema.Struct<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}>
schema: import Schema
Schema.function Struct<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}>(fields: { readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}): Schema.Struct<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}>
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({ newTodoText: Schema.String
newTodoText: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String, filter: Schema.Literals<readonly ["all", "active", "completed"]>
filter: import Schema
Schema.function Literals<readonly ["all", "active", "completed"]>(literals: readonly ["all", "active", "completed"]): Schema.Literals<readonly ["all", "active", "completed"]>
Creates a union schema from an array of literal values.
Example (Defining status codes)
import { Schema } from "effect"
const schema = Schema.Literals(["active", "inactive", "pending"])// accepts "active", "inactive", or "pending"
Literals(['all', 'active', 'completed']), }), default: { readonly id: typeof SessionIdSymbol; readonly value: { readonly newTodoText: ""; readonly filter: "all"; };}
default: { id: typeof SessionIdSymbol
id: const SessionIdSymbol: typeof SessionIdSymbol
Can be used in queries to refer to the current session id.
Will be replaced with the actual session id at runtime.
In client document table:
const uiState = State.SQLite.clientDocument({ name: 'ui_state', schema: Schema.Struct({ theme: Schema.Literals(['dark', 'light', 'system']), user: Schema.String, showToolbar: Schema.Boolean, }), default: { value: defaultFrontendState, id: SessionIdSymbol },})
Or in a client document query:
const query$ = queryDb(tables.uiState.get(SessionIdSymbol))
SessionIdSymbol, value: { readonly newTodoText: ""; readonly filter: "all";}
value: { newTodoText: ""
newTodoText: '', filter: "all"
filter: 'all' } }, }),} 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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>;}
const
export const const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Encoded">>;}
events = { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Encoded">>
todoCreated: import Events
Events.synced<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Encoded">>(args: { name: "v1.TodoCreated"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<...>, 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; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}>(fields: { readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}): Schema.Struct<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}>
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, createdAt: Schema.DateFromString
createdAt: import Schema
Schema.const DateFromString: Schema.DateFromString
Type-level representation of
DateFromString
.
Schema that decodes a string into a JavaScript Date.
When to use
Use to model string-encoded dates that decode to JavaScript Date objects
and encode back to strings.
Details
Decoding:
The string is passed to JavaScript Date construction.
Encoding:
A valid Date is encoded as an ISO string; an invalid Date is encoded as
"Invalid Date".
Gotchas
Invalid date strings can decode to invalid Date instances.
DateFromString.Bottom<unknown, unknown, unknown, unknown, Declaration, decodeTo<Date, String, never, never>, unknown, unknown, readonly [], unknown, "readonly", "required", "no-default", "readonly", "required">.check(checks_0: Check<Date>, ...checks: Check<Date>[]): Schema.DateFromString
check(import Schema
Schema.function isDateValid(annotations?: Schema.Annotations.Filter): Filter<globalThis.Date>
Validates that a Date object represents a valid date (not an invalid date
like new Date("invalid")).
Details
JSON Schema:
This check does not have a direct JSON Schema equivalent, as JSON Schema
validates date strings, not Date objects.
Arbitrary:
When generating test data with fast-check, this applies a valid: true
constraint to ensure generated Date objects are valid.
isDateValid()), }), }),} as type const = { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Encoded">>;}
events, { [const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Encoded">>;}
events.todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "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; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Encoded">>>(_eventDef: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<...>>, 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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Encoded">>;}
events.todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Encoded">>
todoCreated, ({ id: string
id, text: string
text, createdAt: Date
createdAt }) => 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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>;}
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; }; readonly createdAt: { ...; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos.insert: (values: { readonly text: string; readonly id: string; readonly createdAt: Date; 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>; readonly createdAt: Schema.Codec<Date, string, never, never>;}, "Type">[], State.SQLite.TableDefBase<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; ... 4 more ...; autoIncrement: false; }; readonly text: { ...; }; readonly completed: { ...; }; readonly createdAt: { ...; };}>, 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, createdAt: Date
createdAt }), ),})
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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>; }; 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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>; }; 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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>;}
tables, materializers: { "v1.TodoCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Encoded">>;}
events, state: InternalState
state })Computed values
Section titled “Computed values”import { const computed: <TResult>(fn: (get: GetAtomResult) => TResult, options?: { label?: string; deps?: DepKey;}) => LiveQueryDef<TResult>
Creates a derived query that computes a value from other queries or signals.
Computed queries are memoized—they only re-evaluate when their dependencies change,
and if the new result equals the previous result, downstream dependents won't re-run.
Use them for expensive calculations, aggregations, or transformations.
The get function inside computed establishes reactive dependencies automatically.
When any dependency updates, the computed re-evaluates.
computed, const signal: <T>(defaultValue: T, options?: { label?: string;}) => SignalDef<T>
Creates a reactive signal for ephemeral, local-only state that isn't persisted to the database.
Signals are useful for UI state that needs to trigger query re-evaluation but shouldn't be
synced across clients or stored permanently—such as search filters, selected items, or
temporary form values.
Unlike database-backed state (via events), signals:
- Are not persisted or synced
- Exist only for the lifetime of the Store
- Can hold any value type (primitives, objects, functions)
signal } from '@livestore/livestore'const const num$: SignalDef<number>
num$ = signal<number>(defaultValue: number, options?: { label?: string;}): SignalDef<number>
Creates a reactive signal for ephemeral, local-only state that isn't persisted to the database.
Signals are useful for UI state that needs to trigger query re-evaluation but shouldn't be
synced across clients or stored permanently—such as search filters, selected items, or
temporary form values.
Unlike database-backed state (via events), signals:
- Are not persisted or synced
- Exist only for the lifetime of the Store
- Can hold any value type (primitives, objects, functions)
signal(0, { label?: string
label: 'num$' })const const duplicated$: LiveQueryDef<number, "def">
duplicated$ = computed<number>(fn: (get: GetAtomResult) => number, options?: { label?: string; deps?: DepKey;}): LiveQueryDef<number, "def">
Creates a derived query that computes a value from other queries or signals.
Computed queries are memoized—they only re-evaluate when their dependencies change,
and if the new result equals the previous result, downstream dependents won't re-run.
Use them for expensive calculations, aggregations, or transformations.
The get function inside computed establishes reactive dependencies automatically.
When any dependency updates, the computed re-evaluates.
computed((get: GetAtomResult
get) => get: <number>(atom: SignalDef<number> | Atom<number, any, RefreshReason> | LiveQueryDef<number, "def"> | LiveQuery<number> | ISignal<number>, otelContext?: Context, debugRefreshReason?: RefreshReason) => number
get(const num$: SignalDef<number>
num$) * 2, { label?: string
label: 'duplicated$' })
Accessing reactive state
Section titled “Accessing reactive state”Reactive state is always bound to a Store instance. You can access the current value of reactive state the following ways:
Using the Store instance
Section titled “Using the Store instance”import { const queryDb: { <TResultSchema, TResult = TResultSchema>(queryInput: QueryInputRaw<TResultSchema, ReadonlyArray<any>> | QueryBuilder<TResultSchema, any, any>, options?: { map?: (rows: TResultSchema) => TResult; label?: string; deps?: DepKey; }): LiveQueryDef<TResult>; <TResultSchema, TResult = TResultSchema>(queryInput: ((get: GetAtomResult) => QueryInputRaw<TResultSchema, ReadonlyArray<any>>) | ((get: GetAtomResult) => QueryBuilder<TResultSchema, any, any>), options?: { map?: (rows: TResultSchema) => TResult; label?: string; deps?: DepKey; }): LiveQueryDef<TResult>;}
NOTE queryDb is only supposed to read data. Don't use it to insert/update/delete data but use events instead.
When using contextual data when constructing the query, please make sure to include it in the deps option.
queryDb, 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 { type import schema
schema, import tables
tables } from '../framework-integrations/react/schema.ts'
declare const const store: Store<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<typeof import schema
schema>
const const count$: LiveQueryDef<unknown, "def">
count$ = queryDb<unknown, unknown>(queryInput: QueryInputRaw<unknown, readonly any[]> | QueryBuilder<unknown, any, any>, options?: { map?: (rows: unknown) => unknown; label?: string; deps?: DepKey;} | undefined): LiveQueryDef<unknown, "def"> (+1 overload)
NOTE queryDb is only supposed to read data. Don't use it to insert/update/delete data but use events instead.
When using contextual data when constructing the query, please make sure to include it in the deps option.
queryDb(import tables
tables.any
todos.any
count(), { label?: string
Used for debugging / devtools
label: 'count$' })const const count: unknown
count = const store: Store<any, {}>
store.Store<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(const count$: LiveQueryDef<unknown, "def">
count$)var console: Console
console.Console.log(...data: any[]): void (+2 overloads)
The console.log() static method outputs a message to the console.
log(const count: unknown
count)
const const unsubscribe: Unsubscribe
unsubscribe = const store: Store<any, {}>
store.Store<any, {}>.subscribe: <unknown>(query: Queryable<unknown>, onUpdate: (value: unknown) => void, options?: SubscribeOptions<unknown> | undefined) => Unsubscribe (+1 overload)
subscribe(const count$: LiveQueryDef<unknown, "def">
count$, (value: unknown
value) => { var console: Console
console.Console.log(...data: any[]): void (+2 overloads)
The console.log() static method outputs a message to the console.
log(value: unknown
value)})
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, type SessionIdSymbol = typeof SessionIdSymbolconst SessionIdSymbol: typeof SessionIdSymbol
Can be used in queries to refer to the current session id.
Will be replaced with the actual session id at runtime.
In client document table:
const uiState = State.SQLite.clientDocument({ name: 'ui_state', schema: Schema.Struct({ theme: Schema.Literals(['dark', 'light', 'system']), user: Schema.String, showToolbar: Schema.Boolean, }), default: { value: defaultFrontendState, id: SessionIdSymbol },})
Or in a client document query:
const query$ = queryDb(tables.uiState.get(SessionIdSymbol))
SessionIdSymbol, import State
State } from '@livestore/livestore'
export 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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>;}
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; }; readonly createdAt: { ...; };}>, 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; }; readonly createdAt: { ...; };}, 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; }; readonly createdAt: { ...; };}
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 }), createdAt: { columnType: "text"; schema: Schema.Codec<Date, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;}
createdAt: import State
State.import SQLite
SQLite.const datetime: () => { columnType: "text"; schema: Schema.Codec<Date, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;} (+1 overload)
datetime(), }, }), uiState: State.SQLite.ClientDocumentTableDef<"UiState", Schema.Struct.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Encoded">, { partialSet: true; default: { id: typeof SessionIdSymbol; value: { readonly newTodoText: ""; readonly filter: "all"; }; };}>
uiState: import State
State.import SQLite
SQLite.clientDocument<"UiState", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Encoded">, { readonly name: "UiState"; readonly schema: Schema.Struct<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>; }>; readonly default: { ...; };}>({ name, schema: valueSchema, ...inputOptions }: { ...;} & { readonly name: "UiState"; readonly schema: Schema.Struct<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>; }>; readonly default: { ...; };}): State.SQLite.ClientDocumentTableDef<...>export clientDocument
Special:
- Synced across client sessions (e.g. tabs) but not across different clients
- Derived setters
- Emits client-only events
- Has implicit setter-materializers
- Similar to
React.useState (except it's persisted)
Careful:
- When changing the table definitions in a non-backwards compatible way, the state might be lost without
explicit materializers to handle the old auto-generated events
Usage:
// Querying data// `'some-id'` can be ommited for SessionIdSymbolstore.queryDb(clientDocumentTable.get('some-id'))
// Setting data// Again, `'some-id'` can be ommited for SessionIdSymbolstore.commit(clientDocumentTable.set({ someField: 'some-value' }, 'some-id'))
clientDocument({ name: "UiState"
name: 'UiState', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Encoded">, never, never> & Schema.Struct<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}>
schema: import Schema
Schema.function Struct<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}>(fields: { readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}): Schema.Struct<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}>
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({ newTodoText: Schema.String
newTodoText: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String, filter: Schema.Literals<readonly ["all", "active", "completed"]>
filter: import Schema
Schema.function Literals<readonly ["all", "active", "completed"]>(literals: readonly ["all", "active", "completed"]): Schema.Literals<readonly ["all", "active", "completed"]>
Creates a union schema from an array of literal values.
Example (Defining status codes)
import { Schema } from "effect"
const schema = Schema.Literals(["active", "inactive", "pending"])// accepts "active", "inactive", or "pending"
Literals(['all', 'active', 'completed']), }), default: { readonly id: typeof SessionIdSymbol; readonly value: { readonly newTodoText: ""; readonly filter: "all"; };}
default: { id: typeof SessionIdSymbol
id: const SessionIdSymbol: typeof SessionIdSymbol
Can be used in queries to refer to the current session id.
Will be replaced with the actual session id at runtime.
In client document table:
const uiState = State.SQLite.clientDocument({ name: 'ui_state', schema: Schema.Struct({ theme: Schema.Literals(['dark', 'light', 'system']), user: Schema.String, showToolbar: Schema.Boolean, }), default: { value: defaultFrontendState, id: SessionIdSymbol },})
Or in a client document query:
const query$ = queryDb(tables.uiState.get(SessionIdSymbol))
SessionIdSymbol, value: { readonly newTodoText: ""; readonly filter: "all";}
value: { newTodoText: ""
newTodoText: '', filter: "all"
filter: 'all' } }, }),} 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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>;}
const
export const const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Encoded">>;}
events = { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Encoded">>
todoCreated: import Events
Events.synced<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Encoded">>(args: { name: "v1.TodoCreated"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<...>, 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; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}>(fields: { readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}): Schema.Struct<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}>
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, createdAt: Schema.DateFromString
createdAt: import Schema
Schema.const DateFromString: Schema.DateFromString
Type-level representation of
DateFromString
.
Schema that decodes a string into a JavaScript Date.
When to use
Use to model string-encoded dates that decode to JavaScript Date objects
and encode back to strings.
Details
Decoding:
The string is passed to JavaScript Date construction.
Encoding:
A valid Date is encoded as an ISO string; an invalid Date is encoded as
"Invalid Date".
Gotchas
Invalid date strings can decode to invalid Date instances.
DateFromString.Bottom<unknown, unknown, unknown, unknown, Declaration, decodeTo<Date, String, never, never>, unknown, unknown, readonly [], unknown, "readonly", "required", "no-default", "readonly", "required">.check(checks_0: Check<Date>, ...checks: Check<Date>[]): Schema.DateFromString
check(import Schema
Schema.function isDateValid(annotations?: Schema.Annotations.Filter): Filter<globalThis.Date>
Validates that a Date object represents a valid date (not an invalid date
like new Date("invalid")).
Details
JSON Schema:
This check does not have a direct JSON Schema equivalent, as JSON Schema
validates date strings, not Date objects.
Arbitrary:
When generating test data with fast-check, this applies a valid: true
constraint to ensure generated Date objects are valid.
isDateValid()), }), }),} as type const = { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Encoded">>;}
events, { [const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Encoded">>;}
events.todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "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; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Encoded">>>(_eventDef: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<...>>, 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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Encoded">>;}
events.todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Encoded">>
todoCreated, ({ id: string
id, text: string
text, createdAt: Date
createdAt }) => 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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>;}
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; }; readonly createdAt: { ...; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos.insert: (values: { readonly text: string; readonly id: string; readonly createdAt: Date; 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>; readonly createdAt: Schema.Codec<Date, string, never, never>;}, "Type">[], State.SQLite.TableDefBase<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; ... 4 more ...; autoIncrement: false; }; readonly text: { ...; }; readonly completed: { ...; }; readonly createdAt: { ...; };}>, 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, createdAt: Date
createdAt }), ),})
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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>; }; 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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>; }; 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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>;}
tables, materializers: { "v1.TodoCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Encoded">>;}
events, state: InternalState
state })Via framework integrations
Section titled “Via framework integrations”import type { type FC<P = {}> = FunctionComponent<P>
Represents the type of a function component. Can optionally
receive a type argument that represents the props the component
receives.
FC } from 'react'
import { const queryDb: { <TResultSchema, TResult = TResultSchema>(queryInput: QueryInputRaw<TResultSchema, ReadonlyArray<any>> | QueryBuilder<TResultSchema, any, any>, options?: { map?: (rows: TResultSchema) => TResult; label?: string; deps?: DepKey; }): LiveQueryDef<TResult>; <TResultSchema, TResult = TResultSchema>(queryInput: ((get: GetAtomResult) => QueryInputRaw<TResultSchema, ReadonlyArray<any>>) | ((get: GetAtomResult) => QueryBuilder<TResultSchema, any, any>), options?: { map?: (rows: TResultSchema) => TResult; label?: string; deps?: DepKey; }): LiveQueryDef<TResult>;}
NOTE queryDb is only supposed to read data. Don't use it to insert/update/delete data but use events instead.
When using contextual data when constructing the query, please make sure to include it in the deps option.
queryDb } from '@livestore/livestore'
import { import tables
tables } from '../framework-integrations/react/schema.ts'import { import useAppStore
useAppStore } from '../framework-integrations/react/store.ts'
const const todos$: LiveQueryDef<unknown, "def">
todos$ = queryDb<unknown, unknown>(queryInput: QueryInputRaw<unknown, readonly any[]> | QueryBuilder<unknown, any, any>, options?: { map?: (rows: unknown) => unknown; label?: string; deps?: DepKey;} | undefined): LiveQueryDef<unknown, "def"> (+1 overload)
NOTE queryDb is only supposed to read data. Don't use it to insert/update/delete data but use events instead.
When using contextual data when constructing the query, please make sure to include it in the deps option.
queryDb(import tables
tables.any
todos.any
orderBy('createdAt', 'desc'), { label?: string
Used for debugging / devtools
label: 'todos' })
export const const TodoList: FC
TodoList: type FC<P = {}> = FunctionComponent<P>
Represents the type of a function component. Can optionally
receive a type argument that represents the props the component
receives.
FC = () => { const const store: any
store = import useAppStore
useAppStore() const const todos: any
todos = const store: any
store.any
useQuery(const todos$: LiveQueryDef<unknown, "def">
todos$)
return <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>{const todos: any
todos.any
length} items</JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>}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, type SessionIdSymbol = typeof SessionIdSymbolconst SessionIdSymbol: typeof SessionIdSymbol
Can be used in queries to refer to the current session id.
Will be replaced with the actual session id at runtime.
In client document table:
const uiState = State.SQLite.clientDocument({ name: 'ui_state', schema: Schema.Struct({ theme: Schema.Literals(['dark', 'light', 'system']), user: Schema.String, showToolbar: Schema.Boolean, }), default: { value: defaultFrontendState, id: SessionIdSymbol },})
Or in a client document query:
const query$ = queryDb(tables.uiState.get(SessionIdSymbol))
SessionIdSymbol, import State
State } from '@livestore/livestore'
export 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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>;}
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; }; readonly createdAt: { ...; };}>, 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; }; readonly createdAt: { ...; };}, 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; }; readonly createdAt: { ...; };}
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 }), createdAt: { columnType: "text"; schema: Schema.Codec<Date, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;}
createdAt: import State
State.import SQLite
SQLite.const datetime: () => { columnType: "text"; schema: Schema.Codec<Date, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;} (+1 overload)
datetime(), }, }), uiState: State.SQLite.ClientDocumentTableDef<"UiState", Schema.Struct.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Encoded">, { partialSet: true; default: { id: typeof SessionIdSymbol; value: { readonly newTodoText: ""; readonly filter: "all"; }; };}>
uiState: import State
State.import SQLite
SQLite.clientDocument<"UiState", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Encoded">, { readonly name: "UiState"; readonly schema: Schema.Struct<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>; }>; readonly default: { ...; };}>({ name, schema: valueSchema, ...inputOptions }: { ...;} & { readonly name: "UiState"; readonly schema: Schema.Struct<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>; }>; readonly default: { ...; };}): State.SQLite.ClientDocumentTableDef<...>export clientDocument
Special:
- Synced across client sessions (e.g. tabs) but not across different clients
- Derived setters
- Emits client-only events
- Has implicit setter-materializers
- Similar to
React.useState (except it's persisted)
Careful:
- When changing the table definitions in a non-backwards compatible way, the state might be lost without
explicit materializers to handle the old auto-generated events
Usage:
// Querying data// `'some-id'` can be ommited for SessionIdSymbolstore.queryDb(clientDocumentTable.get('some-id'))
// Setting data// Again, `'some-id'` can be ommited for SessionIdSymbolstore.commit(clientDocumentTable.set({ someField: 'some-value' }, 'some-id'))
clientDocument({ name: "UiState"
name: 'UiState', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Encoded">, never, never> & Schema.Struct<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}>
schema: import Schema
Schema.function Struct<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}>(fields: { readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}): Schema.Struct<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}>
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({ newTodoText: Schema.String
newTodoText: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String, filter: Schema.Literals<readonly ["all", "active", "completed"]>
filter: import Schema
Schema.function Literals<readonly ["all", "active", "completed"]>(literals: readonly ["all", "active", "completed"]): Schema.Literals<readonly ["all", "active", "completed"]>
Creates a union schema from an array of literal values.
Example (Defining status codes)
import { Schema } from "effect"
const schema = Schema.Literals(["active", "inactive", "pending"])// accepts "active", "inactive", or "pending"
Literals(['all', 'active', 'completed']), }), default: { readonly id: typeof SessionIdSymbol; readonly value: { readonly newTodoText: ""; readonly filter: "all"; };}
default: { id: typeof SessionIdSymbol
id: const SessionIdSymbol: typeof SessionIdSymbol
Can be used in queries to refer to the current session id.
Will be replaced with the actual session id at runtime.
In client document table:
const uiState = State.SQLite.clientDocument({ name: 'ui_state', schema: Schema.Struct({ theme: Schema.Literals(['dark', 'light', 'system']), user: Schema.String, showToolbar: Schema.Boolean, }), default: { value: defaultFrontendState, id: SessionIdSymbol },})
Or in a client document query:
const query$ = queryDb(tables.uiState.get(SessionIdSymbol))
SessionIdSymbol, value: { readonly newTodoText: ""; readonly filter: "all";}
value: { newTodoText: ""
newTodoText: '', filter: "all"
filter: 'all' } }, }),} 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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>;}
const
export const const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Encoded">>;}
events = { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Encoded">>
todoCreated: import Events
Events.synced<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Encoded">>(args: { name: "v1.TodoCreated"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<...>, 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; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}>(fields: { readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}): Schema.Struct<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}>
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, createdAt: Schema.DateFromString
createdAt: import Schema
Schema.const DateFromString: Schema.DateFromString
Type-level representation of
DateFromString
.
Schema that decodes a string into a JavaScript Date.
When to use
Use to model string-encoded dates that decode to JavaScript Date objects
and encode back to strings.
Details
Decoding:
The string is passed to JavaScript Date construction.
Encoding:
A valid Date is encoded as an ISO string; an invalid Date is encoded as
"Invalid Date".
Gotchas
Invalid date strings can decode to invalid Date instances.
DateFromString.Bottom<unknown, unknown, unknown, unknown, Declaration, decodeTo<Date, String, never, never>, unknown, unknown, readonly [], unknown, "readonly", "required", "no-default", "readonly", "required">.check(checks_0: Check<Date>, ...checks: Check<Date>[]): Schema.DateFromString
check(import Schema
Schema.function isDateValid(annotations?: Schema.Annotations.Filter): Filter<globalThis.Date>
Validates that a Date object represents a valid date (not an invalid date
like new Date("invalid")).
Details
JSON Schema:
This check does not have a direct JSON Schema equivalent, as JSON Schema
validates date strings, not Date objects.
Arbitrary:
When generating test data with fast-check, this applies a valid: true
constraint to ensure generated Date objects are valid.
isDateValid()), }), }),} as type const = { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Encoded">>;}
events, { [const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Encoded">>;}
events.todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "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; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Encoded">>>(_eventDef: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<...>>, 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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Encoded">>;}
events.todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Encoded">>
todoCreated, ({ id: string
id, text: string
text, createdAt: Date
createdAt }) => 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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>;}
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; }; readonly createdAt: { ...; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos.insert: (values: { readonly text: string; readonly id: string; readonly createdAt: Date; 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>; readonly createdAt: Schema.Codec<Date, string, never, never>;}, "Type">[], State.SQLite.TableDefBase<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; ... 4 more ...; autoIncrement: false; }; readonly text: { ...; }; readonly completed: { ...; }; readonly createdAt: { ...; };}>, 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, createdAt: Date
createdAt }), ),})
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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>; }; 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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>; }; 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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>;}
tables, materializers: { "v1.TodoCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Encoded">>;}
events, state: InternalState
state })import { function unstable_batchedUpdates<A, R>(callback: (a: A) => R, a: A): R (+1 overload)
unstable_batchedUpdates as function batchUpdates<A, R>(callback: (a: A) => R, a: A): R (+1 overload)
batchUpdates } from 'react-dom'
import { const makeInMemoryAdapter: (options?: InMemoryAdapterOptions) => Adapter
Creates a web-only in-memory LiveStore adapter.
This adapter runs entirely in memory with no persistence. Ideal for:
- Unit tests and integration tests
- Sandboxes and demos
- Ephemeral sessions where persistence isn't needed
Characteristics:
- Fast, zero I/O overhead
- Works in all browser contexts: Window, WebWorker, SharedWorker, ServiceWorker
- Supports optional sync backends for real-time collaboration
- No data persists after page reload
For persistent storage, use makePersistedAdapter instead.
makeInMemoryAdapter } from '@livestore/adapter-web'import { const useStore: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>(options: RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>) => Store<TSchema, TContext> & ReactApi
Returns a store instance augmented with hooks (store.useQuery() and store.useClientDocument()) for reactive queries.
useStore } from '@livestore/react'
import { import schema
schema } from './schema.ts'
const const adapter: Adapter
adapter = function makeInMemoryAdapter(options?: InMemoryAdapterOptions): Adapter
Creates a web-only in-memory LiveStore adapter.
This adapter runs entirely in memory with no persistence. Ideal for:
- Unit tests and integration tests
- Sandboxes and demos
- Ephemeral sessions where persistence isn't needed
Characteristics:
- Fast, zero I/O overhead
- Works in all browser contexts: Window, WebWorker, SharedWorker, ServiceWorker
- Supports optional sync backends for real-time collaboration
- No data persists after page reload
For persistent storage, use makePersistedAdapter instead.
makeInMemoryAdapter()
export const const useAppStore: () => Store<any, {}> & ReactApi
useAppStore = () => useStore<any, {}, Codec<Json, Json, never, never>>(options: RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>): Store<any, {}> & ReactApi
Returns a store instance augmented with hooks (store.useQuery() and store.useClientDocument()) for reactive queries.
useStore({ 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: 'app-root', 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<any, {}, Codec<Json, Json, never, never>>.batchUpdates?: (run: () => void) => void
Needed in React so LiveStore can apply multiple events in a single render.
batchUpdates, })import type { interface LiveQueryDef<TResult, TTag extends string = "def">
A query definition representing a blueprint for a reactive query.
Query definitions are created by
queryDb
,
computed
, and
signal
.
They're lightweight and can be defined at module scope. The actual query instance
(which holds state) is created lazily when you use the definition with a Store.
Multiple uses of the same definition share a single instance via reference counting.
LiveQueryDef, 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, {}> & { useQuery: <T>(query: LiveQueryDef<T>) => () => T;}
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 & { useQuery: <T>(query: LiveQueryDef<T>) => () => T
useQuery: <function (type parameter) T in <T>(query: LiveQueryDef<T>): () => T
T>(query: LiveQueryDef<T, "def">
query: interface LiveQueryDef<TResult, TTag extends string = "def">
A query definition representing a blueprint for a reactive query.
Query definitions are created by
queryDb
,
computed
, and
signal
.
They're lightweight and can be defined at module scope. The actual query instance
(which holds state) is created lazily when you use the definition with a Store.
Multiple uses of the same definition share a single instance via reference counting.
LiveQueryDef<function (type parameter) T in <T>(query: LiveQueryDef<T>): () => T
T>) => () => function (type parameter) T in <T>(query: LiveQueryDef<T>): () => T
T }declare const const state$: LiveQueryDef<number, "def">
state$: interface LiveQueryDef<TResult, TTag extends string = "def">
A query definition representing a blueprint for a reactive query.
Query definitions are created by
queryDb
,
computed
, and
signal
.
They're lightweight and can be defined at module scope. The actual query instance
(which holds state) is created lazily when you use the definition with a Store.
Multiple uses of the same definition share a single instance via reference counting.
LiveQueryDef<number>
export const const MyComponent: () => JSX.Element
MyComponent = () => { const const value: () => number
value = const store: Store<LiveStoreSchema.Any, {}> & { useQuery: <T>(query: LiveQueryDef<T>) => () => T;}
store.useQuery: <number>(query: LiveQueryDef<number, "def">) => () => number
useQuery(const state$: LiveQueryDef<number, "def">
state$)
return <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>{const value: () => number
value()}</JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>}Reacting to changing variables passed to queries
Section titled “Reacting to changing variables passed to queries”If your query depends on a variable passed in by the component, use the deps array to react to changes in this variable.
import type { type FC<P = {}> = FunctionComponent<P>
Represents the type of a function component. Can optionally
receive a type argument that represents the props the component
receives.
FC } from 'react'
import { const queryDb: { <TResultSchema, TResult = TResultSchema>(queryInput: QueryInputRaw<TResultSchema, ReadonlyArray<any>> | QueryBuilder<TResultSchema, any, any>, options?: { map?: (rows: TResultSchema) => TResult; label?: string; deps?: DepKey; }): LiveQueryDef<TResult>; <TResultSchema, TResult = TResultSchema>(queryInput: ((get: GetAtomResult) => QueryInputRaw<TResultSchema, ReadonlyArray<any>>) | ((get: GetAtomResult) => QueryBuilder<TResultSchema, any, any>), options?: { map?: (rows: TResultSchema) => TResult; label?: string; deps?: DepKey; }): LiveQueryDef<TResult>;}
NOTE queryDb is only supposed to read data. Don't use it to insert/update/delete data but use events instead.
When using contextual data when constructing the query, please make sure to include it in the deps option.
queryDb } from '@livestore/livestore'
import { import tables
tables } from '../framework-integrations/react/schema.ts'import { import useAppStore
useAppStore } from '../framework-integrations/react/store.ts'
export const const todos$: ({ showCompleted }: { showCompleted: boolean;}) => LiveQueryDef<unknown, "def">
todos$ = ({ showCompleted: boolean
showCompleted }: { showCompleted: boolean
showCompleted: boolean }) => queryDb<unknown, unknown>(queryInput: ((get: GetAtomResult) => QueryInputRaw<unknown, readonly any[]>) | ((get: GetAtomResult) => QueryBuilder<unknown, any, any>), options?: { map?: (rows: unknown) => unknown; label?: string; deps?: DepKey;} | undefined): LiveQueryDef<unknown, "def"> (+1 overload)
NOTE queryDb is only supposed to read data. Don't use it to insert/update/delete data but use events instead.
When using contextual data when constructing the query, please make sure to include it in the deps option.
queryDb( () => { return import tables
tables.any
todos.any
where(showCompleted: boolean
showCompleted === true ? { completed: boolean
completed: true } : {}) }, { label?: string
Used for debugging / devtools
label: 'todos$', deps?: DepKey
deps: [showCompleted: boolean
showCompleted === true ? 'true' : 'false'], }, )
export const const MyComponent: FC<{ showCompleted: boolean;}>
MyComponent: type FC<P = {}> = FunctionComponent<P>
Represents the type of a function component. Can optionally
receive a type argument that represents the props the component
receives.
FC<{ showCompleted: boolean
showCompleted: boolean }> = ({ showCompleted: boolean
showCompleted }) => { const const store: any
store = import useAppStore
useAppStore() const const todos: readonly { id: string; text: string; completed: boolean;}[]
todos = const store: any
store.any
useQuery(const todos$: ({ showCompleted }: { showCompleted: boolean;}) => LiveQueryDef<unknown, "def">
todos$({ showCompleted: boolean
showCompleted })) as interface ReadonlyArray<T>
ReadonlyArray<{ id: string
id: string text: string
text: string completed: boolean
completed: boolean }>
return <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>{const todos: readonly { id: string; text: string; completed: boolean;}[]
todos.ReadonlyArray<T>.length: number
Gets the length of the array. This is a number one higher than the highest element defined in an array.
length} Done</JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>}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, type SessionIdSymbol = typeof SessionIdSymbolconst SessionIdSymbol: typeof SessionIdSymbol
Can be used in queries to refer to the current session id.
Will be replaced with the actual session id at runtime.
In client document table:
const uiState = State.SQLite.clientDocument({ name: 'ui_state', schema: Schema.Struct({ theme: Schema.Literals(['dark', 'light', 'system']), user: Schema.String, showToolbar: Schema.Boolean, }), default: { value: defaultFrontendState, id: SessionIdSymbol },})
Or in a client document query:
const query$ = queryDb(tables.uiState.get(SessionIdSymbol))
SessionIdSymbol, import State
State } from '@livestore/livestore'
export 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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>;}
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; }; readonly createdAt: { ...; };}>, 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; }; readonly createdAt: { ...; };}, 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; }; readonly createdAt: { ...; };}
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 }), createdAt: { columnType: "text"; schema: Schema.Codec<Date, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;}
createdAt: import State
State.import SQLite
SQLite.const datetime: () => { columnType: "text"; schema: Schema.Codec<Date, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;} (+1 overload)
datetime(), }, }), uiState: State.SQLite.ClientDocumentTableDef<"UiState", Schema.Struct.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Encoded">, { partialSet: true; default: { id: typeof SessionIdSymbol; value: { readonly newTodoText: ""; readonly filter: "all"; }; };}>
uiState: import State
State.import SQLite
SQLite.clientDocument<"UiState", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Encoded">, { readonly name: "UiState"; readonly schema: Schema.Struct<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>; }>; readonly default: { ...; };}>({ name, schema: valueSchema, ...inputOptions }: { ...;} & { readonly name: "UiState"; readonly schema: Schema.Struct<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>; }>; readonly default: { ...; };}): State.SQLite.ClientDocumentTableDef<...>export clientDocument
Special:
- Synced across client sessions (e.g. tabs) but not across different clients
- Derived setters
- Emits client-only events
- Has implicit setter-materializers
- Similar to
React.useState (except it's persisted)
Careful:
- When changing the table definitions in a non-backwards compatible way, the state might be lost without
explicit materializers to handle the old auto-generated events
Usage:
// Querying data// `'some-id'` can be ommited for SessionIdSymbolstore.queryDb(clientDocumentTable.get('some-id'))
// Setting data// Again, `'some-id'` can be ommited for SessionIdSymbolstore.commit(clientDocumentTable.set({ someField: 'some-value' }, 'some-id'))
clientDocument({ name: "UiState"
name: 'UiState', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}, "Encoded">, never, never> & Schema.Struct<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}>
schema: import Schema
Schema.function Struct<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}>(fields: { readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}): Schema.Struct<{ readonly newTodoText: Schema.String; readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;}>
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({ newTodoText: Schema.String
newTodoText: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String, filter: Schema.Literals<readonly ["all", "active", "completed"]>
filter: import Schema
Schema.function Literals<readonly ["all", "active", "completed"]>(literals: readonly ["all", "active", "completed"]): Schema.Literals<readonly ["all", "active", "completed"]>
Creates a union schema from an array of literal values.
Example (Defining status codes)
import { Schema } from "effect"
const schema = Schema.Literals(["active", "inactive", "pending"])// accepts "active", "inactive", or "pending"
Literals(['all', 'active', 'completed']), }), default: { readonly id: typeof SessionIdSymbol; readonly value: { readonly newTodoText: ""; readonly filter: "all"; };}
default: { id: typeof SessionIdSymbol
id: const SessionIdSymbol: typeof SessionIdSymbol
Can be used in queries to refer to the current session id.
Will be replaced with the actual session id at runtime.
In client document table:
const uiState = State.SQLite.clientDocument({ name: 'ui_state', schema: Schema.Struct({ theme: Schema.Literals(['dark', 'light', 'system']), user: Schema.String, showToolbar: Schema.Boolean, }), default: { value: defaultFrontendState, id: SessionIdSymbol },})
Or in a client document query:
const query$ = queryDb(tables.uiState.get(SessionIdSymbol))
SessionIdSymbol, value: { readonly newTodoText: ""; readonly filter: "all";}
value: { newTodoText: ""
newTodoText: '', filter: "all"
filter: 'all' } }, }),} 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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>;}
const
export const const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Encoded">>;}
events = { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Encoded">>
todoCreated: import Events
Events.synced<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Encoded">>(args: { name: "v1.TodoCreated"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<...>, 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; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}>(fields: { readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}): Schema.Struct<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}>
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, createdAt: Schema.DateFromString
createdAt: import Schema
Schema.const DateFromString: Schema.DateFromString
Type-level representation of
DateFromString
.
Schema that decodes a string into a JavaScript Date.
When to use
Use to model string-encoded dates that decode to JavaScript Date objects
and encode back to strings.
Details
Decoding:
The string is passed to JavaScript Date construction.
Encoding:
A valid Date is encoded as an ISO string; an invalid Date is encoded as
"Invalid Date".
Gotchas
Invalid date strings can decode to invalid Date instances.
DateFromString.Bottom<unknown, unknown, unknown, unknown, Declaration, decodeTo<Date, String, never, never>, unknown, unknown, readonly [], unknown, "readonly", "required", "no-default", "readonly", "required">.check(checks_0: Check<Date>, ...checks: Check<Date>[]): Schema.DateFromString
check(import Schema
Schema.function isDateValid(annotations?: Schema.Annotations.Filter): Filter<globalThis.Date>
Validates that a Date object represents a valid date (not an invalid date
like new Date("invalid")).
Details
JSON Schema:
This check does not have a direct JSON Schema equivalent, as JSON Schema
validates date strings, not Date objects.
Arbitrary:
When generating test data with fast-check, this applies a valid: true
constraint to ensure generated Date objects are valid.
isDateValid()), }), }),} as type const = { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Encoded">>;}
events, { [const events: { readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Encoded">>;}
events.todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "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; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Encoded">>>(_eventDef: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<...>>, 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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Encoded">>;}
events.todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString;}, "Encoded">>
todoCreated, ({ id: string
id, text: string
text, createdAt: Date
createdAt }) => 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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>;}
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; }; readonly createdAt: { ...; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos.insert: (values: { readonly text: string; readonly id: string; readonly createdAt: Date; 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>; readonly createdAt: Schema.Codec<Date, string, never, never>;}, "Type">[], State.SQLite.TableDefBase<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; ... 4 more ...; autoIncrement: false; }; readonly text: { ...; }; readonly completed: { ...; }; readonly createdAt: { ...; };}>, 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, createdAt: Date
createdAt }), ),})
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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>; }; 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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>; }; 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; }; readonly createdAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; readonly uiState: State.SQLite.ClientDocumentTableDef<...>;}
tables, materializers: { "v1.TodoCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "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; readonly createdAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; readonly createdAt: Schema.DateFromString; }, "Encoded">>;}
events, state: InternalState
state })import { function unstable_batchedUpdates<A, R>(callback: (a: A) => R, a: A): R (+1 overload)
unstable_batchedUpdates as function batchUpdates<A, R>(callback: (a: A) => R, a: A): R (+1 overload)
batchUpdates } from 'react-dom'
import { const makeInMemoryAdapter: (options?: InMemoryAdapterOptions) => Adapter
Creates a web-only in-memory LiveStore adapter.
This adapter runs entirely in memory with no persistence. Ideal for:
- Unit tests and integration tests
- Sandboxes and demos
- Ephemeral sessions where persistence isn't needed
Characteristics:
- Fast, zero I/O overhead
- Works in all browser contexts: Window, WebWorker, SharedWorker, ServiceWorker
- Supports optional sync backends for real-time collaboration
- No data persists after page reload
For persistent storage, use makePersistedAdapter instead.
makeInMemoryAdapter } from '@livestore/adapter-web'import { const useStore: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>(options: RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>) => Store<TSchema, TContext> & ReactApi
Returns a store instance augmented with hooks (store.useQuery() and store.useClientDocument()) for reactive queries.
useStore } from '@livestore/react'
import { import schema
schema } from './schema.ts'
const const adapter: Adapter
adapter = function makeInMemoryAdapter(options?: InMemoryAdapterOptions): Adapter
Creates a web-only in-memory LiveStore adapter.
This adapter runs entirely in memory with no persistence. Ideal for:
- Unit tests and integration tests
- Sandboxes and demos
- Ephemeral sessions where persistence isn't needed
Characteristics:
- Fast, zero I/O overhead
- Works in all browser contexts: Window, WebWorker, SharedWorker, ServiceWorker
- Supports optional sync backends for real-time collaboration
- No data persists after page reload
For persistent storage, use makePersistedAdapter instead.
makeInMemoryAdapter()
export const const useAppStore: () => Store<any, {}> & ReactApi
useAppStore = () => useStore<any, {}, Codec<Json, Json, never, never>>(options: RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>): Store<any, {}> & ReactApi
Returns a store instance augmented with hooks (store.useQuery() and store.useClientDocument()) for reactive queries.
useStore({ 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: 'app-root', 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<any, {}, Codec<Json, Json, never, never>>.batchUpdates?: (run: () => void) => void
Needed in React so LiveStore can apply multiple events in a single render.
batchUpdates, })Further reading
Section titled “Further reading”- Riffle: Building data-centric apps with a reactive relational database
- Adapton / miniAdapton
Related technologies
Section titled “Related technologies”- Signia: Signia is a minimal, fast, and scalable signals library for TypeScript developed by TLDraw.