Publishes transaction.transaction.updated, which is how dashboards hear
of a change. null disables realtime (tests); the bust still runs.
Required so every construction site decides.
invalidateMany's cache bust alone, for a writer that publishes its
own specific event in place of transaction.transaction.updated.
The bust for a write that touched several customers' rows (a transfer's two legs, a batch): ONE bust over every change's tags, then one realtime publish per change.
The bust runs first, so the dashboard refetches fresh data, and never throws for a Valkey failure. The publish is best-effort too: an internal-bus failure can never fail the mutation that triggered it.
Clear one customer's transaction listings and the written transactions wherever they are cached, then broadcast that the transactions changed. invalidateMany for a single change.
Record-tag buster + realtime broadcaster over the transaction projection owned by TransactionQueryService. Transaction-writer services (deposit, manual mint, withdrawal, swap, ledger-account transfer, card, reward, redemption, fee, escrow) construct or receive one of these and call invalidateTag after a successful write + event publish, so that — cross-process via the shared Valkey cluster and the internal event bus — both:
The nudge is
transaction.transaction.updated, which theappsyncrelay fans out on AppSync Events. It carries ids only, so writers never needdb/TransactionQueryServiceto re-serialize a row.Transactions are the one entity that keeps a buster collaborator rather than rooting every writer on a
SearchCachesubclass: the writers are deposit / withdrawal / swap / reward / card / … services, each already rooted in its own entity's hierarchy and in another package, so single inheritance forbids them extending aTransactionCachetoo. See CACHING.md "Cross-domain (foreign-key) writers".