Postboy Help

Executor Overview & Core Concepts

An Executor in Postboy is a typed, synchronous operation that runs directly through the bus and produces an immediate result.

Executors are not broadcasts and not request/response flows. They are explicit actions where the caller expects a value back right away.

1. The executor contract

Every executor has three parts:

  • Identity — a unique identifier (static ID) that tells the bus which operation to run.

  • Input — data passed via the constructor.

  • Output — a typed result defined by the generic parameter T.

import {PostboyExecutor} from '@artstesh/postboy'; export class FormatDateExecutor extends PostboyExecutor<string> { static readonly ID = 'date.format'; constructor(public readonly date: Date) { super(); } }

The contract is self‑contained: you can read the class and know exactly what the operation does and what it returns.

2. How executors differ from other message types

Type

Base class

Returns

Semantics

Query

PostboyCallbackMessage<T>

Always

“Give me data”

Command

PostboyCallbackMessage<T>

Optional

“Do something; maybe return a result”

Event

PostboyGenericMessage

Never

“Something happened”

Executor

PostboyExecutor<T>

Always

“Run this operation now and give me the result”

Executors are always synchronous from the caller’s perspective: postboy.exec(executor) returns the result directly.

3. Typical use‑cases

3.1 Synchronous domain logic

Data transformation, validation or computation where the logic lives inside the handler.

export class ValidateDiscountExecutor extends PostboyExecutor<boolean> { static readonly ID = 'discount.validate'; constructor(public readonly code: string) { super(); } }

3.2 Infrastructure operations

Postboy itself uses executors for all bus management tasks.

// Connecting a new message type postboy.exec(new ConnectMessage(OrderPlacedEvent, new Subject<OrderPlacedEvent>())); // Registering a handler for a callback message postboy.exec(new ConnectHandler(GetUserQuery, new GetUserQueryHandler()));

These are still executors — they run directly and return a result (void or a control object).

3.3 Mapping / formatting

Lightweight transformations that don’t need an HTTP call.

export class ProductToViewModelExecutor extends PostboyExecutor<ProductVM> { static readonly ID = 'product.to-vm'; constructor(public readonly product: Product) { super(); } }

4. Registration and execution

An executor is useless until its logic is registered. There are two models:

4.1 Direct function registration

postboy.exec(new ConnectExecutor(FormatDateExecutor, (executor) => { return Intl.DateTimeFormat('en-US').format(executor.date); }));

4.2 Handler‑based registration

class FormatDateHandler extends PostboyExecutionHandler<string, FormatDateExecutor> { handle(executor: FormatDateExecutor): string { return Intl.DateTimeFormat('en-US').format(executor.date); } } postboy.exec(new ConnectHandler(FormatDateExecutor, new FormatDateHandler()));

Both approaches are supported; choose the one that keeps your codebase clean.

4.3 Calling an executor

Once registered, fire the executor and get the result immediately.

const formatted = postboy.exec(new FormatDateExecutor(new Date())); console.log(formatted); // "4/23/2026"

postboy.exec(...) is synchronous and returns T.

5. Middleware

Executors pass through the same middleware pipeline as other messages.
You can observe, trace, or block execution via beforeExecute/afterExecute hooks without touching business logic.

6. Summary

  • An executor is a typed, synchronous operation.

  • It always returns a result — no fire‑and‑forget.

  • Use it for domain logic that fits a request/instant‑response pattern, and for all bus‑level configuration.

  • Registration is simple, with two models to match complexity.

  • The contract keeps the API explicit and easy to test.

03 мая 2026