Skip to content

Cloudflare Durable Object Adapter

The Cloudflare Durable Object adapter enables running LiveStore applications on Cloudflare Workers with stateful Durable Objects for synchronized real-time data.

Terminal window
pnpm add @livestore/adapter-cloudflare @livestore/sync-cf

Configure your wrangler.toml with the required Durable Object bindings:

name = "my-livestore-app"
main = "./src/worker.ts"
compatibility_date = "2025-05-07"
compatibility_flags = [
"enable_request_signal", # Required for HTTP RPC streams
]
[[durable_objects.bindings]]
name = "SYNC_BACKEND_DO"
class_name = "SyncBackendDO"
[[durable_objects.bindings]]
name = "CLIENT_DO"
class_name = "LiveStoreClientDO"
[[migrations]]
tag = "v1"
new_sqlite_classes = ["SyncBackendDO", "LiveStoreClientDO"]
[[d1_databases]]
binding = "DB"
database_name = "my-livestore-db"
database_id = "your-database-id"

Define your Worker bindings so TypeScript can guide you when wiring Durable Objects:

import type {
(alias) interface ClientDoWithRpcCallback
import ClientDoWithRpcCallback
ClientDoWithRpcCallback
} from '@livestore/adapter-cloudflare'
import type {
import CfTypes
CfTypes
,
(alias) interface SyncBackendRpcInterface
import 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;
ADMIN_SECRET: string;
}
Env
= {
type CLIENT_DO: CfTypes.DurableObjectNamespace<ClientDoWithRpcCallback>
CLIENT_DO
:
import CfTypes
CfTypes
.
class DurableObjectNamespace<T extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined>
DurableObjectNamespace
<
(alias) interface ClientDoWithRpcCallback
import 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 SyncBackendRpcInterface
import 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
type ADMIN_SECRET: string
ADMIN_SECRET
: string
}

We also use a small helper to extract the store identifier from incoming requests:

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

This Fetch API interface represents a resource request.

MDN Reference

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

MDN Reference

URL
(
request: CfTypes.Request<unknown, CfTypes.CfProperties<unknown>>
request
.
Request<unknown, CfProperties<unknown>>.url: string

Returns the URL of request as a string.

MDN Reference

url
)
const
const storeId: string | null
storeId
=
const url: URL
url
.
URL.searchParams: URLSearchParams

The searchParams read-only property of the access to the [MISSING: httpmethod('GET')] decoded query arguments contained in the URL.

MDN Reference

searchParams
.
URLSearchParams.get(name: string): string | null

The get() method of the URLSearchParams interface returns the first value associated to the given search parameter.

MDN Reference

get
('storeId')
if (
const storeId: string | null
storeId
=== null) {
throw new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+2 overloads)
Error
('storeId is required in URL search params')
}
return
const storeId: string
storeId
}

The sync backend handles pushing and pulling events between clients:

import * as
import SyncBackend
SyncBackend
from '@livestore/sync-cf/cf-worker'
export class
class SyncBackendDO
SyncBackendDO
extends
import SyncBackend
SyncBackend
.
const makeDurableObject: (options?: SyncBackend.MakeDurableObjectClassOptions) => {
new (ctx: SyncBackend.DoState, env: SyncBackend.Env): SyncBackend.DoObject<SyncBackend.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 file
import { 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
({
// Optional: Handle push events
// onPush: async (message, { storeId }) => {
// console.log(`onPush for store (${storeId})`, message.batch)
// },
}) {}

Each client Durable Object hosts a LiveStore instance and exposes DO RPC callbacks:

import {
class DurableObject<Env = Cloudflare.Env, Props = {}>
DurableObject
} from 'cloudflare:workers'
import { type
(alias) interface ClientDoWithRpcCallback
import ClientDoWithRpcCallback
ClientDoWithRpcCallback
,
const createStoreDoPromise: <TSchema extends LiveStoreSchema, TEnv, TState extends DurableObjectState = DurableObjectState<unknown>>(options: CreateStoreDoOptions<TSchema, TEnv, TState>) => Promise<Store<TSchema, {}>>
createStoreDoPromise
} from '@livestore/adapter-cloudflare'
import {
function nanoid<Type extends string>(size?: number): Type

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"

@paramsize Size of the ID. The default size is 21.

@returnsA random string.

nanoid
, type
class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
Store
, type
type Unsubscribe = () => void
Unsubscribe
} from '@livestore/livestore'
import {
const handleSyncUpdateRpc: (payload: unknown) => Promise<void>

import { DurableObject } from 'cloudflare:workers'
import { ClientDoWithRpcCallback } from '@livestore/common-cf'
export class MyDurableObject extends DurableObject implements ClientDoWithRpcCallback {
// ...
async syncUpdateRpc(payload: RpcMessage.ResponseChunkEncoded) {
return handleSyncUpdateRpc(payload)
}
}

handleSyncUpdateRpc
} from '@livestore/sync-cf/client'
import type {
type Env = {
CLIENT_DO: DurableObjectNamespace<ClientDoWithRpcCallback>;
SYNC_BACKEND_DO: DurableObjectNamespace<SyncBackendRpcInterface>;
DB: D1Database;
ADMIN_SECRET: string;
}
Env
} from './env.ts'
import {
const schema: FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>
schema
,
const tables: {
todos: TableDef<SqliteTableDefForInput<"todos", {
readonly id: {
columnType: "text";
schema: Schema<string, string, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly text: {
columnType: "text";
schema: Schema<string, string, never>;
default: Some<"">;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema<boolean, number, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly deletedAt: {
columnType: "integer";
... 4 more ...;
autoIncrement: false;
};
}>, WithDefaults<...>, Schema<...>>;
}
tables
} from './schema.ts'
import {
const storeIdFromRequest: (request: Request) => string
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
<
type Env = {
CLIENT_DO: DurableObjectNamespace<ClientDoWithRpcCallback>;
SYNC_BACKEND_DO: DurableObjectNamespace<SyncBackendRpcInterface>;
DB: D1Database;
ADMIN_SECRET: string;
}
Env
> implements
(alias) interface ClientDoWithRpcCallback
import ClientDoWithRpcCallback
ClientDoWithRpcCallback
{
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<FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>, {}> | undefined
cachedStore
:
class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
Store
<typeof
const schema: FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>
schema
> | undefined
private
LiveStoreClientDO.storeSubscription: Unsubscribe | undefined
storeSubscription
:
type Unsubscribe = () => void
Unsubscribe
| undefined
private readonly
LiveStoreClientDO.todosQuery: QueryBuilder<readonly {
readonly id: string;
readonly text: string;
readonly deletedAt: Date | null;
readonly completed: boolean;
}[], TableDefBase<SqliteTableDefForInput<"todos", {
readonly id: {
columnType: "text";
schema: Schema<string, string, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly text: {
columnType: "text";
schema: Schema<string, string, never>;
default: Some<"">;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema<boolean, number, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly deletedAt: {
...;
};
}>, WithDefaults<...>>, "select" | ... 3 more ... | "row">
todosQuery
=
const tables: {
todos: TableDef<SqliteTableDefForInput<"todos", {
readonly id: {
columnType: "text";
schema: Schema<string, string, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly text: {
columnType: "text";
schema: Schema<string, string, never>;
default: Some<"">;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema<boolean, number, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly deletedAt: {
...;
};
}>, WithDefaults<...>, Schema<...>>;
}
tables
.
todos: TableDef<SqliteTableDefForInput<"todos", {
readonly id: {
columnType: "text";
schema: Schema<string, string, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly text: {
columnType: "text";
schema: Schema<string, string, never>;
default: Some<"">;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema<boolean, number, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly deletedAt: {
...;
};
}>, WithDefaults<...>, Schema<...>>
todos
.
select: <"id" | "text" | "deletedAt" | "completed">(...columns: ("id" | "text" | "deletedAt" | "completed")[]) => QueryBuilder<readonly {
readonly id: string;
readonly text: string;
readonly deletedAt: Date | null;
readonly completed: boolean;
}[], TableDefBase<SqliteTableDefForInput<"todos", {
readonly id: {
columnType: "text";
schema: Schema<string, string, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly text: {
columnType: "text";
schema: Schema<string, string, never>;
default: Some<"">;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
...;
};
readonly deletedAt: {
...;
};
}>, WithDefaults<...>>, "select" | ... 3 more ... | "row"> (+1 overload)

Select multiple columns

select
()
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.

MDN Reference

This Fetch API interface represents a resource request.

MDN Reference

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.

MDN Reference

This Fetch API interface represents the response to a request.

MDN Reference

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
=
function storeIdFromRequest(request: Request): string
storeIdFromRequest
(
request: Request<unknown, CfProperties<unknown>>
request
)
const
const store: Store<FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>, {}>
store
= await this.
LiveStoreClientDO.getStore(): Promise<Store<FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>, {}>>
getStore
()
await this.
LiveStoreClientDO.subscribeToStore(): Promise<void>
subscribeToStore
()
const
const todos: readonly {
readonly id: string;
readonly text: string;
readonly deletedAt: Date | null;
readonly completed: boolean;
}[]
todos
=
const store: Store<FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>, {}>
store
.
Store<FromInputSchema.DeriveSchema<{ events: { todoCreated: EventDef<"v1.TodoCreated", { readonly id: string; readonly text: string; }, { readonly id: string; readonly text: string; }, false>; todoCompleted: EventDef<...>; todoUncompleted: EventDef<...>; todoDeleted: EventDef<...>; todoClearedCompleted: EventDef<...>; }; state: InternalState; }>, {}>.query: <readonly {
readonly id: string;
readonly text: string;
readonly deletedAt: Date | null;
readonly completed: boolean;
}[]>(query: Queryable<readonly {
readonly id: string;
readonly text: string;
readonly deletedAt: Date | null;
readonly completed: boolean;
}[]> | {
query: string;
bindValues: Bindable;
schema?: Schema<readonly {
readonly id: string;
readonly text: string;
readonly deletedAt: Date | null;
readonly completed: boolean;
}[], readonly {
readonly id: string;
readonly text: string;
readonly deletedAt: Date | null;
readonly completed: boolean;
}[], never>;
}, options?: {
otelContext?: Context;
debugRefreshReason?: RefreshReason;
}) => readonly {
readonly id: string;
readonly text: string;
readonly deletedAt: Date | null;
readonly completed: boolean;
}[]

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: QueryBuilder<readonly {
readonly id: string;
readonly text: string;
readonly deletedAt: Date | null;
readonly completed: boolean;
}[], TableDefBase<SqliteTableDefForInput<"todos", {
readonly id: {
columnType: "text";
schema: Schema<string, string, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly text: {
columnType: "text";
schema: Schema<string, string, never>;
default: Some<"">;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema<boolean, number, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly deletedAt: {
...;
};
}>, WithDefaults<...>>, "select" | ... 3 more ... | "row">
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.

MDN Reference

This Fetch API interface represents the response to a request.

MDN Reference

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.

@paramvalue A JavaScript value, usually an object or array, to be converted.

@paramreplacer An array of strings and numbers that acts as an approved list for selecting the object properties that will be stringified.

@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.

@throws{TypeError} If a circular reference or a BigInt value is found.

stringify
(
const todos: readonly {
readonly id: string;
readonly text: string;
readonly deletedAt: Date | null;
readonly completed: boolean;
}[]
todos
, null, 2), {
ResponseInit.headers?: HeadersInit
headers
: { 'Content-Type': 'application/json' },
})
}
private async
LiveStoreClientDO.getStore(): Promise<Store<FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>, {}>>
getStore
() {
if (this.
LiveStoreClientDO.cachedStore: Store<FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>, {}> | undefined
cachedStore
!==
var undefined
undefined
) {
return this.
LiveStoreClientDO.cachedStore: Store<FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>, {}>
cachedStore
}
const
const storeId: string
storeId
= this.
LiveStoreClientDO.storeId: string | undefined
storeId
??
nanoid<string>(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"

@paramsize Size of the ID. The default size is 21.

@returnsA random string.

nanoid
()
const
const store: Store<FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>, {}>
store
= await
createStoreDoPromise<FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>, Env, DurableObjectState<...>>(options: CreateStoreDoOptions<...>): Promise<...>
createStoreDoPromise
({
schema: FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>

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
:
nanoid<string>(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"

@paramsize Size of the ID. The default size is 21.

@returnsA random string.

nanoid
(),
durableObject: {
ctx: DurableObjectState<unknown>;
env: Env;
bindingName: "CLIENT_DO" | "SYNC_BACKEND_DO";
}

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: "CLIENT_DO" | "SYNC_BACKEND_DO"

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
.
type SYNC_BACKEND_DO: DurableObjectNamespace<SyncBackendRpcInterface>
SYNC_BACKEND_DO
.
DurableObjectNamespace<SyncBackendRpcInterface>.get(id: DurableObjectId, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub<SyncBackendRpcInterface>
get
(this.
CloudflareWorkersModule.DurableObject<Env, {}>.env: Env
env
.
type SYNC_BACKEND_DO: DurableObjectNamespace<SyncBackendRpcInterface>
SYNC_BACKEND_DO
.
DurableObjectNamespace<SyncBackendRpcInterface>.idFromName(name: string): DurableObjectId
idFromName
(
const storeId: string
storeId
)),
livePull?: boolean

Enables live pull mode to receive sync updates via Durable Object RPC callbacks.

@defaultfalse

livePull
: true,
})
this.
LiveStoreClientDO.cachedStore: Store<FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>, {}> | undefined
cachedStore
=
const store: Store<FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>, {}>
store
return
const store: Store<FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>, {}>
store
}
private async
LiveStoreClientDO.subscribeToStore(): Promise<void>
subscribeToStore
() {
const
const store: Store<FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>, {}>
store
= await this.
LiveStoreClientDO.getStore(): Promise<Store<FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>, {}>>
getStore
()
if (this.
LiveStoreClientDO.storeSubscription: Unsubscribe | undefined
storeSubscription
===
var undefined
undefined
) {
this.
LiveStoreClientDO.storeSubscription: Unsubscribe | undefined
storeSubscription
=
const store: Store<FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>, {}>
store
.
Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>.subscribe: <readonly {
readonly id: string;
readonly text: string;
readonly deletedAt: Date | null;
readonly completed: boolean;
}[]>(query: Queryable<readonly {
readonly id: string;
readonly text: string;
readonly deletedAt: Date | null;
readonly completed: boolean;
}[]>, onUpdate: (value: readonly {
readonly id: string;
readonly text: string;
readonly deletedAt: Date | null;
readonly completed: boolean;
}[]) => void, options?: SubscribeOptions<readonly {
readonly id: string;
readonly text: string;
readonly deletedAt: Date | null;
readonly completed: boolean;
}[]> | undefined) => Unsubscribe (+1 overload)
subscribe
(this.
LiveStoreClientDO.todosQuery: QueryBuilder<readonly {
readonly id: string;
readonly text: string;
readonly deletedAt: Date | null;
readonly completed: boolean;
}[], TableDefBase<SqliteTableDefForInput<"todos", {
readonly id: {
columnType: "text";
schema: Schema<string, string, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly text: {
columnType: "text";
schema: Schema<string, string, never>;
default: Some<"">;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema<boolean, number, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly deletedAt: {
...;
};
}>, WithDefaults<...>>, "select" | ... 3 more ... | "row">
todosQuery
, (
todos: readonly {
readonly id: string;
readonly text: string;
readonly completed: boolean;
readonly deletedAt: Date | null;
}[]
todos
:
interface ReadonlyArray<T>
ReadonlyArray
<typeof
const tables: {
todos: TableDef<SqliteTableDefForInput<"todos", {
readonly id: {
columnType: "text";
schema: Schema<string, string, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly text: {
columnType: "text";
schema: Schema<string, string, never>;
default: Some<"">;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema<boolean, number, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly deletedAt: {
...;
};
}>, WithDefaults<...>, Schema<...>>;
}
tables
.
todos: TableDef<SqliteTableDefForInput<"todos", {
readonly id: {
columnType: "text";
schema: Schema<string, string, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly text: {
columnType: "text";
schema: Schema<string, string, never>;
default: Some<"">;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema<boolean, number, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly deletedAt: {
...;
};
}>, WithDefaults<...>, Schema<...>>
todos
.
type Type: {
readonly id: string;
readonly text: string;
readonly completed: boolean;
readonly deletedAt: Date | null;
}
Type
>) => {
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void (+3 overloads)

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
(`todos for store (${this.
LiveStoreClientDO.storeId: string | undefined
storeId
})`,
todos: readonly {
readonly id: string;
readonly text: string;
readonly completed: boolean;
readonly deletedAt: Date | null;
}[]
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)
}
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: unknown): Promise<void>
syncUpdateRpc
(
payload: unknown
payload
: unknown) {
await
function handleSyncUpdateRpc(payload: unknown): Promise<void>

import { DurableObject } from 'cloudflare:workers'
import { ClientDoWithRpcCallback } from '@livestore/common-cf'
export class MyDurableObject extends DurableObject implements ClientDoWithRpcCallback {
// ...
async syncUpdateRpc(payload: RpcMessage.ResponseChunkEncoded) {
return handleSyncUpdateRpc(payload)
}
}

handleSyncUpdateRpc
(
payload: unknown
payload
)
}
}

The worker routes incoming requests either to the sync backend or to the client Durable Object:

import type {
import CfTypes
CfTypes
} from '@livestore/sync-cf/cf-worker'
import * as
import SyncBackend
SyncBackend
from '@livestore/sync-cf/cf-worker'
import type {
type Env = {
CLIENT_DO: CfTypes.DurableObjectNamespace<ClientDoWithRpcCallback>;
SYNC_BACKEND_DO: CfTypes.DurableObjectNamespace<SyncBackend.SyncBackendRpcInterface>;
DB: CfTypes.D1Database;
ADMIN_SECRET: string;
}
Env
} from './env.ts'
import {
const storeIdFromRequest: (request: CfTypes.Request) => string
storeIdFromRequest
} from './shared.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>>

This Fetch API interface represents a resource request.

MDN Reference

Request
,
env: Env
env
:
type Env = {
CLIENT_DO: CfTypes.DurableObjectNamespace<ClientDoWithRpcCallback>;
SYNC_BACKEND_DO: CfTypes.DurableObjectNamespace<SyncBackend.SyncBackendRpcInterface>;
DB: CfTypes.D1Database;
ADMIN_SECRET: string;
}
Env
,
ctx: CfTypes.ExecutionContext<unknown>
ctx
:
import CfTypes
CfTypes
.
interface ExecutionContext<Props = unknown>
ExecutionContext
) => {
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 URL.

MDN Reference

URL
(
request: CfTypes.Request<unknown, CfTypes.CfProperties<unknown>>
request
.
Request<unknown, CfProperties<unknown>>.url: string

Returns the URL of request as a string.

MDN Reference

url
)
const
const searchParams: {
readonly storeId: string;
readonly payload: JsonValue | undefined;
readonly transport: "http" | "ws";
} | undefined
searchParams
=
import SyncBackend
SyncBackend
.
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
(
request: CfTypes.Request<unknown, CfTypes.CfProperties<unknown>>
request
)
if (
const searchParams: {
readonly storeId: string;
readonly payload: JsonValue | undefined;
readonly transport: "http" | "ws";
} | undefined
searchParams
!==
var undefined
undefined
) {
return
import SyncBackend
SyncBackend
.
const handleSyncRequest: <Env, undefined, unknown, JsonValue>({ request, searchParams: { storeId, payload, transport }, env: explicitlyProvidedEnv, syncBackendBinding, headers, validatePayload, syncPayloadSchema, }: {
request: CfTypes.Request<unknown, CfTypes.CfProperties<unknown>>;
searchParams: SearchParams;
env?: Env | undefined;
ctx: CfTypes.ExecutionContext;
syncBackendBinding: "SYNC_BACKEND_DO" | "CLIENT_DO";
headers?: CfTypes.HeadersInit | undefined;
validatePayload?: ((payload: JsonValue, context: {
storeId: string;
}) => void | Promise<void>) | undefined;
syncPayloadSchema?: Schema<JsonValue, JsonValue, never> | undefined;
}) => Promise<CfTypes.Response>

Handles LiveStore sync requests (e.g. with search params ?storeId=...&transport=...).

@example

const validatePayload = (payload: Schema.JsonValue | undefined, context: { storeId: string }) => {
console.log(`Validating connection for store: ${context.storeId}`)
if (payload?.authToken !== 'insecure-token-change-me') {
throw new Error('Invalid auth token')
}
}
export default {
fetch: async (request, env, ctx) => {
const searchParams = matchSyncRequest(request)
// Is LiveStore sync request
if (searchParams !== undefined) {
return handleSyncRequest({
request,
searchParams,
env,
ctx,
syncBackendBinding: 'SYNC_BACKEND_DO',
headers: {},
validatePayload,
})
}
return new Response('Invalid path', { status: 400 })
}
}

@throws{UnexpectedError} If the payload is invalid

handleSyncRequest
({
request: CfTypes.Request<unknown, CfTypes.CfProperties<unknown>>
request
,
searchParams: {
readonly storeId: string;
readonly payload: JsonValue | undefined;
readonly transport: "http" | "ws";
}
searchParams
,
env?: Env | undefined
env
,
ctx: CfTypes.ExecutionContext<unknown>

Only there for type-level reasons

ctx
,
syncBackendBinding: "SYNC_BACKEND_DO" | "CLIENT_DO"

Binding name of the sync backend Durable Object

syncBackendBinding
: 'SYNC_BACKEND_DO',
headers?: CfTypes.HeadersInit | undefined
headers
: {},
})
}
if (
const url: URL
url
.
URL.pathname: string

The pathname property of the URL interface represents a location in a hierarchical structure.

MDN Reference

pathname
.
String.endsWith(searchString: string, endPosition?: number): boolean

Returns true if the sequence of elements of searchString converted to a String is the same as the corresponding elements of this object (converted to a String) starting at endPosition – length(this). Otherwise returns false.

endsWith
('/client-do')) {
const
const storeId: string
storeId
=
function storeIdFromRequest(request: CfTypes.Request): string
storeIdFromRequest
(
request: CfTypes.Request<unknown, CfTypes.CfProperties<unknown>>
request
)
const
const id: CfTypes.DurableObjectId
id
=
env: Env
env
.
type CLIENT_DO: CfTypes.DurableObjectNamespace<ClientDoWithRpcCallback>
CLIENT_DO
.
DurableObjectNamespace<ClientDoWithRpcCallback>.idFromName(name: string): CfTypes.DurableObjectId
idFromName
(
const storeId: string
storeId
)
return
env: Env
env
.
type CLIENT_DO: CfTypes.DurableObjectNamespace<ClientDoWithRpcCallback>
CLIENT_DO
.
DurableObjectNamespace<ClientDoWithRpcCallback>.get(id: CfTypes.DurableObjectId, options?: CfTypes.DurableObjectNamespaceGetDurableObjectOptions): CfTypes.DurableObjectStub<ClientDoWithRpcCallback>
get
(
const id: CfTypes.DurableObjectId
id
).
function fetch(input: CfTypes.RequestInfo | CfTypes.URL, init?: CfTypes.RequestInit): Promise<CfTypes.Response>
fetch
(
request: CfTypes.Request<unknown, CfTypes.CfProperties<unknown>>
request
)
}
return new
var Response: new (body?: BodyInit | null, init?: ResponseInit) => Response

The Response interface of the Fetch API represents the response to a request.

MDN Reference

This Fetch API interface represents the response to a request.

MDN Reference

Response
('Not found', {
ResponseInit.status?: number
status
: 404 }) as unknown as
import CfTypes
CfTypes
.
interface Response

This Fetch API interface represents the response to a request.

MDN Reference

Response
},
} satisfies
import SyncBackend
SyncBackend
.
type CFWorker<TEnv extends SyncBackend.Env = SyncBackend.Env, _T extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined> = {
fetch: <CFHostMetada = unknown>(request: CfTypes.Request<CFHostMetada>, env: TEnv, ctx: CfTypes.ExecutionContext) => Promise<CfTypes.Response>;
}
CFWorker
<
type Env = {
CLIENT_DO: CfTypes.DurableObjectNamespace<ClientDoWithRpcCallback>;
SYNC_BACKEND_DO: CfTypes.DurableObjectNamespace<SyncBackend.SyncBackendRpcInterface>;
DB: CfTypes.D1Database;
ADMIN_SECRET: string;
}
Env
>

Creates a LiveStore instance inside a Durable Object.

Options:

  • schema – LiveStore schema definition
  • storeId – Unique identifier for the store
  • clientId – Client identifier
  • sessionId – Session identifier (use nanoid())
  • durableObject – Context about the Durable Object hosting the store:
    • state – Durable Object state handle (for example this.ctx)
    • env – Environment bindings for the Durable Object
    • bindingName – Name other workers use to reach this Durable Object
  • syncBackendStub – Durable Object stub used to reach the sync backend
  • livePull – Enable real-time updates (default: false)
  • resetPersistence – Drop LiveStore state/eventlog persistence before booting (development only, default: false)

Client Durable Objects must implement this method so the sync backend can deliver live updates. createStoreDoPromise wires it up automatically—just forward the payload to handleSyncUpdateRpc (see the client Durable Object example above).

Resetting LiveStore persistence (development only)

Section titled “Resetting LiveStore persistence (development only)”

When iterating locally, you can instruct the adapter to wipe the Durable Object’s LiveStore databases before booting by enabling resetPersistence. Guard this behind a protected route or admin token.

import {
const createStoreDoPromise: <TSchema extends LiveStoreSchema, TEnv, TState extends CfTypes.DurableObjectState = CfTypes.DurableObjectState<unknown>>(options: CreateStoreDoOptions<TSchema, TEnv, TState>) => Promise<Store<TSchema, {}>>
createStoreDoPromise
} from '@livestore/adapter-cloudflare'
import {
function nanoid<Type extends string>(size?: number): Type

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"

@paramsize Size of the ID. The default size is 21.

@returnsA random string.

nanoid
} from '@livestore/livestore'
import type {
import CfTypes
CfTypes
} from '@livestore/sync-cf/cf-worker'
import type {
type Env = {
CLIENT_DO: CfTypes.DurableObjectNamespace<ClientDoWithRpcCallback>;
SYNC_BACKEND_DO: CfTypes.DurableObjectNamespace<SyncBackendRpcInterface>;
DB: CfTypes.D1Database;
ADMIN_SECRET: string;
}
Env
} from './env.ts'
import {
const schema: FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>
schema
} from './schema.ts'
export const
const maybeResetStore: ({ request, env, ctx, }: {
request: Request;
env: Env;
ctx: CfTypes.DurableObjectState;
}) => Promise<Store<FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
...;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>, {}>>
maybeResetStore
= async ({
request: Request<unknown, CfProperties<unknown>>
request
,
env: Env
env
,
ctx: CfTypes.DurableObjectState<unknown>
ctx
,
}: {
request: Request<unknown, CfProperties<unknown>>
request
:
interface Request<CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>>

The Request interface of the Fetch API represents a resource request.

MDN Reference

This Fetch API interface represents a resource request.

MDN Reference

Request
env: Env
env
:
type Env = {
CLIENT_DO: CfTypes.DurableObjectNamespace<ClientDoWithRpcCallback>;
SYNC_BACKEND_DO: CfTypes.DurableObjectNamespace<SyncBackendRpcInterface>;
DB: CfTypes.D1Database;
ADMIN_SECRET: string;
}
Env
ctx: CfTypes.DurableObjectState<unknown>
ctx
:
import CfTypes
CfTypes
.
interface DurableObjectState<Props = unknown>
DurableObjectState
}) => {
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 URL.

MDN Reference

URL
(
request: Request<unknown, CfProperties<unknown>>
request
.
Request<unknown, CfProperties<unknown>>.url: string

The url read-only property of the Request interface contains the URL of the request.

MDN Reference

Returns the URL of request as a string.

MDN Reference

url
)
const
const shouldReset: boolean
shouldReset
=
env: Env
env
.
type ADMIN_SECRET: string
ADMIN_SECRET
===
const url: URL
url
.
URL.searchParams: URLSearchParams

The searchParams read-only property of the access to the [MISSING: httpmethod('GET')] decoded query arguments contained in the URL.

MDN Reference

searchParams
.
URLSearchParams.get(name: string): string | null

The get() method of the URLSearchParams interface returns the first value associated to the given search parameter.

MDN Reference

get
('token') &&
const url: URL
url
.
URL.pathname: string

The pathname property of the URL interface represents a location in a hierarchical structure.

MDN Reference

pathname
=== '/internal/livestore-dev-reset'
const
const storeId: string
storeId
=
const url: URL
url
.
URL.searchParams: URLSearchParams

The searchParams read-only property of the access to the [MISSING: httpmethod('GET')] decoded query arguments contained in the URL.

MDN Reference

searchParams
.
URLSearchParams.get(name: string): string | null

The get() method of the URLSearchParams interface returns the first value associated to the given search parameter.

MDN Reference

get
('storeId') ??
nanoid<string>(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"

@paramsize Size of the ID. The default size is 21.

@returnsA random string.

nanoid
()
const
const store: Store<FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>, {}>
store
= await
createStoreDoPromise<FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>, Env, CfTypes.DurableObjectState<...>>(options: CreateStoreDoOptions<...>): Promise<...>
createStoreDoPromise
({
schema: FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>

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
:
nanoid<string>(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"

@paramsize Size of the ID. The default size is 21.

@returnsA random string.

nanoid
(),
durableObject: {
ctx: CfTypes.DurableObjectState<unknown>;
env: Env;
bindingName: "CLIENT_DO" | "SYNC_BACKEND_DO";
}

Runtime details about the Durable Object this store runs inside. Needed for sync backend to call back to this instance.

durableObject
: {
ctx: CfTypes.DurableObjectState<unknown>

Durable Object state handle (e.g. this.ctx).

ctx
,
env: Env

Environment bindings associated with the Durable Object.

env
,
bindingName: "CLIENT_DO" | "SYNC_BACKEND_DO"

Binding name Cloudflare uses to reach this Durable Object from other workers.

bindingName
: 'CLIENT_DO' },
syncBackendStub: CfTypes.DurableObjectStub<SyncBackendRpcInterface>

RPC stub pointing at the sync backend Durable Object used for replication.

syncBackendStub
:
env: Env
env
.
type SYNC_BACKEND_DO: CfTypes.DurableObjectNamespace<SyncBackendRpcInterface>
SYNC_BACKEND_DO
.
DurableObjectNamespace<SyncBackendRpcInterface>.get(id: CfTypes.DurableObjectId, options?: CfTypes.DurableObjectNamespaceGetDurableObjectOptions): CfTypes.DurableObjectStub<SyncBackendRpcInterface>
get
(
env: Env
env
.
type SYNC_BACKEND_DO: CfTypes.DurableObjectNamespace<SyncBackendRpcInterface>
SYNC_BACKEND_DO
.
DurableObjectNamespace<SyncBackendRpcInterface>.idFromName(name: string): CfTypes.DurableObjectId
idFromName
(
const storeId: string
storeId
)),
livePull?: boolean

Enables live pull mode to receive sync updates via Durable Object RPC callbacks.

@defaultfalse

livePull
: true,
resetPersistence?: boolean

Clears existing Durable Object persistence before bootstrapping the store.

Note: Only use this for development purposes.

resetPersistence
:
const shouldReset: boolean
shouldReset
,
})
return
const store: Store<FromInputSchema.DeriveSchema<{
events: {
todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
todoCompleted: EventDef<"v1.TodoCompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoUncompleted: EventDef<"v1.TodoUncompleted", {
readonly id: string;
}, {
readonly id: string;
}, false>;
todoDeleted: EventDef<"v1.TodoDeleted", {
readonly id: string;
readonly deletedAt: Date;
}, {
readonly id: string;
readonly deletedAt: string;
}, false>;
todoClearedCompleted: EventDef<...>;
};
state: InternalState;
}>, {}>
store
}
  • Use livePull: true to receive push-based updates via Durable Object RPC callbacks.
  • Subscribe to data changes inside the Durable Object to trigger side effects (see the client Durable Object example).
  • Wire additional routes in the worker fetch handler to expose debugging endpoints or admin operations.

For sync backend-related APIs like makeDurableObject, handleSyncRequest, and matchSyncRequest, see the Cloudflare sync provider documentation.