atelier/ts/router.ts
2025-02-02 12:24:00 -05:00

78 lines
1.7 KiB
TypeScript

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<string, string>,
) => OrPromise<Response>;
export type Match = string | URLPattern;
export class Router {
#pathnameCache: Map<string, URLPattern>;
#routes: Map<URLPattern, Map<Method, Handler>>;
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<Response> {
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<string, string>);
}
return new Response('Not found', { status: STATUS_CODE.NotFound });
}
}