ScrivaDB stores each collection as append-only NDJSON — human-readable, one JSON object per line. Get a durable document store with gRPC + REST from a single Go binary, or embed the engine directly in your program. No daemon, no external runtime, no hidden state.
$ scriva serve --data ./data --api-key dev-key & $ scriva-cli insert users '{"name":"alice","age":30}' --api-key dev-key ✓ id=01J8... key=01J8... rev=1 $ scriva-cli find users '{"field":"name","op":"eq","value":"alice"}' ▶ index hit users.name (1 row scanned, 1 returned) {"name": "alice", "age": 30} $ tail -1 data/users/000001.ndjson # it's just text on disk {"id":"01J8...","key":"01J8...","rev":1,"data":{...},"crc":"a1b2"}
Why ScrivaDB
When you need a real document store but standing up PostgreSQL or MongoDB is
overkill — and you'd like to still be able to cat your data.
Every collection is plain NDJSON — one JSON object per line. Inspect, grep, back up, or migrate your data with any text editor. No binary format, no hidden state.
Inserts, updates, and deletes are always new lines. Choose your fsync policy, and a CRC32C checksum on every entry catches silent on-disk bit-rot on read.
A dual API served from one binary — plus a `go get`-able storage engine you can run in-process, with zero gRPC/Prometheus/OTel dependencies.
No JVM, no Python, no daemon zoo, no config file to get started. One small Go binary that serves, and one CLI that talks to it over a Unix socket.
How it works
Writes append to a segment log; a background compactor merges and dedups sealed segments; an in-memory id index and per-field secondary indexes keep reads fast.
Records carry a key, a
monotonic rev, and a crc. A limited Find is
bounded by the page size, not the collection size, and streams rows as it
reads — with keyset pagination that covers every row exactly once.
Plus a generated OpenAPI spec
for any other language.
Trade throughput against your crash-loss
window with --sync. The embedded façade defaults to interval
(~1s).
A follower bootstraps from a snapshot then
tails the leader's committed writes by global LSN — resumable, no gaps or
dupes. Bound staleness by diffing applied vs leader LSN.
Transparent AEAD encryption at a single
boundary — segments, compaction, indexes, replication, and backups stay
key-oblivious, so an encrypted value is opaque on disk while reads return
plaintext. A wrong key fails fast on open; key rotation and enable/disable migrate
existing data lazily or via a re-encrypting compaction pass. Configure it through the
embedded façade — scriva.WithPassphrase + WithCollectionEncryption.
Two binaries, three ways to run
Run scriva serve and get gRPC on :5433 and REST on :8080 from one binary. Talk to it from any language, or from the CLI over a Unix socket.
Skip the server entirely — go get the engine and run the database in-process. Keyed CRUD, CAS, secondary indexes, and in-process Watch.
Pull ghcr.io/srjn45/scriva or docker compose up. Data persists in a named volume; REST and gRPC ports exposed and ready.
The API surface
Keyed CRUD with caller-supplied string keys, insert-or-replace upsert, and
revision-checked compare-and-swap — plus streaming queries, aggregations, TTL, optimistic
transactions, and live Watch subscriptions.
Quickstart
# start the server (or: docker compose up -d) scriva serve --data ./data --api-key dev-key # insert, then query — via the CLI over the Unix socket scriva-cli insert users '{"name":"alice","age":30}' --api-key dev-key scriva-cli find users '{"field":"age","op":"gt","value":18}' --api-key dev-key # or embed it directly in Go — no server at all go get github.com/srjn45/scriva