Architecture
You only need one idea to use Twill DB well: there is a single engine, it never touches disk directly, and it reaches durable state through one narrow storage seam. Everything else — embedded vs server, local file vs object storage, branching, scale-to-zero — falls out of that one decision. This page is the mental model; the rest of the docs are the how-to.
One engine, two front doors
Twill DB is one compiled engine (libengine). You reach it through one of two interfaces, and you pick durable storage with a connection string — never a rebuild.
| Front door | How you call it | Latency | Use it for |
|---|---|---|---|
| Embedded | In-process via bun:ffi over the C ABI | Function call (no socket) | One app owning its database |
| Server | Over the Postgres wire protocol (pgwire) | Network round-trip | Many clients; existing Postgres tools/ORMs |
The same engine, the same SQL, the same data
Server mode is just "the embedded engine wrapped in a wire listener." Nothing about SQL, transactions, MVCC, or durability changes between the two doors — only how the call arrives. There is no second engine to learn or maintain.
The seam: backend chosen by URL scheme
The engine writes durable state only through one trait, Storage. Which concrete backend is wired in is decided purely by the scheme of the URL you pass at open time:
| URL scheme | Backend | What it is |
|---|---|---|
file:// | LocalFileStorage | Pure embedded, zero network — a crash-safe local database file. |
s3:// · r2:// · gs:// | ObjectStorage | Storage-disaggregated: durability bottoms out on object storage; scale-to-zero capable. |
Unknown schemes are rejected, never silently defaulted. The engine above the seam is byte-for-byte identical for both backends — see Storage backends for the operational differences.
your app (Bun / Node) psql · Bun.sql · Drizzle · Prisma · PostgREST
│ bun:ffi (C ABI) │ Postgres wire
▼ ▼
┌───────────────────────────────────────────────────────┐
│ ENGINE (libengine) │
│ SQL → MVCC snapshot isolation → WAL → group commit │
│ ───────────── trait Storage (THE SEAM) ─────────── │
└───────────────────────────────────────────────────────┘
│ │
▼ file:// ▼ s3:// / r2:// / gs://
LocalFileStorage ObjectStorage (LSM + CAS log)
(local, zero network) (disaggregated, scale-to-zero)
Two front doors above, one seam in the middle, two storage backends below — and the connection string is the only switch.
The five concepts worth knowing
You don't need engine internals to use Twill DB, but these five terms show up across the docs and explain the guarantees you get.
- WAL Write-ahead log. Every change is appended to a durable log before a commit is acknowledged. A commit only returns once its records are
fsync'd — never from an in-memory buffer. That is the durability guarantee. - LSN Log sequence number. A strictly increasing, gap-free integer stamped on every change — the database's clock. It makes "what did the data look like then?" answerable, which powers snapshots, branching, and recovery.
- MVCC Snapshot isolation. Readers capture an LSN and see a consistent snapshot; readers never block writers and writers never block readers.
- Branch Copy-on-write fork. A branch is a new LSN pointer over shared immutable history plus a private overlay for diverged writes. Creating one copies no data. See Branching.
- Scale-to-zero Idle to nothing. With an object-storage backend, compute can stop when idle (only bytes bill at rest) and warm on the next request by replaying the WAL. See Scale-to-zero.
What's built in vs composed around
Twill DB keeps its core small on purpose. Exactly one capability is built into the engine; everything else is composed around it as ordinary libraries or wire-protocol clients — so the core never accumulates feature-specific code.
| Where it lives | Examples | |
|---|---|---|
| Built in | Inside the engine | Vector search — the vector(N) type, HNSW index, and <->/<=>/<#> distance operators |
| Composed around | Outside the engine (adapters, clients, the server) | Auth (better-auth), REST (PostgREST), analytics (DuckDB), every ORM |
This is why integration is easy and the engine stays stable: the same row store serves all of them, and anything stored through any door is just rows — so it inherits durability, branching, and scale-to-zero automatically. The two integration planes are documented in Connect as embedded and Connect as server.