The Base Message Contract
Purpose
Every message in Postboy, regardless of its specific role, extends the abstract PostboyMessage class. This common foundation ensures that all messages share:
a stable identity the bus can rely on
mutable metadata for contextual enrichment
a predictable structure for middleware and subscribers
Understanding this base contract helps you see how the three message families fit together and why they can coexist within the same pipeline.
Public API
PostboyMessage defines a minimal but essential interface:
id: stringmetadata: PostboyMessageMetadatasetMetadata(metadata: Partial<PostboyMessageMetadata>): this
id
A unique string derived from the message class itself. The bus uses this identifier to resolve subscriptions, executors, and any middleware that cares about a specific message type.
Concrete message classes should provide their own static ID field:
The id getter returns this static value, giving the runtime a stable reference.
metadata
A mutable object that stores additional context about the message. It can hold:
tags (e.g., tracing or categorisation strings)
timestamps
correlation identifiers
any other application‑specific annotations
The shape of PostboyMessageMetadata is intentionally minimal to allow flexible enrichment without breaking the contract.
setMetadata(metadata)
Merges the provided partial object into the existing metadata and returns the same message instance. This fluent API allows you to decorate a message during creation or within middleware before it reaches its final destination:
Interaction model
A PostboyMessage instance typically follows this sequence:
A concrete message class is defined with a unique
ID.An instance is created and optionally enriched with metadata.
The message is sent through the bus (
fire,fireCallback, orexec).The bus resolves the message by its
id.Registered middleware may inspect or alter the flow.
Subscribers or handlers react accordingly.
The flow ends with either a side effect, a response, or a result.
Because PostboyMessage is abstract, you never use it directly — only through one of its concrete subclasses:
PostboyCallbackMessage<T>
Each subclass adds its own semantics, but all rely on the same base contract for identity and metadata.
Why inheritance matters
Using a shared abstract base offers several benefits:
Unified infrastructure – Middleware, logging, and tracing operate on
PostboyMessagewithout needing to know the specific flavour.Stable identity – The static
IDfield eliminates the need for manual string constants scattered across the codebase.Extensible metadata –
setMetadataprovides a standard way to enrich any message, keeping the API consistent.
At the same time, the concrete subclasses make the intended communication style explicit. You choose the right tool for the job, but the underlying machinery remains predictable.
Relationship to other contracts
Contract | Extends | Adds |
|---|---|---|
| Broadcast semantics (one‑to‑many) | |
|
| Typed response handling |
| Explicit, direct execution |
Despite their differences, any piece of code that can work with a PostboyMessage can also work with all three. This is what allows middleware to transparently observe or transform any message flowing through the bus.
Notes
PostboyMessageitself is abstract and cannot be instantiated directly.Message identity is class‑based; each concrete message must define its own
static ID.Metadata is intentionally mutable — it is designed to be augmented during the lifecycle, not frozen at creation.
In short
PostboyMessage is the root contract of the Postboy message family. It provides a stable identity, shared metadata, and a common foundation that unifies event‑style, request/response, and explicit execution patterns within a single bus.