Skip to content

StreamEventState + StreamEvents result types for Marten.AspNetCore - #5053

Merged
jeremydmiller merged 1 commit into
masterfrom
feature/5043-stream-query-plans
Jul 26, 2026
Merged

StreamEventState + StreamEvents result types for Marten.AspNetCore#5053
jeremydmiller merged 1 commit into
masterfrom
feature/5043-stream-query-plans

Conversation

@jeremydmiller

@jeremydmiller jeremydmiller commented Jul 26, 2026

Copy link
Copy Markdown
Member

Builds on the FetchStreamStatePlan / FetchStreamPlan query plans from #5043 (@uniquelau), now merged. That PR was conflicting on docs/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 implement IResult and IEndpointMetadataProvider like the rest of the family:

app.MapGet("/orders/{id:guid}/state",  (Guid id, IQuerySession s) => new StreamEventState(s, id));
app.MapGet("/orders/{id:guid}/events", (Guid id, IQuerySession s) => new StreamEvents(s, id));

Both also accept a pre-built plan, so a handler can share one plan between a batched query and its HTTP result:

var plan = new FetchStreamPlan(orderId, version: 5);
var fetcher = batch.QueryByPlan(plan);   // batch it...
return new StreamEvents(session, plan);  // ...or return it

Why the wire types aren't Marten's own

Neither StreamState nor IEvent can be written to the response directly. StreamState.AggregateType and IEvent.EventType are System.Type, and System.Text.Json refuses to serialize those:

NotSupportedException: Serialization and deserialization of 'System.Type' instances
is not supported. Path: $.AggregateType.

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. StreamStateResponse and EventResponse project them onto stable wire shapes: the aggregate type reduces to its simple name, and IEvent's assembly-qualified DotNetTypeName is deliberately left off the wire (EventTypeName, Marten's event type alias, is the discriminator clients should key on).

Empty streams

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. StreamEvents therefore exposes OnEmptyStatus, defaulting to 404 to match the other single-resource results — set it to 200 when running off the end is expected, as when paging forward with fromVersion.

Details

  • Serialization buffers through an ArrayBufferWriter<byte> and ISerializer.WriteTo, so the JSON never round-trips through a .NET string; Content-Length is set on both.
  • WriteStreamState / WriteEvents extension methods on IQuerySession carry the actual write, mirroring how StreamOne delegates to WriteSingle.
  • string stream key overloads on both, and the version / timestamp / fromVersion filters on StreamEvents.

Tests and docs

12 new Alba tests against real Minimal API endpoints, covering both results, the plan constructor, the OnEmptyStatus opt-out, Content-Length, the serialized event body, and OpenAPI metadata. Full Marten.AspNetCore.Testing suite: 97 passed, re-run green after the rebase.

Docs: the result-type table in documents/aspnetcore.md grows 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", and events/querying.md cross-links from the query plan section. markdownlint and cspell clean over all of docs/.

Polecat parity tracked at JasperFx/polecat#370.

🤖 Generated with Claude Code

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
jeremydmiller force-pushed the feature/5043-stream-query-plans branch from 0f7d5c8 to b68fc21 Compare July 26, 2026 12:26
@jeremydmiller jeremydmiller changed the title Stream query plans (#5043) + StreamEventState / StreamEvents ASP.NET Core results StreamEventState + StreamEvents result types for Marten.AspNetCore Jul 26, 2026
@jeremydmiller
jeremydmiller merged commit eb2e416 into master Jul 26, 2026
10 checks passed
@jeremydmiller
jeremydmiller deleted the feature/5043-stream-query-plans branch July 26, 2026 12:35
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