release: WRNexusJS 0.3.0
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
/**
|
||||
* Typed-routes codegen. From the scanned page routes, emit `app/routes.gen.ts`
|
||||
* with a `Routes` map (path → param types) and an `href()` builder — so links
|
||||
* are checked at compile time (unknown path or missing param = type error).
|
||||
* with a `Routes` map (path -> param types) and an `href()` builder.
|
||||
*/
|
||||
|
||||
import type { Route } from "./match.ts";
|
||||
import { getRouteParams, type Route } from "./match.ts";
|
||||
|
||||
export function generateRoutesFile(pages: Route[]): string {
|
||||
const seen = new Set<string>();
|
||||
@@ -12,35 +11,73 @@ export function generateRoutesFile(pages: Route[]): string {
|
||||
for (const page of [...pages].sort((a, b) => a.raw.localeCompare(b.raw))) {
|
||||
if (seen.has(page.raw)) continue;
|
||||
seen.add(page.raw);
|
||||
const type = page.paramNames.length
|
||||
? `{ ${page.paramNames.map((n) => `${JSON.stringify(n)}: string`).join("; ")} }`
|
||||
const params = page.paramMeta ?? getRouteParams(page.raw);
|
||||
const type = params.length
|
||||
? `{ ${params
|
||||
.map((param) => {
|
||||
const key = `${JSON.stringify(param.name)}${param.optional ? "?" : ""}`;
|
||||
const value = param.catchAll ? "string | readonly string[]" : "string";
|
||||
return `${key}: ${value}`;
|
||||
})
|
||||
.join("; ")} }`
|
||||
: "Record<string, never>";
|
||||
entries.push(` ${JSON.stringify(page.raw)}: ${type};`);
|
||||
}
|
||||
|
||||
return `// AUTO-GENERATED by \`wrnexus dev\` — do not edit.
|
||||
// Typed routes: a compile-time map of every page path to its [param] types,
|
||||
// plus an href() builder that fills params and rejects unknown paths.
|
||||
return `// AUTO-GENERATED by \`wrnexus dev\` - do not edit.
|
||||
// Typed routes support required, optional, and catch-all parameters.
|
||||
|
||||
export interface Routes {
|
||||
${entries.join("\n") || " [path: string]: Record<string, string>;"}
|
||||
}
|
||||
|
||||
export type RoutePath = keyof Routes;
|
||||
type RouteValue = string | readonly string[] | undefined;
|
||||
|
||||
function encodeRouteValue(value: RouteValue, catchAll: boolean): string {
|
||||
if (value === undefined) return "";
|
||||
const values = Array.isArray(value) ? value : catchAll ? String(value).split("/") : [String(value)];
|
||||
return values.map((part) => encodeURIComponent(part)).join("/");
|
||||
}
|
||||
|
||||
export function href<P extends RoutePath>(
|
||||
path: P,
|
||||
...args: Routes[P] extends Record<string, never> ? [] : [params: Routes[P]]
|
||||
...args: keyof Routes[P] extends never
|
||||
? []
|
||||
: Record<string, never> extends Routes[P]
|
||||
? [params?: Routes[P]]
|
||||
: [params: Routes[P]]
|
||||
): string {
|
||||
const params = (args[0] ?? {}) as Record<string, string>;
|
||||
return String(path)
|
||||
.split("/")
|
||||
.map((seg) =>
|
||||
seg.startsWith("[") && seg.endsWith("]")
|
||||
? encodeURIComponent(params[seg.slice(1, -1)] ?? "")
|
||||
: seg,
|
||||
)
|
||||
.join("/");
|
||||
const params = (args[0] ?? {}) as Record<string, RouteValue>;
|
||||
const output: string[] = [];
|
||||
for (const segment of String(path).split("/").filter(Boolean)) {
|
||||
let name: string | undefined;
|
||||
let optional = false;
|
||||
let catchAll = false;
|
||||
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
||||
optional = true;
|
||||
name = segment.slice(2, -2);
|
||||
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
||||
name = segment.slice(1, -1);
|
||||
if (name.endsWith("?")) {
|
||||
optional = true;
|
||||
name = name.slice(0, -1);
|
||||
}
|
||||
}
|
||||
if (!name) {
|
||||
output.push(segment);
|
||||
continue;
|
||||
}
|
||||
if (name.startsWith("...")) {
|
||||
catchAll = true;
|
||||
name = name.slice(3);
|
||||
}
|
||||
const value = params[name];
|
||||
if (value === undefined && optional) continue;
|
||||
if (value === undefined) throw new Error(\`WRN-ROUTE-MISSING-PARAM: Missing route parameter '\${name}'.\`);
|
||||
output.push(encodeRouteValue(value, catchAll));
|
||||
}
|
||||
return "/" + output.filter(Boolean).join("/");
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user