import { FastifyRequest, FastifyReply, FastifyPluginAsync, FastifyPluginCallback, FastifyInstance, preHandlerHookHandler, FastifySchema, FastifyBaseLogger, FastifyLoggerOptions } from 'fastify';
export { FastifyReply, FastifyRequest, FastifyReply as ServerReply, FastifyRequest as ServerRequest } from 'fastify';
import { CookieSerializeOptions } from '@fastify/cookie';
import { SecureContext } from 'tls';
import { APIResponseHelpers } from 'unirend/api-envelope';
import { WebSocket } from 'ws';
import { ReactNode } from 'react';
import { RouteObject } from 'react-router';
import { Logger, LoggerService } from 'lifecycleion/logger';

interface PageMetadata {
    title: string;
    description: string;
    keywords?: string;
    canonical?: string;
    og?: {
        title?: string;
        description?: string;
        image?: string;
    };
}
/**
 * Base meta structure with required page metadata
 */
interface BaseMeta {
    page?: PageMetadata;
}
interface ErrorDetails {
    [key: string]: unknown;
}
/**
 * Error details value - supports both object and array formats
 *
 * @example Object format (structured errors)
 * { field: 'email', reason: 'invalid format', code: 'VALIDATION_ERROR' }
 *
 * @example Array format (multiple validation errors with type field)
 * [
 *   { field: 'email', type: 'invalid_email', message: 'Must be a valid email address' },
 *   { field: 'password', type: 'invalid_length', message: 'Must be at least 8 characters long' }
 * ]
 *
 * @example Array format (error trace)
 * ['Step 1 failed', 'Rollback initiated', 'Cleanup completed']
 */
type ErrorDetailsValue = ErrorDetails | unknown[];
/**
 * Error object structure for API error responses
 */
interface ErrorObject {
    code: string;
    message: string;
    details?: ErrorDetailsValue;
}
interface RedirectInfo {
    target: string;
    permanent: boolean;
    preserve_query?: boolean;
}
/**
 * API Success Response with extensible meta
 *
 * @template T - The data type
 * @template M - Additional meta properties (extends BaseMeta)
 *
 * @example
 * // Basic usage (no extra meta)
 * type BasicResponse = APISuccessResponse<User>;
 *
 * @example
 * // With required extra meta fields
 * interface CustomMeta extends BaseMeta {
 *   pagination: { page: number; total: number };
 *   cache: { expires: string };
 * }
 * type PaginatedResponse = APISuccessResponse<User[], CustomMeta>;
 */
interface APISuccessResponse<T, M extends BaseMeta = BaseMeta> {
    status: 'success';
    status_code: number;
    request_id: string;
    request_timestamp?: string;
    type: 'api';
    data: T;
    meta: M;
    error?: null;
}
/**
 * API Error Response with extensible meta
 *
 * @template M - Additional meta properties (extends BaseMeta)
 */
interface APIErrorResponse<M extends BaseMeta = BaseMeta> {
    status: 'error';
    status_code: number;
    request_id: string;
    request_timestamp?: string;
    type: 'api';
    data: null;
    meta: M;
    error: ErrorObject;
}
/**
 * API response envelope as a discriminated union
 *
 * @template T - The data type for success responses
 * @template M - Additional meta properties (extends BaseMeta)
 */
type APIResponseEnvelope<T = unknown, M extends BaseMeta = BaseMeta> = APISuccessResponse<T, M> | APIErrorResponse<M>;
/**
 * Page Success Response with extensible meta
 *
 * @template T - The data type
 * @template M - Additional meta properties (extends BaseMeta)
 */
interface PageSuccessResponse<T, M extends BaseMeta = BaseMeta> {
    status: 'success';
    status_code: number;
    request_id: string;
    request_timestamp?: string;
    type: 'page';
    data: T;
    meta: M;
    error?: null;
    ssr_request_context?: Record<string, unknown>;
}
/**
 * Page Error Response with extensible meta
 *
 * @template M - Additional meta properties (extends BaseMeta)
 */
interface PageErrorResponse<M extends BaseMeta = BaseMeta> {
    status: 'error';
    status_code: number;
    request_id: string;
    request_timestamp?: string;
    type: 'page';
    data: null;
    meta: M;
    error: ErrorObject;
    ssr_request_context?: Record<string, unknown>;
}
/**
 * Page Redirect Response with extensible meta
 *
 * @template M - Additional meta properties (extends BaseMeta)
 */
interface PageRedirectResponse<M extends BaseMeta = BaseMeta> {
    status: 'redirect';
    status_code: 200;
    request_id: string;
    request_timestamp?: string;
    type: 'page';
    data: null;
    meta: M;
    error?: null;
    redirect: RedirectInfo;
    ssr_request_context?: Record<string, unknown>;
}
/**
 * Page response envelope as a discriminated union
 *
 * @template T - The data type for success responses
 * @template M - Additional meta properties (extends BaseMeta)
 */
type PageResponseEnvelope<T = unknown, M extends BaseMeta = BaseMeta> = PageSuccessResponse<T, M> | PageErrorResponse<M> | PageRedirectResponse<M>;

/**
 * Parameters passed to page data loader handlers with shortcuts to common fields
 *
 * Handlers should treat these params as the authoritative routing context
 * (routeParams, queryParams, requestPath, originalURL) produced by the
 * page data loader. Do not reconstruct routing info from the Fastify request.
 *
 * The Fastify request represents the original HTTP request and should be used
 * only for transport/ambient data (cookies, headers, IP, auth tokens, etc.).
 * During SSR, this is the same request that initiated the page render; after
 * hydration, client-side page data loader calls will include their own
 * transport context as appropriate.
 */
interface PageDataHandlerParams {
    pageType: string;
    version?: number;
    /** Indicates how the handler was invoked: via HTTP route or internal short-circuit */
    invocationOrigin: 'http' | 'internal';
    /** Route params (from React Router via POST body) */
    routeParams: Record<string, string>;
    /** Query params (from React Router via POST body) */
    queryParams: Record<string, unknown>;
    /** Request path (from React Router via POST body) */
    requestPath: string;
    /** Original URL (from React Router via POST body) */
    originalURL: string;
    /** The APIResponseHelpers class configured on this server (use this instead of importing directly) */
    APIResponseHelpers: APIResponseHelpersClass;
}
/**
 * Handler function type for page data endpoints
 *
 * @param request - The Fastify request object (original request). Use for cookies, headers, IP, etc.
 * @param params - Page data context (preferred for page routing: path, query, route params)
 * @returns A PageResponseEnvelope (recommended), APIResponseEnvelope (will be converted), or false if response already sent
 *
 * **Recommendation**: Return PageResponseEnvelope for optimal performance and control.
 * APIResponseEnvelope is supported but will be converted by the pageDataLoader, which
 * adds overhead and may not preserve all metadata as intended.
 *
 * **Return false** when you've sent a custom response (e.g., using
 * APIResponseHelpers.sendErrorEnvelope() or validation helpers like ensureJSONBody).
 * This signals that the handler has already sent the response and the framework
 * should not attempt to send anything.
 */
type PageDataHandler<T = unknown, M extends BaseMeta = BaseMeta> = (
/** Original HTTP request (for cookies/headers/IP/auth) */
originalRequest: FastifyRequest, reply: ControlledReply, params: PageDataHandlerParams) => Promise<PageResponseEnvelope<T, M> | APIResponseEnvelope<T, M> | false> | PageResponseEnvelope<T, M> | APIResponseEnvelope<T, M> | false;

/**
 * Parsed domain information derived from a request hostname.
 *
 * Used both on the Fastify request object (`request.domainInfo`) and in the
 * Unirend React context (`useDomainInfo()`).
 */
interface DomainInfo {
    /** Bare hostname with port stripped (IPv6-safe, e.g. `'app.example.com'` or `'::1'`). */
    hostname: string;
    /**
     * Apex domain without a leading dot (e.g. `'example.com'`).
     * Empty string for localhost and raw IP addresses where no root domain can be resolved.
     *
     * When empty, omit the `domain` attribute entirely — `domain=.localhost` is invalid
     * per RFC 6265 and browsers reject it. A cookie without a `domain` attribute becomes
     * a host-only cookie scoped to the exact hostname, which is correct for localhost and IPs.
     *
     * Prepend `.` when using as a cookie `domain` attribute to span subdomains:
     * ```ts
     * document.cookie = [
     *   'theme=dark',
     *   'path=/',
     *   'max-age=31536000',
     *   domainInfo.rootDomain ? `domain=.${domainInfo.rootDomain}` : null,
     * ].filter(Boolean).join('; ');
     * ```
     */
    rootDomain: string;
}

/**
 * Render mode type - SSR, SSG, or Client
 * - "ssr": Server-Side Rendering (runtime server rendering)
 * - "ssg": Static Site Generation (build-time server rendering)
 * - "client": Client-side execution (SPA or after a SSG build/SSR page hydration occurs)
 */
type UnirendRenderMode = 'ssr' | 'ssg' | 'client';
/**
 * Unirend context value type
 */
interface UnirendContextValue {
    /**
     * The render mode:
     * - 'ssr': Server-Side Rendering
     * - 'ssg': Static Site Generation
     * - 'client': Client-side execution (SPA or after a SSG build/SSR page hydration occurs)
     */
    renderMode: UnirendRenderMode;
    /**
     * Whether the app is running in development mode
     */
    isDevelopment: boolean;
    /**
     * The Fetch API Request object (available during SSR/SSG rendering)
     * Undefined on client-side after hydration
     */
    fetchRequest?: Request;
    /**
     * Public application configuration
     * This is a frozen (immutable) copy of the config passed to the server
     * Available on both server and client (injected into HTML during SSR/SSG)
     */
    publicAppConfig?: Record<string, unknown>;
    /**
     * CDN base URL for asset serving (e.g. 'https://cdn.example.com')
     * Available on both server (from app config or per-request override) and client
     * (read from window.__CDN_BASE_URL__ injected into HTML by the server)
     * Empty string when no CDN is configured
     */
    cdnBaseURL?: string;
    /**
     * Domain information computed server-side from the request hostname.
     * Available during SSR (computed per-request) and SSG when a `hostname` option is
     * provided at build time. `null` when hostname is not known (SSG without hostname
     * configured, or pure SPA — no server to compute it via the public suffix list).
     */
    domainInfo?: DomainInfo | null;
    /**
     * Request context revision counter for reactivity
     * Format: `${timestamp}-${counter}` (e.g., "1729123456789-0", "1729123456789-1")
     * Increments whenever request context is modified to trigger re-renders
     * @internal
     */
    requestContextRevision?: string;
}

type RenderType = 'ssg' | 'ssr';
type APIResponseHelpersClass = typeof APIResponseHelpers;
type FastifyTrustProxyFunction = (address: string, hop: number) => boolean;
interface RenderRequest {
    type: RenderType;
    fetchRequest: Request;
    /**
     * Unirend context value to provide to the app
     * Contains render mode, development status, and server request info
     * Always provided by SSRServer or SSG
     */
    unirendContext: UnirendContextValue;
}
/**
 * Base interface for render results with a discriminated union type
 */
interface RenderResultBase {
    resultType: 'page' | 'response' | 'render-error';
}
/**
 * Page result containing HTML content
 */
interface RenderPageResult extends RenderResultBase {
    resultType: 'page';
    html: string;
    preloadLinks: string;
    head?: {
        title: string;
        meta: string;
        link: string;
    };
    statusCode?: number;
    errorDetails?: Error;
    ssOnlyData?: Record<string, unknown>;
}
/**
 * Response result wrapping a standard Response object
 * Used for redirects, errors, or any other non-HTML responses
 */
interface RenderResponseResult extends RenderResultBase {
    resultType: 'response';
    response: Response;
}
/**
 * Error result containing error information
 * Used when rendering fails with an exception
 */
interface RenderErrorResult extends RenderResultBase {
    resultType: 'render-error';
    error: Error;
}
/**
 * Union type for all possible render results
 */
type RenderResult = RenderPageResult | RenderResponseResult | RenderErrorResult;
/**
 * Required paths for SSR development server
 */
interface SSRDevPaths {
    /** Path to the server entry file (e.g. "./src/EntrySSR.tsx") */
    serverEntry: string;
    /** Path to the HTML template file (e.g. "./index.html") */
    template: string;
    /** Path to the Vite config file (e.g. "./vite.config.ts") */
    viteConfig: string;
}
/**
 * Plugin metadata returned by plugins for dependency tracking and cleanup
 */
interface PluginMetadata {
    /** Unique name for this plugin */
    name: string;
    /** Plugin dependencies - other plugin names that must be registered first */
    dependsOn?: string | string[];
}
/**
 * Plugin registration function type
 * Plugins get access to a controlled subset of Fastify functionality
 * Can optionally return metadata for dependency tracking
 */
type ServerPlugin = (pluginHost: PluginHostInstance, options: PluginOptions) => Promise<PluginMetadata | void> | PluginMetadata | void;
/**
 * Fastify hook names that plugins can register
 * Includes common lifecycle hooks plus string for custom hooks
 */
type FastifyHookName = Parameters<FastifyInstance['addHook']>[0];
/**
 * Controlled Fastify instance interface for plugins
 * Exposes safe methods while preventing access to destructive operations
 */
interface PluginHostInstance {
    /** Register plugins and middleware */
    register: <Options extends Record<string, unknown> = Record<string, never>>(plugin: FastifyPluginAsync<Options> | FastifyPluginCallback<Options>, opts?: Options) => Promise<void>;
    /** Add custom hooks */
    addHook: (hookName: FastifyHookName, handler: (request: FastifyRequest, reply: FastifyReply, ...args: unknown[]) => unknown) => void;
    /** Add decorators to request/reply objects */
    decorate: (property: string, value: unknown) => void;
    decorateRequest: (property: string, value: unknown) => void;
    decorateReply: (property: string, value: unknown) => void;
    /** Read-only accessors for server-level decorations */
    hasDecoration: (property: string) => boolean;
    getDecoration: <T = unknown>(property: string) => T | undefined;
    /** Access to route registration with constraints */
    route: (opts: SafeRouteOptions) => void;
    get: (path: string, handler: RouteHandler) => void;
    post: (path: string, handler: RouteHandler) => void;
    put: (path: string, handler: RouteHandler) => void;
    delete: (path: string, handler: RouteHandler) => void;
    patch: (path: string, handler: RouteHandler) => void;
    /** Server-level logger (pino). Use `(obj, msg)` argument order. Useful for logging during plugin setup, before any request exists. */
    log: FastifyBaseLogger;
    /** API route registration shortcuts method for versioned endpoints */
    api?: unknown;
    /** Page data loader handler registration method for page data endpoints */
    pageDataHandler?: unknown;
    /** The APIResponseHelpers class configured on this server — use this to build envelopes so custom subclasses are respected */
    APIResponseHelpers: APIResponseHelpersClass;
}
/**
 * Controlled reply surface available to handlers.
 * Allows setting headers and cookies without giving full reply control.
 *
 * Used by page data loader handlers, API route handlers, and processFileUpload().
 * Provides limited access to prevent handlers from prematurely sending responses
 * or bypassing the framework's envelope pattern.
 */
interface ControlledReply {
    /** Set a response header (content-type may be enforced by framework) */
    header: (name: string, value: string) => void;
    /** Set a cookie if @fastify/cookie is registered */
    setCookie?: (name: string, value: string, options?: CookieSerializeOptions) => void;
    /** Alias for setCookie if @fastify/cookie is registered */
    cookie?: (name: string, value: string, options?: CookieSerializeOptions) => void;
    /** Clear a cookie if @fastify/cookie is registered */
    clearCookie?: (name: string, options?: CookieSerializeOptions) => void;
    /** Verify and unsign a cookie value if @fastify/cookie is registered */
    unsignCookie?: (value: string) => {
        valid: true;
        renew: boolean;
        value: string;
    } | {
        valid: false;
        renew: false;
        value: null;
    };
    /** Sign a cookie value if @fastify/cookie is registered */
    signCookie?: (value: string) => string;
    /** Read a response header value (if available) */
    getHeader: (name: string) => string | number | string[] | undefined;
    /** Read all response headers as a plain object */
    getHeaders: () => Record<string, unknown>;
    /** Remove a response header by name */
    removeHeader: (name: string) => void;
    /** Check if a response header has been set */
    hasHeader: (name: string) => boolean;
    /** Whether the reply has already been sent */
    sent: boolean;
    /**
     * Access to the underlying response stream (for connection monitoring)
     *
     * Limited scope: Only used internally by processFileUpload() for detecting
     * broken connections during file uploads. Most handlers won't need this
     */
    raw: {
        /** Whether the underlying connection has been destroyed */
        destroyed: boolean;
    };
    /**
     * Internal: send an error envelope and finish the response immediately.
     *
     * This is installed by the framework's controlled-reply wrapper so helpers
     * can terminate with the same raw/hijacked path while still reusing shared
     * header logic such as CORS application.
     *
     * ControlledReply intentionally does not expose general-purpose send/write
     * methods to user handlers. This internal escape hatch exists only so
     * framework-owned helpers like APIResponseHelpers can terminate early in a
     * controlled way without reopening arbitrary reply access.
     */
    _sendErrorEnvelope: (statusCode: number, errorEnvelope: APIErrorResponse<BaseMeta> | PageErrorResponse<BaseMeta>) => Promise<void>;
}
/**
 * Safe route options that prevent catch-all conflicts
 */
interface SafeRouteOptions {
    method: string | string[];
    url: string;
    handler: RouteHandler;
    preHandler?: preHandlerHookHandler | preHandlerHookHandler[];
    schema?: FastifySchema;
    config?: unknown;
    constraints?: {
        /** Only allow specific hosts, no wildcards that could conflict with SSR */
        host?: string;
        /** Only allow specific versions */
        version?: string;
    };
}
type RouteHandler = (request: FastifyRequest, reply: FastifyReply) => void | Promise<unknown>;
/**
 * WebSocket server configuration options
 */
interface WebSocketOptions {
    /**
     * Enable/disable permessage-deflate compression
     * @default false
     */
    perMessageDeflate?: boolean;
    /**
     * The maximum allowed message size in bytes
     * @default 100 * 1024 * 1024 (100MB)
     */
    maxPayload?: number;
    /**
     * Custom handler called when the WebSocket server is closing
     * Provides access to all connected clients for graceful shutdown
     * @param clients Set of all connected WebSocket clients
     * @returns Promise that resolves when cleanup is complete
     */
    preClose?: (clients: Set<unknown>) => Promise<void>;
}
/**
 * HTTPS server configuration options
 * Provides first-class HTTPS support with certificate files and SNI callback
 */
interface HTTPSOptions {
    /**
     * Private key in PEM format
     * Can be a string, Buffer, or array of strings/Buffers for multiple keys
     */
    key: string | Buffer | Array<string | Buffer>;
    /**
     * Certificate chain in PEM format
     * Can be a string, Buffer, or array of strings/Buffers for multiple certificates
     */
    cert: string | Buffer | Array<string | Buffer>;
    /**
     * Optional CA certificates in PEM format
     * Used for client certificate verification
     */
    ca?: string | Buffer | Array<string | Buffer>;
    /**
     * Optional passphrase for the private key
     */
    passphrase?: string;
    /**
     * Optional SNI (Server Name Indication) callback for dynamic certificate selection
     * Useful for multi-tenant SaaS applications serving multiple domains
     *
     * The callback receives the server name (domain) and should return a SecureContext
     * with the appropriate certificate for that domain. Can be async.
     *
     * @param servername - The domain name from the TLS handshake
     * @returns SecureContext with the appropriate certificate, or a Promise resolving to one
     *
     * @example
     * ```ts
     * sni: async (servername) => {
     *   const ctx = tls.createSecureContext({
     *     key: await loadKeyForDomain(servername),
     *     cert: await loadCertForDomain(servername),
     *   });
     *
     *   return ctx;
     * }
     * ```
     */
    sni?: (servername: string) => SecureContext | Promise<SecureContext>;
}
/**
 * Shared configuration for versioned API endpoint groups
 * Used by helpers that register versioned endpoints (page data, generic API routes, etc.)
 */
interface APIEndpointConfig {
    /**
     * Endpoint prefix that comes before version/endpoint (default: "/api")
     * Set to `false` to disable API handling (server becomes a plain web server)
     */
    apiEndpointPrefix?: string | false;
    /** Whether to enable versioning (default: true) */
    versioned?: boolean;
    /** Base endpoint name for page data loader handlers (default: "page_data"). Used by SSR/APIServer's page-data registration only. */
    pageDataEndpoint?: string;
}
/**
 * Plugin options passed to each plugin
 *
 * Environment information available at plugin registration time.
 *
 * Notes:
 * - Use these fields inside your plugin setup to decide what to REGISTER
 *   (e.g., which routes, which hooks). This is registration-time context.
 * - For per-request branching inside handlers/middleware, read
 *   `request.isDevelopment` (decorated by the servers). Both reflect the same
 *   underlying mode; they serve different scopes.
 */
interface PluginOptions {
    /** Type of server the plugin is running on */
    serverType: 'ssr' | 'api';
    /** Server mode (development or production) */
    mode: 'development' | 'production';
    /** Whether running in development mode */
    isDevelopment: boolean;
    /** API endpoints configuration from the server */
    apiEndpoints?: APIEndpointConfig;
}
/**
 * Log levels supported by the Unirend logger adapter.
 */
type UnirendLoggerLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
/**
 * Logger function signature used by Unirend logger object methods.
 */
type UnirendLoggerFunction = (message: string, context?: Record<string, unknown>) => void;
/**
 * Framework-level logger object that Unirend adapts to Fastify's logger interface.
 */
interface UnirendLoggerObject {
    trace: UnirendLoggerFunction;
    debug: UnirendLoggerFunction;
    info: UnirendLoggerFunction;
    warn: UnirendLoggerFunction;
    error: UnirendLoggerFunction;
    fatal: UnirendLoggerFunction;
}
/**
 * High-level logging options that Unirend can adapt to Fastify.
 */
interface UnirendLoggingOptions {
    /**
     * Logger object used by Unirend and adapted to Fastify under the hood.
     */
    logger: UnirendLoggerObject;
    /**
     * Initial minimum level used by the adapter.
     * @default "info"
     */
    level?: UnirendLoggerLevel;
}
/**
 * Subset of Fastify server options safe for use with Unirend servers.
 * Excludes options that would conflict with server setup.
 *
 * These options are supported by SSRServer, APIServer, and StaticWebServer.
 */
interface FastifyServerOptions {
    /**
     * Enable/configure Fastify logging
     * @example true | false | { level: 'info' } | { level: 'warn', prettyPrint: true }
     */
    logger?: boolean | FastifyLoggerOptions;
    /**
     * Custom Fastify logger instance (e.g. pino-compatible logger).
     * When provided, this is passed to Fastify as `loggerInstance`.
     */
    loggerInstance?: FastifyBaseLogger;
    /**
     * Trust proxy headers (useful for deployment behind load balancers)
     * Passed directly to Fastify. Supports `true`, IP/CIDR strings like
     * `'127.0.0.1'` or `'127.0.0.1,192.168.1.1/24'`, IP/CIDR lists like
     * `['127.0.0.1', '10.0.0.0/8']`, hop counts, or a custom trust function
     * `(address, hop) => boolean`.
     * @default false
     */
    trustProxy?: boolean | string | string[] | number | FastifyTrustProxyFunction;
    /**
     * Maximum request body size in bytes for non-multipart requests (JSON, text,
     * URL-encoded forms). Does NOT apply to multipart file uploads — those are
     * controlled by `fileUploads.limits.fileSize` (the multipart plugin registers
     * its own streaming content-type parser, bypassing this limit).
     * @default 1048576 (1MB)
     */
    bodyLimit?: number;
    /**
     * Keep-alive timeout in milliseconds
     * @default 72000 (72 seconds)
     */
    keepAliveTimeout?: number;
    /**
     * Request idle timeout in milliseconds. The timer resets on every data chunk
     * received, so large file uploads are not affected as long as data keeps
     * flowing. A stalled or hung request (no new data) is closed with 408 once
     * the timeout elapses. Set to `0` to disable. When running without a reverse
     * proxy, `30000` (30 s) is a reasonable starting point.
     * @default 0 (disabled)
     */
    requestTimeout?: number;
    /**
     * TCP connection timeout in milliseconds. Closes connections that have been
     * idle (no data in either direction) for longer than this value. Protects
     * against clients that open a socket but never send an HTTP request. Set to
     * `0` to disable.
     *
     * When running without a reverse proxy, `10000` (10 s) is a reasonable
     * starting point. **WebSocket caveat:** this timeout applies to the
     * underlying socket, so idle WebSocket connections (e.g. a notification
     * channel waiting for events) will be closed if no frames are exchanged
     * within the timeout window. Only set this when WebSockets are disabled, or
     * ensure your clients send regular ping/pong heartbeats to keep the socket
     * active.
     * @default 0 (disabled)
     */
    connectionTimeout?: number;
}
/**
 * Log level config for access logging.
 * Can be a single level applied to all requests, or per-status-range.
 */
type AccessLogLevelConfig = UnirendLoggerLevel | {
    /** Level for 2xx–3xx responses. @default 'info' */
    success?: UnirendLoggerLevel;
    /** Level for 4xx responses. @default 'warn' */
    clientError?: UnirendLoggerLevel;
    /** Level for 5xx responses. @default 'error' */
    serverError?: UnirendLoggerLevel;
};
/**
 * Context passed to onRequest access log hook (request start).
 */
interface AccessLogRequestContext {
    /** Stable source identifier for access log entries and hooks. */
    logSource: 'unirend.accessLog';
    reqID: string | number;
    method: string;
    url: string;
    ip: string;
    userAgent: string | undefined;
    /** Server label string (e.g. `'SSR'`, `'API'`). Set via the `serverLabel` server option. */
    serverLabel: string;
    /** Whether the request is being handled as a static asset response. */
    isStaticAsset: boolean;
    /** Raw Fastify request — same access pattern as data loaders. */
    request: FastifyRequest;
}
/**
 * Read-only snapshot of reply state at response time.
 * Extracted to prevent accidental mutation of the response.
 */
interface AccessLogReplyInfo {
    statusCode: number;
    headers: Record<string, string | string[] | undefined>;
}
/**
 * Context passed to onResponse access log hook (request finish or abort).
 */
interface AccessLogResponseContext extends AccessLogRequestContext {
    statusCode: number;
    /** Elapsed time in milliseconds. */
    responseTime: number;
    /** Whether the request completed normally or the client disconnected early. */
    finishType: 'completed' | 'aborted';
    /** Read-only reply snapshot. */
    replyInfo: AccessLogReplyInfo;
}
/**
 * First-party access logging configuration.
 * Applies to all server types (SSRServer, APIServer, StaticWebServer, RedirectServer).
 *
 * Fastify's built-in request lifecycle logs are always suppressed internally,
 * this config controls what Unirend logs instead.
 */
interface AccessLogConfig {
    /**
     * Which lifecycle events to log.
     * @default 'finish'
     */
    events?: 'start' | 'finish' | 'both' | 'none';
    /**
     * Template for finish/response log lines. Supports {{variable}} placeholders.
     * Available variables: logSource, method, url, statusCode, responseTime, finishType, reqID, ip, userAgent, serverLabel, isStaticAsset
     * @default 'Request finished {{method}} {{url}} {{statusCode}} ({{responseTime}}ms)'
     */
    responseTemplate?: string;
    /**
     * Template for start/request log lines. Supports {{variable}} placeholders.
     * Available variables: logSource, method, url, reqID, ip, userAgent, serverLabel, isStaticAsset
     * @default 'Request started {{method}} {{url}}'
     */
    requestTemplate?: string;
    /**
     * Log level for printed lines. Defaults to status-code-based:
     * info for 2xx/3xx, warn for 4xx, error for 5xx.
     */
    level?: AccessLogLevelConfig;
    /**
     * Custom hook fired at request start when provided.
     * Awaited before request handling continues.
     * Useful for writing initial access log records (DB insert, etc.).
     */
    onRequest?: (context: AccessLogRequestContext) => void | Promise<void>;
    /**
     * Custom hook fired when a request finishes when provided
     * (both normal completion and client aborts).
     * Awaited after the response finishes or aborts.
     * Use context.finishType to distinguish 'completed' from 'aborted'.
     */
    onResponse?: (context: AccessLogResponseContext) => void | Promise<void>;
}
interface ResponseTimeHeaderOptions {
    /**
     * Whether to emit the response-time header.
     * @default true
     */
    enabled?: boolean;
    /**
     * Header name to emit.
     * @default 'X-Response-Time'
     */
    headerName?: string;
    /**
     * Number of fractional digits to include in the emitted time.
     * @default 2
     */
    digits?: number;
}
/**
 * Base options for SSR
 * @template M Custom meta type extending BaseMeta for error/notFound handlers
 */
interface ServeSSROptions<M extends BaseMeta = BaseMeta> {
    /**
     * Response compression for non-streaming SSR HTML and API responses.
     * Negotiates `Accept-Encoding` and skips range or already-encoded replies.
     *
     * Static files served through `staticContentRouter` use the same shape but
     * handle compression in the static file layer so ETags and range requests
     * remain correct.
     *
     * @default true
     */
    responseCompression?: boolean | ResponseCompressionOptions;
    /**
     * Optional response-time header emitted on completed responses.
     * Normal Fastify-managed replies apply and measure this in `onSend`.
     *
     * For hijacked/raw replies, it is applied when `reply.hijack()` is called so
     * `reply.getHeaders()` includes it before a
     * subsequent raw `writeHead(...)`. Access logging measures when the response
     * finishes, so raw/hijacked responses can report different timings there.
     *
     * @default false
     */
    responseTimeHeader?: boolean | ResponseTimeHeaderOptions;
    /**
     * ID of the container element (defaults to "root")
     * This element will be formatted inline to prevent hydration issues
     */
    containerID?: string;
    /**
     * Optional safe-to-share app configuration object.
     * Cloned and frozen per request. On SSR, exposed to React via
     * usePublicAppConfig() and injected as window.__PUBLIC_APP_CONFIG__.
     *
     * Keep this minimal and non-sensitive; it can be passed to the client.
     */
    publicAppConfig?: Record<string, unknown>;
    /**
     * Cookie forwarding controls for SSR
     *
     * Controls which cookies are forwarded:
     * - from client request → SSR loaders (via the Fetch `Cookie` header)
     * - from backend/server → client (via `Set-Cookie` headers)
     *
     * Behavior:
     * - If both arrays are empty or undefined, all cookies are allowed
     * - If `allowCookieNames` is non-empty, only cookies with those names are allowed
     * - `blockCookieNames` is always applied and will block those cookies even if in allow list
     */
    cookieForwarding?: {
        /**
         * Cookie names that are allowed to be forwarded.
         * If provided and non-empty, only these cookie names will be forwarded.
         */
        allowCookieNames?: string[];
        /**
         * Cookie names that must never be forwarded (takes precedence over allow list).
         *
         * You can also set this to `true` to block ALL cookies from being forwarded.
         * When `true`, no cookies will be forwarded regardless of `allowCookieNames`.
         */
        blockCookieNames?: string[] | true;
    };
    /**
     * Array of plugins to register with the server
     * Plugins get access to a controlled Fastify instance
     */
    plugins?: ServerPlugin[];
    /**
     * Override the helpers used to construct API/Page envelopes.
     * Provide your own class (subclassing `APIResponseHelpers` recommended) to
     * inject default metadata or behavior. If not provided, the default
     * `APIResponseHelpers` will be used.
     */
    APIResponseHelpersClass?: APIResponseHelpersClass;
    /**
     * Configuration for versioned API endpoints (shared by page data and generic API routes)
     * For page data loader handler endpoints, set pageDataEndpoint (default: "page_data")
     */
    apiEndpoints?: APIEndpointConfig;
    /**
     * File upload configuration
     * When enabled, multipart file upload support will be available
     * Allows use of processFileUpload() in your plugins
     */
    fileUploads?: FileUploadsConfig;
    /**
     * Name of the client folder within buildDir
     * Defaults to "client" if not provided
     */
    clientFolderName?: string;
    /**
     * Name of the server folder within buildDir
     * Defaults to "server" if not provided
     */
    serverFolderName?: string;
    /**
     * Custom 500 error page handler
     * Called when SSR rendering fails with an error
     * @param request The Fastify request object
     * @param error The error that occurred
     * @param isDevelopment Whether running in development mode
     * @returns HTML string for the error page
     */
    get500ErrorPage?: (request: FastifyRequest, error: Error, isDevelopment: boolean) => string | Promise<string>;
    /**
     * Custom error/not-found handlers for mixed SSR+API servers
     * These handlers return JSON envelope responses instead of HTML error pages
     * for requests matching the apiEndpoints.apiEndpointPrefix
     */
    APIHandling?: {
        /**
         * Custom error handler for API routes
         * Called when an unhandled error occurs in API routes
         *
         * REQUIRED: Must return a proper API or Page envelope response according to api-envelope-structure.md
         * - For API requests (isPageData=false): Return APIErrorResponse envelope
         * - For Page requests (isPageData=true): Return PageErrorResponse envelope
         *
         * Params: (request, error, isDevelopment, isPageData)
         * - request: The Fastify request object
         * - error: The error that occurred
         * - isDevelopment: Whether running in development mode
         * - isPageData: Whether this is a page-data request (e.g., /api/v1/page_data/home)
         *
         * Required envelope return fields:
         * - status: "error"
         * - status_code: HTTP status code (400, 401, 404, 500, etc.)
         * - request_id: Unique request identifier
         * - type: "api" for API requests, "page" for page data requests
         * - data: null (always null for error responses)
         * - meta: Object containing metadata (page metadata required for page type)
         * - error: Object with { code, message, details? }
         */
        errorHandler?: APIErrorHandlerFn<M>;
        /**
         * Custom handler for API requests that did not match any route (404)
         * If provided, overrides the built-in envelope handler for API routes
         *
         * REQUIRED: Must return a proper API or Page envelope response according to api-envelope-structure.md
         * - For API requests (isPageData=false): Return APIErrorResponse envelope with status_code: 404
         * - For Page requests (isPageData=true): Return PageErrorResponse envelope with status_code: 404
         *
         * Params: (request, isPageData)
         * - request: The Fastify request object
         * - isPageData: Whether this is a page-data request (e.g., /api/v1/page_data/home)
         *
         * Required envelope return fields:
         * - status: "error"
         * - status_code: 404
         * - request_id: Unique request identifier
         * - type: "api" for API requests, "page" for page data requests
         * - data: null (always null for error responses)
         * - meta: Object containing metadata (page metadata required for page type)
         * - error: Object with { code: "not_found", message, details? }
         */
        notFoundHandler?: APINotFoundHandlerFn<M>;
    };
    /**
     * Custom handler for web requests that arrive while the server is shutting down.
     *
     * Function form handles web requests. Object form can split API and web
     * behavior for mixed SSR + API servers. Missing handlers fall back to
     * Unirend's default 503 response.
     */
    closingHandler?: WebClosingHandlerFn | SplitClosingHandler<M>;
    /**
     * Enable WebSocket support on the server
     * @default false
     */
    enableWebSockets?: boolean;
    /**
     * WebSocket server configuration options
     * Only used when enableWebSockets is true
     */
    webSocketOptions?: WebSocketOptions;
    /**
     * HTTPS server configuration
     * Provides first-class HTTPS support with key, cert, and SNI callback
     *
     * @example Basic HTTPS
     * ```ts
     * https: {
     *   key: privateKey,     // string | Buffer
     *   cert: certificate,   // string | Buffer
     * }
     * ```
     *
     * @example SNI callback for multi-tenant SaaS
     * ```ts
     * https: {
     *   key: defaultPrivateKey,   // string | Buffer - Default cert
     *   cert: defaultCertificate,  // string | Buffer
     *   sni: async (servername) => {
     *     // Load certificate based on domain
     *     const { key, cert } = await loadCertForDomain(servername);
     *
     *     // Return a secure context for the domain
     *     return tls.createSecureContext({ key, cert });
     *   },
     * }
     * ```
     */
    https?: HTTPSOptions;
    /**
     * Curated Fastify options for SSR server configuration
     * Only exposes safe options that won't conflict with SSR setup
     */
    fastifyOptions?: FastifyServerOptions;
    /**
     * Framework-level logging options adapted to Fastify under the hood.
     *
     * Note: Cannot be used together with `fastifyOptions.logger` or
     * `fastifyOptions.loggerInstance`.
     */
    logging?: UnirendLoggingOptions;
    /**
     * First-party access logging configuration
     * Controls request/response logging without needing a custom plugin
     */
    accessLog?: AccessLogConfig;
    /**
     * Custom client IP resolver.
     * When set, called once per request to populate `request.clientIP` — available
     * throughout the entire request lifecycle (plugins, hooks, page data loader
     * handlers, API route handlers, access log templates/hooks, etc.).
     * When not set, `request.clientIP` falls back to `request.ip`
     * (which reflects Fastify proxy handling when `fastifyOptions.trustProxy`
     * is configured).
     *
     * Use this when behind Cloudflare, AWS ALB, or other CDNs that carry the
     * real client IP in a custom header.
     */
    getClientIP?: (request: FastifyRequest) => string | Promise<string>;
    /**
     * Whether to automatically log errors via the server logger
     * When enabled, all errors are logged before custom error handlers run
     * Useful for debugging custom error pages that can't show stack traces
     * @default true
     */
    logErrors?: boolean;
    /**
     * Label for this server instance, used in error log messages and access log templates.
     * Useful for distinguishing log output when running multiple server instances.
     * @default 'SSR'
     * @example 'SSR:marketing'
     */
    serverLabel?: string;
    /**
     * Timeout in milliseconds for the SSR render fetch request.
     * If the render takes longer than this, the request is aborted.
     * @default 5000 (5 seconds)
     */
    ssrRenderTimeout?: number;
}
interface ServeSSRDevOptions<M extends BaseMeta = BaseMeta> extends ServeSSROptions<M> {
}
interface ServeSSRProdOptions<M extends BaseMeta = BaseMeta> extends ServeSSROptions<M> {
    /**
     * Name of the server entry file to look for in the Vite manifest
     * Defaults to "EntrySSR" if not provided
     */
    serverEntry?: string;
    /**
     * Path to the HTML template file relative to buildDir
     * Defaults to "client/index.html" if not provided
     *
     * @example
     * // Default behavior - uses buildDir/client/index.html
     * serveSSRProd('./build')
     *
     * @example
     * // Custom template location
     * serveSSRProd('./build', { template: 'dist/app.html' })
     */
    template?: string;
    /**
     * CDN base URL for rewriting asset URLs in HTML at runtime
     * If provided, rewrites <script src> and <link href> to use this base URL
     * Defaults to relative URLs if not provided
     *
     * @example
     * // Rewrite /assets/main.js to https://cdn.example.com/assets/main.js
     * serveSSRProd('./build', {
     *   CDNBaseURL: 'https://cdn.example.com',
     *   staticContentRouter: false,  // Disable local serving
     * })
     */
    CDNBaseURL?: string;
    /**
     * Configuration for the static file router middleware
     * Used to serve static assets in production mode
     *
     * - If not provided: defaults will be used based on the build directory
     * - If set to `false`: static router will be disabled (useful for CDN setups)
     * - If set to an object: custom configuration will be used
     */
    staticContentRouter?: StaticContentRouterOptions | false;
}
/**
 * Options for registering additional dev apps via registerDevApp()
 * Only includes per-app options (excludes server-level shared options)
 */
type RegisterDevAppOptions<M extends BaseMeta = BaseMeta> = Pick<ServeSSRDevOptions<M>, 'publicAppConfig' | 'containerID' | 'get500ErrorPage' | 'clientFolderName' | 'serverFolderName'>;
/**
 * Options for registering additional prod apps via registerProdApp()
 * Only includes per-app options (excludes server-level shared options)
 */
type RegisterProdAppOptions<M extends BaseMeta = BaseMeta> = Pick<ServeSSRProdOptions<M>, 'publicAppConfig' | 'containerID' | 'get500ErrorPage' | 'clientFolderName' | 'serverFolderName' | 'serverEntry' | 'template' | 'CDNBaseURL' | 'staticContentRouter'>;
/**
 * Response type for web (non-API) handlers.
 * Similar to InvalidDomainResponse from domainValidation plugin.
 */
interface WebResponse {
    /** Content type for the response */
    contentType: 'html' | 'text' | 'json';
    /** Response content - string for html/text, object for json */
    content: string | object;
    /** HTTP status code. Defaults depend on the handler that uses this response. */
    statusCode?: number;
}
/**
 * Error handler function type for API/page requests
 */
type APIErrorHandlerFn<M extends BaseMeta = BaseMeta> = (request: FastifyRequest, error: Error, isDevelopment: boolean, isPageData?: boolean) => APIErrorResponse<M> | PageErrorResponse<M> | Promise<APIErrorResponse<M> | PageErrorResponse<M>>;
/**
 * Error handler function type for web (non-API) requests
 */
type WebErrorHandlerFn = (request: FastifyRequest, error: Error, isDevelopment: boolean) => WebResponse | Promise<WebResponse>;
/**
 * Not found handler function type for API/page requests
 */
type APINotFoundHandlerFn<M extends BaseMeta = BaseMeta> = (request: FastifyRequest, isPageData?: boolean) => APIErrorResponse<M> | PageErrorResponse<M> | Promise<APIErrorResponse<M> | PageErrorResponse<M>>;
/**
 * Not found handler function type for web (non-API) requests
 */
type WebNotFoundHandlerFn = (request: FastifyRequest) => WebResponse | Promise<WebResponse>;
/**
 * Split error handler with separate API and web handlers
 * Both handlers are optional - if a handler is missing or throws an error,
 * the error is logged to the Fastify logger and the server falls back to the default error response.
 *
 * @template M Custom meta type extending BaseMeta for API handlers
 */
interface SplitErrorHandler<M extends BaseMeta = BaseMeta> {
    /** Handler for API requests (paths matching apiEndpointPrefix). If missing or throws, logs error and falls back to default. */
    api?: APIErrorHandlerFn<M>;
    /** Handler for web requests (non-API paths). If missing or throws, logs error and falls back to default. */
    web?: WebErrorHandlerFn;
}
/**
 * Split not found handler with separate API and web handlers
 * Both handlers are optional - if a handler is missing or throws an error,
 * the error is logged to the Fastify logger and the server falls back to the default not found response.
 *
 * @template M Custom meta type extending BaseMeta for API handlers
 */
interface SplitNotFoundHandler<M extends BaseMeta = BaseMeta> {
    /** Handler for API requests (paths matching apiEndpointPrefix). If missing or throws, logs error and falls back to default. */
    api?: APINotFoundHandlerFn<M>;
    /** Handler for web requests (non-API paths). If missing or throws, logs error and falls back to default. */
    web?: WebNotFoundHandlerFn;
}
/**
 * Closing handler function type for API/page requests.
 * Called for requests that arrive while the server is shutting down.
 */
type APIClosingHandlerFn<M extends BaseMeta = BaseMeta> = (request: FastifyRequest, isPageData?: boolean) => APIErrorResponse<M> | PageErrorResponse<M> | Promise<APIErrorResponse<M> | PageErrorResponse<M>>;
/**
 * Closing handler function type for web (non-API) requests.
 * Called for requests that arrive while the server is shutting down.
 */
type WebClosingHandlerFn = (request: FastifyRequest) => WebResponse | Promise<WebResponse>;
/**
 * Split closing handler with separate API and web handlers.
 * Both handlers are optional - if a handler is missing or throws an error,
 * the error is logged to the Fastify logger and the server falls back to the default 503 response.
 *
 * @template M Custom meta type extending BaseMeta for API handlers
 */
interface SplitClosingHandler<M extends BaseMeta = BaseMeta> {
    /** Handler for API requests (paths matching apiEndpointPrefix). If missing or throws, falls back to default. */
    api?: APIClosingHandlerFn<M>;
    /** Handler for web requests (non-API paths). If missing or throws, falls back to default. */
    web?: WebClosingHandlerFn;
}
/**
 * Options for configuring the API server
 * @template M Custom meta type extending BaseMeta for error/notFound handlers
 */
interface APIServerOptions<M extends BaseMeta = BaseMeta> {
    /**
     * Optional safe-to-share app configuration object.
     * Cloned and frozen per request as `request.publicAppConfig`.
     *
     * API servers do not inject this into HTML. Include it in response metadata
     * yourself when clients should receive it.
     */
    publicAppConfig?: Record<string, unknown>;
    /**
     * Response compression for non-streaming API and web responses.
     * Negotiates `Accept-Encoding` and skips range or already-encoded replies.
     *
     * @default true
     */
    responseCompression?: boolean | ResponseCompressionOptions;
    /**
     * Optional response-time header emitted on completed responses.
     * Normal Fastify-managed replies apply and measure this in `onSend`.
     *
     * For hijacked/raw replies, it is applied when `reply.hijack()` is called so
     * `reply.getHeaders()` includes it before a
     * subsequent raw `writeHead(...)`. Access logging measures when the response
     * finishes, so raw/hijacked responses can report different timings there.
     *
     * @default false
     */
    responseTimeHeader?: boolean | ResponseTimeHeaderOptions;
    /**
     * Array of plugins to register with the server
     * Plugins get access to a controlled Fastify instance with full wildcard support
     */
    plugins?: ServerPlugin[];
    /**
     * Override the helpers used to construct API/Page envelopes.
     * Provide your own class (subclassing `APIResponseHelpers` recommended) to
     * inject default metadata or behavior. If not provided, the default
     * `APIResponseHelpers` will be used.
     */
    APIResponseHelpersClass?: APIResponseHelpersClass;
    /**
     * Configuration for versioned API endpoints (shared by page data and generic API routes)
     * For page data loader handler endpoints, set pageDataEndpoint (default: "page_data")
     */
    apiEndpoints?: APIEndpointConfig;
    /**
     * File upload configuration
     * When enabled, multipart file upload support will be available
     * Allows use of processFileUpload() in your plugins
     */
    fileUploads?: FileUploadsConfig;
    /**
     * Custom error handler for server errors
     *
     * Can be either:
     * 1. A function (handles all requests the same way - JSON envelope)
     * 2. An object with separate `api` and `web` handlers for split behavior
     *
     * Function form (same signature as SSR APIHandling.errorHandler):
     * - Must return API or Page envelope response (see api-envelope-structure.md)
     * - Used for pure API servers
     *
     * Object form (for mixed API + web servers):
     * - `api`: Handles API requests (paths matching apiEndpointPrefix)
     *   Params: (request, error, isDevelopment, isPageData)
     *   - request: The Fastify request object
     *   - error: The error that occurred
     *   - isDevelopment: Whether running in development mode
     *   - isPageData: Whether this is a page-data request (e.g., /api/v1/page_data/home)
     *   Required envelope return fields:
     *   - status: "error"
     *   - status_code: HTTP status code (400, 401, 404, 500, etc.)
     *   - request_id: Unique request identifier
     *   - type: "api" for API requests, "page" for page data requests
     *   - data: null (always null for error responses)
     *   - meta: Object containing metadata (page metadata required for page type)
     *   - error: Object with { code, message, details? }
     * - `web`: Handles non-API requests
     *   Params: (request, error, isDevelopment)
     *   - request: The Fastify request object
     *   - error: The error that occurred
     *   - isDevelopment: Whether running in development mode
     *   Required WebResponse return fields:
     *   - contentType: 'html' | 'text' | 'json'
     *   - content: string for html/text, object for json
     *   - statusCode?: HTTP status code (defaults to 500)
     *
     * @example Function form
     * errorHandler: (request, error, isDev, isPageData) =>
     *   APIResponseHelpers.createAPIErrorResponse({ request, statusCode: 500, ... })
     *
     * @example Object form
     * errorHandler: {
     *   api: (request, error, isDev, isPageData) => APIResponseHelpers.createAPIErrorResponse({ ... }),
     *   web: (request, error, isDev) => ({ contentType: 'html', content: '<h1>Error</h1>' })
     * }
     */
    errorHandler?: APIErrorHandlerFn<M> | SplitErrorHandler<M>;
    /**
     * Custom handler for requests that did not match any route (404)
     * If provided, overrides the built-in envelope handler.
     *
     * Can be either:
     * 1. A function (handles all requests the same way - JSON envelope)
     * 2. An object with separate `api` and `web` handlers for split behavior
     *
     * Function form (same signature as SSR APIHandling.notFoundHandler):
     * - Must return API or Page envelope response with status_code: 404 (see api-envelope-structure.md)
     * - Used for pure API servers
     *
     * Object form (for mixed API + web servers):
     * - `api`: Handles API requests (paths matching apiEndpointPrefix)
     *   Params: (request, isPageData)
     *   - request: The Fastify request object
     *   - isPageData: Whether this is a page-data request (e.g., /api/v1/page_data/home)
     *   Required envelope return fields:
     *   - status: "error"
     *   - status_code: 404
     *   - request_id: Unique request identifier
     *   - type: "api" for API requests, "page" for page data requests
     *   - data: null (always null for error responses)
     *   - meta: Object containing metadata (page metadata required for page type)
     *   - error: Object with { code: "not_found", message, details? }
     * - `web`: Handles non-API requests
     *   Params: (request)
     *   - request: The Fastify request object
     *   Required WebResponse return fields:
     *   - contentType: 'html' | 'text' | 'json'
     *   - content: string for html/text, object for json
     *   - statusCode?: HTTP status code (defaults to 404)
     *
     * @example Function form
     * notFoundHandler: (request, isPageData) =>
     *   APIResponseHelpers.createAPIErrorResponse({ request, statusCode: 404, ... })
     *
     * @example Object form
     * notFoundHandler: {
     *   api: (request, isPageData) => APIResponseHelpers.createAPIErrorResponse({ ... }),
     *   web: (request) => ({ contentType: 'html', content: '<h1>404 Not Found</h1>' })
     * }
     */
    notFoundHandler?: APINotFoundHandlerFn<M> | SplitNotFoundHandler<M>;
    /**
     * Custom handler for requests that arrive while the server is shutting down.
     *
     * Can be either:
     * 1. A function (handles API requests the same way - JSON envelope)
     * 2. An object with separate `api` and `web` handlers for split behavior
     *
     * Missing handlers fall back to Unirend's default 503 response.
     */
    closingHandler?: APIClosingHandlerFn<M> | SplitClosingHandler<M>;
    /**
     * Whether to automatically log errors via the server logger
     * When enabled, all errors are logged before custom error handlers run
     * Useful for debugging custom error pages that can't show stack traces
     * @default true
     */
    logErrors?: boolean;
    /**
     * Label for this server instance, used in error log messages and access log templates.
     * Useful for distinguishing log output when running multiple server instances.
     * @default 'API'
     * @example 'API:v2'
     */
    serverLabel?: string;
    /**
     * Enable WebSocket support on the server
     * @default false
     */
    enableWebSockets?: boolean;
    /**
     * WebSocket server configuration options
     * Only used when enableWebSockets is true
     */
    webSocketOptions?: WebSocketOptions;
    /**
     * HTTPS server configuration
     * Provides first-class HTTPS support with key, cert, and SNI callback
     *
     * @example Basic HTTPS
     * ```ts
     * https: {
     *   key: privateKey,     // string | Buffer
     *   cert: certificate,   // string | Buffer
     * }
     * ```
     *
     * @example SNI callback for multi-tenant SaaS
     * ```ts
     * https: {
     *   key: defaultPrivateKey,   // string | Buffer - Default cert
     *   cert: defaultCertificate,  // string | Buffer
     *   sni: async (servername) => {
     *     // Load certificate based on domain
     *     const { key, cert } = await loadCertForDomain(servername);
     *
     *     // Return a secure context for the domain
     *     return tls.createSecureContext({ key, cert });
     *   },
     * }
     * ```
     */
    https?: HTTPSOptions;
    /**
     * Curated Fastify options for API server configuration
     * Only exposes safe options that won't conflict with API setup
     */
    fastifyOptions?: FastifyServerOptions;
    /**
     * Framework-level logging options adapted to Fastify under the hood.
     *
     * Note: Cannot be used together with `fastifyOptions.logger` or
     * `fastifyOptions.loggerInstance`.
     */
    logging?: UnirendLoggingOptions;
    /**
     * First-party access logging configuration
     * Controls request/response logging without needing a custom plugin
     */
    accessLog?: AccessLogConfig;
    /**
     * Custom client IP resolver.
     * When set, called once per request to populate `request.clientIP` — available
     * throughout the entire request lifecycle (plugins, hooks, page data loader
     * handlers, API route handlers, access log templates/hooks, etc.).
     * When not set, `request.clientIP` falls back to `request.ip`
     * (which reflects Fastify proxy handling when `fastifyOptions.trustProxy`
     * is configured).
     *
     * Use this when behind Cloudflare, AWS ALB, or other CDNs that carry the
     * real client IP in a custom header.
     */
    getClientIP?: (request: FastifyRequest) => string | Promise<string>;
}
/**
 * Options for configuring the Static Web Server
 * Used for serving SSG-generated static sites
 */
interface StaticWebServerOptions {
    /**
     * Path to page-map.json file, relative to buildDir.
     * Generated by generateSSG() with pageMapOutput option.
     *
     * @default 'page-map.json'
     */
    pageMapPath?: string;
    /**
     * Base directory containing built assets
     *
     * @example "./build/client"
     */
    buildDir: string;
    /**
     * Response compression for static pages/assets and generated web responses.
     * Negotiates `Accept-Encoding` and skips range or already-encoded replies.
     *
     * @default true
     */
    responseCompression?: boolean | ResponseCompressionOptions;
    /**
     * Optional response-time header emitted on completed responses.
     * Normal Fastify-managed replies apply and measure this in `onSend`.
     *
     * For hijacked/raw replies, it is applied when `reply.hijack()` is called so
     * `reply.getHeaders()` includes it before a
     * subsequent raw `writeHead(...)`. Access logging measures when the response
     * finishes, so raw/hijacked responses can report different timings there.
     *
     * @default false
     */
    responseTimeHeader?: boolean | ResponseTimeHeaderOptions;
    /**
     * Whether to automatically log errors via the server logger
     * When enabled, all errors are logged before custom error handlers run
     * Useful for debugging custom error pages that can't show stack traces
     * @default true
     */
    logErrors?: boolean;
    /**
     * Label for this server instance, used in error log messages and access log templates.
     * Useful for distinguishing log output when running multiple server instances.
     * @default 'Static'
     * @example 'Static:marketing'
     */
    serverLabel?: string;
    /**
     * Custom 404 HTML file path (relative to buildDir)
     * If not specified, automatically looks for "404.html" in buildDir
     *
     * @default "404.html" if it exists
     */
    notFoundPage?: string;
    /**
     * Custom 500 error HTML file path (relative to buildDir)
     * If not specified, automatically looks for "500.html" in buildDir
     *
     * @default "500.html" if it exists, otherwise uses generated error page
     */
    errorPage?: string;
    /**
     * Additional folders to serve (for assets like /assets, /images)
     * Maps URL prefix to filesystem directory
     *
     * @example { '/assets': './build/client/assets' }
     */
    assetFolders?: Record<string, string>;
    /**
     * Additional single-file assets to serve (e.g., favicon, robots.txt, sitemap.xml)
     * Maps URL path to filesystem file path
     *
     * These assets are merged with the page-map assets from SSG.
     * If a URL conflicts with a page-map asset, the singleAssets value takes precedence.
     *
     * @example
     * ```typescript
     * {
     *   '/favicon.ico': './public/favicon.ico',
     *   '/robots.txt': './public/robots.txt',
     *   '/sitemap.xml': './public/sitemap.xml'
     * }
     * ```
     */
    singleAssets?: Record<string, string>;
    /**
     * Enable immutable asset detection for fingerprinted files
     * When enabled, files with fingerprinted names get long cache headers
     *
     * @default true
     */
    detectImmutableAssets?: boolean;
    /**
     * Default Cache-Control header for HTML pages
     * @default "public, max-age=0, must-revalidate"
     */
    cacheControl?: string;
    /**
     * Cache-Control header for immutable assets (fingerprinted files)
     * @default "public, max-age=31536000, immutable"
     */
    immutableCacheControl?: string;
    /**
     * HTTPS/SSL configuration (same as APIServer and SSRServer)
     * Supports SNI (Server Name Indication) for multi-domain certificates
     *
     * @example Basic HTTPS
     * {
     *   key: privateKey,     // string | Buffer
     *   cert: certificate    // string | Buffer
     * }
     *
     * @example Multi-domain with SNI
     * {
     *   key: defaultPrivateKey,   // string | Buffer - Default cert
     *   cert: defaultCertificate,  // string | Buffer
     *   sni: (servername) => {
     *     if (servername === 'example.com') {
     *       return tls.createSecureContext({
     *         key: examplePrivateKey,    // string | Buffer
     *         cert: exampleCertificate,   // string | Buffer
     *       });
     *     }
     *     return null;
     *   }
     * }
     */
    https?: HTTPSOptions;
    /**
     * Fastify server options (logging, trust proxy, etc.)
     * Subset of Fastify options that don't conflict with static server setup
     */
    fastifyOptions?: FastifyServerOptions;
    /**
     * Framework-level logging options adapted to Fastify under the hood
     * Cannot be used together with fastifyOptions.logger or fastifyOptions.loggerInstance
     */
    logging?: UnirendLoggingOptions;
    /**
     * First-party access logging configuration
     * Controls request/response logging without needing a custom plugin
     */
    accessLog?: AccessLogConfig;
    /**
     * Custom client IP resolver.
     * When set, called once per request to populate `request.clientIP` — available
     * throughout the entire request lifecycle (plugins, hooks, page data loader
     * handlers, API route handlers, access log templates/hooks, etc.).
     * When not set, `request.clientIP` falls back to `request.ip`
     * (which reflects Fastify proxy handling when `fastifyOptions.trustProxy`
     * is configured).
     *
     * Use this when behind Cloudflare, AWS ALB, or other CDNs that carry the
     * real client IP in a custom header.
     */
    getClientIP?: (request: FastifyRequest) => string | Promise<string>;
    /**
     * Custom handler for requests that arrive while the static server is shutting down.
     * If omitted, Unirend returns a default 503 HTML page.
     */
    closingHandler?: WebClosingHandlerFn;
    /**
     * Additional plugins to register
     * Useful for custom routes, middleware, or request hooks
     * (e.g., analytics, custom headers, redirects)
     */
    plugins?: ServerPlugin[];
}
/**
 * Object logger for the SSG process.
 * This is separate from Fastify's logger configuration.
 */
interface SSGLogger {
    /** Log info messages */
    info: (message: string) => void;
    /** Log warning messages */
    warn: (message: string) => void;
    /** Log error messages */
    error: (message: string) => void;
}
/**
 * Pre-built console logger for SSG with prefixed messages
 * Use this if you want basic console logging during SSG
 */
declare const SSGConsoleLogger: SSGLogger;
/**
 * Options for Static Site Generation
 */
interface SSGOptions {
    /**
     * Optional safe-to-share app configuration object.
     * Cloned and frozen per page. Exposed to React via usePublicAppConfig() and
     * injected as window.__PUBLIC_APP_CONFIG__.
     *
     * Keep this minimal and non-sensitive; it will be passed to the client.
     */
    publicAppConfig?: Record<string, unknown>;
    /**
     * CDN base URL for rewriting asset URLs in the generated HTML
     * (e.g., `'https://cdn.example.com'`).
     * Rewrites `<script src>` and `<link href>` absolute paths in the template to use the CDN.
     * Also injected as `window.__CDN_BASE_URL__` and available via `useCDNBaseURL()` in components.
     * Note: This is resolved at generation time (build-time), not per-request. The CDN URL is baked
     * into the generated HTML files. Set via a build-time environment variable if needed
     * (e.g., `CDNBaseURL: process.env.CDN_BASE_URL`).
     */
    CDNBaseURL?: string;
    /**
     * The hostname of the site being generated (e.g., `'app.example.com'`).
     *
     * Used to compute `window.__DOMAIN_INFO__` (hostname + rootDomain for subdomain-spanning cookies)
     * and populate `useDomainInfo()` in components during SSG rendering and on the client.
     *
     * If not provided, `useDomainInfo()` returns `null` in SSG and SPA pages.
     * Set via a build-time environment variable if needed (e.g., `hostname: process.env.SITE_HOSTNAME`).
     */
    hostname?: string;
    /**
     * ID of the container element (defaults to "root")
     * This element will be formatted inline to prevent hydration issues
     */
    containerID?: string;
    /**
     * Name of the server entry file to look for in the Vite manifest
     * Defaults to "EntrySSG" if not provided
     */
    serverEntry?: string;
    /**
     * Optional logger for the SSG process
     * Defaults to console if not provided
     */
    logger?: SSGLogger;
    /**
     * Name of the client folder within buildDir
     * Defaults to "client" if not provided
     */
    clientFolderName?: string;
    /**
     * Name of the server folder within buildDir
     * Defaults to "server" if not provided
     */
    serverFolderName?: string;
    /**
     * Filename for a JSON file mapping URL paths to generated filenames.
     * Written to buildDir (e.g., `buildDir/page-map.json`).
     *
     * Useful for server code that needs to dynamically serve clean URLs
     * (e.g., `/about` → `about.html`) without hardcoding or configuring rewrites.
     *
     * Serving the correct file matters for React hydration — even a subtle mismatch
     * like `/about` vs `/about.html` can cause hydration errors if the wrong file is served.
     *
     * The generated file contains a simple object mapping paths to filenames:
     * ```json
     * {
     *   "/": "index.html",
     *   "/about": "about.html"
     * }
     * ```
     *
     * If not provided, no page map file is written.
     */
    pageMapOutput?: string;
    /**
     * Whether to treat 5xx server error status codes as generation errors.
     * Defaults to true. Set to false if you intentionally want to generate
     * and write custom error pages (e.g., a 500.html) without failing the build.
     */
    failOn5xx?: boolean;
}
/**
 * Base interface for pages to be generated
 */
interface GeneratorPageBase {
    /** The output filename for the generated HTML */
    filename: string;
}
/**
 * SSG page - server-side rendered at build time
 */
interface SSGPageType extends GeneratorPageBase {
    /** Type of page generation */
    type: 'ssg';
    /** The URL path for the page (required for SSG) */
    path: string;
    /** Optional request context to seed before rendering (merged into SSGHelpers.requestContext before render is called) */
    requestContext?: Record<string, unknown>;
}
/**
 * SPA page - client-side rendered with custom metadata
 */
interface SPAPageType extends GeneratorPageBase {
    /** Type of page generation */
    type: 'spa';
    /** Custom title for the SPA page */
    title?: string;
    /** Custom meta description for the SPA page */
    description?: string;
    /** Additional meta tags as key-value pairs */
    meta?: Record<string, string>;
    /** Optional request context to inject into the page (available as window.__FRONTEND_REQUEST_CONTEXT__) */
    requestContext?: Record<string, unknown>;
    /** URL path for page map entry — derived from filename if not provided */
    path?: string;
}
/**
 * HTML page - raw HTML written directly to the dist folder, no React render pipeline.
 * Use for self-contained pages that must not depend on the JS/CSS bundle (e.g. 500.html, 403.html, etc).
 * Exactly one of `html` or `source` must be provided.
 */
type HTMLPageType = GeneratorPageBase & {
    /** Type of page generation */
    type: 'html';
    /** URL path for the page map entry — derived from filename if not provided */
    path?: string;
} & ({
    /** Inline HTML string to write directly */ html: string;
    source?: never;
} | {
    /** Absolute path to a source HTML file to copy */ source: string;
    html?: never;
});
/**
 * Union type for all page types
 */
type PageTypeWanted = SSGPageType | SPAPageType | HTMLPageType;
/**
 * Status code for a generated page
 */
type SSGPageStatus = 'success' | 'not_found' | 'error';
/**
 * Report for a single generated page
 */
interface SSGPageReport {
    /** The page that was processed */
    page: PageTypeWanted;
    /** Status of the generation */
    status: SSGPageStatus;
    /** Full path to the generated file (if successful) */
    outputPath?: string;
    /** Error details (if status is 'error') */
    errorDetails?: string;
    /** Time taken to generate the page in milliseconds */
    timeMS: number;
}
/**
 * Collection of page reports for the SSG process
 */
interface SSGPagesReport {
    /** Reports for each page */
    pages: SSGPageReport[];
    /** Total number of pages processed */
    totalPages: number;
    /** Number of successfully generated pages */
    successCount: number;
    /** Number of pages with errors */
    errorCount: number;
    /** Number of pages not found (404) */
    notFoundCount: number;
    /** Total time taken for the entire generation process in milliseconds */
    totalTimeMS: number;
    /** Directory where files were generated */
    buildDir: string;
}
/**
 * Complete report for the SSG process, including potential fatal errors
 */
interface SSGReport {
    /**
     * True if the SSG generation should be considered failed. This is true when there is a
     * fatalError (pre-generation failure) or when any pages had errors (e.g. 5xx
     * status with failOn5xx, render failures, write failures).
     *
     * Check this instead of fatalError alone to correctly handle page-level failures.
     */
    generationFailed: boolean;
    /** Fatal error if the process failed before page generation could start */
    fatalError?: Error;
    /** Page generation reports (always present, even on error) */
    pagesReport: SSGPagesReport;
}
/**
 * Configuration for multipart file upload support
 * When provided, the server will automatically enable multipart uploads
 */
interface FileUploadsConfig {
    /**
     * Whether to enable file upload support
     * When true, multipart upload support will be enabled automatically
     * @default false
     */
    enabled: boolean;
    /**
     * Global limits for file uploads (can be overridden per-route)
     * These act as maximum limits for security
     */
    limits?: {
        /**
         * Maximum file size in bytes
         * @default 10485760 (10MB)
         */
        fileSize?: number;
        /**
         * Maximum number of files per request
         * @default 10
         */
        files?: number;
        /**
         * Maximum number of form fields
         * @default 10
         */
        fields?: number;
        /**
         * Maximum size of form field values in bytes
         * @default 1024 (1KB)
         */
        fieldSize?: number;
    };
    /**
     * Optional: List of routes/patterns that allow multipart uploads
     * When provided, a preHandler hook will reject multipart requests to other routes
     * This prevents bandwidth waste and potential DoS attacks
     *
     * Supports exact matches and wildcard patterns.
     * Use asterisk (*) to match any path segment (e.g. /api/upload/workspace/*)
     *
     * @example
     * allowedRoutes: ['/api/upload/avatar', '/api/upload/document']
     */
    allowedRoutes?: string[];
    /**
     * Optional: Pre-validation function that runs BEFORE multipart parsing
     * Use this to reject requests early based on headers (auth, rate limiting, etc.)
     * This saves bandwidth by rejecting before any file data is parsed
     *
     * Supports both synchronous and asynchronous validation functions
     * Return true to allow the request, or an error response object to reject it
     *
     * @example
     * // Async validation
     * preValidation: async (request) => {
     *   const token = request.headers.authorization;
     *   if (!token) {
     *     return { statusCode: 401, error: 'unauthorized', message: 'Auth required' };
     *   }
     *   return true; // Allow request to proceed
     * }
     *
     * @example
     * // Sync validation
     * preValidation: (request) => {
     *   if (!request.headers['x-api-key']) {
     *     return { statusCode: 403, error: 'forbidden', message: 'API key required' };
     *   }
     *   return true;
     * }
     */
    preValidation?: (request: FastifyRequest) => Promise<true | {
        statusCode: number;
        error: string;
        message: string;
    }> | true | {
        statusCode: number;
        error: string;
        message: string;
    };
}
/**
 * Configuration for a folder in the static router folderMap
 */
interface FolderConfig {
    /** Path to the directory */
    path: string;
    /** Whether to detect and use immutable caching for fingerprinted assets */
    detectImmutableAssets?: boolean;
}
/**
 * Options for the static router middleware
 * Used to serve static files in production SSR mode
 */
interface StaticContentRouterOptions {
    /** Exact URL → absolute file path (optional) */
    singleAssetMap?: Record<string, string>;
    /** URL prefix → absolute directory path (as string) or folder config object */
    folderMap?: Record<string, string | FolderConfig>;
    /** Response compression for buffered static responses; default true */
    compression?: boolean | ResponseCompressionOptions;
    /** Maximum size (in bytes) for hashing & in-memory caching; default 5 MB */
    smallFileMaxSize?: number;
    /** Maximum number of entries in ETag/content caches; default 100 */
    cacheEntries?: number;
    /** Maximum total memory size (in bytes) for content cache; default 50 MB */
    contentCacheMaxSize?: number;
    /** Maximum number of entries in the stat cache; default 250 */
    statCacheEntries?: number;
    /** TTL in milliseconds for negative stat cache entries; default 30 seconds */
    negativeCacheTtl?: number;
    /** TTL in milliseconds for positive stat cache entries; default 1 hour */
    positiveCacheTtl?: number;
    /** Custom Cache-Control header; default 'public, max-age=0, must-revalidate' */
    cacheControl?: string;
    /** Cache-Control header for immutable fingerprinted assets; default 'public, max-age=31536000, immutable' */
    immutableCacheControl?: string;
}
interface ResponseCompressionOptions {
    /** Whether compression is enabled when using the object form */
    enabled?: boolean;
    /** Minimum response size in bytes before compression is attempted */
    threshold?: number;
    /** Prefer Brotli over gzip when the client gives both equal q-values */
    preferBrotli?: boolean;
    /** Brotli quality level passed to Node.js zlib */
    brotliQuality?: number;
    /** gzip compression level passed to Node.js zlib */
    gzipLevel?: number;
}
declare module 'fastify' {
    interface FastifyRequest {
        /**
         * Active SSR app key for multi-app routing.
         *
         * Read-only request value. Defaults to `'__default__'`.
         * Use `request.setActiveSSRApp(appKey)` in SSR middleware to select a
         * registered app and refresh app-derived request values.
         */
        readonly activeSSRApp: string;
        /**
         * Select the active SSR app for this request.
         *
         * Validates that the app exists, updates `request.activeSSRApp`, refreshes
         * `request.publicAppConfig`, and updates the app-level CDN default unless
         * middleware already overrode `request.CDNBaseURL`.
         */
        setActiveSSRApp: (appKey: string) => void;
        /**
         * Resolved client IP address.
         *
         * Set once per request by the framework using `getClientIP` (if provided)
         * or falling back to `request.ip` (which reflects Fastify proxy handling
         * when `fastifyOptions.trustProxy` is configured).
         *
         * Available throughout the entire request lifecycle, including plugins,
         * hooks, page data loader handlers, API route handlers, and access log
         * templates/hooks.
         */
        clientIP: string;
        /**
         * Server label for this instance (e.g. `'SSR'`, `'API'`).
         *
         * Set once per request by the framework from the `serverLabel` server option.
         * Available in access log templates as `{{serverLabel}}` and throughout
         * the request lifecycle via `request.serverLabel`.
         */
        serverLabel: string;
        /**
         * Safe-to-share app configuration cloned and frozen for this request.
         *
         * Available to plugins, handlers, and helpers on SSR and API servers.
         * SSR also exposes it to React and injects it into HTML. API servers only
         * send it to clients if your response includes selected values.
         */
        publicAppConfig?: Record<string, unknown>;
        /**
         * Effective CDN base URL for this SSR request.
         *
         * SSR middleware can set this as a per-request override. If it remains
         * unset, the SSR server populates it from the active app's `CDNBaseURL`
         * before preHandler hooks, route handlers, SSR render, and custom 500
         * pages run.
         *
         * API servers do not set this field.
         */
        CDNBaseURL?: string;
        /**
         * Optional request-scoped helper installed by the built-in CORS plugin.
         *
         * Raw/hijacked response paths can call this before `writeHead(...)` to
         * apply the same actual-response CORS/security headers that normal
         * Fastify-managed responses receive.
         */
        applyCORSHeaders?: (reply: FastifyReply) => void | Promise<void>;
        /**
         * Internal request-start timestamp captured by the framework.
         *
         * Used by framework features that need a stable "request received" time,
         * including API/page envelope timestamps and fallback response-time
         * calculation when Fastify's built-in elapsedTime is unavailable.
         */
        receivedAt?: number;
        /**
         * Per-request mutable key-value store initialized by both SSRServer and APIServer
         * before any plugins or hooks run.
         *
         * Use this to pass data between middleware, hooks, and handlers — for example,
         * seeding user session info, theme preferences, or CSRF tokens from an onRequest
         * hook so that page data loader handlers and API handlers can read them.
         *
         * During SSR, the server injects the final context into the rendered HTML so
         * client-side React can hydrate with the same values via `useRequestContext()`.
         *
         * In separated SSR/API deployments the SSR layer forwards this context to trusted
         * API page data requests and merges returned context back automatically.
         */
        requestContext: Record<string, unknown>;
        /**
         * Parsed domain information for this request, computed once per request from
         * `request.hostname` by both SSRServer and APIServer.
         *
         * - `hostname`: bare hostname with port stripped (IPv6-safe)
         * - `rootDomain`: apex domain without a leading dot (e.g. `'example.com'`);
         *   empty string for localhost and raw IP addresses
         *
         * Use `rootDomain` to set subdomain-spanning cookies by prepending a dot:
         * `domain=.${request.domainInfo.rootDomain}` — the same value that the
         * client-side `cycleTheme()` helper uses.
         */
        domainInfo: DomainInfo;
        /**
         * Set to `true` when the request is handled by the built-in static content
         * handler (fingerprinted assets, public files, etc.), regardless of which
         * server type is serving the file.
         *
         * Initialized to `false` for every request. The static content handler sets
         * it to `true` before calling `reply.hijack()`, so it is observable in
         * `onResponse` hooks and access log templates even though `onSend` is bypassed.
         *
         * Use this to skip work that is inappropriate for static asset responses —
         * for example, cookie renewal should only happen on document/API responses,
         * not on every `.js` or `.css` file request.
         */
        isStaticAsset: boolean;
    }
}

/**
 * Response configuration for invalid domain handler
 * Matches the pattern from domain-validation plugin for consistency
 */
interface InvalidDomainResponse {
    contentType: 'json' | 'text' | 'html';
    content: string | object;
}
/**
 * Configuration options for the RedirectServer
 */
interface RedirectServerOptions {
    /**
     * Target protocol to redirect to
     * Currently only 'https' is supported
     * @default 'https'
     */
    targetProtocol?: 'https';
    /**
     * HTTP status code to use for redirects
     * - 301: Permanent redirect
     * - 302: Temporary redirect
     * - 307: Temporary redirect (preserves method)
     * - 308: Permanent redirect (preserves method)
     * @default 301
     */
    statusCode?: 301 | 302 | 307 | 308;
    /**
     * Optional list of allowed domains with wildcard support
     * If provided, only requests to these domains will be redirected
     * Requests to other domains will receive a 403 error
     *
     * Wildcard patterns supported:
     * - "example.com" - allows exact match only
     * - "*.example.com" - allows direct subdomains only (api.example.com ✅, app.api.example.com ❌)
     * - "**.example.com" - allows all subdomains including nested (api.example.com ✅, app.api.example.com ✅)
     *
     * Security: This prevents Host header attacks by rejecting unexpected domains
     *
     * @example
     * allowedDomains: ['example.com', '*.example.com']
     */
    allowedDomains?: string | string[];
    /**
     * Whether to preserve port numbers in redirects
     * - true: example.com:8080 → https://example.com:8080
     * - false: example.com:8080 → https://example.com (strip port)
     * Ignored if targetPort is set.
     * @default false
     */
    preservePort?: boolean;
    /**
     * Override the port in the redirect URL
     * Useful when HTTP and HTTPS servers run on different non-standard ports
     * - example: targetPort: 8443 → http://host:8080 redirects to https://host:8443
     * Takes precedence over preservePort when set.
     */
    targetPort?: number;
    /**
     * Custom handler for invalid domain responses
     * If not provided, returns a default 403 plain text response
     * Matches the pattern from domain-validation plugin for consistency
     *
     * @param request - The Fastify request object
     * @param domain - The domain that was not allowed
     * @returns Response configuration with contentType and content
     */
    invalidDomainHandler?: (request: FastifyRequest, domain: string) => InvalidDomainResponse;
    /**
     * Whether to automatically log errors via the server logger
     * When enabled, all errors are logged before custom error handlers run
     * @default true
     */
    logErrors?: boolean;
    /**
     * Label for this server instance, used in error log messages and access log templates.
     * @default 'Redirect'
     * @example 'Redirect:http'
     */
    serverLabel?: string;
    /**
     * Framework-level logging options adapted to Fastify under the hood
     * Same as APIServer and SSRServer logging configuration
     */
    logging?: UnirendLoggingOptions;
    /**
     * Curated Fastify options for redirect server configuration
     * Only exposes safe options that won't conflict with server setup
     */
    fastifyOptions?: FastifyServerOptions;
    /**
     * First-party access logging configuration
     * Controls request/response logging without needing a custom plugin
     */
    accessLog?: AccessLogConfig;
    /**
     * Optional response-time header emitted on completed responses.
     * Passed through to the underlying APIServer.
     * @default false
     */
    responseTimeHeader?: boolean | ResponseTimeHeaderOptions;
    /**
     * Custom client IP resolver.
     * When set, called once per request to populate `request.clientIP` — available
     * throughout the entire request lifecycle (plugins, hooks, page data loader
     * handlers, API route handlers, access log templates/hooks, etc.).
     * When not set, `request.clientIP` falls back to `request.ip`
     * (which reflects Fastify proxy handling when `fastifyOptions.trustProxy`
     * is configured).
     *
     * Use this when behind Cloudflare, AWS ALB, or other CDNs that carry the
     * real client IP in a custom header.
     */
    getClientIP?: (request: FastifyRequest) => string | Promise<string>;
    /**
     * Custom handler for requests that arrive while the redirect server is shutting down.
     * If omitted, Unirend returns a default 503 HTML page.
     */
    closingHandler?: WebClosingHandlerFn;
    /**
     * HTTPS server configuration for the redirect server itself
     * Typically not needed (redirect servers usually run on HTTP port 80)
     */
    https?: never;
}
/**
 * Dedicated HTTP → HTTPS redirect server
 *
 * Lightweight server specifically designed for HTTP → HTTPS redirects.
 * Wraps APIServer with built-in redirect logic and optional domain validation.
 *
 * Common use case: Run on port 80 to redirect all HTTP traffic to HTTPS (port 443)
 *
 * @example Basic usage
 * ```ts
 * import { RedirectServer } from 'unirend/server';
 *
 * const redirectServer = new RedirectServer({
 *   targetProtocol: 'https',
 *   statusCode: 301,
 * });
 *
 * await redirectServer.listen(80);
 * ```
 *
 * @example With domain validation
 * ```ts
 * const redirectServer = new RedirectServer({
 *   targetProtocol: 'https',
 *   allowedDomains: ['example.com', '*.example.com'],
 * });
 *
 * await redirectServer.listen(80);
 * ```
 */
declare class RedirectServer {
    private apiServer;
    private config;
    constructor(options?: RedirectServerOptions);
    /**
     * Start the redirect server
     * @param port Port to listen on (typically 80 for HTTP)
     * @param host Host to bind to (default: 'localhost')
     */
    listen(port?: number, host?: string): Promise<void>;
    /**
     * Stop the redirect server
     */
    stop(): Promise<void>;
    /**
     * Check if the server is currently listening
     */
    isListening(): boolean;
    /**
     * Force-close all open connections, including those actively serving requests.
     * See BaseServer.closeAllConnections() for full details.
     */
    closeAllConnections(): void;
    /**
     * Merges the provided keys into the current access log config at runtime.
     * Access logging is on by default (finish events, default template). Use
     * `events: 'none'` to disable logging while keeping hooks active.
     * Omitted keys stay unchanged. Pass `undefined` for a hook callback to remove it.
     *
     * Changes take effect on the next request — no restart required.
     */
    updateAccessLoggingConfig(partial: Partial<AccessLogConfig>): void;
    /**
     * Handle the redirect logic
     * @private
     */
    private handleRedirect;
}

/**
 * Create a dedicated HTTP → HTTPS redirect server
 *
 * Lightweight server specifically designed for HTTP → HTTPS redirects.
 * Common use case: Run on port 80 to redirect all HTTP traffic to HTTPS (port 443)
 *
 * @param options Redirect server configuration
 * @returns RedirectServer instance
 *
 * @example Basic usage (redirect all HTTP to HTTPS)
 * ```ts
 * import { serveRedirect } from 'unirend/server';
 *
 * const redirectServer = serveRedirect({
 *   targetProtocol: 'https',
 *   statusCode: 301,
 * });
 *
 * await redirectServer.listen(80);
 * ```
 *
 * @example With domain validation (security)
 * ```ts
 * const redirectServer = serveRedirect({
 *   targetProtocol: 'https',
 *   allowedDomains: ['example.com', '*.example.com'],
 * });
 *
 * await redirectServer.listen(80);
 * ```
 *
 * @example Multi-server setup (HTTP redirect + HTTPS main server)
 * ```ts
 * import { serveRedirect, serveSSRProd } from 'unirend/server';
 *
 * // HTTP → HTTPS redirect server (port 80)
 * const redirectServer = serveRedirect({
 *   allowedDomains: ['example.com', '*.example.com'],
 * });
 *
 * await redirectServer.listen(80);
 *
 * // Main HTTPS server (port 443)
 * const mainServer = serveSSRProd('./build', {
 *   https: {
 *     key: privateKey,     // string | Buffer
 *     cert: certificate,   // string | Buffer
 *   },
 * });
 *
 * await mainServer.listen(443);
 * ```
 */
declare function serveRedirect(options?: RedirectServerOptions): RedirectServer;

/**
 * Supported HTTP methods for API routes
 */
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
/**
 * Handler function type for generic API endpoints
 *
 * Handlers should return an APIResponseEnvelope. Returning any other object
 * will result in a runtime error being thrown during request handling.
 *
 * **Return false** when you've sent a custom response (e.g., using
 * APIResponseHelpers.sendErrorEnvelope() or validation helpers like ensureJSONBody).
 * This signals that the handler has already sent the response and the framework
 * should not attempt to send anything.
 */
type APIRouteHandler<T = unknown, M extends BaseMeta = BaseMeta> = (request: FastifyRequest, controlledReply: ControlledReply, params: {
    /** HTTP method used for this route */
    method: HTTPMethod;
    /** Endpoint path segment after version/prefix (e.g., "users/:id") */
    endpoint: string;
    /** Registered version for this handler (always set, defaults to 1) */
    version: number;
    /** Full path used to register the route */
    fullPath: string;
    /** Route params extracted from Fastify (raw values) */
    routeParams: Record<string, unknown>;
    /** Query params extracted from Fastify (raw values) */
    queryParams: Record<string, unknown>;
    /** Path portion of the URL without query string */
    requestPath: string;
    /** Original URL including query string */
    originalURL: string;
    /** The APIResponseHelpers class configured on this server (use this instead of importing directly) */
    APIResponseHelpers: APIResponseHelpersClass;
}) => APIResponseEnvelope<T, M> | Promise<APIResponseEnvelope<T, M>> | false | Promise<false>;

/**
 * Abstract base class for all server types in unirend
 * Defines the common interface that all servers must implement
 */
declare abstract class BaseServer {
    protected fastifyInstance: FastifyInstance | null;
    protected _isListening: boolean;
    protected _isStarting: boolean;
    protected _isStopping: boolean;
    /**
     * Start the server listening on the specified port and host
     * @param port Port to bind to (default: 3000)
     * @param host Host to bind to (default: "localhost")
     * @returns Promise that resolves when server is ready
     */
    abstract listen(port?: number, host?: string): Promise<void>;
    /**
     * Stop the server if it's currently running
     * @returns Promise that resolves when server is stopped
     */
    abstract stop(): Promise<void>;
    /**
     * Check if the server is currently listening
     * @returns True if server is listening, false otherwise
     */
    isListening(): boolean;
    /**
     * Force-close all open connections, including those actively serving requests.
     * Unlike stop(), this does not wait for in-flight requests to complete.
     * This also terminates upgraded WebSocket connections tracked by the server.
     * Intended as an escalation path when stop() has not resolved within an
     * acceptable window. No-op if the server is not started.
     */
    closeAllConnections(): void;
    /**
     * Register a WebSocket handler for the specified path
     * @throws Error if WebSocket support is not enabled on the server
     */
    abstract registerWebSocketHandler(config: unknown): void;
    /**
     * Get the list of active WebSocket clients (if enabled)
     */
    abstract getWebSocketClients(): Set<unknown>;
    /**
     * Check if a server-level decoration exists. Returns false if the server is not started.
     * Use this to discover metadata decorated by plugins (e.g., "cookiePluginInfo").
     */
    hasDecoration(property: string): boolean;
    /**
     * Read a server-level decoration value. Returns undefined if missing or server not started.
     * Decorations are attached via Fastify's decorate() API inside plugins.
     */
    getDecoration<T = unknown>(property: string): T | undefined;
    /**
     * Best-effort termination of tracked Fastify WebSocket clients.
     */
    protected terminateTrackedWebSocketClients(): void;
    /**
     * Best-effort termination of raw HTTP/HTTPS server connections.
     */
    protected closeRawHTTPConnections(): void;
}

/**
 * Parameters passed to WebSocket handlers with extracted routing context
 *
 * Similar to page data and API handlers, this provides pre-extracted routing
 * information while keeping the raw request available for cookies/headers/IP.
 */
interface WebSocketHandlerParams {
    /** Request path (from Fastify, without query string) */
    path: string;
    /** Original URL (from Fastify, with query string) */
    originalURL: string;
    /** Query params (from Fastify) */
    queryParams: Record<string, unknown>;
    /** Route params (from Fastify, for parameterized paths) */
    routeParams: Record<string, unknown>;
}
/**
 * WebSocket preValidation result - discriminated union
 */
type WebSocketPreValidationResult = {
    /** Allow WebSocket upgrade */
    action: 'upgrade';
    /** Optional data to pass to the WebSocket handler */
    data?: Record<string, unknown>;
} | {
    /** Reject WebSocket upgrade with API envelope response */
    action: 'reject';
    /** API envelope response to send when rejecting */
    envelope: APIResponseEnvelope;
};
/**
 * WebSocket handler configuration
 */
interface WebSocketHandlerConfig {
    /** The WebSocket endpoint path */
    path: string;
    /** Optional preValidation function that returns upgrade/reject decision with optional envelope */
    preValidate?: (request: FastifyRequest, params: WebSocketHandlerParams) => Promise<WebSocketPreValidationResult> | WebSocketPreValidationResult;
    /** WebSocket connection handler */
    handler: (socket: WebSocket, request: FastifyRequest, params: WebSocketHandlerParams, upgradeData?: Record<string, unknown>) => Promise<void> | void;
}

type SSRServerConfigDev = {
    mode: 'development';
    paths: SSRDevPaths;
    options: ServeSSRDevOptions;
};
type SSRServerConfigProd = {
    mode: 'production';
    buildDir: string;
    options: ServeSSRProdOptions;
};
type SSRServerConfig = SSRServerConfigDev | SSRServerConfigProd;
/**
 * Internal server class for handling SSR rendering
 * Not intended to be used directly by library consumers
 */
declare class SSRServer extends BaseServer {
    /** Pluggable helpers class reference for constructing API/Page envelopes */
    readonly APIResponseHelpersClass: APIResponseHelpersClass;
    private serverMode;
    private readonly serverLabel;
    private apps;
    private sharedOptions;
    private _accessLog;
    private pageDataHandlers;
    private apiRoutes;
    private webSocketHelpers;
    private registeredPlugins;
    private cookieAllowList?;
    private cookieBlockList?;
    private readonly normalizedAPIPrefix;
    private readonly normalizedPageDataEndpoint;
    /**
     * Creates a new SSR server instance
     *
     * @param config Server configuration object
     */
    constructor(config: SSRServerConfig);
    /**
     * Register an additional dev-mode SSR app
     *
     * Can only be called on dev servers (created via serveSSRDev).
     * Apps must be registered BEFORE calling listen().
     *
     * Uses the same parameters as serveSSRDev for consistency - you can copy/paste
     * configuration between the default app and additional apps.
     *
     * @param appKey - Unique identifier for this app (selected with request.setActiveSSRApp)
     * @param paths - Dev-specific paths (same as serveSSRDev)
     * @param options - Dev options (same as serveSSRDev)
     *
     * @example
     * ```ts
     * const mainPaths = {
     *   serverEntry: './src/EntrySSR.tsx',
     *   template: './index.html',
     *   viteConfig: './vite.config.ts'
     * };
     * const server = serveSSRDev(mainPaths, { port: 3000 });
     *
     * // Same parameters as above - easy to copy/paste
     * server.registerDevApp('marketing', {
     *   serverEntry: './src/marketing/EntrySSR.tsx',
     *   template: './src/marketing/index.html',
     *   viteConfig: './vite.marketing.config.ts'
     * }, {
     *   publicAppConfig: { api_endpoint: 'http://localhost:3002' }
     * });
     *
     * await server.listen(3000);
     * ```
     */
    registerDevApp(appKey: string, paths: SSRDevPaths, options?: RegisterDevAppOptions): void;
    /**
     * Register an additional prod-mode SSR app
     *
     * Can only be called on prod servers (created via serveSSRProd).
     * Apps must be registered BEFORE calling listen().
     *
     * Uses the same parameters as serveSSRProd for consistency - you can copy/paste
     * configuration between the default app and additional apps.
     *
     * @param appKey - Unique identifier for this app (selected with request.setActiveSSRApp)
     * @param buildDir - Build directory path (same as serveSSRProd)
     * @param options - Prod options (same as serveSSRProd)
     *
     * @example
     * ```ts
     * const server = serveSSRProd('./build-main', { port: 3000 });
     *
     * // Same parameters as above - easy to copy/paste
     * server.registerProdApp('marketing', './build-marketing', {
     *   publicAppConfig: { api_endpoint: 'https://marketing.example.com' }
     * });
     *
     * await server.listen(3000);
     * ```
     */
    registerProdApp(appKey: string, buildDir: string, options?: RegisterProdAppOptions): void;
    /**
     * Start the SSR server listening on the specified port and host
     *
     * @param port Port number to listen on (defaults to 3000)
     * @param host Host to bind to (defaults to localhost)
     * @returns Promise that resolves when server is listening
     */
    listen(port?: number, host?: string): Promise<void>;
    /**
     * Stop the server if it's currently listening
     */
    stop(): Promise<void>;
    /**
     * Force-close all open connections, including Fastify WebSockets and Vite HMR
     * sockets in development mode. Unlike stop(), this does not wait for
     * in-flight requests to complete.
     */
    closeAllConnections(): void;
    /**
     * Merges the provided keys into the current access log config at runtime.
     * Access logging is on by default (finish events, default template). Use
     * `events: 'none'` to disable logging while keeping hooks active.
     * Omitted keys stay unchanged. Pass `undefined` for a hook callback to remove it.
     *
     * Changes take effect on the next request — no restart required.
     */
    updateAccessLoggingConfig(partial: Partial<AccessLogConfig>): void;
    /**
     * Public API method for registering versioned generic API routes
     * Usage: server.api.get("users/:id", handler) or server.api.get("users/:id", 2, handler)
     */
    get api(): {
        readonly get: (endpoint: string, handlerOrVersion: number | APIRouteHandler<unknown, BaseMeta>, maybeHandler?: APIRouteHandler<unknown, BaseMeta> | undefined) => void;
        readonly post: (endpoint: string, handlerOrVersion: number | APIRouteHandler<unknown, BaseMeta>, maybeHandler?: APIRouteHandler<unknown, BaseMeta> | undefined) => void;
        readonly put: (endpoint: string, handlerOrVersion: number | APIRouteHandler<unknown, BaseMeta>, maybeHandler?: APIRouteHandler<unknown, BaseMeta> | undefined) => void;
        readonly delete: (endpoint: string, handlerOrVersion: number | APIRouteHandler<unknown, BaseMeta>, maybeHandler?: APIRouteHandler<unknown, BaseMeta> | undefined) => void;
        readonly patch: (endpoint: string, handlerOrVersion: number | APIRouteHandler<unknown, BaseMeta>, maybeHandler?: APIRouteHandler<unknown, BaseMeta> | undefined) => void;
    };
    /**
     * Public API method for registering page data loader handlers
     * Usage: server.pageDataHandler.register("home", handler) or server.pageDataHandler.register("home", 2, handler)
     */
    get pageDataHandler(): {
        readonly register: (pageType: string, versionOrHandler: number | PageDataHandler, handlerMaybe?: PageDataHandler) => void;
    };
    /**
     * Register a WebSocket handler for the specified path
     *
     * @param config WebSocket handler configuration
     * @throws Error if WebSocket support is not enabled
     */
    registerWebSocketHandler(config: WebSocketHandlerConfig): void;
    /**
     * Get the list of active WebSocket clients
     *
     * @returns Set of WebSocket clients, or empty Set if WebSocket support is disabled or server not started
     */
    getWebSocketClients(): Set<WebSocket>;
    /**
     * Validate app key for registration
     * @private
     */
    private validateAppKey;
    /**
     * Register plugins with controlled access to Fastify instance
     * @private
     */
    private registerPlugins;
    /**
     * Loads and caches the production render function from the server entry
     * This is called once and cached for performance in production mode
     * @param appConfig App configuration to load render function for
     * @returns Promise that resolves to the render function
     * @private
     */
    private loadProductionRenderFunction;
    /**
     * Loads and processes the HTML template based on the server mode
     * @param appConfig App configuration to load template for
     * @returns Promise that resolves to the processed template content and path
     * @private
     */
    private loadHTMLTemplate;
    /**
     * Handles SSR errors with Vite stack trace fixing and custom error pages
     * @param request The Fastify request object
     * @param reply The Fastify reply object
     * @param error The error that occurred
     * @param appConfig The app configuration (contains viteDevServer in development)
     * @private
     */
    private handleSSRError;
    /**
     * Generates a 500 error page using custom handler or default
     * @param request The Fastify request object
     * @param error The error that occurred
     * @param appConfig The active app configuration
     * @returns Promise that resolves to HTML string
     * @private
     */
    private generate500ErrorPage;
    /**
     * Handles API errors with JSON responses using envelope pattern
     * @param request The Fastify request object
     * @param reply The Fastify reply object
     * @param error The error that occurred
     * @private
     */
    private handleAPIError;
    /**
     * Handles API 404 not found responses with JSON envelopes
     * @param request The Fastify request object
     * @param reply The Fastify reply object
     * @private
     */
    private handleAPINotFound;
}

/**
 * API Server class for creating JSON API servers with plugin support
 * Uses createControlledInstance with shouldDisableRootWildcard: false to allow full wildcard flexibility
 */
declare class APIServer extends BaseServer {
    /** Pluggable helpers class reference for constructing API/Page envelopes */
    readonly APIResponseHelpersClass: APIResponseHelpersClass;
    private options;
    private readonly serverLabel;
    private _accessLog;
    private pageDataHandlers;
    private apiRoutes;
    private webSocketHelpers;
    private registeredPlugins;
    private readonly normalizedAPIPrefix;
    private readonly normalizedPageDataEndpoint;
    constructor(options?: APIServerOptions);
    /**
     * Start the API server
     * @param port Port to bind to (default: 3000)
     * @param host Host to bind to (default: "localhost")
     * @returns Promise that resolves when server is ready
     */
    listen(port?: number, host?: string): Promise<void>;
    /**
     * Stop the API server if it's currently listening
     * @returns Promise that resolves when server is stopped
     */
    stop(): Promise<void>;
    /**
     * Merges the provided keys into the current access log config at runtime.
     * Access logging is on by default (finish events, default template). Use
     * `events: 'none'` to disable logging while keeping hooks active.
     * Omitted keys stay unchanged. Pass `undefined` for a hook callback to remove it.
     *
     * Changes take effect on the next request — no restart required.
     */
    updateAccessLoggingConfig(partial: Partial<AccessLogConfig>): void;
    /**
     * Public API method for registering versioned generic API routes
     * Usage: server.api.get("users/:id", handler) or server.api.get("users/:id", 2, handler)
     */
    get api(): {
        readonly get: (endpoint: string, handlerOrVersion: number | APIRouteHandler<unknown, BaseMeta>, maybeHandler?: APIRouteHandler<unknown, BaseMeta> | undefined) => void;
        readonly post: (endpoint: string, handlerOrVersion: number | APIRouteHandler<unknown, BaseMeta>, maybeHandler?: APIRouteHandler<unknown, BaseMeta> | undefined) => void;
        readonly put: (endpoint: string, handlerOrVersion: number | APIRouteHandler<unknown, BaseMeta>, maybeHandler?: APIRouteHandler<unknown, BaseMeta> | undefined) => void;
        readonly delete: (endpoint: string, handlerOrVersion: number | APIRouteHandler<unknown, BaseMeta>, maybeHandler?: APIRouteHandler<unknown, BaseMeta> | undefined) => void;
        readonly patch: (endpoint: string, handlerOrVersion: number | APIRouteHandler<unknown, BaseMeta>, maybeHandler?: APIRouteHandler<unknown, BaseMeta> | undefined) => void;
    };
    /**
     * Public API method for registering page data loader handlers
     * Usage: server.pageDataHandler.register("home", handler) or server.pageDataHandler.register("home", 2, handler)
     */
    get pageDataHandler(): {
        readonly register: (pageType: string, versionOrHandler: number | PageDataHandler, handlerMaybe?: PageDataHandler) => void;
    };
    /**
     * Register a WebSocket handler for the specified path
     *
     * @param config WebSocket handler configuration
     * @throws Error if WebSocket support is not enabled
     */
    registerWebSocketHandler(config: WebSocketHandlerConfig): void;
    /**
     * Get the list of active WebSocket clients
     *
     * @returns Set of WebSocket clients, or empty Set if WebSocket support is disabled or server not started
     */
    getWebSocketClients(): Set<WebSocket>;
    /**
     * Setup global error handler for unhandled errors
     * @private
     */
    private setupErrorHandler;
    /**
     * Setup a default 404 handler that returns standardized envelopes
     * @private
     */
    private setupNotFoundHandler;
    /**
     * Register plugins with controlled access to Fastify instance
     * @private
     */
    private registerPlugins;
}

/**
 * Development server handler for SSR applications using Vite's HMR and middleware.
 * Simplifies dev workflow while preserving React Router SSR consistency.
 *
 * For development, we integrate with Vite's dev server for HMR support and middleware mode.
 *
 * Multi-App Support: The returned SSRServer instance supports serving multiple React applications
 * from a single server. Use `server.registerDevApp(appKey, paths, options)` to register additional
 * dev-mode apps before calling `server.listen()`. See docs/ssr.md "Multi-App SSR Support" for details.
 *
 * @param paths Required file paths for development server setup (default app)
 * @param options Development SSR options (default app)
 *
 * @example Single app
 * ```ts
 * const server = serveSSRDev(devPaths, { port: 3000 });
 * await server.listen(3000);
 * ```
 *
 * @example Multi-app with subdomain routing
 * ```ts
 * const server = serveSSRDev(mainPaths, mainOptions);
 *
 * server.registerDevApp('marketing', marketingPaths, marketingOptions);
 *
 * server.fastifyInstance.addHook('onRequest', async (request, reply) => {
 *   if (request.hostname === 'marketing.example.com') {
 *     request.setActiveSSRApp('marketing');
 *   }
 * });
 *
 * await server.listen(3000);
 * ```
 */
declare function serveSSRDev(paths: SSRDevPaths, options?: ServeSSRDevOptions): SSRServer;
/**
 * Production server handler for SSR applications.
 *
 * Creates an SSR server instance for production mode. The server entry import
 * and manifest loading are deferred until the server starts listening, which
 * provides better error handling and avoids unnecessary work during construction.
 *
 * Multi-App Support: The returned SSRServer instance supports serving multiple React applications
 * from a single server. Use `server.registerProdApp(appKey, buildDir, options)` to register additional
 * prod-mode apps before calling `server.listen()`. See docs/ssr.md "Multi-App SSR Support" for details.
 *
 * @param buildDir Directory containing built assets (HTML template, static files, manifest, etc.) for default app
 * @param options Production SSR options, including serverEntry to specify which entry file to use (default app)
 *
 * @example Single app
 * ```ts
 * const server = serveSSRProd('./build', { port: 3000 });
 * await server.listen(3000);
 * ```
 *
 * @example Multi-app with path-based routing
 * ```ts
 * const server = serveSSRProd('./build-main', mainOptions);
 *
 * server.registerProdApp('marketing', './build-marketing', marketingOptions);
 *
 * server.fastifyInstance.addHook('onRequest', async (request, reply) => {
 *   if (request.url.startsWith('/marketing')) {
 *     request.setActiveSSRApp('marketing');
 *   }
 * });
 *
 * await server.listen(3000);
 * ```
 */
declare function serveSSRProd(buildDir: string, options?: ServeSSRProdOptions): SSRServer;

/**
 * Static Site Generator for pre-rendering pages at build time.
 *
 * Similar to the production SSR function but designed for generating static HTML files
 * during the build process rather than serving them dynamically.
 *
 * @param buildDir Directory containing built assets (HTML template, static files, manifest, etc.)
 * @param pages Array of pages to generate, each with a path and output filename
 * @param options Additional options for the SSG process, including publicAppConfig and serverEntry (defaults to "EntrySSG")
 * @returns Promise that resolves to a detailed report of the generation process
 */
declare function generateSSG(buildDir: string, pages: PageTypeWanted[], options?: SSGOptions): Promise<SSGReport>;

/**
 * Options for base rendering, simplified API
 */
type BaseRenderOptions = {
    /**
     * Whether to wrap the app element with React.StrictMode
     * @default true
     */
    strictMode?: boolean;
    /**
     * Optional custom wrapper component for additional providers
     * Applied after UnirendHeadProvider but before StrictMode (StrictMode is always outermost)
     * Must be a React component that accepts children
     */
    rootProviders?: React.ComponentType<{
        children: ReactNode;
    }>;
};
/**
 * Base render function that handles React Router wrapping and rendering.
 *
 * This function takes routes and handles all the router creation and wrapping logic
 * internally, including UnirendHead collection for SSR/SSG scenarios.
 *
 * @param renderRequest - The render request containing type, URL, and other options
 * @param routes - The React Router routes configuration
 * @param options - Optional configuration for rendering behavior
 * @returns RenderResult with the rendered HTML and metadata
 *
 * @example
 * ```typescript
 * // Basic usage
 * const result = unirendBaseRender({ type: "ssg", url: "/" }, routes);
 *
 * // With custom wrapper
 * const customWrapper = (node) => <ThemeProvider>{node}</ThemeProvider>;
 * const result = unirendBaseRender(
 *   { type: "ssg", url: "/about" },
 *   routes,
 *   { wrapApp: customWrapper }
 * );
 *
 * // Without StrictMode
 * const result = unirendBaseRender(
 *   { type: "ssr", url: "/contact" },
 *   routes,
 *   { strictMode: false }
 * );
 * ```
 */
declare function unirendBaseRender(renderRequest: RenderRequest, routes: RouteObject[], options?: BaseRenderOptions): Promise<RenderResult>;

/**
 * Create an API server instance
 *
 * This creates a JSON API server with plugin support and full wildcard route flexibility.
 * Unlike SSR servers, this allows plugins to register any wildcard routes including root wildcards.
 * Returns an APIServer instance that you can then start with .listen(port, host).
 *
 * @param options Configuration options for the API server
 * @returns APIServer instance ready to be started
 *
 * @example
 * ```typescript
 * import { serveAPI } from 'unirend/server';
 *
 * const server = serveAPI({
 *   plugins: [
 *     async (fastify, options) => {
 *       // Full wildcard support - even root wildcards are allowed
 *       fastify.get('/api/*', async (request, reply) => {
 *         return { message: 'API wildcard route' };
 *       });
 *
 *       fastify.get('*', async (request, reply) => {
 *         return { message: 'Catch-all route' };
 *       });
 *     }
 *   ],
 *   errorHandler: (request, error, isDev) => ({
 *     error: true,
 *     message: error.message,
 *     path: request.url,
 *     timestamp: new Date().toISOString()
 *   })
 * });
 *
 * // Start the server
 * await server.listen(3001, 'localhost');
 * ```
 */
declare function serveAPI(options?: APIServerOptions): APIServer;

/**
 * Static Web Server for serving SSG-generated sites
 *
 * Wraps APIServer with static file serving capabilities, designed specifically
 * for serving pre-rendered static HTML pages generated by SSG.
 *
 * Features:
 * - Consumes page-map.json from SSG generation
 * - Serves clean URLs without .html extensions
 * - Handles 404/500 pages with proper status codes
 * - Supports HTTPS/SSL
 * - ETag caching and range request support
 * - Framework-agnostic (works with Node.js, Bun, etc.)
 *
 * @example Basic usage
 * ```ts
 * const server = new StaticWebServer({
 *   buildDir: './build/client',
 *   // pageMapPath defaults to 'page-map.json' (relative to buildDir)
 * });
 *
 * await server.listen(3000);
 * ```
 *
 * @example With HTTPS
 * ```ts
 * const server = new StaticWebServer({
 *   buildDir: './build/client',
 *   https: {
 *     key: privateKey,     // string | Buffer
 *     cert: certificate,   // string | Buffer
 *   },
 * });
 *
 * await server.listen(443);
 * ```
 */
declare class StaticWebServer {
    private server;
    private cache;
    private notFoundHTML;
    private errorHTML;
    private routeCount;
    private options;
    constructor(options: StaticWebServerOptions);
    /**
     * Start the static web server
     *
     * @param port Port number to listen on (default: 3000)
     * @param host Host to bind to (default: 'localhost')
     * @returns Promise that resolves when server is ready
     */
    listen(port?: number, host?: string): Promise<void>;
    /**
     * Reload the server configuration from disk without restarting.
     *
     * Re-reads the page-map.json and error pages from disk, then updates
     * the static content cache in-place. In-flight requests continue to be
     * served, new requests immediately see the updated file mappings.
     *
     * If the reload fails (e.g., the page-map.json is missing or invalid),
     * the server continues serving the previous configuration unchanged.
     *
     * _When_ to call reload is the caller's responsibility. A common pattern
     * is to invoke it after an SSG build completes:
     * ```ts
     * // After your build tool writes a new page-map.json:
     * await server.reload();
     * ```
     *
     * @throws {Error} If the server is not running
     * @throws {Error} If the page-map.json cannot be read or is invalid
     * @returns Promise that resolves when the reload is complete
     */
    reload(): Promise<void>;
    /**
     * Stop the server
     *
     * @returns Promise that resolves when server is stopped
     */
    stop(): Promise<void>;
    /**
     * Check if server is currently listening
     *
     * @returns True if server is listening, false otherwise
     */
    isListening(): boolean;
    /**
     * Force-close all open connections, including those actively serving requests.
     * See BaseServer.closeAllConnections() for full details.
     */
    closeAllConnections(): void;
    /**
     * Merges the provided keys into the current access log config at runtime.
     * Access logging is on by default (finish events, default template). Use
     * `events: 'none'` to disable logging while keeping hooks active.
     * Omitted keys stay unchanged. Pass `undefined` for a hook callback to remove it.
     *
     * Changes take effect on the next request — no restart required.
     */
    updateAccessLoggingConfig(partial: Partial<AccessLogConfig>): void;
    /**
     * Returns server statistics: route count and cache usage.
     * Returns null if the server is not currently listening.
     */
    getStats(): {
        etag: {
            items: number;
            byteSize: number;
        };
        content: {
            items: number;
            byteSize: number;
        };
        compressedVariants: {
            items: number;
            byteSize: number;
        };
        stat: {
            items: number;
            byteSize: number;
        };
        routeCount: number;
    } | null;
    /**
     * Loads the page-map.json and error pages from disk, returning the built
     * singleAssetMap (with error page routes removed) and the error page HTML.
     *
     * Shared by listen() and reload() to avoid duplicating the loading logic.
     */
    private buildMaps;
}

/**
 * Lifecycleion logger adapter for Unirend servers (SSR/API/Static).
 *
 * For the SSG generator version, see {@link ./ssg-lifecycleion-logger}.
 */

/**
 * Optional Lifecycleion-specific options that can be passed through the
 * UnirendLoggerObject interface via the `context.logger` convention.
 *
 * Place this under the `logger` key in the context object when calling
 * a log method. Both `request.log` (per-request) and `pluginHost.log`
 * (during plugin setup, before any request exists) route through the adaptor
 * and use pino's `(obj, message)` argument order. TypeScript will not catch
 * the wrong order — pino's overloads accept a bare string as the first
 * argument too, so swapping obj/message compiles but silently drops your params.
 *
 * ```typescript
 * pluginHost.get('/api/profile', (request) => {
 *   request.log.info(
 *     { logger: { params: { id: 'u_123' }, redactedKeys: ['token'] } },
 *     'User {{id}} loaded profile',
 *   );
 *   return { ok: true };
 * });
 * ```
 */
interface LifecycleionLogContextOptions {
    /** Template params for `{{variableName}}` placeholders in the message string. */
    params?: Record<string, unknown>;
    /** Keys in params whose values should be redacted in Lifecycleion logger output. */
    redactedKeys?: string[];
    /** Tags to attach to the log entry for categorizing/filtering logs (e.g., ['auth', 'security']). */
    tags?: string[];
}
/**
 * Wraps a Lifecycleion `Logger`, `LoggerService`, or entity logger as a
 * `UnirendLoggerObject` so it can be passed to `logging.logger` in Unirend
 * server options.
 *
 * Level mapping (Unirend → Lifecycleion):
 * - `trace` → `debug` (Lifecycleion has no trace level)
 * - `debug` → `debug`
 * - `info`  → `info`
 * - `warn`  → `warn`
 * - `error` → `error`
 * - `fatal` → `error` (Lifecycleion has no fatal level)
 *
 * Once wired up via `logging.logger`, `request.log` and `pluginHost.log` both
 * route through the adaptor. See {@link LifecycleionLogContextOptions} for
 * template rendering, redaction, and tags via the `context.logger` convention.
 *
 * ```typescript
 * import { Logger } from 'lifecycleion';
 * import { UnirendLifecycleionLoggerAdaptor, serveSSRProd } from 'unirend/server';
 *
 * const logger = new Logger({ sinks: [...] });
 *
 * const server = serveSSRProd('./build', {
 *   logging: {
 *     logger: UnirendLifecycleionLoggerAdaptor(logger),
 *   },
 * });
 * ```
 *
 * See docs/lifecycleion-logger-adaptor.md for full usage and level mapping details.
 */
declare function UnirendLifecycleionLoggerAdaptor(logger: Logger | LoggerService): UnirendLoggerObject;

/**
 * Lifecycleion logger integration for the SSG generator.
 *
 * For the server-side (SSR/API) Lifecycleion adapter, see
 * {@link ./lifecycleion-logger-adaptor}.
 */

/**
 * SSG logger backed by a Lifecycleion logger, scoped to a named service.
 *
 * Pairs with {@link SSGConsoleLogger} — use this when you want SSG generation
 * output routed through your app's existing Lifecycleion logger instead of
 * raw console calls.
 *
 * ```typescript
 * import { Logger } from 'lifecycleion';
 * import { SSGLifecycleionLogger, generateSSG } from 'unirend/server';
 *
 * const logger = new Logger({ sinks: [...] });
 *
 * const result = await generateSSG(buildDir, pages, {
 *   logger: SSGLifecycleionLogger(logger),
 *   // Custom service name:
 *   // logger: SSGLifecycleionLogger(logger, 'my-site-generator'),
 * });
 * ```
 */
declare function SSGLifecycleionLogger(logger: Logger, serviceName?: string): SSGLogger;

/**
 * File Upload Helpers - Type Definitions
 *
 * Public types for the file upload API.
 */

/**
 * Reasons why an upload was aborted
 */
type AbortReason = 
/** File exceeded size limit during streaming */
'size_exceeded'
/** File MIME type not in allowed list */
 | 'mime_type_rejected'
/** Client disconnected during upload */
 | 'connection_broken'
/** Upload timed out */
 | 'timeout'
/** One file in batch failed (fail-fast) */
 | 'batch_file_failed'
/** Storage processor threw an error */
 | 'processor_error'
/** Too many files provided (exceeds maxFiles limit) */
 | 'files_limit_exceeded'
/** No files were provided in the request */
 | 'no_files_provided';
/**
 * Result from MIME type validator function - discriminated union
 */
type MimeTypeValidationResult = {
    allowed: true;
} | {
    allowed: false;
    /** Human-readable rejection reason (defaults to DEFAULT_MIME_TYPE_REJECTION_REASON if not provided) */
    rejectionReason?: string;
    /** Optional list of allowed MIME types to show in error */
    allowedTypes?: string[];
};
/**
 * Metadata about a file being uploaded
 */
interface FileMetadata {
    /** Original filename from client */
    filename: string;
    /** MIME type from client */
    mimetype: string;
    /** Encoding (e.g., '7bit', 'binary') */
    encoding: string;
    /** Form field name */
    fieldname: string;
    /** Index in batch (0 for single uploads) */
    fileIndex: number;
}
/**
 * Context provided to processor function
 *
 * The processor is called once per file, sequentially (file 0, then file 1, ...).
 * Each call receives a `ProcessorContext` instance for that specific file.
 *
 * - fileIndex: The current file's index (0, 1, 2, ...)
 * - onCleanup: register a cleanup handler for this file's side effects
 *   - cleanup handlers are stored in a shared list for the whole batch
 *   - ALL registered handlers run if ANY file fails (fail-fast batch abort)
 *   - handlers run AFTER processor completes or is interrupted
 *   - triggered by: processor error, size exceeded, MIME reject, timeout, connection break
 *   - use closures to capture file-specific data (IDs, paths, keys, etc.)
 * - isAborted: check if upload was aborted (timeout, connection broken, etc.)
 *   - Important: If timeout fires WHILE processor is running, the processor continues
 *     until completion. Check isAborted() periodically in long-running operations.
 *
 * Example:
 * ```typescript
 * processor: async (fileStream, metadata, context) => {
 *   // Use a collision-resistant ID in real systems (uuid/nanoid/cuid2/etc).
 *   const uploadID = `${context.fileIndex}-${createUploadId()}`;
 *
 *   // This cleanup runs if THIS file OR ANY OTHER file in the batch fails
 *   // (or if processor throws, stream breaks, size exceeded, etc.)
 *   context.onCleanup(async (reason, details) => {
 *     await deleteFile(uploadID); // uploadID captured in closure
 *   });
 *
 *   // For long operations, check if aborted to save resources
 *   if (context.isAborted()) {
 *     throw new Error('Upload aborted');
 *   }
 *
 *   // ... process file (if this throws, cleanup WILL run)
 * }
 * ```
 */
interface ProcessorContext {
    /** Index of this file in the batch (0-based) */
    fileIndex: number;
    /**
     * Register cleanup handler that runs when upload fails.
     *
     * Cleanup handlers are called in these scenarios:
     * - Processor throws an error (storage failure, pipe break, etc.)
     * - File exceeds size limit during streaming
     * - MIME type validation fails
     * - Connection broken or timeout
     * - Batch upload: any file fails (fail-fast)
     *
     * Important: Handlers run AFTER the processor completes (or is interrupted),
     * not during processor execution.
     *
     * Note: Cleanup handlers are called for their side effects only. Any return
     * value is ignored.
     */
    onCleanup: (cleanupFn: (reason: AbortReason, details?: Record<string, unknown>) => Promise<void> | void) => void;
    /**
     * Check if upload has been aborted (timeout, connection broken, etc.)
     * Use this to interrupt long-running operations (object storage uploads, image processing, etc.)
     * Note: Processor continues until completion unless you check this periodically
     */
    isAborted: () => boolean;
}
/**
 * Result from processing a single file
 */
interface ProcessedFile<T = unknown> {
    /** File index in batch */
    fileIndex: number;
    /** Original filename */
    filename: string;
    /** Custom data returned by processor */
    data: T;
}
/**
 * Success result from processUpload - contains processed files
 */
interface UploadSuccess<T = unknown> {
    success: true;
    files: ProcessedFile<T>[];
}
/**
 * Error result from processUpload - contains error envelope to return
 */
interface UploadError {
    success: false;
    errorEnvelope: APIErrorResponse;
}
/**
 * Result from processUpload - discriminated union
 */
type UploadResult<T = unknown> = UploadSuccess<T> | UploadError;
/**
 * Configuration for file upload processing
 */
interface FileUploadConfig<T = unknown> {
    /** Fastify request object */
    request: FastifyRequest;
    /** Reply object (for early abort responses) - accepts both ControlledReply and FastifyReply */
    reply: ControlledReply | FastifyReply;
    /** Maximum number of files (default: 1) */
    maxFiles?: number;
    /** Maximum size per file in bytes */
    maxSizePerFile: number;
    /** Maximum number of form fields (optional, uses server default if not specified) */
    maxFields?: number;
    /** Maximum size of form field values in bytes (optional, uses server default if not specified) */
    maxFieldSize?: number;
    /**
     * Allowed MIME types - can be an array of strings (supports wildcards) or a validator function.
     *
     * Array form supports wildcard patterns:
     * - Exact matches: `'image/jpeg'`, `'application/pdf'`
     * - Wildcards: `'image/*'` (all images), `'text/*'` (all text), `'*' + '/*'` (all types)
     *
     * Validator function returns a discriminated union:
     * - `{ allowed: true }` - File type is allowed
     * - `{ allowed: false, rejectionReason?: string, allowedTypes?: string[] }` - Rejected (rejectionReason optional, defaults to DEFAULT_MIME_TYPE_REJECTION_REASON)
     *
     * @example Array of allowed types (with wildcards)
     * ```typescript
     * allowedMimeTypes: ['image/*', 'application/pdf']
     * // Allows all image types and PDFs
     * ```
     *
     * @example Array of exact types
     * ```typescript
     * allowedMimeTypes: ['image/jpeg', 'image/png', 'image/gif']
     * ```
     *
     * @example Function validator with custom rejection reason
     * ```typescript
     * allowedMimeTypes: (mime) => {
     *   if (mime.startsWith('image/')) {
     *     return { allowed: true };
     *   }
     *   return {
     *     allowed: false,
     *     rejectionReason: 'Only image files are allowed',
     *     allowedTypes: ['image/*']
     *   };
     * }
     * ```
     *
     * @example Function validator without custom rejection reason (uses default)
     * ```typescript
     * allowedMimeTypes: (mime) => {
     *   if (mime.startsWith('image/')) {
     *     return { allowed: true };
     *   }
     *   return { allowed: false, allowedTypes: ['image/*'] };
     * }
     * ```
     */
    allowedMimeTypes: string[] | ((mimetype: string) => MimeTypeValidationResult);
    /** Timeout in milliseconds (optional) */
    timeoutMS?: number;
    /**
     * Processor function that handles file stream and returns custom data.
     * Called for each file in the upload.
     *
     * @param fileStream - Readable stream of file data
     * @param metadata - File metadata (filename, mimetype, etc.)
     * @param context - Upload context (abort handlers, file index, etc.)
     * @returns Custom data to include in response (e.g., { url: string, id: string })
     *
     * Note: You must consume the stream (via pipeline or similar). The framework monitors
     * the stream for size violations and will abort if exceeded.
     */
    processor: (fileStream: NodeJS.ReadableStream, metadata: FileMetadata, context: ProcessorContext) => Promise<T>;
    /**
     * Optional completion callback that runs once after all processing is done.
     *
     * TIMING GUARANTEES:
     * - Success case: Called after all files are processed successfully (no cleanup has run)
     * - Failure case: Called AFTER all cleanup handlers have completed
     *
     * This ensures that in error cases, per-file cleanups registered via context.onCleanup()
     * have already run before onComplete is called. This gives you a consistent view of the
     * system state (e.g., temp files already deleted, transactions rolled back, etc.).
     *
     * ERROR HANDLING:
     * - If `onComplete` throws after a successful upload, the operation is treated as failed
     *   (500 with code `file_upload_completion_failed`).
     * - If `onComplete` throws after an already-failed upload, the original error is returned
     *   (onComplete failure is logged only).
     *
     * COMMON USE CASES:
     * - Atomic file moves (move all uploaded files to final location after success)
     * - Transaction commits (commit database after all files processed)
     * - Cleanup of shared resources (temp directories, database connections)
     * - Logging aggregate upload results
     *
     * @param result - The final upload result (success or error)
     *
     * @example Atomic batch move
     * ```typescript
     * const tempDir = `/tmp/upload-${Date.now()}`;
     * const fileMoves: Array<{temp: string, final: string}> = [];
     *
     * const result = await processFileUpload({
     *   request,
     *   reply,
     *   maxFiles: 5,
     *   maxSizePerFile: 10 * 1024 * 1024,
     *   allowedMimeTypes: ['image/*'],
     *   processor: async (fileStream, metadata, context) => {
     *     const tempPath = `${tempDir}/${metadata.filename}`;
     *     await pipeline(fileStream, createWriteStream(tempPath));
     *     fileMoves.push({ temp: tempPath, final: `/uploads/${metadata.filename}` });
     *     return { url: '...' };
     *   },
     *   onComplete: async (result) => {
     *     if (result.success) {
     *       // Move ALL files atomically (if this fails, client gets error)
     *       for (const move of fileMoves) {
     *         await rename(move.temp, move.final);
     *       }
     *     }
     *     // Always cleanup temp directory
     *     await rm(tempDir, { recursive: true, force: true });
     *   }
     * });
     * ```
     */
    onComplete?: (result: UploadResult<T>) => Promise<void> | void;
}

/**
 * File Upload Helpers
 *
 * Framework-provided utilities for handling single and multiple file uploads
 * with streaming validation, cleanup handlers, and standard API error envelopes.
 *
 * Features:
 * - Unified API (maxFiles defaults to 1)
 * - Mid-stream abort on size/MIME type violations
 * - Cleanup handlers via context.onCleanup()
 * - Fail-fast behavior (first error aborts all)
 * - Standard error envelopes for clients
 */

/**
 * Process file upload(s) with validation, streaming, and cleanup handlers.
 *
 * @param config - Upload configuration
 * @returns UploadResult discriminated union (success or error)
 *
 * @example Single file upload
 * ```typescript
 * const result = await processFileUpload({
 *   request,
 *   reply,
 *   maxSizePerFile: 5 * 1024 * 1024, // 5MB
 *   allowedMimeTypes: ['image/jpeg', 'image/png'],
 *   processor: async (stream, metadata, context) => {
 *     const uploadID = generateID();
 *
 *     context.onCleanup(async () => {
 *       await deleteFromObjectStorage(uploadID);
 *     });
 *
 *     const url = await uploadToObjectStorage(stream, uploadID);
 *     return { url, uploadID };
 *   },
 * });
 * if (!result.success) return result.errorEnvelope;
 * // result.files: [{ fileIndex: 0, filename: 'photo.jpg', data: { url: '...', uploadID: '...' } }]
 * ```
 *
 * @example Batch file upload
 * ```typescript
 * const result = await processFileUpload({
 *   request,
 *   reply,
 *   maxFiles: 5,
 *   maxSizePerFile: 10 * 1024 * 1024, // 10MB
 *   allowedMimeTypes: (mime) => {
 *     if (mime.startsWith('image/')) return { allowed: true };
 *     return { allowed: false, allowedTypes: ['image/*'] };
 *   },
 *   processor: async (stream, metadata, context) => {
 *     const uploadID = `${context.fileIndex}-${generateID()}`;
 *
 *     context.onCleanup(async () => {
 *       await deleteFromObjectStorage(uploadID);
 *     });
 *
 *     const url = await uploadToObjectStorage(stream, uploadID);
 *     return { url, uploadID, index: context.fileIndex };
 *   },
 * });
 * if (!result.success) return result.errorEnvelope;
 * // result.files: Array of processed files with data
 * ```
 */
declare function processFileUpload<T = unknown>(config: FileUploadConfig<T>): Promise<UploadResult<T>>;

export { type APIEndpointConfig, type APIRouteHandler, APIServer, type APIServerOptions, type AbortReason, type AccessLogConfig, type AccessLogLevelConfig, type AccessLogReplyInfo, type AccessLogRequestContext, type AccessLogResponseContext, type ControlledReply, type DomainInfo, type FileMetadata, type FileUploadConfig, type HTTPSOptions, type LifecycleionLogContextOptions, type MimeTypeValidationResult, type PageDataHandler, type PageTypeWanted, type PluginHostInstance, type PluginMetadata, type PluginOptions, type ProcessedFile, type ProcessorContext, RedirectServer, type RedirectServerOptions, type RenderRequest, type RenderResult, SSGConsoleLogger, SSGLifecycleionLogger, type SSGOptions, type SSGPageReport, type SSGReport, type SSRDevPaths, SSRServer, type ServeSSRDevOptions, type ServeSSRProdOptions, type ServerPlugin, StaticWebServer, type StaticWebServerOptions, UnirendLifecycleionLoggerAdaptor, type UnirendLoggerLevel, type UnirendLoggerObject, type UnirendLoggingOptions, type UploadError, type UploadResult, type UploadSuccess, generateSSG, processFileUpload, serveAPI, serveRedirect, serveSSRDev, serveSSRProd, unirendBaseRender };
