/** * Mandu Middleware Compose ๐Ÿ”— * Hono ์Šคํƒ€์ผ ๋ฏธ๋“ค์›จ์–ด ์กฐํ•ฉ ํŒจํ„ด * * @see https://github.com/honojs/hono/blob/main/src/compose.ts */ import type { ManduContext } from "../filling/context"; /** * Next ํ•จ์ˆ˜ ํƒ€์ž… */ export type Next = () => Promise; /** * ๋ฏธ๋“ค์›จ์–ด ํ•จ์ˆ˜ ํƒ€์ž… * - Response ๋ฐ˜ํ™˜: ์ฒด์ธ ์ค‘๋‹จ (Guard ์—ญํ• ) * - void ๋ฐ˜ํ™˜: ๋‹ค์Œ ๋ฏธ๋“ค์›จ์–ด ์‹คํ–‰ */ export type Middleware = ( ctx: ManduContext, next: Next ) => Response | void | Promise; /** * ์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ ํƒ€์ž… */ export type ErrorHandler = ( error: Error, ctx: ManduContext ) => Response | Promise; /** * NotFound ํ•ธ๋“ค๋Ÿฌ ํƒ€์ž… */ export type NotFoundHandler = (ctx: ManduContext) => Response | Promise; /** * ๋ฏธ๋“ค์›จ์–ด ์—”ํŠธ๋ฆฌ (๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํฌํ•จ) */ export interface MiddlewareEntry { fn: Middleware; name?: string; isAsync?: boolean; } /** * Compose ์˜ต์…˜ */ export interface ComposeOptions { onError?: ErrorHandler; onNotFound?: NotFoundHandler; } /** * ๋ฏธ๋“ค์›จ์–ด ํ•จ์ˆ˜๋“ค์„ ํ•˜๋‚˜์˜ ์‹คํ–‰ ํ•จ์ˆ˜๋กœ ์กฐํ•ฉ * * @example * ```typescript * const middleware = [ * { fn: async (ctx, next) => { console.log('before'); await next(); console.log('after'); } }, * { fn: async (ctx, next) => { return ctx.ok({ data: 'hello' }); } }, * ]; * * const handler = compose(middleware, { * onError: (err, ctx) => ctx.json({ error: err.message }, 500), * onNotFound: (ctx) => ctx.notFound(), * }); * * const response = await handler(context); * ``` */ export function compose( middleware: MiddlewareEntry[], options: ComposeOptions = {} ): (ctx: ManduContext) => Promise { const { onError, onNotFound } = options; return async (ctx: ManduContext): Promise => { let index = -1; let finalResponse: Response | undefined; /** * ๋ฏธ๋“ค์›จ์–ด ์ˆœ์ฐจ ์‹คํ–‰ * @param i ํ˜„์žฌ ์ธ๋ฑ์Šค */ async function dispatch(i: number): Promise { // next() ์ด์ค‘ ํ˜ธ์ถœ ๋ฐฉ์ง€ if (i <= index) { throw new Error("next() called multiple times"); } index = i; const entry = middleware[i]; if (!entry) { // ๋ชจ๋“  ๋ฏธ๋“ค์›จ์–ด ํ†ต๊ณผ ํ›„ ํ•ธ๋“ค๋Ÿฌ ์—†์Œ if (!finalResponse && onNotFound) { finalResponse = await onNotFound(ctx); } return; } try { const result = await entry.fn(ctx, () => dispatch(i + 1)); // Response ๋ฐ˜ํ™˜ ์‹œ ์ฒด์ธ ์ค‘๋‹จ if (result instanceof Response) { finalResponse = result; return; } } catch (err) { if (err instanceof Error && onError) { finalResponse = await onError(err, ctx); return; } throw err; } } await dispatch(0); // ์‘๋‹ต์ด ์—†์œผ๋ฉด 404 if (!finalResponse) { if (onNotFound) { finalResponse = await onNotFound(ctx); } else { finalResponse = new Response("Not Found", { status: 404 }); } } return finalResponse; }; } /** * ๋ฏธ๋“ค์›จ์–ด ๋ฐฐ์—ด ์ƒ์„ฑ ํ—ฌํผ * * @example * ```typescript * const mw = createMiddleware([ * authGuard, * rateLimitGuard, * mainHandler, * ]); * ``` */ export function createMiddleware( fns: Middleware[] ): MiddlewareEntry[] { return fns.map((fn, i) => ({ fn, name: fn.name || `middleware_${i}`, isAsync: fn.constructor.name === "AsyncFunction", })); } /** * ๋ฏธ๋“ค์›จ์–ด ์ฒด์ธ ๋นŒ๋” * * @example * ```typescript * const chain = new MiddlewareChain() * .use(authGuard) * .use(rateLimitGuard) * .use(mainHandler) * .onError((err, ctx) => ctx.json({ error: err.message }, 500)) * .build(); * * const response = await chain(ctx); * ``` */ export class MiddlewareChain { private middleware: MiddlewareEntry[] = []; private errorHandler?: ErrorHandler; private notFoundHandler?: NotFoundHandler; /** * ๋ฏธ๋“ค์›จ์–ด ์ถ”๊ฐ€ */ use(fn: Middleware, name?: string): this { this.middleware.push({ fn, name: name || fn.name || `middleware_${this.middleware.length}`, isAsync: fn.constructor.name === "AsyncFunction", }); return this; } /** * ์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ ์„ค์ • */ onError(handler: ErrorHandler): this { this.errorHandler = handler; return this; } /** * NotFound ํ•ธ๋“ค๋Ÿฌ ์„ค์ • */ onNotFound(handler: NotFoundHandler): this { this.notFoundHandler = handler; return this; } /** * ๋ฏธ๋“ค์›จ์–ด ์ฒด์ธ ๋นŒ๋“œ */ build(): (ctx: ManduContext) => Promise { return compose(this.middleware, { onError: this.errorHandler, onNotFound: this.notFoundHandler, }); } /** * ๋ฏธ๋“ค์›จ์–ด ๋ชฉ๋ก ์กฐํšŒ */ getMiddleware(): MiddlewareEntry[] { return [...this.middleware]; } }