From 4470ef52b0e61188e5d430020520c044b7adf048 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Wed, 12 Aug 2026 18:52:58 +0200 Subject: [PATCH] Add stream reading guidance: ReadStreamToEnd, memory semantics Document the IEventReader read semantics (KurrentDB streams events as they arrive, relational stores buffer up to count) and steer agents to ReadStreamToEnd for whole-stream reads instead of ReadEvents with int.MaxValue. Matches Eventuous/eventuous#568. Co-Authored-By: Claude Fable 5 --- agents/eventuous-expert.md | 1 + skills/eventuous-dotnet-kurrentdb/SKILL.md | 2 ++ skills/eventuous-dotnet/SKILL.md | 16 ++++++++++++++++ 3 files changed, 19 insertions(+) diff --git a/agents/eventuous-expert.md b/agents/eventuous-expert.md index 4bb4920..de117cd 100644 --- a/agents/eventuous-expert.md +++ b/agents/eventuous-expert.md @@ -62,6 +62,7 @@ Determine the target language from the project context: - Recommend KurrentDB as the default event store unless the user specifies otherwise - Prefer functional command services (`CommandService`) for simple cases; aggregate-based (`CommandService`) when business invariants require it - Use `IEventReader.LoadAggregate<>()` and `IEventWriter.StoreAggregate<>()` extension methods — `IAggregateStore` is deprecated +- Read whole streams with `IEventReader.ReadStreamToEnd()` (paged, bounded memory) — never `ReadEvents` with `int.MaxValue` as the count - Use `.NoContext()` for all async calls (`ConfigureAwait(false)`) - Event types are registered automatically via source generation (no manual `TypeMap` calls) - Follow the default stream naming convention: `{AggregateType}-{AggregateId}` diff --git a/skills/eventuous-dotnet-kurrentdb/SKILL.md b/skills/eventuous-dotnet-kurrentdb/SKILL.md index ba9a5c4..2688155 100644 --- a/skills/eventuous-dotnet-kurrentdb/SKILL.md +++ b/skills/eventuous-dotnet-kurrentdb/SKILL.md @@ -50,6 +50,8 @@ services.AddEventStore(); `KurrentDBEventStore` implements `IEventStore` (which combines `IEventReader` and `IEventWriter`). `AddEventStore()` registers all three interfaces, with tracing wrappers when diagnostics are enabled. +Reads stream events as they arrive from the server — a read holds at most one deserialized event at a time regardless of the requested count. To read a whole stream, use the `ReadStreamToEnd` extension method instead of `ReadEvents` with `int.MaxValue`. + The legacy class `EsdbEventStore` is obsolete -- use `KurrentDBEventStore` instead. ## Subscriptions diff --git a/skills/eventuous-dotnet/SKILL.md b/skills/eventuous-dotnet/SKILL.md index f86d921..ee01620 100644 --- a/skills/eventuous-dotnet/SKILL.md +++ b/skills/eventuous-dotnet/SKILL.md @@ -250,6 +250,22 @@ Extracting ID from stream name (useful in projections): `ctx.Stream.GetId()`. --- +## Reading Event Streams + +`IEventReader.ReadEvents`/`ReadEventsBackwards` return `IAsyncEnumerable` and read a fixed number of events. `KurrentDBEventStore` streams events as they arrive (holds one deserialized event at a time); relational stores buffer up to `count` events per call, so keep the count bounded. + +To read a whole stream, use the `ReadStreamToEnd` extension method — never `ReadEvents` with `int.MaxValue` as the count: + +```csharp +await foreach (var evt in eventReader.ReadStreamToEnd(streamName, StreamReadPosition.Start, cancellationToken: ct)) { + // One event at a time, memory bounded by page size (default 500) +} +``` + +Options: `pageSize` tunes the page size; `failIfNotFound: false` yields nothing instead of throwing `StreamNotFound`. The `ReadStream` extension method does the same paged read and returns `StreamEvent[]` if you need the whole stream as an array. + +--- + ## HTTP API ### Controller-Based