Skip to content

Add StreamOne/StreamMany/StreamAggregate typed streaming result types#4260

Merged
jeremydmiller merged 3 commits intomasterfrom
feature/streaming-result-types
Apr 17, 2026
Merged

Add StreamOne/StreamMany/StreamAggregate typed streaming result types#4260
jeremydmiller merged 3 commits intomasterfrom
feature/streaming-result-types

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

Summary

Marten.AspNetCore already ships WriteSingle / WriteArray / WriteLatest extension helpers that stream raw JSON directly to an HttpResponse. This PR adds three typed endpoint return values that wrap that behavior for Minimal API endpoints (and any framework that dispatches IResult, e.g. Wolverine.Http).

Type Source Response shape 404 on miss?
StreamOne<T> IQueryable<T> — regular Marten document query Single T yes
StreamMany<T> IQueryable<T> — regular Marten document query JSON array T[] no (empty [])
StreamAggregate<T> IDocumentSession + stream id — event-sourced Single T yes

Why

WriteSingle(HttpContext) / WriteArray(HttpContext) / WriteLatest(HttpContext) are already great at what they do, but endpoint authors have to manually wire the result up and lose typed return values (and therefore lose good OpenAPI metadata). These wrappers give Minimal API and Wolverine.Http users a concise, typed alternative that still gets raw-JSON-from-the-database streaming.

Design

  • Each type implements both IResult (so ASP.NET Minimal API dispatches it via ExecuteAsync) and IEndpointMetadataProvider (so Swashbuckle, NSwag, and the built-in OpenAPI generator see the right response shape — Produces<T> / Produces<IReadOnlyList<T>> / Produces(404)).
  • ExecuteAsync delegates to the existing extension helpers — zero behavioral drift.
  • OnFoundStatus (default 200) and ContentType (default application/json) are init-only properties so endpoints can customize e.g. 201 Created or a vendor-specific content type.
  • StreamAggregate<T> requires IDocumentSession (mirroring the existing WriteLatest constraint).

Example

```csharp
app.MapGet("/issues/{id:guid}",
(Guid id, IQuerySession session) =>
new StreamOne(session.Query().Where(x => x.Id == id)));

app.MapGet("/issues/open",
(IQuerySession session) =>
new StreamMany(session.Query().Where(x => x.Open)));

app.MapGet("/orders/{id:guid}",
(Guid id, IDocumentSession session) =>
new StreamAggregate(session, id));
```

Scope

  • New files: `StreamOne.cs`, `StreamMany.cs`, `StreamAggregate.cs` in `Marten.AspNetCore`
  • IssueService now also maps Minimal-API endpoints under `/minimal/...` so the test host exercises both controllers and Minimal API
  • 12 new tests in `Marten.AspNetCore.Testing/streaming_result_types_tests.cs` covering: hit/miss, custom status code, custom content type, empty-array behavior, aggregate with 404, OpenAPI metadata assertions
  • Docs: new "Typed Streaming Result Types" section in `docs/documents/aspnetcore.md` with the StreamOne-vs-StreamAggregate distinction called out

Test plan

  • 12 new Alba-driven Minimal-API tests pass
  • All 40 existing `Marten.AspNetCore.Testing` tests still pass

🤖 Generated with Claude Code

jeremydmiller and others added 3 commits April 17, 2026 09:23
Marten.AspNetCore already has WriteSingle/WriteArray/WriteLatest extension
helpers that stream raw JSON directly to an HttpResponse. These three new
types wrap that behavior as typed endpoint return values for Minimal API
(and Wolverine.Http, or any framework that dispatches IResult):

  StreamOne<T>(IQueryable<T>)       -> WriteSingle<T>, 200/404
  StreamMany<T>(IQueryable<T>)      -> WriteArray<T>,  always 200 (empty [])
  StreamAggregate<T>(session, id)   -> WriteLatest<T>, 200/404

Each implements IResult so ASP.NET Minimal API dispatches it via
ExecuteAsync, and IEndpointMetadataProvider so Swashbuckle, NSwag, and the
built-in OpenAPI generator see the right response shape (Produces<T> /
Produces<IReadOnlyList<T>> / Produces(404)).

OnFoundStatus and ContentType are init-only properties for customization.

- Types live in Marten.AspNetCore namespace alongside QueryableExtensions
- IssueService now also maps Minimal-API endpoints under /minimal/... for
  test coverage
- New streaming_result_types_tests.cs asserts all three types (hit/miss,
  custom status, custom content type, OpenAPI metadata) via Alba

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The "404 on miss?" column's pipes did not line up with the header row
because the StreamMany row's value "no (empty array = 200)" was longer
than the header. Pad header/separator/other rows to match for MD060.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds two-arity generic variants of the streaming IResult types that
wrap Marten's ICompiledQuery<TDoc, TOut>:

  StreamOne<TDoc, TOut>(IQuerySession, ICompiledQuery<TDoc, TOut>)
    -> WriteOne, 200/404

  StreamMany<TDoc, TOut>(IQuerySession, ICompiledQuery<TDoc, TOut>)
    -> WriteArray, always 200

These sit alongside the existing single-arity IQueryable-based types
(StreamOne<T>, StreamMany<T>). Each implements IResult and
IEndpointMetadataProvider, advertising 200: TOut (and 404 for StreamOne)
in OpenAPI metadata.

- Two new Marten.AspNetCore types (StreamOneCompiled.cs, StreamManyCompiled.cs)
- IssueService minimal endpoints exercise IssueById and OpenIssues compiled queries
- Six new Alba tests: hit, 404, custom OnFoundStatus, metadata shape, and
  JSON array body for the list overload
- Docs updated with a compiled-query example section

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller merged commit 91747cb into master Apr 17, 2026
4 of 6 checks passed
@jeremydmiller jeremydmiller deleted the feature/streaming-result-types branch April 17, 2026 16:31
jeremydmiller added a commit to JasperFx/wolverine that referenced this pull request Apr 20, 2026
The streaming result types originally landed locally in Wolverine.Http.Marten
in this branch. They've since shipped upstream in Marten.AspNetCore 8.31.0
(via JasperFx/marten#4260 — the same types, lifted to live next to the
existing WriteSingle/WriteArray/WriteLatest helpers and usable from any
Minimal-API endpoint, not just Wolverine.Http).

This commit:
- Bumps Marten + Marten.AspNetCore 8.28.0 → 8.31.0 and JasperFx.Events
  1.27.0 → 1.28.1 in Directory.Packages.props
- Deletes the local StreamOne/StreamMany/StreamAggregate types from
  Wolverine.Http.Marten
- Switches the WolverineWebApi sample endpoints and the test file to use
  Marten.AspNetCore.StreamOne/StreamMany/StreamAggregate
- Updates docs/guide/http/marten.md to point at Marten.AspNetCore as the
  source of the types and call out that no Wolverine-specific code is
  required — the types implement IResult and Wolverine.Http dispatches
  them through its existing ResultWriterPolicy

The existing tests carry over unchanged — they pass against the upstream
types because the public surface is identical (same constructors, same
init-only OnFoundStatus/ContentType, same OpenAPI metadata via
IEndpointMetadataProvider).

Verified with `dotnet test`: all 12 streaming_endpoints tests + 20
related Marten tests (compiled_query_writer, using_aggregate_handler_workflow)
pass against the new packages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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