@stacksjs/router
Version:
The Stacks framework router.
73 lines (72 loc) • 3.13 kB
JavaScript
import { log } from "@stacksjs/logging";
import { route } from "./stacks-router";
const NO_PREFIX_KEYS = ["web"];
export async function loadRoutes(registry) {
for (const [key, definition] of Object.entries(registry)) {
const config = normalizeDefinition(definition), prefix = config.prefix !== void 0 ? config.prefix ? config.prefix.startsWith("/") ? config.prefix : `/${config.prefix}` : void 0 : NO_PREFIX_KEYS.includes(key) ? void 0 : `/${key}`, middleware = normalizeMiddleware(config.middleware);
log.debug(`[route-loader] Loading: ${config.path} prefix=${prefix || "/"} middleware=[${middleware.join(", ")}]`);
try {
if (prefix || middleware.length > 0)
await route.group({
prefix,
middleware: middleware.length > 0 ? middleware : void 0
}, async () => {
await importRouteFile(config.path);
});
else
await importRouteFile(config.path);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error(`[Routes] Failed to load route file '${config.path}': ${message}`);
throw Error(`Route loading failed for '${config.path}': ${message}`);
}
}
await loadFrameworkRoutes();
}
async function loadFrameworkRoutes() {
if (process.env.STACKS_SKIP_DEFAULT_ROUTES === "1")
return;
try {
const { frameworkPath } = await import("@stacksjs/path"), bootstrapPath = frameworkPath("defaults/bootstrap.ts");
if (await Bun.file(bootstrapPath).exists())
await import(bootstrapPath);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (!message.includes("Cannot find module") && !message.includes("MODULE_NOT_FOUND"))
console.error(`[Routes] Failed to load framework bootstrap: ${message}`);
}
}
function assertSafeRouteName(routeName) {
if (typeof routeName !== "string" || routeName.length === 0)
throw Error("[route-loader] Invalid route path: empty or non-string");
if (routeName.includes("\x00"))
throw Error("[route-loader] Invalid route path: null byte");
let decoded;
try {
decoded = decodeURIComponent(routeName);
} catch {
throw Error("[route-loader] Invalid route path: malformed URL encoding");
}
const cleanPath = decoded.replace(/\.ts$/, "");
if (cleanPath.startsWith("/") || /^[A-Za-z]:[\\/]/.test(cleanPath))
throw Error(`[route-loader] Invalid route path: absolute paths not allowed (${cleanPath})`);
if (cleanPath.split(/[/\\]/).some((s) => s === ".."))
throw Error(`[route-loader] Invalid route path: '..' segment not allowed (${cleanPath})`);
return cleanPath;
}
async function importRouteFile(routeName) {
const cleanPath = assertSafeRouteName(routeName), { projectPath } = await import("@stacksjs/path");
await import(projectPath(`routes/${cleanPath}`));
}
function normalizeDefinition(def) {
if (typeof def === "string")
return { path: def };
return def;
}
function normalizeMiddleware(middleware) {
if (!middleware)
return [];
if (typeof middleware === "string")
return [middleware];
return middleware;
}