Middleware
Overview
Section titled “Overview”A Middleware wraps the rest of a pipeline stage. It receives a PipelineContext and a next function; calling await next() runs the remaining middleware (and, at the innermost layer, the handler). Code before next() runs on the way in; code after — typically inside a try/finally — runs on the way out. Use asMiddleware(fn) to wrap a middleware function in a MiddlewareRegistration that bus.use(...) accepts.
Import
Section titled “Import”import { asMiddleware } from '@serviceconnect/core';import type { Middleware, MiddlewareRegistration, PipelineContext } from '@serviceconnect/core';Signature
Section titled “Signature”export interface PipelineContext { readonly envelope: Envelope; readonly stage: PipelineStage; readonly signal: AbortSignal; readonly logger: Logger;}
export type Middleware = (context: PipelineContext, next: () => Promise<void>) => Promise<void>;
export interface MiddlewareRegistration { readonly kind: 'middleware'; readonly run: Middleware;}
export function asMiddleware(fn: Middleware): MiddlewareRegistration;Parameters
Section titled “Parameters”PipelineContext.envelope— theEnvelopeflowing through the stage; its headers may have been stamped by earlier outgoing middleware or by the transport.PipelineContext.stage— the currentPipelineStage('outgoing','beforeConsuming','afterConsuming', or'onConsumedSuccessfully').PipelineContext.signal— anAbortSignal. On the inbound consume stages (beforeConsuming,afterConsuming,onConsumedSuccessfully) it aborts when the bus is shutting down, so forwarding it into any awaited work lets you participate in graceful shutdown. On theoutgoingstage the signal comes from a freshAbortControllerthat is never aborted, so it does not fire on shutdown.PipelineContext.logger— the bus’sLogger(the same logger configured oncreateBus), passed through unchanged.Middleware— the function signature. Alwaysasync; always returnsPromise<void>. Callawait next()exactly once unless you genuinely intend to skip the rest of the stage.MiddlewareRegistration— the typed wrapperbus.use(...)accepts.kindis the discriminant againstFilterRegistration;runis the wrapped middleware function.asMiddleware(fn)— convenience factory that returns{ kind: 'middleware', run: fn }.
Example
Section titled “Example”import { asMiddleware } from '@serviceconnect/core';
bus.use( 'onConsumedSuccessfully', asMiddleware(async (context, next) => { const start = Date.now(); await next(); metrics.observe(Date.now() - start); }),);Middleware never decides whether a message proceeds — that is the Filter role. Middleware only observes or wraps what happens around next(). If you need to time a handler regardless of whether it threw, put the post-work in a finally block.