Postboy Help

Error Handling & Result Semantics

Postboy executes the logic you register, but it does not own your application’s error policy. The bus is not an error boundary. It is your code’s responsibility to decide how failures are represented and communicated back to the caller.

1. The bus is not an error boundary

When an executor’s handler throws an exception, Postboy does not catch it. The exception propagates directly to the caller, exactly as if you called the function yourself.

// If this handler throws, the caller sees the exception immediately. postboy.exec(new ConnectExecutor(SomeExecutor, (executor) => { throw new Error('Invalid input'); }));

This behaviour is intentional. Postboy stays focused on execution coordination, not on domain failure translation.

2. Two strategies for handling failures

You can choose between two main strategies, depending on the nature of the failure.

2.1 Throw an exception (fast‑fail)

Use exceptions when the failure is exceptional and the caller cannot reasonably proceed.

export class ValidateDiscountExecutor extends PostboyExecutor<boolean> { static readonly ID = 'discount.validate'; constructor(public readonly code: string) { super(); } } postboy.exec(new ConnectExecutor(ValidateDiscountExecutor, (executor) => { if (!executor.code) { throw new Error('Discount code is required'); } return discountService.isValid(executor.code); })); // Caller handles the error try { const valid = postboy.exec(new ValidateDiscountExecutor('')); } catch (e) { console.error(e.message); }

2.2 Return an error‑shaped result

When failure is a normal part of the domain (e.g., validation rejection, not‑found), model the result as a discriminated union.

type RefreshResult = | { success: true } | { success: false; reason: string }; export class RefreshCacheExecutor extends PostboyExecutor<RefreshResult> { static readonly ID = 'cache.refresh'; constructor(public readonly scope: string) { super(); } } postboy.exec(new ConnectExecutor(RefreshCacheExecutor, (executor) => { try { const ok = cacheService.refresh(executor.scope); return {success: ok}; } catch (e) { return {success: false, reason: e.message}; } })); // Caller inspects the result const result = postboy.exec(new RefreshCacheExecutor('users')); if (!result.success) { console.log(result.reason); }

3. Choosing the right strategy

Strategy

When to use

Throw exception

Failure is truly exceptional; the caller should stop the flow.

Error‑shaped result

Failure is expected and part of the domain contract.

4. Key points

  • Postboy does not catch or interpret exceptions. They belong to your application code.

  • The handler decides how to represent a failure; the caller decides how to react.

  • For domain failures that clients must handle, prefer an explicit result type over exceptions. This makes the contract self‑documenting and testable.

24 мая 2026