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

59 lines
1.7 KiB
TypeScript

import "../core/metadata.ts";
import { defineMetadata, getMetadata } from "../core/metadata.ts";
import { callerFile } from "../core/paths.ts";
import { dirname } from "node:path";
/**
* `@Controller()` groups HTTP handlers under a path prefix.
*/
export const CONTROLLER_METADATA_KEY = "muddle:controller";
export interface ControllerOptions {
/** Path prefix of every route of the controller, e.g. `/users`. */
prefix?: string;
/** Optional tag used by documentation adapters. */
tag?: string;
/** Directory of the file that declares the controller. */
dir?: string;
}
/** Normalises a prefix to `/segment` shape (no trailing slash). */
export function normalizePrefix(prefix: string): string {
if (!prefix || prefix === "/") return "";
const withLeading = prefix.startsWith("/") ? prefix : `/${prefix}`;
return withLeading.replace(/\/+$/, "");
}
/** Declares a controller. */
export function Controller(
prefix?: string | ControllerOptions,
): ClassDecorator {
return (target) => {
const options: ControllerOptions =
typeof prefix === "string" ? { prefix } : (prefix ?? {});
const caller = callerFile();
defineMetadata(
CONTROLLER_METADATA_KEY,
{
...options,
dir: options.dir ?? (caller ? dirname(caller) : undefined),
},
target as object,
);
return target;
};
}
/** Reads the options of a `@Controller()` decorated class. */
export function getControllerOptions(target: object): ControllerOptions {
return (
(getMetadata(CONTROLLER_METADATA_KEY, target) as
| ControllerOptions
| undefined) ?? {}
);
}
/** Reads the normalised path prefix of a controller. */
export function getControllerPrefix(target: object): string {
return normalizePrefix(getControllerOptions(target).prefix ?? "");
}