StreamEventState + StreamEvents result types for Marten.AspNetCore - #5053
Merged
Conversation
The event-side siblings of StreamAggregate<T>, backed by the FetchStreamStatePlan
and FetchStreamPlan query plans added in the previous commit. Both implement
IResult and IEndpointMetadataProvider like the rest of the family, and both accept
a pre-built plan so a handler can share one plan between a batched query and its
HTTP result.
Neither writes Marten's own types to the wire, because neither can be:
StreamState.AggregateType and IEvent.EventType are System.Type, and System.Text.Json
refuses to serialize those outright --
NotSupportedException: Serialization and deserialization of 'System.Type'
instances is not supported. Path: $.AggregateType
So StreamStateResponse and EventResponse project the two down to stable wire
shapes, with the aggregate type reduced to its simple name and IEvent's
assembly-qualified DotNetTypeName deliberately left off (EventTypeName, Marten's
event type alias, is the discriminator clients should key on).
FetchStream yields an empty list both for a stream that does not exist and for a
filter that excludes every event, and the two cannot be told apart, so StreamEvents
exposes OnEmptyStatus. It defaults to 404 to match the other single-resource
results; paging forward with fromVersion wants 200 and an empty array instead.
Serialization buffers through an ArrayBufferWriter and ISerializer.WriteTo so the
JSON never round-trips through a .NET string, and Content-Length is set on both.
12 Alba tests over real Minimal API endpoints, plus docs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jeremydmiller
force-pushed
the
feature/5043-stream-query-plans
branch
from
July 26, 2026 12:26
0f7d5c8 to
b68fc21
Compare
This was referenced Jul 27, 2026
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.
Builds on the
FetchStreamStatePlan/FetchStreamPlanquery plans from #5043 (@uniquelau), now merged. That PR was conflicting ondocs/cSpell.json— #5049 had landed"jasperfx"in the same slot in the words array — so I pushed the resolution to its branch and merged it; this PR has been rebased and now carries only the ASP.NET Core work.What this adds
The event-side siblings of
StreamAggregate<T>, backed by those two plans. Both implementIResultandIEndpointMetadataProviderlike the rest of the family:Both also accept a pre-built plan, so a handler can share one plan between a batched query and its HTTP result:
Why the wire types aren't Marten's own
Neither
StreamStatenorIEventcan be written to the response directly.StreamState.AggregateTypeandIEvent.EventTypeareSystem.Type, and System.Text.Json refuses to serialize those:I verified this against the actual store serializer before designing around it — both types throw, so a result that serialized them naively would fail at runtime for every STJ user.
StreamStateResponseandEventResponseproject them onto stable wire shapes: the aggregate type reduces to its simple name, andIEvent's assembly-qualifiedDotNetTypeNameis deliberately left off the wire (EventTypeName, Marten's event type alias, is the discriminator clients should key on).Empty streams
FetchStreamyields an empty list both for a stream that does not exist and for a filter that excludes every event, and the two cannot be told apart.StreamEventstherefore exposesOnEmptyStatus, defaulting to404to match the other single-resource results — set it to200when running off the end is expected, as when paging forward withfromVersion.Details
ArrayBufferWriter<byte>andISerializer.WriteTo, so the JSON never round-trips through a .NET string;Content-Lengthis set on both.WriteStreamState/WriteEventsextension methods onIQuerySessioncarry the actual write, mirroring howStreamOnedelegates toWriteSingle.stringstream key overloads on both, and theversion/timestamp/fromVersionfilters onStreamEvents.Tests and docs
12 new Alba tests against real Minimal API endpoints, covering both results, the plan constructor, the
OnEmptyStatusopt-out,Content-Length, the serialized event body, and OpenAPI metadata. FullMarten.AspNetCore.Testingsuite: 97 passed, re-run green after the rebase.Docs: the result-type table in
documents/aspnetcore.mdgrows to seven entries, with new sections for both types (including the JSON each writes and the empty-stream discussion), the "StreamOne vs StreamAggregate" section becomes a four-way "Choosing between the result types", andevents/querying.mdcross-links from the query plan section.markdownlintandcspellclean over all ofdocs/.Polecat parity tracked at JasperFx/polecat#370.
🤖 Generated with Claude Code