muddle/decorators/Route.ts
2026-10-23 08:53:07 -04:00

80 lines
2.2 KiB
TypeScript

import "../core/metadata.ts";
import {
defineMemberMetadata,
getAllMemberMetadata,
} from "../core/metadata.ts";
import type { HttpMethod } from "../contracts/typegen/http/method.ts";
import { normalizeMethod } from "../contracts/typegen/http/method.ts";
import type { StatusCode } from "../contracts/typegen/http/respcode.ts";
/**
* `@Route()` binds a controller method to an HTTP route.
*/
export const ROUTE_METADATA_KEY = "muddle:route";
export interface RouteOptions {
/** HTTP method(s) handled by the route, defaults to `GET`. */
method?: HttpMethod | HttpMethod[];
/** Status code used when the handler returns a plain value. */
status?: StatusCode;
/** Route name, used by the fluent view helpers. */
name?: string;
/** Human-readable route description. */
description?: string;
/** Parameter description by parameter name. */
params?: Record<string, string>;
}
export interface RouteMetadata {
/** Path relative to the controller prefix. */
path: string;
method: HttpMethod;
options: RouteOptions;
/** Name of the decorated method. */
propertyKey: string;
}
function normalizeMethods(method: RouteOptions["method"]): HttpMethod[] {
if (method === undefined) return ["GET"];
const list = Array.isArray(method) ? method : [method];
return list.map((entry) => normalizeMethod(entry));
}
/** Declares one or more routes for a controller method. */
export function Route(
path: string,
options: RouteOptions = {},
): MethodDecorator {
return (target, propertyKey, descriptor) => {
for (const httpMethod of normalizeMethods(options.method)) {
defineMemberMetadata(
ROUTE_METADATA_KEY,
{
path,
method: httpMethod,
options,
propertyKey: String(propertyKey),
} satisfies RouteMetadata,
target as object,
propertyKey as string,
);
}
return descriptor;
};
}
/**
* Reads every route declared on a controller class.
*
* When a property key is given, only the routes of that method are returned.
*/
export function getRoutes(
target: object,
propertyKey?: string,
): RouteMetadata[] {
return getAllMemberMetadata(ROUTE_METADATA_KEY, target)
.filter(
(entry) => propertyKey === undefined || entry.propertyKey === propertyKey,
)
.map((entry) => entry.value as RouteMetadata);
}