refactor: extract pure state machines from execute() and run() #349

Merged
jwilger-ai-bot merged 1 commit from refactor/internal-state-machines into main 2026-04-11 15:52:02 -07:00
jwilger-ai-bot commented 2026-04-11 15:46:23 -07:00 (Migrated from github.com)

Summary

Extracts the core logic of execute() and ProjectionRunner::run() into pure state machines that yield effects, with thin shell loops dispatching effects to backend traits.

  • ExecutePipeline: Pure state machine encapsulating stream resolution, state reconstruction, handle(), event appending, and retry-on-conflict. Yields StoreEffect (ReadStream, AppendEvents, Sleep) and accepts StoreEffectResult.
  • ProjectionPipeline: Pure state machine encapsulating checkpoint loading, event polling, event application with retry/skip/fatal strategies, and checkpoint saving. Yields ProjectionEffect (ReadEvents, LoadCheckpoint, SaveCheckpoint, Sleep).
  • execute() and run() become ~20-line shell loops that dispatch effects to EventStore/EventReader/CheckpointStore traits.

Benefits

  • State machine transitions are unit-testable without real stores (3 pipeline unit tests included)
  • Retry logic, error handling, and backoff are testable in isolation
  • Shell loops are trivially correct — they just match effects and call trait methods

No Breaking Changes

  • execute() signature unchanged: Result<ExecutionResponse, CommandError>
  • CommandLogic trait unchanged
  • ProjectionRunner::run() signature unchanged
  • All 209 existing tests pass

This is the internal refactor portion of #299, extracted after closing #348 (caller-driven effects were abandoned in favor of pure commands with pre-calculated inputs).

Test plan

  • All 209 existing tests pass unchanged
  • 3 new ExecutePipeline unit tests (success path, retry on conflict, business rule error)
  • Clippy clean, fmt clean
  • All pre-commit hooks pass

🤖 Generated with Claude Code

## Summary Extracts the core logic of `execute()` and `ProjectionRunner::run()` into pure state machines that yield effects, with thin shell loops dispatching effects to backend traits. - **`ExecutePipeline`**: Pure state machine encapsulating stream resolution, state reconstruction, handle(), event appending, and retry-on-conflict. Yields `StoreEffect` (ReadStream, AppendEvents, Sleep) and accepts `StoreEffectResult`. - **`ProjectionPipeline`**: Pure state machine encapsulating checkpoint loading, event polling, event application with retry/skip/fatal strategies, and checkpoint saving. Yields `ProjectionEffect` (ReadEvents, LoadCheckpoint, SaveCheckpoint, Sleep). - `execute()` and `run()` become ~20-line shell loops that dispatch effects to `EventStore`/`EventReader`/`CheckpointStore` traits. ### Benefits - State machine transitions are unit-testable without real stores (3 pipeline unit tests included) - Retry logic, error handling, and backoff are testable in isolation - Shell loops are trivially correct — they just match effects and call trait methods ### No Breaking Changes - `execute()` signature unchanged: `Result<ExecutionResponse, CommandError>` - `CommandLogic` trait unchanged - `ProjectionRunner::run()` signature unchanged - All 209 existing tests pass This is the internal refactor portion of #299, extracted after closing #348 (caller-driven effects were abandoned in favor of pure commands with pre-calculated inputs). ## Test plan - [x] All 209 existing tests pass unchanged - [x] 3 new ExecutePipeline unit tests (success path, retry on conflict, business rule error) - [x] Clippy clean, fmt clean - [x] All pre-commit hooks pass 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jwilger (Migrated from github.com) approved these changes 2026-04-11 15:48:41 -07:00
Sign in to join this conversation.
No description provided.