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