fix: sort routes into route records and lazily import

This commit is contained in:
Andy Burke 2025-06-25 20:46:09 -07:00
parent 1928bfcb5e
commit d917b69753
8 changed files with 33 additions and 37 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "@andyburke/serverus", "name": "@andyburke/serverus",
"description": "A flexible HTTP server for mixed content. Throw static files, markdown, Typescript and (hopefully, eventually) more into a directory and serverus can serve it up a bit more like old-school CGI.", "description": "A flexible HTTP server for mixed content. Throw static files, markdown, Typescript and (hopefully, eventually) more into a directory and serverus can serve it up a bit more like old-school CGI.",
"version": "0.5.0", "version": "0.6.0",
"license": "MIT", "license": "MIT",
"exports": { "exports": {
".": "./serverus.ts", ".": "./serverus.ts",

View file

@ -32,7 +32,13 @@ export interface ROUTE_HANDLER {
default?: ROUTE_HANDLER_METHOD; default?: ROUTE_HANDLER_METHOD;
} }
const routes: Map<URLPattern, ROUTE_HANDLER> = new Map<URLPattern, ROUTE_HANDLER>(); type ROUTE_HANDLER_RECORD = {
route_path: string;
route_pattern: URLPattern;
import_path: string;
module?: ROUTE_HANDLER;
};
const routes: ROUTE_HANDLER_RECORD[] = [];
let loading: boolean = false; let loading: boolean = false;
let all_routes_loaded: boolean = false; let all_routes_loaded: boolean = false;
@ -53,11 +59,7 @@ export default async function handle_typescript(request: Request): Promise<Respo
const root_directory = path.resolve(Deno.cwd()); const root_directory = path.resolve(Deno.cwd());
type import_record = { const imports: ROUTE_HANDLER_RECORD[] = [];
route_path: string;
import_path: string;
};
const imports: import_record[] = [];
for await ( for await (
const entry of walk(root_directory, { const entry of walk(root_directory, {
exts: ['.ts'], exts: ['.ts'],
@ -73,10 +75,13 @@ export default async function handle_typescript(request: Request): Promise<Respo
.replace(/___/g, ':') || // required for windows, uncivilized OS that it is .replace(/___/g, ':') || // required for windows, uncivilized OS that it is
'/'; '/';
const route_pattern = new URLPattern({ pathname: route_path });
const import_path = new URL('file://' + entry.path, import.meta.url).toString(); const import_path = new URL('file://' + entry.path, import.meta.url).toString();
imports.push({ imports.push({
route_path, route_path,
route_pattern,
import_path import_path
}); });
} }
@ -84,26 +89,11 @@ export default async function handle_typescript(request: Request): Promise<Respo
// try to sort imports such that they're registered like: // try to sort imports such that they're registered like:
// /permissions/test // /permissions/test
// /echo/hi // /api/echo/hi
// /echo/:input // /api/echo/:input
// //
// we want paths with parameters to sort later than paths without // we want paths with parameters to sort later than paths without
const sorted_imports = imports.sort((lhs, rhs) => rhs.route_path.localeCompare(lhs.route_path)); routes.push(...imports.sort((lhs, rhs) => rhs.route_path.localeCompare(lhs.route_path)));
for (const import_info of sorted_imports) {
try {
const module: ROUTE_HANDLER = await import(import_info.import_path) as ROUTE_HANDLER;
const pattern = new URLPattern({ pathname: import_info.route_path });
if (Deno.env.get('SERVERUS_TYPESCRIPT_IMPORT_LOGGING')) {
console.log(`${import_info.route_path} : imported: ${import_info.import_path}`);
}
routes.set(pattern, module);
} catch (error) {
console.error(`Error mounting module ${import_info.import_path} at ${import_info.route_path}\n\n${error}\n`);
}
}
all_routes_loaded = true; all_routes_loaded = true;
loading = false; loading = false;
@ -114,11 +104,21 @@ export default async function handle_typescript(request: Request): Promise<Respo
} while (!all_routes_loaded); } while (!all_routes_loaded);
} }
for (const [pattern, handler_module] of routes) { for (const route_record of routes) {
const match = pattern.exec(request.url.replace(/\/$/, '')); const match = route_record.route_pattern.exec(request.url.replace(/\/$/, ''));
if (match) { if (match) {
const method = request.method as keyof ROUTE_HANDLER; 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 (!route_record.module) {
if (Deno.env.get('SERVERUS_TYPESCRIPT_IMPORT_LOGGING')) {
console.log(`${route_record.route_path} : imported: ${route_record.import_path}`);
}
}
route_record.module = route_record.module ?? await import(route_record.import_path) as ROUTE_HANDLER;
const method_handler: ROUTE_HANDLER_METHOD =
(route_record.module[method] ?? route_record.module.default) as ROUTE_HANDLER_METHOD;
if (!method_handler) { if (!method_handler) {
return; return;
} }
@ -132,7 +132,7 @@ export default async function handle_typescript(request: Request): Promise<Respo
query query
}; };
const prechecks: PRECHECK[] | undefined = handler_module.PRECHECKS?.[request.method as keyof PRECHECKS_TABLE]; const prechecks: PRECHECK[] | undefined = route_record.module.PRECHECKS?.[request.method as keyof PRECHECKS_TABLE];
if (Array.isArray(prechecks)) { if (Array.isArray(prechecks)) {
for (const precheck of prechecks) { for (const precheck of prechecks) {
const result = await precheck(request, metadata); const result = await precheck(request, metadata);
@ -149,6 +149,6 @@ export default async function handle_typescript(request: Request): Promise<Respo
export function unload(): void { export function unload(): void {
loading = false; loading = false;
routes.clear(); routes.splice(0, routes.length);
all_routes_loaded = false; all_routes_loaded = false;
} }

View file

@ -17,7 +17,7 @@ Deno.test({
Deno.chdir('./tests/www'); Deno.chdir('./tests/www');
test_server_info = await get_ephemeral_listen_server(); test_server_info = await get_ephemeral_listen_server();
const response = await fetch(`http://${test_server_info.hostname}:${test_server_info.port}/echo/hello_world`, { const response = await fetch(`http://${test_server_info.hostname}:${test_server_info.port}/api/echo/hello_world`, {
method: 'GET' method: 'GET'
}); });

View file

@ -23,7 +23,7 @@ Deno.test({
Deno.chdir('./tests/www'); Deno.chdir('./tests/www');
test_server_info = await get_ephemeral_listen_server(); test_server_info = await get_ephemeral_listen_server();
const response = await fetch(`http://${test_server_info.hostname}:${test_server_info.port}/echo/hello_world.foo`, { const response = await fetch(`http://${test_server_info.hostname}:${test_server_info.port}/api/echo/hello_world.foo`, {
method: 'GET' method: 'GET'
}); });

View file

@ -24,7 +24,7 @@ Deno.test({
}; };
for await (const key of Object.keys(echoes)) { for await (const key of Object.keys(echoes)) {
const response = await fetch(`http://${test_server_info.hostname}:${test_server_info.port}/echo/${key}`, { const response = await fetch(`http://${test_server_info.hostname}:${test_server_info.port}/api/echo/${key}`, {
method: 'GET' method: 'GET'
}); });
@ -32,10 +32,6 @@ Deno.test({
asserts.assert(response.ok); asserts.assert(response.ok);
asserts.assert(body); asserts.assert(body);
console.dir({
body,
key
});
asserts.assertEquals(body, echoes[key]); asserts.assertEquals(body, echoes[key]);
} }
} finally { } finally {