import { ComponentChildren, ComponentType, VNode } from "preact"; import { ServeInit } from "./deps.ts"; import * as router from "./router.ts"; import { InnerRenderFunction, RenderContext } from "./render.ts"; // --- APPLICATION CONFIGURATION --- export type StartOptions = ServeInit & FreshOptions; export interface FreshOptions { render?: RenderFunction; plugins?: Plugin[]; staticDir?: string; router?: RouterOptions; } export interface RouterOptions { /** * Controls whether Fresh will append a trailing slash to the URL. * @default {false} */ trailingSlash?: boolean; } export type RenderFunction = ( ctx: RenderContext, render: InnerRenderFunction, ) => void | Promise; /// --- ROUTES --- // deno-lint-ignore no-explicit-any export interface PageProps> { /** The URL of the request that resulted in this page being rendered. */ url: URL; /** The route matcher (e.g. /blog/:id) that the request matched for this page * to be rendered. */ route: string; /** * The parameters that were matched from the route. * * For the `/foo/:bar` route with url `/foo/123`, `params` would be * `{ bar: '123' }`. For a route with no matchers, `params` would be `{}`. For * a wildcard route, like `/foo/:path*` with url `/foo/bar/baz`, `params` would * be `{ path: 'bar/baz' }`. */ params: Record; /** * Additional data passed into `HandlerContext.render`. Defaults to * `undefined`. */ data: T; state: S; } /** * Context passed to async route components. */ export type RouteContext> = & Omit< HandlerContext, "render" > & Omit, "data">; export interface RouteConfig { /** * A route override for the page. This is useful for pages where the route * can not be expressed through the filesystem routing capabilities. * * The route override must be a path-to-regexp compatible route matcher. */ routeOverride?: string; /** * If Content-Security-Policy should be enabled for this page. If 'true', a * locked down policy will be used that allows only the scripts and styles * that are generated by fresh. Additional scripts and styles can be added * using the `useCSP` hook. */ csp?: boolean; } // deno-lint-ignore no-empty-interface export interface RenderOptions extends ResponseInit {} export type ServeHandlerInfo = { /** * Backwards compatible with std/server * @deprecated */ localAddr?: Deno.NetAddr; remoteAddr: Deno.NetAddr; }; export type ServeHandler = ( request: Request, info: ServeHandlerInfo, ) => Response | Promise; export interface HandlerContext> extends ServeHandlerInfo { params: Record; render: ( data?: Data, options?: RenderOptions, ) => Response | Promise; renderNotFound: (data?: Data) => Response | Promise; state: State; } // deno-lint-ignore no-explicit-any export type Handler> = ( req: Request, ctx: HandlerContext, ) => Response | Promise; // deno-lint-ignore no-explicit-any export type Handlers> = { [K in router.KnownMethod]?: Handler; }; /** * @deprecated This type was a short-lived alternative to `Handlers`. Please use `Handlers` instead. */ export type MultiHandler = Handlers; export interface RouteModule { default?: PageComponent; // deno-lint-ignore no-explicit-any handler?: Handler | Handlers; config?: RouteConfig; } export type AsyncRoute = ( req: Request, ctx: RouteContext, ) => Promise; export type PageComponent = | ComponentType> | AsyncRoute // deno-lint-ignore no-explicit-any | ((props: any) => VNode | ComponentChildren); // deno-lint-ignore no-explicit-any export interface Route { pattern: string; url: string; name: string; component?: PageComponent; handler: Handler | Handlers; csp: boolean; } export interface RouterState { state: Record; } // --- APP --- export interface AppProps extends PageProps { Component: ComponentType>; } export interface AppModule { default: ComponentType; } // --- UNKNOWN PAGE --- // deno-lint-ignore no-explicit-any export interface UnknownPageProps { /** The URL of the request that resulted in this page being rendered. */ url: URL; /** The route matcher (e.g. /blog/:id) that the request matched for this page * to be rendered. */ route: string; /** * Additional data passed into `HandlerContext.renderNotFound`. Defaults to * `undefined`. */ data: T; } export interface UnknownHandlerContext> extends ServeHandlerInfo { render: () => Response | Promise; state: State; } export type UnknownHandler = ( req: Request, ctx: UnknownHandlerContext, ) => Response | Promise; export interface UnknownPageModule { default?: PageComponent; handler?: UnknownHandler; config?: RouteConfig; } export interface UnknownPage { pattern: string; url: string; name: string; component?: PageComponent; handler: UnknownHandler; csp: boolean; } // --- ERROR PAGE --- export interface ErrorPageProps { /** The URL of the request that resulted in this page being rendered. */ url: URL; /** The route matcher (e.g. /blog/:id) that the request matched for this page * to be rendered. */ pattern: string; /** The error that caused the error page to be loaded. */ error: unknown; } export interface ErrorHandlerContext> extends ServeHandlerInfo { error: unknown; render: () => Response | Promise; state: State; } export type ErrorHandler = ( req: Request, ctx: ErrorHandlerContext, ) => Response | Promise; export interface ErrorPageModule { default?: PageComponent; handler?: ErrorHandler; config?: RouteConfig; } export interface ErrorPage { pattern: string; url: string; name: string; component?: PageComponent; handler: ErrorHandler; csp: boolean; } // --- MIDDLEWARES --- export interface MiddlewareHandlerContext> extends ServeHandlerInfo { next: () => Promise; state: State; destination: router.DestinationKind; params: Record; } export interface MiddlewareRoute extends Middleware { /** * path-to-regexp style url path */ pattern: string; /** * URLPattern of the route */ compiledPattern: URLPattern; } export type MiddlewareHandler> = ( req: Request, ctx: MiddlewareHandlerContext, ) => Response | Promise; // deno-lint-ignore no-explicit-any export interface MiddlewareModule { handler: MiddlewareHandler | MiddlewareHandler[]; } export interface Middleware> { handler: MiddlewareHandler | MiddlewareHandler[]; } // --- ISLANDS --- export interface IslandModule { // deno-lint-ignore no-explicit-any [key: string]: ComponentType; } export interface Island { id: string; name: string; url: string; component: ComponentType; exportName: string; } // --- PLUGINS --- export interface Plugin { /** The name of the plugin. Must be snake-case. */ name: string; /** A map of a snake-case names to a import specifiers. The entrypoints * declared here can later be used in the "scripts" option of * `PluginRenderResult` to load the entrypoint's code on the client. */ entrypoints?: Record; /** The render hook is called on the server every time some JSX needs to * be turned into HTML. The render hook needs to call the `ctx.render` * function exactly once. * * The hook can return a `PluginRenderResult` object that can do things like * inject CSS into the page, or load additional JS files on the client. */ render?(ctx: PluginRenderContext): PluginRenderResult; /** The asynchronous render hook is called on the server every time some * JSX needs to be turned into HTML, wrapped around all synchronous render * hooks. The render hook needs to call the `ctx.renderAsync` function * exactly once, and await the result. * * This is useful for when plugins are generating styles and scripts with * asynchronous dependencies. Unlike the synchronous render hook, async render * hooks for multiple pages can be running at the same time. This means that * unlike the synchronous render hook, you can not use global variables to * propagate state between the render hook and the renderer. */ renderAsync?(ctx: PluginAsyncRenderContext): Promise; routes?: PluginRoute[]; middlewares?: PluginMiddleware[]; } export interface PluginRenderContext { render: PluginRenderFunction; } export interface PluginAsyncRenderContext { renderAsync: PluginAsyncRenderFunction; } export interface PluginRenderResult { /** CSS styles to be injected into the page. */ styles?: PluginRenderStyleTag[]; /** JS scripts to ship to the client. */ scripts?: PluginRenderScripts[]; } export interface PluginRenderStyleTag { cssText: string; media?: string; id?: string; } export interface PluginRenderScripts { /** The "key" of the entrypoint (as specified in `Plugin.entrypoints`) for the * script that should be loaded. The script must be an ES module that exports * a default function. * * The default function is invoked with the `state` argument specified below. */ entrypoint: string; /** The state argument that is passed to the default export invocation of the * entrypoint's default export. The state must be JSON-serializable. */ state: unknown; } export type PluginRenderFunction = () => PluginRenderFunctionResult; export type PluginAsyncRenderFunction = () => | PluginRenderFunctionResult | Promise; export interface PluginRenderFunctionResult { /** The HTML text that was rendered. */ htmlText: string; /** If the renderer encountered any islands that require hydration on the * client. */ requiresHydration: boolean; } export interface PluginMiddleware { /** A path in the format of a filename path without filetype */ path: string; middleware: Middleware; } export interface PluginRoute { /** A path in the format of a filename path without filetype */ path: string; component?: ComponentType | ComponentType; // deno-lint-ignore no-explicit-any handler?: Handler | Handlers; }