/** * This middleware pattern has been grabbed from here: * https://evertpot.com/generic-middleware/ */ /** * 'next' function, passed to a middleware */ export type Next = () => void | Promise /** * A middleware */ export type Middleware = (context: T, next: Next) => Promise | void /** * A middleware container and invoker */ export class MiddlewareDispatcher { middlewares: Middleware[] = [] constructor(readonly finalMiddlewares: Middleware[] = []) {} /** * Add a middleware function. */ use(...middlewares: Middleware[]): void { this.middlewares.push(...middlewares) } /** * Add 'final' middlewares that will be added to the end of the * regular middlewares. This allows for finer control when exposing * the @see use functionality to consumers but wanting to ensure that your * final middleware is last to run */ useFinal(...middlewares: Middleware[]): void { this.finalMiddlewares.push(...middlewares) } /** * Execute the chain of middlewares, in the order they were added on a * given Context. */ dispatch(context: T): Promise { return invokeMiddlewares( context, this.middlewares.concat(this.finalMiddlewares) ) } } async function invokeMiddlewares( context: T, middlewares: Middleware[] ): Promise { if (!middlewares.length) { return } const middleware = middlewares[0] return middleware(context, async () => { await invokeMiddlewares(context, middlewares.slice(1)) }) }