React integration for LiveStore
While LiveStore is framework agnostic, the @livestore/react package provides a first-class integration with React.
Features
Section titled “Features”- High performance
- Fine-grained reactivity (using LiveStore’s signals-based reactivity system)
- Instant, synchronous query results (without the need for
useEffectandisLoadingchecks) - Supports multiple store instances
- Transactional state transitions (via
batchUpdates) - Also supports Expo / React Native via
@livestore/adapter-expo
Core Concepts
Section titled “Core Concepts”When using LiveStore in React, you’ll primarily interact with these fundamental components:
StoreRegistry- Manages store instances with automatic caching and disposal<StoreRegistryProvider>- React context provider that supplies the registry to componentsuseStore()- Suspense-enabled hook for accessing store instances
Stores are cached by their storeId and automatically disposed after being unused for a configurable duration (unusedCacheTime).
import { function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>] (+1 overload)
Returns a stateful value, and a function to update it.
useState } from 'react'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 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, class StoreRegistry
Store Registry coordinating store loading, caching, and retention
StoreRegistry, const storeOptions: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>(options: RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>) => RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>
Helper for defining reusable store options with full type inference. Returns
options that can be passed to useStore() or storeRegistry.preload().
storeOptions } from '@livestore/livestore'import { const StoreRegistryProvider: ({ storeRegistry, children }: StoreRegistryProviderProps) => JSX.Element
React context provider that makes a
StoreRegistry
available to descendant components.
Wrap your application (or a subtree) with this provider to enable
useStore
and
useStoreRegistry
hooks within that tree.
StoreRegistryProvider, 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, import tables
tables } from './issue.schema.ts'
const const issueStoreOptions: (issueId: string) => RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>
issueStoreOptions = (issueId: string
issueId: string) => storeOptions<any, {}, Codec<Json, Json, never, never>>(options: RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>): RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>
Helper for defining reusable store options with full type inference. Returns
options that can be passed to useStore() or storeRegistry.preload().
storeOptions({ 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: `issue-${issueId: string
issueId}`, 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: 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 App: () => JSX.Element
App = () => { const [const storeRegistry: StoreRegistry
storeRegistry] = useState<StoreRegistry>(initialState: StoreRegistry | (() => StoreRegistry)): [StoreRegistry, Dispatch<SetStateAction<StoreRegistry>>] (+1 overload)
Returns a stateful value, and a function to update it.
useState(() => new new StoreRegistry(config?: StoreRegistryConfig): StoreRegistry
Creates a new StoreRegistry instance.
StoreRegistry({ defaultOptions?: Partial<Pick<RegistryStoreOptions<LiveStoreSchema.Any, {}, Codec<Json, Json, never, never>>, "batchUpdates" | "disableDevtools" | "confirmUnsavedChanges" | "debug" | "otelOptions" | "unusedCacheTime">>
Default options that are applied to all stores when they are loaded.
defaultOptions: { batchUpdates?: (run: () => void) => void
Needed in React so LiveStore can apply multiple events in a single render.
batchUpdates } })) return ( <const StoreRegistryProvider: ({ storeRegistry, children }: StoreRegistryProviderProps) => JSX.Element
React context provider that makes a
StoreRegistry
available to descendant components.
Wrap your application (or a subtree) with this provider to enable
useStore
and
useStoreRegistry
hooks within that tree.
StoreRegistryProvider storeRegistry: StoreRegistry
storeRegistry={const storeRegistry: StoreRegistry
storeRegistry}> <const IssueView: () => JSX.Element
IssueView /> </const StoreRegistryProvider: ({ storeRegistry, children }: StoreRegistryProviderProps) => JSX.Element
React context provider that makes a
StoreRegistry
available to descendant components.
Wrap your application (or a subtree) with this provider to enable
useStore
and
useStoreRegistry
hooks within that tree.
StoreRegistryProvider> )}
const const IssueView: () => JSX.Element
IssueView = () => { const const store: Store<any, {}> & ReactApi
store = 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(const issueStoreOptions: (issueId: string) => RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>
issueStoreOptions('abc123')) const [const issue: any
issue] = const store: Store<any, {}> & ReactApi
store.useQuery: <LiveQueryDef<unknown, "def">>(queryable: LiveQueryDef<unknown, "def">, options?: { store?: Store;}) => unknown
Returns the result of a query and subscribes to future updates.
Example:
const App = () => { const todos = useQuery(queryDb(tables.todos.query.where({ complete: true }))) return <div>{todos.map((todo) => <div key={todo.id}>{todo.title}</div>)}</div>}
useQuery(queryDb<unknown, unknown>(queryInput: QueryBuilder<unknown, any, any> | QueryInputRaw<unknown, readonly 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
issue.any
select())) return <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>{const issue: any
issue?.any
title}</JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>}import { import Events
Events, const makeSchema: <TInputSchema extends InputSchema>(inputSchema: TInputSchema) => FromInputSchema.DeriveSchema<TInputSchema>
makeSchema, import Schema
Schema, import State
State } from '@livestore/livestore'
// Event definitionsexport const const events: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>;}
events = { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">>
issueCreated: import Events
Events.synced<"v1.IssueCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">>(args: { name: "v1.IssueCreated"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<...>; }, "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.IssueCreated"
name: 'v1.IssueCreated', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}>(fields: { readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}): Schema.Struct<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}>
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, title: Schema.String
title: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String, status: Schema.Literals<readonly ["todo", "done"]>
status: import Schema
Schema.function Literals<readonly ["todo", "done"]>(literals: readonly ["todo", "done"]): Schema.Literals<readonly ["todo", "done"]>
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(['todo', 'done']), }), }), issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">>
issueStatusChanged: import Events
Events.synced<"v1.IssueStatusChanged", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">>(args: { name: "v1.IssueStatusChanged"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "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.IssueStatusChanged"
name: 'v1.IssueStatusChanged', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}>(fields: { readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}): Schema.Struct<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}>
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, status: Schema.Literals<readonly ["todo", "done"]>
status: import Schema
Schema.function Literals<readonly ["todo", "done"]>(literals: readonly ["todo", "done"]): Schema.Literals<readonly ["todo", "done"]>
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(['todo', 'done']), }), }),}
// State definitionexport const const tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables = { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
issue: import State
State.import SQLite
SQLite.function table<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; };}, Partial<...>>(args: { ...;} & Partial<...>): State.SQLite.TableDef<...> (+2 overloads)
Creates a SQLite table definition from columns or an Effect Schema.
This function supports two main ways to define a table:
- Using explicit column definitions
- Using an Effect Schema (either the
name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columnsconst usersTable = State.SQLite.table({ name: 'users', columns: { id: State.SQLite.text({ primaryKey: true }), name: State.SQLite.text({ nullable: false }), email: State.SQLite.text({ nullable: false }), age: State.SQLite.integer({ nullable: true }), },})
// Using Effect Schema with annotationsimport { Schema } from '@livestore/utils/effect'
const UserSchema = Schema.Struct({ id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement), email: Schema.String.pipe(State.SQLite.withUnique), name: Schema.String, active: Schema.Boolean.pipe(State.SQLite.withDefault(true)), createdAt: Schema.optional(Schema.Date),})
// Option 1: With explicit nameconst usersTable = State.SQLite.table({ name: 'users', schema: UserSchema,})
// Option 2: With name from schema annotation (title or identifier)const AnnotatedUserSchema = UserSchema.annotate({ title: 'users' })const usersTable2 = State.SQLite.table({ schema: AnnotatedUserSchema,})
// Adding indexesconst PostSchema = Schema.Struct({ id: Schema.String.pipe(State.SQLite.withPrimaryKey), title: Schema.String, authorId: Schema.String, createdAt: Schema.Date,}).annotate({ identifier: 'posts' })
const postsTable = State.SQLite.table({ schema: PostSchema, indexes: [ { name: 'idx_posts_author', columns: ['authorId'] }, { name: 'idx_posts_created', columns: ['createdAt'], isUnique: false }, ],})
table({ name: "issue"
name: 'issue', columns: { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; };}
columns: { id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;}
id: import State
State.import SQLite
SQLite.const text: <string, string, false, typeof NoDefault, true, false>(args: { schema?: Schema.Codec<string, string, never, never>; default?: typeof NoDefault; nullable?: false; primaryKey?: true; autoIncrement?: false;}) => { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;} (+1 overload)
text({ primaryKey?: true
primaryKey: true }), title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;}
title: 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(), status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;}
status: 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(), }, }),}
const const materializers: { "v1.IssueCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>>; "v1.IssueStatusChanged": State.SQLite.Materializer<State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>>;}
materializers = import State
State.import SQLite
SQLite.const materializers: <{ issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<...>, Schema.Struct.ReadonlySide<...>>;}>(_eventDefRecord: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<...>, Schema.Struct.ReadonlySide<...>>;}, 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: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>;}
events, { 'v1.IssueCreated': ({ id: string
id, title: string
title, status: "todo" | "done"
status }) => const tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
issue.insert: (values: { readonly status: string; readonly id: string; readonly title: string;}) => QueryBuilder<readonly Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly title: Schema.Codec<string, string, never, never>; readonly status: Schema.Codec<string, string, never, never>;}, "Type">[], State.SQLite.TableDefBase<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { ...; }; readonly status: { ...; };}>, State.SQLite.WithDefaults<...>>, "select" | ... 6 more ... | "row">
Insert a new row into the table.
insert({ id: string
id, title: string
title, status: string
status }), 'v1.IssueStatusChanged': ({ id: string
id, status: "todo" | "done"
status }) => const tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
issue.update: (values: Partial<Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly title: Schema.Codec<string, string, never, never>; readonly status: Schema.Codec<string, string, never, never>;}, "Type">>) => QueryBuilder<readonly Schema.Struct.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly title: Schema.Codec<string, string, never, never>; readonly status: Schema.Codec<string, string, never, never>;}, "Type">[], State.SQLite.TableDefBase<...>, "select" | ... 6 more ... | "row">
Update rows in the table that match the where clause
Example:
db.todos.update({ status: 'completed' }).where({ id: '123' })
update({ status?: string
status }).where: (params: Partial<{ readonly id: string | { op: Exclude<QueryBuilder<TResult, TTableDef extends State.SQLite.TableDefBase<any, any>, TWithout extends QueryBuilder.ApiFeature = never>.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[]; } | undefined; readonly title: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { ...; } | undefined; readonly status: string | ... 2 more ... | undefined;}>) => QueryBuilder<...> (+3 overloads)
where({ id?: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string;} | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[];} | undefined
id }),})
const const state: InternalState
state = import State
State.import SQLite
SQLite.const makeState: <{ tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; }; materializers: { ...; };}>(inputSchema: { tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; }; materializers: { ...; };}) => InternalState
makeState({ tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables, materializers: { "v1.IssueCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>>; "v1.IssueStatusChanged": State.SQLite.Materializer<State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>>;}
materializers })
export const const schema: FromInputSchema.DeriveSchema<{ events: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>; }; state: InternalState;}>
schema = makeSchema<{ events: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<...>, Schema.Struct.ReadonlySide<...>>; }; state: InternalState;}>(inputSchema: { events: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<...>, Schema.Struct.ReadonlySide<...>>; }; state: InternalState;}): FromInputSchema.DeriveSchema<...>
makeSchema({ events: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>;}
events, state: InternalState
state })Setting Up
Section titled “Setting Up”1. Configure the Store
Section titled “1. Configure the Store”Create a store configuration file that exports a custom hook wrapping useStore():
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 { 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 })The useStore() hook accepts store configuration options and returns a store instance. It suspends while the store is loading, so components using it need to be wrapped in a Suspense boundary.
2. Set Up the Registry
Section titled “2. Set Up the Registry”Create a StoreRegistry and provide it via <StoreRegistryProvider>. Wrap in a <Suspense> to handle loading states and a <ErrorBoundary> to handle errors:
import { type type ReactNode = string | number | bigint | boolean | ReactElement<unknown, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | Promise<AwaitedReactNode> | null | undefined
Represents all of the things React can render.
Where
ReactElement
only represents JSX, ReactNode represents everything that can be rendered.
ReactNode, const Suspense: ExoticComponent<SuspenseProps>
Lets you display a fallback until its children have finished loading.
Suspense, function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>] (+1 overload)
Returns a stateful value, and a function to update it.
useState } from 'react'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 { class ErrorBoundary
A reusable React error boundary component.
Wrap this component around other React components to "catch" errors and render a fallback UI.
This package is built on top of React error boundaries,
so it has all of the advantages and constraints of that API.
This means that it can't catch errors during:
- Server side rendering
- Event handlers
- Asynchronous code (including effects)
ℹ️ The component provides several ways to render a fallback: fallback, fallbackRender, and FallbackComponent.
Refer to the documentation to determine which is best for your application.
ℹ️ This is a client component. You can only pass props to it that are serializeable or use it in files that have a "use client"; directive.
ErrorBoundary } from 'react-error-boundary'
import { class StoreRegistry
Store Registry coordinating store loading, caching, and retention
StoreRegistry } from '@livestore/livestore'import { const StoreRegistryProvider: ({ storeRegistry, children }: StoreRegistryProviderProps) => JSX.Element
React context provider that makes a
StoreRegistry
available to descendant components.
Wrap your application (or a subtree) with this provider to enable
useStore
and
useStoreRegistry
hooks within that tree.
StoreRegistryProvider } from '@livestore/react'
const const appErrorFallback: JSX.Element
appErrorFallback = <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>Something went wrong</JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>const const appLoadingFallback: JSX.Element
appLoadingFallback = <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>Loading LiveStore...</JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>
export const const App: ({ children }: { children: ReactNode;}) => JSX.Element
App = ({ children: ReactNode
children }: { children: ReactNode
children: type ReactNode = string | number | bigint | boolean | ReactElement<unknown, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | Promise<AwaitedReactNode> | null | undefined
Represents all of the things React can render.
Where
ReactElement
only represents JSX, ReactNode represents everything that can be rendered.
ReactNode }) => { const [const storeRegistry: StoreRegistry
storeRegistry] = useState<StoreRegistry>(initialState: StoreRegistry | (() => StoreRegistry)): [StoreRegistry, Dispatch<SetStateAction<StoreRegistry>>] (+1 overload)
Returns a stateful value, and a function to update it.
useState(() => new new StoreRegistry(config?: StoreRegistryConfig): StoreRegistry
Creates a new StoreRegistry instance.
StoreRegistry({ defaultOptions?: Partial<Pick<RegistryStoreOptions<LiveStoreSchema.Any, {}, Codec<Json, Json, never, never>>, "batchUpdates" | "disableDevtools" | "confirmUnsavedChanges" | "debug" | "otelOptions" | "unusedCacheTime">>
Default options that are applied to all stores when they are loaded.
defaultOptions: { batchUpdates?: (run: () => void) => void
Needed in React so LiveStore can apply multiple events in a single render.
batchUpdates } }))
return ( <class ErrorBoundary
A reusable React error boundary component.
Wrap this component around other React components to "catch" errors and render a fallback UI.
This package is built on top of React error boundaries,
so it has all of the advantages and constraints of that API.
This means that it can't catch errors during:
- Server side rendering
- Event handlers
- Asynchronous code (including effects)
ℹ️ The component provides several ways to render a fallback: fallback, fallbackRender, and FallbackComponent.
Refer to the documentation to determine which is best for your application.
ℹ️ This is a client component. You can only pass props to it that are serializeable or use it in files that have a "use client"; directive.
ErrorBoundary fallback: ReactNode
Static content to render in place of an error if one is thrown.
<ErrorBoundary fallback={<div class="text-red">Something went wrong</div>} />
fallback={const appErrorFallback: JSX.Element
appErrorFallback}> <const Suspense: ExoticComponent<SuspenseProps>
Lets you display a fallback until its children have finished loading.
Suspense SuspenseProps.fallback?: ReactNode
A fallback react tree to show when a Suspense child (like React.lazy) suspends
fallback={const appLoadingFallback: JSX.Element
appLoadingFallback}> <const StoreRegistryProvider: ({ storeRegistry, children }: StoreRegistryProviderProps) => JSX.Element
React context provider that makes a
StoreRegistry
available to descendant components.
Wrap your application (or a subtree) with this provider to enable
useStore
and
useStoreRegistry
hooks within that tree.
StoreRegistryProvider storeRegistry: StoreRegistry
storeRegistry={const storeRegistry: StoreRegistry
storeRegistry}>{children: ReactNode
children}</const StoreRegistryProvider: ({ storeRegistry, children }: StoreRegistryProviderProps) => JSX.Element
React context provider that makes a
StoreRegistry
available to descendant components.
Wrap your application (or a subtree) with this provider to enable
useStore
and
useStoreRegistry
hooks within that tree.
StoreRegistryProvider> </const Suspense: ExoticComponent<SuspenseProps>
Lets you display a fallback until its children have finished loading.
Suspense> </class ErrorBoundary
A reusable React error boundary component.
Wrap this component around other React components to "catch" errors and render a fallback UI.
This package is built on top of React error boundaries,
so it has all of the advantages and constraints of that API.
This means that it can't catch errors during:
- Server side rendering
- Event handlers
- Asynchronous code (including effects)
ℹ️ The component provides several ways to render a fallback: fallback, fallbackRender, and FallbackComponent.
Refer to the documentation to determine which is best for your application.
ℹ️ This is a client component. You can only pass props to it that are serializeable or use it in files that have a "use client"; directive.
ErrorBoundary> )}3. Use the Store
Section titled “3. Use the Store”Components can now access the store via your custom hook:
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 { function useEffect(effect: EffectCallback, deps?: DependencyList): void
Accepts a function that contains imperative, possibly effectful code.
useEffect } from 'react'
import { import events
events } from './schema.ts'import { import useAppStore
useAppStore } from './store.ts'
export const const MyComponent: FC
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 = () => { const const store: any
store = import useAppStore
useAppStore()
function useEffect(effect: EffectCallback, deps?: DependencyList): void
Accepts a function that contains imperative, possibly effectful code.
useEffect(() => { const store: any
store.any
commit(import events
events.any
todoCreated({ id: string
id: '1', text: string
text: 'Hello, world!', createdAt: Date
createdAt: new var Date: DateConstructornew () => Date (+3 overloads)
Date() })) }, [const store: any
store])
return <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>...</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, })Querying Data
Section titled “Querying Data”Use store.useQuery() to subscribe to reactive queries:
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 './schema.ts'import { import useAppStore
useAppStore } from './store.ts'
const const query$: LiveQueryDef<unknown, "def">
query$ = 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
where({ completed: boolean
completed: true }).any
orderBy('id', 'desc'), { label?: string
Used for debugging / devtools
label: 'completedTodos',})
export const const CompletedTodos: FC
CompletedTodos: 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 query$: LiveQueryDef<unknown, "def">
query$)
return ( <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> {const todos: any
todos.any
map((todo: any
todo) => ( <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div Attributes.key?: Key | null | undefined
key={todo: any
todo.any
id}>{todo: any
todo.any
text}</JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> ))} </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, })Client Documents
Section titled “Client Documents”Use store.useClientDocument() for client-specific state:
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, function useCallback<T extends Function>(callback: T, deps: DependencyList): T
useCallback will return a memoized version of the callback that only changes if one of the inputs
has changed.
useCallback } from 'react'
import { import tables
tables } from './schema.ts'import { import useAppStore
useAppStore } from './store.ts'
export const const TodoItem: FC<{ id: string;}>
TodoItem: 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<{ id: string
id: string }> = ({ id: string
id }) => { const const store: any
store = import useAppStore
useAppStore() const [const todo: any
todo, const updateTodo: any
updateTodo] = const store: any
store.any
useClientDocument(import tables
tables.any
uiState, id: string
id)
const const handleClick: () => void
handleClick = useCallback<() => void>(callback: () => void, deps: DependencyList): () => void
useCallback will return a memoized version of the callback that only changes if one of the inputs
has changed.
useCallback(() => { const updateTodo: any
updateTodo({ newTodoText: string
newTodoText: 'Hello, world!' }) }, [const updateTodo: any
updateTodo])
return ( <JSX.IntrinsicElements.button: DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button ButtonHTMLAttributes<HTMLButtonElement>.type?: "button" | "submit" | "reset" | undefined
type="button" DOMAttributes<HTMLButtonElement>.onClick?: MouseEventHandler<HTMLButtonElement> | undefined
onClick={const handleClick: () => void
handleClick}> {const todo: any
todo.any
newTodoText} </JSX.IntrinsicElements.button: DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button> )}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, })Advanced Patterns
Section titled “Advanced Patterns”Multiple Stores
Section titled “Multiple Stores”You can have multiple stores within a single React application. This is useful for:
- Partial data synchronization - Load only the data you need, when you need it
- Multi-tenant applications - Separate stores for each workspace, organization, or team (like Slack workspaces or Linear teams)
Use the storeOptions() helper for type-safe, reusable configurations, and then create multiple instances for the same store configuration by using different storeId values:
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 storeOptions: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>(options: RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>) => RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>
Helper for defining reusable store options with full type inference. Returns
options that can be passed to useStore() or storeRegistry.preload().
storeOptions } from '@livestore/livestore'
import { import schema
schema } from './issue.schema.ts'
// Define reusable store configuration with storeOptions()// This helper provides type safety and can be reused across your appexport const const issueStoreOptions: (issueId: string) => RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>
issueStoreOptions = (issueId: string
issueId: string) => storeOptions<any, {}, Codec<Json, Json, never, never>>(options: RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>): RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>
Helper for defining reusable store options with full type inference. Returns
options that can be passed to useStore() or storeRegistry.preload().
storeOptions({ 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: `issue-${issueId: string
issueId}`, 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: 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(), })import { import Events
Events, const makeSchema: <TInputSchema extends InputSchema>(inputSchema: TInputSchema) => FromInputSchema.DeriveSchema<TInputSchema>
makeSchema, import Schema
Schema, import State
State } from '@livestore/livestore'
// Event definitionsexport const const events: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>;}
events = { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">>
issueCreated: import Events
Events.synced<"v1.IssueCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">>(args: { name: "v1.IssueCreated"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<...>; }, "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.IssueCreated"
name: 'v1.IssueCreated', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}>(fields: { readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}): Schema.Struct<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}>
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, title: Schema.String
title: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String, status: Schema.Literals<readonly ["todo", "done"]>
status: import Schema
Schema.function Literals<readonly ["todo", "done"]>(literals: readonly ["todo", "done"]): Schema.Literals<readonly ["todo", "done"]>
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(['todo', 'done']), }), }), issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">>
issueStatusChanged: import Events
Events.synced<"v1.IssueStatusChanged", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">>(args: { name: "v1.IssueStatusChanged"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "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.IssueStatusChanged"
name: 'v1.IssueStatusChanged', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}>(fields: { readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}): Schema.Struct<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}>
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, status: Schema.Literals<readonly ["todo", "done"]>
status: import Schema
Schema.function Literals<readonly ["todo", "done"]>(literals: readonly ["todo", "done"]): Schema.Literals<readonly ["todo", "done"]>
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(['todo', 'done']), }), }),}
// State definitionexport const const tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables = { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
issue: import State
State.import SQLite
SQLite.function table<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; };}, Partial<...>>(args: { ...;} & Partial<...>): State.SQLite.TableDef<...> (+2 overloads)
Creates a SQLite table definition from columns or an Effect Schema.
This function supports two main ways to define a table:
- Using explicit column definitions
- Using an Effect Schema (either the
name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columnsconst usersTable = State.SQLite.table({ name: 'users', columns: { id: State.SQLite.text({ primaryKey: true }), name: State.SQLite.text({ nullable: false }), email: State.SQLite.text({ nullable: false }), age: State.SQLite.integer({ nullable: true }), },})
// Using Effect Schema with annotationsimport { Schema } from '@livestore/utils/effect'
const UserSchema = Schema.Struct({ id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement), email: Schema.String.pipe(State.SQLite.withUnique), name: Schema.String, active: Schema.Boolean.pipe(State.SQLite.withDefault(true)), createdAt: Schema.optional(Schema.Date),})
// Option 1: With explicit nameconst usersTable = State.SQLite.table({ name: 'users', schema: UserSchema,})
// Option 2: With name from schema annotation (title or identifier)const AnnotatedUserSchema = UserSchema.annotate({ title: 'users' })const usersTable2 = State.SQLite.table({ schema: AnnotatedUserSchema,})
// Adding indexesconst PostSchema = Schema.Struct({ id: Schema.String.pipe(State.SQLite.withPrimaryKey), title: Schema.String, authorId: Schema.String, createdAt: Schema.Date,}).annotate({ identifier: 'posts' })
const postsTable = State.SQLite.table({ schema: PostSchema, indexes: [ { name: 'idx_posts_author', columns: ['authorId'] }, { name: 'idx_posts_created', columns: ['createdAt'], isUnique: false }, ],})
table({ name: "issue"
name: 'issue', columns: { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; };}
columns: { id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;}
id: import State
State.import SQLite
SQLite.const text: <string, string, false, typeof NoDefault, true, false>(args: { schema?: Schema.Codec<string, string, never, never>; default?: typeof NoDefault; nullable?: false; primaryKey?: true; autoIncrement?: false;}) => { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;} (+1 overload)
text({ primaryKey?: true
primaryKey: true }), title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;}
title: 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(), status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;}
status: 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(), }, }),}
const const materializers: { "v1.IssueCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>>; "v1.IssueStatusChanged": State.SQLite.Materializer<State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>>;}
materializers = import State
State.import SQLite
SQLite.const materializers: <{ issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<...>, Schema.Struct.ReadonlySide<...>>;}>(_eventDefRecord: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<...>, Schema.Struct.ReadonlySide<...>>;}, 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: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>;}
events, { 'v1.IssueCreated': ({ id: string
id, title: string
title, status: "todo" | "done"
status }) => const tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
issue.insert: (values: { readonly status: string; readonly id: string; readonly title: string;}) => QueryBuilder<readonly Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly title: Schema.Codec<string, string, never, never>; readonly status: Schema.Codec<string, string, never, never>;}, "Type">[], State.SQLite.TableDefBase<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { ...; }; readonly status: { ...; };}>, State.SQLite.WithDefaults<...>>, "select" | ... 6 more ... | "row">
Insert a new row into the table.
insert({ id: string
id, title: string
title, status: string
status }), 'v1.IssueStatusChanged': ({ id: string
id, status: "todo" | "done"
status }) => const tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
issue.update: (values: Partial<Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly title: Schema.Codec<string, string, never, never>; readonly status: Schema.Codec<string, string, never, never>;}, "Type">>) => QueryBuilder<readonly Schema.Struct.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly title: Schema.Codec<string, string, never, never>; readonly status: Schema.Codec<string, string, never, never>;}, "Type">[], State.SQLite.TableDefBase<...>, "select" | ... 6 more ... | "row">
Update rows in the table that match the where clause
Example:
db.todos.update({ status: 'completed' }).where({ id: '123' })
update({ status?: string
status }).where: (params: Partial<{ readonly id: string | { op: Exclude<QueryBuilder<TResult, TTableDef extends State.SQLite.TableDefBase<any, any>, TWithout extends QueryBuilder.ApiFeature = never>.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[]; } | undefined; readonly title: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { ...; } | undefined; readonly status: string | ... 2 more ... | undefined;}>) => QueryBuilder<...> (+3 overloads)
where({ id?: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string;} | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[];} | undefined
id }),})
const const state: InternalState
state = import State
State.import SQLite
SQLite.const makeState: <{ tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; }; materializers: { ...; };}>(inputSchema: { tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; }; materializers: { ...; };}) => InternalState
makeState({ tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables, materializers: { "v1.IssueCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>>; "v1.IssueStatusChanged": State.SQLite.Materializer<State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>>;}
materializers })
export const const schema: FromInputSchema.DeriveSchema<{ events: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>; }; state: InternalState;}>
schema = makeSchema<{ events: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<...>, Schema.Struct.ReadonlySide<...>>; }; state: InternalState;}>(inputSchema: { events: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<...>, Schema.Struct.ReadonlySide<...>>; }; state: InternalState;}): FromInputSchema.DeriveSchema<...>
makeSchema({ events: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>;}
events, state: InternalState
state })import { const Suspense: ExoticComponent<SuspenseProps>
Lets you display a fallback until its children have finished loading.
Suspense } from 'react'import { class ErrorBoundary
A reusable React error boundary component.
Wrap this component around other React components to "catch" errors and render a fallback UI.
This package is built on top of React error boundaries,
so it has all of the advantages and constraints of that API.
This means that it can't catch errors during:
- Server side rendering
- Event handlers
- Asynchronous code (including effects)
ℹ️ The component provides several ways to render a fallback: fallback, fallbackRender, and FallbackComponent.
Refer to the documentation to determine which is best for your application.
ℹ️ This is a client component. You can only pass props to it that are serializeable or use it in files that have a "use client"; directive.
ErrorBoundary } from 'react-error-boundary'
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 { 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 tables
tables } from './issue.schema.ts'import { import issueStoreOptions
issueStoreOptions } from './issue.store.ts'
const const issueErrorFallback: JSX.Element
issueErrorFallback = <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>Error loading issue</JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>const const issueLoadingFallback: JSX.Element
issueLoadingFallback = <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>Loading issue...</JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>
export const const IssueView: ({ issueId }: { issueId: string;}) => JSX.Element
IssueView = ({ issueId: string
issueId }: { issueId: string
issueId: string }) => { // useStore() suspends the component until the store is loaded // If the same store was already loaded, it returns immediately const const issueStore: Store<LiveStoreSchema<DbSchema, EventDefRecord>, {}> & ReactApi
issueStore = useStore<LiveStoreSchema<DbSchema, EventDefRecord>, {}, Codec<Json, Json, never, never>>(options: RegistryStoreOptions<LiveStoreSchema<DbSchema, EventDefRecord>, {}, Codec<Json, Json, never, never>>): Store<LiveStoreSchema<DbSchema, EventDefRecord>, {}> & ReactApi
Returns a store instance augmented with hooks (store.useQuery() and store.useClientDocument()) for reactive queries.
useStore(import issueStoreOptions
issueStoreOptions(issueId: string
issueId))
// Query data from the store const [const issue: any
issue] = const issueStore: Store<LiveStoreSchema<DbSchema, EventDefRecord>, {}> & ReactApi
issueStore.useQuery: <LiveQueryDef<unknown, "def">>(queryable: LiveQueryDef<unknown, "def">, options?: { store?: Store;}) => unknown
Returns the result of a query and subscribes to future updates.
Example:
const App = () => { const todos = useQuery(queryDb(tables.todos.query.where({ complete: true }))) return <div>{todos.map((todo) => <div key={todo.id}>{todo.title}</div>)}</div>}
useQuery(queryDb<unknown, unknown>(queryInput: QueryBuilder<unknown, any, any> | QueryInputRaw<unknown, readonly 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
issue.any
select().any
where({ id: string
id: issueId: string
issueId })))
if (const issue: any
issue == null) return <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>Issue not found</JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>
return ( <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> <JSX.IntrinsicElements.h3: DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>
h3>{const issue: any
issue.any
title}</JSX.IntrinsicElements.h3: DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>
h3> <JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p>Status: {const issue: any
issue.any
status}</JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p> </JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> )}
// Wrap with Suspense and ErrorBoundary for loading and error statesexport const const IssueViewWithSuspense: ({ issueId }: { issueId: string;}) => JSX.Element
IssueViewWithSuspense = ({ issueId: string
issueId }: { issueId: string
issueId: string }) => { return ( <class ErrorBoundary
A reusable React error boundary component.
Wrap this component around other React components to "catch" errors and render a fallback UI.
This package is built on top of React error boundaries,
so it has all of the advantages and constraints of that API.
This means that it can't catch errors during:
- Server side rendering
- Event handlers
- Asynchronous code (including effects)
ℹ️ The component provides several ways to render a fallback: fallback, fallbackRender, and FallbackComponent.
Refer to the documentation to determine which is best for your application.
ℹ️ This is a client component. You can only pass props to it that are serializeable or use it in files that have a "use client"; directive.
ErrorBoundary fallback: ReactNode
Static content to render in place of an error if one is thrown.
<ErrorBoundary fallback={<div class="text-red">Something went wrong</div>} />
fallback={const issueErrorFallback: JSX.Element
issueErrorFallback}> <const Suspense: ExoticComponent<SuspenseProps>
Lets you display a fallback until its children have finished loading.
Suspense SuspenseProps.fallback?: ReactNode
A fallback react tree to show when a Suspense child (like React.lazy) suspends
fallback={const issueLoadingFallback: JSX.Element
issueLoadingFallback}> <const IssueView: ({ issueId }: { issueId: string;}) => JSX.Element
IssueView issueId: string
issueId={issueId: string
issueId} /> </const Suspense: ExoticComponent<SuspenseProps>
Lets you display a fallback until its children have finished loading.
Suspense> </class ErrorBoundary
A reusable React error boundary component.
Wrap this component around other React components to "catch" errors and render a fallback UI.
This package is built on top of React error boundaries,
so it has all of the advantages and constraints of that API.
This means that it can't catch errors during:
- Server side rendering
- Event handlers
- Asynchronous code (including effects)
ℹ️ The component provides several ways to render a fallback: fallback, fallbackRender, and FallbackComponent.
Refer to the documentation to determine which is best for your application.
ℹ️ This is a client component. You can only pass props to it that are serializeable or use it in files that have a "use client"; directive.
ErrorBoundary> )}import { import Events
Events, const makeSchema: <TInputSchema extends InputSchema>(inputSchema: TInputSchema) => FromInputSchema.DeriveSchema<TInputSchema>
makeSchema, import Schema
Schema, import State
State } from '@livestore/livestore'
// Event definitionsexport const const events: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>;}
events = { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">>
issueCreated: import Events
Events.synced<"v1.IssueCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">>(args: { name: "v1.IssueCreated"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<...>; }, "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.IssueCreated"
name: 'v1.IssueCreated', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}>(fields: { readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}): Schema.Struct<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}>
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, title: Schema.String
title: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String, status: Schema.Literals<readonly ["todo", "done"]>
status: import Schema
Schema.function Literals<readonly ["todo", "done"]>(literals: readonly ["todo", "done"]): Schema.Literals<readonly ["todo", "done"]>
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(['todo', 'done']), }), }), issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">>
issueStatusChanged: import Events
Events.synced<"v1.IssueStatusChanged", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">>(args: { name: "v1.IssueStatusChanged"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "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.IssueStatusChanged"
name: 'v1.IssueStatusChanged', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}>(fields: { readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}): Schema.Struct<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}>
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, status: Schema.Literals<readonly ["todo", "done"]>
status: import Schema
Schema.function Literals<readonly ["todo", "done"]>(literals: readonly ["todo", "done"]): Schema.Literals<readonly ["todo", "done"]>
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(['todo', 'done']), }), }),}
// State definitionexport const const tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables = { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
issue: import State
State.import SQLite
SQLite.function table<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; };}, Partial<...>>(args: { ...;} & Partial<...>): State.SQLite.TableDef<...> (+2 overloads)
Creates a SQLite table definition from columns or an Effect Schema.
This function supports two main ways to define a table:
- Using explicit column definitions
- Using an Effect Schema (either the
name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columnsconst usersTable = State.SQLite.table({ name: 'users', columns: { id: State.SQLite.text({ primaryKey: true }), name: State.SQLite.text({ nullable: false }), email: State.SQLite.text({ nullable: false }), age: State.SQLite.integer({ nullable: true }), },})
// Using Effect Schema with annotationsimport { Schema } from '@livestore/utils/effect'
const UserSchema = Schema.Struct({ id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement), email: Schema.String.pipe(State.SQLite.withUnique), name: Schema.String, active: Schema.Boolean.pipe(State.SQLite.withDefault(true)), createdAt: Schema.optional(Schema.Date),})
// Option 1: With explicit nameconst usersTable = State.SQLite.table({ name: 'users', schema: UserSchema,})
// Option 2: With name from schema annotation (title or identifier)const AnnotatedUserSchema = UserSchema.annotate({ title: 'users' })const usersTable2 = State.SQLite.table({ schema: AnnotatedUserSchema,})
// Adding indexesconst PostSchema = Schema.Struct({ id: Schema.String.pipe(State.SQLite.withPrimaryKey), title: Schema.String, authorId: Schema.String, createdAt: Schema.Date,}).annotate({ identifier: 'posts' })
const postsTable = State.SQLite.table({ schema: PostSchema, indexes: [ { name: 'idx_posts_author', columns: ['authorId'] }, { name: 'idx_posts_created', columns: ['createdAt'], isUnique: false }, ],})
table({ name: "issue"
name: 'issue', columns: { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; };}
columns: { id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;}
id: import State
State.import SQLite
SQLite.const text: <string, string, false, typeof NoDefault, true, false>(args: { schema?: Schema.Codec<string, string, never, never>; default?: typeof NoDefault; nullable?: false; primaryKey?: true; autoIncrement?: false;}) => { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;} (+1 overload)
text({ primaryKey?: true
primaryKey: true }), title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;}
title: 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(), status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;}
status: 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(), }, }),}
const const materializers: { "v1.IssueCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>>; "v1.IssueStatusChanged": State.SQLite.Materializer<State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>>;}
materializers = import State
State.import SQLite
SQLite.const materializers: <{ issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<...>, Schema.Struct.ReadonlySide<...>>;}>(_eventDefRecord: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<...>, Schema.Struct.ReadonlySide<...>>;}, 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: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>;}
events, { 'v1.IssueCreated': ({ id: string
id, title: string
title, status: "todo" | "done"
status }) => const tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
issue.insert: (values: { readonly status: string; readonly id: string; readonly title: string;}) => QueryBuilder<readonly Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly title: Schema.Codec<string, string, never, never>; readonly status: Schema.Codec<string, string, never, never>;}, "Type">[], State.SQLite.TableDefBase<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { ...; }; readonly status: { ...; };}>, State.SQLite.WithDefaults<...>>, "select" | ... 6 more ... | "row">
Insert a new row into the table.
insert({ id: string
id, title: string
title, status: string
status }), 'v1.IssueStatusChanged': ({ id: string
id, status: "todo" | "done"
status }) => const tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
issue.update: (values: Partial<Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly title: Schema.Codec<string, string, never, never>; readonly status: Schema.Codec<string, string, never, never>;}, "Type">>) => QueryBuilder<readonly Schema.Struct.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly title: Schema.Codec<string, string, never, never>; readonly status: Schema.Codec<string, string, never, never>;}, "Type">[], State.SQLite.TableDefBase<...>, "select" | ... 6 more ... | "row">
Update rows in the table that match the where clause
Example:
db.todos.update({ status: 'completed' }).where({ id: '123' })
update({ status?: string
status }).where: (params: Partial<{ readonly id: string | { op: Exclude<QueryBuilder<TResult, TTableDef extends State.SQLite.TableDefBase<any, any>, TWithout extends QueryBuilder.ApiFeature = never>.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[]; } | undefined; readonly title: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { ...; } | undefined; readonly status: string | ... 2 more ... | undefined;}>) => QueryBuilder<...> (+3 overloads)
where({ id?: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string;} | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[];} | undefined
id }),})
const const state: InternalState
state = import State
State.import SQLite
SQLite.const makeState: <{ tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; }; materializers: { ...; };}>(inputSchema: { tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; }; materializers: { ...; };}) => InternalState
makeState({ tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables, materializers: { "v1.IssueCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>>; "v1.IssueStatusChanged": State.SQLite.Materializer<State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>>;}
materializers })
export const const schema: FromInputSchema.DeriveSchema<{ events: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>; }; state: InternalState;}>
schema = makeSchema<{ events: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<...>, Schema.Struct.ReadonlySide<...>>; }; state: InternalState;}>(inputSchema: { events: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<...>, Schema.Struct.ReadonlySide<...>>; }; state: InternalState;}): FromInputSchema.DeriveSchema<...>
makeSchema({ events: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>;}
events, state: InternalState
state })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 storeOptions: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>(options: RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>) => RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>
Helper for defining reusable store options with full type inference. Returns
options that can be passed to useStore() or storeRegistry.preload().
storeOptions } from '@livestore/livestore'
import { import schema
schema } from './issue.schema.ts'
// Define reusable store configuration with storeOptions()// This helper provides type safety and can be reused across your appexport const const issueStoreOptions: (issueId: string) => RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>
issueStoreOptions = (issueId: string
issueId: string) => storeOptions<any, {}, Codec<Json, Json, never, never>>(options: RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>): RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>
Helper for defining reusable store options with full type inference. Returns
options that can be passed to useStore() or storeRegistry.preload().
storeOptions({ 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: `issue-${issueId: string
issueId}`, 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: 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(), })Each store instance is completely isolated with its own data, event log, and synchronization state.
Preloading Stores
Section titled “Preloading Stores”When you know a store will be needed soon, preload it in advance to warm up the cache:
import { const Suspense: ExoticComponent<SuspenseProps>
Lets you display a fallback until its children have finished loading.
Suspense, function useCallback<T extends Function>(callback: T, deps: DependencyList): T
useCallback will return a memoized version of the callback that only changes if one of the inputs
has changed.
useCallback, function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>] (+1 overload)
Returns a stateful value, and a function to update it.
useState } from 'react'import { class ErrorBoundary
A reusable React error boundary component.
Wrap this component around other React components to "catch" errors and render a fallback UI.
This package is built on top of React error boundaries,
so it has all of the advantages and constraints of that API.
This means that it can't catch errors during:
- Server side rendering
- Event handlers
- Asynchronous code (including effects)
ℹ️ The component provides several ways to render a fallback: fallback, fallbackRender, and FallbackComponent.
Refer to the documentation to determine which is best for your application.
ℹ️ This is a client component. You can only pass props to it that are serializeable or use it in files that have a "use client"; directive.
ErrorBoundary } from 'react-error-boundary'
import { const useStoreRegistry: (override?: StoreRegistry) => StoreRegistry
Hook to access the
StoreRegistry
from context. Useful for advanced operations like preloading.
useStoreRegistry } from '@livestore/react'
import { import issueStoreOptions
issueStoreOptions } from './issue.store.ts'import { import IssueView
IssueView } from './IssueView.tsx'
const const preloadedIssueErrorFallback: JSX.Element
preloadedIssueErrorFallback = <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>Error loading issue</JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>const const preloadedIssueLoadingFallback: JSX.Element
preloadedIssueLoadingFallback = <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>Loading issue...</JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>
export const const PreloadedIssue: ({ issueId }: { issueId: string;}) => JSX.Element
PreloadedIssue = ({ issueId: string
issueId }: { issueId: string
issueId: string }) => { const [const showIssue: boolean
showIssue, const setShowIssue: Dispatch<SetStateAction<boolean>>
setShowIssue] = useState<boolean>(initialState: boolean | (() => boolean)): [boolean, Dispatch<SetStateAction<boolean>>] (+1 overload)
Returns a stateful value, and a function to update it.
useState(false) const const storeRegistry: StoreRegistry
storeRegistry = function useStoreRegistry(override?: StoreRegistry): StoreRegistry
Hook to access the
StoreRegistry
from context. Useful for advanced operations like preloading.
useStoreRegistry()
// Preload the store when the user hovers (before they click) const const handleMouseEnter: () => void
handleMouseEnter = useCallback<() => void>(callback: () => void, deps: DependencyList): () => void
useCallback will return a memoized version of the callback that only changes if one of the inputs
has changed.
useCallback(() => { const storeRegistry: StoreRegistry
storeRegistry.StoreRegistry.preload: <LiveStoreSchema<DbSchema, EventDefRecord>, {}, Codec<Json, Json, never, never>>(options: RegistryStoreOptions<LiveStoreSchema<DbSchema, EventDefRecord>, {}, Codec<Json, Json, never, never>>) => Promise<void>
Loads a store (without suspending) to warm up the cache.
preload({ ...import issueStoreOptions
issueStoreOptions(issueId: string
issueId), RegistryStoreOptions<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<...>>.unusedCacheTime?: number
The time in milliseconds that this store should remain
in memory after becoming unused. When this store becomes
unused (no active retentions), it will be disposed after this duration.
Stores transition to the unused state as soon as they have no
active retentions, so when all components which use that store
have unmounted.
unusedCacheTime: 10_000, // Optionally override options }) }, [issueId: string
issueId, const storeRegistry: StoreRegistry
storeRegistry])
const const handleClick: () => void
handleClick = useCallback<() => void>(callback: () => void, deps: DependencyList): () => void
useCallback will return a memoized version of the callback that only changes if one of the inputs
has changed.
useCallback(() => { const setShowIssue: (value: SetStateAction<boolean>) => void
setShowIssue(true) }, [])
return ( <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> {const showIssue: boolean
showIssue == null ? ( <JSX.IntrinsicElements.button: DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button ButtonHTMLAttributes<HTMLButtonElement>.type?: "button" | "submit" | "reset" | undefined
type="button" DOMAttributes<HTMLButtonElement>.onMouseEnter?: MouseEventHandler<HTMLButtonElement> | undefined
onMouseEnter={const handleMouseEnter: () => void
handleMouseEnter} DOMAttributes<HTMLButtonElement>.onClick?: MouseEventHandler<HTMLButtonElement> | undefined
onClick={const handleClick: () => void
handleClick}> Show Issue </JSX.IntrinsicElements.button: DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button> ) : ( <class ErrorBoundary
A reusable React error boundary component.
Wrap this component around other React components to "catch" errors and render a fallback UI.
This package is built on top of React error boundaries,
so it has all of the advantages and constraints of that API.
This means that it can't catch errors during:
- Server side rendering
- Event handlers
- Asynchronous code (including effects)
ℹ️ The component provides several ways to render a fallback: fallback, fallbackRender, and FallbackComponent.
Refer to the documentation to determine which is best for your application.
ℹ️ This is a client component. You can only pass props to it that are serializeable or use it in files that have a "use client"; directive.
ErrorBoundary fallback: ReactNode
Static content to render in place of an error if one is thrown.
<ErrorBoundary fallback={<div class="text-red">Something went wrong</div>} />
fallback={const preloadedIssueErrorFallback: JSX.Element
preloadedIssueErrorFallback}> <const Suspense: ExoticComponent<SuspenseProps>
Lets you display a fallback until its children have finished loading.
Suspense SuspenseProps.fallback?: ReactNode
A fallback react tree to show when a Suspense child (like React.lazy) suspends
fallback={const preloadedIssueLoadingFallback: JSX.Element
preloadedIssueLoadingFallback}> <import IssueView
IssueView issueId: string
issueId={issueId: string
issueId} /> </const Suspense: ExoticComponent<SuspenseProps>
Lets you display a fallback until its children have finished loading.
Suspense> </class ErrorBoundary
A reusable React error boundary component.
Wrap this component around other React components to "catch" errors and render a fallback UI.
This package is built on top of React error boundaries,
so it has all of the advantages and constraints of that API.
This means that it can't catch errors during:
- Server side rendering
- Event handlers
- Asynchronous code (including effects)
ℹ️ The component provides several ways to render a fallback: fallback, fallbackRender, and FallbackComponent.
Refer to the documentation to determine which is best for your application.
ℹ️ This is a client component. You can only pass props to it that are serializeable or use it in files that have a "use client"; directive.
ErrorBoundary> )} </JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> )}import { import Events
Events, const makeSchema: <TInputSchema extends InputSchema>(inputSchema: TInputSchema) => FromInputSchema.DeriveSchema<TInputSchema>
makeSchema, import Schema
Schema, import State
State } from '@livestore/livestore'
// Event definitionsexport const const events: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>;}
events = { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">>
issueCreated: import Events
Events.synced<"v1.IssueCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">>(args: { name: "v1.IssueCreated"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<...>; }, "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.IssueCreated"
name: 'v1.IssueCreated', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}>(fields: { readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}): Schema.Struct<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}>
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, title: Schema.String
title: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String, status: Schema.Literals<readonly ["todo", "done"]>
status: import Schema
Schema.function Literals<readonly ["todo", "done"]>(literals: readonly ["todo", "done"]): Schema.Literals<readonly ["todo", "done"]>
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(['todo', 'done']), }), }), issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">>
issueStatusChanged: import Events
Events.synced<"v1.IssueStatusChanged", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">>(args: { name: "v1.IssueStatusChanged"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "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.IssueStatusChanged"
name: 'v1.IssueStatusChanged', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}>(fields: { readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}): Schema.Struct<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>;}>
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, status: Schema.Literals<readonly ["todo", "done"]>
status: import Schema
Schema.function Literals<readonly ["todo", "done"]>(literals: readonly ["todo", "done"]): Schema.Literals<readonly ["todo", "done"]>
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(['todo', 'done']), }), }),}
// State definitionexport const const tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables = { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
issue: import State
State.import SQLite
SQLite.function table<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; };}, Partial<...>>(args: { ...;} & Partial<...>): State.SQLite.TableDef<...> (+2 overloads)
Creates a SQLite table definition from columns or an Effect Schema.
This function supports two main ways to define a table:
- Using explicit column definitions
- Using an Effect Schema (either the
name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columnsconst usersTable = State.SQLite.table({ name: 'users', columns: { id: State.SQLite.text({ primaryKey: true }), name: State.SQLite.text({ nullable: false }), email: State.SQLite.text({ nullable: false }), age: State.SQLite.integer({ nullable: true }), },})
// Using Effect Schema with annotationsimport { Schema } from '@livestore/utils/effect'
const UserSchema = Schema.Struct({ id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement), email: Schema.String.pipe(State.SQLite.withUnique), name: Schema.String, active: Schema.Boolean.pipe(State.SQLite.withDefault(true)), createdAt: Schema.optional(Schema.Date),})
// Option 1: With explicit nameconst usersTable = State.SQLite.table({ name: 'users', schema: UserSchema,})
// Option 2: With name from schema annotation (title or identifier)const AnnotatedUserSchema = UserSchema.annotate({ title: 'users' })const usersTable2 = State.SQLite.table({ schema: AnnotatedUserSchema,})
// Adding indexesconst PostSchema = Schema.Struct({ id: Schema.String.pipe(State.SQLite.withPrimaryKey), title: Schema.String, authorId: Schema.String, createdAt: Schema.Date,}).annotate({ identifier: 'posts' })
const postsTable = State.SQLite.table({ schema: PostSchema, indexes: [ { name: 'idx_posts_author', columns: ['authorId'] }, { name: 'idx_posts_created', columns: ['createdAt'], isUnique: false }, ],})
table({ name: "issue"
name: 'issue', columns: { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; };}
columns: { id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;}
id: import State
State.import SQLite
SQLite.const text: <string, string, false, typeof NoDefault, true, false>(args: { schema?: Schema.Codec<string, string, never, never>; default?: typeof NoDefault; nullable?: false; primaryKey?: true; autoIncrement?: false;}) => { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;} (+1 overload)
text({ primaryKey?: true
primaryKey: true }), title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;}
title: 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(), status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false;}
status: 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(), }, }),}
const const materializers: { "v1.IssueCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>>; "v1.IssueStatusChanged": State.SQLite.Materializer<State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>>;}
materializers = import State
State.import SQLite
SQLite.const materializers: <{ issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<...>, Schema.Struct.ReadonlySide<...>>;}>(_eventDefRecord: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<...>, Schema.Struct.ReadonlySide<...>>;}, 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: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>;}
events, { 'v1.IssueCreated': ({ id: string
id, title: string
title, status: "todo" | "done"
status }) => const tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
issue.insert: (values: { readonly status: string; readonly id: string; readonly title: string;}) => QueryBuilder<readonly Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly title: Schema.Codec<string, string, never, never>; readonly status: Schema.Codec<string, string, never, never>;}, "Type">[], State.SQLite.TableDefBase<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { ...; }; readonly status: { ...; };}>, State.SQLite.WithDefaults<...>>, "select" | ... 6 more ... | "row">
Insert a new row into the table.
insert({ id: string
id, title: string
title, status: string
status }), 'v1.IssueStatusChanged': ({ id: string
id, status: "todo" | "done"
status }) => const tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
issue.update: (values: Partial<Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly title: Schema.Codec<string, string, never, never>; readonly status: Schema.Codec<string, string, never, never>;}, "Type">>) => QueryBuilder<readonly Schema.Struct.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly title: Schema.Codec<string, string, never, never>; readonly status: Schema.Codec<string, string, never, never>;}, "Type">[], State.SQLite.TableDefBase<...>, "select" | ... 6 more ... | "row">
Update rows in the table that match the where clause
Example:
db.todos.update({ status: 'completed' }).where({ id: '123' })
update({ status?: string
status }).where: (params: Partial<{ readonly id: string | { op: Exclude<QueryBuilder<TResult, TTableDef extends State.SQLite.TableDefBase<any, any>, TWithout extends QueryBuilder.ApiFeature = never>.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[]; } | undefined; readonly title: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { ...; } | undefined; readonly status: string | ... 2 more ... | undefined;}>) => QueryBuilder<...> (+3 overloads)
where({ id?: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string;} | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[];} | undefined
id }),})
const const state: InternalState
state = import State
State.import SQLite
SQLite.const makeState: <{ tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; }; materializers: { ...; };}>(inputSchema: { tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; }; materializers: { ...; };}) => InternalState
makeState({ tables: { issue: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"issue", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly title: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly status: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables, materializers: { "v1.IssueCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>>; "v1.IssueStatusChanged": State.SQLite.Materializer<State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>>;}
materializers })
export const const schema: FromInputSchema.DeriveSchema<{ events: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>; }; state: InternalState;}>
schema = makeSchema<{ events: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<...>, Schema.Struct.ReadonlySide<...>>; }; state: InternalState;}>(inputSchema: { events: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<...>, Schema.Struct.ReadonlySide<...>>; }; state: InternalState;}): FromInputSchema.DeriveSchema<...>
makeSchema({ events: { issueCreated: State.SQLite.EventDef<"v1.IssueCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly title: Schema.String; readonly status: Schema.Literals<readonly ["todo", "done"]>; }, "Encoded">>; issueStatusChanged: State.SQLite.EventDef<"v1.IssueStatusChanged", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly status: Schema.Literals<readonly [...]>; }, "Type">, Schema.Struct.ReadonlySide<...>>;}
events, state: InternalState
state })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 storeOptions: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>(options: RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>) => RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>
Helper for defining reusable store options with full type inference. Returns
options that can be passed to useStore() or storeRegistry.preload().
storeOptions } from '@livestore/livestore'
import { import schema
schema } from './issue.schema.ts'
// Define reusable store configuration with storeOptions()// This helper provides type safety and can be reused across your appexport const const issueStoreOptions: (issueId: string) => RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>
issueStoreOptions = (issueId: string
issueId: string) => storeOptions<any, {}, Codec<Json, Json, never, never>>(options: RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>): RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>
Helper for defining reusable store options with full type inference. Returns
options that can be passed to useStore() or storeRegistry.preload().
storeOptions({ 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: `issue-${issueId: string
issueId}`, 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: 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(), })import { const Suspense: ExoticComponent<SuspenseProps>
Lets you display a fallback until its children have finished loading.
Suspense } from 'react'import { class ErrorBoundary
A reusable React error boundary component.
Wrap this component around other React components to "catch" errors and render a fallback UI.
This package is built on top of React error boundaries,
so it has all of the advantages and constraints of that API.
This means that it can't catch errors during:
- Server side rendering
- Event handlers
- Asynchronous code (including effects)
ℹ️ The component provides several ways to render a fallback: fallback, fallbackRender, and FallbackComponent.
Refer to the documentation to determine which is best for your application.
ℹ️ This is a client component. You can only pass props to it that are serializeable or use it in files that have a "use client"; directive.
ErrorBoundary } from 'react-error-boundary'
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 { 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 tables
tables } from './issue.schema.ts'import { import issueStoreOptions
issueStoreOptions } from './issue.store.ts'
const const issueErrorFallback: JSX.Element
issueErrorFallback = <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>Error loading issue</JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>const const issueLoadingFallback: JSX.Element
issueLoadingFallback = <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>Loading issue...</JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>
export const const IssueView: ({ issueId }: { issueId: string;}) => JSX.Element
IssueView = ({ issueId: string
issueId }: { issueId: string
issueId: string }) => { // useStore() suspends the component until the store is loaded // If the same store was already loaded, it returns immediately const const issueStore: Store<LiveStoreSchema<DbSchema, EventDefRecord>, {}> & ReactApi
issueStore = useStore<LiveStoreSchema<DbSchema, EventDefRecord>, {}, Codec<Json, Json, never, never>>(options: RegistryStoreOptions<LiveStoreSchema<DbSchema, EventDefRecord>, {}, Codec<Json, Json, never, never>>): Store<LiveStoreSchema<DbSchema, EventDefRecord>, {}> & ReactApi
Returns a store instance augmented with hooks (store.useQuery() and store.useClientDocument()) for reactive queries.
useStore(import issueStoreOptions
issueStoreOptions(issueId: string
issueId))
// Query data from the store const [const issue: any
issue] = const issueStore: Store<LiveStoreSchema<DbSchema, EventDefRecord>, {}> & ReactApi
issueStore.useQuery: <LiveQueryDef<unknown, "def">>(queryable: LiveQueryDef<unknown, "def">, options?: { store?: Store;}) => unknown
Returns the result of a query and subscribes to future updates.
Example:
const App = () => { const todos = useQuery(queryDb(tables.todos.query.where({ complete: true }))) return <div>{todos.map((todo) => <div key={todo.id}>{todo.title}</div>)}</div>}
useQuery(queryDb<unknown, unknown>(queryInput: QueryBuilder<unknown, any, any> | QueryInputRaw<unknown, readonly 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
issue.any
select().any
where({ id: string
id: issueId: string
issueId })))
if (const issue: any
issue == null) return <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>Issue not found</JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>
return ( <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> <JSX.IntrinsicElements.h3: DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>
h3>{const issue: any
issue.any
title}</JSX.IntrinsicElements.h3: DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>
h3> <JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p>Status: {const issue: any
issue.any
status}</JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p> </JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> )}
// Wrap with Suspense and ErrorBoundary for loading and error statesexport const const IssueViewWithSuspense: ({ issueId }: { issueId: string;}) => JSX.Element
IssueViewWithSuspense = ({ issueId: string
issueId }: { issueId: string
issueId: string }) => { return ( <class ErrorBoundary
A reusable React error boundary component.
Wrap this component around other React components to "catch" errors and render a fallback UI.
This package is built on top of React error boundaries,
so it has all of the advantages and constraints of that API.
This means that it can't catch errors during:
- Server side rendering
- Event handlers
- Asynchronous code (including effects)
ℹ️ The component provides several ways to render a fallback: fallback, fallbackRender, and FallbackComponent.
Refer to the documentation to determine which is best for your application.
ℹ️ This is a client component. You can only pass props to it that are serializeable or use it in files that have a "use client"; directive.
ErrorBoundary fallback: ReactNode
Static content to render in place of an error if one is thrown.
<ErrorBoundary fallback={<div class="text-red">Something went wrong</div>} />
fallback={const issueErrorFallback: JSX.Element
issueErrorFallback}> <const Suspense: ExoticComponent<SuspenseProps>
Lets you display a fallback until its children have finished loading.
Suspense SuspenseProps.fallback?: ReactNode
A fallback react tree to show when a Suspense child (like React.lazy) suspends
fallback={const issueLoadingFallback: JSX.Element
issueLoadingFallback}> <const IssueView: ({ issueId }: { issueId: string;}) => JSX.Element
IssueView issueId: string
issueId={issueId: string
issueId} /> </const Suspense: ExoticComponent<SuspenseProps>
Lets you display a fallback until its children have finished loading.
Suspense> </class ErrorBoundary
A reusable React error boundary component.
Wrap this component around other React components to "catch" errors and render a fallback UI.
This package is built on top of React error boundaries,
so it has all of the advantages and constraints of that API.
This means that it can't catch errors during:
- Server side rendering
- Event handlers
- Asynchronous code (including effects)
ℹ️ The component provides several ways to render a fallback: fallback, fallbackRender, and FallbackComponent.
Refer to the documentation to determine which is best for your application.
ℹ️ This is a client component. You can only pass props to it that are serializeable or use it in files that have a "use client"; directive.
ErrorBoundary> )}StoreId Guidelines
Section titled “StoreId Guidelines”When creating storeId values:
- Valid characters - Only alphanumeric characters, underscores (
_), and hyphens (-) are allowed (regex:/^[a-zA-Z0-9_-]+$/) - Globally unique - Prefer globally unique IDs (e.g., nanoid) to prevent collisions
- Use namespaces - Prefix with the entity type (e.g.,
workspace-abc123,issue-456) to avoid collisions and easier identification when debugging - Keep them stable - The same entity should always use the same
storeIdacross renders - Sanitize user input - If incorporating user data, validate/sanitize to prevent injection attacks
- Document your conventions - Document special IDs like
user-currentas they’re part of your API contract
Logging
Section titled “Logging”You can customize the logger and log level for debugging:
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, // Optional: swap the logger implementation logger?: Layer<never, never, never> | undefined
Optional Effect logger layer to control logging output.
logger: import Logger
Logger.const layer: <readonly [Logger.Logger<unknown, void>]>(loggers: readonly [Logger.Logger<unknown, void>], options?: { readonly mergeWithExisting?: boolean | undefined;} | undefined) => Layer<never, never, never>
Creates a Layer which will overwrite the current set of loggers with the
specified array of loggers.
Details
If the specified array of loggers should be merged with the current set
of loggers (instead of overwriting them), set mergeWithExisting to true.
Example (Providing logger layers)
import { Effect, Logger } from "effect"
// Single logger layerconst JsonLoggerLive = Logger.layer([Logger.consoleJson])
// Multiple loggers layerconst MultiLoggerLive = Logger.layer([ Logger.consoleJson, Logger.consolePretty(), Logger.formatStructured])
// Merge with existing loggersconst AdditionalLoggerLive = Logger.layer( [Logger.consoleJson], { mergeWithExisting: true })
// Using multiple logger formatsconst jsonLogger = Logger.consoleJsonconst prettyLogger = Logger.consolePretty()
const CustomLoggerLive = Logger.layer([jsonLogger, prettyLogger])
const program = Effect.log("Application started").pipe( Effect.provide(CustomLoggerLive))
layer([import Logger
Logger.const consolePretty: (options?: { readonly colors?: "auto" | boolean | undefined; readonly stderr?: boolean | undefined; readonly formatDate?: ((date: Date) => string) | undefined; readonly mode?: "browser" | "tty" | "auto" | undefined;}) => Logger.Logger<unknown, void>
A Logger which outputs logs in a "pretty" format and writes them to the
console.
Details
For example, pretty output can render as
[09:37:17.579] INFO (#1) label=0ms: hello followed by an annotation line
such as key: value.
Example (Logging with pretty console output)
import { Effect, Logger } from "effect"
// Use the pretty console logger with default settingsconst basicPretty = Effect.log("Hello Pretty Format").pipe( Effect.provide(Logger.layer([Logger.consolePretty()])))
// Configure pretty logger optionsconst customPretty = Logger.consolePretty({ colors: true, stderr: false, mode: "tty", formatDate: (date) => date.toLocaleTimeString()})
// Perfect for development environmentconst developmentProgram = Effect.gen(function*() { yield* Effect.log("Application starting") yield* Effect.logInfo("Database connected") yield* Effect.logWarning("High memory usage detected")}).pipe( Effect.annotateLogs("environment", "development"), Effect.withLogSpan("startup"), Effect.provide(Logger.layer([customPretty])))
// Disable colors for CI/CD environmentsconst ciLogger = Logger.consolePretty({ colors: false })
consolePretty()]), // Optional: set minimum log level (use "None" to disable) logLevel?: LogLevel | undefined
Optional minimum log level for the runtime.
logLevel: 'Info', })
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(),export const const useAppStore: () => Store<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; readonly createdAt: DateFromString; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; readonly createdAt: DateFromString; }, "Encoded">>; }; state: InternalState;}>, {}> & ReactApi
useAppStore = () => useStore<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; readonly createdAt: DateFromString; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; readonly createdAt: DateFromString; }, "Encoded">>; }; state: InternalState;}>, {}, Codec<Json, Json, never, never>>(options: RegistryStoreOptions<...>): Store<...> & 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<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; readonly createdAt: DateFromString; }, "Type">, Struct.ReadonlySide<...>>; }; state: InternalState; }>, {}, Codec<...>>.schema: FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; readonly createdAt: DateFromString; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; readonly createdAt: DateFromString; }, "Encoded">>; }; state: InternalState;}>
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<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; readonly createdAt: DateFromString; }, "Type">, Struct.ReadonlySide<...>>; }; state: InternalState; }>, {}, Codec<...>>.batchUpdates?: (run: () => void) => void
Needed in React so LiveStore can apply multiple events in a single render.
batchUpdates, // Optional: swap the logger implementation logger?: Layer<never, never, never> | undefined
Optional Effect logger layer to control logging output.
logger: import Logger
Logger.const layer: <readonly [Logger.Logger<unknown, void>]>(loggers: readonly [Logger.Logger<unknown, void>], options?: { readonly mergeWithExisting?: boolean | undefined;} | undefined) => Layer<never, never, never>
Creates a Layer which will overwrite the current set of loggers with the
specified array of loggers.
Details
If the specified array of loggers should be merged with the current set
of loggers (instead of overwriting them), set mergeWithExisting to true.
Example (Providing logger layers)
import { Effect, Logger } from "effect"
// Single logger layerconst JsonLoggerLive = Logger.layer([Logger.consoleJson])
// Multiple loggers layerconst MultiLoggerLive = Logger.layer([ Logger.consoleJson, Logger.consolePretty(), Logger.formatStructured])
// Merge with existing loggersconst AdditionalLoggerLive = Logger.layer( [Logger.consoleJson], { mergeWithExisting: true })
// Using multiple logger formatsconst jsonLogger = Logger.consoleJsonconst prettyLogger = Logger.consolePretty()
const CustomLoggerLive = Logger.layer([jsonLogger, prettyLogger])
const program = Effect.log("Application started").pipe( Effect.provide(CustomLoggerLive))
layer([import Logger
Logger.const consolePretty: (options?: { readonly colors?: "auto" | boolean | undefined; readonly stderr?: boolean | undefined; readonly formatDate?: ((date: Date) => string) | undefined; readonly mode?: "browser" | "tty" | "auto" | undefined;}) => Logger.Logger<unknown, void>
A Logger which outputs logs in a "pretty" format and writes them to the
console.
Details
For example, pretty output can render as
[09:37:17.579] INFO (#1) label=0ms: hello followed by an annotation line
such as key: value.
Example (Logging with pretty console output)
import { Effect, Logger } from "effect"
// Use the pretty console logger with default settingsconst basicPretty = Effect.log("Hello Pretty Format").pipe( Effect.provide(Logger.layer([Logger.consolePretty()])))
// Configure pretty logger optionsconst customPretty = Logger.consolePretty({ colors: true, stderr: false, mode: "tty", formatDate: (date) => date.toLocaleTimeString()})
// Perfect for development environmentconst developmentProgram = Effect.gen(function*() { yield* Effect.log("Application starting") yield* Effect.logInfo("Database connected") yield* Effect.logWarning("High memory usage detected")}).pipe( Effect.annotateLogs("environment", "development"), Effect.withLogSpan("startup"), Effect.provide(Logger.layer([customPretty])))
// Disable colors for CI/CD environmentsconst ciLogger = Logger.consolePretty({ colors: false })
consolePretty()]), // Optional: set minimum log level (use "None" to disable) logLevel?: LogLevel | undefined
Optional minimum log level for the runtime.
logLevel: 'Info', })
Use "None" to disable logging entirely.
API Reference
Section titled “API Reference”storeOptions(options)
Section titled “storeOptions(options)”Helper for defining reusable store options with full type inference. Returns options that can be passed to useStore() or storeRegistry.preload().
Options:
storeId- Unique identifier for this store instanceschema- The LiveStore schemaadapter- The platform adapterunusedCacheTime?- Time in ms to keep this store in cache after it becomes unused (default:60_000in browser,Infinityin non-browser environments). Overrides the registry-level default when set.batchUpdates?- Function for batching React updates (recommended)boot?- Function called when the store is loadedonBootStatus?- Callback for boot status updatescontext?- User-defined context for dependency injectionsyncPayload?- Payload sent to sync backend (e.g., auth tokens)syncPayloadSchema?- Schema for type-safe sync payload validationconfirmUnsavedChanges?- Register beforeunload handler (default:true, web only)logger?- Custom logger implementationlogLevel?- Log level (e.g.,"Info","Debug","None")otelOptions?- OpenTelemetry configuration ({ tracer, rootSpanContext })disableDevtools?- Whether to disable devtools (boolean | 'auto', default:'auto')debug?- Debug options ({ instanceId?: string })
useStore(options)
Section titled “useStore(options)”Returns a store instance augmented with hooks (store.useQuery() and store.useClientDocument()) for reactive queries.
- Suspends until the store is loaded.
- Throws an error if loading fails.
- Store gets cached by its
storeIdin theStoreRegistry. Multiple calls with the samestoreIdreturn the same store instance. - Store is cached as long as it’s being used, and after
unusedCacheTimeexpires (default60_000ms in browser,Infinityin non-browser) - Default store options can be configured in
StoreRegistryconstructor. - Store options are only applied when the store is loaded. Subsequent calls with different options will not affect the store if it’s already loaded and cached in the registry.
store.commit(...events) / store.commit(txnFn)
Section titled “store.commit(...events) / store.commit(txnFn)”Commits events to the store. Supports multiple calling patterns:
store.commit(...events)- Commit one or more eventsstore.commit(txnFn)- Commit events via a transaction functionstore.commit(options, ...events)- Commit with optionsstore.commit(options, txnFn)- Options with transaction function
Options:
skipRefresh- Skip refreshing reactive queries after commit (advanced)
store.useQuery(queryable)
Section titled “store.useQuery(queryable)”Subscribes to a reactive query. Re-renders the component when the result changes.
- Takes any
Queryable:QueryBuilder,LiveQueryDef,SignalDef, orLiveQueryinstance - Returns the query result
store.useClientDocument(table, id?, options?)
Section titled “store.useClientDocument(table, id?, options?)”React.useState-like hook for client-document tables.
- Returns
[row, setRow, id, query$]tuple - Works only with tables defined via
State.SQLite.clientDocument() - If the table has a default id, the
idargument is optional
store.useSyncStatus()
Section titled “store.useSyncStatus()”React hook that subscribes to sync status changes. Re-renders the component when sync status changes.
function SyncIndicator() { const store = useStore(storeOptions) const status = store.useSyncStatus()
return <span>{status.isSynced ? '✓ Synced' : `Syncing (${status.pendingCount} pending)...`}</span>}For the SyncStatus type and non-React APIs (syncStatus(), subscribeSyncStatus(), syncStatusStream()), see the Store documentation.
new StoreRegistry(config?)
Section titled “new StoreRegistry(config?)”Creates a registry that coordinates store loading, caching, and retention.
Config:
defaultOptions?- Default options that are applied to all stores when they are loaded.:batchUpdates?- Function for batching React updatesunusedCacheTime?- Cache time for unused storesdisableDevtools?- Whether to disable devtoolsconfirmUnsavedChanges?- beforeunload confirmationotelOptions?- OpenTelemetry configurationdebug?- Debug options
runtime?- Effect runtime for registry operations
<StoreRegistryProvider>
Section titled “<StoreRegistryProvider>”React context provider that makes a StoreRegistry available to descendant components.
Props:
storeRegistry- The registry instance
useStoreRegistry(override?)
Section titled “useStoreRegistry(override?)”Hook that returns the StoreRegistry provided by the nearest <StoreRegistryProvider> ancestor, or the override if provided.
storeRegistry.preload(options)
Section titled “storeRegistry.preload(options)”Loads a store (without suspending) to warm up the cache. Returns a Promise that resolves when loading completes. This is a fire-and-forget operation useful for warming up the cache.
Framework-Specific Notes
Section titled “Framework-Specific Notes”LiveStore works with Vite out of the box.
Tanstack Start
Section titled “Tanstack Start”LiveStore works with Tanstack Start out of the box.
Provider Placement
Section titled “Provider Placement”When using LiveStore with TanStack Start, place <StoreRegistryProvider> in the correct location to avoid remounting on navigation.
Use the component prop on createRootRoute for <StoreRegistryProvider>:
import { Outlet, HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'import { StoreRegistry } from '@livestore/livestore'import { StoreRegistryProvider } from '@livestore/react'import { Suspense, useState } from 'react'
export const Route = createRootRoute({ shellComponent: RootShell, // HTML structure only - NO state or providers component: RootComponent, // App shell - StoreRegistryProvider goes HERE})
// HTML document shell - keep this statelessfunction RootShell({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <head><HeadContent /></head> <body> {children} <Scripts /> </body> </html> )}
// App shell - persists across SPA navigationfunction RootComponent() { const [storeRegistry] = useState(() => new StoreRegistry())
return ( <Suspense fallback={<div>Loading LiveStore...</div>}> <StoreRegistryProvider storeRegistry={storeRegistry}> <Outlet /> </StoreRegistryProvider> </Suspense> )}TanStack Start’s shellComponent is designed for SSR HTML streaming and may be re-evaluated on server requests during navigation. When <StoreRegistryProvider> is placed there, the WebSocket connection is re-established and all LiveStore state is re-initialized on each navigation.
If you see the loading screen on every navigation, check your server logs for multiple “Launching WebSocket” messages.
Expo / React Native
Section titled “Expo / React Native”LiveStore has a first-class integration with Expo / React Native via @livestore/adapter-expo. See the Expo Adapter documentation.
Next.js
Section titled “Next.js”Given various Next.js limitations, LiveStore doesn’t yet work with Next.js out of the box.
Complete Example
Section titled “Complete Example”See the Multi-Store example for a complete working application demonstrating various multi-store patterns.
Technical Notes
Section titled “Technical Notes”@livestore/reactusesReact.useState()under the hood foruseQuery()/useClientDocument()to bind LiveStore’s reactivity to React’s reactivity. Some libraries useReact.useSyncExternalStore()for similar purposes butReact.useState()is more efficient for LiveStore’s architecture.@livestore/reactsupports React Strict Mode.