import { compose } from './compose.ts' import type { Context } from './context.ts' import { HonoContext } from './context.ts' import { extendRequestPrototype } from './request.ts' import type { Router } from './router.ts' import { METHOD_NAME_ALL, METHOD_NAME_ALL_LOWERCASE } from './router.ts' import { TrieRouter } from './router/trie-router/index.ts' // Default Router import { getPathFromURL, mergePath } from './utils/url.ts' export interface ContextVariableMap {} type Env = Record export type Handler = ( c: Context, next: Next ) => Response | Promise | Promise | Promise export type NotFoundHandler = (c: Context) => Response | Promise export type ErrorHandler = (err: Error, c: Context) => Response export type Next = () => Promise // eslint-disable-next-line @typescript-eslint/no-unused-vars type ParamKeyName = NameWithPattern extends `${infer Name}{${infer _Pattern}` ? Name : NameWithPattern type ParamKey = Component extends `:${infer NameWithPattern}` ? ParamKeyName : never type ParamKeys = Path extends `${infer Component}/${infer Rest}` ? ParamKey | ParamKeys : ParamKey interface HandlerInterface> { // app.get('/', handler, handler...) ( path: Path, ...handlers: Handler extends never ? string : ParamKeys, E>[] ): U (path: string, ...handlers: Handler[]): U // app.get(handler...) ( ...handlers: Handler extends never ? string : ParamKeys, E>[] ): U (...handlers: Handler[]): U } const methods = ['get', 'post', 'put', 'delete', 'head', 'options', 'patch'] as const type Methods = typeof methods[number] | typeof METHOD_NAME_ALL_LOWERCASE function defineDynamicClass(): { new (): { [K in Methods]: HandlerInterface } } { return class {} as any } interface Route { path: string method: string handler: Handler } export class Hono extends defineDynamicClass()< E, P, Hono > { readonly router: Router> = new TrieRouter() readonly strict: boolean = true // strict routing - default is true private _tempPath: string = '' private path: string = '/' routes: Route[] = [] constructor(init: Partial> = {}) { super() extendRequestPrototype() const allMethods = [...methods, METHOD_NAME_ALL_LOWERCASE] allMethods.map((method) => { this[method] = ( args1: Path | Handler, E>, ...args: [Handler, E>] ) => { if (typeof args1 === 'string') { this.path = args1 } else { this.addRoute(method, this.path, args1) } args.map((handler) => { if (typeof handler !== 'string') { this.addRoute(method, this.path, handler) } }) return this } }) Object.assign(this, init) } private notFoundHandler: NotFoundHandler = (c: Context) => { const message = '404 Not Found' return c.text(message, 404) } private errorHandler: ErrorHandler = (err: Error, c: Context) => { console.error(`${err.stack || err.message}`) const message = 'Internal Server Error' return c.text(message, 500) } route(path: string, app?: Hono): Hono { this._tempPath = path if (app) { app.routes.map((r) => { this.addRoute(r.method, r.path, r.handler) }) this._tempPath = '' } return this } use(path: string, ...middleware: Handler[]): Hono use(...middleware: Handler[]): Hono use(arg1: string | Handler, ...handlers: Handler[]): Hono { if (typeof arg1 === 'string') { this.path = arg1 } else { handlers.unshift(arg1) } handlers.map((handler) => { this.addRoute(METHOD_NAME_ALL, this.path, handler) }) return this } onError(handler: ErrorHandler): Hono { this.errorHandler = handler as ErrorHandler return this } notFound(handler: NotFoundHandler): Hono { this.notFoundHandler = handler as NotFoundHandler return this } private addRoute(method: string, path: string, handler: Handler): void { method = method.toUpperCase() if (this._tempPath) { path = mergePath(this._tempPath, path) } this.router.add(method, path, handler) const r: Route = { path: path, method: method, handler: handler } this.routes.push(r) } private matchRoute(method: string, path: string) { return this.router.match(method, path) } private async dispatch( request: Request, eventOrExecutionCtx?: ExecutionContext | FetchEvent, env?: E ): Promise { const path = getPathFromURL(request.url, this.strict) const method = request.method const result = this.matchRoute(method, path) request.paramData = result?.params const handlers = result ? result.handlers : [this.notFoundHandler] const c = new HonoContext(request, env, eventOrExecutionCtx, this.notFoundHandler) const composed = compose(handlers, this.errorHandler, this.notFoundHandler) let context: HonoContext try { context = await composed(c) if (!context.finalized) { throw new Error( 'Context is not finalized. You may forget returning Response object or `await next()`' ) } } catch (err) { if (err instanceof Error) { return this.errorHandler(err, c) } throw err } return context.res } handleEvent(event: FetchEvent): Promise { return this.dispatch(event.request, event) } fetch = (request: Request, env?: E, executionCtx?: ExecutionContext) => { return this.dispatch(request, executionCtx, env) } request(input: RequestInfo, requestInit?: RequestInit): Promise { const req = input instanceof Request ? input : new Request(input, requestInit) return this.dispatch(req) } }