Skip to content

feat: deferred end-of-mutation calculation hook#500

Open
smartive-nicolai[bot] wants to merge 1 commit into
mainfrom
feat/deferred-end-of-mutation-calculations
Open

feat: deferred end-of-mutation calculation hook#500
smartive-nicolai[bot] wants to merge 1 commit into
mainfrom
feat/deferred-end-of-mutation-calculations

Conversation

@smartive-nicolai

Copy link
Copy Markdown
Contributor

What

Adds a request-scoped mutation scratch bag (mutationState) and an afterMutations(ctx) lifecycle callback, so consumers can defer work to a single pass at the end of each top-level mutation instead of running it eagerly inside individual mutation hooks.

Why

In zwei-wealth-platform, entity calculations (Portfolio → Goal → Relation, etc.) currently run eagerly inside each afterMutate* hook. When one request mutates several related entities — via mutation reentrancy, delete-cascade or restore-cascade — the same entity is recalculated many times, in the wrong order, and on inconsistent mid-mutation state. This hook lets the consumer accumulate dirty entity ids across the whole mutation and run each calculation exactly once, in dependency order, on a consistent post-write state.

Changes

  • Context / MutationContext gain two optional fields:
    • mutationState?: Record<string, unknown> — a scratch bag, initialized to {} by mutationResolver at the start of every top-level mutation and shared by reference across all nested withTransaction levels (so a mark made deep inside a cascade is visible at the top).
    • afterMutations?: (ctx) => Promise<void> | void — run once at the end of the mutation.
  • mutationResolver sets mutationState = {} per top-level field and calls afterMutations after the write (including all cascade/reentrant writes) but before the result is read back, still inside the mutation transaction. Skipped for dry-run deletes.

Fully backwards compatible: both fields are optional; when afterMutations is unset the behaviour is unchanged.

Consumer

Pairs with the zwei-wealth-platform MR that moves Portfolio/Goal/Relation/PAR/RelationCost calculations to a single deferred flush (link to follow).

🤖 Generated with Claude Code

Add a request-scoped mutation scratch bag (`mutationState`) and an
`afterMutations(ctx)` lifecycle callback so consumers can defer work to a
single pass at the end of each top-level mutation instead of running it
eagerly inside individual mutation hooks.

- `Context`/`MutationContext` gain `mutationState?` (shared by reference
  across all nested `withTransaction` levels) and `afterMutations?`.
- `mutationResolver` initializes `mutationState` to `{}` per top-level
  mutation field and invokes `afterMutations` after the write (including all
  cascade/reentrant writes) but before the result is read back, still inside
  the mutation transaction. Skipped for dry-run deletes.

This lets a consumer accumulate dirty entity ids across cascades/reentrancy
and run each derived calculation exactly once, in dependency order, on a
consistent post-mutation state.

Co-Authored-By: Claude Opus 4.8 <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