Skip to content

docs: improve persistence pages with examples and state store#517

Merged
alexeyzimarev merged 2 commits intodevfrom
docs/persistence-pages-update
Mar 10, 2026
Merged

docs: improve persistence pages with examples and state store#517
alexeyzimarev merged 2 commits intodevfrom
docs/persistence-pages-update

Conversation

@alexeyzimarev
Copy link
Contributor

Summary

  • Event store page: added usage examples covering appending events, multi-stream append, reading events, stream management (StreamExists, TruncateStream, DeleteStream), and aggregate load/store via extension methods
  • State store page (new): documents LoadState extension methods on IEventReader — loading state by stream name, loading by typed identity with StreamNameMap, the FoldedEventStream<TState> return type, and how it underpins both command service variants
  • Event streams page (renamed from "Aggregate stream"): rewritten to be state-centric — covers StreamName.For<T> (unconstrained), StreamName.ForState<TState>, uses Id instead of deprecated AggregateId, notes applicability to both aggregate-based and functional command services
  • Fixed relative links in next/ version that incorrectly escaped the next/ directory

All changes applied to both v0.16 (stable) and next (preview).

Test plan

  • pnpm build passes (140 pages)
  • Spot-check rendered pages in both version tabs

🤖 Generated with Claude Code

…event streams

- Add usage examples to event-store.md: appending, multi-stream append, reading,
  stream management, and aggregate load/store
- Add new state-store.md page documenting LoadState extension methods on IEventReader
- Rename "Aggregate stream" to "Event streams" and rewrite to be state-centric
  rather than aggregate-centric (StreamName.For<T>, StreamName.ForState<TState>,
  Id instead of deprecated AggregateId)
- Fix relative links in next/ version that incorrectly escaped the next/ directory
- Apply changes to both v0.16 (stable) and next (preview) versions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@qodo-free-for-open-source-projects
Copy link
Contributor

Review Summary by Qodo

Enhance persistence documentation with examples and state store guide

📝 Documentation

Grey Divider

Walkthroughs

Description
• Renamed "Aggregate stream" to "Event streams" with state-centric focus
• Added comprehensive usage examples to event store documentation
• Created new state store page documenting LoadState extension methods
• Updated identity references from AggregateId to Id throughout
• Applied all changes to both v0.16 stable and next preview versions
Diagram
flowchart LR
  A["Event Streams Page"] -- "renamed & rewritten" --> B["State-centric focus"]
  C["Event Store Page"] -- "added examples" --> D["Append/Read/Manage operations"]
  E["New State Store Page"] -- "documents" --> F["LoadState extension methods"]
  B --> G["StreamName.For & ForState"]
  D --> H["Multi-stream append & management"]
  F --> I["FoldedEventStream return type"]
Loading

Grey Divider

File Changes

1. docs/src/content/docs/next/persistence/aggregate-stream.md 📝 Documentation +55/-46

Rewrite aggregate stream page to state-centric model

• Renamed page title from "Aggregate stream" to "Event streams"
• Rewrote concept section to be state-centric rather than aggregate-centric
• Updated stream name examples to use StreamName.For<T> and StreamName.ForState<TState>
• Changed identity base class references from AggregateId to Id
• Reorganized custom stream names section with clearer explanation and examples
• Added new "Extracting identity from stream names" section with extension method example
• Moved extension method code before its usage in projection handler

docs/src/content/docs/next/persistence/aggregate-stream.md


2. docs/src/content/docs/next/persistence/event-store.md 📝 Documentation +157/-15

Add extensive usage examples and stream management docs

• Changed sidebar order from 2 to 5 to accommodate new state store page
• Added comprehensive "Usage examples" section with code samples
• Documented appending events with optimistic concurrency control
• Added multi-stream append examples with atomicity guarantees table
• Included reading events with pagination examples
• Added stream management operations (StreamExists, TruncateStream, DeleteStream)
• Documented aggregate load/store extension methods
• Expanded operations table to include all six operations
• Minor grammar improvements (em dash usage, punctuation)

docs/src/content/docs/next/persistence/event-store.md


3. docs/src/content/docs/next/persistence/state-store.md 📝 Documentation +86/-0

Create new state store documentation page

• Created new documentation page for state store functionality
• Documented LoadState extension method on IEventReader
• Provided examples for loading state by stream name and by identity
• Documented FoldedEventStream<TState> return type with property table
• Explained how LoadState works internally with event folding
• Clarified relationship to both aggregate-based and functional command services
• Set sidebar order to 7 to position after event store

docs/src/content/docs/next/persistence/state-store.md


View more (3)
4. docs/src/content/docs/persistence/aggregate-stream.md 📝 Documentation +55/-46

Rewrite aggregate stream page to state-centric model

• Renamed page title from "Aggregate stream" to "Event streams"
• Rewrote concept section to be state-centric rather than aggregate-centric
• Updated stream name examples to use StreamName.For<T> and StreamName.ForState<TState>
• Changed identity base class references from AggregateId to Id
• Reorganized custom stream names section with clearer explanation and examples
• Added new "Extracting identity from stream names" section with extension method example
• Moved extension method code before its usage in projection handler

docs/src/content/docs/persistence/aggregate-stream.md


5. docs/src/content/docs/persistence/event-store.md 📝 Documentation +130/-9

Add extensive usage examples and stream management docs

• Changed sidebar order from 2 to 5 to accommodate new state store page
• Added comprehensive "Usage examples" section with code samples
• Documented appending events with optimistic concurrency control
• Added multi-stream append examples with atomicity guarantees table
• Included reading events with pagination examples
• Added stream management operations (StreamExists, TruncateStream, DeleteStream)
• Documented aggregate load/store extension methods
• Expanded operations table to include all six operations
• Minor grammar improvements (em dash usage, punctuation)

docs/src/content/docs/persistence/event-store.md


6. docs/src/content/docs/persistence/state-store.md 📝 Documentation +86/-0

Create new state store documentation page

• Created new documentation page for state store functionality
• Documented LoadState extension method on IEventReader
• Provided examples for loading state by stream name and by identity
• Documented FoldedEventStream<TState> return type with property table
• Explained how LoadState works internally with event folding
• Clarified relationship to both aggregate-based and functional command services
• Set sidebar order to 7 to position after event store

docs/src/content/docs/persistence/state-store.md


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Contributor

qodo-free-for-open-source-projects bot commented Mar 10, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (2) 📎 Requirement gaps (0)

Grey Divider


Action required

1. BookingId examples mismatch 🐞 Bug ✓ Correctness
Description
The state-store page shows new BookingId("123"), but the event-streams page defines BookingId
with a (string id, string tenantId) constructor; combining these docs examples won’t compile. This
breaks copy/paste learning paths across the new persistence pages.
Code

docs/src/content/docs/next/persistence/state-store.md[R45-49]

+var result = await eventReader.LoadState<BookingState, BookingId>(
+    streamNameMap,
+    new BookingId("123"),
+    cancellationToken: cancellationToken
+);
Evidence
Within the same documentation set, BookingId is defined as requiring two constructor arguments,
but another page instantiates it with one argument.

docs/src/content/docs/next/persistence/state-store.md[45-49]
docs/src/content/docs/next/persistence/aggregate-stream.md[38-45]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`BookingId` is shown with a 2-arg constructor in the event-streams doc, but instantiated with a 1-arg call in the state-store doc. Readers combining the examples will hit a compile error.

### Issue Context
These pages are meant to be used together; identity examples should be consistent.

### Fix Focus Areas
- docs/src/content/docs/next/persistence/state-store.md[40-52]
- docs/src/content/docs/persistence/state-store.md[40-52]
- docs/src/content/docs/next/persistence/aggregate-stream.md[38-46]
- docs/src/content/docs/persistence/aggregate-stream.md[38-46]

### Suggested change options
Option A (recommended): In state-store.md, use the same multi-tenant identity shape:
- Change `new BookingId(&quot;123&quot;)` to `new BookingId(&quot;123&quot;, &quot;tenant-1&quot;)` (and optionally add a comment explaining it).

Option B: Keep state-store.md single-arg identity and adjust aggregate-stream.md to show a simple `BookingId(string value) : Id(value)` example instead, moving multi-tenant identity to a separate explicitly-labeled example.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Await lacks .NoContext() 📘 Rule violation ⛯ Reliability
Description
The new documentation code samples use await without chaining .NoContext(), which conflicts with
the repository convention for deadlock-resistant async usage. This may lead readers to copy patterns
that don't follow the required ConfigureAwait(false) approach.
Code

docs/src/content/docs/next/persistence/event-store.md[56]

+var result = await eventStore.AppendEvents(
Evidence
PR Compliance ID 1 requires using async APIs and applying .NoContext() on awaited tasks where
expected; the updated docs introduce multiple await examples that do not use .NoContext().

CLAUDE.md
docs/src/content/docs/next/persistence/event-store.md[56-56]
docs/src/content/docs/persistence/event-store.md[56-56]
docs/src/content/docs/next/persistence/state-store.md[15-15]
docs/src/content/docs/persistence/state-store.md[15-15]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Documentation examples introduce new `await` calls without using the repository&#x27;s `.NoContext()` helper (ConfigureAwait(false) convention).

## Issue Context
Per PR Compliance ID 1, awaited tasks in library-style code paths are expected to use `.NoContext()` to avoid capturing synchronization context.

## Fix Focus Areas
- docs/src/content/docs/next/persistence/event-store.md[56-179]
- docs/src/content/docs/persistence/event-store.md[56-179]
- docs/src/content/docs/next/persistence/state-store.md[15-77]
- docs/src/content/docs/persistence/state-store.md[15-77]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Events lack TypeMap registration 📘 Rule violation ⛯ Reliability
Description
The new persistence examples introduce event types (e.g., OrderCreated, OrderConfirmed) without
showing or reminding readers to register them in TypeMap. This can lead to runtime
serialization/deserialization failures when readers follow the examples.
Code

docs/src/content/docs/next/persistence/event-store.md[R50-53]

+var events = new[] {
+    new NewStreamEvent(Guid.NewGuid(), new OrderCreated("123", 99.99m), new Metadata()),
+    new NewStreamEvent(Guid.NewGuid(), new OrderConfirmed("123"), new Metadata())
+};
Evidence
PR Compliance ID 3 requires event types used for persistence/transport to be registered in
TypeMap; the new docs examples use custom event types without accompanying TypeMap registration
guidance.

CLAUDE.md
docs/src/content/docs/next/persistence/event-store.md[50-53]
docs/src/content/docs/persistence/event-store.md[50-53]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Docs show persisting/reading events with custom event types but omit the required `TypeMap` registration guidance.

## Issue Context
Per PR Compliance ID 3, event types must be registered in `TypeMap` to ensure serialization/deserialization works at runtime.

## Fix Focus Areas
- docs/src/content/docs/next/persistence/event-store.md[43-104]
- docs/src/content/docs/persistence/event-store.md[43-104]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Misstated stream naming basis 🐞 Bug ✓ Correctness
Description
The event-streams page states that both aggregate-based and functional command services derive
stream names from the state type, but the aggregate command service’s default convention derives
from the aggregate type name. This is misleading and can cause readers to reason about/debug stream
names incorrectly (especially when aggregate and state type names diverge).
Code

docs/src/content/docs/next/persistence/aggregate-stream.md[R12-14]

+When appending events to a stream, the append operation for a single stream must be transactional to ensure consistency. Eventuous handles commands using the [command service](../../application/app-service), and one command handler is the unit of work. All the events produced during the unit of work are appended to the stream as the final step in the command handling process.

-When appending events to a stream, the append operation for a single stream must be transactional to ensure that the stream is consistent. Eventuous handles commands using the [command service](../../application/app-service), and one command handler is the unit of work. All the events generated by the aggregate instance during the unit of work are appended to the stream as the final step in the command handling process.
+This applies equally to both the aggregate-based and the [functional](../../application/func-service) command service — both ultimately store events against a stream derived from the entity's state type and identity.
Evidence
The docs claim stream naming is based on the state type for both services, but the aggregate command
service gets the stream name via StreamNameMap which falls back to
StreamNameFactory.For<TAggregate,...>() using the aggregate type name.

docs/src/content/docs/next/persistence/aggregate-stream.md[12-14]
src/Core/src/Eventuous.Application/AggregateService/CommandService.cs[72-75]
src/Core/src/Eventuous.Persistence/StreamNameMap.cs[31-34]
src/Core/src/Eventuous.Persistence/StreamNameFactory.cs[6-9]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The docs claim both command service variants derive stream names from the *state type*, but the aggregate-based command service’s default stream naming derives from the *aggregate type name*.

### Issue Context
This is a documentation correctness issue that can confuse users (or mislead debugging) when aggregate and state type names are not aligned.

### Fix Focus Areas
- docs/src/content/docs/next/persistence/aggregate-stream.md[12-14]
- docs/src/content/docs/persistence/aggregate-stream.md[12-14]

### Suggested change
Reword the sentence to distinguish:
- Aggregate command service: default stream name prefix comes from `TAggregate` (aggregate type)
- Functional command service: stream name prefix commonly comes from `TState` (state type), e.g. via `StreamName.ForState&lt;TState&gt;`

Optionally add a brief note that in common naming conventions (`Booking` + `BookingState`) both yield the same prefix (`Booking-`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
5. AppendEvents overloads unclear 🐞 Bug ✓ Correctness
Description
The event-store operations table lists two identical AppendEvents rows, making the single-stream
vs multi-stream overloads indistinguishable. This degrades discoverability and can lead readers to
miss the multi-stream API entirely.
Code

docs/src/content/docs/next/persistence/event-store.md[R32-36]

+| Function         | What's it for                                                    |
+|------------------|------------------------------------------------------------------|
+| `AppendEvents`   | Append one or more events to a given stream.                     |
+| `AppendEvents`   | Append events to multiple streams in a single operation.         |
+| `ReadEvents`     | Read events from a stream forwards, from a given start position. |
Evidence
The docs table shows two AppendEvents entries without differentiating the overloads, while the API
distinguishes them by signature (single-stream vs multi-stream).

docs/src/content/docs/next/persistence/event-store.md[30-36]
src/Core/src/Eventuous.Persistence/EventStore/IEventWriter.cs[18-47]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The operations table lists `AppendEvents` twice without distinguishing the overloads, making it unclear which is the multi-stream append.

### Issue Context
The API has two overloads with the same method name but different parameter lists; documentation should clearly label them.

### Fix Focus Areas
- docs/src/content/docs/next/persistence/event-store.md[30-39]
- docs/src/content/docs/persistence/event-store.md[30-39]

### Suggested change
Update the table rows to something like:
- `AppendEvents` (single-stream)
- `AppendEvents` (multi-stream)

Or:
- `AppendEvents(StreamName, ExpectedStreamVersion, ...)`
- `AppendEvents(IReadOnlyCollection&lt;NewStreamAppend&gt;, ...)`

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Mar 10, 2026

Deploying eventuous-main with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8eedb32
Status:⚡️  Build in progress...

View logs

Comment on lines +45 to +49
var result = await eventReader.LoadState<BookingState, BookingId>(
streamNameMap,
new BookingId("123"),
cancellationToken: cancellationToken
);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Bookingid examples mismatch 🐞 Bug ✓ Correctness

The state-store page shows new BookingId("123"), but the event-streams page defines BookingId
with a (string id, string tenantId) constructor; combining these docs examples won’t compile. This
breaks copy/paste learning paths across the new persistence pages.
Agent Prompt
### Issue description
`BookingId` is shown with a 2-arg constructor in the event-streams doc, but instantiated with a 1-arg call in the state-store doc. Readers combining the examples will hit a compile error.

### Issue Context
These pages are meant to be used together; identity examples should be consistent.

### Fix Focus Areas
- docs/src/content/docs/next/persistence/state-store.md[40-52]
- docs/src/content/docs/persistence/state-store.md[40-52]
- docs/src/content/docs/next/persistence/aggregate-stream.md[38-46]
- docs/src/content/docs/persistence/aggregate-stream.md[38-46]

### Suggested change options
Option A (recommended): In state-store.md, use the same multi-tenant identity shape:
- Change `new BookingId("123")` to `new BookingId("123", "tenant-1")` (and optionally add a comment explaining it).

Option B: Keep state-store.md single-arg identity and adjust aggregate-stream.md to show a simple `BookingId(string value) : Id(value)` example instead, moving multi-tenant identity to a separate explicitly-labeled example.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bca2c71e9f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

| `ReadEvents` | Read events from a stream forwards, from a given start position. |
| `StreamExists` | Check if a stream exists. |
| `TruncateStream` | Remove events from a stream up to a given position. |
| `DeleteStream` | Delete a stream entirely. |

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Mark DeleteStream as unsupported on SQL-backed stores

This row documents DeleteStream as a normal event-store operation, but the SQL stores listed as supported on the same page (PostgresStore, SqlServerStore, SqliteStore) inherit SqlEventStoreBase.DeleteStream, which currently throws NotImplementedException (src/Relational/src/Eventuous.Sql.Base/SqlEventStoreBase.cs:253-254) and do not override it. Users following this guidance on those backends will hit runtime failures, so the docs should call out that DeleteStream is store-specific (or not yet available for SQL stores).

Useful? React with 👍 / 👎.

@alexeyzimarev alexeyzimarev merged commit 6f954ce into dev Mar 10, 2026
1 of 2 checks passed
@alexeyzimarev alexeyzimarev deleted the docs/persistence-pages-update branch March 10, 2026 14:37
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