Compiled SQLite extension that streams a Sirannon backup to a caller-supplied destination, for Windows on x86-64.


Keywords
ai-agents, better-sqlite3, change-data-capture, database, device-sync, distributed-systems, embedded-database, etcd, failover, hybrid-logical-clocks, local-first, nodejs, offline-first, replication, sqlite, typescript
License
Apache-2.0
Install
npm install @delali/sirannon-vfs-win32-x64@0.3.4-next.61

Documentation

sirannon-db

CI npm downloads types license

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.

Project status

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.

Install

pnpm add -E @delali/sirannon-db better-sqlite3

Pick the driver for your runtime; better-sqlite3 is the usual choice on Node.js.

Quick start

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')

Pluggable drivers

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

Package exports

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

Features

  • 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.live keeps a query result current by applying each change to the rows it already holds, and @delali/sirannon-db/react renders one through useLiveQuery.
  • Registered operations. The server executes only the statements you registered under a name, and sirannon-codegen turns 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, and backupTo() puts it in storage you supply. The backups option follows a first full copy with only what changed since the previous run, and restoreBackup() 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.

Documentation

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/.

Examples

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 dev

You can start the single-node example on Node.js alone: build the package as above, then cd packages/ts/examples/node && pnpm start.

Architecture

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.

Sirannon replication topology: application clients reach the primary and read replicas, the primary replicates to replicas over gRPC with mutual TLS, and an etcd coordinator tracks authority, leases, and the in-sync set.

Security

  • The server serves only the registered operations until you set acceptSql: true. Authenticate every request either way through the authenticate hook, and check the Origin header on the WebSocket upgrade.
  • A Node client sends its headers on the WebSocket upgrade, so the hook reads headers.authorization on both transports. A browser sends no handshake header, so a browser client puts a short-lived ticket in webSocketProtocols; the server selects the plain sirannon.v1 identifier and never echoes the ticket. A refused upgrade closes with 4401 or 4403; the client raises UNAUTHORIZED or FORBIDDEN and 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 maxBodyBytes raises 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.

Benchmarks

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.

Development

pnpm install
pnpm build
pnpm test
pnpm typecheck
pnpm lint

CONTRIBUTING.md covers the repository layout, the end-to-end and failover suites, and how to propose a change.

License

Apache-2.0