Prerequisites

  • MUST A Rust toolchain (1.80+) to build the native library — rustup.rs.
  • MUST Bun for the embedded client and examples — bun.sh.
  • MAY An S3-compatible bucket (AWS S3 / Cloudflare R2 / MinIO) only if you want the disaggregated (s3://) backend; the file:// path needs nothing.

1 · Build the engine

Clone the repository and build the release libengine. This produces the static and dynamic libraries plus the frozen C ABI header.

git clone https://github.com/bihaviour/twill-db
cd twill-db

cargo build -p twill-engine --release
# → target/release/libengine.{a,so,dylib}  +  crates/engine/include/engine.h

When to rebuild

The Bun client loads the native library through bun:ffi. After any change that touches the C ABI or engine behaviour, rebuild the release libengine before running the client — otherwise it runs against a stale binary.

2 · Your first embedded database

The @twilldb/bun wrapper auto-discovers the library from target/{release,debug} (or set TWILLDB_ENGINE_PATH to an explicit path). Open a file:// database and run SQL at function-call latency.

// notes.ts
import { open } from "@twilldb/bun";

using db = open("file://./local.db");          // pure-embedded, zero network

db.exec(`CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT)`);
db.query("INSERT INTO notes VALUES (?, ?)", [1, "hello"]);

const rows = db.query("SELECT id, body FROM notes");
console.log(rows);                              // [{ id: "1", body: "hello" }]
cd clients/bun
bun install
bun run ../../notes.ts          # or: bun run examples/notes.ts
bun test                        # the end-to-end embedded test suite

The database persists to ./local.db and is rebuilt by replaying the durable WAL on reopen, so your data survives a process restart.

3 · Go disaggregated (optional)

To make the same code path disaggregated and scale-to-zero, change only the connection string — no recompile, no code change.

using db = open("s3://my-bucket/notes");        // disaggregated; durable on object storage

Provide bucket credentials through the environment (never in code). See Connect to your database for the supported schemes and Scale-to-zero for the lifecycle.

4 · Start a server

Run the same engine behind a Postgres-wire listener and connect with any Postgres client (cleartext / sslmode=disable).

cargo run -p twill-server -- --listen 127.0.0.1:5433 --db file://./srv.db   # or s3://bucket/db

psql "host=127.0.0.1 port=5433 user=postgres sslmode=disable"
// Or from Bun's built-in client:
import { SQL } from "bun";
const sql = new SQL("postgres://[email protected]:5433/srv?sslmode=disable");
const rows = await sql`select 1 as n`;

5 · Branch your data

A branch is an instant copy-on-write fork: it sees the base's data and writes in isolation. Creating one copies no pages.

using preview = db.branch("preview");
preview.exec("INSERT INTO notes VALUES (2, 'branch-only')");
// the base never sees the branch's write, and vice versa

Next steps

Twill DB documentation · Licensed under AGPL-3.0. · Author