Version 3.5.x — the middleware pipeline overhaul
Version 3.5 redesigns middleware into a staged pipeline with cancellation, and completes the move to a message-driven bus: mutating the bus directly through service methods is no longer possible.
What changed
1. Middleware is a pipeline with stages
Middleware execution is split into explicit stages — Publish, Callback, Execute — and each middleware can run before the stage, after it, and declare whether it applies at all (canHandle). See Middleware.
2. Middleware can interrupt processing
Returning an interrupt decision from before(...) cancels the operation: the caller gets a CancelError with structured details (stage, middleware, message ID, namespace, reason) and the after hooks are skipped. See Middleware Cancellation.
3. A richer middleware context
Middleware receives a PipelineContext (stage + message), and the abstract class exposes name, canHandle, before, after, dispose — replacing the old single-hook interface.
4. Bus mutations are message-driven only
lock, unlock, addMiddleware, removeMiddleware, addNamespace, eliminateNamespace, and unregister were removed from PostboyService. Their replacements are the infrastructure messages — exec(new LockMessage(...)), exec(new AddMiddleware(...)), and so on. The deprecated record* service methods remain for now; prefer their Connect* replacements (see Registration).
Migration
Custom middleware: follow Migrating middleware from 3.4.
Removed service methods: the mapping table lives in Infrastructure Messages.
exec(new AddMiddleware(mw))-style calls compile on 3.4 and 3.5 alike — default to them in shared code.
Patches
3.5.3
PostboyService.fireCallback now dispatches the message at most once per call, matching the documented contract:
with
action, the eager internal subscription dispatches the message immediately, andactionis invoked once per emitted result value;without
action, the first subscription to the returned observable dispatches the message;further subscriptions to the returned observable never re-send the request.
Previously every subscription re-fired the message, so combining action with a subscription — or two async pipes over the same observable — sent duplicate requests, and the duplicate response was silently lost (the message's result completes after the first finish). See Callbacks.