Skip to content

v7.31.0 — _rev + ifRev + ifAbsent

Choose a tag to compare

@dpsifr dpsifr released this 09 Jun 17:05
· 20 commits to main since this release

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 every update(). Surfaced on get() / find() / search(). Pre-7.31.0 entities read as _rev: 1.
  • update({ ifRev: N }) — CAS check. Throws RevisionConflictError carrying { id, expected, actual } if the persisted _rev no longer matches.
  • add({ ifAbsent: true }) + addMany({ items, ifAbsent: true }) — by-ID idempotent insert. Returns the existing id if present, no throw, no overwrite.
  • RevisionConflictError exported 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.