import { STATUS_CODE } from '@std/http'; import { OrPromise } from './util.ts'; export type Method = string; export const Method = { GET: 'GET', POST: 'POST', PUT: 'PUT', PATCH: 'PATCH', Any: '___INTERNAL_ANY', }; export type Handler = ( request: Request, pathParams: Record, ) => OrPromise; export type Match = string | URLPattern; export class Router { #pathnameCache: Map; #routes: Map>; constructor() { this.#routes = new Map(); this.#pathnameCache = new Map(); } register(method: Method, match: Match, handler: Handler) { const urlPattern = (match instanceof URLPattern) ? match : this.#cachePathname(match); if (!this.#routes.has(urlPattern)) { this.#routes.set(urlPattern, new Map()); } const methodMap = this.#routes.get(urlPattern)!; method = method.toUpperCase(); methodMap.set(method, handler); } #cachePathname(pathname: string) { const cached = this.#pathnameCache.get(pathname); if (cached) { return cached; } const pattern = new URLPattern({ pathname }); this.#pathnameCache.set(pathname, pattern); return pattern; } async handle(req: Request): Promise { const method = req.method.toUpperCase(); for (const [pattern, methodMap] of this.#routes.entries()) { const match = pattern.exec(req.url); if (!match) { continue; } let handler = methodMap.get(method); if (!handler) { handler = methodMap.get(Method.Any); } if (!handler) { return new Response('Method not allowed', { status: STATUS_CODE.MethodNotAllowed, }); } return handler(req, match.pathname.groups as Record); } return new Response('Not found', { status: STATUS_CODE.NotFound }); } }