Middleware
Middleware wraps every handler execution using a Koa-style onion model. Each middleware can run logic before and after the handler, catch errors, modify context, and control whether the handler runs at all.
Adding middleware
At runtime
synkro.use(async (ctx, next) => { console.log(`[${ctx.eventType}] started`); await next(); console.log(`[${ctx.eventType}] finished`);});At startup
Pass a middlewares array in the configuration:
const synkro = await Synkro.start({ transport: "in-memory", middlewares: [timingMiddleware, errorLogMiddleware],});MiddlewareFunction type
type MiddlewareCtx<T = unknown> = HandlerCtx<T> & { eventType: string;};
type MiddlewareFunction = ( ctx: MiddlewareCtx, next: () => Promise<void>,) => Promise<void>;MiddlewareCtx extends HandlerCtx with the eventType field, so middleware knows which event triggered the handler. The next function calls the next middleware in the chain, or the handler itself if there are no more middlewares.
Onion model
Middleware executes in registration order going in, and reverse order going out:
Request → Middleware A (before) → Middleware B (before) → HandlerResponse ← Middleware A (after) ← Middleware B (after) ← HandlerExamples
Timing middleware
const timingMiddleware: MiddlewareFunction = async (ctx, next) => { const start = performance.now(); await next(); const duration = performance.now() - start; console.log(`[${ctx.eventType}] completed in ${duration.toFixed(2)}ms`);};
synkro.use(timingMiddleware);Error logging middleware
const errorLogMiddleware: MiddlewareFunction = async (ctx, next) => { try { await next(); } catch (error) { console.error(`[${ctx.eventType}] handler failed:`, error); throw error; // re-throw to let retry logic handle it }};
synkro.use(errorLogMiddleware);Authentication middleware
const authMiddleware: MiddlewareFunction = async (ctx, next) => { if (!ctx.payload?.userId) { throw new Error("Unauthorized: missing userId"); } await next();};Composition
Under the hood, Synkro composes middlewares using a composeMiddleware function (also exported for advanced use cases). Calling next() more than once in a single middleware throws an error.