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 ?? ""); }