Skip to content

Store

The Store is the most common way to interact with LiveStore from your application code. It provides a way to query data, commit events, and subscribe to data changes.

For how to create a store in React, see the React integration docs. The following example shows how to create a store manually:

import {
const makeAdapter: ({ sync, ...options }: NodeAdapterOptions & {
sync?: SyncOptions;
}) => Adapter

Runs everything in the same thread. Use makeWorkerAdapter for multi-threaded implementation.

makeAdapter
} from '@livestore/adapter-node'
import {
const createStorePromise: <TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}, TSyncPayloadSchema extends Schema<any> = Schema<JsonValue, JsonValue, never>>({ signal, otelOptions, ...options }: CreateStoreOptions<TSchema, TContext, TSyncPayloadSchema> & {
signal?: AbortSignal;
otelOptions?: Partial<OtelOptions>;
}) => Promise<Store<TSchema, TContext>>

Create a new LiveStore Store

createStorePromise
} from '@livestore/livestore'
import {
const schema: FromInputSchema.DeriveSchema<{
events: {
readonly todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
};
state: InternalState;
}>
schema
} from './schema.ts'
const
const adapter: Adapter
adapter
=
function makeAdapter({ sync, ...options }: NodeAdapterOptions & {
sync?: SyncOptions;
}): Adapter

Runs everything in the same thread. Use makeWorkerAdapter for multi-threaded implementation.

makeAdapter
({
NodeAdapterOptions.storage: {
readonly type: "in-memory";
readonly importSnapshot?: Uint8Array<ArrayBuffer> | undefined;
} | {
readonly type: "fs";
readonly baseDirectory?: string | undefined;
}
storage
: {
type: "fs"
type
: 'fs' },
// sync: { backend: makeWsSync({ url: '...' }) },
})
export const
const bootstrap: () => Promise<Store<FromInputSchema.DeriveSchema<{
events: {
readonly todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
};
state: InternalState;
}>, {}>>
bootstrap
= async () => {
const
const store: Store<FromInputSchema.DeriveSchema<{
events: {
readonly todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
};
state: InternalState;
}>, {}>
store
= await
createStorePromise<FromInputSchema.DeriveSchema<{
events: {
readonly todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
};
state: InternalState;
}>, {}, Schema<JsonValue, JsonValue, never>>({ signal, otelOptions, ...options }: CreateStoreOptions<FromInputSchema.DeriveSchema<{
events: {
readonly todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
};
state: InternalState;
}>, {}, Schema<...>> & {
...;
}): Promise<...>

Create a new LiveStore Store

createStorePromise
({
CreateStoreOptions<FromInputSchema.DeriveSchema<{ events: { readonly todoCreated: EventDef<"v1.TodoCreated", { readonly id: string; readonly text: string; }, { readonly id: string; readonly text: string; }, false>; }; state: InternalState; }>, {}, Schema<...>>.schema: FromInputSchema.DeriveSchema<{
events: {
readonly todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
};
state: InternalState;
}>
schema
,
CreateStoreOptions<TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Schema<any> = Schema<JsonValue, JsonValue, never>>.adapter: Adapter
adapter
,
CreateStoreOptions<TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Schema<any> = Schema<JsonValue, JsonValue, never>>.storeId: string
storeId
: 'some-store-id',
})
return
const store: Store<FromInputSchema.DeriveSchema<{
events: {
readonly todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
};
state: InternalState;
}>, {}>
store
}
import type {
class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
Store
} from '@livestore/livestore'
declare const
const store: Store<LiveStoreSchema.Any, {}>
store
:
class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
Store
const
const todos: readonly {
readonly id: string;
readonly text: string;
readonly completed: boolean;
}[]
todos
=
const store: Store<LiveStoreSchema.Any, {}>
store
.
Store<LiveStoreSchema<TDbSchema extends DbSchema = DbSchema, TEventsDefRecord extends EventDefRecord = EventDefRecord>.Any, {}>.query: <readonly {
readonly id: string;
readonly text: string;
readonly completed: boolean;
}[]>(query: Queryable<readonly {
readonly id: string;
readonly text: string;
readonly completed: boolean;
}[]> | {
query: string;
bindValues: Bindable;
schema?: Schema<readonly {
readonly id: string;
readonly text: string;
readonly completed: boolean;
}[], readonly {
readonly id: string;
readonly text: string;
readonly completed: boolean;
}[], never>;
}, options?: {
otelContext?: Context;
debugRefreshReason?: RefreshReason;
}) => readonly {
readonly id: string;
readonly text: string;
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
(
const storeTables: {
readonly 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: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema<boolean, number, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, WithDefaults<...>, Schema<...>>;
}
storeTables
.
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: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema<boolean, number, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, WithDefaults<...>, Schema<...>>
todos
)
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
(
const todos: readonly {
readonly id: string;
readonly text: string;
readonly completed: boolean;
}[]
todos
)
import type {
class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
Store
} from '@livestore/livestore'
import {
const storeTables: {
readonly 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: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema<boolean, number, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, WithDefaults<...>, Schema<...>>;
}
storeTables
} from './schema.ts'
declare const
const store: Store<LiveStoreSchema.Any, {}>
store
:
class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
Store
const
const unsubscribe: Unsubscribe
unsubscribe
=
const store: Store<LiveStoreSchema.Any, {}>
store
.
Store<LiveStoreSchema<TDbSchema extends DbSchema = DbSchema, TEventsDefRecord extends EventDefRecord = EventDefRecord>.Any, {}>.subscribe: <readonly {
readonly id: string;
readonly text: string;
readonly completed: boolean;
}[]>(query: Queryable<readonly {
readonly id: string;
readonly text: string;
readonly completed: boolean;
}[]>, onUpdate: (value: readonly {
readonly id: string;
readonly text: string;
readonly completed: boolean;
}[]) => void, options?: SubscribeOptions<readonly {
readonly id: string;
readonly text: string;
readonly completed: boolean;
}[]> | undefined) => Unsubscribe (+1 overload)
subscribe
(
const storeTables: {
readonly 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: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema<boolean, number, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, WithDefaults<...>, Schema<...>>;
}
storeTables
.
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: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema<boolean, number, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
}>, WithDefaults<...>, Schema<...>>
todos
, (
todos: readonly {
readonly id: string;
readonly text: string;
readonly completed: boolean;
}[]
todos
) => {
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: readonly {
readonly id: string;
readonly text: string;
readonly completed: boolean;
}[]
todos
)
})
const unsubscribe: () => void
unsubscribe
()
import type {
class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
Store
} from '@livestore/livestore'
import {
const storeEvents: {
readonly todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
}
storeEvents
} from './schema.ts'
declare const
const store: Store<LiveStoreSchema.Any, {}>
store
:
class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
Store
const store: Store<LiveStoreSchema.Any, {}>
store
.
Store<LiveStoreSchema<TDbSchema extends DbSchema = DbSchema, TEventsDefRecord extends EventDefRecord = EventDefRecord>.Any, {}>.commit: <readonly [{
name: "v1.TodoCreated";
args: {
readonly id: string;
readonly text: string;
};
}]>(list_0: {
name: "v1.TodoCreated";
args: {
readonly id: string;
readonly text: string;
};
}) => void (+3 overloads)
commit
(
const storeEvents: {
readonly todoCreated: EventDef<"v1.TodoCreated", {
readonly id: string;
readonly text: string;
}, {
readonly id: string;
readonly text: string;
}, false>;
}
storeEvents
.
todoCreated: (args: {
readonly id: string;
readonly text: string;
}) => {
name: "v1.TodoCreated";
args: {
readonly id: string;
readonly text: string;
};
}

Helper function to construct a partial event

todoCreated
({
id: string
id
: '1',
text: string
text
: 'Buy milk' }))

LiveStore provides two APIs for shutting down a store:

import type {
class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
Store
} from '@livestore/livestore'
declare const
const store: Store<LiveStoreSchema.Any, {}>
store
:
class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>
Store
const
const effectShutdown: Effect.Effect<void, never, never>
effectShutdown
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const gen: <YieldWrap<Effect.Effect<void, never, never>>, void>(f: (resume: Effect.Adapter) => Generator<YieldWrap<Effect.Effect<void, never, never>>, void, never>) => Effect.Effect<void, never, never> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to Use

Effect.gen allows you to write code that looks and behaves like synchronous code, but it can handle asynchronous tasks, errors, and complex control flow (like loops and conditions). It helps make asynchronous code more readable and easier to manage.

The generator functions work similarly to async/await but with more explicit control over the execution of effects. You can yield* values from effects and return the final result at the end.

Example

import { Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function* () {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})

@since2.0.0

gen
(function* () {
yield*
const store: Store<LiveStoreSchema.Any, {}>
store
.
Store<LiveStoreSchema<TDbSchema extends DbSchema = DbSchema, TEventsDefRecord extends EventDefRecord = EventDefRecord>.Any, {}>.shutdown: (cause?: Cause<UnexpectedError | MaterializeError>) => Effect.Effect<void>

Shuts down the store and closes the client session.

This is called automatically when the store was created using the React or Effect API.

shutdown
()
})
const
const shutdownWithPromise: () => Promise<void>
shutdownWithPromise
= async () => {
await
const store: Store<LiveStoreSchema.Any, {}>
store
.
Store<LiveStoreSchema<TDbSchema extends DbSchema = DbSchema, TEventsDefRecord extends EventDefRecord = EventDefRecord>.Any, {}>.shutdownPromise: (cause?: UnexpectedError) => Promise<void>

Shuts down the store and closes the client session.

This is called automatically when the store was created using the React or Effect API.

shutdownPromise
()
}

You can create and use multiple stores in the same app. This can be useful when breaking up your data model into smaller pieces.

A store instance also exposes a _dev property that contains some helpful methods for development. For convenience you can access a store on globalThis/window like via __debugLiveStore.default._dev (default is the store id):

// Download the SQLite database
__debugLiveStore.default._dev.downloadDb()
// Download the eventlog database
__debugLiveStore.default._dev.downloadEventlogDb()
// Reset the store
__debugLiveStore.default._dev.hardReset()
// See the current sync state
__debugLiveStore.default._dev.syncStates()