import { walk } from '@std/fs'; import { delay } from '@std/async/delay'; import * as path from '@std/path'; import { getCookies } from '@std/http/cookie'; export type PRECHECK = ( request: Request, meta: Record ) => undefined | Response | Promise; export type PRECHECKS_TABLE = Record<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH', PRECHECK>; export type ROUTE_HANDLER_METHOD = (request: Request, meta: Record) => Promise | Response; export interface ROUTE_HANDLER { PRECHECKS?: PRECHECKS_TABLE; GET?: ROUTE_HANDLER_METHOD; POST?: ROUTE_HANDLER_METHOD; PUT?: ROUTE_HANDLER_METHOD; DELETE?: ROUTE_HANDLER_METHOD; PATCH?: ROUTE_HANDLER_METHOD; default?: ROUTE_HANDLER_METHOD; } const routes: Map = new Map(); let loading: boolean = false; let all_routes_loaded: boolean = false; /** * Handles requests for which there are typescript files in the root tree. * * NOTE: On initial request the tree will be scanned and handlers loaded, * concurrent requests should wait for the load, but until the tree has been * scanned, requests may take longer while the load completes. * * @param request The incoming HTTP request * @returns Either a response (a handler for the request path and method was found) or undefined if unhandled. */ export default async function handle_typescript(request: Request): Promise { if (!all_routes_loaded) { if (!loading) { loading = true; const root_directory = path.resolve(Deno.cwd()); for await ( const entry of walk(root_directory, { exts: ['.ts'], skip: [/\.test\.ts$/] }) ) { if (entry.isFile) { const relative_path = entry.path.substring(root_directory.length); const route_path = relative_path .replace(/\.ts$/, '') //.replace(/\[(\w+)\]/g, ':$1') .replace(/\/index$/, '') .replace(/___/g, ':') || // required for windows, uncivilized OS that it is '/'; const import_path = new URL('file://' + entry.path, import.meta.url).toString(); const module: ROUTE_HANDLER = await import(import_path) as ROUTE_HANDLER; const pattern = new URLPattern({ pathname: route_path }); routes.set(pattern, module); } } all_routes_loaded = true; loading = false; } do { await delay(10); } while (!all_routes_loaded); } for (const [pattern, handler_module] of routes) { const match = pattern.exec(request.url); if (match) { const method = request.method as keyof ROUTE_HANDLER; const method_handler: ROUTE_HANDLER_METHOD = (handler_module[method] ?? handler_module.default) as ROUTE_HANDLER_METHOD; if (!method_handler) { return; } const cookies: Record = getCookies(request.headers); const query = Object.fromEntries(new URL(request.url).searchParams.entries()); const metadata = { cookies, params: match.pathname.groups, query }; const precheck: PRECHECK | undefined = handler_module.PRECHECKS?.[request.method as keyof PRECHECKS_TABLE]; if (precheck) { const result = await precheck(request, metadata); if (result) { return result; } } return await method_handler(request, metadata); } } } export function unload(): void { loading = false; routes.clear(); all_routes_loaded = false; }