import { getRouteParams, type Route } from "./match.ts"; export interface NamedRoute extends Route { name: string; metadata?: Record; } export interface RouteManifestEntry { name: string; path: string; file: string; params: ReturnType; metadata?: Record; } export function routeName(raw: string): string { const value = raw.replace(/^\/+|\/+$/g, ""); return value ? value.replace(/\[\[?\.\.\.|\[\[?|\]\]?/g, "").replace(/[^A-Za-z0-9]+/g, ".") : "index"; } export function nameRoutes( routes: readonly Route[], metadata: Record> = {}, ): NamedRoute[] { const used = new Map(); return routes.map((route) => { const base = routeName(route.raw); const count = (used.get(base) ?? 0) + 1; used.set(base, count); const name = count === 1 ? base : `${base}.${count}`; return { ...route, name, metadata: metadata[name] }; }); } export function createRouteManifest(routes: readonly NamedRoute[]): RouteManifestEntry[] { return routes.map((route) => ({ name: route.name, path: route.raw, file: route.file, params: route.paramMeta ?? getRouteParams(route.raw), metadata: route.metadata, })); } export function routeUrl( route: Pick, params: Record | null | undefined> = {}, query?: Record, ): string { const meta = route.paramMeta ?? getRouteParams(route.raw); let output = route.raw; for (const param of meta) { const value = params[param.name]; if (value === undefined || value === null || value === "") { if (!param.optional) throw new Error(`WRN-ROUTE-MISSING-PARAM: ${param.name}`); output = output.replace(new RegExp(`/\\[\\[?(?:\\.\\.\\.)?${param.name}\\??\\]\\]?`), ""); continue; } const encoded = Array.isArray(value) ? value.map((part) => encodeURIComponent(String(part))).join("/") : encodeURIComponent(String(value)); output = output.replace(new RegExp(`\\[\\[?(?:\\.\\.\\.)?${param.name}\\??\\]\\]?`), encoded); } output = output.replace(/\/+/g, "/") || "/"; if (query) { const search = new URLSearchParams(); for (const [key, value] of Object.entries(query)) if (value !== undefined && value !== null) search.set(key, String(value)); const text = search.toString(); if (text) output += `?${text}`; } return output; } export function findNamedRoute(routes: readonly NamedRoute[], name: string): NamedRoute { const route = routes.find((candidate) => candidate.name === name); if (!route) throw new Error(`WRN-ROUTE-NAME-NOT-FOUND: ${name}`); return route; }