Node
Minimal example
Section titled “Minimal example”import { const makeAdapter: ({ sync, ...options }: NodeAdapterOptions & { sync?: SyncOptions;}) => Adapter
Creates a single-threaded LiveStore adapter for Node.js applications.
This adapter runs the leader thread (persistence and sync) in the same thread as
your application. Suitable for CLI tools, scripts, and applications where simplicity
is preferred over maximum performance.
For production servers or performance-critical applications, consider makeWorkerAdapter
which runs persistence/sync in a separate worker thread.
makeAdapter } from '@livestore/adapter-node'
import { import schema
schema, import tables
tables } from './livestore/schema.ts'
const const adapter: Adapter
adapter = function makeAdapter({ sync, ...options }: NodeAdapterOptions & { sync?: SyncOptions;}): Adapter
Creates a single-threaded LiveStore adapter for Node.js applications.
This adapter runs the leader thread (persistence and sync) in the same thread as
your application. Suitable for CLI tools, scripts, and applications where simplicity
is preferred over maximum performance.
For production servers or performance-critical applications, consider makeWorkerAdapter
which runs persistence/sync in a separate worker thread.
makeAdapter({ NodeAdapterOptions.storage: { readonly type: ["in-memory"]; readonly importSnapshot?: any;} | { readonly type: ["fs"]; readonly baseDirectory?: string | undefined;}
storage: { type: string
type: 'fs' }, // sync: { backend: makeWsSync({ url: 'ws://localhost:8787' }) },})
const const main: () => Promise<void>
main = async () => { const const store: Store<any, {}>
store = await createStorePromise<any, {}, Codec<Json, Json, never, never>>({ signal, otelOptions, ...options }: CreateStoreOptionsPromise<any, {}, Codec<Json, Json, never, never>>): Promise<Store<any, {}>>
Create a new LiveStore Store
createStorePromise({ CreateStoreOptions<TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>.adapter: Adapter
Adapter used for data storage and synchronization.
adapter, CreateStoreOptions<any, {}, Codec<Json, Json, never, never>>.schema: any
The LiveStore schema defining tables, events, and materializers.
schema, CreateStoreOptions<TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>.storeId: string
Unique identifier for the Store instance, stable for its lifetime.
- Valid characters: Only alphanumeric characters, underscores (
_), and hyphens (-)
are allowed. Must match /^[a-zA-Z0-9_-]+$/.
- Globally unique: Use globally unique IDs (e.g., nanoid) to prevent collisions across stores.
- Use namespaces: Prefix to avoid collisions and for easier identification when debugging
(e.g.,
app-root, workspace-abc123, issue-456)
storeId: 'demo-store' })
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(import tables
tables.any
todos) var console: Console
console.Console.log(...data: any[]): void (+2 overloads)
The console.log() static method outputs a message to the console.
log(const todos: unknown
todos)}
const main: () => Promise<void>
main().Promise<void>.catch<undefined>(onrejected?: ((reason: any) => PromiseLike<undefined> | undefined) | null | undefined): Promise<void | undefined>
Attaches a callback for only the rejection of the Promise.
catch(() => var undefined
undefined)
import { const makeAdapter: ({ sync, ...options }: NodeAdapterOptions & { sync?: SyncOptions;}) => Adapter
Creates a single-threaded LiveStore adapter for Node.js applications.
This adapter runs the leader thread (persistence and sync) in the same thread as
your application. Suitable for CLI tools, scripts, and applications where simplicity
is preferred over maximum performance.
For production servers or performance-critical applications, consider makeWorkerAdapter
which runs persistence/sync in a separate worker thread.
makeAdapter } from '@livestore/adapter-node'import { const createStorePromise: <TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>({ signal, otelOptions, ...options }: CreateStoreOptionsPromise<TSchema, TContext, TSyncPayloadSchema>) => Promise<Store<TSchema, TContext>>
Create a new LiveStore Store
createStorePromise } from '@livestore/livestore'
import { const schema: FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; }; state: InternalState;}>
schema, const tables: { readonly todos: TableDef<SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, WithDefaults<...>, Struct<...>>;}
tables } from './livestore/schema.ts'
const const adapter: Adapter
adapter = function makeAdapter({ sync, ...options }: NodeAdapterOptions & { sync?: SyncOptions;}): Adapter
Creates a single-threaded LiveStore adapter for Node.js applications.
This adapter runs the leader thread (persistence and sync) in the same thread as
your application. Suitable for CLI tools, scripts, and applications where simplicity
is preferred over maximum performance.
For production servers or performance-critical applications, consider makeWorkerAdapter
which runs persistence/sync in a separate worker thread.
makeAdapter({ NodeAdapterOptions.storage: { readonly type: ["in-memory"]; readonly importSnapshot?: any;} | { readonly type: ["fs"]; readonly baseDirectory?: string | undefined;}
storage: { type: string
type: 'fs' }, // sync: { backend: makeWsSync({ url: 'ws://localhost:8787' }) },})
const const main: () => Promise<void>
main = async () => { const const store: Store<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; }; state: InternalState;}>, {}>
store = await createStorePromise<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; }; state: InternalState;}>, {}, Codec<Json, Json, never, never>>({ signal, otelOptions, ...options }: CreateStoreOptionsPromise<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; }; state: InternalState;}>, {}, Codec<...>>): Promise<...>
Create a new LiveStore Store
createStorePromise({ CreateStoreOptions<TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>.adapter: Adapter
Adapter used for data storage and synchronization.
adapter, CreateStoreOptions<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<...>>; }; state: InternalState; }>, {}, Codec<...>>.schema: FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; }; state: InternalState;}>
The LiveStore schema defining tables, events, and materializers.
schema, CreateStoreOptions<TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>.storeId: string
Unique identifier for the Store instance, stable for its lifetime.
- Valid characters: Only alphanumeric characters, underscores (
_), and hyphens (-)
are allowed. Must match /^[a-zA-Z0-9_-]+$/.
- Globally unique: Use globally unique IDs (e.g., nanoid) to prevent collisions across stores.
- Use namespaces: Prefix to avoid collisions and for easier identification when debugging
(e.g.,
app-root, workspace-abc123, issue-456)
storeId: 'demo-store' })
const const todos: readonly Struct.ReadonlySide<{ readonly id: Codec<string, string, never, never>; readonly text: Codec<string, string, never, never>; readonly completed: Codec<boolean, number, never, never>;}, "Type">[]
todos = const store: Store<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<{ readonly id: String; readonly text: String; }, "Encoded">>; }; state: InternalState;}>, {}>
store.Store<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: String; readonly text: String; }, "Type">, Struct.ReadonlySide<...>>; }; state: InternalState; }>, {}>.query: <readonly Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly id: Codec<string, string, never, never>; readonly text: Codec<string, string, never, never>; readonly completed: Codec<boolean, number, never, never>;}, "Type">[]>(query: Queryable<readonly Struct.ReadonlySide<{ readonly id: Codec<string, string, never, never>; readonly text: Codec<string, string, never, never>; readonly completed: Codec<boolean, number, never, never>;}, "Type">[]> | { query: string; bindValues: Bindable; schema?: Decoder<...>;}, options?: { otelContext?: Context; debugRefreshReason?: RefreshReason;}) => readonly Struct.ReadonlySide<...>[]
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(const tables: { readonly todos: TableDef<SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; }; }>, WithDefaults<...>, Struct<...>>;}
tables.todos: TableDef<SqliteTableDefForInput<"todos", { readonly id: { columnType: "text"; schema: Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: true; autoIncrement: false; }; readonly text: { columnType: "text"; schema: Codec<string, string, never, never>; default: None<never>; nullable: false; primaryKey: false; autoIncrement: false; }; readonly completed: { columnType: "integer"; schema: Codec<boolean, number, never, never>; default: Some<false>; nullable: false; primaryKey: false; autoIncrement: false; };}>, WithDefaults<...>, Struct<...>>
todos) var console: Console
console.Console.log(...data: any[]): void (+2 overloads)
The console.log() static method outputs a message to the console.
log(const todos: readonly Struct.ReadonlySide<{ readonly id: Codec<string, string, never, never>; readonly text: Codec<string, string, never, never>; readonly completed: Codec<boolean, number, never, never>;}, "Type">[]
todos)}
const main: () => Promise<void>
main().Promise<void>.catch<undefined>(onrejected?: ((reason: any) => PromiseLike<undefined> | undefined) | null | undefined): Promise<void | undefined>
Attaches a callback for only the rejection of the Promise.
catch(() => var undefined
undefined)
Option A: Quick start
Section titled “Option A: Quick start”For a quick start, we recommend using our template app following the steps below.
-
Set up project from template
Terminal window bunx @livestore/cli create --example node-todomvc-sync-cf livestore-appTerminal window pnpm dlx @livestore/cli create --example node-todomvc-sync-cf livestore-appTerminal window npx @livestore/cli create --example node-todomvc-sync-cf livestore-appReplace
livestore-appwith your desired app name. -
Install dependencies
It’s strongly recommended to use
bunorpnpmfor the simplest and most reliable dependency setup (see note on package management for more details).Terminal window bun installTerminal window pnpm installTerminal window npm installPro tip: You can use direnv to manage environment variables.
-
Run dev environment
Terminal window bun startTerminal window pnpm startTerminal window npm run start