Releases: rubylab-app/rouchdb
v0.4.0
[0.4.0] — 2026-06-09
Correctness release. A deep audit fixed 44 bugs across every crate. There are no new features, but several fixes change observable behavior (HTTP status codes, replication checkpoints) or public type shapes — read the migration guide before upgrading.
Migration Guide (0.3.2 → 0.4.0)
Breaking Changes — API
-
ChangesEventhas a newHeartbeatvariant. An exhaustivematchwithout a wildcard arm will no longer compile. Add an arm:ChangesEvent::Heartbeat => { /* keep-alive tick; ignore or reset a timeout */ }
Heartbeats are emitted by
live_changes_eventsonly whenChangesStreamOptions.heartbeatis set (the option was previously inert). -
DbInfohas a new fielddoc_del_count: u64. If you build it with struct-literal syntax, add the field or use..Default::default(). It now reports the real deleted-document count (was hardcoded to0in the server response). -
SecurityDocumenthas a new fieldextra: serde_json::Map<String, Value>— a#[serde(flatten)]catch-all so CouchDB's arbitrary_securityfields round-trip instead of being dropped. Add..Default::default()to struct literals. -
CheckpointDocgained a_revfield andCheckpointer::newtakes a third argument (a filter fingerprint):Checkpointer::new(source_id, target_id, filter_fingerprint). Only affects code that constructs theserouchdb-replicationtypes directly.
Breaking Changes — Behavior
-
Replication checkpoints are invalidated once. The replication ID now incorporates the filter (and uses a revised hashing scheme), so the first replication after upgrading re-reads from sequence 0. This is a one-time, idempotent re-scan (no data loss or duplicates, thanks to
revs_diff); subsequent runs resume normally from the new checkpoint. -
HTTP server status codes now match CouchDB:
PUT /{db}/{docid}returns 409 Conflict on a stale/missing_rev(was 201 Created).DELETE /{db}/{docid}returns 409 / 404 on conflict / not-found (was 200 OK).PUT /{db}returns 412 file_exists when the database already exists (was 201).
Clients that treated the old (incorrect) codes as success must update their handling.
-
PUT /{db}/{docid}with{"_deleted": true}now deletes the document (previously it silently created a new live revision). -
get(rev = X, latest = true)now returns the leaf of X's own branch, not the global winning leaf. Only affects conflicted documents. -
An empty map/reduce now returns
{"rows": []}instead of one{"key": null, "value": 0}row (matches CouchDB). Code that indexesrows[0]unconditionally must guard for emptiness. -
Revisionparsing rejects an empty hash (e.g."3-") withInvalidRev; such strings previously parsed successfully. -
put/update/removereturnErr(DatabaseError)instead of panicking when abefore_writeplugin removes the document from the batch.
Bug Fixes
Core (revision tree, collation, model):
- Stemming stopped at the first conflict branch point, so
revs_limitwas never enforced on conflicted documents (unbounded rev-tree growth). It now prunes past branch points by re-rooting subtrees. - Collation lost precision on integers above 2⁵³, collapsing distinct values to
Equaland breaking Mango$eq/range queries. Integers are now compared exactly. - Inline attachment data serialized as a JSON byte array instead of a base64 string (malformed CouchDB output).
Revision::from_straccepted a missing hash ("{pos}-").
Memory adapter:
put_attachmentreported success but discarded the attachment — bytes were unrecoverable. Attachments are now persisted per revision and round-trip throughget_attachmentand replication.get(latest = true)returned the global winner instead of the requested branch's leaf.style=all_docsemitted an emptychangesarray for fully-deleted documents.changesreportedlast_seq = sincewhen every change was filtered out (endless re-scans).all_docstotal_rowsreflected the filtered count instead of the database total.
Redb adapter:
all_docsdescending key-range used ascending comparisons, returning the wrong rows.all_docsignoredconflicts, omitting_conflictsfrom included docs.changespanicked on a single corrupt/undeserializable record (now propagates an error).info()/all_docsreadupdate_seqin a separate transaction from the document snapshot (TOCTOU); now a single read transaction.
HTTP adapter:
all_docsignoredkey,keys, andinclusive_end.startkey/endkey/key/open_revswere spliced into the URL unencoded (broke on special characters and unicode).- Design-document ids were encoded as
_design%2Fx, breaking routing; the prefix slash is now preserved.
Query (Mango + map/reduce):
$elemMatchwith an operator expression failed on arrays of scalars.skip/limitwere ignored on reduced/grouped output.group_level=0did not collapse into a single global group.- Reduce over an empty result set returned a spurious zero row.
Views:
- Design documents containing a
views.lib(CommonJS shared library) entry failed to parse.
Replication:
- Replication ID omitted the filter, so different filters shared a checkpoint (silent data loss).
- Checkpoint
_localdoc was written without_rev, so every CouchDB checkpoint update after the first failed with 409. - Checkpoint history was reset to a single entry on every write, breaking cross-session resume.
compare_checkpointscompared opaque CouchDB sequences by numeric prefix; transient checkpoint read errors were swallowed as "no checkpoint."- The checkpoint advanced past documents that failed to parse or write (permanent data loss with no resume);
docs_writtencounted failed writes. - Attachments were dropped during replication (now carried end-to-end between memory adapters).
- Live replication emitted a
Completeevent on every poll iteration instead of once at the end.
Changes feed:
limitcounted pre-filter changes, so a filtered live feed delivered fewer thanlimitmatches.- The
heartbeatoption was declared but never honored.
Umbrella (Database):
- Index-accelerated
find()ignored dotted/nested sort fields (e.g.address.city). put/update/removepanicked when a plugin dropped the document.
HTTP server:
- Wrong status codes on conflict/not-found and on existing-database
PUT(see migration notes). since=nowmapped tou64::MAX, causing an integer overflow.PUTignored_deletedin the body.- Attachment downloads guessed the content type from the filename, discarding the stored
content_type. doc_del_countwas hardcoded to0.PUT /{db}/_securitydropped any fields outsideadmins/members.
CLI:
importexited0even when documents failed.put --forceswallowed non-NotFounderrors fromgetand silently created instead of updating.
Tests
- 366 unit tests passing (≈25 new regression tests);
clippy -D warningsandcargo fmt --checkclean.
v0.3.2
What's Changed
- chore: bump to v0.3.2 — update repo URL for org rename by @Perafan18 in #4
Full Changelog: v0.3.1...v0.3.2
v0.3.1
Full Changelog: v0.3.0...v0.3.1
v0.3.0
Full Changelog: v0.2.0...v0.3.0
v0.2.0
What's Changed
- Add filtered replication by @Perafan18 in #1
- v0.2.0 — Full PouchDB API parity by @Perafan18 in #2
New Contributors
- @Perafan18 made their first contribution in #1
Full Changelog: v0.1.1...v0.2.0