first commit

This commit is contained in:
2026-07-12 15:55:18 +05:30
commit ee98026cc5
404 changed files with 44522 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
/**
* Route compilation + matching.
*
* A "route" is a URL pattern compiled to a RegExp. We support static segments
* and dynamic `[param]` segments, e.g. `/users/[id]` -> `{ id }`.
*/
export interface Route {
/** The human-readable route pattern, e.g. `/users/[id]`. */
raw: string;
/** Absolute path to the module that handles this route. */
file: string;
/** Compiled matcher. */
regex: RegExp;
/** Ordered names of dynamic params captured by `regex`. */
paramNames: string[];
}
export interface RouteMatch {
route: Route;
params: Record<string, string>;
}
const ESCAPE_RE = /[.*+?^${}()|[\]\\]/g;
/** Compile a `/users/[id]` style pattern into a RegExp + param names. */
export function compileRoutePattern(raw: string): Pick<Route, "regex" | "paramNames"> {
if (raw === "/") {
return { regex: /^\/$/, paramNames: [] };
}
const paramNames: string[] = [];
const parts = raw
.split("/")
.filter(Boolean)
.map((segment) => {
const dynamic = segment.match(/^\[(.+)\]$/);
if (dynamic) {
paramNames.push(dynamic[1]!);
return "([^/]+)";
}
return segment.replace(ESCAPE_RE, "\\$&");
});
// Allow an optional trailing slash.
const regex = new RegExp("^/" + parts.join("/") + "/?$");
return { regex, paramNames };
}
/**
* Order routes so that static routes win over dynamic ones, and longer/more
* specific routes win over shorter ones. Sorting once keeps matching simple.
*/
export function sortRoutes(routes: Route[]): Route[] {
return [...routes].sort((a, b) => {
if (a.paramNames.length !== b.paramNames.length) {
return a.paramNames.length - b.paramNames.length; // fewer params first
}
return b.raw.length - a.raw.length; // longer/more specific first
});
}
/** Find the first route whose pattern matches `pathname`. */
export function matchRoute(routes: Route[], pathname: string): RouteMatch | null {
for (const route of routes) {
const m = route.regex.exec(pathname);
if (!m) continue;
const params: Record<string, string> = {};
try {
route.paramNames.forEach((name, i) => {
params[name] = decodeURIComponent(m[i + 1]!);
});
} catch {
// A malformed percent-encoded path is not a valid route match. Treat it
// as a 404 instead of allowing decodeURIComponent to become a 500.
return null;
}
return { route, params };
}
return null;
}