SPB Git

Repository Public

router.ts 1.6 KB 31 lines typescript

1import { ApiError, fail, ok } from "../http";23export interface RouteCtx<C> { req: Request; url: URL; params: Record<string, string>; ctx: C }4type Handler<C> = (r: RouteCtx<C>) => Promise<unknown>;5export type RouteDef<C> = [method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", pattern: string, handler: Handler<C>];67/**8 * One catch-all route per API surface. The guard runs before ANY handler is looked up, so a handler cannot be9 * reached without it (no admin route can forget its authentication check).10 */11export function makeRouter<C>(routes: RouteDef<C>[], guard: (req: Request) => Promise<C>) {12  const compiled = routes.map(([method, pattern, handler]) => ({ method, parts: pattern.split("/").filter(Boolean), handler }));13  return async (req: Request, context: { params: Promise<{ path?: string[] }> }): Promise<Response> => {14    try {15      const ctx = await guard(req);16      const path = (await context.params).path ?? [];17      let pathMatched = false;18      for (const r of compiled) {19        if (r.parts.length !== path.length) continue;20        const params: Record<string, string> = {};21        if (!r.parts.every((p, i) => (p.startsWith(":") ? ((params[p.slice(1)] = path[i]), true) : p === path[i]))) continue;22        pathMatched = true;23        if (r.method !== req.method) continue;24        const out = await r.handler({ req, url: new URL(req.url), params, ctx });25        return out instanceof Response ? out : ok(out ?? { ok: true });26      }27      throw pathMatched ? new ApiError(405, "method_not_allowed") : new ApiError(404, "not_found");28    } catch (e) { return fail(e); }29  };30}31
Files main
Menu

Theme