Cloudflare Workers
The @livestore/sync-cf package provides a comprehensive LiveStore sync provider for Cloudflare Workers. It uses Durable Objects for connectivity and, by default, persists events in the Durable Object’s own SQLite. You can optionally use Cloudflare D1 instead. Multiple transports are supported to fit different deployment scenarios.
Architecture
Section titled “Architecture”Key responsibilities:
- Worker: Routes sync requests to Durable Objects by
storeId, handles auth validation - Durable Object: Manages sync state, handles push/pull operations, maintains WebSocket connections
- Storage: Persists events in DO SQLite (default) or D1 (optional)
Installation
Section titled “Installation”pnpm add @livestore/sync-cfTransport modes
Section titled “Transport modes”The sync provider supports three transport protocols, each optimized for different use cases:
WebSocket transport (Recommended)
Section titled “WebSocket transport (Recommended)”Real-time bidirectional communication with automatic reconnection and live pull support.
import { const makeWsSync: (options: WsSyncOptions) => SyncBackendConstructor<SyncMetadata>
Creates a sync backend that uses WebSocket to communicate with the sync backend.
makeWsSync } from '@livestore/sync-cf/client'
export const const syncBackend: SyncBackendConstructor<Struct.ReadonlySide<{ readonly _tag: tag<"SyncMessage.SyncMetadata">; readonly createdAt: String;}, "Type">, Json>
syncBackend = function makeWsSync(options: WsSyncOptions): SyncBackendConstructor<SyncMetadata>
Creates a sync backend that uses WebSocket to communicate with the sync backend.
makeWsSync({ WsSyncOptions.url: string
URL of the sync backend
The protocol can either http/https or ws/wss
url: 'wss://sync.example.com',})HTTP transport
Section titled “HTTP transport”HTTP-based sync with polling for live updates. Requires the enable_request_signal compatibility flag.
import { const makeHttpSync: (options: HttpSyncOptions) => SyncBackendConstructor<SyncMetadata>
Note: This implementation requires the enable_request_signal compatibility flag to properly support pull streaming responses
makeHttpSync } from '@livestore/sync-cf/client'
export const const syncBackend: SyncBackendConstructor<Struct.ReadonlySide<{ readonly _tag: tag<"SyncMessage.SyncMetadata">; readonly createdAt: String;}, "Type">, Json>
syncBackend = function makeHttpSync(options: HttpSyncOptions): SyncBackendConstructor<SyncMetadata>
Note: This implementation requires the enable_request_signal compatibility flag to properly support pull streaming responses
makeHttpSync({ HttpSyncOptions.url: string
URL of the sync backend
url: 'https://sync.example.com', HttpSyncOptions.livePull?: { pollInterval?: Input;}
livePull: { pollInterval?: Input
How often to poll for new events
pollInterval: 3000, // Poll every 3 seconds },})Durable Object RPC transport
Section titled “Durable Object RPC transport”Direct RPC communication between Durable Objects (internal use by @livestore/adapter-cloudflare).
import type { import CfTypes
CfTypes, (alias) interface SyncBackendRpcInterfaceimport SyncBackendRpcInterface
Durable Object interface supporting the DO RPC protocol for DO <> DO syncing.
SyncBackendRpcInterface } from '@livestore/sync-cf/cf-worker'import { const makeDoRpcSync: ({ syncBackendStub, durableObjectContext }: DoRpcSyncOptions) => SyncBackendConstructor<SyncMetadata>
Creates a sync backend that uses Durable Object RPC to communicate with the sync backend.
Used internally by @livestore/adapter-cf to connect to the sync backend.
makeDoRpcSync } from '@livestore/sync-cf/client'
declare const const state: CfTypes.DurableObjectState<unknown>
state: import CfTypes
CfTypes.interface DurableObjectState<Props = unknown>
DurableObjectStatedeclare const const syncBackendDurableObject: CfTypes.DurableObjectStub<SyncBackendRpcInterface>
syncBackendDurableObject: import CfTypes
CfTypes.type DurableObjectStub<T extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined> = (T extends CfTypes.Rpc.EntrypointBranded ? CfTypes.Rpc.Provider<T, "alarm" | "webSocketMessage" | "webSocketClose" | "webSocketError" | "fetch" | "connect"> : unknown) & { fetch(input: CfTypes.RequestInfo | CfTypes.URL, init?: CfTypes.RequestInit): Promise<CfTypes.Response>; connect(address: CfTypes.SocketAddress | string, options?: CfTypes.SocketOptions): CfTypes.Socket;} & { ...;}
DurableObjectStub<(alias) interface SyncBackendRpcInterfaceimport SyncBackendRpcInterface
Durable Object interface supporting the DO RPC protocol for DO <> DO syncing.
SyncBackendRpcInterface>
export const const syncBackend: SyncBackendConstructor<Struct.ReadonlySide<{ readonly _tag: tag<"SyncMessage.SyncMetadata">; readonly createdAt: String;}, "Type">, Json>
syncBackend = function makeDoRpcSync({ syncBackendStub, durableObjectContext }: DoRpcSyncOptions): SyncBackendConstructor<SyncMetadata>
Creates a sync backend that uses Durable Object RPC to communicate with the sync backend.
Used internally by @livestore/adapter-cf to connect to the sync backend.
makeDoRpcSync({ DoRpcSyncOptions.syncBackendStub: SyncBackendRpcStub
Durable Object stub that implements the SyncDoRpc interface
syncBackendStub: const syncBackendDurableObject: CfTypes.DurableObjectStub<SyncBackendRpcInterface>
syncBackendDurableObject, DoRpcSyncOptions.durableObjectContext: { bindingName: string; durableObjectId: string;}
Information about this DurableObject instance so the Sync DO instance can call back to this instance
durableObjectContext: { bindingName: string
See wrangler.toml for the binding name
bindingName: 'CLIENT_DO', durableObjectId: string
state.id.toString() in the DO
durableObjectId: const state: CfTypes.DurableObjectState<unknown>
state.DurableObjectState<unknown>.id: CfTypes.DurableObjectId
id.DurableObjectId.toString(): string
toString(), },})Client API reference
Section titled “Client API reference”makeWsSync(options)
Section titled “makeWsSync(options)”Creates a WebSocket-based sync backend client.
Options:
url- WebSocket URL (supportsws/wssorhttp/httpsprotocols)webSocketFactory?- Custom WebSocket implementationping?- Ping configuration:enabled?: boolean- Enable/disable ping (default:true)requestTimeout?: Duration- Ping timeout (default: 10 seconds)requestInterval?: Duration- Ping interval (default: 10 seconds)
Features:
- Real-time live pull
- Automatic reconnection
- Connection status tracking
- Ping/pong keep-alive
import { const makeWsSync: (options: WsSyncOptions) => SyncBackendConstructor<SyncMetadata>
Creates a sync backend that uses WebSocket to communicate with the sync backend.
makeWsSync } from '@livestore/sync-cf/client'
export const const syncBackend: SyncBackendConstructor<Struct.ReadonlySide<{ readonly _tag: tag<"SyncMessage.SyncMetadata">; readonly createdAt: String;}, "Type">, Json>
syncBackend = function makeWsSync(options: WsSyncOptions): SyncBackendConstructor<SyncMetadata>
Creates a sync backend that uses WebSocket to communicate with the sync backend.
makeWsSync({ WsSyncOptions.url: string
URL of the sync backend
The protocol can either http/https or ws/wss
url: 'wss://sync.example.com', WsSyncOptions.ping?: { enabled?: boolean; requestTimeout?: Input; requestInterval?: Input;}
ping: { enabled?: boolean
enabled: true, requestTimeout?: Input
How long to wait for a ping response before timing out
requestTimeout: 5000, requestInterval?: Input
How often to send ping requests
requestInterval: 15000, },})makeHttpSync(options)
Section titled “makeHttpSync(options)”Creates an HTTP-based sync backend client with polling for live updates.
Options:
url- HTTP endpoint URLheaders?- Additional HTTP headerslivePull?- Live pull configuration:pollInterval?: Duration- Polling interval (default: 5 seconds)
ping?- Ping configuration (same as WebSocket)
Features:
- HTTP request/response based
- Polling-based live pull
- Custom headers support
- Connection status via ping
import { const makeHttpSync: (options: HttpSyncOptions) => SyncBackendConstructor<SyncMetadata>
Note: This implementation requires the enable_request_signal compatibility flag to properly support pull streaming responses
makeHttpSync } from '@livestore/sync-cf/client'
export const const syncBackend: SyncBackendConstructor<Struct.ReadonlySide<{ readonly _tag: tag<"SyncMessage.SyncMetadata">; readonly createdAt: String;}, "Type">, Json>
syncBackend = function makeHttpSync(options: HttpSyncOptions): SyncBackendConstructor<SyncMetadata>
Note: This implementation requires the enable_request_signal compatibility flag to properly support pull streaming responses
makeHttpSync({ HttpSyncOptions.url: string
URL of the sync backend
url: 'https://sync.example.com', HttpSyncOptions.headers?: Record<string, string>
headers: { type Authorization: string
Authorization: 'Bearer token', 'X-Custom-Header': 'value', }, HttpSyncOptions.livePull?: { pollInterval?: Input;}
livePull: { pollInterval?: Input
How often to poll for new events
pollInterval: 2000, // Poll every 2 seconds },})makeDoRpcSync(options)
Section titled “makeDoRpcSync(options)”Creates a Durable Object RPC-based sync backend (for internal use).
Options:
syncBackendStub- Durable Object stub implementingSyncBackendRpcInterfacedurableObjectContext- Context for RPC callbacks:bindingName- Wrangler binding name for the client DOdurableObjectId- Client Durable Object ID
Features:
- Direct RPC communication
- Real-time live pull via callbacks
- Hibernation support
handleSyncUpdateRpc(payload)
Section titled “handleSyncUpdateRpc(payload)”Handles RPC callback for live pull updates in Durable Objects.
import { class DurableObject<Env = Cloudflare.Env, Props = {}>
DurableObject } from 'cloudflare:workers'
import { type (alias) interface ClientDoWithRpcCallbackimport ClientDoWithRpcCallback
ClientDoWithRpcCallback, const createStoreDoPromise: <TSchema extends LiveStoreSchema, TEnv, TState extends DurableObjectState = DurableObjectState<unknown>>(options: CreateStoreDoOptions<TSchema, TEnv, TState>) => Promise<Store<TSchema, {}>>
Promise-based wrapper around createStoreDo for simpler async/await usage.
Equivalent to calling createStoreDo(options).pipe(Effect.runPromise) with
logging configured automatically.
createStoreDoPromise } from '@livestore/adapter-cloudflare'import { function nanoid(size?: number): string
Generate secure URL-friendly unique ID.
By default, the ID will have 21 symbols to have a collision probability
similar to UUID v4.
import { nanoid } from 'nanoid'model.id = nanoid() //=> "Uakgb_J5m9g-0JDMbcJqL"
nanoid, type class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
Central interface to a LiveStore database providing reactive queries, event commits, and sync.
A Store instance wraps a local SQLite database that is kept in sync with other clients via
an event log. Instead of mutating state directly, you commit events that get materialized
into database rows. Queries automatically re-run when their underlying tables change.
Creating a Store
Use createStore (Effect-based) or createStorePromise to obtain a Store instance.
In React applications, use StoreRegistry with <StoreRegistryProvider> and the useStore() hook
which manages the Store lifecycle.
Querying Data
Use
Store.query
for one-shot reads or
Store.subscribe
for reactive subscriptions.
Both accept query builders (e.g. tables.todo.where({ complete: true })) or custom LiveQueryDefs.
Committing Events
Use
Store.commit
to persist events. Events are immediately materialized locally and
asynchronously synced to other clients. Multiple events can be committed atomically.
Lifecycle
The Store must be shut down when no longer needed via
Store.shutdown
or
Store.shutdownPromise
. Framework integrations (React, Effect) handle this automatically.
Store, type type Unsubscribe = () => void
Function returned by store.subscribe() to stop receiving updates.
Call this to unsubscribe from a query and release the associated resources.
Unsubscribe } from '@livestore/livestore'import { const handleSyncUpdateRpc: (payload: Uint8Array<ArrayBuffer>) => Promise<void>
import { DurableObject } from 'cloudflare:workers'import { ClientDoWithRpcCallback } from '@livestore/common-cf'
export class MyDurableObject extends DurableObject implements ClientDoWithRpcCallback { // ...
async syncUpdateRpc(payload: Uint8Array<ArrayBuffer>) { return handleSyncUpdateRpc(payload) }}
handleSyncUpdateRpc } from '@livestore/sync-cf/client'
import type { import Env
Env } from './env.ts'import { import schema
schema, import tables
tables } from './schema.ts'import { import storeIdFromRequest
storeIdFromRequest } from './shared.ts'
type type AlarmInfo = { isRetry: boolean; retryCount: number;}
AlarmInfo = { isRetry: boolean
isRetry: boolean retryCount: number
retryCount: number}
export class class LiveStoreClientDO
LiveStoreClientDO extends class DurableObject<Env = Cloudflare.Env, Props = {}>
DurableObject<import Env
Env> implements (alias) interface ClientDoWithRpcCallbackimport ClientDoWithRpcCallback
ClientDoWithRpcCallback { override LiveStoreClientDO.__DURABLE_OBJECT_BRAND: never
__DURABLE_OBJECT_BRAND: never = var undefined
undefined as never
private LiveStoreClientDO.storeId: string | undefined
storeId: string | undefined private LiveStoreClientDO.cachedStore: Store<any, {}> | undefined
cachedStore: class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
Central interface to a LiveStore database providing reactive queries, event commits, and sync.
A Store instance wraps a local SQLite database that is kept in sync with other clients via
an event log. Instead of mutating state directly, you commit events that get materialized
into database rows. Queries automatically re-run when their underlying tables change.
Creating a Store
Use createStore (Effect-based) or createStorePromise to obtain a Store instance.
In React applications, use StoreRegistry with <StoreRegistryProvider> and the useStore() hook
which manages the Store lifecycle.
Querying Data
Use
Store.query
for one-shot reads or
Store.subscribe
for reactive subscriptions.
Both accept query builders (e.g. tables.todo.where({ complete: true })) or custom LiveQueryDefs.
Committing Events
Use
Store.commit
to persist events. Events are immediately materialized locally and
asynchronously synced to other clients. Multiple events can be committed atomically.
Lifecycle
The Store must be shut down when no longer needed via
Store.shutdown
or
Store.shutdownPromise
. Framework integrations (React, Effect) handle this automatically.
Store<typeof import schema
schema> | undefined private LiveStoreClientDO.storeSubscription: Unsubscribe | undefined
storeSubscription: type Unsubscribe = () => void
Function returned by store.subscribe() to stop receiving updates.
Call this to unsubscribe from a query and release the associated resources.
Unsubscribe | undefined private readonly LiveStoreClientDO.todosQuery: any
todosQuery = import tables
tables.any
todos.any
select()
override async LiveStoreClientDO.fetch(request: Request): Promise<Response>
fetch(request: Request<unknown, CfProperties<unknown>>
request: interface Request<CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>>
The Request interface of the Fetch API represents a resource request.
Request): interface Promise<T>
Represents the completion of an asynchronous operation
Promise<interface Response
The Response interface of the Fetch API represents the response to a request.
Response> { // @ts-expect-error TODO remove casts once CF types are fixed in https://github.com/cloudflare/workerd/issues/4811 this.LiveStoreClientDO.storeId: string | undefined
storeId = import storeIdFromRequest
storeIdFromRequest(request: Request<unknown, CfProperties<unknown>>
request)
const const store: Store<any, {}>
store = await this.LiveStoreClientDO.getStore(): Promise<Store<any, {}>>
getStore() await this.LiveStoreClientDO.subscribeToStore(): Promise<void>
subscribeToStore()
const const todos: unknown
todos = const store: Store<any, {}>
store.Store<any, {}>.query: <unknown>(query: Queryable<unknown> | { query: string; bindValues: Bindable; schema?: Decoder<unknown, never>;}, options?: { otelContext?: Context; debugRefreshReason?: RefreshReason;}) => unknown
Synchronously queries the database without creating a LiveQuery.
This is useful for queries that don't need to be reactive.
Example: Query builder
const completedTodos = store.query(tables.todo.where({ complete: true }))
Example: Raw SQL query
const completedTodos = store.query({ query: 'SELECT * FROM todo WHERE complete = 1', bindValues: {} })
query(this.LiveStoreClientDO.todosQuery: any
todosQuery) return new var Response: new (body?: BodyInit | null, init?: ResponseInit) => Response
The Response interface of the Fetch API represents the response to a request.
Response(var JSON: JSON
An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
JSON.JSON.stringify(value: any, replacer?: (number | string)[] | null, space?: string | number): string (+1 overload)
Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
stringify(const todos: unknown
todos, null, 2), { ResponseInit.headers?: HeadersInit
headers: { 'Content-Type': 'application/json' }, }) }
private async LiveStoreClientDO.getStore(): Promise<Store<any, {}>>
getStore() { if (this.LiveStoreClientDO.cachedStore: Store<any, {}> | undefined
cachedStore !== var undefined
undefined) { return this.LiveStoreClientDO.cachedStore: Store<any, {}>
cachedStore }
const const storeId: string
storeId = this.LiveStoreClientDO.storeId: string | undefined
storeId ?? function nanoid(size?: number): string
Generate secure URL-friendly unique ID.
By default, the ID will have 21 symbols to have a collision probability
similar to UUID v4.
import { nanoid } from 'nanoid'model.id = nanoid() //=> "Uakgb_J5m9g-0JDMbcJqL"
nanoid()
const const store: Store<any, {}>
store = await createStoreDoPromise<any, Env, DurableObjectState<unknown>>(options: CreateStoreDoOptions<any, Env, DurableObjectState<unknown>>): Promise<Store<any, {}>>
Promise-based wrapper around createStoreDo for simpler async/await usage.
Equivalent to calling createStoreDo(options).pipe(Effect.runPromise) with
logging configured automatically.
createStoreDoPromise({ schema: any
LiveStore schema that defines state, migrations, and validators.
schema, storeId: string
Logical identifier for the store instance persisted inside the Durable Object.
storeId, clientId: string
Unique identifier for the client that owns the Durable Object instance.
clientId: 'client-do', sessionId: string
Identifier for the LiveStore session running inside the Durable Object.
sessionId: function nanoid(size?: number): string
Generate secure URL-friendly unique ID.
By default, the ID will have 21 symbols to have a collision probability
similar to UUID v4.
import { nanoid } from 'nanoid'model.id = nanoid() //=> "Uakgb_J5m9g-0JDMbcJqL"
nanoid(), durableObject: { ctx: DurableObjectState<unknown>; env: Env; bindingName: any;}
Runtime details about the Durable Object this store runs inside. Needed for sync backend to call back to this instance.
durableObject: { // @ts-expect-error TODO remove once CF types are fixed in https://github.com/cloudflare/workerd/issues/4811 ctx: DurableObjectState<unknown>
Durable Object state handle (e.g. this.ctx).
ctx: this.CloudflareWorkersModule.DurableObject<Env, {}>.ctx: DurableObjectState<{}>
ctx, env: Env
Environment bindings associated with the Durable Object.
env: this.CloudflareWorkersModule.DurableObject<Env, {}>.env: Env
env, bindingName: any
Binding name Cloudflare uses to reach this Durable Object from other workers.
bindingName: 'CLIENT_DO', }, syncBackendStub: DurableObjectStub<SyncBackendRpcInterface>
RPC stub pointing at the sync backend Durable Object used for replication.
syncBackendStub: this.CloudflareWorkersModule.DurableObject<Env, {}>.env: Env
env.any
SYNC_BACKEND_DO.any
get(this.CloudflareWorkersModule.DurableObject<Env, {}>.env: Env
env.any
SYNC_BACKEND_DO.any
idFromName(const storeId: string
storeId)), livePull?: boolean
Enables live pull mode to receive sync updates via Durable Object RPC callbacks.
livePull: true, })
this.LiveStoreClientDO.cachedStore: Store<any, {}> | undefined
cachedStore = const store: Store<any, {}>
store return const store: Store<any, {}>
store }
private async LiveStoreClientDO.subscribeToStore(): Promise<void>
subscribeToStore() { const const store: Store<any, {}>
store = await this.LiveStoreClientDO.getStore(): Promise<Store<any, {}>>
getStore()
if (this.LiveStoreClientDO.storeSubscription: Unsubscribe | undefined
storeSubscription === var undefined
undefined) { this.LiveStoreClientDO.storeSubscription: Unsubscribe | undefined
storeSubscription = const store: Store<any, {}>
store.Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>.subscribe: <readonly any[]>(query: Queryable<readonly any[]>, onUpdate: (value: readonly any[]) => void, options?: SubscribeOptions<readonly any[]> | undefined) => Unsubscribe (+1 overload)
subscribe(this.LiveStoreClientDO.todosQuery: any
todosQuery, (todos: readonly any[]
todos: interface ReadonlyArray<T>
ReadonlyArray<typeof import tables
tables.any
todos.any
Type>) => { var console: Console
console.Console.log(...data: any[]): void (+3 overloads)
The console.log() static method outputs a message to the console.
log(`todos for store (${this.LiveStoreClientDO.storeId: string | undefined
storeId})`, todos: readonly any[]
todos) }) }
await this.CloudflareWorkersModule.DurableObject<Env, {}>.ctx: DurableObjectState<{}>
ctx.DurableObjectState<{}>.storage: DurableObjectStorage
storage.DurableObjectStorage.setAlarm(scheduledTime: number | Date, options?: DurableObjectSetAlarmOptions): Promise<void>
setAlarm(var Date: DateConstructor
Enables basic storage and retrieval of dates and times.
Date.DateConstructor.now(): number
Returns the number of milliseconds elapsed since midnight, January 1, 1970 Universal Coordinated Time (UTC).
now() + 1000) }
override LiveStoreClientDO.alarm(_alarmInfo?: AlarmInfo): void | Promise<void>
alarm(_alarmInfo: AlarmInfo | undefined
_alarmInfo?: type AlarmInfo = { isRetry: boolean; retryCount: number;}
AlarmInfo): void | interface Promise<T>
Represents the completion of an asynchronous operation
Promise<void> { return this.LiveStoreClientDO.subscribeToStore(): Promise<void>
subscribeToStore() }
async LiveStoreClientDO.syncUpdateRpc(payload: Uint8Array<ArrayBuffer>): Promise<void>
syncUpdateRpc(payload: Uint8Array<ArrayBuffer>
payload: interface Uint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the
requested number of bytes could not be allocated an exception is raised.
Uint8Array<interface ArrayBuffer
Represents a raw buffer of binary data, which is used to store data for the
different typed arrays. ArrayBuffers cannot be read from or written to directly,
but can be passed to a typed array or DataView Object to interpret the raw
buffer as needed.
ArrayBuffer>) { await function handleSyncUpdateRpc(payload: Uint8Array<ArrayBuffer>): Promise<void>
import { DurableObject } from 'cloudflare:workers'import { ClientDoWithRpcCallback } from '@livestore/common-cf'
export class MyDurableObject extends DurableObject implements ClientDoWithRpcCallback { // ...
async syncUpdateRpc(payload: Uint8Array<ArrayBuffer>) { return handleSyncUpdateRpc(payload) }}
handleSyncUpdateRpc(payload: Uint8Array<ArrayBuffer>
payload) }}import type { (alias) interface ClientDoWithRpcCallbackimport ClientDoWithRpcCallback
ClientDoWithRpcCallback } from '@livestore/adapter-cloudflare'import type { import CfTypes
CfTypes, (alias) interface SyncBackendRpcInterfaceimport SyncBackendRpcInterface
Durable Object interface supporting the DO RPC protocol for DO <> DO syncing.
SyncBackendRpcInterface } from '@livestore/sync-cf/cf-worker'
export type type Env = { CLIENT_DO: CfTypes.DurableObjectNamespace<ClientDoWithRpcCallback>; SYNC_BACKEND_DO: CfTypes.DurableObjectNamespace<SyncBackendRpcInterface>; DB: CfTypes.D1Database;}
Env = { type CLIENT_DO: CfTypes.DurableObjectNamespace<ClientDoWithRpcCallback>
CLIENT_DO: import CfTypes
CfTypes.class DurableObjectNamespace<T extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined>
DurableObjectNamespace<(alias) interface ClientDoWithRpcCallbackimport ClientDoWithRpcCallback
ClientDoWithRpcCallback> type SYNC_BACKEND_DO: CfTypes.DurableObjectNamespace<SyncBackendRpcInterface>
SYNC_BACKEND_DO: import CfTypes
CfTypes.class DurableObjectNamespace<T extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined>
DurableObjectNamespace<(alias) interface SyncBackendRpcInterfaceimport SyncBackendRpcInterface
Durable Object interface supporting the DO RPC protocol for DO <> DO syncing.
SyncBackendRpcInterface> type DB: CfTypes.D1Database
DB: import CfTypes
CfTypes.class D1Database
D1Database}import { import Events
Events, const makeSchema: <TInputSchema extends InputSchema>(inputSchema: TInputSchema) => FromInputSchema.DeriveSchema<TInputSchema>
makeSchema, import Schema
Schema, import State
State } from '@livestore/livestore'
export const const tables: { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables = { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos: import State
State.import SQLite
SQLite.function table<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; };}, Partial<...>>(args: { ...;} & Partial<...>): State.SQLite.TableDef<...> (+2 overloads)
Creates a SQLite table definition from columns or an Effect Schema.
This function supports two main ways to define a table:
- Using explicit column definitions
- Using an Effect Schema (either the
name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columnsconst usersTable = State.SQLite.table({ name: 'users', columns: { id: State.SQLite.text({ primaryKey: true }), name: State.SQLite.text({ nullable: false }), email: State.SQLite.text({ nullable: false }), age: State.SQLite.integer({ nullable: true }), },})
// Using Effect Schema with annotationsimport { Schema } from '@livestore/utils/effect'
const UserSchema = Schema.Struct({ id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement), email: Schema.String.pipe(State.SQLite.withUnique), name: Schema.String, active: Schema.Boolean.pipe(State.SQLite.withDefault(true)), createdAt: Schema.optional(Schema.Date),})
// Option 1: With explicit nameconst usersTable = State.SQLite.table({ name: 'users', schema: UserSchema,})
// Option 2: With name from schema annotation (title or identifier)const AnnotatedUserSchema = UserSchema.annotate({ title: 'users' })const usersTable2 = State.SQLite.table({ schema: AnnotatedUserSchema,})
// Adding indexesconst PostSchema = Schema.Struct({ id: Schema.String.pipe(State.SQLite.withPrimaryKey), title: Schema.String, authorId: Schema.String, createdAt: Schema.Date,}).annotate({ identifier: 'posts' })
const postsTable = State.SQLite.table({ schema: PostSchema, indexes: [ { name: 'idx_posts_author', columns: ['authorId'] }, { name: 'idx_posts_created', columns: ['createdAt'], isUnique: false }, ],})
table({ name: "todos"
name: 'todos', columns: { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; };}
columns: { id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;}
id: import State
State.import SQLite
SQLite.const text: <string, string, false, typeof NoDefault, true, false>(args: { schema?: Schema.Codec<string, string, never, never>; default?: typeof NoDefault; nullable?: false; primaryKey?: true; autoIncrement?: false;}) => { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;} (+1 overload)
text({ primaryKey?: true
primaryKey: true }), text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false;}
text: import State
State.import SQLite
SQLite.const text: <string, string, false, "", false, false>(args: { schema?: Schema.Codec<string, string, never, never>; default?: ""; nullable?: false; primaryKey?: false; autoIncrement?: false;}) => { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false;} (+1 overload)
text({ default?: ""
default: '' }), completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false;}
completed: import State
State.import SQLite
SQLite.const boolean: <boolean, false, false, false, false>(args: { default?: false; nullable?: false; primaryKey?: false; autoIncrement?: false;}) => { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false;} (+1 overload)
boolean({ default?: false
default: false }), deletedAt: { columnType: "integer"; schema: Schema.Codec<Date | null, number | null, never, never>; default: None<never>; nullable: true; primaryKey: false; autoIncrement: false;}
deletedAt: import State
State.import SQLite
SQLite.const integer: <number, Date, true, typeof NoDefault, false, false>(args: { schema?: Schema.Codec<Date, number, never, never>; default?: typeof NoDefault; nullable?: true; primaryKey?: false; autoIncrement?: false;}) => { columnType: "integer"; schema: Schema.Codec<Date | null, number | null, never, never>; default: None<never>; nullable: true; primaryKey: false; autoIncrement: false;} (+1 overload)
integer({ nullable?: true
nullable: true, schema?: Schema.Codec<Date, number, never, never>
schema: import Schema
Schema.const DateFromMillis: Schema.DateFromMillis
Type-level representation of
DateFromMillis
.
Schema that decodes epoch milliseconds into a JavaScript Date.
When to use
Use to model numeric millisecond timestamps that decode to JavaScript Date
objects and encode back to numbers.
Details
Decoding:
A number of milliseconds since the Unix epoch is decoded as a Date.
Encoding:
A Date is encoded as its millisecond timestamp.
Gotchas
This schema accepts any number, including NaN, Infinity, and -Infinity.
Those values decode to invalid Date instances.
DateFromMillis }), }, }),}
export const const events: { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>; todoUncompleted: State.SQLite.EventDef<...>; todoDeleted: State.SQLite.EventDef<...>; todoClearedCompleted: State.SQLite.EventDef<...>;}
events = { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>
todoCreated: import Events
Events.synced<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>(args: { name: "v1.TodoCreated"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">, never, never>;} & Omit<...>): State.SQLite.EventDef<...>export synced
Creates a synced event definition.
Synced events are sent to the sync backend and distributed to all connected
clients. Use this for collaborative data that should be shared across users
and devices.
Event names should be versioned (e.g., v1.TodoCreated) to support
schema evolution over time.
synced({ name: "v1.TodoCreated"
name: 'v1.TodoCreated', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String; readonly text: Schema.String;}>(fields: { readonly id: Schema.String; readonly text: Schema.String;}): Schema.Struct<{ readonly id: Schema.String; readonly text: Schema.String;}>
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Example (Defining a basic struct)
import { Schema } from "effect"
const Person = Schema.Struct({ name: Schema.String, age: Schema.Number, email: Schema.optionalKey(Schema.String)})
// { readonly name: string; readonly age: number; readonly email?: string }type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })console.log(alice)// { name: 'Alice', age: 30 }
Struct({ id: Schema.String
id: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String, text: Schema.String
text: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String }), }), todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Encoded">>
todoCompleted: import Events
Events.synced<"v1.TodoCompleted", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Encoded">>(args: { name: "v1.TodoCompleted"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">, never, never>;} & Omit<State.SQLite.DefineEventOptions<Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Type">, false>, "derived" | "clientOnly">): State.SQLite.EventDef<...>export synced
Creates a synced event definition.
Synced events are sent to the sync backend and distributed to all connected
clients. Use this for collaborative data that should be shared across users
and devices.
Event names should be versioned (e.g., v1.TodoCreated) to support
schema evolution over time.
synced({ name: "v1.TodoCompleted"
name: 'v1.TodoCompleted', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String;}>(fields: { readonly id: Schema.String;}): Schema.Struct<{ readonly id: Schema.String;}>
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Example (Defining a basic struct)
import { Schema } from "effect"
const Person = Schema.Struct({ name: Schema.String, age: Schema.Number, email: Schema.optionalKey(Schema.String)})
// { readonly name: string; readonly age: number; readonly email?: string }type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })console.log(alice)// { name: 'Alice', age: 30 }
Struct({ id: Schema.String
id: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String }), }), todoUncompleted: State.SQLite.EventDef<"v1.TodoUncompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Encoded">>
todoUncompleted: import Events
Events.synced<"v1.TodoUncompleted", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Encoded">>(args: { name: "v1.TodoUncompleted"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">, never, never>;} & Omit<State.SQLite.DefineEventOptions<Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Type">, false>, "derived" | "clientOnly">): State.SQLite.EventDef<...>export synced
Creates a synced event definition.
Synced events are sent to the sync backend and distributed to all connected
clients. Use this for collaborative data that should be shared across users
and devices.
Event names should be versioned (e.g., v1.TodoCreated) to support
schema evolution over time.
synced({ name: "v1.TodoUncompleted"
name: 'v1.TodoUncompleted', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String;}>(fields: { readonly id: Schema.String;}): Schema.Struct<{ readonly id: Schema.String;}>
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Example (Defining a basic struct)
import { Schema } from "effect"
const Person = Schema.Struct({ name: Schema.String, age: Schema.Number, email: Schema.optionalKey(Schema.String)})
// { readonly name: string; readonly age: number; readonly email?: string }type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })console.log(alice)// { name: 'Alice', age: 30 }
Struct({ id: Schema.String
id: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String }), }), todoDeleted: State.SQLite.EventDef<"v1.TodoDeleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly deletedAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly deletedAt: Schema.DateFromString;}, "Encoded">>
todoDeleted: import Events
Events.synced<"v1.TodoDeleted", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly deletedAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly deletedAt: Schema.DateFromString;}, "Encoded">>(args: { name: "v1.TodoDeleted"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly deletedAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly deletedAt: Schema.DateFromString; }, "Encoded">, never, never>;} & Omit<...>): State.SQLite.EventDef<...>export synced
Creates a synced event definition.
Synced events are sent to the sync backend and distributed to all connected
clients. Use this for collaborative data that should be shared across users
and devices.
Event names should be versioned (e.g., v1.TodoCreated) to support
schema evolution over time.
synced({ name: "v1.TodoDeleted"
name: 'v1.TodoDeleted', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly deletedAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly deletedAt: Schema.DateFromString;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String; readonly deletedAt: Schema.DateFromString;}>(fields: { readonly id: Schema.String; readonly deletedAt: Schema.DateFromString;}): Schema.Struct<{ readonly id: Schema.String; readonly deletedAt: Schema.DateFromString;}>
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Example (Defining a basic struct)
import { Schema } from "effect"
const Person = Schema.Struct({ name: Schema.String, age: Schema.Number, email: Schema.optionalKey(Schema.String)})
// { readonly name: string; readonly age: number; readonly email?: string }type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })console.log(alice)// { name: 'Alice', age: 30 }
Struct({ id: Schema.String
id: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String, 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.
DateFromString.Bottom<unknown, unknown, unknown, unknown, Declaration, decodeTo<Date, String, never, never>, unknown, unknown, readonly [], unknown, "readonly", "required", "no-default", "readonly", "required">.check(checks_0: Check<Date>, ...checks: Check<Date>[]): Schema.DateFromString
check(import Schema
Schema.function isDateValid(annotations?: Schema.Annotations.Filter): Filter<globalThis.Date>
Validates that a Date object represents a valid date (not an invalid date
like new Date("invalid")).
Details
JSON Schema:
This check does not have a direct JSON Schema equivalent, as JSON Schema
validates date strings, not Date objects.
Arbitrary:
When generating test data with fast-check, this applies a valid: true
constraint to ensure generated Date objects are valid.
isDateValid()), }), }), todoClearedCompleted: State.SQLite.EventDef<"v1.TodoClearedCompleted", Schema.Struct.ReadonlySide<{ readonly deletedAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly deletedAt: Schema.DateFromString;}, "Encoded">>
todoClearedCompleted: import Events
Events.synced<"v1.TodoClearedCompleted", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly deletedAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly deletedAt: Schema.DateFromString;}, "Encoded">>(args: { name: "v1.TodoClearedCompleted"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly deletedAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly deletedAt: Schema.DateFromString; }, "Encoded">, never, never>;} & Omit<...>): State.SQLite.EventDef<...>export synced
Creates a synced event definition.
Synced events are sent to the sync backend and distributed to all connected
clients. Use this for collaborative data that should be shared across users
and devices.
Event names should be versioned (e.g., v1.TodoCreated) to support
schema evolution over time.
synced({ name: "v1.TodoClearedCompleted"
name: 'v1.TodoClearedCompleted', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly deletedAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly deletedAt: Schema.DateFromString;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly deletedAt: Schema.DateFromString;}>(fields: { readonly deletedAt: Schema.DateFromString;}): Schema.Struct<{ readonly deletedAt: Schema.DateFromString;}>
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Example (Defining a basic struct)
import { Schema } from "effect"
const Person = Schema.Struct({ name: Schema.String, age: Schema.Number, email: Schema.optionalKey(Schema.String)})
// { readonly name: string; readonly age: number; readonly email?: string }type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })console.log(alice)// { name: 'Alice', age: 30 }
Struct({ deletedAt: Schema.DateFromString
deletedAt: import Schema
Schema.const DateFromString: Schema.DateFromString
Type-level representation of
DateFromString
.
Schema that decodes a string into a JavaScript Date.
When to use
Use to model string-encoded dates that decode to JavaScript Date objects
and encode back to strings.
Details
Decoding:
The string is passed to JavaScript Date construction.
Encoding:
A valid Date is encoded as an ISO string; an invalid Date is encoded as
"Invalid Date".
Gotchas
Invalid date strings can decode to invalid Date instances.
DateFromString.Bottom<unknown, unknown, unknown, unknown, Declaration, decodeTo<Date, String, never, never>, unknown, unknown, readonly [], unknown, "readonly", "required", "no-default", "readonly", "required">.check(checks_0: Check<Date>, ...checks: Check<Date>[]): Schema.DateFromString
check(import Schema
Schema.function isDateValid(annotations?: Schema.Annotations.Filter): Filter<globalThis.Date>
Validates that a Date object represents a valid date (not an invalid date
like new Date("invalid")).
Details
JSON Schema:
This check does not have a direct JSON Schema equivalent, as JSON Schema
validates date strings, not Date objects.
Arbitrary:
When generating test data with fast-check, this applies a valid: true
constraint to ensure generated Date objects are valid.
isDateValid()) }), }),}
const const materializers: { "v1.TodoCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>>; "v1.TodoCompleted": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>>; "v1.TodoUncompleted": State.SQLite.Materializer<...>; "v1.TodoDeleted": State.SQLite.Materializer<...>; "v1.TodoClearedCompleted": State.SQLite.Materializer<...>;}
materializers = import State
State.import SQLite
SQLite.const materializers: <{ todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>; todoUncompleted: State.SQLite.EventDef<...>; todoDeleted: State.SQLite.EventDef<...>; todoClearedCompleted: State.SQLite.EventDef<...>;}>(_eventDefRecord: { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>; todoUncompleted: State.SQLite.EventDef<...>; todoDeleted: State.SQLite.EventDef<...>; todoClearedCompleted: State.SQLite.EventDef<...>;}, handlers: { ...;}) => { ...;}
Builder function for creating a type-safe materializer map.
This is the primary way to define materializers in LiveStore. It ensures:
- Every non-derived event has a corresponding materializer
- Materializer argument types match their event schemas
- Derived events are excluded from the required handlers
materializers(const events: { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>; todoUncompleted: State.SQLite.EventDef<...>; todoDeleted: State.SQLite.EventDef<...>; todoClearedCompleted: State.SQLite.EventDef<...>;}
events, { 'v1.TodoCreated': ({ id: string
id, text: string
text }) => const tables: { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos.insert: (values: { readonly id: string; readonly text?: string; readonly completed?: boolean; readonly deletedAt?: Date | null;}) => QueryBuilder<readonly Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>; readonly deletedAt: Schema.Codec<Date | null, number | null, never, never>;}, "Type">[], State.SQLite.TableDefBase<State.SQLite.SqliteTableDefForInput<"todos", { ...;}>, State.SQLite.WithDefaults<...>>, "select" | ... 6 more ... | "row">
Insert a new row into the table.
insert({ id: string
id, text?: string
text, completed?: boolean
completed: false }), 'v1.TodoCompleted': ({ id: string
id }) => const tables: { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos.update: (values: Partial<Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>; readonly deletedAt: Schema.Codec<Date | null, number | null, never, never>;}, "Type">>) => QueryBuilder<readonly Schema.Struct.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>; readonly deletedAt: Schema.Codec<Date | null, number | null, never, never>;}, "Type">[], State.SQLite.TableDefBase<...>, "select" | ... 6 more ... | "row">
Update rows in the table that match the where clause
Example:
db.todos.update({ status: 'completed' }).where({ id: '123' })
update({ completed?: boolean
completed: true }).where: (params: Partial<{ readonly id: string | { op: Exclude<QueryBuilder<TResult, TTableDef extends State.SQLite.TableDefBase<any, any>, TWithout extends QueryBuilder.ApiFeature = never>.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[]; } | undefined; readonly text: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { ...; } | undefined; readonly completed: boolean | ... 2 more ... | undefined; readonly deletedAt: Date | ... 3 more ... | undefined;}>) => QueryBuilder<...> (+3 overloads)
where({ id?: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string;} | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[];} | undefined
id }), 'v1.TodoUncompleted': ({ id: string
id }) => const tables: { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos.update: (values: Partial<Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>; readonly deletedAt: Schema.Codec<Date | null, number | null, never, never>;}, "Type">>) => QueryBuilder<readonly Schema.Struct.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>; readonly deletedAt: Schema.Codec<Date | null, number | null, never, never>;}, "Type">[], State.SQLite.TableDefBase<...>, "select" | ... 6 more ... | "row">
Update rows in the table that match the where clause
Example:
db.todos.update({ status: 'completed' }).where({ id: '123' })
update({ completed?: boolean
completed: false }).where: (params: Partial<{ readonly id: string | { op: Exclude<QueryBuilder<TResult, TTableDef extends State.SQLite.TableDefBase<any, any>, TWithout extends QueryBuilder.ApiFeature = never>.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[]; } | undefined; readonly text: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { ...; } | undefined; readonly completed: boolean | ... 2 more ... | undefined; readonly deletedAt: Date | ... 3 more ... | undefined;}>) => QueryBuilder<...> (+3 overloads)
where({ id?: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string;} | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[];} | undefined
id }), 'v1.TodoDeleted': ({ id: string
id, deletedAt: Date
deletedAt }) => const tables: { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos.update: (values: Partial<Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>; readonly deletedAt: Schema.Codec<Date | null, number | null, never, never>;}, "Type">>) => QueryBuilder<readonly Schema.Struct.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>; readonly deletedAt: Schema.Codec<Date | null, number | null, never, never>;}, "Type">[], State.SQLite.TableDefBase<...>, "select" | ... 6 more ... | "row">
Update rows in the table that match the where clause
Example:
db.todos.update({ status: 'completed' }).where({ id: '123' })
update({ deletedAt?: Date | null
deletedAt }).where: (params: Partial<{ readonly id: string | { op: Exclude<QueryBuilder<TResult, TTableDef extends State.SQLite.TableDefBase<any, any>, TWithout extends QueryBuilder.ApiFeature = never>.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[]; } | undefined; readonly text: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { ...; } | undefined; readonly completed: boolean | ... 2 more ... | undefined; readonly deletedAt: Date | ... 3 more ... | undefined;}>) => QueryBuilder<...> (+3 overloads)
where({ id?: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string;} | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[];} | undefined
id }), 'v1.TodoClearedCompleted': ({ deletedAt: Date
deletedAt }) => const tables: { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos.update: (values: Partial<Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>; readonly deletedAt: Schema.Codec<Date | null, number | null, never, never>;}, "Type">>) => QueryBuilder<readonly Schema.Struct.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>; readonly deletedAt: Schema.Codec<Date | null, number | null, never, never>;}, "Type">[], State.SQLite.TableDefBase<...>, "select" | ... 6 more ... | "row">
Update rows in the table that match the where clause
Example:
db.todos.update({ status: 'completed' }).where({ id: '123' })
update({ deletedAt?: Date | null
deletedAt }).where: (params: Partial<{ readonly id: string | { op: Exclude<QueryBuilder<TResult, TTableDef extends State.SQLite.TableDefBase<any, any>, TWithout extends QueryBuilder.ApiFeature = never>.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[]; } | undefined; readonly text: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { ...; } | undefined; readonly completed: boolean | ... 2 more ... | undefined; readonly deletedAt: Date | ... 3 more ... | undefined;}>) => QueryBuilder<...> (+3 overloads)
where({ completed?: boolean | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: boolean;} | { op: QueryBuilder.WhereOps.MultiValue; value: readonly boolean[];} | undefined
completed: true }),})
const const state: InternalState
state = import State
State.import SQLite
SQLite.const makeState: <{ tables: { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; }; materializers: { ...; };}>(inputSchema: { tables: { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; }; materializers: { ...; };}) => InternalState
makeState({ tables: { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables, materializers: { "v1.TodoCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>>; "v1.TodoCompleted": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>>; "v1.TodoUncompleted": State.SQLite.Materializer<...>; "v1.TodoDeleted": State.SQLite.Materializer<...>; "v1.TodoClearedCompleted": State.SQLite.Materializer<...>;}
materializers })
export const const schema: FromInputSchema.DeriveSchema<{ events: { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>; todoUncompleted: State.SQLite.EventDef<...>; todoDeleted: State.SQLite.EventDef<...>; todoClearedCompleted: State.SQLite.EventDef<...>; }; state: InternalState;}>
schema = makeSchema<{ events: { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>; todoUncompleted: State.SQLite.EventDef<...>; todoDeleted: State.SQLite.EventDef<...>; todoClearedCompleted: State.SQLite.EventDef<...>; }; state: InternalState;}>(inputSchema: { events: { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>; todoUncompleted: State.SQLite.EventDef<...>; todoDeleted: State.SQLite.EventDef<...>; todoClearedCompleted: State.SQLite.EventDef<...>; }; state: InternalState;}): FromInputSchema.DeriveSchema<...>
makeSchema({ events: { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>; todoUncompleted: State.SQLite.EventDef<...>; todoDeleted: State.SQLite.EventDef<...>; todoClearedCompleted: State.SQLite.EventDef<...>;}
events, state: InternalState
state })import type { import CfTypes
CfTypes } from '@livestore/sync-cf/cf-worker'
export const const storeIdFromRequest: (request: CfTypes.Request) => string
storeIdFromRequest = (request: CfTypes.Request<unknown, CfTypes.CfProperties<unknown>>
request: import CfTypes
CfTypes.interface Request<CfHostMetadata = unknown, Cf = CfTypes.CfProperties<CfHostMetadata>>
The Request interface of the Fetch API represents a resource request.
Request) => { const const url: URL
url = new var URL: new (url: string | URL, base?: string | URL) => URL
The URL interface is used to parse, construct, normalize, and encode URLs. It works by providing properties which allow you to easily read and modify the components of a URL.
URL(request: CfTypes.Request<unknown, CfTypes.CfProperties<unknown>>
request.Request<unknown, CfProperties<unknown>>.url: string
The url read-only property of the Request interface contains the URL of the request.
url) const const storeId: string | null
storeId = const url: URL
url.URL.searchParams: URLSearchParams
The searchParams read-only property of the URL interface returns a URLSearchParams object allowing access to the GET decoded query arguments contained in the URL.
searchParams.URLSearchParams.get(name: string): string | null (+1 overload)
The get() method of the URLSearchParams interface returns the first value associated to the given search parameter.
get('storeId')
if (const storeId: string | null
storeId === null) { throw new var Error: ErrorConstructornew (message?: string, options?: ErrorOptions) => Error (+2 overloads)
Error('storeId is required in URL search params') }
return const storeId: string
storeId}Server API reference
Section titled “Server API reference”makeDurableObject(options)
Section titled “makeDurableObject(options)”Creates a sync backend Durable Object class.
Options:
onPush?- Callback for push events:(message, context) => void | Promise<void>onPushRes?- Callback for push responses:(message) => void | Promise<void>onPull?- Callback for pull requests:(message, context) => void | Promise<void>onPullRes?- Callback for pull responses:(message) => void | Promise<void>storage?- Storage engine:{ _tag: 'do-sqlite' } | { _tag: 'd1', binding: string }(default:do-sqlite)enabledTransports?- Set of enabled transports:Set<'http' | 'ws' | 'do-rpc'>otel?- OpenTelemetry configuration:baseUrl?- OTEL endpoint URLserviceName?- Service name for traces
import { const makeDurableObject: MakeDurableObjectClass
Creates a Durable Object class for handling WebSocket-based sync.
A sync Durable Object is uniquely scoped to a specific storeId.
The sync DO supports 3 transport modes:
- HTTP JSON-RPC
- WebSocket
- Durable Object RPC calls (only works in combination with
@livestore/adapter-cf)
Example:
// In your Cloudflare Worker fileimport { makeDurableObject } from '@livestore/sync-cf/cf-worker'
export class SyncBackendDO extends makeDurableObject({ onPush: async (message) => { console.log('onPush', message.batch) }, onPull: async (message) => { console.log('onPull', message) },}) {}
wrangler.toml
[[durable_objects.bindings]]name = "SYNC_BACKEND_DO"class_name = "SyncBackendDO"
[[migrations]]tag = "v1"new_sqlite_classes = ["SyncBackendDO"]
makeDurableObject } from '@livestore/sync-cf/cf-worker'
const const hasUserId: (p: unknown) => p is { userId: string;}
hasUserId = (p: unknown
p: unknown): p: unknown
p is { userId: string
userId: string } => typeof p: unknown
p === 'object' && p: object | null
p !== var undefined
undefined && p: object | null
p !== null && 'userId' in p: object
p
export class class SyncBackendDO
SyncBackendDO extends function makeDurableObject(options?: MakeDurableObjectClassOptions): { new (ctx: DoState, env: Env): DoObject<SyncBackendRpcInterface>;}
Creates a Durable Object class for handling WebSocket-based sync.
A sync Durable Object is uniquely scoped to a specific storeId.
The sync DO supports 3 transport modes:
- HTTP JSON-RPC
- WebSocket
- Durable Object RPC calls (only works in combination with
@livestore/adapter-cf)
Example:
// In your Cloudflare Worker fileimport { makeDurableObject } from '@livestore/sync-cf/cf-worker'
export class SyncBackendDO extends makeDurableObject({ onPush: async (message) => { console.log('onPush', message.batch) }, onPull: async (message) => { console.log('onPull', message) },}) {}
wrangler.toml
[[durable_objects.bindings]]name = "SYNC_BACKEND_DO"class_name = "SyncBackendDO"
[[migrations]]tag = "v1"new_sqlite_classes = ["SyncBackendDO"]
makeDurableObject({ onPush?: (message: PushRequest, context: CallbackContext) => SyncOrPromiseOrEffect<void>
onPush: async (message: Struct.ReadonlySide<{ readonly batch: $Array<Struct<{ readonly name: String; readonly args: Any; readonly seqNum: brand<Int, "GlobalEventSequenceNumber">; readonly parentSeqNum: brand<Int, "GlobalEventSequenceNumber">; readonly clientId: String; readonly sessionId: String; }>>; readonly backendId: Option<String>;}, "Type">
message, { storeId: string
storeId, payload: Json | undefined
payload }) => { var console: Console
console.Console.log(...data: any[]): void (+2 overloads)
The console.log() static method outputs a message to the console.
log(`Push to store ${storeId: string
storeId}:`, message: Struct.ReadonlySide<{ readonly batch: $Array<Struct<{ readonly name: String; readonly args: Any; readonly seqNum: brand<Int, "GlobalEventSequenceNumber">; readonly parentSeqNum: brand<Int, "GlobalEventSequenceNumber">; readonly clientId: String; readonly sessionId: String; }>>; readonly backendId: Option<String>;}, "Type">
message.batch: readonly Struct.ReadonlySide<{ readonly name: String; readonly args: Any; readonly seqNum: brand<Int, "GlobalEventSequenceNumber">; readonly parentSeqNum: brand<Int, "GlobalEventSequenceNumber">; readonly clientId: String; readonly sessionId: String;}, "Type">[]
batch)
// Custom business logic if (const hasUserId: (p: unknown) => p is { userId: string;}
hasUserId(payload: Json | undefined
payload) === true) { await var Promise: PromiseConstructor
Represents the completion of an asynchronous operation
Promise.PromiseConstructor.resolve(): Promise<void> (+2 overloads)
Creates a new resolved promise.
resolve() } }, onPull?: (message: PullRequest, context: CallbackContext) => SyncOrPromiseOrEffect<void>
onPull: async (_message: Struct.ReadonlySide<{ readonly cursor: Option<Struct<{ readonly backendId: String; readonly eventSequenceNumber: brand<Int, "GlobalEventSequenceNumber">; }>>;}, "Type">
_message, { storeId: string
storeId }) => { var console: Console
console.Console.log(...data: any[]): void (+2 overloads)
The console.log() static method outputs a message to the console.
log(`Pull from store ${storeId: string
storeId}`) }, enabledTransports?: Set<"http" | "ws" | "do-rpc">
Enabled transports for sync backend
http: HTTP JSON-RPC
ws: WebSocket
do-rpc: Durable Object RPC calls (only works in combination with @livestore/adapter-cf)
enabledTransports: new var Set: SetConstructornew <"http" | "ws">(iterable?: Iterable<"http" | "ws"> | null | undefined) => Set<"http" | "ws"> (+1 overload)
Set(['ws', 'http']), // Disable DO RPC otel?: { baseUrl?: string; serviceName?: string;}
otel: { baseUrl?: string
baseUrl: 'https://otel.example.com', serviceName?: string
serviceName: 'livestore-sync', },}) {}makeWorker(options)
Section titled “makeWorker(options)”Creates a complete Cloudflare Worker for the sync backend.
Options:
syncBackendBinding- Durable Object binding name defined inwrangler.tomlvalidatePayload?- Payload validation function:(payload, context) => void | Promise<void>enableCORS?- Enable CORS headers (default:false)
makeWorker is a quick way to get started in simple demos. In most production workers you typically want to share routing logic with other endpoints, so prefer wiring your own fetch handler and call handleSyncRequest when you detect a sync request. A minimal example:
import type { type CFWorker<TEnv extends Env = Env, _T extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined> = { fetch: <CFHostMetada = unknown>(request: CfTypes.Request<CFHostMetada>, env: TEnv, ctx: CfTypes.ExecutionContext) => Promise<CfTypes.Response>;}
CFWorker, import CfTypes
CfTypes } from '@livestore/sync-cf/cf-worker'import { const handleSyncRequest: <TEnv extends Env = Env, TDurableObjectRpc extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined, CFHostMetada = unknown, TSyncPayload = Json>({ request, searchParams: { storeId, payload, transport }, env: explicitlyProvidedEnv, syncBackendBinding, headers, validatePayload, syncPayloadSchema, }: { request: CfTypes.Request<CFHostMetada>; searchParams: SearchParams; env?: TEnv | undefined; ctx: CfTypes.ExecutionContext; syncBackendBinding: MakeWorkerOptions<TEnv, TSyncPayload>["syncBackendBinding"]; headers?: CfTypes.HeadersInit | undefined; validatePayload?: MakeWorkerOptions<TEnv, TSyncPayload>["validatePayload"]; syncPayloadSchema?: MakeWorkerOptions<TEnv, TSyncPayload>["syncPayloadSchema"];}) => Promise<CfTypes.Response>
Handles LiveStore sync requests (e.g. with search params ?storeId=...&transport=...).
handleSyncRequest, const matchSyncRequest: (request: CfTypes.Request) => SearchParams | undefined
Extracts the LiveStore sync search parameters from a request. Returns
undefined when the request does not carry valid sync metadata so callers
can fall back to custom routing.
matchSyncRequest } from '@livestore/sync-cf/cf-worker'
import type { import Env
Env } from './env.ts'
export default { fetch: <CFHostMetada = unknown>(request: CfTypes.Request<CFHostMetada, CfTypes.CfProperties<CFHostMetada>>, env: Env, ctx: CfTypes.ExecutionContext) => Promise<CfTypes.Response>
fetch: async (request: CfTypes.Request<unknown, CfTypes.CfProperties<unknown>>
request: import CfTypes
CfTypes.interface Request<CfHostMetadata = unknown, Cf = CfTypes.CfProperties<CfHostMetadata>>
The Request interface of the Fetch API represents a resource request.
Request, env: Env
env: import Env
Env, ctx: CfTypes.ExecutionContext<unknown>
ctx: import CfTypes
CfTypes.interface ExecutionContext<Props = unknown>
ExecutionContext) => { const const searchParams: { readonly transport: "http" | "ws"; readonly storeId: string; readonly payload?: Json | undefined;} | undefined
searchParams = function matchSyncRequest(request: CfTypes.Request): SearchParams | undefined
Extracts the LiveStore sync search parameters from a request. Returns
undefined when the request does not carry valid sync metadata so callers
can fall back to custom routing.
matchSyncRequest(request: CfTypes.Request<unknown, CfTypes.CfProperties<unknown>>
request)
if (const searchParams: { readonly transport: "http" | "ws"; readonly storeId: string; readonly payload?: Json | undefined;} | undefined
searchParams !== var undefined
undefined) { return handleSyncRequest<Env, undefined, unknown, Json>({ request, searchParams: { storeId, payload, transport }, env: explicitlyProvidedEnv, syncBackendBinding, headers, validatePayload, syncPayloadSchema, }: { request: CfTypes.Request<unknown, CfTypes.CfProperties<unknown>>; searchParams: SearchParams; env?: any; ctx: CfTypes.ExecutionContext; syncBackendBinding: any; headers?: CfTypes.HeadersInit | undefined; validatePayload?: ((payload: Json, context: ValidatePayloadContext) => void | Promise<void>) | undefined; syncPayloadSchema?: Decoder<Json, never> | undefined;}): Promise<CfTypes.Response>
Handles LiveStore sync requests (e.g. with search params ?storeId=...&transport=...).
handleSyncRequest({ request: CfTypes.Request<unknown, CfTypes.CfProperties<unknown>>
request, searchParams: { readonly transport: "http" | "ws"; readonly storeId: string; readonly payload?: Json | undefined;}
searchParams, env?: any
env, ctx: CfTypes.ExecutionContext<unknown>
Only there for type-level reasons
ctx, syncBackendBinding: any
Binding name of the sync backend Durable Object
syncBackendBinding: 'SYNC_BACKEND_DO', }) }
// Custom routes, assets, etc. return new var Response: new (body?: BodyInit | null, init?: ResponseInit) => Response
The Response interface of the Fetch API represents the response to a request.
Response('Not found', { ResponseInit.status?: number
status: 404 }) as unknown as import CfTypes
CfTypes.interface Response
The Response interface of the Fetch API represents the response to a request.
Response },} satisfies type CFWorker<TEnv extends Env = Env, _T extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined> = { fetch: <CFHostMetada = unknown>(request: CfTypes.Request<CFHostMetada>, env: TEnv, ctx: CfTypes.ExecutionContext) => Promise<CfTypes.Response>;}
CFWorker<import Env
Env>import type { import CfTypes
CfTypes, (alias) interface SyncBackendRpcInterfaceimport SyncBackendRpcInterface
Durable Object interface supporting the DO RPC protocol for DO <> DO syncing.
SyncBackendRpcInterface } from '@livestore/sync-cf/cf-worker'
export interface interface Env
Env { Env.SYNC_BACKEND_DO: CfTypes.DurableObjectNamespace<SyncBackendRpcInterface>
SYNC_BACKEND_DO: import CfTypes
CfTypes.class DurableObjectNamespace<T extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined>
DurableObjectNamespace<(alias) interface SyncBackendRpcInterfaceimport SyncBackendRpcInterface
Durable Object interface supporting the DO RPC protocol for DO <> DO syncing.
SyncBackendRpcInterface>}import { const makeWorker: <TEnv extends Env = Env, TDurableObjectRpc extends Rpc.DurableObjectBranded | undefined = undefined, TSyncPayload = Json>(options: MakeWorkerOptions<TEnv, TSyncPayload>) => CFWorker<TEnv, TDurableObjectRpc>
Produces a Cloudflare Worker fetch handler that delegates sync traffic to the
Durable Object identified by syncBackendBinding.
For more complex setups prefer implementing a custom fetch and call
handleSyncRequest
from the branch that handles LiveStore sync requests.
makeWorker } from '@livestore/sync-cf/cf-worker'
export default makeWorker<{ SYNC_BACKEND_DO: any;} & { SYNC_BACKEND_DO: any;}, undefined, Json>(options: MakeWorkerOptions<{ SYNC_BACKEND_DO: any;} & { SYNC_BACKEND_DO: any;}, Json>): CFWorker<{ SYNC_BACKEND_DO: any;} & { SYNC_BACKEND_DO: any;}, undefined>
Produces a Cloudflare Worker fetch handler that delegates sync traffic to the
Durable Object identified by syncBackendBinding.
For more complex setups prefer implementing a custom fetch and call
handleSyncRequest
from the branch that handles LiveStore sync requests.
makeWorker({ syncBackendBinding: "SYNC_BACKEND_DO"
Binding name of the sync Durable Object declared in wrangler config.
syncBackendBinding: 'SYNC_BACKEND_DO', validatePayload?: (payload: Json, context: ValidatePayloadContext) => void | Promise<void>
Validates the (optionally decoded) payload during WebSocket connection establishment.
If
syncPayloadSchema
is provided, payload will be of the schema's inferred type.
The context includes request headers for cookie-based or header-based authentication.
validatePayload: (payload: Json
payload, { storeId: string
storeId }) => { // Simple token-based guard at connection time const const hasAuthToken: boolean
hasAuthToken = typeof payload: Json
payload === 'object' && payload: JsonArray | JsonObject | null
payload !== null && 'authToken' in payload: JsonArray | JsonObject
payload if (const hasAuthToken: boolean
hasAuthToken === false) { throw new var Error: ErrorConstructornew (message?: string, options?: ErrorOptions) => Error (+2 overloads)
Error('Missing auth token') } if ((payload: JsonObject
payload as any).any
authToken !== 'insecure-token-change-me') { throw new var Error: ErrorConstructornew (message?: string, options?: ErrorOptions) => Error (+2 overloads)
Error('Invalid auth token') } var console: Console
console.Console.log(...data: any[]): void (+2 overloads)
The console.log() static method outputs a message to the console.
log(`Validated connection for store: ${storeId: string
storeId}`) }, enableCORS?: boolean
enableCORS: true,})handleSyncRequest(args)
Section titled “handleSyncRequest(args)”Handles sync backend HTTP requests in custom workers.
Options:
request- The incoming requestsearchParams- Parsed sync request parametersenv- Worker environmentctx- Worker execution contextsyncBackendBinding- Durable Object binding name defined inwrangler.tomlheaders?- Response headersvalidatePayload?- Payload validation function
import type { type CFWorker<TEnv extends Env = Env, _T extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined> = { fetch: <CFHostMetada = unknown>(request: CfTypes.Request<CFHostMetada>, env: TEnv, ctx: CfTypes.ExecutionContext) => Promise<CfTypes.Response>;}
CFWorker, import CfTypes
CfTypes } from '@livestore/sync-cf/cf-worker'import { const handleSyncRequest: <TEnv extends Env = Env, TDurableObjectRpc extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined, CFHostMetada = unknown, TSyncPayload = Json>({ request, searchParams: { storeId, payload, transport }, env: explicitlyProvidedEnv, syncBackendBinding, headers, validatePayload, syncPayloadSchema, }: { request: CfTypes.Request<CFHostMetada>; searchParams: SearchParams; env?: TEnv | undefined; ctx: CfTypes.ExecutionContext; syncBackendBinding: MakeWorkerOptions<TEnv, TSyncPayload>["syncBackendBinding"]; headers?: CfTypes.HeadersInit | undefined; validatePayload?: MakeWorkerOptions<TEnv, TSyncPayload>["validatePayload"]; syncPayloadSchema?: MakeWorkerOptions<TEnv, TSyncPayload>["syncPayloadSchema"];}) => Promise<CfTypes.Response>
Handles LiveStore sync requests (e.g. with search params ?storeId=...&transport=...).
handleSyncRequest, const matchSyncRequest: (request: CfTypes.Request) => SearchParams | undefined
Extracts the LiveStore sync search parameters from a request. Returns
undefined when the request does not carry valid sync metadata so callers
can fall back to custom routing.
matchSyncRequest } from '@livestore/sync-cf/cf-worker'
import type { import Env
Env } from './env.ts'
export default { fetch: <CFHostMetada = unknown>(request: CfTypes.Request<CFHostMetada, CfTypes.CfProperties<CFHostMetada>>, env: Env, ctx: CfTypes.ExecutionContext) => Promise<CfTypes.Response>
fetch: async (request: CfTypes.Request<unknown, CfTypes.CfProperties<unknown>>
request: import CfTypes
CfTypes.interface Request<CfHostMetadata = unknown, Cf = CfTypes.CfProperties<CfHostMetadata>>
The Request interface of the Fetch API represents a resource request.
Request, env: Env
env: import Env
Env, ctx: CfTypes.ExecutionContext<unknown>
ctx: import CfTypes
CfTypes.interface ExecutionContext<Props = unknown>
ExecutionContext) => { const const searchParams: { readonly transport: "http" | "ws"; readonly storeId: string; readonly payload?: Json | undefined;} | undefined
searchParams = function matchSyncRequest(request: CfTypes.Request): SearchParams | undefined
Extracts the LiveStore sync search parameters from a request. Returns
undefined when the request does not carry valid sync metadata so callers
can fall back to custom routing.
matchSyncRequest(request: CfTypes.Request<unknown, CfTypes.CfProperties<unknown>>
request)
if (const searchParams: { readonly transport: "http" | "ws"; readonly storeId: string; readonly payload?: Json | undefined;} | undefined
searchParams !== var undefined
undefined) { return handleSyncRequest<Env, undefined, unknown, Json>({ request, searchParams: { storeId, payload, transport }, env: explicitlyProvidedEnv, syncBackendBinding, headers, validatePayload, syncPayloadSchema, }: { request: CfTypes.Request<unknown, CfTypes.CfProperties<unknown>>; searchParams: SearchParams; env?: any; ctx: CfTypes.ExecutionContext; syncBackendBinding: any; headers?: CfTypes.HeadersInit | undefined; validatePayload?: ((payload: Json, context: ValidatePayloadContext) => void | Promise<void>) | undefined; syncPayloadSchema?: Decoder<Json, never> | undefined;}): Promise<CfTypes.Response>
Handles LiveStore sync requests (e.g. with search params ?storeId=...&transport=...).
handleSyncRequest({ request: CfTypes.Request<unknown, CfTypes.CfProperties<unknown>>
request, searchParams: { readonly transport: "http" | "ws"; readonly storeId: string; readonly payload?: Json | undefined;}
searchParams, env?: any
env, ctx: CfTypes.ExecutionContext<unknown>
Only there for type-level reasons
ctx, syncBackendBinding: any
Binding name of the sync backend Durable Object
syncBackendBinding: 'SYNC_BACKEND_DO', headers?: CfTypes.HeadersInit | undefined
headers: { 'X-Custom': 'header' }, validatePayload?: ((payload: Json, context: ValidatePayloadContext) => void | Promise<void>) | undefined
validatePayload: (payload: Json
payload, { storeId: string
storeId }) => { // Custom validation logic if (!(typeof payload: Json
payload === 'object' && payload: JsonArray | JsonObject | null
payload !== null && 'authToken' in payload: JsonArray | JsonObject
payload)) { throw new var Error: ErrorConstructornew (message?: string, options?: ErrorOptions) => Error (+2 overloads)
Error('Missing auth token') } var console: Console
console.Console.log(...data: any[]): void (+2 overloads)
The console.log() static method outputs a message to the console.
log('Validating store', storeId: string
storeId) }, }) }
return new var Response: new (body?: BodyInit | null, init?: ResponseInit) => Response
The Response interface of the Fetch API represents the response to a request.
Response('Not found', { ResponseInit.status?: number
status: 404 }) as unknown as import CfTypes
CfTypes.interface Response
The Response interface of the Fetch API represents the response to a request.
Response },} satisfies type CFWorker<TEnv extends Env = Env, _T extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined> = { fetch: <CFHostMetada = unknown>(request: CfTypes.Request<CFHostMetada>, env: TEnv, ctx: CfTypes.ExecutionContext) => Promise<CfTypes.Response>;}
CFWorker<import Env
Env>import type { import CfTypes
CfTypes, (alias) interface SyncBackendRpcInterfaceimport SyncBackendRpcInterface
Durable Object interface supporting the DO RPC protocol for DO <> DO syncing.
SyncBackendRpcInterface } from '@livestore/sync-cf/cf-worker'
export interface interface Env
Env { Env.SYNC_BACKEND_DO: CfTypes.DurableObjectNamespace<SyncBackendRpcInterface>
SYNC_BACKEND_DO: import CfTypes
CfTypes.class DurableObjectNamespace<T extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined>
DurableObjectNamespace<(alias) interface SyncBackendRpcInterfaceimport SyncBackendRpcInterface
Durable Object interface supporting the DO RPC protocol for DO <> DO syncing.
SyncBackendRpcInterface>}matchSyncRequest(request)
Section titled “matchSyncRequest(request)”Parses and validates sync request search parameters.
Returns the decoded search params or undefined if the request is not a LiveStore sync request.
import type { import CfTypes
CfTypes } from '@livestore/sync-cf/cf-worker'import { const matchSyncRequest: (request: CfTypes.Request) => SearchParams | undefined
Extracts the LiveStore sync search parameters from a request. Returns
undefined when the request does not carry valid sync metadata so callers
can fall back to custom routing.
matchSyncRequest } from '@livestore/sync-cf/cf-worker'
declare const const request: CfTypes.Request<unknown, CfTypes.CfProperties<unknown>>
request: import CfTypes
CfTypes.interface Request<CfHostMetadata = unknown, Cf = CfTypes.CfProperties<CfHostMetadata>>
The Request interface of the Fetch API represents a resource request.
Request
const const searchParams: { readonly transport: "http" | "ws"; readonly storeId: string; readonly payload?: Json | undefined;} | undefined
searchParams = function matchSyncRequest(request: CfTypes.Request): SearchParams | undefined
Extracts the LiveStore sync search parameters from a request. Returns
undefined when the request does not carry valid sync metadata so callers
can fall back to custom routing.
matchSyncRequest(const request: CfTypes.Request<unknown, CfTypes.CfProperties<unknown>>
request)if (const searchParams: { readonly transport: "http" | "ws"; readonly storeId: string; readonly payload?: Json | undefined;} | undefined
searchParams !== var undefined
undefined) { const { const storeId: string
storeId, const payload: Json | undefined
payload, const transport: "http" | "ws"
transport } = const searchParams: { readonly transport: "http" | "ws"; readonly storeId: string; readonly payload?: Json | undefined;}
searchParams var console: Console
console.Console.log(...data: any[]): void (+2 overloads)
The console.log() static method outputs a message to the console.
log(`Sync request for store ${const storeId: string
storeId} via ${const transport: "http" | "ws"
transport}`) var console: Console
console.Console.log(...data: any[]): void (+2 overloads)
The console.log() static method outputs a message to the console.
log(const payload: Json | undefined
payload)}Configuration
Section titled “Configuration”Wrangler configuration
Section titled “Wrangler configuration”Configure your wrangler.toml for sync backend deployment (default: DO SQLite storage):
name = "livestore-sync"main = "./src/worker.ts"compatibility_date = "2025-05-07"compatibility_flags = [ "enable_request_signal", # Required for HTTP streaming]
[[durable_objects.bindings]]name = "SYNC_BACKEND_DO"class_name = "SyncBackendDO"
[[migrations]]tag = "v1"new_sqlite_classes = ["SyncBackendDO"]To use D1 instead of DO SQLite, add a D1 binding and reference it from makeDurableObject({ storage: { _tag: 'd1', binding: '...' } }):
[[d1_databases]]binding = "DB"database_name = "livestore-sync"database_id = "your-database-id"Environment variables
Section titled “Environment variables”Required environment bindings:
import type { import CfTypes
CfTypes, (alias) interface SyncBackendRpcInterfaceimport SyncBackendRpcInterface
Durable Object interface supporting the DO RPC protocol for DO <> DO syncing.
SyncBackendRpcInterface } from '@livestore/sync-cf/cf-worker'
export interface interface Env
Env { Env.SYNC_BACKEND_DO: CfTypes.DurableObjectNamespace<SyncBackendRpcInterface>
SYNC_BACKEND_DO: import CfTypes
CfTypes.class DurableObjectNamespace<T extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined>
DurableObjectNamespace<(alias) interface SyncBackendRpcInterfaceimport SyncBackendRpcInterface
Durable Object interface supporting the DO RPC protocol for DO <> DO syncing.
SyncBackendRpcInterface>}Transport protocol details
Section titled “Transport protocol details”LiveStore identifies sync requests purely by search parameters; the request path does not matter. Use matchSyncRequest(request) to detect sync traffic.
Required search parameters:
| Param | Type | Required | Description |
|---|---|---|---|
storeId | string | Yes | Target LiveStore identifier. |
transport | 'ws' | 'http' | Yes | Transport protocol selector. |
payload | JSON (URI-encoded) | No | Arbitrary JSON used for auth/tenant routing; validated in validatePayload. |
Examples (any path):
- WebSocket:
https://sync.example.com?storeId=abc&transport=ws(must includeUpgrade: websocket) - HTTP:
https://sync.example.com?storeId=abc&transport=http
Notes:
- For
transport=ws, if the request is not a WebSocket upgrade, the backend returns426 Upgrade Required. transport='do-rpc'is internal for Durable Object RPC and not exposed via URL parameters.
Data storage
Section titled “Data storage”By default, events are stored in the Durable Object’s SQLite with tables following the pattern:
eventlog_{PERSISTENCE_FORMAT_VERSION}_{storeId}You can opt into D1 with the same table shape. The persistence format version is automatically managed and incremented when the storage schema changes.
Storage engines
Section titled “Storage engines”- DO SQLite (default)
- Pros: easiest deploy (no D1), data co-located with the DO, lowest latency
- Cons: not directly inspectable outside the DO; operational tooling must go through the DO
- D1 (optional)
- Pros: inspectable using D1 tools/clients; enables cross-store analytics outside DOs
- Cons: extra hop, JSON response size considerations; requires D1 provisioning
Deployment
Section titled “Deployment”Deploy to Cloudflare Workers:
# Deploy the workernpx wrangler deploy
# Create D1 databasenpx wrangler d1 create livestore-sync
# Run migrations if needednpx wrangler d1 migrations apply livestore-syncLocal development
Section titled “Local development”Run locally with Wrangler:
# Start local development servernpx wrangler dev
# Access local D1 database# Located at: .wrangler/state/d1/miniflare-D1DatabaseObject/XXX.sqliteExamples
Section titled “Examples”Basic WebSocket client
Section titled “Basic WebSocket client”import { const makeWorker: (options: WorkerOptions) => void
makeWorker } from '@livestore/adapter-web/worker'import { const makeWsSync: (options: WsSyncOptions) => SyncBackendConstructor<SyncMetadata>
Creates a sync backend that uses WebSocket to communicate with the sync backend.
makeWsSync } from '@livestore/sync-cf/client'
import { import schema
schema } from './schema.ts'
function makeWorker(options: WorkerOptions): void
makeWorker({ schema: LiveStoreSchema<DbSchema, EventDefRecord>
schema, sync?: SyncOptions
sync: { backend?: SyncBackendConstructor<any, Json>
backend: function makeWsSync(options: WsSyncOptions): SyncBackendConstructor<SyncMetadata>
Creates a sync backend that uses WebSocket to communicate with the sync backend.
makeWsSync({ WsSyncOptions.url: string
URL of the sync backend
The protocol can either http/https or ws/wss
url: 'wss://sync.example.com', }), },})import { import Events
Events, const makeSchema: <TInputSchema extends InputSchema>(inputSchema: TInputSchema) => FromInputSchema.DeriveSchema<TInputSchema>
makeSchema, import Schema
Schema, import State
State } from '@livestore/livestore'
export const const tables: { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables = { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos: import State
State.import SQLite
SQLite.function table<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; };}, Partial<...>>(args: { ...;} & Partial<...>): State.SQLite.TableDef<...> (+2 overloads)
Creates a SQLite table definition from columns or an Effect Schema.
This function supports two main ways to define a table:
- Using explicit column definitions
- Using an Effect Schema (either the
name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columnsconst usersTable = State.SQLite.table({ name: 'users', columns: { id: State.SQLite.text({ primaryKey: true }), name: State.SQLite.text({ nullable: false }), email: State.SQLite.text({ nullable: false }), age: State.SQLite.integer({ nullable: true }), },})
// Using Effect Schema with annotationsimport { Schema } from '@livestore/utils/effect'
const UserSchema = Schema.Struct({ id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement), email: Schema.String.pipe(State.SQLite.withUnique), name: Schema.String, active: Schema.Boolean.pipe(State.SQLite.withDefault(true)), createdAt: Schema.optional(Schema.Date),})
// Option 1: With explicit nameconst usersTable = State.SQLite.table({ name: 'users', schema: UserSchema,})
// Option 2: With name from schema annotation (title or identifier)const AnnotatedUserSchema = UserSchema.annotate({ title: 'users' })const usersTable2 = State.SQLite.table({ schema: AnnotatedUserSchema,})
// Adding indexesconst PostSchema = Schema.Struct({ id: Schema.String.pipe(State.SQLite.withPrimaryKey), title: Schema.String, authorId: Schema.String, createdAt: Schema.Date,}).annotate({ identifier: 'posts' })
const postsTable = State.SQLite.table({ schema: PostSchema, indexes: [ { name: 'idx_posts_author', columns: ['authorId'] }, { name: 'idx_posts_created', columns: ['createdAt'], isUnique: false }, ],})
table({ name: "todos"
name: 'todos', columns: { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; };}
columns: { id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;}
id: import State
State.import SQLite
SQLite.const text: <string, string, false, typeof NoDefault, true, false>(args: { schema?: Schema.Codec<string, string, never, never>; default?: typeof NoDefault; nullable?: false; primaryKey?: true; autoIncrement?: false;}) => { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false;} (+1 overload)
text({ primaryKey?: true
primaryKey: true }), text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false;}
text: import State
State.import SQLite
SQLite.const text: <string, string, false, "", false, false>(args: { schema?: Schema.Codec<string, string, never, never>; default?: ""; nullable?: false; primaryKey?: false; autoIncrement?: false;}) => { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false;} (+1 overload)
text({ default?: ""
default: '' }), completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false;}
completed: import State
State.import SQLite
SQLite.const boolean: <boolean, false, false, false, false>(args: { default?: false; nullable?: false; primaryKey?: false; autoIncrement?: false;}) => { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false;} (+1 overload)
boolean({ default?: false
default: false }), deletedAt: { columnType: "integer"; schema: Schema.Codec<Date | null, number | null, never, never>; default: None<never>; nullable: true; primaryKey: false; autoIncrement: false;}
deletedAt: import State
State.import SQLite
SQLite.const integer: <number, Date, true, typeof NoDefault, false, false>(args: { schema?: Schema.Codec<Date, number, never, never>; default?: typeof NoDefault; nullable?: true; primaryKey?: false; autoIncrement?: false;}) => { columnType: "integer"; schema: Schema.Codec<Date | null, number | null, never, never>; default: None<never>; nullable: true; primaryKey: false; autoIncrement: false;} (+1 overload)
integer({ nullable?: true
nullable: true, schema?: Schema.Codec<Date, number, never, never>
schema: import Schema
Schema.const DateFromMillis: Schema.DateFromMillis
Type-level representation of
DateFromMillis
.
Schema that decodes epoch milliseconds into a JavaScript Date.
When to use
Use to model numeric millisecond timestamps that decode to JavaScript Date
objects and encode back to numbers.
Details
Decoding:
A number of milliseconds since the Unix epoch is decoded as a Date.
Encoding:
A Date is encoded as its millisecond timestamp.
Gotchas
This schema accepts any number, including NaN, Infinity, and -Infinity.
Those values decode to invalid Date instances.
DateFromMillis }), }, }),}
export const const events: { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>; todoUncompleted: State.SQLite.EventDef<...>; todoDeleted: State.SQLite.EventDef<...>; todoClearedCompleted: State.SQLite.EventDef<...>;}
events = { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>
todoCreated: import Events
Events.synced<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">>(args: { name: "v1.TodoCreated"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">, never, never>;} & Omit<...>): State.SQLite.EventDef<...>export synced
Creates a synced event definition.
Synced events are sent to the sync backend and distributed to all connected
clients. Use this for collaborative data that should be shared across users
and devices.
Event names should be versioned (e.g., v1.TodoCreated) to support
schema evolution over time.
synced({ name: "v1.TodoCreated"
name: 'v1.TodoCreated', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String; readonly text: Schema.String;}>(fields: { readonly id: Schema.String; readonly text: Schema.String;}): Schema.Struct<{ readonly id: Schema.String; readonly text: Schema.String;}>
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Example (Defining a basic struct)
import { Schema } from "effect"
const Person = Schema.Struct({ name: Schema.String, age: Schema.Number, email: Schema.optionalKey(Schema.String)})
// { readonly name: string; readonly age: number; readonly email?: string }type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })console.log(alice)// { name: 'Alice', age: 30 }
Struct({ id: Schema.String
id: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String, text: Schema.String
text: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String }), }), todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Encoded">>
todoCompleted: import Events
Events.synced<"v1.TodoCompleted", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Encoded">>(args: { name: "v1.TodoCompleted"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">, never, never>;} & Omit<State.SQLite.DefineEventOptions<Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Type">, false>, "derived" | "clientOnly">): State.SQLite.EventDef<...>export synced
Creates a synced event definition.
Synced events are sent to the sync backend and distributed to all connected
clients. Use this for collaborative data that should be shared across users
and devices.
Event names should be versioned (e.g., v1.TodoCreated) to support
schema evolution over time.
synced({ name: "v1.TodoCompleted"
name: 'v1.TodoCompleted', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String;}>(fields: { readonly id: Schema.String;}): Schema.Struct<{ readonly id: Schema.String;}>
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Example (Defining a basic struct)
import { Schema } from "effect"
const Person = Schema.Struct({ name: Schema.String, age: Schema.Number, email: Schema.optionalKey(Schema.String)})
// { readonly name: string; readonly age: number; readonly email?: string }type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })console.log(alice)// { name: 'Alice', age: 30 }
Struct({ id: Schema.String
id: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String }), }), todoUncompleted: State.SQLite.EventDef<"v1.TodoUncompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Encoded">>
todoUncompleted: import Events
Events.synced<"v1.TodoUncompleted", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Encoded">>(args: { name: "v1.TodoUncompleted"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">, never, never>;} & Omit<State.SQLite.DefineEventOptions<Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Type">, false>, "derived" | "clientOnly">): State.SQLite.EventDef<...>export synced
Creates a synced event definition.
Synced events are sent to the sync backend and distributed to all connected
clients. Use this for collaborative data that should be shared across users
and devices.
Event names should be versioned (e.g., v1.TodoCreated) to support
schema evolution over time.
synced({ name: "v1.TodoUncompleted"
name: 'v1.TodoUncompleted', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String;}>(fields: { readonly id: Schema.String;}): Schema.Struct<{ readonly id: Schema.String;}>
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Example (Defining a basic struct)
import { Schema } from "effect"
const Person = Schema.Struct({ name: Schema.String, age: Schema.Number, email: Schema.optionalKey(Schema.String)})
// { readonly name: string; readonly age: number; readonly email?: string }type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })console.log(alice)// { name: 'Alice', age: 30 }
Struct({ id: Schema.String
id: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String }), }), todoDeleted: State.SQLite.EventDef<"v1.TodoDeleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly deletedAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly deletedAt: Schema.DateFromString;}, "Encoded">>
todoDeleted: import Events
Events.synced<"v1.TodoDeleted", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly deletedAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly deletedAt: Schema.DateFromString;}, "Encoded">>(args: { name: "v1.TodoDeleted"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly deletedAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly deletedAt: Schema.DateFromString; }, "Encoded">, never, never>;} & Omit<...>): State.SQLite.EventDef<...>export synced
Creates a synced event definition.
Synced events are sent to the sync backend and distributed to all connected
clients. Use this for collaborative data that should be shared across users
and devices.
Event names should be versioned (e.g., v1.TodoCreated) to support
schema evolution over time.
synced({ name: "v1.TodoDeleted"
name: 'v1.TodoDeleted', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly deletedAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly deletedAt: Schema.DateFromString;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly id: Schema.String; readonly deletedAt: Schema.DateFromString;}>(fields: { readonly id: Schema.String; readonly deletedAt: Schema.DateFromString;}): Schema.Struct<{ readonly id: Schema.String; readonly deletedAt: Schema.DateFromString;}>
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Example (Defining a basic struct)
import { Schema } from "effect"
const Person = Schema.Struct({ name: Schema.String, age: Schema.Number, email: Schema.optionalKey(Schema.String)})
// { readonly name: string; readonly age: number; readonly email?: string }type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })console.log(alice)// { name: 'Alice', age: 30 }
Struct({ id: Schema.String
id: import Schema
Schema.const String: Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String, 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.
DateFromString.Bottom<unknown, unknown, unknown, unknown, Declaration, decodeTo<Date, String, never, never>, unknown, unknown, readonly [], unknown, "readonly", "required", "no-default", "readonly", "required">.check(checks_0: Check<Date>, ...checks: Check<Date>[]): Schema.DateFromString
check(import Schema
Schema.function isDateValid(annotations?: Schema.Annotations.Filter): Filter<globalThis.Date>
Validates that a Date object represents a valid date (not an invalid date
like new Date("invalid")).
Details
JSON Schema:
This check does not have a direct JSON Schema equivalent, as JSON Schema
validates date strings, not Date objects.
Arbitrary:
When generating test data with fast-check, this applies a valid: true
constraint to ensure generated Date objects are valid.
isDateValid()), }), }), todoClearedCompleted: State.SQLite.EventDef<"v1.TodoClearedCompleted", Schema.Struct.ReadonlySide<{ readonly deletedAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly deletedAt: Schema.DateFromString;}, "Encoded">>
todoClearedCompleted: import Events
Events.synced<"v1.TodoClearedCompleted", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly deletedAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly deletedAt: Schema.DateFromString;}, "Encoded">>(args: { name: "v1.TodoClearedCompleted"; schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly deletedAt: Schema.DateFromString; }, "Type">, Schema.Struct.ReadonlySide<{ readonly deletedAt: Schema.DateFromString; }, "Encoded">, never, never>;} & Omit<...>): State.SQLite.EventDef<...>export synced
Creates a synced event definition.
Synced events are sent to the sync backend and distributed to all connected
clients. Use this for collaborative data that should be shared across users
and devices.
Event names should be versioned (e.g., v1.TodoCreated) to support
schema evolution over time.
synced({ name: "v1.TodoClearedCompleted"
name: 'v1.TodoClearedCompleted', schema: Schema.Codec<Schema.Struct.ReadonlySide<{ readonly deletedAt: Schema.DateFromString;}, "Type">, Schema.Struct.ReadonlySide<{ readonly deletedAt: Schema.DateFromString;}, "Encoded">, never, never>
schema: import Schema
Schema.function Struct<{ readonly deletedAt: Schema.DateFromString;}>(fields: { readonly deletedAt: Schema.DateFromString;}): Schema.Struct<{ readonly deletedAt: Schema.DateFromString;}>
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Example (Defining a basic struct)
import { Schema } from "effect"
const Person = Schema.Struct({ name: Schema.String, age: Schema.Number, email: Schema.optionalKey(Schema.String)})
// { readonly name: string; readonly age: number; readonly email?: string }type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })console.log(alice)// { name: 'Alice', age: 30 }
Struct({ deletedAt: Schema.DateFromString
deletedAt: import Schema
Schema.const DateFromString: Schema.DateFromString
Type-level representation of
DateFromString
.
Schema that decodes a string into a JavaScript Date.
When to use
Use to model string-encoded dates that decode to JavaScript Date objects
and encode back to strings.
Details
Decoding:
The string is passed to JavaScript Date construction.
Encoding:
A valid Date is encoded as an ISO string; an invalid Date is encoded as
"Invalid Date".
Gotchas
Invalid date strings can decode to invalid Date instances.
DateFromString.Bottom<unknown, unknown, unknown, unknown, Declaration, decodeTo<Date, String, never, never>, unknown, unknown, readonly [], unknown, "readonly", "required", "no-default", "readonly", "required">.check(checks_0: Check<Date>, ...checks: Check<Date>[]): Schema.DateFromString
check(import Schema
Schema.function isDateValid(annotations?: Schema.Annotations.Filter): Filter<globalThis.Date>
Validates that a Date object represents a valid date (not an invalid date
like new Date("invalid")).
Details
JSON Schema:
This check does not have a direct JSON Schema equivalent, as JSON Schema
validates date strings, not Date objects.
Arbitrary:
When generating test data with fast-check, this applies a valid: true
constraint to ensure generated Date objects are valid.
isDateValid()) }), }),}
const const materializers: { "v1.TodoCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>>; "v1.TodoCompleted": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>>; "v1.TodoUncompleted": State.SQLite.Materializer<...>; "v1.TodoDeleted": State.SQLite.Materializer<...>; "v1.TodoClearedCompleted": State.SQLite.Materializer<...>;}
materializers = import State
State.import SQLite
SQLite.const materializers: <{ todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>; todoUncompleted: State.SQLite.EventDef<...>; todoDeleted: State.SQLite.EventDef<...>; todoClearedCompleted: State.SQLite.EventDef<...>;}>(_eventDefRecord: { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>; todoUncompleted: State.SQLite.EventDef<...>; todoDeleted: State.SQLite.EventDef<...>; todoClearedCompleted: State.SQLite.EventDef<...>;}, handlers: { ...;}) => { ...;}
Builder function for creating a type-safe materializer map.
This is the primary way to define materializers in LiveStore. It ensures:
- Every non-derived event has a corresponding materializer
- Materializer argument types match their event schemas
- Derived events are excluded from the required handlers
materializers(const events: { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>; todoUncompleted: State.SQLite.EventDef<...>; todoDeleted: State.SQLite.EventDef<...>; todoClearedCompleted: State.SQLite.EventDef<...>;}
events, { 'v1.TodoCreated': ({ id: string
id, text: string
text }) => const tables: { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos.insert: (values: { readonly id: string; readonly text?: string; readonly completed?: boolean; readonly deletedAt?: Date | null;}) => QueryBuilder<readonly Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>; readonly deletedAt: Schema.Codec<Date | null, number | null, never, never>;}, "Type">[], State.SQLite.TableDefBase<State.SQLite.SqliteTableDefForInput<"todos", { ...;}>, State.SQLite.WithDefaults<...>>, "select" | ... 6 more ... | "row">
Insert a new row into the table.
insert({ id: string
id, text?: string
text, completed?: boolean
completed: false }), 'v1.TodoCompleted': ({ id: string
id }) => const tables: { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos.update: (values: Partial<Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>; readonly deletedAt: Schema.Codec<Date | null, number | null, never, never>;}, "Type">>) => QueryBuilder<readonly Schema.Struct.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>; readonly deletedAt: Schema.Codec<Date | null, number | null, never, never>;}, "Type">[], State.SQLite.TableDefBase<...>, "select" | ... 6 more ... | "row">
Update rows in the table that match the where clause
Example:
db.todos.update({ status: 'completed' }).where({ id: '123' })
update({ completed?: boolean
completed: true }).where: (params: Partial<{ readonly id: string | { op: Exclude<QueryBuilder<TResult, TTableDef extends State.SQLite.TableDefBase<any, any>, TWithout extends QueryBuilder.ApiFeature = never>.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[]; } | undefined; readonly text: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { ...; } | undefined; readonly completed: boolean | ... 2 more ... | undefined; readonly deletedAt: Date | ... 3 more ... | undefined;}>) => QueryBuilder<...> (+3 overloads)
where({ id?: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string;} | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[];} | undefined
id }), 'v1.TodoUncompleted': ({ id: string
id }) => const tables: { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos.update: (values: Partial<Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>; readonly deletedAt: Schema.Codec<Date | null, number | null, never, never>;}, "Type">>) => QueryBuilder<readonly Schema.Struct.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>; readonly deletedAt: Schema.Codec<Date | null, number | null, never, never>;}, "Type">[], State.SQLite.TableDefBase<...>, "select" | ... 6 more ... | "row">
Update rows in the table that match the where clause
Example:
db.todos.update({ status: 'completed' }).where({ id: '123' })
update({ completed?: boolean
completed: false }).where: (params: Partial<{ readonly id: string | { op: Exclude<QueryBuilder<TResult, TTableDef extends State.SQLite.TableDefBase<any, any>, TWithout extends QueryBuilder.ApiFeature = never>.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[]; } | undefined; readonly text: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { ...; } | undefined; readonly completed: boolean | ... 2 more ... | undefined; readonly deletedAt: Date | ... 3 more ... | undefined;}>) => QueryBuilder<...> (+3 overloads)
where({ id?: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string;} | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[];} | undefined
id }), 'v1.TodoDeleted': ({ id: string
id, deletedAt: Date
deletedAt }) => const tables: { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos.update: (values: Partial<Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>; readonly deletedAt: Schema.Codec<Date | null, number | null, never, never>;}, "Type">>) => QueryBuilder<readonly Schema.Struct.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>; readonly deletedAt: Schema.Codec<Date | null, number | null, never, never>;}, "Type">[], State.SQLite.TableDefBase<...>, "select" | ... 6 more ... | "row">
Update rows in the table that match the where clause
Example:
db.todos.update({ status: 'completed' }).where({ id: '123' })
update({ deletedAt?: Date | null
deletedAt }).where: (params: Partial<{ readonly id: string | { op: Exclude<QueryBuilder<TResult, TTableDef extends State.SQLite.TableDefBase<any, any>, TWithout extends QueryBuilder.ApiFeature = never>.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[]; } | undefined; readonly text: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { ...; } | undefined; readonly completed: boolean | ... 2 more ... | undefined; readonly deletedAt: Date | ... 3 more ... | undefined;}>) => QueryBuilder<...> (+3 overloads)
where({ id?: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string;} | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[];} | undefined
id }), 'v1.TodoClearedCompleted': ({ deletedAt: Date
deletedAt }) => const tables: { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables.todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; };}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos.update: (values: Partial<Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>; readonly deletedAt: Schema.Codec<Date | null, number | null, never, never>;}, "Type">>) => QueryBuilder<readonly Schema.Struct.ReadonlySide<{ readonly id: Schema.Codec<string, string, never, never>; readonly text: Schema.Codec<string, string, never, never>; readonly completed: Schema.Codec<boolean, number, never, never>; readonly deletedAt: Schema.Codec<Date | null, number | null, never, never>;}, "Type">[], State.SQLite.TableDefBase<...>, "select" | ... 6 more ... | "row">
Update rows in the table that match the where clause
Example:
db.todos.update({ status: 'completed' }).where({ id: '123' })
update({ deletedAt?: Date | null
deletedAt }).where: (params: Partial<{ readonly id: string | { op: Exclude<QueryBuilder<TResult, TTableDef extends State.SQLite.TableDefBase<any, any>, TWithout extends QueryBuilder.ApiFeature = never>.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { op: QueryBuilder.WhereOps.MultiValue; value: readonly string[]; } | undefined; readonly text: string | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: string; } | { ...; } | undefined; readonly completed: boolean | ... 2 more ... | undefined; readonly deletedAt: Date | ... 3 more ... | undefined;}>) => QueryBuilder<...> (+3 overloads)
where({ completed?: boolean | { op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>; value: boolean;} | { op: QueryBuilder.WhereOps.MultiValue; value: readonly boolean[];} | undefined
completed: true }),})
const const state: InternalState
state = import State
State.import SQLite
SQLite.const makeState: <{ tables: { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; }; materializers: { ...; };}>(inputSchema: { tables: { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>; }; materializers: { ...; };}) => InternalState
makeState({ tables: { todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Schema.Codec<string, string, never, never>; default: Some<"">; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Schema.Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly deletedAt: { ...; }; }>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;}
tables, materializers: { "v1.TodoCreated": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>>; "v1.TodoCompleted": State.SQLite.Materializer<State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>>; "v1.TodoUncompleted": State.SQLite.Materializer<...>; "v1.TodoDeleted": State.SQLite.Materializer<...>; "v1.TodoClearedCompleted": State.SQLite.Materializer<...>;}
materializers })
export const const schema: FromInputSchema.DeriveSchema<{ events: { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>; todoUncompleted: State.SQLite.EventDef<...>; todoDeleted: State.SQLite.EventDef<...>; todoClearedCompleted: State.SQLite.EventDef<...>; }; state: InternalState;}>
schema = makeSchema<{ events: { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>; todoUncompleted: State.SQLite.EventDef<...>; todoDeleted: State.SQLite.EventDef<...>; todoClearedCompleted: State.SQLite.EventDef<...>; }; state: InternalState;}>(inputSchema: { events: { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>; todoUncompleted: State.SQLite.EventDef<...>; todoDeleted: State.SQLite.EventDef<...>; todoClearedCompleted: State.SQLite.EventDef<...>; }; state: InternalState;}): FromInputSchema.DeriveSchema<...>
makeSchema({ events: { todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly text: Schema.String; }, "Encoded">>; todoCompleted: State.SQLite.EventDef<"v1.TodoCompleted", Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Type">, Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, "Encoded">>; todoUncompleted: State.SQLite.EventDef<...>; todoDeleted: State.SQLite.EventDef<...>; todoClearedCompleted: State.SQLite.EventDef<...>;}
events, state: InternalState
state })Custom worker with authentication
Section titled “Custom worker with authentication”import { const makeDurableObject: MakeDurableObjectClass
Creates a Durable Object class for handling WebSocket-based sync.
A sync Durable Object is uniquely scoped to a specific storeId.
The sync DO supports 3 transport modes:
- HTTP JSON-RPC
- WebSocket
- Durable Object RPC calls (only works in combination with
@livestore/adapter-cf)
Example:
// In your Cloudflare Worker fileimport { makeDurableObject } from '@livestore/sync-cf/cf-worker'
export class SyncBackendDO extends makeDurableObject({ onPush: async (message) => { console.log('onPush', message.batch) }, onPull: async (message) => { console.log('onPull', message) },}) {}
wrangler.toml
[[durable_objects.bindings]]name = "SYNC_BACKEND_DO"class_name = "SyncBackendDO"
[[migrations]]tag = "v1"new_sqlite_classes = ["SyncBackendDO"]
makeDurableObject, const makeWorker: <TEnv extends Env = Env, TDurableObjectRpc extends Rpc.DurableObjectBranded | undefined = undefined, TSyncPayload = Json>(options: MakeWorkerOptions<TEnv, TSyncPayload>) => CFWorker<TEnv, TDurableObjectRpc>
Produces a Cloudflare Worker fetch handler that delegates sync traffic to the
Durable Object identified by syncBackendBinding.
For more complex setups prefer implementing a custom fetch and call
handleSyncRequest
from the branch that handles LiveStore sync requests.
makeWorker } from '@livestore/sync-cf/cf-worker'
export class class SyncBackendDO
SyncBackendDO extends function makeDurableObject(options?: MakeDurableObjectClassOptions): { new (ctx: DoState, env: Env): DoObject<SyncBackendRpcInterface>;}
Creates a Durable Object class for handling WebSocket-based sync.
A sync Durable Object is uniquely scoped to a specific storeId.
The sync DO supports 3 transport modes:
- HTTP JSON-RPC
- WebSocket
- Durable Object RPC calls (only works in combination with
@livestore/adapter-cf)
Example:
// In your Cloudflare Worker fileimport { makeDurableObject } from '@livestore/sync-cf/cf-worker'
export class SyncBackendDO extends makeDurableObject({ onPush: async (message) => { console.log('onPush', message.batch) }, onPull: async (message) => { console.log('onPull', message) },}) {}
wrangler.toml
[[durable_objects.bindings]]name = "SYNC_BACKEND_DO"class_name = "SyncBackendDO"
[[migrations]]tag = "v1"new_sqlite_classes = ["SyncBackendDO"]
makeDurableObject({ onPush?: (message: PushRequest, context: CallbackContext) => SyncOrPromiseOrEffect<void>
onPush: async (message: Struct.ReadonlySide<{ readonly batch: $Array<Struct<{ readonly name: String; readonly args: Any; readonly seqNum: brand<Int, "GlobalEventSequenceNumber">; readonly parentSeqNum: brand<Int, "GlobalEventSequenceNumber">; readonly clientId: String; readonly sessionId: String; }>>; readonly backendId: Option<String>;}, "Type">
message, { storeId: string
storeId }) => { // Log all sync events var console: Console
console.Console.log(...data: any[]): void (+2 overloads)
The console.log() static method outputs a message to the console.
log(`Store ${storeId: string
storeId} received ${message: Struct.ReadonlySide<{ readonly batch: $Array<Struct<{ readonly name: String; readonly args: Any; readonly seqNum: brand<Int, "GlobalEventSequenceNumber">; readonly parentSeqNum: brand<Int, "GlobalEventSequenceNumber">; readonly clientId: String; readonly sessionId: String; }>>; readonly backendId: Option<String>;}, "Type">
message.batch: readonly Struct.ReadonlySide<{ readonly name: String; readonly args: Any; readonly seqNum: brand<Int, "GlobalEventSequenceNumber">; readonly parentSeqNum: brand<Int, "GlobalEventSequenceNumber">; readonly clientId: String; readonly sessionId: String;}, "Type">[]
batch.ReadonlyArray<Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly name: String; readonly args: Any; readonly seqNum: brand<Int, "GlobalEventSequenceNumber">; readonly parentSeqNum: brand<Int, "GlobalEventSequenceNumber">; readonly clientId: String; readonly sessionId: String; }, "Type">>.length: number
Gets the length of the array. This is a number one higher than the highest element defined in an array.
length} events`) },}) {}
const const hasStoreAccess: (_userId: string, _storeId: string) => boolean
hasStoreAccess = (_userId: string
_userId: string, _storeId: string
_storeId: string): boolean => true
export default makeWorker<{ SYNC_BACKEND_DO: any;} & { SYNC_BACKEND_DO: any;}, undefined, Json>(options: MakeWorkerOptions<{ SYNC_BACKEND_DO: any;} & { SYNC_BACKEND_DO: any;}, Json>): CFWorker<{ SYNC_BACKEND_DO: any;} & { SYNC_BACKEND_DO: any;}, undefined>
Produces a Cloudflare Worker fetch handler that delegates sync traffic to the
Durable Object identified by syncBackendBinding.
For more complex setups prefer implementing a custom fetch and call
handleSyncRequest
from the branch that handles LiveStore sync requests.
makeWorker({ syncBackendBinding: "SYNC_BACKEND_DO"
Binding name of the sync Durable Object declared in wrangler config.
syncBackendBinding: 'SYNC_BACKEND_DO', validatePayload?: (payload: Json, context: ValidatePayloadContext) => void | Promise<void>
Validates the (optionally decoded) payload during WebSocket connection establishment.
If
syncPayloadSchema
is provided, payload will be of the schema's inferred type.
The context includes request headers for cookie-based or header-based authentication.
validatePayload: (payload: Json
payload, { storeId: string
storeId }) => { if (!(typeof payload: Json
payload === 'object' && payload: JsonArray | JsonObject | null
payload !== null && 'userId' in payload: JsonArray | JsonObject
payload)) { throw new var Error: ErrorConstructornew (message?: string, options?: ErrorOptions) => Error (+2 overloads)
Error('User ID required') }
// Validate user has access to store if (const hasStoreAccess: (_userId: string, _storeId: string) => boolean
hasStoreAccess((payload: JsonObject
payload as any).any
userId as string, storeId: string
storeId) === false) { throw new var Error: ErrorConstructornew (message?: string, options?: ErrorOptions) => Error (+2 overloads)
Error('Unauthorized access to store') } }, enableCORS?: boolean
enableCORS: true,})Multi-Transport Setup
Section titled “Multi-Transport Setup”import { const makeDurableObject: MakeDurableObjectClass
Creates a Durable Object class for handling WebSocket-based sync.
A sync Durable Object is uniquely scoped to a specific storeId.
The sync DO supports 3 transport modes:
- HTTP JSON-RPC
- WebSocket
- Durable Object RPC calls (only works in combination with
@livestore/adapter-cf)
Example:
// In your Cloudflare Worker fileimport { makeDurableObject } from '@livestore/sync-cf/cf-worker'
export class SyncBackendDO extends makeDurableObject({ onPush: async (message) => { console.log('onPush', message.batch) }, onPull: async (message) => { console.log('onPull', message) },}) {}
wrangler.toml
[[durable_objects.bindings]]name = "SYNC_BACKEND_DO"class_name = "SyncBackendDO"
[[migrations]]tag = "v1"new_sqlite_classes = ["SyncBackendDO"]
makeDurableObject } from '@livestore/sync-cf/cf-worker'
type type Transport = "http" | "ws" | "do-rpc"
Transport = 'http' | 'ws' | 'do-rpc'
const const getTransportFromContext: (ctx: unknown) => Transport
getTransportFromContext = (ctx: unknown
ctx: unknown): type Transport = "http" | "ws" | "do-rpc"
Transport => { if (typeof ctx: unknown
ctx === 'object' && ctx: object | null
ctx !== null && 'transport' in (ctx: object
ctx as any)) { const const t: any
t = (ctx: object
ctx as any).any
transport if (const t: any
t === 'http' || const t: any
t === 'ws' || const t: any
t === 'do-rpc') return const t: any
t } return 'http'}
export class class SyncBackendDO
SyncBackendDO extends function makeDurableObject(options?: MakeDurableObjectClassOptions): { new (ctx: DoState, env: Env): DoObject<SyncBackendRpcInterface>;}
Creates a Durable Object class for handling WebSocket-based sync.
A sync Durable Object is uniquely scoped to a specific storeId.
The sync DO supports 3 transport modes:
- HTTP JSON-RPC
- WebSocket
- Durable Object RPC calls (only works in combination with
@livestore/adapter-cf)
Example:
// In your Cloudflare Worker fileimport { makeDurableObject } from '@livestore/sync-cf/cf-worker'
export class SyncBackendDO extends makeDurableObject({ onPush: async (message) => { console.log('onPush', message.batch) }, onPull: async (message) => { console.log('onPull', message) },}) {}
wrangler.toml
[[durable_objects.bindings]]name = "SYNC_BACKEND_DO"class_name = "SyncBackendDO"
[[migrations]]tag = "v1"new_sqlite_classes = ["SyncBackendDO"]
makeDurableObject({ // Enable all transport modes enabledTransports?: Set<"http" | "ws" | "do-rpc">
Enabled transports for sync backend
http: HTTP JSON-RPC
ws: WebSocket
do-rpc: Durable Object RPC calls (only works in combination with @livestore/adapter-cf)
enabledTransports: new var Set: SetConstructornew <Transport>(iterable?: Iterable<Transport> | null | undefined) => Set<Transport> (+1 overload)
Set<type Transport = "http" | "ws" | "do-rpc"
Transport>(['http', 'ws', 'do-rpc']),
onPush?: (message: PushRequest, context: CallbackContext) => SyncOrPromiseOrEffect<void>
onPush: async (message: Struct.ReadonlySide<{ readonly batch: $Array<Struct<{ readonly name: String; readonly args: Any; readonly seqNum: brand<Int, "GlobalEventSequenceNumber">; readonly parentSeqNum: brand<Int, "GlobalEventSequenceNumber">; readonly clientId: String; readonly sessionId: String; }>>; readonly backendId: Option<String>;}, "Type">
message, context: CallbackContext
context) => { const const transport: Transport
transport = const getTransportFromContext: (ctx: unknown) => Transport
getTransportFromContext(context: CallbackContext
context) var console: Console
console.Console.log(...data: any[]): void (+2 overloads)
The console.log() static method outputs a message to the console.
log(`Push via ${const transport: Transport
transport}:`, message: Struct.ReadonlySide<{ readonly batch: $Array<Struct<{ readonly name: String; readonly args: Any; readonly seqNum: brand<Int, "GlobalEventSequenceNumber">; readonly parentSeqNum: brand<Int, "GlobalEventSequenceNumber">; readonly clientId: String; readonly sessionId: String; }>>; readonly backendId: Option<String>;}, "Type">
message.batch: readonly Struct.ReadonlySide<{ readonly name: String; readonly args: Any; readonly seqNum: brand<Int, "GlobalEventSequenceNumber">; readonly parentSeqNum: brand<Int, "GlobalEventSequenceNumber">; readonly clientId: String; readonly sessionId: String;}, "Type">[]
batch.ReadonlyArray<Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly name: String; readonly args: Any; readonly seqNum: brand<Int, "GlobalEventSequenceNumber">; readonly parentSeqNum: brand<Int, "GlobalEventSequenceNumber">; readonly clientId: String; readonly sessionId: String; }, "Type">>.length: number
Gets the length of the array. This is a number one higher than the highest element defined in an array.
length) },}) {}