Sirannon lets you keep real SQLite underneath your application as it grows, so the queries you write against a file on your laptop work unchanged against an HTTP and WebSocket server and against a primary replicating to its read replicas. A language-agnostic specification under packages/spec sets out the wire formats, the value encodings, and the replication invariants that every implementation follows, including the TypeScript package in this repository, which is its reference implementation.
Read the documentation, or start the distributed entitlements example to watch a three-node cluster serve through a primary failure on your own machine.
sirannon means 'gate-stream' in Sindarin.
| Part | Status | Details |
|---|---|---|
Core engine (@delali/sirannon-db) |
Stable | Queries, transactions, connection pooling, change data capture, live queries, migrations, backups, hooks, metrics, and multi-tenant lifecycle, covered by more than 130 test files on Node 22 and 24. |
Server and client (@delali/sirannon-db/server, /client) |
Stable | HTTP and WebSocket access with reconnection and subscription restore. The server serves registered operations and accepts no SQL until you turn it on. |
Device sync (@delali/sirannon-db/client) |
Experimental | Offline-first two-way sync between a device's local database and a server, with push, live pull, snapshot resync, and a migration handshake. |
Primary-replica replication (@delali/sirannon-db/replication) |
Stable | Hybrid Logical Clock stamping, conflict resolvers, first sync, write concerns, and a gRPC transport with mutual TLS. |
Coordinator-backed failover (/replication/coordinator/etcd) |
Experimental | etcd authority, primary terms, and in-sync sets, verified by a Docker conformance run under fault injection, which is the whole of its evidence so far. |
| Drivers | Stable: better-sqlite3, Node, wa-sqlite. Experimental: Bun, Expo | The Bun and Expo drivers work today, and their TypeScript declarations are still outstanding. |
Sirannon defaults to SQLite's WAL mode with synchronous=NORMAL, which you can raise. The roadmap sets out what comes next.
pnpm add -E @delali/sirannon-db better-sqlite3Pick the driver for your runtime; better-sqlite3 is the usual choice on Node.js.
import { Sirannon } from '@delali/sirannon-db'
import { betterSqlite3 } from '@delali/sirannon-db/driver/better-sqlite3'
const sirannon = new Sirannon({ driver: betterSqlite3() })
const db = await sirannon.open('app', './data/app.db')
await db.execute('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)')
await db.execute('INSERT INTO users (name, email) VALUES (?, ?)', ['Ada', 'ada@example.com'])
const users = await db.query<{ id: number; name: string }>('SELECT * FROM users')| Driver | Import | Runtime |
|---|---|---|
| better-sqlite3 | @delali/sirannon-db/driver/better-sqlite3 |
Node.js |
| Node built-in | @delali/sirannon-db/driver/node |
Node.js >= 22 |
| wa-sqlite | @delali/sirannon-db/driver/wa-sqlite |
Browser (IndexedDB persistence) |
| Bun | @delali/sirannon-db/driver/bun |
Bun |
| Expo | @delali/sirannon-db/driver/expo |
React Native |
| Import | What you get |
|---|---|
@delali/sirannon-db |
Core library: queries, transactions, CDC, live queries, migrations, backups, hooks, metrics, lifecycle |
@delali/sirannon-db/driver/* |
SQLite driver adapters |
@delali/sirannon-db/file-migrations |
Load .up.sql and .down.sql files from a directory |
@delali/sirannon-db/backup |
Backup destination types, backup chain records, and restoreBackup
|
@delali/sirannon-db/backup-scheduler |
Cron-scheduled backup runner with file rotation, also re-exported from the core entry |
@delali/sirannon-db/server |
HTTP and WebSocket server powered by uWebSockets.js |
@delali/sirannon-db/client |
Browser and Node.js client SDK with auto-reconnect, subscription restore, and the device sync controller |
@delali/sirannon-db/client/topology |
Topology-aware client that routes reads and writes across a replication group |
@delali/sirannon-db/react |
useLiveQuery and useCommand hooks |
@delali/sirannon-db/codegen |
Typed operation references generated from your server's registry |
@delali/sirannon-db/replication |
Replication engine, primary-replica topology, HLC, write concerns, and conflict resolvers |
@delali/sirannon-db/replication/coordinator/etcd |
etcd-backed cluster coordinator for primary authority and automatic failover |
@delali/sirannon-db/transport/grpc |
gRPC replication transport with TLS support |
@delali/sirannon-db/transport/memory |
In-memory replication transport for tests and single-process clusters |
- Queries and transactions. Sirannon gives reads, writes, batches, and transactions full ACID guarantees over one write connection and a pool of read connections, with WAL mode on by default.
- Change data capture. Watch a table for insert, update, and delete events in real time through SQLite triggers and configurable polling.
-
Live queries.
db.livekeeps a query result current by applying each change to the rows it already holds, and@delali/sirannon-db/reactrenders one throughuseLiveQuery. -
Registered operations. The server executes only the statements you registered under a name, and
sirannon-codegenturns that registry into typed client references. -
Migrations. File-based or programmatic migrations apply once each with content checksums, mirror
PRAGMA user_version, roll back to any version, and squash into a baseline. Two processes migrating at once still end with one applied set, and a set declared on the registry covers every database it opens, tenants included. - Bulk load. A large import works inside one transaction under relaxed durability, and Sirannon then restores the configured level, so the whole import crosses one durability barrier.
-
Backups.
backup()copies a database to a file while it stays open for reads and writes, because SQLite moves the pages in steps and a write happens in the gap between two of them.scheduleBackup()repeats that copy on a cron expression, andbackupTo()puts it in storage you supply. Thebackupsoption follows a first full copy with only what changed since the previous run, andrestoreBackup()rebuilds the database from any moment that chain reaches. - Hooks and metrics. Before and after hooks cover queries, connections, and subscriptions, and throwing from a before-hook denies the operation. Metrics callbacks collect query timing, connection events, and CDC activity.
- Multi-tenant lifecycle. Sirannon opens a database on first access, closes it on an idle timeout, and evicts the least recently used one once the count passes a cap.
- Server and client SDK. Expose a registry over HTTP and WebSocket with one call, and reach it through a client that mirrors the core interface, reconnects, and restores its subscriptions.
- Device sync. An end-user device keeps its whole local database in step with a server, offline-first and both ways, with snapshot resync, a migration handshake, and capability negotiation.
- Distributed replication. A primary stamps each change with a Hybrid Logical Clock and replicates checksummed batches to read replicas over gRPC with mutual TLS.
- Coordinator-backed failover. etcd authority, primary terms, in-sync sets, and write concerns keep write ownership clear, while a minority partition fails closed.
- Conflict resolution. Choose LWW, PrimaryWins, FieldMerge, or your own resolver for an incoming change that targets an existing row.
| Guide | What it covers |
|---|---|
| Core engine | Bulk load, live queries, migrations, hooks, metrics, and the multi-tenant lifecycle |
| Backups | Copies to a file or to storage you supply, the chain of changes after one, and restoring from a moment you name |
| Server | HTTP routes, WebSocket messages, authentication, write shapes, the writer worker, and value encoding |
| Registered operations | Naming the statements a server runs, identity-filled arguments, capabilities, and code generation |
| Live queries | Maintained query results locally, over the network, and in React |
| Client SDK | Transports, subscriptions, topology-aware routing, and read concern |
| Device sync | Offline-first two-way sync between a device's local database and a server |
| Distributed replication | Replication, first sync, write and read concerns, coordinator failover, resolvers, and transports |
| Configuration reference | Every option table, from SirannonOptions to GrpcReplicationOptions
|
| Errors | Every code, when it happens, whether the call is safe to retry, and its HTTP status |
You will find the wire formats, the value encodings, and the replication invariants in the specification, and the decision records behind the replication design in docs/adr/.
| Example | Runtime | What it demonstrates |
|---|---|---|
node |
Node.js >= 22 | Core features, live queries, and multi-tenant lifecycle on either better-sqlite3 or Node's built-in SQLite driver |
web-wa-sqlite |
Browser and Node.js | Offline-first device sync: a local database in the browser, snapshot load, offline writes, conflict resolution, and a local live query |
web-client |
Browser and Node.js | Live queries and the React hooks over registered operations, with no SQL on the wire |
distributed-entitlements |
Node.js and browser | Three-node coordinator-backed replication with etcd, gRPC, mTLS, and Toxiproxy failure controls |
Every example works against the built package, so build it from the repository root before you start one. The commands below bring up the three-node cluster and its dashboard, for which you will need Docker with Compose and Node.js 22 or newer.
pnpm install && pnpm --filter @delali/sirannon-db build
cd packages/ts/examples/distributed-entitlements && pnpm run devYou can start the single-node example on Node.js alone: build the package as above, then cd packages/ts/examples/node && pnpm start.
Application clients reach the primary and read replicas over HTTP and WebSocket. The primary accepts every write, assigns each change a Hybrid Logical Clock timestamp, and sends checksummed batches to the replicas over gRPC with mutual TLS. An etcd coordinator tracks primary authority, node leases, and the in-sync set, and promotes an in-sync replica when the primary fails.
- The server serves only the registered operations until you set
acceptSql: true. Authenticate every request either way through theauthenticatehook, and check theOriginheader on the WebSocket upgrade. - A Node client sends its
headerson the WebSocket upgrade, so the hook readsheaders.authorizationon both transports. A browser sends no handshake header, so a browser client puts a short-lived ticket inwebSocketProtocols; the server selects the plainsirannon.v1identifier and never echoes the ticket. A refused upgrade closes with 4401 or 4403; the client raisesUNAUTHORIZEDorFORBIDDENand leaves that connection closed. - Every statement binds its parameters through the driver, so user input never reaches the SQL text.
- Sirannon validates CDC table and column names against
/^[a-zA-Z_][a-zA-Z0-9_]*$/, and rejects null bytes,..segments, and control characters in migration and backup paths. - Sirannon caps HTTP bodies and WebSocket messages at 1 MB, and
maxBodyBytesraises or lowers that ceiling. - The built-in server binds plain HTTP and WebSocket. Terminate TLS upstream with a reverse proxy such as nginx or Caddy, or a cloud load balancer, before you carry traffic outside a trusted network.
The suite measures Sirannon and Postgres 17 on the same OLTP workloads: point-select, single-row-insert, single-row-update, YCSB A/B/C/F, and a TPC-C-shaped mix. It drives Sirannon over its SDK's WebSocket transport into the real server and Postgres over node-postgres on its binary socket protocol, both as native processes on pinned cores under a hard memory ceiling at matched durability, under an open-loop load generator that corrects for coordinated omission. It also records change-feed latency, cold start, and connection scaling for Sirannon alone. The harness is a Python project under benchmarks/server, and the write-up generator rewrites BENCHMARKS.md from the latest committed run.
On point-select at 10,000,000 rows, with both engines fsyncing every commit, Sirannon sustained 64.0K operations a second against PostgreSQL's 16.0K. Postgres held the lower tail latency at those operating points, 2.378 ms against Sirannon's 6.177 ms. That pattern holds on 7 of the 8 workloads at this durability level, so read the rate and the latency together. The harness recorded both engines in run 20260804T221053Z on 2026-08-04, on GCP c3-standard-8-lssd, us-central1-b. You will find every workload, both durability levels, and the full method in BENCHMARKS.md.
pnpm install
pnpm build
pnpm test
pnpm typecheck
pnpm lintCONTRIBUTING.md covers the repository layout, the end-to-end and failover suites, and how to propose a change.
Apache-2.0