nedb-engine 3.1.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.
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/PKG-INFO +106 -110
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/README.md +105 -109
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/client/node/README.md +14 -5
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/client/python/README.md +14 -5
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/pyproject.toml +1 -1
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/__init__.py +1 -1
- nedb_engine-3.2.1/python/nedb/_native.abi3.so +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/backends/redis_backend.py +1 -2
- nedb_engine-3.2.1/python/nedb/nedbd-v2 +0 -0
- nedb_engine-3.2.1/python/nedb/nedbd-v2-darwin-arm64 +0 -0
- nedb_engine-3.2.1/python/nedb/nedbd-v2-darwin-x64 +0 -0
- nedb_engine-3.2.1/python/nedb/nedbd-v2.exe +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/server.py +11 -2
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/wrap_redis.py +7 -1
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/rust/nedb-v2/README.md +15 -2
- nedb_engine-3.1.0/python/nedb/_native.abi3.so +0 -0
- nedb_engine-3.1.0/python/nedb/nedbd-v2 +0 -0
- nedb_engine-3.1.0/python/nedb/nedbd-v2-darwin-arm64 +0 -0
- nedb_engine-3.1.0/python/nedb/nedbd-v2-darwin-x64 +0 -0
- nedb_engine-3.1.0/python/nedb/nedbd-v2.exe +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/.gitignore +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/LICENSE +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/examples/agent-loop/README.md +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/examples/mini-chain/README.md +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/autoindex.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/backends/__init__.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/backends/dag.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/cascade.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/client.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/concurrent.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/crypto.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/engine.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/index.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/log.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/merkle.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/mongo.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/proof.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/query.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/redis_compat.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/relations.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/resp2.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/snapshot.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/sql.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/store.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/wrap_core.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/wrap_mongo.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/wrap_mysql.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/wrap_postgresql.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/python/nedb/wrap_sqlite.py +0 -0
- {nedb_engine-3.1.0 → nedb_engine-3.2.1}/rust/crates/nedb-wrap/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: nedb-engine
|
|
3
|
-
Version: 3.1
|
|
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
|
-
##
|
|
50
|
+
## New in 3.2.0 — wrap the databases you already run
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
55
|
+
```python
|
|
56
|
+
from nedb import wrap_redis, wrap_sqlite, wrap_mysql, wrap_mongo, wrap_postgresql
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
|
|
71
|
-
|
|
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
|
-
|
|
108
|
+
---
|
|
74
109
|
|
|
75
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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.
|
|
86
121
|
|
|
87
|
-
|
|
122
|
+
**2. Flush errors were unobservable.** `flush_all()` returns `()`, so a caller could not tell a durable
|
|
123
|
+
flush from a failed one.
|
|
88
124
|
|
|
89
|
-
|
|
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
|
+
```
|
|
90
129
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
`
|
|
94
|
-
`
|
|
130
|
+
Also added: `Db::try_flush_manifest()` and `IdIndex::try_flush_write_buf()`.
|
|
131
|
+
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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.
|
|
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`.
|
|
113
148
|
|
|
114
|
-
**
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
155
|
+
## NEDB v3.2.0 — Production Stable
|
|
128
156
|
|
|
129
|
-
**Current stable: 2.
|
|
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":"
|
|
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
|
|
@@ -324,58 +370,7 @@ Typed errors throughout: `NedbAuthError`, `NedbNotFound`, `NedbBadRequest`,
|
|
|
324
370
|
|
|
325
371
|
---
|
|
326
372
|
|
|
327
|
-
##
|
|
328
|
-
|
|
329
|
-
**One line. Any stack.** Wrap your existing connection and gain tamper-evident, causally-provable, bi-temporal storage *alongside* your app — no migration, no rip-and-replace. NEDB never touches your namespace; shadow data lives only in the embedded DAG engine.
|
|
330
|
-
|
|
331
|
-
| Language | Package | Redis | SQLite | MySQL | MongoDB | PostgreSQL |
|
|
332
|
-
|---|---|:---:|:---:|:---:|:---:|:---:|
|
|
333
|
-
| Python | `pip install nedb-engine` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
334
|
-
| Node.js | `npm install nedb-engine` → `require('nedb-engine/wrap')` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
335
|
-
| Rust | `nedb-wrap` (crates.io) | ✅ (`redis` feature) | 📋 engine-direct | 📋 engine-direct | 📋 engine-direct | 📋 engine-direct |
|
|
336
|
-
|
|
337
|
-
✅ adapter shipped · 📋 embed the DAG core directly (`Surface` trait, same contract)
|
|
338
|
-
|
|
339
|
-
The same contract everywhere — register → backfill → shadow → full NEDB API:
|
|
340
|
-
|
|
341
|
-
```python
|
|
342
|
-
# Python — every write auto-chained
|
|
343
|
-
import redis, json
|
|
344
|
-
from nedb import wrap_redis
|
|
345
|
-
|
|
346
|
-
r = wrap_redis(redis.Redis("localhost", 6379), db_name="rideshare",
|
|
347
|
-
dag_path="./nedb-data") # embedded v2/v3 DAG — no server
|
|
348
|
-
r.nedb.register("driver:*", "driver", value_parser=json.loads)
|
|
349
|
-
r.nedb.backfill()
|
|
350
|
-
r.nedb.shadow_writes = True
|
|
351
|
-
r.set("driver:d1", json.dumps({"name": "Bob", "status": "active"}))
|
|
352
|
-
r.nedb.query('FROM driver WHERE status = "active"')
|
|
353
|
-
r.nedb.verify() # → True — BLAKE2b chain intact
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
```js
|
|
357
|
-
// Node.js — same shape, real Rust DAG core via napi-rs
|
|
358
|
-
const { wrapRedis } = require('nedb-engine/wrap');
|
|
359
|
-
const r = wrapRedis(redisClient, { dbName: 'rideshare', dagPath: './nedb-data' });
|
|
360
|
-
r.nedb.register('driver:*', 'driver');
|
|
361
|
-
r.nedb.shadowWrites = true;
|
|
362
|
-
await r.set('driver:d1', JSON.stringify({ name: 'Bob' }));
|
|
363
|
-
r.nedb.query('FROM driver');
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
```rust
|
|
367
|
-
// Rust — embed the engine directly (nedb-wrap crate)
|
|
368
|
-
use nedb_wrap::Surface;
|
|
369
|
-
let s = Surface::in_memory(); // or Surface::open(path)?
|
|
370
|
-
s.register("driver:*", "driver");
|
|
371
|
-
s.shadow_writes.store(true, std::sync::atomic::Ordering::Relaxed);
|
|
372
|
-
s.shadow("driver:d1", serde_json::json!({"name": "Bob"}), true)?;
|
|
373
|
-
assert!(s.verify());
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
Engine selection (all languages): `nedbd` HTTP server if you point at one (v1 AOF, `--dag` v2, `--dag-v3` v3) → **embedded DAG** if the native wheel is installed → v1 in-process fallback. Pass `dag_path=` for a durable store, `dag_tmk=` for AES-256-GCM encryption.
|
|
377
|
-
|
|
378
|
-
## Redis layer-2 — wrap_redis() in depth
|
|
373
|
+
## Redis layer-2 — wrap_redis()
|
|
379
374
|
|
|
380
375
|
Already running on Redis? Wrap your connection in one line and gain NEDB features *alongside* your existing Redis app — no migration required.
|
|
381
376
|
|
|
@@ -985,7 +980,8 @@ Requires `GITHUB_TOKEN` (`repo` + `workflow` scope) in the environment. It never
|
|
|
985
980
|
## Authors
|
|
986
981
|
|
|
987
982
|
Built by **[Mark Allen Evans Jr.](https://interchained.org)** (INTERCHAINED, LLC)
|
|
988
|
-
with **
|
|
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.
|
|
989
985
|
|
|
990
986
|
> *"Take one idea, turn it into an LP, then an app, then a system, then a platform, then infrastructure that is irreplaceable."*
|
|
991
987
|
|
|
@@ -26,86 +26,114 @@ One Rust core → ships to **PyPI** and **npm** from a single source.
|
|
|
26
26
|
|
|
27
27
|
---
|
|
28
28
|
|
|
29
|
-
##
|
|
29
|
+
## New in 3.2.0 — wrap the databases you already run
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
34
|
+
```python
|
|
35
|
+
from nedb import wrap_redis, wrap_sqlite, wrap_mysql, wrap_mongo, wrap_postgresql
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
87
|
+
---
|
|
53
88
|
|
|
54
|
-
|
|
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
|
-
|
|
59
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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.
|
|
65
100
|
|
|
66
|
-
|
|
101
|
+
**2. Flush errors were unobservable.** `flush_all()` returns `()`, so a caller could not tell a durable
|
|
102
|
+
flush from a failed one.
|
|
67
103
|
|
|
68
|
-
|
|
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
|
+
```
|
|
69
108
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
`
|
|
73
|
-
`
|
|
109
|
+
Also added: `Db::try_flush_manifest()` and `IdIndex::try_flush_write_buf()`.
|
|
110
|
+
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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.
|
|
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`.
|
|
92
127
|
|
|
93
|
-
**
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
134
|
+
## NEDB v3.2.0 — Production Stable
|
|
107
135
|
|
|
108
|
-
**Current stable: 2.
|
|
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":"
|
|
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
|
|
@@ -303,58 +349,7 @@ Typed errors throughout: `NedbAuthError`, `NedbNotFound`, `NedbBadRequest`,
|
|
|
303
349
|
|
|
304
350
|
---
|
|
305
351
|
|
|
306
|
-
##
|
|
307
|
-
|
|
308
|
-
**One line. Any stack.** Wrap your existing connection and gain tamper-evident, causally-provable, bi-temporal storage *alongside* your app — no migration, no rip-and-replace. NEDB never touches your namespace; shadow data lives only in the embedded DAG engine.
|
|
309
|
-
|
|
310
|
-
| Language | Package | Redis | SQLite | MySQL | MongoDB | PostgreSQL |
|
|
311
|
-
|---|---|:---:|:---:|:---:|:---:|:---:|
|
|
312
|
-
| Python | `pip install nedb-engine` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
313
|
-
| Node.js | `npm install nedb-engine` → `require('nedb-engine/wrap')` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
314
|
-
| Rust | `nedb-wrap` (crates.io) | ✅ (`redis` feature) | 📋 engine-direct | 📋 engine-direct | 📋 engine-direct | 📋 engine-direct |
|
|
315
|
-
|
|
316
|
-
✅ adapter shipped · 📋 embed the DAG core directly (`Surface` trait, same contract)
|
|
317
|
-
|
|
318
|
-
The same contract everywhere — register → backfill → shadow → full NEDB API:
|
|
319
|
-
|
|
320
|
-
```python
|
|
321
|
-
# Python — every write auto-chained
|
|
322
|
-
import redis, json
|
|
323
|
-
from nedb import wrap_redis
|
|
324
|
-
|
|
325
|
-
r = wrap_redis(redis.Redis("localhost", 6379), db_name="rideshare",
|
|
326
|
-
dag_path="./nedb-data") # embedded v2/v3 DAG — no server
|
|
327
|
-
r.nedb.register("driver:*", "driver", value_parser=json.loads)
|
|
328
|
-
r.nedb.backfill()
|
|
329
|
-
r.nedb.shadow_writes = True
|
|
330
|
-
r.set("driver:d1", json.dumps({"name": "Bob", "status": "active"}))
|
|
331
|
-
r.nedb.query('FROM driver WHERE status = "active"')
|
|
332
|
-
r.nedb.verify() # → True — BLAKE2b chain intact
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
```js
|
|
336
|
-
// Node.js — same shape, real Rust DAG core via napi-rs
|
|
337
|
-
const { wrapRedis } = require('nedb-engine/wrap');
|
|
338
|
-
const r = wrapRedis(redisClient, { dbName: 'rideshare', dagPath: './nedb-data' });
|
|
339
|
-
r.nedb.register('driver:*', 'driver');
|
|
340
|
-
r.nedb.shadowWrites = true;
|
|
341
|
-
await r.set('driver:d1', JSON.stringify({ name: 'Bob' }));
|
|
342
|
-
r.nedb.query('FROM driver');
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
```rust
|
|
346
|
-
// Rust — embed the engine directly (nedb-wrap crate)
|
|
347
|
-
use nedb_wrap::Surface;
|
|
348
|
-
let s = Surface::in_memory(); // or Surface::open(path)?
|
|
349
|
-
s.register("driver:*", "driver");
|
|
350
|
-
s.shadow_writes.store(true, std::sync::atomic::Ordering::Relaxed);
|
|
351
|
-
s.shadow("driver:d1", serde_json::json!({"name": "Bob"}), true)?;
|
|
352
|
-
assert!(s.verify());
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
Engine selection (all languages): `nedbd` HTTP server if you point at one (v1 AOF, `--dag` v2, `--dag-v3` v3) → **embedded DAG** if the native wheel is installed → v1 in-process fallback. Pass `dag_path=` for a durable store, `dag_tmk=` for AES-256-GCM encryption.
|
|
356
|
-
|
|
357
|
-
## Redis layer-2 — wrap_redis() in depth
|
|
352
|
+
## Redis layer-2 — wrap_redis()
|
|
358
353
|
|
|
359
354
|
Already running on Redis? Wrap your connection in one line and gain NEDB features *alongside* your existing Redis app — no migration required.
|
|
360
355
|
|
|
@@ -964,7 +959,8 @@ Requires `GITHUB_TOKEN` (`repo` + `workflow` scope) in the environment. It never
|
|
|
964
959
|
## Authors
|
|
965
960
|
|
|
966
961
|
Built by **[Mark Allen Evans Jr.](https://interchained.org)** (INTERCHAINED, LLC)
|
|
967
|
-
with **
|
|
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.
|
|
968
964
|
|
|
969
965
|
> *"Take one idea, turn it into an LP, then an app, then a system, then a platform, then infrastructure that is irreplaceable."*
|
|
970
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.
|
|
14
|
+
## ⚠️ Upgrade your server to nedbd 3.2.0
|
|
15
15
|
|
|
16
|
-
This client talks to `nedbd`, so its durability comes from the server. **
|
|
17
|
-
|
|
18
|
-
|
|
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.
|
|
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.
|
|
15
|
+
## ⚠️ Upgrade your server to nedbd 3.2.0
|
|
16
16
|
|
|
17
|
-
This client talks to `nedbd`, so its durability comes from the server. **
|
|
18
|
-
|
|
19
|
-
|
|
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.
|
|
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.1
|
|
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"
|
|
Binary file
|
|
@@ -13,8 +13,7 @@ On startup NEDB replays the stream to rebuild its in-memory MVCC store.
|
|
|
13
13
|
On every write a new entry is XADD'd. One Redis connection, zero impact on
|
|
14
14
|
the user's existing keys.
|
|
15
15
|
|
|
16
|
-
© INTERCHAINED LLC × Claude
|
|
17
|
-
"""
|
|
16
|
+
© INTERCHAINED LLC × Vex (Interchained AI fleet: GLM · Claude · Opus · Fable · GPT-6)"""
|
|
18
17
|
from __future__ import annotations
|
|
19
18
|
|
|
20
19
|
import json
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -812,6 +812,16 @@ def main() -> None:
|
|
|
812
812
|
prog="nedbd",
|
|
813
813
|
description="NEDB server daemon — serves durable, time-traveling databases over HTTP/JSON.",
|
|
814
814
|
)
|
|
815
|
+
# Every CLI should answer --version. The Rust daemon (nedbd-v2) has had -V
|
|
816
|
+
# since it existed; this console script -- the one `pip install nedb-engine`
|
|
817
|
+
# actually puts on PATH -- did not, so `nedbd --version` exited 2 with
|
|
818
|
+
# "unrecognized arguments". argparse's version action fires at parse time,
|
|
819
|
+
# so it also short-circuits before the --dag exec into the Rust binary.
|
|
820
|
+
parser.add_argument(
|
|
821
|
+
"-V", "--version", action="version",
|
|
822
|
+
version=f"nedbd {__version__}",
|
|
823
|
+
help="Print the version and exit.",
|
|
824
|
+
)
|
|
815
825
|
parser.add_argument("--host", default=os.environ.get("NEDBD_HOST", "127.0.0.1"))
|
|
816
826
|
parser.add_argument("--port", type=int, default=int(os.environ.get("NEDBD_PORT", "7070")))
|
|
817
827
|
parser.add_argument("--data", default=os.environ.get("NEDBD_DATA", "./nedb-data"))
|
|
@@ -952,8 +962,7 @@ def main() -> None:
|
|
|
952
962
|
|
|
953
963
|
a versioned, time-traveling, encrypted database
|
|
954
964
|
─────────────────────────────────────────────────
|
|
955
|
-
INTERCHAINED
|
|
956
|
-
interchained.org hyperagent.com/refer/J2G6TCD7
|
|
965
|
+
INTERCHAINED LLC × Vex (Interchained AI fleet: GLM · Claude · Opus · Fable · GPT-6)interchained.org hyperagent.com/refer/J2G6TCD7
|
|
957
966
|
"""
|
|
958
967
|
print(BANNER)
|
|
959
968
|
level_label = {0: "errors-only", 1: "requests", 2: "deploy", 3: "verbose"}.get(_log_level, str(_log_level))
|
|
@@ -31,7 +31,7 @@ Isolation guarantee: NEDB NEVER writes to Alice's namespace. It owns only:
|
|
|
31
31
|
nedb:{db_name}:events Pub/Sub (live subs, future)
|
|
32
32
|
nedb:{db_name}:meta Redis Hash (index config)
|
|
33
33
|
|
|
34
|
-
© INTERCHAINED LLC × Claude
|
|
34
|
+
© INTERCHAINED LLC × Vex (Interchained AI fleet: GLM · Claude · Opus · Fable · GPT-6)
|
|
35
35
|
"""
|
|
36
36
|
from __future__ import annotations
|
|
37
37
|
|
|
@@ -302,6 +302,12 @@ class NEDBSurface:
|
|
|
302
302
|
self._mappings: List[CollectionMapping] = []
|
|
303
303
|
self.shadow_writes: bool = False
|
|
304
304
|
self._backfilled: bool = False
|
|
305
|
+
# Set BEFORE any branching. _reload() reads this, and the
|
|
306
|
+
# DagBackend-ImportError fallback below reaches _reload() without
|
|
307
|
+
# passing through a branch that assigns it — which made wrap_redis()
|
|
308
|
+
# raise AttributeError on every install lacking the native wheel,
|
|
309
|
+
# i.e. exactly the universal py3-none-any path.
|
|
310
|
+
self._nedbd_mode: bool = False
|
|
305
311
|
|
|
306
312
|
if nedbd_url:
|
|
307
313
|
# Route all NEDB operations to a running nedbd server
|
|
@@ -16,7 +16,20 @@ nedbd --dag ./data # DAG engine (v2, content-addressed, recommended)
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
-
## ⚠️ New in 2.
|
|
19
|
+
## ⚠️ New in 3.2.0 — the flush ticker pinned the database
|
|
20
|
+
|
|
21
|
+
`start_manifest_ticker` held a strong `Arc<Db>` in an unconditional loop, so the thread never
|
|
22
|
+
exited and the `Db` was never dropped. The exclusive data-dir `LOCK` taken in `Db::open` was
|
|
23
|
+
therefore never released: reopening the same path **in the same process** failed with "locked by
|
|
24
|
+
another process (pid N)" where N is the caller's own pid. Every `open()` also leaked a thread and
|
|
25
|
+
the entire `Db`, and `Drop for Db` (flush-on-close) could never fire. The ticker now holds a
|
|
26
|
+
`Weak<Db>` and exits when its last owner drops. **Live in 2.8.5 through 3.1.0 — upgrade.**
|
|
27
|
+
|
|
28
|
+
New prebuilt targets: **`aarch64-unknown-linux-gnu`** and **musl** (x86_64 + aarch64).
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Earlier — 2.8.6 durability & recovery
|
|
20
33
|
|
|
21
34
|
Three defects found by killing a real engine at every persistence boundary and by filling a real
|
|
22
35
|
filesystem to zero free blocks. All three are fixed. **If you are on 2.8.5 or earlier, upgrade.**
|
|
@@ -414,4 +427,4 @@ nedbd --doctor
|
|
|
414
427
|
|
|
415
428
|
---
|
|
416
429
|
|
|
417
|
-
*Built by [INTERCHAINED, LLC](https://interchained.org) × Claude
|
|
430
|
+
*Built by [INTERCHAINED, LLC](https://interchained.org) × Vex (Interchained AI fleet: GLM · Claude · Opus · Fable · GPT-6)*
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|