v7.31.0 — _rev + ifRev + ifAbsent
Highlights
Per-entity optimistic concurrency, the read-then-CAS lock pattern, and idempotent-bootstrap singleton inserts.
entity._rev: number— monotonic per-entity counter, auto-bumped on everyupdate(). Surfaced onget()/find()/search(). Pre-7.31.0 entities read as_rev: 1.update({ ifRev: N })— CAS check. ThrowsRevisionConflictErrorcarrying{ id, expected, actual }if the persisted_revno longer matches.add({ ifAbsent: true })+addMany({ items, ifAbsent: true })— by-ID idempotent insert. Returns the existingidif present, no throw, no overwrite.RevisionConflictErrorexported from the public API.
The full optimistic-concurrency guide walks through the lock pattern, read-modify-write retry, idempotent bootstrap, and how _rev interacts with branches, snapshots, and VFS file versioning.
Why this scope (and why no brain.transaction())
A public transaction wrapper was on the table but was cut. The internal TransactionManager exposes raw Operation classes that take a StorageAdapter constructor arg; a clean high-level facade in 7.31.0 was either (a) "delegate to brain.add() / update() / relate()" which each commit their own internal transaction before the closure returns (looks atomic, isn't), or (b) thread tx? through every internal write site (~2 days + regression surface). The SDK-scheduler use case driving this is the read-then-CAS lock pattern, fully solved by _rev + ifRev. Multi-write atomicity lands in 8.0's Datomic-style brain.transact() where it's atomic by construction.
Cortex compatibility
Zero changes required. _rev is a single integer field updated alongside every other metadata field; Cortex never sees the per-entity counter.
Tests
- New
tests/integration/rev-and-ifabsent.test.ts(18 tests) - 1468 / 1468 unit suite passing
See RELEASES.md for the full entry.