angzarr_client.router.dispatch¶
Dispatch logic for the unified Router runtime.
In R6 this module provides only the command-handler dispatch path with a fresh state per call. State rebuild (R7), multi-handler merge (R8), seq incrementing (R9), rejection (R10), saga/PM/projector (R11-R13) land in subsequent rounds.
Attributes¶
Exceptions¶
gRPC-compatible dispatch error carrying a StatusCode. |
Functions¶
|
Dispatch a ContextualCommand to the matching @handles or @rejected method. |
|
Dispatch a |
|
Replay events on top of a base snapshot to compute final state. |
|
Dispatch a source event through registered saga handlers. |
Dispatch a trigger event through registered process-manager handlers. |
|
|
Fan out each event in |
|
Transform events via registered @upcaster handlers. |
Module Contents¶
- angzarr_client.router.dispatch.Factory¶
- exception angzarr_client.router.dispatch.DispatchError(code: grpc.StatusCode, details: str, *, error_code: str = '', extras: dict[str, Any] | None = None)¶
Bases:
grpc.RpcErrorgRPC-compatible dispatch error carrying a StatusCode.
Raised when a command cannot be routed (unknown type, unknown domain), when a request is malformed (missing command/page/cover), or for any other caller-facing violation. The gRPC servicer can read
.code()and forward the status to the client.Audit finding #59:
messageis a static string (no interpolation); runtime context like type URLs, domains, etc. ride inextras. The SCREAMING_SNAKEerror_codeis a stable cross-language identifier suitable for cucumber assertions.Initialize self. See help(type(self)) for accurate signature.
- error_code = ''¶
- code() grpc.StatusCode¶
- angzarr_client.router.dispatch.dispatch_command(factories: list[Factory], request: angzarr_client.proto.angzarr.ContextualCommand) angzarr_client.proto.angzarr.BusinessResponse¶
Dispatch a ContextualCommand to the matching @handles or @rejected method.
Command path: routes by
(domain, type_url). Audit #62: at most one handler per(domain, type_url)— duplicate registration is a build-time error (BuildErrorfromrouter/builder.py:115-138), so this loop matches at most one factory and returns at the first match. Mirrors Rustruntime.rs:43-108.Notification path: routes rejection compensation to
@rejectedhandlers; that path does fan out across every matching@rejectedset (sagas / multi-handler compensation legitimately broadcast).
- angzarr_client.router.dispatch.dispatch_fact(factories: list[Factory], request: Any) angzarr_client.proto.angzarr.EventBook¶
Dispatch a
FactRequestthrough @handles_fact methods.Audit #45. Walks each fact in
request.facts, finds the matching@handles_fact(EventType)method, rebuilds state fromrequest.prior_events, invokes the method, and concatenates emitted events into the returnedEventBook.Caller (the gRPC adapter) is responsible for gating on
CommandHandlerRouter.supports_handle_fact()first — when no handler declares any@handles_fact, the adapter returnsUNIMPLEMENTEDwithout invoking dispatch. Once dispatch IS invoked, individual fact events with no matching@handles_factare silently skipped (the framework returns whatever events the matching handlers emitted; the coordinator persists the original facts as-is via its own path).
- angzarr_client.router.dispatch.dispatch_replay(factories: list[Factory], request: Any) Any¶
Replay events on top of a base snapshot to compute final state.
Audit #45. Decodes the base snapshot’s state into the registered state type, applies
request.eventsthrough the aggregate’s@appliesmethods, and returns the resulting state wrapped in anAnyinside aReplayResponse.Caller (the gRPC adapter) is responsible for gating on
CommandHandlerRouter.supports_replay()first.Replay uses the FIRST registered handler’s state type and applier set — multi-handler routers are forbidden post-#62 so there is at most one. If somehow none is registered, returns an empty
ReplayResponse.
- angzarr_client.router.dispatch.dispatch_saga(factories: list[Factory], request: angzarr_client.proto.angzarr.SagaHandleRequest) angzarr_client.proto.angzarr.SagaResponse¶
Dispatch a source event through registered saga handlers.
The trigger is the last event in
request.source; sagas whose@saga(source=...)matches the source book’s domain AND whose@handles(Evt)matches the trigger’s proto type are all invoked in registration order. Emitted commands + fact books are merged.
- angzarr_client.router.dispatch.dispatch_process_manager(factories: list[Factory], request: Any) angzarr_client.proto.angzarr.ProcessManagerHandleResponse¶
Dispatch a trigger event through registered process-manager handlers.
Trigger is the last event in
request.trigger; PMs whosesourcesinclude the trigger’s domain AND whose@handles(Evt)matches the trigger type are invoked in registration order. Each PM rebuilds its own state fromrequest.process_statevia its@appliesmethods.ProcessManagerResponsereturns merged into a singleProcessManagerHandleResponse.
- angzarr_client.router.dispatch.dispatch_projector(factories: list[Factory], events: angzarr_client.proto.angzarr.EventBook) angzarr_client.proto.angzarr.Projection¶
Fan out each event in
eventsto matching @handles methods on all registered projectors that declare the source domain.Projector handlers are side-effect only — their return value is ignored. Returns a
Projectionwith the source cover copied through for framework bookkeeping.One handler instance is constructed per matching projector per dispatch call (via its factory) and reused across every event in the book, so a projector can accumulate state within a single projection run.
- angzarr_client.router.dispatch.dispatch_upcaster(factories: list[Factory], request: angzarr_client.proto.angzarr.UpcastRequest) angzarr_client.proto.angzarr.UpcastResponse¶
Transform events via registered @upcaster handlers.
Only handlers whose
@upcaster(domain=...)matchesrequest.domainare consulted. Every matching upcaster runs in registration order; the output of one is the input of the next — chained transforms let schema evolution compose across versions (V1 → V2 → V3) without forcing each upcaster to know about every newer version. The@upcasts(From, To)match is exact on the current event’s type_url, so an upcaster only fires when its From matches the incoming/transformed value. Events with no matching transform pass through unchanged.Audit finding #43 (cucumber C-0136 / C-0137): mirrors Rust’s
router/upcaster.rs::dispatchsemantics. Was previously first-match-wins (broke after first transform).