Skip to content

Todo app with shared workspaces

Let’s consider a fairly common application scenario: An app (in this case a todo app) with shared workspaces. For the sake of this guide, we’ll keep things simple but you should be able to nicely extend this to a more complex app.

  • There are multiple independent todo workspaces
  • Each workspace is initially created by a single user
  • Users can join the workspace by knowing the workspace id and get read and write access
  • For simplicity, the user identity is chosen when the app initially starts (i.e. a username) but in a real app this would be handled by a proper auth setup
  • We are splitting up our data model into two kinds of stores (with respective eventlogs and SQLite databases): The workspace store and the user store.

For the workspace store we have the following events:

  • workspaceCreated
  • todoAdded
  • todoCompleted
  • todoDeleted
  • userJoined

And the following state model:

  • workspace table (with a single row for the workspace itself)
  • todo table (with one row per todo item)
  • member table (with one row per user who has joined the workspace)

For the user store we have the following events:

  • workspaceCreated
  • workspaceJoined

And the following state model:

  • user table (with a single row for the user itself)

Note that the workspaceCreated event is used both in the workspace and the user store. This is because each eventlog should be “self-sufficient” and not rely on other eventlogs to be present to fulfill its purpose.

todo-list-1

user-alice

user-bob

user-charlie

todo-list-2

Alice

🔒

🔒

Bob

🔒

🔒

🔒

Charlie

🔒

🔒

User-related data

User

Workspace data

todo-list-1

user-alice

user-bob

user-charlie

todo-list-2

Alice

🔒

🔒

Bob

🔒

🔒

🔒

Charlie

🔒

🔒

User-related data

User

Workspace data

User store:

import {
import Events
Events
,
const makeSchema: <TInputSchema extends InputSchema>(inputSchema: TInputSchema) => FromInputSchema.DeriveSchema<TInputSchema>
makeSchema
,
import Schema
Schema
,
import State
State
} from '@livestore/livestore'
// Emitted when this user creates a new workspace
const
const workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>
workspaceCreated
=
import Events
Events
.
synced<"v1.WorkspaceCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>(args: {
name: "v1.WorkspaceCreated";
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: 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.WorkspaceCreated"
name
: 'v1.WorkspaceCreated',
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">, never, never>
schema
:
import Schema
Schema
.
function Struct<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}>(fields: {
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}): Schema.Struct<{
readonly workspaceId: Schema.String;
readonly name: 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
({
workspaceId: Schema.String
workspaceId
:
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
,
name: Schema.String
name
:
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
}),
})
// Emitted when this user joins an existing workspace
const
const workspaceJoined: State.SQLite.EventDef<"v1.WorkspaceJoined", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>
workspaceJoined
=
import Events
Events
.
synced<"v1.WorkspaceJoined", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>(args: {
name: "v1.WorkspaceJoined";
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: 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.WorkspaceJoined"
name
: 'v1.WorkspaceJoined',
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">, never, never>
schema
:
import Schema
Schema
.
function Struct<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}>(fields: {
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}): Schema.Struct<{
readonly workspaceId: Schema.String;
readonly name: 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
({
workspaceId: Schema.String
workspaceId
:
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
,
name: Schema.String
name
:
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
}),
})
export const
const userEvents: {
workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>;
workspaceJoined: State.SQLite.EventDef<"v1.WorkspaceJoined", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>;
}
userEvents
= {
workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>
workspaceCreated
,
workspaceJoined: State.SQLite.EventDef<"v1.WorkspaceJoined", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>
workspaceJoined
}
// Table to store basic user info
// Contains only one row as this store is per-user.
const
const userTable: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"user", {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, Schema.Struct<{
readonly username: Schema.Codec<string, string, never, never>;
}>>
userTable
=
import State
State
.
import SQLite
SQLite
.
function table<"user", {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}, Partial<{
indexes: Index[];
}>>(args: {
name: "user";
columns: {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
};
} & Partial<Partial<{
indexes: Index[];
}>>): 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: "user"
name
: 'user',
columns: {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}
columns
: {
// Assuming username is unique and used as the identifier
username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
}
username
:
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 }),
},
})
// Table to track which workspaces this user is part of
const
const userWorkspacesTable: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"userWorkspaces", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, Schema.Struct<...>>
userWorkspacesTable
=
import State
State
.
import SQLite
SQLite
.
function table<"userWorkspaces", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}, Partial<{
indexes: Index[];
}>>(args: {
name: "userWorkspaces";
columns: {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
};
} & 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: "userWorkspaces"
name
: 'userWorkspaces',
columns: {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}
columns
: {
workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
}
workspaceId
:
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 }),
name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
}
name
:
import State
State
.
import SQLite
SQLite
.
const text: () => {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
} (+1 overload)
text
(),
// Could add role/permissions here later
},
})
export const
const userTables: {
user: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"user", {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, Schema.Struct<{
readonly username: Schema.Codec<string, string, never, never>;
}>>;
userWorkspaces: State.SQLite.TableDef<...>;
}
userTables
= {
user: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"user", {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, Schema.Struct<{
readonly username: Schema.Codec<string, string, never, never>;
}>>
user
:
const userTable: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"user", {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, Schema.Struct<{
readonly username: Schema.Codec<string, string, never, never>;
}>>
userTable
,
userWorkspaces: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"userWorkspaces", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, Schema.Struct<...>>
userWorkspaces
:
const userWorkspacesTable: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"userWorkspaces", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, Schema.Struct<...>>
userWorkspacesTable
}
const
const materializers: {
"v1.WorkspaceCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>>;
"v1.WorkspaceJoined": State.SQLite.Materializer<State.SQLite.EventDef<"v1.WorkspaceJoined", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<...>>>;
}
materializers
=
import State
State
.
import SQLite
SQLite
.
const materializers: <{
workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>;
workspaceJoined: State.SQLite.EventDef<"v1.WorkspaceJoined", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<...>>;
}>(_eventDefRecord: {
workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>;
workspaceJoined: State.SQLite.EventDef<"v1.WorkspaceJoined", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<...>>;
}, handlers: {
...;
}) => {
...;
}

Builder function for creating a type-safe materializer map.

This is the primary way to define materializers in LiveStore. It ensures:

  • Every non-derived event has a corresponding materializer
  • Materializer argument types match their event schemas
  • Derived events are excluded from the required handlers

@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 userEvents: {
workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>;
workspaceJoined: State.SQLite.EventDef<"v1.WorkspaceJoined", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>;
}
userEvents
, {
// When the user creates or joins a workspace, add it to their workspace table
'v1.WorkspaceCreated': ({
workspaceId: string
workspaceId
,
name: string
name
}) =>
const userTables: {
user: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"user", {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, Schema.Struct<{
readonly username: Schema.Codec<string, string, never, never>;
}>>;
userWorkspaces: State.SQLite.TableDef<...>;
}
userTables
.
userWorkspaces: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"userWorkspaces", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, Schema.Struct<...>>
userWorkspaces
.
insert: (values: {
readonly workspaceId: string;
readonly name: string;
}) => QueryBuilder<readonly Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly workspaceId: Schema.Codec<string, string, never, never>;
readonly name: Schema.Codec<string, string, never, never>;
}, "Type">[], State.SQLite.TableDefBase<State.SQLite.SqliteTableDefForInput<"userWorkspaces", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
... 4 more ...;
autoIncrement: false;
};
}>, 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
({
workspaceId: string
workspaceId
,
name: string
name
}),
'v1.WorkspaceJoined': ({
workspaceId: string
workspaceId
,
name: string
name
}) =>
const userTables: {
user: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"user", {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, Schema.Struct<{
readonly username: Schema.Codec<string, string, never, never>;
}>>;
userWorkspaces: State.SQLite.TableDef<...>;
}
userTables
.
userWorkspaces: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"userWorkspaces", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, Schema.Struct<...>>
userWorkspaces
.
insert: (values: {
readonly workspaceId: string;
readonly name: string;
}) => QueryBuilder<readonly Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly workspaceId: Schema.Codec<string, string, never, never>;
readonly name: Schema.Codec<string, string, never, never>;
}, "Type">[], State.SQLite.TableDefBase<State.SQLite.SqliteTableDefForInput<"userWorkspaces", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
... 4 more ...;
autoIncrement: false;
};
}>, 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
({
workspaceId: string
workspaceId
,
name: string
name
}),
})
const
const state: InternalState
state
=
import State
State
.
import SQLite
SQLite
.
const makeState: <{
tables: {
user: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"user", {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, Schema.Struct<{
readonly username: Schema.Codec<string, string, never, never>;
}>>;
userWorkspaces: State.SQLite.TableDef<...>;
};
materializers: {
...;
};
}>(inputSchema: {
tables: {
user: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"user", {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, Schema.Struct<{
readonly username: Schema.Codec<string, string, never, never>;
}>>;
userWorkspaces: State.SQLite.TableDef<...>;
};
materializers: {
...;
};
}) => InternalState
makeState
({
tables: {
user: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"user", {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, Schema.Struct<{
readonly username: Schema.Codec<string, string, never, never>;
}>>;
userWorkspaces: State.SQLite.TableDef<...>;
}
tables
:
const userTables: {
user: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"user", {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, Schema.Struct<{
readonly username: Schema.Codec<string, string, never, never>;
}>>;
userWorkspaces: State.SQLite.TableDef<...>;
}
userTables
,
materializers: {
"v1.WorkspaceCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>>;
"v1.WorkspaceJoined": State.SQLite.Materializer<State.SQLite.EventDef<"v1.WorkspaceJoined", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<...>>>;
}
materializers
})
export const
const schema: FromInputSchema.DeriveSchema<{
events: {
workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>;
workspaceJoined: State.SQLite.EventDef<"v1.WorkspaceJoined", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>;
};
state: InternalState;
}>
schema
=
makeSchema<{
events: {
workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>;
workspaceJoined: State.SQLite.EventDef<"v1.WorkspaceJoined", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<...>>;
};
state: InternalState;
}>(inputSchema: {
events: {
workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>;
workspaceJoined: State.SQLite.EventDef<"v1.WorkspaceJoined", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<...>>;
};
state: InternalState;
}): FromInputSchema.DeriveSchema<...>
makeSchema
({
events: {
workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>;
workspaceJoined: State.SQLite.EventDef<"v1.WorkspaceJoined", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>;
}
events
:
const userEvents: {
workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>;
workspaceJoined: State.SQLite.EventDef<"v1.WorkspaceJoined", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
}, "Encoded">>;
}
userEvents
,
state: InternalState
state
})

Workspace store:

import {
import Events
Events
,
const makeSchema: <TInputSchema extends InputSchema>(inputSchema: TInputSchema) => FromInputSchema.DeriveSchema<TInputSchema>
makeSchema
,
import Schema
Schema
,
import State
State
} from '@livestore/livestore'
// Emitted when a new workspace is created (originates this store)
const
const workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Encoded">>
workspaceCreated
=
import Events
Events
.
synced<"v1.WorkspaceCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Encoded">>(args: {
name: "v1.WorkspaceCreated";
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<...>, never, never>;
} & Omit<...>): State.SQLite.EventDef<...>
export synced

Creates a synced event definition.

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

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

@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.WorkspaceCreated"
name
: 'v1.WorkspaceCreated',
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Encoded">, never, never>
schema
:
import Schema
Schema
.
function Struct<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}>(fields: {
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}): Schema.Struct<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: 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
({
workspaceId: Schema.String
workspaceId
:
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
,
name: Schema.String
name
:
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
,
createdByUsername: Schema.String
createdByUsername
:
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
,
}),
})
// Emitted when a todo item is added to this workspace
const
const todoAdded: State.SQLite.EventDef<"v1.TodoAdded", Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly text: Schema.String;
}, "Encoded">>
todoAdded
=
import Events
Events
.
synced<"v1.TodoAdded", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly todoId: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly text: Schema.String;
}, "Encoded">>(args: {
name: "v1.TodoAdded";
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly todoId: 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.TodoAdded"
name
: 'v1.TodoAdded',
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly text: Schema.String;
}, "Encoded">, never, never>
schema
:
import Schema
Schema
.
function Struct<{
readonly todoId: Schema.String;
readonly text: Schema.String;
}>(fields: {
readonly todoId: Schema.String;
readonly text: Schema.String;
}): Schema.Struct<{
readonly todoId: 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
({
todoId: Schema.String
todoId
:
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
}),
})
// Emitted when a todo item is marked as completed
const
const todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
}, "Encoded">>
todoCompleted
=
import Events
Events
.
synced<"v1.TodoCompleted", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly todoId: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
}, "Encoded">>(args: {
name: "v1.TodoCompleted";
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
}, "Encoded">, never, never>;
} & Omit<State.SQLite.DefineEventOptions<Schema.Struct.ReadonlySide<{
readonly todoId: 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 todoId: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
}, "Encoded">, never, never>
schema
:
import Schema
Schema
.
function Struct<{
readonly todoId: Schema.String;
}>(fields: {
readonly todoId: Schema.String;
}): Schema.Struct<{
readonly todoId: 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
({
todoId: Schema.String
todoId
:
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
}),
})
// Emitted when a todo item is deleted (soft delete)
const
const todoDeleted: State.SQLite.EventDef<"v1.TodoDeleted", Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly deletedAt: Schema.DateFromString;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly deletedAt: Schema.DateFromString;
}, "Encoded">>
todoDeleted
=
import Events
Events
.
synced<"v1.TodoDeleted", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly todoId: Schema.String;
readonly deletedAt: Schema.DateFromString;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly deletedAt: Schema.DateFromString;
}, "Encoded">>(args: {
name: "v1.TodoDeleted";
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly deletedAt: Schema.DateFromString;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly todoId: 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 todoId: Schema.String;
readonly deletedAt: Schema.DateFromString;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly deletedAt: Schema.DateFromString;
}, "Encoded">, never, never>
schema
:
import Schema
Schema
.
function Struct<{
readonly todoId: Schema.String;
readonly deletedAt: Schema.DateFromString;
}>(fields: {
readonly todoId: Schema.String;
readonly deletedAt: Schema.DateFromString;
}): Schema.Struct<{
readonly todoId: 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
({
todoId: Schema.String
todoId
:
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
()),
}),
})
// Emitted when a new user joins this workspace
const
const userJoined: State.SQLite.EventDef<"v1.UserJoined", Schema.Struct.ReadonlySide<{
readonly username: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly username: Schema.String;
}, "Encoded">>
userJoined
=
import Events
Events
.
synced<"v1.UserJoined", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly username: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly username: Schema.String;
}, "Encoded">>(args: {
name: "v1.UserJoined";
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly username: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly username: Schema.String;
}, "Encoded">, never, never>;
} & Omit<State.SQLite.DefineEventOptions<Schema.Struct.ReadonlySide<{
readonly username: 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.UserJoined"
name
: 'v1.UserJoined',
schema: Schema.Codec<Schema.Struct.ReadonlySide<{
readonly username: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly username: Schema.String;
}, "Encoded">, never, never>
schema
:
import Schema
Schema
.
function Struct<{
readonly username: Schema.String;
}>(fields: {
readonly username: Schema.String;
}): Schema.Struct<{
readonly username: 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
({
username: Schema.String
username
:
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
}),
})
export const
const workspaceEvents: {
workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Encoded">>;
todoAdded: State.SQLite.EventDef<"v1.TodoAdded", Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<...>>;
todoCompleted: State.SQLite.EventDef<...>;
todoDeleted: State.SQLite.EventDef<...>;
userJoined: State.SQLite.EventDef<...>;
}
workspaceEvents
= {
workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Encoded">>
workspaceCreated
,
todoAdded: State.SQLite.EventDef<"v1.TodoAdded", Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly text: Schema.String;
}, "Encoded">>
todoAdded
,
todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
}, "Encoded">>
todoCompleted
,
todoDeleted: State.SQLite.EventDef<"v1.TodoDeleted", Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly deletedAt: Schema.DateFromString;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly deletedAt: Schema.DateFromString;
}, "Encoded">>
todoDeleted
,
userJoined: State.SQLite.EventDef<"v1.UserJoined", Schema.Struct.ReadonlySide<{
readonly username: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly username: Schema.String;
}, "Encoded">>
userJoined
}
// Table for the workspace itself (only one row as this store is per-workspace)
const
const workspaceTable: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"workspace", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly createdByUsername: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
workspaceTable
=
import State
State
.
import SQLite
SQLite
.
function table<"workspace", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly createdByUsername: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}, Partial<...>>(args: {
...;
} & Partial<...>): State.SQLite.TableDef<...> (+2 overloads)

Creates a SQLite table definition from columns or an Effect Schema.

This function supports two main ways to define a table:

  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: "workspace"
name
: 'workspace',
columns: {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly createdByUsername: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}
columns
: {
workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
}
workspaceId
:
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 }),
name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
}
name
:
import State
State
.
import SQLite
SQLite
.
const text: () => {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
} (+1 overload)
text
(),
createdByUsername: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
}
createdByUsername
:
import State
State
.
import SQLite
SQLite
.
const text: () => {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
} (+1 overload)
text
(),
},
})
// Table for the todo items in this workspace
const
const todosTable: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", {
readonly todoId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly text: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema.Codec<boolean, number, never, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly deletedAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todosTable
=
import State
State
.
import SQLite
SQLite
.
function table<"todos", {
readonly todoId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly text: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema.Codec<boolean, number, never, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly 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 todoId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly text: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema.Codec<boolean, number, never, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly deletedAt: {
...;
};
}
columns
: {
todoId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
}
todoId
:
import State
State
.
import SQLite
SQLite
.
const text: <string, string, false, typeof NoDefault, true, false>(args: {
schema?: Schema.Codec<string, string, never, never>;
default?: typeof NoDefault;
nullable?: false;
primaryKey?: true;
autoIncrement?: false;
}) => {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
} (+1 overload)
text
({
primaryKey?: true
primaryKey
: true }),
text: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
}
text
:
import State
State
.
import SQLite
SQLite
.
const text: () => {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
} (+1 overload)
text
(),
completed: {
columnType: "integer";
schema: Schema.Codec<boolean, number, never, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
}
completed
:
import State
State
.
import SQLite
SQLite
.
const boolean: <boolean, false, false, false, false>(args: {
default?: false;
nullable?: false;
primaryKey?: false;
autoIncrement?: false;
}) => {
columnType: "integer";
schema: Schema.Codec<boolean, number, never, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
} (+1 overload)
boolean
({
default?: false
default
: false }),
// Using soft delete by adding a deletedAt timestamp
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
}),
},
})
// Table for members of this workspace
const
const membersTable: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"members", {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, Schema.Struct<{
readonly username: Schema.Codec<string, string, never, never>;
}>>
membersTable
=
import State
State
.
import SQLite
SQLite
.
function table<"members", {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}, Partial<{
indexes: Index[];
}>>(args: {
name: "members";
columns: {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
};
} & Partial<Partial<{
indexes: Index[];
}>>): 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: "members"
name
: 'members',
columns: {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}
columns
: {
username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
}
username
:
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 }),
// Could add role/permissions here later
},
})
export const
const workspaceTables: {
workspace: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"workspace", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly createdByUsername: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
todos: State.SQLite.TableDef<...>;
members: State.SQLite.TableDef<...>;
}
workspaceTables
= {
workspace: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"workspace", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly createdByUsername: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
workspace
:
const workspaceTable: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"workspace", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly createdByUsername: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
workspaceTable
,
todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", {
readonly todoId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly text: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema.Codec<boolean, number, never, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly deletedAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos
:
const todosTable: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", {
readonly todoId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly text: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema.Codec<boolean, number, never, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly deletedAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todosTable
,
members: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"members", {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, Schema.Struct<{
readonly username: Schema.Codec<string, string, never, never>;
}>>
members
:
const membersTable: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"members", {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, Schema.Struct<{
readonly username: Schema.Codec<string, string, never, never>;
}>>
membersTable
}
const
const materializers: {
"v1.WorkspaceCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Encoded">>>;
"v1.TodoAdded": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoAdded", Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<...>>>;
"v1.TodoCompleted": State.SQLite.Materializer<...>;
"v1.TodoDeleted": State.SQLite.Materializer<...>;
"v1.UserJoined": State.SQLite.Materializer<...>;
}
materializers
=
import State
State
.
import SQLite
SQLite
.
const materializers: <{
workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Encoded">>;
todoAdded: State.SQLite.EventDef<"v1.TodoAdded", Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<...>>;
todoCompleted: State.SQLite.EventDef<...>;
todoDeleted: State.SQLite.EventDef<...>;
userJoined: State.SQLite.EventDef<...>;
}>(_eventDefRecord: {
workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Encoded">>;
todoAdded: State.SQLite.EventDef<"v1.TodoAdded", Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<...>>;
todoCompleted: State.SQLite.EventDef<...>;
todoDeleted: State.SQLite.EventDef<...>;
userJoined: State.SQLite.EventDef<...>;
}, 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 workspaceEvents: {
workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Encoded">>;
todoAdded: State.SQLite.EventDef<"v1.TodoAdded", Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<...>>;
todoCompleted: State.SQLite.EventDef<...>;
todoDeleted: State.SQLite.EventDef<...>;
userJoined: State.SQLite.EventDef<...>;
}
workspaceEvents
, {
'v1.WorkspaceCreated': ({
workspaceId: string
workspaceId
,
name: string
name
,
createdByUsername: string
createdByUsername
}) => [
const workspaceTables: {
workspace: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"workspace", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly createdByUsername: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
todos: State.SQLite.TableDef<...>;
members: State.SQLite.TableDef<...>;
}
workspaceTables
.
workspace: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"workspace", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly createdByUsername: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
workspace
.
insert: (values: {
readonly workspaceId: string;
readonly name: string;
readonly createdByUsername: string;
}) => QueryBuilder<readonly Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly workspaceId: Schema.Codec<string, string, never, never>;
readonly name: Schema.Codec<string, string, never, never>;
readonly createdByUsername: Schema.Codec<string, string, never, never>;
}, "Type">[], State.SQLite.TableDefBase<State.SQLite.SqliteTableDefForInput<"workspace", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<...>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
...;
};
readonly createdByUsername: {
...;
};
}>, 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
({
workspaceId: string
workspaceId
,
name: string
name
,
createdByUsername: string
createdByUsername
}),
// Add the creator as the first member
const workspaceTables: {
workspace: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"workspace", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly createdByUsername: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
todos: State.SQLite.TableDef<...>;
members: State.SQLite.TableDef<...>;
}
workspaceTables
.
members: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"members", {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, Schema.Struct<{
readonly username: Schema.Codec<string, string, never, never>;
}>>
members
.
insert: (values: {
readonly username: string;
}) => QueryBuilder<readonly Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly username: Schema.Codec<string, string, never, never>;
}, "Type">[], State.SQLite.TableDefBase<State.SQLite.SqliteTableDefForInput<"members", {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>>, "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
({
username: string
username
:
createdByUsername: string
createdByUsername
}),
],
'v1.TodoAdded': ({
todoId: string
todoId
,
text: string
text
}) =>
const workspaceTables: {
workspace: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"workspace", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly createdByUsername: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
todos: State.SQLite.TableDef<...>;
members: State.SQLite.TableDef<...>;
}
workspaceTables
.
todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", {
readonly todoId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly text: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema.Codec<boolean, number, never, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly deletedAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos
.
insert: (values: {
readonly todoId: string;
readonly text: string;
readonly deletedAt?: Date | null;
readonly completed?: boolean;
}) => QueryBuilder<readonly Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly todoId: 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<...>, 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
({
todoId: string
todoId
,
text: string
text
}),
'v1.TodoCompleted': ({
todoId: string
todoId
}) =>
const workspaceTables: {
workspace: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"workspace", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly createdByUsername: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
todos: State.SQLite.TableDef<...>;
members: State.SQLite.TableDef<...>;
}
workspaceTables
.
todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", {
readonly todoId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly text: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema.Codec<boolean, number, never, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly deletedAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos
.
update: (values: Partial<Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly todoId: 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 todoId: 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 todoId: 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
({
todoId?: string | {
op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>;
value: string;
} | {
op: QueryBuilder.WhereOps.MultiValue;
value: readonly string[];
} | undefined
todoId
}),
'v1.TodoDeleted': ({
todoId: string
todoId
,
deletedAt: Date
deletedAt
}) =>
const workspaceTables: {
workspace: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"workspace", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly createdByUsername: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
todos: State.SQLite.TableDef<...>;
members: State.SQLite.TableDef<...>;
}
workspaceTables
.
todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", {
readonly todoId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly text: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema.Codec<boolean, number, never, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly deletedAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos
.
update: (values: Partial<Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly todoId: 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 todoId: 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 todoId: 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
({
todoId?: string | {
op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>;
value: string;
} | {
op: QueryBuilder.WhereOps.MultiValue;
value: readonly string[];
} | undefined
todoId
}),
'v1.UserJoined': ({
username: string
username
}) =>
const workspaceTables: {
workspace: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"workspace", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly createdByUsername: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
todos: State.SQLite.TableDef<...>;
members: State.SQLite.TableDef<...>;
}
workspaceTables
.
members: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"members", {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, Schema.Struct<{
readonly username: Schema.Codec<string, string, never, never>;
}>>
members
.
insert: (values: {
readonly username: string;
}) => QueryBuilder<readonly Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly username: Schema.Codec<string, string, never, never>;
}, "Type">[], State.SQLite.TableDefBase<State.SQLite.SqliteTableDefForInput<"members", {
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<{
readonly username: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
}>>, "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
({
username: string
username
}),
})
const
const state: InternalState
state
=
import State
State
.
import SQLite
SQLite
.
const makeState: <{
tables: {
workspace: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"workspace", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly createdByUsername: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
todos: State.SQLite.TableDef<...>;
members: State.SQLite.TableDef<...>;
};
materializers: {
...;
};
}>(inputSchema: {
tables: {
workspace: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"workspace", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly createdByUsername: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
todos: State.SQLite.TableDef<...>;
members: State.SQLite.TableDef<...>;
};
materializers: {
...;
};
}) => InternalState
makeState
({
tables: {
workspace: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"workspace", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly createdByUsername: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
todos: State.SQLite.TableDef<...>;
members: State.SQLite.TableDef<...>;
}
tables
:
const workspaceTables: {
workspace: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"workspace", {
readonly workspaceId: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly name: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly createdByUsername: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
todos: State.SQLite.TableDef<...>;
members: State.SQLite.TableDef<...>;
}
workspaceTables
,
materializers: {
"v1.WorkspaceCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Encoded">>>;
"v1.TodoAdded": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoAdded", Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<...>>>;
"v1.TodoCompleted": State.SQLite.Materializer<...>;
"v1.TodoDeleted": State.SQLite.Materializer<...>;
"v1.UserJoined": State.SQLite.Materializer<...>;
}
materializers
})
export const
const schema: FromInputSchema.DeriveSchema<{
events: {
workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Encoded">>;
todoAdded: State.SQLite.EventDef<"v1.TodoAdded", Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<...>>;
todoCompleted: State.SQLite.EventDef<...>;
todoDeleted: State.SQLite.EventDef<...>;
userJoined: State.SQLite.EventDef<...>;
};
state: InternalState;
}>
schema
=
makeSchema<{
events: {
workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Encoded">>;
todoAdded: State.SQLite.EventDef<"v1.TodoAdded", Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<...>>;
todoCompleted: State.SQLite.EventDef<...>;
todoDeleted: State.SQLite.EventDef<...>;
userJoined: State.SQLite.EventDef<...>;
};
state: InternalState;
}>(inputSchema: {
events: {
workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Encoded">>;
todoAdded: State.SQLite.EventDef<"v1.TodoAdded", Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<...>>;
todoCompleted: State.SQLite.EventDef<...>;
todoDeleted: State.SQLite.EventDef<...>;
userJoined: State.SQLite.EventDef<...>;
};
state: InternalState;
}): FromInputSchema.DeriveSchema<...>
makeSchema
({
events: {
workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Encoded">>;
todoAdded: State.SQLite.EventDef<"v1.TodoAdded", Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<...>>;
todoCompleted: State.SQLite.EventDef<...>;
todoDeleted: State.SQLite.EventDef<...>;
userJoined: State.SQLite.EventDef<...>;
}
events
:
const workspaceEvents: {
workspaceCreated: State.SQLite.EventDef<"v1.WorkspaceCreated", Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly workspaceId: Schema.String;
readonly name: Schema.String;
readonly createdByUsername: Schema.String;
}, "Encoded">>;
todoAdded: State.SQLite.EventDef<"v1.TodoAdded", Schema.Struct.ReadonlySide<{
readonly todoId: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<...>>;
todoCompleted: State.SQLite.EventDef<...>;
todoDeleted: State.SQLite.EventDef<...>;
userJoined: State.SQLite.EventDef<...>;
}
workspaceEvents
,
state: InternalState
state
})

Now that we’ve defined our schemas, let’s configure the stores:

Workspace store:

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 sharedWorker: new (options?: {
name?: string;
}) => SharedWorker
sharedWorker
from '@livestore/adapter-web/shared-worker?sharedworker'
import {
const storeOptions: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>(options: RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>) => RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>

Helper for defining reusable store options with full type inference. Returns options that can be passed to useStore() or storeRegistry.preload().

@paramoptions - The store configuration options

@returnsThe same options object, unchanged

@example

export const issueStoreOptions = (issueId: string) =>
storeOptions({
storeId: `issue-${issueId}`,
schema,
adapter,
unusedCacheTime: 30_000,
})
// In a component
const issueStore = useStore(issueStoreOptions(issueId))
// In a route loader or event handler
storeRegistry.preload({
...issueStoreOptions(issueId),
unusedCacheTime: 10_000,
});

storeOptions
} from '@livestore/livestore'
import {
import schema
schema
} from './workspace.schema.ts'
import
const worker: new (options?: {
name?: string;
}) => Worker
worker
from './workspace.worker.ts?worker'
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
,
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
,
})
// Define workspace store configuration
// Each workspace gets its own isolated store instance
export const
const workspaceStoreOptions: (workspaceId: string) => RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>
workspaceStoreOptions
= (
workspaceId: string
workspaceId
: string) =>
storeOptions<any, {}, Codec<Json, Json, never, never>>(options: RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>): RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>

Helper for defining reusable store options with full type inference. Returns options that can be passed to useStore() or storeRegistry.preload().

@paramoptions - The store configuration options

@returnsThe same options object, unchanged

@example

export const issueStoreOptions = (issueId: string) =>
storeOptions({
storeId: `issue-${issueId}`,
schema,
adapter,
unusedCacheTime: 30_000,
})
// In a component
const issueStore = useStore(issueStoreOptions(issueId))
// In a route loader or event handler
storeRegistry.preload({
...issueStoreOptions(issueId),
unusedCacheTime: 10_000,
});

storeOptions
({
CreateStoreOptions<TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>.storeId: string

Unique identifier for the Store instance, stable for its lifetime.

  • Valid characters: Only alphanumeric characters, underscores (_), and hyphens (-) are allowed. Must match /^[a-zA-Z0-9_-]+$/.
  • Globally unique: Use globally unique IDs (e.g., nanoid) to prevent collisions across stores.
  • Use namespaces: Prefix to avoid collisions and for easier identification when debugging (e.g., app-root, workspace-abc123, issue-456)

storeId
: `workspace-${
workspaceId: string
workspaceId
}`,
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
,
RegistryStoreOptions<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<...>>.unusedCacheTime?: number

The time in milliseconds that this store should remain in memory after becoming unused. When this store becomes unused (no active retentions), it will be disposed after this duration.

Stores transition to the unused state as soon as they have no active retentions, so when all components which use that store have unmounted.

unusedCacheTime
: 60_000, // Keep in memory for 60 seconds after last use
})

User store:

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 sharedWorker: new (options?: {
name?: string;
}) => SharedWorker
sharedWorker
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 {
import schema
schema
} from './user.schema.ts'
import
const worker: new (options?: {
name?: string;
}) => Worker
worker
from './user.worker.ts?worker'
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
,
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
,
})
// Hook to access the current user's store
export const
const useCurrentUserStore: () => Store<any, {}> & ReactApi
useCurrentUserStore
= () =>
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
: 'user-current', // Backend should resolve this to the authenticated user's store
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
,
RegistryStoreOptions<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<...>>.unusedCacheTime?: number

The time in milliseconds that this store should remain in memory after becoming unused. When this store becomes unused (no active retentions), it will be disposed after this duration.

Stores transition to the unused state as soon as they have no active retentions, so when all components which use that store have unmounted.

unusedCacheTime
:
var Number: NumberConstructor

An object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.

Number
.
NumberConstructor.POSITIVE_INFINITY: number

A value greater than the largest number that can be represented in JavaScript. JavaScript displays POSITIVE_INFINITY values as infinity.

POSITIVE_INFINITY
, // Keep user store in memory indefinitely
})

Create a StoreRegistry and provide it to your React app:

import { type
type ReactNode = string | number | bigint | boolean | ReactElement<unknown, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | Promise<AwaitedReactNode> | null | undefined

Represents all of the things React can render.

Where

ReactElement

only represents JSX, ReactNode represents everything that can be rendered.

@seehttps://react-typescript-cheatsheet.netlify.app/docs/react-types/reactnode/ React TypeScript Cheatsheet

@example

// Typing children
type Props = { children: ReactNode }
const Component = ({ children }: Props) => <div>{children}</div>
<Component>hello</Component>

@example

// Typing a custom element
type Props = { customElement: ReactNode }
const Component = ({ customElement }: Props) => <div>{customElement}</div>
<Component customElement={<div>hello</div>} />

ReactNode
,
function useState<S>(initialState: S | (() => S)): [S, Dispatch<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 {
function unstable_batchedUpdates<A, R>(callback: (a: A) => R, a: A): R (+1 overload)
unstable_batchedUpdates
as
function batchUpdates<A, R>(callback: (a: A) => R, a: A): R (+1 overload)
batchUpdates
} from 'react-dom'
import {
class StoreRegistry

Store Registry coordinating store loading, caching, and retention

@public

StoreRegistry
} from '@livestore/livestore'
import {
const StoreRegistryProvider: ({ storeRegistry, children }: StoreRegistryProviderProps) => JSX.Element

React context provider that makes a

StoreRegistry

available to descendant components.

Wrap your application (or a subtree) with this provider to enable

useStore

and

useStoreRegistry

hooks within that tree.

@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'
export const
const App: ({ children }: {
children: ReactNode;
}) => JSX.Element
App
= ({
children: ReactNode
children
}: {
children: ReactNode
children
:
type ReactNode = string | number | bigint | boolean | ReactElement<unknown, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | Promise<AwaitedReactNode> | null | undefined

Represents all of the things React can render.

Where

ReactElement

only represents JSX, ReactNode represents everything that can be rendered.

@seehttps://react-typescript-cheatsheet.netlify.app/docs/react-types/reactnode/ React TypeScript Cheatsheet

@example

// Typing children
type Props = { children: ReactNode }
const Component = ({ children }: Props) => <div>{children}</div>
<Component>hello</Component>

@example

// Typing a custom element
type Props = { customElement: ReactNode }
const Component = ({ customElement }: Props) => <div>{customElement}</div>
<Component customElement={<div>hello</div>} />

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

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

@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
({
defaultOptions?: Partial<Pick<RegistryStoreOptions<LiveStoreSchema.Any, {}, Codec<Json, Json, never, never>>, "batchUpdates" | "disableDevtools" | "confirmUnsavedChanges" | "debug" | "otelOptions" | "unusedCacheTime">>

Default options that are applied to all stores when they are loaded.

defaultOptions
: {
batchUpdates?: (run: () => void) => void

Needed in React so LiveStore can apply multiple events in a single render.

@example

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

batchUpdates
,
},
}),
)
return <
const StoreRegistryProvider: ({ storeRegistry, children }: StoreRegistryProviderProps) => JSX.Element

React context provider that makes a

StoreRegistry

available to descendant components.

Wrap your application (or a subtree) with this provider to enable

useStore

and

useStoreRegistry

hooks within that tree.

@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
}>{
children: ReactNode
children
}</
const StoreRegistryProvider: ({ storeRegistry, children }: StoreRegistryProviderProps) => JSX.Element

React context provider that makes a

StoreRegistry

available to descendant components.

Wrap your application (or a subtree) with this provider to enable

useStore

and

useStoreRegistry

hooks within that tree.

@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
>
}

Use the useStore() hook to access specific workspace instances:

import {
function useCallback<T extends Function>(callback: T, deps: DependencyList): T

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

useCallback
} from 'react'
import {
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 {
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 {
import userTables
userTables
} from './user.schema.ts'
import {
import useCurrentUserStore
useCurrentUserStore
} from './user.store.ts'
import {
import workspaceEvents
workspaceEvents
,
import workspaceTables
workspaceTables
} from './workspace.schema.ts'
import {
import workspaceStoreOptions
workspaceStoreOptions
} from './workspace.store.ts'
// Component that accesses a specific workspace store
export const
const Workspace: ({ workspaceId }: {
workspaceId: string;
}) => JSX.Element
Workspace
= ({
workspaceId: string
workspaceId
}: {
workspaceId: string
workspaceId
: string }) => {
const
const userStore: any
userStore
=
import useCurrentUserStore
useCurrentUserStore
()
const
const workspaceStore: Store<LiveStoreSchema<DbSchema, EventDefRecord>, {}> & ReactApi
workspaceStore
=
useStore<LiveStoreSchema<DbSchema, EventDefRecord>, {}, Codec<Json, Json, never, never>>(options: RegistryStoreOptions<LiveStoreSchema<DbSchema, EventDefRecord>, {}, Codec<Json, Json, never, never>>): Store<LiveStoreSchema<DbSchema, EventDefRecord>, {}> & ReactApi

Returns a store instance augmented with hooks (store.useQuery() and store.useClientDocument()) for reactive queries.

@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
(
import workspaceStoreOptions
workspaceStoreOptions
(
workspaceId: string
workspaceId
))
// Check if this workspace exists in user's workspace list
const [
const knownWorkspace: any
knownWorkspace
] =
const userStore: any
userStore
.
any
useQuery
(
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 userTables
userTables
.
any
userWorkspaces
.
any
select
().
any
where
({
workspaceId: string
workspaceId
})))
// Query workspace data
const [
const workspace: any
workspace
] =
const workspaceStore: Store<LiveStoreSchema<DbSchema, EventDefRecord>, {}> & ReactApi
workspaceStore
.
useQuery: <LiveQueryDef<unknown, "def">>(queryable: LiveQueryDef<unknown, "def">, options?: {
store?: Store;
}) => unknown

Returns the result of a query and subscribes to future updates.

Example:

const App = () => {
const todos = useQuery(queryDb(tables.todos.query.where({ complete: true })))
return <div>{todos.map((todo) => <div key={todo.id}>{todo.title}</div>)}</div>
}

useQuery
(
queryDb<unknown, unknown>(queryInput: 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 workspaceTables
workspaceTables
.
any
workspace
.
any
select
().
any
limit
(1)))
const
const todos: unknown
todos
=
const workspaceStore: Store<LiveStoreSchema<DbSchema, EventDefRecord>, {}> & ReactApi
workspaceStore
.
useQuery: <LiveQueryDef<unknown, "def">>(queryable: LiveQueryDef<unknown, "def">, options?: {
store?: Store;
}) => unknown

Returns the result of a query and subscribes to future updates.

Example:

const App = () => {
const todos = useQuery(queryDb(tables.todos.query.where({ complete: true })))
return <div>{todos.map((todo) => <div key={todo.id}>{todo.title}</div>)}</div>
}

useQuery
(
queryDb<unknown, unknown>(queryInput: 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 workspaceTables
workspaceTables
.
any
todos
.
any
select
()))
// Workspace not in user's list → truly doesn't exist
if (
const knownWorkspace: any
knownWorkspace
== null) return <
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>Workspace not found</
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
// Workspace is in user's list but not yet initialized → loading state
if (
const workspace: any
workspace
== null) return <
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>Loading workspace...</
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
const
const addTodo: (text: string) => void
addTodo
=
useCallback<(text: string) => void>(callback: (text: string) => void, deps: DependencyList): (text: string) => void

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 workspaceStore: Store<LiveStoreSchema<DbSchema, EventDefRecord>, {}> & ReactApi
workspaceStore
.
Store<LiveStoreSchema<DbSchema, EventDefRecord>, {}>.commit: <readonly [any]>(list_0: any) => void (+3 overloads)
commit
(
import workspaceEvents
workspaceEvents
.
any
todoAdded
({
todoId: string
todoId
: `todo-${
var Date: DateConstructor

Enables basic storage and retrieval of dates and times.

Date
.
DateConstructor.now(): number

Returns the number of milliseconds elapsed since midnight, January 1, 1970 Universal Coordinated Time (UTC).

now
()}`,
text: string
text
,
}),
)
},
[
const workspaceStore: Store<LiveStoreSchema<DbSchema, EventDefRecord>, {}> & ReactApi
workspaceStore
],
)
const
const addNewTodo: () => void
addNewTodo
=
useCallback<() => void>(callback: () => void, deps: DependencyList): () => void

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

useCallback
(() =>
const addTodo: (text: string) => void
addTodo
('New todo'), [
const addTodo: (text: string) => void
addTodo
])
return (
<
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
<
JSX.IntrinsicElements.h2: DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>
h2
>{
const workspace: any
workspace
.
any
name
}</
JSX.IntrinsicElements.h2: DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>
h2
>
<
JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p
>Created by: {
const workspace: any
workspace
.
any
createdByUsername
}</
JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p
>
<
JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p
>Store ID: {
const workspaceStore: Store<LiveStoreSchema<DbSchema, EventDefRecord>, {}> & ReactApi
workspaceStore
.
Store<LiveStoreSchema<DbSchema, EventDefRecord>, {}>.storeId: string

Unique identifier for this Store instance, stable for its lifetime.

storeId
}</
JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p
>
<
JSX.IntrinsicElements.h3: DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>
h3
>Todos ({
const todos: unknown
todos
.
any
length
})</
JSX.IntrinsicElements.h3: DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>
h3
>
<
JSX.IntrinsicElements.ul: DetailedHTMLProps<HTMLAttributes<HTMLUListElement>, HTMLUListElement>
ul
>
{
const todos: unknown
todos
.
any
map
((
todo: any
todo
) => (
<
JSX.IntrinsicElements.li: DetailedHTMLProps<LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>
li
Attributes.key?: Key | null | undefined
key
={
todo: any
todo
.
any
todoId
}>
{
todo: any
todo
.
any
text
} {
todo: any
todo
.
any
completed
=== true ? '✓' : ''}
</
JSX.IntrinsicElements.li: DetailedHTMLProps<LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>
li
>
))}
</
JSX.IntrinsicElements.ul: DetailedHTMLProps<HTMLAttributes<HTMLUListElement>, HTMLUListElement>
ul
>
<
JSX.IntrinsicElements.button: DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button
ButtonHTMLAttributes<HTMLButtonElement>.type?: "button" | "submit" | "reset" | undefined
type
="button"
DOMAttributes<HTMLButtonElement>.onClick?: MouseEventHandler<HTMLButtonElement> | undefined
onClick
={
const addNewTodo: () => void
addNewTodo
}>
Add Todo
</
JSX.IntrinsicElements.button: DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button
>
</
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
)
}