Postboy Help

Cookbook

A cookbook of the most common postboy patterns — copy, paste, adjust. For full walkthrough applications see the ecosystem guides: Angular, React, Vue; for testing recipes see Testing recipes.

All snippets assume an imported bus (import { ... } from '@artstesh/postboy') and messages declaring their own unique static readonly ID.

1. Fire-and-forget event

export class SectionSelectedEvent extends PostboyGenericMessage { static readonly ID = 'app.section.selected'; constructor(public sectionId: string) { super(); } } // register (registrator's _up), then: this.postboy.sub(SectionSelectedEvent) .subscribe((ev) => this.select(ev.sectionId)); this.postboy.fire(new SectionSelectedEvent('section-2'));

2. Request / response

export class GetItemsQuery extends PostboyCallbackMessage<Item[]> { static readonly ID = 'app.items.get'; constructor(public filter: FilterSettings) { super(); } } // responder this.postboy.sub(GetItemsQuery).subscribe((ev) => ev.finish(this.loadItems(ev.filter))); // requester this.postboy.fireCallback(new GetItemsQuery(filter), (items) => (this.items = items));

Intermediate results are possible: emit partial data with ev.next(partial) and complete with ev.finish(final). See Callbacks and Callback patterns.

3. Synchronous command

export class MultiplyExecutor extends PostboyExecutor<number> { static readonly ID = 'app.math.multiply'; constructor(public x: number, public y: number) { super(); } } // registration — the registrator's chainable method, not the deprecated PostboyService one this.recordExecutor(MultiplyExecutor, (e) => e.x * e.y); // usage — synchronous, returns the value directly const result = this.postboy.exec(new MultiplyExecutor(2, 5)); // 10

See Executors for the full command model.

4. State and history

// Current state — new subscribers immediately receive the latest value this.recordBehavior(ThemeEvent, new ThemeEvent({ mode: 'dark' })); // Short history — late subscribers receive the last search query this.recordReplay(SearchQueryEvent, 1);

The initial value of recordBehavior is a message instance. See Registration and the registrators reference.

5. A feature with lifecycle

export class FeatureRegistrator extends PostboyAbstractRegistrator { constructor(postboy: PostboyService, private service: FeatureService) { super(postboy); this.registerServices([service]); // service.up() runs after the registrations } protected _up(): void { this.recordSubject(FeatureStartedEvent); this.recordExecutor(FeatureCommand, (e) => this.service.run(e)); } } // mount this.registrator.up(); // unmount — disconnects everything recorded above and calls services' down() this.registrator.down();

6. A short-lived scope without a registrator class

const scope = this.postboy.exec(new AddNamespace('quick-scope')); scope.recordSubject(DialogOpenedEvent); // ... later, one call tears it all down: this.postboy.exec(new EliminateNamespace('quick-scope'));

See Namespaces.

7. Gating a command with middleware

class AdminOnlyMiddleware extends PostboyMiddleware { canHandle(context: PipelineContext): boolean { return context.message.id === AdminCommand.ID; } before(context: PipelineContext): MiddlewareDecision { return this.auth.isAdmin() ? { type: MiddlewareDecisionType.Continue } : { type: MiddlewareDecisionType.Interrupt }; } } this.postboy.exec(new AddMiddleware(new AdminOnlyMiddleware(this.auth)));

Call sites catch the structured CancelError where gating is expected. See Middleware and Middleware cancellation.

8. Muting a noisy stream

this.postboy.exec(new LockMessage(TelemetryEvent)); // silently skip delivery // ... heavy work ... this.postboy.exec(new UnlockMessage(TelemetryEvent));

9. Manual one-off registration

this.postboy.exec(new ConnectMessage(TemporaryEvent, new Subject<TemporaryEvent>())); // ... this.postboy.exec(new DisconnectMessage(TemporaryEvent.ID)); // completes the stream

10. A quick test

const world = new PostboyWorld(); const service = new MyService(world.postboy); // feed the recording mock world.given.executor(GetDataQuery, 42); service.doWork(); world.then.fired(DataLoadedEvent).once(); world.dispose();

See Testing quick start for the full Given/When/Then toolkit.

07 September 2026