Skip to content

Getting started with LiveStore + React

  • Recommended: Bun 1.2 or higher
  • Node.js 23.0.0 or higher

For a quick start, we recommend using our template app following the steps below.

For existing projects, see Existing project setup.

  1. Set up project from template

    Terminal window
    bunx @livestore/cli create --example web-todomvc-sync-cf livestore-app

    Replace livestore-app with your desired app name.

  2. Install dependencies

    It’s strongly recommended to use bun or pnpm for the simplest and most reliable dependency setup (see note on package management for more details).

    Terminal window
    bun install

    Pro tip: You can use direnv to manage environment variables.

  3. Run dev environment

    Terminal window
    bun dev
  4. Open browser

    Open http://localhost:60000 in your browser.

    You can also open the devtools by going to http://localhost:60000/_livestore.

  1. Install dependencies

    Terminal window
    bun install @livestore/livestore @livestore/wa-sqlite @livestore/adapter-web @livestore/react @livestore/peer-deps @livestore/sync-cf @livestore/devtools-vite
  2. Update Vite config

    Add the following code to your vite.config.js file:

    vite.config.js
    import process from 'node:process'
    import { cloudflare } from '@cloudflare/vite-plugin'
    import { livestoreDevtoolsPlugin } from '@livestore/devtools-vite'
    import react from '@vitejs/plugin-react'
    import { defineConfig } from 'vite'
    export default defineConfig({
    server: {
    port: process.env.PORT ? Number(process.env.PORT) : 60_001,
    fs: { strict: false },
    },
    worker: { format: 'es' },
    plugins: [cloudflare(), react(), livestoreDevtoolsPlugin({ schemaPath: './src/livestore/schema.ts' })],
    })

Create a file named schema.ts inside the src/livestore folder. This file defines your LiveStore schema consisting of your app’s event definitions (describing how data changes), derived state (i.e. SQLite tables), and materializers (how state is derived from events).

Here’s an example schema:

import {
import Events
Events
,
const makeSchema: <TInputSchema extends InputSchema>(inputSchema: TInputSchema) => FromInputSchema.DeriveSchema<TInputSchema>
makeSchema
,
import Schema
Schema
,
type SessionIdSymbol = typeof SessionIdSymbol
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
,
import State
State
} from '@livestore/livestore'
// You can model your state as SQLite tables (https://docs.livestore.dev/reference/state/sqlite-schema)
export const
const 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: Some<"">;
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 deletedAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
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: Some<"">;
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 deletedAt: {
...;
};
}>, 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: Some<"">;
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 deletedAt: {
...;
};
}, 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:

  1. Using explicit column definitions
  2. Using an Effect Schema (either the name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columns
const 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 annotations
import { 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 name
const 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 indexes
const 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: Some<"">;
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 deletedAt: {
...;
};
}
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: Some<"">;
nullable: false;
primaryKey: false;
autoIncrement: false;
}
text
:
import State
State
.
import SQLite
SQLite
.
const text: <string, string, false, "", false, false>(args: {
schema?: Schema.Codec<string, string, never, never>;
default?: "";
nullable?: false;
primaryKey?: false;
autoIncrement?: false;
}) => {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: Some<"">;
nullable: false;
primaryKey: false;
autoIncrement: false;
} (+1 overload)
text
({
default?: ""
default
: '' }),
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 }),
deletedAt: {
columnType: "integer";
schema: Schema.Codec<Date | null, number | null, never, never>;
default: None<never>;
nullable: true;
primaryKey: false;
autoIncrement: false;
}
deletedAt
:
import State
State
.
import SQLite
SQLite
.
const integer: <number, Date, true, typeof NoDefault, false, false>(args: {
schema?: Schema.Codec<Date, number, never, never>;
default?: typeof NoDefault;
nullable?: true;
primaryKey?: false;
autoIncrement?: false;
}) => {
columnType: "integer";
schema: Schema.Codec<Date | null, number | null, never, never>;
default: None<never>;
nullable: true;
primaryKey: false;
autoIncrement: false;
} (+1 overload)
integer
({
nullable?: true
nullable
: true,
schema?: Schema.Codec<Date, number, never, never>
schema
:
import Schema
Schema
.
const DateFromMillis: Schema.DateFromMillis

Type-level representation of

DateFromMillis

.

Schema that decodes epoch milliseconds into a JavaScript Date.

When to use

Use to model numeric millisecond timestamps that decode to JavaScript Date objects and encode back to numbers.

Details

Decoding: A number of milliseconds since the Unix epoch is decoded as a Date.

Encoding: A Date is encoded as its millisecond timestamp.

Gotchas

This schema accepts any number, including NaN, Infinity, and -Infinity. Those values decode to invalid Date instances.

@since4.0.0

@seeDateFromString for decoding string-encoded dates

@seeDateTimeUtcFromMillis for decoding epoch milliseconds into UTC values

@since4.0.0

DateFromMillis
}),
},
}),
// Client documents can be used for local-only state (e.g. form inputs)
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 SessionIdSymbol
store.queryDb(clientDocumentTable.get('some-id'))
// Setting data
// Again, `'some-id'` can be ommited for SessionIdSymbol
store.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 }

@since3.10.0

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".

@since4.0.0

@since4.0.0

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"

@seeLiteral for a schema that represents a single literal.

@since4.0.0

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' } },
}),
}
// Events describe data changes (https://docs.livestore.dev/reference/events)
export const
const events: {
todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Encoded">>;
todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Encoded">>;
todoUncompleted: State.SQLite.EventDef<...>;
todoDeleted: State.SQLite.EventDef<...>;
todoClearedCompleted: State.SQLite.EventDef<...>;
uiStateSet: State.SQLite.ClientDocumentTableDef.SetEventDefLike<...>;
}
events
= {
todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Encoded">>
todoCreated
:
import Events
Events
.
synced<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Encoded">>(args: {
name: "v1.TodoCreated";
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Encoded">, never, never>;
} & Omit<...>): State.SQLite.EventDef<...>
export synced

Creates a synced event definition.

Synced events are sent to the sync backend and distributed to all connected clients. Use this for collaborative data that should be shared across users and devices.

Event names should be versioned (e.g., v1.TodoCreated) to support schema evolution over time.

@example

import { Events } from '@livestore/livestore'
import { Schema } from 'effect'
const todoCreated = Events.synced({
name: 'v1.TodoCreated',
schema: Schema.Struct({
id: Schema.String,
text: Schema.String,
completed: Schema.Boolean,
}),
})
// Commit the event
store.commit(todoCreated({ id: 'abc', text: 'Buy milk', completed: false }))

synced
({
name: "v1.TodoCreated"
name
: 'v1.TodoCreated',
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Encoded">, never, never>
schema
:
import Schema
Schema
.
function Struct<{
readonly id: Schema.String;
readonly text: Schema.String;
}>(fields: {
readonly id: Schema.String;
readonly text: Schema.String;
}): Schema.Struct<{
readonly id: Schema.String;
readonly text: Schema.String;
}>

Defines a struct schema from a map of field schemas.

Details

Each field value is a schema. Use

optionalKey

or

optional

to mark fields as optional, and

mutableKey

to mark them as mutable.

The resulting schema's Type is a readonly object type with the fields' decoded types. The Encoded form mirrors the field schemas' encoded types.

Example (Defining a basic struct)

import { Schema } from "effect"
const Person = Schema.Struct({
name: Schema.String,
age: Schema.Number,
email: Schema.optionalKey(Schema.String)
})
// { readonly name: string; readonly age: number; readonly email?: string }
type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })
console.log(alice)
// { name: 'Alice', age: 30 }

@since3.10.0

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".

@since4.0.0

@since4.0.0

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".

@since4.0.0

@since4.0.0

String
}),
}),
todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Encoded">>
todoCompleted
:
import Events
Events
.
synced<"v1.TodoCompleted", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly id: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Encoded">>(args: {
name: "v1.TodoCompleted";
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Encoded">, never, never>;
} & Omit<State.SQLite.DefineEventOptions<Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Type">, false>, "derived" | "clientOnly">): State.SQLite.EventDef<...>
export synced

Creates a synced event definition.

Synced events are sent to the sync backend and distributed to all connected clients. Use this for collaborative data that should be shared across users and devices.

Event names should be versioned (e.g., v1.TodoCreated) to support schema evolution over time.

@example

import { Events } from '@livestore/livestore'
import { Schema } from 'effect'
const todoCreated = Events.synced({
name: 'v1.TodoCreated',
schema: Schema.Struct({
id: Schema.String,
text: Schema.String,
completed: Schema.Boolean,
}),
})
// Commit the event
store.commit(todoCreated({ id: 'abc', text: 'Buy milk', completed: false }))

synced
({
name: "v1.TodoCompleted"
name
: 'v1.TodoCompleted',
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Encoded">, never, never>
schema
:
import Schema
Schema
.
function Struct<{
readonly id: Schema.String;
}>(fields: {
readonly id: Schema.String;
}): Schema.Struct<{
readonly id: Schema.String;
}>

Defines a struct schema from a map of field schemas.

Details

Each field value is a schema. Use

optionalKey

or

optional

to mark fields as optional, and

mutableKey

to mark them as mutable.

The resulting schema's Type is a readonly object type with the fields' decoded types. The Encoded form mirrors the field schemas' encoded types.

Example (Defining a basic struct)

import { Schema } from "effect"
const Person = Schema.Struct({
name: Schema.String,
age: Schema.Number,
email: Schema.optionalKey(Schema.String)
})
// { readonly name: string; readonly age: number; readonly email?: string }
type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })
console.log(alice)
// { name: 'Alice', age: 30 }

@since3.10.0

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".

@since4.0.0

@since4.0.0

String
}),
}),
todoUncompleted: State.SQLite.EventDef<"v1.TodoUncompleted", Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Encoded">>
todoUncompleted
:
import Events
Events
.
synced<"v1.TodoUncompleted", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly id: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Encoded">>(args: {
name: "v1.TodoUncompleted";
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Encoded">, never, never>;
} & Omit<State.SQLite.DefineEventOptions<Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Type">, false>, "derived" | "clientOnly">): State.SQLite.EventDef<...>
export synced

Creates a synced event definition.

Synced events are sent to the sync backend and distributed to all connected clients. Use this for collaborative data that should be shared across users and devices.

Event names should be versioned (e.g., v1.TodoCreated) to support schema evolution over time.

@example

import { Events } from '@livestore/livestore'
import { Schema } from 'effect'
const todoCreated = Events.synced({
name: 'v1.TodoCreated',
schema: Schema.Struct({
id: Schema.String,
text: Schema.String,
completed: Schema.Boolean,
}),
})
// Commit the event
store.commit(todoCreated({ id: 'abc', text: 'Buy milk', completed: false }))

synced
({
name: "v1.TodoUncompleted"
name
: 'v1.TodoUncompleted',
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Encoded">, never, never>
schema
:
import Schema
Schema
.
function Struct<{
readonly id: Schema.String;
}>(fields: {
readonly id: Schema.String;
}): Schema.Struct<{
readonly id: Schema.String;
}>

Defines a struct schema from a map of field schemas.

Details

Each field value is a schema. Use

optionalKey

or

optional

to mark fields as optional, and

mutableKey

to mark them as mutable.

The resulting schema's Type is a readonly object type with the fields' decoded types. The Encoded form mirrors the field schemas' encoded types.

Example (Defining a basic struct)

import { Schema } from "effect"
const Person = Schema.Struct({
name: Schema.String,
age: Schema.Number,
email: Schema.optionalKey(Schema.String)
})
// { readonly name: string; readonly age: number; readonly email?: string }
type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })
console.log(alice)
// { name: 'Alice', age: 30 }

@since3.10.0

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".

@since4.0.0

@since4.0.0

String
}),
}),
todoDeleted: State.SQLite.EventDef<"v1.TodoDeleted", Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly deletedAt: Schema.DateFromString;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly deletedAt: Schema.DateFromString;
}, "Encoded">>
todoDeleted
:
import Events
Events
.
synced<"v1.TodoDeleted", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly id: Schema.String;
readonly deletedAt: Schema.DateFromString;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly deletedAt: Schema.DateFromString;
}, "Encoded">>(args: {
name: "v1.TodoDeleted";
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly deletedAt: Schema.DateFromString;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly deletedAt: Schema.DateFromString;
}, "Encoded">, never, never>;
} & Omit<...>): State.SQLite.EventDef<...>
export synced

Creates a synced event definition.

Synced events are sent to the sync backend and distributed to all connected clients. Use this for collaborative data that should be shared across users and devices.

Event names should be versioned (e.g., v1.TodoCreated) to support schema evolution over time.

@example

import { Events } from '@livestore/livestore'
import { Schema } from 'effect'
const todoCreated = Events.synced({
name: 'v1.TodoCreated',
schema: Schema.Struct({
id: Schema.String,
text: Schema.String,
completed: Schema.Boolean,
}),
})
// Commit the event
store.commit(todoCreated({ id: 'abc', text: 'Buy milk', completed: false }))

synced
({
name: "v1.TodoDeleted"
name
: 'v1.TodoDeleted',
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly deletedAt: Schema.DateFromString;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly deletedAt: Schema.DateFromString;
}, "Encoded">, never, never>
schema
:
import Schema
Schema
.
function Struct<{
readonly id: Schema.String;
readonly deletedAt: Schema.DateFromString;
}>(fields: {
readonly id: Schema.String;
readonly deletedAt: Schema.DateFromString;
}): Schema.Struct<{
readonly id: Schema.String;
readonly deletedAt: 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 }

@since3.10.0

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".

@since4.0.0

@since4.0.0

String
,
deletedAt: Schema.DateFromString
deletedAt
:
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.

@since3.10.0

@seeDateFromMillis for decoding epoch milliseconds into Date instances

@seeDateTimeUtcFromString for decoding date-time strings into UTC values

@seeDate for accepting Date instances directly

@seeDateValid for rejecting invalid Date instances

@since3.10.0

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.

@since4.0.0

isDateValid
()),
}),
}),
todoClearedCompleted: State.SQLite.EventDef<"v1.TodoClearedCompleted", Schema.Struct.ReadonlySide<{
readonly deletedAt: Schema.DateFromString;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly deletedAt: Schema.DateFromString;
}, "Encoded">>
todoClearedCompleted
:
import Events
Events
.
synced<"v1.TodoClearedCompleted", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly deletedAt: Schema.DateFromString;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly deletedAt: Schema.DateFromString;
}, "Encoded">>(args: {
name: "v1.TodoClearedCompleted";
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly deletedAt: Schema.DateFromString;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly deletedAt: Schema.DateFromString;
}, "Encoded">, never, never>;
} & Omit<...>): State.SQLite.EventDef<...>
export synced

Creates a synced event definition.

Synced events are sent to the sync backend and distributed to all connected clients. Use this for collaborative data that should be shared across users and devices.

Event names should be versioned (e.g., v1.TodoCreated) to support schema evolution over time.

@example

import { Events } from '@livestore/livestore'
import { Schema } from 'effect'
const todoCreated = Events.synced({
name: 'v1.TodoCreated',
schema: Schema.Struct({
id: Schema.String,
text: Schema.String,
completed: Schema.Boolean,
}),
})
// Commit the event
store.commit(todoCreated({ id: 'abc', text: 'Buy milk', completed: false }))

synced
({
name: "v1.TodoClearedCompleted"
name
: 'v1.TodoClearedCompleted',
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly deletedAt: Schema.DateFromString;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly deletedAt: Schema.DateFromString;
}, "Encoded">, never, never>
schema
:
import Schema
Schema
.
function Struct<{
readonly deletedAt: Schema.DateFromString;
}>(fields: {
readonly deletedAt: Schema.DateFromString;
}): Schema.Struct<{
readonly deletedAt: 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 }

@since3.10.0

Struct
({
deletedAt: Schema.DateFromString
deletedAt
:
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.

@since3.10.0

@seeDateFromMillis for decoding epoch milliseconds into Date instances

@seeDateTimeUtcFromString for decoding date-time strings into UTC values

@seeDate for accepting Date instances directly

@seeDateValid for rejecting invalid Date instances

@since3.10.0

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.

@since4.0.0

isDateValid
()) }),
}),
uiStateSet: State.SQLite.ClientDocumentTableDef.SetEventDefLike<"uiState", Schema.Struct.ReadonlySide<{
readonly newTodoText: Schema.String;
readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;
}, "Type">, {
partialSet: true;
default: {
id: typeof SessionIdSymbol;
value: {
readonly newTodoText: "";
readonly filter: "all";
};
};
}>
uiStateSet
:
const 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: Some<"">;
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 deletedAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
uiState: State.SQLite.ClientDocumentTableDef<...>;
}
tables
.
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
.
ClientDocumentTableDef<TName extends string, TType, TEncoded, TOptions extends ClientDocumentTableOptions<TType>>.Trait<"uiState", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly newTodoText: String; readonly filter: Literals<readonly ["all", "active", "completed"]>; }, "Type">, Struct.ReadonlySide<...>, { ...; }>.set: State.SQLite.ClientDocumentTableDef.SetEventDefLike<"uiState", Schema.Struct.ReadonlySide<{
readonly newTodoText: Schema.String;
readonly filter: Schema.Literals<readonly ["all", "active", "completed"]>;
}, "Type">, {
partialSet: true;
default: {
id: typeof SessionIdSymbol;
value: {
readonly newTodoText: "";
readonly filter: "all";
};
};
}>

Derived event definition for setting the value of the client document table. If the document doesn't exist yet, the first .set event will create it.

@example

const someDocumentTable = State.SQLite.clientDocument({
name: 'SomeDocumentTable',
schema: Schema.Struct({
someField: Schema.String,
someOtherField: Schema.String,
}),
default: { value: { someField: 'some-default-value', someOtherField: 'some-other-default-value' } },
})
const setEventDef = store.commit(someDocumentTable.set({ someField: 'explicit-value' }, 'some-id'))
// Will commit an event with the following payload:
// { id: 'some-id', value: { someField: 'explicit-value', someOtherField: 'some-other-default-value' } }

Similar to .get, you can omit the id argument if you've set a default id.

@example

const uiState = State.SQLite.clientDocument({
name: 'UiState',
schema: Schema.Struct({ someField: Schema.String }),
default: { id: SessionIdSymbol, value: { someField: 'some-default-value' } },
})
const setEventDef = store.commit(uiState.set({ someField: 'explicit-value' }))
// Will commit an event with the following payload:
// { id: '...', value: { someField: 'explicit-value' } }
// ^^^
// Automatically replaced with the client session id

set
,
}
// Materializers are used to map events to state (https://docs.livestore.dev/reference/state/materializers)
const
const materializers: {
"v1.TodoCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Encoded">>>;
"v1.TodoCompleted": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Encoded">>>;
"v1.TodoUncompleted": State.SQLite.Materializer<...>;
"v1.TodoDeleted": State.SQLite.Materializer<...>;
"v1.TodoClearedCompleted": State.SQLite.Materializer<...>;
}
materializers
=
import State
State
.
import SQLite
SQLite
.
const materializers: <{
todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Encoded">>;
todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Encoded">>;
todoUncompleted: State.SQLite.EventDef<...>;
todoDeleted: State.SQLite.EventDef<...>;
todoClearedCompleted: State.SQLite.EventDef<...>;
uiStateSet: State.SQLite.ClientDocumentTableDef.SetEventDefLike<...>;
}>(_eventDefRecord: {
todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Encoded">>;
todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Encoded">>;
todoUncompleted: State.SQLite.EventDef<...>;
todoDeleted: State.SQLite.EventDef<...>;
todoClearedCompleted: State.SQLite.EventDef<...>;
uiStateSet: State.SQLite.ClientDocumentTableDef.SetEventDefLike<...>;
}, 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

@example

import { State } from '@livestore/livestore'
const handlers = State.SQLite.materializers(events, {
// Handler for each event - argument types are inferred
'v1.TodoCreated': ({ id, text, completed }) =>
tables.todos.insert({ id, text, completed }),
'v1.TodoUpdated': ({ id, text }) =>
tables.todos.update({ text }).where({ id }),
// Can return multiple operations
'v1.UserCreatedWithDefaults': ({ userId, name }) => [
tables.users.insert({ id: userId, name }),
tables.settings.insert({ userId, theme: 'light' }),
],
// Can query current state
'v1.TodoToggled': ({ id }, { query }) => {
const todo = query(tables.todos.select().where({ id }).first())
return tables.todos.update({ completed: !todo?.completed }).where({ id })
},
})

materializers
(
const events: {
todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Encoded">>;
todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Encoded">>;
todoUncompleted: State.SQLite.EventDef<...>;
todoDeleted: State.SQLite.EventDef<...>;
todoClearedCompleted: State.SQLite.EventDef<...>;
uiStateSet: State.SQLite.ClientDocumentTableDef.SetEventDefLike<...>;
}
events
, {
'v1.TodoCreated': ({
id: string
id
,
text: string
text
}) =>
const 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: Some<"">;
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 deletedAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
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: Some<"">;
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 deletedAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos
.
insert: (values: {
readonly id: string;
readonly text?: string;
readonly completed?: boolean;
readonly deletedAt?: Date | null;
}) => 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 deletedAt: Schema.Codec<Date | null, number | null, never, never>;
}, "Type">[], State.SQLite.TableDefBase<State.SQLite.SqliteTableDefForInput<"todos", {
...;
}>, State.SQLite.WithDefaults<...>>, "select" | ... 6 more ... | "row">

Insert a new row into the table.

@example

db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' })

@paramvalues - The row values to insert.

insert
({
id: string
id
,
text?: string
text
,
completed?: boolean
completed
: false }),
'v1.TodoCompleted': ({
id: string
id
}) =>
const 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: Some<"">;
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 deletedAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
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: Some<"">;
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 deletedAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos
.
update: (values: Partial<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 deletedAt: Schema.Codec<Date | null, number | null, never, never>;
}, "Type">>) => QueryBuilder<readonly Schema.Struct.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 deletedAt: Schema.Codec<Date | null, number | null, 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
({
completed?: boolean
completed
: true }).
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 text: string | {
op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>;
value: string;
} | {
...;
} | undefined;
readonly completed: boolean | ... 2 more ... | undefined;
readonly deletedAt: Date | ... 3 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
}),
'v1.TodoUncompleted': ({
id: string
id
}) =>
const 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: Some<"">;
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 deletedAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
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: Some<"">;
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 deletedAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos
.
update: (values: Partial<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 deletedAt: Schema.Codec<Date | null, number | null, never, never>;
}, "Type">>) => QueryBuilder<readonly Schema.Struct.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 deletedAt: Schema.Codec<Date | null, number | null, 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
({
completed?: boolean
completed
: false }).
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 text: string | {
op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>;
value: string;
} | {
...;
} | undefined;
readonly completed: boolean | ... 2 more ... | undefined;
readonly deletedAt: Date | ... 3 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
}),
'v1.TodoDeleted': ({
id: string
id
,
deletedAt: Date
deletedAt
}) =>
const 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: Some<"">;
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 deletedAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
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: Some<"">;
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 deletedAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos
.
update: (values: Partial<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 deletedAt: Schema.Codec<Date | null, number | null, never, never>;
}, "Type">>) => QueryBuilder<readonly Schema.Struct.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 deletedAt: Schema.Codec<Date | null, number | null, 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
({
deletedAt?: Date | null
deletedAt
}).
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 text: string | {
op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>;
value: string;
} | {
...;
} | undefined;
readonly completed: boolean | ... 2 more ... | undefined;
readonly deletedAt: Date | ... 3 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
}),
'v1.TodoClearedCompleted': ({
deletedAt: Date
deletedAt
}) =>
const 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: Some<"">;
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 deletedAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
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: Some<"">;
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 deletedAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos
.
update: (values: Partial<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 deletedAt: Schema.Codec<Date | null, number | null, never, never>;
}, "Type">>) => QueryBuilder<readonly Schema.Struct.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 deletedAt: Schema.Codec<Date | null, number | null, 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
({
deletedAt?: Date | null
deletedAt
}).
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 text: string | {
op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>;
value: string;
} | {
...;
} | undefined;
readonly completed: boolean | ... 2 more ... | undefined;
readonly deletedAt: Date | ... 3 more ... | undefined;
}>) => QueryBuilder<...> (+3 overloads)
where
({
completed?: boolean | {
op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>;
value: boolean;
} | {
op: QueryBuilder.WhereOps.MultiValue;
value: readonly boolean[];
} | undefined
completed
: true }),
})
const
const state: InternalState
state
=
import State
State
.
import SQLite
SQLite
.
const makeState: <{
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: Some<"">;
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 deletedAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
uiState: State.SQLite.ClientDocumentTableDef<...>;
};
materializers: {
...;
};
}>(inputSchema: {
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: Some<"">;
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 deletedAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
uiState: State.SQLite.ClientDocumentTableDef<...>;
};
materializers: {
...;
};
}) => InternalState
makeState
({
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: Some<"">;
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 deletedAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
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;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Encoded">>>;
"v1.TodoCompleted": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Encoded">>>;
"v1.TodoUncompleted": State.SQLite.Materializer<...>;
"v1.TodoDeleted": State.SQLite.Materializer<...>;
"v1.TodoClearedCompleted": State.SQLite.Materializer<...>;
}
materializers
})
export const
const schema: FromInputSchema.DeriveSchema<{
events: {
todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Encoded">>;
todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Encoded">>;
todoUncompleted: State.SQLite.EventDef<...>;
todoDeleted: State.SQLite.EventDef<...>;
todoClearedCompleted: State.SQLite.EventDef<...>;
uiStateSet: State.SQLite.ClientDocumentTableDef.SetEventDefLike<...>;
};
state: InternalState;
}>
schema
=
makeSchema<{
events: {
todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Encoded">>;
todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Encoded">>;
todoUncompleted: State.SQLite.EventDef<...>;
todoDeleted: State.SQLite.EventDef<...>;
todoClearedCompleted: State.SQLite.EventDef<...>;
uiStateSet: State.SQLite.ClientDocumentTableDef.SetEventDefLike<...>;
};
state: InternalState;
}>(inputSchema: {
events: {
todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Encoded">>;
todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Encoded">>;
todoUncompleted: State.SQLite.EventDef<...>;
todoDeleted: State.SQLite.EventDef<...>;
todoClearedCompleted: State.SQLite.EventDef<...>;
uiStateSet: State.SQLite.ClientDocumentTableDef.SetEventDefLike<...>;
};
state: InternalState;
}): FromInputSchema.DeriveSchema<...>
makeSchema
({
events: {
todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Encoded">>;
todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
}, "Encoded">>;
todoUncompleted: State.SQLite.EventDef<...>;
todoDeleted: State.SQLite.EventDef<...>;
todoClearedCompleted: State.SQLite.EventDef<...>;
uiStateSet: State.SQLite.ClientDocumentTableDef.SetEventDefLike<...>;
}
events
,
state: InternalState
state
})

Create a file named livestore.worker.ts inside the src folder. This file will contain the LiveStore web worker. When importing this file, make sure to add the ?worker extension to the import path to ensure that Vite treats it as a worker file.

import {
const makeWorker: (options: WorkerOptions) => void
makeWorker
} from '@livestore/adapter-web/worker'
import {
import schema
schema
} from './livestore/schema.ts'
function makeWorker(options: WorkerOptions): void
makeWorker
({
schema: LiveStoreSchema<DbSchema, EventDefRecord>
schema
})

Create a store.ts file in the src folder. This file configures the store adapter and exports a custom hook that components will use to access the store.

The useStore() hook accepts store configuration options (schema, adapter, store ID) and returns a store instance. It suspends while the store is loading, so make sure to use a Suspense boundary to handle the loading 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 makePersistedAdapter: (options: WebAdapterOptions) => Adapter

Creates a web adapter with persistent storage (currently only supports OPFS). Requires both a web worker and a shared worker.

On browsers without SharedWorker support (e.g. Android Chrome), this adapter automatically falls back to single-tab mode. In single-tab mode:

  • Each tab runs independently with its own leader worker
  • Multi-tab synchronization is not available
  • Devtools are not supported

@seehttps://github.com/livestorejs/livestore/issues/321 - SharedWorker tracking issue

@seehttps://issues.chromium.org/issues/40290702 - Chromium SharedWorker bug

@example

import { makePersistedAdapter } from '@livestore/adapter-web'
import LiveStoreWorker from './livestore.worker.ts?worker'
import LiveStoreSharedWorker from '@livestore/adapter-web/shared-worker?sharedworker'
const adapter = makePersistedAdapter({
worker: LiveStoreWorker,
sharedWorker: LiveStoreSharedWorker,
storage: { type: 'opfs' },
})

makePersistedAdapter
} from '@livestore/adapter-web'
import
const LiveStoreSharedWorker: new (options?: {
name?: string;
}) => SharedWorker
LiveStoreSharedWorker
from '@livestore/adapter-web/shared-worker?sharedworker'
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.

@example

function Issue() {
// Suspends until loaded or returns immediately if already loaded
const issueStore = useStore(issueStoreOptions('abc123'))
const [issue] = issueStore.useQuery(queryDb(tables.issue.select()))
const toggleStatus = () =>
issueStore.commit(
issueEvents.issueStatusChanged({
id: issue.id,
status: issue.status === 'done' ? 'todo' : 'done',
}),
)
const preloadParentIssue = (issueId: string) =>
storeRegistry.preload({
...issueStoreOptions(issueId),
unusedCacheTime: 10_000,
})
return (
<>
<h2>{issue.title}</h2>
<button onClick={() => toggleStatus()}>Toggle Status</button>
<button onMouseEnter={() => preloadParentIssue(issue.parentIssueId)}>Open Parent Issue</button>
</>
)
}

@returnsThe loaded store instance augmented with React hooks

@throwsunknown - store loading error or if called outside <StoreRegistryProvider>

useStore
} from '@livestore/react'
import
const LiveStoreWorker: new (options?: {
name?: string;
}) => Worker
LiveStoreWorker
from './livestore.worker.ts?worker'
import {
import schema
schema
} from './livestore/schema.ts'
const
const adapter: Adapter
adapter
=
function makePersistedAdapter(options: WebAdapterOptions): Adapter

Creates a web adapter with persistent storage (currently only supports OPFS). Requires both a web worker and a shared worker.

On browsers without SharedWorker support (e.g. Android Chrome), this adapter automatically falls back to single-tab mode. In single-tab mode:

  • Each tab runs independently with its own leader worker
  • Multi-tab synchronization is not available
  • Devtools are not supported

@seehttps://github.com/livestorejs/livestore/issues/321 - SharedWorker tracking issue

@seehttps://issues.chromium.org/issues/40290702 - Chromium SharedWorker bug

@example

import { makePersistedAdapter } from '@livestore/adapter-web'
import LiveStoreWorker from './livestore.worker.ts?worker'
import LiveStoreSharedWorker from '@livestore/adapter-web/shared-worker?sharedworker'
const adapter = makePersistedAdapter({
worker: LiveStoreWorker,
sharedWorker: LiveStoreSharedWorker,
storage: { type: 'opfs' },
})

makePersistedAdapter
({
storage: {
readonly type: "opfs";
readonly directory?: string | undefined;
}

Specifies where to persist data for this adapter

storage
: {
type: "opfs"
type
: 'opfs' },
worker: ((options: {
name: string;
}) => globalThis.Worker) | (new (options: {
name: string;
}) => globalThis.Worker)
worker
:
const LiveStoreWorker: new (options?: {
name?: string;
}) => Worker
LiveStoreWorker
,
sharedWorker: ((options: {
name: string;
}) => globalThis.SharedWorker) | (new (options: {
name: string;
}) => globalThis.SharedWorker)

This is mostly an implementation detail and needed to be exposed into app code due to a current Vite limitation (https://github.com/vitejs/vite/issues/8427).

In most cases this should look like:

import LiveStoreSharedWorker from '@livestore/adapter-web/shared-worker?sharedworker'
const adapter = makePersistedAdapter({
sharedWorker: LiveStoreSharedWorker,
// ...
})

sharedWorker
:
const LiveStoreSharedWorker: new (options?: {
name?: string;
}) => SharedWorker
LiveStoreSharedWorker
,
})
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.

@example

function Issue() {
// Suspends until loaded or returns immediately if already loaded
const issueStore = useStore(issueStoreOptions('abc123'))
const [issue] = issueStore.useQuery(queryDb(tables.issue.select()))
const toggleStatus = () =>
issueStore.commit(
issueEvents.issueStatusChanged({
id: issue.id,
status: issue.status === 'done' ? 'todo' : 'done',
}),
)
const preloadParentIssue = (issueId: string) =>
storeRegistry.preload({
...issueStoreOptions(issueId),
unusedCacheTime: 10_000,
})
return (
<>
<h2>{issue.title}</h2>
<button onClick={() => toggleStatus()}>Toggle Status</button>
<button onMouseEnter={() => preloadParentIssue(issue.parentIssueId)}>Open Parent Issue</button>
</>
)
}

@returnsThe loaded store instance augmented with React hooks

@throwsunknown - store loading error or if called outside <StoreRegistryProvider>

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.

@example

// With React DOM
import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
// With React Native
import { unstable_batchedUpdates as batchUpdates } from 'react-native'

batchUpdates
,
})

To enable store management throughout your app, create a StoreRegistry and provide it with a <StoreRegistryProvider>. The registry manages store instance lifecycles (loading, caching, disposal).

Wrap the provider in a Suspense boundary to handle the loading state for when the store is loading.

import type
(alias) namespace React
import React
React
from 'react'
import {
const Suspense: React.ExoticComponent<React.SuspenseProps>

Lets you display a fallback until its children have finished loading.

@seehttps://react.dev/reference/react/Suspense React Docs

@example

import { Suspense } from 'react';
<Suspense fallback={<Loading />}>
<ProfileDetails />
</Suspense>

Suspense
,
function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)

Returns a stateful value, and a function to update it.

@version16.8.0

@seehttps://react.dev/reference/react/useState

useState
} from 'react'
import {
class StoreRegistry

Store Registry coordinating store loading, caching, and retention

@public

StoreRegistry
} from '@livestore/livestore'
import {
const StoreRegistryProvider: ({ storeRegistry, children }: StoreRegistryProviderProps) => React.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.

@example

import { StoreRegistry } from '@livestore/livestore'
import { StoreRegistryProvider } from '@livestore/react'
import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
const storeRegistry = new StoreRegistry({
defaultOptions: { batchUpdates }
})
function App() {
return (
<StoreRegistryProvider storeRegistry={storeRegistry}>
<MyComponent />
</StoreRegistryProvider>
)
}

StoreRegistryProvider
} from '@livestore/react'
const
const suspenseFallback: JSX.Element
suspenseFallback
= <
JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>Loading app...</
JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
export const
const App: React.FC<{}>
App
:
(alias) namespace React
import React
React
.
type FC<P = {}> = React.FunctionComponent<P>

Represents the type of a function component. Can optionally receive a type argument that represents the props the component receives.

@seehttps://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/function_components React TypeScript Cheatsheet

@aliasfor FunctionComponent

@example

// With props:
type Props = { name: string }
const MyComponent: FC<Props> = (props) => {
return <div>{props.name}</div>
}

@example

// Without props:
const MyComponentWithoutProps: FC = () => {
return <div>MyComponentWithoutProps</div>
}

FC
= () => {
const [
const storeRegistry: StoreRegistry
storeRegistry
] =
useState<StoreRegistry>(initialState: StoreRegistry | (() => StoreRegistry)): [StoreRegistry, React.Dispatch<React.SetStateAction<StoreRegistry>>] (+1 overload)

Returns a stateful value, and a function to update it.

@version16.8.0

@seehttps://react.dev/reference/react/useState

useState
(() => new
new StoreRegistry(config?: StoreRegistryConfig): StoreRegistry

Creates a new StoreRegistry instance.

@example

const registry = new StoreRegistry({
defaultOptions: {
batchUpdates,
unusedCacheTime: 30_000,
}
})

StoreRegistry
())
return (
<
const Suspense: React.ExoticComponent<React.SuspenseProps>

Lets you display a fallback until its children have finished loading.

@seehttps://react.dev/reference/react/Suspense React Docs

@example

import { Suspense } from 'react';
<Suspense fallback={<Loading />}>
<ProfileDetails />
</Suspense>

Suspense
SuspenseProps.fallback?: React.ReactNode

A fallback react tree to show when a Suspense child (like React.lazy) suspends

fallback
={
const suspenseFallback: JSX.Element
suspenseFallback
}>
<
const StoreRegistryProvider: ({ storeRegistry, children }: StoreRegistryProviderProps) => React.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.

@example

import { StoreRegistry } from '@livestore/livestore'
import { StoreRegistryProvider } from '@livestore/react'
import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
const storeRegistry = new StoreRegistry({
defaultOptions: { batchUpdates }
})
function App() {
return (
<StoreRegistryProvider storeRegistry={storeRegistry}>
<MyComponent />
</StoreRegistryProvider>
)
}

StoreRegistryProvider
storeRegistry: StoreRegistry
storeRegistry
={
const storeRegistry: StoreRegistry
storeRegistry
}>
<
JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
HTMLAttributes<HTMLDivElement>.className?: string | undefined
className
="todoapp">{/* Your app components go here */}</
JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
</
const StoreRegistryProvider: ({ storeRegistry, children }: StoreRegistryProviderProps) => React.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.

@example

import { StoreRegistry } from '@livestore/livestore'
import { StoreRegistryProvider } from '@livestore/react'
import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
const storeRegistry = new StoreRegistry({
defaultOptions: { batchUpdates }
})
function App() {
return (
<StoreRegistryProvider storeRegistry={storeRegistry}>
<MyComponent />
</StoreRegistryProvider>
)
}

StoreRegistryProvider
>
</
const Suspense: React.ExoticComponent<React.SuspenseProps>

Lets you display a fallback until its children have finished loading.

@seehttps://react.dev/reference/react/Suspense React Docs

@example

import { Suspense } from 'react';
<Suspense fallback={<Loading />}>
<ProfileDetails />
</Suspense>

Suspense
>
)
}

After setting up the registry, use the useAppStore() hook from any component to access the store and commit events.

import type
(alias) namespace React
import React
React
from 'react'
import {
function useCallback<T extends Function>(callback: T, deps: React.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 {
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.

@example

const todos$ = queryDb(tables.todos.where({ complete: true }))

@example

// Group-by raw SQL query
const colorCounts$ = queryDb({
query: sql`SELECT color, COUNT(*) as count FROM todos WHERE complete = ? GROUP BY color`,
schema: Schema.Array(Schema.Struct({
color: Schema.String,
count: Schema.Number,
})),
bindValues: [1],
})

@example

// Using contextual data when constructing the query
const makeFilteredQuery = (filter: string) =>
queryDb(tables.todos.where({ title: { op: 'like', value: filter } }), { deps: [filter] })
const filteredTodos$ = makeFilteredQuery('buy coffee')

queryDb
} from '@livestore/livestore'
import {
import events
events
,
import tables
tables
} from './livestore/schema.ts'
import {
import useAppStore
useAppStore
} from './store.ts'
const
const uiState$: LiveQueryDef<unknown, "def">
uiState$
=
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.

@example

const todos$ = queryDb(tables.todos.where({ complete: true }))

@example

// Group-by raw SQL query
const colorCounts$ = queryDb({
query: sql`SELECT color, COUNT(*) as count FROM todos WHERE complete = ? GROUP BY color`,
schema: Schema.Array(Schema.Struct({
color: Schema.String,
count: Schema.Number,
})),
bindValues: [1],
})

@example

// Using contextual data when constructing the query
const makeFilteredQuery = (filter: string) =>
queryDb(tables.todos.where({ title: { op: 'like', value: filter } }), { deps: [filter] })
const filteredTodos$ = makeFilteredQuery('buy coffee')

queryDb
(
import tables
tables
.
any
uiState
.
any
get
(), {
label?: string

Used for debugging / devtools

label
: 'uiState' })
export const
const Header: React.FC<{}>
Header
:
(alias) namespace React
import React
React
.
type FC<P = {}> = React.FunctionComponent<P>

Represents the type of a function component. Can optionally receive a type argument that represents the props the component receives.

@seehttps://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/function_components React TypeScript Cheatsheet

@aliasfor FunctionComponent

@example

// With props:
type Props = { name: string }
const MyComponent: FC<Props> = (props) => {
return <div>{props.name}</div>
}

@example

// Without props:
const MyComponentWithoutProps: FC = () => {
return <div>MyComponentWithoutProps</div>
}

FC
= () => {
const
const store: any
store
=
import useAppStore
useAppStore
()
const {
const newTodoText: any
newTodoText
} =
const store: any
store
.
any
useQuery
(
const uiState$: LiveQueryDef<unknown, "def">
uiState$
)
const
const updateNewTodoText: (text: string) => any
updateNewTodoText
=
useCallback<(text: string) => any>(callback: (text: string) => any, deps: React.DependencyList): (text: string) => any

useCallback will return a memoized version of the callback that only changes if one of the inputs has changed.

useCallback
(
(
text: string
text
: string) =>
const store: any
store
.
any
commit
(
import events
events
.
any
uiStateSet
({
newTodoText: string
newTodoText
:
text: string
text
})),
[
const store: any
store
],
)
const
const createTodo: () => any
createTodo
=
useCallback<() => any>(callback: () => any, deps: React.DependencyList): () => any

useCallback will return a memoized version of the callback that only changes if one of the inputs has changed.

useCallback
(
() =>
const store: any
store
.
any
commit
(
import events
events
.
any
todoCreated
({
id: `${string}-${string}-${string}-${string}-${string}`
id
:
var crypto: Crypto
crypto
.
Crypto.randomUUID(): `${string}-${string}-${string}-${string}-${string}` (+2 overloads)
randomUUID
(),
text: any
text
:
const newTodoText: any
newTodoText
}),
import events
events
.
any
uiStateSet
({
newTodoText: string
newTodoText
: '' }),
),
[
const newTodoText: any
newTodoText
,
const store: any
store
],
)
const
const handleNewTodoTextChange: (event: React.ChangeEvent<HTMLInputElement>) => void
handleNewTodoTextChange
=
useCallback<(event: React.ChangeEvent<HTMLInputElement>) => void>(callback: (event: React.ChangeEvent<HTMLInputElement>) => void, deps: React.DependencyList): (event: React.ChangeEvent<HTMLInputElement>) => void

useCallback will return a memoized version of the callback that only changes if one of the inputs has changed.

useCallback
(
(
event: React.ChangeEvent<HTMLInputElement>
event
:
(alias) namespace React
import React
React
.
interface ChangeEvent<T = Element>
ChangeEvent
<
interface HTMLInputElement

The HTMLInputElement interface provides special properties and methods for manipulating the options, layout, and presentation of elements.

MDN Reference

HTMLInputElement
>) => {
const updateNewTodoText: (text: string) => any
updateNewTodoText
(
event: React.ChangeEvent<HTMLInputElement>
event
.
ChangeEvent<HTMLInputElement>.target: EventTarget & HTMLInputElement
target
.
HTMLInputElement.value: string

The value property of the HTMLInputElement interface represents the current value of the element as a string.

MDN Reference

value
)
},
[
const updateNewTodoText: (text: string) => any
updateNewTodoText
],
)
const
const handleInputKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => void
handleInputKeyDown
=
useCallback<(event: React.KeyboardEvent<HTMLInputElement>) => void>(callback: (event: React.KeyboardEvent<HTMLInputElement>) => void, deps: React.DependencyList): (event: React.KeyboardEvent<HTMLInputElement>) => void

useCallback will return a memoized version of the callback that only changes if one of the inputs has changed.

useCallback
(
(
event: React.KeyboardEvent<HTMLInputElement>
event
:
(alias) namespace React
import React
React
.
interface KeyboardEvent<T = Element>
KeyboardEvent
<
interface HTMLInputElement

The HTMLInputElement interface provides special properties and methods for manipulating the options, layout, and presentation of elements.

MDN Reference

HTMLInputElement
>) => {
if (
event: React.KeyboardEvent<HTMLInputElement>
event
.
KeyboardEvent<HTMLInputElement>.key: string

See the DOM Level 3 Events spec. for possible values

key
=== 'Enter') {
const createTodo: () => any
createTodo
()
}
},
[
const createTodo: () => any
createTodo
],
)
return (
<
JSX.IntrinsicElements.header: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>
header
HTMLAttributes<HTMLElement>.className?: string | undefined
className
="header">
<
JSX.IntrinsicElements.h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>
h1
>TodoMVC</
JSX.IntrinsicElements.h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>
h1
>
<
JSX.IntrinsicElements.input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>
input
HTMLAttributes<T>.className?: string | undefined
className
="new-todo"
InputHTMLAttributes<HTMLInputElement>.placeholder?: string | undefined
placeholder
="What needs to be done?"
InputHTMLAttributes<HTMLInputElement>.value?: string | number | readonly string[] | undefined
value
={
const newTodoText: any
newTodoText
}
InputHTMLAttributes<HTMLInputElement>.onChange?: React.ChangeEventHandler<HTMLInputElement> | undefined
onChange
={
const handleNewTodoTextChange: (event: React.ChangeEvent<HTMLInputElement>) => void
handleNewTodoTextChange
}
DOMAttributes<HTMLInputElement>.onKeyDown?: React.KeyboardEventHandler<HTMLInputElement> | undefined
onKeyDown
={
const handleInputKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => void
handleInputKeyDown
}
/>
</
JSX.IntrinsicElements.header: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>
header
>
)
}

To retrieve data from the database, define a query using queryDb from @livestore/livestore, then execute it with store.useQuery().

Consider abstracting queries into a separate file to keep your code organized, though you can also define them directly within components if preferred.

import
(alias) namespace React
import React
React
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.

@example

const todos$ = queryDb(tables.todos.where({ complete: true }))

@example

// Group-by raw SQL query
const colorCounts$ = queryDb({
query: sql`SELECT color, COUNT(*) as count FROM todos WHERE complete = ? GROUP BY color`,
schema: Schema.Array(Schema.Struct({
color: Schema.String,
count: Schema.Number,
})),
bindValues: [1],
})

@example

// Using contextual data when constructing the query
const makeFilteredQuery = (filter: string) =>
queryDb(tables.todos.where({ title: { op: 'like', value: filter } }), { deps: [filter] })
const filteredTodos$ = makeFilteredQuery('buy coffee')

queryDb
} from '@livestore/livestore'
import {
import events
events
,
import tables
tables
} from './livestore/schema.ts'
import {
import useAppStore
useAppStore
} from './store.ts'
const
const uiState$: LiveQueryDef<unknown, "def">
uiState$
=
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.

@example

const todos$ = queryDb(tables.todos.where({ complete: true }))

@example

// Group-by raw SQL query
const colorCounts$ = queryDb({
query: sql`SELECT color, COUNT(*) as count FROM todos WHERE complete = ? GROUP BY color`,
schema: Schema.Array(Schema.Struct({
color: Schema.String,
count: Schema.Number,
})),
bindValues: [1],
})

@example

// Using contextual data when constructing the query
const makeFilteredQuery = (filter: string) =>
queryDb(tables.todos.where({ title: { op: 'like', value: filter } }), { deps: [filter] })
const filteredTodos$ = makeFilteredQuery('buy coffee')

queryDb
(
import tables
tables
.
any
uiState
.
any
get
(), {
label?: string

Used for debugging / devtools

label
: 'uiState' })
const
const visibleTodos$: LiveQueryDef<unknown, "def">
visibleTodos$
=
queryDb<unknown, unknown>(queryInput: ((get: GetAtomResult) => QueryInputRaw<unknown, readonly any[]>) | ((get: GetAtomResult) => QueryBuilder<unknown, any, any>), options?: {
map?: (rows: unknown) => unknown;
label?: string;
deps?: DepKey;
} | undefined): LiveQueryDef<unknown, "def"> (+1 overload)

NOTE queryDb is only supposed to read data. Don't use it to insert/update/delete data but use events instead.

When using contextual data when constructing the query, please make sure to include it in the deps option.

@example

const todos$ = queryDb(tables.todos.where({ complete: true }))

@example

// Group-by raw SQL query
const colorCounts$ = queryDb({
query: sql`SELECT color, COUNT(*) as count FROM todos WHERE complete = ? GROUP BY color`,
schema: Schema.Array(Schema.Struct({
color: Schema.String,
count: Schema.Number,
})),
bindValues: [1],
})

@example

// Using contextual data when constructing the query
const makeFilteredQuery = (filter: string) =>
queryDb(tables.todos.where({ title: { op: 'like', value: filter } }), { deps: [filter] })
const filteredTodos$ = makeFilteredQuery('buy coffee')

queryDb
(
(
get: GetAtomResult
get
) => {
const {
const filter: any
filter
} =
get: <unknown>(atom: LiveQueryDef<unknown, "def"> | LiveQuery<unknown> | ISignal<unknown> | SignalDef<unknown> | Atom<unknown, any, RefreshReason>, otelContext?: Context, debugRefreshReason?: RefreshReason) => unknown
get
(
const uiState$: LiveQueryDef<unknown, "def">
uiState$
)
return
import tables
tables
.
any
todos
.
any
where
({
deletedAt: null
deletedAt
: null,
completed: boolean | undefined
completed
:
const filter: any
filter
=== 'all' ?
var undefined
undefined
:
const filter: any
filter
=== 'completed',
})
},
{
label?: string

Used for debugging / devtools

label
: 'visibleTodos' },
)
export const
const MainSection: React.FC<{}>
MainSection
:
(alias) namespace React
import React
React
.
type FC<P = {}> = React.FunctionComponent<P>

Represents the type of a function component. Can optionally receive a type argument that represents the props the component receives.

@seehttps://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/function_components React TypeScript Cheatsheet

@aliasfor FunctionComponent

@example

// With props:
type Props = { name: string }
const MyComponent: FC<Props> = (props) => {
return <div>{props.name}</div>
}

@example

// Without props:
const MyComponentWithoutProps: FC = () => {
return <div>MyComponentWithoutProps</div>
}

FC
= () => {
const
const store: any
store
=
import useAppStore
useAppStore
()
const
const handleToggleTodo: (event: React.ChangeEvent<HTMLInputElement>) => void
handleToggleTodo
=
(alias) namespace React
import React
React
.
function useCallback<(event: React.ChangeEvent<HTMLInputElement>) => void>(callback: (event: React.ChangeEvent<HTMLInputElement>) => void, deps: React.DependencyList): (event: React.ChangeEvent<HTMLInputElement>) => void

useCallback will return a memoized version of the callback that only changes if one of the inputs has changed.

useCallback
(
(
event: React.ChangeEvent<HTMLInputElement>
event
:
(alias) namespace React
import React
React
.
interface ChangeEvent<T = Element>
ChangeEvent
<
interface HTMLInputElement

The HTMLInputElement interface provides special properties and methods for manipulating the options, layout, and presentation of elements.

MDN Reference

HTMLInputElement
>) => {
const
const todoId: string | undefined
todoId
=
event: React.ChangeEvent<HTMLInputElement>
event
.
BaseSyntheticEvent<Event, EventTarget & HTMLInputElement, EventTarget>.currentTarget: EventTarget & HTMLInputElement
currentTarget
.
HTMLOrSVGElement.dataset: DOMStringMap
dataset
.
DOMStringMap[string]: string | undefined
todoId
if (
const todoId: string | undefined
todoId
== null) return
const store: any
store
.
any
commit
(
event: React.ChangeEvent<HTMLInputElement>
event
.
BaseSyntheticEvent<Event, EventTarget & HTMLInputElement, EventTarget>.currentTarget: EventTarget & HTMLInputElement
currentTarget
.
HTMLInputElement.checked: boolean

The checked property of the HTMLInputElement interface specifies the current checkedness of the element; that is, whether the form control is checked or not.

MDN Reference

checked
=== true
?
import events
events
.
any
todoCompleted
({
id: string
id
:
const todoId: string
todoId
})
:
import events
events
.
any
todoUncompleted
({
id: string
id
:
const todoId: string
todoId
}),
)
},
[
const store: any
store
],
)
const
const handleDeleteTodo: (event: React.MouseEvent<HTMLButtonElement>) => void
handleDeleteTodo
=
(alias) namespace React
import React
React
.
function useCallback<(event: React.MouseEvent<HTMLButtonElement>) => void>(callback: (event: React.MouseEvent<HTMLButtonElement>) => void, deps: React.DependencyList): (event: React.MouseEvent<HTMLButtonElement>) => void

useCallback will return a memoized version of the callback that only changes if one of the inputs has changed.

useCallback
(
(
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
event
:
(alias) namespace React
import React
React
.
interface MouseEvent<T = Element, E = MouseEvent>
MouseEvent
<
interface HTMLButtonElement

The HTMLButtonElement interface provides properties and methods (beyond the regular HTMLElement interface it also has available to it by inheritance) for manipulating elements.

MDN Reference

HTMLButtonElement
>) => {
const
const todoId: string | undefined
todoId
=
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
event
.
BaseSyntheticEvent<MouseEvent, EventTarget & HTMLButtonElement, EventTarget>.currentTarget: EventTarget & HTMLButtonElement
currentTarget
.
HTMLOrSVGElement.dataset: DOMStringMap
dataset
.
DOMStringMap[string]: string | undefined
todoId
if (
const todoId: string | undefined
todoId
== null) return
const store: any
store
.
any
commit
(
import events
events
.
any
todoDeleted
({
id: string
id
:
const todoId: string
todoId
,
deletedAt: Date
deletedAt
: new
var Date: DateConstructor
new () => Date (+3 overloads)
Date
() }))
},
[
const store: any
store
],
)
const
const visibleTodos: any
visibleTodos
=
const store: any
store
.
any
useQuery
(
const visibleTodos$: LiveQueryDef<unknown, "def">
visibleTodos$
)
return (
<
JSX.IntrinsicElements.section: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>
section
HTMLAttributes<HTMLElement>.className?: string | undefined
className
="main">
<
JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>
ul
HTMLAttributes<T>.className?: string | undefined
className
="todo-list">
{
const visibleTodos: any
visibleTodos
.
any
map
((
todo: any
todo
) => (
<
JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>
li
Attributes.key?: React.Key | null | undefined
key
={
todo: any
todo
.
any
id
}>
<
JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
HTMLAttributes<T>.className?: string | undefined
className
="view">
<
JSX.IntrinsicElements.input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>
input
InputHTMLAttributes<HTMLInputElement>.type?: React.HTMLInputTypeAttribute | undefined
type
="checkbox"
HTMLAttributes<T>.className?: string | undefined
className
="toggle"
HTMLAttributes<HTMLInputElement>.id?: string | undefined
id
={`todo-${
todo: any
todo
.
any
id
}`}
data-todo-id: any
data-todo-id
={
todo: any
todo
.
any
id
}
InputHTMLAttributes<HTMLInputElement>.checked?: boolean | undefined
checked
={
todo: any
todo
.
any
completed
}
InputHTMLAttributes<HTMLInputElement>.onChange?: React.ChangeEventHandler<HTMLInputElement> | undefined
onChange
={
const handleToggleTodo: (event: React.ChangeEvent<HTMLInputElement>) => void
handleToggleTodo
}
/>
<
JSX.IntrinsicElements.label: React.DetailedHTMLProps<React.LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>
label
LabelHTMLAttributes<HTMLLabelElement>.htmlFor?: string | undefined
htmlFor
={`todo-${
todo: any
todo
.
any
id
}`}>{
todo: any
todo
.
any
text
}</
JSX.IntrinsicElements.label: React.DetailedHTMLProps<React.LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>
label
>
<
JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button
ButtonHTMLAttributes<HTMLButtonElement>.type?: "button" | "reset" | "submit" | undefined
type
="button"
HTMLAttributes<T>.className?: string | undefined
className
="destroy"
data-todo-id: any
data-todo-id
={
todo: any
todo
.
any
id
}
DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefined
onClick
={
const handleDeleteTodo: (event: React.MouseEvent<HTMLButtonElement>) => void
handleDeleteTodo
} />
</
JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
</
JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>
li
>
))}
</
JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>
ul
>
</
JSX.IntrinsicElements.section: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>
section
>
)
}