import "../core/metadata.ts"; import { defineMemberMetadata, getAllMemberMetadata } from "../core/metadata.ts"; import { callerFile } from "../core/paths.ts"; import { dirname } from "node:path"; 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"; export const VIEW_METADATA_KEY = "muddle:view"; export interface ViewOptions { /** HTTP method of the route, defaults to `GET`. */ method?: HttpMethod | HttpMethod[]; /** Route path, derived from the template when omitted. */ path?: string; /** Status code of the response. */ status?: StatusCode; name?: string; description?: string; params?: Record; } export interface ViewMetadata { /** Name of the method the view belongs to. */ propertyKey: string; /** Path of the template, relative to the view folder. */ path: string; /** Directory of the file that declared the view. */ dir: string; method: HttpMethod; status?: StatusCode; name?: string; description?: string; params?: Record; } /** Declares the template rendered by a controller method. */ export function View(path?: string, options: ViewOptions = {}): MethodDecorator { return (target, propertyKey, descriptor) => { const caller = callerFile(); const method = String(propertyKey); const methods = options.method ? Array.isArray(options.method) ? options.method : [options.method] : ["GET" as HttpMethod]; for (const httpMethod of methods) { defineMemberMetadata( VIEW_METADATA_KEY, { propertyKey: method, path: path ?? `./${method}.spug`, dir: caller ? dirname(caller) : process.cwd(), method: normalizeMethod(httpMethod), status: options.status, name: options.name, description: options.description, params: options.params, } satisfies ViewMetadata, target as object, propertyKey as string, ); } return descriptor; }; } /** * Reads the view metadata of a controller class. * * When a property key is given, only the views of that method are returned. */ export function getViews( target: object, propertyKey?: string, ): ViewMetadata[] { return getAllMemberMetadata(VIEW_METADATA_KEY, target) .filter( (entry) => propertyKey === undefined || entry.propertyKey === propertyKey, ) .map((entry) => entry.value as ViewMetadata); }