muddle/decorators/View.ts
2026-10-23 09:12:49 -04:00

86 lines
2.4 KiB
TypeScript

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<string, string>;
}
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<string, string>;
}
/** 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);
}