@rotorsoft/act-sqlite
v1.13.0
Published
act sqlite adapters
Downloads
2,789
Maintainers
Readme
@rotorsoft/act-sqlite
SQLite event store for @rotorsoft/act via @libsql/client. File-based, edge-ready, ACID — for single-node deployments. Lane-aware claim/ack via streams.lane + streams_lane_ix since v0.9.0 (ACT-1103).
Why this package
Not every Act app needs Postgres. Single-server apps, embedded deployments, edge functions, and unit tests all want the same thing: a real event store with ACID guarantees, but no operational overhead. SqliteStore is that — @libsql/client under the hood (zero native bindings, browser-incompatible parts already stripped), full conformance with Act's Store port, the same one-line bootstrap swap.
SQLite serializes all writes at the database level. For a single-server deployment this gives you the same isolation guarantees as Postgres's FOR UPDATE SKIP LOCKED without any coordination layer. When you outgrow that — multi-server distributed processing, sub-poll cross-process wakeup — swap in @rotorsoft/act-pg. Application code doesn't change.
Installation
pnpm add @rotorsoft/act @rotorsoft/act-sqliteQuick start
import { act, state, store } from "@rotorsoft/act";
import { SqliteStore } from "@rotorsoft/act-sqlite";
import { z } from "zod";
// File-based persistence
store(new SqliteStore({ url: "file:myapp.db" }));
// One-time schema setup (idempotent — safe to leave in your bootstrap).
await store().seed();
const Counter = state({ Counter: z.object({ count: z.number() }) })
.init(() => ({ count: 0 }))
.emits({ Incremented: z.object({ amount: z.number() }) })
.patch({ Incremented: ({ data }, s) => ({ count: s.count + data.amount }) })
.on({ increment: z.object({ by: z.number() }) })
.emit((a) => ["Incremented", { amount: a.by }])
.build();
const app = act().withState(Counter).build();
await app.do("increment", { stream: "c1", actor: { id: "1", name: "u" } }, { by: 1 });API
SqliteStore— class implementing Act'sStoreport. Construct once, pass tostore().SqliteConfig— constructor options (url,authToken).
Full type reference: typedoc.
Configuration
| Option | Default | Description |
|---|---|---|
| url | required | libSQL connection URL. Use file:path.db for a persistent file, libsql://… for Turso, :memory: for the shared in-memory database. |
| authToken | — | Auth token for libSQL server connections (Turso). |
File-based persistence
store(new SqliteStore({ url: "file:data/events.db" }));In-memory (tests / quick experiments)
store(new SqliteStore({ url: ":memory:" }));There is no default url — a store has to be told where to write, and
constructing one without a URL throws. libSQL gives every connection its
own private in-memory database and does not pin statements to a single
connection, so a zero-config store used to accept writes into a database
the next statement could not see. :memory: is therefore normalized to
libSQL's shared-cache form, the only one that round-trips.
That comes with a caveat: the shared-cache database is one per process,
visible to every store pointed at it, and it outlives dispose(). For
isolated throwaway state — parallel tests, two independent stores in one
process — use InMemoryStore from @rotorsoft/act, or give each store its
own file: path.
Turso (edge)
store(new SqliteStore({
url: process.env.TURSO_URL!,
authToken: process.env.TURSO_AUTH_TOKEN,
}));Common patterns
Schema setup
await store().seed();Idempotent. Creates the events table, the streams (subscription) table, and the indexes that support claim ordering. PRAGMA journal_mode=WAL is set at the same time so readers don't block writers. Safe to leave in your bootstrap.
Concurrency model
SQLite serializes write transactions at the database level. No application-layer locking, no FOR UPDATE SKIP LOCKED needed — writes queue automatically and ack/block validate leased_by to prevent stale workers from interfering. For a single-server deployment, this gives the same isolation guarantees as Postgres.
Database schema reference
Created by seed():
- Events (
events):id(INTEGER PRIMARY KEY AUTOINCREMENT),name,data(TEXT/JSON),stream,version,created(ISO 8601),meta(TEXT/JSON). Unique index on(stream, version). - Streams (
streams):stream(PK),source,at,retry,blocked,error,leased_by,leased_until,priority. Composite index on(blocked, priority DESC, at).
When to use this vs act-pg
| You want… | Use |
|---|---|
| Single server / embedded / edge | act-sqlite |
| Zero infrastructure setup (file path is the config) | act-sqlite |
| Edge runtime with Turso replication | act-sqlite (with Turso URL) |
| Multi-server, distributed processing | act-pg |
| Sub-poll cross-process reaction latency | act-pg (with notify: true) |
| Heavy write contention across many writers | act-pg |
Both adapters pass the same runStoreTck suite. Application code doesn't change between them; only the bootstrap line differs.
What's intentionally not implemented
Store.notify is absent. The notify hook is a cross-process wake-up signal that lets a horizontally-scaled deployment skip polling lag on remote commits. SQLite is single-node by design — there's no remote writer to be notified of — so the Act orchestrator falls back to the existing debounce/poll path, which is correct for this topology. If you outgrow it, switch to @rotorsoft/act-pg.
Compatibility
- Node: >=22.18.0
- Peer:
@rotorsoft/act>=0.39.0,zod^4.4.3 - Bundled deps:
@libsql/client^0.17.3 (no native bindings) - Module formats: ESM + CJS
- Runtimes: Node, Bun, Deno (libSQL pure-TS implementation); also runs in Turso-compatible edge environments
Stability
Public API governed by the Act Stability Charter. SqliteStore implements the Store contract from @rotorsoft/act and is validated against @rotorsoft/act-tck on @libsql/client pinned + latest in CI. Charter is in effect as of 1.0.0; the milestone tracker is milestone 1.0.
Versioning note. Version
1.0.0is reserved on the npm registry from a prior publish and cannot be republished. The first 1.x release of this package on npm is1.0.1; its public surface is identical to the intended 1.0.0 cut.
Related packages
- @rotorsoft/act — the framework whose
Storeport this implements. - @rotorsoft/act-pg — sibling store adapter for multi-server / distributed deployments.
- @rotorsoft/act-tck — conformance suite.
SqliteStorepassesrunStoreTck.
Documentation
- Production checklist — operator-facing guide; the SQLite path is called out where it differs from the PG path.
- Concurrency model — lease lifecycle, single-writer guarantees, optimistic concurrency.
- Writing a custom Store adapter — for authors building against other databases;
SqliteStoreis one of the reference implementations.
License
MIT
