Postboy Help

Decision & Cancellation Reference

Summary

This page defines the exact meaning of middleware decisions (Continue / Interrupt) and what “cancellation” means in Postboy middleware terms: when it happens, what gets executed (and what does not), and what diagnostic information you can expect.

This is a reference page. For motivation and patterns, see:

  • Middleware: Interrupt / Cancel (concept)

  • How-to: Block execution (guard middleware)

Applies to

  • Middleware functionality available since Postboy v3.5.

MiddlewareDecisionType

Enum values

  • MiddlewareDecisionType.Continue

  • MiddlewareDecisionType.Interrupt

Meaning

Value

Meaning

Effect on operation

Continue

Allow processing to proceed

Next middleware runs, then the stage operation executes

Interrupt

Stop processing immediately

Operation is cancelled before handlers/subscribers run

MiddlewareDecision (return type of before)

Middleware before(context) returns a MiddlewareDecision object.

Required fields

Field

Type

Required

Notes

type

MiddlewareDecisionType

yes

Continue or Interrupt

Even if the library’s minimal contract is just type, in practice you often want a stable policy identifier and a human-readable reason. If your project exposes those via the cancellation error, keep them:

  • short

  • searchable

  • safe to log

What counts as “cancellation”

An operation is considered “cancelled by middleware” when:

  • a middleware runs before(context), and

  • it returns a decision with type = Interrupt.

Cancellation is a control-flow stop at the bus boundary.

Non-cancellation cases (important)

These are not “middleware cancellation”:

  • a handler throws an error during execution

  • a callback result completes with an error later

  • a subscriber unsubscribes

  • an operation is not executed because nothing is registered (if applicable in your setup)

Those are failures or runtime conditions, but not middleware-driven cancellation.

Cancellation semantics (exact effects)

1) Cancellation happens before the stage operation

If any middleware interrupts in before:

  • the stage operation does not run

    • Publish: message is not broadcast

    • Callback: request is not delivered

    • Execute: handler/function is not invoked

2) Cancellation is immediate and chain-short-circuited

  • The first middleware that interrupts stops the chain.

  • No later middleware is evaluated for that operation.

3) after() is not executed for that operation

Because the stage operation never runs, the post phase is not reached:

  • after(context, result?) is not called for any middleware for that operation.

4) A cancellation error is raised

Postboy raises a cancellation error containing diagnostic metadata:

  • stage

  • message id (if available)

  • middleware name (the one that cancelled)

  • optional reason text

Use this error for:

  • debugging

  • audit logging

  • mapping to a user-facing “forbidden/blocked” response (in your app layer)

Ordering rules (deterministic precedence)

Middleware order is deterministic:

  • evaluated in registration order

Precedence rule:

  • the earliest middleware in the chain that returns Interrupt “wins”

  • its name/reason becomes the cancellation identity

This matters when you have multiple policies. If precedence is important, document the ordering.

Stage-specific notes (cancellation impact)

Publish stage

  • Interrupt prevents broadcasting.

  • Recommended usage: rare; prefer guarding the cause (commands/executors) instead of blocking events.

Callback stage

  • Interrupt prevents delivering the request to subscribers.

  • Caller receives cancellation through the callback call boundary (often as an error in the returned stream).

Execute stage

  • Interrupt prevents running the executor handler/function.

  • Caller receives a cancellation error immediately (synchronous boundary).

Reference matrix: which hooks run on cancellation?

Phase

Hook

Runs if canHandle is false?

Runs if decision is Interrupt?

Filter

canHandle

yes (it is the filter)

not applicable

Pre

before

no

yes (this is where Interrupt occurs)

Operation

handler/subscribers

not applicable

no

Post

after

no

no

Cleanup

dispose

not related to a single operation

not related to a single operation

Key takeaway:

  • Interrupt affects only the current operation.

  • Dispose is lifecycle-level, not operation-level.

When handling or troubleshooting cancellations, record:

  • stage

  • message id

  • message type/category (stable)

  • cancelled-by middleware name

  • reason (if present)

Do not put in logs/metrics:

  • secrets

  • PII

  • high-cardinality identifiers as metric labels (message id is fine for logs, not for metrics)

Diagram: cancellation short-circuits the chain

@startuml title Cancellation by middleware Interrupt (short-circuit)

participant "Postboy" as Bus participant "Middleware A" as A participant "Middleware B" as B participant "Handler/Subscribers" as H

Bus -> A: canHandle? (true) Bus -> A: before() -> Interrupt note over A: decides to cancel Bus --> Bus: raise cancellation error note over B: B is not evaluated note over H: operation never runs @enduml

Troubleshooting quick lookup

“Why do I not see after() logs?”

Most common cause:

  • the operation was cancelled by an earlier middleware (Interrupt), so after() never ran.

Next step:

  • log attempt in before()

  • ensure logging middleware is placed before guards if you want to see cancelled attempts

“Which middleware cancelled?”

Use cancellation error metadata:

  • middleware name

  • stage

  • message id

If metadata is missing or ambiguous:

  • set explicit middleware names

  • keep one-policy-per-middleware to improve diagnostics

Next steps

  • [Middleware API reference]

  • [Interrupt / Cancel (concept page)]

  • [How-to: block execution (guard middleware)]

  • [How-to: logging/tracing including cancelled attempts]

29 июня 2026