Skip to content

Add FetchStreamStatePlan + FetchStreamPlan: raw event stream fetches as batchable query plans - #5043

Merged
jeremydmiller merged 1 commit into
JasperFx:masterfrom
uniquelau:fetch-stream-query-plans
Jul 26, 2026
Merged

Add FetchStreamStatePlan + FetchStreamPlan: raw event stream fetches as batchable query plans#5043
jeremydmiller merged 1 commit into
JasperFx:masterfrom
uniquelau:fetch-stream-query-plans

Conversation

@uniquelau

Copy link
Copy Markdown
Contributor

What this adds

Two concrete query plan types in the Marten namespace — the event-side siblings of QueryListPlan<T>:

  • FetchStreamStatePlan — fetches a stream's StreamState? by stream identity (null when the stream does not exist).
  • FetchStreamPlan — fetches a stream's raw IReadOnlyList<IEvent> by stream identity (empty list when the stream does not exist), carrying FetchStream's optional version / timestamp / fromVersion arguments through both paths.

Both types implement both IQueryPlan<T> and IBatchQueryPlan<T>, and both support the two stream identity styles (Guid streamId / string streamKey) via constructor overloads, mirroring the overload pairs on IBatchEvents.

Why

Wolverine's fetch-specification feature batches any IBatchQueryPlan returned from a handler's Load into a single database round trip, and IBatchedQuery.Events already exposes FetchStreamState / FetchStream — but no shipped plan connects the two, so users hand-roll adapter plans per project. There is also a footgun in doing that: a hand-rolled plan implementing only IBatchQueryPlan<T> (without IQueryPlan<T>) produces uncompilable generated code through Wolverine. Shipping the pair with both interfaces implemented removes both the boilerplate and the trap.

Downstream we measured the batching at −38% latency / +27% throughput on an endpoint that fetches a stream state plus a second query in one round trip instead of two.

API

// Standalone
var state  = await session.QueryByPlanAsync(new FetchStreamStatePlan(streamId));
var events = await session.QueryByPlanAsync(new FetchStreamPlan(streamId, version: 5));

// Batched — one round trip
var batch = session.CreateBatchQuery();
var stateFetcher  = batch.QueryByPlan(new FetchStreamStatePlan(streamId));
var eventsFetcher = batch.QueryByPlan(new FetchStreamPlan(streamId));
await batch.Execute();

String-keyed streams use the string streamKey constructors identically.

Test coverage

src/EventSourcingTests/fetching_stream_query_plans.cs (8 tests, all passing, plus the 2 existing DocumentDbTests.Reading.query_plans tests re-run green):

  • standalone QueryByPlanAsync for both plans
  • both plans combined in one CreateBatchQuery + QueryByPlan + Execute
  • Guid and string stream identity for both plans
  • missing stream behaviour (null state / empty event list)
  • FetchStreamPlan with a version cap

The exemplary tests carry #region sample_using_fetch_stream_plan / #region sample_fetch_stream_plans_in_batch markers.

Docs

  • docs/documents/querying/compiled-queries.md — new "Query Plans for Event Streams" subsection under Query Plans, with both samples (expanded via mdsnippets).
  • docs/events/querying.md — new "Stream Query Plans" section linking the stream fetches to the query plan concept.
  • docs/cSpell.json — added batchable.

Note to maintainers

If you'd rather not grow the shipped API surface, we're happy to convert this to a documentation-only sample (the two classes moved into the test project and shown in the docs the way LonghandColorTargets is today) — say the word and we'll rework the PR.

🤖 Generated with Claude Code

…as batchable query plans

Concrete IQueryPlan<T>/IBatchQueryPlan<T> implementations wrapping
Events.FetchStreamState and Events.FetchStream, supporting both Guid and
string stream identity and FetchStream's optional version/timestamp/
fromVersion filters, so stream fetches can participate in batched queries
without hand-rolled adapter plans.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jeremydmiller
jeremydmiller force-pushed the fetch-stream-query-plans branch from 4010fbe to 6a26157 Compare July 26, 2026 12:15
@jeremydmiller
jeremydmiller merged commit 1010f67 into JasperFx:master Jul 26, 2026
10 checks passed
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.

2 participants