import { HSHtml } from '@hyperspan/html'; import * as z from 'zod'; /** * Hyperspan Types */ export namespace Hyperspan { export interface Server { _config: Hyperspan.Config; _routes: Array; _middleware: Record>; use: ( middleware: Hyperspan.MiddlewareFunction, opts?: Hyperspan.MiddlewareMethodOptions ) => Hyperspan.Server; get: ( path: string, handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions ) => Hyperspan.Route; post: ( path: string, handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions ) => Hyperspan.Route; put: ( path: string, handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions ) => Hyperspan.Route; patch: ( path: string, handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions ) => Hyperspan.Route; delete: ( path: string, handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions ) => Hyperspan.Route; options: ( path: string, handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions ) => Hyperspan.Route; all: ( path: string, handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions ) => Hyperspan.Route; } export type Plugin = (config: Hyperspan.Config) => Promise | void; export type DisableStreamingFn = ( context: Hyperspan.Context, options: { hyperspanDisableStreaming: (context: Hyperspan.Context) => boolean; } ) => boolean; export type ResponseOptions = { disableStreaming?: DisableStreamingFn; }; export type Config = { appDir: string; publicDir: string; plugins: Array; // Loaders for client islands // For customizing the routes and adding your own... beforeRoutesAdded?: (server: Hyperspan.Server) => void; afterRoutesAdded?: (server: Hyperspan.Server) => void; responseOptions?: ResponseOptions; }; export type CookieOptions = { maxAge?: number; domain?: string; path?: string; expires?: Date; httpOnly?: boolean; secure?: boolean; sameSite?: 'lax' | 'strict' | true; }; export type Cookies = { _req: Request; _responseHeaders: Headers | undefined; _parsedCookies: Record; _encrypt: ((str: string) => string) | undefined; _decrypt: ((str: string) => string) | undefined; get: (name: string) => string | undefined; set: (name: string, value: string, options?: CookieOptions) => void; delete: (name: string) => void; }; export type HSRequest = { url: URL; raw: Request; method: string; // Always uppercase headers: Headers; // Case-insensitive query: URLSearchParams; cookies: Hyperspan.Cookies; text: () => Promise; json(): Promise; formData(): Promise; urlencoded(): Promise; }; export type HSResponse = { cookies: Hyperspan.Cookies; headers: Headers; // Headers to merge with final outgoing response status: number | undefined; html: (html: string, options?: ResponseInit) => Promise; json: (json: any, options?: ResponseInit) => Promise; text: (text: string, options?: ResponseInit) => Promise; redirect: (url: string, options?: ResponseInit) => Promise; error: (error: Error, options?: ResponseInit) => Promise; notFound: (options?: ResponseInit) => Promise; merge: (response: Response) => Promise; }; export interface Context { vars: Record; route: RouteConfig; req: HSRequest; res: HSResponse; } export type ClientIslandOptions = { ssr?: boolean; loading?: 'lazy' | undefined; }; export type RouteConfig = { name: string | undefined; path: string; params: Record; cssImports: string[]; responseOptions?: ResponseOptions; }; export type RouteHandlerReturn = | Response | HSHtml | string | ReadableStream | AsyncIterable | Iterable | undefined | null | void; export type RouteHandler = ( context: Hyperspan.Context ) => RouteHandlerReturn | Promise; export type RouteHandlerOptions = { middleware?: Hyperspan.MiddlewareFunction[]; }; // TypeScript inference for typed route params // Source - https://stackoverflow.com/a/78170543 // Posted by jcalz // Retrieved 2025-11-12, License - CC BY-SA 4.0 export type RouteParamsParser< T extends string, A = unknown, > = T extends `${string}:${infer F}/${infer R}` ? RouteParamsParser> : A & (T extends `${string}:${infer F}` ? Record : unknown) extends infer U ? { [K in keyof U]: U[K] } : never; /** * Next function type for middleware */ export type NextFunction = () => Promise; /** * Error handler function signature */ export type ErrorHandler = (context: Hyperspan.Context, error: Error) => unknown | undefined; /** * Middleware function signature * Accepts context and next function, returns a Response */ export type MiddlewareFunction = ( context: Hyperspan.Context, next: Hyperspan.NextFunction ) => Promise | Response; export type MiddlewareMethod = | 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS' | '*'; export type MiddlewareMethodOptions = { methods?: Hyperspan.MiddlewareMethod[]; }; export interface Route { _kind: 'hsRoute'; _config: Partial; _serverConfig?: Hyperspan.Config; _middleware: Record>; _path(): string; _methods(): string[]; get: ( handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions ) => Hyperspan.Route; post: ( handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions ) => Hyperspan.Route; put: ( handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions ) => Hyperspan.Route; patch: ( handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions ) => Hyperspan.Route; delete: ( handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions ) => Hyperspan.Route; options: ( handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions ) => Hyperspan.Route; all: ( handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions ) => Hyperspan.Route; errorHandler: (handler: Hyperspan.ErrorHandler) => Hyperspan.Route; use: ( middleware: Hyperspan.MiddlewareFunction, opts?: Hyperspan.MiddlewareMethodOptions ) => Hyperspan.Route; middleware: ( middleware: Array, opts?: Hyperspan.MiddlewareMethodOptions ) => Hyperspan.Route; fetch: (request: Request) => Promise; } /** * Action = Form + route handler */ /** Raw form field values when an action has no schema */ export type ActionFormValues = Record; /** Infer validated action data from an optional schema */ export type InferActionData = S extends z.ZodType ? z.output : ActionFormValues; // Form renderer export type ActionFormResponse = HSHtml | void | null | Promise; export type ActionFormProps = { data?: Partial>; error?: ZodValidationError; }; export type ActionForm = ( c: Context, props: ActionFormProps ) => ActionFormResponse; // Form handler export type ActionFormHandlerReturn = RouteHandlerReturn; export type ActionFormHandlerProps = { data: InferActionData; error?: ZodValidationError | Error; }; export type ActionFormHandler = ( c: Context, props: ActionFormHandlerProps ) => ActionFormHandlerReturn | Promise; // Action API export interface Action { _kind: 'hsAction'; _config: Partial; _serverConfig?: Hyperspan.Config; _path(): string; _form: null | ActionForm; form(form: ActionForm): Action; render: (c: Context, props?: ActionFormProps) => ActionFormResponse; post: (handler: ActionFormHandler) => Action; errorHandler: (handler: ActionFormHandler) => Action; use: ( middleware: Hyperspan.MiddlewareFunction, opts?: Hyperspan.MiddlewareMethodOptions ) => Action; middleware: ( middleware: Array, opts?: Hyperspan.MiddlewareMethodOptions ) => Action; fetch: (request: Request) => Promise; } /** * Client-side action lifecycle events (dispatched from ``, bubbles to document). * * - `hs:action:before-fetch` — before the action request starts (cancelable). * - `hs:action:after-fetch` — after the action request finishes (success or error). * - `hs:action:before-swap` — before HTML morph (cancelable). Close modals here. * - `hs:action:after-swap` — after HTML morph. * - `hs:action:before-navigate` — before redirect soft/hard navigation (cancelable). * Set `detail.hardNavigate = true` for a full page load, or `false` to fetch+morph in place. */ export type ActionEventName = | 'hs:action:before-fetch' | 'hs:action:after-fetch' | 'hs:action:before-swap' | 'hs:action:after-swap' | 'hs:action:before-navigate'; export type ActionFetchDetail = { form: HTMLFormElement; /** The current `` element, if present. */ action: HTMLElement | null; url: string; method: string; /** * Mutable. When `true` (default), appends `` inside `` for the * duration of the request. Set to `false` in `hs:action:before-fetch` to skip it. */ loadingElement: boolean; }; export type ActionSwapDetail = { form: HTMLFormElement; /** The current `` element, if present. */ action: HTMLElement | null; html: string; fullDocument: boolean; }; export type ActionNavigateDetail = { form: HTMLFormElement; /** The current `` element, if present. */ action: HTMLElement | null; url: string; /** * Mutable. Default is soft (false) for same-origin+same-path redirects, hard (true) otherwise. * Set to `true` for `window.location.assign`, or `false` to fetch + morph in place. */ hardNavigate: boolean; }; /** * Client JS Module = ESM Module + Public Path + Render Script Tag */ export type ClientJSBuildResult = { assetHash: string; // Asset hash of the module path esmName: string; // Filename of the built JavaScript file without the extension publicPath: string; // Full public path of the built JavaScript file /** * Render a