Skip to content

JasperFx 2.48.0

Choose a tag to compare

@jeremydmiller jeremydmiller released this 15 Aug 16:25
· 253 commits to main since this release

What's Changed

Six projection-dispatch defects in the aggregate source generator and its runtime, plus a silent event-identity bug on string-keyed appends.

⚠️ Breaking: JFXEVT003 is now an Error

JFXEVT003 was Info — invisible in a CLI build at any verbosity — and its message promised a "fallback to runtime expression compilation" that does not exist. It is now an Error with an accurate message, and it fires in far fewer places.

A projection that needs partial and lacks it now fails the build instead of failing later at store construction with InvalidProjectionException: No source-generated dispatcher found. Every affected consumer is already broken at runtime today; this moves the failure earlier and names the fix. Suppressible with dotnet_diagnostic.JFXEVT003.severity if you need a staged upgrade.

partial is required in far fewer places

Projection subclasses were silently skipped unless declared partial — build clean, then a hard failure at DocumentStore construction. The gate predated #462, which moved aggregation dispatch to a standalone file-scoped evolver that needs nothing from the user's declaration. Rider's redundant-partial inspection would also strip the modifier back off, correctly by the language's rules, reintroducing the failure on the next cleanup-on-save.

After this release partial is required in exactly two places, both reported at build time:

  • an EventProjection subclass using conventional methods — its dispatcher is an ApplyAsync override on your class;
  • a generic projection, or one behind private/protected, which a file-scoped type cannot name (and then every containing type must be partial too).

Everything else — including DI-activated projections and every ordinary SingleStreamProjection / MultiStreamProjection subclass — needs no modifier. Generic and non-visible projections previously emitted uncompilable code (CS0246 / CS0122) even with partial; they now work. (#650, #651)

Other fixes

  • The evolver now dispatches on the projection the store actually registered, instead of a new'd or GetUninitializedObject shadow — so injected dependencies are reachable and anything the real constructor set is present. This also removes the member-injection emission that had reintroduced the CS0111 double-load failure when the generator ships in two referenced packages (#653, #661)
  • ShouldDelete and Create/Apply covering the same event type produced two identical switch arms and failed the consumer build with CS8120. They now compose into one arm — delete when the predicate says so, otherwise fold the event in. A ctor-registered DeleteEvent<T>() stays unconditional (#652, #660)
  • A projection method that hides rather than overrides Evolve is no longer treated as an override. Previously this either threw a bogus "can only use the override of 'Evolve' or conventional Apply/Create/ShouldDelete methods, but not both" at registration, or reached first-event dispatch and threw NotImplementedException. Resolution also no longer throws AmbiguousMatchException when the projection declares a same-named helper (#656, #659)
  • A non-partial EventProjection with an explicit ApplyAsync override silently dropped its published-type registration with no diagnostic at all. Now reported as JFXEVT006 (Warning) naming the projection and the unregistered document types (#654, #658)
  • JFXEVT001 and JFXEVT004 are removed. Neither was ever reported, and the lambda-registration opt-out JFXEVT004 described could only produce false skips — the ProjectEvent/CreateEvent/DeleteEvent lambda APIs it matched were removed in JasperFx 2.0 / Marten 9.0, so it could only fire on a user-defined helper that happened to share a name (#655, #657)
  • StreamAction.Append(graph, string streamKey, …) and StreamAction.Append(string streamKey, IEvent[]) appended straight to the backing list instead of going through AddEvents, so the envelopes never got StreamId/StreamKey/TenantId. PrepareEvents does not close the gap either. A store handing stream.Events to an inline projection gave it envelopes with an empty StreamKey — the normal way a string-identified projection learns which entity it is projecting — and the projection silently wrote a document with a blank field (#663, #664)

Note on version coupling

The generator and the JasperFx.Events runtime now move together: an evolver that takes the projection through its constructor cannot be activated by a runtime without activateEvolver. Pin JasperFx.Events and JasperFx.Events.SourceGenerator to the same version. Evolvers generated before this release keep working — the parameterless constructor is still the fallback.

Validation

Packed locally and validated against both stores before publishing:

  • Marten master @ 9ddfd0ac8 — EventSourcingTests 1850 passed / 0 failed, CoreTests 535 passed / 0 failed, 0 build errors and no JFXEVT diagnostics anywhere.
  • Polecat main @ 70b4367 — Polecat.Tests 2151 passed / 0 failed, 0 build errors and no JFXEVT diagnostics.

The JFXEVT003 escalation produces no diagnostics in either corpus, so the severity change should be inert for existing projections in both stores.

Full Changelog: V2.47.0...V2.48.0