Skip to content

Strong-typed aggregate ids in a Native AOT image, on JasperFx 2.80.2 (#412) - #427

Merged
jeremydmiller merged 1 commit into
mainfrom
fisher-412-strong-typed-aggregates
Oct 4, 2026
Merged

jeremydmiller merged 1 commit into
mainfrom
fisher-412-strong-typed-aggregates

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Closes #412, finishing its last item (items 2 and 3 shipped in #421).

What changed

  • Projections.Snapshot<T, TId>(), a composite Snapshot<T, TId>(), and Projections.LiveStreamAggregation<T, TId>() close a strong-typed aggregate's projection while both types are still generic arguments. Each records a factory on the event graph that live aggregation, Snapshot<T> and composites all consult before falling back to MakeGenericType. This is Native AOT: strong-typed id wrappers and document hierarchies still build their storage reflectively #386's pattern for documents, one layer over.
  • The declared id type is checked against the identity member, since the source generator keys the dispatcher on it and a mismatch would only fail at the first event.
  • An undeclared wrapper is still refused by name in a native image: a readonly record struct has no shared instantiation for MakeGenericType. The message now names the two registrations.
  • JasperFx 2.80.2, which carries jasperfx#950 (GH-950: strong-typed aggregate identity sources work under Native AOT jasperfx#951, written and verified from here). It was the second half of what JasperFx's projection constructor needed, after #942.
  • Docs: a compiled sample in native-aot.md, plus CLAUDE.md and HANDOFF.

Verification

  • Native smoke (macOS arm64, ILC 10.0.1) on the published 2.80.2: a PodId aggregate snapshots inline, live-aggregates, and FetchForWritings by PodId, and a live-only ShoalId aggregate folds. On 2.80.1 the same image threw PlatformNotSupportedException from the projection constructor.
  • Full CI-shaped run green on net9.0 and net10.0: 2531 / 36 / 31. check_scoreboard.py agrees for both TFMs; leak check clean.

Not in scope: live aggregation leaves a strong-typed Id at its default. It's pre-existing (it reproduces on 2.80.1 under the JIT), filed as #426. The smoke asserts the folded state only.

🤖 Generated with Claude Code

…412)

The last item of #412. A projection over a strong-typed aggregate id
used to be closed with MakeGenericType, which has no native code for a
readonly record struct, and JasperFx's projection base then compiled the
wrapper's accessors with FastExpressionCompiler.

- Projections.Snapshot<T, TId>(), a composite Snapshot<T, TId>() and
  Projections.LiveStreamAggregation<T, TId>() record a statically closed
  factory on the event graph (SingleStreamProjectionFactory.Declare).
  Live aggregation, Snapshot<T> and composites consult it before falling
  back to MakeGenericType. The id type is checked against the identity
  member. An undeclared wrapper is still refused by name in a native
  image, now naming the two registrations.
- JasperFx 2.80.2 carries jasperfx#950 (written and verified from here),
  which stops IEvent / StreamAction.CreateAggregateIdentitySource from
  CompileFast()ing for a wrapper id. Together with jasperfx#942 that is
  what JasperFxSingleStreamProjectionBase needed.
- smoke/aot-consumer snapshots, live-aggregates and FetchForWritings a
  PodId aggregate, and folds a live-only ShoalId aggregate, natively. On
  2.80.1 the same image threw from the projection constructor.
- docs/configuration/native-aot.md gets a compiled sample.

Live aggregation leaving a strong-typed Id at its default is
pre-existing and not AOT-specific; filed as #426.

Closes #412

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jeremydmiller
jeremydmiller merged commit 33be020 into main Oct 4, 2026
3 checks passed
@jeremydmiller
jeremydmiller deleted the fisher-412-strong-typed-aggregates branch October 4, 2026 21:45
@jeremydmiller jeremydmiller mentioned this pull request Oct 4, 2026
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.

Native AOT: async daemon, strong-typed aggregates, and the remaining reflection-serializing LINQ paths

1 participant