stratum-db
v0.1.0
Published
A single-file embedded database with automatic, zero-config version history. SQLite's ergonomics, git's memory.
Maintainers
Readme
Stratum
A single-file database that remembers everything, automatically.

The Time Machine: drag through your database's full history, live. This is stratum serve, not a mockup — see the 60-second quickstart to run it yourself.
The problem
Every mainstream database shows you the present and makes you fight for the past. On Postgres or MySQL, you bolt on your own audit_log tables, triggers, or SQL:2011 temporal tables — supported unevenly, awkward syntax — just to know what a row looked like yesterday. MongoDB has no native versioning at all; change history is entirely the application's job. DuckDB is brilliant for "what does my data look like right now" and has no concept of "what did it look like then." Purpose-built alternatives like Dolt, XTDB, and Datomic exist, but each asks for a new mental model, a server, or a JVM — none give the "grab one binary, get a file, start querying" experience that made SQLite and DuckDB beloved.
The result: teams either ship without a real audit trail and find out the hard way during a compliance review or a bad migration, or spend months building one from scratch.
The bet: make history a first-class, zero-config default. Every write is versioned automatically. You query the past with the SQL you already know, plus a small set of time-travel verbs. No branches to learn, no server to run, no separate audit table to maintain.
Features
- Automatic versioning, zero config. Every
INSERT/UPDATE/DELETEis captured. Nothing is destructively overwritten unless you explicitly compact or purge history. - One file, no server. A Stratum database is a single
.stratfile, same story as SQLite/DuckDB.stratum serveruns a local web UI when you want one, but it's never required. - Time travel with SQL you already know.
SELECT * FROM users AS OF 'yesterday'— exact timestamps, relative phrases, snapshot tags, or version ids all work afterAS OF. DIFF,HISTORY,BLAME,SNAPSHOT,ROLLBACKas first-class query verbs, not a separate audit table you maintain by hand.- A CLI and a local web UI, including a Time Machine scrubber that drags through your database's full history live.
- Natural-language time parsing via chrono-node — "3 days ago," "last Monday," and ISO timestamps all resolve correctly.
How it compares
Real capability differences as of this writing. Partial support is marked with a caveat, not a false checkmark — verify against each project's current docs before relying on this table for a decision.
| Capability | Stratum | Postgres | DuckDB | MongoDB | Dolt |
|---|---|---|---|---|---|
| Zero-config automatic versioning | ✅ every write | ❌ requires triggers/extensions (e.g. temporal_tables) | ❌ none by default | ❌ none | ✅ every commit |
| Time-travel query (AS OF) | ✅ built-in verb | ⚠️ SQL:2011 temporal tables exist but need explicit PERIOD/history-table setup and are unevenly supported across versions | ⚠️ not in plain DuckDB — DuckLake, a separate extension/storage format you explicitly attach to (ATTACH 'ducklake:...'), adds AT (VERSION => n) / AT (TIMESTAMP => ...) | ❌ none | ✅ AS OF |
| Single file, no server for local use | ✅ | ❌ always a server process | ✅ | ❌ always a server process | ⚠️ dolt sql queries a local repo with no server, like sqlite3 — but storage is a .dolt/ repo directory (git-style, content-addressed), not one file; dolt sql-server is opt-in, for remote/multi-client access |
| Git-style branching & merge | ❌ not in v1 (see Roadmap) | ❌ | ❌ | ❌ | ✅ core feature |
| Built-in blame (who/when changed a cell) | ✅ | ❌ (needs custom audit schema) | ❌ | ❌ | ✅ dolt blame |
| SQL surface | SQLite dialect + time-travel verbs | Full Postgres SQL | Full SQL, analytics-focused, great columnar performance | Not SQL (document/aggregation API) | MySQL-compatible SQL |
| Analytical (OLAP) query performance | Basic — inherits SQLite's row store | Good for OLTP, not analytics-optimized | Excellent — columnar, built for this | N/A (document workloads) | Basic |
| Designed scale | Single-node, embedded, local-first | Scales to large multi-node clusters | Single-node, large local/in-process analytics | Distributed, horizontal scale | Single-node / small server |
Stratum is not trying to win on any row where "single-node embedded tool" isn't the right category — it's trying to be the thing you reach for instead of hand-rolling an audit table on top of Postgres, or instead of standing up Debezium + Kafka for a change log you don't need at that scale.
Install
# npm (recommended — requires Node.js >= 18)
npm install -g stratum-db
# or run without installing
npx stratum-db init mydb
# curl install script (downloads a prebuilt binary, or falls back to npm)
curl -fsSL https://raw.githubusercontent.com/prakriti31/stratum/main/scripts/install.sh | shNo tagged release exists yet (see .github/workflows/release.yml), so the curl script currently falls through to its
npm install -g stratum-dbfallback path — that part is real and tested. Once av*.*.*tag is pushed, CI builds and attaches the prebuilt binaries and the script picks them up automatically. See STATUS.md for the exact state.
60-second quickstart
# create a new database
stratum init mydb
# -> created mydb.strat
# open a REPL against it
stratum shell mydbstratum> CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER);
stratum> INSERT INTO users (id, name, age) VALUES (1, 'Ada', 30);
stratum> UPDATE users SET age = 31 WHERE id = 1;
stratum> SNAPSHOT users AS 'after-birthday';
stratum> UPDATE users SET age = 99 WHERE id = 1; -- oops, fat-fingered
stratum> SELECT * FROM users AS OF 'after-birthday';
-- shows age = 31, the mistaken 99 never happened as far as this query is concerned
stratum> BLAME users WHERE id = 1;
-- shows every change to row id=1: old value, new value, who, when
stratum> ROLLBACK users TO 'after-birthday';
-- the live table now matches that snapshot; the bad write is undone, not deleted from historyOr skip the REPL entirely:
stratum query mydb "SELECT * FROM users AS OF '3 days ago'"
stratum serve mydb --port 4321 # local web UI with the Time Machine viewCLI reference
stratum init <name> Create a new database (<name>.strat)
stratum shell <db> Open an interactive REPL
stratum query <db> "<query>" Run a single query and print the result
stratum serve <db> [--port <n>] Start the local web UI (default port 4321)
stratum snapshot <db> --tag=<name> Tag the current point in time
stratum rollback <db> --to=<time> Revert a database (or table) to a point in time
stratum diff <db> --table=<t> --from=<t1> --to=<t2> Row-level diff between two points in time
stratum blame <db> <table> --where "<expr>" Change history (who/when) for matching rows
stratum export <db> --table=<t> [--as-of=<t>] --format=<csv|json|sql> Export table dataRun stratum <command> --help for full flag documentation on any subcommand.
stratum servehas no authentication. It binds to127.0.0.1only (not reachable from other machines by default), but anyone with access to that port on your machine has full read/write/rollback access to the database — it's a local admin console, the same trust model as runningsqlite3from a terminal, not a hardened multi-user service. Don't port-forward it onto a shared or public network.
Query grammar — ordinary SQL, plus time-travel verbs, inside shell/query:
SELECT * FROM users WHERE age > 21; -- ordinary SQL, unchanged
SELECT * FROM users AS OF '2026-07-01'; -- exact timestamp
SELECT * FROM users AS OF '3 days ago'; -- natural language
SELECT * FROM users AS OF 'pre-migration'; -- snapshot tag
DIFF users BETWEEN '2026-01-01' AND NOW; -- what changed between two points
HISTORY users WHERE id = 42; -- full change history for matching rows
BLAME users WHERE id = 42; -- who changed what, and when
SNAPSHOT users AS 'pre-migration'; -- name a point in time
ROLLBACK users TO 'pre-migration'; -- undo to that pointRun stratum <command> --help for full flag documentation on any subcommand.
Architecture
flowchart TB
subgraph Interfaces
CLI["CLI (commander)\nshell / query / snapshot / rollback / diff / blame / export"]
UI["Web UI (React + Vite)\ntable browser, SQL editor, Time Machine"]
end
subgraph Server["stratum serve (express)"]
API["REST API"]
end
subgraph Core["src/core — the versioning engine"]
Grammar["Query grammar\nAS OF / DIFF / HISTORY / BLAME / SNAPSHOT / ROLLBACK"]
TimeParse["Time resolution\n(chrono-node: NL phrases, ISO, tags, version ids)"]
Store["VersionedStore\nwrite-interception layer"]
end
subgraph Disk["one .strat file"]
UserTables["user tables"]
History["__stratum_history"]
Snapshots["__stratum_snapshots"]
end
CLI --> Grammar
UI --> API --> Grammar
Grammar --> TimeParse
Grammar --> Store
Store -->|every write| UserTables
Store -->|every write, mirrored| History
Grammar -->|SNAPSHOT| Snapshots
Grammar -.reads.-> UserTables
Grammar -.reads.-> History
Grammar -.reads.-> Snapshots
style Core fill:#1a1a2e,color:#fff
style Disk fill:#16213e,color:#fffbetter-sqlite3 provides storage, transactions, and the SQL engine itself — Stratum does not implement its own WAL or B-tree. The versioning layer is a thin interception point: every write goes through VersionedStore, which performs the underlying SQLite write and appends the corresponding history row(s) in the same transaction. That one __stratum_history table is what powers AS OF, DIFF, HISTORY, and BLAME — there's no separate audit subsystem to keep in sync.
Roadmap
- [x] Core versioning engine + time-travel query grammar
- [x] CLI with REPL, natural-language time parsing
- [x] Web UI: table browser, SQL editor, Time Machine scrubber, diff view, blame popover
- [ ] npm package + curl-installable prebuilt binaries
- [ ] Compaction / history-purge tooling for long-lived databases
- [ ] Hosted, no-install browser playground (future work — not a v1 promise)
- [ ] Git-style branching (exploratory — not committed)
See STATUS.md for the live, phase-by-phase build state.
Who this is for
- Compliance-heavy teams (fintech, healthtech) who need to prove "who changed this record and when" for SOC 2 / HIPAA-style reviews.
- Platform/data teams currently running Debezium + Kafka for a change-data-capture log that's more operational overhead than the problem requires.
- SaaS products that want an "activity history" or "undo" feature for their own end users without building the versioning layer themselves.
- ML/data teams who need point-in-time-correct data for reproducible training sets.
These are segments Stratum is being built for, not confirmed customers — feedback from people in these positions is exactly what shapes the roadmap above.
Recipes
Short, runnable walkthroughs of the everyday problems Stratum is for:
- Undo a bad migration — a migration overshoots its
WHEREclause;DIFFshows the blast radius,ROLLBACKfixes it without erasing what happened. - Answer an auditor's question — "who changed this record, and when?" as one
BLAMEcommand, not a backup-restore project. - Debug a support ticket by seeing an old value — a field went blank; find out what it used to be and put it back, in two commands.
Non-goals
Stratum is an embedded, single-node tool in the SQLite/DuckDB category. It is not a distributed database, not built for multi-terabyte scale, and not trying to replace Postgres for high-throughput OLTP. See CLAUDE.md for the full list of non-goals and design constraints.
Contributing
Issues and PRs are welcome. Before sending a PR:
- Read CLAUDE.md — in particular, the core invariant that all writes to user tables must go through the versioning layer (
src/core). A rawINSERT/UPDATE/DELETEagainst a user table anywhere else is a correctness bug, not a style nit. npm install && npm testshould pass.npm run lint && npm run typecheckshould be clean.- Keep PRs scoped — one behavior change per PR is easier to review than a bundle.
