Middleware Cancellation
A middleware cancels an operation by returning { type: MiddlewareDecisionType.Interrupt } from its before(...) hook. The bus then throws a CancelError from fire, exec, or fireCallback: the operation does not run and after hooks are skipped. Use interruption for policy enforcement — read-only mode, feature flags, access control. Keep business validation that must produce typed responses inside handlers.
Returning Interrupt
What happens on interrupt
The operation does not run: subscribers are not notified (
Publish), the request is not delivered (Callback), the executor is not invoked (Execute).Middleware later in the chain is not evaluated for that operation — the first
Interruptwins.after(...)is skipped for every middleware, not only the one that interrupted.fire,exec, andfireCallbackthrow aCancelErrorsynchronously from the call itself.
The CancelError
error.details carries:
Field | Meaning |
|---|---|
| The stage whose |
|
|
| Static |
| Reserved for namespace attribution; not populated by the built-in pipeline |
| Generated by the pipeline: |
MiddlewareDecision carries only type — there is no custom reason field. The reason is derived from the middleware name, so pass a descriptive name to the constructor to make cancellations searchable in logs.
Catching CancelError at call sites
Catch the error where cancellation is an expected outcome, and re-throw everything else:
The error is thrown by the call itself, before anything is returned. For fireCallback, wrap the call — when the request is cancelled, the Observable is never created, so there is nothing to catch in the subscription.
Blocking by stage
Choose the stage deliberately in canHandle:
Execute— the natural place for hard guards: intentional commands with a synchronous boundary.Callback— request/response flows: reject disabled features or unauthorized requests before delivery.Publish— avoid. Events are facts that other components rely on; blocking them hides signals and causes inconsistencies. Guard the command or request that causes the event instead.
Scope guards narrowly. A broad canHandle cancels operations you did not intend to block, and every cancellation is visible to callers as a thrown error.
Pitfalls
Do not throw from
beforeto block an operation. A thrown error is an unexpected failure; interruption is a decision, and only a decision produces the structuredCancelError.afternever runs for a cancelled operation. Never allocate resources inbeforethat onlyafterwould release.Distinguish cancellation from failure before swallowing errors at call sites — check
error.name, not the message text.