nedb-engine 3.0.0__tar.gz → 3.2.1__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 (49) hide show
  1. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/PKG-INFO +105 -58
  2. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/README.md +104 -57
  3. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/client/node/README.md +14 -5
  4. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/client/python/README.md +14 -5
  5. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/pyproject.toml +1 -1
  6. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/__init__.py +9 -1
  7. nedb_engine-3.2.1/python/nedb/_native.abi3.so +0 -0
  8. nedb_engine-3.2.1/python/nedb/backends/dag.py +151 -0
  9. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/backends/redis_backend.py +1 -2
  10. nedb_engine-3.2.1/python/nedb/nedbd-v2 +0 -0
  11. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/nedbd-v2-darwin-arm64 +0 -0
  12. nedb_engine-3.2.1/python/nedb/nedbd-v2-darwin-x64 +0 -0
  13. nedb_engine-3.2.1/python/nedb/nedbd-v2.exe +0 -0
  14. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/server.py +11 -2
  15. nedb_engine-3.2.1/python/nedb/wrap_core.py +262 -0
  16. nedb_engine-3.2.1/python/nedb/wrap_mongo.py +137 -0
  17. nedb_engine-3.2.1/python/nedb/wrap_mysql.py +174 -0
  18. nedb_engine-3.2.1/python/nedb/wrap_postgresql.py +178 -0
  19. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/wrap_redis.py +98 -12
  20. nedb_engine-3.2.1/python/nedb/wrap_sqlite.py +235 -0
  21. nedb_engine-3.2.1/rust/crates/nedb-wrap/README.md +54 -0
  22. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/rust/nedb-v2/README.md +15 -2
  23. nedb_engine-3.0.0/python/nedb/_native.abi3.so +0 -0
  24. nedb_engine-3.0.0/python/nedb/nedbd-v2 +0 -0
  25. nedb_engine-3.0.0/python/nedb/nedbd-v2-darwin-x64 +0 -0
  26. nedb_engine-3.0.0/python/nedb/nedbd-v2.exe +0 -0
  27. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/.gitignore +0 -0
  28. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/LICENSE +0 -0
  29. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/examples/agent-loop/README.md +0 -0
  30. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/examples/mini-chain/README.md +0 -0
  31. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/autoindex.py +0 -0
  32. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/backends/__init__.py +0 -0
  33. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/cascade.py +0 -0
  34. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/client.py +0 -0
  35. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/concurrent.py +0 -0
  36. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/crypto.py +0 -0
  37. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/engine.py +0 -0
  38. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/index.py +0 -0
  39. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/log.py +0 -0
  40. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/merkle.py +0 -0
  41. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/mongo.py +0 -0
  42. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/proof.py +0 -0
  43. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/query.py +0 -0
  44. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/redis_compat.py +0 -0
  45. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/relations.py +0 -0
  46. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/resp2.py +0 -0
  47. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/snapshot.py +0 -0
  48. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/sql.py +0 -0
  49. {nedb_engine-3.0.0 → nedb_engine-3.2.1}/python/nedb/store.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: nedb-engine
3
- Version: 3.0.0
3
+ Version: 3.2.1
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
@@ -47,86 +47,114 @@ One Rust core → ships to **PyPI** and **npm** from a single source.
47
47
 
48
48
  ---
49
49
 
50
- ## ⚠️ New in 2.8.6 — Durability & Recovery (read this if you store anything you care about)
50
+ ## New in 3.2.0 — wrap the databases you already run
51
51
 
52
- Three defects found by killing a real engine at every persistence boundary and by filling a real
53
- filesystem to zero free blocks. All three are fixed. **If you are on 2.8.5 or earlier, upgrade.**
52
+ NEDB adds **tamper-evident causal provenance to a database you already have**, in one line, without
53
+ rip-and-replace. Five adapters, one surface:
54
54
 
55
- ### 1. A failed flush silently discarded acknowledged writes
55
+ ```python
56
+ from nedb import wrap_redis, wrap_sqlite, wrap_mysql, wrap_mongo, wrap_postgresql
56
57
 
57
- `IdIndex::flush_write_buf` cleared every buffered entry regardless of whether its disk write
58
- succeeded. So a flush that hit `ENOSPC` threw the entry away, and no later flush retried it.
58
+ r = wrap_redis(redis.Redis()) # or wrap_sqlite(sqlite3.connect("app.db")), ...
59
+ r.nedb.register("driver:*", "driver") # teach NEDB the host's shape
60
+ r.nedb.backfill() # import what is already there
61
+ r.nedb.shadow_writes = True # every future write is chained
59
62
 
60
- Reproduced on a full 22 MiB filesystem: **30 rows acknowledged by `put() -> Ok`, then `list()`
61
- returned 0 after reopen — while `verify()` reported all 30 objects healthy.** The content-addressed
62
- objects were durable; the id-index entries that make them findable were gone.
63
+ r.set("driver:d1", json.dumps({"name": "Bob", "status": "active"}))
63
64
 
64
- ```
65
- before: try_flush_all() -> (no return value) reopen -> 0 rows, verify() = 30 ok
66
- after: try_flush_all() -> Err("id-index leaf rows/buf_25: No space left on device (os error 28)")
67
- ...free space, retry -> Ok reopen -> 30 rows
65
+ r.nedb.query('FROM driver WHERE status = "active"') # NQL over your Redis data
66
+ r.nedb.query('FROM driver AS OF 41') # what it looked like at seq 41
67
+ r.nedb.verify() # True — BLAKE2b chain intact
68
68
  ```
69
69
 
70
- **Fixed:** an entry leaves the WAL only when its write actually landed. Failures stay buffered and
71
- retry on the next flush.
70
+ | wrapper | host | shadowing |
71
+ |---|---|---|
72
+ | `wrap_redis` | `redis.Redis` / compatible | automatic — every write command intercepted |
73
+ | `wrap_sqlite` | `sqlite3.Connection` | automatic — `execute()` intercepted post-write |
74
+ | `wrap_mysql` | DB-API 2.0 (mysql-connector, PyMySQL) | explicit `shadow_row()` |
75
+ | `wrap_mongo` | `pymongo.MongoClient` | explicit `shadow_row()` |
76
+ | `wrap_postgresql` | DB-API 2.0 (psycopg2, psycopg 3) | explicit `shadow_row()` |
77
+
78
+ **NEDB never writes into the host database's namespace.** Shadow data lives only in the NEDB engine.
79
+
80
+ Three backends behind the same surface, selected by `backend="auto"`: **nedbd over HTTP** (`nedbd_url=`),
81
+ **embedded v2/v3 DAG** (the Rust core, in-process, no server — `dag_path=` for a durable store,
82
+ `dag_tmk=` for AES-256-GCM at rest), or the **v1 in-process AOF** engine as a universal fallback. On the
83
+ DAG backend you also get `tip()`, `tip_collection()`, `since()` (changefeed) and `scan_status()`.
84
+
85
+ ### 🟢 MIT licensed since 3.0.0
86
+
87
+ No production restriction, no copyleft, no Change Date. Use it in production, embed it commercially,
88
+ ship it closed-source, fork it, sell it. License review is a wall, not a speed bump — that wall is gone.
89
+
90
+ ### Also in 3.2.0
91
+
92
+ - **A durability defect that pinned every embedded database.** The background flush ticker held a
93
+ strong `Arc<Db>` in an unconditional loop, so the handle was never dropped: the exclusive data-dir
94
+ `LOCK` was never released (reopening the same path *in the same process* failed with "locked by
95
+ another process" naming your own pid), every `open()` leaked a thread and the whole `Db`, and
96
+ flush-on-close could never fire. The ticker now holds a `Weak<Db>` and exits when its owner does.
97
+ **Live in 2.8.5 through 3.1.0 — upgrade if you embed the engine.**
98
+ - **`wrap_redis` crashed on any install without the native wheel** — the pure-Python fallback path
99
+ raised `AttributeError` from inside `wrap_redis()`. Fixed.
100
+ - **Prebuilt binaries for `linux-arm64` and musl/Alpine**, on npm and PyPI. Graviton, Ampere, Linux
101
+ containers on Apple Silicon, and Alpine images previously installed cleanly and then failed at
102
+ import.
103
+ - **CI actually runs the test suites.** Until now the only workflows fired on a version tag, so the
104
+ first automated opinion about a change arrived *after* it was published to three registries. All
105
+ 26 suites now run on every push and pull request. It found four real defects in its first hour,
106
+ including two of the ones listed above.
72
107
 
73
- ### 2. Flush errors were unobservable — new `try_flush_all()`
108
+ ---
74
109
 
75
- `flush_all()` returns `()` and logged fsync failures to stderr, so a caller could not tell a durable
76
- flush from a failed one. Anything that takes a destructive or externally-visible action on the
77
- strength of a persisted record needs to know.
110
+ ## Earlier — 2.8.6 durability & recovery
78
111
 
79
- ```rust
80
- // Use this when the outcome matters:
81
- db.try_flush_all()?; // Result<()> — id-index WAL + segment sync + MANIFEST
112
+ Three defects found by killing a real engine at every persistence boundary and by filling a real
113
+ filesystem to zero free blocks. **If you are on 2.8.5 or earlier, upgrade.**
82
114
 
83
- // Still available, still logs, nowhere to propagate (ticker / Drop):
84
- db.flush_all();
85
- ```
115
+ **1. A failed flush silently discarded acknowledged writes.** `IdIndex::flush_write_buf` cleared every
116
+ buffered entry regardless of whether its disk write succeeded, so a flush that hit `ENOSPC` threw the
117
+ entry away and no later flush retried it. Reproduced on a full 22 MiB filesystem: 30 rows acknowledged
118
+ by `put() -> Ok`, then `list()` returned 0 after reopen — while `verify()` reported all 30 objects
119
+ healthy. The content-addressed objects were durable; the id-index entries that make them findable were
120
+ gone. An entry now leaves the WAL only when its write actually landed.
121
+
122
+ **2. Flush errors were unobservable.** `flush_all()` returns `()`, so a caller could not tell a durable
123
+ flush from a failed one.
86
124
 
87
- Also new: `Db::try_flush_manifest()` and `IdIndex::try_flush_write_buf()`.
125
+ ```rust
126
+ db.try_flush_all()?; // Result<()> — use this when the outcome matters
127
+ db.flush_all(); // still logs; for ticker / Drop, nowhere to propagate
128
+ ```
88
129
 
89
- ### 3. `repair` could not repair, and `since()` claimed "caught up" while behind
130
+ Also added: `Db::try_flush_manifest()` and `IdIndex::try_flush_write_buf()`.
90
131
 
91
- The cold scan rebuilt `seq_index`, per-collection tips, the Merkle head and `MANIFEST` — but **never
92
- the id index**. A database whose WAL never reached disk came back with every object verifying and
93
- `list()` empty, and `nedb-cli repair` printed success without fixing it, because
94
- `start_cold_scan()` is a deliberate no-op on a warm store.
132
+ **3. `repair` could not repair, and `since()` claimed "caught up" while behind.** The cold scan rebuilt
133
+ `seq_index`, per-collection tips, the Merkle head and `MANIFEST` — but never the id index, and
134
+ `start_cold_scan()` is a deliberate no-op on a warm store, so `nedb-cli repair` printed success on
135
+ exactly the database it exists to fix.
95
136
 
96
137
  ```bash
97
138
  nedb-cli repair ./data
98
139
  # repaired: 203 id-index entr(ies) rebuilt, 203 node(s) verified, flushed
99
140
  ```
100
141
 
101
- ```rust
102
- let restored = db.repair()?; // rebuild id index from objects; highest seq wins
103
- ```
104
-
105
- Every object carries its own `coll`, `id` and `seq`, so the id index is fully derivable — a lost WAL
106
- is recoverable and nothing is invented. `repair()` also recomputes head and tips, so a repaired
107
- database reopens **warm** instead of coming back up cold with an empty head.
108
-
109
- Separately, `since()` set `has_more = hit_limit` alone. On a warm boot the seq index is empty **by
110
- design** (that is why warm start is O(1)), so every lookup missed and `since()` returned zero nodes
111
- with `has_more = false` — indistinguishable from genuinely up to date. A consumer following the
112
- documented drain loop stopped one call in, with every record unread.
113
-
114
- **Fixed:** `has_more` is true whenever the cursor is behind the log head. `ScanStatus` gains
115
- **`seq_index_ready`** — replication consumers should gate on that, not on `scan_complete`, which is
116
- true on a warm boot precisely because the scan was skipped.
142
+ Every object carries its own `coll`, `id` and `seq`, so the index is fully derivable — nothing is
143
+ invented. Separately, `since()` set `has_more = hit_limit` alone; on a warm boot the seq index is empty
144
+ *by design*, so `since()` returned zero nodes with `has_more = false` — indistinguishable from
145
+ genuinely up to date, and a consumer following the documented drain loop stopped one call in with every
146
+ record unread. `has_more` is now true whenever the cursor is behind head, and `ScanStatus` gains
147
+ **`seq_index_ready`** — gate replication on that, not on `scan_complete`.
117
148
 
118
- ### Known sharp edge (documented, not changed)
119
-
120
- `since()`'s cursor is **exclusive** and seqs start at 0, so `since(0, _)` returns `(0, head]` and the
121
- very first write in a database (seq 0) is unreachable through any cursor value. Ten writes drain as
122
- nine records. Changing the convention would break existing consumers; a replica seeded from
123
- `since()` alone starts one record short.
149
+ **Known sharp edge (documented, not changed):** `since()`'s cursor is **exclusive** and seqs start at 0,
150
+ so `since(0, _)` returns `(0, head]` and the very first write (seq 0) is unreachable through any cursor
151
+ value. Ten writes drain as nine records. Changing the convention would break existing consumers.
124
152
 
125
153
  ---
126
154
 
127
- ## NEDB v2.8.0 — Production Stable
155
+ ## NEDB v3.2.0 — Production Stable
128
156
 
129
- **Current stable: 2.8.0** — NEDB ships as **three version-aligned distributions** on one tag — `nedb-engine` (flagship), `crypto-database` (verifiable v2/v3 DAG), and `aof-db` (fast append-only) — across npm / PyPI / crates.io with full mac + linux + windows native addons (see [**Releasing**](#releasing) below). All native wheels (Linux + Windows on GitHub Actions; macOS arm64 + x86_64 on Codemagic M2 Mac Minis) **plus** the universal pure-Python wheel ship from a single `v*` tag, with the `nedbd-v2` binary bundled inside `pip install nedb-engine`.
157
+ **Current stable: 3.2.0** — NEDB ships as **three version-aligned distributions** on one tag — `nedb-engine` (flagship), `crypto-database` (verifiable v2/v3 DAG), and `aof-db` (fast append-only) — across npm / PyPI / crates.io with native addons for **macOS (arm64 + x86_64), Linux (x86_64 + aarch64, glibc + musl) and Windows x86_64** (see [**Releasing**](#releasing) below). All native wheels (Linux + Windows on GitHub Actions; macOS on Codemagic M2 Mac Minis) **plus** the universal pure-Python wheel ship from a single `v*` tag, with the `nedbd-v2` binary bundled inside `pip install nedb-engine`.
130
158
 
131
159
  ### New in 2.8.0 — Cast: the database understands English
132
160
 
@@ -171,7 +199,7 @@ nedbd --dag --data ./data
171
199
  NEDBD_DAG=1 NEDB_TMK=<32-byte-hex> nedbd --data ./data
172
200
 
173
201
  curl http://127.0.0.1:7070/health
174
- # {"ok":true,"version":"2.2.31","service":"nedbd","engine":"dag","startup_ready":true,"encrypted":true}
202
+ # {"ok":true,"version":"3.2.0","service":"nedbd","engine":"dag","startup_ready":true,"encrypted":true}
175
203
 
176
204
  # Tail the live event stream (new in v2.2.31)
177
205
  curl http://127.0.0.1:7070/events
@@ -225,6 +253,24 @@ pip install nedb-engine # Python ≥ 3.8 — pure-Python + optional Rust na
225
253
  npm install nedb-engine # Node ≥ 16 — napi-rs prebuilt binaries
226
254
  ```
227
255
 
256
+ ### Prebuilt platforms
257
+
258
+ Both registries ship prebuilt binaries for:
259
+
260
+ | Platform | libc | Python wheel | Node addon |
261
+ |---|---|---|---|
262
+ | Linux x86_64 | glibc | ✅ manylinux | ✅ |
263
+ | Linux x86_64 | musl (Alpine) | ✅ musllinux | ✅ |
264
+ | Linux aarch64 (Graviton, Ampere, Apple-Silicon containers) | glibc | ✅ manylinux | ✅ |
265
+ | Linux aarch64 | musl (Alpine) | ✅ musllinux | ✅ |
266
+ | macOS arm64 + x86_64 | — | ✅ | ✅ |
267
+ | Windows x86_64 | MSVC | ✅ | ✅ |
268
+
269
+ On Python, any platform without a prebuilt wheel still installs: pip falls back
270
+ to the universal `py3-none-any` wheel and you get the pure-Python v1 AOF engine
271
+ (correct, slower, no embedded DAG). On Node there is no such fallback — an
272
+ unlisted platform has no addon.
273
+
228
274
  ---
229
275
 
230
276
  ## Python — 5-minute tour
@@ -934,7 +980,8 @@ Requires `GITHUB_TOKEN` (`repo` + `workflow` scope) in the environment. It never
934
980
  ## Authors
935
981
 
936
982
  Built by **[Mark Allen Evans Jr.](https://interchained.org)** (INTERCHAINED, LLC)
937
- with **Claude Sonnet 4.6** on [Hyperagent](https://hyperagent.com/refer/J2G6TCD7).
983
+ with the **Interchained AI fleet** on [Hyperagent](https://hyperagent.com/refer/J2G6TCD7) —
984
+ Vex (GLM · Claude Sonnet · Opus · Fable · GPT-6 Astra/Sol), across hundreds of sessions.
938
985
 
939
986
  > *"Take one idea, turn it into an LP, then an app, then a system, then a platform, then infrastructure that is irreplaceable."*
940
987
 
@@ -26,86 +26,114 @@ One Rust core → ships to **PyPI** and **npm** from a single source.
26
26
 
27
27
  ---
28
28
 
29
- ## ⚠️ New in 2.8.6 — Durability & Recovery (read this if you store anything you care about)
29
+ ## New in 3.2.0 — wrap the databases you already run
30
30
 
31
- Three defects found by killing a real engine at every persistence boundary and by filling a real
32
- filesystem to zero free blocks. All three are fixed. **If you are on 2.8.5 or earlier, upgrade.**
31
+ NEDB adds **tamper-evident causal provenance to a database you already have**, in one line, without
32
+ rip-and-replace. Five adapters, one surface:
33
33
 
34
- ### 1. A failed flush silently discarded acknowledged writes
34
+ ```python
35
+ from nedb import wrap_redis, wrap_sqlite, wrap_mysql, wrap_mongo, wrap_postgresql
35
36
 
36
- `IdIndex::flush_write_buf` cleared every buffered entry regardless of whether its disk write
37
- succeeded. So a flush that hit `ENOSPC` threw the entry away, and no later flush retried it.
37
+ r = wrap_redis(redis.Redis()) # or wrap_sqlite(sqlite3.connect("app.db")), ...
38
+ r.nedb.register("driver:*", "driver") # teach NEDB the host's shape
39
+ r.nedb.backfill() # import what is already there
40
+ r.nedb.shadow_writes = True # every future write is chained
38
41
 
39
- Reproduced on a full 22 MiB filesystem: **30 rows acknowledged by `put() -> Ok`, then `list()`
40
- returned 0 after reopen — while `verify()` reported all 30 objects healthy.** The content-addressed
41
- objects were durable; the id-index entries that make them findable were gone.
42
+ r.set("driver:d1", json.dumps({"name": "Bob", "status": "active"}))
42
43
 
43
- ```
44
- before: try_flush_all() -> (no return value) reopen -> 0 rows, verify() = 30 ok
45
- after: try_flush_all() -> Err("id-index leaf rows/buf_25: No space left on device (os error 28)")
46
- ...free space, retry -> Ok reopen -> 30 rows
44
+ r.nedb.query('FROM driver WHERE status = "active"') # NQL over your Redis data
45
+ r.nedb.query('FROM driver AS OF 41') # what it looked like at seq 41
46
+ r.nedb.verify() # True — BLAKE2b chain intact
47
47
  ```
48
48
 
49
- **Fixed:** an entry leaves the WAL only when its write actually landed. Failures stay buffered and
50
- retry on the next flush.
49
+ | wrapper | host | shadowing |
50
+ |---|---|---|
51
+ | `wrap_redis` | `redis.Redis` / compatible | automatic — every write command intercepted |
52
+ | `wrap_sqlite` | `sqlite3.Connection` | automatic — `execute()` intercepted post-write |
53
+ | `wrap_mysql` | DB-API 2.0 (mysql-connector, PyMySQL) | explicit `shadow_row()` |
54
+ | `wrap_mongo` | `pymongo.MongoClient` | explicit `shadow_row()` |
55
+ | `wrap_postgresql` | DB-API 2.0 (psycopg2, psycopg 3) | explicit `shadow_row()` |
56
+
57
+ **NEDB never writes into the host database's namespace.** Shadow data lives only in the NEDB engine.
58
+
59
+ Three backends behind the same surface, selected by `backend="auto"`: **nedbd over HTTP** (`nedbd_url=`),
60
+ **embedded v2/v3 DAG** (the Rust core, in-process, no server — `dag_path=` for a durable store,
61
+ `dag_tmk=` for AES-256-GCM at rest), or the **v1 in-process AOF** engine as a universal fallback. On the
62
+ DAG backend you also get `tip()`, `tip_collection()`, `since()` (changefeed) and `scan_status()`.
63
+
64
+ ### 🟢 MIT licensed since 3.0.0
65
+
66
+ No production restriction, no copyleft, no Change Date. Use it in production, embed it commercially,
67
+ ship it closed-source, fork it, sell it. License review is a wall, not a speed bump — that wall is gone.
68
+
69
+ ### Also in 3.2.0
70
+
71
+ - **A durability defect that pinned every embedded database.** The background flush ticker held a
72
+ strong `Arc<Db>` in an unconditional loop, so the handle was never dropped: the exclusive data-dir
73
+ `LOCK` was never released (reopening the same path *in the same process* failed with "locked by
74
+ another process" naming your own pid), every `open()` leaked a thread and the whole `Db`, and
75
+ flush-on-close could never fire. The ticker now holds a `Weak<Db>` and exits when its owner does.
76
+ **Live in 2.8.5 through 3.1.0 — upgrade if you embed the engine.**
77
+ - **`wrap_redis` crashed on any install without the native wheel** — the pure-Python fallback path
78
+ raised `AttributeError` from inside `wrap_redis()`. Fixed.
79
+ - **Prebuilt binaries for `linux-arm64` and musl/Alpine**, on npm and PyPI. Graviton, Ampere, Linux
80
+ containers on Apple Silicon, and Alpine images previously installed cleanly and then failed at
81
+ import.
82
+ - **CI actually runs the test suites.** Until now the only workflows fired on a version tag, so the
83
+ first automated opinion about a change arrived *after* it was published to three registries. All
84
+ 26 suites now run on every push and pull request. It found four real defects in its first hour,
85
+ including two of the ones listed above.
51
86
 
52
- ### 2. Flush errors were unobservable — new `try_flush_all()`
87
+ ---
53
88
 
54
- `flush_all()` returns `()` and logged fsync failures to stderr, so a caller could not tell a durable
55
- flush from a failed one. Anything that takes a destructive or externally-visible action on the
56
- strength of a persisted record needs to know.
89
+ ## Earlier — 2.8.6 durability & recovery
57
90
 
58
- ```rust
59
- // Use this when the outcome matters:
60
- db.try_flush_all()?; // Result<()> — id-index WAL + segment sync + MANIFEST
91
+ Three defects found by killing a real engine at every persistence boundary and by filling a real
92
+ filesystem to zero free blocks. **If you are on 2.8.5 or earlier, upgrade.**
61
93
 
62
- // Still available, still logs, nowhere to propagate (ticker / Drop):
63
- db.flush_all();
64
- ```
94
+ **1. A failed flush silently discarded acknowledged writes.** `IdIndex::flush_write_buf` cleared every
95
+ buffered entry regardless of whether its disk write succeeded, so a flush that hit `ENOSPC` threw the
96
+ entry away and no later flush retried it. Reproduced on a full 22 MiB filesystem: 30 rows acknowledged
97
+ by `put() -> Ok`, then `list()` returned 0 after reopen — while `verify()` reported all 30 objects
98
+ healthy. The content-addressed objects were durable; the id-index entries that make them findable were
99
+ gone. An entry now leaves the WAL only when its write actually landed.
100
+
101
+ **2. Flush errors were unobservable.** `flush_all()` returns `()`, so a caller could not tell a durable
102
+ flush from a failed one.
65
103
 
66
- Also new: `Db::try_flush_manifest()` and `IdIndex::try_flush_write_buf()`.
104
+ ```rust
105
+ db.try_flush_all()?; // Result<()> — use this when the outcome matters
106
+ db.flush_all(); // still logs; for ticker / Drop, nowhere to propagate
107
+ ```
67
108
 
68
- ### 3. `repair` could not repair, and `since()` claimed "caught up" while behind
109
+ Also added: `Db::try_flush_manifest()` and `IdIndex::try_flush_write_buf()`.
69
110
 
70
- The cold scan rebuilt `seq_index`, per-collection tips, the Merkle head and `MANIFEST` — but **never
71
- the id index**. A database whose WAL never reached disk came back with every object verifying and
72
- `list()` empty, and `nedb-cli repair` printed success without fixing it, because
73
- `start_cold_scan()` is a deliberate no-op on a warm store.
111
+ **3. `repair` could not repair, and `since()` claimed "caught up" while behind.** The cold scan rebuilt
112
+ `seq_index`, per-collection tips, the Merkle head and `MANIFEST` — but never the id index, and
113
+ `start_cold_scan()` is a deliberate no-op on a warm store, so `nedb-cli repair` printed success on
114
+ exactly the database it exists to fix.
74
115
 
75
116
  ```bash
76
117
  nedb-cli repair ./data
77
118
  # repaired: 203 id-index entr(ies) rebuilt, 203 node(s) verified, flushed
78
119
  ```
79
120
 
80
- ```rust
81
- let restored = db.repair()?; // rebuild id index from objects; highest seq wins
82
- ```
83
-
84
- Every object carries its own `coll`, `id` and `seq`, so the id index is fully derivable — a lost WAL
85
- is recoverable and nothing is invented. `repair()` also recomputes head and tips, so a repaired
86
- database reopens **warm** instead of coming back up cold with an empty head.
87
-
88
- Separately, `since()` set `has_more = hit_limit` alone. On a warm boot the seq index is empty **by
89
- design** (that is why warm start is O(1)), so every lookup missed and `since()` returned zero nodes
90
- with `has_more = false` — indistinguishable from genuinely up to date. A consumer following the
91
- documented drain loop stopped one call in, with every record unread.
92
-
93
- **Fixed:** `has_more` is true whenever the cursor is behind the log head. `ScanStatus` gains
94
- **`seq_index_ready`** — replication consumers should gate on that, not on `scan_complete`, which is
95
- true on a warm boot precisely because the scan was skipped.
121
+ Every object carries its own `coll`, `id` and `seq`, so the index is fully derivable — nothing is
122
+ invented. Separately, `since()` set `has_more = hit_limit` alone; on a warm boot the seq index is empty
123
+ *by design*, so `since()` returned zero nodes with `has_more = false` — indistinguishable from
124
+ genuinely up to date, and a consumer following the documented drain loop stopped one call in with every
125
+ record unread. `has_more` is now true whenever the cursor is behind head, and `ScanStatus` gains
126
+ **`seq_index_ready`** — gate replication on that, not on `scan_complete`.
96
127
 
97
- ### Known sharp edge (documented, not changed)
98
-
99
- `since()`'s cursor is **exclusive** and seqs start at 0, so `since(0, _)` returns `(0, head]` and the
100
- very first write in a database (seq 0) is unreachable through any cursor value. Ten writes drain as
101
- nine records. Changing the convention would break existing consumers; a replica seeded from
102
- `since()` alone starts one record short.
128
+ **Known sharp edge (documented, not changed):** `since()`'s cursor is **exclusive** and seqs start at 0,
129
+ so `since(0, _)` returns `(0, head]` and the very first write (seq 0) is unreachable through any cursor
130
+ value. Ten writes drain as nine records. Changing the convention would break existing consumers.
103
131
 
104
132
  ---
105
133
 
106
- ## NEDB v2.8.0 — Production Stable
134
+ ## NEDB v3.2.0 — Production Stable
107
135
 
108
- **Current stable: 2.8.0** — NEDB ships as **three version-aligned distributions** on one tag — `nedb-engine` (flagship), `crypto-database` (verifiable v2/v3 DAG), and `aof-db` (fast append-only) — across npm / PyPI / crates.io with full mac + linux + windows native addons (see [**Releasing**](#releasing) below). All native wheels (Linux + Windows on GitHub Actions; macOS arm64 + x86_64 on Codemagic M2 Mac Minis) **plus** the universal pure-Python wheel ship from a single `v*` tag, with the `nedbd-v2` binary bundled inside `pip install nedb-engine`.
136
+ **Current stable: 3.2.0** — NEDB ships as **three version-aligned distributions** on one tag — `nedb-engine` (flagship), `crypto-database` (verifiable v2/v3 DAG), and `aof-db` (fast append-only) — across npm / PyPI / crates.io with native addons for **macOS (arm64 + x86_64), Linux (x86_64 + aarch64, glibc + musl) and Windows x86_64** (see [**Releasing**](#releasing) below). All native wheels (Linux + Windows on GitHub Actions; macOS on Codemagic M2 Mac Minis) **plus** the universal pure-Python wheel ship from a single `v*` tag, with the `nedbd-v2` binary bundled inside `pip install nedb-engine`.
109
137
 
110
138
  ### New in 2.8.0 — Cast: the database understands English
111
139
 
@@ -150,7 +178,7 @@ nedbd --dag --data ./data
150
178
  NEDBD_DAG=1 NEDB_TMK=<32-byte-hex> nedbd --data ./data
151
179
 
152
180
  curl http://127.0.0.1:7070/health
153
- # {"ok":true,"version":"2.2.31","service":"nedbd","engine":"dag","startup_ready":true,"encrypted":true}
181
+ # {"ok":true,"version":"3.2.0","service":"nedbd","engine":"dag","startup_ready":true,"encrypted":true}
154
182
 
155
183
  # Tail the live event stream (new in v2.2.31)
156
184
  curl http://127.0.0.1:7070/events
@@ -204,6 +232,24 @@ pip install nedb-engine # Python ≥ 3.8 — pure-Python + optional Rust na
204
232
  npm install nedb-engine # Node ≥ 16 — napi-rs prebuilt binaries
205
233
  ```
206
234
 
235
+ ### Prebuilt platforms
236
+
237
+ Both registries ship prebuilt binaries for:
238
+
239
+ | Platform | libc | Python wheel | Node addon |
240
+ |---|---|---|---|
241
+ | Linux x86_64 | glibc | ✅ manylinux | ✅ |
242
+ | Linux x86_64 | musl (Alpine) | ✅ musllinux | ✅ |
243
+ | Linux aarch64 (Graviton, Ampere, Apple-Silicon containers) | glibc | ✅ manylinux | ✅ |
244
+ | Linux aarch64 | musl (Alpine) | ✅ musllinux | ✅ |
245
+ | macOS arm64 + x86_64 | — | ✅ | ✅ |
246
+ | Windows x86_64 | MSVC | ✅ | ✅ |
247
+
248
+ On Python, any platform without a prebuilt wheel still installs: pip falls back
249
+ to the universal `py3-none-any` wheel and you get the pure-Python v1 AOF engine
250
+ (correct, slower, no embedded DAG). On Node there is no such fallback — an
251
+ unlisted platform has no addon.
252
+
207
253
  ---
208
254
 
209
255
  ## Python — 5-minute tour
@@ -913,7 +959,8 @@ Requires `GITHUB_TOKEN` (`repo` + `workflow` scope) in the environment. It never
913
959
  ## Authors
914
960
 
915
961
  Built by **[Mark Allen Evans Jr.](https://interchained.org)** (INTERCHAINED, LLC)
916
- with **Claude Sonnet 4.6** on [Hyperagent](https://hyperagent.com/refer/J2G6TCD7).
962
+ with the **Interchained AI fleet** on [Hyperagent](https://hyperagent.com/refer/J2G6TCD7) —
963
+ Vex (GLM · Claude Sonnet · Opus · Fable · GPT-6 Astra/Sol), across hundreds of sessions.
917
964
 
918
965
  > *"Take one idea, turn it into an LP, then an app, then a system, then a platform, then infrastructure that is irreplaceable."*
919
966
 
@@ -11,11 +11,20 @@ Connect to any running `nedbd` instance from Node.js or the browser. No engine e
11
11
 
12
12
  ---
13
13
 
14
- ## ⚠️ Upgrade your server to nedbd 2.8.6
14
+ ## ⚠️ Upgrade your server to nedbd 3.2.0
15
15
 
16
- This client talks to `nedbd`, so its durability comes from the server. **2.8.6 fixes three defects in
17
- 2.8.5 and earlier** — a flush that hit a full disk silently discarded acknowledged writes, flush
18
- failures were unobservable, and `nedb-cli repair` could not actually rebuild a damaged id index.
16
+ This client talks to `nedbd`, so its durability comes from the server. **Upgrade if you are on
17
+ anything earlier than 3.2.0.**
18
+
19
+ **3.2.0 — the embedded engine was pinning its own data directory.** The background flush ticker held
20
+ a strong reference to the database in a loop that never exited, so the handle was never dropped: the
21
+ exclusive data-dir `LOCK` was never released, reopening the same path in the same process failed with
22
+ "locked by another process" naming your *own* pid, and every open leaked a thread and the whole
23
+ database for the life of the process. Live in **2.8.5 through 3.1.0**.
24
+
25
+ **2.8.6 fixed three defects in 2.8.5 and earlier** — a flush that hit a full disk silently discarded
26
+ acknowledged writes, flush failures were unobservable, and `nedb-cli repair` could not actually
27
+ rebuild a damaged id index.
19
28
 
20
29
  If a database ever returned rows and now returns none while `/verify` reports every object healthy,
21
30
  that is the lost-id-index symptom and it is recoverable:
@@ -86,7 +95,7 @@ NEDBD_DAG=1 NEDBD_CAST=1 nedbd --data ./data
86
95
 
87
96
  # Check health
88
97
  curl http://127.0.0.1:7070/health
89
- # {"ok":true,"version":"2.8.0","service":"nedbd","encrypted":true}
98
+ # {"ok":true,"version":"3.2.0","service":"nedbd","encrypted":true}
90
99
  ```
91
100
 
92
101
  ---
@@ -12,11 +12,20 @@ Connect to any running `nedbd` instance — local or remote — with a clean asy
12
12
 
13
13
  ---
14
14
 
15
- ## ⚠️ Upgrade your server to nedbd 2.8.6
15
+ ## ⚠️ Upgrade your server to nedbd 3.2.0
16
16
 
17
- This client talks to `nedbd`, so its durability comes from the server. **2.8.6 fixes three defects in
18
- 2.8.5 and earlier** — a flush that hit a full disk silently discarded acknowledged writes, flush
19
- failures were unobservable, and `nedb-cli repair` could not actually rebuild a damaged id index.
17
+ This client talks to `nedbd`, so its durability comes from the server. **Upgrade if you are on
18
+ anything earlier than 3.2.0.**
19
+
20
+ **3.2.0 — the embedded engine was pinning its own data directory.** The background flush ticker held
21
+ a strong reference to the database in a loop that never exited, so the handle was never dropped: the
22
+ exclusive data-dir `LOCK` was never released, reopening the same path in the same process failed with
23
+ "locked by another process" naming your *own* pid, and every open leaked a thread and the whole
24
+ database for the life of the process. Live in **2.8.5 through 3.1.0**.
25
+
26
+ **2.8.6 fixed three defects in 2.8.5 and earlier** — a flush that hit a full disk silently discarded
27
+ acknowledged writes, flush failures were unobservable, and `nedb-cli repair` could not actually
28
+ rebuild a damaged id index.
20
29
 
21
30
  If a database ever returned rows and now returns none while `/verify` reports every object healthy,
22
31
  that is the lost-id-index symptom and it is recoverable:
@@ -101,7 +110,7 @@ NEDBD_DAG=1 NEDBD_CAST=1 nedbd --data ./data
101
110
 
102
111
  # Check health
103
112
  curl http://127.0.0.1:7070/health
104
- # {"ok":true,"version":"2.8.0","service":"nedbd","encrypted":true}
113
+ # {"ok":true,"version":"3.2.0","service":"nedbd","encrypted":true}
105
114
  ```
106
115
 
107
116
  ---
@@ -10,7 +10,7 @@ build-backend = "hatchling.build"
10
10
 
11
11
  [project]
12
12
  name = "nedb-engine"
13
- version = "3.0.0"
13
+ version = "3.2.1"
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"
@@ -35,6 +35,10 @@ from .autoindex import AutoIndexDB
35
35
  from .concurrent import Sequencer
36
36
  from .wrap_redis import wrap_redis, WrappedRedis
37
37
  from .proof import verify_proof, fold_head
38
+ from .wrap_sqlite import wrap_sqlite, WrappedSqlite
39
+ from .wrap_mysql import wrap_mysql, WrappedMysql
40
+ from .wrap_mongo import wrap_mongo, WrappedMongoClient
41
+ from .wrap_postgresql import wrap_postgresql, WrappedPostgres
38
42
 
39
43
  try: # compiled Rust core, present in platform wheels (PyO3 via maturin)
40
44
  from . import _native # type: ignore
@@ -91,7 +95,11 @@ __all__ = [
91
95
  "MongoCompat", "MongoClient", "MongoError", "MongoUnsupportedError", "ObjectId",
92
96
  "AutoIndexDB", "Sequencer",
93
97
  "wrap_redis", "WrappedRedis",
98
+ "wrap_sqlite", "WrappedSqlite",
99
+ "wrap_mysql", "WrappedMysql",
100
+ "wrap_mongo", "WrappedMongoClient",
101
+ "wrap_postgresql", "WrappedPostgres",
94
102
  "verify_proof", "fold_head",
95
103
  "_native", "__has_native__",
96
104
  ]
97
- __version__ = "3.0.0"
105
+ __version__ = "3.2.1"