JasperFx.Events 1.29.0: per-message metadata on side-effect PublishMessage#186
Merged
jeremydmiller merged 1 commit intomainfrom Apr 20, 2026
Merged
Conversation
Adds an independent path through the side-effect publishing pipeline that lets projection authors attach per-message metadata (correlation id, causation id, tenant, headers, user name) when calling PublishMessage from inside RaiseSideEffects. Motivation: Wolverine's Marten integration currently builds a fresh MessageContext for side-effect publishing, which means the outgoing Wolverine envelope is never stamped with the originating event's correlation id. Without a per-message override at the JasperFx level, there is no user-addressable path to fix this — PublishMessage only accepts the message object itself. See JasperFx/wolverine#2545 for the motivating scenario. ## New surface - MessageMetadata struct implementing IMetadataContext — a lightweight value carrier for the metadata. Headers lazy-allocate. - IMessageSink.PublishAsync<T>(T message, MessageMetadata metadata) — new overload with a default implementation forwarding to the tenant-only overload for back-compat. - IEventSlice<T>.PublishMessage(object message, MessageMetadata metadata) + PublishedMessagesWithMetadata() — same story. - IProjectionBatch.PublishMessageAsync(object message, MessageMetadata metadata) — same story. ## Internal routing EventSlice<TDoc,TId> keeps the original PublishedMessages list untouched and adds a separate PublishedMessagesWithMetadata list. The three projection dispatch sites (AggregationRunner, inline single-stream, and inline multi-stream projections) iterate both lists independently, so the original path remains byte-identical for callers that don't opt in. ## Compatibility All new interface members carry default implementations that drop the metadata and forward to existing signatures, so every existing implementation of IMessageSink / IEventSlice<T> / IProjectionBatch compiles and runs unchanged. This is strictly additive — no breaking binary or source changes. ## Tests EventTests: 219/219 pass. EventStoreTests: 72/72 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an independent path through the side-effect publishing pipeline that lets projection authors attach per-message metadata (correlation id, causation id, tenant, headers, user name) when calling
PublishMessagefrom insideRaiseSideEffects.Motivation
Wolverine's Marten integration builds a fresh
MessageContextfor side-effect publishing, which means the outgoing Wolverine envelope is never stamped with the originating event's correlation id. Users have no per-message override becausePublishMessageonly accepts the message object itself. See JasperFx/wolverine#2545 for the motivating scenario.This PR opens the path. The Marten and Wolverine PRs will follow once this is released.
New surface
MessageMetadatastruct implementingIMetadataContext— a lightweight value carrier.Headerslazy-allocates so unused metadata stays allocation-free.IMessageSink.PublishAsync<T>(T message, MessageMetadata metadata)— new overload with a default implementation forwarding to the tenant-only overload.IEventSlice<T>.PublishMessage(object message, MessageMetadata metadata)+PublishedMessagesWithMetadata()— same story.IProjectionBatch.PublishMessageAsync(object message, MessageMetadata metadata)— same story.Internal routing
EventSlice<TDoc,TId>keeps the originalPublishedMessageslist untouched and adds a separatePublishedMessagesWithMetadatalist. The three projection dispatch sites (AggregationRunner, inline single-stream, and inline multi-stream projections) iterate both lists independently, so the original path remains byte-identical for callers that don't opt in.Compatibility
All new interface members carry default implementations that drop the metadata and forward to the existing signatures. Every existing implementation of
IMessageSink/IEventSlice<T>/IProjectionBatchcompiles and runs unchanged. Strictly additive.Test plan
dotnet test src/EventTests— 219/219 passdotnet test src/EventStoreTests— 72/72 passDownstream
Once this ships as
JasperFx.Events 1.29.0:ProjectionBatch.PublishMessageAsyncforwarding toIMessageSink.PublishAsync(message, metadata).MartenToWolverineMessageBatch, mapping toDeliveryOptions(Automatic correlation ID propagation through event-sourced handler chains wolverine#2545).🤖 Generated with Claude Code