fix(repositories) Make optimistic-lock updates race-free #23
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix-optimistic-lock-race"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes #20.
Problem
Two saves of the same entity that overlap in time could both pass the version check, and the later one silently overwrote the earlier one. The check (
findOnewith an optimistic lock) and thesave()were separate statements with no row lock in between.A second gap showed up while fixing it:
save()only increments the root row's version when one of the root's own columns changed. A recipe edit that only touches ingredients or steps (or a day edit that only touches dishes) left the version unchanged, so a second stale edit passed the check even without overlap.Change
AbstractRepository.updateWithVersionCheck()is now the one optimistic-lock update. In one transaction it locks the root row withSELECT ... FOR UPDATE, compares the version (404 if the row is gone, 409 if stale), saves, and increments the root's version if the save did not. Every successful update raises the version by exactly 1.update(),RecipeRepository.updateRecipe(),MealPlanRepository.updateDay()andupdateShare()all use it; the three copies of check-then-save are gone.versionyields 400VALIDATION_FAILEDinstead of 409.AbstractRepositoryand its subclasses take anEntityManager(defaultAppDataSource.manager) and no longer callAppDataSourcedirectly, so they can run inside a caller's transaction.ShoppingListRepositoryis unchanged.AbstractEntityJSDoc no longer claims thatsave()rejects a stale version.docs/adr/2026-10-03-race-free-optimistic-locking.md;docs/architecture.mdandCLAUDE.mdupdated. The shopping list item writes ADR is marked as partially superseded (only its statements about the racyupdate()).Behavior changes to be aware of
versionnow changes on every successful update, including child-only edits.Verification
npm test: 57 files, 562 tests pass, including newsrc/repositories/tests/AbstractRepository.test.ts.save()leaves it at 1)npm run buildreports one error insrc/mappers/CompactShoppingListMapper.tson my machine. It is not from this branch: the localrecipe-platformcheckout is on002-shopping-list-overview-previewswith uncommitted contract changes that add fields toCompactShoppingListDto. The same error appears onstagewithout this change.🤖 Generated with Claude Code