# getAndUpdate is the portable update; DynamoDB alone keeps a native update

Every entity service (multi- and single-entity, all three adapters) exposes `getAndUpdate`/`getAndUpdateOp`: read the current entity, derive a partial from it (plain partial or `current => partial | null` callback over the domain value — `null` skips the write), and write the full merged record back guarded on the `_u` that was read. On conflict, `getAndUpdate` re-reads and re-runs up to `retries` times (default 3) before surfacing `ConditionFailed`; `getAndUpdateOp` cannot retry because its guard fires later inside `transact`. `lastWriteWins: true` drops the guard, accepting a full-record read-time-merge clobber — uniformly on every adapter, so the conformance suite pins one semantic. DynamoDB also keeps an adapter-native expression `update`. It takes the Entity definition and semantic key, migrates stale values, stamps `_u`, and rejects updates to key, index-derived, and metadata fields.

The trade-off is deliberate: portable `getAndUpdate` offers callback-over-current-value ergonomics and automatic conflict retries. Dynamo-native `update` uses one server-side expression write after a consistent existence/schema check and is intended for arithmetic and list operations. Single-entity `getAndUpdate` treats the schema default (sentinel `_u: ''`) as a valid current value and guards the write on "record does not exist yet", so `NoItemToUpdate` is not part of that path. Single-entity `getAndUpdateOp` fails with `NoItemToUpdate` before the first persisted write.

Rejected: an auto-retry-free variant where callers hand-roll the retry loop (every caller writes the same loop); field-level patch writes under `lastWriteWins` (subtly adapter-dependent clobber scope, unconformable); keeping `update` alongside `getAndUpdate` on SQLite/IndexedDB (two names for one behavior).

Amended by ADR 0006: `getAndUpdateOp` performs no read, and a rule that declines the write is an **entity invariant** given as the `check` option; the `null`-returning callback and `UpdateRefused` are gone. It still does not retry, but now because the caller owns retry, not because its guard fires later. `getAndUpdate` keeps its retries and is now expressed as `getAndUpdateOp` plus a single-op runner, so the two share one implementation.
