Postboy Help

How It Works

What actually happens on fire, exec, and fireCallback — and why Postboy behaves the way it does. Understanding the two facts below prevents most real-world surprises.

Fact 1: routing is keyed by the static ID string

Registration stores a Subject (or an executor handler) under the message class's static readonly ID. Dispatch looks the ID up in that store. Class identity plays no role.

Consequences:

  • A class without its own static readonly ID fails the internal checkId with "<ClassName> should have a static ID field".

  • Inheriting a parent's ID makes two message types share one route — messages silently cross-talk between the types. Never do it (see Caveats).

  • Registering the same ID twice overwrites the first registration silently (with a console warning) — there is exactly one route per ID.

Fact 2: the bus is mutated only by infrastructure messages

Since v3.5 the service exposes no mutating convenience methods. Registration, locking, middleware, and namespaces are all performed by executing infrastructure messages:

postboy.exec(new ConnectMessage(OrderPlacedEvent, new Subject<OrderPlacedEvent>()));

Anatomy of a fire

  1. The message's ID is checked (checkId).

  2. The middleware pipeline's Publish stage runs: canHandlebefore for each applicable middleware. An Interrupt decision aborts here — the caller gets a CancelError and the subject is never touched (see Middleware Cancellation).

  3. The registered Subject receives next(message); every subscriber of postboy.sub(Type) gets the instance.

  4. after hooks run.

If the ID is not registered, fire throws. If the type is locked (exec(new LockMessage(Type))), the message is silently dropped — no error, no delivery, but middleware hooks still run. Mute, not error.

Anatomy of an exec

The Execute middleware stage runs, then the registered handler function is invoked synchronously; its return value is the exec result. await-ing it is always a mistake — for asynchronous work use a callback message. An unregistered executor throws.

Anatomy of a fireCallback

fireCallback(message, action?) returns an Observable<T> over the message's internal result subject. The responder completes it via next/finish:

  • With an action, the message is dispatched eagerly and action runs once per emitted value.

  • Without action, dispatch is lazy — the request is sent only when the returned Observable is subscribed.

  • Since 3.5.3, the message is dispatched at most once per fireCallback call; extra subscriptions to the returned Observable never re-send the request.

Details and patterns: Callbacks.

Teardown

DisconnectMessage completes the registered Subject — subscriber streams end. A registrator's down() sends one DisconnectMessage per recorded ID (Lifecycle Management). postboy.dispose() additionally eliminates every namespace, clears the store, and disposes all middleware.

Next steps

07 September 2026