nedb-engine 2.8.4__tar.gz → 2.8.6__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 (43) hide show
  1. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/.gitignore +4 -0
  2. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/PKG-INFO +79 -1
  3. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/README.md +78 -0
  4. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/client/node/README.md +20 -0
  5. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/client/python/README.md +20 -0
  6. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/pyproject.toml +1 -1
  7. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/__init__.py +1 -1
  8. nedb_engine-2.8.6/python/nedb/_native.abi3.so +0 -0
  9. nedb_engine-2.8.6/python/nedb/nedbd-v2 +0 -0
  10. nedb_engine-2.8.6/python/nedb/nedbd-v2-darwin-arm64 +0 -0
  11. nedb_engine-2.8.6/python/nedb/nedbd-v2-darwin-x64 +0 -0
  12. nedb_engine-2.8.6/python/nedb/nedbd-v2.exe +0 -0
  13. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/rust/nedb-v2/README.md +77 -0
  14. nedb_engine-2.8.4/python/nedb/_native.abi3.so +0 -0
  15. nedb_engine-2.8.4/python/nedb/nedbd-v2 +0 -0
  16. nedb_engine-2.8.4/python/nedb/nedbd-v2-darwin-arm64 +0 -0
  17. nedb_engine-2.8.4/python/nedb/nedbd-v2-darwin-x64 +0 -0
  18. nedb_engine-2.8.4/python/nedb/nedbd-v2.exe +0 -0
  19. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/LICENSE +0 -0
  20. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/examples/agent-loop/README.md +0 -0
  21. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/examples/mini-chain/README.md +0 -0
  22. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/autoindex.py +0 -0
  23. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/backends/__init__.py +0 -0
  24. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/backends/redis_backend.py +0 -0
  25. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/cascade.py +0 -0
  26. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/client.py +0 -0
  27. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/concurrent.py +0 -0
  28. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/crypto.py +0 -0
  29. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/engine.py +0 -0
  30. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/index.py +0 -0
  31. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/log.py +0 -0
  32. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/merkle.py +0 -0
  33. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/mongo.py +0 -0
  34. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/proof.py +0 -0
  35. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/query.py +0 -0
  36. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/redis_compat.py +0 -0
  37. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/relations.py +0 -0
  38. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/resp2.py +0 -0
  39. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/server.py +0 -0
  40. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/snapshot.py +0 -0
  41. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/sql.py +0 -0
  42. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/store.py +0 -0
  43. {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/wrap_redis.py +0 -0
@@ -22,3 +22,7 @@ npm/
22
22
 
23
23
  # Ephemeral maturin staging copy (CI stages python/nedb here before build)
24
24
  rust/crates/nedb-py/python/
25
+
26
+ # generated by `napi build --js native.js --dts native.d.ts` (index.js is the hand-written wrapper)
27
+ native.js
28
+ native.d.ts
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: nedb-engine
3
- Version: 2.8.4
3
+ Version: 2.8.6
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
@@ -41,6 +41,83 @@ One Rust core → ships to **PyPI** and **npm** from a single source.
41
41
 
42
42
  ---
43
43
 
44
+ ## ⚠️ New in 2.8.6 — Durability & Recovery (read this if you store anything you care about)
45
+
46
+ Three defects found by killing a real engine at every persistence boundary and by filling a real
47
+ filesystem to zero free blocks. All three are fixed. **If you are on 2.8.5 or earlier, upgrade.**
48
+
49
+ ### 1. A failed flush silently discarded acknowledged writes
50
+
51
+ `IdIndex::flush_write_buf` cleared every buffered entry regardless of whether its disk write
52
+ succeeded. So a flush that hit `ENOSPC` threw the entry away, and no later flush retried it.
53
+
54
+ Reproduced on a full 22 MiB filesystem: **30 rows acknowledged by `put() -> Ok`, then `list()`
55
+ returned 0 after reopen — while `verify()` reported all 30 objects healthy.** The content-addressed
56
+ objects were durable; the id-index entries that make them findable were gone.
57
+
58
+ ```
59
+ before: try_flush_all() -> (no return value) reopen -> 0 rows, verify() = 30 ok
60
+ after: try_flush_all() -> Err("id-index leaf rows/buf_25: No space left on device (os error 28)")
61
+ ...free space, retry -> Ok reopen -> 30 rows
62
+ ```
63
+
64
+ **Fixed:** an entry leaves the WAL only when its write actually landed. Failures stay buffered and
65
+ retry on the next flush.
66
+
67
+ ### 2. Flush errors were unobservable — new `try_flush_all()`
68
+
69
+ `flush_all()` returns `()` and logged fsync failures to stderr, so a caller could not tell a durable
70
+ flush from a failed one. Anything that takes a destructive or externally-visible action on the
71
+ strength of a persisted record needs to know.
72
+
73
+ ```rust
74
+ // Use this when the outcome matters:
75
+ db.try_flush_all()?; // Result<()> — id-index WAL + segment sync + MANIFEST
76
+
77
+ // Still available, still logs, nowhere to propagate (ticker / Drop):
78
+ db.flush_all();
79
+ ```
80
+
81
+ Also new: `Db::try_flush_manifest()` and `IdIndex::try_flush_write_buf()`.
82
+
83
+ ### 3. `repair` could not repair, and `since()` claimed "caught up" while behind
84
+
85
+ The cold scan rebuilt `seq_index`, per-collection tips, the Merkle head and `MANIFEST` — but **never
86
+ the id index**. A database whose WAL never reached disk came back with every object verifying and
87
+ `list()` empty, and `nedb-cli repair` printed success without fixing it, because
88
+ `start_cold_scan()` is a deliberate no-op on a warm store.
89
+
90
+ ```bash
91
+ nedb-cli repair ./data
92
+ # repaired: 203 id-index entr(ies) rebuilt, 203 node(s) verified, flushed
93
+ ```
94
+
95
+ ```rust
96
+ let restored = db.repair()?; // rebuild id index from objects; highest seq wins
97
+ ```
98
+
99
+ Every object carries its own `coll`, `id` and `seq`, so the id index is fully derivable — a lost WAL
100
+ is recoverable and nothing is invented. `repair()` also recomputes head and tips, so a repaired
101
+ database reopens **warm** instead of coming back up cold with an empty head.
102
+
103
+ Separately, `since()` set `has_more = hit_limit` alone. On a warm boot the seq index is empty **by
104
+ design** (that is why warm start is O(1)), so every lookup missed and `since()` returned zero nodes
105
+ with `has_more = false` — indistinguishable from genuinely up to date. A consumer following the
106
+ documented drain loop stopped one call in, with every record unread.
107
+
108
+ **Fixed:** `has_more` is true whenever the cursor is behind the log head. `ScanStatus` gains
109
+ **`seq_index_ready`** — replication consumers should gate on that, not on `scan_complete`, which is
110
+ true on a warm boot precisely because the scan was skipped.
111
+
112
+ ### Known sharp edge (documented, not changed)
113
+
114
+ `since()`'s cursor is **exclusive** and seqs start at 0, so `since(0, _)` returns `(0, head]` and the
115
+ very first write in a database (seq 0) is unreachable through any cursor value. Ten writes drain as
116
+ nine records. Changing the convention would break existing consumers; a replica seeded from
117
+ `since()` alone starts one record short.
118
+
119
+ ---
120
+
44
121
  ## NEDB v2.8.0 — Production Stable
45
122
 
46
123
  **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`.
@@ -68,6 +145,7 @@ Off by default — feature-gated at compile time, flag-gated at runtime, and `ex
68
145
  **New in 2.5.x:**
69
146
 
70
147
  - **Durable-mode auto-flush-on-exit** — a durable store flushes buffered writes on `Ctrl+C` / `SIGTERM`, not just on a clean `Drop`. Automatic in the Node and Python bindings; `Db::install_exit_flush(Arc<Db>)` for standalone Rust binaries. See [**docs/DURABILITY.md**](docs/DURABILITY.md).
148
+ - **2.8.5 — embedded bindings flush on a cadence.** `NedbCore.open()` (Node + Python) now runs the 1 s manifest ticker exactly as `nedbd` does, so a `SIGKILL` / OOM / power cut loses at most one tick of acknowledged writes instead of everything since open. `NEDB_FLUSH_MS` tunes or disables it. See [docs/DURABILITY.md](docs/DURABILITY.md).
71
149
  - **`nedb-cli`** — operate on a store directory offline (`head` · `status` · `verify` · `get` · `scan` · `flush` · `repair` · `export`), and **`nedb-inspector`** — a deterministic (no-regex, no-LLM) checker that warns when a durable open lacks flush-on-exit wiring. See [**docs/CLI.md**](docs/CLI.md).
72
150
  - **Replication contract** — `tip()` (the latest write), a bounded `since()` changefeed, and a `scan_status()` readiness gate. See [**docs/REPLICATION.md**](docs/REPLICATION.md).
73
151
 
@@ -20,6 +20,83 @@ One Rust core → ships to **PyPI** and **npm** from a single source.
20
20
 
21
21
  ---
22
22
 
23
+ ## ⚠️ New in 2.8.6 — Durability & Recovery (read this if you store anything you care about)
24
+
25
+ Three defects found by killing a real engine at every persistence boundary and by filling a real
26
+ filesystem to zero free blocks. All three are fixed. **If you are on 2.8.5 or earlier, upgrade.**
27
+
28
+ ### 1. A failed flush silently discarded acknowledged writes
29
+
30
+ `IdIndex::flush_write_buf` cleared every buffered entry regardless of whether its disk write
31
+ succeeded. So a flush that hit `ENOSPC` threw the entry away, and no later flush retried it.
32
+
33
+ Reproduced on a full 22 MiB filesystem: **30 rows acknowledged by `put() -> Ok`, then `list()`
34
+ returned 0 after reopen — while `verify()` reported all 30 objects healthy.** The content-addressed
35
+ objects were durable; the id-index entries that make them findable were gone.
36
+
37
+ ```
38
+ before: try_flush_all() -> (no return value) reopen -> 0 rows, verify() = 30 ok
39
+ after: try_flush_all() -> Err("id-index leaf rows/buf_25: No space left on device (os error 28)")
40
+ ...free space, retry -> Ok reopen -> 30 rows
41
+ ```
42
+
43
+ **Fixed:** an entry leaves the WAL only when its write actually landed. Failures stay buffered and
44
+ retry on the next flush.
45
+
46
+ ### 2. Flush errors were unobservable — new `try_flush_all()`
47
+
48
+ `flush_all()` returns `()` and logged fsync failures to stderr, so a caller could not tell a durable
49
+ flush from a failed one. Anything that takes a destructive or externally-visible action on the
50
+ strength of a persisted record needs to know.
51
+
52
+ ```rust
53
+ // Use this when the outcome matters:
54
+ db.try_flush_all()?; // Result<()> — id-index WAL + segment sync + MANIFEST
55
+
56
+ // Still available, still logs, nowhere to propagate (ticker / Drop):
57
+ db.flush_all();
58
+ ```
59
+
60
+ Also new: `Db::try_flush_manifest()` and `IdIndex::try_flush_write_buf()`.
61
+
62
+ ### 3. `repair` could not repair, and `since()` claimed "caught up" while behind
63
+
64
+ The cold scan rebuilt `seq_index`, per-collection tips, the Merkle head and `MANIFEST` — but **never
65
+ the id index**. A database whose WAL never reached disk came back with every object verifying and
66
+ `list()` empty, and `nedb-cli repair` printed success without fixing it, because
67
+ `start_cold_scan()` is a deliberate no-op on a warm store.
68
+
69
+ ```bash
70
+ nedb-cli repair ./data
71
+ # repaired: 203 id-index entr(ies) rebuilt, 203 node(s) verified, flushed
72
+ ```
73
+
74
+ ```rust
75
+ let restored = db.repair()?; // rebuild id index from objects; highest seq wins
76
+ ```
77
+
78
+ Every object carries its own `coll`, `id` and `seq`, so the id index is fully derivable — a lost WAL
79
+ is recoverable and nothing is invented. `repair()` also recomputes head and tips, so a repaired
80
+ database reopens **warm** instead of coming back up cold with an empty head.
81
+
82
+ Separately, `since()` set `has_more = hit_limit` alone. On a warm boot the seq index is empty **by
83
+ design** (that is why warm start is O(1)), so every lookup missed and `since()` returned zero nodes
84
+ with `has_more = false` — indistinguishable from genuinely up to date. A consumer following the
85
+ documented drain loop stopped one call in, with every record unread.
86
+
87
+ **Fixed:** `has_more` is true whenever the cursor is behind the log head. `ScanStatus` gains
88
+ **`seq_index_ready`** — replication consumers should gate on that, not on `scan_complete`, which is
89
+ true on a warm boot precisely because the scan was skipped.
90
+
91
+ ### Known sharp edge (documented, not changed)
92
+
93
+ `since()`'s cursor is **exclusive** and seqs start at 0, so `since(0, _)` returns `(0, head]` and the
94
+ very first write in a database (seq 0) is unreachable through any cursor value. Ten writes drain as
95
+ nine records. Changing the convention would break existing consumers; a replica seeded from
96
+ `since()` alone starts one record short.
97
+
98
+ ---
99
+
23
100
  ## NEDB v2.8.0 — Production Stable
24
101
 
25
102
  **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`.
@@ -47,6 +124,7 @@ Off by default — feature-gated at compile time, flag-gated at runtime, and `ex
47
124
  **New in 2.5.x:**
48
125
 
49
126
  - **Durable-mode auto-flush-on-exit** — a durable store flushes buffered writes on `Ctrl+C` / `SIGTERM`, not just on a clean `Drop`. Automatic in the Node and Python bindings; `Db::install_exit_flush(Arc<Db>)` for standalone Rust binaries. See [**docs/DURABILITY.md**](docs/DURABILITY.md).
127
+ - **2.8.5 — embedded bindings flush on a cadence.** `NedbCore.open()` (Node + Python) now runs the 1 s manifest ticker exactly as `nedbd` does, so a `SIGKILL` / OOM / power cut loses at most one tick of acknowledged writes instead of everything since open. `NEDB_FLUSH_MS` tunes or disables it. See [docs/DURABILITY.md](docs/DURABILITY.md).
50
128
  - **`nedb-cli`** — operate on a store directory offline (`head` · `status` · `verify` · `get` · `scan` · `flush` · `repair` · `export`), and **`nedb-inspector`** — a deterministic (no-regex, no-LLM) checker that warns when a durable open lacks flush-on-exit wiring. See [**docs/CLI.md**](docs/CLI.md).
51
129
  - **Replication contract** — `tip()` (the latest write), a bounded `since()` changefeed, and a `scan_status()` readiness gate. See [**docs/REPLICATION.md**](docs/REPLICATION.md).
52
130
 
@@ -9,6 +9,26 @@ Connect to any running `nedbd` instance from Node.js or the browser. No engine e
9
9
 
10
10
  ---
11
11
 
12
+ ---
13
+
14
+ ## ⚠️ Upgrade your server to nedbd 2.8.6
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.
19
+
20
+ If a database ever returned rows and now returns none while `/verify` reports every object healthy,
21
+ that is the lost-id-index symptom and it is recoverable:
22
+
23
+ ```bash
24
+ nedb-cli repair ./data
25
+ ```
26
+
27
+ Replication consumers paging `/since`: gate historical catch-up on **`seq_index_ready`** from
28
+ `/status`, not on `scan_complete`. `scan_complete` is true on a warm boot precisely because the scan
29
+ was skipped, and before 2.8.6 `since` reported `has_more: false` in that state — which reads as
30
+ "caught up" on a database with every record unread.
31
+
12
32
  ## Install
13
33
 
14
34
  ```bash
@@ -10,6 +10,26 @@ Connect to any running `nedbd` instance — local or remote — with a clean asy
10
10
 
11
11
  ---
12
12
 
13
+ ---
14
+
15
+ ## ⚠️ Upgrade your server to nedbd 2.8.6
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.
20
+
21
+ If a database ever returned rows and now returns none while `/verify` reports every object healthy,
22
+ that is the lost-id-index symptom and it is recoverable:
23
+
24
+ ```bash
25
+ nedb-cli repair ./data
26
+ ```
27
+
28
+ Replication consumers paging `/since`: gate historical catch-up on **`seq_index_ready`** from
29
+ `/status`, not on `scan_complete`. `scan_complete` is true on a warm boot precisely because the scan
30
+ was skipped, and before 2.8.6 `since` reported `has_more: false` in that state — which reads as
31
+ "caught up" on a database with every record unread.
32
+
13
33
  ## Install
14
34
 
15
35
  ```bash
@@ -10,7 +10,7 @@ build-backend = "hatchling.build"
10
10
 
11
11
  [project]
12
12
  name = "nedb-engine"
13
- version = "2.8.4"
13
+ version = "2.8.6"
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"
@@ -94,4 +94,4 @@ __all__ = [
94
94
  "verify_proof", "fold_head",
95
95
  "_native", "__has_native__",
96
96
  ]
97
- __version__ = "2.8.4"
97
+ __version__ = "2.8.6"
Binary file
@@ -16,6 +16,83 @@ nedbd --dag ./data # DAG engine (v2, content-addressed, recommended)
16
16
 
17
17
  ---
18
18
 
19
+ ## ⚠️ New in 2.8.6 — Durability & Recovery (read this if you store anything you care about)
20
+
21
+ Three defects found by killing a real engine at every persistence boundary and by filling a real
22
+ filesystem to zero free blocks. All three are fixed. **If you are on 2.8.5 or earlier, upgrade.**
23
+
24
+ ### 1. A failed flush silently discarded acknowledged writes
25
+
26
+ `IdIndex::flush_write_buf` cleared every buffered entry regardless of whether its disk write
27
+ succeeded. So a flush that hit `ENOSPC` threw the entry away, and no later flush retried it.
28
+
29
+ Reproduced on a full 22 MiB filesystem: **30 rows acknowledged by `put() -> Ok`, then `list()`
30
+ returned 0 after reopen — while `verify()` reported all 30 objects healthy.** The content-addressed
31
+ objects were durable; the id-index entries that make them findable were gone.
32
+
33
+ ```
34
+ before: try_flush_all() -> (no return value) reopen -> 0 rows, verify() = 30 ok
35
+ after: try_flush_all() -> Err("id-index leaf rows/buf_25: No space left on device (os error 28)")
36
+ ...free space, retry -> Ok reopen -> 30 rows
37
+ ```
38
+
39
+ **Fixed:** an entry leaves the WAL only when its write actually landed. Failures stay buffered and
40
+ retry on the next flush.
41
+
42
+ ### 2. Flush errors were unobservable — new `try_flush_all()`
43
+
44
+ `flush_all()` returns `()` and logged fsync failures to stderr, so a caller could not tell a durable
45
+ flush from a failed one. Anything that takes a destructive or externally-visible action on the
46
+ strength of a persisted record needs to know.
47
+
48
+ ```rust
49
+ // Use this when the outcome matters:
50
+ db.try_flush_all()?; // Result<()> — id-index WAL + segment sync + MANIFEST
51
+
52
+ // Still available, still logs, nowhere to propagate (ticker / Drop):
53
+ db.flush_all();
54
+ ```
55
+
56
+ Also new: `Db::try_flush_manifest()` and `IdIndex::try_flush_write_buf()`.
57
+
58
+ ### 3. `repair` could not repair, and `since()` claimed "caught up" while behind
59
+
60
+ The cold scan rebuilt `seq_index`, per-collection tips, the Merkle head and `MANIFEST` — but **never
61
+ the id index**. A database whose WAL never reached disk came back with every object verifying and
62
+ `list()` empty, and `nedb-cli repair` printed success without fixing it, because
63
+ `start_cold_scan()` is a deliberate no-op on a warm store.
64
+
65
+ ```bash
66
+ nedb-cli repair ./data
67
+ # repaired: 203 id-index entr(ies) rebuilt, 203 node(s) verified, flushed
68
+ ```
69
+
70
+ ```rust
71
+ let restored = db.repair()?; // rebuild id index from objects; highest seq wins
72
+ ```
73
+
74
+ Every object carries its own `coll`, `id` and `seq`, so the id index is fully derivable — a lost WAL
75
+ is recoverable and nothing is invented. `repair()` also recomputes head and tips, so a repaired
76
+ database reopens **warm** instead of coming back up cold with an empty head.
77
+
78
+ Separately, `since()` set `has_more = hit_limit` alone. On a warm boot the seq index is empty **by
79
+ design** (that is why warm start is O(1)), so every lookup missed and `since()` returned zero nodes
80
+ with `has_more = false` — indistinguishable from genuinely up to date. A consumer following the
81
+ documented drain loop stopped one call in, with every record unread.
82
+
83
+ **Fixed:** `has_more` is true whenever the cursor is behind the log head. `ScanStatus` gains
84
+ **`seq_index_ready`** — replication consumers should gate on that, not on `scan_complete`, which is
85
+ true on a warm boot precisely because the scan was skipped.
86
+
87
+ ### Known sharp edge (documented, not changed)
88
+
89
+ `since()`'s cursor is **exclusive** and seqs start at 0, so `since(0, _)` returns `(0, head]` and the
90
+ very first write in a database (seq 0) is unreachable through any cursor value. Ten writes drain as
91
+ nine records. Changing the convention would break existing consumers; a replica seeded from
92
+ `since()` alone starts one record short.
93
+
94
+ ---
95
+
19
96
  ## What is NEDB v2?
20
97
 
21
98
  NEDB v2 replaces the append-only log (AOF) with a **content-addressed Merkle DAG**:
Binary file
Binary file
File without changes