nedb-engine 8.0.0 → 8.8.9
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 +501 -412
- package/index.d.ts +64 -64
- package/index.js +84 -84
- package/nedb.darwin-arm64.node +0 -0
- package/nedb.darwin-x64.node +0 -0
- package/nedb.linux-arm64-gnu.node +0 -0
- package/nedb.linux-arm64-musl.node +0 -0
- package/nedb.linux-x64-gnu.node +0 -0
- package/nedb.linux-x64-musl.node +0 -0
- package/nedb.win32-x64-msvc.node +0 -0
- package/nedbd-v2-darwin-arm64 +0 -0
- package/nedbd-v2-darwin-x64 +0 -0
- package/nedbd-v2-linux-arm64 +0 -0
- package/nedbd-v2-linux-arm64-musl +0 -0
- package/nedbd-v2-linux-x64 +0 -0
- package/nedbd-v2-linux-x64-musl +0 -0
- package/nedbd-v2-win-x64.exe +0 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ at the same version on the same tag.
|
|
|
16
16
|
[](https://www.npmjs.com/package/nedb-engine-client)
|
|
17
17
|
[](https://github.com/Eth-Interchained/nedb/blob/master/LICENSE) [](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,18 +171,25 @@ evaluator with no flag to set, the `nesql` CLI likewise.
|
|
|
76
171
|
[](https://crates.io/crates/nesql)
|
|
77
172
|
[](https://www.npmjs.com/package/nesql-engine)
|
|
78
173
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
174
|
+
The three neSQL packages are **real and version-aligned with the engine** — the
|
|
175
|
+
badges above are live registry state, not placeholders. They split by delivery
|
|
176
|
+
path, not by function:
|
|
177
|
+
|
|
178
|
+
| package | registry | what it is |
|
|
179
|
+
|---|---|---|
|
|
180
|
+
| [`nesql`](https://crates.io/crates/nesql) | crates.io | **the real CLI crate** — `cargo add nesql`, builds against the registry engine |
|
|
181
|
+
| [`nesql`](https://pypi.org/project/nesql/) | PyPI | the language reference (vendored-grammar facts, clause extensions) |
|
|
182
|
+
| [`nesql-engine`](https://www.npmjs.com/package/nesql-engine) | npm | the language reference |
|
|
183
|
+
|
|
184
|
+
The [neSQL repository](https://github.com/Eth-Interchained/neSQL) is the language's
|
|
185
|
+
home: the vendored PostgreSQL grammar with its licence intact, the CLI crate source,
|
|
186
|
+
the NQL grammar reference, and the NEDB specs. And because the CLI is also staged
|
|
187
|
+
into every platform build of the engine (below), **one install carries everything**:
|
|
86
188
|
|
|
87
189
|
### `nesql` — the CLI, and it speaks neSQL
|
|
88
190
|
|
|
89
|
-
Ships
|
|
90
|
-
|
|
191
|
+
Ships inside `pip install nedb-engine` — no daemon, no port, the compiled binary on
|
|
192
|
+
your PATH — and answers both halves of the language through **one** `query` command:
|
|
91
193
|
|
|
92
194
|
```console
|
|
93
195
|
$ nesql --db ./store query "SELECT who, total FROM orders ORDER BY total DESC"
|
|
@@ -359,12 +461,6 @@ a seq is exact where a time would be approximate.
|
|
|
359
461
|
compacted store answers "not available at that sequence" rather than
|
|
360
462
|
returning a stale value, and `verify()` stays clean.
|
|
361
463
|
|
|
362
|
-
Provenance is selectable like any other column:
|
|
363
|
-
|
|
364
|
-
```sql
|
|
365
|
-
SELECT _id, _hash, _seq FROM audit ORDER BY _seq;
|
|
366
|
-
```
|
|
367
|
-
|
|
368
464
|
**NEDB speaks SQL. That sentence used to carry a caveat, and no longer does.**
|
|
369
465
|
|
|
370
466
|
For most of this project's life it was true that the endpoint served a
|
|
@@ -399,42 +495,6 @@ NEDB is append-only *so that history cannot be discarded* — that is the produc
|
|
|
399
495
|
not a gap — and DDL is refused because collections are created by the first write
|
|
400
496
|
to them. Those answers do not change.
|
|
401
497
|
|
|
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
498
|
Every refusal names the boundary instead of saying "syntax error", and a
|
|
439
499
|
grouped query that projects a column SQL would reject gets Postgres's own
|
|
440
500
|
message rather than a silent `NULL`.
|
|
@@ -787,291 +847,141 @@ The deployment figures below are a **dated snapshot**, not a live readout: **1,3
|
|
|
787
847
|
|
|
788
848
|
---
|
|
789
849
|
|
|
790
|
-
##
|
|
791
|
-
|
|
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.
|
|
793
|
-
|
|
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 | ✅ | ❌ | ❌ | — |
|
|
850
|
+
## Performance — every number dated and reproducible
|
|
804
851
|
|
|
805
|
-
|
|
852
|
+
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.
|
|
806
853
|
|
|
807
|
-
|
|
854
|
+
**Embedded core (Rust engine, in-process) — `bench/RESULTS.md`, 2026-06-14, Linux x86-64, Python 3.9.25.
|
|
855
|
+
Reproduce: `python3 bench/benchmarks.py --save`**
|
|
808
856
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
857
|
+
| Operation | Throughput | Latency (avg) |
|
|
858
|
+
|-----------|-----------|---------------|
|
|
859
|
+
| PUT (replace, no index) | **63.5K/s** | 15.74 µs |
|
|
860
|
+
| GET (point read, HEAD) | **1.33M/s** | 0.75 µs |
|
|
861
|
+
| GET (`AS OF` — time-travel read) | **942.9K/s** | 1.06 µs |
|
|
862
|
+
| QUERY: eq filter, no index (scan) | 514.1K/s | 1.95 µs |
|
|
863
|
+
| QUERY: eq filter, eq index | **1.45M/s** | 0.69 µs |
|
|
864
|
+
| QUERY: ORDER BY + ordered index, LIMIT 20 | 454.7K/s | 2.20 µs |
|
|
865
|
+
| QUERY: SEARCH, inverted index | 492.3K/s | 2.03 µs |
|
|
866
|
+
| PUT durable (AOF + fsync) | 7.3K/s | 137.59 µs |
|
|
813
867
|
|
|
814
|
-
|
|
868
|
+
Time travel is not a tax: an `AS OF` read runs at **~70% of the speed of a current-state read**.
|
|
815
869
|
|
|
816
|
-
|
|
870
|
+
**The daemon over HTTP (v2 DAG server v2.2.31, Intel iMac — 10k writes / 100k reads / 30k objects, AES-256-GCM on).
|
|
871
|
+
Reproduce: `NEDBD_DAG=1 nedbd --data /tmp/perf &` then `python3 tests/test_dag_perf.py --n 10000 --reads 100000`**
|
|
817
872
|
|
|
818
|
-
|
|
|
873
|
+
| Operation | Throughput | p50 | p99 |
|
|
819
874
|
|---|---|---|---|
|
|
820
|
-
|
|
|
821
|
-
|
|
|
822
|
-
|
|
|
823
|
-
|
|
|
824
|
-
|
|
|
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
|
-
---
|
|
875
|
+
| Sequential writes | **418 ops/s** | 2.3 ms | 3.3 ms |
|
|
876
|
+
| Point-lookup reads | **478 ops/s** | 2.0 ms | 3.0 ms |
|
|
877
|
+
| ORDER BY queries | **489 ops/s** | 1.8 ms | 4.3 ms |
|
|
878
|
+
| Batch writes (500 ops/req) | **1,104 ops/s** | 0.9 ms | 1.2 ms |
|
|
879
|
+
| Tamper-verify (30k objects) | ~21,000 BLAKE2b/sec | — | 1.38 s total |
|
|
833
880
|
|
|
834
|
-
|
|
881
|
+
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
882
|
|
|
836
|
-
|
|
837
|
-
|
|
883
|
+
**SQL joins inside the evaluator (nested loop vs hash, fixed-seed workload, engine 4.0.0, Linux x86-64) —
|
|
884
|
+
`docs/BENCH-sqlselect.md`. Reproduce: `cargo run --release --example sqlbench`**
|
|
838
885
|
|
|
839
|
-
|
|
840
|
-
|
|
886
|
+
| workload | nested (ms) | hash (ms) | speedup |
|
|
887
|
+
|---|---:|---:|---:|
|
|
888
|
+
| equality join (1,000 × 500 rows) | 366.62 | 4.05 | **90.6×** |
|
|
889
|
+
| left join | 373.27 | 4.03 | **92.5×** |
|
|
890
|
+
| join + sort | 403.51 | 6.35 | **63.6×** |
|
|
891
|
+
| join + broad pred | 336.34 | 4.68 | **71.9×** |
|
|
892
|
+
| non-equality join | 344.69 | 346.33 | — *(same path, no hash key)* |
|
|
841
893
|
|
|
842
|
-
|
|
843
|
-
db.create_index("users", "bio", "search")
|
|
894
|
+
**Indexed range scans (20,000 rows, two identical stores, one indexed) — `scripts/bench_index_range.py`**
|
|
844
895
|
|
|
845
|
-
|
|
846
|
-
|
|
896
|
+
| Query | Scan | Indexed | Speedup |
|
|
897
|
+
| --- | --- | --- | --- |
|
|
898
|
+
| `WHERE fee = 10000` | 137 ms | 0.01 ms | **17,000×** |
|
|
899
|
+
| `WHERE fee IN (a, b, c)` | 185 ms | 0.02 ms | 9,700× |
|
|
900
|
+
| `WHERE fee BETWEEN …` (1% of rows) | 186 ms | 1.1 ms | 170× |
|
|
901
|
+
| `WHERE fee BETWEEN …` (10% of rows) | 188 ms | 13 ms | 14× |
|
|
902
|
+
| unindexed field (control) | 188 ms | 187 ms | 1.0× |
|
|
847
903
|
|
|
848
|
-
|
|
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')
|
|
904
|
+
**v1 Python server (baseline — single-threaded AOF):**
|
|
852
905
|
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
906
|
+
| Operation | Throughput | p99 latency |
|
|
907
|
+
|---|---|---|
|
|
908
|
+
| Sequential PUT | ~23/s | 44 ms |
|
|
909
|
+
| Concurrent PUT (16 workers) | ~92/s | 48 ms |
|
|
910
|
+
| Batch PUT (500 ops/request) | ~520 ops/s | 1.9 ms/op |
|
|
911
|
+
| Point-lookup read (NQL) | ~23/s | 44 ms |
|
|
912
|
+
| Rust napi PUT (FFI) | ~70K/s | — |
|
|
913
|
+
| Rust napi GET (FFI) | ~330K/s | — |
|
|
858
914
|
|
|
859
|
-
|
|
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
|
|
915
|
+
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
916
|
|
|
864
|
-
|
|
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
|
|
917
|
+
---
|
|
868
918
|
|
|
869
|
-
|
|
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
|
|
919
|
+
## nedbd — the concurrent server daemon
|
|
876
920
|
|
|
877
|
-
|
|
878
|
-
db.link("users:alice", "follows", "users:bob")
|
|
879
|
-
db.query('FROM users WHERE _id = "alice" TRAVERSE follows')
|
|
921
|
+
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
922
|
|
|
881
|
-
|
|
882
|
-
|
|
923
|
+
```bash
|
|
924
|
+
nedbd # :7070, data ./nedb-data (v1 AOF engine)
|
|
925
|
+
nedbd --dag --data ./data # v2 DAG engine (or NEDBD_DAG=1)
|
|
926
|
+
NEDBD_RESP2_PORT=6380 nedbd # also speak RESP2 (redis-cli compatible)
|
|
927
|
+
nedbd --log-level 2 # 0=errors 1=requests 2=deploy 3=verbose
|
|
883
928
|
|
|
884
|
-
#
|
|
885
|
-
|
|
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()
|
|
929
|
+
# Live event stream (since 2.2.31) — SSE: scan progress, ready, per-write head
|
|
930
|
+
curl http://127.0.0.1:7070/events
|
|
889
931
|
```
|
|
890
932
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
## Official Python client — talk to nedbd over HTTP
|
|
933
|
+
### Companion CLIs
|
|
894
934
|
|
|
895
|
-
|
|
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.
|
|
935
|
+
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).
|
|
901
936
|
|
|
902
|
-
|
|
903
|
-
from nedb import NedbClient, PreconditionFailed, op_put
|
|
937
|
+
### Startup modes
|
|
904
938
|
|
|
905
|
-
|
|
906
|
-
|
|
939
|
+
- **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.
|
|
940
|
+
- **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`.
|
|
907
941
|
|
|
908
|
-
|
|
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
|
|
942
|
+
### Environment variables
|
|
911
943
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
944
|
+
| Variable | Default | Description |
|
|
945
|
+
|---|---|---|
|
|
946
|
+
| `NEDBD_DAG` | `0` | Set `1` to launch the v2 DAG engine (`nedbd-v2`). Same as `--dag`. |
|
|
947
|
+
| `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. |
|
|
948
|
+
| `NEDBD_PORT` | `7070` | HTTP bind port. |
|
|
949
|
+
| `NEDBD_TOKEN` | unset | Optional bearer token; required on every `/v1/*` request when set. |
|
|
950
|
+
| `NEDB_TMK` | unset | 32-byte hex AES-256-GCM at-rest encryption key. |
|
|
951
|
+
| `NEDBD_DATA` | `./nedb-data` | Root directory. v2 creates `dag/`, IdIndex sharded across **256 subdirectories**, and a small `MANIFEST` file. |
|
|
952
|
+
| `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). |
|
|
953
|
+
| `NEDBD_CAST_MODEL` | unset | Explicit path to a `model.cast` container. Otherwise searched in the data dir, `$CAST_HOME`, and `~/.cache/nedb-cast-slm/`. |
|
|
916
954
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
955
|
+
```bash
|
|
956
|
+
# Create a database with seed data and relations
|
|
957
|
+
curl -X POST :7070/v1/databases -d '{
|
|
958
|
+
"name": "shop",
|
|
959
|
+
"init": {
|
|
960
|
+
"indexes": [["users","status","eq"]],
|
|
961
|
+
"seed": {"users": [{"_id":"u1","name":"Alice","status":"active"}]},
|
|
962
|
+
"links": [["users:u1","buys","orders:o1"]]
|
|
963
|
+
}}'
|
|
923
964
|
|
|
924
|
-
#
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
965
|
+
# Query — the endpoint speaks neSQL: SQL *or* NQL, routed on the first keyword
|
|
966
|
+
curl -X POST :7070/v1/databases/shop/query \
|
|
967
|
+
-d '{"nql":"SELECT name FROM users WHERE status = '"'"'active'"'"' ORDER BY name"}'
|
|
968
|
+
# → {"rows":[{"name":"Alice"}],"count":1,"dialect":"sql", ...}
|
|
928
969
|
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
Typed errors throughout: `NedbAuthError`, `NedbNotFound`, `NedbBadRequest`,
|
|
933
|
-
`NedbConflict`, `CasExhausted`.
|
|
970
|
+
curl -X POST :7070/v1/databases/shop/query \
|
|
971
|
+
-d '{"nql":"FROM users WHERE status = \"active\" ORDER BY name ASC"}'
|
|
972
|
+
# → {"rows":[...],"count":1,"dialect":"nql", ...}
|
|
934
973
|
|
|
935
|
-
---
|
|
936
974
|
|
|
937
|
-
|
|
975
|
+
**The field is still called `nql`, and its contents no longer have to be.** This
|
|
976
|
+
endpoint accepts **neSQL** — NQL *or* PostgreSQL SQL — and answers with the
|
|
977
|
+
`dialect` it chose. The name is unchanged because every existing HTTP client
|
|
978
|
+
sends it; renaming would break them to gain nothing. Old NQL clients are
|
|
979
|
+
unaffected.
|
|
938
980
|
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
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.
|
|
981
|
+
Routing is **structural, not guessed**. NQL statements begin `FROM`; PostgreSQL
|
|
982
|
+
has no statement form that begins with `FROM`, so the leading keyword partitions
|
|
983
|
+
the two vocabularies rather than hinting at them. A first word in neither is
|
|
984
|
+
refused *naming both* — never handed to whichever parser seems likelier.
|
|
1075
985
|
|
|
1076
986
|
```bash
|
|
1077
987
|
curl -X POST :7070/v1/databases/shop/query -d '{"nql":"GRANT ALL ON users"}'
|
|
@@ -1182,14 +1092,13 @@ db.query("FROM blocks WHERE height BETWEEN 600000 AND 600100")
|
|
|
1182
1092
|
```
|
|
1183
1093
|
|
|
1184
1094
|
Measured on 20,000 rows with `scripts/bench_index_range.py` — two identical
|
|
1185
|
-
databases, one indexed, one not
|
|
1095
|
+
databases, one indexed, one not (the full table lives in
|
|
1096
|
+
[**Performance**](#performance--every-number-dated-and-reproducible)):
|
|
1186
1097
|
|
|
1187
1098
|
| Query | Scan | Indexed | Speedup |
|
|
1188
1099
|
| --- | --- | --- | --- |
|
|
1189
1100
|
| `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
1101
|
| `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
1102
|
| unindexed field (control) | 188 ms | 187 ms | 1.0× |
|
|
1194
1103
|
|
|
1195
1104
|
The planner asks the index how many rows each candidate range covers and takes
|
|
@@ -1510,108 +1419,237 @@ Two habits that avoid most misses:
|
|
|
1510
1419
|
|
|
1511
1420
|
---
|
|
1512
1421
|
|
|
1513
|
-
##
|
|
1422
|
+
## Architecture
|
|
1514
1423
|
|
|
1515
|
-
|
|
1424
|
+
```
|
|
1425
|
+
┌──────────────────────────────────────────────────────────┐
|
|
1426
|
+
put/del → │ OpLog (BLAKE2b hash chain · per-client nonce · │ ← single source of truth
|
|
1427
|
+
link │ idempotency keys · causal provenance fields) │
|
|
1428
|
+
└───────────────┬──────────────────────────────────────────┘
|
|
1429
|
+
deterministic fold │ (state = pure function of the log)
|
|
1430
|
+
┌──────────────┬──────────┴──────┬───────────────┬────────────────┐
|
|
1431
|
+
▼ ▼ ▼ ▼ ▼
|
|
1432
|
+
MVCC store Relations Indexes CauseMap BlobStore
|
|
1433
|
+
(time-travel) (graph+AS OF) eq/ord/search (reverse index) (Cascade CDC)
|
|
1516
1434
|
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
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 |
|
|
1435
|
+
┌─────────────────────────────────┐
|
|
1436
|
+
Thread-safe → │ Sequencer (group-commit) │ ← single writer, parallel readers
|
|
1437
|
+
│ — one committer thread/db │
|
|
1438
|
+
│ — batch fsync │
|
|
1439
|
+
└─────────────────────────────────┘
|
|
1524
1440
|
|
|
1525
|
-
|
|
1441
|
+
Compatibility adapters: SQL · Redis · MongoDB
|
|
1442
|
+
Wire protocols: HTTP/JSON · RESP2 · PostgreSQL wire
|
|
1443
|
+
Encryption: AES-256-GCM at-rest (TMK/DEK double-envelope)
|
|
1444
|
+
```
|
|
1526
1445
|
|
|
1527
|
-
|
|
1446
|
+
The provenance machinery is load-bearing infrastructure, not metadata stapled on: `CauseMap` is a
|
|
1447
|
+
reverse index over causal edges (that is why `TRACE … REVERSE` is as fast as forward), and the
|
|
1448
|
+
`Relations` layer backs both `TRAVERSE` and the `AS OF` joins.
|
|
1528
1449
|
|
|
1529
|
-
|
|
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 | — |
|
|
1450
|
+
---
|
|
1537
1451
|
|
|
1538
|
-
|
|
1452
|
+
## Install
|
|
1539
1453
|
|
|
1540
1454
|
```bash
|
|
1541
|
-
|
|
1542
|
-
|
|
1455
|
+
pip install nedb-engine # Python ≥ 3.8 — pure-Python + optional Rust native wheel
|
|
1456
|
+
npm install nedb-engine # Node ≥ 16 — napi-rs prebuilt binaries
|
|
1543
1457
|
```
|
|
1544
1458
|
|
|
1459
|
+
### Prebuilt platforms
|
|
1460
|
+
|
|
1461
|
+
Both registries ship prebuilt binaries for:
|
|
1462
|
+
|
|
1463
|
+
| Platform | libc | Python wheel | Node addon |
|
|
1464
|
+
|---|---|---|---|
|
|
1465
|
+
| Linux x86_64 | glibc | ✅ manylinux | ✅ |
|
|
1466
|
+
| Linux x86_64 | musl (Alpine) | ✅ musllinux | ✅ |
|
|
1467
|
+
| Linux aarch64 (Graviton, Ampere, Apple-Silicon containers) | glibc | ✅ manylinux | ✅ |
|
|
1468
|
+
| Linux aarch64 | musl (Alpine) | ✅ musllinux | ✅ |
|
|
1469
|
+
| macOS arm64 + x86_64 | — | ✅ | ✅ |
|
|
1470
|
+
| Windows x86_64 | MSVC | ✅ | ✅ |
|
|
1471
|
+
|
|
1472
|
+
On Python, any platform without a prebuilt wheel still installs: pip falls back
|
|
1473
|
+
to the universal `py3-none-any` wheel and you get the pure-Python v1 AOF engine
|
|
1474
|
+
(correct, slower, no embedded DAG). On Node there is no such fallback — an
|
|
1475
|
+
unlisted platform has no addon.
|
|
1476
|
+
|
|
1545
1477
|
---
|
|
1546
1478
|
|
|
1547
|
-
##
|
|
1479
|
+
## Python — 5-minute tour
|
|
1548
1480
|
|
|
1549
|
-
|
|
1481
|
+
Every example in this tour was executed against the released **8.0.0** package.
|
|
1550
1482
|
|
|
1551
|
-
|
|
1483
|
+
```python
|
|
1484
|
+
from nedb import NEDB
|
|
1552
1485
|
|
|
1553
|
-
|
|
1486
|
+
db = NEDB("./mydata") # durable: every op is AOF-logged, fsync'd, and hash-chained
|
|
1487
|
+
# db = NEDB() # or in-memory
|
|
1554
1488
|
|
|
1555
|
-
|
|
1489
|
+
db.create_index("users", "status", "eq")
|
|
1490
|
+
db.create_index("users", "bio", "search")
|
|
1556
1491
|
|
|
1557
|
-
|
|
1492
|
+
db.put("users", "alice", {"name": "Alice", "age": 31, "status": "active", "bio": "rust hacker"})
|
|
1493
|
+
db.put("users", "bob", {"name": "Bob", "age": 24, "status": "active", "bio": "python dev"})
|
|
1558
1494
|
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1495
|
+
# NQL: WHERE + ORDER BY + LIMIT + SEARCH + TRAVERSE + GROUP BY
|
|
1496
|
+
db.query('FROM users WHERE status = "active" ORDER BY age ASC')
|
|
1497
|
+
db.query('FROM users SEARCH "rust"')
|
|
1498
|
+
db.query('FROM users GROUP BY status COUNT')
|
|
1563
1499
|
|
|
1564
|
-
|
|
1500
|
+
# Full boolean predicates — IN, BETWEEN, LIKE, IS NULL, OR, NOT, parentheses
|
|
1501
|
+
db.query('FROM users WHERE status IN ("active", "trialing")')
|
|
1502
|
+
db.query('FROM users WHERE age BETWEEN 25 AND 40')
|
|
1503
|
+
db.query('FROM users WHERE bio LIKE "%rust%" AND NOT (status = "retired")')
|
|
1504
|
+
db.query('FROM users WHERE (age < 25 OR age > 60) AND bio IS NOT NULL')
|
|
1565
1505
|
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1506
|
+
# Time-travel — AS OF any past sequence
|
|
1507
|
+
snap = db.seq
|
|
1508
|
+
db.put("users", "alice", {"name": "Alice", "age": 32, "status": "retired"})
|
|
1509
|
+
db.get("users", "alice", as_of=snap) # → age 31, status active
|
|
1569
1510
|
|
|
1570
|
-
#
|
|
1571
|
-
|
|
1511
|
+
# Bi-temporal — VALID AS OF any past date
|
|
1512
|
+
db.put("policy", "rate_2024", {"pct": 5.0}, valid_from="2024-01-01", valid_to="2024-12-31")
|
|
1513
|
+
db.put("policy", "rate_2025", {"pct": 6.0}, valid_from="2025-01-01")
|
|
1514
|
+
db.query('FROM policy VALID AS OF "2024-06-15"') # → rate 5.0
|
|
1515
|
+
|
|
1516
|
+
# Causal Write Provenance — why did this write happen?
|
|
1517
|
+
db.put("inputs", "msg_1", {"text": "user prefers dark mode"})
|
|
1518
|
+
seq_msg = db.seq
|
|
1519
|
+
db.put("beliefs", "dark_mode", {"value": True},
|
|
1520
|
+
caused_by=[seq_msg], evidence="user_message", confidence=0.95)
|
|
1521
|
+
db.query('FROM beliefs WHERE _id = "dark_mode" TRACE caused_by') # → msg_1
|
|
1522
|
+
db.query('FROM inputs WHERE _id = "msg_1" TRACE caused_by REVERSE') # → dark_mode
|
|
1523
|
+
|
|
1524
|
+
# Relations + graph traversal
|
|
1525
|
+
db.link("users:alice", "follows", "users:bob")
|
|
1526
|
+
db.query('FROM users WHERE _id = "alice" TRAVERSE follows')
|
|
1527
|
+
|
|
1528
|
+
# Hash-chain integrity — verify() is a call, head is the commitment
|
|
1529
|
+
assert db.verify() # cryptographic proof — no tampering
|
|
1530
|
+
db.head # → 64-char BLAKE2b Merkle head (a property, not a call)
|
|
1531
|
+
|
|
1532
|
+
# SQL, Redis, MongoDB compatibility adapters
|
|
1533
|
+
from nedb import sql_exec, RedisCompat, MongoClient
|
|
1534
|
+
sql_exec(db, "SELECT * FROM users WHERE status = 'active' ORDER BY age DESC")
|
|
1535
|
+
r = RedisCompat(db); r.execute("HSET", "user:1", "name", "Alice")
|
|
1536
|
+
MongoClient(db)["users"].find({"status": "active"}).sort("age", -1).to_list()
|
|
1572
1537
|
```
|
|
1573
1538
|
|
|
1574
|
-
|
|
1539
|
+
---
|
|
1575
1540
|
|
|
1576
|
-
|
|
1541
|
+
## Official Python client — talk to nedbd over HTTP
|
|
1577
1542
|
|
|
1578
|
-
|
|
1543
|
+
Running the daemon? `nedb.client.NedbClient` is the official client for its
|
|
1544
|
+
HTTP API — extracted from the battle-tested clients that ran a production
|
|
1545
|
+
Redis→NEDB mainnet migration, speaking the full route surface: queries,
|
|
1546
|
+
atomic CAS transactions, TTL, indexes, relations, Merkle proofs, and the
|
|
1547
|
+
Mongo-compat endpoint. Env-var defaults (`NEDBD_URL`, `NEDBD_TOKEN`,
|
|
1548
|
+
`NEDB_DB`) mirror the daemon's own.
|
|
1579
1549
|
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
| 2,002 coins / 275 kB | **1.93 s** | *minutes* |
|
|
1583
|
-
| 2,549 coins / 366 kB | **1.71 s** | *minutes* |
|
|
1550
|
+
```python
|
|
1551
|
+
from nedb import NedbClient, PreconditionFailed, op_put
|
|
1584
1552
|
|
|
1585
|
-
|
|
1553
|
+
c = NedbClient("http://127.0.0.1:7070", db="app", token="s3cret")
|
|
1554
|
+
c.ensure_database()
|
|
1586
1555
|
|
|
1587
|
-
|
|
1556
|
+
c.put("users", "u1", {"id": "u1", "email": "a@b.c"}, idem="signup-u1")
|
|
1557
|
+
c.query('FROM users WHERE email = "a@b.c"') # full NQL rides through
|
|
1558
|
+
c.query("FROM users AS OF 41") # time-travel included
|
|
1588
1559
|
|
|
1589
|
-
|
|
1560
|
+
# Atomic all-or-nothing transaction with engine-checked preconditions —
|
|
1561
|
+
# the primitive that replaces Redis Lua scripts (if_seq: N = CAS, -1 = create-once)
|
|
1562
|
+
doc = c.get_doc("users", "u1") # docs carry _seq
|
|
1563
|
+
c.tx([op_put("users", "u1", {**doc, "plan": "pro"}, if_seq=doc["_seq"])])
|
|
1564
|
+
|
|
1565
|
+
# Contested writes: retry ONLY on PreconditionFailed, capped backoff
|
|
1566
|
+
def bump():
|
|
1567
|
+
d = c.get_doc("counters", "hits") or {"n": 0}
|
|
1568
|
+
return c.tx([op_put("counters", "hits", {"n": d.get("n", 0) + 1},
|
|
1569
|
+
if_seq=d.get("_seq", -1))])
|
|
1570
|
+
c.cas_retry(bump)
|
|
1571
|
+
|
|
1572
|
+
# Integrity, verifiable WITHOUT trusting the server
|
|
1573
|
+
proof = c.proof(c.log(limit=1)[0]["hash"])
|
|
1574
|
+
from nedb import verify_proof; verify_proof(proof) # -> True, locally
|
|
1575
|
+
```
|
|
1576
|
+
|
|
1577
|
+
A CAS miss raises the **same `PreconditionFailed`** (with the same
|
|
1578
|
+
`.failures` shape) the embedded engine raises — code written against
|
|
1579
|
+
`NEDB.tx` ports to the HTTP client without changing its except-clauses.
|
|
1580
|
+
Typed errors throughout: `NedbAuthError`, `NedbNotFound`, `NedbBadRequest`,
|
|
1581
|
+
`NedbConflict`, `CasExhausted`.
|
|
1590
1582
|
|
|
1591
1583
|
---
|
|
1592
1584
|
|
|
1593
|
-
##
|
|
1585
|
+
## Redis layer-2 — wrap_redis()
|
|
1586
|
+
|
|
1587
|
+
Already running on Redis? Wrap your connection in one line and gain NEDB features *alongside* your existing Redis app — no migration required.
|
|
1588
|
+
|
|
1589
|
+
```python
|
|
1590
|
+
import redis, json
|
|
1591
|
+
from nedb import wrap_redis
|
|
1592
|
+
|
|
1593
|
+
r = wrap_redis(redis.Redis("localhost", 6379), db_name="rideshare")
|
|
1594
|
+
|
|
1595
|
+
# Step 1 — register: map Redis key globs to NEDB collections (chainable)
|
|
1596
|
+
(r.nedb
|
|
1597
|
+
.register("driver:*", collection="driver", value_parser=json.loads)
|
|
1598
|
+
.register("trip:*", collection="trip", value_type="hash")
|
|
1599
|
+
)
|
|
1600
|
+
|
|
1601
|
+
# Step 2 — backfill: import all existing Redis data into NEDB in one pass
|
|
1602
|
+
imported = r.nedb.backfill() # → int (keys imported)
|
|
1603
|
+
|
|
1604
|
+
# Step 3 — shadow: all future r.set/hset/... auto-chain into NEDB
|
|
1605
|
+
r.nedb.shadow_writes = True
|
|
1594
1606
|
|
|
1607
|
+
# ─── Alice's app keeps running — zero changes ───────────────────────────
|
|
1608
|
+
r.set("driver:d1", json.dumps({"name": "Bob", "status": "active"})) # ← shadowed
|
|
1609
|
+
r.hset("trip:t1", mapping={"status": "en_route", "driver_id": "d1"}) # ← shadowed
|
|
1610
|
+
|
|
1611
|
+
# ─── New features available on the same connection ──────────────────────
|
|
1612
|
+
r.nedb.query('FROM driver WHERE status = "active" ORDER BY lat ASC')
|
|
1613
|
+
r.nedb.verify() # → True (every write chain-verified)
|
|
1614
|
+
r.nedb.head() # → 64-char BLAKE2b commitment hash
|
|
1595
1615
|
```
|
|
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
1616
|
|
|
1606
|
-
|
|
1607
|
-
Thread-safe → │ Sequencer (group-commit) │ ← single writer, parallel readers
|
|
1608
|
-
│ — one committer thread/db │
|
|
1609
|
-
│ — batch fsync │
|
|
1610
|
-
└─────────────────────────────────┘
|
|
1617
|
+
**Isolation guarantee:** NEDB never writes to Alice's namespace. It owns only:
|
|
1611
1618
|
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1619
|
+
| Key | Type | Purpose |
|
|
1620
|
+
|-----|------|---------|
|
|
1621
|
+
| `nedb:{db_name}:oplog` | Redis Stream | append-only op log |
|
|
1622
|
+
| `nedb:{db_name}:snapshot` | Redis Hash | checkpoint |
|
|
1623
|
+
| `nedb:{db_name}:meta` | Redis Hash | index config |
|
|
1624
|
+
|
|
1625
|
+
See [`examples/fakeredis_demo.py`](examples/fakeredis_demo.py) for a full local demo (no Redis server needed).
|
|
1626
|
+
|
|
1627
|
+
---
|
|
1628
|
+
|
|
1629
|
+
## Node.js
|
|
1630
|
+
|
|
1631
|
+
```javascript
|
|
1632
|
+
import { NedbCore } from "nedb-engine";
|
|
1633
|
+
|
|
1634
|
+
const db = new NedbCore(); // in-memory
|
|
1635
|
+
// const db = NedbCore.open("./data"); // durable
|
|
1636
|
+
|
|
1637
|
+
db.createIndex("users", "status", "eq");
|
|
1638
|
+
db.put("users", "alice", JSON.stringify({ name: "Alice", age: 31, status: "active" }));
|
|
1639
|
+
|
|
1640
|
+
// Time-travel
|
|
1641
|
+
const snap = db.seq(); // BigInt
|
|
1642
|
+
db.put("users", "alice", JSON.stringify({ name: "Alice", age: 32, status: "retired" }));
|
|
1643
|
+
JSON.parse(db.getAsOf("users", "alice", snap)).age; # → 31
|
|
1644
|
+
|
|
1645
|
+
// Full NQL
|
|
1646
|
+
const rows = db.query('FROM users WHERE status = "active" ORDER BY age ASC');
|
|
1647
|
+
rows.map(r => JSON.parse(r));
|
|
1648
|
+
|
|
1649
|
+
// Tamper evidence
|
|
1650
|
+
db.verify(); // → true
|
|
1651
|
+
db.head(); // → 64-char BLAKE2b commitment hash
|
|
1652
|
+
db.seq(); // → BigInt
|
|
1615
1653
|
```
|
|
1616
1654
|
|
|
1617
1655
|
---
|
|
@@ -1644,53 +1682,98 @@ const rows = await db.query("FROM blocks LIMIT 10");
|
|
|
1644
1682
|
|
|
1645
1683
|
---
|
|
1646
1684
|
|
|
1685
|
+
## NEDB v3 — Segment / Pack Object Store
|
|
1686
|
+
|
|
1687
|
+
**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.
|
|
1688
|
+
|
|
1689
|
+
### Why it exists
|
|
1690
|
+
|
|
1691
|
+
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.
|
|
1692
|
+
|
|
1693
|
+
### What it does
|
|
1694
|
+
|
|
1695
|
+
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.**
|
|
1696
|
+
|
|
1697
|
+
- **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.
|
|
1698
|
+
- **`.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.
|
|
1699
|
+
- **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.
|
|
1700
|
+
- **Durable flush-on-close** — `flush_all()` (and `Db`'s `Drop`) fsync the active segment, matching the flush-on-close contract of sled / RocksDB.
|
|
1701
|
+
|
|
1702
|
+
### How to enable
|
|
1703
|
+
|
|
1704
|
+
```bash
|
|
1705
|
+
# Engine / nedbd-v2 (the native daemon from npm / the native wheel)
|
|
1706
|
+
nedbd-v2 --dag-v3 --data /var/lib/nedb # real flag as of v2.4.3 — or set NEDB_DAG_V3=1
|
|
1707
|
+
|
|
1708
|
+
# itcd — Bitcoin-fork node embedding NEDB via nedb-ffi
|
|
1709
|
+
interchainedd -dagv3 # puts chainstate AND block index on segments
|
|
1710
|
+
```
|
|
1711
|
+
|
|
1712
|
+
The switch is read once, when each database's object store is constructed at open time. Default off → v2 loose objects.
|
|
1713
|
+
|
|
1714
|
+
### Real-world result
|
|
1715
|
+
|
|
1716
|
+
itcd (a Bitcoin Core 0.21 fork that replaces LevelDB chainstate with NEDB) syncing on `-dagv3`, measured `FlushStateToDisk` on real chainstate:
|
|
1717
|
+
|
|
1718
|
+
| Flush (coins → disk) | v3 segment store | v2 loose store |
|
|
1719
|
+
|---|---|---|
|
|
1720
|
+
| 2,002 coins / 275 kB | **1.93 s** | *minutes* |
|
|
1721
|
+
| 2,549 coins / 366 kB | **1.71 s** | *minutes* |
|
|
1722
|
+
|
|
1723
|
+
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.
|
|
1724
|
+
|
|
1725
|
+
### When to use it
|
|
1726
|
+
|
|
1727
|
+
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.
|
|
1728
|
+
|
|
1729
|
+
---
|
|
1730
|
+
|
|
1647
1731
|
## Repo layout
|
|
1648
1732
|
|
|
1649
1733
|
```
|
|
1650
1734
|
python/nedb/ reference engine (pure Python — always-works baseline)
|
|
1651
1735
|
rust/
|
|
1736
|
+
nedb-v2/ v2 DAG engine (tokio + axum + BLAKE2b DAG) — the core everything binds
|
|
1737
|
+
nesql-cli/ the nesql CLI (query / root / inspect / diff / tag / branch / merge)
|
|
1652
1738
|
nedb-core/ v1 production Rust engine (shared by both runtimes)
|
|
1653
1739
|
nedb-py/ maturin PyO3 binding → PyPI native wheels
|
|
1654
1740
|
nedb-node/ napi-rs binding → npm native addons
|
|
1655
|
-
nedb-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1741
|
+
nedb-wrap/ the wrap_* surface at native speed
|
|
1742
|
+
vendor/postgresql/ PostgreSQL 17.4 grammar, vendored (gram.y, kwlist.h, system_views.sql, information_schema.sql)
|
|
1743
|
+
distributions/ crypto-database + aof-db submodules (the tri-distribution release)
|
|
1744
|
+
bench/ benchmarks.py + RESULTS.md — the dated embedded-core numbers
|
|
1659
1745
|
tests/ engine + concurrent + causal + bitemporal + deploy + perf benchmarks
|
|
1660
|
-
|
|
1661
|
-
docs/
|
|
1746
|
+
vectors/ state_root_v1.json — cross-engine state-root test vectors
|
|
1747
|
+
docs/ SPEC.md · CLI.md · DURABILITY.md · REPLICATION.md · BENCH-sqlselect.md
|
|
1748
|
+
client/ nedb-engine-client — async Python + TypeScript HTTP clients
|
|
1749
|
+
examples/ agent-loop · mini-chain · resp2 demos · fakeredis demo
|
|
1750
|
+
scripts/ release.py · test-cast.sh · bench_index_range.py · seed-shop.sh
|
|
1662
1751
|
```
|
|
1663
1752
|
|
|
1664
1753
|
---
|
|
1665
1754
|
|
|
1666
|
-
##
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
-
|
|
1672
|
-
|
|
1673
|
-
-
|
|
1674
|
-
|
|
1675
|
-
-
|
|
1676
|
-
-
|
|
1677
|
-
|
|
1678
|
-
-
|
|
1679
|
-
-
|
|
1680
|
-
|
|
1681
|
-
-
|
|
1682
|
-
-
|
|
1683
|
-
-
|
|
1684
|
-
-
|
|
1685
|
-
-
|
|
1686
|
-
-
|
|
1687
|
-
-
|
|
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
|
|
1755
|
+
## Ship log — the highlights, in order
|
|
1756
|
+
|
|
1757
|
+
NEDB's release history outgrew any changelog, and the README was where versions went to pile up.
|
|
1758
|
+
This is the map; the commits are the story.
|
|
1759
|
+
|
|
1760
|
+
- **8.x — neSQL ships.** PostgreSQL's real grammar vendored and extended; one evaluator answers every
|
|
1761
|
+
`SELECT` with nothing to enable; NQL's verbs are SQL clauses; the `nesql` CLI ships in the wheel.
|
|
1762
|
+
- **7.x — the neSQL endpoint surface.** The HTTP `/query` route speaks both dialects through one
|
|
1763
|
+
router; the CLI's publish path lands.
|
|
1764
|
+
- **6.x — hardening, discipline, the road to one name.**
|
|
1765
|
+
- **5.x — neSQL named, registered, vendored.** The grammar lands at `vendor/postgresql/`;
|
|
1766
|
+
`nesql` reserved on all three registries.
|
|
1767
|
+
- **4.0 — BUSL-1.1** (free under $1M revenue; Apache 2.0 on the 2030-09-11 Change Date).
|
|
1768
|
+
- **3.3 — the query language grows up.** Full boolean predicates in both engines; nine silent
|
|
1769
|
+
defects fixed; cross-engine parity gated.
|
|
1770
|
+
- **3.2 — the wrap family.** Redis/SQLite/Postgres/MySQL/Mongo adapters; durability fixes; PR CI.
|
|
1771
|
+
- **3.0–3.3.1 — the MIT era** (irrevocably MIT).
|
|
1772
|
+
- **2.8 — Cast.** A 3.33M-parameter model trained inside the engine's own parser; drift detection.
|
|
1773
|
+
- **2.5 — flush-on-exit, nedb-cli, inspector, replication contract.**
|
|
1774
|
+
- **2.4 — v3 segment store** (`--dag-v3`): itcd chainstate flush minutes → ~1.3 s.
|
|
1775
|
+
- **2.2 — v2 DAG engine.** Content-addressed Merkle DAG, O(1) warm start, SSE `/events`.
|
|
1776
|
+
- **1.x — v1 AOF engine.** Hash-chained log, MVCC time-travel, bi-temporal, `TRACE caused_by`.
|
|
1694
1777
|
|
|
1695
1778
|
---
|
|
1696
1779
|
|
|
@@ -1702,14 +1785,16 @@ Prompt-to-database scaffolding GUI with schema graph, NQL console, time-travel s
|
|
|
1702
1785
|
|
|
1703
1786
|
---
|
|
1704
1787
|
|
|
1705
|
-
## Repos
|
|
1788
|
+
## Repos & packages
|
|
1706
1789
|
|
|
1707
|
-
|
|
|
1790
|
+
| Where | What |
|
|
1708
1791
|
|---|---|
|
|
1709
|
-
| [
|
|
1792
|
+
| [Eth-Interchained/nedb](https://github.com/Eth-Interchained/nedb) | **canonical source** — engine, Rust core, CI, this README |
|
|
1793
|
+
| [aiassistsecure/nedb](https://github.com/aiassistsecure/nedb) | mirror + [GitHub Pages site](https://nedb.aiassist.net) |
|
|
1794
|
+
| [Eth-Interchained/neSQL](https://github.com/Eth-Interchained/neSQL) | the language — both halves of the grammar, CLI source |
|
|
1710
1795
|
| [aiassistsecure/nedb-studio](https://github.com/aiassistsecure/nedb-studio) | Studio UI (GPLv3) |
|
|
1711
1796
|
|
|
1712
|
-
**Packages:** [PyPI nedb-engine](https://pypi.org/project/nedb-engine/) · [npm nedb-engine](https://www.npmjs.com/package/nedb-engine)
|
|
1797
|
+
**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
1798
|
|
|
1714
1799
|
---
|
|
1715
1800
|
|
|
@@ -1737,7 +1822,11 @@ Requires `GITHUB_TOKEN` (`repo` + `workflow` scope) in the environment. It never
|
|
|
1737
1822
|
|
|
1738
1823
|
## License
|
|
1739
1824
|
|
|
1740
|
-
**
|
|
1825
|
+
**Business Source License 1.1** — free for any organisation under USD $1M annual revenue, including
|
|
1826
|
+
commercial and production use. Converts to **Apache 2.0** on **2030-09-11**, automatically and
|
|
1827
|
+
permanently. Versions 3.0.0–3.3.1 remain MIT, irrevocably. See [`LICENSE`](LICENSE) and
|
|
1828
|
+
[`COPYING-APACHE-2.0.txt`](COPYING-APACHE-2.0.txt).
|
|
1829
|
+
|
|
1741
1830
|
© 2026 INTERCHAINED LLC — [interchained.org](https://interchained.org)
|
|
1742
1831
|
|
|
1743
1832
|
---
|