Skip to content

fix(cache): invalidate identity-keyed entries on compound-key updates#26

Closed
alexstandiford wants to merge 2 commits into
mainfrom
fix/update-compound-identity-invalidation
Closed

fix(cache): invalidate identity-keyed entries on compound-key updates#26
alexstandiford wants to merge 2 commits into
mainfrom
fix/update-compound-identity-invalidation

Conversation

@alexstandiford

Copy link
Copy Markdown
Contributor

where()/getModels() hydration caches each row under its table identity (primary columns). updateCompound() only invalidated the caller-provided compound context — so an update keyed on a compound business key (e.g. a config row's type/subtype/configKey) left the identity-keyed entry serving the pre-update row until TTL.

Found in Siren: with a persistent cache backing (Redis), REST reads returned stale entities after writes for every field stored via compound-key updates, and it reproduced against any working cache. Sibling of the int/string identity divergence fixed in 6e423f7 — same cache, third key-shape mismatch.

Fix: resolve the row's table identity before the update and invalidate that context too. Skipped when the caller's key already is the table identity (the common update-by-id path — no extra query) and for tables without identity fields. Identity values are projected from the query result so the context contains identity fields only.

Includes a regression test (testUpdateByCompoundBusinessKeyInvalidatesIdentityKeyedEntry). All 18 package tests green.

Downstream note: Siren also carries this fix in its tenant-scoped handler override (Novatorius/siren#195), which additionally handles tenant-decorated/bare key variants the framework can't know about — that override stays correct with or without this patch.

🤖 Generated with Claude Code

alexstandiford and others added 2 commits July 11, 2026 18:39
where()/getModels() hydration caches each row under its table identity
(primary columns). updateCompound() only invalidated the caller-provided
compound context, so an update keyed on a compound business key (e.g. a
config row's type/subtype/configKey) left the identity-keyed cache entry
serving the pre-update row until TTL — observed in production-shaped
E2E as reads returning stale entities after writes.

updateCompound() now resolves the row's table identity before the
update and invalidates that context too. The resolution is skipped for
the common update-by-identity path (the caller's key already IS the
identity) and for tables without identity fields. Identity values are
projected from the query result so the cache context contains identity
fields only, regardless of what the query strategy returns.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ution

- from() before select(): the builder prefixes selected columns with
  the current table's alias, which is stale until from() runs.
- When the caller's key has the same fields as the table identity in a
  different order, reorder locally instead of querying — the cache
  context is order-sensitive.
- Resolution failures degrade to a possibly-stale entry (logged)
  instead of breaking the write.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant