feat(eventcore)!: streaming reads for read_stream (#364) #414
No reviewers
Labels
No labels
adr
automated
bug
chore
dependencies
documentation
enhancement
epic
github-actions
P1-high
P2-medium
P3-low
release
research
rust
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Slipstream/eventcore!414
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/364-streaming-reads"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Makes
EventStore::read_streamreturn a lazy async stream instead of a fully-materializedVec, so large streams are no longer all decoded into memory at once and the executor folds events as they arrive. Breaking change to theEventStoretrait (intentional, pre-1.0 — see ADR-0049 for the decision rationale: a clean stream return type over a lazyEventStreamReaderwrapper that would secretly buffer forlen()/first()).API
read_stream<E>(id) -> Result<EventStream<E>, EventStoreError>, whereEventStream<E>is a named newtype overPin<Box<dyn Stream<Item = Result<E, EventStoreError>> + Send>>and implementsStream. Events are yielded oldest→newest; per-event decode failures surface asErritems (preserving theread_stream_errors_on_type_mismatchbehavior).EventStreamReaderis removed; a free async helpercollect_events(stream) -> Result<Vec<E>, _>(re-exported fromeventcoreandeventcore-types) covers the "I want them all" case.Executor: incremental fold (the actual memory win)
The execute pipeline is refactored to a push model: the async shell (
pump_stream_reads) pulls one event at a time and pushes each into the pipeline viaStoreEffectResult::StreamEvent, which folds it into command state and increments a counter;StreamEndedsetsexpected_versionto the streamed count (== the oldreader.len()) and runs dynamic stream discovery. The shell never buffers the whole stream into aVec; the fold stays in the pipeline (which owns the command). Multi-stream handling, retries, and error propagation are preserved.Backends
sqlxquery(...).fetch(pool)over a cloned pool (real streaming win).spawn_blocking, streamed over a boundedtokio::sync::mpscchannel (incremental + backpressure).Tests / verification
cargo clippy --workspace --all-targets --all-features -- -D warningscleancargo nextest run --workspace— 275 tests pass (all four backend contract suites incl. type-mismatch, executor push-fold + per-event-error tests,collect_eventstests, lib.rs mock stores).ADR-0049 records the decision;
event-sourcing,store-backends, andcommand-executionblueprints updated.Closes #364
This PR introduces a significant change to the
EventStore::read_streammethod, transitioning it to return a lazy async stream instead of a fully-materializedVec. This change aims to improve memory efficiency by processing events incrementally. The update is a breaking change, but it aligns with pre-1.0 expectations and is well-documented in ADR-0049.Walkthrough
EventStreamReader.collect_eventswhere necessary.LLM usage and cost
Estimated total USD: $0.139198 via https://api.openai.com and https://api.openai.com