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",
"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",
"exports": {
".": "./serverus.ts",

View file

@ -32,7 +32,13 @@ export interface ROUTE_HANDLER {
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 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());
type import_record = {
route_path: string;
import_path: string;
};
const imports: import_record[] = [];
const imports: ROUTE_HANDLER_RECORD[] = [];
for await (
const entry of walk(root_directory, {
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
'/';
const route_pattern = new URLPattern({ pathname: route_path });
const import_path = new URL('file://' + entry.path, import.meta.url).toString();
imports.push({
route_path,
route_pattern,
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:
// /permissions/test
// /echo/hi
// /echo/:input
// /api/echo/hi
// /api/echo/:input
//
// 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));
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`);
}
}
routes.push(...imports.sort((lhs, rhs) => rhs.route_path.localeCompare(lhs.route_path)));
all_routes_loaded = true;
loading = false;
@ -114,11 +104,21 @@ export default async function handle_typescript(request: Request): Promise<Respo
} while (!all_routes_loaded);
}
for (const [pattern, handler_module] of routes) {
const match = pattern.exec(request.url.replace(/\/$/, ''));
for (const route_record of routes) {
const match = route_record.route_pattern.exec(request.url.replace(/\/$/, ''));
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 (!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) {
return;
}
@ -132,7 +132,7 @@ export default async function handle_typescript(request: Request): Promise<Respo
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)) {
for (const precheck of prechecks) {
const result = await precheck(request, metadata);
@ -149,6 +149,6 @@ export default async function handle_typescript(request: Request): Promise<Respo
export function unload(): void {
loading = false;
routes.clear();
routes.splice(0, routes.length);
all_routes_loaded = false;
}

View file

@ -17,7 +17,7 @@ Deno.test({
Deno.chdir('./tests/www');
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'
});

View file

@ -23,7 +23,7 @@ Deno.test({
Deno.chdir('./tests/www');
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'
});

View file

@ -24,7 +24,7 @@ Deno.test({
};
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'
});
@ -32,10 +32,6 @@ Deno.test({
asserts.assert(response.ok);
asserts.assert(body);
console.dir({
body,
key
});
asserts.assertEquals(body, echoes[key]);
}
} finally {