muddle/views/components.ts
2026-10-23 08:53:07 -04:00

79 lines
2.5 KiB
TypeScript

import type { ComponentMetadata } from "../decorators/Component.ts";
/**
* Server components.
*
* A server component is a class that renders html on the server. It behaves
* like a template, but it is written in TypeScript, may inject services and
* may be asynchronous.
*/
/** Context handed to a server component. */
export interface ComponentContext {
/**
* Defers a chunk of html until the end of the response.
*
* The response is streamed, therefore the shell reaches the browser
* before the deferred content is rendered.
*/
defer(id: string, content: string | Promise<string>): void;
/** Locals of the current request. */
locals: Record<string, unknown>;
/** Route parameters of the current request. */
params: Record<string, string>;
}
/** Declarative output from a decorated server component. */
export interface SpugComponentRender {
/** Component template name or path, resolved as SPUG. */
spug: string;
/** STCSS source or a stylesheet path relative to the component directory. */
stcss?: string | string[];
/** Props merged with the invocation props before rendering the SPUG template. */
props?: Record<string, unknown>;
}
/** Contract of a server component. */
export interface ServerComponent {
/** Renders the component. */
render(
props: Record<string, unknown>,
context: ComponentContext,
): Promise<string | SpugComponentRender> | string | SpugComponentRender;
}
export function isSpugComponentRender(value: unknown): value is SpugComponentRender {
return typeof value === "object" && value !== null && typeof (value as { spug?: unknown }).spug === "string";
}
/** A registered server component. */
export interface ComponentDefinition {
/** Name used inside the templates. */
name: string;
component: ServerComponent;
/** `true` when the component is rendered after the shell. */
deferred: boolean;
description?: string;
params?: Record<string, string>;
/** Decorated components must return a SPUG and STCSS composition. */
requiresSpug?: boolean;
}
/** Registry of the server components of an application. */
export type ComponentRegistry = Map<string, ComponentDefinition>;
/** Builds a component definition from a decorated class. */
export function defineComponent(
name: string,
component: ServerComponent,
metadata?: ComponentMetadata,
): ComponentDefinition {
return {
name,
component,
deferred: metadata?.deferred === true,
description: metadata?.description,
params: metadata?.params,
requiresSpug: metadata !== undefined,
};
}