nedb-engine 8.0.0 → 8.8.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,7 +16,7 @@ at the same version on the same tag.
16
16
  [![nedb-engine-client npm](https://img.shields.io/npm/v/nedb-engine-client?label=nedb-engine-client&color=34d399)](https://www.npmjs.com/package/nedb-engine-client)
17
17
  [![License: BUSL-1.1](https://img.shields.io/badge/license-BUSL--1.1-f59e0b)](https://github.com/Eth-Interchained/nedb/blob/master/LICENSE) [![Free under $1M revenue](https://img.shields.io/badge/free%20under%20%241M%20revenue-22c55e)](https://github.com/Eth-Interchained/nedb/blob/master/LICENSE)
18
18
 
19
- **[Studio → studio.interchained.org](https://studio.interchained.org)** · **[nedb.aiassist.net](https://nedb.aiassist.net)**
19
+ **[Studio → studio.interchained.org](https://studio.interchained.org)** · **[nedb.aiassist.net](https://nedb.aiassist.net)** · **[neSQL → the language](https://github.com/Eth-Interchained/neSQL)**
20
20
 
21
21
  > ## 🟢 Free in production under $1M revenue
22
22
  > NEDB is licensed under the **Business Source License 1.1** (since 4.0.0). If your organisation's annual
@@ -32,6 +32,101 @@ at the same version on the same tag.
32
32
 
33
33
  ---
34
34
 
35
+ ## Provenance — the reason NEDB exists
36
+
37
+ Every database stores *what*. NEDB stores *what*, *when*, *when it was true*, and *why* — all sealed
38
+ in a cryptographic hash chain that proves none of it was tampered with.
39
+
40
+ Two time axes, one causal graph, one hash chain:
41
+
42
+ | Question | Clause / field | Answers |
43
+ |---|---|---|
44
+ | *What* is the value? | the row itself | current state |
45
+ | *When was it written?* | `AS OF <seq>` — transaction time | "set to 999 at seq 41" |
46
+ | *When was it true in the world?* | `VALID AS OF "<date>"` — valid time | "the 2024 rate" |
47
+ | *Why did it happen?* | `caused_by` → `TRACE caused_by` | the exact parent writes |
48
+ | *What did it cause?* | `TRACE caused_by REVERSE` | forward consequence |
49
+ | *Can I prove any of this to a third party?* | `verify()` · Merkle head · `proof()` | yes — locally, without trusting the server |
50
+
51
+ ```python
52
+ from nedb import NEDB
53
+
54
+ db = NEDB("./mydata")
55
+
56
+ # Transaction time — the value at any past sequence, forever (no garbage collection)
57
+ snap = db.seq
58
+ db.put("users", "alice", {"age": 32})
59
+ db.get("users", "alice", as_of=snap) # → the prior version
60
+
61
+ # Valid time — what the world believed on a date, regardless of when written
62
+ db.put("policy", "rate_2024", {"pct": 5.0}, valid_from="2024-01-01", valid_to="2024-12-31")
63
+ db.query('FROM policy VALID AS OF "2024-06-15"') # → rate 5.0
64
+
65
+ # Both axes at once — what did the system KNOW at seq 200 about 2024-02-15?
66
+ db.query('FROM policy AS OF 200 VALID AS OF "2024-02-15"')
67
+
68
+ # Causal provenance — an edge, not a comment
69
+ db.put("inputs", "msg_1", {"text": "user prefers dark mode"})
70
+ seq_msg = db.seq
71
+ db.put("beliefs", "dark_mode", {"value": True},
72
+ caused_by=[seq_msg], evidence="user_message", confidence=0.95)
73
+
74
+ db.query('FROM beliefs WHERE _id = "dark_mode" TRACE caused_by') # → msg_1
75
+ db.query('FROM inputs WHERE _id = "msg_1" TRACE caused_by REVERSE') # → dark_mode
76
+
77
+ # Tamper evidence — a 64-char BLAKE2b Merkle head on every write, verifiable offline
78
+ assert db.verify()
79
+ ```
80
+
81
+ Provenance travels with the row, and it is **selectable like any other column** — `_id`, `_hash`,
82
+ `_seq`, `_coll` on every document, `_caused_by` / `_valid_from` / `_valid_to` where set:
83
+
84
+ ```sql
85
+ SELECT _id, _hash, _seq FROM audit ORDER BY _seq;
86
+ SELECT _id FROM audit TRACE caused_by; -- works in NQL and in SQL
87
+ INSERT INTO audit (_id, _caused_by, kind) VALUES ('leaf', '<parent-hash>', 'reprice');
88
+ ```
89
+
90
+ One hash that commits to **what a database currently says** — independent of the route it took to
91
+ get there — is a separate primitive, and it is specified, versioned, and test-locked across both
92
+ engines: see [`docs/state-root-v1.md`](docs/state-root-v1.md) and
93
+ [`vectors/state_root_v1.json`](vectors/state_root_v1.json). The running Merkle head commits to
94
+ *history*; the state root commits to *now*; replica agreement and drift detection compare roots.
95
+
96
+ **The chain is not a side feature — it is the write path.** A SQL `UPDATE` is a new version, a SQL
97
+ `DELETE` is a tombstone, and `verify()` still passes afterwards, because writes through any surface
98
+ are ordinary engine writes. There is no configuration that silently drops history.
99
+
100
+ ---
101
+
102
+ ## The four-axis model
103
+
104
+ | Axis | Mechanism | Other databases |
105
+ |---|---|:---:|
106
+ | **What** — current state | content-addressed objects, BLAKE2b-verified on every read | the only axis they have |
107
+ | **When** — transaction time | every version retained, `AS OF <seq>` | ❌ |
108
+ | **True-when** — valid time | bi-temporal fields, `VALID AS OF "<date>"` | ❌ |
109
+ | **Why** — causal provenance | `caused_by` edges in the DAG, `TRACE` both directions | ❌ |
110
+
111
+ SQLite, Redis and MongoDB store *what*. Add triggers and you have written a side table no query
112
+ optimizer knows about, with no chain and no proof. NEDB's provenance is native, indexed,
113
+ traversable — and the same hash chain that keeps it honest also protects it.
114
+
115
+ ### The comparison table
116
+
117
+ | Capability | NEDB | SQLite | Redis | MongoDB |
118
+ |---|:---:|:---:|:---:|:---:|
119
+ | Hash-chained tamper evidence | ✅ | ❌ | ❌ | ❌ |
120
+ | Time-travel reads (`AS OF seq`) | ✅ | ❌ | ❌ | ❌ |
121
+ | Bi-temporal (`VALID AS OF date`) | ✅ | ❌ | ❌ | ❌ |
122
+ | Causal Write Provenance | ✅ | ❌ | ❌ | ❌ |
123
+ | Replay-protected idempotent writes | ✅ | ❌ | ❌ | ❌ |
124
+ | SQL + Redis + MongoDB adapters | ✅ | — | — | — |
125
+ | Concurrent group-commit daemon | ✅ | ❌ | ✅ | ✅ |
126
+ | At-rest AES-256-GCM encryption | ✅ | ❌ | ❌ | — |
127
+
128
+ ---
129
+
35
130
  ## [neSQL](https://github.com/Eth-Interchained/neSQL) — the language this engine speaks
36
131
 
37
132
  Nobody should have to learn a query language to use a database. That sentence cost
@@ -76,7 +171,7 @@ evaluator with no flag to set, the `nesql` CLI likewise.
76
171
  [![neSQL on crates.io](https://img.shields.io/crates/v/nesql?label=nesql%20·%20crates.io&color=a855f7)](https://crates.io/crates/nesql)
77
172
  [![neSQL on npm](https://img.shields.io/npm/v/nesql-engine?label=nesql-engine%20·%20npm&color=a855f7)](https://www.npmjs.com/package/nesql-engine)
78
173
 
79
- Those three badges read **0.0.1** next to an engine at 6.1.0, and that is
174
+ Those three badges read **0.0.1** next to an engine at 8.0.0, and that is
80
175
  deliberate rather than neglected. They are **reserved names**: each package loads,
81
176
  reports the vendored PostgreSQL release, and answers `is_release() == false`,
82
177
  because a package that imports cleanly and then lies is worse than one that is not
@@ -86,8 +181,8 @@ both halves of the grammar and the CLI's source, side by side.
86
181
 
87
182
  ### `nesql` — the CLI, and it speaks neSQL
88
183
 
89
- Ships in this release, no flag. `nesql` opens a store directly no daemon, no
90
- port — and answers both halves of the language through **one** `query` command:
184
+ Ships inside `pip install nedb-engine` no daemon, no portand answers both
185
+ halves of the language through **one** `query` command:
91
186
 
92
187
  ```console
93
188
  $ nesql --db ./store query "SELECT who, total FROM orders ORDER BY total DESC"
@@ -359,12 +454,6 @@ a seq is exact where a time would be approximate.
359
454
  compacted store answers "not available at that sequence" rather than
360
455
  returning a stale value, and `verify()` stays clean.
361
456
 
362
- Provenance is selectable like any other column:
363
-
364
- ```sql
365
- SELECT _id, _hash, _seq FROM audit ORDER BY _seq;
366
- ```
367
-
368
457
  **NEDB speaks SQL. That sentence used to carry a caveat, and no longer does.**
369
458
 
370
459
  For most of this project's life it was true that the endpoint served a
@@ -399,42 +488,6 @@ NEDB is append-only *so that history cannot be discarded* — that is the produc
399
488
  not a gap — and DDL is refused because collections are created by the first write
400
489
  to them. Those answers do not change.
401
490
 
402
- ### Every other row on that table was a translation artefact — and they are gone
403
-
404
- > ### 🆕 [**neSQL**](https://github.com/Eth-Interchained/neSQL) — PostgreSQL's grammar, NEDB's memory
405
- >
406
- > Those refusals were never the engine's limits. `sqlselect.rs` has had
407
- > nested-loop and hash joins, subqueries, `EXISTS`, quantified comparisons, set
408
- > operations, `array_agg(x ORDER BY y)` and derived tables for some time — they
409
- > were simply unreachable *through a translator*, because the translator's
410
- > target was NQL. They are reachable now, with nothing to set.
411
- >
412
- > neSQL vendors PostgreSQL's **real grammar** — `gram.y`, 19,513 lines and 492
413
- > keywords, from 17.4, licence intact — and extends it with the clauses NEDB
414
- > needs, rather than rewriting SQL into a language that cannot express it.
415
- >
416
- > It is worth knowing *why* the temporal clauses were never free: `SYSTEM_TIME`,
417
- > `PERIOD` and `PORTION` appear **zero** times in PostgreSQL's grammar. Postgres
418
- > has no temporal SQL at all, and `AS OF SYSTEM TIME` is a CockroachDB
419
- > extension — so NEDB's temporal clauses are additions *to* the vendored grammar
420
- > rather than deviations *from* it. The same road CockroachDB, Materialize and
421
- > RisingWave took. What the grammar does hand over free: `WITH RECURSIVE`,
422
- > window functions, `GROUPING SETS` and `MERGE`.
423
- >
424
- > ```bash
425
- > pip install nesql · cargo add nesql · npm install nesql-engine
426
- > ```
427
- >
428
- > Those three are **reserved names**, not a product: each loads and answers
429
- > `is_release() == false`, because a package that imports cleanly and then lies
430
- > is worse than one that isn't published. The engine is what ships today, and
431
- > neSQL will be this same engine under its own name.
432
- >
433
- > **NQL is not being deleted, and it is not being wrapped.** Its verbs are SQL
434
- > clauses now — `AS OF SYSTEM TIME`, `VALID AS OF`, `SEARCH` — parsed by the SQL
435
- > side and executed by the NQL engine. One implementation, two front-ends,
436
- > neither one a second-class guest.
437
-
438
491
  Every refusal names the boundary instead of saying "syntax error", and a
439
492
  grouped query that projects a column SQL would reject gets Postgres's own
440
493
  message rather than a silent `NULL`.
@@ -787,298 +840,148 @@ The deployment figures below are a **dated snapshot**, not a live readout: **1,3
787
840
 
788
841
  ---
789
842
 
790
- ## What makes NEDB different
843
+ ## Performance every number dated and reproducible
791
844
 
792
- Every database stores *what*. NEDB stores *what*, *when*, *when it was true*, and *why* all sealed in a cryptographic hash chain that proves none of it was tampered with.
845
+ NEDB is benchmarked honestly: the machine is named, the command is in the repo, and a number you cannot reproduce is a number we do not print. Absolute figures are **not** cross-machine comparable — read the ratios.
793
846
 
794
- | Capability | NEDB | SQLite | Redis | MongoDB |
795
- |---|:---:|:---:|:---:|:---:|
796
- | Hash-chained tamper evidence | ✅ | ❌ | ❌ | ❌ |
797
- | Time-travel reads (`AS OF seq`) | ✅ | ❌ | ❌ | ❌ |
798
- | Bi-temporal (`VALID AS OF date`) | ✅ | ❌ | ❌ | ❌ |
799
- | Causal Write Provenance | ✅ | ❌ | ❌ | ❌ |
800
- | Replay-protected idempotent writes | ✅ | ❌ | ❌ | ❌ |
801
- | SQL + Redis + MongoDB adapters | ✅ | — | — | — |
802
- | Concurrent group-commit daemon | ✅ | ❌ | ✅ | ✅ |
803
- | At-rest AES-256-GCM encryption | ✅ | ❌ | ❌ | — |
804
-
805
- ---
847
+ **Embedded core (Rust engine, in-process) `bench/RESULTS.md`, 2026-06-14, Linux x86-64, Python 3.9.25.
848
+ Reproduce: `python3 bench/benchmarks.py --save`**
806
849
 
807
- ## Install
808
-
809
- ```bash
810
- pip install nedb-engine # Python 3.8 pure-Python + optional Rust native wheel
811
- npm install nedb-engine # Node ≥ 16 napi-rs prebuilt binaries
812
- ```
850
+ | Operation | Throughput | Latency (avg) |
851
+ |-----------|-----------|---------------|
852
+ | PUT (replace, no index) | **63.5K/s** | 15.74 µs |
853
+ | GET (point read, HEAD) | **1.33M/s** | 0.75 µs |
854
+ | GET (`AS OF`time-travel read) | **942.9K/s** | 1.06 µs |
855
+ | QUERY: eq filter, no index (scan) | 514.1K/s | 1.95 µs |
856
+ | QUERY: eq filter, eq index | **1.45M/s** | 0.69 µs |
857
+ | QUERY: ORDER BY + ordered index, LIMIT 20 | 454.7K/s | 2.20 µs |
858
+ | QUERY: SEARCH, inverted index | 492.3K/s | 2.03 µs |
859
+ | PUT durable (AOF + fsync) | 7.3K/s | 137.59 µs |
813
860
 
814
- ### Prebuilt platforms
861
+ Time travel is not a tax: an `AS OF` read runs at **~70% of the speed of a current-state read**.
815
862
 
816
- Both registries ship prebuilt binaries for:
863
+ **The daemon over HTTP (v2 DAG server v2.2.31, Intel iMac — 10k writes / 100k reads / 30k objects, AES-256-GCM on).
864
+ Reproduce: `NEDBD_DAG=1 nedbd --data /tmp/perf &` then `python3 tests/test_dag_perf.py --n 10000 --reads 100000`**
817
865
 
818
- | Platform | libc | Python wheel | Node addon |
866
+ | Operation | Throughput | p50 | p99 |
819
867
  |---|---|---|---|
820
- | Linux x86_64 | glibc | manylinux | |
821
- | Linux x86_64 | musl (Alpine) | musllinux | |
822
- | Linux aarch64 (Graviton, Ampere, Apple-Silicon containers) | glibc | manylinux | ✅ |
823
- | Linux aarch64 | musl (Alpine) | musllinux | |
824
- | macOS arm64 + x86_64 | — | | |
825
- | Windows x86_64 | MSVC | ✅ | ✅ |
826
-
827
- On Python, any platform without a prebuilt wheel still installs: pip falls back
828
- to the universal `py3-none-any` wheel and you get the pure-Python v1 AOF engine
829
- (correct, slower, no embedded DAG). On Node there is no such fallback — an
830
- unlisted platform has no addon.
831
-
832
- ---
868
+ | Sequential writes | **418 ops/s** | 2.3 ms | 3.3 ms |
869
+ | Point-lookup reads | **478 ops/s** | 2.0 ms | 3.0 ms |
870
+ | ORDER BY queries | **489 ops/s** | 1.8 ms | 4.3 ms |
871
+ | Batch writes (500 ops/req) | **1,104 ops/s** | 0.9 ms | 1.2 ms |
872
+ | Tamper-verify (30k objects) | ~21,000 BLAKE2b/sec | — | 1.38 s total |
833
873
 
834
- ## Python5-minute tour
874
+ p99 latencies hold because of `TCP_NODELAY` on the axum listener without it macOS loopback adds the Nagle algorithm's 40–200 ms delay on small writes.
835
875
 
836
- ```python
837
- from nedb import NEDB
876
+ **SQL joins inside the evaluator (nested loop vs hash, fixed-seed workload, engine 4.0.0, Linux x86-64) —
877
+ `docs/BENCH-sqlselect.md`. Reproduce: `cargo run --release --example sqlbench`**
838
878
 
839
- db = NEDB("./mydata") # durable: every op is AOF-logged, fsync'd, and hash-chained
840
- # db = NEDB() # or in-memory
879
+ | workload | nested (ms) | hash (ms) | speedup |
880
+ |---|---:|---:|---:|
881
+ | equality join (1,000 × 500 rows) | 366.62 | 4.05 | **90.6×** |
882
+ | left join | 373.27 | 4.03 | **92.5×** |
883
+ | join + sort | 403.51 | 6.35 | **63.6×** |
884
+ | join + broad pred | 336.34 | 4.68 | **71.9×** |
885
+ | non-equality join | 344.69 | 346.33 | — *(same path, no hash key)* |
841
886
 
842
- db.create_index("users", "status", "eq")
843
- db.create_index("users", "bio", "search")
887
+ **Indexed range scans (20,000 rows, two identical stores, one indexed) — `scripts/bench_index_range.py`**
844
888
 
845
- db.put("users", "alice", {"name": "Alice", "age": 31, "status": "active", "bio": "rust hacker"})
846
- db.put("users", "bob", {"name": "Bob", "age": 24, "status": "active", "bio": "python dev"})
889
+ | Query | Scan | Indexed | Speedup |
890
+ | --- | --- | --- | --- |
891
+ | `WHERE fee = 10000` | 137 ms | 0.01 ms | **17,000×** |
892
+ | `WHERE fee IN (a, b, c)` | 185 ms | 0.02 ms | 9,700× |
893
+ | `WHERE fee BETWEEN …` (1% of rows) | 186 ms | 1.1 ms | 170× |
894
+ | `WHERE fee BETWEEN …` (10% of rows) | 188 ms | 13 ms | 14× |
895
+ | unindexed field (control) | 188 ms | 187 ms | 1.0× |
847
896
 
848
- # NQL: WHERE + ORDER BY + LIMIT + SEARCH + TRAVERSE + GROUP BY
849
- db.query('FROM users WHERE status = "active" ORDER BY age ASC')
850
- db.query('FROM users SEARCH "rust"')
851
- db.query('FROM users GROUP BY status COUNT')
897
+ **v1 Python server (baseline single-threaded AOF):**
852
898
 
853
- # Full boolean predicates IN, BETWEEN, LIKE, IS NULL, OR, NOT, parentheses
854
- db.query('FROM users WHERE status IN ("active", "trialing")')
855
- db.query('FROM users WHERE age BETWEEN 25 AND 40')
856
- db.query('FROM users WHERE bio LIKE "%rust%" AND NOT (status = "retired")')
857
- db.query('FROM users WHERE (age < 25 OR age > 60) AND bio IS NOT NULL')
899
+ | Operation | Throughput | p99 latency |
900
+ |---|---|---|
901
+ | Sequential PUT | ~23/s | 44 ms |
902
+ | Concurrent PUT (16 workers) | ~92/s | 48 ms |
903
+ | Batch PUT (500 ops/request) | ~520 ops/s | 1.9 ms/op |
904
+ | Point-lookup read (NQL) | ~23/s | 44 ms |
905
+ | Rust napi PUT (FFI) | ~70K/s | — |
906
+ | Rust napi GET (FFI) | ~330K/s | — |
858
907
 
859
- # Time-travelAS OF any past sequence
860
- snap = db.seq
861
- db.put("users", "alice", {"name": "Alice", "age": 32, "status": "retired"})
862
- db.get("users", "alice", as_of=snap) # → age 31, status active
908
+ And the number from the v3 section that justifies the whole storage line: a real itcd chainstate flush of 2,549 coins went from **minutes** on the v2 loose store to **1.71 s** on `--dag-v3`measured on the real chain, not a fixture.
863
909
 
864
- # Bi-temporal — VALID AS OF any past date
865
- db.put("policy", "rate_2024", {"pct": 5.0}, valid_from="2024-01-01", valid_to="2024-12-31")
866
- db.put("policy", "rate_2025", {"pct": 6.0}, valid_from="2025-01-01")
867
- db.query('FROM policy VALID AS OF "2024-06-15"') # → rate 5.0
910
+ ---
868
911
 
869
- # Causal Write Provenance why did this write happen?
870
- db.put("inputs", "msg_1", {"text": "user prefers dark mode"})
871
- seq_msg = db.seq
872
- db.put("beliefs", "dark_mode", {"value": True},
873
- caused_by=[seq_msg], evidence="user_message", confidence=0.95)
874
- db.query('FROM beliefs WHERE _id = "dark_mode" TRACE caused_by') # → msg_1
875
- db.query('FROM inputs WHERE _id = "msg_1" TRACE caused_by REVERSE') # → dark_mode
912
+ ## nedbdthe concurrent server daemon
876
913
 
877
- # Relations + graph traversal
878
- db.link("users:alice", "follows", "users:bob")
879
- db.query('FROM users WHERE _id = "alice" TRAVERSE follows')
914
+ nedbd runs NEDB as a long-lived process with an HTTP/JSON API and an optional RESP2 wire protocol. Built on a **single-writer group-commit sequencer** — parallel reads, batched durable writes, one hash-chain per database, zero write-write races.
880
915
 
881
- # Hash-chain integrity
882
- assert db.verify() # cryptographic proof no tampering
916
+ ```bash
917
+ nedbd # :7070, data ./nedb-data (v1 AOF engine)
918
+ nedbd --dag --data ./data # v2 DAG engine (or NEDBD_DAG=1)
919
+ NEDBD_RESP2_PORT=6380 nedbd # also speak RESP2 (redis-cli compatible)
920
+ nedbd --log-level 2 # 0=errors 1=requests 2=deploy 3=verbose
883
921
 
884
- # SQL, Redis, MongoDB compatibility adapters
885
- from nedb import sql_exec, RedisCompat, MongoClient
886
- sql_exec(db, "SELECT * FROM users WHERE status = 'active' ORDER BY age DESC")
887
- r = RedisCompat(db); r.execute("HSET", "user:1", "name", "Alice")
888
- MongoClient(db)["users"].find({"status": "active"}).sort("age", -1).to_list()
922
+ # Live event stream (since 2.2.31) — SSE: scan progress, ready, per-write head
923
+ curl http://127.0.0.1:7070/events
889
924
  ```
890
925
 
891
- ---
926
+ ### Companion CLIs
892
927
 
893
- ## Official Python clienttalk to nedbd over HTTP
928
+ Alongside the daemon, `cargo install nedb-engine` ships **`nedb-cli`** operate on a store directory offline (`head`/`status`/`verify`/`get`/`scan`/`flush`/`repair`/`export`) — and **`nedb-inspector`**, a deterministic checker that warns when a durable open lacks flush-on-exit wiring. Full reference: [**docs/CLI.md**](docs/CLI.md).
894
929
 
895
- Running the daemon? `nedb.client.NedbClient` is the official client for its
896
- HTTP API — extracted from the battle-tested clients that ran a production
897
- Redis→NEDB mainnet migration, speaking the full route surface: queries,
898
- atomic CAS transactions, TTL, indexes, relations, Merkle proofs, and the
899
- Mongo-compat endpoint. Env-var defaults (`NEDBD_URL`, `NEDBD_TOKEN`,
900
- `NEDB_DB`) mirror the daemon's own.
930
+ ### Startup modes
901
931
 
902
- ```python
903
- from nedb import NedbClient, PreconditionFailed, op_put
932
+ - **Warm start** — every restart after the first open reads the `MANIFEST` file and restores `seq` + Merkle `head` in **O(1)**. No scan, no replay, independent of dataset size. Boots in milliseconds.
933
+ - **Cold start** — first open of an existing dataset spawns the integrity scan in a background thread *and accepts connections immediately*. Reads serve instantly from the content-addressed DAG; writes return `HTTP 503 startup in progress` until the `startup_ready` gate flips. Progress (objects, rate, ETA) streams over `GET /events`.
904
934
 
905
- c = NedbClient("http://127.0.0.1:7070", db="app", token="s3cret")
906
- c.ensure_database()
935
+ ### Environment variables
907
936
 
908
- c.put("users", "u1", {"id": "u1", "email": "a@b.c"}, idem="signup-u1")
909
- c.query('FROM users WHERE email = "a@b.c"') # full NQL rides through
910
- c.query("FROM users AS OF 41") # time-travel included
937
+ | Variable | Default | Description |
938
+ |---|---|---|
939
+ | `NEDBD_DAG` | `0` | Set `1` to launch the v2 DAG engine (`nedbd-v2`). Same as `--dag`. |
940
+ | `NEDBD_HOST` | `127.0.0.1` | Bind address. **v2.2.31** defaults to loopback (was `0.0.0.0`) — security hardening fix. Set explicitly to `0.0.0.0` to expose. |
941
+ | `NEDBD_PORT` | `7070` | HTTP bind port. |
942
+ | `NEDBD_TOKEN` | unset | Optional bearer token; required on every `/v1/*` request when set. |
943
+ | `NEDB_TMK` | unset | 32-byte hex AES-256-GCM at-rest encryption key. |
944
+ | `NEDBD_DATA` | `./nedb-data` | Root directory. v2 creates `dag/`, IdIndex sharded across **256 subdirectories**, and a small `MANIFEST` file. |
945
+ | `NEDBD_CAST` | `0` | Set `1` to enable the `/cast` natural-language planner. Same as `--cast`. Requires a build with `--features cast`. See [**Cast**](#cast--natural-language-into-nql). |
946
+ | `NEDBD_CAST_MODEL` | unset | Explicit path to a `model.cast` container. Otherwise searched in the data dir, `$CAST_HOME`, and `~/.cache/nedb-cast-slm/`. |
911
947
 
912
- # Atomic all-or-nothing transaction with engine-checked preconditions —
913
- # the primitive that replaces Redis Lua scripts (if_seq: N = CAS, -1 = create-once)
914
- doc = c.get_doc("users", "u1") # docs carry _seq
915
- c.tx([op_put("users", "u1", {**doc, "plan": "pro"}, if_seq=doc["_seq"])])
948
+ ```bash
949
+ # Create a database with seed data and relations
950
+ curl -X POST :7070/v1/databases -d '{
951
+ "name": "shop",
952
+ "init": {
953
+ "indexes": [["users","status","eq"]],
954
+ "seed": {"users": [{"_id":"u1","name":"Alice","status":"active"}]},
955
+ "links": [["users:u1","buys","orders:o1"]]
956
+ }}'
916
957
 
917
- # Contested writes: retry ONLY on PreconditionFailed, capped backoff
918
- def bump():
919
- d = c.get_doc("counters", "hits") or {"n": 0}
920
- return c.tx([op_put("counters", "hits", {"n": d.get("n", 0) + 1},
921
- if_seq=d.get("_seq", -1))])
922
- c.cas_retry(bump)
958
+ # Query — the endpoint speaks neSQL: SQL *or* NQL, routed on the first keyword
959
+ curl -X POST :7070/v1/databases/shop/query \
960
+ -d '{"nql":"SELECT name FROM users WHERE status = '"'"'active'"'"' ORDER BY name"}'
961
+ # → {"rows":[{"name":"Alice"}],"count":1,"dialect":"sql", ...}
923
962
 
924
- # Integrity, verifiable WITHOUT trusting the server
925
- proof = c.proof(c.log(limit=1)[0]["hash"])
926
- from nedb import verify_proof; verify_proof(proof) # -> True, locally
927
- ```
963
+ curl -X POST :7070/v1/databases/shop/query \
964
+ -d '{"nql":"FROM users WHERE status = \"active\" ORDER BY name ASC"}'
965
+ # {"rows":[...],"count":1,"dialect":"nql", ...}
928
966
 
929
- A CAS miss raises the **same `PreconditionFailed`** (with the same
930
- `.failures` shape) the embedded engine raises — code written against
931
- `NEDB.tx` ports to the HTTP client without changing its except-clauses.
932
- Typed errors throughout: `NedbAuthError`, `NedbNotFound`, `NedbBadRequest`,
933
- `NedbConflict`, `CasExhausted`.
934
967
 
935
- ---
968
+ **The field is still called `nql`, and its contents no longer have to be.** This
969
+ endpoint accepts **neSQL** — NQL *or* PostgreSQL SQL — and answers with the
970
+ `dialect` it chose. The name is unchanged because every existing HTTP client
971
+ sends it; renaming would break them to gain nothing. Old NQL clients are
972
+ unaffected.
936
973
 
937
- ## Redis layer-2 wrap_redis()
974
+ Routing is **structural, not guessed**. NQL statements begin `FROM`; PostgreSQL
975
+ has no statement form that begins with `FROM`, so the leading keyword partitions
976
+ the two vocabularies rather than hinting at them. A first word in neither is
977
+ refused *naming both* — never handed to whichever parser seems likelier.
938
978
 
939
- Already running on Redis? Wrap your connection in one line and gain NEDB features *alongside* your existing Redis app — no migration required.
940
-
941
- ```python
942
- import redis, json
943
- from nedb import wrap_redis
944
-
945
- r = wrap_redis(redis.Redis("localhost", 6379), db_name="rideshare")
946
-
947
- # Step 1 — register: map Redis key globs to NEDB collections (chainable)
948
- (r.nedb
949
- .register("driver:*", collection="driver", value_parser=json.loads)
950
- .register("trip:*", collection="trip", value_type="hash")
951
- )
952
-
953
- # Step 2 — backfill: import all existing Redis data into NEDB in one pass
954
- imported = r.nedb.backfill() # → int (keys imported)
955
-
956
- # Step 3 — shadow: all future r.set/hset/... auto-chain into NEDB
957
- r.nedb.shadow_writes = True
958
-
959
- # ─── Alice's app keeps running — zero changes ───────────────────────────
960
- r.set("driver:d1", json.dumps({"name": "Bob", "status": "active"})) # ← shadowed
961
- r.hset("trip:t1", mapping={"status": "en_route", "driver_id": "d1"}) # ← shadowed
962
-
963
- # ─── New features available on the same connection ──────────────────────
964
- r.nedb.query('FROM driver WHERE status = "active" ORDER BY lat ASC')
965
- r.nedb.verify() # → True (every write chain-verified)
966
- r.nedb.head() # → 64-char BLAKE2b commitment hash
967
- ```
968
-
969
- **Isolation guarantee:** NEDB never writes to Alice's namespace. It owns only:
970
-
971
- | Key | Type | Purpose |
972
- |-----|------|---------|
973
- | `nedb:{db_name}:oplog` | Redis Stream | append-only op log |
974
- | `nedb:{db_name}:snapshot` | Redis Hash | checkpoint |
975
- | `nedb:{db_name}:meta` | Redis Hash | index config |
976
-
977
- See [`examples/fakeredis_demo.py`](examples/fakeredis_demo.py) for a full local demo (no Redis server needed).
978
-
979
- ---
980
-
981
- ## Node.js
982
-
983
- ```javascript
984
- import { NedbCore } from "nedb-engine";
985
-
986
- const db = new NedbCore(); // in-memory
987
- // const db = NedbCore.open("./data"); // durable
988
-
989
- db.createIndex("users", "status", "eq");
990
- db.put("users", "alice", JSON.stringify({ name: "Alice", age: 31, status: "active" }));
991
-
992
- // Time-travel
993
- const snap = db.seq(); // BigInt
994
- db.put("users", "alice", JSON.stringify({ name: "Alice", age: 32, status: "retired" }));
995
- JSON.parse(db.getAsOf("users", "alice", snap)).age; // → 31
996
-
997
- // Full NQL
998
- const rows = db.query('FROM users WHERE status = "active" ORDER BY age ASC');
999
- rows.map(r => JSON.parse(r));
1000
-
1001
- // Tamper evidence
1002
- db.verify(); // → true
1003
- db.head(); // → 64-char BLAKE2b commitment hash
1004
- db.seq(); // → BigInt
1005
- ```
1006
-
1007
- ---
1008
-
1009
- ## nedbd — the concurrent server daemon
1010
-
1011
- nedbd runs NEDB as a long-lived process with an HTTP/JSON API and an optional RESP2 wire protocol. Built on a **single-writer group-commit sequencer** — parallel reads, batched durable writes, one hash-chain per database, zero write-write races.
1012
-
1013
- ```bash
1014
- nedbd # :7070, data ./nedb-data (v1 AOF engine)
1015
- nedbd --dag --data ./data # v2 DAG engine (or NEDBD_DAG=1)
1016
- NEDBD_RESP2_PORT=6380 nedbd # also speak RESP2 (redis-cli compatible)
1017
- nedbd --log-level 2 # 0=errors 1=requests 2=deploy 3=verbose
1018
-
1019
- # Live event stream (since 2.2.31) — SSE: scan progress, ready, per-write head
1020
- curl http://127.0.0.1:7070/events
1021
- ```
1022
-
1023
- ### Companion CLIs
1024
-
1025
- Alongside the daemon, `cargo install nedb-engine` ships **`nedb-cli`** — operate on a store directory offline (`head`/`status`/`verify`/`get`/`scan`/`flush`/`repair`/`export`) — and **`nedb-inspector`**, a deterministic checker that warns when a durable open lacks flush-on-exit wiring. Full reference: [**docs/CLI.md**](docs/CLI.md).
1026
-
1027
- ### Startup modes
1028
-
1029
- - **Warm start** — every restart after the first open reads the `MANIFEST` file and restores `seq` + Merkle `head` in **O(1)**. No scan, no replay, independent of dataset size. Boots in milliseconds.
1030
- - **Cold start** — first open of an existing dataset spawns the integrity scan in a background thread *and accepts connections immediately*. Reads serve instantly from the content-addressed DAG; writes return `HTTP 503 startup in progress` until the `startup_ready` gate flips. Progress (objects, rate, ETA) streams over `GET /events`.
1031
-
1032
- ### Environment variables
1033
-
1034
- | Variable | Default | Description |
1035
- |---|---|---|
1036
- | `NEDBD_DAG` | `0` | Set `1` to launch the v2 DAG engine (`nedbd-v2`). Same as `--dag`. |
1037
- | `NEDBD_HOST` | `127.0.0.1` | Bind address. **v2.2.31** defaults to loopback (was `0.0.0.0`) — security hardening fix. Set explicitly to `0.0.0.0` to expose. |
1038
- | `NEDBD_PORT` | `7070` | HTTP bind port. |
1039
- | `NEDBD_TOKEN` | unset | Optional bearer token; required on every `/v1/*` request when set. |
1040
- | `NEDB_TMK` | unset | 32-byte hex AES-256-GCM at-rest encryption key. |
1041
- | `NEDBD_DATA` | `./nedb-data` | Root directory. v2 creates `dag/`, IdIndex sharded across **256 subdirectories**, and a small `MANIFEST` file. |
1042
- | `NEDBD_CAST` | `0` | Set `1` to enable the `/cast` natural-language planner. Same as `--cast`. Requires a build with `--features cast`. See [**Cast**](#cast--natural-language-into-nql). |
1043
- | `NEDBD_CAST_MODEL` | unset | Explicit path to a `model.cast` container. Otherwise searched in the data dir, `$CAST_HOME`, and `~/.cache/nedb-cast-slm/`. |
1044
-
1045
- ```bash
1046
- # Create a database with seed data and relations
1047
- curl -X POST :7070/v1/databases -d '{
1048
- "name": "shop",
1049
- "init": {
1050
- "indexes": [["users","status","eq"]],
1051
- "seed": {"users": [{"_id":"u1","name":"Alice","status":"active"}]},
1052
- "links": [["users:u1","buys","orders:o1"]]
1053
- }}'
1054
-
1055
- # Query — the endpoint speaks neSQL: SQL *or* NQL, routed on the first keyword
1056
- curl -X POST :7070/v1/databases/shop/query \
1057
- -d '{"nql":"SELECT name FROM users WHERE status = '"'"'active'"'"' ORDER BY name"}'
1058
- # → {"rows":[{"name":"Alice"}],"count":1,"dialect":"sql", ...}
1059
-
1060
- curl -X POST :7070/v1/databases/shop/query \
1061
- -d '{"nql":"FROM users WHERE status = \"active\" ORDER BY name ASC"}'
1062
- # → {"rows":[...],"count":1,"dialect":"nql", ...}
1063
-
1064
-
1065
- **The field is still called `nql`, and its contents no longer have to be.** This
1066
- endpoint accepts **neSQL** — NQL *or* PostgreSQL SQL — and answers with the
1067
- `dialect` it chose. The name is unchanged because every existing HTTP client
1068
- sends it; renaming would break them to gain nothing. Old NQL clients are
1069
- unaffected.
1070
-
1071
- Routing is **structural, not guessed**. NQL statements begin `FROM`; PostgreSQL
1072
- has no statement form that begins with `FROM`, so the leading keyword partitions
1073
- the two vocabularies rather than hinting at them. A first word in neither is
1074
- refused *naming both* — never handed to whichever parser seems likelier.
1075
-
1076
- ```bash
1077
- curl -X POST :7070/v1/databases/shop/query -d '{"nql":"GRANT ALL ON users"}'
1078
- # → 400 "GRANT" does not begin a statement in either half of neSQL
1079
- # NQL statements begin with: FROM
1080
- # SQL statements begin with: SELECT, INSERT, UPDATE, ...
1081
- ```
979
+ ```bash
980
+ curl -X POST :7070/v1/databases/shop/query -d '{"nql":"GRANT ALL ON users"}'
981
+ # → 400 "GRANT" does not begin a statement in either half of neSQL
982
+ # NQL statements begin with: FROM
983
+ # SQL statements begin with: SELECT, INSERT, UPDATE, ...
984
+ ```
1082
985
 
1083
986
  It is the **same router** `nesql query` uses — `nedb_engine::neql::route`, which
1084
987
  the CLI re-exports rather than copies. Two implementations of that decision
@@ -1182,14 +1085,13 @@ db.query("FROM blocks WHERE height BETWEEN 600000 AND 600100")
1182
1085
  ```
1183
1086
 
1184
1087
  Measured on 20,000 rows with `scripts/bench_index_range.py` — two identical
1185
- databases, one indexed, one not:
1088
+ databases, one indexed, one not (the full table lives in
1089
+ [**Performance**](#performance--every-number-dated-and-reproducible)):
1186
1090
 
1187
1091
  | Query | Scan | Indexed | Speedup |
1188
1092
  | --- | --- | --- | --- |
1189
1093
  | `WHERE fee = 10000` | 137 ms | 0.01 ms | 17,000× |
1190
- | `WHERE fee IN (a, b, c)` | 185 ms | 0.02 ms | 9,700× |
1191
1094
  | `WHERE fee BETWEEN …` (1% of rows) | 186 ms | 1.1 ms | 170× |
1192
- | `WHERE fee BETWEEN …` (10% of rows) | 188 ms | 13 ms | 14× |
1193
1095
  | unindexed field (control) | 188 ms | 187 ms | 1.0× |
1194
1096
 
1195
1097
  The planner asks the index how many rows each candidate range covers and takes
@@ -1510,108 +1412,237 @@ Two habits that avoid most misses:
1510
1412
 
1511
1413
  ---
1512
1414
 
1513
- ## Performance
1415
+ ## Architecture
1514
1416
 
1515
- **v2 DAG Rust server (v2.2.31, Intel iMac — 10k writes / 100k reads / 30k objects, AES-256-GCM on):**
1417
+ ```
1418
+ ┌──────────────────────────────────────────────────────────┐
1419
+ put/del → │ OpLog (BLAKE2b hash chain · per-client nonce · │ ← single source of truth
1420
+ link │ idempotency keys · causal provenance fields) │
1421
+ └───────────────┬──────────────────────────────────────────┘
1422
+ deterministic fold │ (state = pure function of the log)
1423
+ ┌──────────────┬──────────┴──────┬───────────────┬────────────────┐
1424
+ ▼ ▼ ▼ ▼ ▼
1425
+ MVCC store Relations Indexes CauseMap BlobStore
1426
+ (time-travel) (graph+AS OF) eq/ord/search (reverse index) (Cascade CDC)
1516
1427
 
1517
- | Operation | Throughput | p50 | p99 |
1518
- |---|---|---|---|
1519
- | Sequential writes | **418 ops/s** | 2.3 ms | 3.3 ms |
1520
- | Point-lookup reads | **478 ops/s** | 2.0 ms | 3.0 ms |
1521
- | ORDER BY queries | **489 ops/s** | 1.8 ms | 4.3 ms |
1522
- | Batch writes (500 ops/req) | **1,104 ops/s** | 0.9 ms | 1.2 ms |
1523
- | Tamper-verify (30k objects) | ~21,000 BLAKE2b/sec | — | 1.38 s total |
1428
+ ┌─────────────────────────────────┐
1429
+ Thread-safe → │ Sequencer (group-commit) │ ← single writer, parallel readers
1430
+ │ — one committer thread/db │
1431
+ │ — batch fsync │
1432
+ └─────────────────────────────────┘
1524
1433
 
1525
- p99 latencies hold because of `TCP_NODELAY` on the axum listener — without it macOS loopback adds the Nagle algorithm's 40–200 ms delay on small writes.
1434
+ Compatibility adapters: SQL · Redis · MongoDB
1435
+ Wire protocols: HTTP/JSON · RESP2 · PostgreSQL wire
1436
+ Encryption: AES-256-GCM at-rest (TMK/DEK double-envelope)
1437
+ ```
1526
1438
 
1527
- **v1 Python server (baseline — single-threaded AOF):**
1439
+ The provenance machinery is load-bearing infrastructure, not metadata stapled on: `CauseMap` is a
1440
+ reverse index over causal edges (that is why `TRACE … REVERSE` is as fast as forward), and the
1441
+ `Relations` layer backs both `TRAVERSE` and the `AS OF` joins.
1528
1442
 
1529
- | Operation | Throughput | p99 latency |
1530
- |---|---|---|
1531
- | Sequential PUT | ~23/s | 44 ms |
1532
- | Concurrent PUT (16 workers) | ~92/s | 48 ms |
1533
- | Batch PUT (500 ops/request) | ~520 ops/s | 1.9 ms/op |
1534
- | Point-lookup read (NQL) | ~23/s | 44 ms |
1535
- | Rust napi PUT (FFI) | ~70K/s | — |
1536
- | Rust napi GET (FFI) | ~330K/s | — |
1443
+ ---
1537
1444
 
1538
- Reproduce with the included benchmark:
1445
+ ## Install
1539
1446
 
1540
1447
  ```bash
1541
- NEDBD_DAG=1 nedbd --data /tmp/perf &
1542
- python3 tests/test_dag_perf.py --n 10000 --reads 100000
1448
+ pip install nedb-engine # Python ≥ 3.8 — pure-Python + optional Rust native wheel
1449
+ npm install nedb-engine # Node 16 — napi-rs prebuilt binaries
1543
1450
  ```
1544
1451
 
1452
+ ### Prebuilt platforms
1453
+
1454
+ Both registries ship prebuilt binaries for:
1455
+
1456
+ | Platform | libc | Python wheel | Node addon |
1457
+ |---|---|---|---|
1458
+ | Linux x86_64 | glibc | ✅ manylinux | ✅ |
1459
+ | Linux x86_64 | musl (Alpine) | ✅ musllinux | ✅ |
1460
+ | Linux aarch64 (Graviton, Ampere, Apple-Silicon containers) | glibc | ✅ manylinux | ✅ |
1461
+ | Linux aarch64 | musl (Alpine) | ✅ musllinux | ✅ |
1462
+ | macOS arm64 + x86_64 | — | ✅ | ✅ |
1463
+ | Windows x86_64 | MSVC | ✅ | ✅ |
1464
+
1465
+ On Python, any platform without a prebuilt wheel still installs: pip falls back
1466
+ to the universal `py3-none-any` wheel and you get the pure-Python v1 AOF engine
1467
+ (correct, slower, no embedded DAG). On Node there is no such fallback — an
1468
+ unlisted platform has no addon.
1469
+
1545
1470
  ---
1546
1471
 
1547
- ## NEDB v3 Segment / Pack Object Store
1472
+ ## Python5-minute tour
1548
1473
 
1549
- **v3 is an opt-in storage substrate that replaces the loose one-file-per-object layout with append-only *segment packs* — the difference between a chainstate flush that takes *minutes* and one that takes *under two seconds*.** It is **off by default** (byte-for-byte v2), enabled with one flag, and **transparent** to everything above the storage layer: NQL, `AS OF`, `VALID AS OF`, `TRACE`, the BLAKE2b Merkle head, and causal provenance all behave identically.
1474
+ Every example in this tour was executed against the released **8.0.0** package.
1550
1475
 
1551
- ### Why it exists
1476
+ ```python
1477
+ from nedb import NEDB
1552
1478
 
1553
- v2 stores every document version as its own content-addressed file at `objects/{hash[:2]}/{hash[2:]}`. That makes writes trivially atomic (write `.tmp` → `rename`) and corruption-proof — but each write costs a file create + `fsync` + rename **plus** a directory B-tree update. At scale that filesystem-metadata churn dominates: on a busy disk it caps sustained writes around **~185/s**, and a batch flush of a few thousand objects degrades into minutes. The bottleneck is the *number of files touched*, not the bytes written.
1479
+ db = NEDB("./mydata") # durable: every op is AOF-logged, fsync'd, and hash-chained
1480
+ # db = NEDB() # or in-memory
1554
1481
 
1555
- ### What it does
1482
+ db.create_index("users", "status", "eq")
1483
+ db.create_index("users", "bio", "search")
1556
1484
 
1557
- v3 batches objects into append-only **segment packs** — `objects/segments/seg-NNNNNN.dat` — where each record is `[content_len: u32-LE][content]`. A write appends to the active segment and updates an in-memory `hash → (segment_id, offset, len)` map; a batch commits with a **single `fsync`**. Thousands of per-file syscalls collapse into one sequential append plus one durability point, so **flush cost scales with bytes (sequential I/O), not object-count × syscall overhead.**
1485
+ db.put("users", "alice", {"name": "Alice", "age": 31, "status": "active", "bio": "rust hacker"})
1486
+ db.put("users", "bob", {"name": "Bob", "age": 24, "status": "active", "bio": "python dev"})
1558
1487
 
1559
- - **Compaction / pruning** `compact()` keeps the *live set* (the current version of every document, resolved from the id-index), rewrites those records into fresh segments, and reclaims the superseded/dead versions.
1560
- - **`.idx` sidecars** — each segment carries a sidecar (`NIX1` magic + entry count + fixed 44-byte entries + a BLAKE2b-256 checksum) so reopen rebuilds the in-memory index by reading the sidecar instead of scanning the whole pack. A missing or corrupt sidecar falls back to a full scan-and-heal — slower, never fatal.
1561
- - **Dual-read migration** — opening an existing v2 store in v3 mode is **non-destructive**: old loose objects stay fully readable, and only *new* writes go to segments. No migration step, no downtime, no rewrite.
1562
- - **Durable flush-on-close** `flush_all()` (and `Db`'s `Drop`) fsync the active segment, matching the flush-on-close contract of sled / RocksDB.
1488
+ # NQL: WHERE + ORDER BY + LIMIT + SEARCH + TRAVERSE + GROUP BY
1489
+ db.query('FROM users WHERE status = "active" ORDER BY age ASC')
1490
+ db.query('FROM users SEARCH "rust"')
1491
+ db.query('FROM users GROUP BY status COUNT')
1563
1492
 
1564
- ### How to enable
1493
+ # Full boolean predicates — IN, BETWEEN, LIKE, IS NULL, OR, NOT, parentheses
1494
+ db.query('FROM users WHERE status IN ("active", "trialing")')
1495
+ db.query('FROM users WHERE age BETWEEN 25 AND 40')
1496
+ db.query('FROM users WHERE bio LIKE "%rust%" AND NOT (status = "retired")')
1497
+ db.query('FROM users WHERE (age < 25 OR age > 60) AND bio IS NOT NULL')
1565
1498
 
1566
- ```bash
1567
- # Engine / nedbd-v2 (the native daemon from npm / the native wheel)
1568
- nedbd-v2 --dag-v3 --data /var/lib/nedb # real flag as of v2.4.3 — or set NEDB_DAG_V3=1
1499
+ # Time-travel — AS OF any past sequence
1500
+ snap = db.seq
1501
+ db.put("users", "alice", {"name": "Alice", "age": 32, "status": "retired"})
1502
+ db.get("users", "alice", as_of=snap) # → age 31, status active
1569
1503
 
1570
- # itcdBitcoin-fork node embedding NEDB via nedb-ffi
1571
- interchainedd -dagv3 # puts chainstate AND block index on segments
1504
+ # Bi-temporalVALID AS OF any past date
1505
+ db.put("policy", "rate_2024", {"pct": 5.0}, valid_from="2024-01-01", valid_to="2024-12-31")
1506
+ db.put("policy", "rate_2025", {"pct": 6.0}, valid_from="2025-01-01")
1507
+ db.query('FROM policy VALID AS OF "2024-06-15"') # → rate 5.0
1508
+
1509
+ # Causal Write Provenance — why did this write happen?
1510
+ db.put("inputs", "msg_1", {"text": "user prefers dark mode"})
1511
+ seq_msg = db.seq
1512
+ db.put("beliefs", "dark_mode", {"value": True},
1513
+ caused_by=[seq_msg], evidence="user_message", confidence=0.95)
1514
+ db.query('FROM beliefs WHERE _id = "dark_mode" TRACE caused_by') # → msg_1
1515
+ db.query('FROM inputs WHERE _id = "msg_1" TRACE caused_by REVERSE') # → dark_mode
1516
+
1517
+ # Relations + graph traversal
1518
+ db.link("users:alice", "follows", "users:bob")
1519
+ db.query('FROM users WHERE _id = "alice" TRAVERSE follows')
1520
+
1521
+ # Hash-chain integrity — verify() is a call, head is the commitment
1522
+ assert db.verify() # cryptographic proof — no tampering
1523
+ db.head # → 64-char BLAKE2b Merkle head (a property, not a call)
1524
+
1525
+ # SQL, Redis, MongoDB compatibility adapters
1526
+ from nedb import sql_exec, RedisCompat, MongoClient
1527
+ sql_exec(db, "SELECT * FROM users WHERE status = 'active' ORDER BY age DESC")
1528
+ r = RedisCompat(db); r.execute("HSET", "user:1", "name", "Alice")
1529
+ MongoClient(db)["users"].find({"status": "active"}).sort("age", -1).to_list()
1572
1530
  ```
1573
1531
 
1574
- The switch is read once, when each database's object store is constructed at open time. Default off → v2 loose objects.
1532
+ ---
1575
1533
 
1576
- ### Real-world result
1534
+ ## Official Python client — talk to nedbd over HTTP
1577
1535
 
1578
- itcd (a Bitcoin Core 0.21 fork that replaces LevelDB chainstate with NEDB) syncing on `-dagv3`, measured `FlushStateToDisk` on real chainstate:
1536
+ Running the daemon? `nedb.client.NedbClient` is the official client for its
1537
+ HTTP API — extracted from the battle-tested clients that ran a production
1538
+ Redis→NEDB mainnet migration, speaking the full route surface: queries,
1539
+ atomic CAS transactions, TTL, indexes, relations, Merkle proofs, and the
1540
+ Mongo-compat endpoint. Env-var defaults (`NEDBD_URL`, `NEDBD_TOKEN`,
1541
+ `NEDB_DB`) mirror the daemon's own.
1579
1542
 
1580
- | Flush (coins → disk) | v3 segment store | v2 loose store |
1581
- |---|---|---|
1582
- | 2,002 coins / 275 kB | **1.93 s** | *minutes* |
1583
- | 2,549 coins / 366 kB | **1.71 s** | *minutes* |
1543
+ ```python
1544
+ from nedb import NedbClient, PreconditionFailed, op_put
1584
1545
 
1585
- Note the *larger* batch finishing *faster* — v3's cost is dominated by the single per-batch `fsync`, not per-coin work, so effective throughput (~1,000–1,500 coins/s here) climbs as batches grow, against the loose store's ~185 writes/s metadata ceiling. The gap only widens as the UTXO set grows: sequential-append cost tracks data volume, while per-file cost compounds with object count.
1546
+ c = NedbClient("http://127.0.0.1:7070", db="app", token="s3cret")
1547
+ c.ensure_database()
1586
1548
 
1587
- ### When to use it
1549
+ c.put("users", "u1", {"id": "u1", "email": "a@b.c"}, idem="signup-u1")
1550
+ c.query('FROM users WHERE email = "a@b.c"') # full NQL rides through
1551
+ c.query("FROM users AS OF 41") # time-travel included
1588
1552
 
1589
- Reach for v3 on high-write, large-object-count workloads blockchain chainstate / block index, event sourcing, high-frequency agent memory. For small or read-mostly stores the loose layout is perfectly fine, which is exactly why v3 stays opt-in.
1553
+ # Atomic all-or-nothing transaction with engine-checked preconditions
1554
+ # the primitive that replaces Redis Lua scripts (if_seq: N = CAS, -1 = create-once)
1555
+ doc = c.get_doc("users", "u1") # docs carry _seq
1556
+ c.tx([op_put("users", "u1", {**doc, "plan": "pro"}, if_seq=doc["_seq"])])
1557
+
1558
+ # Contested writes: retry ONLY on PreconditionFailed, capped backoff
1559
+ def bump():
1560
+ d = c.get_doc("counters", "hits") or {"n": 0}
1561
+ return c.tx([op_put("counters", "hits", {"n": d.get("n", 0) + 1},
1562
+ if_seq=d.get("_seq", -1))])
1563
+ c.cas_retry(bump)
1564
+
1565
+ # Integrity, verifiable WITHOUT trusting the server
1566
+ proof = c.proof(c.log(limit=1)[0]["hash"])
1567
+ from nedb import verify_proof; verify_proof(proof) # -> True, locally
1568
+ ```
1569
+
1570
+ A CAS miss raises the **same `PreconditionFailed`** (with the same
1571
+ `.failures` shape) the embedded engine raises — code written against
1572
+ `NEDB.tx` ports to the HTTP client without changing its except-clauses.
1573
+ Typed errors throughout: `NedbAuthError`, `NedbNotFound`, `NedbBadRequest`,
1574
+ `NedbConflict`, `CasExhausted`.
1590
1575
 
1591
1576
  ---
1592
1577
 
1593
- ## Architecture
1578
+ ## Redis layer-2 — wrap_redis()
1579
+
1580
+ Already running on Redis? Wrap your connection in one line and gain NEDB features *alongside* your existing Redis app — no migration required.
1581
+
1582
+ ```python
1583
+ import redis, json
1584
+ from nedb import wrap_redis
1585
+
1586
+ r = wrap_redis(redis.Redis("localhost", 6379), db_name="rideshare")
1587
+
1588
+ # Step 1 — register: map Redis key globs to NEDB collections (chainable)
1589
+ (r.nedb
1590
+ .register("driver:*", collection="driver", value_parser=json.loads)
1591
+ .register("trip:*", collection="trip", value_type="hash")
1592
+ )
1593
+
1594
+ # Step 2 — backfill: import all existing Redis data into NEDB in one pass
1595
+ imported = r.nedb.backfill() # → int (keys imported)
1596
+
1597
+ # Step 3 — shadow: all future r.set/hset/... auto-chain into NEDB
1598
+ r.nedb.shadow_writes = True
1599
+
1600
+ # ─── Alice's app keeps running — zero changes ───────────────────────────
1601
+ r.set("driver:d1", json.dumps({"name": "Bob", "status": "active"})) # ← shadowed
1602
+ r.hset("trip:t1", mapping={"status": "en_route", "driver_id": "d1"}) # ← shadowed
1594
1603
 
1604
+ # ─── New features available on the same connection ──────────────────────
1605
+ r.nedb.query('FROM driver WHERE status = "active" ORDER BY lat ASC')
1606
+ r.nedb.verify() # → True (every write chain-verified)
1607
+ r.nedb.head() # → 64-char BLAKE2b commitment hash
1595
1608
  ```
1596
- ┌──────────────────────────────────────────────────────────┐
1597
- put/del → │ OpLog (BLAKE2b hash chain · per-client nonce · │ ← single source of truth
1598
- link │ idempotency keys · causal provenance fields) │
1599
- └───────────────┬──────────────────────────────────────────┘
1600
- deterministic fold │ (state = pure function of the log)
1601
- ┌──────────────┬──────────┴──────┬───────────────┬────────────────┐
1602
- ▼ ▼ ▼ ▼ ▼
1603
- MVCC store Relations Indexes CauseMap BlobStore
1604
- (time-travel) (graph+AS OF) eq/ord/search (reverse index) (Cascade CDC)
1605
1609
 
1606
- ┌─────────────────────────────────┐
1607
- Thread-safe → │ Sequencer (group-commit) │ ← single writer, parallel readers
1608
- │ — one committer thread/db │
1609
- │ — batch fsync │
1610
- └─────────────────────────────────┘
1610
+ **Isolation guarantee:** NEDB never writes to Alice's namespace. It owns only:
1611
1611
 
1612
- Compatibility adapters: SQL · Redis · MongoDB
1613
- Wire protocols: HTTP/JSON · RESP2
1614
- Encryption: AES-256-GCM at-rest (TMK/DEK double-envelope)
1612
+ | Key | Type | Purpose |
1613
+ |-----|------|---------|
1614
+ | `nedb:{db_name}:oplog` | Redis Stream | append-only op log |
1615
+ | `nedb:{db_name}:snapshot` | Redis Hash | checkpoint |
1616
+ | `nedb:{db_name}:meta` | Redis Hash | index config |
1617
+
1618
+ See [`examples/fakeredis_demo.py`](examples/fakeredis_demo.py) for a full local demo (no Redis server needed).
1619
+
1620
+ ---
1621
+
1622
+ ## Node.js
1623
+
1624
+ ```javascript
1625
+ import { NedbCore } from "nedb-engine";
1626
+
1627
+ const db = new NedbCore(); // in-memory
1628
+ // const db = NedbCore.open("./data"); // durable
1629
+
1630
+ db.createIndex("users", "status", "eq");
1631
+ db.put("users", "alice", JSON.stringify({ name: "Alice", age: 31, status: "active" }));
1632
+
1633
+ // Time-travel
1634
+ const snap = db.seq(); // BigInt
1635
+ db.put("users", "alice", JSON.stringify({ name: "Alice", age: 32, status: "retired" }));
1636
+ JSON.parse(db.getAsOf("users", "alice", snap)).age; # → 31
1637
+
1638
+ // Full NQL
1639
+ const rows = db.query('FROM users WHERE status = "active" ORDER BY age ASC');
1640
+ rows.map(r => JSON.parse(r));
1641
+
1642
+ // Tamper evidence
1643
+ db.verify(); // → true
1644
+ db.head(); // → 64-char BLAKE2b commitment hash
1645
+ db.seq(); // → BigInt
1615
1646
  ```
1616
1647
 
1617
1648
  ---
@@ -1644,53 +1675,98 @@ const rows = await db.query("FROM blocks LIMIT 10");
1644
1675
 
1645
1676
  ---
1646
1677
 
1678
+ ## NEDB v3 — Segment / Pack Object Store
1679
+
1680
+ **v3 is an opt-in storage substrate that replaces the loose one-file-per-object layout with append-only *segment packs* — the difference between a chainstate flush that takes *minutes* and one that takes *under two seconds*.** It is **off by default** (byte-for-byte v2), enabled with one flag, and **transparent** to everything above the storage layer: NQL, `AS OF`, `VALID AS OF`, `TRACE`, the BLAKE2b Merkle head, and causal provenance all behave identically.
1681
+
1682
+ ### Why it exists
1683
+
1684
+ v2 stores every document version as its own content-addressed file at `objects/{hash[:2]}/{hash[2:]}`. That makes writes trivially atomic (write `.tmp` → `rename`) and corruption-proof — but each write costs a file create + `fsync` + rename **plus** a directory B-tree update. At scale that filesystem-metadata churn dominates: on a busy disk it caps sustained writes around **~185/s**, and a batch flush of a few thousand objects degrades into minutes. The bottleneck is the *number of files touched*, not the bytes written.
1685
+
1686
+ ### What it does
1687
+
1688
+ v3 batches objects into append-only **segment packs** — `objects/segments/seg-NNNNNN.dat` — where each record is `[content_len: u32-LE][content]`. A write appends to the active segment and updates an in-memory `hash → (segment_id, offset, len)` map; a batch commits with a **single `fsync`**. Thousands of per-file syscalls collapse into one sequential append plus one durability point, so **flush cost scales with bytes (sequential I/O), not object-count × syscall overhead.**
1689
+
1690
+ - **Compaction / pruning** — `compact()` keeps the *live set* (the current version of every document, resolved from the id-index), rewrites those records into fresh segments, and reclaims the superseded/dead versions.
1691
+ - **`.idx` sidecars** — each segment carries a sidecar (`NIX1` magic + entry count + fixed 44-byte entries + a BLAKE2b-256 checksum) so reopen rebuilds the in-memory index by reading the sidecar instead of scanning the whole pack. A missing or corrupt sidecar falls back to a full scan-and-heal — slower, never fatal.
1692
+ - **Dual-read migration** — opening an existing v2 store in v3 mode is **non-destructive**: old loose objects stay fully readable, and only *new* writes go to segments. No migration step, no downtime, no rewrite.
1693
+ - **Durable flush-on-close** — `flush_all()` (and `Db`'s `Drop`) fsync the active segment, matching the flush-on-close contract of sled / RocksDB.
1694
+
1695
+ ### How to enable
1696
+
1697
+ ```bash
1698
+ # Engine / nedbd-v2 (the native daemon from npm / the native wheel)
1699
+ nedbd-v2 --dag-v3 --data /var/lib/nedb # real flag as of v2.4.3 — or set NEDB_DAG_V3=1
1700
+
1701
+ # itcd — Bitcoin-fork node embedding NEDB via nedb-ffi
1702
+ interchainedd -dagv3 # puts chainstate AND block index on segments
1703
+ ```
1704
+
1705
+ The switch is read once, when each database's object store is constructed at open time. Default off → v2 loose objects.
1706
+
1707
+ ### Real-world result
1708
+
1709
+ itcd (a Bitcoin Core 0.21 fork that replaces LevelDB chainstate with NEDB) syncing on `-dagv3`, measured `FlushStateToDisk` on real chainstate:
1710
+
1711
+ | Flush (coins → disk) | v3 segment store | v2 loose store |
1712
+ |---|---|---|
1713
+ | 2,002 coins / 275 kB | **1.93 s** | *minutes* |
1714
+ | 2,549 coins / 366 kB | **1.71 s** | *minutes* |
1715
+
1716
+ Note the *larger* batch finishing *faster* — v3's cost is dominated by the single per-batch `fsync`, not per-coin work, so effective throughput (~1,000–1,500 coins/s here) climbs as batches grow, against the loose store's ~185 writes/s metadata ceiling. The gap only widens as the UTXO set grows: sequential-append cost tracks data volume, while per-file cost compounds with object count.
1717
+
1718
+ ### When to use it
1719
+
1720
+ Reach for v3 on high-write, large-object-count workloads — blockchain chainstate / block index, event sourcing, high-frequency agent memory. For small or read-mostly stores the loose layout is perfectly fine, which is exactly why v3 stays opt-in.
1721
+
1722
+ ---
1723
+
1647
1724
  ## Repo layout
1648
1725
 
1649
1726
  ```
1650
1727
  python/nedb/ reference engine (pure Python — always-works baseline)
1651
1728
  rust/
1729
+ nedb-v2/ v2 DAG engine (tokio + axum + BLAKE2b DAG) — the core everything binds
1730
+ nesql-cli/ the nesql CLI (query / root / inspect / diff / tag / branch / merge)
1652
1731
  nedb-core/ v1 production Rust engine (shared by both runtimes)
1653
1732
  nedb-py/ maturin PyO3 binding → PyPI native wheels
1654
1733
  nedb-node/ napi-rs binding → npm native addons
1655
- nedb-v2/ v2 DAG engine (tokio + axum + BLAKE2b DAG)
1656
- client/
1657
- python/ nedb-client async Python HTTP client (pip install nedb-engine-client)
1658
- node/ nedb-client TypeScript HTTP client (npm install nedb-client)
1734
+ nedb-wrap/ the wrap_* surface at native speed
1735
+ vendor/postgresql/ PostgreSQL 17.4 grammar, vendored (gram.y, kwlist.h, system_views.sql, information_schema.sql)
1736
+ distributions/ crypto-database + aof-db submodules (the tri-distribution release)
1737
+ bench/ benchmarks.py + RESULTS.md the dated embedded-core numbers
1659
1738
  tests/ engine + concurrent + causal + bitemporal + deploy + perf benchmarks
1660
- examples/ resp2_python.py resp2_demo.sh
1661
- docs/ index.html reference.html SPEC.md
1739
+ vectors/ state_root_v1.json — cross-engine state-root test vectors
1740
+ docs/ SPEC.md · CLI.md · DURABILITY.md · REPLICATION.md · BENCH-sqlselect.md
1741
+ client/ nedb-engine-client — async Python + TypeScript HTTP clients
1742
+ examples/ agent-loop · mini-chain · resp2 demos · fakeredis demo
1743
+ scripts/ release.py · test-cast.sh · bench_index_range.py · seed-shop.sh
1662
1744
  ```
1663
1745
 
1664
1746
  ---
1665
1747
 
1666
- ## Roadmap
1667
-
1668
- - [x] Hash-chained append-only log tamper evidence, replay protection, idempotency
1669
- - [x] MVCC time-travel `AS OF seq`
1670
- - [x] Bi-temporal — `VALID AS OF "date"` (transaction time + valid time)
1671
- - [x] Causal Write Provenance `caused_by`, `evidence`, `confidence`, `TRACE`
1672
- - [x] Durable AOF persistence + snapshot checkpoints
1673
- - [x] Concurrent group-commit sequencer (nedbd, 15K writes/s under load)
1674
- - [x] AES-256-GCM at-rest encryption (TMK/DEK double-envelope)
1675
- - [x] SQL / Redis / MongoDB compatibility adapters
1676
- - [x] RESP2 wire protocol (redis-cli / redis-benchmark compatible)
1677
- - [x] Rust native core — napi-rs (npm) + maturin PyO3 (PyPI)
1678
- - [x] Self-healing AOF auto-truncates corrupt tail on startup, never hangs
1679
- - [x] **v2 DAG engine** content-addressed Merkle DAG, atomic writes, instant cold start
1680
- - [x] **`nedbd --dag`** — one flag switches to v2 Rust engine; v1 untouched
1681
- - [x] **BLAKE2b Merkle head** tamper-evident root on every response
1682
- - [x] **Tombstone deletes** history preserved in DAG, live id removed from index
1683
- - [x] **Auto-migration**v1 AOF v2 DAG on first `--dag` startup
1684
- - [x] **nedb-client**async Python + TypeScript HTTP client (`pip/npm install nedb-client`)
1685
- - [x] **Intel Mac support** native wheels for `aarch64` + `x86_64` Apple Darwin
1686
- - [x] **v3 segment/pack object store** opt-in `--dag-v3`: append-only packs, one fsync per batch, compaction + `.idx` sidecars, non-destructive dual-read (minutes → <2s chainstate flush on itcd)
1687
- - [ ] In-memory DAG mode `Db::in_memory()` for zero-disk ephemeral sessions
1688
- - [ ] PyO3 + napi-rs bindings updated to v2 DAG API
1689
- - [ ] NEDB Studio DAG mode toggle
1690
- - [ ] Merkle inclusion proofs — prove a document existed at a specific time to a third party
1691
- - [ ] Git-style branching — fork database state, experiment, merge or discard
1692
- - [ ] Agent Memory SDK — `Memory.remember()` / `Memory.recall()` / `Memory.trace()`
1693
- - [ ] Live query subscriptions (SSE) — push diffs when query results change
1748
+ ## Ship log — the highlights, in order
1749
+
1750
+ NEDB's release history outgrew any changelog, and the README was where versions went to pile up.
1751
+ This is the map; the commits are the story.
1752
+
1753
+ - **8.x neSQL ships.** PostgreSQL's real grammar vendored and extended; one evaluator answers every
1754
+ `SELECT` with nothing to enable; NQL's verbs are SQL clauses; the `nesql` CLI ships in the wheel.
1755
+ - **7.x the neSQL endpoint surface.** The HTTP `/query` route speaks both dialects through one
1756
+ router; the CLI's publish path lands.
1757
+ - **6.x hardening, discipline, the road to one name.**
1758
+ - **5.x neSQL named, registered, vendored.** The grammar lands at `vendor/postgresql/`;
1759
+ `nesql` reserved on all three registries.
1760
+ - **4.0 — BUSL-1.1** (free under $1M revenue; Apache 2.0 on the 2030-09-11 Change Date).
1761
+ - **3.3 the query language grows up.** Full boolean predicates in both engines; nine silent
1762
+ defects fixed; cross-engine parity gated.
1763
+ - **3.2 the wrap family.** Redis/SQLite/Postgres/MySQL/Mongo adapters; durability fixes; PR CI.
1764
+ - **3.0–3.3.1the MIT era** (irrevocably MIT).
1765
+ - **2.8Cast.** A 3.33M-parameter model trained inside the engine's own parser; drift detection.
1766
+ - **2.5flush-on-exit, nedb-cli, inspector, replication contract.**
1767
+ - **2.4 v3 segment store** (`--dag-v3`): itcd chainstate flush minutes ~1.3 s.
1768
+ - **2.2 v2 DAG engine.** Content-addressed Merkle DAG, O(1) warm start, SSE `/events`.
1769
+ - **1.x v1 AOF engine.** Hash-chained log, MVCC time-travel, bi-temporal, `TRACE caused_by`.
1694
1770
 
1695
1771
  ---
1696
1772
 
@@ -1702,14 +1778,16 @@ Prompt-to-database scaffolding GUI with schema graph, NQL console, time-travel s
1702
1778
 
1703
1779
  ---
1704
1780
 
1705
- ## Repos
1781
+ ## Repos & packages
1706
1782
 
1707
- | Repo | Description |
1783
+ | Where | What |
1708
1784
  |---|---|
1709
- | [aiassistsecure/nedb](https://github.com/aiassistsecure/nedb) | Source — engine, Rust core, CI |
1785
+ | [Eth-Interchained/nedb](https://github.com/Eth-Interchained/nedb) | **canonical source** — engine, Rust core, CI, this README |
1786
+ | [aiassistsecure/nedb](https://github.com/aiassistsecure/nedb) | mirror + [GitHub Pages site](https://nedb.aiassist.net) |
1787
+ | [Eth-Interchained/neSQL](https://github.com/Eth-Interchained/neSQL) | the language — both halves of the grammar, CLI source |
1710
1788
  | [aiassistsecure/nedb-studio](https://github.com/aiassistsecure/nedb-studio) | Studio UI (GPLv3) |
1711
1789
 
1712
- **Packages:** [PyPI nedb-engine](https://pypi.org/project/nedb-engine/) · [npm nedb-engine](https://www.npmjs.com/package/nedb-engine)
1790
+ **Packages:** [PyPI nedb-engine](https://pypi.org/project/nedb-engine/) · [npm nedb-engine](https://www.npmjs.com/package/nedb-engine) · [crates.io nedb-engine](https://crates.io/crates/nedb-engine) — plus the aligned `crypto-database` and `aof-db` distributions on all three registries.
1713
1791
 
1714
1792
  ---
1715
1793
 
@@ -1737,7 +1815,11 @@ Requires `GITHUB_TOKEN` (`repo` + `workflow` scope) in the environment. It never
1737
1815
 
1738
1816
  ## License
1739
1817
 
1740
- **MIT License** — free for any use, including commercial and production. See [`LICENSE`](LICENSE).
1818
+ **Business Source License 1.1** — free for any organisation under USD $1M annual revenue, including
1819
+ commercial and production use. Converts to **Apache 2.0** on **2030-09-11**, automatically and
1820
+ permanently. Versions 3.0.0–3.3.1 remain MIT, irrevocably. See [`LICENSE`](LICENSE) and
1821
+ [`COPYING-APACHE-2.0.txt`](COPYING-APACHE-2.0.txt).
1822
+
1741
1823
  © 2026 INTERCHAINED LLC — [interchained.org](https://interchained.org)
1742
1824
 
1743
1825
  ---