Skip to content

Node adapter

Works with Node.js, Bun and Deno.

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.

@example

import { makeAdapter } from '@livestore/adapter-node'
import { makeWsSync } from '@livestore/sync-cf/client'
const adapter = makeAdapter({
storage: { type: 'fs', baseDirectory: './data' },
sync: {
backend: makeWsSync({ url: 'wss://api.example.com/sync' }),
},
})

@example

// With DevTools support
const adapter = makeAdapter({
storage: { type: 'fs', baseDirectory: './data' },
devtools: {
schemaPath: new URL('./schema.ts', import.meta.url),
port: 4242,
},
})

@seehttps://livestore.dev/docs/reference/adapters/node for setup guide

makeAdapter
} from '@livestore/adapter-node'
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.

@example

import { makeAdapter } from '@livestore/adapter-node'
import { makeWsSync } from '@livestore/sync-cf/client'
const adapter = makeAdapter({
storage: { type: 'fs', baseDirectory: './data' },
sync: {
backend: makeWsSync({ url: 'wss://api.example.com/sync' }),
},
})

@example

// With DevTools support
const adapter = makeAdapter({
storage: { type: 'fs', baseDirectory: './data' },
devtools: {
schemaPath: new URL('./schema.ts', import.meta.url),
port: 4242,
},
})

@seehttps://livestore.dev/docs/reference/adapters/node for setup guide

makeAdapter
({
NodeAdapterOptions.storage: {
readonly type: ["in-memory"];
readonly importSnapshot?: any;
} | {
readonly type: ["fs"];
readonly baseDirectory?: string | undefined;
}
storage
: {
type: string
type
: 'fs' },
// or in-memory:
// storage: { type: 'in-memory' },
sync?: SyncOptions
sync
: {
backend?: SyncBackendConstructor<any, JsonValue>
backend
:
function makeWsSync(options: WsSyncOptions): SyncBackendConstructor<SyncMetadata>

Creates a sync backend that uses WebSocket to communicate with the sync backend.

@example

import { makeWsSync } from '@livestore/sync-cf/client'
const syncBackend = makeWsSync({ url: 'wss://sync.example.com' })

makeWsSync
({
WsSyncOptions.url: string

URL of the sync backend

The protocol can either http/https or ws/wss

url
: 'ws://localhost:8787' }) },
// To enable devtools:
// devtools: { schemaPath: new URL('./schema.ts', import.meta.url) },
})

During development you can instruct the adapter to wipe the locally persisted state and eventlog databases on startup:

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.

@example

import { makeAdapter } from '@livestore/adapter-node'
import { makeWsSync } from '@livestore/sync-cf/client'
const adapter = makeAdapter({
storage: { type: 'fs', baseDirectory: './data' },
sync: {
backend: makeWsSync({ url: 'wss://api.example.com/sync' }),
},
})

@example

// With DevTools support
const adapter = makeAdapter({
storage: { type: 'fs', baseDirectory: './data' },
devtools: {
schemaPath: new URL('./schema.ts', import.meta.url),
port: 4242,
},
})

@seehttps://livestore.dev/docs/reference/adapters/node for setup guide

makeAdapter
} from '@livestore/adapter-node'
const
const resetPersistence: boolean
resetPersistence
=
var process: NodeJS.Process
process
.
NodeJS.Process.env: NodeJS.ProcessEnv

The process.env property returns an object containing the user environment. See environ(7).

An example of this object looks like:

{
TERM: 'xterm-256color',
SHELL: '/usr/local/bin/bash',
USER: 'maciej',
PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin',
PWD: '/Users/maciej',
EDITOR: 'vim',
SHLVL: '1',
HOME: '/Users/maciej',
LOGNAME: 'maciej',
_: '/usr/local/bin/node'
}

It is possible to modify this object, but such modifications will not be reflected outside the Node.js process, or (unless explicitly requested) to other Worker threads. In other words, the following example would not work:

node -e 'process.env.foo = "bar"' &#x26;&#x26; echo $foo

While the following will:

import { env } from 'node:process';
env.foo = 'bar';
console.log(env.foo);

Assigning a property on process.env will implicitly convert the value to a string. This behavior is deprecated. Future versions of Node.js may throw an error when the value is not a string, number, or boolean.

import { env } from 'node:process';
env.test = null;
console.log(env.test);
// => 'null'
env.test = undefined;
console.log(env.test);
// => 'undefined'

Use delete to delete a property from process.env.

import { env } from 'node:process';
env.TEST = 1;
delete env.TEST;
console.log(env.TEST);
// => undefined

On Windows operating systems, environment variables are case-insensitive.

import { env } from 'node:process';
env.TEST = 1;
console.log(env.test);
// => 1

Unless explicitly specified when creating a Worker instance, each Worker thread has its own copy of process.env, based on its parent thread's process.env, or whatever was specified as the env option to the Worker constructor. Changes to process.env will not be visible across Worker threads, and only the main thread can make changes that are visible to the operating system or to native add-ons. On Windows, a copy of process.env on a Worker instance operates in a case-sensitive manner unlike the main thread.

@sincev0.1.27

env
.
string | undefined
NODE_ENV
!== 'production' &&
var Boolean: BooleanConstructor
<string>(value?: string | undefined) => boolean
Boolean
(
var process: NodeJS.Process
process
.
NodeJS.Process.env: NodeJS.ProcessEnv

The process.env property returns an object containing the user environment. See environ(7).

An example of this object looks like:

{
TERM: 'xterm-256color',
SHELL: '/usr/local/bin/bash',
USER: 'maciej',
PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin',
PWD: '/Users/maciej',
EDITOR: 'vim',
SHLVL: '1',
HOME: '/Users/maciej',
LOGNAME: 'maciej',
_: '/usr/local/bin/node'
}

It is possible to modify this object, but such modifications will not be reflected outside the Node.js process, or (unless explicitly requested) to other Worker threads. In other words, the following example would not work:

node -e 'process.env.foo = "bar"' &#x26;&#x26; echo $foo

While the following will:

import { env } from 'node:process';
env.foo = 'bar';
console.log(env.foo);

Assigning a property on process.env will implicitly convert the value to a string. This behavior is deprecated. Future versions of Node.js may throw an error when the value is not a string, number, or boolean.

import { env } from 'node:process';
env.test = null;
console.log(env.test);
// => 'null'
env.test = undefined;
console.log(env.test);
// => 'undefined'

Use delete to delete a property from process.env.

import { env } from 'node:process';
env.TEST = 1;
delete env.TEST;
console.log(env.TEST);
// => undefined

On Windows operating systems, environment variables are case-insensitive.

import { env } from 'node:process';
env.TEST = 1;
console.log(env.test);
// => 1

Unless explicitly specified when creating a Worker instance, each Worker thread has its own copy of process.env, based on its parent thread's process.env, or whatever was specified as the env option to the Worker constructor. Changes to process.env will not be visible across Worker threads, and only the main thread can make changes that are visible to the operating system or to native add-ons. On Windows, a copy of process.env on a Worker instance operates in a case-sensitive manner unlike the main thread.

@sincev0.1.27

env
.
string | undefined
RESET_LIVESTORE
)
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.

@example

import { makeAdapter } from '@livestore/adapter-node'
import { makeWsSync } from '@livestore/sync-cf/client'
const adapter = makeAdapter({
storage: { type: 'fs', baseDirectory: './data' },
sync: {
backend: makeWsSync({ url: 'wss://api.example.com/sync' }),
},
})

@example

// With DevTools support
const adapter = makeAdapter({
storage: { type: 'fs', baseDirectory: './data' },
devtools: {
schemaPath: new URL('./schema.ts', import.meta.url),
port: 4242,
},
})

@seehttps://livestore.dev/docs/reference/adapters/node for setup guide

makeAdapter
({
NodeAdapterOptions.storage: {
readonly type: ["in-memory"];
readonly importSnapshot?: any;
} | {
readonly type: ["fs"];
readonly baseDirectory?: string | undefined;
}
storage
: {
type: string
type
: 'fs' },
NodeAdapterOptions.resetPersistence?: boolean

Warning: This will reset both the app and eventlog database. This should only be used during development.

@defaultfalse

resetPersistence
,
})

The worker adapter can be used for more advanced scenarios where it’s preferable to reduce the load of the main thread and run persistence/syncing in a worker thread.

import {
const makeWorkerAdapter: ({ workerUrl, workerExtraArgs, ...options }: NodeAdapterOptions & {
workerUrl: URL;
workerExtraArgs?: JsonValue;
}) => Adapter

Creates a multi-threaded LiveStore adapter for Node.js applications.

This adapter runs the leader thread (persistence, sync, and heavy SQLite operations) in a separate worker thread, keeping your main thread responsive. Recommended for production servers and performance-critical applications.

You must create a worker file that calls makeLeaderWorker() and pass its URL to this function.

@example

// In your main file:
import { makeWorkerAdapter } from '@livestore/adapter-node'
const adapter = makeWorkerAdapter({
storage: { type: 'fs', baseDirectory: './data' },
workerUrl: new URL('./livestore.worker.ts', import.meta.url),
})

@example

// In livestore.worker.ts:
import { makeLeaderWorker } from '@livestore/adapter-node/worker'
import { schema } from './schema'
makeLeaderWorker({ schema })

@seehttps://livestore.dev/docs/reference/adapters/node for setup guide

makeWorkerAdapter
} from '@livestore/adapter-node'
const
const adapter: Adapter
adapter
=
function makeWorkerAdapter({ workerUrl, workerExtraArgs, ...options }: NodeAdapterOptions & {
workerUrl: URL;
workerExtraArgs?: JsonValue;
}): Adapter

Creates a multi-threaded LiveStore adapter for Node.js applications.

This adapter runs the leader thread (persistence, sync, and heavy SQLite operations) in a separate worker thread, keeping your main thread responsive. Recommended for production servers and performance-critical applications.

You must create a worker file that calls makeLeaderWorker() and pass its URL to this function.

@example

// In your main file:
import { makeWorkerAdapter } from '@livestore/adapter-node'
const adapter = makeWorkerAdapter({
storage: { type: 'fs', baseDirectory: './data' },
workerUrl: new URL('./livestore.worker.ts', import.meta.url),
})

@example

// In livestore.worker.ts:
import { makeLeaderWorker } from '@livestore/adapter-node/worker'
import { schema } from './schema'
makeLeaderWorker({ schema })

@seehttps://livestore.dev/docs/reference/adapters/node for setup guide

makeWorkerAdapter
({
NodeAdapterOptions.storage: {
readonly type: ["in-memory"];
readonly importSnapshot?: any;
} | {
readonly type: ["fs"];
readonly baseDirectory?: string | undefined;
}
storage
: {
type: string
type
: 'fs' },
workerUrl: URL

Example: new URL('./livestore.worker.ts', import.meta.url)

workerUrl
: new
var URL: new (url: string | URL, base?: string | URL) => URL

The URL interface is used to parse, construct, normalize, and encode URLs. It works by providing properties which allow you to easily read and modify the components of a URL.

MDN Reference

URL
('./livestore.worker.js', import.

The type of import.meta.

If you need to declare that a given property exists on import.meta, this type may be augmented via interface merging.

meta
.
ImportMeta.url: string
url
),
})
import {
const makeWorker: (options: WorkerOptions) => void
makeWorker
} from '@livestore/adapter-node/worker'
import {
const makeWsSync: (options: WsSyncOptions) => SyncBackendConstructor<SyncMetadata>

Creates a sync backend that uses WebSocket to communicate with the sync backend.

@example

import { makeWsSync } from '@livestore/sync-cf/client'
const syncBackend = makeWsSync({ url: 'wss://sync.example.com' })

makeWsSync
} from '@livestore/sync-cf/client'
import {
import schema
schema
} from './schema.ts'
function makeWorker(options: WorkerOptions): void
makeWorker
({
schema: LiveStoreSchema<DbSchema, EventDefRecord>
schema
,
sync?: SyncOptions
sync
: {
backend?: SyncBackendConstructor<any, JsonValue>
backend
:
function makeWsSync(options: WsSyncOptions): SyncBackendConstructor<SyncMetadata>

Creates a sync backend that uses WebSocket to communicate with the sync backend.

@example

import { makeWsSync } from '@livestore/sync-cf/client'
const syncBackend = makeWsSync({ url: 'wss://sync.example.com' })

makeWsSync
({
WsSyncOptions.url: string

URL of the sync backend

The protocol can either http/https or ws/wss

url
: 'ws://localhost:8787' }) },
})

You can control what the Node worker logs and how it’s formatted. Pass two optional options to makeWorker:

  • logger — where/format of logs (e.g. pretty console output)
  • logLevel — how verbose logs are ("None" silences logs)
import {
const makeWorker: (options: WorkerOptions) => void
makeWorker
} from '@livestore/adapter-node/worker'
import {
import Logger
Logger
} from '@livestore/utils/effect'
import {
import schema
schema
} from './schema.ts'
function makeWorker(options: WorkerOptions): void
makeWorker
({
schema: LiveStoreSchema<DbSchema, EventDefRecord>
schema
,
// readable console output
logger?: Layer<never, never, never> | undefined

Optional Effect logger layer to control logging output.

logger
:
import Logger
Logger
.
const layer: <readonly [Logger.Logger<unknown, void>]>(loggers: readonly [Logger.Logger<unknown, void>], options?: {
readonly mergeWithExisting?: boolean | undefined;
} | undefined) => Layer<never, never, never>

Creates a Layer which will overwrite the current set of loggers with the specified array of loggers.

Details

If the specified array of loggers should be merged with the current set of loggers (instead of overwriting them), set mergeWithExisting to true.

Example (Providing logger layers)

import { Effect, Logger } from "effect"
// Single logger layer
const JsonLoggerLive = Logger.layer([Logger.consoleJson])
// Multiple loggers layer
const MultiLoggerLive = Logger.layer([
Logger.consoleJson,
Logger.consolePretty(),
Logger.formatStructured
])
// Merge with existing loggers
const AdditionalLoggerLive = Logger.layer(
[Logger.consoleJson],
{ mergeWithExisting: true }
)
// Using multiple logger formats
const jsonLogger = Logger.consoleJson
const prettyLogger = Logger.consolePretty()
const CustomLoggerLive = Logger.layer([jsonLogger, prettyLogger])
const program = Effect.log("Application started").pipe(
Effect.provide(CustomLoggerLive)
)

@since4.0.0

layer
([
import Logger
Logger
.
const consolePretty: (options?: {
readonly colors?: "auto" | boolean | undefined;
readonly stderr?: boolean | undefined;
readonly formatDate?: ((date: Date) => string) | undefined;
readonly mode?: "browser" | "tty" | "auto" | undefined;
}) => Logger.Logger<unknown, void>

A Logger which outputs logs in a "pretty" format and writes them to the console.

Details

For example, pretty output can render as [09:37:17.579] INFO (#1) label=0ms: hello followed by an annotation line such as key: value.

Example (Logging with pretty console output)

import { Effect, Logger } from "effect"
// Use the pretty console logger with default settings
const basicPretty = Effect.log("Hello Pretty Format").pipe(
Effect.provide(Logger.layer([Logger.consolePretty()]))
)
// Configure pretty logger options
const customPretty = Logger.consolePretty({
colors: true,
stderr: false,
mode: "tty",
formatDate: (date) => date.toLocaleTimeString()
})
// Perfect for development environment
const developmentProgram = Effect.gen(function*() {
yield* Effect.log("Application starting")
yield* Effect.logInfo("Database connected")
yield* Effect.logWarning("High memory usage detected")
}).pipe(
Effect.annotateLogs("environment", "development"),
Effect.withLogSpan("startup"),
Effect.provide(Logger.layer([customPretty]))
)
// Disable colors for CI/CD environments
const ciLogger = Logger.consolePretty({ colors: false })

@since4.0.0

consolePretty
()]),
// choose verbosity: None | Error | Warn | Info | Debug
logLevel?: LogLevel | undefined

Optional minimum log level for the runtime.

logLevel
: 'Info',
})

Tips:

  • Use "None" to keep test output quiet.
  • Keep the default (Debug) when diagnosing issues.