nedb-engine
NEDB v2 — content-addressed DAG storage engine with NQL and HTTP server
This crate ships the nedbd binary — the NEDB v2 DAG HTTP server. Install it, point it at a data directory, and any language can speak to it over HTTP/JSON.
What is NEDB v2?
NEDB v2 replaces the append-only log (AOF) with a content-addressed Merkle DAG:
- Every document version is an immutable, BLAKE2b-hashed object. Nothing is ever overwritten.
- Every write produces a new chain head — a BLAKE2b commitment over the entire database history.
- Time-travel: read any document
AS OF seq Nto see exactly what it contained at that point. - Causal provenance: documents link to their causal parents via
caused_byhashes.TRACE caused_bywalks the full causal graph backward. - TRAVERSE: named graph relations via
__links__.FROM person WHERE _id = "robert" TRAVERSE parent_ofreturns linked nodes. - O(1) warm start: a
MANIFESTfile storesseq+ Merkle head so restarts never re-scan the entire object store. - Instant cold start: the daemon accepts connections immediately; background scan loads objects incrementally.
- AES-256-GCM at rest: optional symmetric encryption with a double-envelope key structure (TMK wraps DEK).
Install
The nedbd binary lands in ~/.cargo/bin/. Make sure that's on your PATH.
To verify:
Usage
nedbd [OPTIONS] [data_dir]
| Argument | Default | Description |
|---|---|---|
data_dir |
./nedb-data |
Directory for database files |
--dag |
off | Use the v2 content-addressed DAG engine |
--cast |
off | Enable natural-language query planning (requires --features cast) |
--doctor |
— | Diagnose environment, print fix commands |
Environment variables
| Variable | Default | Description |
|---|---|---|
NEDBD_HOST |
127.0.0.1 |
Bind address |
NEDBD_PORT |
7070 |
HTTP port |
NEDBD_TOKEN |
— | Bearer token for auth (optional) |
NEDB_TMK |
— | 64-char hex master key for AES-256-GCM encryption at rest |
NEDBD_MEMORY |
— | 1 = pure in-memory mode (no disk I/O) |
NEDBD_DAG |
— | 1 = same as --dag flag |
NEDBD_CAST |
— | 1 = same as --cast flag |
NEDBD_CAST_MODEL |
— | Explicit path to a model.cast container |
HTTP API
All endpoints return JSON. Auth: Authorization: Bearer <token> if NEDBD_TOKEN is set.
GET /health
GET /v1/databases
POST /v1/databases {name, init?}
GET /v1/databases/<name>
DELETE /v1/databases/<name>
POST /v1/databases/<name>/put {coll, id, doc, caused_by?}
POST /v1/databases/<name>/query {nql}
POST /v1/databases/<name>/link {frm, rel, to}
POST /v1/databases/<name>/neighbors {node, rel}
POST /v1/databases/<name>/cast {prompt, execute?} # feature = "cast"
GET /v1/databases/<name>/verify
POST /v1/databases/<name>/checkpoint
Cast — natural language into NQL
Optional. Feature-gated, off by default.
A 3.33M-parameter model (nedb-cast-slm) turns a short English prompt into NQL. It runs locally on CPU in a few milliseconds. No API key, no network call, no tokens billed to anyone.
Why this lives in the engine
The hard part of natural-language querying isn't the model — it's knowing the schema. A client has to fetch the collection list and pass it in, and it's stale the moment it arrives. The engine already holds the live list.
So the planner is constrained against real collections at the instant of the call:
HTTP 422. Not an empty result set — an empty result set reads as "no matching rows", which would be a lie. The query was fine; the collection was imaginary.
Every nedbd client inherits this for free instead of reimplementing it.
Three safety properties
1 · The model never executes anything. It emits text. That text goes to the same nql::query path a hand-typed query uses. There is no second executor to audit.
2 · Validation is parsing, not pattern-matching. nql::parse and nql::execute share one code path, so they cannot disagree about what is well-formed. Unparseable output returns 422 with the offending text attached.
3 · execute defaults to false. You get a plan for review. Opt in explicitly:
# → { …, "executed": true, "count": 2, "rows": [ … ] }
That default is not decoration. Here is a real miss, from a real run:
prompt "paid orders over 100"
nql FROM orders WHERE status = "paid" LIMIT 100 ← wrong
correct FROM orders WHERE status = "paid" AND total > 100
It read "over 100" as LIMIT 100 and dropped the second predicate. The row count still came back 2, because both paid orders happened to exceed 100 — a count-only check would have called that a pass. A human reading LIMIT 100 catches it instantly. An auto-executing client does not.
Multi-predicate WHERE is the model's weakest clause (85.1% eval / 61.2% holdout). Ship accordingly.
Enabling it
Off by default because it pulls a model dependency and expects weights at runtime — most deployments want neither.
# 1. build with the feature
# 2. get the weights (~13 MB)
# 3. run
# cast enabled — 3.33M params, vocab 581, ./data/model.cast
Model search order, first hit wins:
$NEDBD_CAST_MODEL<data_dir>/model.cast$CAST_HOME/model.cast~/.cache/nedb-cast-slm/v10.30.90/model.cast— where the Python and npm packages cache it, so a machine that has run either one is already ready
The container is checksum-verified on load. A silently corrupt model would emit plausible-but-wrong queries, which is the worst possible failure mode for a query planner.
Without the feature the route still exists and returns 501 — so a client can detect the capability instead of guessing. Missing weights on a --cast build is loud but non-fatal: the daemon starts and serves everything else normally.
As a library
use Caster;
let caster = load?;
let out = caster.cast_checked;
if out.collection_known && parse.is_ok
Decoding is greedy and deterministic — a DSL has exactly one right answer, so sampling could only hurt. The same prompt always produces the same plan, which makes the endpoint safe to cache.
NQL — NEDB Query Language
-- Basic queries
FROM person LIMIT 10
FROM driver WHERE status = "active"
FROM driver WHERE rating >= 4.5 ORDER BY rating DESC
-- Time-travel
FROM will WHERE _id = "evans_will_2019" AS OF 5
-- Causal trace (walks caused_by links backward)
FROM event WHERE _id = "probate_filing" TRACE caused_by
-- Graph traversal
FROM person WHERE _id = "robert" TRAVERSE parent_of
Example: The Will — causal DAG in action
=
=
return
=
# Robert writes his will
=
=
# Amendment — chains off v1
=
# TRACE proves the amendment links back to the original
=
=
=
# → both versions + Robert's testator record, in causal order
Python companion
The nedb-engine PyPI package ships the same server binary bundled in the wheel, plus:
- Pure-Python AOF engine (
NEDBclass) - Embedded v2 DAG API (
nedb._native.NedbCore) on supported platforms nedbdconsole script
# Use embedded API (Linux/macOS/Windows CPython)
# Use HTTP mode (any platform, including MSYS2/MinGW)
NEDB_URL=http://localhost:7070
# Diagnose
License
BUSL-1.1 — Business Source License. Free for development and evaluation; production use requires a commercial licence from INTERCHAINED, LLC.
Built by INTERCHAINED, LLC × Claude Sonnet 4.6