Skip to content

MapSseQuery: map stream queries to Server-Sent Events endpoints #14

Description

@AndrewMcLachlan

What problem would this solve?

Stream queries currently map to one wire shape: an incrementally-written JSON array (MapStreamQuery). That fits exports and large result sets, but not live feeds — progress of a long-running job, tail-style updates — where the natural consumer is a browser EventSource. SSE gives that consumer automatic reconnection, Last-Event-ID resume semantics, and zero client libraries, but wiring a CQRS stream handler to a text/event-stream response is boilerplate every app writes itself today. The producer side already exists in Postie (IStreamQuery/IStreamQueryHandler/IStreamEndpointDispatcher); only the response shaping is missing.

Proposed API or behavior

// same handler shape as MapStreamQuery — no new mediator concepts
public record TailImportProgress(Guid JobId) : IStreamQuery<ImportProgressEvent>;

app.MapSseQuery<TailImportProgress, ImportProgressEvent>("/imports/{jobId}/progress");
// -> text/event-stream; each yielded item is one SSE event (JSON data payload);
//    stream ends when the handler completes; binding/verb semantics match MapStreamQuery
//    (QueryMethod method = Get, RequestBinding? binding = null)

Dispatch goes through IStreamEndpointDispatcher, so it is mediator-agnostic like every other mapping. Same pipeline behaviors and enumeration-time activity semantics as MapStreamQuery.

TFM note: net10.0 can use the framework's TypedResults.ServerSentEvents; net8.0/net9.0 would write the (simple) SSE wire format via the downlevel System.Net.ServerSentEvents formatter so the API surface is identical on all TFMs.

Alternatives considered

  • MapStreamQuery (JSON array): works for progressive data, but has no reconnection/resume story and browsers can't consume it with EventSource; a dropped connection mid-feed loses the stream.
  • Chaining on the returned RouteHandlerBuilder: can't express this — the response shaping happens inside the handler delegate.
  • Hand-rolled minimal API endpoint injecting IStreamQueryDispatcher: the escape hatch that works today, at the cost of re-implementing binding, verb selection and the SSE plumbing per app.
  • SignalR/WebSockets: bidirectional machinery for a unidirectional feed; SSE is the right-sized primitive.

Contribution

  • I'm willing to send a PR for this

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions