nedb-engine 2.2.28__tar.gz → 2.2.32__tar.gz

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.
Files changed (37) hide show
  1. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/PKG-INFO +13 -12
  2. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/README.md +12 -11
  3. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/pyproject.toml +1 -1
  4. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/__init__.py +29 -13
  5. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/_native.abi3.so +0 -0
  6. nedb_engine-2.2.32/python/nedb/nedbd-v2 +0 -0
  7. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/nedbd-v2-darwin-arm64 +0 -0
  8. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/nedbd-v2-darwin-x64 +0 -0
  9. nedb_engine-2.2.32/python/nedb/nedbd-v2.exe +0 -0
  10. nedb_engine-2.2.32/python/nedb/proof.py +126 -0
  11. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/server.py +78 -2
  12. nedb_engine-2.2.32/rust/nedb-v2/README.md +179 -0
  13. nedb_engine-2.2.28/python/nedb/nedbd-v2 +0 -0
  14. nedb_engine-2.2.28/python/nedb/nedbd-v2.exe +0 -0
  15. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/.gitignore +0 -0
  16. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/LICENSE +0 -0
  17. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/client/node/README.md +0 -0
  18. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/client/python/README.md +0 -0
  19. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/autoindex.py +0 -0
  20. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/backends/__init__.py +0 -0
  21. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/backends/redis_backend.py +0 -0
  22. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/cascade.py +0 -0
  23. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/concurrent.py +0 -0
  24. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/crypto.py +0 -0
  25. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/engine.py +0 -0
  26. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/index.py +0 -0
  27. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/log.py +0 -0
  28. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/merkle.py +0 -0
  29. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/mongo.py +0 -0
  30. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/query.py +0 -0
  31. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/redis_compat.py +0 -0
  32. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/relations.py +0 -0
  33. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/resp2.py +0 -0
  34. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/snapshot.py +0 -0
  35. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/sql.py +0 -0
  36. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/store.py +0 -0
  37. {nedb_engine-2.2.28 → nedb_engine-2.2.32}/python/nedb/wrap_redis.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nedb-engine
3
- Version: 2.2.28
3
+ Version: 2.2.32
4
4
  Summary: NEDB — a versioned, self-compressing, time-traveling embedded database (replay-protected, idempotent, relational, searchable) with durable AOF persistence and a server daemon (nedbd).
5
5
  Project-URL: Homepage, https://github.com/aiassistsecure/nedb
6
6
  Project-URL: Repository, https://github.com/aiassistsecure/nedb
@@ -29,8 +29,9 @@ Replay-protected · idempotent · relational · filterable · sortable · search
29
29
  One Rust core → ships to **PyPI** and **npm** from a single source.
30
30
 
31
31
  [![PyPI](https://img.shields.io/pypi/v/nedb-engine?label=PyPI&color=6366f1)](https://pypi.org/project/nedb-engine/)
32
+ [![crates.io](https://img.shields.io/crates/v/nedb-engine?label=crates.io&color=f97316)](https://crates.io/crates/nedb-engine)
32
33
  [![npm](https://img.shields.io/npm/v/nedb-engine?label=npm&color=00d4ff)](https://www.npmjs.com/package/nedb-engine)
33
- [![Tests](https://img.shields.io/badge/tests-266%20passing-34d399)](https://github.com/aiassistsecure/nedb/actions)
34
+ [![CI](https://img.shields.io/github/actions/workflow/status/Eth-Interchained/nedb/release.yml?label=CI&color=34d399)](https://github.com/Eth-Interchained/nedb/actions)
34
35
  [![nedb-engine-client PyPI](https://img.shields.io/pypi/v/nedb-engine-client?label=nedb-engine-client&color=34d399)](https://pypi.org/project/nedb-engine-client/)
35
36
  [![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)
36
37
 
@@ -40,11 +41,11 @@ One Rust core → ships to **PyPI** and **npm** from a single source.
40
41
 
41
42
  ---
42
43
 
43
- ## NEDB v2.0.36 — Production Stable
44
+ ## NEDB v2.2.31 — Production Stable
44
45
 
45
- **Current stable: 2.0.36** — Cross-platform native wheels shipping `nedbd-v2` binary inside `pip install nedb-engine`. Linux + Windows wheels built on GitHub Actions; macOS arm64 + x86_64 wheels built on Codemagic (M2 Mac Minis). All four platforms publish from one `v*` tag.
46
+ **Current stable: 2.2.31** — Cross-platform native wheels shipping `nedbd-v2` binary inside `pip install nedb-engine`. Linux + Windows wheels built on GitHub Actions; macOS arm64 + x86_64 wheels built on Codemagic (M2 Mac Minis). All four platforms publish from one `v*` tag.
46
47
 
47
- NEDB v2 replaces the append-only log (AOF) with a **content-addressed Merkle DAG**. Every document version is an immutable, BLAKE2b-verified object. Nothing is ever overwritten. As of **v2.0.36**, restarts after the first open are **O(1) warm starts** (driven by a `MANIFEST` of `seq` + Merkle head), the **cold scan is deferred** so the daemon accepts connections immediately, and a new **`GET /events` SSE endpoint** streams scan progress + per-write events live.
48
+ NEDB v2 replaces the append-only log (AOF) with a **content-addressed Merkle DAG**. Every document version is an immutable, BLAKE2b-verified object. Nothing is ever overwritten. As of **v2.2.31**, restarts after the first open are **O(1) warm starts** (driven by a `MANIFEST` of `seq` + Merkle head), the **cold scan is deferred** so the daemon accepts connections immediately, and a new **`GET /events` SSE endpoint** streams scan progress + per-write events live.
48
49
 
49
50
  ```bash
50
51
  # Run the v2 DAG engine — ships inside pip install nedb-engine
@@ -53,9 +54,9 @@ nedbd --dag --data ./data
53
54
  NEDBD_DAG=1 NEDB_TMK=<32-byte-hex> nedbd --data ./data
54
55
 
55
56
  curl http://127.0.0.1:7070/health
56
- # {"ok":true,"version":"2.0.36","service":"nedbd","engine":"dag","startup_ready":true,"encrypted":true}
57
+ # {"ok":true,"version":"2.2.31","service":"nedbd","engine":"dag","startup_ready":true,"encrypted":true}
57
58
 
58
- # Tail the live event stream (new in v2.0.36)
59
+ # Tail the live event stream (new in v2.2.31)
59
60
  curl http://127.0.0.1:7070/events
60
61
  # event: scan data: {"objects":730000,"of":1310703,"rate":21043,"eta_s":28}
61
62
  # event: ready data: {"seq":1310703,"head":"b2:9c14e07a…"}
@@ -79,7 +80,7 @@ curl http://127.0.0.1:7070/events
79
80
 
80
81
  **v1 AOF engine is still shipped and unchanged** — `nedbd` (no flag) runs v1.
81
82
 
82
- **Production status:** [vision.interchained.org](https://vision.interchained.org) is live on v2.0.36 — **1,310,703 sequences** indexed in the Vision database, AES-256-GCM encrypted at rest, at block height **620,989**.
83
+ **Production status:** [vision.interchained.org](https://vision.interchained.org) is live on v2.2.31 — **1,310,703 sequences** indexed in the Vision database, AES-256-GCM encrypted at rest, at block height **620,989**.
83
84
 
84
85
  ---
85
86
 
@@ -244,11 +245,11 @@ nedbd --dag --data ./data # v2 DAG engine (or NEDBD_DAG=1)
244
245
  NEDBD_RESP2_PORT=6380 nedbd # also speak RESP2 (redis-cli compatible)
245
246
  nedbd --log-level 2 # 0=errors 1=requests 2=deploy 3=verbose
246
247
 
247
- # Live event stream (new in v2.0.36) — SSE: scan progress, ready, per-write head
248
+ # Live event stream (new in v2.2.31) — SSE: scan progress, ready, per-write head
248
249
  curl http://127.0.0.1:7070/events
249
250
  ```
250
251
 
251
- ### Startup modes (v2.0.36)
252
+ ### Startup modes (v2.2.31)
252
253
 
253
254
  - **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.
254
255
  - **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`.
@@ -258,7 +259,7 @@ curl http://127.0.0.1:7070/events
258
259
  | Variable | Default | Description |
259
260
  |---|---|---|
260
261
  | `NEDBD_DAG` | `0` | Set `1` to launch the v2 DAG engine (`nedbd-v2`). Same as `--dag`. |
261
- | `NEDBD_HOST` | `127.0.0.1` | Bind address. **v2.0.36** defaults to loopback (was `0.0.0.0`) — security hardening fix. Set explicitly to `0.0.0.0` to expose. |
262
+ | `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. |
262
263
  | `NEDBD_PORT` | `7070` | HTTP bind port. |
263
264
  | `NEDBD_TOKEN` | unset | Optional bearer token; required on every `/v1/*` request when set. |
264
265
  | `NEDB_TMK` | unset | 32-byte hex AES-256-GCM at-rest encryption key. |
@@ -321,7 +322,7 @@ db.query('FROM policy AS OF 200 VALID AS OF "2024-02-15"')
321
322
 
322
323
  ## Performance
323
324
 
324
- **v2 DAG Rust server (v2.0.36, Intel iMac — 10k writes / 100k reads / 30k objects, AES-256-GCM on):**
325
+ **v2 DAG Rust server (v2.2.31, Intel iMac — 10k writes / 100k reads / 30k objects, AES-256-GCM on):**
325
326
 
326
327
  | Operation | Throughput | p50 | p99 |
327
328
  |---|---|---|---|
@@ -8,8 +8,9 @@ Replay-protected · idempotent · relational · filterable · sortable · search
8
8
  One Rust core → ships to **PyPI** and **npm** from a single source.
9
9
 
10
10
  [![PyPI](https://img.shields.io/pypi/v/nedb-engine?label=PyPI&color=6366f1)](https://pypi.org/project/nedb-engine/)
11
+ [![crates.io](https://img.shields.io/crates/v/nedb-engine?label=crates.io&color=f97316)](https://crates.io/crates/nedb-engine)
11
12
  [![npm](https://img.shields.io/npm/v/nedb-engine?label=npm&color=00d4ff)](https://www.npmjs.com/package/nedb-engine)
12
- [![Tests](https://img.shields.io/badge/tests-266%20passing-34d399)](https://github.com/aiassistsecure/nedb/actions)
13
+ [![CI](https://img.shields.io/github/actions/workflow/status/Eth-Interchained/nedb/release.yml?label=CI&color=34d399)](https://github.com/Eth-Interchained/nedb/actions)
13
14
  [![nedb-engine-client PyPI](https://img.shields.io/pypi/v/nedb-engine-client?label=nedb-engine-client&color=34d399)](https://pypi.org/project/nedb-engine-client/)
14
15
  [![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)
15
16
 
@@ -19,11 +20,11 @@ One Rust core → ships to **PyPI** and **npm** from a single source.
19
20
 
20
21
  ---
21
22
 
22
- ## NEDB v2.0.36 — Production Stable
23
+ ## NEDB v2.2.31 — Production Stable
23
24
 
24
- **Current stable: 2.0.36** — Cross-platform native wheels shipping `nedbd-v2` binary inside `pip install nedb-engine`. Linux + Windows wheels built on GitHub Actions; macOS arm64 + x86_64 wheels built on Codemagic (M2 Mac Minis). All four platforms publish from one `v*` tag.
25
+ **Current stable: 2.2.31** — Cross-platform native wheels shipping `nedbd-v2` binary inside `pip install nedb-engine`. Linux + Windows wheels built on GitHub Actions; macOS arm64 + x86_64 wheels built on Codemagic (M2 Mac Minis). All four platforms publish from one `v*` tag.
25
26
 
26
- NEDB v2 replaces the append-only log (AOF) with a **content-addressed Merkle DAG**. Every document version is an immutable, BLAKE2b-verified object. Nothing is ever overwritten. As of **v2.0.36**, restarts after the first open are **O(1) warm starts** (driven by a `MANIFEST` of `seq` + Merkle head), the **cold scan is deferred** so the daemon accepts connections immediately, and a new **`GET /events` SSE endpoint** streams scan progress + per-write events live.
27
+ NEDB v2 replaces the append-only log (AOF) with a **content-addressed Merkle DAG**. Every document version is an immutable, BLAKE2b-verified object. Nothing is ever overwritten. As of **v2.2.31**, restarts after the first open are **O(1) warm starts** (driven by a `MANIFEST` of `seq` + Merkle head), the **cold scan is deferred** so the daemon accepts connections immediately, and a new **`GET /events` SSE endpoint** streams scan progress + per-write events live.
27
28
 
28
29
  ```bash
29
30
  # Run the v2 DAG engine — ships inside pip install nedb-engine
@@ -32,9 +33,9 @@ nedbd --dag --data ./data
32
33
  NEDBD_DAG=1 NEDB_TMK=<32-byte-hex> nedbd --data ./data
33
34
 
34
35
  curl http://127.0.0.1:7070/health
35
- # {"ok":true,"version":"2.0.36","service":"nedbd","engine":"dag","startup_ready":true,"encrypted":true}
36
+ # {"ok":true,"version":"2.2.31","service":"nedbd","engine":"dag","startup_ready":true,"encrypted":true}
36
37
 
37
- # Tail the live event stream (new in v2.0.36)
38
+ # Tail the live event stream (new in v2.2.31)
38
39
  curl http://127.0.0.1:7070/events
39
40
  # event: scan data: {"objects":730000,"of":1310703,"rate":21043,"eta_s":28}
40
41
  # event: ready data: {"seq":1310703,"head":"b2:9c14e07a…"}
@@ -58,7 +59,7 @@ curl http://127.0.0.1:7070/events
58
59
 
59
60
  **v1 AOF engine is still shipped and unchanged** — `nedbd` (no flag) runs v1.
60
61
 
61
- **Production status:** [vision.interchained.org](https://vision.interchained.org) is live on v2.0.36 — **1,310,703 sequences** indexed in the Vision database, AES-256-GCM encrypted at rest, at block height **620,989**.
62
+ **Production status:** [vision.interchained.org](https://vision.interchained.org) is live on v2.2.31 — **1,310,703 sequences** indexed in the Vision database, AES-256-GCM encrypted at rest, at block height **620,989**.
62
63
 
63
64
  ---
64
65
 
@@ -223,11 +224,11 @@ nedbd --dag --data ./data # v2 DAG engine (or NEDBD_DAG=1)
223
224
  NEDBD_RESP2_PORT=6380 nedbd # also speak RESP2 (redis-cli compatible)
224
225
  nedbd --log-level 2 # 0=errors 1=requests 2=deploy 3=verbose
225
226
 
226
- # Live event stream (new in v2.0.36) — SSE: scan progress, ready, per-write head
227
+ # Live event stream (new in v2.2.31) — SSE: scan progress, ready, per-write head
227
228
  curl http://127.0.0.1:7070/events
228
229
  ```
229
230
 
230
- ### Startup modes (v2.0.36)
231
+ ### Startup modes (v2.2.31)
231
232
 
232
233
  - **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.
233
234
  - **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`.
@@ -237,7 +238,7 @@ curl http://127.0.0.1:7070/events
237
238
  | Variable | Default | Description |
238
239
  |---|---|---|
239
240
  | `NEDBD_DAG` | `0` | Set `1` to launch the v2 DAG engine (`nedbd-v2`). Same as `--dag`. |
240
- | `NEDBD_HOST` | `127.0.0.1` | Bind address. **v2.0.36** defaults to loopback (was `0.0.0.0`) — security hardening fix. Set explicitly to `0.0.0.0` to expose. |
241
+ | `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. |
241
242
  | `NEDBD_PORT` | `7070` | HTTP bind port. |
242
243
  | `NEDBD_TOKEN` | unset | Optional bearer token; required on every `/v1/*` request when set. |
243
244
  | `NEDB_TMK` | unset | 32-byte hex AES-256-GCM at-rest encryption key. |
@@ -300,7 +301,7 @@ db.query('FROM policy AS OF 200 VALID AS OF "2024-02-15"')
300
301
 
301
302
  ## Performance
302
303
 
303
- **v2 DAG Rust server (v2.0.36, Intel iMac — 10k writes / 100k reads / 30k objects, AES-256-GCM on):**
304
+ **v2 DAG Rust server (v2.2.31, Intel iMac — 10k writes / 100k reads / 30k objects, AES-256-GCM on):**
304
305
 
305
306
  | Operation | Throughput | p50 | p99 |
306
307
  |---|---|---|---|
@@ -10,7 +10,7 @@ build-backend = "hatchling.build"
10
10
 
11
11
  [project]
12
12
  name = "nedb-engine"
13
- version = "2.2.28"
13
+ version = "2.2.32"
14
14
  description = "NEDB — a versioned, self-compressing, time-traveling embedded database (replay-protected, idempotent, relational, searchable) with durable AOF persistence and a server daemon (nedbd)."
15
15
  readme = "README.md"
16
16
  requires-python = ">=3.8"
@@ -29,6 +29,7 @@ from .mongo import (
29
29
  from .autoindex import AutoIndexDB
30
30
  from .concurrent import Sequencer
31
31
  from .wrap_redis import wrap_redis, WrappedRedis
32
+ from .proof import verify_proof, fold_head
32
33
 
33
34
  try: # compiled Rust core, present in platform wheels (PyO3 via maturin)
34
35
  from . import _native # type: ignore
@@ -38,21 +39,35 @@ except ImportError: # pure-Python install (sdist / unsupported platform)
38
39
  # informative error instead of a bare ImportError with no guidance.
39
40
  import types as _types, sys as _sys
40
41
 
42
+ import sys as _sys_tmp, os as _os_tmp
43
+ _is_msys2 = bool(_os_tmp.environ.get("MSYSTEM")) or "mingw" in _sys_tmp.executable.lower()
44
+ del _sys_tmp, _os_tmp
45
+
41
46
  class _NativeStub(_types.ModuleType):
42
- _MSG = (
47
+ # Primary fix: install the Rust crate → get the nedbd server → use HTTP mode.
48
+ # Secondary fix (CPython only): pip reinstall to get the platform wheel with _native embedded.
49
+ _MSG_MSYS2 = (
50
+ "\n\n"
51
+ " nedb._native (embedded v2 DAG core) is not available on MSYS2/MinGW Python.\n\n"
52
+ " To use NEDB v2 features, install the server binary and use HTTP mode:\n\n"
53
+ " cargo install nedb-engine # install nedbd v2 server\n"
54
+ " nedbd --dag ./data # start DAG server\n"
55
+ " NEDB_URL=http://localhost:7070 python3 your_script.py\n\n"
56
+ " Run 'nedbd --doctor' for a full diagnosis.\n"
57
+ )
58
+ _MSG_OTHER = (
43
59
  "\n\n"
44
- " nedb._native is not available on this platform.\n\n"
45
- " The compiled Rust core ships with platform wheels (Linux x86_64,\n"
46
- " macOS arm64/x86_64, Windows x64 CPython). It is NOT included in\n"
47
- " the universal wheel installed on MSYS2/MinGW Python.\n\n"
48
- " Options:\n"
49
- " 1. Use the HTTP server instead (works on any platform):\n"
50
- " nedbd --dag ./data # start DAG server\n"
51
- " NEDB_URL=http://localhost:7070 python3 script.py\n\n"
52
- " 2. Re-install on a platform that has a native wheel:\n"
53
- " pip install --force-reinstall --no-cache-dir nedb-engine\n\n"
54
- " 3. Run 'nedbd --doctor' for a full diagnosis.\n"
60
+ " nedb._native (embedded v2 DAG core) is not available.\n"
61
+ " You have the universal wheel reinstall to get the platform wheel:\n\n"
62
+ " pip install --force-reinstall --no-cache-dir nedb-engine\n\n"
63
+ " Or install the server binary and use HTTP mode (works everywhere):\n\n"
64
+ " cargo install nedb-engine # install nedbd v2 server\n"
65
+ " nedbd --dag ./data # start DAG server\n"
66
+ " NEDB_URL=http://localhost:7070 python3 your_script.py\n\n"
67
+ " Run 'nedbd --doctor' for a full diagnosis.\n"
55
68
  )
69
+ _MSG = _MSG_MSYS2 if _is_msys2 else _MSG_OTHER
70
+
56
71
  def __getattr__(self, name: str):
57
72
  raise ImportError(f"nedb._native.{name} is not available.{self._MSG}")
58
73
 
@@ -71,6 +86,7 @@ __all__ = [
71
86
  "MongoCompat", "MongoClient", "MongoError", "MongoUnsupportedError", "ObjectId",
72
87
  "AutoIndexDB", "Sequencer",
73
88
  "wrap_redis", "WrappedRedis",
89
+ "verify_proof", "fold_head",
74
90
  "_native", "__has_native__",
75
91
  ]
76
- __version__ = "2.2.28"
92
+ __version__ = "2.2.32"
Binary file
@@ -0,0 +1,126 @@
1
+ """
2
+ nedb.proof — Merkle inclusion proofs for the hash-chained op log.
3
+
4
+ Every NEDB write produces an Op whose ``hash`` field is the chain head after that op
5
+ (``op.hash = blake2b(prev_head.utf8 + canon(body))`` — see ``nedb.log``). The chain
6
+ head therefore commits cryptographically to the entire write history.
7
+
8
+ This module derives a *parallel*, compact Merkle commitment chain over the same
9
+ op-hash sequence so a client can verify — offline, with no server round-trip and
10
+ without re-running the engine — that a specific op-hash was at a specific seq in
11
+ the log, and that the log's head followed from chaining all subsequent op-hashes
12
+ on top of it.
13
+
14
+ The chain step used by both ``verify_proof`` (client) and the server endpoint is::
15
+
16
+ head_next = blake2b(bytes.fromhex(head_prev) || bytes.fromhex(op_hash)).hexdigest()
17
+
18
+ starting from the genesis head (the all-zero 32-byte string). Folding over the
19
+ full ordered list of op-hashes yields a deterministic head; tamper with any op
20
+ hash (or reorder ops) and the fold no longer matches. The Op's own ``hash`` field
21
+ already commits to its body (payload, seq, nonce, ts, …) under the engine's chain
22
+ formula, so this fold is a complete commitment to the write history through the
23
+ op-hashes alone — exactly the property a Merkle inclusion proof needs.
24
+
25
+ The fold's terminal value (``proof["head"]``) is a *derived* head, distinct from
26
+ ``db.head`` (the engine's UTF-8-hex chain) but a deterministic function of the
27
+ same log. The server computes the derived head over the live log when issuing
28
+ each proof, so any divergence between client fold and server fold means the
29
+ proof was tampered with in transit.
30
+ """
31
+ from __future__ import annotations
32
+
33
+ import hashlib
34
+ from typing import Dict, List
35
+
36
+ # Genesis head matches the engine: all-zero 32-byte string in hex.
37
+ GENESIS = "0" * 64
38
+
39
+
40
+ def _blake(data: bytes) -> str:
41
+ """BLAKE2b-256 over raw bytes, returned as hex — same primitive the engine uses."""
42
+ return hashlib.blake2b(data, digest_size=32).hexdigest()
43
+
44
+
45
+ def _chain_step(head_hex: str, op_hash_hex: str) -> str:
46
+ """One Merkle-chain step on raw bytes (decoded from hex).
47
+
48
+ Mirrors the user-facing spec exactly:
49
+ new_head = blake2b(prev_head_bytes || op_hash_bytes)
50
+ """
51
+ return _blake(bytes.fromhex(head_hex) + bytes.fromhex(op_hash_hex))
52
+
53
+
54
+ def fold_head(op_hashes: List[str], start: str = GENESIS) -> str:
55
+ """Fold an ordered list of op-hashes into a Merkle-chain head.
56
+
57
+ Used by the server to produce ``proof["head"]`` for any given log state and
58
+ by tests to sanity-check that the fold is order-sensitive and deterministic.
59
+ """
60
+ head = start
61
+ for h in op_hashes:
62
+ head = _chain_step(head, h)
63
+ return head
64
+
65
+
66
+ def verify_proof(proof: Dict) -> bool:
67
+ """Verify a Merkle inclusion proof client-side — no server, no engine needed.
68
+
69
+ ``proof`` shape::
70
+
71
+ {
72
+ "hash": "<64-hex>", # the document op's content hash (op.hash)
73
+ "seq": N, # sequence number of this op in the log
74
+ "prev_head": "<64-hex>", # chain head BEFORE this op (= op.prev_hash)
75
+ "subsequent": ["<64-hex>"], # op-hashes of every op AFTER this one
76
+ "head": "<64-hex>", # claimed final (derived) chain head
77
+ }
78
+
79
+ Verification (matches the spec):
80
+ 1. ``head_at_seq = blake2b(prev_head_bytes || hash_bytes)``
81
+ 2. For each ``sub_hash`` in subsequent:
82
+ ``head = blake2b(head_bytes || sub_hash_bytes)``
83
+ 3. ``final head == proof["head"]``
84
+
85
+ Returns True iff every step is internally consistent. Tampering with any
86
+ field — flipping a byte of ``hash``, reordering ``subsequent``, swapping
87
+ ``prev_head``, etc. — will flip the fold and make this return False.
88
+ """
89
+ if not isinstance(proof, dict):
90
+ return False
91
+
92
+ # Required keys with the right shapes.
93
+ required = ("hash", "seq", "prev_head", "subsequent", "head")
94
+ if not all(k in proof for k in required):
95
+ return False
96
+ if not isinstance(proof["seq"], int) or proof["seq"] < 0:
97
+ return False
98
+ if not isinstance(proof["subsequent"], list):
99
+ return False
100
+
101
+ # Hex shape check on the 64-char fields. Anything malformed → not verified.
102
+ def _is_hex32(s: object) -> bool:
103
+ if not isinstance(s, str) or len(s) != 64:
104
+ return False
105
+ try:
106
+ bytes.fromhex(s)
107
+ except ValueError:
108
+ return False
109
+ return True
110
+
111
+ if not _is_hex32(proof["hash"]): return False
112
+ if not _is_hex32(proof["prev_head"]): return False
113
+ if not _is_hex32(proof["head"]): return False
114
+ for s in proof["subsequent"]:
115
+ if not _is_hex32(s):
116
+ return False
117
+
118
+ # 1. Re-derive the chain head AT this op's seq from prev_head + this op's hash.
119
+ head = _chain_step(proof["prev_head"], proof["hash"])
120
+
121
+ # 2. Fold each subsequent op-hash on top to advance the chain to the tail.
122
+ for sub in proof["subsequent"]:
123
+ head = _chain_step(head, sub)
124
+
125
+ # 3. The fold must equal the claimed final head bit-for-bit.
126
+ return head == proof["head"]
@@ -33,6 +33,7 @@ HTTP API (all JSON):
33
33
  POST /v1/databases/<name>/files {name, data_b64, tier?} — store a file (Cascade-compressed)
34
34
  GET /v1/databases/<name>/files/<filename>?version=N&tier=warm — retrieve a file
35
35
  GET /v1/databases/<name>/files/<filename>/root?version=N&tier=warm — Merkle root (anchorable)
36
+ POST /v1/databases/<name>/proof {hash} — Merkle inclusion proof (verifiable offline)
36
37
  POST /v1/databases/<name>/checkpoint — on-demand checkpoint
37
38
  GET /v1/databases/<name>/batch — batch writes (array of {op,coll,id,doc})
38
39
  """
@@ -368,6 +369,57 @@ def make_handler(manager: Manager, token: Optional[str]):
368
369
  ops = [o.to_dict() for o in db.log.ops[-limit:]][::-1]
369
370
  self._send(200, {"log": ops, "seq": db.seq, "head": db.head})
370
371
  return
372
+ # POST /v1/databases/<name>/proof
373
+ # Body: {"hash": "<64-hex>"} — the op.hash of a previously-written doc
374
+ # Response: a self-contained Merkle inclusion proof + a verified flag.
375
+ # The proof is verifiable client-side via nedb.verify_proof() with no
376
+ # server / engine — the server's role is only to look up the seq,
377
+ # collect the prev_head and subsequent op-hashes, and fold them into
378
+ # the same derived head the client will re-derive locally.
379
+ if method == "POST" and action == "proof":
380
+ from .proof import verify_proof as _vp, fold_head as _fold, GENESIS as _GEN
381
+ b = self._body()
382
+ want = str(b.get("hash", "")).strip().lower()
383
+ if not want or len(want) != 64:
384
+ raise HttpError(400, "hash (64-char hex) is required")
385
+ try:
386
+ bytes.fromhex(want)
387
+ except ValueError:
388
+ raise HttpError(400, "hash must be 64-char hexadecimal")
389
+ # Find the Op with this exact chain-hash.
390
+ ops = db.log.ops
391
+ target = None
392
+ for o in ops:
393
+ if o.hash == want:
394
+ target = o
395
+ break
396
+ if target is None:
397
+ raise HttpError(404, f"no op with hash={want[:16]}…")
398
+ # The proof scheme is a Merkle fold over op-hashes — a
399
+ # PARALLEL commitment to the engine's per-op chain. Both
400
+ # are deterministic functions of the same log. Replay the
401
+ # fold from GENESIS to determine the fold-value just before
402
+ # this op (prev_head), then collect every subsequent op-hash
403
+ # so the client can fold forward to the same derived head.
404
+ op_hashes = [o.hash for o in ops]
405
+ prev_head = _fold(op_hashes[: target.seq], start=_GEN)
406
+ subsequent = op_hashes[target.seq + 1:]
407
+ derived_head = _fold(op_hashes, start=_GEN)
408
+ proof = {
409
+ "hash": target.hash,
410
+ "seq": target.seq,
411
+ "prev_head": prev_head,
412
+ "subsequent": subsequent,
413
+ "head": derived_head,
414
+ }
415
+ # Self-check: confirm the proof verifies before returning it,
416
+ # so a 200 from this endpoint is a hard guarantee of soundness.
417
+ proof["verified"] = _vp(proof)
418
+ # Helpful extras for clients that want to anchor to db.head too.
419
+ proof["engine_head"] = db.head
420
+ proof["engine_seq"] = db.seq
421
+ self._send(200, proof)
422
+ return
371
423
  # MongoDB-compatible query endpoint
372
424
  # POST /v1/databases/<name>/mongo
373
425
  # Body: {collection, op, ...op-specific args}
@@ -676,7 +728,7 @@ def _run_doctor() -> None: # noqa: C901
676
728
  step += 1
677
729
  if has_cargo:
678
730
  _cmd("install from crates.io (recommended)",
679
- "cargo install nedb-core-v2",
731
+ "cargo install nedb-engine",
680
732
  f"binary → {os.path.join(_cargo_bin, 'nedbd')}")
681
733
  print()
682
734
  _cmd("OR reinstall pip wheel (also bundles the binary)",
@@ -810,7 +862,7 @@ def main() -> None:
810
862
  print("", file=_sys.stderr)
811
863
  print(" Quick fixes:", file=_sys.stderr)
812
864
  print(" pip install --force-reinstall --no-cache-dir nedb-engine # re-install with binary", file=_sys.stderr)
813
- print(" cargo install nedb-core-v2 # build from source (any platform)", file=_sys.stderr)
865
+ print(" cargo install nedb-engine # build from source (any platform)", file=_sys.stderr)
814
866
  print("", file=_sys.stderr)
815
867
  _sys.exit(1)
816
868
  # Rust binary: nedbd-v2 [data_dir]
@@ -861,6 +913,30 @@ def main() -> None:
861
913
  print(BANNER)
862
914
  level_label = {0: "errors-only", 1: "requests", 2: "deploy", 3: "verbose"}.get(_log_level, str(_log_level))
863
915
  print(f" nedbd {__version__} — http://{host}:{port} data={os.path.abspath(data)} auth={auth} log={level_label}")
916
+
917
+ # ── v2 DAG health notice ──────────────────────────────────────────────────
918
+ # Check once at startup whether the v2 binary and _native extension are available.
919
+ # Print a single actionable line if either is missing — non-blocking, no exit.
920
+ _has_native = False
921
+ try:
922
+ from nedb import __has_native__ as _has_native # type: ignore[attr-defined]
923
+ except Exception:
924
+ pass
925
+ _dag_bin_available = shutil.which("nedbd-v2") or shutil.which("nedbd_v2") or \
926
+ any(os.path.isfile(os.path.join(os.path.dirname(os.path.abspath(__file__)), n))
927
+ for n in ["nedbd-v2", "nedbd-v2.exe", "nedbd_v2", "nedbd_v2.exe"])
928
+ if not _has_native or not _dag_bin_available:
929
+ print()
930
+ if not _dag_bin_available:
931
+ print(" ⚡ v2 DAG engine not found — for content-addressed, instant-cold-start mode:")
932
+ print(" cargo install nedb-engine → nedbd --dag ./data")
933
+ if not _has_native:
934
+ print(" ⚡ nedb._native not available — embedded v2 API requires the platform wheel")
935
+ print(" pip install --force-reinstall --no-cache-dir nedb-engine")
936
+ print(" OR use HTTP mode: NEDB_URL=http://localhost:7070 python3 your_script.py")
937
+ if not _has_native or not _dag_bin_available:
938
+ print(" ℹ Run 'nedbd --doctor' for a full diagnosis and exact fix commands.")
939
+ print()
864
940
  # Eagerly open all known databases so backfill-encrypt (if needed) runs
865
941
  # immediately and is visible in the boot log, not hidden on first request.
866
942
  names = manager.names()
@@ -0,0 +1,179 @@
1
+ # nedb-engine
2
+
3
+ **NEDB v2 — content-addressed DAG storage engine with NQL and HTTP server**
4
+
5
+ [![crates.io](https://img.shields.io/crates/v/nedb-engine?color=f97316)](https://crates.io/crates/nedb-engine)
6
+ [![License: BUSL-1.1](https://img.shields.io/badge/license-BUSL--1.1-blue)](https://github.com/aiassistsecure/nedb/blob/master/LICENSE)
7
+ [![GitHub](https://img.shields.io/badge/github-aiassistsecure%2Fnedb-8da0cb)](https://github.com/aiassistsecure/nedb)
8
+
9
+ This crate ships the **`nedbd`** binary — the NEDB v2 DAG HTTP server. Install it, point it at a data directory, and any language can speak to it over HTTP/JSON.
10
+
11
+ ```bash
12
+ cargo install nedb-engine
13
+ nedbd ./data # AOF engine (pure Rust, v1-compatible)
14
+ nedbd --dag ./data # DAG engine (v2, content-addressed, recommended)
15
+ ```
16
+
17
+ ---
18
+
19
+ ## What is NEDB v2?
20
+
21
+ NEDB v2 replaces the append-only log (AOF) with a **content-addressed Merkle DAG**:
22
+
23
+ - Every document version is an **immutable, BLAKE2b-hashed object**. Nothing is ever overwritten.
24
+ - Every write produces a new **chain head** — a BLAKE2b commitment over the entire database history.
25
+ - **Time-travel**: read any document `AS OF seq N` to see exactly what it contained at that point.
26
+ - **Causal provenance**: documents link to their causal parents via `caused_by` hashes. `TRACE caused_by` walks the full causal graph backward.
27
+ - **TRAVERSE**: named graph relations via `__links__`. `FROM person WHERE _id = "robert" TRAVERSE parent_of` returns linked nodes.
28
+ - **O(1) warm start**: a `MANIFEST` file stores `seq` + Merkle head so restarts never re-scan the entire object store.
29
+ - **Instant cold start**: the daemon accepts connections immediately; background scan loads objects incrementally.
30
+ - **AES-256-GCM at rest**: optional symmetric encryption with a double-envelope key structure (TMK wraps DEK).
31
+
32
+ ---
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ cargo install nedb-engine
38
+ ```
39
+
40
+ The `nedbd` binary lands in `~/.cargo/bin/`. Make sure that's on your `PATH`.
41
+
42
+ To verify:
43
+
44
+ ```bash
45
+ nedbd --doctor # diagnose your NEDB environment
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Usage
51
+
52
+ ```
53
+ nedbd [OPTIONS] [data_dir]
54
+ ```
55
+
56
+ | Argument | Default | Description |
57
+ |---|---|---|
58
+ | `data_dir` | `./nedb-data` | Directory for database files |
59
+ | `--dag` | off | Use the v2 content-addressed DAG engine |
60
+ | `--doctor` | — | Diagnose environment, print fix commands |
61
+
62
+ ### Environment variables
63
+
64
+ | Variable | Default | Description |
65
+ |---|---|---|
66
+ | `NEDBD_HOST` | `127.0.0.1` | Bind address |
67
+ | `NEDBD_PORT` | `7070` | HTTP port |
68
+ | `NEDBD_TOKEN` | — | Bearer token for auth (optional) |
69
+ | `NEDB_TMK` | — | 64-char hex master key for AES-256-GCM encryption at rest |
70
+ | `NEDBD_MEMORY` | — | `1` = pure in-memory mode (no disk I/O) |
71
+ | `NEDBD_DAG` | — | `1` = same as `--dag` flag |
72
+
73
+ ---
74
+
75
+ ## HTTP API
76
+
77
+ All endpoints return JSON. Auth: `Authorization: Bearer <token>` if `NEDBD_TOKEN` is set.
78
+
79
+ ```
80
+ GET /health
81
+ GET /v1/databases
82
+ POST /v1/databases {name, init?}
83
+ GET /v1/databases/<name>
84
+ DELETE /v1/databases/<name>
85
+ POST /v1/databases/<name>/put {coll, id, doc, caused_by?}
86
+ POST /v1/databases/<name>/query {nql}
87
+ POST /v1/databases/<name>/link {frm, rel, to}
88
+ POST /v1/databases/<name>/neighbors {node, rel}
89
+ GET /v1/databases/<name>/verify
90
+ POST /v1/databases/<name>/checkpoint
91
+ ```
92
+
93
+ ---
94
+
95
+ ## NQL — NEDB Query Language
96
+
97
+ ```sql
98
+ -- Basic queries
99
+ FROM person LIMIT 10
100
+ FROM driver WHERE status = "active"
101
+ FROM driver WHERE rating >= 4.5 ORDER BY rating DESC
102
+
103
+ -- Time-travel
104
+ FROM will WHERE _id = "evans_will_2019" AS OF 5
105
+
106
+ -- Causal trace (walks caused_by links backward)
107
+ FROM event WHERE _id = "probate_filing" TRACE caused_by
108
+
109
+ -- Graph traversal
110
+ FROM person WHERE _id = "robert" TRAVERSE parent_of
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Example: The Will — causal DAG in action
116
+
117
+ ```python
118
+ import json, os, urllib.request
119
+
120
+ def put(db_url, coll, id_, doc):
121
+ body = json.dumps({"coll": coll, "id": id_, "doc": doc}).encode()
122
+ req = urllib.request.Request(f"{db_url}/put", body,
123
+ {"Content-Type": "application/json"}, method="POST")
124
+ return json.loads(urllib.request.urlopen(req).read())["doc"]
125
+
126
+ BASE = "http://localhost:7070/v1/databases/will"
127
+
128
+ # Robert writes his will
129
+ robert = put(BASE, "person", "robert", {"name": "Robert Evans", "role": "testator"})
130
+ will_v1 = put(BASE, "will", "evans_will", {
131
+ "house": "mark", "business": "lisa",
132
+ "caused_by": [robert["_hash"]], # causal link to testator record
133
+ })
134
+
135
+ # Amendment — chains off v1
136
+ will_v2 = put(BASE, "will", "evans_will", {
137
+ "house": "mark", "business": "lisa", "vintage_car": "mark",
138
+ "caused_by": [will_v1["_hash"]],
139
+ })
140
+
141
+ # TRACE proves the amendment links back to the original
142
+ query = json.dumps({"nql": 'FROM will WHERE _id = "evans_will" TRACE caused_by'}).encode()
143
+ req = urllib.request.Request(f"{BASE}/query", query,
144
+ {"Content-Type": "application/json"}, method="POST")
145
+ trace = json.loads(urllib.request.urlopen(req).read())["rows"]
146
+ # → both versions + Robert's testator record, in causal order
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Python companion
152
+
153
+ The `nedb-engine` PyPI package ships the same server binary bundled in the wheel, plus:
154
+ - Pure-Python AOF engine (`NEDB` class)
155
+ - Embedded v2 DAG API (`nedb._native.NedbCore`) on supported platforms
156
+ - `nedbd` console script
157
+
158
+ ```bash
159
+ pip install nedb-engine
160
+
161
+ # Use embedded API (Linux/macOS/Windows CPython)
162
+ python3 -c "from nedb._native import NedbCore; db = NedbCore(); ..."
163
+
164
+ # Use HTTP mode (any platform, including MSYS2/MinGW)
165
+ NEDB_URL=http://localhost:7070 python3 your_script.py
166
+
167
+ # Diagnose
168
+ nedbd --doctor
169
+ ```
170
+
171
+ ---
172
+
173
+ ## License
174
+
175
+ [BUSL-1.1](https://github.com/aiassistsecure/nedb/blob/master/LICENSE) — Business Source License. Free for development and evaluation; production use requires a commercial licence from INTERCHAINED, LLC.
176
+
177
+ ---
178
+
179
+ *Built by [INTERCHAINED, LLC](https://interchained.org) × Claude Sonnet 4.6*
Binary file
Binary file
File without changes
File without changes