A lightweight, append-only, file-based document database — single binary, zero dependencies, human-readable storage.
# Homebrew (via the tap)
brew install --cask srjn45/scriva/scriva
# Go
go install github.com/srjn45/scriva/cmd/scriva@latest
# Docker
docker run ghcr.io/srjn45/scrivaSee Getting Started for Scoop, apt/rpm, and AUR channels.
# 1. Start the server
scriva serve --data ./data --api-key dev-key
# 2. Insert a record
scriva-cli insert users '{"name":"alice","age":30}' --api-key dev-key
# 3. Query it
scriva-cli find users '{"field":"name","op":"eq","value":"alice"}' --api-key dev-keyOr use the interactive REPL:
scriva-cli repl --api-key dev-key# Start the server (builds the image from the local Dockerfile on first run)
SCRIVA_API_KEY=dev-key docker compose up -d
# Insert a record against the REST gateway on :8080
curl -H "x-api-key: dev-key" \
-d '{"data":{"name":"alice","age":30}}' \
http://localhost:8080/v1/users/recordsgRPC is exposed on :5433 and REST on :8080; data persists in the named
scriva-data volume. See docker-compose.yml and the
fully commented scriva.example.yaml for configuration.
ScrivaDB stores each collection as a set of NDJSON segment files — one JSON object per line, appended in order. There is no binary format, no external runtime, and no hidden state. You can inspect, backup, or migrate your data with any text editor or Unix tool.
Key properties:
- Append-only writes — inserts, updates, and deletes are always new lines; no in-place modification
- Configurable durability — choose
none(OS flush),always(fsync per write), orinterval(fsync on a timer) to trade throughput against crash-loss window - End-to-end integrity — every segment entry carries a CRC32C checksum, so silent on-disk bit-rot is caught on read instead of returning wrong data
- Encryption at rest (embedded) — transparent field- or record-level AEAD encryption (XChaCha20-Poly1305) configured through the embedded façade:
scriva.WithPassphrase/WithEncryptionKey/WithKeyProvider+WithCollectionEncryption(EncryptFields/EncryptRecord). Values are opaque on disk under a reserved marker while reads return plaintext; a wrong key/passphrase fails fast on open; key rotation and enable/disable migrate existing data lazily or in bulk via a re-encrypting compaction pass. Segments, backups, and replication carry ciphertext for free. See docs/encryption-at-rest.md - Background compaction — a goroutine per collection merges and deduplicates sealed segments; operators can also force a synchronous pass on demand (
scriva-cli compact) - Online backup —
scriva-cli backupstreams a consistent gzip snapshot of the live database; restore is a plaintar xzfinto a data directory - Leader→follower replication — start a hot standby with
--replicate-from <leader>: the follower bootstraps from a snapshot then tails the leader's committed writes (each tagged with a monotonic global LSN), applying them through the normal write path so its indexes match exactly. Async (bounded lag), resumes after a disconnect from its persisted applied-LSN with no gaps or duplicates, and exposesReplicationStatus(leader LSN, per-follower lag). - Read replicas — a follower serves reads (
Find/FindById/FindByKey/Aggregate) from its applied state and refuses writes with a typedFAILED_PRECONDITION, so read traffic scales horizontally across followers. Bound staleness by diffing a follower'sapplied_lsnagainst the leader'sleader_lsn - Manual failover — after a leader loss, promote a caught-up follower to leader with the admin
PromoteRPC (scriva-cli promote): it stops replicating, lifts the read-only guard, and accepts writes. A lag guard refuses promoting a stale replica (override with--force); promotion is one-way and automatic election is out of scope. See the operator runbook - In-memory index — O(1) lookup by id, persisted with a checksum for fast restarts
- Secondary indexes — per-field indexes for O(1) equality lookups and O(matches) range queries (
gt/lt/…); automatically maintained and persisted - Streaming queries —
Findpusheslimit/offset, a multi-fieldorder_by_fieldssort, and a keysetpage_tokencursor into the engine and streams results as it reads; a limited query is bounded by the page size, not the collection size, and honours client cancellation. Cursor pagination seeks past already-returned rows in O(page) — concatenated pages cover every row exactly once, no dupes or gaps. Optional field projection (--fields) returns only the requested fields (id/key/revalways included) - Aggregations —
Aggregatecomputes acountand numericsum/avg/min/maxover the same filter asFind, optionally grouped by a field; it streams one result per group and folds records in the engine (memory bounded by distinct groups, not rows), so clients count and total server-side instead of pulling the whole collection. CLI:aggregate --group-by --field --aggs count,sum,avg,min,max - Transactions — optimistic multi-operation transactions via
BeginTx/CommitTx/RollbackTx - Keyed CRUD, upsert & CAS over the wire — caller-supplied string keys, insert-or-replace
upsert, natural-key find/update/delete, and revision-checked compare-and-swap (update-if-rev) are now exposed over gRPC/REST (not just the embedded engine); every record carries akeyand a monotonicrev, with duplicate/missing keys mapped toAlreadyExists/NotFound - TTL / expiring records — per-record deadlines (
--ttl/ttl_secondson Insert & Update), a per-collection default (create-collection --default-ttl, persisted), and a server-wide default (--default-ttl); expired records are hidden from reads immediately and reclaimed by compaction - gRPC + REST — dual API served from one binary; CLI uses the Unix socket when local
- OpenAPI spec —
docs/openapi/scriva.swagger.jsongenerated from the proto; generate clients for any language with openapi-generator - Official client SDKs — idiomatic, hand-written libraries for 10 languages (Python, JavaScript/TypeScript, PHP, Java, Kotlin, Scala, Clojure, Ruby, Rust, C#/.NET) under
clients/ - Scoped API keys — multiple named keys with
readorread-writescope; a read-only key is refused on writes, and keys hot-reload onSIGHUPfor rotation without a restart - Optional TLS — TCP gRPC listener can be secured with a cert/key pair; CLI verifies via
--tls-ca - YAML config file —
--config scriva.yamlwith CLI flag overrides always winning - Prometheus metrics — per-collection gauges, compaction histograms, gRPC request duration, and per-query rows-scanned at
--metrics-addr - Structured logging — leveled
log/slogoutput (--log-level,--log-format json|text); one record per RPC with method, principal, duration, and status code - Slow-query log — opt-in
--slow-query-mslogs anyFindover the threshold atWARNwith filter shape, rows scanned vs returned, and whether an index was used, so unindexed hot queries surface from logs and metrics (off by default) - Health & readiness — standard
grpc.health.v1.Healthservice (SERVING → NOT_SERVING on graceful shutdown) plus HTTP/healthz(liveness) and/readyz(DB open + data dir writable) probes - Backpressure & limits — opt-in
--max-inflightin-flight ceiling and per-key--rate-limittoken bucket shed load withRESOURCE_EXHAUSTEDinstead of unbounded resource growth;--max-concurrent-streamscaps per-connection HTTP/2 streams (all off by default) - Distributed tracing — opt-in OpenTelemetry spans to an OTLP collector (
--otlp-endpoint,--otlp-sample-ratio); one span per RPC plus childengine.scan/engine.compactionspans, so a slowFindis traceable gateway → gRPC → engine (off by default; the embeddable engine gains no OTel dependency) - Single binary — no JVM, no Python, no config files required to get started
- Web admin UI — browser-based collection and record manager at
clients/web/(React + Vite, talks to the REST gateway)
ScrivaDB is the right tool when:
- You need persistence without standing up PostgreSQL or MongoDB
- Your data fits on one machine and you want human-readable files
- You're building CLI tools, local services, IoT daemons, or small web apps
- You want a simple HTTP/gRPC API you can call from any language
It is not the right tool for multi-node replication, complex joins, or datasets too large to compact on a single machine.
ScrivaDB's storage engine is a plain Go library, so you can skip the server entirely and run the database in-process — no gRPC, no network, no separate daemon. This is the right choice when your program is the only writer and you want ScrivaDB's durability and query model directly inside your binary.
go get github.com/srjn45/scriva/engine # the storage engine
go get github.com/srjn45/scriva # the ergonomic façade (recommended)import "github.com/srjn45/scriva"
db, _ := scriva.Open("./data") // embedded durability defaults (fsync ~1s)
defer db.Close()
sessions := db.MustCollection("sessions")
id, _, _ := sessions.InsertWithKey("sess-1", map[string]any{"status": "open"})
rec, _ := sessions.GetByKey("sess-1") // caller-supplied string keys
_, _ = sessions.UpdateIfRev("sess-1", rec.Rev, map[string]any{"status": "closed"}) // CASOpt into transparent encryption at rest with a key option plus a per-collection policy — secrets are sealed on write and returned as plaintext on read, with nothing else in your code changing:
db, _ := scriva.Open("./data",
scriva.WithPassphrase(os.Getenv("SCRIVA_PASSPHRASE")),
scriva.WithCollectionEncryption("users", scriva.EncryptFields("password", "ssn")), // field-level
scriva.WithCollectionEncryption("audit", scriva.EncryptRecord("id", "tenant")), // record-level
)
users := db.MustCollection("users")
id, _, _ := users.Insert(map[string]any{"email": "a@b.com", "password": "hunter2"})
rec, _ := users.Get(id) // rec.Data["password"] == "hunter2"; ciphertext on diskA wrong passphrase fails fast on Open. Key rotation and enable/disable are
runtime operations on the returned collection (RotateKey, SetEncryptionPolicy,
MigrateNow, EncryptionStatus). See
docs/encryption-at-rest.md for the full design.
The embedded surface includes caller-supplied string keys, per-record revisions
with compare-and-swap, upsert, count/exists, secondary indexes, in-process
Watch subscriptions, transparent encryption at rest, and a LoadJSONL
bulk-import path. The engine pulls in no
gRPC/protobuf/Prometheus/cobra/OpenTelemetry dependencies — a CI gate enforces
that.
This is a distinct distribution channel: go get for embedding, while the
standalone server ships via Homebrew/apt/GHCR and tagged binary releases. Both
build from the same repo. See docs/embedding.md for the
full API reference, durability modes, the Watch overflow contract, the
versioning/stability policy, and a migration guide.
Idiomatic, hand-written client libraries are available for ten languages. Each
wraps the same gRPC API, takes the same connection config (host, port,
api_key, optional TLS CA cert), and exposes every RPC including the streaming
Find and Watch calls.
| Language | Install | Reference |
|---|---|---|
| Python | pip install scriva |
clients/python |
| JavaScript / TypeScript | npm i scriva |
clients/js |
| PHP | composer require srjn45/scriva |
clients/php |
| Java | io.github.srjn45:scriva-client (Maven Central) |
clients/java |
| Kotlin | io.github.srjn45:scriva-client-kotlin:1.2.1 (Gradle, Maven Central) |
clients/kotlin |
| Scala | "io.github.srjn45" %% "scriva-client-scala" % "1.2.1" (sbt, Maven Central) |
clients/scala |
| Clojure | io.github.srjn45/scriva-client-clojure {:mvn/version "1.2.1"} (Clojars) |
clients/clojure |
| Ruby | gem install scriva |
clients/ruby |
| Rust | cargo add scriva |
clients/rust |
| C# / .NET | dotnet add package Scriva.Client |
clients/csharp |
Prefer to generate your own? The checked-in OpenAPI spec covers every RPC — see Getting Started.
| Document | Description |
|---|---|
| Getting Started | Install, run, first queries, TLS, config file, secondary indexes, metrics, logging, health probes |
| Architecture | Storage model, write/read paths, compaction, secondary indexes, crash safety |
| Embedding | Use ScrivaDB as an in-process Go library: scriva/engine API, keyed ops, CAS, Watch, migration, versioning policy |
git clone https://github.com/srjn45/scriva
cd scriva
make build # produces bin/scriva and bin/scriva-cli
make test # run tests with race detector
make proto # regenerate gRPC code (requires buf)Contributions are welcome — see CONTRIBUTING.md for how to build, test, and submit changes.
MIT — see LICENSE.
Rebuilt from FileDB PHP — original college project, now in Go.