{"version":3,"sources":["../../src/server.ts","../../src/lib/types.ts","../../src/lib/internal/access-log-plugin.ts","../../src/lib/internal/response-time-header.ts","../../src/lib/internal/utils.ts","../../src/lib/internal/fs-utils.ts","../../src/lib/internal/html-utils/format.ts","../../src/lib/internal/consts.ts","../../src/lib/internal/html-utils/inject.ts","../../src/lib/internal/ssr-server.ts","../../src/lib/internal/html-utils/escape.ts","../../src/lib/internal/error-page-utils.ts","../../src/lib/internal/server-utils.ts","../../src/lib/internal/error-envelope-send.ts","../../src/lib/internal/static-content-cache.ts","../../src/lib/internal/http-header-utils.ts","../../src/lib/internal/response-compression.ts","../../src/lib/internal/static-content-hook.ts","../../src/lib/internal/base-server.ts","../../src/lib/api-envelope/response-helpers.ts","../../src/lib/internal/api-response-helpers-utils.ts","../../src/lib/internal/version-helpers.ts","../../src/lib/internal/data-loader-server-handler-helpers.ts","../../src/lib/internal/api-routes-server-helpers.ts","../../src/lib/internal/web-socket-server-helpers.ts","../../src/lib/internal/cookie-utils.ts","../../src/lib/internal/file-upload-validation-helpers.ts","../../src/lib/internal/unirend-logger-adapter.ts","../../src/lib/internal/logger-config-utils.ts","../../src/lib/ssr.ts","../../src/lib/ssg.ts","../../src/lib/base-render.ts","../../src/lib/internal/WrapAppElement/WrapRouter.tsx","../../src/lib/internal/UnirendContext/UnirendProvider.tsx","../../src/lib/internal/UnirendContext/hooks.ts","../../src/lib/internal/WrapAppElement/Wrappers.tsx","../../src/lib/internal/UnirendHead/UnirendHeadProvider.tsx","../../src/lib/internal/UnirendHead/UnirendHead.tsx","../../src/lib/internal/UnirendHead/serialize-head-collector.ts","../../src/lib/internal/WrapAppElement/CreateAppWrapper.tsx","../../src/lib/internal/WrapAppElement/WrapStaticRouter.tsx","../../src/lib/internal/api-server.ts","../../src/lib/api.ts","../../src/lib/internal/redirect-server.ts","../../src/lib/redirect.ts","../../src/lib/built-in-plugins/static-content.ts","../../src/lib/internal/static-web-server.ts","../../src/lib/internal/lifecycleion-logger-adaptor.ts","../../src/lib/internal/ssg-lifecycleion-logger.ts","../../src/lib/server/process-file-upload.ts","../../src/lib/internal/mime-type-utils.ts"],"sourcesContent":["/**\n * Server-only exports for unirend\n *\n * This entry point includes server-side functionality like SSR servers,\n * SSG generation, file system operations, and server-side rendering utilities.\n * It should only be imported in server environments (Node.js, Bun, etc.).\n *\n * Import from 'unirend/server' in your server-side code:\n *\n * ```typescript\n * import { serveSSRDev, serveSSRProd, generateSSG } from 'unirend/server';\n *\n * // Development\n * const devServer = serveSSRDev({\n *   serverEntry: './src/EntrySSR.tsx',\n *   template: './index.html',\n *   viteConfig: './vite.config.ts'\n * });\n *\n * // Production\n * const prodServer = serveSSRProd('./build');\n *\n * // Static Site Generation\n * const result = await generateSSG('./build', pages);\n * ```\n */\n\n// Server-safe types\nexport type {\n  RenderRequest,\n  RenderResult,\n  ServeSSRDevOptions,\n  ServeSSRProdOptions,\n  SSGOptions,\n  SSGReport,\n  SSGPageReport,\n  PageTypeWanted,\n  APIServerOptions,\n  ControlledReply,\n  UnirendLoggingOptions,\n  UnirendLoggerObject,\n  UnirendLoggerLevel,\n  HTTPSOptions,\n  AccessLogConfig,\n  AccessLogLevelConfig,\n  AccessLogRequestContext,\n  AccessLogResponseContext,\n  AccessLogReplyInfo,\n} from './lib/types';\nexport type { RedirectServerOptions } from './lib/redirect';\n\n// Server-safe constants\nexport { SSGConsoleLogger } from './lib/types';\n\nexport type {\n  SSRDevPaths,\n  ServerPlugin,\n  PluginHostInstance,\n  PluginOptions,\n  PluginMetadata,\n  StaticWebServerOptions,\n} from './lib/types';\nexport type { SSRServer } from './lib/internal/ssr-server';\nexport type { APIServer } from './lib/internal/api-server';\n// only export the config type as class not used internally\nexport type { PageDataHandler } from './lib/internal/data-loader-server-handler-helpers';\nexport type { APIRouteHandler } from './lib/internal/api-routes-server-helpers';\nexport type { APIEndpointConfig } from './lib/types';\n\n// Server-safe functions\nexport { serveSSRDev, serveSSRProd } from './lib/ssr';\nexport { generateSSG } from './lib/ssg';\nexport { unirendBaseRender } from './lib/base-render';\nexport { serveAPI } from './lib/api';\nexport { serveRedirect, RedirectServer } from './lib/redirect';\n\n// Re-export Fastify request/reply types to avoid forcing consumers to import 'fastify'\nexport type { FastifyRequest, FastifyReply } from 'fastify';\nexport type { DomainInfo } from './lib/internal/domain-info';\nexport type {\n  FastifyRequest as ServerRequest,\n  FastifyReply as ServerReply,\n} from 'fastify';\n\n// Export our out of the box static web server\nexport { StaticWebServer } from './lib/internal/static-web-server';\n\n// Lifecycleion logger adaptor\nexport { UnirendLifecycleionLoggerAdaptor } from './lib/internal/lifecycleion-logger-adaptor';\nexport { SSGLifecycleionLogger } from './lib/internal/ssg-lifecycleion-logger';\nexport type { LifecycleionLogContextOptions } from './lib/internal/lifecycleion-logger-adaptor';\n\n// File upload helpers\nexport { processFileUpload } from './lib/server/process-file-upload';\nexport type {\n  AbortReason,\n  FileMetadata,\n  ProcessorContext,\n  ProcessedFile,\n  FileUploadConfig,\n  MimeTypeValidationResult,\n  UploadSuccess,\n  UploadError,\n  UploadResult,\n} from './lib/server/process-file-upload-types';\n","import type {\n  FastifyRequest,\n  FastifyLoggerOptions,\n  FastifyBaseLogger,\n  FastifyReply,\n  FastifyPluginAsync,\n  FastifyPluginCallback,\n  FastifySchema,\n  preHandlerHookHandler,\n  FastifyInstance,\n} from 'fastify';\nimport type { CookieSerializeOptions } from '@fastify/cookie';\nimport type { ViteDevServer } from 'vite';\nimport type { SecureContext } from 'tls';\nimport type { DataLoaderServerHandlerHelpers } from './internal/data-loader-server-handler-helpers';\nimport type {\n  APIErrorResponse,\n  PageErrorResponse,\n  BaseMeta,\n} from './api-envelope/api-envelope-types';\nimport type { UnirendContextValue } from './internal/UnirendContext';\nimport type { DomainInfo } from './internal/domain-info';\n// NOTE: APIResponseHelpers uses a package-name import — do not change to a relative path.\n// `typeof APIResponseHelpers` captures the class constructor type, which has predicate methods\n// (isSuccessResponse, etc.). The `typeof` is the key: TypeScript 5.5 switched from structural\n// to nominal checking for types referenced inside predicates on constructor types — unlike\n// plain interfaces which use duck typing, two independently inlined copies of this class\n// are treated as incompatible.\n//\n// A relative import would cause rollup-plugin-dts to inline duplicate declarations across\n// bundles, breaking cross-bundle ServerPlugin compatibility.\n// tsconfig.json `paths` maps 'unirend/api-envelope' → ./src/api-envelope.ts so tsc can\n// resolve this when type-checking from source or running demos directly (the package\n// isn't in its own node_modules).\nimport type { APIResponseHelpers } from 'unirend/api-envelope';\n\nexport type RenderType = 'ssg' | 'ssr';\nexport type APIResponseHelpersClass = typeof APIResponseHelpers;\ntype FastifyTrustProxyFunction = (address: string, hop: number) => boolean;\n\nexport interface RenderRequest {\n  type: RenderType;\n  fetchRequest: Request;\n  /**\n   * Unirend context value to provide to the app\n   * Contains render mode, development status, and server request info\n   * Always provided by SSRServer or SSG\n   */\n  unirendContext: UnirendContextValue;\n}\n\n/**\n * Helper object attached to SSR Fetch Request for server-only context\n */\nexport interface SSRHelpers {\n  fastifyRequest: FastifyRequest;\n  /** Controlled reply to allow handlers to set headers/cookies in short-circuit path */\n  controlledReply: ControlledReply;\n  handlers: DataLoaderServerHandlerHelpers;\n}\n\n/**\n * Helper object attached to SSG Fetch Request for build-time context\n * Similar to SSRHelpers but simplified for static generation\n */\nexport interface SSGHelpers {\n  /** Request context object that can be populated and injected into the page */\n  requestContext: Record<string, unknown>;\n}\n\n/**\n * Base interface for render results with a discriminated union type\n */\ninterface RenderResultBase {\n  resultType: 'page' | 'response' | 'render-error';\n}\n\n/**\n * Page result containing HTML content\n */\nexport interface RenderPageResult extends RenderResultBase {\n  resultType: 'page';\n  html: string;\n  preloadLinks: string;\n  head?: {\n    title: string;\n    meta: string;\n    link: string;\n  };\n  statusCode?: number;\n  errorDetails?: Error;\n  ssOnlyData?: Record<string, unknown>;\n}\n\n/**\n * Response result wrapping a standard Response object\n * Used for redirects, errors, or any other non-HTML responses\n */\nexport interface RenderResponseResult extends RenderResultBase {\n  resultType: 'response';\n  response: Response;\n}\n\n/**\n * Error result containing error information\n * Used when rendering fails with an exception\n */\nexport interface RenderErrorResult extends RenderResultBase {\n  resultType: 'render-error';\n  error: Error;\n}\n\n/**\n * Union type for all possible render results\n */\nexport type RenderResult =\n  | RenderPageResult\n  | RenderResponseResult\n  | RenderErrorResult;\n\n/**\n * Required paths for SSR development server\n */\nexport interface SSRDevPaths {\n  /** Path to the server entry file (e.g. \"./src/EntrySSR.tsx\") */\n  serverEntry: string;\n  /** Path to the HTML template file (e.g. \"./index.html\") */\n  template: string;\n  /** Path to the Vite config file (e.g. \"./vite.config.ts\") */\n  viteConfig: string;\n}\n\n/**\n * Plugin metadata returned by plugins for dependency tracking and cleanup\n */\nexport interface PluginMetadata {\n  /** Unique name for this plugin */\n  name: string;\n  /** Plugin dependencies - other plugin names that must be registered first */\n  dependsOn?: string | string[];\n}\n\n/**\n * Plugin registration function type\n * Plugins get access to a controlled subset of Fastify functionality\n * Can optionally return metadata for dependency tracking\n */\nexport type ServerPlugin = (\n  pluginHost: PluginHostInstance,\n  options: PluginOptions,\n) => Promise<PluginMetadata | void> | PluginMetadata | void;\n\n/**\n * Fastify hook names that plugins can register\n * Includes common lifecycle hooks plus string for custom hooks\n */\nexport type FastifyHookName = Parameters<FastifyInstance['addHook']>[0];\n\n/**\n * Controlled Fastify instance interface for plugins\n * Exposes safe methods while preventing access to destructive operations\n */\nexport interface PluginHostInstance {\n  /** Register plugins and middleware */\n  register: <Options extends Record<string, unknown> = Record<string, never>>(\n    plugin: FastifyPluginAsync<Options> | FastifyPluginCallback<Options>,\n    opts?: Options,\n  ) => Promise<void>;\n  /** Add custom hooks */\n  addHook: (\n    hookName: FastifyHookName,\n    handler: (\n      request: FastifyRequest,\n      reply: FastifyReply,\n      ...args: unknown[]\n    ) => unknown,\n  ) => void;\n  /** Add decorators to request/reply objects */\n  decorate: (property: string, value: unknown) => void;\n  decorateRequest: (property: string, value: unknown) => void;\n  decorateReply: (property: string, value: unknown) => void;\n  /** Read-only accessors for server-level decorations */\n  hasDecoration: (property: string) => boolean;\n  getDecoration: <T = unknown>(property: string) => T | undefined;\n  /** Access to route registration with constraints */\n  route: (opts: SafeRouteOptions) => void;\n  get: (path: string, handler: RouteHandler) => void;\n  post: (path: string, handler: RouteHandler) => void;\n  put: (path: string, handler: RouteHandler) => void;\n  delete: (path: string, handler: RouteHandler) => void;\n  patch: (path: string, handler: RouteHandler) => void;\n  /** Server-level logger (pino). Use `(obj, msg)` argument order. Useful for logging during plugin setup, before any request exists. */\n  log: FastifyBaseLogger;\n  /** API route registration shortcuts method for versioned endpoints */\n  api?: unknown;\n  /** Page data loader handler registration method for page data endpoints */\n  pageDataHandler?: unknown;\n  /** The APIResponseHelpers class configured on this server — use this to build envelopes so custom subclasses are respected */\n  APIResponseHelpers: APIResponseHelpersClass;\n}\n\n/**\n * Controlled reply surface available to handlers.\n * Allows setting headers and cookies without giving full reply control.\n *\n * Used by page data loader handlers, API route handlers, and processFileUpload().\n * Provides limited access to prevent handlers from prematurely sending responses\n * or bypassing the framework's envelope pattern.\n */\nexport interface ControlledReply {\n  /** Set a response header (content-type may be enforced by framework) */\n  header: (name: string, value: string) => void;\n  /** Set a cookie if @fastify/cookie is registered */\n  setCookie?: (\n    name: string,\n    value: string,\n    options?: CookieSerializeOptions,\n  ) => void;\n  /** Alias for setCookie if @fastify/cookie is registered */\n  cookie?: (\n    name: string,\n    value: string,\n    options?: CookieSerializeOptions,\n  ) => void;\n  /** Clear a cookie if @fastify/cookie is registered */\n  clearCookie?: (name: string, options?: CookieSerializeOptions) => void;\n  /** Verify and unsign a cookie value if @fastify/cookie is registered */\n  unsignCookie?: (\n    value: string,\n  ) =>\n    | { valid: true; renew: boolean; value: string }\n    | { valid: false; renew: false; value: null };\n  /** Sign a cookie value if @fastify/cookie is registered */\n  signCookie?: (value: string) => string;\n  /** Read a response header value (if available) */\n  getHeader: (name: string) => string | number | string[] | undefined;\n  /** Read all response headers as a plain object */\n  getHeaders: () => Record<string, unknown>;\n  /** Remove a response header by name */\n  removeHeader: (name: string) => void;\n  /** Check if a response header has been set */\n  hasHeader: (name: string) => boolean;\n  /** Whether the reply has already been sent */\n  sent: boolean;\n  /**\n   * Access to the underlying response stream (for connection monitoring)\n   *\n   * Limited scope: Only used internally by processFileUpload() for detecting\n   * broken connections during file uploads. Most handlers won't need this\n   */\n  raw: {\n    /** Whether the underlying connection has been destroyed */\n    destroyed: boolean;\n  };\n  /**\n   * Internal: send an error envelope and finish the response immediately.\n   *\n   * This is installed by the framework's controlled-reply wrapper so helpers\n   * can terminate with the same raw/hijacked path while still reusing shared\n   * header logic such as CORS application.\n   *\n   * ControlledReply intentionally does not expose general-purpose send/write\n   * methods to user handlers. This internal escape hatch exists only so\n   * framework-owned helpers like APIResponseHelpers can terminate early in a\n   * controlled way without reopening arbitrary reply access.\n   */\n  _sendErrorEnvelope: (\n    statusCode: number,\n    errorEnvelope: APIErrorResponse<BaseMeta> | PageErrorResponse<BaseMeta>,\n  ) => Promise<void>;\n}\n\n/**\n * Safe route options that prevent catch-all conflicts\n */\nexport interface SafeRouteOptions {\n  method: string | string[];\n  url: string;\n  handler: RouteHandler;\n  preHandler?: preHandlerHookHandler | preHandlerHookHandler[];\n  schema?: FastifySchema;\n  config?: unknown;\n  constraints?: {\n    /** Only allow specific hosts, no wildcards that could conflict with SSR */\n    host?: string;\n    /** Only allow specific versions */\n    version?: string;\n  };\n}\n\nexport type RouteHandler = (\n  request: FastifyRequest,\n  reply: FastifyReply,\n) => void | Promise<unknown>;\n\n/**\n * WebSocket server configuration options\n */\nexport interface WebSocketOptions {\n  /**\n   * Enable/disable permessage-deflate compression\n   * @default false\n   */\n  perMessageDeflate?: boolean;\n  /**\n   * The maximum allowed message size in bytes\n   * @default 100 * 1024 * 1024 (100MB)\n   */\n  maxPayload?: number;\n  /**\n   * Custom handler called when the WebSocket server is closing\n   * Provides access to all connected clients for graceful shutdown\n   * @param clients Set of all connected WebSocket clients\n   * @returns Promise that resolves when cleanup is complete\n   */\n  preClose?: (clients: Set<unknown>) => Promise<void>;\n}\n\n/**\n * HTTPS server configuration options\n * Provides first-class HTTPS support with certificate files and SNI callback\n */\nexport interface HTTPSOptions {\n  /**\n   * Private key in PEM format\n   * Can be a string, Buffer, or array of strings/Buffers for multiple keys\n   */\n  key: string | Buffer | Array<string | Buffer>;\n  /**\n   * Certificate chain in PEM format\n   * Can be a string, Buffer, or array of strings/Buffers for multiple certificates\n   */\n  cert: string | Buffer | Array<string | Buffer>;\n  /**\n   * Optional CA certificates in PEM format\n   * Used for client certificate verification\n   */\n  ca?: string | Buffer | Array<string | Buffer>;\n  /**\n   * Optional passphrase for the private key\n   */\n  passphrase?: string;\n  /**\n   * Optional SNI (Server Name Indication) callback for dynamic certificate selection\n   * Useful for multi-tenant SaaS applications serving multiple domains\n   *\n   * The callback receives the server name (domain) and should return a SecureContext\n   * with the appropriate certificate for that domain. Can be async.\n   *\n   * @param servername - The domain name from the TLS handshake\n   * @returns SecureContext with the appropriate certificate, or a Promise resolving to one\n   *\n   * @example\n   * ```ts\n   * sni: async (servername) => {\n   *   const ctx = tls.createSecureContext({\n   *     key: await loadKeyForDomain(servername),\n   *     cert: await loadCertForDomain(servername),\n   *   });\n   *\n   *   return ctx;\n   * }\n   * ```\n   */\n  sni?: (servername: string) => SecureContext | Promise<SecureContext>;\n}\n\n/**\n * Shared configuration for versioned API endpoint groups\n * Used by helpers that register versioned endpoints (page data, generic API routes, etc.)\n */\nexport interface APIEndpointConfig {\n  /**\n   * Endpoint prefix that comes before version/endpoint (default: \"/api\")\n   * Set to `false` to disable API handling (server becomes a plain web server)\n   */\n  apiEndpointPrefix?: string | false;\n  /** Whether to enable versioning (default: true) */\n  versioned?: boolean;\n  /** Base endpoint name for page data loader handlers (default: \"page_data\"). Used by SSR/APIServer's page-data registration only. */\n  pageDataEndpoint?: string;\n}\n\n/**\n * Plugin options passed to each plugin\n *\n * Environment information available at plugin registration time.\n *\n * Notes:\n * - Use these fields inside your plugin setup to decide what to REGISTER\n *   (e.g., which routes, which hooks). This is registration-time context.\n * - For per-request branching inside handlers/middleware, read\n *   `request.isDevelopment` (decorated by the servers). Both reflect the same\n *   underlying mode; they serve different scopes.\n */\nexport interface PluginOptions {\n  /** Type of server the plugin is running on */\n  serverType: 'ssr' | 'api';\n  /** Server mode (development or production) */\n  mode: 'development' | 'production';\n  /** Whether running in development mode */\n  isDevelopment: boolean;\n  /** API endpoints configuration from the server */\n  apiEndpoints?: APIEndpointConfig;\n}\n\n/**\n * Log levels supported by the Unirend logger adapter.\n */\nexport type UnirendLoggerLevel =\n  | 'trace'\n  | 'debug'\n  | 'info'\n  | 'warn'\n  | 'error'\n  | 'fatal';\n\n/**\n * Logger function signature used by Unirend logger object methods.\n */\nexport type UnirendLoggerFunction = (\n  message: string,\n  context?: Record<string, unknown>,\n) => void;\n\n/**\n * Framework-level logger object that Unirend adapts to Fastify's logger interface.\n */\nexport interface UnirendLoggerObject {\n  trace: UnirendLoggerFunction;\n  debug: UnirendLoggerFunction;\n  info: UnirendLoggerFunction;\n  warn: UnirendLoggerFunction;\n  error: UnirendLoggerFunction;\n  fatal: UnirendLoggerFunction;\n}\n\n/**\n * High-level logging options that Unirend can adapt to Fastify.\n */\nexport interface UnirendLoggingOptions {\n  /**\n   * Logger object used by Unirend and adapted to Fastify under the hood.\n   */\n  logger: UnirendLoggerObject;\n  /**\n   * Initial minimum level used by the adapter.\n   * @default \"info\"\n   */\n  level?: UnirendLoggerLevel;\n}\n\n/**\n * Subset of Fastify server options safe for use with Unirend servers.\n * Excludes options that would conflict with server setup.\n *\n * These options are supported by SSRServer, APIServer, and StaticWebServer.\n */\nexport interface FastifyServerOptions {\n  /**\n   * Enable/configure Fastify logging\n   * @example true | false | { level: 'info' } | { level: 'warn', prettyPrint: true }\n   */\n  logger?: boolean | FastifyLoggerOptions;\n  /**\n   * Custom Fastify logger instance (e.g. pino-compatible logger).\n   * When provided, this is passed to Fastify as `loggerInstance`.\n   */\n  loggerInstance?: FastifyBaseLogger;\n  /**\n   * Trust proxy headers (useful for deployment behind load balancers)\n   * Passed directly to Fastify. Supports `true`, IP/CIDR strings like\n   * `'127.0.0.1'` or `'127.0.0.1,192.168.1.1/24'`, IP/CIDR lists like\n   * `['127.0.0.1', '10.0.0.0/8']`, hop counts, or a custom trust function\n   * `(address, hop) => boolean`.\n   * @default false\n   */\n  trustProxy?: boolean | string | string[] | number | FastifyTrustProxyFunction;\n  /**\n   * Maximum request body size in bytes for non-multipart requests (JSON, text,\n   * URL-encoded forms). Does NOT apply to multipart file uploads — those are\n   * controlled by `fileUploads.limits.fileSize` (the multipart plugin registers\n   * its own streaming content-type parser, bypassing this limit).\n   * @default 1048576 (1MB)\n   */\n  bodyLimit?: number;\n  /**\n   * Keep-alive timeout in milliseconds\n   * @default 72000 (72 seconds)\n   */\n  keepAliveTimeout?: number;\n  /**\n   * Request idle timeout in milliseconds. The timer resets on every data chunk\n   * received, so large file uploads are not affected as long as data keeps\n   * flowing. A stalled or hung request (no new data) is closed with 408 once\n   * the timeout elapses. Set to `0` to disable. When running without a reverse\n   * proxy, `30000` (30 s) is a reasonable starting point.\n   * @default 0 (disabled)\n   */\n  requestTimeout?: number;\n  /**\n   * TCP connection timeout in milliseconds. Closes connections that have been\n   * idle (no data in either direction) for longer than this value. Protects\n   * against clients that open a socket but never send an HTTP request. Set to\n   * `0` to disable.\n   *\n   * When running without a reverse proxy, `10000` (10 s) is a reasonable\n   * starting point. **WebSocket caveat:** this timeout applies to the\n   * underlying socket, so idle WebSocket connections (e.g. a notification\n   * channel waiting for events) will be closed if no frames are exchanged\n   * within the timeout window. Only set this when WebSockets are disabled, or\n   * ensure your clients send regular ping/pong heartbeats to keep the socket\n   * active.\n   * @default 0 (disabled)\n   */\n  connectionTimeout?: number;\n}\n\n/**\n * Log level config for access logging.\n * Can be a single level applied to all requests, or per-status-range.\n */\nexport type AccessLogLevelConfig =\n  | UnirendLoggerLevel\n  | {\n      /** Level for 2xx–3xx responses. @default 'info' */\n      success?: UnirendLoggerLevel;\n      /** Level for 4xx responses. @default 'warn' */\n      clientError?: UnirendLoggerLevel;\n      /** Level for 5xx responses. @default 'error' */\n      serverError?: UnirendLoggerLevel;\n    };\n\n/**\n * Context passed to onRequest access log hook (request start).\n */\nexport interface AccessLogRequestContext {\n  /** Stable source identifier for access log entries and hooks. */\n  logSource: 'unirend.accessLog';\n  reqID: string | number;\n  method: string;\n  url: string;\n  ip: string;\n  userAgent: string | undefined;\n  /** Server label string (e.g. `'SSR'`, `'API'`). Set via the `serverLabel` server option. */\n  serverLabel: string;\n  /** Whether the request is being handled as a static asset response. */\n  isStaticAsset: boolean;\n  /** Raw Fastify request — same access pattern as data loaders. */\n  request: FastifyRequest;\n}\n\n/**\n * Read-only snapshot of reply state at response time.\n * Extracted to prevent accidental mutation of the response.\n */\nexport interface AccessLogReplyInfo {\n  statusCode: number;\n  headers: Record<string, string | string[] | undefined>;\n}\n\n/**\n * Context passed to onResponse access log hook (request finish or abort).\n */\nexport interface AccessLogResponseContext extends AccessLogRequestContext {\n  statusCode: number;\n  /** Elapsed time in milliseconds. */\n  responseTime: number;\n  /** Whether the request completed normally or the client disconnected early. */\n  finishType: 'completed' | 'aborted';\n  /** Read-only reply snapshot. */\n  replyInfo: AccessLogReplyInfo;\n}\n\n/**\n * First-party access logging configuration.\n * Applies to all server types (SSRServer, APIServer, StaticWebServer, RedirectServer).\n *\n * Fastify's built-in request lifecycle logs are always suppressed internally,\n * this config controls what Unirend logs instead.\n */\nexport interface AccessLogConfig {\n  /**\n   * Which lifecycle events to log.\n   * @default 'finish'\n   */\n  events?: 'start' | 'finish' | 'both' | 'none';\n  /**\n   * Template for finish/response log lines. Supports {{variable}} placeholders.\n   * Available variables: logSource, method, url, statusCode, responseTime, finishType, reqID, ip, userAgent, serverLabel, isStaticAsset\n   * @default 'Request finished {{method}} {{url}} {{statusCode}} ({{responseTime}}ms)'\n   */\n  responseTemplate?: string;\n  /**\n   * Template for start/request log lines. Supports {{variable}} placeholders.\n   * Available variables: logSource, method, url, reqID, ip, userAgent, serverLabel, isStaticAsset\n   * @default 'Request started {{method}} {{url}}'\n   */\n  requestTemplate?: string;\n  /**\n   * Log level for printed lines. Defaults to status-code-based:\n   * info for 2xx/3xx, warn for 4xx, error for 5xx.\n   */\n  level?: AccessLogLevelConfig;\n  /**\n   * Custom hook fired at request start when provided.\n   * Awaited before request handling continues.\n   * Useful for writing initial access log records (DB insert, etc.).\n   */\n  onRequest?: (context: AccessLogRequestContext) => void | Promise<void>;\n  /**\n   * Custom hook fired when a request finishes when provided\n   * (both normal completion and client aborts).\n   * Awaited after the response finishes or aborts.\n   * Use context.finishType to distinguish 'completed' from 'aborted'.\n   */\n  onResponse?: (context: AccessLogResponseContext) => void | Promise<void>;\n}\n\nexport interface ResponseTimeHeaderOptions {\n  /**\n   * Whether to emit the response-time header.\n   * @default true\n   */\n  enabled?: boolean;\n  /**\n   * Header name to emit.\n   * @default 'X-Response-Time'\n   */\n  headerName?: string;\n  /**\n   * Number of fractional digits to include in the emitted time.\n   * @default 2\n   */\n  digits?: number;\n}\n\n/**\n * Base options for SSR\n * @template M Custom meta type extending BaseMeta for error/notFound handlers\n */\ninterface ServeSSROptions<M extends BaseMeta = BaseMeta> {\n  /**\n   * Response compression for non-streaming SSR HTML and API responses.\n   * Negotiates `Accept-Encoding` and skips range or already-encoded replies.\n   *\n   * Static files served through `staticContentRouter` use the same shape but\n   * handle compression in the static file layer so ETags and range requests\n   * remain correct.\n   *\n   * @default true\n   */\n  responseCompression?: boolean | ResponseCompressionOptions;\n  /**\n   * Optional response-time header emitted on completed responses.\n   * Normal Fastify-managed replies apply and measure this in `onSend`.\n   *\n   * For hijacked/raw replies, it is applied when `reply.hijack()` is called so\n   * `reply.getHeaders()` includes it before a\n   * subsequent raw `writeHead(...)`. Access logging measures when the response\n   * finishes, so raw/hijacked responses can report different timings there.\n   *\n   * @default false\n   */\n  responseTimeHeader?: boolean | ResponseTimeHeaderOptions;\n  /**\n   * ID of the container element (defaults to \"root\")\n   * This element will be formatted inline to prevent hydration issues\n   */\n  containerID?: string;\n  /**\n   * Optional safe-to-share app configuration object.\n   * Cloned and frozen per request. On SSR, exposed to React via\n   * usePublicAppConfig() and injected as window.__PUBLIC_APP_CONFIG__.\n   *\n   * Keep this minimal and non-sensitive; it can be passed to the client.\n   */\n  publicAppConfig?: Record<string, unknown>;\n  /**\n   * Cookie forwarding controls for SSR\n   *\n   * Controls which cookies are forwarded:\n   * - from client request → SSR loaders (via the Fetch `Cookie` header)\n   * - from backend/server → client (via `Set-Cookie` headers)\n   *\n   * Behavior:\n   * - If both arrays are empty or undefined, all cookies are allowed\n   * - If `allowCookieNames` is non-empty, only cookies with those names are allowed\n   * - `blockCookieNames` is always applied and will block those cookies even if in allow list\n   */\n  cookieForwarding?: {\n    /**\n     * Cookie names that are allowed to be forwarded.\n     * If provided and non-empty, only these cookie names will be forwarded.\n     */\n    allowCookieNames?: string[];\n    /**\n     * Cookie names that must never be forwarded (takes precedence over allow list).\n     *\n     * You can also set this to `true` to block ALL cookies from being forwarded.\n     * When `true`, no cookies will be forwarded regardless of `allowCookieNames`.\n     */\n    blockCookieNames?: string[] | true;\n  };\n  /**\n   * Array of plugins to register with the server\n   * Plugins get access to a controlled Fastify instance\n   */\n  plugins?: ServerPlugin[];\n  /**\n   * Override the helpers used to construct API/Page envelopes.\n   * Provide your own class (subclassing `APIResponseHelpers` recommended) to\n   * inject default metadata or behavior. If not provided, the default\n   * `APIResponseHelpers` will be used.\n   */\n  APIResponseHelpersClass?: APIResponseHelpersClass;\n  /**\n   * Configuration for versioned API endpoints (shared by page data and generic API routes)\n   * For page data loader handler endpoints, set pageDataEndpoint (default: \"page_data\")\n   */\n  apiEndpoints?: APIEndpointConfig;\n  /**\n   * File upload configuration\n   * When enabled, multipart file upload support will be available\n   * Allows use of processFileUpload() in your plugins\n   */\n  fileUploads?: FileUploadsConfig;\n  /**\n   * Name of the client folder within buildDir\n   * Defaults to \"client\" if not provided\n   */\n  clientFolderName?: string;\n  /**\n   * Name of the server folder within buildDir\n   * Defaults to \"server\" if not provided\n   */\n  serverFolderName?: string;\n  /**\n   * Custom 500 error page handler\n   * Called when SSR rendering fails with an error\n   * @param request The Fastify request object\n   * @param error The error that occurred\n   * @param isDevelopment Whether running in development mode\n   * @returns HTML string for the error page\n   */\n  get500ErrorPage?: (\n    request: FastifyRequest,\n    error: Error,\n    isDevelopment: boolean,\n  ) => string | Promise<string>;\n  /**\n   * Custom error/not-found handlers for mixed SSR+API servers\n   * These handlers return JSON envelope responses instead of HTML error pages\n   * for requests matching the apiEndpoints.apiEndpointPrefix\n   */\n  APIHandling?: {\n    /**\n     * Custom error handler for API routes\n     * Called when an unhandled error occurs in API routes\n     *\n     * REQUIRED: Must return a proper API or Page envelope response according to api-envelope-structure.md\n     * - For API requests (isPageData=false): Return APIErrorResponse envelope\n     * - For Page requests (isPageData=true): Return PageErrorResponse envelope\n     *\n     * Params: (request, error, isDevelopment, isPageData)\n     * - request: The Fastify request object\n     * - error: The error that occurred\n     * - isDevelopment: Whether running in development mode\n     * - isPageData: Whether this is a page-data request (e.g., /api/v1/page_data/home)\n     *\n     * Required envelope return fields:\n     * - status: \"error\"\n     * - status_code: HTTP status code (400, 401, 404, 500, etc.)\n     * - request_id: Unique request identifier\n     * - type: \"api\" for API requests, \"page\" for page data requests\n     * - data: null (always null for error responses)\n     * - meta: Object containing metadata (page metadata required for page type)\n     * - error: Object with { code, message, details? }\n     */\n    errorHandler?: APIErrorHandlerFn<M>;\n    /**\n     * Custom handler for API requests that did not match any route (404)\n     * If provided, overrides the built-in envelope handler for API routes\n     *\n     * REQUIRED: Must return a proper API or Page envelope response according to api-envelope-structure.md\n     * - For API requests (isPageData=false): Return APIErrorResponse envelope with status_code: 404\n     * - For Page requests (isPageData=true): Return PageErrorResponse envelope with status_code: 404\n     *\n     * Params: (request, isPageData)\n     * - request: The Fastify request object\n     * - isPageData: Whether this is a page-data request (e.g., /api/v1/page_data/home)\n     *\n     * Required envelope return fields:\n     * - status: \"error\"\n     * - status_code: 404\n     * - request_id: Unique request identifier\n     * - type: \"api\" for API requests, \"page\" for page data requests\n     * - data: null (always null for error responses)\n     * - meta: Object containing metadata (page metadata required for page type)\n     * - error: Object with { code: \"not_found\", message, details? }\n     */\n    notFoundHandler?: APINotFoundHandlerFn<M>;\n  };\n  /**\n   * Custom handler for web requests that arrive while the server is shutting down.\n   *\n   * Function form handles web requests. Object form can split API and web\n   * behavior for mixed SSR + API servers. Missing handlers fall back to\n   * Unirend's default 503 response.\n   */\n  closingHandler?: WebClosingHandlerFn | SplitClosingHandler<M>;\n  /**\n   * Enable WebSocket support on the server\n   * @default false\n   */\n  enableWebSockets?: boolean;\n  /**\n   * WebSocket server configuration options\n   * Only used when enableWebSockets is true\n   */\n  webSocketOptions?: WebSocketOptions;\n  /**\n   * HTTPS server configuration\n   * Provides first-class HTTPS support with key, cert, and SNI callback\n   *\n   * @example Basic HTTPS\n   * ```ts\n   * https: {\n   *   key: privateKey,     // string | Buffer\n   *   cert: certificate,   // string | Buffer\n   * }\n   * ```\n   *\n   * @example SNI callback for multi-tenant SaaS\n   * ```ts\n   * https: {\n   *   key: defaultPrivateKey,   // string | Buffer - Default cert\n   *   cert: defaultCertificate,  // string | Buffer\n   *   sni: async (servername) => {\n   *     // Load certificate based on domain\n   *     const { key, cert } = await loadCertForDomain(servername);\n   *\n   *     // Return a secure context for the domain\n   *     return tls.createSecureContext({ key, cert });\n   *   },\n   * }\n   * ```\n   */\n  https?: HTTPSOptions;\n  /**\n   * Curated Fastify options for SSR server configuration\n   * Only exposes safe options that won't conflict with SSR setup\n   */\n  fastifyOptions?: FastifyServerOptions;\n  /**\n   * Framework-level logging options adapted to Fastify under the hood.\n   *\n   * Note: Cannot be used together with `fastifyOptions.logger` or\n   * `fastifyOptions.loggerInstance`.\n   */\n  logging?: UnirendLoggingOptions;\n  /**\n   * First-party access logging configuration\n   * Controls request/response logging without needing a custom plugin\n   */\n  accessLog?: AccessLogConfig;\n  /**\n   * Custom client IP resolver.\n   * When set, called once per request to populate `request.clientIP` — available\n   * throughout the entire request lifecycle (plugins, hooks, page data loader\n   * handlers, API route handlers, access log templates/hooks, etc.).\n   * When not set, `request.clientIP` falls back to `request.ip`\n   * (which reflects Fastify proxy handling when `fastifyOptions.trustProxy`\n   * is configured).\n   *\n   * Use this when behind Cloudflare, AWS ALB, or other CDNs that carry the\n   * real client IP in a custom header.\n   */\n  getClientIP?: (request: FastifyRequest) => string | Promise<string>;\n  /**\n   * Whether to automatically log errors via the server logger\n   * When enabled, all errors are logged before custom error handlers run\n   * Useful for debugging custom error pages that can't show stack traces\n   * @default true\n   */\n  logErrors?: boolean;\n  /**\n   * Label for this server instance, used in error log messages and access log templates.\n   * Useful for distinguishing log output when running multiple server instances.\n   * @default 'SSR'\n   * @example 'SSR:marketing'\n   */\n  serverLabel?: string;\n  /**\n   * Timeout in milliseconds for the SSR render fetch request.\n   * If the render takes longer than this, the request is aborted.\n   * @default 5000 (5 seconds)\n   */\n  ssrRenderTimeout?: number;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface ServeSSRDevOptions<\n  M extends BaseMeta = BaseMeta,\n> extends ServeSSROptions<M> {\n  // Currently no development-specific options\n  // This is a placeholder for any future development-specific options\n}\n\nexport interface ServeSSRProdOptions<\n  M extends BaseMeta = BaseMeta,\n> extends ServeSSROptions<M> {\n  /**\n   * Name of the server entry file to look for in the Vite manifest\n   * Defaults to \"EntrySSR\" if not provided\n   */\n  serverEntry?: string;\n  /**\n   * Path to the HTML template file relative to buildDir\n   * Defaults to \"client/index.html\" if not provided\n   *\n   * @example\n   * // Default behavior - uses buildDir/client/index.html\n   * serveSSRProd('./build')\n   *\n   * @example\n   * // Custom template location\n   * serveSSRProd('./build', { template: 'dist/app.html' })\n   */\n  template?: string;\n  /**\n   * CDN base URL for rewriting asset URLs in HTML at runtime\n   * If provided, rewrites <script src> and <link href> to use this base URL\n   * Defaults to relative URLs if not provided\n   *\n   * @example\n   * // Rewrite /assets/main.js to https://cdn.example.com/assets/main.js\n   * serveSSRProd('./build', {\n   *   CDNBaseURL: 'https://cdn.example.com',\n   *   staticContentRouter: false,  // Disable local serving\n   * })\n   */\n  CDNBaseURL?: string;\n  /**\n   * Configuration for the static file router middleware\n   * Used to serve static assets in production mode\n   *\n   * - If not provided: defaults will be used based on the build directory\n   * - If set to `false`: static router will be disabled (useful for CDN setups)\n   * - If set to an object: custom configuration will be used\n   */\n  staticContentRouter?: StaticContentRouterOptions | false;\n}\n\n// ============================================================================\n// Multi-App SSR Types\n// ============================================================================\n\n/**\n * Shared app configuration options (common to both dev and prod modes)\n */\ninterface SSRInternalAppConfigBase {\n  /** Safe-to-share app configuration object */\n  publicAppConfig?: Record<string, unknown>;\n  /** Client folder name within build directory (default: \"client\") */\n  clientFolderName?: string;\n  /** Server folder name within build directory (default: \"server\") */\n  serverFolderName?: string;\n  /** Root element ID for React mounting (default: \"root\") */\n  containerID?: string;\n  /** Custom 500 error page generator */\n  get500ErrorPage?: (\n    request: FastifyRequest,\n    error: Error,\n    isDevelopment: boolean,\n  ) => string | Promise<string>;\n}\n\n/**\n * Dev-mode app configuration (internal storage)\n * Used internally by SSRServer for dev apps\n */\nexport interface SSRInternalAppConfigDev extends SSRInternalAppConfigBase {\n  /** Dev-specific paths */\n  paths: SSRDevPaths;\n  /** Vite dev server instance (INTERNAL - created and managed by framework) */\n  viteDevServer?: ViteDevServer;\n}\n\n/**\n * Prod-mode app configuration (internal storage)\n * Used internally by SSRServer for prod apps\n */\nexport interface SSRInternalAppConfigProd extends SSRInternalAppConfigBase {\n  /** Prod-specific build directory */\n  buildDir: string;\n  /** Server entry name in manifest (default: \"EntrySSR\") */\n  serverEntry?: string;\n  /** HTML template path relative to buildDir (default: \"client/index.html\") */\n  template?: string;\n  /** CDN base URL for asset URL rewriting (prod only) */\n  CDNBaseURL?: string;\n  /** Static content router config (prod only) */\n  staticContentRouter?: StaticContentRouterOptions | false;\n  /** Cached render function (INTERNAL - cached by framework) */\n  cachedRenderFunction?: (\n    renderRequest: RenderRequest,\n  ) => Promise<RenderResult>;\n  /** Cached HTML template (INTERNAL - cached by framework) */\n  cachedHTMLTemplate?: string;\n}\n\n/**\n * Union type for internal app storage (discriminated by presence of paths vs buildDir)\n */\nexport type SSRInternalAppConfig =\n  | SSRInternalAppConfigDev\n  | SSRInternalAppConfigProd;\n\n/**\n * Options for registering additional dev apps via registerDevApp()\n * Only includes per-app options (excludes server-level shared options)\n */\nexport type RegisterDevAppOptions<M extends BaseMeta = BaseMeta> = Pick<\n  ServeSSRDevOptions<M>,\n  | 'publicAppConfig'\n  | 'containerID'\n  | 'get500ErrorPage'\n  | 'clientFolderName'\n  | 'serverFolderName'\n>;\n\n/**\n * Options for registering additional prod apps via registerProdApp()\n * Only includes per-app options (excludes server-level shared options)\n */\nexport type RegisterProdAppOptions<M extends BaseMeta = BaseMeta> = Pick<\n  ServeSSRProdOptions<M>,\n  | 'publicAppConfig'\n  | 'containerID'\n  | 'get500ErrorPage'\n  | 'clientFolderName'\n  | 'serverFolderName'\n  | 'serverEntry'\n  | 'template'\n  | 'CDNBaseURL'\n  | 'staticContentRouter'\n>;\n\n// ============================================================================\n// API Server Types\n// ============================================================================\n\n/**\n * Response type for web (non-API) handlers.\n * Similar to InvalidDomainResponse from domainValidation plugin.\n */\nexport interface WebResponse {\n  /** Content type for the response */\n  contentType: 'html' | 'text' | 'json';\n  /** Response content - string for html/text, object for json */\n  content: string | object;\n  /** HTTP status code. Defaults depend on the handler that uses this response. */\n  statusCode?: number;\n}\n\n/**\n * Error handler function type for API/page requests\n */\nexport type APIErrorHandlerFn<M extends BaseMeta = BaseMeta> = (\n  request: FastifyRequest,\n  error: Error,\n  isDevelopment: boolean,\n  isPageData?: boolean,\n) =>\n  | APIErrorResponse<M>\n  | PageErrorResponse<M>\n  | Promise<APIErrorResponse<M> | PageErrorResponse<M>>;\n\n/**\n * Error handler function type for web (non-API) requests\n */\nexport type WebErrorHandlerFn = (\n  request: FastifyRequest,\n  error: Error,\n  isDevelopment: boolean,\n) => WebResponse | Promise<WebResponse>;\n\n/**\n * Not found handler function type for API/page requests\n */\nexport type APINotFoundHandlerFn<M extends BaseMeta = BaseMeta> = (\n  request: FastifyRequest,\n  isPageData?: boolean,\n) =>\n  | APIErrorResponse<M>\n  | PageErrorResponse<M>\n  | Promise<APIErrorResponse<M> | PageErrorResponse<M>>;\n\n/**\n * Not found handler function type for web (non-API) requests\n */\nexport type WebNotFoundHandlerFn = (\n  request: FastifyRequest,\n) => WebResponse | Promise<WebResponse>;\n\n/**\n * Split error handler with separate API and web handlers\n * Both handlers are optional - if a handler is missing or throws an error,\n * the error is logged to the Fastify logger and the server falls back to the default error response.\n *\n * @template M Custom meta type extending BaseMeta for API handlers\n */\nexport interface SplitErrorHandler<M extends BaseMeta = BaseMeta> {\n  /** Handler for API requests (paths matching apiEndpointPrefix). If missing or throws, logs error and falls back to default. */\n  api?: APIErrorHandlerFn<M>;\n  /** Handler for web requests (non-API paths). If missing or throws, logs error and falls back to default. */\n  web?: WebErrorHandlerFn;\n}\n\n/**\n * Split not found handler with separate API and web handlers\n * Both handlers are optional - if a handler is missing or throws an error,\n * the error is logged to the Fastify logger and the server falls back to the default not found response.\n *\n * @template M Custom meta type extending BaseMeta for API handlers\n */\nexport interface SplitNotFoundHandler<M extends BaseMeta = BaseMeta> {\n  /** Handler for API requests (paths matching apiEndpointPrefix). If missing or throws, logs error and falls back to default. */\n  api?: APINotFoundHandlerFn<M>;\n  /** Handler for web requests (non-API paths). If missing or throws, logs error and falls back to default. */\n  web?: WebNotFoundHandlerFn;\n}\n\n/**\n * Closing handler function type for API/page requests.\n * Called for requests that arrive while the server is shutting down.\n */\nexport type APIClosingHandlerFn<M extends BaseMeta = BaseMeta> = (\n  request: FastifyRequest,\n  isPageData?: boolean,\n) =>\n  | APIErrorResponse<M>\n  | PageErrorResponse<M>\n  | Promise<APIErrorResponse<M> | PageErrorResponse<M>>;\n\n/**\n * Closing handler function type for web (non-API) requests.\n * Called for requests that arrive while the server is shutting down.\n */\nexport type WebClosingHandlerFn = (\n  request: FastifyRequest,\n) => WebResponse | Promise<WebResponse>;\n\n/**\n * Split closing handler with separate API and web handlers.\n * Both handlers are optional - if a handler is missing or throws an error,\n * the error is logged to the Fastify logger and the server falls back to the default 503 response.\n *\n * @template M Custom meta type extending BaseMeta for API handlers\n */\nexport interface SplitClosingHandler<M extends BaseMeta = BaseMeta> {\n  /** Handler for API requests (paths matching apiEndpointPrefix). If missing or throws, falls back to default. */\n  api?: APIClosingHandlerFn<M>;\n  /** Handler for web requests (non-API paths). If missing or throws, falls back to default. */\n  web?: WebClosingHandlerFn;\n}\n\n/**\n * Options for configuring the API server\n * @template M Custom meta type extending BaseMeta for error/notFound handlers\n */\nexport interface APIServerOptions<M extends BaseMeta = BaseMeta> {\n  /**\n   * Optional safe-to-share app configuration object.\n   * Cloned and frozen per request as `request.publicAppConfig`.\n   *\n   * API servers do not inject this into HTML. Include it in response metadata\n   * yourself when clients should receive it.\n   */\n  publicAppConfig?: Record<string, unknown>;\n  /**\n   * Response compression for non-streaming API and web responses.\n   * Negotiates `Accept-Encoding` and skips range or already-encoded replies.\n   *\n   * @default true\n   */\n  responseCompression?: boolean | ResponseCompressionOptions;\n  /**\n   * Optional response-time header emitted on completed responses.\n   * Normal Fastify-managed replies apply and measure this in `onSend`.\n   *\n   * For hijacked/raw replies, it is applied when `reply.hijack()` is called so\n   * `reply.getHeaders()` includes it before a\n   * subsequent raw `writeHead(...)`. Access logging measures when the response\n   * finishes, so raw/hijacked responses can report different timings there.\n   *\n   * @default false\n   */\n  responseTimeHeader?: boolean | ResponseTimeHeaderOptions;\n  /**\n   * Array of plugins to register with the server\n   * Plugins get access to a controlled Fastify instance with full wildcard support\n   */\n  plugins?: ServerPlugin[];\n  /**\n   * Override the helpers used to construct API/Page envelopes.\n   * Provide your own class (subclassing `APIResponseHelpers` recommended) to\n   * inject default metadata or behavior. If not provided, the default\n   * `APIResponseHelpers` will be used.\n   */\n  APIResponseHelpersClass?: APIResponseHelpersClass;\n  /**\n   * Configuration for versioned API endpoints (shared by page data and generic API routes)\n   * For page data loader handler endpoints, set pageDataEndpoint (default: \"page_data\")\n   */\n  apiEndpoints?: APIEndpointConfig;\n  /**\n   * File upload configuration\n   * When enabled, multipart file upload support will be available\n   * Allows use of processFileUpload() in your plugins\n   */\n  fileUploads?: FileUploadsConfig;\n  /**\n   * Custom error handler for server errors\n   *\n   * Can be either:\n   * 1. A function (handles all requests the same way - JSON envelope)\n   * 2. An object with separate `api` and `web` handlers for split behavior\n   *\n   * Function form (same signature as SSR APIHandling.errorHandler):\n   * - Must return API or Page envelope response (see api-envelope-structure.md)\n   * - Used for pure API servers\n   *\n   * Object form (for mixed API + web servers):\n   * - `api`: Handles API requests (paths matching apiEndpointPrefix)\n   *   Params: (request, error, isDevelopment, isPageData)\n   *   - request: The Fastify request object\n   *   - error: The error that occurred\n   *   - isDevelopment: Whether running in development mode\n   *   - isPageData: Whether this is a page-data request (e.g., /api/v1/page_data/home)\n   *   Required envelope return fields:\n   *   - status: \"error\"\n   *   - status_code: HTTP status code (400, 401, 404, 500, etc.)\n   *   - request_id: Unique request identifier\n   *   - type: \"api\" for API requests, \"page\" for page data requests\n   *   - data: null (always null for error responses)\n   *   - meta: Object containing metadata (page metadata required for page type)\n   *   - error: Object with { code, message, details? }\n   * - `web`: Handles non-API requests\n   *   Params: (request, error, isDevelopment)\n   *   - request: The Fastify request object\n   *   - error: The error that occurred\n   *   - isDevelopment: Whether running in development mode\n   *   Required WebResponse return fields:\n   *   - contentType: 'html' | 'text' | 'json'\n   *   - content: string for html/text, object for json\n   *   - statusCode?: HTTP status code (defaults to 500)\n   *\n   * @example Function form\n   * errorHandler: (request, error, isDev, isPageData) =>\n   *   APIResponseHelpers.createAPIErrorResponse({ request, statusCode: 500, ... })\n   *\n   * @example Object form\n   * errorHandler: {\n   *   api: (request, error, isDev, isPageData) => APIResponseHelpers.createAPIErrorResponse({ ... }),\n   *   web: (request, error, isDev) => ({ contentType: 'html', content: '<h1>Error</h1>' })\n   * }\n   */\n  errorHandler?: APIErrorHandlerFn<M> | SplitErrorHandler<M>;\n  /**\n   * Custom handler for requests that did not match any route (404)\n   * If provided, overrides the built-in envelope handler.\n   *\n   * Can be either:\n   * 1. A function (handles all requests the same way - JSON envelope)\n   * 2. An object with separate `api` and `web` handlers for split behavior\n   *\n   * Function form (same signature as SSR APIHandling.notFoundHandler):\n   * - Must return API or Page envelope response with status_code: 404 (see api-envelope-structure.md)\n   * - Used for pure API servers\n   *\n   * Object form (for mixed API + web servers):\n   * - `api`: Handles API requests (paths matching apiEndpointPrefix)\n   *   Params: (request, isPageData)\n   *   - request: The Fastify request object\n   *   - isPageData: Whether this is a page-data request (e.g., /api/v1/page_data/home)\n   *   Required envelope return fields:\n   *   - status: \"error\"\n   *   - status_code: 404\n   *   - request_id: Unique request identifier\n   *   - type: \"api\" for API requests, \"page\" for page data requests\n   *   - data: null (always null for error responses)\n   *   - meta: Object containing metadata (page metadata required for page type)\n   *   - error: Object with { code: \"not_found\", message, details? }\n   * - `web`: Handles non-API requests\n   *   Params: (request)\n   *   - request: The Fastify request object\n   *   Required WebResponse return fields:\n   *   - contentType: 'html' | 'text' | 'json'\n   *   - content: string for html/text, object for json\n   *   - statusCode?: HTTP status code (defaults to 404)\n   *\n   * @example Function form\n   * notFoundHandler: (request, isPageData) =>\n   *   APIResponseHelpers.createAPIErrorResponse({ request, statusCode: 404, ... })\n   *\n   * @example Object form\n   * notFoundHandler: {\n   *   api: (request, isPageData) => APIResponseHelpers.createAPIErrorResponse({ ... }),\n   *   web: (request) => ({ contentType: 'html', content: '<h1>404 Not Found</h1>' })\n   * }\n   */\n  notFoundHandler?: APINotFoundHandlerFn<M> | SplitNotFoundHandler<M>;\n  /**\n   * Custom handler for requests that arrive while the server is shutting down.\n   *\n   * Can be either:\n   * 1. A function (handles API requests the same way - JSON envelope)\n   * 2. An object with separate `api` and `web` handlers for split behavior\n   *\n   * Missing handlers fall back to Unirend's default 503 response.\n   */\n  closingHandler?: APIClosingHandlerFn<M> | SplitClosingHandler<M>;\n  /**\n   * Whether to automatically log errors via the server logger\n   * When enabled, all errors are logged before custom error handlers run\n   * Useful for debugging custom error pages that can't show stack traces\n   * @default true\n   */\n  logErrors?: boolean;\n  /**\n   * Label for this server instance, used in error log messages and access log templates.\n   * Useful for distinguishing log output when running multiple server instances.\n   * @default 'API'\n   * @example 'API:v2'\n   */\n  serverLabel?: string;\n  /**\n   * Enable WebSocket support on the server\n   * @default false\n   */\n  enableWebSockets?: boolean;\n  /**\n   * WebSocket server configuration options\n   * Only used when enableWebSockets is true\n   */\n  webSocketOptions?: WebSocketOptions;\n  /**\n   * HTTPS server configuration\n   * Provides first-class HTTPS support with key, cert, and SNI callback\n   *\n   * @example Basic HTTPS\n   * ```ts\n   * https: {\n   *   key: privateKey,     // string | Buffer\n   *   cert: certificate,   // string | Buffer\n   * }\n   * ```\n   *\n   * @example SNI callback for multi-tenant SaaS\n   * ```ts\n   * https: {\n   *   key: defaultPrivateKey,   // string | Buffer - Default cert\n   *   cert: defaultCertificate,  // string | Buffer\n   *   sni: async (servername) => {\n   *     // Load certificate based on domain\n   *     const { key, cert } = await loadCertForDomain(servername);\n   *\n   *     // Return a secure context for the domain\n   *     return tls.createSecureContext({ key, cert });\n   *   },\n   * }\n   * ```\n   */\n  https?: HTTPSOptions;\n  /**\n   * Curated Fastify options for API server configuration\n   * Only exposes safe options that won't conflict with API setup\n   */\n  fastifyOptions?: FastifyServerOptions;\n  /**\n   * Framework-level logging options adapted to Fastify under the hood.\n   *\n   * Note: Cannot be used together with `fastifyOptions.logger` or\n   * `fastifyOptions.loggerInstance`.\n   */\n  logging?: UnirendLoggingOptions;\n  /**\n   * First-party access logging configuration\n   * Controls request/response logging without needing a custom plugin\n   */\n  accessLog?: AccessLogConfig;\n  /**\n   * Custom client IP resolver.\n   * When set, called once per request to populate `request.clientIP` — available\n   * throughout the entire request lifecycle (plugins, hooks, page data loader\n   * handlers, API route handlers, access log templates/hooks, etc.).\n   * When not set, `request.clientIP` falls back to `request.ip`\n   * (which reflects Fastify proxy handling when `fastifyOptions.trustProxy`\n   * is configured).\n   *\n   * Use this when behind Cloudflare, AWS ALB, or other CDNs that carry the\n   * real client IP in a custom header.\n   */\n  getClientIP?: (request: FastifyRequest) => string | Promise<string>;\n}\n\n/**\n * Options for configuring the Static Web Server\n * Used for serving SSG-generated static sites\n */\nexport interface StaticWebServerOptions {\n  /**\n   * Path to page-map.json file, relative to buildDir.\n   * Generated by generateSSG() with pageMapOutput option.\n   *\n   * @default 'page-map.json'\n   */\n  pageMapPath?: string;\n\n  /**\n   * Base directory containing built assets\n   *\n   * @example \"./build/client\"\n   */\n  buildDir: string;\n\n  /**\n   * Response compression for static pages/assets and generated web responses.\n   * Negotiates `Accept-Encoding` and skips range or already-encoded replies.\n   *\n   * @default true\n   */\n  responseCompression?: boolean | ResponseCompressionOptions;\n  /**\n   * Optional response-time header emitted on completed responses.\n   * Normal Fastify-managed replies apply and measure this in `onSend`.\n   *\n   * For hijacked/raw replies, it is applied when `reply.hijack()` is called so\n   * `reply.getHeaders()` includes it before a\n   * subsequent raw `writeHead(...)`. Access logging measures when the response\n   * finishes, so raw/hijacked responses can report different timings there.\n   *\n   * @default false\n   */\n  responseTimeHeader?: boolean | ResponseTimeHeaderOptions;\n\n  /**\n   * Whether to automatically log errors via the server logger\n   * When enabled, all errors are logged before custom error handlers run\n   * Useful for debugging custom error pages that can't show stack traces\n   * @default true\n   */\n  logErrors?: boolean;\n  /**\n   * Label for this server instance, used in error log messages and access log templates.\n   * Useful for distinguishing log output when running multiple server instances.\n   * @default 'Static'\n   * @example 'Static:marketing'\n   */\n  serverLabel?: string;\n\n  /**\n   * Custom 404 HTML file path (relative to buildDir)\n   * If not specified, automatically looks for \"404.html\" in buildDir\n   *\n   * @default \"404.html\" if it exists\n   */\n  notFoundPage?: string;\n\n  /**\n   * Custom 500 error HTML file path (relative to buildDir)\n   * If not specified, automatically looks for \"500.html\" in buildDir\n   *\n   * @default \"500.html\" if it exists, otherwise uses generated error page\n   */\n  errorPage?: string;\n\n  /**\n   * Additional folders to serve (for assets like /assets, /images)\n   * Maps URL prefix to filesystem directory\n   *\n   * @example { '/assets': './build/client/assets' }\n   */\n  assetFolders?: Record<string, string>;\n\n  /**\n   * Additional single-file assets to serve (e.g., favicon, robots.txt, sitemap.xml)\n   * Maps URL path to filesystem file path\n   *\n   * These assets are merged with the page-map assets from SSG.\n   * If a URL conflicts with a page-map asset, the singleAssets value takes precedence.\n   *\n   * @example\n   * ```typescript\n   * {\n   *   '/favicon.ico': './public/favicon.ico',\n   *   '/robots.txt': './public/robots.txt',\n   *   '/sitemap.xml': './public/sitemap.xml'\n   * }\n   * ```\n   */\n  singleAssets?: Record<string, string>;\n\n  /**\n   * Enable immutable asset detection for fingerprinted files\n   * When enabled, files with fingerprinted names get long cache headers\n   *\n   * @default true\n   */\n  detectImmutableAssets?: boolean;\n\n  /**\n   * Default Cache-Control header for HTML pages\n   * @default \"public, max-age=0, must-revalidate\"\n   */\n  cacheControl?: string;\n\n  /**\n   * Cache-Control header for immutable assets (fingerprinted files)\n   * @default \"public, max-age=31536000, immutable\"\n   */\n  immutableCacheControl?: string;\n\n  /**\n   * HTTPS/SSL configuration (same as APIServer and SSRServer)\n   * Supports SNI (Server Name Indication) for multi-domain certificates\n   *\n   * @example Basic HTTPS\n   * {\n   *   key: privateKey,     // string | Buffer\n   *   cert: certificate    // string | Buffer\n   * }\n   *\n   * @example Multi-domain with SNI\n   * {\n   *   key: defaultPrivateKey,   // string | Buffer - Default cert\n   *   cert: defaultCertificate,  // string | Buffer\n   *   sni: (servername) => {\n   *     if (servername === 'example.com') {\n   *       return tls.createSecureContext({\n   *         key: examplePrivateKey,    // string | Buffer\n   *         cert: exampleCertificate,   // string | Buffer\n   *       });\n   *     }\n   *     return null;\n   *   }\n   * }\n   */\n  https?: HTTPSOptions;\n\n  /**\n   * Fastify server options (logging, trust proxy, etc.)\n   * Subset of Fastify options that don't conflict with static server setup\n   */\n  fastifyOptions?: FastifyServerOptions;\n\n  /**\n   * Framework-level logging options adapted to Fastify under the hood\n   * Cannot be used together with fastifyOptions.logger or fastifyOptions.loggerInstance\n   */\n  logging?: UnirendLoggingOptions;\n\n  /**\n   * First-party access logging configuration\n   * Controls request/response logging without needing a custom plugin\n   */\n  accessLog?: AccessLogConfig;\n\n  /**\n   * Custom client IP resolver.\n   * When set, called once per request to populate `request.clientIP` — available\n   * throughout the entire request lifecycle (plugins, hooks, page data loader\n   * handlers, API route handlers, access log templates/hooks, etc.).\n   * When not set, `request.clientIP` falls back to `request.ip`\n   * (which reflects Fastify proxy handling when `fastifyOptions.trustProxy`\n   * is configured).\n   *\n   * Use this when behind Cloudflare, AWS ALB, or other CDNs that carry the\n   * real client IP in a custom header.\n   */\n  getClientIP?: (request: FastifyRequest) => string | Promise<string>;\n\n  /**\n   * Custom handler for requests that arrive while the static server is shutting down.\n   * If omitted, Unirend returns a default 503 HTML page.\n   */\n  closingHandler?: WebClosingHandlerFn;\n\n  /**\n   * Additional plugins to register\n   * Useful for custom routes, middleware, or request hooks\n   * (e.g., analytics, custom headers, redirects)\n   */\n  plugins?: ServerPlugin[];\n}\n\n/**\n * Object logger for the SSG process.\n * This is separate from Fastify's logger configuration.\n */\nexport interface SSGLogger {\n  /** Log info messages */\n  info: (message: string) => void;\n  /** Log warning messages */\n  warn: (message: string) => void;\n  /** Log error messages */\n  error: (message: string) => void;\n}\n\n/**\n * Pre-built console logger for SSG with prefixed messages\n * Use this if you want basic console logging during SSG\n */\nexport const SSGConsoleLogger: SSGLogger = {\n  // eslint-disable-next-line no-console\n  info: (message: string) => console.log(`[SSG Info] ${message}`),\n  // eslint-disable-next-line no-console\n  warn: (message: string) => console.warn(`[SSG Warn] ${message}`),\n  // eslint-disable-next-line no-console\n  error: (message: string) => console.error(`[SSG Error] ${message}`),\n};\n\n/**\n * Options for Static Site Generation\n */\nexport interface SSGOptions {\n  /**\n   * Optional safe-to-share app configuration object.\n   * Cloned and frozen per page. Exposed to React via usePublicAppConfig() and\n   * injected as window.__PUBLIC_APP_CONFIG__.\n   *\n   * Keep this minimal and non-sensitive; it will be passed to the client.\n   */\n  publicAppConfig?: Record<string, unknown>;\n  /**\n   * CDN base URL for rewriting asset URLs in the generated HTML\n   * (e.g., `'https://cdn.example.com'`).\n   * Rewrites `<script src>` and `<link href>` absolute paths in the template to use the CDN.\n   * Also injected as `window.__CDN_BASE_URL__` and available via `useCDNBaseURL()` in components.\n   * Note: This is resolved at generation time (build-time), not per-request. The CDN URL is baked\n   * into the generated HTML files. Set via a build-time environment variable if needed\n   * (e.g., `CDNBaseURL: process.env.CDN_BASE_URL`).\n   */\n  CDNBaseURL?: string;\n  /**\n   * The hostname of the site being generated (e.g., `'app.example.com'`).\n   *\n   * Used to compute `window.__DOMAIN_INFO__` (hostname + rootDomain for subdomain-spanning cookies)\n   * and populate `useDomainInfo()` in components during SSG rendering and on the client.\n   *\n   * If not provided, `useDomainInfo()` returns `null` in SSG and SPA pages.\n   * Set via a build-time environment variable if needed (e.g., `hostname: process.env.SITE_HOSTNAME`).\n   */\n  hostname?: string;\n  /**\n   * ID of the container element (defaults to \"root\")\n   * This element will be formatted inline to prevent hydration issues\n   */\n  containerID?: string;\n  /**\n   * Name of the server entry file to look for in the Vite manifest\n   * Defaults to \"EntrySSG\" if not provided\n   */\n  serverEntry?: string;\n  /**\n   * Optional logger for the SSG process\n   * Defaults to console if not provided\n   */\n  logger?: SSGLogger;\n  /**\n   * Name of the client folder within buildDir\n   * Defaults to \"client\" if not provided\n   */\n  clientFolderName?: string;\n  /**\n   * Name of the server folder within buildDir\n   * Defaults to \"server\" if not provided\n   */\n  serverFolderName?: string;\n  /**\n   * Filename for a JSON file mapping URL paths to generated filenames.\n   * Written to buildDir (e.g., `buildDir/page-map.json`).\n   *\n   * Useful for server code that needs to dynamically serve clean URLs\n   * (e.g., `/about` → `about.html`) without hardcoding or configuring rewrites.\n   *\n   * Serving the correct file matters for React hydration — even a subtle mismatch\n   * like `/about` vs `/about.html` can cause hydration errors if the wrong file is served.\n   *\n   * The generated file contains a simple object mapping paths to filenames:\n   * ```json\n   * {\n   *   \"/\": \"index.html\",\n   *   \"/about\": \"about.html\"\n   * }\n   * ```\n   *\n   * If not provided, no page map file is written.\n   */\n  pageMapOutput?: string;\n  /**\n   * Whether to treat 5xx server error status codes as generation errors.\n   * Defaults to true. Set to false if you intentionally want to generate\n   * and write custom error pages (e.g., a 500.html) without failing the build.\n   */\n  failOn5xx?: boolean;\n}\n\n/**\n * Base interface for pages to be generated\n */\nexport interface GeneratorPageBase {\n  /** The output filename for the generated HTML */\n  filename: string;\n}\n\n/**\n * SSG page - server-side rendered at build time\n */\nexport interface SSGPageType extends GeneratorPageBase {\n  /** Type of page generation */\n  type: 'ssg';\n  /** The URL path for the page (required for SSG) */\n  path: string;\n  /** Optional request context to seed before rendering (merged into SSGHelpers.requestContext before render is called) */\n  requestContext?: Record<string, unknown>;\n}\n\n/**\n * SPA page - client-side rendered with custom metadata\n */\nexport interface SPAPageType extends GeneratorPageBase {\n  /** Type of page generation */\n  type: 'spa';\n  /** Custom title for the SPA page */\n  title?: string;\n  /** Custom meta description for the SPA page */\n  description?: string;\n  /** Additional meta tags as key-value pairs */\n  meta?: Record<string, string>;\n  /** Optional request context to inject into the page (available as window.__FRONTEND_REQUEST_CONTEXT__) */\n  requestContext?: Record<string, unknown>;\n  /** URL path for page map entry — derived from filename if not provided */\n  path?: string;\n}\n\n/**\n * HTML page - raw HTML written directly to the dist folder, no React render pipeline.\n * Use for self-contained pages that must not depend on the JS/CSS bundle (e.g. 500.html, 403.html, etc).\n * Exactly one of `html` or `source` must be provided.\n */\nexport type HTMLPageType = GeneratorPageBase & {\n  /** Type of page generation */\n  type: 'html';\n  /** URL path for the page map entry — derived from filename if not provided */\n  path?: string;\n} & (\n    | {\n        /** Inline HTML string to write directly */ html: string;\n        source?: never;\n      }\n    | {\n        /** Absolute path to a source HTML file to copy */ source: string;\n        html?: never;\n      }\n  );\n\n/**\n * Union type for all page types\n */\nexport type PageTypeWanted = SSGPageType | SPAPageType | HTMLPageType;\n\n/**\n * Status code for a generated page\n */\nexport type SSGPageStatus = 'success' | 'not_found' | 'error';\n\n/**\n * Report for a single generated page\n */\nexport interface SSGPageReport {\n  /** The page that was processed */\n  page: PageTypeWanted;\n  /** Status of the generation */\n  status: SSGPageStatus;\n  /** Full path to the generated file (if successful) */\n  outputPath?: string;\n  /** Error details (if status is 'error') */\n  errorDetails?: string;\n  /** Time taken to generate the page in milliseconds */\n  timeMS: number;\n}\n\n/**\n * Collection of page reports for the SSG process\n */\nexport interface SSGPagesReport {\n  /** Reports for each page */\n  pages: SSGPageReport[];\n  /** Total number of pages processed */\n  totalPages: number;\n  /** Number of successfully generated pages */\n  successCount: number;\n  /** Number of pages with errors */\n  errorCount: number;\n  /** Number of pages not found (404) */\n  notFoundCount: number;\n  /** Total time taken for the entire generation process in milliseconds */\n  totalTimeMS: number;\n  /** Directory where files were generated */\n  buildDir: string;\n}\n\n/**\n * Complete report for the SSG process, including potential fatal errors\n */\nexport interface SSGReport {\n  /**\n   * True if the SSG generation should be considered failed. This is true when there is a\n   * fatalError (pre-generation failure) or when any pages had errors (e.g. 5xx\n   * status with failOn5xx, render failures, write failures).\n   *\n   * Check this instead of fatalError alone to correctly handle page-level failures.\n   */\n  generationFailed: boolean;\n  /** Fatal error if the process failed before page generation could start */\n  fatalError?: Error;\n  /** Page generation reports (always present, even on error) */\n  pagesReport: SSGPagesReport;\n}\n\n/**\n * Configuration for multipart file upload support\n * When provided, the server will automatically enable multipart uploads\n */\nexport interface FileUploadsConfig {\n  /**\n   * Whether to enable file upload support\n   * When true, multipart upload support will be enabled automatically\n   * @default false\n   */\n  enabled: boolean;\n  /**\n   * Global limits for file uploads (can be overridden per-route)\n   * These act as maximum limits for security\n   */\n  limits?: {\n    /**\n     * Maximum file size in bytes\n     * @default 10485760 (10MB)\n     */\n    fileSize?: number;\n    /**\n     * Maximum number of files per request\n     * @default 10\n     */\n    files?: number;\n    /**\n     * Maximum number of form fields\n     * @default 10\n     */\n    fields?: number;\n    /**\n     * Maximum size of form field values in bytes\n     * @default 1024 (1KB)\n     */\n    fieldSize?: number;\n  };\n  /**\n   * Optional: List of routes/patterns that allow multipart uploads\n   * When provided, a preHandler hook will reject multipart requests to other routes\n   * This prevents bandwidth waste and potential DoS attacks\n   *\n   * Supports exact matches and wildcard patterns.\n   * Use asterisk (*) to match any path segment (e.g. /api/upload/workspace/*)\n   *\n   * @example\n   * allowedRoutes: ['/api/upload/avatar', '/api/upload/document']\n   */\n  allowedRoutes?: string[];\n  /**\n   * Optional: Pre-validation function that runs BEFORE multipart parsing\n   * Use this to reject requests early based on headers (auth, rate limiting, etc.)\n   * This saves bandwidth by rejecting before any file data is parsed\n   *\n   * Supports both synchronous and asynchronous validation functions\n   * Return true to allow the request, or an error response object to reject it\n   *\n   * @example\n   * // Async validation\n   * preValidation: async (request) => {\n   *   const token = request.headers.authorization;\n   *   if (!token) {\n   *     return { statusCode: 401, error: 'unauthorized', message: 'Auth required' };\n   *   }\n   *   return true; // Allow request to proceed\n   * }\n   *\n   * @example\n   * // Sync validation\n   * preValidation: (request) => {\n   *   if (!request.headers['x-api-key']) {\n   *     return { statusCode: 403, error: 'forbidden', message: 'API key required' };\n   *   }\n   *   return true;\n   * }\n   */\n  preValidation?: (\n    request: FastifyRequest,\n  ) =>\n    | Promise<true | { statusCode: number; error: string; message: string }>\n    | true\n    | { statusCode: number; error: string; message: string };\n}\n\n/**\n * Configuration for a folder in the static router folderMap\n */\nexport interface FolderConfig {\n  /** Path to the directory */\n  path: string;\n  /** Whether to detect and use immutable caching for fingerprinted assets */\n  detectImmutableAssets?: boolean;\n}\n\n/**\n * Options for the static router middleware\n * Used to serve static files in production SSR mode\n */\nexport interface StaticContentRouterOptions {\n  /** Exact URL → absolute file path (optional) */\n  singleAssetMap?: Record<string, string>;\n  /** URL prefix → absolute directory path (as string) or folder config object */\n  folderMap?: Record<string, string | FolderConfig>;\n  /** Response compression for buffered static responses; default true */\n  compression?: boolean | ResponseCompressionOptions;\n  /** Maximum size (in bytes) for hashing & in-memory caching; default 5 MB */\n  smallFileMaxSize?: number;\n  /** Maximum number of entries in ETag/content caches; default 100 */\n  cacheEntries?: number;\n  /** Maximum total memory size (in bytes) for content cache; default 50 MB */\n  contentCacheMaxSize?: number;\n  /** Maximum number of entries in the stat cache; default 250 */\n  statCacheEntries?: number;\n  /** TTL in milliseconds for negative stat cache entries; default 30 seconds */\n  negativeCacheTtl?: number;\n  /** TTL in milliseconds for positive stat cache entries; default 1 hour */\n  positiveCacheTtl?: number;\n  /** Custom Cache-Control header; default 'public, max-age=0, must-revalidate' */\n  cacheControl?: string;\n  /** Cache-Control header for immutable fingerprinted assets; default 'public, max-age=31536000, immutable' */\n  immutableCacheControl?: string;\n}\n\nexport interface ResponseCompressionOptions {\n  /** Whether compression is enabled when using the object form */\n  enabled?: boolean;\n  /** Minimum response size in bytes before compression is attempted */\n  threshold?: number;\n  /** Prefer Brotli over gzip when the client gives both equal q-values */\n  preferBrotli?: boolean;\n  /** Brotli quality level passed to Node.js zlib */\n  brotliQuality?: number;\n  /** gzip compression level passed to Node.js zlib */\n  gzipLevel?: number;\n}\n\n// ============================================================================\n// Fastify Module Augmentation for Multi-App SSR\n// ============================================================================\n\ndeclare module 'fastify' {\n  interface FastifyRequest {\n    /**\n     * Active SSR app key for multi-app routing.\n     *\n     * Read-only request value. Defaults to `'__default__'`.\n     * Use `request.setActiveSSRApp(appKey)` in SSR middleware to select a\n     * registered app and refresh app-derived request values.\n     */\n    readonly activeSSRApp: string;\n    /**\n     * Select the active SSR app for this request.\n     *\n     * Validates that the app exists, updates `request.activeSSRApp`, refreshes\n     * `request.publicAppConfig`, and updates the app-level CDN default unless\n     * middleware already overrode `request.CDNBaseURL`.\n     */\n    setActiveSSRApp: (appKey: string) => void;\n    /**\n     * Resolved client IP address.\n     *\n     * Set once per request by the framework using `getClientIP` (if provided)\n     * or falling back to `request.ip` (which reflects Fastify proxy handling\n     * when `fastifyOptions.trustProxy` is configured).\n     *\n     * Available throughout the entire request lifecycle, including plugins,\n     * hooks, page data loader handlers, API route handlers, and access log\n     * templates/hooks.\n     */\n    clientIP: string;\n    /**\n     * Server label for this instance (e.g. `'SSR'`, `'API'`).\n     *\n     * Set once per request by the framework from the `serverLabel` server option.\n     * Available in access log templates as `{{serverLabel}}` and throughout\n     * the request lifecycle via `request.serverLabel`.\n     */\n    serverLabel: string;\n    /**\n     * Safe-to-share app configuration cloned and frozen for this request.\n     *\n     * Available to plugins, handlers, and helpers on SSR and API servers.\n     * SSR also exposes it to React and injects it into HTML. API servers only\n     * send it to clients if your response includes selected values.\n     */\n    publicAppConfig?: Record<string, unknown>;\n    /**\n     * Effective CDN base URL for this SSR request.\n     *\n     * SSR middleware can set this as a per-request override. If it remains\n     * unset, the SSR server populates it from the active app's `CDNBaseURL`\n     * before preHandler hooks, route handlers, SSR render, and custom 500\n     * pages run.\n     *\n     * API servers do not set this field.\n     */\n    CDNBaseURL?: string;\n    /**\n     * Optional request-scoped helper installed by the built-in CORS plugin.\n     *\n     * Raw/hijacked response paths can call this before `writeHead(...)` to\n     * apply the same actual-response CORS/security headers that normal\n     * Fastify-managed responses receive.\n     */\n    applyCORSHeaders?: (reply: FastifyReply) => void | Promise<void>;\n    /**\n     * Internal request-start timestamp captured by the framework.\n     *\n     * Used by framework features that need a stable \"request received\" time,\n     * including API/page envelope timestamps and fallback response-time\n     * calculation when Fastify's built-in elapsedTime is unavailable.\n     */\n    receivedAt?: number;\n    /**\n     * Per-request mutable key-value store initialized by both SSRServer and APIServer\n     * before any plugins or hooks run.\n     *\n     * Use this to pass data between middleware, hooks, and handlers — for example,\n     * seeding user session info, theme preferences, or CSRF tokens from an onRequest\n     * hook so that page data loader handlers and API handlers can read them.\n     *\n     * During SSR, the server injects the final context into the rendered HTML so\n     * client-side React can hydrate with the same values via `useRequestContext()`.\n     *\n     * In separated SSR/API deployments the SSR layer forwards this context to trusted\n     * API page data requests and merges returned context back automatically.\n     */\n    requestContext: Record<string, unknown>;\n    /**\n     * Parsed domain information for this request, computed once per request from\n     * `request.hostname` by both SSRServer and APIServer.\n     *\n     * - `hostname`: bare hostname with port stripped (IPv6-safe)\n     * - `rootDomain`: apex domain without a leading dot (e.g. `'example.com'`);\n     *   empty string for localhost and raw IP addresses\n     *\n     * Use `rootDomain` to set subdomain-spanning cookies by prepending a dot:\n     * `domain=.${request.domainInfo.rootDomain}` — the same value that the\n     * client-side `cycleTheme()` helper uses.\n     */\n    domainInfo: DomainInfo;\n    /**\n     * Set to `true` when the request is handled by the built-in static content\n     * handler (fingerprinted assets, public files, etc.), regardless of which\n     * server type is serving the file.\n     *\n     * Initialized to `false` for every request. The static content handler sets\n     * it to `true` before calling `reply.hijack()`, so it is observable in\n     * `onResponse` hooks and access log templates even though `onSend` is bypassed.\n     *\n     * Use this to skip work that is inappropriate for static asset responses —\n     * for example, cookie renewal should only happen on document/API responses,\n     * not on every `.js` or `.css` file request.\n     */\n    isStaticAsset: boolean;\n  }\n}\n","import { CurlyBrackets } from 'lifecycleion/curly-brackets';\nimport type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';\nimport type {\n  AccessLogConfig,\n  AccessLogLevelConfig,\n  AccessLogRequestContext,\n  AccessLogResponseContext,\n  AccessLogReplyInfo,\n  UnirendLoggerLevel,\n} from '../types';\nimport {\n  formatRoundedResponseTime,\n  getResponseTimeMS,\n} from './response-time-header';\n\nconst DEFAULT_REQUEST_TEMPLATE =\n  '[{{serverLabel}}] Request started {{method}} {{url}}';\nconst DEFAULT_RESPONSE_TEMPLATE =\n  '[{{serverLabel}}] Request finished {{method}} {{url}} {{statusCode}} ({{responseTime}}ms)';\n\nconst VALID_EVENTS = new Set(['start', 'finish', 'both', 'none']);\nconst VALID_LEVELS = new Set<UnirendLoggerLevel>([\n  'trace',\n  'debug',\n  'info',\n  'warn',\n  'error',\n  'fatal',\n]);\n\nconst DEFAULT_ACCESS_LOG_LEVELS = {\n  success: 'info',\n  clientError: 'warn',\n  serverError: 'error',\n} as const;\n\n/**\n * Validate a full or partial AccessLogConfig object.\n * Throws a descriptive Error for any invalid field value.\n */\nexport function validateAccessLogConfig(\n  config: Partial<AccessLogConfig>,\n): void {\n  if (config.events !== undefined && !VALID_EVENTS.has(config.events)) {\n    throw new Error(\n      `accessLog.events must be 'start', 'finish', 'both', or 'none'; got ${JSON.stringify(config.events)}`,\n    );\n  }\n\n  if (config.level !== undefined) {\n    const level = config.level;\n\n    if (typeof level === 'string') {\n      if (!VALID_LEVELS.has(level)) {\n        throw new Error(\n          `accessLog.level must be a valid log level or an object; got ${JSON.stringify(level)}`,\n        );\n      }\n    } else if (typeof level === 'object') {\n      for (const key of ['success', 'clientError', 'serverError'] as const) {\n        const v = level[key];\n\n        if (v !== undefined && !VALID_LEVELS.has(v)) {\n          throw new Error(\n            `accessLog.level.${key} must be a valid log level; got ${JSON.stringify(v)}`,\n          );\n        }\n      }\n    } else {\n      throw new TypeError(\n        `accessLog.level must be a string or object; got ${typeof level}`,\n      );\n    }\n  }\n\n  if (\n    config.responseTemplate !== undefined &&\n    typeof config.responseTemplate !== 'string'\n  ) {\n    throw new Error('accessLog.responseTemplate must be a string');\n  }\n\n  if (\n    config.requestTemplate !== undefined &&\n    typeof config.requestTemplate !== 'string'\n  ) {\n    throw new Error('accessLog.requestTemplate must be a string');\n  }\n\n  if (\n    config.onRequest !== undefined &&\n    typeof config.onRequest !== 'function'\n  ) {\n    throw new Error('accessLog.onRequest must be a function');\n  }\n\n  if (\n    config.onResponse !== undefined &&\n    typeof config.onResponse !== 'function'\n  ) {\n    throw new Error('accessLog.onResponse must be a function');\n  }\n}\n\n/**\n * Resolve the log level to use for a given status code and level config.\n * Defaults: info for 2xx/3xx, warn for 4xx, error for 5xx.\n */\nexport function resolveAccessLogLevel(\n  level: AccessLogLevelConfig | undefined,\n  statusCode: number,\n): UnirendLoggerLevel {\n  if (!level) {\n    if (statusCode >= 500) {\n      return DEFAULT_ACCESS_LOG_LEVELS.serverError;\n    }\n\n    if (statusCode >= 400) {\n      return DEFAULT_ACCESS_LOG_LEVELS.clientError;\n    }\n\n    return DEFAULT_ACCESS_LOG_LEVELS.success;\n  }\n\n  if (typeof level === 'string') {\n    return level;\n  }\n\n  if (statusCode >= 500) {\n    return level.serverError ?? DEFAULT_ACCESS_LOG_LEVELS.serverError;\n  }\n\n  if (statusCode >= 400) {\n    return level.clientError ?? DEFAULT_ACCESS_LOG_LEVELS.clientError;\n  }\n\n  return level.success ?? DEFAULT_ACCESS_LOG_LEVELS.success;\n}\n\nconst TEMPLATE_FALLBACK = '???';\n\nfunction buildRequestContext(\n  request: FastifyRequest,\n  serverLabel: string,\n): AccessLogRequestContext {\n  return {\n    logSource: 'unirend.accessLog',\n    reqID: request.id,\n    method: request.method,\n    url: request.url,\n    ip: request.clientIP,\n    userAgent: request.headers['user-agent'],\n    serverLabel,\n    isStaticAsset: request.isStaticAsset ?? false,\n    request,\n  };\n}\n\nfunction buildReplyInfo(reply: FastifyReply): AccessLogReplyInfo {\n  const raw = reply.getHeaders();\n  const headers: Record<string, string | string[] | undefined> = {};\n\n  for (const [key, value] of Object.entries(raw)) {\n    if (typeof value === 'number') {\n      headers[key] = String(value);\n    } else {\n      headers[key] = value;\n    }\n  }\n\n  return { statusCode: reply.statusCode, headers };\n}\n\nfunction buildResponseContext(\n  request: FastifyRequest,\n  reply: FastifyReply,\n  finishType: 'completed' | 'aborted',\n  serverLabel: string,\n): AccessLogResponseContext {\n  const responseTimeMS = getResponseTimeMS(reply);\n\n  return {\n    ...buildRequestContext(request, serverLabel),\n    statusCode: reply.statusCode,\n    responseTime: formatRoundedResponseTime(responseTimeMS),\n    finishType,\n    replyInfo: buildReplyInfo(reply),\n  };\n}\n\n/**\n * Owns access log configuration state and registers Fastify lifecycle hooks.\n * Config is read on every request so runtime updates via update() take effect\n * immediately without restarting the server.\n *\n * Internal — not part of the public plugin API. Users configure access logging\n * via the accessLog option on server config and server.updateAccessLoggingConfig().\n */\nexport class AccessLogPlugin {\n  private _config: AccessLogConfig;\n  private readonly serverLabel: string;\n\n  constructor(serverLabel: string, initialConfig?: AccessLogConfig) {\n    this.serverLabel = serverLabel;\n\n    if (initialConfig !== undefined) {\n      validateAccessLogConfig(initialConfig);\n    }\n\n    this._config = initialConfig ?? {};\n  }\n\n  /**\n   * Partially update config at runtime. Only provided keys are merged;\n   * omitted keys remain unchanged. Changes take effect on the next request.\n   */\n  public update(partial: Partial<AccessLogConfig>): void {\n    validateAccessLogConfig(partial);\n    this._config = { ...this._config, ...partial };\n  }\n\n  /**\n   * Register Fastify lifecycle hooks on the given instance.\n   * Call once per Fastify instance (during listen()).\n   */\n  public register(fastify: FastifyInstance): void {\n    // ── onRequest: start event ────────────────────────────────────────────────\n    fastify.addHook(\n      'onRequest',\n      async (request: FastifyRequest, _reply: FastifyReply) => {\n        const config = this._config;\n        const events = config.events ?? 'finish';\n        const ctx = buildRequestContext(request, this.serverLabel);\n\n        // Template printing — only when events includes 'start' or 'both'\n        if (events === 'start' || events === 'both') {\n          const template = config.requestTemplate ?? DEFAULT_REQUEST_TEMPLATE;\n          const { request: _req, ...templateData } = ctx;\n          const msg = CurlyBrackets(template, templateData, TEMPLATE_FALLBACK);\n\n          request.log[resolveAccessLogLevel(config.level, -1)](\n            { ...templateData, event: 'start' },\n            msg,\n          );\n        }\n\n        // Hook fires unconditionally (for DB writes, audit logs, etc.)\n        if (config.onRequest) {\n          await config.onRequest(ctx);\n        }\n      },\n    );\n\n    // ── onResponse: finish event (normal completion) ──────────────────────────\n    fastify.addHook(\n      'onResponse',\n      async (request: FastifyRequest, reply: FastifyReply) => {\n        const config = this._config;\n        const events = config.events ?? 'finish';\n        const ctx = buildResponseContext(\n          request,\n          reply,\n          'completed',\n          this.serverLabel,\n        );\n\n        // Template printing — only when events includes 'finish' or 'both'\n        if (events === 'finish' || events === 'both') {\n          const template = config.responseTemplate ?? DEFAULT_RESPONSE_TEMPLATE;\n          const { request: _req, ...templateData } = ctx;\n          const msg = CurlyBrackets(template, templateData, TEMPLATE_FALLBACK);\n\n          request.log[resolveAccessLogLevel(config.level, reply.statusCode)](\n            { ...templateData, event: 'finish' },\n            msg,\n          );\n        }\n\n        // Hook fires unconditionally (for DB writes, audit logs, etc.)\n        if (config.onResponse) {\n          await config.onResponse(ctx);\n        }\n      },\n    );\n\n    // ── onRequestAbort: client disconnected before response finished ──────────\n    // Fastify does NOT fire onResponse in this case, so we handle it here.\n    fastify.addHook('onRequestAbort', async (request: FastifyRequest) => {\n      const config = this._config;\n      const events = config.events ?? 'finish';\n\n      // Build a response context with whatever state is available at abort time.\n      // reply is not passed to onRequestAbort, so we synthesize replyInfo from\n      // the request object (status code may be 0 if never set).\n      const ctx: AccessLogResponseContext = {\n        ...buildRequestContext(request, this.serverLabel),\n        statusCode: 0,\n        responseTime: 0,\n        finishType: 'aborted',\n        replyInfo: { statusCode: 0, headers: {} },\n      };\n\n      // Template printing — only when events includes 'finish' or 'both'\n      if (events === 'finish' || events === 'both') {\n        const template = config.responseTemplate ?? DEFAULT_RESPONSE_TEMPLATE;\n        const { request: _req, ...templateData } = ctx;\n        const msg = CurlyBrackets(template, templateData, TEMPLATE_FALLBACK);\n\n        request.log[resolveAccessLogLevel(config.level, 0)](\n          { ...templateData, event: 'finish' },\n          msg,\n        );\n      }\n\n      // Hook fires unconditionally (for DB writes, audit logs, etc.)\n      if (config.onResponse) {\n        await config.onResponse(ctx);\n      }\n    });\n  }\n}\n","import type { FastifyInstance, FastifyReply } from 'fastify';\nimport type { ResponseTimeHeaderOptions } from '../types';\n\nexport interface NormalizedResponseTimeHeaderOptions {\n  enabled: boolean;\n  headerName: string;\n  digits: number;\n}\n\nconst DEFAULT_OPTIONS: NormalizedResponseTimeHeaderOptions = {\n  enabled: false,\n  headerName: 'X-Response-Time',\n  digits: 2,\n};\n\nconst SIMPLE_HEADER_NAME_PATTERN = /^[A-Za-z0-9-]+$/;\n\n/**\n * Resolve the best available response-time measurement for the current reply.\n *\n * Fastify usually provides `reply.elapsedTime`, but we harden this helper for\n * edge cases where that value is missing or non-finite by falling back to the\n * request-start timestamp captured by the framework. If no timing source is\n * usable, we return `-1` as an explicit sentinel meaning \"measurement\n * unavailable\" so it is distinguishable from a genuine near-zero response.\n */\nfunction normalizeResponseTimeMS(reply: FastifyReply): number {\n  if (\n    typeof reply.elapsedTime === 'number' &&\n    Number.isFinite(reply.elapsedTime)\n  ) {\n    return Math.max(0, reply.elapsedTime);\n  }\n\n  const receivedAt = reply.request.receivedAt;\n\n  if (typeof receivedAt === 'number' && Number.isFinite(receivedAt)) {\n    return Math.max(0, Date.now() - receivedAt);\n  }\n\n  return -1;\n}\n\n/**\n * Normalize and validate the public response-time-header config into one\n * internal shape so the runtime hooks do not need to branch on boolean/object\n * forms or repeat input validation on the hot path.\n */\nexport function normalizeResponseTimeHeaderOptions(\n  options: boolean | ResponseTimeHeaderOptions | undefined,\n): NormalizedResponseTimeHeaderOptions {\n  // Public config supports boolean shorthand for the common cases:\n  // - `true` enables the feature with defaults\n  // - `false`/`undefined` disables it entirely\n  if (options === true) {\n    return {\n      ...DEFAULT_OPTIONS,\n      enabled: true,\n    };\n  }\n\n  if (options === false || options === undefined) {\n    return { ...DEFAULT_OPTIONS };\n  }\n\n  // Object form means \"enabled unless explicitly turned off\", mirroring the\n  // style used by other server options in this codebase.\n  const digits = options.digits ?? DEFAULT_OPTIONS.digits;\n\n  // Keep formatting constrained to a small, predictable range so header output\n  // stays readable and we do not accidentally expose extremely long decimals.\n  if (!Number.isInteger(digits) || digits < 0 || digits > 6) {\n    throw new RangeError(\n      `responseTimeHeader.digits must be an integer between 0 and 6; got ${JSON.stringify(options.digits)}`,\n    );\n  }\n\n  const headerName = options.headerName ?? DEFAULT_OPTIONS.headerName;\n\n  // We intentionally use a simpler validation rule than the full HTTP token\n  // grammar: letters, numbers, and dashes cover the normal custom-header use\n  // cases while keeping the error message easy to understand.\n  if (typeof headerName !== 'string' || headerName.trim().length === 0) {\n    throw new TypeError(\n      'responseTimeHeader.headerName must be a non-empty string',\n    );\n  }\n\n  if (!SIMPLE_HEADER_NAME_PATTERN.test(headerName)) {\n    throw new TypeError(\n      'responseTimeHeader.headerName must contain only letters, numbers, and dashes',\n    );\n  }\n\n  return {\n    enabled: options.enabled ?? true,\n    headerName,\n    digits,\n  };\n}\n\nexport function getResponseTimeMS(reply: FastifyReply): number {\n  return normalizeResponseTimeMS(reply);\n}\n\n/**\n * Format the numeric response time into the wire/header form.\n */\nexport function formatResponseTimeHeaderValue(\n  responseTimeMS: number,\n  digits: number,\n): string {\n  // Headers carry a user-facing string (`12.34ms`), while the internal\n  // measurement remains numeric for logs and related helpers.\n  return `${responseTimeMS.toFixed(digits)}ms`;\n}\n\n/**\n * Format the numeric response time for access-log output, which uses integer\n * milliseconds today.\n */\nexport function formatRoundedResponseTime(responseTimeMS: number): number {\n  return Math.round(responseTimeMS);\n}\n\nfunction applyResponseTimeHeader(\n  reply: FastifyReply,\n  options: NormalizedResponseTimeHeaderOptions,\n): void {\n  const responseTimeMS = getResponseTimeMS(reply);\n\n  reply.header(\n    options.headerName,\n    formatResponseTimeHeaderValue(responseTimeMS, options.digits),\n  );\n}\n\nexport function registerResponseTimeHijackPatch(\n  fastifyInstance: FastifyInstance,\n  options: boolean | ResponseTimeHeaderOptions | undefined,\n): void {\n  const normalized = normalizeResponseTimeHeaderOptions(options);\n\n  if (!normalized.enabled) {\n    return;\n  }\n\n  // This hook must be registered early, before routes, so every route sees the\n  // wrapped hijack() method. Hijacked replies bypass onSend entirely.\n  fastifyInstance.addHook('onRequest', (_request, reply, done) => {\n    const originalHijack = reply.hijack.bind(reply);\n\n    reply.hijack = ((...args: unknown[]) => {\n      // If a route switches to raw Node response handling, make sure the\n      // response-time header is already present in reply.getHeaders() before\n      // user code calls raw.writeHead(...). This is an early measurement for\n      // raw/hijacked replies; access logging still measures at completion.\n      if (!reply.raw.headersSent) {\n        applyResponseTimeHeader(reply, normalized);\n      }\n\n      return originalHijack(...(args as []));\n    }) as typeof reply.hijack;\n\n    done();\n  });\n}\n\n/**\n * Register the normal Fastify-managed onSend path for the response-time\n * header. This handles non-hijacked replies and can be registered later in\n * bootstrap so third-party onSend hooks run first.\n */\nexport function registerResponseTimeHeader(\n  fastifyInstance: FastifyInstance,\n  options: boolean | ResponseTimeHeaderOptions | undefined,\n): void {\n  const normalized = normalizeResponseTimeHeaderOptions(options);\n\n  if (!normalized.enabled) {\n    return;\n  }\n\n  // Keep the normal onSend path separate from the hijack patch so we can\n  // register this later in server bootstrap, after plugins/routes, which lets\n  // third-party onSend hooks run first.\n  fastifyInstance.addHook('onSend', (_request, reply, payload, done) => {\n    if (!reply.sent && !reply.raw?.headersSent) {\n      applyResponseTimeHeader(reply, normalized);\n    }\n\n    done(null, payload);\n  });\n}\n","/**\n * Recursively freezes an object and all nested objects, making the entire\n * structure immutable (deep freeze, vs Object.freeze which is shallow).\n *\n * Pure utility with no dependencies — safe to import in both server and\n * client code.\n *\n * Used to freeze public app config clones (so they cannot be mutated\n * within a request, even on nested sub-objects) and debug context snapshots\n * returned by useRequestContextObjectRaw(). The source object is never affected.\n */\nexport function deepFreeze<T>(obj: T): T {\n  if (obj === null || typeof obj !== 'object') {\n    return obj;\n  }\n\n  Object.freeze(obj);\n\n  for (const value of Object.values(obj as object)) {\n    if (value && typeof value === 'object' && !Object.isFrozen(value)) {\n      deepFreeze(value);\n    }\n  }\n\n  return obj;\n}\n\nexport const MINIMUM_SUPPORTED_NODE_MAJOR = 25;\n\nexport type RuntimeName = 'bun' | 'node' | 'unknown';\n\nexport interface RuntimeSupportInfo {\n  runtime: RuntimeName;\n  isSupported: boolean;\n  minimumNodeMajor: number;\n  nodeVersion?: string;\n  bunVersion?: string;\n}\n\ninterface RuntimeEnvironmentLike {\n  Bun?: unknown;\n  process?: {\n    versions?: Partial<Record<'node' | 'bun', string>>;\n  };\n}\n\nfunction parseMajorVersion(version?: string): number | undefined {\n  if (!version) {\n    return undefined;\n  }\n\n  const [majorPart] = version.split('.');\n  const major = Number.parseInt(majorPart, 10);\n\n  return Number.isFinite(major) ? major : undefined;\n}\n\n/**\n * Detect the current JavaScript runtime and whether it satisfies Unirend's\n * runtime requirement. Bun is treated as supported even if it reports an older\n * Node compatibility version via `process.versions.node`.\n */\nexport function getRuntimeSupportInfo(\n  minimumNodeMajor = MINIMUM_SUPPORTED_NODE_MAJOR,\n  environment: RuntimeEnvironmentLike = globalThis as RuntimeEnvironmentLike,\n): RuntimeSupportInfo {\n  const versions = environment.process?.versions;\n  const nodeVersion =\n    typeof versions?.node === 'string' ? versions.node : undefined;\n  const bunVersion =\n    typeof versions?.bun === 'string' ? versions.bun : undefined;\n  const isBun =\n    typeof environment.Bun !== 'undefined' || typeof bunVersion === 'string';\n\n  if (isBun) {\n    return {\n      runtime: 'bun',\n      isSupported: true,\n      minimumNodeMajor,\n      nodeVersion,\n      bunVersion,\n    };\n  }\n\n  if (!nodeVersion) {\n    return {\n      runtime: 'unknown',\n      isSupported: false,\n      minimumNodeMajor,\n    };\n  }\n\n  const nodeMajor = parseMajorVersion(nodeVersion);\n\n  return {\n    runtime: 'node',\n    isSupported: typeof nodeMajor === 'number' && nodeMajor >= minimumNodeMajor,\n    minimumNodeMajor,\n    nodeVersion,\n  };\n}\n\n/**\n * Convenience boolean check for Unirend's runtime requirement.\n */\nexport function isSupportedRuntime(\n  minimumNodeMajor = MINIMUM_SUPPORTED_NODE_MAJOR,\n  environment?: RuntimeEnvironmentLike,\n): boolean {\n  return getRuntimeSupportInfo(minimumNodeMajor, environment).isSupported;\n}\n\n/**\n * Throw a descriptive error when the current runtime does not satisfy\n * Unirend's runtime requirement.\n */\nexport function assertSupportedRuntime(\n  minimumNodeMajor = MINIMUM_SUPPORTED_NODE_MAJOR,\n  environment?: RuntimeEnvironmentLike,\n): void {\n  const runtimeInfo = getRuntimeSupportInfo(minimumNodeMajor, environment);\n\n  if (runtimeInfo.isSupported) {\n    return;\n  }\n\n  const detectedVersion = runtimeInfo.nodeVersion ?? 'unknown';\n\n  throw new Error(\n    `Unirend requires Node >= ${minimumNodeMajor} or Bun. Detected ${runtimeInfo.runtime} runtime with Node version ${detectedVersion}.`,\n  );\n}\n","import fs from 'fs/promises';\nimport path from 'path';\n\ninterface ManifestResult {\n  success: boolean;\n  manifest?: Record<string, unknown>;\n  error?: string;\n}\n\ninterface ServerEntryResult {\n  success: boolean;\n  entryPath?: string;\n  error?: string;\n}\n\ninterface HTMLFileResult {\n  exists: boolean;\n  content?: string;\n  error?: string;\n}\n\ninterface JSONFileResult {\n  exists: boolean;\n  data?: Record<string, unknown>;\n  error?: string;\n}\n\ninterface WriteResult {\n  success: boolean;\n  error?: string;\n}\n\n/**\n * Checks for and loads the Vite manifest file\n * @param buildDir Directory containing built assets\n * @param isSSR Whether to load SSR manifest (default: false for regular manifest)\n * @returns Result object with success status and manifest data or error\n */\n\nexport async function checkAndLoadManifest(\n  buildDir: string,\n  isSSR: boolean = false,\n): Promise<ManifestResult> {\n  const manifestFile = isSSR ? 'ssr-manifest.json' : 'manifest.json';\n  const manifestPath = path.resolve(buildDir, `.vite/${manifestFile}`);\n\n  try {\n    const manifestContent = await fs.readFile(manifestPath, 'utf-8');\n    const manifest = JSON.parse(manifestContent) as Record<string, unknown>;\n\n    return {\n      success: true,\n      manifest,\n    };\n  } catch (error: unknown) {\n    const errorMessage = error instanceof Error ? error.message : String(error);\n\n    return {\n      success: false,\n      error: `Failed to load ${isSSR ? 'SSR ' : ''}manifest from ${manifestPath}: ${errorMessage}`,\n    };\n  }\n}\n\n/**\n * Finds the built server entry file from an already-loaded manifest\n * @param manifest The already-loaded server manifest\n * @param serverBuildDir Directory containing server build assets\n * @param serverEntry Name of the server entry file to look for (without extension)\n * @returns Result object with success status and entry path or error\n */\nexport function getServerEntryFromManifest(\n  manifest: Record<string, unknown>,\n  serverBuildDir: string,\n  serverEntry: string = 'EntrySSR',\n): ServerEntryResult {\n  // Find the entry in the manifest\n  for (const [key, value] of Object.entries(manifest)) {\n    if (\n      key.includes(serverEntry) &&\n      typeof value === 'object' &&\n      value !== null &&\n      'file' in value\n    ) {\n      const fileName = (value as { file: string }).file;\n      const entryPath = path.resolve(serverBuildDir, fileName);\n      return {\n        success: true,\n        entryPath,\n      };\n    }\n  }\n\n  // Entry not found in manifest\n  return {\n    success: false,\n    error: `Server entry '${serverEntry}' not found in server manifest`,\n  };\n}\n\n/**\n * Reads an HTML file and returns its contents as a string\n * @param filePath Path to the HTML file\n * @returns Result object with existence status and file content or error\n */\nexport async function readHTMLFile(filePath: string): Promise<HTMLFileResult> {\n  try {\n    const content = await fs.readFile(filePath, 'utf-8');\n    return {\n      exists: true,\n      content,\n    };\n  } catch (error: unknown) {\n    // Check if it's a file not found error\n    if (\n      error &&\n      typeof error === 'object' &&\n      'code' in error &&\n      error.code === 'ENOENT'\n    ) {\n      return {\n        exists: false,\n      };\n    }\n\n    // File exists but couldn't be read\n    const errorMessage = error instanceof Error ? error.message : String(error);\n\n    return {\n      exists: true,\n      error: `Failed to read HTML file ${filePath}: ${errorMessage}`,\n    };\n  }\n}\n\n/**\n * Reads a JSON file and returns its parsed contents\n * @param filePath Path to the JSON file\n * @returns Result object with existence status and parsed JSON data or error\n */\nexport async function readJSONFile(filePath: string): Promise<JSONFileResult> {\n  try {\n    const content = await fs.readFile(filePath, 'utf-8');\n    const data = JSON.parse(content) as Record<string, unknown>;\n\n    return {\n      exists: true,\n      data,\n    };\n  } catch (error: unknown) {\n    // Check if it's a file not found error\n    if (\n      error &&\n      typeof error === 'object' &&\n      'code' in error &&\n      error.code === 'ENOENT'\n    ) {\n      return {\n        exists: false,\n      };\n    }\n\n    // File exists but couldn't be read or parsed\n    const errorMessage = error instanceof Error ? error.message : String(error);\n\n    return {\n      exists: true,\n      error: `Failed to read or parse JSON file ${filePath}: ${errorMessage}`,\n    };\n  }\n}\n\n/**\n * Writes data to a JSON file with human-readable formatting\n * @param filePath Path to the JSON file to write\n * @param data Data to write to the file\n * @returns Result object with success status and optional error\n */\nexport async function writeJSONFile(\n  filePath: string,\n  data: Record<string, unknown>,\n): Promise<WriteResult> {\n  try {\n    // Format JSON with 2-space indentation for human readability\n    const jsonContent = JSON.stringify(data, null, 2);\n    await fs.writeFile(filePath, jsonContent, 'utf-8');\n\n    return {\n      success: true,\n    };\n  } catch (error: unknown) {\n    const errorMessage = error instanceof Error ? error.message : String(error);\n\n    return {\n      success: false,\n      error: `Failed to write JSON file ${filePath}: ${errorMessage}`,\n    };\n  }\n}\n\n/**\n * Writes HTML content to a file\n * @param filePath Path to the HTML file to write\n * @param content HTML content to write to the file\n * @returns Result object with success status and optional error\n */\nexport async function writeHTMLFile(\n  filePath: string,\n  content: string,\n): Promise<WriteResult> {\n  try {\n    await fs.writeFile(filePath, content, 'utf-8');\n\n    return {\n      success: true,\n    };\n  } catch (error: unknown) {\n    const errorMessage = error instanceof Error ? error.message : String(error);\n\n    return {\n      success: false,\n      error: `Failed to write HTML file ${filePath}: ${errorMessage}`,\n    };\n  }\n}\n\n/**\n * Validates that all required development paths exist\n * @param paths SSRDevPaths object containing serverEntry, template, and viteConfig paths\n * @returns Result object with success status and error details for any missing files\n */\nexport async function validateDevPaths(paths: {\n  serverEntry: string;\n  template: string;\n  viteConfig: string;\n}): Promise<{ success: boolean; errors: string[] }> {\n  const errors: string[] = [];\n\n  // Check server entry file\n  try {\n    await fs.access(paths.serverEntry);\n  } catch {\n    errors.push(`Server entry file not found: ${paths.serverEntry}`);\n  }\n\n  // Check template file\n  try {\n    await fs.access(paths.template);\n  } catch {\n    errors.push(`Template file not found: ${paths.template}`);\n  }\n\n  // Check vite config file\n  try {\n    await fs.access(paths.viteConfig);\n  } catch {\n    errors.push(`Vite config file not found: ${paths.viteConfig}`);\n  }\n\n  return {\n    success: errors.length === 0,\n    errors,\n  };\n}\n","import type { RenderType } from '../../types';\n\n/*\n * NOTE: This file uses @ts-expect-error on certain node type checks.\n * The underlying HTML parser returns node types like 'root' and 'directive',\n * which are not present in the outdated TypeScript types. These errors are\n * expected and safe to ignore, as the code works at runtime.\n */\n\n// Define a lightweight type for directive nodes from the parser\ntype DirectiveElement = { type: 'directive'; data: string };\n\n// Development comment that should be preserved\nconst DEVELOPMENT_COMMENT =\n  'React hydration relies on data attributes. Do not remove them.';\n\nfunction formatNode(\n  el: cheerio.Element,\n  level = 0,\n  isInRoot = false,\n  containerID = 'root',\n): string {\n  const indent = isInRoot ? '' : '  '.repeat(level);\n\n  // @ts-expect-error: 'root' is a valid node type at runtime, but not in the types\n  if (el.type === 'root') {\n    // @ts-expect-error: 'children' exists on root node at runtime\n    const children = (el.children || []) as cheerio.Element[];\n\n    return children\n      .map((child: cheerio.Element) =>\n        formatNode(child, level, false, containerID),\n      )\n      .filter(Boolean)\n      .join('\\n');\n  }\n\n  // @ts-expect-error: 'directive' is a valid node type at runtime, but not in the types\n  if (el.type === 'directive') {\n    const dir = el as unknown as DirectiveElement;\n    return `${indent}<${dir.data}>`;\n  }\n\n  // Comment nodes\n  if (el.type === 'comment') {\n    return `${indent}<!--${el.data}-->`;\n  }\n\n  // Text nodes\n  if (el.type === 'text') {\n    const text = el.data?.trim() ?? '';\n    if (!text) {\n      return '';\n    }\n\n    return `${indent}${text}`;\n  }\n\n  // Tag elements\n  const tag = el;\n  const tagName = tag.name;\n  const attrs = Object.entries(tag.attribs || {})\n    .map(([key, val]) => (val === '' ? key : `${key}=\"${val}\"`))\n    .join(' ');\n  const openTag = attrs ? `<${tagName} ${attrs}>` : `<${tagName}>`;\n\n  // Special handling for container element to prevent whitespace nodes\n  const isRoot =\n    tagName === 'div' &&\n    'id' in tag.attribs &&\n    tag.attribs['id'] === containerID;\n\n  const selfClosingTags = new Set([\n    'area',\n    'base',\n    'br',\n    'col',\n    'embed',\n    'hr',\n    'img',\n    'input',\n    'link',\n    'meta',\n    'param',\n    'source',\n    'track',\n    'wbr',\n  ]);\n\n  if (tag.children && tag.children.length > 0) {\n    // Different handling for root element - keep content on a single line for hydration\n    if (isRoot) {\n      let result = `${indent}${openTag}`;\n\n      for (const child of tag.children) {\n        const childStr = formatNode(child, 0, true, containerID);\n        if (childStr) {\n          result += childStr;\n        }\n      }\n\n      result += `</${tagName}>`;\n      return result;\n    } else {\n      // Standard handling. If we're inside the #root element (isInRoot), we want to keep\n      // everything on a single line to avoid introducing whitespace nodes that would\n      // break React hydration. Therefore, we skip inserting newlines in that case.\n\n      const isInline = isInRoot;\n      let result = `${indent}${openTag}`;\n\n      for (const child of tag.children) {\n        const childStr = formatNode(\n          child,\n          isInline ? 0 : level + 1,\n          isInRoot,\n          containerID,\n        );\n\n        if (childStr) {\n          result += isInline ? childStr : `\\n${childStr}`;\n        }\n      }\n\n      result += isInline ? `</${tagName}>` : `\\n${indent}</${tagName}>`;\n      return result;\n    }\n  }\n\n  // Self-closing tags\n  if (selfClosingTags.has(tagName)) {\n    return `${indent}<${tagName}${attrs ? ` ${attrs}` : ''}/>`;\n  }\n\n  // Empty standard tag\n  return `${indent}${openTag}</${tagName}>`;\n}\n\nexport function prettifyHTML(\n  $: cheerio.Root | cheerio.CheerioAPI,\n  containerID = 'root',\n): string {\n  let html = '';\n\n  for (const el of $.root().toArray()) {\n    html += formatNode(el, 0, false, containerID) + '\\n';\n  }\n\n  return html;\n}\n\nexport type ProcessTemplateResult =\n  | { success: true; html: string }\n  | { success: false; error: string };\n\nexport async function processTemplate(\n  html: string,\n  mode: RenderType,\n  isDevelopment: boolean,\n  isDevServer: boolean,\n  containerID = 'root',\n): Promise<ProcessTemplateResult> {\n  try {\n    // isDevelopment = runtime behavior (dev comment injection)\n    // isDevServer  = asset serving strategy (CDN rewriting skipped for Vite dev server)\n\n    // Dynamic import to prevent bundling in client builds\n    const cheerio = await import('cheerio');\n    const $ = cheerio.load(html);\n\n    // Remove title tags from head\n    $('head title').remove();\n\n    if (isDevelopment) {\n      $('body').prepend(`<!-- ${DEVELOPMENT_COMMENT} -->\\n`);\n    }\n\n    // Remove meta tags except apple-mobile-web-app-title\n    $('meta[name]').each((_, el) => {\n      const name = $(el).attr('name');\n      if (name !== 'apple-mobile-web-app-title') {\n        $(el).remove();\n      }\n    });\n\n    // Replace absolute asset URLs with CDN injection placeholder (production builds only)\n    // In dev server mode, Vite needs the original URLs to serve files from its dev server\n    // This allows runtime CDN URL override per request in production\n    // The placeholder will be replaced in injectContent() for SSR with:\n    // 1. request.CDNBaseURL (if set), or\n    // 2. appConfig.CDNBaseURL (if set), or\n    // 3. empty string (preserves original /assets/... paths)\n    if (!isDevServer) {\n      $('script[src]').each((_, el) => {\n        const src = $(el).attr('src');\n        if (src && src.startsWith('/')) {\n          $(el).attr('src', `__CDN__INJECTION__POINT__${src}`);\n        }\n      });\n\n      $('link[href]').each((_, el) => {\n        const href = $(el).attr('href');\n        if (href && href.startsWith('/')) {\n          $(el).attr('href', `__CDN__INJECTION__POINT__${href}`);\n        }\n      });\n    }\n\n    // Collect head and body scripts separately so we can control insertion order.\n    // Head scripts (e.g. inline theme flash scripts) are re-inserted after the context\n    // placeholder, guaranteeing they can always read __FRONTEND_REQUEST_CONTEXT__.\n    const headScripts: string[] = [];\n\n    $('head script').each((_, el) => {\n      headScripts.push($.html(el));\n    });\n\n    const bodyScripts: string[] = [];\n\n    $('body script').each((_, el) => {\n      bodyScripts.push($.html(el));\n    });\n\n    // Remove all scripts from their original locations\n    $('script').remove();\n\n    // Track required markers during comment processing\n    let hasHeadMarker = false;\n    let hasOutletMarker = false;\n\n    // Remove comments that don't start with ss- or the development comment\n    // Also normalize ss- comments by trimming their content\n    // AND validate that required markers are present\n    $('*:not(script):not(style)')\n      .contents()\n      .each((_index: number, node: cheerio.Element) => {\n        if (node.type === 'comment') {\n          const commentData = node.data?.trim() || '';\n          const shouldKeep =\n            commentData.startsWith('ss-') ||\n            commentData === DEVELOPMENT_COMMENT;\n\n          if (shouldKeep) {\n            // Check for required markers (after normalization)\n            if (commentData === 'ss-head') {\n              hasHeadMarker = true;\n            } else if (commentData === 'ss-outlet') {\n              hasOutletMarker = true;\n            }\n\n            // Normalize ss- comments by trimming their content\n            if (commentData.startsWith('ss-') && node.data !== commentData) {\n              node.data = commentData;\n            }\n          } else {\n            $(node).remove();\n          }\n        }\n      });\n\n    // Validate required markers after comment processing\n    if (!hasHeadMarker || !hasOutletMarker) {\n      const missingMarkers: string[] = [];\n\n      if (!hasHeadMarker) {\n        missingMarkers.push('<!--ss-head-->');\n      }\n\n      if (!hasOutletMarker) {\n        missingMarkers.push('<!--ss-outlet-->');\n      }\n\n      const contentDescription =\n        mode === 'ssg'\n          ? 'generated content will be injected'\n          : 'server-rendered content will be injected';\n\n      return {\n        success: false,\n        error: `Missing required comment markers in HTML template: ${missingMarkers.join(', ')}. These markers indicate where ${contentDescription}.`,\n      };\n    }\n\n    // Append context placeholder first, then user head scripts — so context globals\n    // are always available when user inline scripts run.\n    // Final order in <head>: ss-head content → static tags → context globals → user inline scripts.\n    // Must be added AFTER comment cleanup so the placeholder isn't stripped by the ss- filter.\n    $('head').append('\\n<!--context-scripts-injection-point-->');\n\n    if (headScripts.length > 0) {\n      $('head').append('\\n' + headScripts.join('\\n'));\n    }\n\n    // Find the container element and append body scripts AFTER it, not inside it\n    const rootElement = $(`#${containerID}`);\n\n    if (rootElement.length > 0) {\n      if (bodyScripts.length > 0) {\n        rootElement.after(bodyScripts.join('\\n'));\n      }\n    } else {\n      // Fallback: If no #root element is found, append to the end of body\n      if (bodyScripts.length > 0) {\n        $('body').append(bodyScripts.join('\\n'));\n      }\n    }\n\n    return {\n      success: true,\n      html: prettifyHTML($, containerID),\n    };\n  } catch (error) {\n    return {\n      success: false,\n      error: `Failed to process HTML template: ${error instanceof Error ? error.message : String(error)}`,\n    };\n  }\n}\n","export const TAB_SPACES = ' '.repeat(4);\n\n/**\n * Default API endpoint prefix (e.g., \"/api\")\n * Used when apiEndpoints.apiEndpointPrefix is not configured\n */\nexport const DEFAULT_API_PREFIX = '/api';\n\n/**\n * Default page data endpoint name (e.g., \"page_data\")\n * Used when apiEndpoints.pageDataEndpoint is not configured\n */\nexport const DEFAULT_PAGE_DATA_ENDPOINT = 'page_data';\n","import { TAB_SPACES } from '../consts';\nimport { getDevMode } from 'lifecycleion/dev-mode';\n\n// Prettify all head tags: each tag (<title>, <meta>, <link>, etc.) on its own line, indented\nexport function prettifyHeadTags(head: string, indent = TAB_SPACES): string {\n  // Use a non-capturing group so tag names are not included in the split output\n  return head\n    .split(/(?=<(?:title|meta|link|script|style|base|noscript|preload)\\b)/g)\n    .filter(Boolean)\n    .map((line) => indent + line.trim())\n    .join('\\n')\n    .trim();\n}\n\n// Utility to inject content, preserving React attributes\nexport async function injectContent(\n  template: string,\n  headContent: string,\n  bodyContent: string,\n  context?: {\n    app?: Record<string, unknown>;\n    request?: Record<string, unknown>;\n  },\n  CDNBaseURL?: string,\n  domainInfo?: { hostname: string; rootDomain: string } | null,\n): Promise<string> {\n  // Prettify all head tags with consistent indentation\n  const compactedHead = prettifyHeadTags(headContent);\n\n  // Use cheerio to find React Router's hydration script in the rendered body content.\n  // StaticRouterProvider (server) renders window.__staticRouterHydrationData as a React child,\n  // but RouterProvider (client) renders no such script — causing a hydration mismatch when any\n  // HTML wrapper sits between the framework root and the router. Moving it to <head> eliminates\n  // the mismatch; the client reads the global before React hydrates so location doesn't matter.\n  //\n  // We use cheerio only for detection/offsets, then splice the original bodyContent.\n  // This avoids any risk of cheerio re-serializing React's hydration markers/attributes.\n\n  const cheerio = await import('cheerio');\n  // Cheerio forwards this parse5 option at runtime, but its exported TypeScript\n  // type does not expose it. The option gives us original source offsets.\n  const parseOptions = {\n    sourceCodeLocationInfo: true,\n  } as unknown as Parameters<typeof cheerio.load>[1];\n  const $body = cheerio.load(bodyContent, parseOptions);\n  const routerHydrationScripts: string[] = [];\n  const removalRanges: Array<{ start: number; end: number }> = [];\n\n  $body('script').each((_, el) => {\n    if (($body(el).html() ?? '').includes('__staticRouterHydrationData')) {\n      const location = (\n        el as {\n          sourceCodeLocation?: { startOffset: number; endOffset: number };\n        }\n      ).sourceCodeLocation;\n\n      if (!location) {\n        return;\n      }\n\n      // Keep the script exactly as React Router emitted it. Do not use\n      // $body.html(el), because serializer output is not hydration-safe.\n      routerHydrationScripts.push(\n        bodyContent.slice(location.startOffset, location.endOffset),\n      );\n\n      removalRanges.push({\n        start: location.startOffset,\n        end: location.endOffset,\n      });\n    }\n  });\n\n  let cleanBodyContent = bodyContent;\n\n  // Remove from the end first so earlier offsets remain valid.\n  for (const range of [...removalRanges].sort((a, b) => b.start - a.start)) {\n    cleanBodyContent =\n      cleanBodyContent.slice(0, range.start) +\n      cleanBodyContent.slice(range.end);\n  }\n\n  // Start with head and body replacement\n  // The <!--ss-outlet--> marker should be directly replaced with the content\n  // without any additional or changed comments/whitespace that could cause hydration issues\n  let result = template\n    .replace('<!--ss-head-->', compactedHead)\n    .replace('<!--ss-outlet-->', cleanBodyContent);\n\n  // Build context scripts array\n  const contextScripts: string[] = [];\n\n  // Inject dev mode global so the client always matches the server\n  contextScripts.push(\n    `<script>globalThis.__lifecycleion_is_dev__=${String(getDevMode())};</script>`,\n  );\n\n  // Add __FRONTEND_REQUEST_CONTEXT__ if provided (even if empty object)\n  if (context?.request !== undefined) {\n    const safeContextJSON = JSON.stringify(context.request).replace(\n      /</g,\n      '\\\\u003c',\n    );\n\n    contextScripts.push(\n      `<script>window.__FRONTEND_REQUEST_CONTEXT__=${safeContextJSON};</script>`,\n    );\n  }\n\n  // Add __PUBLIC_APP_CONFIG__ if provided (even if empty object)\n  if (context?.app !== undefined) {\n    const safeConfigJSON = JSON.stringify(context.app).replace(/</g, '\\\\u003c');\n\n    contextScripts.push(\n      `<script>window.__PUBLIC_APP_CONFIG__=${safeConfigJSON};</script>`,\n    );\n  }\n\n  // Normalize CDN base URL (strip trailing slash) so it's consistent everywhere\n  const normalizedCDN = CDNBaseURL\n    ? CDNBaseURL.endsWith('/')\n      ? CDNBaseURL.slice(0, -1)\n      : CDNBaseURL\n    : '';\n\n  // Always inject __CDN_BASE_URL__ — empty string when no CDN configured so client\n  // code can read it unconditionally without guarding against undefined\n  const safeCDNJSON = JSON.stringify(normalizedCDN).replace(/</g, '\\\\u003c');\n\n  contextScripts.push(\n    `<script>window.__CDN_BASE_URL__=${safeCDNJSON};</script>`,\n  );\n\n  // Inject __DOMAIN_INFO__ — null when hostname not known (SSG without hostname configured, or SPA)\n  // so client code can check for null rather than guarding against undefined\n  const safeDomainJSON = JSON.stringify(domainInfo ?? null).replace(\n    /</g,\n    '\\\\u003c',\n  );\n\n  contextScripts.push(\n    `<script>window.__DOMAIN_INFO__=${safeDomainJSON};</script>`,\n  );\n\n  // Router hydration data last — only needed once the client module runs, order relative\n  // to other head scripts doesn't matter since all head scripts run before any module script\n  for (const script of routerHydrationScripts) {\n    contextScripts.push(script);\n  }\n\n  // Replace the placeholder with all context scripts (or remove if none).\n  // Detect the placeholder's leading whitespace so injected scripts match indentation.\n  const indentMatch = result.match(\n    /^([ \\t]*)<!--context-scripts-injection-point-->/m,\n  );\n\n  const indent = indentMatch ? indentMatch[1] : '';\n  result = result.replace(\n    '<!--context-scripts-injection-point-->',\n    contextScripts.join('\\n' + indent),\n  );\n\n  // Replace CDN injection placeholder with actual CDN URL or empty string\n  // This allows runtime CDN URL override per request\n  if (normalizedCDN) {\n    result = result.replace(/__CDN__INJECTION__POINT__/g, normalizedCDN);\n  } else {\n    // No CDN URL provided - remove placeholder to preserve original /assets/... paths\n    result = result.replace(/__CDN__INJECTION__POINT__/g, '');\n  }\n\n  return result;\n}\n","import type {\n  RenderRequest,\n  RenderResult,\n  ServeSSRDevOptions,\n  ServeSSRProdOptions,\n  RegisterDevAppOptions,\n  RegisterProdAppOptions,\n  SSRDevPaths,\n  StaticContentRouterOptions,\n  SSRHelpers,\n  PluginMetadata,\n  APIResponseHelpersClass,\n  SSRInternalAppConfig,\n  SSRInternalAppConfigDev,\n  SSRInternalAppConfigProd,\n  AccessLogConfig,\n} from '../types';\nimport { AccessLogPlugin } from './access-log-plugin';\nimport { deepFreeze } from './utils';\nimport {\n  readHTMLFile,\n  checkAndLoadManifest,\n  getServerEntryFromManifest,\n  validateDevPaths,\n} from './fs-utils';\nimport { processTemplate } from './html-utils/format';\nimport { injectContent } from './html-utils/inject';\nimport path from 'path';\nimport type {\n  FastifyRequest,\n  FastifyReply,\n  FastifyServerOptions,\n} from 'fastify';\nimport {\n  createControlledInstance,\n  classifyRequest,\n  normalizeAPIPrefix,\n  normalizePageDataEndpoint,\n  normalizeCDNBaseURL,\n  computeDomainInfo,\n  createDefaultAPIErrorResponse,\n  createDefaultAPINotFoundResponse,\n  registerClosingResponseHook,\n  createControlledReply,\n  validateAndRegisterPlugin,\n  validateNoHandlersWhenAPIDisabled,\n  buildFastifyHTTPSOptions,\n  registerClientIPDecoration,\n} from './server-utils';\nimport { generateDefault500ErrorPage } from './error-page-utils';\nimport { StaticContentCache } from './static-content-cache';\nimport { staticContentHookHandler } from './static-content-hook';\nimport { BaseServer } from './base-server';\nimport { DataLoaderServerHandlerHelpers } from './data-loader-server-handler-helpers';\nimport { APIRoutesServerHelpers } from './api-routes-server-helpers';\nimport { WebSocketServerHelpers } from './web-socket-server-helpers';\nimport type { WebSocketHandlerConfig } from './web-socket-server-helpers';\nimport {\n  filterIncomingCookieHeader as applyCookiePolicyToCookieHeader,\n  filterSetCookieHeaderValues as applyCookiePolicyToSetCookie,\n} from './cookie-utils';\nimport { APIResponseHelpers } from '../../api-envelope';\nimport type { WebSocket, WebSocketServer } from 'ws';\nimport {\n  registerFileUploadValidationHooks,\n  registerMultipartPlugin,\n} from './file-upload-validation-helpers';\nimport { resolveFastifyLoggerConfig } from './logger-config-utils';\nimport { getDevMode } from 'lifecycleion/dev-mode';\nimport { registerResponseCompression } from './response-compression';\nimport {\n  registerResponseTimeHeader,\n  registerResponseTimeHijackPatch,\n} from './response-time-header';\n\ntype SSRServerConfigDev = {\n  mode: 'development';\n  paths: SSRDevPaths; // Contains serverEntry, template, and viteConfig paths\n  options: ServeSSRDevOptions;\n};\n\ntype SSRServerConfigProd = {\n  mode: 'production';\n  buildDir: string; // Directory containing built assets (HTML template, static files, manifest, etc.)\n  options: ServeSSRProdOptions;\n};\n\ntype SSRServerConfig = SSRServerConfigDev | SSRServerConfigProd;\n\n// Private per-request fields used to back the public active-app API.\n// Keeping these separate lets request.activeSSRApp stay read-only while\n// request.setActiveSSRApp() can validate and refresh app-derived values.\ntype ActiveSSRAppInternalState = {\n  appKey?: string;\n  lastAppDefaultCDNBaseURL?: string;\n};\n\ntype SSRRequestInternalState = FastifyRequest & {\n  activeSSRAppInternal?: ActiveSSRAppInternalState;\n};\n\n/**\n * Internal server class for handling SSR rendering\n * Not intended to be used directly by library consumers\n */\n\nexport class SSRServer extends BaseServer {\n  /** Pluggable helpers class reference for constructing API/Page envelopes */\n  public readonly APIResponseHelpersClass: APIResponseHelpersClass;\n\n  // config state\n  private serverMode: 'development' | 'production';\n  private readonly serverLabel: string;\n\n  // Multi-app storage\n  private apps: Map<string, SSRInternalAppConfig> = new Map();\n\n  // Shared server configuration (used across all apps)\n  private sharedOptions: ServeSSRDevOptions | ServeSSRProdOptions;\n  private _accessLog: AccessLogPlugin;\n\n  // Shared server resources (used across all apps)\n  private pageDataHandlers!: DataLoaderServerHandlerHelpers;\n  private apiRoutes!: APIRoutesServerHelpers;\n  private webSocketHelpers: WebSocketServerHelpers | null = null;\n  private registeredPlugins: PluginMetadata[] = [];\n\n  // Cookie forwarding policy (computed from options for quick checks)\n  private cookieAllowList?: Set<string>;\n  private cookieBlockList?: Set<string> | true;\n\n  // Normalized endpoint config (computed once at construction)\n  // false means API handling is disabled (matches config type)\n  private readonly normalizedAPIPrefix: string | false;\n  private readonly normalizedPageDataEndpoint: string;\n\n  /**\n   * Creates a new SSR server instance\n   *\n   * @param config Server configuration object\n   */\n  constructor(config: SSRServerConfig) {\n    super();\n\n    // Store server mode and shared options\n    this.serverMode = config.mode;\n    this.sharedOptions = config.options;\n    this.serverLabel = config.options.serverLabel ?? 'SSR';\n    this._accessLog = new AccessLogPlugin(\n      this.serverLabel,\n      config.options.accessLog,\n    );\n\n    // Convert single config to Map with '__default__' key\n    const defaultApp: SSRInternalAppConfig =\n      config.mode === 'development'\n        ? {\n            // Dev mode - has paths\n            paths: config.paths,\n            publicAppConfig: config.options.publicAppConfig,\n            clientFolderName: config.options.clientFolderName || 'client',\n            serverFolderName: config.options.serverFolderName || 'server',\n            containerID: config.options.containerID,\n            get500ErrorPage: config.options.get500ErrorPage,\n          }\n        : {\n            // Prod mode - has buildDir\n            buildDir: config.buildDir,\n            serverEntry: config.options.serverEntry,\n            template: config.options.template,\n            CDNBaseURL: config.options.CDNBaseURL,\n            staticContentRouter: config.options.staticContentRouter,\n            publicAppConfig: config.options.publicAppConfig,\n            clientFolderName: config.options.clientFolderName || 'client',\n            serverFolderName: config.options.serverFolderName || 'server',\n            containerID: config.options.containerID,\n            get500ErrorPage: config.options.get500ErrorPage,\n          };\n\n    this.apps.set('__default__', defaultApp);\n\n    // Set helpers class (custom or default)\n    this.APIResponseHelpersClass =\n      this.sharedOptions.APIResponseHelpersClass || APIResponseHelpers;\n\n    // Normalize API endpoint config once at construction\n    this.normalizedAPIPrefix = normalizeAPIPrefix(\n      config.options.apiEndpoints?.apiEndpointPrefix,\n    );\n\n    // Normalize page data endpoint once at construction\n    this.normalizedPageDataEndpoint = normalizePageDataEndpoint(\n      config.options.apiEndpoints?.pageDataEndpoint,\n    );\n\n    // Initialize helpers (available immediately for handler registration)\n    this.pageDataHandlers = new DataLoaderServerHandlerHelpers();\n    this.apiRoutes = new APIRoutesServerHelpers();\n\n    // Initialize WebSocket helpers if enabled\n    if (config.options.enableWebSockets) {\n      this.webSocketHelpers = new WebSocketServerHelpers(\n        this.APIResponseHelpersClass,\n        config.options.webSocketOptions,\n      );\n    }\n\n    // Initialize cookie forwarding policy\n    const allow = config.options.cookieForwarding?.allowCookieNames;\n    const block = config.options.cookieForwarding?.blockCookieNames;\n\n    this.cookieAllowList =\n      Array.isArray(allow) && allow.length > 0 ? new Set(allow) : undefined;\n    // Support block = true (block all)\n    this.cookieBlockList =\n      block === true\n        ? true\n        : Array.isArray(block) && block.length > 0\n          ? new Set(block)\n          : undefined;\n  }\n\n  /**\n   * Register an additional dev-mode SSR app\n   *\n   * Can only be called on dev servers (created via serveSSRDev).\n   * Apps must be registered BEFORE calling listen().\n   *\n   * Uses the same parameters as serveSSRDev for consistency - you can copy/paste\n   * configuration between the default app and additional apps.\n   *\n   * @param appKey - Unique identifier for this app (selected with request.setActiveSSRApp)\n   * @param paths - Dev-specific paths (same as serveSSRDev)\n   * @param options - Dev options (same as serveSSRDev)\n   *\n   * @example\n   * ```ts\n   * const mainPaths = {\n   *   serverEntry: './src/EntrySSR.tsx',\n   *   template: './index.html',\n   *   viteConfig: './vite.config.ts'\n   * };\n   * const server = serveSSRDev(mainPaths, { port: 3000 });\n   *\n   * // Same parameters as above - easy to copy/paste\n   * server.registerDevApp('marketing', {\n   *   serverEntry: './src/marketing/EntrySSR.tsx',\n   *   template: './src/marketing/index.html',\n   *   viteConfig: './vite.marketing.config.ts'\n   * }, {\n   *   publicAppConfig: { api_endpoint: 'http://localhost:3002' }\n   * });\n   *\n   * await server.listen(3000);\n   * ```\n   */\n  public registerDevApp(\n    appKey: string,\n    paths: SSRDevPaths,\n    options?: RegisterDevAppOptions,\n  ): void {\n    if (!appKey || typeof appKey !== 'string') {\n      throw new Error('App key must be a non-empty string');\n    }\n\n    const trimmedAppKey = appKey.trim();\n\n    if (this._isListening) {\n      throw new Error(\n        'Cannot register apps after server has started listening. Register all apps before calling listen().',\n      );\n    }\n\n    this.validateAppKey(trimmedAppKey);\n\n    if (this.serverMode !== 'development') {\n      throw new Error(\n        `Cannot register dev app \"${trimmedAppKey}\" on prod server. Use registerProdApp() instead.`,\n      );\n    }\n\n    const opts = options || {};\n    const appConfig: SSRInternalAppConfigDev = {\n      paths,\n      publicAppConfig: opts.publicAppConfig,\n      clientFolderName: opts.clientFolderName || 'client',\n      serverFolderName: opts.serverFolderName || 'server',\n      containerID: opts.containerID,\n      get500ErrorPage: opts.get500ErrorPage,\n    };\n\n    this.apps.set(trimmedAppKey, appConfig);\n  }\n\n  /**\n   * Register an additional prod-mode SSR app\n   *\n   * Can only be called on prod servers (created via serveSSRProd).\n   * Apps must be registered BEFORE calling listen().\n   *\n   * Uses the same parameters as serveSSRProd for consistency - you can copy/paste\n   * configuration between the default app and additional apps.\n   *\n   * @param appKey - Unique identifier for this app (selected with request.setActiveSSRApp)\n   * @param buildDir - Build directory path (same as serveSSRProd)\n   * @param options - Prod options (same as serveSSRProd)\n   *\n   * @example\n   * ```ts\n   * const server = serveSSRProd('./build-main', { port: 3000 });\n   *\n   * // Same parameters as above - easy to copy/paste\n   * server.registerProdApp('marketing', './build-marketing', {\n   *   publicAppConfig: { api_endpoint: 'https://marketing.example.com' }\n   * });\n   *\n   * await server.listen(3000);\n   * ```\n   */\n  public registerProdApp(\n    appKey: string,\n    buildDir: string,\n    options?: RegisterProdAppOptions,\n  ): void {\n    if (!appKey || typeof appKey !== 'string') {\n      throw new Error('App key must be a non-empty string');\n    }\n\n    const trimmedAppKey = appKey.trim();\n\n    if (this._isListening) {\n      throw new Error(\n        'Cannot register apps after server has started listening. Register all apps before calling listen().',\n      );\n    }\n\n    this.validateAppKey(trimmedAppKey);\n\n    if (this.serverMode !== 'production') {\n      throw new Error(\n        `Cannot register prod app \"${trimmedAppKey}\" on dev server. Use registerDevApp() instead.`,\n      );\n    }\n\n    const opts = options || {};\n    const appConfig: SSRInternalAppConfigProd = {\n      buildDir,\n      serverEntry: opts.serverEntry,\n      template: opts.template,\n      CDNBaseURL: opts.CDNBaseURL,\n      staticContentRouter: opts.staticContentRouter,\n      publicAppConfig: opts.publicAppConfig,\n      clientFolderName: opts.clientFolderName || 'client',\n      serverFolderName: opts.serverFolderName || 'server',\n      containerID: opts.containerID,\n      get500ErrorPage: opts.get500ErrorPage,\n    };\n\n    this.apps.set(trimmedAppKey, appConfig);\n  }\n\n  /**\n   * Start the SSR server listening on the specified port and host\n   *\n   * @param port Port number to listen on (defaults to 3000)\n   * @param host Host to bind to (defaults to localhost)\n   * @returns Promise that resolves when server is listening\n   */\n  public async listen(\n    port: number = 3000,\n    host: string = 'localhost',\n  ): Promise<void> {\n    if (this._isListening) {\n      throw new Error(\n        'SSRServer is already listening. Call stop() first before listening again.',\n      );\n    }\n\n    if (this._isStarting) {\n      throw new Error(\n        'SSRServer is already starting. Please wait for the current startup to complete.',\n      );\n    }\n\n    this._isStarting = true;\n    this._isStopping = false;\n\n    // Clear plugin tracking state on startup (handles restart scenarios)\n    this.registeredPlugins = [];\n\n    // Clean up any existing instances from previous failed startups\n    if (this.fastifyInstance) {\n      try {\n        await this.fastifyInstance.close();\n      } catch {\n        // Ignore cleanup errors for stale instances\n      }\n\n      this.fastifyInstance = null;\n    }\n\n    // Clean up Vite dev servers and clear caches from all apps\n    // This ensures clean state even if previous stop() failed partway through\n    for (const [_, appConfig] of this.apps) {\n      if ('viteDevServer' in appConfig && appConfig.viteDevServer) {\n        try {\n          await appConfig.viteDevServer.close();\n        } catch {\n          // Ignore cleanup errors for stale instances\n        }\n\n        appConfig.viteDevServer = undefined;\n      }\n\n      // Clear cached templates and render functions (defensive programming)\n      if ('cachedHTMLTemplate' in appConfig) {\n        appConfig.cachedHTMLTemplate = undefined;\n      }\n\n      if ('cachedRenderFunction' in appConfig) {\n        appConfig.cachedRenderFunction = undefined;\n      }\n    }\n\n    try {\n      // Validate development paths exist before proceeding for ALL dev apps\n      if (this.serverMode === 'development') {\n        for (const [appKey, appConfig] of this.apps) {\n          if ('paths' in appConfig) {\n            const pathValidation = await validateDevPaths(appConfig.paths);\n            if (!pathValidation.success) {\n              throw new Error(\n                `Development paths validation failed for app \"${appKey}\":\\n${pathValidation.errors.join('\\n')}`,\n              );\n            }\n          }\n        }\n      }\n\n      // Load HTML templates and render functions for all prod apps\n      // (dev will read/load fresh per request for HMR support)\n      if (this.serverMode === 'production') {\n        for (const [appKey, appConfig] of this.apps) {\n          // In production mode, all apps should have buildDir (enforced by TypeScript)\n          if (!('buildDir' in appConfig)) {\n            throw new Error(\n              `Production app \"${appKey}\" is missing buildDir. This should not happen.`,\n            );\n          }\n\n          // Load and cache HTML template\n          try {\n            const templateResult = await this.loadHTMLTemplate(appConfig);\n            // CDN rewriting is now handled inside processTemplate() during loadHTMLTemplate()\n            appConfig.cachedHTMLTemplate = templateResult.content;\n          } catch (loadError) {\n            throw new Error(\n              `Failed to load HTML template for app \"${appKey}\": ${loadError instanceof Error ? loadError.message : String(loadError)}`,\n            );\n          }\n\n          // Load and cache render function (fail fast at startup instead of on first request)\n          try {\n            await this.loadProductionRenderFunction(appConfig);\n          } catch (loadError) {\n            throw new Error(\n              `Failed to load render function for app \"${appKey}\": ${loadError instanceof Error ? loadError.message : String(loadError)}`,\n            );\n          }\n        }\n      }\n\n      // Dynamic import to prevent bundling in client builds\n      const { default: fastify } = await import('fastify');\n      const { default: qs } = await import('qs');\n\n      // Build Fastify options from curated subset\n      const fastifyOptions: FastifyServerOptions & { https?: unknown } = {};\n\n      Object.assign(\n        fastifyOptions,\n        resolveFastifyLoggerConfig({\n          logging: this.sharedOptions.logging,\n          fastifyOptions: this.sharedOptions.fastifyOptions,\n        }),\n      );\n\n      if (this.sharedOptions.fastifyOptions) {\n        const {\n          trustProxy,\n          bodyLimit,\n          keepAliveTimeout,\n          requestTimeout,\n          connectionTimeout,\n        } = this.sharedOptions.fastifyOptions;\n\n        if (trustProxy !== undefined) {\n          fastifyOptions.trustProxy = trustProxy;\n        }\n\n        if (bodyLimit !== undefined) {\n          fastifyOptions.bodyLimit = bodyLimit;\n        }\n\n        if (keepAliveTimeout !== undefined) {\n          fastifyOptions.keepAliveTimeout = keepAliveTimeout;\n        }\n\n        if (requestTimeout !== undefined) {\n          fastifyOptions.requestTimeout = requestTimeout;\n        }\n\n        if (connectionTimeout !== undefined) {\n          fastifyOptions.connectionTimeout = connectionTimeout;\n        }\n      }\n\n      // Add HTTPS configuration if provided\n      if (this.sharedOptions.https) {\n        fastifyOptions.https = buildFastifyHTTPSOptions(\n          this.sharedOptions.https,\n        );\n      }\n\n      // Framework-owned Fastify behavior. These are intentionally not exposed\n      // through fastifyOptions because Unirend depends on them for consistent\n      // routing and shutdown responses across server types.\n      fastifyOptions.return503OnClosing = false;\n\n      fastifyOptions.routerOptions = {\n        // Ignore trailing slashes for flexible routing (matches Express behavior)\n        ignoreTrailingSlash: true,\n        // Use qs for richer query string parsing (nested objects, arrays, encoded brackets)\n        // querystringParser is a router option in Fastify v5+\n        querystringParser: (str) => qs.parse(str),\n      };\n\n      // Create Fastify instance with merged options (user options + defaults + HTTPS + trailing slash)\n      this.fastifyInstance = fastify(fastifyOptions);\n\n      // Register formbody to support application/x-www-form-urlencoded bodies\n      await this.fastifyInstance.register(\n        (await import('@fastify/formbody')).default,\n      );\n\n      // Register WebSocket plugin if enabled\n      if (this.webSocketHelpers) {\n        await this.webSocketHelpers.registerWebSocketPlugin(\n          this.fastifyInstance,\n        );\n      }\n\n      // Decorate requests with environment info\n      // The default here is just a shape hint for Fastify; the live value is set per-request in the onRequest hook below.\n      this.fastifyInstance.decorateRequest('isDevelopment', false);\n      this.fastifyInstance.decorateRequest('serverLabel', this.serverLabel);\n\n      // Decorate active app routing (defaults to '__default__') and app-derived request values.\n      // activeSSRApp is intentionally read-only so plugins cannot change the\n      // selected app without also refreshing publicAppConfig/CDNBaseURL.\n      this.fastifyInstance.decorateRequest('activeSSRAppInternal', undefined);\n      this.fastifyInstance.decorateRequest('activeSSRApp', {\n        getter(this: FastifyRequest) {\n          return (\n            (this as SSRRequestInternalState).activeSSRAppInternal?.appKey ||\n            '__default__'\n          );\n        },\n        setter() {\n          throw new Error(\n            'request.activeSSRApp is read-only. Use request.setActiveSSRApp(appKey) to choose an SSR app.',\n          );\n        },\n      });\n\n      this.fastifyInstance.decorateRequest('setActiveSSRApp', () => {\n        throw new Error(\n          'request.setActiveSSRApp() is not initialized for this request.',\n        );\n      });\n      this.fastifyInstance.decorateRequest('publicAppConfig', undefined);\n      this.fastifyInstance.decorateRequest('CDNBaseURL', undefined);\n\n      // Decorate requests with APIResponseHelpersClass for file upload helpers\n      this.fastifyInstance.decorateRequest(\n        'APIResponseHelpersClass',\n        this.APIResponseHelpersClass,\n      );\n\n      // Initialize request context and set live dev-mode flag for all requests\n      this.fastifyInstance.addHook('onRequest', async (request, _reply) => {\n        // Set live dev-mode flag (read fresh each request so overrideDevMode() takes effect)\n        (\n          request as FastifyRequest & {\n            isDevelopment?: boolean;\n          }\n        ).isDevelopment = getDevMode();\n\n        // Capture request start time for envelope timestamp\n        (request as { receivedAt?: number }).receivedAt = Date.now();\n\n        // Initialize per-request context object (always present, never undefined)\n        request.requestContext = {};\n\n        // Compute domain info once per request so plugins/hooks can read rootDomain\n        // (e.g. to set domain=.rootDomain on cookies) without re-parsing the hostname.\n        // computeDomainInfo handles empty/missing hostnames gracefully:\n        // parseHostHeader('') → { domain: '', port: '' }, rootDomain falls back to ''.\n        request.domainInfo = computeDomainInfo(request.hostname);\n\n        // Default false — set true by the static content handler before hijacking,\n        // whether that's the built-in /assets serving or a staticContent plugin\n        // registered by the app. Lets onResponse hooks detect static asset requests.\n        (request as { isStaticAsset?: boolean }).isStaticAsset = false;\n\n        const activeSSRAppInternal: ActiveSSRAppInternalState = {};\n        request.setDecorator<ActiveSSRAppInternalState>(\n          'activeSSRAppInternal',\n          activeSSRAppInternal,\n        );\n\n        // Use one path for every app selection, including the default app\n        // below. That keeps validation, config cloning, and CDN state updates\n        // identical for the initial request and later middleware changes.\n        const applyActiveApp = (appKey: string): void => {\n          const trimmedAppKey = appKey.trim();\n\n          if (!trimmedAppKey) {\n            throw new Error('Active app key must be a non-empty string');\n          }\n\n          const activeAppConfig = this.apps.get(trimmedAppKey);\n\n          if (!activeAppConfig) {\n            const availableApps = Array.from(this.apps.keys()).join(', ');\n\n            throw new Error(\n              `Active app \"${trimmedAppKey}\" not found. Available apps: ${availableApps}`,\n            );\n          }\n\n          if (trimmedAppKey !== activeSSRAppInternal.appKey) {\n            // Keep one immutable public config snapshot per active app choice.\n            // Re-selecting the same app leaves the current request snapshot alone.\n            request.publicAppConfig = activeAppConfig.publicAppConfig\n              ? deepFreeze(structuredClone(activeAppConfig.publicAppConfig))\n              : undefined;\n          }\n\n          // App config provides the default CDN URL, but SSR middleware may set\n          // request.CDNBaseURL for request-specific routing such as regional\n          // CDNs. Replace only a missing value or the last value we applied\n          // from app config. If middleware changed request.CDNBaseURL, it will\n          // no longer match lastAppDefaultCDNBaseURL, so preserve it.\n          if (\n            request.CDNBaseURL === undefined ||\n            request.CDNBaseURL === activeSSRAppInternal.lastAppDefaultCDNBaseURL\n          ) {\n            const appCDNBaseURL =\n              'CDNBaseURL' in activeAppConfig\n                ? activeAppConfig.CDNBaseURL\n                : undefined;\n\n            request.CDNBaseURL = appCDNBaseURL;\n            activeSSRAppInternal.lastAppDefaultCDNBaseURL = appCDNBaseURL;\n          }\n\n          // Store this last so a failed refresh cannot leave the request\n          // pointing at an app whose derived values were not applied.\n          activeSSRAppInternal.appKey = trimmedAppKey;\n        };\n\n        // Expose the public setter after its per-request closure exists. User\n        // onRequest hooks registered later can call this immediately.\n        request.setDecorator<(appKey: string) => void>(\n          'setActiveSSRApp',\n          (appKey: string): void => {\n            applyActiveApp(appKey);\n          },\n        );\n\n        // Seed the request with the default app through the same code path that\n        // middleware uses for multi-app routing.\n        applyActiveApp('__default__');\n      });\n\n      // Set request.clientIP once per request — available to plugins, hooks, and access logs.\n      registerClientIPDecoration(\n        this.fastifyInstance,\n        this.sharedOptions.getClientIP,\n      );\n\n      // Register access logging hooks. Config is read per request so\n      // updateAccessLoggingConfig() changes take effect without a restart.\n      this._accessLog.register(this.fastifyInstance);\n\n      registerClosingResponseHook(\n        this.fastifyInstance,\n        () => this._isStopping,\n        {\n          handler: this.sharedOptions.closingHandler,\n          // SSR function form is web-first. Split form can still customize\n          // API and page-data requests when API handling is enabled.\n          functionHandlerType: 'web',\n          serverLabel: this.serverLabel,\n          HelpersClass: this.APIResponseHelpersClass,\n          apiPrefix: this.normalizedAPIPrefix,\n          pageDataEndpoint: this.normalizedPageDataEndpoint,\n        },\n      );\n\n      // Patch reply.hijack() early so all subsequently registered routes\n      // inherit the wrapper, including user/plugin routes that bypass onSend.\n      registerResponseTimeHijackPatch(\n        this.fastifyInstance,\n        this.sharedOptions.responseTimeHeader,\n      );\n\n      // --- Setup Global Error Handling ---\n      // IMPORTANT: The global error handler must be registered *before* any plugins\n      // or routes. This ensures it can catch errors that occur during plugin\n      // loading or from any registered route.\n      this.fastifyInstance.setErrorHandler(\n        async (error: Error, request: FastifyRequest, reply: FastifyReply) => {\n          // Avoid double-send if a previous step already wrote the response\n          if (reply.sent || reply.raw.headersSent) {\n            return;\n          }\n\n          // Get active app config for error handling. setActiveSSRApp validates\n          // app keys, so the fallback is defensive for unexpected internal state.\n          const appKey = request.activeSSRApp || '__default__';\n          const appConfig =\n            this.apps.get(appKey) || this.apps.get('__default__');\n\n          if (!appConfig) {\n            // This should never happen, but handle gracefully\n            request.log.error(\n              { method: request.method, url: request.url },\n              `[${this.serverLabel}] No app config found for error handling`,\n            );\n\n            reply.code(500).header('Content-Type', 'text/plain');\n            return 'Internal Server Error';\n          }\n\n          // In development, fix Vite stack traces for all errors so source locations are accurate.\n          // ssrFixStacktrace is idempotent — safe to call here even if handleSSRError calls it again.\n          if (\n            'viteDevServer' in appConfig &&\n            appConfig.viteDevServer &&\n            this.serverMode === 'development' &&\n            error instanceof Error\n          ) {\n            appConfig.viteDevServer.ssrFixStacktrace(error);\n          }\n\n          // If the response hasn't been sent, determine response type\n          if (!reply.sent && !reply.raw.headersSent) {\n            // Check if this is an API request\n            // classifyRequest handles false prefix internally (returns isAPI: false)\n            const { isAPI } = classifyRequest(\n              request.url,\n              this.normalizedAPIPrefix,\n              this.normalizedPageDataEndpoint,\n            );\n\n            if (isAPI && this.normalizedAPIPrefix) {\n              // Log the original request error here (single log point for API errors).\n              // If a custom errorHandler also throws, that failure is logged separately\n              // inside handleAPIError — two different errors, intentionally two log entries.\n              if (this.sharedOptions.logErrors !== false) {\n                const requestID = (request as unknown as { requestID?: string })\n                  .requestID;\n\n                request.log.error(\n                  {\n                    err: error,\n                    method: request.method,\n                    url: request.url,\n                    ...(requestID ? { requestID } : {}),\n                  },\n                  `[${this.serverLabel}] Request error`,\n                );\n              }\n\n              // Return the envelope so wrapThenable makes exactly one reply.send() call.\n              return await this.handleAPIError(request, reply, error);\n            } else {\n              // Return the HTML from handleSSRError so wrapThenable makes one reply.send() call.\n              return await this.handleSSRError(\n                request,\n                reply,\n                error,\n                appConfig,\n              );\n            }\n          }\n        },\n      );\n\n      // Register plugins if provided\n      if (this.sharedOptions.plugins && this.sharedOptions.plugins.length > 0) {\n        await this.registerPlugins();\n      }\n\n      // Register file upload hooks and plugin after user plugins\n      // This ensures user plugin hooks (auth, etc.) run before upload validation\n      if (this.sharedOptions.fileUploads?.enabled) {\n        // Register validation hook using shared helper\n        registerFileUploadValidationHooks(\n          this.fastifyInstance,\n          this.sharedOptions.fileUploads,\n        );\n\n        // Register multipart plugin using shared helper (also decorates with multipartEnabled)\n        await registerMultipartPlugin(\n          this.fastifyInstance,\n          this.sharedOptions.fileUploads,\n        );\n      }\n\n      // Register WebSocket preValidation hook if enabled (before routes but after plugins)\n      if (this.webSocketHelpers) {\n        this.webSocketHelpers.registerPreValidationHook(this.fastifyInstance);\n      }\n\n      // Register API routes if enabled, or validate no handlers were registered if disabled\n      if (this.normalizedAPIPrefix === false) {\n        // API is disabled - validate that no handlers were registered\n        validateNoHandlersWhenAPIDisabled(\n          this.apiRoutes,\n          this.pageDataHandlers,\n        );\n      } else {\n        // API is enabled - register page data and API routes\n        this.pageDataHandlers.registerRoutes(\n          this.fastifyInstance,\n          this.normalizedAPIPrefix,\n          this.normalizedPageDataEndpoint,\n          {\n            versioned: this.sharedOptions.apiEndpoints?.versioned,\n          },\n        );\n\n        // Register API routes\n        this.apiRoutes.registerRoutes(\n          this.fastifyInstance,\n          this.normalizedAPIPrefix,\n          {\n            versioned: this.sharedOptions.apiEndpoints?.versioned,\n            allowWildcardAtRoot: false,\n          },\n        );\n      }\n\n      // Register WebSocket routes if enabled\n      if (this.webSocketHelpers) {\n        this.webSocketHelpers.registerRoutes(this.fastifyInstance);\n      }\n\n      // Create Vite Dev Server Middleware (Development Only)\n      if (this.serverMode === 'development') {\n        // Collect all dev apps (apps with paths)\n        const devApps = Array.from(this.apps.entries()).filter(\n          ([_, app]) => 'paths' in app,\n        );\n\n        if (devApps.length > 0) {\n          // Create Vite instances for all dev apps in parallel\n          // Each instance needs a unique HMR port to avoid conflicts\n          await Promise.all(\n            devApps.map(async ([appKey, appConfig], index) => {\n              const devApp = appConfig as SSRInternalAppConfigDev;\n\n              // Auto-assign HMR port: base port + index offset\n              // Use port + 1000 + index to avoid conflicts with main server port\n              const hmrPort = port + 1000 + index;\n\n              devApp.viteDevServer = await (\n                await import('vite')\n              ).createServer({\n                configFile: devApp.paths.viteConfig,\n                server: {\n                  middlewareMode: true,\n                  hmr: {\n                    // Auto-assign unique HMR port for each app\n                    port: hmrPort,\n                  },\n                },\n                appType: 'custom',\n              });\n\n              this.fastifyInstance?.log.debug(\n                `Created Vite dev server for app \"${appKey}\" with HMR port ${hmrPort}`,\n              );\n            }),\n          );\n\n          // Dispatch Vite dev middleware via a Fastify onRequest hook.\n          // We use onRequest instead of @fastify/middie because we need this to run\n          // AFTER user plugin hooks (which can call setActiveSSRApp and run auth) so that\n          // multi-app routing works correctly. The .use() approach from @fastify/middie\n          // runs at the raw Node layer before Fastify decorations are available.\n          // Vite's Connect-style middleware is wrapped in a Promise to integrate\n          // with Fastify's async hook chain.\n          this.fastifyInstance.addHook('onRequest', async (request, reply) => {\n            const appKey = request.activeSSRApp || '__default__';\n            const appConfig = this.apps.get(appKey);\n\n            if (\n              !appConfig ||\n              !('viteDevServer' in appConfig) ||\n              !appConfig.viteDevServer\n            ) {\n              // No Vite server for this app — continue to route handler\n              return;\n            }\n\n            const viteMiddleware = appConfig.viteDevServer.middlewares;\n\n            // Wrap Connect-style middleware (req, res, next) in a Promise.\n            // If Vite handles the request (HMR, source files, /@vite/client, etc.)\n            // it writes to res directly and never calls next(). We detect this via\n            // res.writableEnded and tell Fastify we're done.\n            // If Vite doesn't handle it, next() is called and we resolve to let\n            // Fastify continue to the route handler for SSR rendering.\n            await new Promise<void>((resolve, reject) => {\n              viteMiddleware(request.raw, reply.raw, (err?: unknown) => {\n                if (err) {\n                  reject(\n                    err instanceof Error\n                      ? err\n                      : new Error(\n                          typeof err === 'string'\n                            ? err\n                            : 'Vite middleware error',\n                        ),\n                  );\n                } else {\n                  resolve();\n                }\n              });\n            });\n\n            // If Vite handled the request (wrote to res directly), hijack the\n            // reply so Fastify doesn't try to send a second response.\n            if (reply.raw.writableEnded) {\n              reply.hijack();\n            }\n          });\n        }\n      }\n      // Production Server Middleware (Production Only)\n      else {\n        // Create static content caches for all prod apps\n        const staticContentCaches = new Map<string, StaticContentCache>();\n\n        for (const [appKey, appConfig] of this.apps) {\n          if ('buildDir' in appConfig) {\n            // TypeScript knows appConfig is SSRProdAppConfig after the check\n            // Check if static router is disabled for this app\n            const staticRouterConfig = appConfig.staticContentRouter;\n\n            // Skip if explicitly disabled (false)\n            if (staticRouterConfig === false) {\n              continue;\n            }\n\n            const clientBuildAssetDir = path.join(\n              appConfig.buildDir,\n              appConfig.clientFolderName || 'client',\n              'assets',\n            );\n\n            // Use provided config or default to assets folder with immutable caching\n            const finalConfig: StaticContentRouterOptions = staticRouterConfig\n              ? {\n                  ...staticRouterConfig,\n                  compression:\n                    staticRouterConfig.compression ??\n                    this.sharedOptions.responseCompression,\n                }\n              : {\n                  folderMap: {\n                    '/assets': {\n                      path: clientBuildAssetDir,\n                      detectImmutableAssets: true,\n                    },\n                  },\n                  compression: this.sharedOptions.responseCompression,\n                };\n\n            // Create cache instance for this app\n            const cache = new StaticContentCache(\n              finalConfig,\n              this.fastifyInstance.log,\n            );\n            staticContentCaches.set(appKey, cache);\n          }\n        }\n\n        // Register routing hook if we have any caches\n        if (staticContentCaches.size > 0) {\n          this.fastifyInstance.addHook('onRequest', async (request, reply) => {\n            const appKey = request.activeSSRApp || '__default__';\n            const cache = staticContentCaches.get(appKey);\n\n            if (cache) {\n              // Use shared static content handler (includes GET check and URL validation)\n              await staticContentHookHandler(cache, request, reply);\n              // If file was served, reply was sent and hook returns early automatically\n            }\n          });\n        }\n      }\n\n      // This handler will catch all requests\n      this.fastifyInstance.get(\n        '*',\n        async (request: FastifyRequest, reply: FastifyReply) => {\n          // Check if this is an API request that should return 404 JSON instead of SSR\n          // classifyRequest handles false prefix internally (returns isAPI: false)\n          const { isAPI } = classifyRequest(\n            request.url,\n            this.normalizedAPIPrefix,\n            this.normalizedPageDataEndpoint,\n          );\n\n          if (isAPI && this.normalizedAPIPrefix) {\n            // This is an API request that didn't match any route - return 404 JSON\n            return this.handleAPINotFound(request, reply);\n          }\n\n          // Continue with SSR handling for non-API requests\n          // Get active app based on request.activeSSRApp (defaults to '__default__')\n          const appKey = request.activeSSRApp || '__default__';\n          const appConfig = this.apps.get(appKey);\n\n          if (!appConfig) {\n            const availableApps = Array.from(this.apps.keys()).join(', ');\n            throw new Error(\n              `Active app \"${appKey}\" not found. Available apps: ${availableApps}`,\n            );\n          }\n\n          // Load and call the actual render function from the server entry\n          // Signature should be: (renderRequest: RenderRequest) => Promise<RenderResult>\n          let render: (renderRequest: RenderRequest) => Promise<RenderResult>;\n\n          let template: string;\n\n          if (\n            this.serverMode === 'development' &&\n            'viteDevServer' in appConfig &&\n            appConfig.viteDevServer\n          ) {\n            // --- Development SSR ---\n            // Read template fresh per request in dev mode\n            const templateResult = await this.loadHTMLTemplate(appConfig);\n            template = templateResult.content;\n\n            // Apply Vite HTML transforms (injects HMR client, plugins)\n            template = await appConfig.viteDevServer.transformIndexHtml(\n              request.url,\n              template,\n            );\n\n            // Load server entry using Vite's SSR loader (from src)\n            const entryServer = await appConfig.viteDevServer.ssrLoadModule(\n              appConfig.paths.serverEntry,\n            );\n\n            if (\n              !entryServer.render ||\n              typeof entryServer.render !== 'function'\n            ) {\n              throw new Error(\n                \"Server entry module must export a 'render' function\",\n              );\n            }\n\n            // Type assertion: We've validated render exists and is a function\n            render = entryServer.render as (\n              renderRequest: RenderRequest,\n            ) => Promise<RenderResult>;\n          } else {\n            // --- Production SSR ---\n            // Use template and render function loaded at startup\n            // Both are loaded once at startup for performance and fail-fast validation\n            if (\n              !('cachedHTMLTemplate' in appConfig) ||\n              !appConfig.cachedHTMLTemplate\n            ) {\n              throw new Error(\n                `HTML template not loaded for app \"${appKey}\" in production mode`,\n              );\n            }\n\n            if (\n              !('cachedRenderFunction' in appConfig) ||\n              !appConfig.cachedRenderFunction\n            ) {\n              throw new Error(\n                `Render function not loaded for app \"${appKey}\" in production mode`,\n              );\n            }\n\n            template = appConfig.cachedHTMLTemplate;\n            render = appConfig.cachedRenderFunction;\n          }\n\n          // Create Fetch API Request object for React Router\n          // Create Request object with appropriate data\n          const fetchRequest = new Request(\n            `${request.protocol}://${request.hostname}${request.url}`,\n            {\n              method: request.method,\n              headers: (() => {\n                // Safely construct Headers from Fastify request headers, normalizing string | string[]\n                const headers = new Headers();\n                const reqHeaders = request.headers as Record<\n                  string,\n                  string | string[] | undefined\n                >;\n\n                for (const key in reqHeaders) {\n                  const value = reqHeaders[key];\n\n                  if (typeof value === 'string') {\n                    headers.set(key, value);\n                  } else if (Array.isArray(value)) {\n                    for (const v of value) {\n                      headers.append(key, v);\n                    }\n                  }\n                }\n\n                // First, delete any sensitive SSR headers that might be present in the client request\n                // This prevents clients from spoofing these secure headers\n                headers.delete('X-SSR-Request');\n                headers.delete('X-SSR-Original-IP');\n                headers.delete('X-SSR-Forwarded-User-Agent');\n                headers.delete('X-Correlation-ID');\n\n                // Now set these headers with our trusted server-side values\n                headers.set('X-SSR-Request', 'true');\n                headers.set('X-SSR-Original-IP', request.clientIP);\n\n                // Forward the user agent if needed\n                const userAgent = request.headers['user-agent'];\n\n                if (typeof userAgent === 'string') {\n                  headers.set('X-SSR-Forwarded-User-Agent', userAgent);\n                }\n\n                // Forward the correlation ID (which is the same as request ID at this point)\n                if ((request as unknown as { requestID: string }).requestID) {\n                  headers.set(\n                    'X-Correlation-ID',\n                    (request as unknown as { requestID: string }).requestID,\n                  );\n                }\n\n                // Apply cookie forwarding policy to inbound Cookie header\n                const originalCookieHeader = headers.get('cookie');\n                const filteredCookieHeader = applyCookiePolicyToCookieHeader(\n                  originalCookieHeader || undefined,\n                  this.cookieAllowList,\n                  this.cookieBlockList,\n                );\n\n                if (filteredCookieHeader && filteredCookieHeader.length > 0) {\n                  headers.set('cookie', filteredCookieHeader);\n                } else {\n                  headers.delete('cookie');\n                }\n\n                return headers;\n              })(),\n              signal: AbortSignal.timeout(\n                this.sharedOptions.ssrRenderTimeout ?? 5000,\n              ),\n            },\n          );\n\n          // Attach SSRHelper for server-only access in loaders\n          const SSRHelpers: SSRHelpers = {\n            fastifyRequest: request,\n            controlledReply: createControlledReply(request, reply),\n            handlers: this.pageDataHandlers,\n          } as const;\n\n          try {\n            Object.defineProperty(fetchRequest, 'SSRHelpers', {\n              value: SSRHelpers,\n              enumerable: false,\n              configurable: false,\n              writable: false,\n            });\n          } catch {\n            // If defineProperty fails for any reason, fallback to direct assignment\n            (\n              fetchRequest as unknown as { SSRHelpers?: SSRHelpers }\n            ).SSRHelpers = SSRHelpers;\n          }\n\n          // --- Render the App ---\n          try {\n            // Resolve CDN URL before render so it's available via useCDNBaseURL() in components\n            // and the HTML global. request.CDNBaseURL was populated before preHandler.\n            // Use ?? so that an explicit empty-string override (disabling CDN for this request)\n            // is honoured rather than silently falling through to the app-level default.\n            const CDNBaseURL =\n              request.CDNBaseURL ??\n              ('CDNBaseURL' in appConfig ? appConfig.CDNBaseURL : undefined);\n\n            const renderResult = await render({\n              type: 'ssr',\n              fetchRequest,\n              unirendContext: {\n                renderMode: 'ssr',\n                isDevelopment: (\n                  request as FastifyRequest & { isDevelopment: boolean }\n                ).isDevelopment,\n                fetchRequest: fetchRequest,\n                publicAppConfig: request.publicAppConfig,\n                cdnBaseURL: normalizeCDNBaseURL(CDNBaseURL),\n                domainInfo: request.domainInfo,\n                requestContextRevision: '0-0', // Initial revision for this request\n              },\n            });\n\n            if (renderResult.resultType === 'page') {\n              // ---> Extract status code from render result\n              const statusCode = renderResult.statusCode || 200;\n\n              // ---> Extract cookies from ssOnlyData set by data loader\n              // cookies are returned as an array of strings, each string is a cookie header value already formatted\n              const cookies = renderResult.ssOnlyData?.cookies;\n\n              // set cookies on reply\n              if (Array.isArray(cookies)) {\n                const filteredCookies = applyCookiePolicyToSetCookie(\n                  cookies as string[],\n                  this.cookieAllowList,\n                  this.cookieBlockList,\n                );\n\n                for (const cookie of filteredCookies) {\n                  reply.header('Set-Cookie', cookie);\n                }\n              }\n\n              // if a 500 error is returned, send the server 500 error page version instead\n              /// This is used when there is a error boundary that sets the custom 500 error page\n              // To simplify return a server generated 500 error page instead of trying to hydrate the custom 500 error page error boundary\n              if (statusCode === 500) {\n                const error =\n                  renderResult.errorDetails ||\n                  new Error('Internal Server Error');\n\n                return await this.handleSSRError(\n                  request,\n                  reply,\n                  error,\n                  appConfig,\n                );\n              }\n\n              // --- Prepare head data for injection ---\n              const headParts = [\n                renderResult.head?.title || '',\n                renderResult.head?.meta || '',\n                renderResult.head?.link || '',\n                renderResult.preloadLinks || '',\n              ].filter(Boolean);\n\n              const headInject = headParts.join('\\n');\n\n              const finalHTML = await injectContent(\n                template,\n                headInject,\n                renderResult.html,\n                {\n                  app: request.publicAppConfig,\n                  // inject per-request context so client-side React hydrates with the same values\n                  request: request.requestContext,\n                },\n                CDNBaseURL,\n                request.domainInfo,\n              );\n\n              // ---> Send response with the extracted status code\n              if (statusCode >= 400) {\n                reply.header('Cache-Control', 'no-store');\n              }\n\n              // Return the HTML string instead of calling reply.send() directly.\n              // In Fastify 5, async handlers that call reply.send() and return undefined\n              // trigger wrapThenable to call reply.send(undefined) a second time\n              // while any async onSend hook is still pending (reply.sent stays false\n              // until headers are actually written). Returning the payload here lets\n              // wrapThenable make exactly one reply.send() call.\n              reply.code(statusCode).header('Content-Type', 'text/html');\n              return finalHTML;\n            } else if (renderResult.resultType === 'response') {\n              // If React Router returned a Response (redirect/error as a response), handle it\n              // Forward status and headers\n              reply.code(renderResult.response.status);\n\n              // Apply no-store for all 4xx/5xx in SSR Response path\n              if (renderResult.response.status >= 400) {\n                reply.header('Cache-Control', 'no-store');\n              }\n\n              // Forward headers safe for redirects/responses\n              // Headers is iterable at runtime but TS DOM lib types don't expose entries(),\n              // so we cast to the expected iterable shape for safe iteration.\n              const responseHeaders = renderResult.response\n                .headers as unknown as Iterable<[string, string]>;\n\n              for (const [key, value] of Array.from(responseHeaders)) {\n                const lowerKey = key.toLowerCase();\n\n                if (lowerKey === 'location' || lowerKey === 'set-cookie') {\n                  if (lowerKey === 'set-cookie') {\n                    const filtered = applyCookiePolicyToSetCookie(\n                      value,\n                      this.cookieAllowList,\n                      this.cookieBlockList,\n                    );\n\n                    for (const v of filtered) {\n                      reply.header('Set-Cookie', v);\n                    }\n                  } else {\n                    reply.header(key, value);\n                  }\n                }\n              }\n\n              // Return the body (or undefined for an intentionally empty\n              // response) so wrapThenable makes exactly one reply.send() call.\n              // See the page path above for why.\n              try {\n                const body = await renderResult.response.text();\n                return body || undefined;\n              } catch (bodyError) {\n                request.log.error(\n                  { err: bodyError, method: request.method, url: request.url },\n                  `[${this.serverLabel}] Error reading response body`,\n                );\n\n                // If we cannot read the body from a returned Response, treat it\n                // as an internal server failure rather than silently ending the\n                // request with an empty body under the original status code.\n                return await this.handleSSRError(\n                  request,\n                  reply,\n                  bodyError instanceof Error\n                    ? bodyError\n                    : new Error('Failed to read response body'),\n                  appConfig,\n                );\n              }\n            } else if (renderResult.resultType === 'render-error') {\n              // Handle render errors\n              return await this.handleSSRError(\n                request,\n                reply,\n                renderResult.error,\n                appConfig,\n              );\n            } else {\n              // Handle unexpected result types (this should never happen with proper typing)\n              // TypeScript knows this is never, but we handle it for runtime safety\n              const resultType =\n                (renderResult as { resultType?: string }).resultType ||\n                'unknown';\n              const unexpectedError = new Error(\n                `Unexpected render result type: ${resultType}`,\n              );\n\n              return await this.handleSSRError(\n                request,\n                reply,\n                unexpectedError,\n                appConfig,\n              );\n            }\n          } catch (error) {\n            return await this.handleSSRError(\n              request,\n              reply,\n              error as Error,\n              appConfig,\n            );\n          }\n\n          // Safety check - if we somehow reach here without sending a response\n          if (!reply.sent && !reply.raw.headersSent) {\n            this.fastifyInstance?.log.warn(\n              'No response was sent, sending 500 error',\n            );\n\n            // Re-fetch appConfig for safety check (should always exist, but be defensive)\n            const safetyAppKey = request.activeSSRApp || '__default__';\n            const fallbackAppConfig =\n              this.apps.get(safetyAppKey) || this.apps.get('__default__');\n\n            if (!fallbackAppConfig) {\n              // Ultimate fallback if even default app is missing\n              reply.code(500).header('Content-Type', 'text/plain');\n              return 'Internal Server Error';\n            }\n\n            // TypeScript doesn't narrow the type properly here, but we've verified it exists above\n            return await this.handleSSRError(\n              request,\n              reply,\n              new Error('No response was generated'),\n              fallbackAppConfig as SSRInternalAppConfig,\n            );\n          }\n        },\n      );\n\n      // Register response compression for non-streaming SSR/API responses.\n      // Static file compression is handled separately in the static content layer.\n      registerResponseCompression(\n        this.fastifyInstance,\n        this.sharedOptions.responseCompression,\n      );\n\n      // Register the response-time header hook after plugins and routes so\n      // third-party onSend hooks run first. Normal Fastify-managed replies\n      // measure the header here, while access logging measures on completion.\n      registerResponseTimeHeader(\n        this.fastifyInstance,\n        this.sharedOptions.responseTimeHeader,\n      );\n\n      // Start the server\n      await this.fastifyInstance.listen({\n        port,\n        host: host || 'localhost',\n      });\n\n      this._isListening = true;\n      this._isStarting = false;\n    } catch (error) {\n      // Cleanup on any startup failure\n      this._isListening = false;\n      this._isStarting = false;\n\n      const cleanupErrors: string[] = [];\n\n      // Close Fastify if it was created but startup failed\n      if (this.fastifyInstance) {\n        try {\n          await this.fastifyInstance.close();\n        } catch (closeError) {\n          cleanupErrors.push(\n            `Fastify cleanup failed: ${closeError instanceof Error ? closeError.message : String(closeError)}`,\n          );\n        }\n\n        this.fastifyInstance = null;\n      }\n\n      // Close all Vite dev servers if any were created but startup failed\n      for (const [appKey, appConfig] of this.apps) {\n        if ('viteDevServer' in appConfig && appConfig.viteDevServer) {\n          try {\n            await appConfig.viteDevServer.close();\n          } catch (closeError) {\n            cleanupErrors.push(\n              `Vite dev server cleanup failed for app \"${appKey}\": ${closeError instanceof Error ? closeError.message : String(closeError)}`,\n            );\n          }\n\n          appConfig.viteDevServer = undefined;\n        }\n      }\n\n      // Clear plugin tracking state on failure\n      this.registeredPlugins = [];\n\n      // Append cleanup errors to original error message if any\n      if (cleanupErrors.length > 0 && error instanceof Error) {\n        // Modify the original error's message directly\n        error.message = `${error.message}. Additional errors occurred: ${cleanupErrors.join(', ')}`;\n      }\n\n      throw error;\n    }\n  }\n\n  /**\n   * Stop the server if it's currently listening\n   */\n  public async stop(): Promise<void> {\n    if (!this._isListening) {\n      return;\n    }\n\n    // Close all Vite dev servers and clear caches\n    const cleanupErrors: string[] = [];\n\n    // Close Fastify server if it exists\n    if (this.fastifyInstance) {\n      this._isStopping = true;\n\n      try {\n        await this.fastifyInstance.close();\n      } catch (closeError) {\n        cleanupErrors.push(\n          `Fastify close failed: ${closeError instanceof Error ? closeError.message : String(closeError)}`,\n        );\n      } finally {\n        this._isStopping = false;\n      }\n\n      this.fastifyInstance = null;\n    }\n\n    for (const [appKey, appConfig] of this.apps) {\n      // Close Vite dev server if present\n      if ('viteDevServer' in appConfig && appConfig.viteDevServer) {\n        const viteDevServer = appConfig.viteDevServer;\n\n        const viteCleanupWarnings: string[] = [];\n\n        // Unref the watcher so the process can exit even if Bun doesn't\n        // fully release the underlying fs handle after close().\n        viteDevServer.watcher?.unref?.();\n\n        // Close the file watcher explicitly first so Bun handles the fs\n        // handle release as a discrete step rather than racing it against\n        // HMR teardown inside Vite's internal close().\n        try {\n          await viteDevServer.watcher?.close();\n        } catch (closeError) {\n          viteCleanupWarnings.push(\n            `watcher.close() failed: ${closeError instanceof Error ? closeError.message : String(closeError)}`,\n          );\n        }\n\n        // Start ws.close() first — its Promise executor runs synchronously,\n        // so the server stops accepting new connections immediately.\n        try {\n          const wsClosePromise = viteDevServer.ws.close();\n\n          // Terminate any connected clients. terminate() forcefully closes the\n          // socket and its underlying TCP connection so the HMR HTTP server has\n          // no remaining connections when wsHttpServer.close() fires.\n          for (const client of viteDevServer.ws.clients) {\n            client.socket.terminate();\n          }\n\n          await wsClosePromise;\n        } catch (closeError) {\n          viteCleanupWarnings.push(\n            `ws.close() failed: ${closeError instanceof Error ? closeError.message : String(closeError)}`,\n          );\n        }\n\n        // Always run Vite's full close path so watcher/HMR failures do not\n        // skip environment, HTTP server, or SSR runner cleanup.\n        try {\n          await viteDevServer.close();\n\n          // Only clear reference if Vite reports that its full close path finished.\n          appConfig.viteDevServer = undefined;\n        } catch (closeError) {\n          const errorDetails = [\n            ...viteCleanupWarnings,\n            `viteDevServer.close() failed: ${closeError instanceof Error ? closeError.message : String(closeError)}`,\n          ];\n          cleanupErrors.push(\n            `Failed to close Vite dev server for app \"${appKey}\": ${errorDetails.join('; ')}`,\n          );\n          // Don't clear viteDevServer reference - it might still be running\n        }\n      }\n\n      // Clear cached templates and render functions (production mode)\n      if ('cachedHTMLTemplate' in appConfig) {\n        appConfig.cachedHTMLTemplate = undefined;\n      }\n\n      if ('cachedRenderFunction' in appConfig) {\n        appConfig.cachedRenderFunction = undefined;\n      }\n    }\n\n    // Throw if any cleanup errors occurred\n    if (cleanupErrors.length > 0) {\n      throw new Error(\n        `Server stop failed with ${cleanupErrors.length} error(s):\\n${cleanupErrors.join('\\n')}`,\n      );\n    }\n\n    // Only mark as stopped after both are successfully closed\n    this._isListening = false;\n\n    // Clear plugin tracking state\n    this.registeredPlugins = [];\n  }\n\n  /**\n   * Force-close all open connections, including Fastify WebSockets and Vite HMR\n   * sockets in development mode. Unlike stop(), this does not wait for\n   * in-flight requests to complete.\n   */\n  public override closeAllConnections(): void {\n    for (const appConfig of this.apps.values()) {\n      if ('viteDevServer' in appConfig && appConfig.viteDevServer) {\n        for (const client of appConfig.viteDevServer.ws.clients) {\n          client.socket.terminate();\n        }\n      }\n    }\n\n    super.closeAllConnections();\n  }\n\n  /**\n   * Merges the provided keys into the current access log config at runtime.\n   * Access logging is on by default (finish events, default template). Use\n   * `events: 'none'` to disable logging while keeping hooks active.\n   * Omitted keys stay unchanged. Pass `undefined` for a hook callback to remove it.\n   *\n   * Changes take effect on the next request — no restart required.\n   */\n  public updateAccessLoggingConfig(partial: Partial<AccessLogConfig>): void {\n    this._accessLog.update(partial);\n  }\n\n  /**\n   * Public API method for registering versioned generic API routes\n   * Usage: server.api.get(\"users/:id\", handler) or server.api.get(\"users/:id\", 2, handler)\n   */\n  public get api() {\n    return this.apiRoutes.apiMethod;\n  }\n\n  /**\n   * Public API method for registering page data loader handlers\n   * Usage: server.pageDataHandler.register(\"home\", handler) or server.pageDataHandler.register(\"home\", 2, handler)\n   */\n  public get pageDataHandler() {\n    return this.pageDataHandlers.pageDataHandlerMethod;\n  }\n\n  /**\n   * Register a WebSocket handler for the specified path\n   *\n   * @param config WebSocket handler configuration\n   * @throws Error if WebSocket support is not enabled\n   */\n  public registerWebSocketHandler(config: WebSocketHandlerConfig): void {\n    if (!this.webSocketHelpers) {\n      throw new Error(\n        \"WebSocket support is not enabled. Set 'enableWebSockets: true' in ServeSSROptions to use WebSocket handlers.\",\n      );\n    }\n\n    this.webSocketHelpers.registerWebSocketHandler(config);\n  }\n\n  /**\n   * Get the list of active WebSocket clients\n   *\n   * @returns Set of WebSocket clients, or empty Set if WebSocket support is disabled or server not started\n   */\n  public getWebSocketClients(): Set<WebSocket> {\n    if (!this.fastifyInstance || !this._isListening) {\n      // Server not started or Fastify instance missing — return empty set as a safe fallback\n      return new Set<WebSocket>();\n    }\n\n    // Access the websocketServer decorated by @fastify/websocket plugin\n    const websocketServer = (\n      this.fastifyInstance as unknown as { websocketServer?: WebSocketServer }\n    ).websocketServer;\n\n    if (!websocketServer || !websocketServer.clients) {\n      // WebSocket server not available (plugin not enabled/initialized) — return empty set fallback\n      return new Set<WebSocket>();\n    }\n\n    // Return the underlying ws client set (Set<WebSocket>)\n    return websocketServer.clients;\n  }\n\n  /**\n   * Validate app key for registration\n   * @private\n   */\n  private validateAppKey(appKey: string): void {\n    // appKey is already validated as a non-empty string and trimmed by the caller\n    if (appKey.length === 0) {\n      throw new Error('App key cannot be empty or whitespace-only');\n    }\n\n    if (appKey === '__default__') {\n      throw new Error(\n        'Cannot register app with reserved key \"__default__\". This key is used for the initial app.',\n      );\n    }\n\n    if (appKey.includes('/') || appKey.includes('\\\\')) {\n      throw new Error(\n        'App key cannot contain path separators. Use alphanumeric names like \"marketing\" or \"admin\".',\n      );\n    }\n\n    if (this.apps.has(appKey)) {\n      throw new Error(\n        `App \"${appKey}\" is already registered. Use a different key or unregister the existing app first.`,\n      );\n    }\n  }\n\n  /**\n   * Register plugins with controlled access to Fastify instance\n   * @private\n   */\n  private async registerPlugins(): Promise<void> {\n    // If no fastify instance or plugins are provided, return early\n    if (!this.fastifyInstance || !this.sharedOptions.plugins) {\n      return;\n    }\n\n    // Create controlled instance wrapper\n    const controlledInstance = createControlledInstance(\n      this.fastifyInstance,\n      true,\n      this.apiRoutes.apiMethod,\n      this.pageDataHandlers.pageDataHandlerMethod,\n      this.APIResponseHelpersClass,\n    );\n\n    // Plugin options to pass to each plugin\n    const pluginOptions = {\n      serverType: 'ssr' as const,\n      mode: this.serverMode,\n      isDevelopment: getDevMode(),\n      apiEndpoints: this.sharedOptions.apiEndpoints,\n    };\n\n    // Register each plugin with dependency validation\n    for (const plugin of this.sharedOptions.plugins) {\n      try {\n        // Call plugin and get potential metadata\n        const pluginResult = await plugin(controlledInstance, pluginOptions);\n\n        // Validate dependencies and track plugin\n        validateAndRegisterPlugin(this.registeredPlugins, pluginResult);\n      } catch (error) {\n        this.fastifyInstance?.log.error(\n          { err: error },\n          `[${this.serverLabel}] Failed to register plugin`,\n        );\n\n        throw new Error(\n          `Plugin registration failed: ${error instanceof Error ? error.message : String(error)}`,\n        );\n      }\n    }\n  }\n\n  /**\n   * Loads and caches the production render function from the server entry\n   * This is called once and cached for performance in production mode\n   * @param appConfig App configuration to load render function for\n   * @returns Promise that resolves to the render function\n   * @private\n   */\n  private async loadProductionRenderFunction(\n    appConfig: SSRInternalAppConfig,\n  ): Promise<(renderRequest: RenderRequest) => Promise<RenderResult>> {\n    // Check if already cached on app config\n    if ('cachedRenderFunction' in appConfig && appConfig.cachedRenderFunction) {\n      return appConfig.cachedRenderFunction;\n    }\n\n    if (this.serverMode !== 'production' || !('buildDir' in appConfig)) {\n      throw new Error(\n        'loadProductionRenderFunction requires production mode with buildDir',\n      );\n    }\n\n    const serverEntry = appConfig.serverEntry || 'EntrySSR';\n    const serverBuildDir = path.join(\n      appConfig.buildDir,\n      appConfig.serverFolderName || 'server',\n    );\n\n    // Load the server's regular manifest\n    const serverManifestResult = await checkAndLoadManifest(\n      serverBuildDir,\n      false,\n    );\n\n    if (!serverManifestResult.success || !serverManifestResult.manifest) {\n      throw new Error(\n        `Failed to load server manifest: ${serverManifestResult.error}`,\n      );\n    }\n\n    const entryResult = getServerEntryFromManifest(\n      serverManifestResult.manifest,\n      serverBuildDir,\n      serverEntry,\n    );\n\n    if (!entryResult.success || !entryResult.entryPath) {\n      throw new Error(`Failed to find server entry: ${entryResult.error}`);\n    }\n\n    // Import the server entry module\n    let entryServer: unknown;\n\n    try {\n      entryServer = await import(/* @vite-ignore */ entryResult.entryPath);\n    } catch (error) {\n      // Type assertion for error message - error could be anything\n      const errorMessage =\n        error instanceof Error ? error.message : String(error);\n\n      throw new Error(\n        `Failed to import server entry from ${entryResult.entryPath}: ${errorMessage}`,\n      );\n    }\n\n    // Validate the imported module has a render function\n    if (\n      !entryServer ||\n      typeof entryServer !== 'object' ||\n      !('render' in entryServer) ||\n      typeof entryServer.render !== 'function'\n    ) {\n      throw new Error(\"Server entry module must export a 'render' function\");\n    }\n\n    // Type assertion: We've validated render exists and is a function\n    const renderFunction = entryServer.render as (\n      renderRequest: RenderRequest,\n    ) => Promise<RenderResult>;\n\n    // Cache the render function on the app config for subsequent requests\n    appConfig.cachedRenderFunction = renderFunction;\n    return renderFunction;\n  }\n\n  /**\n   * Loads and processes the HTML template based on the server mode\n   * @param appConfig App configuration to load template for\n   * @returns Promise that resolves to the processed template content and path\n   * @private\n   */\n  private async loadHTMLTemplate(\n    appConfig: SSRInternalAppConfig,\n  ): Promise<{ content: string; path: string }> {\n    // Determine template path based on mode\n    let htmlTemplatePath: string;\n\n    if (this.serverMode === 'development' && 'paths' in appConfig) {\n      // Development mode: use provided template path\n      htmlTemplatePath = appConfig.paths.template;\n    } else if (this.serverMode === 'production' && 'buildDir' in appConfig) {\n      // Production mode: use custom template or default to client/index.html\n      if (appConfig.template) {\n        // Custom template path (relative to buildDir)\n        htmlTemplatePath = path.join(appConfig.buildDir, appConfig.template);\n      } else {\n        // Default: client folder from build directory\n        htmlTemplatePath = path.join(\n          appConfig.buildDir,\n          appConfig.clientFolderName || 'client',\n          'index.html',\n        );\n      }\n    } else {\n      throw new Error('Invalid app config for template loading');\n    }\n\n    // Read the HTML template file\n    const templateResult = await readHTMLFile(htmlTemplatePath);\n\n    if (!templateResult.exists) {\n      throw new Error(\n        `HTML template not found at ${htmlTemplatePath}. ` +\n          (this.serverMode === 'development'\n            ? 'Please check the templatePath parameter.'\n            : 'Make sure to run the client build first.'),\n      );\n    }\n\n    if (templateResult.error) {\n      throw new Error(\n        `Failed to read HTML template from ${htmlTemplatePath}: ${templateResult.error}`,\n      );\n    }\n\n    // At this point, templateResult.content should exist\n    const rawHTMLTemplate = templateResult.content as string;\n\n    if (!rawHTMLTemplate || rawHTMLTemplate.length === 0) {\n      throw new Error(`HTML template at ${htmlTemplatePath} is empty`);\n    }\n\n    // Process the template based on mode and app-specific container ID\n    const isDevServer = this.serverMode === 'development';\n    const containerID = appConfig.containerID || 'root';\n\n    const processResult = await processTemplate(\n      rawHTMLTemplate,\n      'ssr', // mode\n      getDevMode(), // runtime behavior (dev comment)\n      isDevServer, // asset serving strategy (CDN rewriting)\n      containerID,\n    );\n\n    // For SSR, throw error if processing fails\n    if (!processResult.success) {\n      throw new Error(\n        `Failed to process HTML template: ${processResult.error}`,\n      );\n    }\n\n    return {\n      content: processResult.html,\n      path: htmlTemplatePath,\n    };\n  }\n\n  /**\n   * Handles SSR errors with Vite stack trace fixing and custom error pages\n   * @param request The Fastify request object\n   * @param reply The Fastify reply object\n   * @param error The error that occurred\n   * @param appConfig The app configuration (contains viteDevServer in development)\n   * @private\n   */\n  private async handleSSRError(\n    request: FastifyRequest,\n    reply: FastifyReply,\n    error: Error,\n    appConfig: SSRInternalAppConfig,\n  ): Promise<string | undefined> {\n    // This method is invoked both by the global Fastify error handler and\n    // by our route-level try/catch around the render call. If a response\n    // was already sent, bail out to prevent double-sending.\n    if (reply.sent || reply.raw.headersSent) {\n      return undefined;\n    }\n\n    // If an error is caught, let Vite fix the stack trace so it maps back\n    // to your actual source code.\n    const vite = 'viteDevServer' in appConfig ? appConfig.viteDevServer : null;\n\n    if (vite && error instanceof Error && this.serverMode === 'development') {\n      vite.ssrFixStacktrace(error);\n    }\n\n    // Log SSR errors here (single log point — avoids double-logging when called from global error handler)\n    // Uses request.log to include per-request logger bindings\n    if (this.sharedOptions.logErrors !== false) {\n      const requestID = (request as unknown as { requestID?: string })\n        .requestID;\n\n      request.log.error(\n        {\n          err: error,\n          method: request.method,\n          url: request.url,\n          ...(requestID ? { requestID } : {}),\n        },\n        `[${this.serverLabel}] Request error`,\n      );\n    }\n\n    // Generate error page HTML (handles dev vs prod internally).\n    // Callers inside async Fastify route handlers should `return await handleSSRError(...)`\n    // so that wrapThenable makes exactly one reply.send() call with the returned HTML.\n    const errorPage = await this.generate500ErrorPage(\n      request,\n      error,\n      appConfig,\n    );\n\n    reply\n      .code(500)\n      .header('Content-Type', 'text/html')\n      .header('Cache-Control', 'no-store');\n\n    return errorPage;\n  }\n\n  /**\n   * Generates a 500 error page using custom handler or default\n   * @param request The Fastify request object\n   * @param error The error that occurred\n   * @param appConfig The active app configuration\n   * @returns Promise that resolves to HTML string\n   * @private\n   */\n  private async generate500ErrorPage(\n    request: FastifyRequest,\n    error: Error,\n    appConfig: SSRInternalAppConfig,\n  ): Promise<string> {\n    const isDevelopment = (\n      request as FastifyRequest & { isDevelopment: boolean }\n    ).isDevelopment;\n\n    try {\n      // Use app-specific error handler if provided\n      if (appConfig.get500ErrorPage) {\n        return await appConfig.get500ErrorPage(request, error, isDevelopment);\n      }\n\n      // Fall back to built-in default error page\n      return generateDefault500ErrorPage(request, error, isDevelopment);\n    } catch (errorHandlerError) {\n      // If custom handler throws, log that failure separately and fall back to the default page.\n      // The original request error was already logged in handleSSRError — two different errors,\n      // intentionally two log entries.\n      request.log.error(\n        { err: errorHandlerError, method: request.method, url: request.url },\n        `[${this.serverLabel}] Custom 500 error page handler failed`,\n      );\n      return generateDefault500ErrorPage(request, error, isDevelopment);\n    }\n  }\n\n  /**\n   * Handles API errors with JSON responses using envelope pattern\n   * @param request The Fastify request object\n   * @param reply The Fastify reply object\n   * @param error The error that occurred\n   * @private\n   */\n  private async handleAPIError(\n    request: FastifyRequest,\n    reply: FastifyReply,\n    error: Error,\n  ): Promise<unknown> {\n    const isDevelopment = (\n      request as FastifyRequest & { isDevelopment: boolean }\n    ).isDevelopment;\n\n    const { isPageData } = classifyRequest(\n      request.url,\n      this.normalizedAPIPrefix,\n      this.normalizedPageDataEndpoint,\n    );\n\n    // Check for custom API error handler if provided\n    if (this.sharedOptions.APIHandling?.errorHandler) {\n      try {\n        const customResponse = await Promise.resolve(\n          this.sharedOptions.APIHandling.errorHandler(\n            request,\n            error,\n            isDevelopment,\n            isPageData,\n          ),\n        );\n\n        // Extract status code from envelope response\n        const statusCode = customResponse.status_code || 500;\n        reply.code(statusCode).header('Cache-Control', 'no-store');\n\n        // Return the envelope instead of calling reply.send() directly.\n        // The caller returns this value so wrapThenable makes exactly one reply.send() call.\n        return customResponse;\n      } catch (handlerError) {\n        // If custom handler fails, fall back to default\n        request.log.error(\n          { err: handlerError, method: request.method, url: request.url },\n          `[${this.serverLabel}] Custom API error handler failed`,\n        );\n      }\n    }\n\n    // Default case\n    const response = createDefaultAPIErrorResponse(\n      this.APIResponseHelpersClass,\n      request,\n      error,\n      isDevelopment,\n      this.normalizedAPIPrefix,\n      this.normalizedPageDataEndpoint,\n    );\n\n    // Extract status code from envelope response\n    const statusCode =\n      (response as { status_code?: number }).status_code || 500;\n\n    reply.code(statusCode).header('Cache-Control', 'no-store');\n\n    return response;\n  }\n\n  /**\n   * Handles API 404 not found responses with JSON envelopes\n   * @param request The Fastify request object\n   * @param reply The Fastify reply object\n   * @private\n   */\n  private async handleAPINotFound(\n    request: FastifyRequest,\n    reply: FastifyReply,\n  ): Promise<unknown> {\n    const { isPageData } = classifyRequest(\n      request.url,\n      this.normalizedAPIPrefix,\n      this.normalizedPageDataEndpoint,\n    );\n\n    // Check for custom API not-found handler\n    if (this.sharedOptions.APIHandling?.notFoundHandler) {\n      try {\n        const customResponse = await Promise.resolve(\n          this.sharedOptions.APIHandling.notFoundHandler(request, isPageData),\n        );\n\n        // Extract status code from envelope response\n        const statusCode = customResponse.status_code || 404;\n        reply.code(statusCode).header('Cache-Control', 'no-store');\n\n        // Return the envelope instead of calling reply.send() directly.\n        // The caller returns this value so wrapThenable makes exactly one reply.send() call.\n        return customResponse;\n      } catch (handlerError) {\n        // If custom handler fails, fall back to default\n        request.log.error(\n          { err: handlerError, method: request.method, url: request.url },\n          `[${this.serverLabel}] Custom API not-found handler failed`,\n        );\n      }\n    }\n\n    // Default case\n    const response = createDefaultAPINotFoundResponse(\n      this.APIResponseHelpersClass,\n      request,\n      this.normalizedAPIPrefix,\n      this.normalizedPageDataEndpoint,\n    );\n\n    // Extract status code from envelope response\n    const statusCode =\n      (response as { status_code?: number }).status_code || 404;\n\n    reply.code(statusCode).header('Cache-Control', 'no-store');\n\n    return response;\n  }\n}\n","/**\n * Escapes HTML special characters to prevent XSS attacks\n *\n * Converts the following characters to HTML entities:\n * - & → &amp;\n * - < → &lt;\n * - > → &gt;\n * - \" → &quot;\n * - ' → &#39;\n *\n * @param str - The string to escape\n * @returns The escaped string safe for insertion into HTML\n *\n * @example\n * ```ts\n * escapeHTML('<script>alert(\"xss\")</script>');\n * // Returns: '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'\n * ```\n */\nexport function escapeHTML(str: string): string {\n  return str\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;')\n    .replace(/\"/g, '&quot;')\n    .replace(/'/g, '&#39;');\n}\n\n/**\n * Escapes a string for safe insertion into double-quoted HTML attributes.\n *\n * Converts the following characters to HTML entities:\n * - & → &amp;\n * - \" → &quot;\n * - < → &lt;\n * - > → &gt;\n *\n * @param str - The string to escape\n * @returns The escaped string safe for insertion into HTML attributes\n */\nexport function escapeHTMLAttr(str: string): string {\n  return str\n    .replace(/&/g, '&amp;')\n    .replace(/\"/g, '&quot;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;');\n}\n","import type { FastifyRequest } from 'fastify';\nimport { escapeHTML } from './html-utils/escape';\n\ntype ErrorPageStyleRules = Record<string, Record<string, string>>;\n\nconst DEFAULT_ERROR_PAGE_STYLE_RULES = {\n  'html, body': {\n    height: '100%',\n    margin: '0',\n    padding: '0',\n    background: '#fff',\n  },\n  body: {\n    'min-height': '100vh',\n    display: 'flex',\n    'align-items': 'center',\n    'justify-content': 'center',\n    'font-family': 'system-ui, Arial, sans-serif',\n    background: '#f7f7f8',\n  },\n  '.ep-card': {\n    background: '#fff',\n    'border-radius': '14px',\n    'box-shadow': '0 2px 16px rgba(0,0,0,0.08)',\n    'max-width': '440px',\n    width: '100%',\n    margin: '32px',\n    padding: '32px 28px 24px 28px',\n    'text-align': 'center',\n  },\n  '.ep-title': {\n    'font-size': '2rem',\n    'font-weight': '600',\n    'margin-bottom': '12px',\n  },\n  '.ep-sub': {\n    'font-size': '1.1rem',\n    'font-weight': '500',\n    'margin-bottom': '24px',\n    color: '#222',\n  },\n  '.ep-panel': {\n    background: '#f1f1f3',\n    'border-radius': '6px',\n    padding: '12px 14px',\n    'font-size': '0.98rem',\n    color: '#222',\n  },\n} satisfies ErrorPageStyleRules;\n\nfunction generateErrorPageStyles(overrides: ErrorPageStyleRules): string {\n  const rules: ErrorPageStyleRules = {};\n\n  for (const [selector, declarations] of Object.entries(\n    DEFAULT_ERROR_PAGE_STYLE_RULES,\n  )) {\n    rules[selector] = { ...declarations };\n  }\n\n  for (const [selector, declarations] of Object.entries(overrides)) {\n    rules[selector] = { ...rules[selector], ...declarations };\n  }\n\n  return Object.entries(rules)\n    .map(\n      ([selector, declarations]) => `    ${selector} {\n${Object.entries(declarations)\n  .map(([property, value]) => `      ${property}: ${value};`)\n  .join('\\n')}\n    }`,\n    )\n    .join('\\n');\n}\n\n/**\n * Generates a default 500 error page.\n * @param request The Fastify request object\n * @param error The error that occurred\n * @param isDevelopment Whether running in development mode\n * @returns HTML string for the error page\n */\nexport function generateDefault500ErrorPage(\n  request: FastifyRequest,\n  error: Error,\n  isDevelopment: boolean,\n): string {\n  // Panels for dev mode\n  const devPanels = isDevelopment\n    ? `<div class=\"ep-section\">\n      <div class=\"ep-label\">Message:</div>\n      <div class=\"ep-panel\">${escapeHTML(error.message)}</div>\n    </div>\n    <div class=\"ep-section\">\n      <div class=\"ep-label\">Stack Trace:</div>\n      <div class=\"ep-panel ep-stack\">${escapeHTML(error.stack || 'No stack trace available')}</div>\n    </div>\n    <div class=\"ep-section\">\n      <div class=\"ep-label\">Request Info:</div>\n      <div class=\"ep-panel\">\n        URL: ${escapeHTML(request.url)}<br>\n        Method: ${request.method}\n      </div>\n    </div>`\n    : '';\n\n  return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>500 - Internal Server Error</title>\n  <style>\n${generateErrorPageStyles({\n  '.ep-title': {\n    color: '#e53935',\n    'letter-spacing': '0.01em',\n  },\n  '.ep-section': {\n    'margin-bottom': '18px',\n    'text-align': 'left',\n  },\n  '.ep-label': {\n    'font-size': '1rem',\n    'font-weight': '600',\n    color: '#444',\n    'margin-bottom': '2px',\n  },\n  '.ep-panel': {\n    'word-break': 'break-all',\n    'overflow-x': 'auto',\n  },\n  '.ep-stack': {\n    'font-size': '0.92rem',\n    'white-space': 'pre-wrap',\n    'font-family':\n      'ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace',\n    'max-height': '300px',\n    'overflow-y': 'auto',\n  },\n  '.ep-note': {\n    'margin-top': '30px',\n    'font-size': '0.97rem',\n    color: '#888',\n  },\n  '.ep-btn': {\n    margin: '18px auto 0 auto',\n    display: 'block',\n    background: '#2563eb',\n    color: '#fff',\n    border: 'none',\n    'border-radius': '6px',\n    padding: '10px 22px',\n    'font-size': '1rem',\n    'font-weight': '500',\n    cursor: 'pointer',\n    'box-shadow': '0 1px 3px rgba(0,0,0,0.1)',\n    transition: 'background 0.15s',\n  },\n  '.ep-btn:hover, .ep-btn:focus': {\n    background: '#1d4ed8',\n    outline: 'none',\n  },\n})}\n  </style>\n</head>\n<body>\n  <main class=\"ep-card\">\n    <div class=\"ep-title\">500 - Internal Server Error</div>\n    <div class=\"ep-sub\">\n      ${isDevelopment ? 'Error Details (Development Mode)' : \"We're sorry, something went wrong.\"}\n    </div>\n    ${\n      isDevelopment\n        ? devPanels\n        : '<div class=\"ep-panel\">An unexpected error occurred. Please try again later.</div>'\n    }\n    <button class=\"ep-btn\" onclick=\"window.location.reload()\" type=\"button\">Refresh Page</button>\n    ${\n      isDevelopment\n        ? '<div class=\"ep-note\"><b>Note:</b> Detailed error information is only shown in development mode.</div>'\n        : ''\n    }\n  </main>\n</body>\n</html>`;\n}\n\n/**\n * Generates a default 503 page for requests received while the server is closing.\n * @returns HTML string for the shutdown page\n */\nexport function generateDefault503ClosingPage(): string {\n  return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>503 - Service Unavailable</title>\n  <style>\n${generateErrorPageStyles({\n  '.ep-title': {\n    color: '#374151',\n  },\n})}\n  </style>\n</head>\n<body>\n  <main class=\"ep-card\">\n    <div class=\"ep-title\">503 - Service Unavailable</div>\n    <div class=\"ep-sub\">Server is shutting down</div>\n    <div class=\"ep-panel\">Please try again shortly.</div>\n  </main>\n</body>\n</html>`;\n}\n","import type {\n  FastifyInstance,\n  FastifyPluginAsync,\n  FastifyPluginCallback,\n  FastifyRequest,\n  FastifyReply,\n  RouteHandler,\n} from 'fastify';\nimport type {\n  PluginMetadata,\n  PluginHostInstance,\n  FastifyHookName,\n  SafeRouteOptions,\n  ControlledReply,\n  APIResponseHelpersClass,\n  HTTPSOptions,\n  WebResponse,\n  APIClosingHandlerFn,\n  WebClosingHandlerFn,\n  SplitClosingHandler,\n} from '../types';\nimport type { BaseMeta } from '../api-envelope/api-envelope-types';\nimport type { CookieSerializeOptions } from '@fastify/cookie';\nimport { DEFAULT_API_PREFIX, DEFAULT_PAGE_DATA_ENDPOINT } from './consts';\nimport { generateDefault503ClosingPage } from './error-page-utils';\nimport { parseHostHeader, getDomain } from 'lifecycleion/domain-utils';\nimport { sendRawErrorEnvelopeResponse } from './error-envelope-send';\nimport type { DomainInfo } from './domain-info';\n\n/**\n * Normalize an API prefix to ensure it has a leading slash and no trailing slash.\n *\n * Handles: \"api\", \"/api\", \"/api/\", \"api/\", \"//api//\" → \"/api\"\n *\n * Special handling:\n * - `false` returns `false` (API disabled)\n * - `null`, `undefined`, or empty/whitespace-only string returns the default prefix\n *\n * @param prefix - The prefix to normalize, or false to disable API handling\n * @param defaultPrefix - Default prefix to use when input is null/undefined/empty (defaults to DEFAULT_API_PREFIX)\n * @returns Normalized prefix string, or false if API is disabled\n */\n\nexport function normalizeAPIPrefix(\n  prefix: string | false | null | undefined,\n  defaultPrefix: string = DEFAULT_API_PREFIX,\n): string | false {\n  // Explicit false means API is disabled\n  if (prefix === false) {\n    return false;\n  }\n\n  // null, undefined, or empty/whitespace-only string → use default\n  const trimmed = (prefix ?? '').trim();\n  let normalized = trimmed.length === 0 ? defaultPrefix : trimmed;\n\n  // Add leading slash if missing\n  if (!normalized.startsWith('/')) {\n    normalized = '/' + normalized;\n  }\n\n  // Collapse multiple consecutive slashes to a single slash\n  normalized = normalized.replace(/\\/+/g, '/');\n\n  // Remove trailing slash if present (but keep root \"/\" as-is)\n  if (normalized.endsWith('/') && normalized.length > 1) {\n    normalized = normalized.slice(0, -1);\n  }\n\n  return normalized;\n}\n\n/**\n * Normalize a page data endpoint name to have no leading or trailing slashes.\n *\n * Handles: \"/page_data\", \"page_data/\", \"/page_data/\" → \"page_data\"\n *\n * Special handling:\n * - `null`, `undefined`, or empty/whitespace-only string returns the default endpoint\n *\n * @param endpoint - The endpoint name to normalize\n * @param defaultEndpoint - Default endpoint to use when input is null/undefined/empty (defaults to DEFAULT_PAGE_DATA_ENDPOINT)\n * @returns Normalized endpoint string (never false, page data is always needed)\n */\nexport function normalizePageDataEndpoint(\n  endpoint: string | null | undefined,\n  defaultEndpoint: string = DEFAULT_PAGE_DATA_ENDPOINT,\n): string {\n  // null, undefined, or empty/whitespace-only string → use default\n  const trimmed = (endpoint ?? '').trim();\n  let normalized = trimmed.length === 0 ? defaultEndpoint : trimmed;\n\n  // Collapse multiple consecutive slashes to a single slash\n  normalized = normalized.replace(/\\/+/g, '/');\n\n  // Remove leading slash if present\n  if (normalized.startsWith('/')) {\n    normalized = normalized.slice(1);\n  }\n\n  // Remove trailing slash if present\n  if (normalized.endsWith('/')) {\n    normalized = normalized.slice(0, -1);\n  }\n\n  return normalized;\n}\n\n/**\n * Result of classifying a request path for API/page-data handling\n */\nexport interface RequestClassification {\n  /** True if path starts with the API prefix (e.g., /api/...) */\n  isAPI: boolean;\n  /** True if path is a page-data endpoint (e.g., /api/v1/page_data/home) */\n  isPageData: boolean;\n}\n\n/**\n * Classify a request URL to determine if it's an API request and/or a page-data request.\n *\n * Page data endpoints are always registered under the API prefix, so isPageData will\n * only be true when isAPI is also true.\n *\n * @param url - Request URL (may include query string, which will be stripped internally)\n * @param apiPrefix - The API prefix to match against (e.g., \"/api\"), or false if API is disabled\n * @param pageDataEndpoint - The page data endpoint name (e.g., \"page_data\")\n * @returns Object with isAPI and isPageData booleans\n *\n * @example\n * classifyRequest('/api/v1/page_data/home', '/api', 'page_data')\n * // => { isAPI: true, isPageData: true }\n *\n * classifyRequest('/api/users?id=123', '/api', 'page_data')\n * // => { isAPI: true, isPageData: false }\n *\n * classifyRequest('/about', '/api', 'page_data')\n * // => { isAPI: false, isPageData: false }\n *\n * classifyRequest('/api/users', false, 'page_data')\n * // => { isAPI: false, isPageData: false } (API disabled)\n */\nexport function classifyRequest(\n  url: string,\n  apiPrefix: string | false,\n  pageDataEndpoint: string,\n): RequestClassification {\n  // IMPORTANT: apiPrefix should be pre-normalized (e.g., \"/api\" with leading slash, no trailing)\n  // or false if API is disabled\n\n  // IMPORTANT: pageDataEndpoint should be pre-normalized (e.g., \"page_data\" with no slashes)\n  // Callers are responsible for normalizing these values once at startup\n\n  // Extract pathname (strip query string if present)\n  const rawPath = url.split('?')[0];\n\n  // If API is disabled (prefix is false), nothing is an API request\n  if (apiPrefix === false) {\n    return { isAPI: false, isPageData: false };\n  }\n\n  // Check if this is an API request (path starts with prefix)\n  // Special case: \"/\" prefix means ALL paths are API paths\n  const isRootPrefix = apiPrefix === '/';\n  const isAPI = isRootPrefix\n    ? rawPath.startsWith('/')\n    : !!apiPrefix &&\n      (rawPath.startsWith(apiPrefix + '/') || rawPath === apiPrefix);\n\n  // Page data is always under API prefix, so if not API, can't be page data\n  if (!isAPI) {\n    return { isAPI: false, isPageData: false };\n  }\n\n  // Strip API prefix and check for page data endpoint pattern\n  // Matches: /{pageDataEndpoint} or /v{n}/{pageDataEndpoint}\n  // For root prefix, we don't strip anything (pathAfterPrefix starts with /)\n  const pathAfterPrefix = isRootPrefix\n    ? rawPath\n    : rawPath.slice(apiPrefix.length);\n\n  // Page data path pattern: /{pageDataEndpoint} (e.g., \"/page_data\")\n  const pageDataPath = '/' + pageDataEndpoint;\n\n  // Check direct match: /{pageDataEndpoint} or /{pageDataEndpoint}/...\n  let isPageData =\n    pathAfterPrefix === pageDataPath ||\n    pathAfterPrefix.startsWith(pageDataPath + '/');\n\n  // If not matched, check versioned pattern: /v{digits}/{pageDataEndpoint}...\n  if (!isPageData && pathAfterPrefix.startsWith('/v')) {\n    // Scan for digits after /v (e.g., /v1 → i=3, /v100 → i=5)\n    // Using manual charCodeAt parsing instead of regex for better performance\n    // since this runs on every request in hot path\n    let i = 2; // start after '/v'\n\n    while (\n      i < pathAfterPrefix.length &&\n      pathAfterPrefix.charCodeAt(i) >= 48 && // '0'\n      pathAfterPrefix.charCodeAt(i) <= 57 // '9'\n    ) {\n      i++;\n    }\n\n    // Valid version needs at least one digit (/v1, /v100 — not just /v)\n    if (i > 2) {\n      const pathAfterVersion = pathAfterPrefix.slice(i);\n      isPageData =\n        pathAfterVersion === pageDataPath ||\n        pathAfterVersion.startsWith(pageDataPath + '/');\n    }\n  }\n\n  return { isAPI, isPageData };\n}\n\n/**\n * Creates a default JSON error response using the envelope pattern.\n * Used by both APIServer and SSRServer for consistent error handling.\n * @param request - The Fastify request object\n * @param error - The error that occurred\n * @param isDevelopment - Whether running in development mode\n * @param apiPrefix - API prefix for request classification (e.g., \"/api\"), or false if API is disabled\n * @param pageDataEndpoint - Page data endpoint name (e.g., \"page_data\")\n * @returns JSON error response object\n */\n\nexport function createDefaultAPIErrorResponse(\n  HelpersClass: APIResponseHelpersClass,\n  request: FastifyRequest,\n  error: Error,\n  isDevelopment: boolean,\n  apiPrefix: string | false,\n  pageDataEndpoint: string,\n): unknown {\n  const { isPageData } = classifyRequest(\n    request.url,\n    apiPrefix,\n    pageDataEndpoint,\n  );\n\n  const statusCode =\n    (error as Error & { statusCode?: number }).statusCode || 500;\n  const errorCode =\n    statusCode === 500 ? 'internal_server_error' : 'request_error';\n  const errorMessage = isDevelopment ? error.message : 'Internal Server Error';\n  const errorDetails = isDevelopment ? { stack: error.stack } : undefined;\n\n  if (isPageData) {\n    return HelpersClass.createPageErrorResponse({\n      request,\n      statusCode,\n      errorCode,\n      errorMessage,\n      errorDetails,\n      pageMetadata: {\n        title: 'Error',\n        description: 'An error occurred while processing your request',\n      },\n    });\n  }\n\n  return HelpersClass.createAPIErrorResponse({\n    request,\n    statusCode,\n    errorCode,\n    errorMessage,\n    errorDetails,\n  });\n}\n\n/**\n * Creates a default JSON 404 not-found response using the envelope pattern.\n * Used by both APIServer and SSRServer for consistent 404 handling.\n * @param request - The Fastify request object\n * @param apiPrefix - API prefix for request classification (e.g., \"/api\"), or false if API is disabled\n * @param pageDataEndpoint - Page data endpoint name (e.g., \"page_data\")\n * @returns JSON 404 response object\n */\nexport function createDefaultAPINotFoundResponse(\n  HelpersClass: APIResponseHelpersClass,\n  request: FastifyRequest,\n  apiPrefix: string | false,\n  pageDataEndpoint: string,\n): unknown {\n  const { isPageData } = classifyRequest(\n    request.url,\n    apiPrefix,\n    pageDataEndpoint,\n  );\n\n  const statusCode = 404;\n\n  if (isPageData) {\n    return HelpersClass.createPageErrorResponse({\n      request,\n      statusCode,\n      errorCode: 'not_found',\n      errorMessage: 'Page Not Found',\n      pageMetadata: {\n        title: 'Not Found',\n        description: 'The requested page could not be found',\n      },\n    });\n  }\n\n  return HelpersClass.createAPIErrorResponse({\n    request,\n    statusCode,\n    errorCode: 'not_found',\n    errorMessage: 'Resource Not Found',\n  });\n}\n\n/**\n * Creates a default JSON 503 shutdown response using the envelope pattern.\n * Used by both APIServer and SSRServer for requests that arrive while closing.\n * @param request - The Fastify request object\n * @param isPageData - Whether the request targets the page-data endpoint\n * @returns JSON 503 response object\n */\nexport function createDefaultAPIClosingResponse(\n  HelpersClass: APIResponseHelpersClass,\n  request: FastifyRequest,\n  isPageData: boolean,\n): unknown {\n  const statusCode = 503;\n  const errorCode = 'service_unavailable';\n  const errorMessage = 'Server is shutting down';\n\n  if (isPageData) {\n    return HelpersClass.createPageErrorResponse({\n      request,\n      statusCode,\n      errorCode,\n      errorMessage,\n      pageMetadata: {\n        title: 'Service Unavailable',\n        description: 'The server is shutting down. Please try again shortly.',\n      },\n    });\n  }\n\n  return HelpersClass.createAPIErrorResponse({\n    request,\n    statusCode,\n    errorCode,\n    errorMessage,\n  });\n}\n\n/**\n * Creates the default web 503 shutdown response.\n * Used by API, SSR, static, and redirect servers for web requests while closing.\n */\nexport function createDefaultWebClosingResponse(): WebResponse {\n  return {\n    contentType: 'html',\n    content: generateDefault503ClosingPage(),\n    statusCode: 503,\n  };\n}\n\ntype ClosingFunctionHandlerType = 'api' | 'web';\n\ntype ClosingHandler<M extends BaseMeta = BaseMeta> =\n  | APIClosingHandlerFn<M>\n  | WebClosingHandlerFn\n  | SplitClosingHandler<M>;\n\ninterface ClosingResponseConfig<M extends BaseMeta = BaseMeta> {\n  handler?: ClosingHandler<M>;\n  functionHandlerType: ClosingFunctionHandlerType;\n  serverLabel: string;\n  HelpersClass: APIResponseHelpersClass;\n  apiPrefix: string | false;\n  pageDataEndpoint: string;\n}\n\ninterface ClosingResponseContext<\n  M extends BaseMeta = BaseMeta,\n> extends ClosingResponseConfig<M> {\n  request: FastifyRequest;\n  reply: FastifyReply;\n}\n\n/**\n * Resolves the payload sent by registerClosingResponseHook when the server is\n * stopping. The resolver sets status/cache/content headers on the reply and\n * returns the body that the hook will pass to sendClosingPayload().\n */\nexport async function resolveClosingResponse<M extends BaseMeta = BaseMeta>({\n  request,\n  reply,\n  handler,\n  functionHandlerType,\n  serverLabel,\n  HelpersClass,\n  apiPrefix,\n  pageDataEndpoint,\n}: ClosingResponseContext<M>): Promise<unknown> {\n  // Closing responses need the same API/page-data classification as normal\n  // errors so defaults and split handlers return the expected response shape.\n  const { isAPI, isPageData } = classifyRequest(\n    request.url,\n    apiPrefix,\n    pageDataEndpoint,\n  );\n\n  if (handler) {\n    try {\n      if (isSplitHandler<Partial<SplitClosingHandler<M>>>(handler)) {\n        // Split form lets mixed API + web servers customize each handler\n        // independently. Missing handlers fall through to Unirend defaults.\n        if (isAPI && handler.api) {\n          const apiResponse = await Promise.resolve(\n            handler.api(request, isPageData),\n          );\n\n          const statusCode = apiResponse.status_code || 503;\n          reply.code(statusCode).header('Cache-Control', 'no-store');\n          return apiResponse;\n        }\n\n        if (!isAPI && handler.web) {\n          const webResponse = await Promise.resolve(handler.web(request));\n\n          return prepareWebResponse(reply, webResponse, 503);\n        }\n      } else if (functionHandlerType === 'api' && isAPI) {\n        // Function form follows the server's primary response type. APIServer\n        // uses API envelopes, while non-API web requests fall through to the\n        // default web response unless split form provides a web handler.\n        const apiHandler = handler as APIClosingHandlerFn<M>;\n        const apiResponse = await Promise.resolve(\n          apiHandler(request, isPageData),\n        );\n\n        const statusCode = apiResponse.status_code || 503;\n        reply.code(statusCode).header('Cache-Control', 'no-store');\n        return apiResponse;\n      } else if (functionHandlerType === 'web' && !isAPI) {\n        // SSR/static/redirect servers use web responses for function form.\n        // API/page-data requests fall through to the default API envelope unless\n        // split form provides an API handler.\n        const webHandler = handler as WebClosingHandlerFn;\n        const webResponse = await Promise.resolve(webHandler(request));\n\n        return prepareWebResponse(reply, webResponse, 503);\n      }\n    } catch (handlerError) {\n      request.log.error(\n        { err: handlerError, method: request.method, url: request.url },\n        `[${serverLabel}] Custom closing handler failed`,\n      );\n    }\n  }\n\n  // No custom handler matched, or the matched handler failed. API and page-data\n  // requests fall back to the standard error envelope so clients see the same\n  // shape as other API failures.\n  if (isAPI && apiPrefix) {\n    const response = createDefaultAPIClosingResponse(\n      HelpersClass,\n      request,\n      isPageData,\n    );\n\n    const statusCode =\n      (response as { status_code?: number }).status_code || 503;\n\n    reply.code(statusCode).header('Cache-Control', 'no-store');\n\n    return response;\n  }\n\n  // Web requests fall back to the built-in HTML 503 page. This also covers\n  // servers with API handling disabled because classifyRequest reports them as\n  // non-API requests.\n  return prepareWebResponse(reply, createDefaultWebClosingResponse(), 503);\n}\n\nexport function sendClosingPayload(\n  reply: FastifyReply,\n  payload: unknown,\n): FastifyReply {\n  if (\n    payload !== null &&\n    typeof payload === 'object' &&\n    !Buffer.isBuffer(payload)\n  ) {\n    return reply.type('application/json').send(JSON.stringify(payload));\n  }\n\n  return reply.send(payload);\n}\n\nexport function registerClosingResponseHook(\n  fastify: FastifyInstance,\n  isStopping: () => boolean,\n  responseConfig: ClosingResponseConfig,\n): void {\n  fastify.addHook('onRequest', (request, reply, done) => {\n    if (!isStopping()) {\n      done();\n      return;\n    }\n\n    Promise.resolve(\n      resolveClosingResponse({ ...responseConfig, request, reply }),\n    )\n      .then((payload) => {\n        sendClosingPayload(reply, payload);\n      })\n      .catch(done);\n  });\n}\n\n/**\n * Check if a handler is the split form (object with api and/or web).\n * Either handler can be optional - missing handlers fall through to defaults.\n */\nexport function isSplitHandler<T extends { api?: unknown; web?: unknown }>(\n  handler: unknown,\n): handler is T {\n  if (handler === null || typeof handler !== 'object') {\n    return false;\n  }\n\n  // It's split form if it has at least one of api/web as a function\n  const obj = handler as Record<string, unknown>;\n  const hasAPIHandler = 'api' in obj && typeof obj.api === 'function';\n  const hasWebHandler = 'web' in obj && typeof obj.web === 'function';\n\n  return hasAPIHandler || hasWebHandler;\n}\n\nexport function prepareWebResponse(\n  reply: FastifyReply,\n  response: WebResponse,\n  defaultStatusCode: number,\n): unknown {\n  const statusCode = response.statusCode ?? defaultStatusCode;\n  reply.code(statusCode).header('Cache-Control', 'no-store');\n\n  // Set Content-Type but do NOT call reply.send() here.\n  // The callers returns the content so wrapThenable makes exactly one reply.send() call.\n  if (response.contentType === 'json') {\n    reply.type('application/json');\n  } else if (response.contentType === 'html') {\n    reply.type('text/html');\n  } else {\n    reply.type('text/plain');\n  }\n\n  return response.content;\n}\n\nconst DEFERRED_REPLY_ACTION_SENTINEL = Symbol('unirend.deferred-reply-action');\n\n/**\n * Wrap a route handler to throw a helpful error if reply.send() is called.\n *\n * Async route handlers must return the payload directly instead of calling\n * reply.send(). In Fastify 5, returning a value from an async handler causes\n * wrapThenable to call reply.send(payload) exactly once. If the handler also\n * calls reply.send() manually, wrapThenable fires a second send while the\n * async onSend pipeline is still pending — causing an ERR_HTTP_HEADERS_SENT\n * crash or silent response corruption.\n *\n * Correct pattern:\n *   reply.code(201).header('X-Foo', 'bar');\n *   return { your: 'data' };  // ✓\n *\n * Forbidden pattern:\n *   return reply.send({ your: 'data' });  // ✗ — double-send race\n *\n * Special case:\n *   return reply.redirect('/login');  // ✓ — redirect is normalized to headers\n *   and status only so Fastify still performs the single final send itself\n *\n *   return reply.callNotFound();  // ✓ — delegates the remainder of the request\n *   to Fastify's not-found pipeline, which owns the final send\n */\nfunction guardRouteHandler(handler: RouteHandler): RouteHandler {\n  return async function guardedHandler(\n    request: FastifyRequest,\n    reply: FastifyReply,\n  ): Promise<unknown> {\n    // Temporarily replace reply.send so any call from inside the handler body\n    // throws immediately with a helpful message. We restore it in `finally` so\n    // that wrapThenable can still call reply.send(returnValue) after the handler\n    // resolves — that single wrapThenable-driven send is the correct path.\n    const originalSend = (\n      reply as unknown as { send: (...args: unknown[]) => unknown }\n    ).send.bind(reply);\n    const originalRedirect = reply.redirect.bind(reply);\n    const originalCallNotFound = reply.callNotFound.bind(reply);\n    let deferredActionKind: 'redirect' | 'callNotFound' | null = null;\n    let deferredRedirectURL: string | undefined;\n    let deferredRedirectCode: number | undefined;\n    let handlerResult: unknown;\n\n    (reply as unknown as { send: unknown }).send = function (\n      // eslint-disable-next-line @typescript-eslint/no-unused-vars\n      ...args: unknown[]\n    ) {\n      throw new Error(\n        'Do not call reply.send() inside a unirend plugin route handler.\\n' +\n          'Set status and headers with reply.code() / reply.header(), then return the payload:\\n' +\n          '  ✓  reply.code(201); return { ok: true };\\n' +\n          '  ✗  return reply.send({ ok: true });  // causes double-send race in Fastify 5\\n\\n' +\n          'reply.send() is only safe inside Fastify lifecycle hooks (addHook), not in route handlers.',\n      );\n    };\n\n    reply.redirect = ((url: string, code?: number) => {\n      // Record the redirect intent but defer the real Fastify redirect call\n      // until after this wrapper restores the original reply methods.\n      deferredActionKind = 'redirect';\n      deferredRedirectURL = url;\n      deferredRedirectCode = code;\n      return DEFERRED_REPLY_ACTION_SENTINEL as unknown as FastifyReply;\n    }) as typeof reply.redirect;\n\n    reply.callNotFound = (() => {\n      // Record the delegation intent but defer the real Fastify helper until\n      // after this wrapper restores the original reply methods.\n      deferredActionKind = 'callNotFound';\n      return DEFERRED_REPLY_ACTION_SENTINEL as unknown as FastifyReply;\n    }) as typeof reply.callNotFound;\n\n    try {\n      handlerResult = await (\n        handler as (\n          this: unknown,\n          req: FastifyRequest,\n          reply: FastifyReply,\n        ) => unknown\n      ).call(this, request, reply);\n    } finally {\n      // Restore so wrapThenable's reply.send(returnedPayload) works normally.\n      (reply as unknown as { send: unknown }).send = originalSend;\n      reply.redirect = originalRedirect;\n      reply.callNotFound = originalCallNotFound;\n    }\n\n    const actionKind = deferredActionKind;\n\n    if (actionKind) {\n      if (handlerResult !== DEFERRED_REPLY_ACTION_SENTINEL) {\n        const delegatedHelper =\n          actionKind === 'redirect'\n            ? 'reply.redirect()'\n            : 'reply.callNotFound()';\n\n        throw new Error(\n          `When using ${delegatedHelper} inside a unirend plugin route handler, return it immediately.\\n` +\n            'Do not continue execution or return a payload after delegating the response.',\n        );\n      }\n\n      switch (actionKind) {\n        case 'redirect':\n          return originalRedirect(\n            deferredRedirectURL as string,\n            deferredRedirectCode,\n          );\n        case 'callNotFound':\n          return originalCallNotFound();\n      }\n    }\n\n    return handlerResult;\n  };\n}\n\n/**\n * Creates a controlled wrapper around the Fastify instance\n * This prevents plugins from accessing dangerous methods\n * @param fastifyInstance The real Fastify instance\n * @param shouldDisableRootWildcard Whether to disable root wildcard routes (e.g., \"*\" or \"/*\")\n * @returns Controlled interface for plugins\n */\n\nexport function createControlledInstance(\n  fastifyInstance: FastifyInstance,\n  shouldDisableRootWildcard: boolean,\n  apiShortcuts: unknown,\n  pageDataHandlerShortcuts: unknown,\n  apiResponseHelpersClass: APIResponseHelpersClass,\n): PluginHostInstance {\n  const earlyResponseHooks = new Set<FastifyHookName>([\n    'onRequest',\n    'preValidation',\n    'preHandler',\n  ]);\n\n  return {\n    register: <Options extends Record<string, unknown> = Record<string, never>>(\n      plugin: FastifyPluginAsync<Options> | FastifyPluginCallback<Options>,\n      opts?: Options,\n    ) => {\n      // Note: Fastify's register method has complex overloads that don't align perfectly\n      // with our simplified generic constraints. These casts are necessary for compatibility.\n      return fastifyInstance.register(\n        plugin as Parameters<typeof fastifyInstance.register>[0],\n        opts as Parameters<typeof fastifyInstance.register>[1],\n      ) as unknown as Promise<void>;\n    },\n    addHook: (\n      hookName: FastifyHookName,\n      handler: (\n        request: FastifyRequest,\n        reply: FastifyReply,\n        ...args: unknown[]\n      ) => unknown,\n    ) => {\n      // Prevent plugins from overriding critical hooks\n      if (hookName === 'onRoute' || hookName.includes('*')) {\n        throw new Error(\n          'Plugins cannot register catch-all route hooks that would conflict with SSR',\n        );\n      }\n      // Fastify has two incompatible hook completion styles:\n      // - async/promise hooks finish when the promise resolves\n      // - callback hooks finish when done() is called\n      //\n      // We wrap plugin hooks so plain sync handlers don't hang. For early\n      // request hooks, use callback-style wrapping because these hooks may\n      // intentionally terminate the request with reply.send()/reply.redirect().\n      // Calling done() after that would continue the lifecycle and can trigger\n      // double-send/header-sent errors, so the wrapper only calls done() when\n      // the hook did not already send a response.\n      const wrappedHandler = earlyResponseHooks.has(hookName)\n        ? (\n            request: FastifyRequest,\n            reply: FastifyReply,\n            done: (error?: Error) => void,\n          ) => {\n            // Fastify's sent/header flags can lag behind an in-progress\n            // reply.send()/reply.redirect() on the live server, so track calls\n            // made inside the hook body directly.\n            const replyWithSend = reply as unknown as {\n              send: (...args: unknown[]) => unknown;\n            };\n            const originalSend = replyWithSend.send;\n            let didSend = false;\n\n            replyWithSend.send = function (\n              this: FastifyReply,\n              ...args: unknown[]\n            ) {\n              didSend = true;\n              return originalSend.apply(this, args);\n            };\n\n            const restoreSend = () => {\n              replyWithSend.send = originalSend;\n            };\n\n            const didAlreadySend = () =>\n              didSend || reply.sent || reply.raw.headersSent;\n\n            try {\n              const result = handler(request, reply);\n\n              // Async early hooks are allowed. Wait for them and then advance\n              // only if they did not send the response while awaiting.\n              if (\n                result &&\n                typeof (result as Promise<unknown>).then === 'function'\n              ) {\n                void (result as Promise<unknown>).then(\n                  () => {\n                    restoreSend();\n                    if (!didAlreadySend()) {\n                      done();\n                    }\n                  },\n                  (error: unknown) => {\n                    restoreSend();\n                    done(\n                      error instanceof Error ? error : new Error(String(error)),\n                    );\n                  },\n                );\n                return;\n              }\n\n              // Sync early hooks that sent a response are complete. Sync early\n              // hooks that only mutated request/reply still need done().\n              restoreSend();\n              if (!didAlreadySend()) {\n                done();\n              }\n            } catch (error) {\n              restoreSend();\n              done(error instanceof Error ? error : new Error(String(error)));\n            }\n          }\n        : async (\n            request: FastifyRequest,\n            reply: FastifyReply,\n            ...args: unknown[]\n          ) => {\n            // Later hooks do not control routing continuation in the same way,\n            // so the simple async wrapper is enough to support sync handlers.\n            return handler(request, reply, ...args);\n          };\n      return fastifyInstance.addHook(\n        hookName as Parameters<typeof fastifyInstance.addHook>[0],\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument\n        wrappedHandler as any,\n      );\n    },\n    decorate: (property: string, value: unknown) =>\n      fastifyInstance.decorate(property, value),\n    decorateRequest: (property: string, value: unknown) =>\n      fastifyInstance.decorateRequest(property, value),\n    decorateReply: (property: string, value: unknown) =>\n      fastifyInstance.decorateReply(property, value),\n    hasDecoration: (property: string) =>\n      Object.prototype.hasOwnProperty.call(\n        fastifyInstance as unknown as Record<string, unknown>,\n        property,\n      ),\n    getDecoration: <T = unknown>(property: string): T | undefined =>\n      (fastifyInstance as unknown as Record<string, unknown>)[property] as\n        | T\n        | undefined,\n    route: (opts: SafeRouteOptions) => {\n      // Prevent catch-all routes that would conflict with SSR\n      if (opts.url === '*' || opts.url.includes('*')) {\n        throw new Error(\n          'Plugins cannot register catch-all routes that would conflict with SSR rendering',\n        );\n      }\n      // Note: SafeRouteOptions may not perfectly match Fastify's RouteOptions interface.\n      // This cast ensures compatibility with Fastify's internal route registration.\n      return fastifyInstance.route({\n        ...(opts as Parameters<typeof fastifyInstance.route>[0]),\n        handler: guardRouteHandler(opts.handler),\n      });\n    },\n    get: (path: string, handler: RouteHandler) => {\n      if (shouldDisableRootWildcard && (path === '*' || path === '/*')) {\n        throw new Error(\n          'Plugins cannot register root wildcard GET routes that would conflict with SSR rendering',\n        );\n      }\n\n      return fastifyInstance.get(path, guardRouteHandler(handler));\n    },\n    post: (path: string, handler: RouteHandler) =>\n      fastifyInstance.post(path, guardRouteHandler(handler)),\n    put: (path: string, handler: RouteHandler) =>\n      fastifyInstance.put(path, guardRouteHandler(handler)),\n    delete: (path: string, handler: RouteHandler) =>\n      fastifyInstance.delete(path, guardRouteHandler(handler)),\n    patch: (path: string, handler: RouteHandler) =>\n      fastifyInstance.patch(path, guardRouteHandler(handler)),\n    log: fastifyInstance.log,\n    api: apiShortcuts,\n    pageDataHandler: pageDataHandlerShortcuts,\n    APIResponseHelpers: apiResponseHelpersClass,\n  };\n}\n\n/**\n * Wrap Fastify's reply object with a constrained, safe surface for handlers.\n */\nexport function createControlledReply(\n  request: FastifyRequest,\n  reply: FastifyReply,\n): ControlledReply {\n  return {\n    header: (name: string, value: string) => {\n      reply.header(name, value);\n    },\n    getHeader: (name: string) =>\n      reply.getHeader(name) as unknown as\n        | string\n        | number\n        | string[]\n        | undefined,\n    getHeaders: () => reply.getHeaders() as unknown as Record<string, unknown>,\n    removeHeader: (name: string) => {\n      reply.removeHeader(name);\n    },\n    hasHeader: (name: string) => reply.hasHeader(name),\n    get sent() {\n      return reply.sent;\n    },\n    raw: {\n      get destroyed() {\n        return reply.raw.destroyed;\n      },\n    },\n    _sendErrorEnvelope: async (statusCode, errorEnvelope) => {\n      // ControlledReply does not expose reply.send()/raw writes to handlers,\n      // but framework-owned helpers still need one sanctioned way to send an\n      // early error envelope. Keep that capability internal here so handlers\n      // cannot treat ControlledReply like a full FastifyReply.\n      await sendRawErrorEnvelopeResponse(\n        request,\n        reply,\n        statusCode,\n        errorEnvelope,\n      );\n    },\n    setCookie:\n      typeof (reply as unknown as { setCookie?: unknown }).setCookie ===\n      'function'\n        ? (\n            reply as unknown as {\n              setCookie: (\n                name: string,\n                value: string,\n                options?: CookieSerializeOptions,\n              ) => void;\n            }\n          ).setCookie\n        : undefined,\n    cookie:\n      typeof (reply as unknown as { cookie?: unknown }).cookie === 'function'\n        ? (\n            reply as unknown as {\n              cookie: (\n                name: string,\n                value: string,\n                options?: CookieSerializeOptions,\n              ) => void;\n            }\n          ).cookie\n        : undefined,\n    clearCookie:\n      typeof (reply as unknown as { clearCookie?: unknown }).clearCookie ===\n      'function'\n        ? (\n            reply as unknown as {\n              clearCookie: (\n                name: string,\n                options?: CookieSerializeOptions,\n              ) => void;\n            }\n          ).clearCookie\n        : undefined,\n    unsignCookie:\n      typeof (reply as unknown as { unsignCookie?: unknown }).unsignCookie ===\n      'function'\n        ? (\n            reply as unknown as {\n              unsignCookie: (\n                value: string,\n              ) =>\n                | { valid: true; renew: boolean; value: string }\n                | { valid: false; renew: false; value: null };\n            }\n          ).unsignCookie\n        : undefined,\n    signCookie:\n      typeof (reply as unknown as { signCookie?: unknown }).signCookie ===\n      'function'\n        ? (\n            reply as unknown as {\n              signCookie: (value: string) => string;\n            }\n          ).signCookie\n        : undefined,\n  };\n}\n\n/**\n * Validates that no API or page data loader handlers were registered when API handling is disabled.\n * This prevents configuration errors where handlers are registered but won't be used.\n *\n * @param apiRoutes API routes helper instance\n * @param pageDataHandlers Page data loader handlers helper instance\n * @throws Error if handlers were registered when API is disabled\n */\nexport function validateNoHandlersWhenAPIDisabled(\n  apiRoutes: { hasRegisteredHandlers: () => boolean },\n  pageDataHandlers: { hasRegisteredHandlers: () => boolean },\n): void {\n  const hasAPIRoutes = apiRoutes.hasRegisteredHandlers();\n  const hasPageDataHandlers = pageDataHandlers.hasRegisteredHandlers();\n\n  if (hasAPIRoutes || hasPageDataHandlers) {\n    const registered = [\n      hasAPIRoutes ? 'API routes' : null,\n      hasPageDataHandlers ? 'page data loader handlers' : null,\n    ]\n      .filter(Boolean)\n      .join(' and ');\n\n    throw new Error(\n      `Cannot start server: ${registered} were registered but API handling is disabled ` +\n        `(apiEndpoints.apiEndpointPrefix is false). Either enable API handling by setting ` +\n        `apiEndpointPrefix to a value like '/api', or remove the registered handlers.`,\n    );\n  }\n}\n\n/**\n * Validates plugin dependencies and registers a plugin with metadata tracking\n *\n * @param registeredPlugins Array of already registered plugin metadata (mutated by this function)\n * @param pluginResult The result returned by the plugin (either PluginMetadata or void)\n * @throws Error if plugin dependencies are not met or duplicate plugin names\n */\nexport function validateAndRegisterPlugin(\n  registeredPlugins: PluginMetadata[],\n  pluginResult: PluginMetadata | void,\n): void {\n  // If plugin returned no metadata, nothing to track\n  if (!pluginResult) {\n    return;\n  }\n\n  // Check for duplicate plugin names\n  if (registeredPlugins.some((p) => p.name === pluginResult.name)) {\n    throw new Error(\n      `Plugin with name \"${pluginResult.name}\" is already registered`,\n    );\n  }\n\n  // Check dependencies\n  if (pluginResult.dependsOn) {\n    const dependencies = Array.isArray(pluginResult.dependsOn)\n      ? pluginResult.dependsOn\n      : [pluginResult.dependsOn];\n\n    const registeredNames = new Set(registeredPlugins.map((p) => p.name));\n\n    for (const dep of dependencies) {\n      if (!registeredNames.has(dep)) {\n        throw new Error(\n          `Plugin \"${pluginResult.name}\" depends on \"${dep}\" which has not been registered yet. ` +\n            `Plugins must be registered in dependency order.`,\n        );\n      }\n    }\n  }\n\n  // Add to registered plugins list\n  registeredPlugins.push(pluginResult);\n}\n\n/**\n * Decorates requests with a resolved client IP, set once per request.\n * Always sets clientIP to request.ip first (which respects fastifyOptions.trustProxy),\n * then overwrites it with the awaited return value of getClientIP if provided.\n *\n * If getClientIP throws or rejects, clientIP retains request.ip and the error\n * propagates as a normal 500.\n */\nexport function registerClientIPDecoration(\n  fastify: FastifyInstance,\n  getClientIP:\n    | ((request: FastifyRequest) => string | Promise<string>)\n    | undefined,\n): void {\n  fastify.decorateRequest('clientIP', '');\n\n  fastify.addHook('onRequest', async (request, _reply) => {\n    request.clientIP = request.ip;\n\n    if (getClientIP) {\n      request.clientIP = await getClientIP(request);\n    }\n  });\n}\n\n/**\n * Builds Fastify-compatible HTTPS options from the shared HTTPSOptions type.\n * Handles extracting the `sni` field and converting it to a Node.js `SNICallback`\n * that supports both sync and async user functions.\n *\n * Used by APIServer, and SSRServer to avoid duplicating\n * the SNI callback adapter logic.\n *\n * @param httpsConfig - The HTTPSOptions from server configuration\n * @returns A plain object suitable for passing as `fastifyOptions.https`\n */\nexport function buildFastifyHTTPSOptions(\n  httpsConfig: HTTPSOptions,\n): Record<string, unknown> {\n  const { sni, ...httpsOptions } = httpsConfig;\n\n  // Build HTTPS options for Fastify\n  const fastifyHTTPSOptions: Record<string, unknown> = {\n    ...httpsOptions,\n  };\n\n  // Add SNI callback if provided\n  if (sni) {\n    fastifyHTTPSOptions.SNICallback = (\n      servername: string,\n      callback?: (err: Error | null, ctx?: unknown) => void,\n    ) => {\n      // Call user's SNI function (supports both sync and async)\n      const result = sni(servername);\n\n      // Handle Promise return\n      if (result && typeof result === 'object' && 'then' in result) {\n        if (callback) {\n          result\n            .then((ctx: unknown) => {\n              callback(null, ctx);\n            })\n            .catch((error: unknown) => {\n              callback(\n                error instanceof Error ? error : new Error(String(error)),\n              );\n            });\n        } else {\n          return result;\n        }\n      } else if (callback) {\n        callback(null, result);\n      } else {\n        return result;\n      }\n    };\n  }\n\n  return fastifyHTTPSOptions;\n}\n\n/**\n * Normalizes a CDN base URL by stripping a trailing slash, so the value is\n * consistent whether it comes from server config, per-request override, or\n * the injected `window.__CDN_BASE_URL__` global read by the client.\n *\n * Must be applied before the URL is placed into `unirendContext.cdnBaseURL`\n * so that `useCDNBaseURL()` returns the same value on server and client —\n * avoiding React hydration mismatches when a trailing-slash URL is configured.\n */\nexport function normalizeCDNBaseURL(url: string | undefined): string {\n  if (!url) {\n    return '';\n  }\n\n  return url.endsWith('/') ? url.slice(0, -1) : url;\n}\n\n/**\n * Computes domain info from a request hostname using the public suffix list.\n * - `hostname`: the bare hostname (port stripped)\n * - `rootDomain`: the apex domain without a leading dot (e.g. `'example.com'`),\n *   or empty string for localhost / IP addresses where no root domain can be resolved.\n */\nexport function computeDomainInfo(hostname: string): DomainInfo {\n  // Use parseHostHeader for correct IPv6 bracket handling\n  // e.g. '[::1]:3000' → '::1', 'localhost:3000' → 'localhost'\n  const { domain: host } = parseHostHeader(hostname);\n  const root = getDomain(host) ?? '';\n\n  return {\n    hostname: host,\n    // Empty string when domain-utils cannot resolve a root (localhost, raw IP, etc.)\n    rootDomain: root,\n  };\n}\n","import type { FastifyRequest, FastifyReply } from 'fastify';\nimport type { OutgoingHttpHeaders } from 'node:http';\nimport type {\n  APIErrorResponse,\n  BaseMeta,\n  PageErrorResponse,\n} from '../api-envelope/api-envelope-types';\n\n/**\n * Sends an error envelope immediately via the raw/hijacked response path.\n *\n * This is shared by controlled reply wrappers and the public helper fallback so\n * both paths apply the same CORS/header logic before writing the final JSON\n * body directly to the socket.\n */\nexport async function sendRawErrorEnvelopeResponse(\n  request: FastifyRequest,\n  reply: FastifyReply,\n  statusCode: number,\n  errorResponse: APIErrorResponse<BaseMeta> | PageErrorResponse<BaseMeta>,\n): Promise<void> {\n  const body = JSON.stringify(errorResponse);\n\n  // Raw/hijacked sends bypass Fastify's normal onSend pipeline, so any\n  // plugin-managed headers that still matter here must be applied explicitly\n  // before we snapshot reply.getHeaders() for writeHead(). Keep this ahead of\n  // reply.hijack() so header-application failures still propagate through\n  // Fastify's normal error handling instead of failing after raw ownership has\n  // already been taken.\n  await request.applyCORSHeaders?.(reply);\n\n  // Keep Fastify's reply state aligned with the status/content-type we are\n  // about to send even though the final body write happens on reply.raw.\n  reply.code(statusCode);\n  reply.type('application/json; charset=utf-8');\n  reply.header('Content-Length', String(Buffer.byteLength(body)));\n\n  // Hijack before writeHead() so Fastify does not attempt its own send path\n  // after this helper has already fully terminated the response.\n  reply.hijack();\n  reply.raw.writeHead(statusCode, reply.getHeaders() as OutgoingHttpHeaders);\n\n  // Error envelopes are always JSON here, so a single buffered end() keeps the\n  // transport simple and makes the termination point explicit.\n  reply.raw.end(request.method === 'HEAD' ? undefined : body);\n}\n","import type { FastifyRequest, FastifyReply } from 'fastify';\nimport type { OutgoingHttpHeaders } from 'node:http';\nimport fs from 'fs';\nimport path from 'path';\nimport crypto from 'node:crypto';\nimport { pipeline } from 'node:stream/promises';\nimport { LRUCache, type LRUCacheChangeEvent } from 'lifecycleion/lru-cache';\nimport type { StaticContentRouterOptions, FolderConfig } from '../types';\nimport { addToVaryHeader } from './http-header-utils';\nimport {\n  buildEncodedETag,\n  compressPayload,\n  isCompressibleContentType,\n  matchesIfNoneMatch,\n  normalizeResponseCompressionOptions,\n  selectResponseEncoding,\n} from './response-compression';\n\n/**\n * Minimal stat info interface with only the properties we actually use\n */\ninterface MinimalStatInfo {\n  isFile: boolean;\n  size: number;\n  mtime: Date;\n  // eslint-disable-next-line @typescript-eslint/naming-convention\n  mtimeMs: number;\n}\n\n/**\n * Negative cache entry type (for 404s and access errors)\n */\ninterface NegativeCacheEntry {\n  notFound: true;\n}\n\ntype CompressedVariantState =\n  | {\n      kind: 'compressed';\n      data: Buffer;\n    }\n  | {\n      kind: 'not-worth-it';\n    }\n  | {\n      kind: 'tombstone';\n    };\n\n/**\n * Combined type for stat cache entries\n */\ntype StatCacheEntry = MinimalStatInfo | NegativeCacheEntry | null;\n\n/**\n * Options for getFile() method\n */\nexport interface GetFileOptions {\n  /** Whether to detect immutable assets for cache control decisions */\n  shouldDetectImmutable?: boolean;\n  /** Optional ETag from client's If-None-Match header (for 304 optimization) */\n  clientETag?: string;\n  /** Accepted content encodings from the request for representation selection */\n  acceptEncoding?: string | string[];\n}\n\n/**\n * Options for creating a read stream (for range requests)\n */\nexport interface CreateStreamOptions {\n  /** Start byte position (inclusive) */\n  start?: number;\n  /** End byte position (inclusive) */\n  end?: number;\n}\n\n/**\n * Result from serveFile() indicating what action was taken\n */\nexport type ServeFileResult =\n  | { served: false; reason: 'not-found' }\n  | { served: false; reason: 'error'; error: Error }\n  | {\n      served: true;\n      statusCode:\n        | 200 // Full file served\n        | 206 // Partial content served\n        | 304 // Not modified\n        | 400 // Invalid range request\n        | 416; // Range not satisfiable\n    };\n\n/**\n * File content discriminated union - either buffered in memory or needs streaming\n */\nexport type FileContent =\n  | {\n      /** Content is buffered in memory (small files) */\n      shouldStream: false;\n      /** The file content buffer */\n      data: Buffer;\n    }\n  | {\n      /** Content needs to be streamed from disk (large files) */\n      shouldStream: true;\n      /** Factory function to create a read stream with optional range support */\n      createStream: (options?: CreateStreamOptions) => fs.ReadStream;\n    };\n\n/**\n * Internal logger object used by static content helpers.\n */\nexport type StaticContentWarnLoggerObject = {\n  warn: (obj: object, msg: string) => void;\n};\n\n/**\n * Result when file is not found (404)\n */\nexport interface FileNotFoundResult {\n  status: 'not-found';\n}\n\n/**\n * Result when an unexpected error occurs (500)\n */\nexport interface FileErrorResult {\n  status: 'error';\n  error: Error;\n}\n\n/**\n * Result when client's ETag matches (304 Not Modified)\n */\nexport interface FileNotModifiedResult {\n  status: 'not-modified';\n  /** Generated ETag for the selected response representation */\n  etag: string;\n  /** Last-Modified date as HTTP header string */\n  lastModified: string;\n  /** Selected content encoding, if a compressed representation was chosen */\n  contentEncoding?: 'br' | 'gzip';\n  /** Whether the response should include `Vary: Accept-Encoding` */\n  varyByAcceptEncoding: boolean;\n}\n\n/**\n * Result when file is found and should be served (200)\n */\nexport interface FileFoundResult {\n  status: 'ok';\n  /** File stats (size, modification time, etc.) */\n  stat: MinimalStatInfo;\n  /** Generated ETag for the selected response representation */\n  etag: string;\n  /** Base file ETag before representation-specific encoding suffixes */\n  baseETag: string;\n  /** Last-Modified date as HTTP header string */\n  lastModified: string;\n  /** MIME type based on file extension */\n  mimeType: string;\n  /** File content - either buffered or needs streaming */\n  content: FileContent;\n  /** Selected content encoding, if a compressed representation was chosen */\n  contentEncoding?: 'br' | 'gzip';\n  /** Whether the response should include `Vary: Accept-Encoding` */\n  varyByAcceptEncoding: boolean;\n  /** Whether this file appears to be fingerprinted/immutable (for aggressive caching) */\n  isImmutableAsset: boolean;\n}\n\nfunction waitForReadStreamOpen(stream: fs.ReadStream): Promise<void> {\n  if (\n    stream.pending === false ||\n    typeof (stream as fs.ReadStream & { fd?: number }).fd === 'number'\n  ) {\n    return Promise.resolve();\n  }\n\n  return new Promise((resolve, reject) => {\n    const onOpen = () => {\n      cleanup();\n      resolve();\n    };\n\n    const onError = (error: Error) => {\n      cleanup();\n      reject(error);\n    };\n\n    const cleanup = () => {\n      stream.off('open', onOpen);\n      stream.off('error', onError);\n    };\n\n    stream.once('open', onOpen);\n    stream.once('error', onError);\n  });\n}\n\n/**\n * Union type for all possible getFile() results\n */\nexport type FileResult =\n  | FileNotFoundResult\n  | FileErrorResult\n  | FileNotModifiedResult\n  | FileFoundResult;\n\n/**\n * Parse a Range header into [start, end] byte offsets (both inclusive).\n *\n * Supports:\n *   bytes=0-499      explicit range\n *   bytes=500-       from offset to end of file\n *   bytes=-500       last 500 bytes (suffix range)\n *\n * Returns [start, end] on success.\n * Returns 'malformed' (→ 400) for syntactically invalid headers (no bytes= prefix, bad spec format).\n * Returns 'unsatisfiable' (→ 416) for multipart ranges or ranges that exceed the file size.\n */\nfunction parseRange(\n  header: string,\n  fileSize: number,\n): [number, number] | 'malformed' | 'unsatisfiable' {\n  if (!header.startsWith('bytes=')) {\n    return 'malformed';\n  }\n\n  const spec = header.slice(6);\n\n  // Reject multipart ranges (satisfiable syntax, but unsupported)\n  if (spec.includes(',')) {\n    return 'unsatisfiable';\n  }\n\n  const match = /^(\\d*)-(\\d*)$/.exec(spec);\n\n  if (!match) {\n    return 'malformed';\n  }\n\n  const startStr = match[1];\n  const endStr = match[2];\n\n  if (startStr === '' && endStr === '') {\n    return 'malformed';\n  }\n\n  let start: number;\n  let end: number;\n\n  if (startStr === '') {\n    // Suffix range: bytes=-500 → last 500 bytes\n    const suffix = parseInt(endStr, 10);\n    start = Math.max(0, fileSize - suffix);\n    end = fileSize - 1;\n  } else if (endStr === '') {\n    // Open-ended range: bytes=500-\n    start = parseInt(startStr, 10);\n    end = fileSize - 1;\n  } else {\n    start = parseInt(startStr, 10);\n    end = parseInt(endStr, 10);\n  }\n\n  // Validate: start must be within file, start must not exceed end\n  if (start >= fileSize || start > end) {\n    return 'unsatisfiable';\n  }\n\n  // Clamp end to last valid byte\n  end = Math.min(end, fileSize - 1);\n\n  return [start, end];\n}\n\n/**\n * Encapsulates caching and serving of static content files.\n *\n * This class manages:\n * - Multiple LRU caches (ETag, file content, and file stats)\n * - Configuration for single asset and folder mappings\n * - Optimized file serving with HTTP caching headers\n * - Content-based ETags for small files, weak ETags for large files\n * - Automatic detection of immutable assets (fingerprinted files)\n *\n * Each instance maintains its own independent caches, allowing\n * multiple instances with different configurations.\n */\nexport class StaticContentCache {\n  // Normalized mappings (mutable to allow runtime updates)\n  private singleAssetMap: Map<string, string>; // URL path → filesystem path\n  private folderMap: Map<string, FolderConfig>; // URL prefix → folder config\n\n  // Cache configuration\n  private readonly smallFileMaxSize: number;\n  private readonly cacheControl: string;\n  private readonly immutableCacheControl: string;\n  private readonly negativeCacheTtl: number;\n  private readonly positiveCacheTtl: number;\n  private readonly compression: ReturnType<\n    typeof normalizeResponseCompressionOptions\n  >;\n\n  // LRU caches (all keyed by filesystem path)\n  private readonly etagCache: LRUCache<string, string>; // fs path → ETag\n  private readonly contentCache: LRUCache<string, Buffer>; // fs path → file content\n  // Keyed by fs path + BASE file ETag + encoding.\n  // The stored ETag component is the uncompressed file's identity; the\n  // representation-specific HTTP ETag is derived later by suffixing the\n  // encoding (e.g. \"--gzip\", \"--br\") when sending the response.\n  private readonly compressedVariantCache: LRUCache<\n    string,\n    CompressedVariantState\n  >; // fs path + base etag + encoding → compressed variant state\n  private readonly statCache: LRUCache<string, StatCacheEntry>; // fs path → file stats\n  // Reverse index of filesystem path -> compressed cache keys for that file.\n  // This lets invalidateFile() clear every cached compressed representation for\n  // a path without needing to know which base ETag variants are currently live.\n  private readonly compressedContentIndex: Map<string, Set<string>> = new Map();\n\n  // Optional logger\n  private readonly logger?: StaticContentWarnLoggerObject;\n\n  /**\n   * Creates a new StaticContentCache instance\n   *\n   * @param options Static content configuration (file mappings, cache settings, etc.)\n   * @param logger Optional logger (e.g., fastify.log) for error logging\n   */\n  constructor(\n    options: StaticContentRouterOptions,\n    logger?: StaticContentWarnLoggerObject,\n  ) {\n    const {\n      singleAssetMap = {},\n      folderMap = {},\n      smallFileMaxSize = 5 * 1024 * 1024, // 5 MB\n      cacheEntries = 100,\n      contentCacheMaxSize = 50 * 1024 * 1024, // 50 MB\n      statCacheEntries = 250,\n      negativeCacheTtl = 30 * 1000, // 30 seconds\n      positiveCacheTtl = 60 * 60 * 1000, // 1 hour\n      cacheControl = 'public, max-age=0, must-revalidate',\n      immutableCacheControl = 'public, max-age=31536000, immutable',\n      compression = true,\n    } = options;\n\n    this.smallFileMaxSize = smallFileMaxSize;\n    this.cacheControl = cacheControl;\n    this.immutableCacheControl = immutableCacheControl;\n    this.negativeCacheTtl = negativeCacheTtl;\n    this.positiveCacheTtl = positiveCacheTtl;\n    this.compression = normalizeResponseCompressionOptions(compression);\n    this.logger = logger;\n\n    // Normalize singleAssetMap\n    this.singleAssetMap = this.normalizeSingleAssetMap(singleAssetMap);\n\n    // Normalize folderMap\n    this.folderMap = this.normalizeFolderMap(folderMap);\n\n    // Initialize LRU caches\n    const defaultTtl = positiveCacheTtl > 0 ? positiveCacheTtl : undefined;\n\n    this.etagCache = new LRUCache<string, string>(cacheEntries, { defaultTtl });\n    this.contentCache = new LRUCache<string, Buffer>(cacheEntries, {\n      defaultTtl,\n      maxSize: contentCacheMaxSize,\n    });\n\n    this.compressedVariantCache = new LRUCache<string, CompressedVariantState>(\n      cacheEntries,\n      {\n        defaultTtl,\n        maxSize: contentCacheMaxSize,\n        // Keep the reverse index aligned when compressed variants disappear from\n        // the LRU on their own, not just when StaticContentCache deletes them.\n        onChange: (event) => this.handleCompressedVariantCacheChange(event),\n        onChangeReasons: ['evict', 'expired', 'delete', 'clear'],\n      },\n    );\n    this.statCache = new LRUCache<string, StatCacheEntry>(statCacheEntries, {\n      defaultTtl,\n    });\n  }\n\n  /**\n   * Gets file metadata and content with optimized caching\n   *\n   * This method handles all the core file operations and caching:\n   * - File stats caching to avoid repeated filesystem operations\n   * - ETag generation and caching (content-based for small files, weak for large files)\n   * - Small file content caching in memory for performance\n   * - Proper MIME type detection\n   * - Immutable asset detection for cache control decisions\n   * - Optional short-circuit if client ETag matches (for 304 responses)\n   *\n   * Useful for both HTTP serving (via serveFile) and programmatic access\n   *\n   * @param resolvedPath The absolute path to the file\n   * @param options Optional configuration for file retrieval\n   * @returns Result with status: 'not-found', 'error', 'not-modified', or 'ok'\n   */\n  public async getFile(\n    resolvedPath: string,\n    options?: GetFileOptions,\n  ): Promise<FileResult> {\n    // Wrap entire operation in try-catch to return errors instead of throwing\n    try {\n      const {\n        shouldDetectImmutable = false,\n        clientETag,\n        acceptEncoding,\n      } = options || {};\n\n      // Step 1: Resolve file metadata, preferably from the stat cache.\n      // Try to get file stats from cache to avoid filesystem operations\n      const cachedStat = this.statCache.get(resolvedPath);\n\n      // Variable that will hold our file information\n      let stat: MinimalStatInfo | null = null;\n\n      // Handle cached entries (LRU handles TTL expiration internally)\n      if (cachedStat) {\n        if ('notFound' in cachedStat) {\n          // File is known to not exist\n          return { status: 'not-found' };\n        } else if (cachedStat !== null) {\n          // We have a valid cached stat, use it\n          stat = cachedStat;\n        }\n      }\n\n      // If stats aren't cached, retrieve them from filesystem\n      if (!stat) {\n        try {\n          const fullStat = await fs.promises.stat(resolvedPath);\n\n          // Only serve regular files, not directories or special files\n          if (!fullStat.isFile()) {\n            // Cache as negative entry with specific TTL\n            this.statCache.set(\n              resolvedPath,\n              { notFound: true },\n              this.negativeCacheTtl,\n            );\n\n            return { status: 'not-found' };\n          }\n\n          // Extract only the properties we need to minimize memory usage\n          stat = {\n            isFile: true, // We know it's a file at this point\n            size: fullStat.size,\n            mtime: fullStat.mtime,\n            // eslint-disable-next-line @typescript-eslint/naming-convention\n            mtimeMs: fullStat.mtimeMs,\n          };\n\n          // Cache the minimal stats for future requests\n          // The TTL was already set when creating the cache\n          this.statCache.set(resolvedPath, stat);\n        } catch (error) {\n          // File doesn't exist or can't be accessed\n          // Cache as negative entry with specific TTL\n          this.statCache.set(\n            resolvedPath,\n            { notFound: true },\n            this.negativeCacheTtl,\n          );\n\n          // Log unexpected errors (like permission issues) but not 'file not found' errors\n          // ENOENT is expected for files that don't exist and shouldn't be logged\n          if (\n            error instanceof Error &&\n            'code' in error &&\n            (error as NodeJS.ErrnoException).code !== 'ENOENT' &&\n            this.logger\n          ) {\n            this.logger.warn(\n              {\n                err: error,\n                path: resolvedPath,\n              },\n              'Unexpected error accessing static file',\n            );\n          }\n\n          return { status: 'not-found' };\n        }\n      }\n\n      // Generate Last-Modified header from file modification time\n      const lastModified = stat.mtime.toUTCString();\n\n      // Step 2: Derive the base file validator used for identity responses and\n      // as the source for encoding-specific ETags.\n      // Try to get ETag from cache\n      let etag = this.etagCache.get(resolvedPath);\n\n      // Generate a new ETag if not cached\n      if (!etag) {\n        // For small files: create content-based strong ETag using SHA-256\n        if (stat.size <= this.smallFileMaxSize) {\n          // Try to get file content from cache for ETag generation\n          let buf = this.contentCache.get(resolvedPath);\n\n          // If content not cached, read and cache it\n          if (!buf) {\n            try {\n              buf = await fs.promises.readFile(resolvedPath);\n              this.contentCache.set(resolvedPath, buf);\n            } catch (error) {\n              // Log unexpected errors when reading file content\n              // Cast to NodeJS.ErrnoException to access error codes if needed\n              const fsError = error as NodeJS.ErrnoException;\n\n              if (this.logger) {\n                this.logger.warn(\n                  {\n                    err: fsError,\n                    path: resolvedPath,\n                    code: fsError.code,\n                  },\n                  'Error reading static file content',\n                );\n              }\n\n              // Re-throw to be handled by outer error handling\n              throw error;\n            }\n          }\n\n          // Generate a strong hash-based ETag from file content\n          const hash = crypto.createHash('sha256').update(buf).digest('base64');\n          etag = `\"${hash}\"`;\n        } else {\n          // For large files: create a weak ETag based on size and modification time\n          // Using W/ prefix to indicate a weak validator per RFC specs\n          etag = `W/\"${stat.size}-${Number(stat.mtimeMs)}\"`;\n        }\n\n        // Cache the ETag for future requests\n        this.etagCache.set(resolvedPath, etag);\n      }\n\n      // Determine if we should use immutable cache headers based on the filename pattern\n      // A fingerprinted file typically has a name like main.a1b2c3.js or chunk-5a7d9c8b.js\n      const isImmutableAsset =\n        shouldDetectImmutable && this.isImmutableAsset(resolvedPath);\n\n      // Get MIME type based on file extension\n      const mimeType = this.getMimeType(resolvedPath);\n\n      // Step 3: Load the file body as either a cached/buffered payload or a\n      // stream factory, depending on size.\n      // Build content discriminated union based on file size\n      // Small files: buffered in memory (get from cache or read from disk as fallback)\n      // Large files: must be streamed from disk with factory function that supports ranges\n      let fileContent: FileContent;\n\n      if (stat.size <= this.smallFileMaxSize) {\n        // Try to get content from cache first\n        let content = this.contentCache.get(resolvedPath);\n\n        // If not in cache, read from disk\n        if (!content) {\n          try {\n            content = await fs.promises.readFile(resolvedPath);\n            this.contentCache.set(resolvedPath, content);\n          } catch (error) {\n            // File disappeared or became inaccessible\n            const fsError = error as NodeJS.ErrnoException;\n\n            // If file no longer exists, treat as not-found\n            if (fsError.code === 'ENOENT') {\n              // Invalidate caches since file disappeared\n              this.statCache.set(\n                resolvedPath,\n                { notFound: true },\n                this.negativeCacheTtl,\n              );\n\n              this.etagCache.delete(resolvedPath);\n              this.contentCache.delete(resolvedPath);\n\n              return { status: 'not-found' };\n            }\n\n            // Other errors - log and re-throw\n            if (this.logger) {\n              this.logger.warn(\n                {\n                  err: fsError,\n                  path: resolvedPath,\n                  code: fsError.code,\n                },\n                'Error reading static file content',\n              );\n            }\n\n            throw error;\n          }\n        }\n\n        fileContent = { shouldStream: false, data: content };\n      } else {\n        fileContent = {\n          shouldStream: true,\n          createStream: (options) => fs.createReadStream(resolvedPath, options),\n        };\n      }\n\n      // Step 4: Choose the response representation before checking\n      // If-None-Match so gzip/br variants get their own ETags and 304 behavior.\n      const shouldVaryByAcceptEncoding =\n        this.compression.enabled &&\n        !fileContent.shouldStream &&\n        isCompressibleContentType(mimeType) &&\n        fileContent.data.length >= this.compression.threshold;\n\n      const selectedEncoding = shouldVaryByAcceptEncoding\n        ? selectResponseEncoding(acceptEncoding, this.compression.preferBrotli)\n        : null;\n      let responseEncoding: 'br' | 'gzip' | undefined;\n\n      // Step 5: For buffered responses, reuse or build a compressed variant if\n      // the negotiated encoding is smaller than the original bytes.\n      if (!fileContent.shouldStream && selectedEncoding) {\n        const compressedCacheKey = this.getCompressedCacheKey(\n          resolvedPath,\n          etag,\n          selectedEncoding,\n        );\n\n        const cachedVariant =\n          this.compressedVariantCache.get(compressedCacheKey);\n\n        let compressed =\n          cachedVariant?.kind === 'compressed' ? cachedVariant.data : undefined;\n        const isCompressedNotWorthIt = cachedVariant?.kind === 'not-worth-it';\n        const isCompressedTombstone = cachedVariant?.kind === 'tombstone';\n\n        if (!cachedVariant || isCompressedTombstone) {\n          // A plain cache miss may leave behind a stale reverse-index entry, so\n          // clean that up before recomputing. Tombstones stay tracked on\n          // purpose: they still represent a live variant key that should block\n          // immediate reinsertion after invalidateFile().\n          if (!isCompressedTombstone) {\n            this.untrackCompressedVariant(resolvedPath, compressedCacheKey);\n          }\n\n          compressed = await compressPayload(\n            fileContent.data,\n            selectedEncoding,\n            this.compression,\n          );\n        }\n\n        // Only keep an encoded variant if it is actually smaller than the\n        // original bytes. Otherwise prefer the identity response and clear any\n        // stale cached compressed entry for this representation.\n        if (compressed && compressed.length < fileContent.data.length) {\n          responseEncoding = selectedEncoding;\n\n          // Only store compressed bytes if we do not already have them cached\n          // and this exact variant key is not still inside the invalidation\n          // tombstone window from a recent invalidateFile() call.\n          if (\n            !this.compressedVariantCache.get(compressedCacheKey) &&\n            !isCompressedTombstone\n          ) {\n            // invalidateFile() leaves a short-lived tombstone for the old\n            // path + base ETag + encoding key so an older in-flight request\n            // cannot immediately repopulate a stale compressed variant.\n            this.compressedVariantCache.set(compressedCacheKey, {\n              kind: 'compressed',\n              data: compressed,\n            });\n\n            // The reverse index groups all compressed variants for a file path\n            // so invalidateFile() can clear them without knowing the current\n            // base ETag or encoding ahead of time.\n            const existingCompressedKeys =\n              this.compressedContentIndex.get(resolvedPath);\n\n            if (existingCompressedKeys) {\n              existingCompressedKeys.add(compressedCacheKey);\n            } else {\n              this.compressedContentIndex.set(\n                resolvedPath,\n                new Set([compressedCacheKey]),\n              );\n            }\n          }\n\n          fileContent = {\n            shouldStream: false,\n            data: compressed,\n          };\n        } else {\n          // Only record a fresh negative result. Reuse existing tombstones and\n          // prior \"not worth it\" decisions instead of resetting their state.\n          if (!isCompressedNotWorthIt && !isCompressedTombstone) {\n            // Record that this exact variant key negotiated successfully but\n            // did not beat the identity response, so future requests can skip\n            // recompressing until the file version changes or the entry expires.\n            this.compressedVariantCache.delete(compressedCacheKey);\n            this.compressedVariantCache.set(compressedCacheKey, {\n              kind: 'not-worth-it',\n            });\n\n            // Track negative variant decisions in the same reverse index so\n            // invalidateFile() can clear all per-variant state for the path.\n            const existingVariantKeys =\n              this.compressedContentIndex.get(resolvedPath);\n\n            if (existingVariantKeys) {\n              existingVariantKeys.add(compressedCacheKey);\n            } else {\n              this.compressedContentIndex.set(\n                resolvedPath,\n                new Set([compressedCacheKey]),\n              );\n            }\n          }\n        }\n      }\n\n      const responseETag = responseEncoding\n        ? buildEncodedETag(etag, responseEncoding)\n        : etag;\n\n      if (clientETag && matchesIfNoneMatch(clientETag, responseETag)) {\n        return {\n          status: 'not-modified',\n          etag: responseETag,\n          lastModified,\n          contentEncoding: responseEncoding,\n          varyByAcceptEncoding: shouldVaryByAcceptEncoding,\n        };\n      }\n\n      return {\n        status: 'ok',\n        stat,\n        etag: responseETag,\n        baseETag: etag,\n        lastModified,\n        mimeType,\n        content: fileContent,\n        contentEncoding: responseEncoding,\n        varyByAcceptEncoding: shouldVaryByAcceptEncoding,\n        isImmutableAsset,\n      };\n    } catch (error) {\n      // Return error status for unexpected errors\n      return {\n        status: 'error',\n        error: error instanceof Error ? error : new Error(String(error)),\n      };\n    }\n  }\n\n  /**\n   * Serves a static file via HTTP with conditional responses\n   *\n   * This is a thin HTTP wrapper around getFile() that handles:\n   * - HTTP 304 Not Modified responses when client cache is valid (If-None-Match)\n   * - HTTP 206 Partial Content responses for range requests\n   * - Proper HTTP headers (Cache-Control, ETag, Content-Type, Last-Modified, etc.)\n   * - Streaming large files vs sending cached buffers for small files\n   *\n   * The heavy lifting (file I/O, caching, ETag generation) is done by getFile()\n   *\n   * @param req The Fastify request object\n   * @param reply The Fastify reply object\n   * @param resolvedPath The absolute path to the file to be served\n   * @param options Optional configuration for file serving\n   * @returns Information about whether the file was served and what status code\n   */\n  public async serveFile(\n    req: FastifyRequest,\n    reply: FastifyReply,\n    resolvedPath: string,\n    options?: GetFileOptions,\n  ): Promise<ServeFileResult> {\n    // Raw static responses use reply.hijack() + writeHead(), which bypasses\n    // Fastify's normal onSend pipeline. Built-in CORS exposes a request-scoped\n    // helper so hijacked paths can still apply the same actual-response headers\n    // before we snapshot reply.getHeaders(). Keep this ahead of hijack so a\n    // CORS/config failure still propagates through normal Fastify error\n    // handling instead of failing after raw ownership has been taken.\n\n    // Get file with all metadata and caching\n    const result = await this.getFile(resolvedPath, {\n      ...options,\n      // Extract client cache validation header (ETag-based validation).\n      clientETag: req.headers['if-none-match'],\n      // Pass through Accept-Encoding so getFile() can choose the response\n      // representation before doing ETag/304 handling.\n      acceptEncoding: req.headers['accept-encoding'],\n    });\n\n    // Handle different result statuses\n    if (result.status === 'not-found') {\n      // File not found, return early (let hook fall through to 404)\n      return { served: false, reason: 'not-found' };\n    } else if (result.status === 'error') {\n      // Unexpected error occurred, return error info\n      return { served: false, reason: 'error', error: result.error };\n    }\n\n    // Mark only when static content is about to take ownership of the response.\n    // This keeps onResponse/access-log consumers from seeing failed pre-hijack\n    // stream opens as static asset responses.\n    const markStaticAsset = () => {\n      (req as { isStaticAsset?: boolean }).isStaticAsset = true;\n    };\n\n    if (result.status === 'not-modified') {\n      // Client's cache is still valid, send 304.\n      // Return HTTP 304 Not Modified response (no body). This saves bandwidth\n      // because the client reuses its cached representation.\n      //\n      // reply.hijack() bypasses Fastify's onSend pipeline (including the generic\n      // response-compression hook), so we write directly to the raw socket.\n      // onResponse hooks still fire because setupResponseListeners attaches to\n      // reply.raw.on('finish', ...) before any hooks run.\n\n      // Representation selection depends on Accept-Encoding, so advertise that\n      // caches must keep separate variants when compression is in play.\n      if (result.varyByAcceptEncoding) {\n        addToVaryHeader(reply, 'Accept-Encoding');\n      }\n\n      // A 304 carries metadata for the representation the client validated, so\n      // keep Content-Encoding aligned with the selected cached variant.\n      if (result.contentEncoding) {\n        reply.header('Content-Encoding', result.contentEncoding);\n      }\n\n      reply\n        .code(304)\n        .header('ETag', result.etag)\n        .header('Last-Modified', result.lastModified);\n\n      await req.applyCORSHeaders?.(reply);\n      markStaticAsset();\n      reply.hijack();\n      reply.raw.writeHead(304, reply.getHeaders() as OutgoingHttpHeaders);\n      reply.raw.end();\n\n      return { served: true, statusCode: 304 };\n    }\n\n    // File found (status === 'ok'), proceed with serving.\n    //\n    // reply.hijack() bypasses Fastify's onSend pipeline entirely, preventing\n    // the generic response-compression hook from re-processing a response whose\n    // representation (identity/gzip/br) and ETag were already finalized by\n    // getFile(). Without hijack(), the compression hook could re-compress an\n    // identity response and mutate the ETag the client already validated against.\n\n    // Determine Cache-Control header based on immutability\n    const headerCacheControl = result.isImmutableAsset\n      ? this.immutableCacheControl\n      : this.cacheControl;\n\n    // Representation selection depends on Accept-Encoding, so advertise that\n    // caches must keep separate variants when compression is in play.\n    if (result.varyByAcceptEncoding) {\n      addToVaryHeader(reply, 'Accept-Encoding');\n    }\n\n    // Only encoded representations send Content-Encoding; identity responses\n    // intentionally omit it even when compression was considered.\n    if (result.contentEncoding) {\n      reply.header('Content-Encoding', result.contentEncoding);\n    }\n\n    // Set common headers\n    reply\n      .header('Last-Modified', result.lastModified)\n      .header('ETag', result.etag)\n      .header('Cache-Control', headerCacheControl)\n      .type(result.mimeType);\n\n    // Only advertise range support for streaming files\n    // (files larger than smallFileMaxSize that are streamed from disk)\n    // Buffered files in memory don't support range requests\n    if (result.content.shouldStream) {\n      reply.header('Accept-Ranges', 'bytes');\n    }\n\n    // Check for Range header to handle partial content requests\n    const rangeHeader = req.headers.range;\n\n    // Handle range requests if present and file is being streamed\n    if (rangeHeader && result.content.shouldStream) {\n      const range = parseRange(rangeHeader, result.stat.size);\n\n      if (range === 'malformed') {\n        // Bad Request — syntactically invalid Range header\n        const body = JSON.stringify({ error: 'Invalid Range header' });\n        reply\n          .code(400)\n          .header('Cache-Control', 'no-store')\n          .type('application/json')\n          .header('Content-Length', String(Buffer.byteLength(body)));\n        await req.applyCORSHeaders?.(reply);\n        markStaticAsset();\n        reply.hijack();\n        reply.raw.writeHead(400, reply.getHeaders() as OutgoingHttpHeaders);\n        reply.raw.end(req.method === 'HEAD' ? undefined : body);\n        return { served: true, statusCode: 400 };\n      } else if (range === 'unsatisfiable') {\n        const body = JSON.stringify({ error: 'Range not satisfiable' });\n        reply\n          .code(416)\n          .header('Cache-Control', 'no-store')\n          .type('application/json')\n          .header('Content-Range', `bytes */${result.stat.size}`)\n          .header('Content-Length', String(Buffer.byteLength(body)));\n        await req.applyCORSHeaders?.(reply);\n        markStaticAsset();\n        reply.hijack();\n        reply.raw.writeHead(416, reply.getHeaders() as OutgoingHttpHeaders);\n        reply.raw.end(req.method === 'HEAD' ? undefined : body);\n        return { served: true, statusCode: 416 };\n      }\n\n      const [start, end] = range;\n      const chunkSize = end - start + 1;\n      const rangeStream = result.content.createStream({ start, end });\n\n      await waitForReadStreamOpen(rangeStream);\n\n      // Set headers for partial content response\n      reply\n        .code(206) // Partial Content\n        .header('Content-Range', `bytes ${start}-${end}/${result.stat.size}`)\n        .header('Content-Length', chunkSize.toString());\n\n      await req.applyCORSHeaders?.(reply);\n      markStaticAsset();\n      reply.hijack();\n      reply.raw.writeHead(206, reply.getHeaders() as OutgoingHttpHeaders);\n\n      // HEAD — headers are set; skip stream creation entirely (no fd opened, no disk I/O)\n      if (req.method === 'HEAD') {\n        reply.raw.end();\n        return { served: true, statusCode: 206 };\n      }\n\n      // Stream the requested range using factory function with range options\n      await pipeline(rangeStream, reply.raw);\n      return { served: true, statusCode: 206 };\n    }\n\n    // HEAD — set Content-Length from stat, then end without a body\n    if (req.method === 'HEAD') {\n      // When the response would be compressed, report the compressed size so\n      // the Content-Length matches what a GET would actually transfer.\n      // Compressed responses are always buffered (!shouldStream), so narrow first.\n      const headContentLength =\n        result.contentEncoding && !result.content.shouldStream\n          ? result.content.data.length\n          : result.stat.size;\n      reply.header('Content-Length', headContentLength.toString());\n      await req.applyCORSHeaders?.(reply);\n      markStaticAsset();\n      reply.hijack();\n      reply.raw.writeHead(\n        reply.statusCode,\n        reply.getHeaders() as OutgoingHttpHeaders,\n      );\n      reply.raw.end();\n      return { served: true, statusCode: 200 };\n    }\n\n    // Serve full file based on whether streaming is needed\n    const fullFileStream = result.content.shouldStream\n      ? result.content.createStream()\n      : null;\n\n    if (fullFileStream) {\n      await waitForReadStreamOpen(fullFileStream);\n    }\n\n    await req.applyCORSHeaders?.(reply);\n\n    if (!result.content.shouldStream) {\n      // reply.raw.end(buffer) does not get Fastify's normal Content-Length\n      // inference, so buffered 200 responses must set it explicitly here.\n      reply.header('Content-Length', result.content.data.length.toString());\n    }\n\n    markStaticAsset();\n    reply.hijack();\n    reply.raw.writeHead(200, reply.getHeaders() as OutgoingHttpHeaders);\n\n    if (result.content.shouldStream) {\n      // Large file — stream from disk directly to the socket.\n      // pipeline() propagates backpressure and destroys the stream on error.\n      await pipeline(fullFileStream as fs.ReadStream, reply.raw);\n    } else {\n      // Small file — send the buffered bytes in a single write.\n      reply.raw.end(result.content.data);\n    }\n\n    return { served: true, statusCode: 200 };\n  }\n\n  /**\n   * Replaces routing maps and clears all file caches in one shot.\n   *\n   * Use this after a full build has completed. Unlike `updateConfig`, this method\n   * makes no attempt at smart per-path invalidation — it simply replaces\n   * whichever maps you provide and wipes the content, stat, and ETag caches\n   * unconditionally, guaranteeing fresh reads for the next request.\n   *\n   * You may pass `singleAssetMap`, `folderMap`, or both. Omitted sections retain\n   * their current routing configuration. Pass an empty object (`{}`) for a\n   * section to clear all mappings in that section. All file caches are always\n   * cleared, regardless of which sections are provided — even when only\n   * `singleAssetMap` is passed, the rebuilt HTML pages likely reference JS/CSS\n   * bundles served from `folderMap` directories that were also regenerated in\n   * the same build step, so preserving folder caches would risk serving stale\n   * assets alongside fresh pages.\n   *\n   * For targeted cache invalidation (when URL-to-path mappings changed but\n   * file contents at those paths are unchanged), use `updateConfig` instead.\n   * Note: `updateConfig` does not detect in-place file content changes — it\n   * only tracks which filesystem paths entered or left the map.\n   *\n   * @param newConfig Sections to replace (at least one should be provided)\n   *\n   * @example\n   * ```typescript\n   * // After an SSG build completes (page map only):\n   * cache.replaceConfig({ singleAssetMap: await loadPageMap() });\n   *\n   * // After a build that changes both pages and asset folders:\n   * cache.replaceConfig({\n   *   singleAssetMap: await loadPageMap(),\n   *   folderMap: { '/assets/': { path: './dist/assets', detectImmutableAssets: true } },\n   * });\n   * ```\n   */\n  public replaceConfig(newConfig: {\n    singleAssetMap?: Record<string, string>;\n    folderMap?: Record<string, string | FolderConfig>;\n  }): void {\n    if (newConfig.singleAssetMap !== undefined) {\n      this.singleAssetMap = this.normalizeSingleAssetMap(\n        newConfig.singleAssetMap,\n      );\n    }\n\n    if (newConfig.folderMap !== undefined) {\n      this.folderMap = this.normalizeFolderMap(newConfig.folderMap);\n    }\n\n    // Always clear all caches — no smart invalidation.\n    // A build can change file contents in-place without renaming files,\n    // so preserving any cached content or stat data would risk stale reads.\n    this.clearCaches();\n  }\n\n  /**\n   * Evicts a single file's cached content, stat, and ETag without touching\n   * any URL-to-path mappings.\n   *\n   * Use this when you know a specific file changed on disk and want to force\n   * a fresh read on the next request — without flushing the entire cache.\n   * Works for files served via `singleAssetMap` or `folderMap`.\n   *\n   * The parameter is the **filesystem path** (as it appears in the cache key),\n   * not a URL.\n   *\n   * For `singleAssetMap` entries these are the absolute paths you\n   * provided.\n   *\n   * For folder-served files the cache key is the absolute path\n   * resolved at request time.\n   *\n   * @param fsPath Absolute filesystem path of the file to evict\n   *\n   * @example\n   * ```typescript\n   * // A file watcher detected /dist/about.html was rewritten:\n   * cache.invalidateFile('/dist/about.html');\n   * ```\n   */\n  public invalidateFile(fsPath: string): void {\n    this.etagCache.delete(fsPath);\n    this.contentCache.delete(fsPath);\n    this.statCache.delete(fsPath);\n    this.invalidateCompressedVariants(fsPath);\n  }\n\n  /**\n   * Clears all caches (useful for testing or cache invalidation)\n   */\n  public clearCaches(): void {\n    this.etagCache.clear();\n    this.contentCache.clear();\n    this.compressedVariantCache.clear();\n    this.statCache.clear();\n    this.compressedContentIndex.clear();\n  }\n\n  /**\n   * Gets statistics about cache usage\n   */\n  public getCacheStats() {\n    return {\n      etag: {\n        items: this.etagCache.size,\n        byteSize: this.etagCache.byteSize,\n      },\n      content: {\n        items: this.contentCache.size,\n        byteSize: this.contentCache.byteSize,\n      },\n      compressedVariants: {\n        items: this.compressedVariantCache.size,\n        byteSize: this.compressedVariantCache.byteSize,\n      },\n      stat: {\n        items: this.statCache.size,\n        byteSize: this.statCache.byteSize,\n      },\n    };\n  }\n\n  /**\n   * Updates the static content configuration at runtime with targeted cache\n   * invalidation — only evicting entries whose URL-to-path mapping changed.\n   *\n   * Use this when URL routing is changing but file contents at existing paths\n   * are unchanged (e.g., adding or removing pages without rebuilding assets).\n   * For post-build reloads where file contents may have changed, use\n   * `replaceConfig` instead.\n   *\n   * **Important:** When providing a section, you must provide the COMPLETE mapping for that section.\n   * - If you provide `singleAssetMap`, it replaces the entire single asset map\n   * - If you provide `folderMap`, it replaces the entire folder map\n   * - You can update one section, the other, or both\n   * - Omitted sections remain unchanged\n   *\n   * **Cache invalidation strategy:**\n   * - `singleAssetMap` changes: Only invalidates filesystem paths whose URL-to-path\n   *   *mapping* changed (added, removed, or pointed to a different file). Paths whose\n   *   mapping is unchanged are not evicted — this method has no visibility into whether\n   *   the file content on disk changed. If files were rebuilt in-place, use\n   *   `replaceConfig` instead.\n   * - `folderMap` changes: Clears all caches (folder changes are structural)\n   *\n   * @param newConfig Complete mapping(s) for the section(s) you want to update\n   *\n   * @example Update only single asset mappings\n   * ```typescript\n   * cache.updateConfig({\n   *   singleAssetMap: {\n   *     '/': './dist/index.html',\n   *     '/blog/new-post': './dist/blog/new-post.html'\n   *   }\n   * });\n   * ```\n   *\n   * @example Update only folder mappings\n   * ```typescript\n   * cache.updateConfig({\n   *   folderMap: {\n   *     '/assets': { path: './dist/assets', detectImmutableAssets: true }\n   *   }\n   * });\n   * ```\n   *\n   * @example Update both sections\n   * ```typescript\n   * cache.updateConfig({\n   *   singleAssetMap: { '/': './dist/index.html' },\n   *   folderMap: { '/assets': './dist/assets' }\n   * });\n   * ```\n   */\n  public updateConfig(newConfig: {\n    singleAssetMap?: Record<string, string>;\n    folderMap?: Record<string, string | FolderConfig>;\n  }): void {\n    // Handle singleAssetMap - smart invalidation of specific filesystem paths\n    if (newConfig.singleAssetMap !== undefined) {\n      const newMap = this.normalizeSingleAssetMap(newConfig.singleAssetMap);\n\n      // Track filesystem paths that need cache invalidation\n      const pathsToInvalidate = new Set<string>();\n\n      // Loop 1: Iterate over OLD map to find filesystem paths that are no longer in use\n      // Example: '/page' used to point to '/dist/old.html', now points to '/dist/new.html'\n      // Result: Invalidate '/dist/old.html' (the old file's cache is stale)\n      for (const [url, oldFsPath] of this.singleAssetMap.entries()) {\n        const newFsPath = newMap.get(url);\n\n        // If URL was removed OR now points to a different file, invalidate OLD filesystem path\n        if (newFsPath === undefined || newFsPath !== oldFsPath) {\n          pathsToInvalidate.add(oldFsPath);\n        }\n      }\n\n      // Loop 2: Iterate over NEW map to find filesystem paths that changed\n      // Example: '/page' used to point to '/dist/old.html', now points to '/dist/new.html'\n      // Result: Invalidate '/dist/new.html' (ensure fresh read from disk)\n      // IMPORTANT: This prevents serving stale cached data if the new file was already cached\n      // from a previous mapping (e.g., same file was previously mapped to a different URL)\n      for (const [url, newFsPath] of newMap.entries()) {\n        const oldFsPath = this.singleAssetMap.get(url);\n\n        // Only invalidate NEW filesystem path if URL existed before AND now points to different file\n        if (oldFsPath !== undefined && oldFsPath !== newFsPath) {\n          pathsToInvalidate.add(newFsPath);\n        }\n      }\n\n      // Replace the map\n      this.singleAssetMap = newMap;\n\n      // Invalidate caches for affected filesystem paths only\n      for (const fsPath of pathsToInvalidate) {\n        this.etagCache.delete(fsPath);\n        this.contentCache.delete(fsPath);\n        this.statCache.delete(fsPath);\n        this.invalidateCompressedVariants(fsPath);\n      }\n    }\n\n    // Handle folderMap - clear all caches only if it changed\n    if (newConfig.folderMap !== undefined) {\n      const newFolderMap = this.normalizeFolderMap(newConfig.folderMap);\n\n      // Check if folderMap actually changed\n      // Note: Can't just compare size - could have same number of folders but different prefixes/configs\n      let hasFolderMapChanged = false;\n\n      // Quick check: if sizes differ, it definitely changed\n      if (this.folderMap.size !== newFolderMap.size) {\n        hasFolderMapChanged = true;\n      } else {\n        // Sizes match - need to check if any prefix or config changed\n        for (const [prefix, config] of newFolderMap.entries()) {\n          const oldConfig = this.folderMap.get(prefix);\n\n          if (!oldConfig || !this.isSameFolderConfig(oldConfig, config)) {\n            hasFolderMapChanged = true;\n            break;\n          }\n        }\n      }\n\n      this.folderMap = newFolderMap;\n\n      // Only clear all caches if folderMap actually changed\n      // Folder mapping changes are rare and structural - clearing everything is safe\n      if (hasFolderMapChanged) {\n        this.clearCaches();\n      }\n    }\n  }\n\n  /**\n   * Handles an HTTP request by resolving the URL to a file path and serving it\n   *\n   * This is a convenience method that combines URL resolution with file serving.\n   * If no file matches the URL, it returns without sending a response (lets the hook fall through).\n   *\n   * @param rawURL The raw request URL (may include query string or hash)\n   * @param req The Fastify request object\n   * @param reply The Fastify reply object\n   * @returns Information about whether a file was served\n   */\n  public async handleRequest(\n    rawURL: string,\n    req: FastifyRequest,\n    reply: FastifyReply,\n  ): Promise<ServeFileResult> {\n    // Strip off query string, hash, etc., and ensure a single leading slash for matching\n    const cleanedURL = rawURL.split('?')[0].split('#')[0];\n    const url = cleanedURL.startsWith('/') ? cleanedURL : '/' + cleanedURL;\n\n    // Security: Reject URLs containing null bytes to prevent path truncation attacks\n    if (url.includes('\\0')) {\n      return { served: false, reason: 'not-found' };\n    }\n\n    let resolved = '';\n    let shouldDetectImmutable = false;\n\n    // 1. Try singleAssetMap first (exact URL → file)\n    if (this.singleAssetMap.has(url)) {\n      resolved = this.singleAssetMap.get(url) as string;\n    }\n    // 2. If not matched, try folderMap (URL prefix → directory)\n    else {\n      const folder = Array.from(this.folderMap.keys()).find((prefix) =>\n        url.startsWith(prefix),\n      );\n\n      if (folder) {\n        // Get resolved base folder and config\n        const folderConfig = this.folderMap.get(folder);\n\n        if (folderConfig) {\n          // Calculate file path relative to the matched prefix\n          const relativePath = url.slice(folder.length);\n\n          // Guard against absolute path behavior if a leading slash sneaks in\n          const safeRelativePath = relativePath.startsWith('/')\n            ? relativePath.slice(1)\n            : relativePath;\n\n          // Only allow files that don't contain '..' to prevent directory traversal\n          if (\n            !safeRelativePath.includes('../') &&\n            !safeRelativePath.includes('..\\\\')\n          ) {\n            resolved = path.join(folderConfig.path, safeRelativePath);\n            shouldDetectImmutable = folderConfig.detectImmutableAssets ?? false;\n          }\n        }\n      }\n    }\n\n    // If we found a file to serve, serve it\n    // otherwise: return not-found (let hook fall through)\n    if (resolved) {\n      return this.serveFile(req, reply, resolved, { shouldDetectImmutable });\n    }\n\n    return { served: false, reason: 'not-found' };\n  }\n\n  /**\n   * Normalizes single asset map keys to ensure leading slash\n   * Also validates against null bytes to prevent path injection\n   */\n  private normalizeSingleAssetMap(\n    singleAssetMap: Record<string, string>,\n  ): Map<string, string> {\n    const normalized = new Map<string, string>();\n\n    for (const [key, value] of Object.entries(singleAssetMap)) {\n      // Security: Skip entries with null bytes to prevent path truncation attacks\n      if (key.includes('\\0') || value.includes('\\0')) {\n        if (this.logger) {\n          this.logger.warn(\n            { key, value },\n            'Skipping singleAssetMap entry with null byte',\n          );\n        }\n\n        continue;\n      }\n\n      const normalizedKey = key.startsWith('/') ? key : '/' + key;\n      normalized.set(normalizedKey, value);\n    }\n\n    return normalized;\n  }\n\n  /**\n   * Normalizes folder map with proper prefix formatting\n   * Also validates against null bytes to prevent path injection\n   *\n   * Handles two config formats:\n   * 1. String shorthand: { \"/assets/\": \"/path/to/assets\" }\n   * 2. Full config object: { \"/assets/\": { path: \"/path/to/assets\", detectImmutableAssets: true } }\n   */\n  private normalizeFolderMap(\n    folderMap: Record<string, string | FolderConfig>,\n  ): Map<string, FolderConfig> {\n    const normalized = new Map<string, FolderConfig>();\n\n    for (const [prefix, config] of Object.entries(folderMap)) {\n      const normalizedPrefix = this.normalizePrefix(prefix);\n\n      // Security: Skip entries with null bytes to prevent path truncation attacks\n      const configPath = typeof config === 'string' ? config : config.path;\n      if (prefix.includes('\\0') || configPath.includes('\\0')) {\n        if (this.logger) {\n          this.logger.warn(\n            { prefix, configPath },\n            'Skipping folderMap entry with null byte',\n          );\n        }\n\n        continue;\n      }\n\n      // Handle string shorthand: just a directory path\n      if (typeof config === 'string') {\n        normalized.set(normalizedPrefix, {\n          path: config,\n          detectImmutableAssets: false,\n        });\n      } else {\n        // Handle full config object with optional detectImmutableAssets\n        normalized.set(normalizedPrefix, {\n          path: config.path,\n          detectImmutableAssets: config.detectImmutableAssets ?? false,\n        });\n      }\n    }\n\n    return normalized;\n  }\n\n  /**\n   * Normalizes URL prefix: ensures leading and trailing slash, collapses multiple slashes\n   */\n  private normalizePrefix(prefix: string): string {\n    let p = prefix || '/';\n\n    // Collapse multiple consecutive slashes into a single slash\n    p = p.replace(/\\/+/g, '/');\n\n    if (!p.startsWith('/')) {\n      p = '/' + p;\n    }\n\n    if (!p.endsWith('/')) {\n      p = p + '/';\n    }\n\n    return p;\n  }\n\n  /**\n   * Gets the MIME type for a file based on its extension\n   */\n  private getMimeType(filePath: string): string {\n    // Strip the leading dot from the extension\n    const ext = path.extname(filePath).toLowerCase().replace(/^\\./, '');\n\n    // Map common extensions to MIME types (alphabetical order)\n    const mimeTypes: Record<string, string> = {\n      css: 'text/css',\n      gif: 'image/gif',\n      html: 'text/html',\n      ico: 'image/x-icon',\n      jpeg: 'image/jpeg',\n      jpg: 'image/jpeg',\n      js: 'application/javascript',\n      json: 'application/json',\n      mp4: 'video/mp4',\n      pdf: 'application/pdf',\n      png: 'image/png',\n      svg: 'image/svg+xml',\n      txt: 'text/plain',\n      webmanifest: 'application/manifest+json',\n      xml: 'application/xml',\n    };\n\n    return mimeTypes[ext] || 'application/octet-stream';\n  }\n\n  /**\n   * Compares two FolderConfig objects for equality\n   * Dynamically checks all properties so we don't need to update this if FolderConfig changes\n   */\n  private isSameFolderConfig(a: FolderConfig, b: FolderConfig): boolean {\n    const keysA = Object.keys(a) as (keyof FolderConfig)[];\n    const keysB = Object.keys(b) as (keyof FolderConfig)[];\n\n    // Different number of keys means they're not equal\n    if (keysA.length !== keysB.length) {\n      return false;\n    }\n\n    // Check all keys from a (sufficient now since lengths match)\n    return keysA.every((key) => a[key] === b[key]);\n  }\n\n  /**\n   * Checks if a file appears to be fingerprinted/immutable based on filename\n   *\n   * Detects common build tool fingerprinting patterns:\n   * - .{hash}.{ext} format (e.g., main.a1b2c3d4.js, styles.CTpDmzGw.css)\n   * - -{hash}.{ext} format (e.g., chunk-a1b2c3d4.js, vendor-5f8e9a2b.js)\n   *\n   * Hash must be at least 6 alphanumeric characters\n   *\n   * @param filePath The file path to check\n   * @returns True if the file appears to be fingerprinted\n   */\n  private isImmutableAsset(filePath: string): boolean {\n    const fileBasename = path.basename(filePath);\n\n    // Check for fingerprint patterns:\n    // 1. .{hash}.{ext} pattern (e.g., main.CTpDmzGw.js)\n    // 2. -{hash}.{ext} pattern (e.g., chunk-CTpDmzGw.js)\n    return (\n      /\\.[A-Za-z0-9]{6,}\\./.test(fileBasename) ||\n      /-[A-Za-z0-9]{6,}\\./.test(fileBasename)\n    );\n  }\n\n  private getCompressedCacheKey(\n    resolvedPath: string,\n    etag: string,\n    encoding: string,\n  ): string {\n    return `${resolvedPath}::${etag}::${encoding}`;\n  }\n\n  private invalidateCompressedVariants(fsPath: string): void {\n    // Leave a short-lived tombstone for each invalidated compressed variant so\n    // an older in-flight request cannot immediately repopulate the same stale\n    // path + base ETag + encoding entry after invalidateFile() runs.\n    const keys = this.compressedContentIndex.get(fsPath);\n\n    if (!keys) {\n      return;\n    }\n\n    for (const key of keys) {\n      // Replace the current variant state with a short-lived tombstone so an\n      // older in-flight request cannot immediately repopulate the same stale\n      // compressed variant after invalidateFile() runs.\n      this.compressedVariantCache.set(key, { kind: 'tombstone' }, 5 * 1000);\n    }\n\n    this.compressedContentIndex.delete(fsPath);\n  }\n\n  private handleCompressedVariantCacheChange(\n    event: LRUCacheChangeEvent<string, CompressedVariantState>,\n  ): void {\n    if (\n      event.reason !== 'evict' &&\n      event.reason !== 'expired' &&\n      event.reason !== 'delete' &&\n      event.reason !== 'clear'\n    ) {\n      return;\n    }\n\n    // The compressed LRU is keyed by path + base ETag + encoding, but the\n    // reverse index is keyed only by filesystem path.\n    this.untrackCompressedVariantByKey(event.key);\n  }\n\n  private untrackCompressedVariantByKey(cacheKey: string): void {\n    // Compressed cache keys are stored as path + base ETag + encoding, so drop\n    // the final two segments to recover the filesystem path used by the index.\n    const keyParts = cacheKey.split('::');\n    const fsPath = keyParts.slice(0, -2).join('::');\n\n    this.untrackCompressedVariant(fsPath, cacheKey);\n  }\n\n  private untrackCompressedVariant(fsPath: string, cacheKey: string): void {\n    // Missing index state is harmless here. This map only helps invalidate all\n    // variant keys for a file path later, it is not consulted when choosing\n    // what bytes or variant state to serve for the current request.\n    const existing = this.compressedContentIndex.get(fsPath);\n\n    if (!existing) {\n      return;\n    }\n\n    existing.delete(cacheKey);\n\n    // Remove the path entry entirely once no compressed variants remain for it.\n    if (existing.size === 0) {\n      this.compressedContentIndex.delete(fsPath);\n    }\n  }\n}\n","import type { FastifyReply } from 'fastify';\n\n/**\n * Add one or more values to the Vary header without duplicates.\n */\nexport function addToVaryHeader(\n  reply: FastifyReply,\n  ...values: string[]\n): void {\n  const existing = reply.getHeader('Vary');\n  const current = Array.isArray(existing)\n    ? existing.join(', ')\n    : ((existing ?? '') as string);\n\n  const vary = new Set(\n    current\n      .split(',')\n      .map((header) => header.trim())\n      .filter(Boolean),\n  );\n\n  for (const value of values) {\n    vary.add(value);\n  }\n\n  reply.header('Vary', Array.from(vary).join(', '));\n}\n","import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';\nimport { brotliCompress, constants as zlibConstants, gzip } from 'node:zlib';\nimport { promisify } from 'node:util';\nimport type { ResponseCompressionOptions } from '../types';\nimport { addToVaryHeader } from './http-header-utils';\n\nconst gzipAsync = promisify(gzip);\nconst brotliCompressAsync = promisify(brotliCompress);\n\nexport type ResponseEncoding = 'br' | 'gzip';\n\nexport interface NormalizedResponseCompressionOptions {\n  enabled: boolean;\n  threshold: number;\n  preferBrotli: boolean;\n  brotliQuality: number;\n  gzipLevel: number;\n}\n\nconst DEFAULT_OPTIONS: NormalizedResponseCompressionOptions = {\n  enabled: true,\n  threshold: 1024,\n  preferBrotli: true,\n  brotliQuality: 4,\n  gzipLevel: 6,\n};\n\nconst COMPRESSIBLE_CONTENT_TYPE_PREFIXES = [\n  'text/',\n  'application/json',\n  'application/ld+json',\n  'application/manifest+json',\n  'application/javascript',\n  'text/javascript',\n  'application/xml',\n  'text/xml',\n  'application/xhtml+xml',\n  'application/rss+xml',\n  'application/atom+xml',\n  'image/svg+xml',\n];\n\n/**\n * Normalize the public boolean/object config into one internal shape so the\n * hot path does not need to branch on user-facing option forms.\n */\nexport function normalizeResponseCompressionOptions(\n  options: boolean | ResponseCompressionOptions | undefined,\n): NormalizedResponseCompressionOptions {\n  if (options === false) {\n    return {\n      ...DEFAULT_OPTIONS,\n      enabled: false,\n    };\n  }\n\n  if (options === true || options === undefined) {\n    return { ...DEFAULT_OPTIONS };\n  }\n\n  return {\n    enabled: options.enabled ?? DEFAULT_OPTIONS.enabled,\n    threshold: options.threshold ?? DEFAULT_OPTIONS.threshold,\n    preferBrotli: options.preferBrotli ?? DEFAULT_OPTIONS.preferBrotli,\n    brotliQuality: options.brotliQuality ?? DEFAULT_OPTIONS.brotliQuality,\n    gzipLevel: options.gzipLevel ?? DEFAULT_OPTIONS.gzipLevel,\n  };\n}\n\n/**\n * Minimal content-type allowlist for generic response compression.\n *\n * Static file handling uses MIME metadata from the file layer, but the\n * server-level onSend hook needs a straightforward check based only on the\n * response header that was already chosen.\n */\nexport function isCompressibleContentType(\n  contentType: string | undefined,\n): boolean {\n  if (!contentType) {\n    return false;\n  }\n\n  const normalized = contentType.split(';')[0]?.trim().toLowerCase();\n\n  if (!normalized) {\n    return false;\n  }\n\n  return COMPRESSIBLE_CONTENT_TYPE_PREFIXES.some((prefix) =>\n    normalized.startsWith(prefix),\n  );\n}\n\n/**\n * Parse Accept-Encoding into a simple encoding -> q-value map.\n *\n * This intentionally supports the small subset we need: direct `br`, `gzip`,\n * and wildcard fallback. We do not need full RFC-grade preference sorting for\n * the current use case.\n */\nfunction parseAcceptEncoding(\n  acceptEncoding: string | string[] | undefined,\n): Map<string, number> {\n  const header = Array.isArray(acceptEncoding)\n    ? acceptEncoding.join(',')\n    : acceptEncoding;\n  const values = new Map<string, number>();\n\n  if (!header) {\n    return values;\n  }\n\n  for (const part of header.split(',')) {\n    const [encodingRaw, ...params] = part.trim().split(';');\n\n    if (!encodingRaw) {\n      continue;\n    }\n\n    let quality = 1;\n\n    for (const param of params) {\n      const [key, value] = param.trim().split('=');\n\n      if (key === 'q' && value) {\n        const parsed = Number.parseFloat(value);\n\n        if (!Number.isNaN(parsed)) {\n          quality = parsed;\n        }\n      }\n    }\n\n    values.set(encodingRaw.toLowerCase(), quality);\n  }\n\n  return values;\n}\n\n/**\n * Choose the best supported encoding for the current request.\n *\n * Preference is controlled by config (`preferBrotli`), while q-values still\n * gate whether an encoding is allowed at all.\n */\nexport function selectResponseEncoding(\n  acceptEncoding: string | string[] | undefined,\n  shouldPreferBrotli: boolean,\n): ResponseEncoding | null {\n  const parsed = parseAcceptEncoding(acceptEncoding);\n  const brQuality = parsed.get('br') ?? parsed.get('*') ?? 0;\n  const gzipQuality = parsed.get('gzip') ?? parsed.get('*') ?? 0;\n\n  if (brQuality <= 0 && gzipQuality <= 0) {\n    return null;\n  }\n\n  if (brQuality > gzipQuality) {\n    return 'br';\n  }\n\n  if (gzipQuality > brQuality) {\n    return 'gzip';\n  }\n\n  return shouldPreferBrotli\n    ? brQuality > 0\n      ? 'br'\n      : 'gzip'\n    : gzipQuality > 0\n      ? 'gzip'\n      : 'br';\n}\n\n/**\n * Derive a representation-specific ETag from a base file/response ETag.\n *\n * This lets compressed and identity responses vary independently while still\n * preserving the original validator as the underlying content identity.\n */\nexport function buildEncodedETag(\n  etag: string,\n  encoding: ResponseEncoding,\n): string {\n  const isWeakPrefix = etag.startsWith('W/');\n  const quoted = isWeakPrefix ? etag.slice(2) : etag;\n\n  if (quoted.startsWith('\"') && quoted.endsWith('\"')) {\n    return `${isWeakPrefix ? 'W/' : ''}\"${quoted.slice(1, -1)}--${encoding}\"`;\n  }\n\n  return `${etag}--${encoding}`;\n}\n\n/**\n * Simple If-None-Match matcher for the exact representation ETag we are about\n * to send. Wildcard `*` is supported because callers may use it to indicate\n * \"any current representation\".\n */\nexport function matchesIfNoneMatch(\n  ifNoneMatchHeader: string | string[] | undefined,\n  etag: string,\n): boolean {\n  const header = Array.isArray(ifNoneMatchHeader)\n    ? ifNoneMatchHeader.join(',')\n    : ifNoneMatchHeader;\n\n  if (!header) {\n    return false;\n  }\n\n  const normalizeWeakETag = (value: string): string =>\n    value.startsWith('W/') ? value.slice(2) : value;\n  const normalizedETag = normalizeWeakETag(etag);\n\n  return header\n    .split(',')\n    .map((value) => value.trim())\n    .some(\n      (value) => value === '*' || normalizeWeakETag(value) === normalizedETag,\n    );\n}\n\nexport async function compressPayload(\n  payload: Buffer,\n  encoding: ResponseEncoding,\n  options: NormalizedResponseCompressionOptions,\n): Promise<Buffer> {\n  if (encoding === 'br') {\n    return brotliCompressAsync(payload, {\n      params: {\n        [zlibConstants.BROTLI_PARAM_QUALITY]: options.brotliQuality,\n      },\n    });\n  }\n\n  return gzipAsync(payload, {\n    level: options.gzipLevel,\n  });\n}\n\n/**\n * Compress a non-streaming reply payload when it is safe and worthwhile.\n *\n * Important constraints:\n * - skips replies that already selected a representation (Content-Encoding)\n * - skips ranged responses because byte ranges and on-the-fly compression do\n *   not compose cleanly\n * - skips small payloads to avoid wasting CPU and bytes\n *\n * The hook is deliberately representation-aware:\n * - once we choose gzip/br, we must treat the compressed bytes as a distinct\n *   HTTP representation with their own validator (`ETag`)\n * - `HEAD` must negotiate the same representation metadata as `GET`, even\n *   though no body will actually be written to the socket\n * - `If-None-Match` must be evaluated against the representation we are about\n *   to send, not the original uncompressed bytes\n */\nexport async function compressReplyPayload(\n  request: FastifyRequest,\n  reply: FastifyReply,\n  payload: unknown,\n  options: boolean | ResponseCompressionOptions | undefined,\n): Promise<unknown> {\n  const normalized = normalizeResponseCompressionOptions(options);\n\n  if (!normalized.enabled) {\n    return payload;\n  }\n\n  // Safety guard: if headers are already written (e.g. hijacked reply or a\n  // race in edge cases), adding compression headers would be a no-op at best\n  // and would corrupt the response at worst. Bail out early.\n  if (reply.sent || reply.raw?.headersSent) {\n    return payload;\n  }\n\n  if (\n    reply.statusCode < 200 ||\n    reply.statusCode === 204 ||\n    reply.statusCode === 304\n  ) {\n    return payload;\n  }\n\n  if (request.headers.range || reply.hasHeader('Content-Range')) {\n    return payload;\n  }\n\n  if (reply.hasHeader('Content-Encoding')) {\n    return payload;\n  }\n\n  const contentType = reply.getHeader('Content-Type') as string | undefined;\n\n  if (!isCompressibleContentType(contentType)) {\n    return payload;\n  }\n\n  // This generic hook only handles fully materialized payloads. Streamed\n  // responses stay on their existing path because on-the-fly compression would\n  // need different range/backpressure semantics.\n  if (typeof payload !== 'string' && !Buffer.isBuffer(payload)) {\n    return payload;\n  }\n\n  const bufferPayload =\n    typeof payload === 'string' ? Buffer.from(payload) : payload;\n\n  // Match the static-content behavior: small bodies usually are not worth the\n  // extra CPU, header bytes, and cache fragmentation that compression adds.\n  if (bufferPayload.length < normalized.threshold) {\n    return payload;\n  }\n\n  // Only add Vary when the payload is otherwise eligible for compression.\n  // That keeps the header tied to actual response-shape variation.\n  addToVaryHeader(reply, 'Accept-Encoding');\n\n  const encoding = selectResponseEncoding(\n    request.headers['accept-encoding'],\n    normalized.preferBrotli,\n  );\n\n  if (!encoding) {\n    return payload;\n  }\n\n  // Compression is attempted before mutating representation headers so we can\n  // bail out cleanly when the encoded bytes are not actually smaller.\n  const compressed = await compressPayload(bufferPayload, encoding, normalized);\n\n  if (compressed.length >= bufferPayload.length) {\n    return payload;\n  }\n\n  // If upstream already attached an ETag to the identity response, convert it\n  // into a representation-specific validator before the reply leaves the hook.\n  // Otherwise caches would see the same ETag for both compressed and\n  // uncompressed bytes and could serve or validate the wrong representation.\n  const existingETag = reply.getHeader('ETag');\n  const currentETag = Array.isArray(existingETag)\n    ? existingETag[0]\n    : existingETag;\n\n  if (typeof currentETag === 'string' && currentETag.length > 0) {\n    reply.header('ETag', buildEncodedETag(currentETag, encoding));\n  }\n\n  const encodedETag = reply.getHeader('ETag');\n  const responseETag =\n    typeof encodedETag === 'string'\n      ? encodedETag\n      : Array.isArray(encodedETag) && typeof encodedETag[0] === 'string'\n        ? encodedETag[0]\n        : undefined;\n\n  reply.header('Content-Encoding', encoding);\n\n  // Conditional GET/HEAD must be checked against the final representation\n  // validator, not the base identity ETag. If the client already has the\n  // encoded variant we can return 304 without sending the compressed body.\n  if (\n    responseETag &&\n    matchesIfNoneMatch(request.headers['if-none-match'], responseETag)\n  ) {\n    reply.code(304);\n    reply.removeHeader('Content-Length');\n    return '';\n  }\n\n  if (request.method === 'HEAD') {\n    // HEAD still needs to advertise the metadata of the representation that a\n    // GET would have produced after negotiation.\n    //\n    // Fastify still uses the payload returned from onSend to derive outgoing\n    // metadata for HEAD responses. Returning the compressed buffer keeps the\n    // wire-level Content-Length aligned with the corresponding GET, while\n    // Fastify itself suppresses the actual response body for HEAD.\n    reply.header('Content-Length', compressed.length.toString());\n    return compressed;\n  }\n\n  // We already know the exact compressed byte length here, so set it\n  // explicitly instead of relying on later framework inference.\n  reply.header('Content-Length', compressed.length.toString());\n\n  return compressed;\n}\n\n/**\n * Register the generic response-compression hook for dynamic API/web replies.\n *\n * Static file serving uses its own representation-selection path so it can keep\n * ETags, range requests, and cache invalidation tied to concrete file variants.\n *\n * This hook uses an async buffer approach (brotli/gzip in memory) rather than a\n * streaming Transform. The async approach preserves Content-Length, ETag\n * representation variants, and If-None-Match 304 responses.\n *\n * Safety note: async onSend hooks interact with Fastify 5's wrapThenable mechanism.\n * When an async route handler calls reply.send() and returns undefined,\n * wrapThenable can fire a second reply.send(undefined) while the async onSend\n * hook is still pending (reply.sent may remain false until headers are written).\n *\n * To avoid this race, Unirend async route handlers return the payload directly\n * instead of calling reply.send() manually. That lets wrapThenable make exactly\n * one reply.send() call.\n */\nexport function registerResponseCompression(\n  fastifyInstance: FastifyInstance,\n  options: boolean | ResponseCompressionOptions | undefined,\n): void {\n  const normalized = normalizeResponseCompressionOptions(options);\n\n  if (!normalized.enabled) {\n    return;\n  }\n\n  fastifyInstance.addHook('onSend', async (request, reply, payload) => {\n    try {\n      return await compressReplyPayload(request, reply, payload, normalized);\n    } catch (error) {\n      // Compression failure must not take down the server — fall back to the\n      // uncompressed payload so the request still completes.\n      request.log.error(\n        { error },\n        'Response compression failed; sending uncompressed',\n      );\n      return payload;\n    }\n  });\n}\n","import type { FastifyRequest, FastifyReply } from 'fastify';\nimport { StaticContentCache } from './static-content-cache';\nimport type {\n  StaticContentWarnLoggerObject,\n  ServeFileResult,\n} from './static-content-cache';\nimport type { StaticContentRouterOptions } from '../types';\n\n/**\n * Static content hook handler that delegates to a StaticContentCache instance.\n *\n * Performs safety checks before delegating to the cache:\n * - Only handles GET requests\n * - Requires a valid URL to be present\n *\n * @param cache StaticContentCache instance to delegate to\n * @param req Fastify request object\n * @param reply Fastify reply object\n * @returns Promise that resolves to ServeFileResult, or undefined if request was filtered out\n * @internal Shared handler logic used by createStaticContentHook and SSRServer\n */\nexport async function staticContentHookHandler(\n  cache: StaticContentCache,\n  req: FastifyRequest,\n  reply: FastifyReply,\n): Promise<ServeFileResult | undefined> {\n  // Only handle GET and HEAD requests — all other methods (POST, PUT, etc.) fall through\n  if (req.method !== 'GET' && req.method !== 'HEAD') {\n    return;\n  }\n\n  // If there's no URL, we can't handle it\n  if (!req.raw.url) {\n    return;\n  }\n\n  // Delegate to cache to handle URL cleaning, resolution, and file serving\n  return cache.handleRequest(req.raw.url, req, reply);\n}\n\n/**\n * Creates a static content serving hook with its own caches and configuration.\n *\n * Rationale:\n * - Unlike generic static handlers which may check disk for every path or apply\n *   wildcard matching, this only hits the filesystem when:\n *     1) the request URL exactly matches an entry in `singleAssetMap`, or\n *     2) it falls under a configured prefix in `folderMap`.\n * - Adds strong ETag support with optional LRU caching of ETag values and small file content.\n * - Caches file stat results to avoid repeated `stat()` calls.\n * - This minimizes unnecessary disk I/O, improves performance, and locks down\n *   asset serving to known files and directories, preventing accidental exposure\n *   or directory traversal beyond the intended public paths.\n *\n * Each call creates an independent instance with its own caches, allowing multiple\n * instances to be registered with different configurations.\n *\n * @param optionsOrCache Static content configuration OR an existing StaticContentCache instance\n * @param logger Optional logger (e.g., fastify.log) for error logging (ignored if cache instance provided)\n * @returns Fastify onRequest hook handler function\n * @internal Used by SSRServer (internal) and staticContent() plugin (public API)\n */\nexport function createStaticContentHook(\n  optionsOrCache: StaticContentRouterOptions | StaticContentCache,\n  logger?: StaticContentWarnLoggerObject,\n) {\n  // Determine cache source: use provided instance or create new one from options\n  let cache: StaticContentCache;\n\n  if (optionsOrCache instanceof StaticContentCache) {\n    // Using externally-created cache instance (for runtime updates)\n    cache = optionsOrCache;\n    // Note: logger parameter is ignored when cache instance is provided\n  } else {\n    // Creating new cache from configuration options\n    cache = new StaticContentCache(optionsOrCache, logger);\n  }\n\n  // Return the hook handler using shared handler logic\n  return async (req: FastifyRequest, reply: FastifyReply) => {\n    return staticContentHookHandler(cache, req, reply);\n  };\n}\n","import type { FastifyInstance } from 'fastify';\n\n/**\n * Abstract base class for all server types in unirend\n * Defines the common interface that all servers must implement\n */\nexport abstract class BaseServer {\n  protected fastifyInstance: FastifyInstance | null = null;\n  protected _isListening: boolean = false;\n  protected _isStarting: boolean = false;\n  protected _isStopping: boolean = false;\n\n  /**\n   * Start the server listening on the specified port and host\n   * @param port Port to bind to (default: 3000)\n   * @param host Host to bind to (default: \"localhost\")\n   * @returns Promise that resolves when server is ready\n   */\n  public abstract listen(port?: number, host?: string): Promise<void>;\n\n  /**\n   * Stop the server if it's currently running\n   * @returns Promise that resolves when server is stopped\n   */\n  public abstract stop(): Promise<void>;\n\n  /**\n   * Check if the server is currently listening\n   * @returns True if server is listening, false otherwise\n   */\n  public isListening(): boolean {\n    return this._isListening;\n  }\n\n  /**\n   * Force-close all open connections, including those actively serving requests.\n   * Unlike stop(), this does not wait for in-flight requests to complete.\n   * This also terminates upgraded WebSocket connections tracked by the server.\n   * Intended as an escalation path when stop() has not resolved within an\n   * acceptable window. No-op if the server is not started.\n   */\n  public closeAllConnections(): void {\n    this.terminateTrackedWebSocketClients();\n    this.closeRawHTTPConnections();\n  }\n\n  // ---------------------------------------------------------------------------\n  // WebSocket support\n  // ---------------------------------------------------------------------------\n\n  /**\n   * Register a WebSocket handler for the specified path\n   * @throws Error if WebSocket support is not enabled on the server\n   */\n  public abstract registerWebSocketHandler(config: unknown): void;\n\n  /**\n   * Get the list of active WebSocket clients (if enabled)\n   */\n  public abstract getWebSocketClients(): Set<unknown>;\n\n  // ---------------------------------------------------------------------------\n  // Server-level decorations (read-only access)\n  // ---------------------------------------------------------------------------\n\n  /**\n   * Check if a server-level decoration exists. Returns false if the server is not started.\n   * Use this to discover metadata decorated by plugins (e.g., \"cookiePluginInfo\").\n   */\n  public hasDecoration(property: string): boolean {\n    const instance = this.fastifyInstance as unknown as Record<\n      string,\n      unknown\n    > | null;\n    if (!instance) {\n      return false;\n    }\n\n    return Object.prototype.hasOwnProperty.call(instance, property);\n  }\n\n  /**\n   * Read a server-level decoration value. Returns undefined if missing or server not started.\n   * Decorations are attached via Fastify's decorate() API inside plugins.\n   */\n  public getDecoration<T = unknown>(property: string): T | undefined {\n    const instance = this.fastifyInstance as unknown as Record<\n      string,\n      unknown\n    > | null;\n    if (!instance) {\n      return undefined;\n    }\n\n    return instance[property] as T | undefined;\n  }\n\n  /**\n   * Best-effort termination of tracked Fastify WebSocket clients.\n   */\n  protected terminateTrackedWebSocketClients(): void {\n    for (const client of this.getWebSocketClients() as Set<{\n      terminate?: () => void;\n    }>) {\n      client.terminate?.();\n    }\n  }\n\n  /**\n   * Best-effort termination of raw HTTP/HTTPS server connections.\n   */\n  protected closeRawHTTPConnections(): void {\n    const rawServer = this.fastifyInstance?.server as\n      | {\n          closeAllConnections?: () => void;\n        }\n      | undefined;\n\n    rawServer?.closeAllConnections?.();\n  }\n}\n","import type { FastifyRequest, FastifyReply } from 'fastify';\nimport type {\n  ErrorDetailsValue,\n  BaseMeta,\n  APIErrorResponse,\n  APISuccessResponse,\n  PageErrorResponse,\n  PageSuccessResponse,\n  PageRedirectResponse,\n  RedirectInfo,\n  PageMetadata,\n  APIResponseEnvelope,\n  PageResponseEnvelope,\n} from './api-envelope-types';\nimport type { ControlledReply } from '../types';\nimport { sendRawErrorEnvelopeResponse } from '../internal/error-envelope-send';\n\n/**\n * Helper utilities for constructing API/Page response envelopes.\n *\n * These are static so the class can be easily subclassed or the methods can be\n * re-exported. Users may extend this class to inject their own default meta or\n * wrap additional logic (e.g., account metadata, logging, etc.).\n */\nexport class APIResponseHelpers {\n  // API Response Helpers\n\n  /**\n   * Creates a standardized API success response envelope for API (AJAX/JSON) endpoints.\n   *\n   * @typeParam T - The type of the response data payload.\n   * @typeParam M - Meta type that extends BaseMeta.\n   *   Allows consumers to add application specific meta keys\n   *   (e.g. `account`, `pagination`, etc.).\n   * @param params - Object containing request, data, statusCode (default 200), and optional meta.\n   * @returns An APISuccessResponse envelope with merged meta and a request_id.\n   */\n\n  public static createAPISuccessResponse<\n    T,\n    M extends BaseMeta = BaseMeta,\n  >(params: {\n    request: FastifyRequest;\n    data: T;\n    statusCode?: number;\n    meta?: Partial<M>;\n  }): APISuccessResponse<T, M> {\n    const { request, data, statusCode = 200, meta } = params;\n\n    // API responses should not include page metadata by default\n    // Only include meta if explicitly provided\n    const defaultMeta = {} as M;\n\n    const receivedAt = (request as { receivedAt?: number }).receivedAt;\n\n    return {\n      status: 'success',\n      status_code: statusCode,\n      request_id: (request as { requestID?: string }).requestID ?? 'unknown',\n      ...(receivedAt !== undefined\n        ? { request_timestamp: new Date(receivedAt).toISOString() }\n        : {}),\n      type: 'api',\n      data,\n      meta: { ...defaultMeta, ...(meta as Partial<M>) } as M,\n    };\n  }\n\n  /**\n   * Creates a standardized API error response envelope for API (AJAX/JSON) endpoints.\n   *\n   * @typeParam M - Meta type that extends BaseMeta.\n   *   Allows consumers to add application specific meta keys\n   *   (e.g. `account`, `pagination`, etc.).\n   * @param params - Object containing request, statusCode, errorCode, errorMessage, optional errorDetails, and optional meta.\n   * @returns An APIErrorResponse envelope with merged meta and a request_id.\n   */\n  public static createAPIErrorResponse<M extends BaseMeta = BaseMeta>(params: {\n    request: FastifyRequest;\n    statusCode: number;\n    errorCode: string;\n    errorMessage: string;\n    errorDetails?: ErrorDetailsValue;\n    meta?: Partial<M>;\n  }): APIErrorResponse<M> {\n    const { request, statusCode, errorCode, errorMessage, errorDetails, meta } =\n      params;\n\n    // API responses should not include page metadata by default\n    // Only include meta if explicitly provided\n    const defaultMeta = {} as M;\n\n    const receivedAt = (request as { receivedAt?: number }).receivedAt;\n\n    return {\n      status: 'error',\n      status_code: statusCode,\n      request_id: (request as { requestID?: string }).requestID ?? 'unknown',\n      ...(receivedAt !== undefined\n        ? { request_timestamp: new Date(receivedAt).toISOString() }\n        : {}),\n      type: 'api',\n      data: null,\n      meta: { ...defaultMeta, ...(meta as Partial<M>) } as M,\n      error: {\n        code: errorCode,\n        message: errorMessage,\n        ...(errorDetails && { details: errorDetails }),\n      },\n    };\n  }\n\n  // Page Response Helpers\n\n  /**\n   * Creates a standardized Page success response envelope for SSR/data loaders.\n   *\n   * @typeParam T - The type of the response data payload.\n   * @typeParam M - Meta type that extends BaseMeta.\n   *   Allows consumers to add application specific meta keys\n   *   (e.g. `account`, `pagination`, etc.).\n   * @param params - Object containing request, data, pageMetadata, statusCode (default 200), and optional meta.\n   * @returns A PageSuccessResponse envelope with merged meta and a request_id.\n   */\n  public static createPageSuccessResponse<\n    T,\n    M extends BaseMeta = BaseMeta,\n  >(params: {\n    request: FastifyRequest;\n    data: T;\n    pageMetadata: PageMetadata;\n    statusCode?: number;\n    meta?: Partial<M>;\n  }): PageSuccessResponse<T, M> {\n    const { request, data, pageMetadata, statusCode = 200, meta } = params;\n\n    const baseMeta: BaseMeta = {\n      page: pageMetadata,\n    };\n\n    // Auto-populate ssr_request_context from request.requestContext if available and non-empty\n    const requestContext = (\n      request as { requestContext?: Record<string, unknown> }\n    ).requestContext;\n\n    const receivedAt = (request as { receivedAt?: number }).receivedAt;\n\n    return {\n      status: 'success',\n      status_code: statusCode,\n      request_id: (request as { requestID?: string }).requestID ?? 'unknown',\n      ...(receivedAt !== undefined\n        ? { request_timestamp: new Date(receivedAt).toISOString() }\n        : {}),\n      type: 'page',\n      data,\n      meta: { ...(baseMeta as M), ...(meta as Partial<M>) } as M,\n      ...(requestContext &&\n      typeof requestContext === 'object' &&\n      !Array.isArray(requestContext) &&\n      Object.keys(requestContext).length > 0\n        ? { ssr_request_context: requestContext }\n        : {}),\n    };\n  }\n\n  /**\n   * Creates a standardized Page redirect response envelope for SSR/data loaders.\n   * Always uses status code 200 to avoid confusion with HTTP redirects.\n   *\n   * @typeParam M - Meta type that extends BaseMeta.\n   *   Allows consumers to add application specific meta keys.\n   * @param params - Object containing request, redirectInfo, pageMetadata, and optional meta.\n   * @returns A PageRedirectResponse envelope with merged meta and a request_id.\n   */\n  public static createPageRedirectResponse<\n    M extends BaseMeta = BaseMeta,\n  >(params: {\n    request: FastifyRequest;\n    redirectInfo: RedirectInfo;\n    pageMetadata: PageMetadata;\n    meta?: Partial<M>;\n  }): PageRedirectResponse<M> {\n    const { request, redirectInfo, pageMetadata, meta } = params;\n\n    const baseMeta: BaseMeta = {\n      page: pageMetadata,\n    };\n\n    // Auto-populate ssr_request_context from request.requestContext if available and non-empty\n    const requestContext = (\n      request as { requestContext?: Record<string, unknown> }\n    ).requestContext;\n\n    const receivedAt = (request as { receivedAt?: number }).receivedAt;\n\n    return {\n      status: 'redirect',\n      status_code: 200,\n      request_id: (request as { requestID?: string }).requestID ?? 'unknown',\n      ...(receivedAt !== undefined\n        ? { request_timestamp: new Date(receivedAt).toISOString() }\n        : {}),\n      type: 'page',\n      data: null,\n      meta: { ...(baseMeta as M), ...(meta as Partial<M>) } as M,\n      redirect: redirectInfo,\n      ...(requestContext &&\n      typeof requestContext === 'object' &&\n      !Array.isArray(requestContext) &&\n      Object.keys(requestContext).length > 0\n        ? { ssr_request_context: requestContext }\n        : {}),\n    };\n  }\n\n  /**\n   * Creates a standardized Page error response envelope for SSR/data loaders.\n   *\n   * @typeParam M - Meta type that extends BaseMeta.\n   *   Allows consumers to add application specific meta keys\n   *   (e.g. `account`, `pagination`, etc.).\n   * @param params - Object containing request, statusCode, errorCode, errorMessage,\n   *   optional errorDetails, pageMetadata, and optional meta.\n   * @returns A PageErrorResponse envelope with merged meta and a request_id.\n   */\n\n  public static createPageErrorResponse<M extends BaseMeta = BaseMeta>(params: {\n    request: FastifyRequest;\n    statusCode: number;\n    errorCode: string;\n    errorMessage: string;\n    errorDetails?: ErrorDetailsValue;\n    pageMetadata: PageMetadata;\n    meta?: Partial<M>;\n  }): PageErrorResponse<M> {\n    const {\n      request,\n      statusCode,\n      errorCode,\n      errorMessage,\n      pageMetadata,\n      errorDetails,\n      meta,\n    } = params;\n\n    const baseMeta: BaseMeta = {\n      page: pageMetadata,\n    };\n\n    // Auto-populate ssr_request_context from request.requestContext if available and non-empty\n    const requestContext = (\n      request as { requestContext?: Record<string, unknown> }\n    ).requestContext;\n\n    const receivedAt = (request as { receivedAt?: number }).receivedAt;\n\n    return {\n      status: 'error',\n      status_code: statusCode,\n      request_id: (request as { requestID?: string }).requestID ?? 'unknown',\n      ...(receivedAt !== undefined\n        ? { request_timestamp: new Date(receivedAt).toISOString() }\n        : {}),\n      type: 'page',\n      data: null,\n      meta: { ...(baseMeta as M), ...(meta as Partial<M>) } as M,\n      error: {\n        code: errorCode,\n        message: errorMessage,\n        ...(errorDetails && { details: errorDetails }),\n      },\n      ...(requestContext &&\n      typeof requestContext === 'object' &&\n      !Array.isArray(requestContext) &&\n      Object.keys(requestContext).length > 0\n        ? { ssr_request_context: requestContext }\n        : {}),\n    };\n  }\n\n  // Validation Helpers\n\n  /**\n   * Send an error envelope response with the appropriate method\n   * Works with both FastifyReply and ControlledReply\n   *\n   * This is a public utility for sending error responses in a way that works\n   * with both standard Fastify handlers and controlled reply handlers.\n   *\n   * @param reply - Fastify reply object or ControlledReply\n   * @param statusCode - HTTP status code to send\n   * @param errorResponse - Error envelope to send\n   *\n   * @example\n   * ```typescript\n   * const errorResponse = APIResponseHelpers.createAPIErrorResponse({\n   *   request,\n   *   statusCode: 400,\n   *   errorCode: 'invalid_input',\n   *   errorMessage: 'Invalid input provided',\n   * });\n   *\n   * await APIResponseHelpers.sendErrorEnvelope(\n   *   request,\n   *   reply,\n   *   400,\n   *   errorResponse,\n   * );\n   * ```\n   *\n   * This helper is usable directly, but it is also part of the framework's\n   * controlled early-termination path. Unlike the envelope creation helpers,\n   * it has transport semantics (shared headers, hijack/raw write, immediate\n   * response finalization), so overriding it in a custom helpers subclass is\n   * discouraged unless you intend to preserve that contract.\n   */\n  public static async sendErrorEnvelope(\n    request: FastifyRequest,\n    reply: FastifyReply | ControlledReply,\n    statusCode: number,\n    errorResponse: APIErrorResponse<BaseMeta> | PageErrorResponse<BaseMeta>,\n  ): Promise<void> {\n    // Controlled handlers are intentionally not given general-purpose send\n    // methods. When the framework wrapped the reply, use its internal\n    // _sendErrorEnvelope hook; otherwise fall back to the shared raw-send path\n    // for direct FastifyReply usage (including tests and non-controlled routes).\n    if ('_sendErrorEnvelope' in reply) {\n      await reply._sendErrorEnvelope(statusCode, errorResponse);\n      return;\n    }\n\n    await sendRawErrorEnvelopeResponse(\n      request,\n      reply,\n      statusCode,\n      errorResponse,\n    );\n  }\n\n  /**\n   * Ensures an incoming Fastify request has a valid JSON body.\n   * If invalid, sends a standardized error response and returns false.\n   *\n   * Use this helper for POST, PUT, PATCH, and DELETE endpoints that expect JSON payloads.\n   * This is a pre-validation convenience before using schema validators like Zod.\n   *\n   * @param request - Fastify request object\n   * @param reply - Fastify reply object or ControlledReply\n   * @returns true if body is valid JSON, otherwise false (error envelope already sent)\n   *\n   * @example\n   * ```typescript\n   * server.api.post('users', async (request, reply) => {\n   *   if (!(await APIResponseHelpers.ensureJSONBody(request, reply))) {\n   *     return false; // Error envelope already sent\n   *   }\n   *\n   *   // Now safe to validate using a schema validator (e.g. Zod) or process the body\n   *   const validated = userSchema.parse(request.body);\n   *   // ...\n   * });\n   * ```\n   */\n  public static async ensureJSONBody(\n    request: FastifyRequest,\n    reply: FastifyReply | ControlledReply,\n  ): Promise<boolean> {\n    // Check Content-Type header first\n    const contentType = request.headers['content-type'];\n\n    if (!contentType || !contentType.includes('application/json')) {\n      const errorResponse = this.createAPIErrorResponse({\n        request,\n        statusCode: 415, // Unsupported Media Type\n        errorCode: 'invalid_content_type',\n        errorMessage: 'Content-Type must be application/json',\n        errorDetails: {\n          received_content_type: contentType || 'none',\n          expected_content_type: 'application/json',\n        },\n      });\n\n      // Send the standardized error envelope and terminate early.\n      await this.sendErrorEnvelope(request, reply, 415, errorResponse);\n      return false;\n    }\n\n    // Then validate the parsed body exists and is an object\n    if (!request.body || typeof request.body !== 'object') {\n      const errorResponse = this.createAPIErrorResponse({\n        request,\n        statusCode: 400,\n        errorCode: 'invalid_request_body_format',\n        errorMessage:\n          'Request body is required and must be a valid JSON object',\n        errorDetails: {\n          received_body_type: typeof request.body,\n        },\n      });\n\n      // Send the standardized error envelope and terminate early.\n      await this.sendErrorEnvelope(request, reply, 400, errorResponse);\n      return false;\n    }\n\n    return true;\n  }\n\n  /**\n   * Ensures an incoming Fastify request has a valid URL-encoded form body.\n   * If invalid, sends a standardized error response and returns false.\n   *\n   * Use this helper for POST, PUT, or PATCH endpoints that expect URL-encoded form data.\n   * This is a pre-validation convenience before processing form fields.\n   *\n   * Note: For file uploads with multipart/form-data, use ensureMultipartBody instead.\n   *\n   * @param request - Fastify request object\n   * @param reply - Fastify reply object or ControlledReply\n   * @returns true if form body is valid, otherwise false (error envelope already sent)\n   *\n   * @example\n   * ```typescript\n   * server.api.post('contact', async (request, reply) => {\n   *   if (!(await APIResponseHelpers.ensureURLEncodedBody(request, reply))) {\n   *     return false; // Error envelope already sent\n   *   }\n   *\n   *   // Now safe to process form fields\n   *   const formData = request.body as Record<string, unknown>;\n   *   // ...\n   * });\n   * ```\n   */\n  public static async ensureURLEncodedBody(\n    request: FastifyRequest,\n    reply: FastifyReply | ControlledReply,\n  ): Promise<boolean> {\n    // Check Content-Type header first\n    const contentType = request.headers['content-type'];\n\n    if (\n      !contentType ||\n      !contentType.includes('application/x-www-form-urlencoded')\n    ) {\n      const errorResponse = this.createAPIErrorResponse({\n        request,\n        statusCode: 415, // Unsupported Media Type\n        errorCode: 'invalid_content_type',\n        errorMessage: 'Content-Type must be application/x-www-form-urlencoded',\n        errorDetails: {\n          received_content_type: contentType || 'none',\n          expected_content_type: 'application/x-www-form-urlencoded',\n        },\n      });\n\n      // Send the standardized error envelope and terminate early.\n      await this.sendErrorEnvelope(request, reply, 415, errorResponse);\n      return false;\n    }\n\n    // Validate the parsed body exists and is an object\n    if (!request.body || typeof request.body !== 'object') {\n      const errorResponse = this.createAPIErrorResponse({\n        request,\n        statusCode: 400,\n        errorCode: 'invalid_request_body_format',\n        errorMessage:\n          'Request body is required and must be valid URL-encoded form data',\n        errorDetails: {\n          received_body_type: typeof request.body,\n        },\n      });\n\n      // Send the standardized error envelope and terminate early.\n      await this.sendErrorEnvelope(request, reply, 400, errorResponse);\n      return false;\n    }\n\n    return true;\n  }\n\n  /**\n   * Ensures an incoming Fastify request has multipart/form-data Content-Type.\n   * If invalid, sends a standardized error response and returns false.\n   *\n   * **Note:** `processFileUpload()` automatically validates Content-Type,\n   * so you typically don't need this helper when using `processFileUpload()`.\n   *\n   * **Advanced use case:** Use this for early validation in middleware (e.g., auth/rate-limiting)\n   * before multipart parsing begins:\n   *\n   * ```typescript\n   * // Block uploads for non-premium users before parsing\n   * pluginHost.addHook('preHandler', async (request, reply) => {\n   *   if (request.headers['content-type']?.includes('multipart/form-data')) {\n   *     if (!user.isPremium) {\n   *       return reply.code(403).send({ error: 'Premium feature' });\n   *     }\n   *   }\n   * });\n   * ```\n   *\n   * For standard file uploads, use `processFileUpload()` instead:\n   * ```typescript\n   * import { processFileUpload } from 'unirend/server';\n   *\n   * const results = await processFileUpload({\n   *   request,\n   *   reply,\n   *   maxSizePerFile: 5 * 1024 * 1024,\n   *   allowedMimeTypes: ['image/jpeg', 'image/png'],\n   *   processor: async (stream, metadata, context) => {\n   *     // ... handle upload\n   *   },\n   * });\n   * ```\n   *\n   * @param request - Fastify request object\n   * @param reply - Fastify reply object or ControlledReply\n   * @returns true if Content-Type is multipart/form-data, otherwise false (error envelope already sent)\n   *\n   */\n  public static async ensureMultipartBody(\n    request: FastifyRequest,\n    reply: FastifyReply | ControlledReply,\n  ): Promise<boolean> {\n    // Check Content-Type header\n    const contentType = request.headers['content-type'];\n\n    if (!contentType || !contentType.includes('multipart/form-data')) {\n      const errorResponse = this.createAPIErrorResponse({\n        request,\n        statusCode: 415, // Unsupported Media Type\n        errorCode: 'invalid_content_type',\n        errorMessage: 'Content-Type must be multipart/form-data',\n        errorDetails: {\n          received_content_type: contentType || 'none',\n          expected_content_type: 'multipart/form-data',\n        },\n      });\n\n      // Send the standardized error envelope and terminate early.\n      await this.sendErrorEnvelope(request, reply, 415, errorResponse);\n      return false;\n    }\n\n    // Note: This helper only validates the Content-Type header.\n    // In this codebase, multipart payloads are consumed later through the\n    // streaming request.file()/request.files() APIs, so we do not validate\n    // request.body here.\n    return true;\n  }\n\n  // ---------------------------------------------------------------------------\n  // Static Type-Guard Helpers\n  // ---------------------------------------------------------------------------\n\n  /** Determines if envelope is a success response */\n  public static isSuccessResponse<T, M extends BaseMeta = BaseMeta>(\n    response: APIResponseEnvelope<T, M> | PageResponseEnvelope<T, M>,\n  ): response is APISuccessResponse<T, M> | PageSuccessResponse<T, M> {\n    return response.status === 'success';\n  }\n\n  /** Determines if envelope is an error response */\n  public static isErrorResponse<M extends BaseMeta = BaseMeta>(\n    response:\n      | APIResponseEnvelope<unknown, M>\n      | PageResponseEnvelope<unknown, M>,\n  ): response is APIErrorResponse<M> | PageErrorResponse<M> {\n    return response.status === 'error';\n  }\n\n  /** Determines if envelope is a redirect response */\n  public static isRedirectResponse<M extends BaseMeta = BaseMeta>(\n    response:\n      | APIResponseEnvelope<unknown, M>\n      | PageResponseEnvelope<unknown, M>,\n  ): response is PageRedirectResponse<M> {\n    return response.status === 'redirect';\n  }\n\n  /** Determines if envelope is a page (SSR) response */\n  public static isPageResponse<T, M extends BaseMeta = BaseMeta>(\n    response: APIResponseEnvelope<T, M> | PageResponseEnvelope<T, M>,\n  ): response is PageResponseEnvelope<T, M> {\n    return response.type === 'page';\n  }\n\n  /**\n   * Validates that an unknown value is a proper envelope object\n   * This is a catch-all validation function that checks for proper envelope structure\n   * without requiring specific typing - useful for runtime validation of handler responses\n   */\n  public static isValidEnvelope(\n    result: unknown,\n  ): result is PageResponseEnvelope | APIResponseEnvelope {\n    if (!result || typeof result !== 'object') {\n      return false;\n    }\n\n    const envelope = result as Record<string, unknown>;\n\n    // Check required fields\n    const hasStatus =\n      typeof envelope.status === 'string' &&\n      ['success', 'error', 'redirect'].includes(envelope.status);\n\n    const hasStatusCode = typeof envelope.status_code === 'number';\n\n    const hasType =\n      typeof envelope.type === 'string' &&\n      ['api', 'page'].includes(envelope.type);\n\n    const hasRequestID = typeof envelope.request_id === 'string';\n    const hasMeta = envelope.meta && typeof envelope.meta === 'object';\n\n    // Basic structure validation\n    if (!hasStatus || !hasStatusCode || !hasType || !hasRequestID || !hasMeta) {\n      return false;\n    }\n\n    // Validate meta has required page field ONLY for page type envelopes\n    // API type envelopes do not require page metadata\n    if (envelope.type === 'page') {\n      const meta = envelope.meta as Record<string, unknown>;\n\n      if (!meta.page || typeof meta.page !== 'object') {\n        return false;\n      }\n\n      const page = meta.page as Record<string, unknown>;\n\n      if (\n        typeof page.title !== 'string' ||\n        typeof page.description !== 'string'\n      ) {\n        return false;\n      }\n    }\n\n    // Status-specific validation\n    if (envelope.status === 'success') {\n      return (\n        envelope.data !== undefined &&\n        (envelope.error === undefined || envelope.error === null)\n      );\n    } else if (envelope.status === 'error') {\n      return (\n        envelope.data === null &&\n        envelope.error !== null &&\n        typeof envelope.error === 'object'\n      );\n    } else if (envelope.status === 'redirect') {\n      return (\n        envelope.data === null &&\n        (envelope.error === undefined || envelope.error === null) &&\n        envelope.redirect !== null &&\n        typeof envelope.redirect === 'object'\n      );\n    }\n\n    return false;\n  }\n}\n","/**\n * Shared utility for getting API response helpers class from request\n *\n * This is used by file upload helpers and validation hooks to create\n * consistent error responses using the user's custom APIResponseHelpersClass\n * if decorated on the request, or the default APIResponseHelpers.\n */\n\nimport type { FastifyRequest } from 'fastify';\nimport { APIResponseHelpers } from '../api-envelope/response-helpers';\nimport type { APIResponseHelpersClass } from '../types';\n\n/**\n * Get the APIResponseHelpersClass to use for creating error responses.\n *\n * Priority:\n * 1. Custom class decorated on the request (if available)\n * 2. Default APIResponseHelpers class\n *\n * @param request - Fastify request object\n * @returns The helpers class to use\n */\nexport function getAPIResponseHelpersClass(\n  request: FastifyRequest,\n): APIResponseHelpersClass {\n  // Try to get custom class from request decoration\n  const decoratedClass = (\n    request as FastifyRequest & {\n      APIResponseHelpersClass?: APIResponseHelpersClass;\n    }\n  ).APIResponseHelpersClass;\n\n  if (decoratedClass?.createAPIErrorResponse) {\n    return decoratedClass;\n  }\n\n  // Fall back to default helpers\n  return APIResponseHelpers;\n}\n","/**\n * Shared utilities for handler version management\n * Used by both APIRoutesServerHelpers and DataLoaderServerHandlerHelpers\n */\n\n/**\n * Validate that a version number is >= 1\n * Throws an error if validation fails\n */\nexport function validateVersion(version: number, context: string): void {\n  if (version < 1) {\n    throw new Error(`${context} version must be >= 1, got ${version}`);\n  }\n}\n\n/**\n * Check if versioning is disabled but multiple versions exist, throwing if so\n */\nexport function validateSingleVersionWhenDisabled(\n  useVersioning: boolean,\n  versionMap: Map<number, unknown>,\n  contextMessage: string,\n): void {\n  if (!useVersioning && versionMap.size > 1) {\n    const versions = Array.from(versionMap.keys()).sort((a, b) => a - b);\n    throw new Error(\n      `${contextMessage} has multiple versions (${versions.join(', ')}) but versioning is disabled. ` +\n        `Either enable versioning or register only one version per ${contextMessage.includes('Endpoint') ? 'endpoint' : 'page type'}.`,\n    );\n  }\n}\n","import type { FastifyRequest, FastifyInstance } from 'fastify';\nimport type {\n  PageResponseEnvelope,\n  APIResponseEnvelope,\n  BaseMeta,\n} from '../api-envelope/api-envelope-types';\nimport { APIResponseHelpers } from '../api-envelope/response-helpers';\nimport type { APIResponseHelpersClass, ControlledReply } from '../types';\nimport { createControlledReply } from './server-utils';\nimport { getAPIResponseHelpersClass } from './api-response-helpers-utils';\nimport {\n  validateVersion,\n  validateSingleVersionWhenDisabled,\n} from './version-helpers';\n\n/**\n * Parameters passed to page data loader handlers with shortcuts to common fields\n *\n * Handlers should treat these params as the authoritative routing context\n * (routeParams, queryParams, requestPath, originalURL) produced by the\n * page data loader. Do not reconstruct routing info from the Fastify request.\n *\n * The Fastify request represents the original HTTP request and should be used\n * only for transport/ambient data (cookies, headers, IP, auth tokens, etc.).\n * During SSR, this is the same request that initiated the page render; after\n * hydration, client-side page data loader calls will include their own\n * transport context as appropriate.\n */\nexport interface PageDataHandlerParams {\n  pageType: string;\n  version?: number;\n  /** Indicates how the handler was invoked: via HTTP route or internal short-circuit */\n  invocationOrigin: 'http' | 'internal';\n  // Shortcuts to common page data loader fields (extracted from request.body)\n  /** Route params (from React Router via POST body) */\n  routeParams: Record<string, string>;\n  /** Query params (from React Router via POST body) */\n  queryParams: Record<string, unknown>;\n  /** Request path (from React Router via POST body) */\n  requestPath: string;\n  /** Original URL (from React Router via POST body) */\n  originalURL: string;\n  /** The APIResponseHelpers class configured on this server (use this instead of importing directly) */\n  APIResponseHelpers: APIResponseHelpersClass;\n}\n\n/**\n * Handler function type for page data endpoints\n *\n * @param request - The Fastify request object (original request). Use for cookies, headers, IP, etc.\n * @param params - Page data context (preferred for page routing: path, query, route params)\n * @returns A PageResponseEnvelope (recommended), APIResponseEnvelope (will be converted), or false if response already sent\n *\n * **Recommendation**: Return PageResponseEnvelope for optimal performance and control.\n * APIResponseEnvelope is supported but will be converted by the pageDataLoader, which\n * adds overhead and may not preserve all metadata as intended.\n *\n * **Return false** when you've sent a custom response (e.g., using\n * APIResponseHelpers.sendErrorEnvelope() or validation helpers like ensureJSONBody).\n * This signals that the handler has already sent the response and the framework\n * should not attempt to send anything.\n */\nexport type PageDataHandler<T = unknown, M extends BaseMeta = BaseMeta> = (\n  /** Original HTTP request (for cookies/headers/IP/auth) */\n  originalRequest: FastifyRequest,\n  reply: ControlledReply,\n  params: PageDataHandlerParams,\n) =>\n  | Promise<PageResponseEnvelope<T, M> | APIResponseEnvelope<T, M> | false>\n  | PageResponseEnvelope<T, M>\n  | APIResponseEnvelope<T, M>\n  | false;\n\n/**\n * Result returned from callHandler()\n */\nexport interface CallHandlerResult<T = unknown, M extends BaseMeta = BaseMeta> {\n  /** Whether a handler exists for the given page type */\n  exists: boolean;\n  /** Version that was used when invoking the handler (if it exists) */\n  version?: number;\n  /** The envelope returned by the handler when successful, or false if handler sent response directly */\n  result?: PageResponseEnvelope<T, M> | APIResponseEnvelope<T, M> | false;\n}\n\n/**\n * Helper class for registering page data loader handlers on server instances\n *\n * This class provides a convenient API for registering page data endpoints that match\n * the frontend pageDataLoader expectations. It supports both versioned and non-versioned\n * endpoints with method overloading for clean API usage.\n *\n * Handlers are stored internally and registered when registerRoutes() is called.\n * Storage structure: Map<pageType, Map<version, handler>> for efficient lookups\n *\n * ## Page Type Convention\n *\n * Page types should be specified as path segments WITHOUT leading slashes:\n * - ✅ `server.pageDataHandler.register(\"home\", handler)`           → `/api/v1/page_data/home`\n * - ✅ `server.pageDataHandler.register(\"protected-page\", handler)` → `/api/v1/page_data/protected-page`\n * - ⚠️ `server.pageDataHandler.register(\"/home\", handler)`          → `/api/v1/page_data/home` (leading slash stripped)\n *\n * Leading slashes are allowed but will be automatically stripped during normalization.\n *\n * This design treats page types as path segments that get appended to the API prefix,\n * version, and page data endpoint, rather than as absolute paths.\n */\nexport class DataLoaderServerHandlerHelpers {\n  // Map<pageType, Map<version, handler>> - version defaults to 1 if not specified\n  private handlersByPageType = new Map<string, Map<number, PageDataHandler>>();\n\n  // pageDataHandler method-specific helpers\n  private readonly pageDataHandler = {\n    /**\n     * Register a page data handler\n     *\n     * @param pageType - Page type identifier (e.g., \"home\" or \"protected-page\")\n     *   - Convention: Do NOT include leading slash - page types are path segments, not full paths\n     *   - Leading slashes are allowed but will be stripped during normalization\n     *   - The final path is constructed as: prefix + version + pageDataEndpoint + pageType\n     *     Example: \"home\" → \"/api/v1/page_data/home\" (with prefix=\"/api\", versioned, pageDataEndpoint=\"page_data\")\n     * @param versionOrHandler - Handler function, or version number if using versioned handler\n     * @param handlerMaybe - Handler function when version is specified\n     */\n    register: (\n      pageType: string,\n      versionOrHandler: number | PageDataHandler,\n      handlerMaybe?: PageDataHandler,\n    ): void => {\n      if (typeof versionOrHandler === 'number') {\n        this.registerDataLoaderHandler(\n          pageType,\n          versionOrHandler,\n          handlerMaybe as PageDataHandler,\n        );\n      } else {\n        this.registerDataLoaderHandler(pageType, versionOrHandler);\n      }\n    },\n  } as const;\n\n  /** Expose only the lightweight shortcuts surface for external consumers */\n  public get pageDataHandlerMethod() {\n    return this.pageDataHandler;\n  }\n\n  /**\n   * Check if any page data loader handlers have been registered\n   * Useful for validation when API handling is disabled\n   */\n  public hasRegisteredHandlers(): boolean {\n    return this.handlersByPageType.size > 0;\n  }\n\n  /**\n   * Register all stored handlers with the Fastify instance.\n   * This is called during server listen() to actually register the routes.\n   *\n   * @param fastify - The Fastify instance to register routes on\n   * @param apiPrefix - Pre-normalized API prefix (e.g., \"/api\")\n   * @param pageDataEndpoint - Pre-normalized page data endpoint (e.g., \"page_data\")\n   * @param options - Optional config for versioning\n   */\n  public registerRoutes(\n    fastify: FastifyInstance,\n    apiPrefix: string,\n    pageDataEndpoint: string,\n    options?: {\n      versioned?: boolean;\n    },\n  ): void {\n    const useVersioning = options?.versioned ?? true;\n\n    // Iterate over all page types and their versions\n    for (const [pageType, versionMap] of this.handlersByPageType) {\n      // Check if versioning is disabled but multiple versions exist\n      validateSingleVersionWhenDisabled(\n        useVersioning,\n        versionMap,\n        `Page type \"${pageType}\"`,\n      );\n\n      // Register each version\n      for (const [version, handler] of versionMap) {\n        // Build the endpoint path\n        const endpointPath = useVersioning\n          ? `${apiPrefix}/v${version}/${pageDataEndpoint}/${pageType}`\n          : `${apiPrefix}/${pageDataEndpoint}/${pageType}`;\n\n        // Register the POST route with Fastify\n        fastify.post(endpointPath, async (request, reply) => {\n          try {\n            // Extract page data loader fields from request body\n            const requestBody = (request.body as Record<string, unknown>) || {};\n\n            // Merge ssr_request_context into request.requestContext (SSR forwarding)\n            // SECURITY: Only trust ssr_request_context when request comes from a trusted SSR server\n            // This requires the clientInfo plugin to be registered and validates the source IP\n            const clientInfo = (\n              request as { clientInfo?: { isFromSSRServerAPICall?: boolean } }\n            ).clientInfo;\n\n            if (\n              clientInfo?.isFromSSRServerAPICall &&\n              requestBody.ssr_request_context !== null &&\n              typeof requestBody.ssr_request_context === 'object' &&\n              !Array.isArray(requestBody.ssr_request_context)\n            ) {\n              const contextToMerge = requestBody.ssr_request_context as Record<\n                string,\n                unknown\n              >;\n\n              // Only merge if there's actual data to merge (optimization: skip empty objects)\n              // requestContext is always initialized as {} by SSRServer/APIServer before hooks run\n              if (Object.keys(contextToMerge).length > 0) {\n                Object.assign(request.requestContext, contextToMerge);\n              }\n            }\n\n            // Validate required POST body fields from frontend page data loader\n            // These represent the React Router URL/params (NOT the Fastify API endpoint)\n            // If missing, fail fast with 400 Bad Request rather than continuing with empty values\n            const routeParams = requestBody.route_params;\n            const queryParams = requestBody.query_params;\n            const requestPath = requestBody.request_path;\n            const originalURL = requestBody.original_url;\n\n            const helpersClass = getAPIResponseHelpersClass(request);\n\n            // Validate that routing fields have correct types\n            const invalidFields = [];\n\n            // Required string fields\n            if (typeof requestPath !== 'string') {\n              invalidFields.push('request_path (must be string)');\n            }\n\n            if (typeof originalURL !== 'string') {\n              invalidFields.push('original_url (must be string)');\n            }\n\n            // Optional object fields - if present and not null/undefined, must be objects\n            if (\n              routeParams !== null &&\n              routeParams !== undefined &&\n              (typeof routeParams !== 'object' || Array.isArray(routeParams))\n            ) {\n              invalidFields.push(\n                'route_params (must be object or null/undefined)',\n              );\n            }\n\n            if (\n              queryParams !== null &&\n              queryParams !== undefined &&\n              (typeof queryParams !== 'object' || Array.isArray(queryParams))\n            ) {\n              invalidFields.push(\n                'query_params (must be object or null/undefined)',\n              );\n            }\n\n            if (invalidFields.length > 0) {\n              // Client error: malformed request body - return proper API error envelope.\n              // Return the envelope directly so wrapThenable makes exactly one reply.send() call.\n              reply.code(400);\n\n              return helpersClass.createAPIErrorResponse({\n                request,\n                statusCode: 400,\n                errorCode: 'invalid_page_data_body_fields',\n                errorMessage:\n                  'Request body has invalid field types for page data loader',\n                errorDetails: {\n                  invalid_fields: invalidFields,\n                  received_body: requestBody,\n                },\n              });\n            }\n\n            const result = await handler(\n              request,\n              createControlledReply(request, reply),\n              {\n                pageType,\n                version,\n                invocationOrigin: 'http',\n                // Extract from POST body (sent by frontend page data loader with React Router context)\n                // Note: These represent the React Router URL/params, NOT the Fastify request URL\n                routeParams:\n                  (routeParams as Record<string, string> | undefined) || {},\n                queryParams:\n                  (queryParams as Record<string, unknown> | undefined) || {},\n                requestPath: requestPath as string,\n                originalURL: originalURL as string,\n                APIResponseHelpers: helpersClass,\n              },\n            );\n\n            // If handler returned false, it has already sent the response\n            // (e.g., via reply.sendErrorEnvelope() in a validation helper)\n            if (result === false) {\n              // Verify that the response was actually sent by the handler\n              if (!reply.sent && !reply.raw.headersSent) {\n                // Handler bug: returned false without sending a response\n                // This is a programming error in the user's handler code\n                const error = new Error(\n                  `Handler for page type \"${pageType}\" returned false but did not send a response. ` +\n                    `When returning false, you must send a response first using APIResponseHelpers.sendErrorEnvelope().`,\n                );\n                (error as unknown as { pageType: string }).pageType = pageType;\n                (error as unknown as { version: number }).version = version;\n                (error as unknown as { errorCode: string }).errorCode =\n                  'handler_returned_false_without_sending';\n                throw error;\n              }\n\n              return; // Response was sent by handler, do not send anything more\n            }\n\n            // Validate that the handler returned a proper envelope object\n            if (!APIResponseHelpers.isValidEnvelope(result)) {\n              // Create error with detailed context - logging will be handled by catch block\n              const error = new Error(\n                `Handler for page type \"${pageType}\" returned invalid response envelope`,\n              );\n\n              // Add metadata for error handlers\n              (error as unknown as { pageType: string }).pageType = pageType;\n\n              (error as unknown as { version: number }).version = version;\n              (\n                error as unknown as { handlerResponse: unknown }\n              ).handlerResponse = result; // Include the actual invalid response\n\n              (\n                error as unknown as { handlerResponseType: string }\n              ).handlerResponseType =\n                typeof result === 'object' ? 'invalid_object' : typeof result;\n\n              (error as unknown as { errorCode: string }).errorCode =\n                'invalid_handler_response';\n\n              throw error;\n            }\n\n            // Set HTTP status code from the envelope response\n            reply.code(result.status_code);\n\n            if (result.status_code >= 400) {\n              reply.header('Cache-Control', 'no-store');\n            }\n\n            return result;\n          } catch (error) {\n            // Handle any errors thrown by the handler\n            fastify.log.error(\n              { err: error },\n              `Error in handler for ${pageType} v${version}`,\n            );\n\n            // Re-throw the error to let downstream error handlers manage it\n            // Add context metadata if it's not already present\n            if (error instanceof Error) {\n              if (!(error as unknown as { pageType: string }).pageType) {\n                (error as unknown as { pageType: string }).pageType = pageType;\n                (error as unknown as { version: number }).version = version;\n              }\n            }\n\n            throw error;\n          }\n        });\n      }\n    }\n  }\n\n  /**\n   * Programmatically invoke the latest registered handler for a page type.\n   *\n   * - Selects the highest version registered for the provided page type\n   * - Supports an optional timeout (milliseconds). On timeout, throws an Error\n   *   (mirrors fetch-style timeout behavior).\n   * - Returns an object indicating existence and the handler's envelope when available\n   */\n  public async callHandler<\n    T = unknown,\n    M extends BaseMeta = BaseMeta,\n  >(options: {\n    /** Original HTTP request (for cookies/headers/IP/auth) */\n    originalRequest: FastifyRequest;\n    /** Controlled reply (required for internal short-circuit path) */\n    controlledReply: ControlledReply;\n    pageType: string;\n    /** Timeout in milliseconds; if omitted or <= 0, no timeout is applied */\n    timeoutMS?: number;\n    /** Route params (from React Router via POST body) */\n    routeParams: Record<string, string>;\n    /** Query params (from React Router via POST body) */\n    queryParams: Record<string, unknown>;\n    /** Request path (from React Router via POST body) */\n    requestPath: string;\n    /** Original URL (from React Router via POST body) */\n    originalURL: string;\n  }): Promise<CallHandlerResult<T, M>> {\n    const {\n      originalRequest,\n      pageType,\n      timeoutMS,\n      routeParams,\n      queryParams,\n      requestPath,\n      originalURL,\n    } = options;\n\n    // Normalize pageType for consistent lookups\n    const normalizedPageType = this.normalizePageType(pageType);\n    const versionMap = this.handlersByPageType.get(normalizedPageType);\n\n    if (!versionMap || versionMap.size === 0) {\n      return { exists: false };\n    }\n\n    const latestVersion = this.getLatestVersion(pageType);\n    if (latestVersion === undefined) {\n      return { exists: false };\n    }\n\n    const handlerUncasted = versionMap.get(latestVersion);\n\n    if (!handlerUncasted) {\n      return { exists: false };\n    }\n\n    const handler = handlerUncasted as PageDataHandler<T, M>;\n\n    // Assemble the request body with normalized pageType\n    const finalParams: PageDataHandlerParams = {\n      pageType: normalizedPageType,\n      version: latestVersion,\n      invocationOrigin: 'internal',\n      routeParams,\n      queryParams,\n      requestPath,\n      originalURL,\n      APIResponseHelpers: getAPIResponseHelpersClass(originalRequest),\n    };\n\n    // Defer invocation to the microtask queue and normalize to a Promise.\n    // Using Promise.resolve().then(() => ...) ensures synchronous throws from\n    // the handler become Promise rejections instead of escaping before our\n    // timeout race is set up. Non-Promise returns are treated as resolved values.\n    const invocation = Promise.resolve().then(() =>\n      handler(originalRequest, options.controlledReply, finalParams),\n    );\n\n    // Attach a no-op catch when using a timeout to prevent a possible\n    // unhandledRejection if the timeout \"wins\" and the handler later rejects.\n    if (timeoutMS && timeoutMS > 0) {\n      void invocation.catch(() => {});\n    }\n\n    // Track the timeout ID to ensure it is cleared regardless of timeout path\n    let timeoutID: ReturnType<typeof setTimeout> | undefined;\n\n    // Build a single promise that either resolves to the handler result or rejects on timeout\n    const resultPromise: Promise<\n      PageResponseEnvelope<T, M> | APIResponseEnvelope<T, M> | false\n    > =\n      // Check if a timeout is specified\n      !timeoutMS || timeoutMS <= 0\n        ? // No timeout specified, return the handler result immediately\n          // Handler promise when no timer is specified\n          (invocation as Promise<\n            PageResponseEnvelope<T, M> | APIResponseEnvelope<T, M> | false\n          >)\n        : // If a timeout is specified, race the handler promise with a timer promise\n          (Promise.race([\n            // Handler promise\n            invocation,\n            // Timer promise\n            new Promise<never>((_, reject) => {\n              timeoutID = setTimeout(() => {\n                const error = new Error(`Request timeout after ${timeoutMS}ms`);\n                (error as unknown as { pageType: string }).pageType = pageType;\n                (error as unknown as { version: number }).version =\n                  latestVersion;\n                (error as unknown as { timeoutMS: number }).timeoutMS =\n                  timeoutMS;\n                (error as unknown as { errorCode: string }).errorCode =\n                  'handler_timeout';\n                reject(error);\n              }, timeoutMS);\n            }),\n          ]) as Promise<\n            PageResponseEnvelope<T, M> | APIResponseEnvelope<T, M> | false\n          >);\n\n    // Ensure the timeout is cleared regardless of timeout path\n    const result = await resultPromise.finally(() => {\n      if (timeoutID) {\n        clearTimeout(timeoutID);\n      }\n    });\n\n    // If handler returned false, it has already sent the response\n    if (result === false) {\n      return { exists: true, version: latestVersion, result: false };\n    }\n\n    // Validate that the handler returned a proper envelope object\n    if (!APIResponseHelpers.isValidEnvelope(result)) {\n      const error = new Error(\n        `Handler for page type \"${pageType}\" returned invalid response envelope`,\n      );\n      (error as unknown as { pageType: string }).pageType = pageType;\n      (error as unknown as { version: number }).version = latestVersion;\n      (error as unknown as { handlerResponse: unknown }).handlerResponse =\n        result;\n      (\n        error as unknown as { handlerResponseType: string }\n      ).handlerResponseType =\n        typeof result === 'object' ? 'invalid_object' : typeof result;\n      (error as unknown as { errorCode: string }).errorCode =\n        'invalid_handler_response';\n      throw error;\n    }\n\n    return { exists: true, version: latestVersion, result };\n  }\n\n  /**\n   * Check if a handler is registered for the given page type and version\n   * Used internally by pageDataLoader for short-circuit optimization\n   */\n  public hasHandler(pageType: string, version?: number): boolean {\n    const normalizedPageType = this.normalizePageType(pageType);\n    const versionMap = this.handlersByPageType.get(normalizedPageType);\n\n    if (!versionMap) {\n      return false;\n    }\n\n    // If no version specified, check if any version exists\n    if (version === undefined) {\n      return versionMap.size > 0;\n    }\n\n    return versionMap.has(version);\n  }\n\n  /**\n   * Normalize and validate pageType string\n   *\n   * Page types are treated as path segments that will be appended to the\n   * API prefix, version, and page data endpoint. Leading slashes are stripped\n   * to enforce this segment-based approach.\n   *\n   * Convention: Callers should NOT include leading slashes, but they are\n   * allowed and will be normalized away.\n   *\n   * @example\n   * normalizePageType(\"home\")            → \"home\"\n   * normalizePageType(\"/home\")           → \"home\" (leading slash stripped)\n   * normalizePageType(\"protected-page/\") → \"protected-page\" (trailing slash stripped)\n   */\n  private normalizePageType(pageType: string): string {\n    const trimmed = (pageType || '').trim();\n\n    if (trimmed.length === 0) {\n      throw new Error('Page type cannot be empty');\n    }\n\n    // Remove leading slash - page types are path segments, not full paths\n    let normalized = trimmed.startsWith('/') ? trimmed.slice(1) : trimmed;\n\n    // Remove trailing slash for consistency\n    if (normalized.endsWith('/') && normalized.length > 1) {\n      normalized = normalized.slice(0, -1);\n    }\n\n    // Prevent empty pageType after normalization\n    if (normalized.length === 0) {\n      throw new Error('Page type cannot be empty after normalization');\n    }\n\n    return normalized;\n  }\n\n  /**\n   * Returns the latest (highest) version registered for a given page type\n   */\n  private getLatestVersion(pageType: string): number | undefined {\n    const normalizedPageType = this.normalizePageType(pageType);\n    const versionMap = this.handlersByPageType.get(normalizedPageType);\n\n    if (!versionMap || versionMap.size === 0) {\n      return undefined;\n    }\n\n    let latestVersion = -Infinity;\n\n    for (const version of versionMap.keys()) {\n      if (version > latestVersion) {\n        latestVersion = version;\n      }\n    }\n\n    return Number.isFinite(latestVersion) ? latestVersion : undefined;\n  }\n\n  /**\n   * Register a page data loader handler without explicit version (uses default version if versioned)\n   */\n  private registerDataLoaderHandler(\n    pageType: string,\n    handler: PageDataHandler,\n  ): void;\n\n  /**\n   * Register a page data loader handler with explicit version\n   */\n  private registerDataLoaderHandler(\n    pageType: string,\n    version: number,\n    handler: PageDataHandler,\n  ): void;\n\n  /**\n   * Implementation of the overloaded method\n   */\n  private registerDataLoaderHandler(\n    pageType: string,\n    versionOrHandler: number | PageDataHandler,\n    handler?: PageDataHandler,\n  ): void {\n    // Normalize pageType to handle leading/trailing slashes\n    const normalizedPageType = this.normalizePageType(pageType);\n\n    let version: number;\n    let actualHandler: PageDataHandler;\n\n    if (typeof versionOrHandler === 'function') {\n      // 2-param overload: registerDataLoaderHandler(pageType, handler)\n      // Default to version 1 when not specified\n      version = 1;\n      actualHandler = versionOrHandler;\n    } else {\n      // 3-param overload: registerDataLoaderHandler(pageType, version, handler)\n      if (!handler) {\n        throw new Error(\n          'Handler function is required when version is specified',\n        );\n      }\n\n      validateVersion(versionOrHandler, 'Page data loader');\n      version = versionOrHandler;\n      actualHandler = handler;\n    }\n\n    // Get or create the version map for this page type\n    let versionMap = this.handlersByPageType.get(normalizedPageType);\n\n    if (!versionMap) {\n      versionMap = new Map<number, PageDataHandler>();\n      this.handlersByPageType.set(normalizedPageType, versionMap);\n    }\n\n    // Last registration wins for the same pageType + version\n    versionMap.set(version, actualHandler);\n  }\n}\n","import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';\nimport type {\n  APIResponseEnvelope,\n  BaseMeta,\n} from '../api-envelope/api-envelope-types';\nimport { APIResponseHelpers } from '../api-envelope/response-helpers';\nimport type { APIResponseHelpersClass, ControlledReply } from '../types';\nimport { createControlledReply } from './server-utils';\nimport {\n  validateVersion,\n  validateSingleVersionWhenDisabled,\n} from './version-helpers';\nimport { getAPIResponseHelpersClass } from './api-response-helpers-utils';\n\n/**\n * Supported HTTP methods for API routes\n */\nexport type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n\n/**\n * Handler function type for generic API endpoints\n *\n * Handlers should return an APIResponseEnvelope. Returning any other object\n * will result in a runtime error being thrown during request handling.\n *\n * **Return false** when you've sent a custom response (e.g., using\n * APIResponseHelpers.sendErrorEnvelope() or validation helpers like ensureJSONBody).\n * This signals that the handler has already sent the response and the framework\n * should not attempt to send anything.\n */\nexport type APIRouteHandler<T = unknown, M extends BaseMeta = BaseMeta> = (\n  request: FastifyRequest,\n  controlledReply: ControlledReply,\n  params: {\n    /** HTTP method used for this route */\n    method: HTTPMethod;\n    /** Endpoint path segment after version/prefix (e.g., \"users/:id\") */\n    endpoint: string;\n    /** Registered version for this handler (always set, defaults to 1) */\n    version: number;\n    /** Full path used to register the route */\n    fullPath: string;\n    /** Route params extracted from Fastify (raw values) */\n    routeParams: Record<string, unknown>;\n    /** Query params extracted from Fastify (raw values) */\n    queryParams: Record<string, unknown>;\n    /** Path portion of the URL without query string */\n    requestPath: string;\n    /** Original URL including query string */\n    originalURL: string;\n    /** The APIResponseHelpers class configured on this server (use this instead of importing directly) */\n    APIResponseHelpers: APIResponseHelpersClass;\n  },\n  // Allow either sync or async returns, including false for early-exit cases\n) =>\n  | APIResponseEnvelope<T, M>\n  | Promise<APIResponseEnvelope<T, M>>\n  | false\n  | Promise<false>;\n\n/**\n * Internal structure for storing handlers by method → endpoint → version\n */\ntype VersionToHandlerMap<T, M extends BaseMeta> = Map<\n  number,\n  APIRouteHandler<T, M>\n>;\ntype EndpointToVersionMap<T, M extends BaseMeta> = Map<\n  string,\n  VersionToHandlerMap<T, M>\n>;\ntype MethodToEndpointMap<T, M extends BaseMeta> = Map<\n  HTTPMethod,\n  EndpointToVersionMap<T, M>\n>;\n\n/**\n * Helper class for registering generic API routes, mirroring the ergonomics of\n * DataLoaderServerHandlerHelpers but for non-page endpoints.\n *\n * - Supports versioned or non-versioned endpoints\n * - Validates handler return envelopes using APIResponseHelpers\n * - Provides convenient shortcuts via .api.get/.post/.put/.delete/.patch\n * - Controls wildcard usage via constructor flag\n *\n * ## Endpoint Convention\n *\n * Endpoints should be specified as path segments WITHOUT leading slashes:\n * - ✅ `server.api.get(\"users/:id\", handler)`  → `/api/v1/users/:id`\n * - ✅ `server.api.post(\"upload/avatar\", h)`   → `/api/v1/upload/avatar`\n * - ⚠️ `server.api.get(\"/users/:id\", handler)` → `/api/v1/users/:id` (leading slash stripped)\n *\n * Leading slashes are allowed but will be automatically stripped during normalization.\n *\n * This design treats endpoints as path segments that get appended to the API prefix\n * and version, rather than as absolute paths.\n */\nexport class APIRoutesServerHelpers<\n  T = unknown,\n  M extends BaseMeta = BaseMeta,\n> {\n  private handlersByMethod: MethodToEndpointMap<T, M> = new Map();\n\n  // API Shortcut method-specific helpers\n  private readonly api = {\n    /**\n     * Register a GET endpoint\n     *\n     * @param endpoint - Path segment (e.g., \"users/:id\" or \"items\")\n     *   - Convention: Do NOT include leading slash - endpoints are path segments, not full paths\n     *   - Leading slashes are allowed but will be stripped during normalization\n     *   - The final path is constructed as: prefix + version + endpoint\n     *     Example: \"users/:id\" → \"/api/v1/users/:id\" (with prefix=\"/api\", versioned)\n     * @param handlerOrVersion - Handler function, or version number if using versioned handler\n     * @param maybeHandler - Handler function when version is specified\n     */\n    get: (\n      endpoint: string,\n      handlerOrVersion: number | APIRouteHandler<T, M>,\n      maybeHandler?: APIRouteHandler<T, M>,\n    ): void => {\n      if (typeof handlerOrVersion === 'number') {\n        this.registerAPIHandler(\n          'GET',\n          endpoint,\n          handlerOrVersion,\n          maybeHandler as APIRouteHandler<T, M>,\n        );\n      } else {\n        this.registerAPIHandler('GET', endpoint, handlerOrVersion);\n      }\n    },\n    /**\n     * Register a POST endpoint\n     *\n     * @param endpoint - Path segment (e.g., \"users\" or \"upload/avatar\")\n     *   - Convention: Do NOT include leading slash - endpoints are path segments, not full paths\n     *   - Leading slashes are allowed but will be stripped during normalization\n     * @param handlerOrVersion - Handler function, or version number if using versioned handler\n     * @param maybeHandler - Handler function when version is specified\n     */\n    post: (\n      endpoint: string,\n      handlerOrVersion: number | APIRouteHandler<T, M>,\n      maybeHandler?: APIRouteHandler<T, M>,\n    ): void => {\n      if (typeof handlerOrVersion === 'number') {\n        this.registerAPIHandler(\n          'POST',\n          endpoint,\n          handlerOrVersion,\n          maybeHandler as APIRouteHandler<T, M>,\n        );\n      } else {\n        this.registerAPIHandler('POST', endpoint, handlerOrVersion);\n      }\n    },\n    /**\n     * Register a PUT endpoint\n     *\n     * @param endpoint - Path segment (e.g., \"users/:id\" or \"items/:itemId\")\n     *   - Convention: Do NOT include leading slash - endpoints are path segments, not full paths\n     *   - Leading slashes are allowed but will be stripped during normalization\n     * @param handlerOrVersion - Handler function, or version number if using versioned handler\n     * @param maybeHandler - Handler function when version is specified\n     */\n    put: (\n      endpoint: string,\n      handlerOrVersion: number | APIRouteHandler<T, M>,\n      maybeHandler?: APIRouteHandler<T, M>,\n    ): void => {\n      if (typeof handlerOrVersion === 'number') {\n        this.registerAPIHandler(\n          'PUT',\n          endpoint,\n          handlerOrVersion,\n          maybeHandler as APIRouteHandler<T, M>,\n        );\n      } else {\n        this.registerAPIHandler('PUT', endpoint, handlerOrVersion);\n      }\n    },\n    /**\n     * Register a DELETE endpoint\n     *\n     * @param endpoint - Path segment (e.g., \"users/:id\" or \"items/:itemId\")\n     *   - Convention: Do NOT include leading slash - endpoints are path segments, not full paths\n     *   - Leading slashes are allowed but will be stripped during normalization\n     * @param handlerOrVersion - Handler function, or version number if using versioned handler\n     * @param maybeHandler - Handler function when version is specified\n     */\n    delete: (\n      endpoint: string,\n      handlerOrVersion: number | APIRouteHandler<T, M>,\n      maybeHandler?: APIRouteHandler<T, M>,\n    ): void => {\n      if (typeof handlerOrVersion === 'number') {\n        this.registerAPIHandler(\n          'DELETE',\n          endpoint,\n          handlerOrVersion,\n          maybeHandler as APIRouteHandler<T, M>,\n        );\n      } else {\n        this.registerAPIHandler('DELETE', endpoint, handlerOrVersion);\n      }\n    },\n    /**\n     * Register a PATCH endpoint\n     *\n     * @param endpoint - Path segment (e.g., \"users/:id\" or \"profile\")\n     *   - Convention: Do NOT include leading slash - endpoints are path segments, not full paths\n     *   - Leading slashes are allowed but will be stripped during normalization\n     * @param handlerOrVersion - Handler function, or version number if using versioned handler\n     * @param maybeHandler - Handler function when version is specified\n     */\n    patch: (\n      endpoint: string,\n      handlerOrVersion: number | APIRouteHandler<T, M>,\n      maybeHandler?: APIRouteHandler<T, M>,\n    ): void => {\n      if (typeof handlerOrVersion === 'number') {\n        this.registerAPIHandler(\n          'PATCH',\n          endpoint,\n          handlerOrVersion,\n          maybeHandler as APIRouteHandler<T, M>,\n        );\n      } else {\n        this.registerAPIHandler('PATCH', endpoint, handlerOrVersion);\n      }\n    },\n  } as const;\n\n  /**\n   * Register all stored handlers with the Fastify instance.\n   *\n   * @param fastify - The Fastify instance to register routes on\n   * @param apiPrefix - Pre-normalized API prefix (e.g., \"/api\")\n   * @param options - Optional config for versioning and wildcard behavior\n   */\n  public registerRoutes(\n    fastify: FastifyInstance,\n    apiPrefix: string,\n    options?: {\n      versioned?: boolean;\n      allowWildcardAtRoot?: boolean;\n    },\n  ): void {\n    const useVersioning = options?.versioned ?? true;\n\n    // Prefix is already normalized by the caller (APIServer/SSRServer)\n    const prefix = apiPrefix;\n    const isRootPrefix = prefix === '/';\n    const allowWildAtRoot = options?.allowWildcardAtRoot === true;\n\n    for (const [method, endpointMap] of this.handlersByMethod) {\n      for (const [endpoint, versionMap] of endpointMap) {\n        // Enforce wildcard rule based on prefix: allow wildcards only when prefix is non-root\n        if (\n          !allowWildAtRoot &&\n          isRootPrefix &&\n          (endpoint === '*' || endpoint.includes('*'))\n        ) {\n          throw new Error(\n            \"Wildcard endpoints are not allowed when apiEndpointPrefix is root ('/' or empty). Set a non-root prefix like '/api' to use wildcards.\",\n          );\n        }\n\n        // Check if versioning is disabled but multiple versions exist\n        validateSingleVersionWhenDisabled(\n          useVersioning,\n          versionMap,\n          `Endpoint \"${endpoint}\" (${method})`,\n        );\n\n        // Register each version\n        for (const [version, handler] of versionMap) {\n          const fullPath = this.buildPath(\n            prefix,\n            endpoint,\n            useVersioning,\n            version,\n          );\n\n          // Register with Fastify according to method\n          const wrappedHandler = async (\n            request: FastifyRequest,\n            reply: FastifyReply,\n          ) => {\n            const routeParams = (request.params || {}) as Record<\n              string,\n              unknown\n            >;\n\n            const queryParams = (request.query || {}) as Record<\n              string,\n              unknown\n            >;\n\n            const originalURL = request.url;\n            const requestPath = originalURL.split('?')[0] || originalURL;\n\n            const envelope = await handler(\n              request,\n              createControlledReply(request, reply),\n              {\n                method,\n                endpoint,\n                version,\n                fullPath,\n                routeParams,\n                queryParams,\n                requestPath,\n                originalURL,\n                APIResponseHelpers: getAPIResponseHelpersClass(request),\n              },\n            );\n\n            // If handler returned false, it has already sent the response\n            // (e.g., via reply.sendErrorEnvelope() in a validation helper)\n            if (envelope === false) {\n              // Verify that the response was actually sent by the handler\n              if (!reply.sent && !reply.raw.headersSent) {\n                // Handler bug: returned false without sending a response\n                // This is a programming error in the user's handler code\n                const error = new Error(\n                  `API route ${method} ${fullPath} returned false but did not send a response. ` +\n                    `When returning false, you must send a response first using APIResponseHelpers.sendErrorEnvelope().`,\n                );\n                (error as unknown as { errorCode: string }).errorCode =\n                  'handler_returned_false_without_sending';\n                (error as unknown as { route: string }).route =\n                  `${method} ${fullPath}`;\n                throw error;\n              }\n\n              return; // Response was sent by handler, do not send anything more\n            }\n\n            if (!APIResponseHelpers.isValidEnvelope(envelope)) {\n              const error = new Error(\n                'API route ' +\n                  method +\n                  ' ' +\n                  fullPath +\n                  ' returned an invalid response envelope',\n              );\n              (error as unknown as { errorCode: string }).errorCode =\n                'invalid_handler_response';\n              (error as unknown as { route: string }).route =\n                method + ' ' + fullPath;\n              (\n                error as unknown as { handlerResponse: unknown }\n              ).handlerResponse = envelope;\n              throw error;\n            }\n\n            reply.code(envelope.status_code);\n\n            if (envelope.status_code >= 400) {\n              reply.header('Cache-Control', 'no-store');\n            }\n\n            return envelope;\n          };\n\n          switch (method) {\n            case 'GET':\n              fastify.get(fullPath, wrappedHandler);\n              break;\n            case 'POST':\n              fastify.post(fullPath, wrappedHandler);\n              break;\n            case 'PUT':\n              fastify.put(fullPath, wrappedHandler);\n              break;\n            case 'DELETE':\n              fastify.delete(fullPath, wrappedHandler);\n              break;\n            case 'PATCH':\n              fastify.patch(fullPath, wrappedHandler);\n              break;\n          }\n        }\n      }\n    }\n  }\n\n  /**\n   * Expose only the lightweight shortcuts method surface for external consumers\n   */\n  public get apiMethod() {\n    return this.api;\n  }\n\n  /**\n   * Check if any API handlers have been registered\n   * Useful for validation when API handling is disabled\n   */\n  public hasRegisteredHandlers(): boolean {\n    return this.handlersByMethod.size > 0;\n  }\n\n  /**\n   * Normalize and validate endpoint string (no prefix, no version)\n   *\n   * Endpoints are treated as path segments that will be appended to the\n   * API prefix and version. Leading slashes are stripped to enforce this\n   * segment-based approach.\n   *\n   * Convention: Callers should NOT include leading slashes, but they are\n   * allowed and will be normalized away.\n   *\n   * @example\n   * normalizeEndpoint(\"users/:id\")  → \"users/:id\"\n   * normalizeEndpoint(\"/users/:id\") → \"users/:id\" (leading slash stripped)\n   * normalizeEndpoint(\"upload/\")    → \"upload\"    (trailing slash stripped)\n   */\n  private normalizeEndpoint(endpoint: string): string {\n    const trimmed = (endpoint || '').trim();\n\n    if (trimmed.length === 0) {\n      throw new Error('Endpoint path segment cannot be empty');\n    }\n\n    // Remove leading slash - endpoints are path segments, not full paths\n    let normalized = trimmed.startsWith('/') ? trimmed.slice(1) : trimmed;\n\n    // Remove trailing slash for consistency\n    if (normalized.endsWith('/') && normalized.length > 1) {\n      normalized = normalized.slice(0, -1);\n    }\n\n    // Prevent empty endpoint after normalization\n    if (normalized.length === 0) {\n      throw new Error(\n        'Endpoint path segment cannot be empty after normalization',\n      );\n    }\n\n    return normalized;\n  }\n\n  private ensureMethod(method: string): HTTPMethod {\n    const upper = (method || '').toUpperCase();\n    if (\n      upper === 'GET' ||\n      upper === 'POST' ||\n      upper === 'PUT' ||\n      upper === 'DELETE' ||\n      upper === 'PATCH'\n    ) {\n      return upper as HTTPMethod;\n    }\n\n    throw new Error('Unsupported HTTP method: ' + method);\n  }\n\n  private getOrCreateEndpointMap(\n    method: HTTPMethod,\n  ): EndpointToVersionMap<T, M> {\n    let map = this.handlersByMethod.get(method);\n\n    if (!map) {\n      map = new Map();\n      this.handlersByMethod.set(method, map);\n    }\n\n    return map;\n  }\n\n  private getOrCreateVersionMap(\n    endpointMap: EndpointToVersionMap<T, M>,\n    endpoint: string,\n  ): VersionToHandlerMap<T, M> {\n    let versionMap = endpointMap.get(endpoint);\n\n    if (!versionMap) {\n      versionMap = new Map();\n      endpointMap.set(endpoint, versionMap);\n    }\n\n    return versionMap;\n  }\n\n  /**\n   * Register an API handler without explicit version (uses default version if versioned)\n   *\n   * This method is used internally by the `.api` shortcuts method (`.api.get`, `.api.post`, etc.).\n   * External users should use those shortcuts method instead, which are exposed on SSRServer and\n   * APIServer via the `.api` getter property.\n   *\n   * @example\n   * // Preferred public API:\n   * server.api.get('users/:id', handler)\n   * server.api.post('items', 2, handler)\n   */\n  private registerAPIHandler(\n    method: HTTPMethod,\n    endpoint: string,\n    handler: APIRouteHandler<T, M>,\n  ): void;\n\n  /**\n   * Register an API handler with explicit version\n   *\n   * This method is used internally by the `.api` method.\n   * External users should use the public method instead: `server.api.get()`, etc.\n   */\n  private registerAPIHandler(\n    method: HTTPMethod,\n    endpoint: string,\n    version: number,\n    handler: APIRouteHandler<T, M>,\n  ): void;\n\n  /**\n   * Implementation of the overloaded method\n   *\n   * This is the internal implementation used by the `.api` method.\n   */\n  private registerAPIHandler(\n    method: HTTPMethod,\n    endpoint: string,\n    versionOrHandler: number | APIRouteHandler<T, M>,\n    handlerMaybe?: APIRouteHandler<T, M>,\n  ): void {\n    const httpMethod = this.ensureMethod(method);\n    const normalizedEndpoint = this.normalizeEndpoint(endpoint);\n\n    let version: number;\n    let handler: APIRouteHandler<T, M>;\n\n    if (typeof versionOrHandler === 'function') {\n      // 2-param overload: registerAPIHandler(method, endpoint, handler)\n      // Default to version 1 when not specified\n      version = 1;\n      handler = versionOrHandler;\n    } else {\n      // 3-param overload: registerAPIHandler(method, endpoint, version, handler)\n      if (!handlerMaybe) {\n        throw new Error(\n          'Handler function is required when version is specified',\n        );\n      }\n\n      validateVersion(versionOrHandler, 'API');\n      version = versionOrHandler;\n      handler = handlerMaybe;\n    }\n\n    const endpointMap = this.getOrCreateEndpointMap(httpMethod);\n    const versionMap = this.getOrCreateVersionMap(\n      endpointMap,\n      normalizedEndpoint,\n    );\n\n    // Last registration wins for the same method + endpoint + version\n    versionMap.set(version, handler);\n  }\n\n  private buildPath(\n    prefix: string,\n    endpoint: string,\n    useVersioning: boolean,\n    version: number,\n  ): string {\n    const base = useVersioning ? prefix + '/v' + version : prefix;\n    return base + '/' + endpoint;\n  }\n}\n","import type { FastifyInstance, FastifyRequest } from 'fastify';\nimport websocket from '@fastify/websocket';\nimport type { WebSocket } from 'ws';\nimport type { APIResponseEnvelope } from '../api-envelope/api-envelope-types';\nimport { APIResponseHelpers } from '../api-envelope/response-helpers';\nimport type { APIResponseHelpersClass, WebSocketOptions } from '../types';\n\n/**\n * Internal Fastify WebSocket plugin options interface\n * Maps our WebSocketOptions to @fastify/websocket configuration\n */\ninterface FastifyWebSocketPluginOptions {\n  options: {\n    clientTracking: boolean;\n    perMessageDeflate: boolean;\n    maxPayload: number;\n  };\n  preClose?: (done: () => void) => void;\n}\n\n/**\n * Parameters passed to WebSocket handlers with extracted routing context\n *\n * Similar to page data and API handlers, this provides pre-extracted routing\n * information while keeping the raw request available for cookies/headers/IP.\n */\nexport interface WebSocketHandlerParams {\n  /** Request path (from Fastify, without query string) */\n  path: string;\n  /** Original URL (from Fastify, with query string) */\n  originalURL: string;\n  /** Query params (from Fastify) */\n  queryParams: Record<string, unknown>;\n  /** Route params (from Fastify, for parameterized paths) */\n  routeParams: Record<string, unknown>;\n}\n\n/**\n * Extract WebSocket handler params from Fastify request\n * Pure helper function with defensive fallbacks\n */\nfunction extractWebSocketParams(\n  request: FastifyRequest,\n): WebSocketHandlerParams {\n  const routeParams = (request.params || {}) as Record<string, unknown>;\n  const queryParams = (request.query || {}) as Record<string, unknown>;\n  const originalURL = request.url;\n  const path = originalURL.split('?')[0] || originalURL;\n\n  return {\n    path,\n    originalURL,\n    queryParams,\n    routeParams,\n  };\n}\n\n/**\n * WebSocket preValidation result - discriminated union\n */\nexport type WebSocketPreValidationResult =\n  | {\n      /** Allow WebSocket upgrade */\n      action: 'upgrade';\n      /** Optional data to pass to the WebSocket handler */\n      data?: Record<string, unknown>;\n    }\n  | {\n      /** Reject WebSocket upgrade with API envelope response */\n      action: 'reject';\n      /** API envelope response to send when rejecting */\n      envelope: APIResponseEnvelope;\n    };\n\n/**\n * WebSocket upgrade validation information stored on request\n */\nexport interface WebSocketUpgradeInfo {\n  /** Whether the request path matches a registered WebSocket handler */\n  validPath: boolean;\n  /** Whether the handler has a preValidation function */\n  hasPreValidator: boolean;\n  /** The result from the preValidation handler, if called */\n  upgradeResult: WebSocketPreValidationResult | null;\n  /** Any error that occurred during preValidation */\n  error: Error | null;\n}\n\n/**\n * WebSocket handler configuration\n */\nexport interface WebSocketHandlerConfig {\n  /** The WebSocket endpoint path */\n  path: string;\n  /** Optional preValidation function that returns upgrade/reject decision with optional envelope */\n  preValidate?: (\n    request: FastifyRequest,\n    params: WebSocketHandlerParams,\n  ) => Promise<WebSocketPreValidationResult> | WebSocketPreValidationResult;\n  /** WebSocket connection handler */\n  handler: (\n    socket: WebSocket,\n    request: FastifyRequest,\n    params: WebSocketHandlerParams,\n    upgradeData?: Record<string, unknown>,\n  ) => Promise<void> | void;\n}\n\nexport class WebSocketServerHelpers {\n  private readonly APIResponseHelpersClass: APIResponseHelpersClass;\n  private readonly webSocketOptions: WebSocketOptions;\n  private handlersByPath = new Map<string, WebSocketHandlerConfig>();\n\n  constructor(\n    APIResponseHelpersClass: APIResponseHelpersClass,\n    webSocketOptions: WebSocketOptions = {},\n  ) {\n    // Initialize handlers storage\n    this.APIResponseHelpersClass = APIResponseHelpersClass;\n    this.webSocketOptions = webSocketOptions;\n  }\n\n  /**\n   * Register the @fastify/websocket plugin with the Fastify instance\n   *\n   * @param fastify The Fastify instance to register the WebSocket plugin with\n   */\n  public async registerWebSocketPlugin(\n    fastify: FastifyInstance,\n  ): Promise<void> {\n    const pluginOptions: FastifyWebSocketPluginOptions = {\n      options: {\n        clientTracking: true,\n        perMessageDeflate: this.webSocketOptions.perMessageDeflate ?? false,\n        maxPayload: this.webSocketOptions.maxPayload ?? 100 * 1024 * 1024, // 100MB default\n      },\n    };\n\n    // Add preClose handler if provided\n    if (this.webSocketOptions.preClose) {\n      const userPreCloseHandler = this.webSocketOptions.preClose;\n      pluginOptions.preClose = (done) => {\n        // Get the WebSocket server clients\n        const websocketServer = (\n          fastify as unknown as { websocketServer?: { clients: Set<unknown> } }\n        ).websocketServer;\n        const clients = websocketServer?.clients || new Set();\n\n        // Call user's preClose handler with clients and handle both sync throws\n        // and async rejections (Promise.resolve wraps sync throws into a rejection)\n        Promise.resolve()\n          .then(() => userPreCloseHandler(clients))\n          .then(() => done())\n          .catch((error) => {\n            fastify.log.error(\n              { err: error },\n              'WebSocket preClose handler error:',\n            );\n            done(); // Still call done to prevent hanging\n          });\n      };\n    }\n\n    await fastify.register(websocket, pluginOptions);\n  }\n\n  /**\n   * Register a WebSocket handler for a specific path\n   *\n   * @param config WebSocket handler configuration\n   */\n  public registerWebSocketHandler(config: WebSocketHandlerConfig): void {\n    // Last registration wins for the same path (consistent with other helpers)\n    this.handlersByPath.set(config.path, config);\n  }\n\n  /**\n   * Register WebSocket routes and handlers with the Fastify instance\n   */\n  public registerRoutes(fastify: FastifyInstance): void {\n    // Register all stored WebSocket handlers\n    for (const [path, config] of this.handlersByPath) {\n      fastify.register(function (fastify) {\n        fastify.get(path, { websocket: true }, (socket, request) => {\n          // Check upgrade validation info from preValidation hook\n          const upgradeInfo = (\n            request as unknown as { wsUpgradeInfo?: WebSocketUpgradeInfo }\n          ).wsUpgradeInfo;\n\n          // Disconnect immediately if path was not valid\n          if (!upgradeInfo || !upgradeInfo.validPath) {\n            socket.close(1008, 'Invalid WebSocket path');\n            return;\n          }\n\n          // Fallback check: ensure upgrade was actually allowed if preValidator exists\n          if (upgradeInfo.hasPreValidator) {\n            if (\n              !upgradeInfo.upgradeResult ||\n              upgradeInfo.upgradeResult.action !== 'upgrade'\n            ) {\n              socket.close(1008, 'WebSocket upgrade not allowed');\n              return;\n            }\n          }\n\n          // Get upgrade data from request if available\n          const upgradeData = (\n            request as unknown as { wsUpgradeData?: Record<string, unknown> }\n          ).wsUpgradeData;\n\n          // Extract params from request\n          const params = extractWebSocketParams(request);\n\n          // Call the handler with socket, request, params, and upgrade data\n          return config.handler(socket, request, params, upgradeData);\n        });\n      });\n    }\n  }\n\n  /**\n   * Register preValidation hook for WebSocket handling\n   *\n   * This hook checks if the request path matches any registered WebSocket handlers\n   * and runs their preValidation logic to determine upgrade/reject decisions.\n   *\n   * @param fastify The Fastify instance to register the hook with\n   */\n  public registerPreValidationHook(fastify: FastifyInstance): void {\n    fastify.addHook('preValidation', async (request, reply) => {\n      // Only act on WebSocket upgrade attempts - check both headers and Fastify's ws flag\n      const upgrade = request.headers['upgrade'];\n\n      if (\n        !request.ws ||\n        !upgrade ||\n        typeof upgrade !== 'string' ||\n        upgrade.toLowerCase() !== 'websocket'\n      ) {\n        return;\n      }\n\n      // Optional sanity-check for Connection: upgrade\n      const connHeader = Array.isArray(request.headers.connection)\n        ? request.headers.connection.join(',')\n        : String(request.headers.connection ?? '');\n\n      if (!/\\bupgrade\\b/i.test(connHeader)) {\n        // Early bail if invalid upgrade attempt\n        await reply\n          .code(400)\n          .header('Cache-Control', 'no-store')\n          .send({ error: 'Invalid Connection header for upgrade' });\n        return;\n      }\n\n      // Initialize upgrade info object\n      const upgradeInfo: WebSocketUpgradeInfo = {\n        validPath: false,\n        hasPreValidator: false,\n        upgradeResult: null,\n        error: null,\n      };\n\n      // Store upgrade info on request for handler access\n      (\n        request as unknown as { wsUpgradeInfo?: WebSocketUpgradeInfo }\n      ).wsUpgradeInfo = upgradeInfo;\n\n      // Find matching WebSocket handler for this path\n      const path = request.url.split('?')[0];\n      const matchingHandler = this.handlersByPath.get(path);\n\n      if (!matchingHandler) {\n        // No handler found - reject with 404 error\n        upgradeInfo.validPath = false;\n        upgradeInfo.hasPreValidator = false;\n\n        const notFoundEnvelope = this.createNotFoundEnvelope(request, path);\n\n        if (notFoundEnvelope.status_code >= 400) {\n          reply.header('Cache-Control', 'no-store');\n        }\n\n        await reply.code(notFoundEnvelope.status_code).send(notFoundEnvelope);\n        return;\n      }\n\n      // Handler found - mark as valid path\n      upgradeInfo.validPath = true;\n\n      if (!matchingHandler.preValidate) {\n        // No preValidation function - allow upgrade\n        upgradeInfo.hasPreValidator = false;\n        return;\n      }\n\n      // PreValidation handler exists - call it\n      upgradeInfo.hasPreValidator = true;\n\n      try {\n        // Extract params from request\n        const params = extractWebSocketParams(request);\n\n        // Run the preValidation function\n        const result = await matchingHandler.preValidate(request, params);\n        upgradeInfo.upgradeResult = result;\n\n        if (result.action === 'reject') {\n          // Send API envelope response and prevent WebSocket upgrade\n          const envelope = result.envelope;\n\n          // Validate the envelope before sending\n          if (!APIResponseHelpers.isValidEnvelope(envelope)) {\n            const error = new Error(\n              `WebSocket preValidation handler returned invalid envelope for path: ${path}`,\n            );\n            (error as unknown as { path: string }).path = path;\n            (error as unknown as { handlerResponse: unknown }).handlerResponse =\n              envelope;\n            (error as unknown as { errorCode: string }).errorCode =\n              'websocket_invalid_prevalidation_envelope';\n            throw error;\n          }\n\n          if (envelope.status_code >= 400) {\n            reply.header('Cache-Control', 'no-store');\n          }\n\n          await reply.code(envelope.status_code).send(envelope);\n          return;\n        }\n\n        // Action is \"upgrade\" - allow WebSocket upgrade to proceed\n        // Store any upgrade data on the request for handler access\n        if (result.action === 'upgrade' && result.data !== undefined) {\n          (\n            request as unknown as { wsUpgradeData?: Record<string, unknown> }\n          ).wsUpgradeData = result.data;\n        }\n      } catch (error) {\n        // PreValidation function threw an error - store error and reject with 500\n        upgradeInfo.error =\n          error instanceof Error ? error : new Error(String(error));\n        const errorEnvelope = this.createErrorEnvelope(request, error);\n\n        if (errorEnvelope.status_code >= 400) {\n          reply.header('Cache-Control', 'no-store');\n        }\n\n        await reply.code(errorEnvelope.status_code).send(errorEnvelope);\n      }\n    });\n  }\n\n  /**\n   * Create not found envelope for unregistered WebSocket paths\n   * @private\n   */\n  private createNotFoundEnvelope(\n    request: FastifyRequest,\n    path: string,\n  ): APIResponseEnvelope {\n    return this.APIResponseHelpersClass.createAPIErrorResponse({\n      request,\n      statusCode: 404,\n      errorCode: 'websocket_handler_not_found',\n      errorMessage: `No WebSocket handler registered for path: ${path}`,\n      errorDetails: {\n        path,\n      },\n      meta: {\n        page: {\n          title: 'WebSocket Handler Not Found',\n          description: 'No WebSocket handler registered for this path',\n        },\n      },\n    });\n  }\n\n  /**\n   * Create error envelope for preValidation exceptions\n   * @private\n   */\n  private createErrorEnvelope(\n    request: FastifyRequest,\n    error: unknown,\n  ): APIResponseEnvelope {\n    return this.APIResponseHelpersClass.createAPIErrorResponse({\n      request,\n      statusCode: 500,\n      errorCode: 'websocket_validation_error',\n      errorMessage:\n        error instanceof Error ? error.message : 'Unknown validation error',\n      errorDetails: {\n        error: error instanceof Error ? error.message : String(error),\n      },\n      meta: {\n        page: {\n          title: 'WebSocket Validation Error',\n          description: 'An error occurred during WebSocket validation',\n        },\n      },\n    });\n  }\n}\n","/**\n * Internal cookie forwarding utilities\n *\n * All functions are pure and accept explicit allow/block sets.\n *\n * Policy behavior:\n * - If both allow and block are empty/undefined, all cookies are allowed\n * - If allow is non-empty, only cookie names in allow are permitted\n * - Block list always takes precedence and denies matching cookie names\n */\n\n/** Determine if a cookie name is permitted by the current allow/block policy */\nexport function isCookieNameAllowed(\n  name: string,\n  allowList?: ReadonlySet<string>,\n  blockList?: ReadonlySet<string> | true,\n): boolean {\n  const cookieName = name.trim();\n\n  if (!cookieName) {\n    return false;\n  }\n\n  // Block all\n  if (blockList === true) {\n    return false;\n  }\n\n  if (blockList && blockList.has(cookieName)) {\n    return false;\n  }\n\n  if (allowList) {\n    return allowList.has(cookieName);\n  }\n\n  return true;\n}\n\n/**\n * Filter an inbound Cookie header value according to policy\n * Returns the new header string, or undefined if no cookies remain\n */\nexport function filterIncomingCookieHeader(\n  header: string | undefined | null,\n  allowList?: ReadonlySet<string>,\n  blockList?: ReadonlySet<string> | true,\n): string | undefined {\n  if (!header) {\n    return undefined;\n  }\n\n  // Cookie: name=value; name2=value2; ...\n  const parts = header\n    .split(';')\n    .map((s) => s.trim())\n    .filter(Boolean);\n\n  if (parts.length === 0) {\n    return undefined;\n  }\n\n  const allowedPairs: string[] = [];\n\n  for (const part of parts) {\n    const eqIndex = part.indexOf('=');\n\n    if (eqIndex <= 0) {\n      continue; // skip invalid segment or empty name\n    }\n\n    const name = part.slice(0, eqIndex).trim();\n    // Allow empty value (e.g., \"x=\") to pass through\n\n    if (isCookieNameAllowed(name, allowList, blockList)) {\n      allowedPairs.push(part);\n    }\n  }\n\n  if (allowedPairs.length === 0) {\n    return undefined;\n  }\n\n  return allowedPairs.join('; ');\n}\n\n/**\n * Filter outbound Set-Cookie header values\n * Accepts a single header value or an array of values and returns only allowed ones\n */\nexport function filterSetCookieHeaderValues(\n  values: string | string[],\n  allowList?: ReadonlySet<string>,\n  blockList?: ReadonlySet<string> | true,\n): string[] {\n  const arr = Array.isArray(values) ? values : [values];\n  const result: string[] = [];\n\n  // Fast path: block all\n  if (blockList === true) {\n    return result;\n  }\n\n  for (const value of arr) {\n    // Set-Cookie: name=value; Attr=...; Attr2=...\n    const firstSemicolon = value.indexOf(';');\n    const firstSegment =\n      firstSemicolon === -1 ? value : value.slice(0, firstSemicolon);\n\n    const eqIndex = firstSegment.indexOf('=');\n\n    if (eqIndex <= 0) {\n      continue; // invalid\n    }\n\n    const name = firstSegment.slice(0, eqIndex).trim();\n\n    if (isCookieNameAllowed(name, allowList, blockList)) {\n      result.push(value);\n    }\n  }\n\n  return result;\n}\n","import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';\nimport type { FileUploadsConfig } from '../types';\nimport multipart from '@fastify/multipart';\nimport { getAPIResponseHelpersClass } from './api-response-helpers-utils';\n\n/**\n * Normalize a URL path for route matching\n * - Removes query strings\n * - Removes trailing slashes (except for root path \"/\")\n * - Collapses multiple consecutive slashes\n * @param path - The path to normalize\n * @returns Normalized path\n */\nfunction normalizePath(path: string): string {\n  // Remove query string\n  let normalized = path.split('?')[0];\n\n  // Collapse multiple consecutive slashes to single slash\n  normalized = normalized.replace(/\\/+/g, '/');\n\n  // Remove trailing slash (unless it's the root path \"/\")\n  if (normalized.length > 1 && normalized.endsWith('/')) {\n    normalized = normalized.slice(0, -1);\n  }\n\n  return normalized;\n}\n\n/**\n * Check if a URL matches a route pattern (supports wildcards)\n *\n * Wildcard patterns:\n * - Single asterisk (*) matches exactly one path segment\n *   Example: \"/api/star/upload\" matches \"/api/foo/upload\" but NOT \"/api/foo/bar/upload\"\n * - Double asterisk (**) matches zero or more path segments\n *   Example: \"/api/**\" matches \"/api\", \"/api/foo\", \"/api/foo/bar\", etc.\n *\n * @param url - The URL to check (will be normalized before matching)\n * @param pattern - The pattern to match against (will be normalized, supports * and ** wildcards)\n * @returns true if the URL matches the pattern\n */\nexport function matchesRoutePattern(url: string, pattern: string): boolean {\n  // Normalize both URL and pattern for consistent matching\n  const normalizedPath = normalizePath(url);\n  const normalizedPattern = normalizePath(pattern);\n\n  // Exact match\n  if (normalizedPattern === normalizedPath) {\n    return true;\n  } else if (!normalizedPattern.includes('*')) {\n    // No wildcard and not an exact match\n    return false;\n  } else {\n    // Wildcard pattern - convert to regex\n    // Process segments to handle * and ** wildcards\n    const segments = normalizedPattern.split('/');\n    const regexParts: string[] = [];\n\n    for (let i = 0; i < segments.length; i++) {\n      const segment = segments[i];\n      const isLast = i === segments.length - 1;\n\n      if (segment === '**') {\n        if (isLast) {\n          // ** at end: match \"/\" followed by anything, or nothing at all\n          regexParts.push('(?:/.*)?');\n        } else {\n          // ** in middle: match anything (including slashes) non-greedy\n          regexParts.push('(?:.*?)');\n        }\n      } else if (segment === '*') {\n        // * matches exactly one path segment (no slashes)\n        regexParts.push('[^/]+');\n      } else {\n        // Escape regex special characters for literal matching\n        regexParts.push(segment.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'));\n      }\n    }\n\n    // Join with slashes, but handle ** specially (it already includes slash handling)\n    let regexPattern = '';\n    for (const [i, part] of regexParts.entries()) {\n      const originalSegment = segments[i];\n\n      if (i > 0 && originalSegment !== '**' && segments[i - 1] !== '**') {\n        regexPattern += '/';\n      } else if (\n        i > 0 &&\n        originalSegment !== '**' &&\n        segments[i - 1] === '**'\n      ) {\n        // After ** in middle, need a slash before the next segment\n        regexPattern += '/';\n      }\n      regexPattern += part;\n    }\n\n    const regex = new RegExp(`^${regexPattern}$`);\n    return regex.test(normalizedPath);\n  }\n}\n\n/**\n * Register file upload validation hooks after user plugins\n * This ensures user plugin hooks (auth, etc.) run before upload validation\n * @param fastifyInstance - The Fastify instance to register hooks on\n * @param fileUploadsConfig - The file uploads configuration\n */\nexport function registerFileUploadValidationHooks(\n  fastifyInstance: FastifyInstance,\n  fileUploadsConfig: FileUploadsConfig,\n): void {\n  const preValidation = fileUploadsConfig.preValidation;\n  const allowedRoutes = fileUploadsConfig.allowedRoutes;\n\n  // Only register hook if there's something to validate\n  if (!preValidation && (!allowedRoutes || allowedRoutes.length === 0)) {\n    return;\n  }\n\n  fastifyInstance.addHook(\n    'preHandler',\n    async (request: FastifyRequest, reply: FastifyReply) => {\n      const isMultipart = request.headers['content-type']?.startsWith(\n        'multipart/form-data',\n      );\n\n      if (isMultipart) {\n        // Check allowed routes FIRST (no point validating if route not allowed)\n        if (allowedRoutes && allowedRoutes.length > 0) {\n          // matchesRoutePattern handles normalization (query strings, trailing slashes, etc.)\n          const isAllowedRoute = allowedRoutes.some((pattern) =>\n            matchesRoutePattern(request.url, pattern),\n          );\n\n          if (!isAllowedRoute) {\n            const helpersClass = getAPIResponseHelpersClass(request);\n            const errorEnvelope = helpersClass.createAPIErrorResponse({\n              request,\n              statusCode: 400,\n              errorCode: 'multipart_not_allowed',\n              errorMessage: 'Multipart uploads not allowed on this endpoint',\n            });\n\n            return reply\n              .code(400)\n              .header('Cache-Control', 'no-store')\n              .send(errorEnvelope);\n          }\n        }\n\n        // Then run pre-validation if provided (useful for header-based auth, rate limiting, etc.)\n        if (preValidation) {\n          // Support both sync and async validation functions (matches onComplete pattern)\n          // Wrap in Promise.resolve().then() to normalize sync/async and catch sync throws\n          const result = await Promise.resolve().then(() =>\n            preValidation(request),\n          );\n\n          if (result !== true) {\n            const helpersClass = getAPIResponseHelpersClass(request);\n            const errorEnvelope = helpersClass.createAPIErrorResponse({\n              request,\n              statusCode: result.statusCode,\n              errorCode: result.error,\n              errorMessage: result.message,\n            });\n\n            return reply\n              .code(result.statusCode)\n              .header('Cache-Control', 'no-store')\n              .send(errorEnvelope);\n          }\n        }\n      }\n    },\n  );\n}\n\n/**\n * Register the @fastify/multipart plugin with default limits\n * @param fastifyInstance - The Fastify instance to register the plugin on\n * @param fileUploadsConfig - The file uploads configuration\n */\nexport async function registerMultipartPlugin(\n  fastifyInstance: FastifyInstance,\n  fileUploadsConfig: FileUploadsConfig,\n): Promise<void> {\n  const limits = fileUploadsConfig.limits || {};\n\n  await fastifyInstance.register(multipart, {\n    throwFileSizeLimit: false,\n    limits: {\n      fileSize: limits.fileSize ?? 10 * 1024 * 1024, // 10MB default\n      files: limits.files ?? 10, // default\n      fields: limits.fields ?? 10, // default\n      fieldSize: limits.fieldSize ?? 1024, // 1KB default\n    },\n  });\n\n  // Decorate to indicate multipart is available\n  fastifyInstance.decorate('multipartEnabled', true);\n}\n","import type { FastifyBaseLogger } from 'fastify';\nimport type {\n  UnirendLoggerLevel,\n  UnirendLoggerObject,\n  UnirendLoggingOptions,\n} from '../types';\n\ntype AdapterLogLevel = UnirendLoggerLevel;\ntype AdapterInternalLevel = AdapterLogLevel | 'silent';\n\ntype LevelState = {\n  value: AdapterInternalLevel;\n};\n\ntype NormalizedLogCall = {\n  message: string;\n  context?: Record<string, unknown>;\n};\n\ntype AdapterLogMethodLevel = AdapterLogLevel;\n\ntype AdapterLoggerErrorContext = {\n  stage: 'write' | 'fallback_error_write';\n  level: AdapterLogMethodLevel;\n  message: string;\n  args: unknown[];\n  bindings: Record<string, unknown>;\n  context?: Record<string, unknown>;\n  originalError?: unknown;\n};\n\nconst LOG_LEVEL_PRIORITY: Record<AdapterInternalLevel, number> = {\n  trace: 10,\n  debug: 20,\n  info: 30,\n  warn: 40,\n  error: 50,\n  fatal: 60,\n  silent: 70,\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n  return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction normalizeLevel(level: string | undefined): AdapterInternalLevel {\n  const normalized = (level || 'info').toLowerCase();\n\n  if (normalized in LOG_LEVEL_PRIORITY) {\n    return normalized as AdapterInternalLevel;\n  }\n\n  return 'info';\n}\n\nfunction isLevelEnabled(\n  currentLevel: AdapterInternalLevel,\n  targetLevel: AdapterInternalLevel,\n): boolean {\n  return LOG_LEVEL_PRIORITY[targetLevel] >= LOG_LEVEL_PRIORITY[currentLevel];\n}\n\nfunction normalizeLogCallArguments(\n  args: unknown[],\n  bindings: Record<string, unknown>,\n): NormalizedLogCall {\n  const [firstArg, secondArg, ...restArgs] = args;\n\n  let message = '';\n  let context: Record<string, unknown> | undefined;\n\n  if (typeof firstArg === 'string') {\n    message = firstArg;\n\n    if (isRecord(secondArg)) {\n      context = secondArg;\n    } else if (secondArg instanceof Error) {\n      context = { err: secondArg };\n    } else if (secondArg !== undefined) {\n      context = { value: secondArg };\n    }\n  } else if (firstArg instanceof Error) {\n    context = { err: firstArg };\n\n    if (typeof secondArg === 'string') {\n      message = secondArg;\n    } else {\n      message = firstArg.message;\n\n      if (secondArg !== undefined) {\n        context.secondArg = secondArg;\n      }\n    }\n  } else if (isRecord(firstArg)) {\n    context = firstArg;\n\n    if (typeof secondArg === 'string') {\n      message = secondArg;\n    } else if (secondArg !== undefined) {\n      context.secondArg = secondArg;\n    }\n\n    if (message === '' && typeof firstArg.msg === 'string') {\n      message = firstArg.msg;\n    }\n  } else if (firstArg !== undefined) {\n    if (\n      typeof firstArg === 'number' ||\n      typeof firstArg === 'bigint' ||\n      typeof firstArg === 'boolean'\n    ) {\n      message = String(firstArg);\n    } else if (typeof firstArg === 'symbol') {\n      message = firstArg.toString();\n    } else {\n      context = { value: firstArg };\n    }\n\n    if (secondArg !== undefined) {\n      context = {\n        ...(context || {}),\n        secondArg,\n      };\n    }\n  }\n\n  if (restArgs.length > 0) {\n    context = {\n      ...(context || {}),\n      extraArgs: restArgs,\n    };\n  }\n\n  const hasBindings = Object.keys(bindings).length > 0;\n\n  if (hasBindings) {\n    context = {\n      ...bindings,\n      ...(context || {}),\n    };\n  }\n\n  return { message, context };\n}\n\nfunction reportUnhandledLoggerFailure(\n  error: unknown,\n  context: AdapterLoggerErrorContext,\n): void {\n  const globalScope = globalThis as typeof globalThis & {\n    reportError?: (error: unknown) => void;\n  };\n\n  if (typeof globalScope.reportError === 'function') {\n    try {\n      globalScope.reportError(error);\n      return;\n    } catch {\n      // Fall through to console fallback\n    }\n  }\n\n  // eslint-disable-next-line no-console\n  console.error('[Unirend Logger Adapter] Logger call failed', {\n    error,\n    context,\n  });\n}\n\nfunction assertValidLoggerObject(logger: UnirendLoggerObject): void {\n  const loggerWithLegacyGenericLog = logger as UnirendLoggerObject & {\n    log?: unknown;\n  };\n\n  if (typeof loggerWithLegacyGenericLog.log === 'function') {\n    throw new TypeError(\n      'options.logging.logger.log(level, message, context) is not supported. Use level methods (trace/debug/info/warn/error/fatal) instead.',\n    );\n  }\n\n  const requiredMethods: Array<keyof UnirendLoggerObject> = [\n    'trace',\n    'debug',\n    'info',\n    'warn',\n    'error',\n    'fatal',\n  ];\n\n  const missingMethods = requiredMethods.filter(\n    (methodName) => typeof logger[methodName] !== 'function',\n  );\n\n  if (missingMethods.length > 0) {\n    throw new TypeError(\n      `options.logging.logger must provide all log methods (trace/debug/info/warn/error/fatal). Missing: ${missingMethods.join(', ')}`,\n    );\n  }\n}\n\nfunction createAdapterLogger(\n  loggingOptions: UnirendLoggingOptions,\n  bindings: Record<string, unknown>,\n  levelState: LevelState,\n): FastifyBaseLogger {\n  const emit = (level: AdapterInternalLevel, args: unknown[]): void => {\n    // Keep pino-compatible semantics: silent is a no-op log method.\n    if (level === 'silent') {\n      return;\n    }\n\n    // Match pino/Fastify level filtering before doing any argument work.\n    if (!isLevelEnabled(levelState.value, level)) {\n      return;\n    }\n\n    // Accept pino-style arg shapes and attach child logger bindings as context.\n    const normalizedCall = normalizeLogCallArguments(args, bindings);\n    const loggerMethod = loggingOptions.logger[level];\n\n    try {\n      loggerMethod(normalizedCall.message, normalizedCall.context);\n    } catch (error) {\n      // Preserve enough state to make fallback/error reports debuggable.\n      const writeErrorContext: AdapterLoggerErrorContext = {\n        stage: 'write',\n        level,\n        message: normalizedCall.message,\n        args,\n        bindings,\n        context: normalizedCall.context,\n      };\n\n      // First fallback stays inside user logger infrastructure.\n      const fallbackMessage = '[Unirend Logger Adapter] Logger write failed';\n      const fallbackContext: Record<string, unknown> = {\n        originalError: error,\n        failedLevel: writeErrorContext.level,\n        failedMessage: writeErrorContext.message,\n        failedBindings: writeErrorContext.bindings,\n        failedContext: writeErrorContext.context,\n      };\n\n      try {\n        loggingOptions.logger.error(fallbackMessage, fallbackContext);\n        return;\n      } catch (fallbackError) {\n        reportUnhandledLoggerFailure(fallbackError, {\n          ...writeErrorContext,\n          stage: 'fallback_error_write',\n          originalError: error,\n        });\n        return;\n      }\n    }\n  };\n\n  return {\n    get level() {\n      return levelState.value;\n    },\n    set level(value: string) {\n      levelState.value = normalizeLevel(value);\n    },\n    trace: (...args: unknown[]) => emit('trace', args),\n    debug: (...args: unknown[]) => emit('debug', args),\n    info: (...args: unknown[]) => emit('info', args),\n    warn: (...args: unknown[]) => emit('warn', args),\n    error: (...args: unknown[]) => emit('error', args),\n    fatal: (...args: unknown[]) => emit('fatal', args),\n    // Fastify expects this method when logger level is set to \"silent\".\n    silent: () => {},\n    child: (\n      childBindings: Record<string, unknown>,\n      _options?: Record<string, unknown>,\n    ) => {\n      const normalizedBindings = isRecord(childBindings) ? childBindings : {};\n\n      // Child loggers share level state and merge structured context bindings.\n      return createAdapterLogger(\n        loggingOptions,\n        {\n          ...bindings,\n          ...normalizedBindings,\n        },\n        levelState,\n      );\n    },\n  };\n}\n\nexport function createFastifyLoggerFromUnirendLogging(\n  loggingOptions: UnirendLoggingOptions,\n): FastifyBaseLogger {\n  assertValidLoggerObject(loggingOptions.logger);\n\n  const levelState: LevelState = {\n    value: normalizeLevel(loggingOptions.level),\n  };\n\n  return createAdapterLogger(loggingOptions, {}, levelState);\n}\n","import type {\n  FastifyBaseLogger,\n  FastifyLoggerOptions,\n  FastifyServerOptions,\n} from 'fastify';\nimport type { UnirendLoggingOptions } from '../types';\nimport { createFastifyLoggerFromUnirendLogging } from './unirend-logger-adapter';\n\ntype CuratedFastifyLoggerOptions = {\n  logger?: boolean | FastifyLoggerOptions;\n  loggerInstance?: FastifyBaseLogger;\n};\n\n/**\n * Resolve logging options into Fastify logger configuration while enforcing\n * mutual exclusivity across logging configuration paths.\n *\n * Note: disableRequestLogging is always set to true — Fastify's built-in\n * request lifecycle logs are permanently suppressed. Use accessLog on the\n * server config for first-party request logging instead.\n */\nexport function resolveFastifyLoggerConfig({\n  logging,\n  fastifyOptions,\n}: {\n  logging?: UnirendLoggingOptions;\n  fastifyOptions?: CuratedFastifyLoggerOptions;\n}): Pick<\n  FastifyServerOptions,\n  'logger' | 'loggerInstance' | 'disableRequestLogging'\n> {\n  const configuredPaths: string[] = [];\n\n  if (logging) {\n    configuredPaths.push('logging');\n  }\n\n  if (fastifyOptions?.logger !== undefined) {\n    configuredPaths.push('fastifyOptions.logger');\n  }\n\n  if (fastifyOptions?.loggerInstance !== undefined) {\n    configuredPaths.push('fastifyOptions.loggerInstance');\n  }\n\n  if (configuredPaths.length > 1) {\n    throw new Error(\n      `Logging configuration conflict: choose exactly one of \\`logging\\`, \\`fastifyOptions.logger\\`, or \\`fastifyOptions.loggerInstance\\`. Received: ${configuredPaths.join(', ')}`,\n    );\n  }\n\n  const resolvedConfig: Pick<\n    FastifyServerOptions,\n    'logger' | 'loggerInstance' | 'disableRequestLogging'\n  > = {\n    // Always suppress Fastify's built-in \"incoming request\" / \"request completed\" logs.\n    // Use the accessLog server option for first-party request logging.\n    disableRequestLogging: true,\n  };\n\n  if (logging) {\n    resolvedConfig.loggerInstance =\n      createFastifyLoggerFromUnirendLogging(logging);\n  } else if (fastifyOptions?.logger !== undefined) {\n    resolvedConfig.logger = fastifyOptions.logger;\n  } else if (fastifyOptions?.loggerInstance !== undefined) {\n    resolvedConfig.loggerInstance = fastifyOptions.loggerInstance;\n  }\n\n  return resolvedConfig;\n}\n","import { SSRServer } from './internal/ssr-server';\nimport type {\n  ServeSSRDevOptions,\n  ServeSSRProdOptions,\n  SSRDevPaths,\n} from './types';\n\n/**\n * Development server handler for SSR applications using Vite's HMR and middleware.\n * Simplifies dev workflow while preserving React Router SSR consistency.\n *\n * For development, we integrate with Vite's dev server for HMR support and middleware mode.\n *\n * Multi-App Support: The returned SSRServer instance supports serving multiple React applications\n * from a single server. Use `server.registerDevApp(appKey, paths, options)` to register additional\n * dev-mode apps before calling `server.listen()`. See docs/ssr.md \"Multi-App SSR Support\" for details.\n *\n * @param paths Required file paths for development server setup (default app)\n * @param options Development SSR options (default app)\n *\n * @example Single app\n * ```ts\n * const server = serveSSRDev(devPaths, { port: 3000 });\n * await server.listen(3000);\n * ```\n *\n * @example Multi-app with subdomain routing\n * ```ts\n * const server = serveSSRDev(mainPaths, mainOptions);\n *\n * server.registerDevApp('marketing', marketingPaths, marketingOptions);\n *\n * server.fastifyInstance.addHook('onRequest', async (request, reply) => {\n *   if (request.hostname === 'marketing.example.com') {\n *     request.setActiveSSRApp('marketing');\n *   }\n * });\n *\n * await server.listen(3000);\n * ```\n */\n\nexport function serveSSRDev(\n  paths: SSRDevPaths,\n  options: ServeSSRDevOptions = {},\n): SSRServer {\n  return new SSRServer({\n    mode: 'development',\n    paths,\n    options,\n  });\n}\n\n/**\n * Production server handler for SSR applications.\n *\n * Creates an SSR server instance for production mode. The server entry import\n * and manifest loading are deferred until the server starts listening, which\n * provides better error handling and avoids unnecessary work during construction.\n *\n * Multi-App Support: The returned SSRServer instance supports serving multiple React applications\n * from a single server. Use `server.registerProdApp(appKey, buildDir, options)` to register additional\n * prod-mode apps before calling `server.listen()`. See docs/ssr.md \"Multi-App SSR Support\" for details.\n *\n * @param buildDir Directory containing built assets (HTML template, static files, manifest, etc.) for default app\n * @param options Production SSR options, including serverEntry to specify which entry file to use (default app)\n *\n * @example Single app\n * ```ts\n * const server = serveSSRProd('./build', { port: 3000 });\n * await server.listen(3000);\n * ```\n *\n * @example Multi-app with path-based routing\n * ```ts\n * const server = serveSSRProd('./build-main', mainOptions);\n *\n * server.registerProdApp('marketing', './build-marketing', marketingOptions);\n *\n * server.fastifyInstance.addHook('onRequest', async (request, reply) => {\n *   if (request.url.startsWith('/marketing')) {\n *     request.setActiveSSRApp('marketing');\n *   }\n * });\n *\n * await server.listen(3000);\n * ```\n */\n\nexport function serveSSRProd(\n  buildDir: string,\n  options: ServeSSRProdOptions = {},\n): SSRServer {\n  return new SSRServer({\n    mode: 'production',\n    buildDir,\n    options,\n  });\n}\n","import type {\n  PageTypeWanted,\n  RenderResult,\n  RenderRequest,\n  SSGOptions,\n  SSGPageReport,\n  SSGReport,\n  SSGLogger,\n  SSGHelpers,\n} from './types';\nimport path from 'path';\nimport {\n  checkAndLoadManifest,\n  getServerEntryFromManifest,\n  readHTMLFile,\n  readJSONFile,\n  writeJSONFile,\n  writeHTMLFile,\n} from './internal/fs-utils';\nimport { processTemplate } from './internal/html-utils/format';\nimport {\n  normalizeCDNBaseURL,\n  computeDomainInfo,\n} from './internal/server-utils';\nimport { deepFreeze } from './internal/utils';\nimport { injectContent } from './internal/html-utils/inject';\nimport { getDevMode } from 'lifecycleion/dev-mode';\n\n/**\n * Normalize a URL path: collapse multiple slashes, remove trailing slash (except root),\n * ensure leading slash.\n *\n * Examples: \"///about//\", \"/about/\", \"about\" → \"/about\"\n *           \"/\", \"//\", \"///\" → \"/\"\n */\nfunction normalizeURLPath(p: string): string {\n  // Collapse multiple slashes into one\n  let normalized = p.replace(/\\/+/g, '/');\n\n  // Remove trailing slash (but keep \"/\" for root)\n  if (normalized.length > 1 && normalized.endsWith('/')) {\n    normalized = normalized.slice(0, -1);\n  }\n\n  // Ensure it starts with /\n  if (!normalized.startsWith('/')) {\n    normalized = '/' + normalized;\n  }\n\n  return normalized;\n}\n\n/**\n * Creates a complete SSGReport with proper typing and defaults\n */\nfunction createSSGReport({\n  buildDir,\n  startTime,\n  fatalError,\n  pages = [],\n  successCount = 0,\n  errorCount = 0,\n  notFoundCount = 0,\n}: {\n  buildDir: string;\n  startTime: number;\n  fatalError?: Error;\n  pages?: SSGPageReport[];\n  successCount?: number;\n  errorCount?: number;\n  notFoundCount?: number;\n}): SSGReport {\n  return {\n    generationFailed: !!fatalError || errorCount > 0,\n    fatalError,\n    pagesReport: {\n      pages,\n      totalPages: pages.length,\n      successCount,\n      errorCount,\n      notFoundCount,\n      totalTimeMS: Date.now() - startTime,\n      buildDir,\n    },\n  };\n}\n\n/**\n * Static Site Generator for pre-rendering pages at build time.\n *\n * Similar to the production SSR function but designed for generating static HTML files\n * during the build process rather than serving them dynamically.\n *\n * @param buildDir Directory containing built assets (HTML template, static files, manifest, etc.)\n * @param pages Array of pages to generate, each with a path and output filename\n * @param options Additional options for the SSG process, including publicAppConfig and serverEntry (defaults to \"EntrySSG\")\n * @returns Promise that resolves to a detailed report of the generation process\n */\n\nexport async function generateSSG(\n  buildDir: string,\n  pages: PageTypeWanted[],\n  options: SSGOptions = {},\n): Promise<SSGReport> {\n  const startTime = Date.now();\n  const pageReports: SSGPageReport[] = [];\n\n  let successCount = 0;\n  let errorCount = 0;\n  let notFoundCount = 0;\n\n  const isDevelopment: boolean = getDevMode();\n\n  // Set up logger - default to silent, opt-in for logging\n  const logger: SSGLogger = options.logger || {\n    info: () => {}, // Silent by default\n    warn: () => {}, // Silent by default\n    error: () => {}, // Silent by default\n  };\n\n  // Load the server manifest and find the server entry\n  const serverEntry = options.serverEntry || 'EntrySSG';\n  const serverFolderName = options.serverFolderName || 'server';\n  const clientFolderName = options.clientFolderName || 'client';\n  const serverBuildDir = path.join(buildDir, serverFolderName);\n\n  // Load the server's regular manifest\n  const serverManifestResult = await checkAndLoadManifest(\n    serverBuildDir,\n    false,\n  );\n\n  if (!serverManifestResult.success || !serverManifestResult.manifest) {\n    return createSSGReport({\n      buildDir,\n      startTime,\n      fatalError: new Error(\n        `Failed to load server manifest: ${serverManifestResult.error}`,\n      ),\n    });\n  }\n\n  const entryResult = getServerEntryFromManifest(\n    serverManifestResult.manifest,\n    serverBuildDir,\n    serverEntry,\n  );\n\n  if (!entryResult.success || !entryResult.entryPath) {\n    return createSSGReport({\n      buildDir,\n      startTime,\n      fatalError: new Error(\n        `Failed to find server entry: ${entryResult.error}`,\n      ),\n    });\n  }\n\n  // Create the import function\n  // At this point we know entryPath exists due to the check above\n  const entryPath = entryResult.entryPath;\n\n  const importFn = async (): Promise<unknown> => {\n    try {\n      return await import(/* @vite-ignore */ entryPath);\n    } catch (error: unknown) {\n      throw new Error(\n        `Failed to import server entry from ${entryPath}: ${error instanceof Error ? error.message : String(error)}`,\n      );\n    }\n  };\n\n  // Check for .unirend-ssg.json file in client folder\n  // This stores the process html template\n  const clientBuildDir = path.join(buildDir, clientFolderName);\n  const unirendSsgPath = path.join(clientBuildDir, '.unirend-ssg.json');\n\n  const ssgConfigResult = await readJSONFile(unirendSsgPath);\n\n  // If there's an error reading/parsing the config file, treat it as fatal\n  // Note: file not existing is not an error, only read/parse errors are fatal\n  if (ssgConfigResult.error) {\n    return createSSGReport({\n      buildDir,\n      startTime,\n      fatalError: new Error(\n        `Failed to read or parse .unirend-ssg.json config file: ${ssgConfigResult.error}`,\n      ),\n    });\n  }\n\n  let htmlTemplate: string;\n\n  if (ssgConfigResult.exists && ssgConfigResult.data) {\n    // Found the unirend SSG config file - use cached template\n    const template = ssgConfigResult.data.template;\n    if (typeof template === 'string') {\n      htmlTemplate = template;\n      // Using cached template from .unirend-ssg.json\n    } else {\n      return createSSGReport({\n        buildDir,\n        startTime,\n        fatalError: new Error(\n          'Invalid .unirend-ssg.json: template key is not a string',\n        ),\n      });\n    }\n  } else {\n    // No config file found, read the HTML template and create config if needed\n    // Read the HTML template from client build directory\n    const templatePath = path.join(clientBuildDir, 'index.html');\n    const templateResult = await readHTMLFile(templatePath);\n\n    if (!templateResult.exists) {\n      return createSSGReport({\n        buildDir,\n        startTime,\n        fatalError: new Error(\n          `HTML template not found at ${templatePath}. Make sure to run the client build first.`,\n        ),\n      });\n    }\n\n    if (templateResult.error) {\n      return createSSGReport({\n        buildDir,\n        startTime,\n        fatalError: new Error(\n          `Failed to read HTML template: ${templateResult.error}`,\n        ),\n      });\n    }\n\n    // Process the template with SSG options\n    // Assert that content exists since we've already checked templateResult.exists and !templateResult.error\n    const templateContent = templateResult.content as string;\n\n    const processResult = await processTemplate(\n      templateContent,\n      'ssg', // mode\n      getDevMode(), // runtime behavior (dev comment)\n      false, // isDevServer — SSG always uses built assets\n      options.containerID,\n    );\n\n    // Check if processing failed\n    if (!processResult.success) {\n      return createSSGReport({\n        buildDir,\n        startTime,\n        fatalError: new Error(\n          `Failed to process HTML template: ${processResult.error}`,\n        ),\n      });\n    }\n\n    // Store the processed template for future use\n    htmlTemplate = processResult.html;\n\n    // Write the processed template to .unirend-ssg.json with timestamp\n    const ssgConfig = {\n      template: htmlTemplate,\n      generatedAt: new Date().toISOString(),\n    };\n\n    const writeResult = await writeJSONFile(unirendSsgPath, ssgConfig);\n    if (!writeResult.success) {\n      return createSSGReport({\n        buildDir,\n        startTime,\n        fatalError: new Error(\n          `Failed to write .unirend-ssg.json: ${writeResult.error}`,\n        ),\n      });\n    }\n  }\n\n  // At this point, htmlTemplate contains the processed HTML template\n  // ready for page generation (either from cache or freshly processed)\n\n  // Validate that we have a template to work with\n  if (!htmlTemplate || htmlTemplate.length === 0) {\n    return createSSGReport({\n      buildDir,\n      startTime,\n      fatalError: new Error('HTML template is empty or invalid'),\n    });\n  }\n\n  // Import the server entry module with error handling\n  let entryServer: unknown;\n\n  try {\n    entryServer = await importFn();\n  } catch (error) {\n    return createSSGReport({\n      buildDir,\n      startTime,\n      fatalError: new Error(\n        `Failed to import server entry module: ${error instanceof Error ? error.message : String(error)}`,\n      ),\n    });\n  }\n\n  // Validate that the imported module has a render function\n  if (\n    !entryServer ||\n    typeof (entryServer as { render: unknown }).render !== 'function'\n  ) {\n    return createSSGReport({\n      buildDir,\n      startTime,\n      fatalError: new Error(\n        \"Server entry module must export a 'render' function\",\n      ),\n    });\n  }\n\n  const render: (renderRequest: RenderRequest) => Promise<RenderResult> = (\n    entryServer as {\n      render: (renderRequest: RenderRequest) => Promise<RenderResult>;\n    }\n  ).render;\n\n  // Compute domain info once for all pages (null if hostname not provided)\n  const domainInfo = options.hostname\n    ? computeDomainInfo(options.hostname)\n    : null;\n\n  // Process pages here...\n  for (const page of pages) {\n    const pageStartedAt = Date.now();\n\n    if (page.type === 'ssg') {\n      // Create a simulated fetch request for the current page\n      const fetchRequest = new Request(`http://localhost${page.path}`);\n\n      // Clone publicAppConfig to ensure it stays immutable for the entire page render\n      const publicAppConfig = options.publicAppConfig\n        ? deepFreeze(structuredClone(options.publicAppConfig))\n        : undefined;\n\n      // Create SSGHelpers with requestContext that can be populated during render\n      // Seed with any requestContext provided on the page definition\n      const SSGHelpers: SSGHelpers = {\n        requestContext: page.requestContext ? { ...page.requestContext } : {},\n      };\n\n      // Attach SSGHelpers to fetch request for access during rendering\n      (fetchRequest as Request & { SSGHelpers?: SSGHelpers }).SSGHelpers =\n        SSGHelpers;\n\n      const renderRequest: RenderRequest = {\n        type: 'ssg',\n        fetchRequest: fetchRequest,\n        unirendContext: {\n          renderMode: 'ssg',\n          isDevelopment,\n          fetchRequest: fetchRequest, // Fetch request available in SSG\n          publicAppConfig,\n          cdnBaseURL: normalizeCDNBaseURL(options.CDNBaseURL),\n          domainInfo, // Computed from options.hostname if provided, null otherwise\n          requestContextRevision: '0-0', // Initial revision for this page\n        },\n      };\n\n      const renderResult = await render(renderRequest);\n\n      if (renderResult.resultType === 'page') {\n        // --- Prepare head data for injection ---\n        const headInject = `\n        ${renderResult.head?.title || ''}\n        ${renderResult.head?.meta || ''}\n        ${renderResult.head?.link || ''}\n        ${renderResult.preloadLinks}\n      `;\n\n        // Get the requestContext from ssgHelper (may have been populated during render)\n        const requestContext = (\n          fetchRequest as Request & { SSGHelpers?: SSGHelpers }\n        ).SSGHelpers?.requestContext;\n\n        const htmlToWrite = await injectContent(\n          htmlTemplate,\n          headInject,\n          renderResult.html,\n          {\n            app: publicAppConfig,\n            request: requestContext,\n          },\n          options.CDNBaseURL,\n          domainInfo,\n        );\n\n        // Write the HTML file to the client directory (where assets are)\n        const clientBuildDir = path.join(buildDir, clientFolderName);\n        const outputPath = path.join(clientBuildDir, page.filename);\n        const writeResult = await writeHTMLFile(outputPath, htmlToWrite);\n\n        const pageEndedAt = Date.now();\n        const timeMS = pageEndedAt - pageStartedAt;\n\n        if (writeResult.success) {\n          // Check if the page rendered with a 404 status\n          if (renderResult.statusCode === 404) {\n            // Page rendered successfully but with 404 status (e.g., custom 404 page)\n            notFoundCount++;\n\n            pageReports.push({\n              page,\n              status: 'not_found',\n              outputPath,\n              timeMS,\n            });\n\n            logger.warn(`⚠ Generated 404 page ${page.filename} (${timeMS}ms)`);\n          } else if (\n            renderResult.statusCode !== undefined &&\n            renderResult.statusCode >= 500 &&\n            (options.failOn5xx ?? true)\n          ) {\n            // 5xx status code — treat as error by default unless failOn5xx is disabled\n            errorCount++;\n\n            pageReports.push({\n              page,\n              status: 'error',\n              outputPath, // File was written — inspect it to debug the render error\n              errorDetails: `Server error status code: ${renderResult.statusCode}`,\n              timeMS,\n            });\n\n            logger.error(\n              `✗ Server error (${renderResult.statusCode}) on page ${page.path}: ${page.filename} — file written for inspection (${timeMS}ms)`,\n            );\n          } else {\n            // Normal success\n            successCount++;\n\n            pageReports.push({\n              page,\n              status: 'success',\n              outputPath,\n              timeMS,\n            });\n\n            logger.info(`✓ Generated ${page.filename} (${timeMS}ms)`);\n          }\n        } else {\n          // Write failed - treat as error\n          errorCount++;\n\n          pageReports.push({\n            page,\n            status: 'error',\n            errorDetails: writeResult.error,\n            timeMS,\n          });\n\n          logger.error(\n            `✗ Failed to write ${page.filename}: ${writeResult.error}`,\n          );\n        }\n      } else {\n        // Handle all non-page results (redirects, errors, unexpected types)\n        const pageEndedAt = Date.now();\n        const timeMS = pageEndedAt - pageStartedAt;\n        let errorDetails: string;\n\n        if (renderResult.resultType === 'response') {\n          const status = renderResult.response.status;\n          const statusText =\n            renderResult.response.statusText || 'Unknown error';\n          errorDetails = `Non-page response (${status}): ${statusText}`;\n        } else if (renderResult.resultType === 'render-error') {\n          errorDetails = `Render error: ${renderResult.error.message}`;\n        } else {\n          // This should never happen with proper RenderResult types, but handle gracefully\n          const resultType =\n            (renderResult as unknown as { resultType?: string }).resultType ||\n            'unknown';\n          errorDetails = `Unexpected render result type: ${resultType}`;\n        }\n\n        errorCount++;\n\n        pageReports.push({\n          page,\n          status: 'error',\n          errorDetails,\n          timeMS,\n        });\n\n        logger.error(\n          `✗ Error on page ${page.path}: ${errorDetails} (${timeMS}ms)`,\n        );\n      }\n    } else if (page.type === 'spa') {\n      // Generate SPA page with custom metadata but no server-side content\n      const pageEndedAt = Date.now();\n      const timeMS = pageEndedAt - pageStartedAt;\n\n      // Build head content from SPA page metadata\n      let headInject = '';\n\n      if (page.title) {\n        headInject += `<title>${page.title}</title>\\n`;\n      }\n\n      if (page.description) {\n        headInject += `<meta name=\"description\" content=\"${page.description}\">\\n`;\n      }\n\n      if (page.meta) {\n        for (const [name, content] of Object.entries(page.meta)) {\n          headInject += `<meta property=\"${name}\" content=\"${content}\">\\n`;\n        }\n      }\n\n      const publicAppConfig = options.publicAppConfig\n        ? deepFreeze(structuredClone(options.publicAppConfig))\n        : undefined;\n\n      // For SPA pages, use empty body content (client will render)\n      const htmlToWrite = await injectContent(\n        htmlTemplate,\n        headInject.trim(),\n        '', // Empty body content for SPA\n        {\n          app: publicAppConfig, // Inject app config for SPA pages if provided from options\n          request: page.requestContext, // Inject request context for SPA pages that was manually provided for the specific page\n        },\n        options.CDNBaseURL,\n        domainInfo,\n      );\n\n      // Write the HTML file to the client directory (where assets are)\n      const clientBuildDir = path.join(buildDir, clientFolderName);\n      const outputPath = path.join(clientBuildDir, page.filename);\n      const writeResult = await writeHTMLFile(outputPath, htmlToWrite);\n\n      if (writeResult.success) {\n        // Success - increment count and add to reports\n        successCount++;\n\n        pageReports.push({\n          page,\n          status: 'success',\n          outputPath,\n          timeMS,\n        });\n\n        logger.info(`✓ Generated SPA ${page.filename} (${timeMS}ms)`);\n      } else {\n        // Write failed - treat as error\n        errorCount++;\n\n        pageReports.push({\n          page,\n          status: 'error',\n          errorDetails: writeResult.error,\n          timeMS,\n        });\n\n        logger.error(\n          `✗ Failed to write SPA ${page.filename}: ${writeResult.error}`,\n        );\n      }\n    } else if (page.type === 'html') {\n      // Generate HTML page by writing a raw HTML string or source file directly to\n      // the client directory — no React render, no Vite template, no JS bundle injected.\n      // Use for self-contained pages (e.g. 500.html) that must not depend on external\n      // assets loading successfully.\n      const pageEndedAt = Date.now();\n      const timeMS = pageEndedAt - pageStartedAt;\n\n      const htmlFilename = page.filename;\n\n      let htmlContent: string;\n\n      if (page.html !== undefined) {\n        // Inline HTML string provided — use it directly\n        htmlContent = page.html;\n      } else if (page.source !== undefined) {\n        // Source file path provided — read the file\n        const sourceResult = await readHTMLFile(page.source);\n\n        if (!sourceResult.exists) {\n          // Source file not found — treat as error, skip this page\n          errorCount++;\n\n          pageReports.push({\n            page,\n            status: 'error',\n            errorDetails: `Source file not found: ${page.source}`,\n            timeMS,\n          });\n\n          logger.error(\n            `✗ Source file not found for ${htmlFilename}: ${page.source} (${timeMS}ms)`,\n          );\n\n          continue;\n        }\n\n        if (sourceResult.error || sourceResult.content === undefined) {\n          // File exists but could not be read — treat as error\n          errorCount++;\n\n          pageReports.push({\n            page,\n            status: 'error',\n            errorDetails: `Failed to read source file: ${sourceResult.error}`,\n            timeMS,\n          });\n\n          logger.error(\n            `✗ Failed to read source for ${htmlFilename}: ${sourceResult.error} (${timeMS}ms)`,\n          );\n\n          continue;\n        }\n\n        htmlContent = sourceResult.content;\n      } else {\n        // Neither html nor source provided — TypeScript enforces this at compile time\n        // via the never trick, but guard at runtime to produce a clear error message\n        errorCount++;\n        pageReports.push({\n          page,\n          status: 'error',\n          errorDetails:\n            'html page type requires either html or source to be provided',\n          timeMS,\n        });\n        logger.error(\n          `✗ ${htmlFilename}: html page type requires either html or source`,\n        );\n        continue;\n      }\n\n      // Write the HTML file to the client directory (where assets are)\n      const clientBuildDir = path.join(buildDir, clientFolderName);\n      const outputPath = path.join(clientBuildDir, page.filename);\n      const writeResult = await writeHTMLFile(outputPath, htmlContent);\n\n      if (writeResult.success) {\n        // Success - increment count and add to reports\n        successCount++;\n\n        pageReports.push({ page, status: 'success', outputPath, timeMS });\n\n        logger.info(`✓ Generated HTML ${htmlFilename} (${timeMS}ms)`);\n      } else {\n        // Write failed - treat as error\n        errorCount++;\n\n        pageReports.push({\n          page,\n          status: 'error',\n          errorDetails: writeResult.error,\n          timeMS,\n        });\n\n        logger.error(`✗ Failed to write ${htmlFilename}: ${writeResult.error}`);\n      }\n    } else {\n      // Handle unknown page type\n      const pageEndedAt = Date.now();\n      const timeMS = pageEndedAt - pageStartedAt;\n\n      errorCount++;\n\n      pageReports.push({\n        page,\n        status: 'error',\n        errorDetails: `Unknown page type: ${(page as unknown as { type?: string }).type || 'undefined'}`,\n        timeMS,\n      });\n\n      logger.error(\n        `✗ Unknown page type for ${(page as unknown as { filename?: string }).filename || 'unknown'}: ${(page as unknown as { type?: string }).type || 'undefined'} (${timeMS}ms)`,\n      );\n    }\n  }\n\n  // Generate page map file if pageMapOutput is specified\n  if (options.pageMapOutput) {\n    const pageMap: Record<string, string> = {};\n    const pathConflicts: Array<{ path: string; files: string[] }> = [];\n\n    // Build the path-to-filename mapping from successful pages\n    for (const report of pageReports) {\n      if (report.status === 'success' || report.status === 'not_found') {\n        let urlPath: string;\n\n        // SSG pages use their explicit path; SPA and HTML pages use explicit path\n        // if provided, otherwise derive from filename\n        if (report.page.type === 'ssg') {\n          urlPath = normalizeURLPath(report.page.path);\n        } else if (report.page.path) {\n          urlPath = normalizeURLPath(report.page.path);\n        } else {\n          // SPA/HTML pages without explicit path: derive from filename\n          // e.g., \"dashboard.html\" -> \"/dashboard\", \"index.html\" -> \"/\"\n          const basename = report.page.filename.replace(/\\.html$/, '');\n          urlPath = basename === 'index' ? '/' : `/${basename}`;\n        }\n\n        // Check for path conflicts\n        if (pageMap[urlPath]) {\n          // Find existing conflict or create new one\n          const existingConflict = pathConflicts.find(\n            (c) => c.path === urlPath,\n          );\n\n          if (existingConflict) {\n            existingConflict.files.push(report.page.filename);\n          } else {\n            pathConflicts.push({\n              path: urlPath,\n              files: [pageMap[urlPath], report.page.filename],\n            });\n          }\n        }\n\n        pageMap[urlPath] = report.page.filename;\n      }\n    }\n\n    // If there are path conflicts, return fatal error\n    if (pathConflicts.length > 0) {\n      const conflictDetails = pathConflicts\n        .map((c) => `  \"${c.path}\" -> [${c.files.join(', ')}]`)\n        .join('\\n');\n\n      return createSSGReport({\n        buildDir,\n        startTime,\n        pages: pageReports,\n        successCount,\n        errorCount,\n        notFoundCount,\n        fatalError: new Error(\n          `Page map has conflicting paths (multiple files map to the same URL):\\n${conflictDetails}`,\n        ),\n      });\n    }\n\n    // Write the page map file to the client directory (same location as HTML files)\n    const pageMapPath = path.join(clientBuildDir, options.pageMapOutput);\n\n    const pageMapWriteResult = await writeJSONFile(pageMapPath, pageMap);\n\n    if (!pageMapWriteResult.success) {\n      return createSSGReport({\n        buildDir,\n        startTime,\n        pages: pageReports,\n        successCount,\n        errorCount,\n        notFoundCount,\n        fatalError: new Error(\n          `Failed to write page map file: ${pageMapWriteResult.error}`,\n        ),\n      });\n    }\n\n    logger.info(`✓ Generated page map: ${pageMapPath}`);\n  }\n\n  return createSSGReport({\n    buildDir,\n    startTime,\n    pages: pageReports,\n    successCount,\n    errorCount,\n    notFoundCount,\n  });\n}\n","import type { RenderRequest, RenderResult } from './types';\nimport type { ReactNode } from 'react';\nimport { createStaticRouter, createStaticHandler } from 'react-router';\nimport type { RouteObject, StaticHandlerContext } from 'react-router';\nimport { wrapStaticRouter } from './internal/WrapAppElement';\nimport { serializeHeadCollector } from './internal/UnirendHead';\nimport type { HeadCollector } from './internal/UnirendHead';\nimport { renderToString } from 'react-dom/server';\n\n// Debug flag to enable/disable logging in the base renderer\nconst DEBUG_BASE_RENDER = false; // Set to false in a production release\n\n/**\n * Options for base rendering, simplified API\n */\nexport type BaseRenderOptions = {\n  /**\n   * Whether to wrap the app element with React.StrictMode\n   * @default true\n   */\n  strictMode?: boolean;\n  /**\n   * Optional custom wrapper component for additional providers\n   * Applied after UnirendHeadProvider but before StrictMode (StrictMode is always outermost)\n   * Must be a React component that accepts children\n   */\n  rootProviders?: React.ComponentType<{ children: ReactNode }>;\n};\n\n/**\n * Base render function that handles React Router wrapping and rendering.\n *\n * This function takes routes and handles all the router creation and wrapping logic\n * internally, including UnirendHead collection for SSR/SSG scenarios.\n *\n * @param renderRequest - The render request containing type, URL, and other options\n * @param routes - The React Router routes configuration\n * @param options - Optional configuration for rendering behavior\n * @returns RenderResult with the rendered HTML and metadata\n *\n * @example\n * ```typescript\n * // Basic usage\n * const result = unirendBaseRender({ type: \"ssg\", url: \"/\" }, routes);\n *\n * // With custom wrapper\n * const customWrapper = (node) => <ThemeProvider>{node}</ThemeProvider>;\n * const result = unirendBaseRender(\n *   { type: \"ssg\", url: \"/about\" },\n *   routes,\n *   { wrapApp: customWrapper }\n * );\n *\n * // Without StrictMode\n * const result = unirendBaseRender(\n *   { type: \"ssr\", url: \"/contact\" },\n *   routes,\n *   { strictMode: false }\n * );\n * ```\n */\nexport async function unirendBaseRender(\n  renderRequest: RenderRequest,\n  routes: RouteObject[],\n  options: BaseRenderOptions = {},\n): Promise<RenderResult> {\n  // Create new instances per request for isolation\n  const headCollector: HeadCollector = { title: '', metas: [], links: [] };\n\n  // Create a Static Handler\n  // The handler examines the routes and prepares data for rendering\n  const handler = createStaticHandler(routes);\n\n  // Pass the Fetch Request and Query the Handler\n  // Pass the request to the handler to get a rendering context\n  let context: StaticHandlerContext | Response;\n\n  try {\n    context = await handler.query(renderRequest.fetchRequest);\n  } catch (error) {\n    if (DEBUG_BASE_RENDER) {\n      // eslint-disable-next-line no-console\n      console.error('Error querying static handler:', error);\n    }\n\n    // Return error result instead of generic 500 response\n    return {\n      resultType: 'render-error',\n      error: error instanceof Error ? error : new Error(String(error)),\n    };\n  }\n\n  // Handle Redirects and Other Responses\n  // If the handler returns a Response, it's a redirect or error, pass it along\n  if (context instanceof Response) {\n    // Log redirects for debugging\n    if (DEBUG_BASE_RENDER && context.status >= 300 && context.status < 400) {\n      // eslint-disable-next-line no-console\n      console.log(`Redirecting to: ${context.headers.get('Location')}`);\n    }\n\n    // Note: We return the response here. The server framework (Express)\n    // needs to catch this and handle the response accordingly (e.g., res.redirect).\n    return {\n      resultType: 'response',\n      response: context,\n    };\n  }\n\n  // Ensure context is not undefined or null before proceeding\n  if (!context) {\n    if (DEBUG_BASE_RENDER) {\n      // eslint-disable-next-line no-console\n      console.error(\n        'Static handler query returned undefined context for request:',\n        renderRequest.fetchRequest.url,\n      );\n    }\n\n    // When throwing here, it won't return RenderResult, but the catch block\n    // in server.ts handles this. This throw is correct.\n    return {\n      resultType: 'response',\n      response: new Response('Not Found', { status: 404 }),\n    };\n  }\n\n  // 5. Create Static Router\n  // Use the context from the handler to create the router instance\n  const router = createStaticRouter(handler.dataRoutes, context);\n\n  // ---> Get the status code from the context BEFORE rendering\n  let statusCode = context.statusCode || 200; // Default to 200 if no error/response\n  let errorDetails: Error | undefined = undefined;\n\n  const ssOnlyData: Record<string, unknown> = {};\n\n  // Check for __ssOnly data, extract it and remove it from the context\n  for (const key in context.loaderData) {\n    const data = context.loaderData[key] as Record<string, unknown> | undefined;\n\n    if (\n      data &&\n      typeof data === 'object' &&\n      '__ssOnly' in data &&\n      data.__ssOnly\n    ) {\n      // Clone the __ssOnly data to avoid modifying the original\n      Object.assign(ssOnlyData, structuredClone(data.__ssOnly));\n\n      // remove __ssOnly from the data since it doesn't need to be sent to the client\n      // and will not appear in the window.__staticRouterHydrationData object\n      delete data.__ssOnly;\n    }\n  }\n\n  // Check for React Router context.errors first (the status code will default if error boundary is hit)\n  if (context.errors && statusCode === 500) {\n    for (const key in context.errors) {\n      const error = context.errors[key] as Error & { status?: number };\n\n      // Extract status code if available\n      if (error.status && typeof error.status === 'number') {\n        statusCode = error.status;\n      }\n\n      // Use the error object directly\n      errorDetails = error;\n\n      break; // Handle the first error we find\n    }\n  }\n  // Check if any loaders returned a status code or error following our API envelope\n  else if (context?.loaderData) {\n    for (const key in context.loaderData) {\n      const data = context.loaderData[key] as\n        | {\n            status_code?: number;\n            error?: {\n              message?: string;\n              details?: { stack?: string };\n            };\n          }\n        | undefined;\n\n      let hasDataInThisEntry = false;\n\n      // Check for our API envelope status_code if a custom status code is not already set\n      if (\n        data?.status_code &&\n        typeof data.status_code === 'number' &&\n        statusCode === 200\n      ) {\n        statusCode = data.status_code;\n        hasDataInThisEntry = true;\n      }\n\n      // Check for our API envelope error object\n      if (data?.error && typeof data.error === 'object') {\n        // Create Error object from the envelope\n        const errorMessage =\n          typeof data.error.message === 'string'\n            ? data.error.message\n            : 'Unknown error';\n\n        const errorObj = new Error(errorMessage);\n\n        // Add stack trace if available\n        if (\n          data.error.details &&\n          typeof data.error.details === 'object' &&\n          typeof data.error.details.stack === 'string'\n        ) {\n          errorObj.stack = data.error.details.stack;\n        }\n\n        // Mark this as an API envelope error\n        (errorObj as Error & { source?: string }).source = 'api-envelope';\n\n        errorDetails = errorObj;\n        hasDataInThisEntry = true;\n      }\n\n      // If we found relevant data in this entry, we can break\n      if (hasDataInThisEntry) {\n        break;\n      }\n    }\n  }\n\n  // Render the App HTML\n  const wrappedElement = wrapStaticRouter(\n    router,\n    context,\n    {\n      strictMode: options.strictMode,\n      rootProviders: options.rootProviders,\n      unirendContext: renderRequest.unirendContext, // unirendContext is always provided in renderRequest by SSRServer or SSG Generation\n    },\n    headCollector,\n  );\n\n  const appHTML = renderToString(wrappedElement);\n\n  // Serialize collected head data AFTER rendering\n  const head = serializeHeadCollector(headCollector);\n\n  // TODO: Inject Preload Links (using ssrManifest)\n  // This part requires logic to parse the ssrManifest and context.modules\n  // to generate <link rel=\"modulepreload\"> tags.\n  // Most likely only want the main entry, not the chunks\n  const preloadLinks = ''; // Placeholder\n\n  return {\n    resultType: 'page',\n    html: appHTML,\n    preloadLinks: preloadLinks,\n    head,\n    statusCode: statusCode,\n    errorDetails: errorDetails,\n    ssOnlyData: ssOnlyData,\n  };\n}\n","import type { ReactElement } from 'react';\nimport { RouterProvider } from 'react-router';\nimport type { DataRouter } from 'react-router';\nimport { createAppWrapper } from './CreateAppWrapper';\nimport type { WrapAppElementOptions } from './types';\n\n/**\n * CLIENT-SIDE: Wraps a Browser Router with the standard app wrappers\n * Uses RouterProvider with UnirendHeadProvider (null collector — React 19 hoists natively)\n *\n * @param router - The Browser Router instance\n * @param options - Configuration options for wrapping\n * @returns The wrapped RouterProvider element\n */\n\nexport function wrapRouter(\n  router: DataRouter,\n  options: WrapAppElementOptions,\n): ReactElement {\n  const routerElement = <RouterProvider router={router} />;\n  return createAppWrapper(routerElement, options);\n}\n","import { UnirendContext } from './context';\nimport type { UnirendProviderProps } from './context';\n\n/**\n * UnirendProvider component that provides context to the app\n *\n * @example\n * ```tsx\n * <UnirendProvider value={{ renderMode: 'ssr', isDevelopment: true }}>\n *   <App />\n * </UnirendProvider>\n * ```\n */\nexport function UnirendProvider({ children, value }: UnirendProviderProps) {\n  return (\n    <UnirendContext.Provider value={value}>{children}</UnirendContext.Provider>\n  );\n}\n","import { useContext, useState, useEffect } from 'react';\nimport { deepFreeze } from '../utils';\nimport {\n  UnirendContext,\n  getRequestContextValue,\n  setRequestContextValue,\n  getRequestContextObject,\n  hasSSRRequestContext,\n  hasSSGRequestContext,\n  hasWindowRequestContext,\n  incrementContextRevision,\n} from './context';\nimport type {\n  UnirendRenderMode,\n  RequestContextManager,\n  DomainInfo,\n} from './context';\n\n/**\n * Hook to check if the app is rendering in SSR mode\n *\n * @returns true if rendering mode is 'ssr', false if 'ssg'\n *\n * @example\n * ```tsx\n * function MyComponent() {\n *   const isSSR = useIsSSR();\n *\n *   return <div>{isSSR ? 'Server-Side Rendered' : 'Static Generated'}</div>;\n * }\n * ```\n */\nexport function useIsSSR(): boolean {\n  const { renderMode } = useContext(UnirendContext);\n  return renderMode === 'ssr';\n}\n\n/**\n * Hook to check if the app is rendering in SSG mode\n *\n * @returns true if rendering mode is 'ssg', false otherwise\n *\n * @example\n * ```tsx\n * function MyComponent() {\n *   const isSSG = useIsSSG();\n *\n *   return <div>{isSSG ? 'Static Generated' : 'Not SSG'}</div>;\n * }\n * ```\n */\nexport function useIsSSG(): boolean {\n  const { renderMode } = useContext(UnirendContext);\n  return renderMode === 'ssg';\n}\n\n/**\n * Hook to check if the app is in client mode\n * Returns true for SPAs or after SSG build/SSR page hydration occurs\n *\n * @returns true if rendering mode is 'client', false otherwise\n *\n * @example\n * ```tsx\n * function MyComponent() {\n *   const isClient = useIsClient();\n *\n *   return <div>{isClient ? 'Client Mode' : 'Server Rendering'}</div>;\n * }\n * ```\n */\nexport function useIsClient(): boolean {\n  const { renderMode } = useContext(UnirendContext);\n  return renderMode === 'client';\n}\n\n/**\n * Hook to get the render mode\n *\n * @returns The current render mode ('ssr', 'ssg', or 'client')\n *\n * @example\n * ```tsx\n * function MyComponent() {\n *   const renderMode = useRenderMode();\n *\n *   return <div>Render Mode: {renderMode}</div>;\n * }\n * ```\n */\nexport function useRenderMode(): UnirendRenderMode {\n  const { renderMode } = useContext(UnirendContext);\n  return renderMode;\n}\n\n/**\n * Hook to check if the app is running in development mode\n *\n * @returns true if in development mode, false if in production\n *\n * @example\n * ```tsx\n * function MyComponent() {\n *   const isDev = useIsDevelopment();\n *\n *   return (\n *     <div>\n *       {isDev && <div>Development Mode - Debug Info</div>}\n *     </div>\n *   );\n * }\n * ```\n */\nexport function useIsDevelopment(): boolean {\n  const { isDevelopment } = useContext(UnirendContext);\n  return isDevelopment;\n}\n\n/**\n * Hook to check if the code is running on the server (SSR)\n * This checks if fetchRequest has the SSRHelper property attached\n *\n * @returns true if on SSR server (has SSRHelper), false if on client or SSG\n *\n * @example\n * ```tsx\n * function MyComponent() {\n *   const isServer = useIsServer();\n *\n *   return (\n *     <div>\n *       {isServer ? 'Running on SSR server' : 'Running on client or SSG'}\n *     </div>\n *   );\n * }\n * ```\n */\nexport function useIsServer(): boolean {\n  const { fetchRequest } = useContext(UnirendContext);\n  return fetchRequest !== undefined && 'SSRHelper' in fetchRequest;\n}\n\n/**\n * Hook to access public application configuration\n * This is a frozen (immutable) copy of the config passed to the server\n * Available on both server and client\n *\n * @returns The public app config object, or undefined if not provided\n *\n * @example\n * ```tsx\n * function MyComponent() {\n *   const config = usePublicAppConfig();\n *\n *   if (!config) {\n *     return <div>No config available</div>;\n *   }\n *\n *   return (\n *     <div>\n *       <p>API URL: {config.api_endpoint}</p>\n *       <p>App Name: {config.appName}</p>\n *     </div>\n *   );\n * }\n * ```\n */\nexport function usePublicAppConfig(): Record<string, unknown> | undefined {\n  const { publicAppConfig } = useContext(UnirendContext);\n  return publicAppConfig;\n}\n\n/**\n * Hook to get the CDN base URL configured for asset serving.\n *\n * Returns the effective CDN base URL for the current request — either a per-request\n * override (set in middleware via `request.CDNBaseURL`) or the app-level default\n * (`CDNBaseURL` in `serveSSRProd`/`registerProdApp` options). Returns an empty string\n * when no CDN is configured or when running Vite directly without the unirend server.\n *\n * Available on both server (during SSR rendering) and client (read from\n * `window.__CDN_BASE_URL__` injected into the page).\n *\n * @example\n * ```tsx\n * function AssetImage({ path }: { path: string }) {\n *   const cdnBase = useCDNBaseURL();\n *\n *   return <img src={`${cdnBase}${path}`} />;\n * }\n * ```\n */\nexport function useCDNBaseURL(): string {\n  const { cdnBaseURL } = useContext(UnirendContext);\n  return cdnBaseURL ?? '';\n}\n\n/**\n * Returns domain information computed server-side from the request hostname.\n *\n * - `hostname`: the bare requested hostname (port stripped), e.g. `'app.example.com'`\n * - `rootDomain`: the apex domain without a leading dot, e.g. `'example.com'`.\n *   Empty string for localhost / IP addresses.\n *   Prepend `.` when using as a cookie `domain` attribute to span subdomains:\n *   ```ts\n *   document.cookie = [\n *     'theme=dark',\n *     'path=/',\n *     'max-age=31536000',\n *     domainInfo?.rootDomain ? `domain=.${domainInfo.rootDomain}` : null,\n *   ].filter(Boolean).join('; ');\n *   ```\n *\n * Returns `null` when hostname is not known — SSG without a `hostname` option\n * configured, or pure SPA (no server to compute it via the public suffix list).\n *\n * @example\n * ```tsx\n * function ThemeProvider({ children }) {\n *   const domainInfo = useDomainInfo();\n *   // domainInfo?.hostname  → 'app.example.com'\n *   // domainInfo?.rootDomain → 'example.com'\n * }\n * ```\n */\nexport function useDomainInfo(): DomainInfo | null {\n  const { domainInfo } = useContext(UnirendContext);\n  return domainInfo ?? null;\n}\n\n/**\n * Hook to get the raw request context object for debugging purposes.\n * Returns a cloned, immutable copy of the entire request context.\n *\n * **Note:** This is primarily for debugging. Use `useRequestContextValue()`\n * or `useRequestContext()` for production code.\n *\n * @returns A cloned copy of the request context object, or undefined if not populated\n *\n * @example\n * ```tsx\n * function DebugPanel() {\n *   const rawContext = useRequestContextObjectRaw();\n *\n *   if (!rawContext) {\n *     return <div>Request context not populated</div>;\n *   }\n *\n *   return (\n *     <pre>{JSON.stringify(rawContext, null, 2)}</pre>\n *   );\n * }\n * ```\n */\nexport function useRequestContextObjectRaw():\n  | Record<string, unknown>\n  | undefined {\n  const context = useContext(UnirendContext);\n  const [rawContext, setRawContext] = useState<\n    Record<string, unknown> | undefined\n  >(() => {\n    // Get initial value on server\n    const contextObj = getRequestContextObject(context);\n    return contextObj ? deepFreeze(structuredClone(contextObj)) : undefined;\n  });\n\n  useEffect(() => {\n    // Update when context changes (reactive to modifications)\n    const contextObj = getRequestContextObject(context);\n\n    if (contextObj) {\n      // Create a cloned, immutable copy\n      const cloned = structuredClone(contextObj);\n\n      // Synchronizing with external state (request context) tracked by revision counter\n      setRawContext(deepFreeze(cloned));\n    } else {\n      setRawContext(undefined);\n    }\n  }, [context.requestContextRevision, context]);\n\n  return rawContext;\n}\n\n/**\n * Hook to access and manage the request context\n *\n * Returns an object with methods to get, set, check, delete, and inspect\n * the request context. The returned methods can be safely called in callbacks,\n * effects, or event handlers.\n *\n * @returns RequestContextManager object with context management methods\n *\n * @example\n * ```tsx\n * function MyComponent() {\n *   const requestContext = useRequestContext();\n *\n *   const handleThemeChange = (theme: string) => {\n *     requestContext.set('theme', theme);\n *   };\n *\n *   const userID = requestContext.get('userID');\n *   const hasTheme = requestContext.has('theme');\n *   const allKeys = requestContext.keys();\n *\n *   return (\n *     <div>\n *       <p>User ID: {userID}</p>\n *       <p>Has theme: {hasTheme ? 'Yes' : 'No'}</p>\n *       <p>Total entries: {requestContext.size()}</p>\n *       <button onClick={() => handleThemeChange('dark')}>Dark Theme</button>\n *       <button onClick={() => requestContext.clear()}>Clear All</button>\n *     </div>\n *   );\n * }\n * ```\n */\nexport function useRequestContext(): RequestContextManager {\n  const context = useContext(UnirendContext);\n\n  return {\n    get: (key: string): unknown => {\n      return getRequestContextValue(context, key);\n    },\n    set: (key: string, value: unknown): void => {\n      setRequestContextValue(context, key, value);\n    },\n    has: (key: string): boolean => {\n      // Try SSR first - check if we have SSR helpers with request context\n      if (context.fetchRequest && hasSSRRequestContext(context.fetchRequest)) {\n        // SSR: Check if key exists in fastify request context\n        return (\n          key in context.fetchRequest.SSRHelpers.fastifyRequest.requestContext\n        );\n      } else if (\n        // Try SSG - check if we have SSG helpers with request context\n        context.fetchRequest &&\n        hasSSGRequestContext(context.fetchRequest)\n      ) {\n        // SSG: Check if key exists in SSG request context\n        return key in context.fetchRequest.SSGHelpers.requestContext;\n      } else if (hasWindowRequestContext()) {\n        // Client: Check if key exists in window global\n        return (\n          key in\n          (\n            window as unknown as {\n              // eslint-disable-next-line @typescript-eslint/naming-convention\n              __FRONTEND_REQUEST_CONTEXT__: Record<string, unknown>;\n            }\n          ).__FRONTEND_REQUEST_CONTEXT__\n        );\n      } else {\n        // No context available\n        return false;\n      }\n    },\n    delete: (key: string): boolean => {\n      let didExist = false;\n\n      // Try SSR first - check if we have SSR helpers with request context\n      if (context.fetchRequest && hasSSRRequestContext(context.fetchRequest)) {\n        // SSR: Delete from fastify request context\n        didExist =\n          key in context.fetchRequest.SSRHelpers.fastifyRequest.requestContext;\n\n        // requestContext is intentionally mutable (context itself is not modified)\n        delete context.fetchRequest.SSRHelpers.fastifyRequest.requestContext[\n          key\n        ];\n      } else if (\n        // Try SSG - check if we have SSG helpers with request context\n        context.fetchRequest &&\n        hasSSGRequestContext(context.fetchRequest)\n      ) {\n        // SSG: Delete from SSG request context\n        didExist = key in context.fetchRequest.SSGHelpers.requestContext;\n        delete context.fetchRequest.SSGHelpers.requestContext[key];\n      } else if (hasWindowRequestContext()) {\n        // Client: Delete from window global\n        const ctx = (\n          window as unknown as {\n            // eslint-disable-next-line @typescript-eslint/naming-convention\n            __FRONTEND_REQUEST_CONTEXT__: Record<string, unknown>;\n          }\n        ).__FRONTEND_REQUEST_CONTEXT__;\n\n        didExist = key in ctx;\n        delete ctx[key];\n      }\n\n      // Increment revision to trigger re-renders if key existed\n      if (didExist) {\n        incrementContextRevision(context);\n      }\n\n      return didExist;\n    },\n    clear: (): number => {\n      let count = 0;\n\n      // Try SSR first - check if we have SSR helpers with request context\n      if (context.fetchRequest && hasSSRRequestContext(context.fetchRequest)) {\n        // SSR: Clear all keys from fastify request context\n        const ctx =\n          context.fetchRequest.SSRHelpers.fastifyRequest.requestContext;\n        const keys = Object.keys(ctx);\n        count = keys.length;\n\n        // Delete each key individually to preserve object reference\n        for (const key of keys) {\n          // requestContext is intentionally mutable (context itself is not modified)\n          delete ctx[key];\n        }\n      } else if (\n        // Try SSG - check if we have SSG helpers with request context\n        context.fetchRequest &&\n        hasSSGRequestContext(context.fetchRequest)\n      ) {\n        // SSG: Clear all keys from SSG request context\n        const ctx = context.fetchRequest.SSGHelpers.requestContext;\n        const keys = Object.keys(ctx);\n        count = keys.length;\n\n        // Delete each key individually to preserve object reference\n        for (const key of keys) {\n          delete ctx[key];\n        }\n      } else if (hasWindowRequestContext()) {\n        // Client: Clear all keys from window global\n        const ctx = (\n          window as unknown as {\n            // eslint-disable-next-line @typescript-eslint/naming-convention\n            __FRONTEND_REQUEST_CONTEXT__: Record<string, unknown>;\n          }\n        ).__FRONTEND_REQUEST_CONTEXT__;\n\n        const keys = Object.keys(ctx);\n        count = keys.length;\n\n        // Delete each key individually to preserve object reference\n        for (const key of keys) {\n          delete ctx[key];\n        }\n      }\n\n      // Increment revision to trigger re-renders if any keys were cleared\n      if (count > 0) {\n        incrementContextRevision(context);\n      }\n\n      return count;\n    },\n    keys: (): string[] => {\n      // Try SSR first - check if we have SSR helpers with request context\n      if (context.fetchRequest && hasSSRRequestContext(context.fetchRequest)) {\n        // SSR: Return keys from fastify request context\n        return Object.keys(\n          context.fetchRequest.SSRHelpers.fastifyRequest.requestContext,\n        );\n      } else if (\n        // Try SSG - check if we have SSG helpers with request context\n        context.fetchRequest &&\n        hasSSGRequestContext(context.fetchRequest)\n      ) {\n        // SSG: Return keys from SSG request context\n        return Object.keys(context.fetchRequest.SSGHelpers.requestContext);\n      } else if (hasWindowRequestContext()) {\n        // Client: Return keys from window global\n        return Object.keys(\n          (\n            window as unknown as {\n              // eslint-disable-next-line @typescript-eslint/naming-convention\n              __FRONTEND_REQUEST_CONTEXT__: Record<string, unknown>;\n            }\n          ).__FRONTEND_REQUEST_CONTEXT__,\n        );\n      } else {\n        // No context available - return empty array\n        return [];\n      }\n    },\n    size: (): number => {\n      // Try SSR first - check if we have SSR helpers with request context\n      if (context.fetchRequest && hasSSRRequestContext(context.fetchRequest)) {\n        // SSR: Return count of keys from fastify request context\n        return Object.keys(\n          context.fetchRequest.SSRHelpers.fastifyRequest.requestContext,\n        ).length;\n      } else if (\n        // Try SSG - check if we have SSG helpers with request context\n        context.fetchRequest &&\n        hasSSGRequestContext(context.fetchRequest)\n      ) {\n        // SSG: Return count of keys from SSG request context\n        return Object.keys(context.fetchRequest.SSGHelpers.requestContext)\n          .length;\n      } else if (hasWindowRequestContext()) {\n        // Client: Return count of keys from window global\n        return Object.keys(\n          (\n            window as unknown as {\n              // eslint-disable-next-line @typescript-eslint/naming-convention\n              __FRONTEND_REQUEST_CONTEXT__: Record<string, unknown>;\n            }\n          ).__FRONTEND_REQUEST_CONTEXT__,\n        ).length;\n      } else {\n        // No context available - return 0\n        return 0;\n      }\n    },\n  };\n}\n\n/**\n * Hook to access and reactively update a single request context value\n *\n * Similar to useState, this hook returns a tuple of [value, setValue] and will\n * cause the component to re-render when the value changes.\n *\n * @param key - The key to track in the request context\n * @returns A tuple of [value, setValue] similar to useState\n *\n * @example\n * ```tsx\n * function ThemeToggle() {\n *   const [theme, setTheme] = useRequestContextValue<string>('theme');\n *\n *   return (\n *     <div>\n *       <p>Current theme: {theme || 'default'}</p>\n *       <button onClick={() => setTheme('dark')}>Dark</button>\n *       <button onClick={() => setTheme('light')}>Light</button>\n *     </div>\n *   );\n * }\n * ```\n */\nexport function useRequestContextValue<T = unknown>(\n  key: string,\n): [T | undefined, (value: T) => void] {\n  const context = useContext(UnirendContext);\n\n  // State to track the current value\n  const [value, setValue] = useState<T | undefined>(\n    () => getRequestContextValue(context, key) as T | undefined,\n  );\n\n  // Effect to sync value when requestContextRevision changes (from other components)\n  // We intentionally only depend on requestContextRevision, not key\n  useEffect(() => {\n    // Synchronizing with external state (request context) tracked by revision counter\n    setValue(getRequestContextValue(context, key) as T | undefined);\n  }, [context.requestContextRevision, context, key]);\n\n  // Setter function that updates storage and increments revision\n  const setContextValue = (newValue: T): void => {\n    setRequestContextValue(context, key, newValue);\n    // Update local state immediately for this component\n    setValue(newValue);\n  };\n\n  return [value, setContextValue];\n}\n","import React, { type ComponentType, type ReactNode } from 'react';\nimport { UnirendHeadProvider } from '../UnirendHead';\nimport type { HeadCollector } from '../UnirendHead';\n\n/**\n * Conditional StrictMode wrapper component\n */\nexport function ConditionalStrictMode({\n  isEnabled,\n  children,\n}: {\n  isEnabled: boolean;\n  children: ReactNode;\n}) {\n  if (isEnabled) {\n    return <React.StrictMode>{children}</React.StrictMode>;\n  }\n\n  return <>{children}</>;\n}\n\n/**\n * UnirendHead wrapper — on server passes the collector, on client passes null\n */\nexport function UnirendHeadWrapper({\n  collector,\n  children,\n}: {\n  collector?: HeadCollector;\n  children: ReactNode;\n}) {\n  return (\n    <UnirendHeadProvider collector={collector ?? null}>\n      {children}\n    </UnirendHeadProvider>\n  );\n}\n\n/**\n * Custom wrapper component handler\n */\nexport function CustomWrapper({\n  WrapComponent,\n  children,\n}: {\n  WrapComponent?: ComponentType<{ children: ReactNode }>;\n  children: ReactNode;\n}) {\n  if (WrapComponent) {\n    return <WrapComponent>{children}</WrapComponent>;\n  }\n\n  return <>{children}</>;\n}\n","import type { ReactNode } from 'react';\nimport { UnirendHeadContext } from './context';\nimport type { HeadCollector } from './context';\n\n/**\n * Wraps the app tree to enable UnirendHead on both server and client.\n *\n * Server: pass a collector object — UnirendHead will push entries into it\n * during renderToString, then the caller reads the collected data.\n *\n * Client: pass null — UnirendHead renders the actual <title>/<meta>/<link>\n * tags and React 19 hoists them to <head> automatically.\n */\nexport function UnirendHeadProvider({\n  children,\n  collector,\n}: {\n  children: ReactNode;\n  collector: HeadCollector | null;\n}) {\n  return (\n    <UnirendHeadContext.Provider value={collector}>\n      {children}\n    </UnirendHeadContext.Provider>\n  );\n}\n","import React, { useContext } from 'react';\nimport type { ReactNode } from 'react';\nimport { UnirendHeadContext } from './context';\nimport type { HeadCollector } from './context';\n\n/**\n * Framework-native document head manager.\n *\n * Place <title>, <meta>, and <link> tags as direct children.\n * Works identically in SSR, SSG, and SPA modes.\n *\n * Server: collects tags via context for injection into the HTML template.\n * Client: renders tags directly; React 19 hoists them to <head>.\n *\n * @example\n * ```tsx\n * import { UnirendHead } from 'unirend/client';\n *\n * function HomePage() {\n *   return (\n *     <>\n *       <UnirendHead>\n *         <title>Home - My App</title>\n *         <meta name=\"description\" content=\"Welcome to my app\" />\n *         <link rel=\"canonical\" href=\"https://example.com/\" />\n *       </UnirendHead>\n *       <main>...</main>\n *     </>\n *   );\n * }\n * ```\n */\nexport function UnirendHead({ children }: { children?: ReactNode }) {\n  const collector = useContext(UnirendHeadContext);\n\n  if (collector !== null) {\n    // Server-side: walk children and collect into the ref.\n    // renderToString is synchronous so mutations here are safe.\n    collectServerHead(collector, children);\n\n    // Render nothing server-side — data is captured in the collector.\n    // The server injects it into <head> via the <!--ss-head--> marker.\n    return null;\n  }\n\n  // Client-side: render the children as real DOM elements.\n  // React 19 automatically hoists <title>, <meta>, <link> to <head>.\n  return <>{children}</>;\n}\n\nfunction collectServerHead(\n  collector: HeadCollector,\n  children: ReactNode,\n): void {\n  React.Children.forEach(children, (child) => {\n    if (!React.isValidElement(child)) {\n      return;\n    }\n\n    const type = child.type as string;\n    const props = child.props as Record<string, unknown>;\n\n    if (type === 'title') {\n      // Last write wins — child route titles override parent layout titles\n      collector.title = toTitleText(props.children as ReactNode);\n    } else if (type === 'meta') {\n      // Accumulate — parent layout and child page metas coexist\n      collector.metas.push(toHeadAttributes(props));\n    } else if (type === 'link') {\n      collector.links.push(toHeadAttributes(props));\n    }\n  });\n}\n\nfunction toTitleText(children: ReactNode): string {\n  return React.Children.toArray(children)\n    .map((node) => {\n      if (\n        typeof node === 'string' ||\n        typeof node === 'number' ||\n        typeof node === 'bigint'\n      ) {\n        return String(node);\n      }\n\n      return '';\n    })\n    .join('');\n}\n\nfunction toHeadAttributes(\n  props: Record<string, unknown>,\n): Record<string, string> {\n  const attrs: Record<string, string> = {};\n\n  for (const [key, value] of Object.entries(props)) {\n    if (key === 'children' || value === null || value === undefined) {\n      continue;\n    }\n\n    const attrValue = toHeadAttributeValue(value);\n    if (attrValue !== null) {\n      attrs[key] = attrValue;\n    }\n  }\n\n  return attrs;\n}\n\nfunction toHeadAttributeValue(value: unknown): string | null {\n  if (typeof value === 'string') {\n    return value;\n  }\n\n  if (typeof value === 'number' || typeof value === 'bigint') {\n    return String(value);\n  }\n\n  if (typeof value === 'boolean') {\n    return value ? 'true' : 'false';\n  }\n\n  return null;\n}\n","import { escapeHTML, escapeHTMLAttr } from '../html-utils/escape';\nimport type { HeadCollector } from './context';\n\n/**\n * Serialize a collected HeadCollector into three HTML strings\n * suitable for injection into the <!--ss-head--> slot.\n */\nexport function serializeHeadCollector(collector: HeadCollector): {\n  title: string;\n  meta: string;\n  link: string;\n} {\n  const title = collector.title\n    ? `<title>${escapeHTML(collector.title)}</title>`\n    : '';\n\n  const meta = collector.metas\n    .map((attrs) => {\n      const attrsStr = Object.entries(attrs)\n        .map(([k, v]) => `${k}=\"${escapeHTMLAttr(v)}\"`)\n        .join(' ');\n\n      return `<meta ${attrsStr} />`;\n    })\n    .join('\\n');\n\n  const link = collector.links\n    .map((attrs) => {\n      const attrsStr = Object.entries(attrs)\n        .map(([k, v]) => `${k}=\"${escapeHTMLAttr(v)}\"`)\n        .join(' ');\n\n      return `<link ${attrsStr} />`;\n    })\n    .join('\\n');\n\n  return { title, meta, link };\n}\n","import type { ReactElement } from 'react';\nimport type { HeadCollector } from '../UnirendHead';\nimport { UnirendProvider } from '../UnirendContext';\nimport {\n  ConditionalStrictMode,\n  UnirendHeadWrapper,\n  CustomWrapper,\n} from './Wrappers';\nimport type { WrapAppElementOptions } from './types';\n\n/**\n * Core unified wrapper function that applies the standard app wrapper chain\n * This ensures EXACTLY the same wrapping order between client and server:\n * StrictMode (outermost) > UnirendProvider > UnirendHeadProvider > rootProviders > RouterElement (innermost)\n *\n * The key insight is that client and server should render identically:\n * - Router type (RouterProvider vs StaticRouterProvider) - different\n * - UnirendHeadProvider - SAME on both, but server gets a collector, client gets null\n * - UnirendProvider - SAME on both, provides render mode and server context\n *\n * @param routerElement - The router element (RouterProvider or StaticRouterProvider)\n * @param options - Configuration options for wrapping\n * @param headCollector - Head data collector for server-side rendering (null on client)\n * @returns The wrapped React element\n */\n\nexport function createAppWrapper(\n  routerElement: ReactElement,\n  options: WrapAppElementOptions,\n  headCollector?: HeadCollector,\n): ReactElement {\n  const {\n    strictMode: isStrictMode = true,\n    rootProviders,\n    unirendContext,\n  } = options;\n\n  return (\n    <ConditionalStrictMode isEnabled={isStrictMode}>\n      <UnirendProvider value={unirendContext}>\n        <UnirendHeadWrapper collector={headCollector}>\n          <CustomWrapper WrapComponent={rootProviders}>\n            {routerElement}\n          </CustomWrapper>\n        </UnirendHeadWrapper>\n      </UnirendProvider>\n    </ConditionalStrictMode>\n  );\n}\n","import type { ReactElement } from 'react';\nimport { StaticRouterProvider } from 'react-router';\nimport type { StaticHandlerContext } from 'react-router';\nimport type { HeadCollector } from '../UnirendHead';\nimport { createAppWrapper } from './CreateAppWrapper';\nimport type { WrapAppElementOptions } from './types';\n\n/**\n * SERVER-SIDE: Wraps a Static Router with the standard app wrappers\n * Uses StaticRouterProvider with UnirendHeadProvider (collector captures head data)\n *\n * @param router - The Static Router instance\n * @param context - The static router context\n * @param options - Configuration options for wrapping\n * @param headCollector - Head data collector for server-side rendering\n * @returns The wrapped StaticRouterProvider element\n */\n\nexport function wrapStaticRouter(\n  router: Parameters<typeof StaticRouterProvider>[0]['router'],\n  context: StaticHandlerContext,\n  options: WrapAppElementOptions,\n  headCollector?: HeadCollector,\n): ReactElement {\n  const routerElement = (\n    <StaticRouterProvider router={router} context={context} />\n  );\n\n  return createAppWrapper(routerElement, options, headCollector);\n}\n","import fastify from 'fastify';\nimport qs from 'qs';\nimport formbody from '@fastify/formbody';\nimport type { FastifyServerOptions, FastifyError } from 'fastify';\nimport {\n  createControlledInstance,\n  classifyRequest,\n  normalizeAPIPrefix,\n  normalizePageDataEndpoint,\n  createDefaultAPIErrorResponse,\n  createDefaultAPINotFoundResponse,\n  registerClosingResponseHook,\n  isSplitHandler,\n  prepareWebResponse,\n  validateAndRegisterPlugin,\n  validateNoHandlersWhenAPIDisabled,\n  buildFastifyHTTPSOptions,\n  registerClientIPDecoration,\n  computeDomainInfo,\n} from './server-utils';\nimport type {\n  APIServerOptions,\n  PluginMetadata,\n  APIResponseHelpersClass,\n  PluginOptions,\n  SplitErrorHandler,\n  SplitNotFoundHandler,\n  AccessLogConfig,\n} from '../types';\nimport { AccessLogPlugin } from './access-log-plugin';\nimport { BaseServer } from './base-server';\nimport { DataLoaderServerHandlerHelpers } from './data-loader-server-handler-helpers';\nimport { APIRoutesServerHelpers } from './api-routes-server-helpers';\nimport { WebSocketServerHelpers } from './web-socket-server-helpers';\nimport type { WebSocketHandlerConfig } from './web-socket-server-helpers';\nimport { resolveFastifyLoggerConfig } from './logger-config-utils';\nimport {\n  registerFileUploadValidationHooks,\n  registerMultipartPlugin,\n} from './file-upload-validation-helpers';\nimport { APIResponseHelpers } from '../../api-envelope';\nimport type { WebSocket, WebSocketServer } from 'ws';\nimport { getDevMode } from 'lifecycleion/dev-mode';\nimport { registerResponseCompression } from './response-compression';\nimport {\n  registerResponseTimeHeader,\n  registerResponseTimeHijackPatch,\n} from './response-time-header';\nimport { deepFreeze } from './utils';\n\n/**\n * API Server class for creating JSON API servers with plugin support\n * Uses createControlledInstance with shouldDisableRootWildcard: false to allow full wildcard flexibility\n */\n\nexport class APIServer extends BaseServer {\n  /** Pluggable helpers class reference for constructing API/Page envelopes */\n  public readonly APIResponseHelpersClass: APIResponseHelpersClass;\n\n  private options: APIServerOptions;\n  private readonly serverLabel: string;\n  private _accessLog: AccessLogPlugin;\n  private pageDataHandlers!: DataLoaderServerHandlerHelpers;\n  private apiRoutes!: APIRoutesServerHelpers;\n  private webSocketHelpers: WebSocketServerHelpers | null = null;\n  private registeredPlugins: PluginMetadata[] = [];\n\n  // Normalized endpoint config (computed once at construction)\n  // Can be false if API handling is disabled (server becomes a plain web server)\n  private readonly normalizedAPIPrefix: string | false;\n  private readonly normalizedPageDataEndpoint: string;\n\n  constructor(options: APIServerOptions = {}) {\n    super();\n\n    this.options = {\n      ...options,\n    };\n\n    this.serverLabel = options.serverLabel ?? 'API';\n    this._accessLog = new AccessLogPlugin(this.serverLabel, options.accessLog);\n\n    // Normalize API endpoint config once at construction\n    this.normalizedAPIPrefix = normalizeAPIPrefix(\n      this.options.apiEndpoints?.apiEndpointPrefix,\n    );\n\n    // Normalize page data endpoint once at construction\n    this.normalizedPageDataEndpoint = normalizePageDataEndpoint(\n      this.options.apiEndpoints?.pageDataEndpoint,\n    );\n\n    // Set helpers class (custom or default)\n    this.APIResponseHelpersClass =\n      this.options.APIResponseHelpersClass || APIResponseHelpers;\n\n    // Initialize helpers (available immediately for handler registration)\n    this.pageDataHandlers = new DataLoaderServerHandlerHelpers();\n    this.apiRoutes = new APIRoutesServerHelpers();\n\n    // Initialize WebSocket helpers if enabled\n    if (this.options.enableWebSockets) {\n      this.webSocketHelpers = new WebSocketServerHelpers(\n        this.APIResponseHelpersClass,\n        this.options.webSocketOptions,\n      );\n    }\n  }\n\n  /**\n   * Start the API server\n   * @param port Port to bind to (default: 3000)\n   * @param host Host to bind to (default: \"localhost\")\n   * @returns Promise that resolves when server is ready\n   */\n  public async listen(\n    port: number = 3000,\n    host: string = 'localhost',\n  ): Promise<void> {\n    if (this._isListening) {\n      throw new Error(\n        'APIServer is already listening. Call stop() first before listening again.',\n      );\n    }\n\n    if (this._isStarting) {\n      throw new Error(\n        'APIServer is already starting. Please wait for the current startup to complete.',\n      );\n    }\n\n    this._isStarting = true;\n    this._isStopping = false;\n\n    // Clear plugin tracking state on startup (handles restart scenarios)\n    this.registeredPlugins = [];\n\n    // Clean up any existing instances from previous failed startups\n    if (this.fastifyInstance) {\n      try {\n        await this.fastifyInstance.close();\n      } catch {\n        // Ignore cleanup errors for stale instances\n      }\n\n      this.fastifyInstance = null;\n    }\n\n    try {\n      // Build Fastify options from curated subset\n      const fastifyOptions: FastifyServerOptions & { https?: unknown } = {};\n\n      Object.assign(\n        fastifyOptions,\n        resolveFastifyLoggerConfig({\n          logging: this.options.logging,\n          fastifyOptions: this.options.fastifyOptions,\n        }),\n      );\n\n      if (this.options.fastifyOptions) {\n        const {\n          trustProxy,\n          bodyLimit,\n          keepAliveTimeout,\n          requestTimeout,\n          connectionTimeout,\n        } = this.options.fastifyOptions;\n\n        if (trustProxy !== undefined) {\n          fastifyOptions.trustProxy = trustProxy;\n        }\n\n        if (bodyLimit !== undefined) {\n          fastifyOptions.bodyLimit = bodyLimit;\n        }\n\n        if (keepAliveTimeout !== undefined) {\n          fastifyOptions.keepAliveTimeout = keepAliveTimeout;\n        }\n\n        if (requestTimeout !== undefined) {\n          fastifyOptions.requestTimeout = requestTimeout;\n        }\n\n        if (connectionTimeout !== undefined) {\n          fastifyOptions.connectionTimeout = connectionTimeout;\n        }\n      }\n\n      // Add HTTPS configuration if provided\n      if (this.options.https) {\n        fastifyOptions.https = buildFastifyHTTPSOptions(this.options.https);\n      }\n\n      // Framework-owned Fastify behavior. These are intentionally not exposed\n      // through fastifyOptions because Unirend depends on them for consistent\n      // routing and shutdown responses across server types.\n      fastifyOptions.return503OnClosing = false;\n\n      fastifyOptions.routerOptions = {\n        // Ignore trailing slashes for flexible routing (matches Express behavior)\n        ignoreTrailingSlash: true,\n        // Use qs for richer query string parsing (nested objects, arrays, encoded brackets)\n        // querystringParser is a router option in Fastify v5+\n        querystringParser: (str) => qs.parse(str),\n      };\n\n      // Create Fastify instance with merged options (user options + defaults + HTTPS + trailing slash)\n      this.fastifyInstance = fastify(fastifyOptions);\n\n      // Register formbody to support application/x-www-form-urlencoded bodies\n      await this.fastifyInstance.register(formbody);\n\n      // Register WebSocket plugin if enabled\n      if (this.webSocketHelpers) {\n        await this.webSocketHelpers.registerWebSocketPlugin(\n          this.fastifyInstance,\n        );\n      }\n\n      // Decorate requests with environment info\n      // The default here is just a shape hint for Fastify; the live value is set per-request in the onRequest hook below.\n      this.fastifyInstance.decorateRequest('isDevelopment', false);\n      this.fastifyInstance.decorateRequest('serverLabel', this.serverLabel);\n      this.fastifyInstance.decorateRequest('publicAppConfig', undefined);\n\n      // Decorate requests with APIResponseHelpersClass for file upload helpers\n      this.fastifyInstance.decorateRequest(\n        'APIResponseHelpersClass',\n        this.APIResponseHelpersClass,\n      );\n\n      // Initialize request context and set live dev-mode flag for all requests (consistent with SSRServer)\n      // This runs early before plugins, so requestContext is always at least an empty object\n      this.fastifyInstance.addHook('onRequest', async (request, _reply) => {\n        // Set live dev-mode flag (read fresh each request so overrideDevMode() takes effect)\n        (request as { isDevelopment?: boolean }).isDevelopment = getDevMode();\n\n        // Capture request start time for envelope timestamp\n        (request as { receivedAt?: number }).receivedAt = Date.now();\n\n        // Initialize per-request context object (always present, never undefined)\n        request.requestContext = {};\n\n        // Compute domain info once per request so plugins/hooks can read rootDomain\n        // (e.g. to set domain=.rootDomain on cookies) without re-parsing the hostname.\n        // computeDomainInfo handles empty/missing hostnames gracefully:\n        // parseHostHeader('') → { domain: '', port: '' }, rootDomain falls back to ''.\n        request.domainInfo = computeDomainInfo(request.hostname);\n\n        // Default false — set true by the static content handler if a static file is served\n        // (e.g. via the staticContent plugin from unirend/plugins).\n        (request as { isStaticAsset?: boolean }).isStaticAsset = false;\n\n        request.publicAppConfig = this.options.publicAppConfig\n          ? deepFreeze(structuredClone(this.options.publicAppConfig))\n          : undefined;\n      });\n\n      // Set request.clientIP once per request — available to plugins, hooks, and access logs.\n      registerClientIPDecoration(\n        this.fastifyInstance,\n        this.options.getClientIP,\n      );\n\n      // Register access logging hooks. Config is read per request so\n      // updateAccessLoggingConfig() changes take effect without a restart.\n      this._accessLog.register(this.fastifyInstance);\n\n      registerClosingResponseHook(\n        this.fastifyInstance,\n        () => this._isStopping,\n        {\n          handler: this.options.closingHandler,\n          // APIServer function form is API-first. Split form can still\n          // customize web requests when APIServer is used as a mixed or\n          // plain web server.\n          functionHandlerType: 'api',\n          serverLabel: this.serverLabel,\n          HelpersClass: this.APIResponseHelpersClass,\n          apiPrefix: this.normalizedAPIPrefix,\n          pageDataEndpoint: this.normalizedPageDataEndpoint,\n        },\n      );\n\n      // Patch reply.hijack() early so all subsequently registered routes\n      // inherit the wrapper, including user/plugin routes that bypass onSend.\n      registerResponseTimeHijackPatch(\n        this.fastifyInstance,\n        this.options.responseTimeHeader,\n      );\n\n      // Register global error handler\n      this.setupErrorHandler();\n      // Register not-found handler\n      this.setupNotFoundHandler();\n\n      // Register plugins if provided\n      if (this.options.plugins && this.options.plugins.length > 0) {\n        await this.registerPlugins();\n      }\n\n      // Register file upload hooks and plugin after user plugins\n      // This ensures user plugin hooks (auth, etc.) run before upload validation\n      if (this.options.fileUploads?.enabled) {\n        // Register validation hook using shared helper\n        registerFileUploadValidationHooks(\n          this.fastifyInstance,\n          this.options.fileUploads,\n        );\n\n        // Register multipart plugin using shared helper (also decorates with multipartEnabled)\n        await registerMultipartPlugin(\n          this.fastifyInstance,\n          this.options.fileUploads,\n        );\n      }\n\n      // Register WebSocket preValidation hook if enabled (before routes but after plugins)\n      if (this.webSocketHelpers) {\n        this.webSocketHelpers.registerPreValidationHook(this.fastifyInstance);\n      }\n\n      // Register API routes if enabled, or validate no handlers were registered if disabled\n      if (this.normalizedAPIPrefix === false) {\n        // API is disabled - validate that no handlers were registered\n        validateNoHandlersWhenAPIDisabled(\n          this.apiRoutes,\n          this.pageDataHandlers,\n        );\n      } else {\n        // API is enabled - register page data and API routes\n        this.pageDataHandlers.registerRoutes(\n          this.fastifyInstance,\n          this.normalizedAPIPrefix,\n          this.normalizedPageDataEndpoint,\n          {\n            versioned: this.options.apiEndpoints?.versioned,\n          },\n        );\n\n        this.apiRoutes.registerRoutes(\n          this.fastifyInstance,\n          this.normalizedAPIPrefix,\n          {\n            versioned: this.options.apiEndpoints?.versioned,\n            allowWildcardAtRoot: true,\n          },\n        );\n      }\n\n      // Register WebSocket routes if enabled\n      if (this.webSocketHelpers) {\n        this.webSocketHelpers.registerRoutes(this.fastifyInstance);\n      }\n\n      // Register response compression for non-streaming API/web responses.\n      // Static file compression is handled separately in the static content layer.\n      registerResponseCompression(\n        this.fastifyInstance,\n        this.options.responseCompression,\n      );\n\n      // Register the response-time header hook after plugins and routes so\n      // third-party onSend hooks run first. Normal Fastify-managed replies\n      // measure the header here, while access logging measures on completion.\n      registerResponseTimeHeader(\n        this.fastifyInstance,\n        this.options.responseTimeHeader,\n      );\n\n      // Start the server\n      await this.fastifyInstance.listen({\n        port,\n        host: host || 'localhost',\n      });\n\n      this._isListening = true;\n      this._isStarting = false;\n    } catch (error) {\n      // Cleanup on any startup failure\n      this._isListening = false;\n      this._isStarting = false;\n\n      const cleanupErrors: string[] = [];\n\n      // Close Fastify if it was created but startup failed\n      if (this.fastifyInstance) {\n        try {\n          await this.fastifyInstance.close();\n        } catch (closeError) {\n          cleanupErrors.push(\n            `Fastify cleanup failed: ${closeError instanceof Error ? closeError.message : String(closeError)}`,\n          );\n        }\n\n        this.fastifyInstance = null;\n      }\n\n      // Clear plugin tracking state on failure\n      this.registeredPlugins = [];\n\n      // Append cleanup errors to original error message if any\n      if (cleanupErrors.length > 0 && error instanceof Error) {\n        // Modify the original error's message directly\n        error.message = `${error.message}. Additional errors occurred: ${cleanupErrors.join(', ')}`;\n      }\n\n      throw error;\n    }\n  }\n\n  /**\n   * Stop the API server if it's currently listening\n   * @returns Promise that resolves when server is stopped\n   */\n  public async stop(): Promise<void> {\n    if (this.fastifyInstance && this._isListening) {\n      this._isStopping = true;\n\n      try {\n        await this.fastifyInstance.close();\n        this._isListening = false;\n        this.fastifyInstance = null;\n      } finally {\n        this._isStopping = false;\n      }\n\n      // Clear plugin tracking state\n      this.registeredPlugins = [];\n    }\n  }\n\n  /**\n   * Merges the provided keys into the current access log config at runtime.\n   * Access logging is on by default (finish events, default template). Use\n   * `events: 'none'` to disable logging while keeping hooks active.\n   * Omitted keys stay unchanged. Pass `undefined` for a hook callback to remove it.\n   *\n   * Changes take effect on the next request — no restart required.\n   */\n  public updateAccessLoggingConfig(partial: Partial<AccessLogConfig>): void {\n    this._accessLog.update(partial);\n  }\n\n  /**\n   * Public API method for registering versioned generic API routes\n   * Usage: server.api.get(\"users/:id\", handler) or server.api.get(\"users/:id\", 2, handler)\n   */\n  public get api() {\n    return this.apiRoutes.apiMethod;\n  }\n\n  /**\n   * Public API method for registering page data loader handlers\n   * Usage: server.pageDataHandler.register(\"home\", handler) or server.pageDataHandler.register(\"home\", 2, handler)\n   */\n  public get pageDataHandler() {\n    return this.pageDataHandlers.pageDataHandlerMethod;\n  }\n\n  /**\n   * Register a WebSocket handler for the specified path\n   *\n   * @param config WebSocket handler configuration\n   * @throws Error if WebSocket support is not enabled\n   */\n  public registerWebSocketHandler(config: WebSocketHandlerConfig): void {\n    if (!this.webSocketHelpers) {\n      throw new Error(\n        \"WebSocket support is not enabled. Set 'enableWebSockets: true' in APIServerOptions to use WebSocket handlers.\",\n      );\n    }\n\n    this.webSocketHelpers.registerWebSocketHandler(config);\n  }\n\n  /**\n   * Get the list of active WebSocket clients\n   *\n   * @returns Set of WebSocket clients, or empty Set if WebSocket support is disabled or server not started\n   */\n  public getWebSocketClients(): Set<WebSocket> {\n    if (!this.fastifyInstance || !this._isListening) {\n      // Server not started or Fastify instance missing — return empty set as a safe fallback\n      return new Set<WebSocket>();\n    }\n\n    // Access the websocketServer decorated by @fastify/websocket plugin\n    const websocketServer = (\n      this.fastifyInstance as unknown as { websocketServer?: WebSocketServer }\n    ).websocketServer;\n\n    if (!websocketServer || !websocketServer.clients) {\n      // WebSocket server not available (plugin not enabled/initialized) — return empty set fallback\n      return new Set<WebSocket>();\n    }\n\n    // Return the underlying ws client set (Set<WebSocket>)\n    return websocketServer.clients;\n  }\n\n  /**\n   * Setup global error handler for unhandled errors\n   * @private\n   */\n  private setupErrorHandler(): void {\n    if (!this.fastifyInstance) {\n      return;\n    }\n\n    this.fastifyInstance.setErrorHandler(async (error, request, reply) => {\n      // If a handler already sent a response and then threw, avoid double-send\n      if (reply.sent || reply.raw.headersSent) {\n        return;\n      }\n\n      // Log errors for debugging (unless explicitly disabled)\n      // This ensures errors are never lost even with custom error handlers\n      if (this.options.logErrors !== false) {\n        const requestID = (request as unknown as { requestID?: string })\n          .requestID;\n\n        request.log.error(\n          {\n            err: error,\n            method: request.method,\n            url: request.url,\n            ...(requestID ? { requestID } : {}),\n          },\n          `[${this.serverLabel}] Request error`,\n        );\n      }\n\n      const { isAPI, isPageData } = classifyRequest(\n        request.url,\n        this.normalizedAPIPrefix,\n        this.normalizedPageDataEndpoint,\n      );\n\n      const isDev = (request as unknown as { isDevelopment: boolean })\n        .isDevelopment;\n\n      // Use custom error handler if provided\n      if (this.options.errorHandler) {\n        try {\n          // Check if it's the split form (object with api and/or web handlers)\n          if (\n            isSplitHandler<Partial<SplitErrorHandler>>(\n              this.options.errorHandler,\n            )\n          ) {\n            const splitHandler = this.options.errorHandler;\n\n            if (isAPI && splitHandler.api) {\n              // Use API handler\n              const errorResponse = await Promise.resolve(\n                splitHandler.api(\n                  request,\n                  error as FastifyError,\n                  isDev,\n                  isPageData,\n                ),\n              );\n\n              // Extract status code from envelope response\n              const statusCode = errorResponse.status_code || 500;\n              reply.code(statusCode);\n\n              if (statusCode >= 400) {\n                reply.header('Cache-Control', 'no-store');\n              }\n\n              return errorResponse;\n            } else if (!isAPI && splitHandler.web) {\n              // Use web handler\n              const webResponse = await Promise.resolve(\n                splitHandler.web(request, error as FastifyError, isDev),\n              );\n\n              return prepareWebResponse(reply, webResponse, 500);\n            }\n\n            // Missing handler for this case - fall through to default\n          } else if (typeof this.options.errorHandler === 'function') {\n            // Function form (SSR compatible)\n            const errorResponse = await Promise.resolve(\n              this.options.errorHandler(\n                request,\n                error as FastifyError,\n                isDev,\n                isPageData,\n              ),\n            );\n\n            // Extract status code from envelope response\n            const statusCode = errorResponse.status_code || 500;\n            reply.code(statusCode);\n\n            if (statusCode >= 400) {\n              reply.header('Cache-Control', 'no-store');\n            }\n\n            return errorResponse;\n          }\n        } catch (handlerError) {\n          // If custom handler fails, fall back to default\n          request.log.error(\n            { err: handlerError, method: request.method, url: request.url },\n            `[${this.serverLabel}] Custom error handler failed`,\n          );\n        }\n      }\n\n      // Default case (also used when split handler is missing api/web)\n      const response = createDefaultAPIErrorResponse(\n        this.APIResponseHelpersClass,\n        request,\n        error as FastifyError,\n        isDev,\n        this.normalizedAPIPrefix,\n        this.normalizedPageDataEndpoint,\n      );\n\n      // Extract status code from envelope response\n      const statusCode =\n        (response as { status_code?: number }).status_code || 500;\n\n      reply.code(statusCode).header('Cache-Control', 'no-store');\n\n      return response;\n    });\n  }\n\n  /**\n   * Setup a default 404 handler that returns standardized envelopes\n   * @private\n   */\n  private setupNotFoundHandler(): void {\n    if (!this.fastifyInstance) {\n      return;\n    }\n\n    this.fastifyInstance.setNotFoundHandler(async (request, reply) => {\n      const { isAPI, isPageData } = classifyRequest(\n        request.url,\n        this.normalizedAPIPrefix,\n        this.normalizedPageDataEndpoint,\n      );\n\n      // If user provided custom not-found handler, use it\n      if (this.options.notFoundHandler) {\n        try {\n          // Check if it's the split form (object with api and/or web handlers)\n          if (\n            isSplitHandler<Partial<SplitNotFoundHandler>>(\n              this.options.notFoundHandler,\n            )\n          ) {\n            const splitHandler = this.options.notFoundHandler;\n\n            if (isAPI && splitHandler.api) {\n              // Use API handler\n              const apiResponse = await Promise.resolve(\n                splitHandler.api(request, isPageData),\n              );\n\n              // Extract status code from envelope response\n              const statusCode = apiResponse.status_code || 404;\n              reply.code(statusCode).header('Cache-Control', 'no-store');\n\n              return apiResponse;\n            } else if (!isAPI && splitHandler.web) {\n              // Use web handler\n              const webResponse = await Promise.resolve(\n                splitHandler.web(request),\n              );\n\n              return prepareWebResponse(reply, webResponse, 404);\n            }\n\n            // Missing handler for this case - fall through to default\n          } else if (typeof this.options.notFoundHandler === 'function') {\n            // Function form (SSR compatible)\n            const custom = await Promise.resolve(\n              this.options.notFoundHandler(request, isPageData),\n            );\n\n            // Extract status code from envelope response\n            const statusCode = custom.status_code || 404;\n            reply.code(statusCode).header('Cache-Control', 'no-store');\n\n            return custom;\n          }\n        } catch (handlerError) {\n          // If custom handler fails, fall back to default\n          request.log.error(\n            { err: handlerError, method: request.method, url: request.url },\n            `[${this.serverLabel}] Custom not-found handler failed`,\n          );\n        }\n      }\n\n      // Default case (also used when split handler is missing api/web)\n      const response = createDefaultAPINotFoundResponse(\n        this.APIResponseHelpersClass,\n        request,\n        this.normalizedAPIPrefix,\n        this.normalizedPageDataEndpoint,\n      );\n\n      // Extract status code from envelope response\n      const statusCode =\n        (response as { status_code?: number }).status_code || 404;\n\n      reply.code(statusCode).header('Cache-Control', 'no-store');\n\n      return response;\n    });\n  }\n\n  /**\n   * Register plugins with controlled access to Fastify instance\n   * @private\n   */\n  private async registerPlugins(): Promise<void> {\n    // If no fastify instance or plugins are provided, return early\n    if (!this.fastifyInstance || !this.options.plugins) {\n      return;\n    }\n\n    // Create controlled instance wrapper with full wildcard support\n    const controlledInstance = createControlledInstance(\n      this.fastifyInstance,\n      false,\n      this.apiRoutes.apiMethod,\n      this.pageDataHandlers.pageDataHandlerMethod,\n      this.APIResponseHelpersClass,\n    );\n\n    // Plugin options to pass to each plugin\n    const isDevelopment = getDevMode();\n\n    const pluginOptions: PluginOptions = {\n      serverType: 'api',\n      mode: isDevelopment ? 'development' : 'production',\n      isDevelopment,\n      apiEndpoints: this.options.apiEndpoints,\n    };\n\n    // Register each plugin with dependency validation\n    for (const plugin of this.options.plugins) {\n      try {\n        // Call plugin and get potential metadata\n        const pluginResult = await plugin(controlledInstance, pluginOptions);\n\n        // Validate dependencies and track plugin\n        validateAndRegisterPlugin(this.registeredPlugins, pluginResult);\n      } catch (error) {\n        this.fastifyInstance?.log.error(\n          { err: error },\n          `[${this.serverLabel}] Failed to register plugin`,\n        );\n\n        throw new Error(\n          `Plugin registration failed: ${error instanceof Error ? error.message : String(error)}`,\n        );\n      }\n    }\n  }\n}\n","import { APIServer } from './internal/api-server';\nimport type { APIServerOptions } from './types';\n\n/**\n * Create an API server instance\n *\n * This creates a JSON API server with plugin support and full wildcard route flexibility.\n * Unlike SSR servers, this allows plugins to register any wildcard routes including root wildcards.\n * Returns an APIServer instance that you can then start with .listen(port, host).\n *\n * @param options Configuration options for the API server\n * @returns APIServer instance ready to be started\n *\n * @example\n * ```typescript\n * import { serveAPI } from 'unirend/server';\n *\n * const server = serveAPI({\n *   plugins: [\n *     async (fastify, options) => {\n *       // Full wildcard support - even root wildcards are allowed\n *       fastify.get('/api/*', async (request, reply) => {\n *         return { message: 'API wildcard route' };\n *       });\n *\n *       fastify.get('*', async (request, reply) => {\n *         return { message: 'Catch-all route' };\n *       });\n *     }\n *   ],\n *   errorHandler: (request, error, isDev) => ({\n *     error: true,\n *     message: error.message,\n *     path: request.url,\n *     timestamp: new Date().toISOString()\n *   })\n * });\n *\n * // Start the server\n * await server.listen(3001, 'localhost');\n * ```\n */\nexport function serveAPI(options: APIServerOptions = {}): APIServer {\n  return new APIServer(options);\n}\n\nexport { APIServer } from './internal/api-server';\n","import { APIServer } from './api-server';\nimport type { FastifyRequest, FastifyReply } from 'fastify';\nimport {\n  normalizeDomain,\n  matchesDomainList,\n  validateConfigEntry,\n  parseHostHeader,\n} from 'lifecycleion/domain-utils';\nimport type {\n  UnirendLoggingOptions,\n  FastifyServerOptions,\n  AccessLogConfig,\n  ResponseTimeHeaderOptions,\n  WebClosingHandlerFn,\n} from '../types';\n\n/**\n * Response configuration for invalid domain handler\n * Matches the pattern from domain-validation plugin for consistency\n */\nexport interface InvalidDomainResponse {\n  contentType: 'json' | 'text' | 'html';\n  content: string | object;\n}\n\n/**\n * Configuration options for the RedirectServer\n */\nexport interface RedirectServerOptions {\n  /**\n   * Target protocol to redirect to\n   * Currently only 'https' is supported\n   * @default 'https'\n   */\n  targetProtocol?: 'https';\n\n  /**\n   * HTTP status code to use for redirects\n   * - 301: Permanent redirect\n   * - 302: Temporary redirect\n   * - 307: Temporary redirect (preserves method)\n   * - 308: Permanent redirect (preserves method)\n   * @default 301\n   */\n  statusCode?: 301 | 302 | 307 | 308;\n\n  /**\n   * Optional list of allowed domains with wildcard support\n   * If provided, only requests to these domains will be redirected\n   * Requests to other domains will receive a 403 error\n   *\n   * Wildcard patterns supported:\n   * - \"example.com\" - allows exact match only\n   * - \"*.example.com\" - allows direct subdomains only (api.example.com ✅, app.api.example.com ❌)\n   * - \"**.example.com\" - allows all subdomains including nested (api.example.com ✅, app.api.example.com ✅)\n   *\n   * Security: This prevents Host header attacks by rejecting unexpected domains\n   *\n   * @example\n   * allowedDomains: ['example.com', '*.example.com']\n   */\n  allowedDomains?: string | string[];\n\n  /**\n   * Whether to preserve port numbers in redirects\n   * - true: example.com:8080 → https://example.com:8080\n   * - false: example.com:8080 → https://example.com (strip port)\n   * Ignored if targetPort is set.\n   * @default false\n   */\n  preservePort?: boolean;\n\n  /**\n   * Override the port in the redirect URL\n   * Useful when HTTP and HTTPS servers run on different non-standard ports\n   * - example: targetPort: 8443 → http://host:8080 redirects to https://host:8443\n   * Takes precedence over preservePort when set.\n   */\n  targetPort?: number;\n\n  /**\n   * Custom handler for invalid domain responses\n   * If not provided, returns a default 403 plain text response\n   * Matches the pattern from domain-validation plugin for consistency\n   *\n   * @param request - The Fastify request object\n   * @param domain - The domain that was not allowed\n   * @returns Response configuration with contentType and content\n   */\n  invalidDomainHandler?: (\n    request: FastifyRequest,\n    domain: string,\n  ) => InvalidDomainResponse;\n\n  /**\n   * Whether to automatically log errors via the server logger\n   * When enabled, all errors are logged before custom error handlers run\n   * @default true\n   */\n  logErrors?: boolean;\n  /**\n   * Label for this server instance, used in error log messages and access log templates.\n   * @default 'Redirect'\n   * @example 'Redirect:http'\n   */\n  serverLabel?: string;\n\n  /**\n   * Framework-level logging options adapted to Fastify under the hood\n   * Same as APIServer and SSRServer logging configuration\n   */\n  logging?: UnirendLoggingOptions;\n\n  /**\n   * Curated Fastify options for redirect server configuration\n   * Only exposes safe options that won't conflict with server setup\n   */\n  fastifyOptions?: FastifyServerOptions;\n\n  /**\n   * First-party access logging configuration\n   * Controls request/response logging without needing a custom plugin\n   */\n  accessLog?: AccessLogConfig;\n\n  /**\n   * Optional response-time header emitted on completed responses.\n   * Passed through to the underlying APIServer.\n   * @default false\n   */\n  responseTimeHeader?: boolean | ResponseTimeHeaderOptions;\n\n  /**\n   * Custom client IP resolver.\n   * When set, called once per request to populate `request.clientIP` — available\n   * throughout the entire request lifecycle (plugins, hooks, page data loader\n   * handlers, API route handlers, access log templates/hooks, etc.).\n   * When not set, `request.clientIP` falls back to `request.ip`\n   * (which reflects Fastify proxy handling when `fastifyOptions.trustProxy`\n   * is configured).\n   *\n   * Use this when behind Cloudflare, AWS ALB, or other CDNs that carry the\n   * real client IP in a custom header.\n   */\n  getClientIP?: (request: FastifyRequest) => string | Promise<string>;\n\n  /**\n   * Custom handler for requests that arrive while the redirect server is shutting down.\n   * If omitted, Unirend returns a default 503 HTML page.\n   */\n  closingHandler?: WebClosingHandlerFn;\n\n  /**\n   * HTTPS server configuration for the redirect server itself\n   * Typically not needed (redirect servers usually run on HTTP port 80)\n   */\n  https?: never; // Explicitly prevent HTTPS on redirect server\n}\n\n/**\n * Dedicated HTTP → HTTPS redirect server\n *\n * Lightweight server specifically designed for HTTP → HTTPS redirects.\n * Wraps APIServer with built-in redirect logic and optional domain validation.\n *\n * Common use case: Run on port 80 to redirect all HTTP traffic to HTTPS (port 443)\n *\n * @example Basic usage\n * ```ts\n * import { RedirectServer } from 'unirend/server';\n *\n * const redirectServer = new RedirectServer({\n *   targetProtocol: 'https',\n *   statusCode: 301,\n * });\n *\n * await redirectServer.listen(80);\n * ```\n *\n * @example With domain validation\n * ```ts\n * const redirectServer = new RedirectServer({\n *   targetProtocol: 'https',\n *   allowedDomains: ['example.com', '*.example.com'],\n * });\n *\n * await redirectServer.listen(80);\n * ```\n */\nexport class RedirectServer {\n  private apiServer: APIServer;\n  private config: {\n    targetProtocol: 'https';\n    statusCode: 301 | 302 | 307 | 308;\n    allowedDomains?: string | string[];\n    preservePort: boolean;\n    targetPort?: number;\n    invalidDomainHandler?: (\n      request: FastifyRequest,\n      domain: string,\n    ) => InvalidDomainResponse;\n  };\n\n  constructor(options: RedirectServerOptions = {}) {\n    // Set defaults\n    this.config = {\n      targetProtocol: options.targetProtocol || 'https',\n      statusCode: options.statusCode || 301,\n      allowedDomains: options.allowedDomains,\n      preservePort: options.preservePort ?? false,\n      targetPort: options.targetPort,\n      invalidDomainHandler: options.invalidDomainHandler,\n    };\n\n    // Validate allowedDomains if provided\n    if (this.config.allowedDomains) {\n      const domains = Array.isArray(this.config.allowedDomains)\n        ? this.config.allowedDomains\n        : [this.config.allowedDomains];\n\n      for (const domain of domains) {\n        const verdict = validateConfigEntry(domain, 'domain');\n\n        if (!verdict.valid) {\n          throw new Error(\n            `Invalid domain in allowedDomains: \"${domain}\"${verdict.info ? ': ' + verdict.info : ''}`,\n          );\n        }\n      }\n    }\n\n    // Create APIServer with API handling disabled (plain web server mode)\n    // Register redirect logic via plugin\n    this.apiServer = new APIServer({\n      apiEndpoints: {\n        apiEndpointPrefix: false, // Disable API handling\n      },\n      logErrors: options.logErrors, // Pass through error logging config\n      serverLabel: options.serverLabel ?? 'Redirect', // Pass through server label with redirect default\n      logging: options.logging, // Pass through logging config\n      fastifyOptions: options.fastifyOptions, // Pass through Fastify options\n      accessLog: options.accessLog, // Pass through access log config\n      responseTimeHeader: options.responseTimeHeader, // Pass through response-time header config\n      closingHandler: options.closingHandler\n        ? { web: options.closingHandler }\n        : undefined,\n      getClientIP: options.getClientIP, // Pass through client IP resolver\n      plugins: [\n        (pluginHost) => {\n          // Register redirect logic as an onRequest hook\n          pluginHost.addHook('onRequest', (request, reply) => {\n            return this.handleRedirect(request, reply);\n          });\n        },\n      ],\n    });\n  }\n\n  /**\n   * Start the redirect server\n   * @param port Port to listen on (typically 80 for HTTP)\n   * @param host Host to bind to (default: 'localhost')\n   */\n  public async listen(\n    port: number = 80,\n    host: string = 'localhost',\n  ): Promise<void> {\n    await this.apiServer.listen(port, host);\n  }\n\n  /**\n   * Stop the redirect server\n   */\n  public async stop(): Promise<void> {\n    await this.apiServer.stop();\n  }\n\n  /**\n   * Check if the server is currently listening\n   */\n  public isListening(): boolean {\n    return this.apiServer.isListening();\n  }\n\n  /**\n   * Force-close all open connections, including those actively serving requests.\n   * See BaseServer.closeAllConnections() for full details.\n   */\n  public closeAllConnections(): void {\n    this.apiServer.closeAllConnections();\n  }\n\n  /**\n   * Merges the provided keys into the current access log config at runtime.\n   * Access logging is on by default (finish events, default template). Use\n   * `events: 'none'` to disable logging while keeping hooks active.\n   * Omitted keys stay unchanged. Pass `undefined` for a hook callback to remove it.\n   *\n   * Changes take effect on the next request — no restart required.\n   */\n  public updateAccessLoggingConfig(partial: Partial<AccessLogConfig>): void {\n    this.apiServer.updateAccessLoggingConfig(partial);\n  }\n\n  /**\n   * Handle the redirect logic\n   * @private\n   */\n  private handleRedirect(\n    request: FastifyRequest,\n    reply: FastifyReply,\n  ): FastifyReply {\n    // Parse host header (supports IPv6 brackets)\n    const host = request.headers.host || '';\n    const { domain, port } = parseHostHeader(host);\n\n    // Normalize domain for validation\n    const normalizedDomain = normalizeDomain(domain);\n\n    // Reject empty domains (missing/malformed Host header)\n    if (!normalizedDomain) {\n      return reply\n        .code(400)\n        .header('Cache-Control', 'no-store')\n        .type('text/plain')\n        .send('Bad Request: Missing or invalid Host header');\n    }\n\n    // Domain validation if allowedDomains is configured\n    if (this.config.allowedDomains) {\n      const allowedDomains = Array.isArray(this.config.allowedDomains)\n        ? this.config.allowedDomains\n        : [this.config.allowedDomains];\n\n      const isAllowed = matchesDomainList(normalizedDomain, allowedDomains);\n\n      if (!isAllowed) {\n        // Domain not allowed - return 403\n        const response = this.config.invalidDomainHandler\n          ? this.config.invalidDomainHandler(request, domain)\n          : {\n              contentType: 'text' as const,\n              content: `Access denied: Domain \"${domain}\" is not authorized`,\n            };\n\n        // Set appropriate content type and send response (do not cache)\n        if (response.contentType === 'json') {\n          return reply\n            .code(403)\n            .header('Cache-Control', 'no-store')\n            .type('application/json')\n            .send(response.content);\n        } else if (response.contentType === 'html') {\n          return reply\n            .code(403)\n            .header('Cache-Control', 'no-store')\n            .type('text/html')\n            .send(response.content);\n        } else {\n          return reply\n            .code(403)\n            .header('Cache-Control', 'no-store')\n            .type('text/plain')\n            .send(response.content);\n        }\n      }\n    }\n\n    // Build redirect URL\n    const protocol = this.config.targetProtocol;\n    let targetHost = normalizedDomain;\n\n    // Handle IPv6 bracketing\n    if (targetHost.includes(':') && !targetHost.startsWith('[')) {\n      targetHost = `[${targetHost}]`;\n    }\n\n    // Add port: targetPort overrides, then preservePort, then strip\n    // Skip port 443 — it's the default for HTTPS and shouldn't appear in the URL\n    const portPart =\n      this.config.targetPort !== null &&\n      this.config.targetPort !== undefined &&\n      this.config.targetPort !== 443\n        ? `:${this.config.targetPort}`\n        : this.config.preservePort && port\n          ? `:${port}`\n          : '';\n\n    // Build final redirect URL\n    const redirectURL = `${protocol}://${targetHost}${portPart}${request.url}`;\n\n    // Perform redirect\n    return reply.code(this.config.statusCode).redirect(redirectURL);\n  }\n}\n","import { RedirectServer } from './internal/redirect-server';\nimport type { RedirectServerOptions } from './internal/redirect-server';\n\n/**\n * Create a dedicated HTTP → HTTPS redirect server\n *\n * Lightweight server specifically designed for HTTP → HTTPS redirects.\n * Common use case: Run on port 80 to redirect all HTTP traffic to HTTPS (port 443)\n *\n * @param options Redirect server configuration\n * @returns RedirectServer instance\n *\n * @example Basic usage (redirect all HTTP to HTTPS)\n * ```ts\n * import { serveRedirect } from 'unirend/server';\n *\n * const redirectServer = serveRedirect({\n *   targetProtocol: 'https',\n *   statusCode: 301,\n * });\n *\n * await redirectServer.listen(80);\n * ```\n *\n * @example With domain validation (security)\n * ```ts\n * const redirectServer = serveRedirect({\n *   targetProtocol: 'https',\n *   allowedDomains: ['example.com', '*.example.com'],\n * });\n *\n * await redirectServer.listen(80);\n * ```\n *\n * @example Multi-server setup (HTTP redirect + HTTPS main server)\n * ```ts\n * import { serveRedirect, serveSSRProd } from 'unirend/server';\n *\n * // HTTP → HTTPS redirect server (port 80)\n * const redirectServer = serveRedirect({\n *   allowedDomains: ['example.com', '*.example.com'],\n * });\n *\n * await redirectServer.listen(80);\n *\n * // Main HTTPS server (port 443)\n * const mainServer = serveSSRProd('./build', {\n *   https: {\n *     key: privateKey,     // string | Buffer\n *     cert: certificate,   // string | Buffer\n *   },\n * });\n *\n * await mainServer.listen(443);\n * ```\n */\nexport function serveRedirect(\n  options: RedirectServerOptions = {},\n): RedirectServer {\n  return new RedirectServer(options);\n}\n\nexport {\n  type RedirectServerOptions,\n  RedirectServer,\n} from './internal/redirect-server';\n","import type { ServerPlugin, StaticContentRouterOptions } from '../types';\nimport { createStaticContentHook } from '../internal/static-content-hook';\nimport { StaticContentCache } from '../internal/static-content-cache';\n\n// Re-export the options type for convenience\n\n// Also re-export FolderConfig since users will need it for folderMap\nexport type { FolderConfig, StaticContentRouterOptions } from '../types';\n\n// Re-export StaticContentCache for external cache management\n\n/**\n * Creates a static content serving plugin that can be used with any Unirend server.\n *\n * This plugin serves static files from configured paths with:\n * - Efficient file caching and ETag support for conditional requests\n * - Content-based strong ETags for small files (SHA-256)\n * - Weak ETags for large files (size + mtime based)\n * - LRU caching for stats, content, and ETags\n * - Range request support for large files\n * - Immutable asset detection for fingerprinted files (optional)\n *\n * Multiple instances can be registered with different configurations,\n * allowing you to serve files from different directories with different settings.\n *\n * @example Basic usage - serve uploads folder\n * ```typescript\n * import { staticContent } from 'unirend/plugins';\n *\n * const server = serveSSRDev(paths, {\n *   plugins: [\n *     staticContent({\n *       folderMap: {\n *         '/uploads': './uploads',\n *         '/static': './public/static',\n *       },\n *     }),\n *   ],\n * });\n * ```\n *\n * @example Multiple folders with different settings\n * ```typescript\n * import { staticContent } from 'unirend/plugins';\n *\n * const server = serveSSRProd(buildDir, {\n *   plugins: [\n *     // User uploads - no immutable caching\n *     staticContent({\n *       folderMap: {\n *         '/uploads': { path: './uploads', detectImmutableAssets: false },\n *       },\n *     }),\n *     // Static assets with fingerprinted filenames - immutable caching\n *     staticContent({\n *       folderMap: {\n *         '/static': { path: './public/static', detectImmutableAssets: true },\n *       },\n *     }),\n *   ],\n * });\n * ```\n *\n * @example Custom plugin name for debugging and dependencies\n * ```typescript\n * const server = serveSSRProd(buildDir, {\n *   plugins: [\n *     staticContent({\n *       folderMap: { '/uploads': './uploads' },\n *     }, 'uploads-handler'),\n *   ],\n * });\n * ```\n *\n * @example Use on standalone API server\n * ```typescript\n * import { createAPIServer } from 'unirend/server';\n * import { staticContent } from 'unirend/plugins';\n *\n * const server = createAPIServer({\n *   plugins: [\n *     staticContent({\n *       folderMap: {\n *         '/files': './data/files',\n *       },\n *       singleAssetMap: {\n *         '/favicon.ico': './public/favicon.ico',\n *       },\n *     }),\n *   ],\n * });\n * ```\n *\n * @example Fine-tuned caching settings\n * ```typescript\n * staticContent({\n *   folderMap: { '/assets': './dist/assets' },\n *   smallFileMaxSize: 1024 * 1024, // 1MB - files below this get content-based ETags\n *   cacheEntries: 200, // Max LRU cache entries\n *   contentCacheMaxSize: 100 * 1024 * 1024, // 100MB total content cache\n *   positiveCacheTtl: 3600 * 1000, // 1 hour for found files\n *   negativeCacheTtl: 60 * 1000, // 1 minute for 404s\n *   cacheControl: 'public, max-age=3600', // Custom Cache-Control\n * })\n * ```\n *\n * @example Provide external cache for runtime updates\n * ```typescript\n * import { staticContent, StaticContentCache } from 'unirend/plugins';\n *\n * // Create cache externally for runtime control\n * const cache = new StaticContentCache({\n *   folderMap: { '/pages': './dist/pages' }\n * });\n *\n * const server = serveSSRDev(paths, {\n *   plugins: [\n *     staticContent(cache, 'pages-handler'),\n *   ],\n * });\n *\n * await server.listen(3000);\n *\n * // Later: update mappings dynamically\n * cache.updateConfig({\n *   singleAssetMap: {\n *     '/blog/new-post': './dist/blog/new-post.html'\n *   }\n * });\n * ```\n *\n * @param configOrCache Static content router configuration OR an existing StaticContentCache instance\n * @param name Optional custom name for this plugin instance (useful for debugging and plugin dependencies)\n * @returns A ServerPlugin that can be added to the plugins array\n */\nexport function staticContent(\n  configOrCache: StaticContentRouterOptions | StaticContentCache,\n  name?: string,\n): ServerPlugin {\n  // Validate custom name if provided\n  if (name !== undefined) {\n    if (typeof name !== 'string' || name.trim().length === 0) {\n      throw new Error(\n        'staticContent plugin name must be a non-empty string if provided',\n      );\n    }\n  }\n\n  // Use custom name or generate a unique instance ID for this plugin registration\n  // This allows multiple instances to be registered and tracked independently\n  const instanceID =\n    name ||\n    `static-content-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n\n  const staticContentPlugin: ServerPlugin = (pluginHost, _pluginOptions) => {\n    // Determine cache source: use provided instance or create new one from config\n    let cache: StaticContentCache;\n\n    if (configOrCache instanceof StaticContentCache) {\n      // Using externally-created cache instance (for runtime updates via updateConfig)\n      // User maintains reference to cache for dynamic updates\n      cache = configOrCache;\n    } else {\n      // Creating new cache from configuration options\n      // Try to get logger from fastify instance if available\n      const logger = pluginHost.getDecoration<{\n        warn: (obj: object, msg: string) => void;\n      }>('log');\n      cache = new StaticContentCache(configOrCache, logger);\n    }\n\n    // Create and register the hook with the cache instance\n    const hook = createStaticContentHook(cache);\n    pluginHost.addHook('onRequest', hook);\n\n    return {\n      name: instanceID,\n    };\n  };\n\n  return staticContentPlugin;\n}\n\nexport { StaticContentCache } from '../internal/static-content-cache';\n","import { APIServer } from './api-server';\nimport { staticContent } from '../built-in-plugins/static-content';\nimport { StaticContentCache } from './static-content-cache';\nimport type {\n  StaticWebServerOptions,\n  ServerPlugin,\n  AccessLogConfig,\n} from '../types';\nimport { readJSONFile, readHTMLFile } from './fs-utils';\nimport { escapeHTML } from './html-utils/escape';\nimport type { FastifyRequest } from 'fastify';\nimport path from 'path';\n\n/**\n * Default 404 Not Found HTML page\n * Proper HTML5 document structure with accessibility and mobile viewport support\n */\nconst DEFAULT_404_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>404 Not Found</title>\n</head>\n<body>\n  <h1>404 Not Found</h1>\n  <p>The page you requested could not be found.</p>\n</body>\n</html>`;\n\n/**\n * Default 500 Internal Server Error HTML page template\n * Proper HTML5 document structure with accessibility and mobile viewport support\n * Includes error stack trace when in development mode (HTML-escaped to prevent XSS)\n */\nfunction createDefault500HTML(isDevelopment: boolean, error?: Error): string {\n  return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>500 Internal Server Error</title>\n</head>\n<body>\n  <h1>500 Internal Server Error</h1>\n  <p>An error occurred while processing your request.</p>\n  ${isDevelopment && error ? `<pre>${escapeHTML(error.stack || 'No stack trace available')}</pre>` : ''}\n</body>\n</html>`;\n}\n\n/**\n * Load an error page HTML file with fallback strategy\n * 1. Try page map entry first (e.g., singleAssetMap['/404']) - already in memory\n * 2. Try custom path if specified - filesystem read\n * 3. Try default filename in buildDir (e.g., '404.html') - filesystem read\n * 4. Return undefined if not found\n *\n * Returns both the HTML content and the resolved file path (for removing from page map)\n */\nasync function loadErrorPageHTML(\n  customPath: string | undefined,\n  pageMapPath: string | undefined,\n  defaultFilename: string,\n  buildDir: string,\n): Promise<{ html: string; filePath: string } | undefined> {\n  // Try page map first (already in memory, most efficient)\n  if (pageMapPath) {\n    const result = await readHTMLFile(pageMapPath);\n\n    if (result.exists && result.content) {\n      return { html: result.content, filePath: pageMapPath };\n    }\n  }\n\n  // Try custom path next\n  if (customPath) {\n    const customFullPath = path.resolve(buildDir, customPath);\n    const result = await readHTMLFile(customFullPath);\n\n    if (result.exists && result.content) {\n      return { html: result.content, filePath: customFullPath };\n    }\n  }\n\n  // Fall back to default filename\n  const defaultPath = path.resolve(buildDir, defaultFilename);\n  const result = await readHTMLFile(defaultPath);\n\n  if (result.exists && result.content) {\n    return { html: result.content, filePath: defaultPath };\n  }\n\n  return undefined;\n}\n\n/**\n * Static Web Server for serving SSG-generated sites\n *\n * Wraps APIServer with static file serving capabilities, designed specifically\n * for serving pre-rendered static HTML pages generated by SSG.\n *\n * Features:\n * - Consumes page-map.json from SSG generation\n * - Serves clean URLs without .html extensions\n * - Handles 404/500 pages with proper status codes\n * - Supports HTTPS/SSL\n * - ETag caching and range request support\n * - Framework-agnostic (works with Node.js, Bun, etc.)\n *\n * @example Basic usage\n * ```ts\n * const server = new StaticWebServer({\n *   buildDir: './build/client',\n *   // pageMapPath defaults to 'page-map.json' (relative to buildDir)\n * });\n *\n * await server.listen(3000);\n * ```\n *\n * @example With HTTPS\n * ```ts\n * const server = new StaticWebServer({\n *   buildDir: './build/client',\n *   https: {\n *     key: privateKey,     // string | Buffer\n *     cert: certificate,   // string | Buffer\n *   },\n * });\n *\n * await server.listen(443);\n * ```\n */\nexport class StaticWebServer {\n  private server: APIServer | null = null;\n  private cache: StaticContentCache | null = null;\n  private notFoundHTML: string | undefined = undefined;\n  private errorHTML: string | undefined = undefined;\n  private routeCount = 0;\n  private options: StaticWebServerOptions;\n\n  constructor(options: StaticWebServerOptions) {\n    // Validate required options\n    if (\n      options.pageMapPath !== undefined &&\n      (typeof options.pageMapPath !== 'string' || options.pageMapPath === '')\n    ) {\n      throw new TypeError(\n        'StaticWebServerOptions.pageMapPath must be a non-empty string',\n      );\n    }\n\n    if (!options.buildDir || typeof options.buildDir !== 'string') {\n      throw new TypeError(\n        'StaticWebServerOptions.buildDir is required and must be a string',\n      );\n    }\n\n    // Validate optional string paths if provided\n    if (options.notFoundPage && typeof options.notFoundPage !== 'string') {\n      throw new TypeError(\n        'StaticWebServerOptions.notFoundPage must be a string',\n      );\n    }\n\n    if (options.errorPage && typeof options.errorPage !== 'string') {\n      throw new TypeError('StaticWebServerOptions.errorPage must be a string');\n    }\n\n    // Validate singleAssets if provided\n    if (options.singleAssets) {\n      if (\n        typeof options.singleAssets !== 'object' ||\n        Array.isArray(options.singleAssets)\n      ) {\n        throw new TypeError(\n          'StaticWebServerOptions.singleAssets must be a Record<string, string>',\n        );\n      }\n\n      for (const [urlPath, filePath] of Object.entries(options.singleAssets)) {\n        if (typeof urlPath !== 'string' || typeof filePath !== 'string') {\n          throw new TypeError(\n            'StaticWebServerOptions.singleAssets keys and values must be strings',\n          );\n        }\n      }\n    }\n\n    // Validate assetFolders if provided\n    if (options.assetFolders) {\n      if (\n        typeof options.assetFolders !== 'object' ||\n        Array.isArray(options.assetFolders)\n      ) {\n        throw new TypeError(\n          'StaticWebServerOptions.assetFolders must be a Record<string, string>',\n        );\n      }\n\n      for (const [urlPrefix, fsPath] of Object.entries(options.assetFolders)) {\n        if (typeof urlPrefix !== 'string' || typeof fsPath !== 'string') {\n          throw new TypeError(\n            'StaticWebServerOptions.assetFolders keys and values must be strings',\n          );\n        }\n      }\n    }\n\n    this.options = options;\n    // Server initialization is deferred to listen() for async setup\n    // (reading page map, error pages, etc.)\n  }\n\n  /**\n   * Start the static web server\n   *\n   * @param port Port number to listen on (default: 3000)\n   * @param host Host to bind to (default: 'localhost')\n   * @returns Promise that resolves when server is ready\n   */\n  public async listen(\n    port: number = 3000,\n    host: string = 'localhost',\n  ): Promise<void> {\n    // If server already exists, delegate to APIServer's own _isListening guard\n    // (matches RedirectServer pattern — single source of truth for the error)\n    if (this.server) {\n      await this.server.listen(port, host);\n      return;\n    }\n\n    // 1. Load page map and error pages\n    const { singleAssetMap, notFoundHTML, errorHTML } = await this.buildMaps();\n\n    // 2. Set error page state (used by handlers below via `this`)\n    this.notFoundHTML = notFoundHTML;\n    this.errorHTML = errorHTML;\n    this.routeCount = Object.keys(singleAssetMap).length;\n\n    // 3. Build folderMap for additional asset directories\n    const folderMap: Record<\n      string,\n      string | { path: string; detectImmutableAssets: boolean }\n    > = {};\n\n    // Paths are resolved relative to buildDir for consistency\n    if (this.options.assetFolders) {\n      for (const [urlPrefix, fsPath] of Object.entries(\n        this.options.assetFolders,\n      )) {\n        folderMap[urlPrefix] = {\n          path: path.resolve(this.options.buildDir, fsPath),\n          detectImmutableAssets: this.options.detectImmutableAssets ?? true,\n        };\n      }\n    }\n\n    // 4. Create StaticContentCache externally so we can call updateConfig() on reload\n    this.cache = new StaticContentCache({\n      singleAssetMap,\n      folderMap,\n      compression: this.options.responseCompression,\n      cacheControl:\n        this.options.cacheControl || 'public, max-age=0, must-revalidate',\n      immutableCacheControl:\n        this.options.immutableCacheControl ||\n        'public, max-age=31536000, immutable',\n    });\n\n    // 5. Create plugins array (pass cache instance rather than raw config)\n    const plugins: ServerPlugin[] = [\n      staticContent(this.cache, 'static-web-server'),\n      ...(this.options.plugins || []),\n    ];\n\n    // 6. Create APIServer with web-only configuration\n    this.server = new APIServer({\n      logErrors: this.options.logErrors, // Pass through error logging config\n      serverLabel: this.options.serverLabel ?? 'Static', // Pass through server label with static default\n      plugins,\n      https: this.options.https, // Pass through HTTPS options (includes SNI support)\n      fastifyOptions: this.options.fastifyOptions,\n      logging: this.options.logging,\n      accessLog: this.options.accessLog,\n      getClientIP: this.options.getClientIP,\n      responseCompression: this.options.responseCompression,\n      responseTimeHeader: this.options.responseTimeHeader,\n      closingHandler: this.options.closingHandler\n        ? { web: this.options.closingHandler }\n        : undefined,\n      // Disable API handling entirely (pure web server mode)\n      apiEndpoints: {\n        apiEndpointPrefix: false,\n      },\n      // Split error handlers for web routes only\n      // API handlers are omitted since apiEndpointPrefix is false (API handling disabled)\n      // If API requests somehow occur, they fall back to default handlers\n      // Arrow functions capture `this` so they always read the current notFoundHTML / errorHTML\n      notFoundHandler: {\n        web: (_request: FastifyRequest) => ({\n          contentType: 'html' as const,\n          content: this.notFoundHTML || DEFAULT_404_HTML,\n          statusCode: 404,\n        }),\n      },\n      errorHandler: {\n        web: (\n          _request: FastifyRequest,\n          error: Error,\n          isDevelopment: boolean,\n        ) => ({\n          contentType: 'html' as const,\n          content: this.errorHTML || createDefault500HTML(isDevelopment, error),\n          statusCode: 500,\n        }),\n      },\n    });\n\n    // 7. Start listening\n    await this.server.listen(port, host);\n  }\n\n  /**\n   * Reload the server configuration from disk without restarting.\n   *\n   * Re-reads the page-map.json and error pages from disk, then updates\n   * the static content cache in-place. In-flight requests continue to be\n   * served, new requests immediately see the updated file mappings.\n   *\n   * If the reload fails (e.g., the page-map.json is missing or invalid),\n   * the server continues serving the previous configuration unchanged.\n   *\n   * _When_ to call reload is the caller's responsibility. A common pattern\n   * is to invoke it after an SSG build completes:\n   * ```ts\n   * // After your build tool writes a new page-map.json:\n   * await server.reload();\n   * ```\n   *\n   * @throws {Error} If the server is not running\n   * @throws {Error} If the page-map.json cannot be read or is invalid\n   * @returns Promise that resolves when the reload is complete\n   */\n  public async reload(): Promise<void> {\n    if (!this.server || !this.cache) {\n      throw new Error('Server is not running. Call listen() first.');\n    }\n\n    // Load fresh page map + error pages from disk.\n    // If this throws, the old state is fully preserved (no partial update).\n    const { singleAssetMap, notFoundHTML, errorHTML } = await this.buildMaps();\n\n    // Re-check after await in case stop() was called concurrently\n    if (!this.cache) {\n      return; // Server was stopped during reload, nothing to update\n    }\n\n    // Replace the page map and flush all file caches in one shot.\n    // A build can change file contents in-place (same filename, new content),\n    // and the rebuilt HTML pages reference JS/CSS bundles in the asset folders\n    // that were likely rebuilt in the same step — so all caches need flushing,\n    // not just the routes that changed. folderMap routing is not reloaded. It\n    // derives from static options.assetFolders which doesn't change between builds.\n    this.cache.replaceConfig({ singleAssetMap });\n\n    // Update error page HTML (handlers read these via `this` on each request)\n    this.notFoundHTML = notFoundHTML;\n    this.errorHTML = errorHTML;\n    this.routeCount = Object.keys(singleAssetMap).length;\n  }\n\n  /**\n   * Stop the server\n   *\n   * @returns Promise that resolves when server is stopped\n   */\n  public async stop(): Promise<void> {\n    if (this.server) {\n      await this.server.stop();\n      this.server = null;\n      this.cache = null;\n      this.notFoundHTML = undefined;\n      this.errorHTML = undefined;\n      this.routeCount = 0;\n    }\n  }\n\n  /**\n   * Check if server is currently listening\n   *\n   * @returns True if server is listening, false otherwise\n   */\n  public isListening(): boolean {\n    return this.server?.isListening() ?? false;\n  }\n\n  /**\n   * Force-close all open connections, including those actively serving requests.\n   * See BaseServer.closeAllConnections() for full details.\n   */\n  public closeAllConnections(): void {\n    this.server?.closeAllConnections();\n  }\n\n  /**\n   * Merges the provided keys into the current access log config at runtime.\n   * Access logging is on by default (finish events, default template). Use\n   * `events: 'none'` to disable logging while keeping hooks active.\n   * Omitted keys stay unchanged. Pass `undefined` for a hook callback to remove it.\n   *\n   * Changes take effect on the next request — no restart required.\n   */\n  public updateAccessLoggingConfig(partial: Partial<AccessLogConfig>): void {\n    this.server?.updateAccessLoggingConfig(partial);\n  }\n\n  /**\n   * Returns server statistics: route count and cache usage.\n   * Returns null if the server is not currently listening.\n   */\n  public getStats() {\n    if (!this.cache) {\n      return null;\n    }\n\n    return { routeCount: this.routeCount, ...this.cache.getCacheStats() };\n  }\n\n  /**\n   * Loads the page-map.json and error pages from disk, returning the built\n   * singleAssetMap (with error page routes removed) and the error page HTML.\n   *\n   * Shared by listen() and reload() to avoid duplicating the loading logic.\n   */\n  private async buildMaps(): Promise<{\n    singleAssetMap: Record<string, string>;\n    notFoundHTML: string | undefined;\n    errorHTML: string | undefined;\n  }> {\n    // 1. Load page-map.json (resolve relative to buildDir)\n    const pageMapPath = path.resolve(\n      this.options.buildDir,\n      this.options.pageMapPath ?? 'page-map.json',\n    );\n    const pageMapResult = await readJSONFile(pageMapPath);\n\n    if (pageMapResult.error || !pageMapResult.exists) {\n      throw new Error(\n        `Failed to load page map from ${pageMapPath}: ${pageMapResult.error || 'File not found'}`,\n      );\n    }\n\n    const pageMap = pageMapResult.data;\n\n    // Validate page map format\n    if (\n      typeof pageMap !== 'object' ||\n      pageMap === null ||\n      Array.isArray(pageMap)\n    ) {\n      throw new TypeError(\n        `Invalid page map format: expected an object, got ${Array.isArray(pageMap) ? 'array' : typeof pageMap}`,\n      );\n    }\n\n    // Validate page map entries (URL path → filename)\n    for (const [urlPath, filename] of Object.entries(pageMap)) {\n      if (typeof urlPath !== 'string' || typeof filename !== 'string') {\n        throw new TypeError(\n          `Invalid page map entry: keys and values must be strings (got ${typeof urlPath} → ${typeof filename})`,\n        );\n      }\n    }\n\n    // 2. Build singleAssetMap (URL → absolute file path)\n    const singleAssetMap: Record<string, string> = {};\n\n    // Add page-map assets\n    for (const [urlPath, filename] of Object.entries(\n      pageMap as Record<string, string>,\n    )) {\n      singleAssetMap[urlPath] = path.resolve(this.options.buildDir, filename);\n    }\n\n    // Merge in user-provided singleAssets (can override page-map assets)\n    // Paths are resolved relative to buildDir for consistency\n    if (this.options.singleAssets) {\n      for (const [urlPath, filePath] of Object.entries(\n        this.options.singleAssets,\n      )) {\n        singleAssetMap[urlPath] = path.resolve(this.options.buildDir, filePath);\n      }\n    }\n\n    // 3. Load custom error pages if specified\n    // Check page map first (if generated as SSG pages), then fall back to disk\n    const notFoundResult = await loadErrorPageHTML(\n      this.options.notFoundPage,\n      singleAssetMap['/404'], // Check if user generated /404 page\n      '404.html',\n      this.options.buildDir,\n    );\n\n    const errorResult = await loadErrorPageHTML(\n      this.options.errorPage,\n      singleAssetMap['/500'], // Check if user generated /500 page\n      '500.html',\n      this.options.buildDir,\n    );\n\n    // 4. Remove error page routes from singleAssetMap to prevent serving them as normal routes\n    // Error pages should only be accessible via error handlers (with proper 404/500 status codes)\n    // If user generated error pages at ANY URL, they shouldn't be served with 200 status\n    if (notFoundResult || errorResult) {\n      // Remove ALL URLs that point to error page files (single loop for efficiency)\n      for (const [url, filePath] of Object.entries(singleAssetMap)) {\n        if (\n          (notFoundResult && filePath === notFoundResult.filePath) ||\n          (errorResult && filePath === errorResult.filePath)\n        ) {\n          delete singleAssetMap[url];\n        }\n      }\n    }\n\n    return {\n      singleAssetMap,\n      notFoundHTML: notFoundResult?.html,\n      errorHTML: errorResult?.html,\n    };\n  }\n}\n","/**\n * Lifecycleion logger adapter for Unirend servers (SSR/API/Static).\n *\n * For the SSG generator version, see {@link ./ssg-lifecycleion-logger}.\n */\n\nimport type { Logger, LoggerService, LogOptions } from 'lifecycleion/logger';\nimport { isPlainObject } from 'lifecycleion/is-plain-object';\nimport type { UnirendLoggerObject } from '../types';\n\n/**\n * Optional Lifecycleion-specific options that can be passed through the\n * UnirendLoggerObject interface via the `context.logger` convention.\n *\n * Place this under the `logger` key in the context object when calling\n * a log method. Both `request.log` (per-request) and `pluginHost.log`\n * (during plugin setup, before any request exists) route through the adaptor\n * and use pino's `(obj, message)` argument order. TypeScript will not catch\n * the wrong order — pino's overloads accept a bare string as the first\n * argument too, so swapping obj/message compiles but silently drops your params.\n *\n * ```typescript\n * pluginHost.get('/api/profile', (request) => {\n *   request.log.info(\n *     { logger: { params: { id: 'u_123' }, redactedKeys: ['token'] } },\n *     'User {{id}} loaded profile',\n *   );\n *   return { ok: true };\n * });\n * ```\n */\nexport interface LifecycleionLogContextOptions {\n  /** Template params for `{{variableName}}` placeholders in the message string. */\n  params?: Record<string, unknown>;\n  /** Keys in params whose values should be redacted in Lifecycleion logger output. */\n  redactedKeys?: string[];\n  /** Tags to attach to the log entry for categorizing/filtering logs (e.g., ['auth', 'security']). */\n  tags?: string[];\n}\n\nfunction extractLogOptions(\n  context: Record<string, unknown> | undefined,\n): LogOptions | undefined {\n  if (!context) {\n    return undefined;\n  }\n\n  // Strip the Lifecycleion-specific `logger` key from the rest of the context.\n  // Everything else (pino bindings like reqId/pid/hostname, user context fields\n  // like err/requestID) is passed as a nested `pinoContext` param — accessible\n  // in templates as {{pinoContext.reqId}} etc.\n  // User-provided params (context.logger.params) are spread beside it at the\n  // top level, keeping them separate and clearly user-owned.\n  const { logger: rawLogger, ...pinoContext } = context;\n  const raw = isPlainObject(rawLogger) ? rawLogger : null;\n\n  const result: LogOptions = {};\n  let hasAny = false;\n\n  const hasPinoContext = Object.keys(pinoContext).length > 0;\n  const userParams = raw && isPlainObject(raw.params) ? raw.params : null;\n\n  if (hasPinoContext || userParams) {\n    result.params = {\n      ...(hasPinoContext ? { pinoContext } : {}),\n      ...(userParams ?? {}),\n    };\n\n    hasAny = true;\n  }\n\n  if (raw && Array.isArray(raw.redactedKeys)) {\n    result.redactedKeys = raw.redactedKeys as string[];\n    hasAny = true;\n  }\n\n  if (raw && Array.isArray(raw.tags)) {\n    result.tags = raw.tags as string[];\n    hasAny = true;\n  }\n\n  return hasAny ? result : undefined;\n}\n\n/**\n * Wraps a Lifecycleion `Logger`, `LoggerService`, or entity logger as a\n * `UnirendLoggerObject` so it can be passed to `logging.logger` in Unirend\n * server options.\n *\n * Level mapping (Unirend → Lifecycleion):\n * - `trace` → `debug` (Lifecycleion has no trace level)\n * - `debug` → `debug`\n * - `info`  → `info`\n * - `warn`  → `warn`\n * - `error` → `error`\n * - `fatal` → `error` (Lifecycleion has no fatal level)\n *\n * Once wired up via `logging.logger`, `request.log` and `pluginHost.log` both\n * route through the adaptor. See {@link LifecycleionLogContextOptions} for\n * template rendering, redaction, and tags via the `context.logger` convention.\n *\n * ```typescript\n * import { Logger } from 'lifecycleion';\n * import { UnirendLifecycleionLoggerAdaptor, serveSSRProd } from 'unirend/server';\n *\n * const logger = new Logger({ sinks: [...] });\n *\n * const server = serveSSRProd('./build', {\n *   logging: {\n *     logger: UnirendLifecycleionLoggerAdaptor(logger),\n *   },\n * });\n * ```\n *\n * See docs/lifecycleion-logger-adaptor.md for full usage and level mapping details.\n */\nexport function UnirendLifecycleionLoggerAdaptor(\n  logger: Logger | LoggerService,\n): UnirendLoggerObject {\n  return {\n    trace: (msg, ctx) => logger.debug(msg, extractLogOptions(ctx)),\n    debug: (msg, ctx) => logger.debug(msg, extractLogOptions(ctx)),\n    info: (msg, ctx) => logger.info(msg, extractLogOptions(ctx)),\n    warn: (msg, ctx) => logger.warn(msg, extractLogOptions(ctx)),\n    error: (msg, ctx) => logger.error(msg, extractLogOptions(ctx)),\n    fatal: (msg, ctx) => logger.error(msg, extractLogOptions(ctx)),\n  };\n}\n","/**\n * Lifecycleion logger integration for the SSG generator.\n *\n * For the server-side (SSR/API) Lifecycleion adapter, see\n * {@link ./lifecycleion-logger-adaptor}.\n */\n\nimport type { Logger } from 'lifecycleion/logger';\nimport type { SSGLogger } from '../types';\n\n/**\n * SSG logger backed by a Lifecycleion logger, scoped to a named service.\n *\n * Pairs with {@link SSGConsoleLogger} — use this when you want SSG generation\n * output routed through your app's existing Lifecycleion logger instead of\n * raw console calls.\n *\n * ```typescript\n * import { Logger } from 'lifecycleion';\n * import { SSGLifecycleionLogger, generateSSG } from 'unirend/server';\n *\n * const logger = new Logger({ sinks: [...] });\n *\n * const result = await generateSSG(buildDir, pages, {\n *   logger: SSGLifecycleionLogger(logger),\n *   // Custom service name:\n *   // logger: SSGLifecycleionLogger(logger, 'my-site-generator'),\n * });\n * ```\n */\nexport function SSGLifecycleionLogger(\n  logger: Logger,\n  serviceName = 'SSG',\n): SSGLogger {\n  const service = logger.service(serviceName);\n\n  return {\n    info: (message) => service.info(message),\n    warn: (message) => service.warn(message),\n    error: (message) => service.error(message),\n  };\n}\n","/**\n * File Upload Helpers\n *\n * Framework-provided utilities for handling single and multiple file uploads\n * with streaming validation, cleanup handlers, and standard API error envelopes.\n *\n * Features:\n * - Unified API (maxFiles defaults to 1)\n * - Mid-stream abort on size/MIME type violations\n * - Cleanup handlers via context.onCleanup()\n * - Fail-fast behavior (first error aborts all)\n * - Standard error envelopes for clients\n */\n\nimport type { FastifyRequest } from 'fastify';\nimport { Transform, type Readable } from 'stream';\nimport type { APIErrorResponse } from '../api-envelope/api-envelope-types';\nimport { matchesMimeTypePattern } from '../internal/mime-type-utils';\nimport { getAPIResponseHelpersClass } from '../internal/api-response-helpers-utils';\nimport type {\n  AbortReason,\n  FileUploadConfig,\n  UploadResult,\n  UploadError,\n  UploadSuccess,\n  ProcessedFile,\n  FileMetadata,\n  ProcessorContext,\n  MimeTypeValidationResult,\n} from './process-file-upload-types';\n\n/**\n * Default rejection reason when MIME type validation fails\n */\nconst DEFAULT_MIME_TYPE_REJECTION_REASON = 'File type not allowed';\n\n/**\n * Internal state for tracking upload progress\n */\ninterface UploadState {\n  aborted: boolean;\n  abortReason?: AbortReason;\n  abortDetails?: Record<string, unknown>;\n  cleanupHandlers: Array<\n    (\n      reason: AbortReason,\n      details?: Record<string, unknown>,\n    ) => Promise<void> | void\n  >;\n  processedFiles: number;\n}\n\n/**\n * BusboyFileStream interface\n *\n * Extends Node.js Readable with Busboy-specific properties.\n * The 'truncated' property is set by @fastify/multipart when a file exceeds size limits.\n */\ninterface BusboyFileStream extends Readable {\n  truncated?: boolean;\n}\n\n/**\n * Error class for upload aborts\n */\nclass UploadAbortError extends Error {\n  constructor(\n    public readonly reason: AbortReason,\n    public readonly details?: Record<string, unknown>,\n  ) {\n    super(`Upload aborted: ${reason}`);\n    this.name = 'UploadAbortError';\n  }\n}\n\n/**\n * Internal processor class that handles upload state and complexity\n */\nclass FileUploadProcessor<T = unknown> {\n  private readonly config: FileUploadConfig<T>;\n  private readonly state: UploadState;\n\n  // Track if cleanup has been initiated (prevents duplicate cleanup calls)\n  private hasCleanupStarted = false;\n\n  // Track the current file stream being processed (so we can destroy it on timeout/abort)\n  private currentFileStream: BusboyFileStream | null = null;\n\n  // Timer handles for timeout and connection monitoring\n  private timeoutHandle: ReturnType<typeof setTimeout> | null = null;\n  private connectionMonitor: ReturnType<typeof setInterval> | null = null;\n\n  // Track the close event listener to ensure proper cleanup\n  private closeHandler: (() => void) | null = null;\n\n  constructor(config: FileUploadConfig<T>) {\n    this.config = config;\n    this.state = {\n      aborted: false,\n      cleanupHandlers: [],\n      processedFiles: 0,\n    };\n  }\n\n  /**\n   * Execute the upload process\n   */\n  public async execute(): Promise<UploadResult<T>> {\n    const { request, onComplete } = this.config;\n\n    // Check if multipart is enabled on the server\n    const fastifyInstance = (\n      request as { server?: { multipartEnabled?: boolean } }\n    ).server;\n\n    if (!fastifyInstance?.multipartEnabled) {\n      throw new Error(\n        'File uploads are not enabled. Add fileUploads: { enabled: true } to your server options.',\n      );\n    }\n\n    // Validate Content-Type header first\n    const contentTypeError = this.validateContentType();\n    if (contentTypeError) {\n      await this.executeOnCompleteCallback(onComplete, contentTypeError);\n      return contentTypeError;\n    }\n\n    try {\n      // Setup monitoring\n      this.setupTimeoutHandler();\n      this.setupConnectionMonitor();\n\n      // Process files\n      const results = await this.processFiles();\n\n      // Release resources (timers + event listeners)\n      this.releaseResources();\n\n      // Handle success\n      return await this.handleSuccess(results);\n    } catch (error) {\n      // Important: release resources immediately on error\n      this.releaseResources();\n\n      // Handle error\n      return await this.handleError(error);\n    } finally {\n      // Safety net: ensure resources are always released\n      this.releaseResources();\n    }\n  }\n\n  /**\n   * Validate Content-Type header\n   */\n  private validateContentType(): UploadError | null {\n    const { request } = this.config;\n    const contentType = request.headers['content-type'];\n\n    if (!contentType || !contentType.includes('multipart/form-data')) {\n      // Note: No cleanup handlers to run here - this check happens before any file processing\n      // and before the state object is created (no processors have run yet)\n\n      // Create custom error response for invalid Content-Type\n      const errorEnvelope = this.buildValidationErrorResponse(\n        415,\n        'invalid_content_type',\n        'Content-Type must be multipart/form-data',\n        {\n          received_content_type: contentType || 'none',\n          expected_content_type: 'multipart/form-data',\n        },\n      );\n\n      return {\n        success: false,\n        errorEnvelope,\n      };\n    }\n\n    return null;\n  }\n\n  /**\n   * Setup timeout handler\n   */\n  private setupTimeoutHandler(): void {\n    const { timeoutMS } = this.config;\n\n    if (timeoutMS) {\n      this.timeoutHandle = setTimeout(() => {\n        if (!this.state.aborted) {\n          this.state.aborted = true;\n          this.state.abortReason = 'timeout';\n          this.releaseResources();\n          if (this.currentFileStream && !this.currentFileStream.destroyed) {\n            this.currentFileStream.destroy(new Error('Upload timeout'));\n          }\n        }\n      }, timeoutMS);\n    }\n  }\n\n  /**\n   * Setup connection monitoring\n   *\n   * Uses both event-based detection (via 'close' event on the request socket)\n   * and polling as a failsafe (every 500ms). The 'close' event provides immediate\n   * detection when the client disconnects, while polling catches edge cases where\n   * the event might not fire.\n   */\n  private setupConnectionMonitor(): void {\n    const { reply, request } = this.config;\n\n    // Primary detection: listen for 'close' event on the connection\n    // This fires immediately when the client disconnects\n    const onClose = () => {\n      if (!this.state.aborted) {\n        this.state.aborted = true;\n        this.state.abortReason = 'connection_broken';\n        this.releaseResources();\n        if (this.currentFileStream && !this.currentFileStream.destroyed) {\n          this.currentFileStream.destroy(new Error('Connection broken'));\n        }\n      }\n    };\n\n    // Store the handler so we can remove it during cleanup (prevents memory leaks)\n    this.closeHandler = onClose;\n\n    // Attach close listener to request (which holds the socket connection)\n    // Type assertion: request.raw is NodeJS.Socket which implements EventEmitter\n    (request.raw as NodeJS.EventEmitter).on('close', onClose);\n\n    // Fallback polling: check reply.raw.destroyed every 500ms\n    // This is a safety net in case the 'close' event doesn't fire\n    // 500ms is reasonable for large uploads that take minutes (vs aggressive 100ms polling)\n    this.connectionMonitor = setInterval(() => {\n      if (reply.raw.destroyed && !this.state.aborted) {\n        this.state.aborted = true;\n        this.state.abortReason = 'connection_broken';\n        this.releaseResources();\n        if (this.currentFileStream && !this.currentFileStream.destroyed) {\n          this.currentFileStream.destroy(new Error('Connection broken'));\n        }\n      }\n    }, 500);\n  }\n\n  /**\n   * Release internal resources (timers and event listeners)\n   *\n   * Removes the connection monitoring interval, timeout, and close event listener\n   * to prevent memory leaks and ensure proper resource cleanup.\n   */\n  private releaseResources(): void {\n    // Remove close event listener to prevent memory leaks\n    if (this.closeHandler) {\n      (this.config.request.raw as NodeJS.EventEmitter).removeListener(\n        'close',\n        this.closeHandler,\n      );\n\n      this.closeHandler = null;\n    }\n\n    if (this.connectionMonitor) {\n      clearInterval(this.connectionMonitor);\n      this.connectionMonitor = null;\n    }\n\n    if (this.timeoutHandle) {\n      clearTimeout(this.timeoutHandle);\n      this.timeoutHandle = null;\n    }\n  }\n\n  /**\n   * Process files from multipart iterator\n   */\n  private async processFiles(): Promise<ProcessedFile<T>[]> {\n    const {\n      request,\n      maxFiles = 1,\n      maxSizePerFile,\n      maxFields,\n      maxFieldSize,\n    } = this.config;\n\n    // Process files inline during iteration\n    // This is critical for:\n    // 1. Validating MIME types BEFORE consuming bandwidth (DoS prevention)\n    // 2. Consuming streams during iteration (prevents iterator hanging)\n    // 3. No memory buffering (process one file at a time)\n    const results: ProcessedFile<T>[] = [];\n    let fileIndex = 0;\n\n    const filesIterator = request.files({\n      throwFileSizeLimit: false,\n      limits: {\n        fileSize: maxSizePerFile,\n        files: maxFiles,\n        ...(maxFields !== undefined && { fields: maxFields }),\n        ...(maxFieldSize !== undefined && { fieldSize: maxFieldSize }),\n      },\n    });\n\n    for await (const file of filesIterator) {\n      // Check if already aborted (timeout, connection broken, etc.)\n      // This check runs BETWEEN files - can't interrupt a running processor\n      // If abort happened during previous processor, we detect it here before starting next file\n      if (this.state.aborted) {\n        if (file.file && !file.file.destroyed) {\n          file.file.destroy();\n        }\n\n        await this.drainMultipartIterator(filesIterator);\n        const abortReason = this.state.abortReason || 'processor_error';\n\n        throw new UploadAbortError(\n          abortReason,\n          this.state.abortDetails || { fileIndex },\n        );\n      }\n\n      // Validate MIME type BEFORE consuming stream (prevents bandwidth waste / DoS)\n      await this.validateMimeType(file, fileIndex, filesIterator);\n\n      // Process the file\n      const result = await this.processFile(file, fileIndex);\n      results.push(result);\n      fileIndex++;\n    }\n\n    // Validate file count\n    if (fileIndex === 0) {\n      throw new UploadAbortError('no_files_provided', {\n        message: 'No files were provided in the request',\n      });\n    }\n\n    return results;\n  }\n\n  /**\n   * Validate MIME type for a file\n   */\n  private async validateMimeType(\n    file: {\n      file: BusboyFileStream;\n      fieldname: string;\n      filename: string;\n      encoding: string;\n      mimetype: string;\n    },\n    fileIndex: number,\n    filesIterator: AsyncIterableIterator<{\n      file: BusboyFileStream;\n      fieldname: string;\n      filename: string;\n      encoding: string;\n      mimetype: string;\n    }>,\n  ): Promise<void> {\n    const { allowedMimeTypes, maxFiles = 1 } = this.config;\n\n    const mimeTypeValidation: MimeTypeValidationResult =\n      typeof allowedMimeTypes === 'function'\n        ? allowedMimeTypes(file.mimetype)\n        : allowedMimeTypes.some((pattern) =>\n              matchesMimeTypePattern(file.mimetype, pattern),\n            )\n          ? { allowed: true }\n          : {\n              allowed: false,\n              rejectionReason: DEFAULT_MIME_TYPE_REJECTION_REASON,\n              allowedTypes: allowedMimeTypes,\n            };\n\n    if (!mimeTypeValidation.allowed) {\n      this.state.aborted = true;\n\n      const failureDetails: Record<string, unknown> = {\n        fileIndex,\n        filename: file.filename,\n        receivedMimeType: file.mimetype,\n        rejectionReason:\n          mimeTypeValidation.rejectionReason ||\n          DEFAULT_MIME_TYPE_REJECTION_REASON,\n      };\n\n      if (mimeTypeValidation.allowedTypes) {\n        failureDetails.allowedMimeTypes = mimeTypeValidation.allowedTypes;\n      }\n\n      const abortReason = this.buildAbortDetails(\n        maxFiles,\n        fileIndex,\n        'mime_type_rejected',\n        'MIME_TYPE_REJECTED',\n        failureDetails,\n      );\n\n      if (file.file && !file.file.destroyed) {\n        file.file.destroy();\n      }\n\n      await this.drainMultipartIterator(filesIterator);\n      throw new UploadAbortError(abortReason, this.state.abortDetails);\n    }\n  }\n\n  /**\n   * Process a single file\n   */\n  private async processFile(\n    file: {\n      file: BusboyFileStream;\n      fieldname: string;\n      filename: string;\n      encoding: string;\n      mimetype: string;\n    },\n    fileIndex: number,\n  ): Promise<ProcessedFile<T>> {\n    const { processor, maxFiles = 1 } = this.config;\n\n    // Track this file's cleanup handlers separately to handle race conditions\n    const fileCleanupHandlers: Array<\n      (\n        reason: AbortReason,\n        details?: Record<string, unknown>,\n      ) => Promise<void> | void\n    > = [];\n\n    const context: ProcessorContext = {\n      fileIndex,\n      onCleanup: (cleanupFn) => {\n        // Add to both file-specific and batch-level cleanup lists\n        //\n        // IMPORTANT: Cleanup handlers for successful files are INTENTIONALLY kept in the\n        // batch-level list (this.state.cleanupHandlers). This implements fail-fast\n        // transactional semantics:\n        //\n        // Example: File 0 succeeds, File 1 succeeds, File 2 fails\n        // - Result: ENTIRE batch fails (all-or-nothing)\n        // - Cleanup runs for ALL files (0, 1, 2) to maintain consistency\n        // - Files 0 and 1 were uploaded to storage, so they must be deleted\n        //\n        // This prevents orphaned files when a batch operation fails partway through.\n        fileCleanupHandlers.push(cleanupFn);\n        this.state.cleanupHandlers.push(cleanupFn);\n      },\n      isAborted: () => this.state.aborted,\n    };\n\n    const metadata: FileMetadata = {\n      filename: file.filename,\n      mimetype: file.mimetype,\n      encoding: file.encoding,\n      fieldname: file.fieldname,\n      fileIndex,\n    };\n\n    // Create byte counter to track actual bytes received\n    const byteCounter = this.createByteCounter();\n    const wrappedStream = file.file.pipe(byteCounter.stream);\n\n    try {\n      // Track current file stream so timeout/connection monitor can destroy it\n      this.currentFileStream = file.file;\n      // Run processor (this consumes the stream)\n      const processorResult = await processor(wrappedStream, metadata, context);\n      // Clear current file stream reference\n      this.currentFileStream = null;\n\n      // Check if aborted during processor execution (timeout, connection broken, etc.)\n      // If stream was destroyed, processor likely threw an error (caught below)\n      // CRITICAL: This check must happen IMMEDIATELY after processor completes to catch race conditions\n      // where timeout/connection-break callbacks were scheduled during processor execution\n      // but haven't executed yet\n      if (this.state.aborted) {\n        // Processor completed but we detected an abort - run THIS FILE's cleanup immediately\n        // (prevents cleanup delay in batch uploads)\n        // Note: We do NOT set hasCleanupStarted=true here because the outer catch block\n        // needs to run cleanup for ALL files (including previously-successful ones)\n        await this.runFileCleanupAndRemoveFromBatch(\n          fileCleanupHandlers,\n          this.state.abortReason || 'timeout',\n          this.state.abortDetails || { fileIndex },\n        );\n\n        // Processor completed but we timed out - treat as abort\n        this.state.abortReason = this.state.abortReason || 'timeout';\n        throw new UploadAbortError(\n          this.state.abortReason,\n          this.state.abortDetails || { fileIndex },\n        );\n      }\n\n      // Check for truncation\n      // IMPORTANT: Due to how @fastify/multipart works with throwFileSizeLimit: false,\n      // truncation is only detected AFTER the stream has been consumed. This means\n      // the processor has already uploaded/processed the truncated file before we detect it.\n      // Cleanup will delete the partial file, but bandwidth has already been consumed.\n      // This is a known limitation of the multipart parser's streaming behavior.\n      if (file.file.truncated) {\n        this.state.aborted = true;\n\n        const bytesReceived = byteCounter.getBytesRead();\n        const failureDetails = {\n          fileIndex,\n          filename: file.filename,\n          maxSizePerFile: this.config.maxSizePerFile,\n          bytesReceived,\n        };\n\n        // Build abort details with batch mode wrapping if needed\n        const abortReason = this.buildAbortDetails(\n          maxFiles,\n          fileIndex,\n          'size_exceeded',\n          'SIZE_EXCEEDED',\n          failureDetails,\n        );\n\n        // Run THIS FILE's cleanup immediately (before throwing to prevent delay in batch uploads)\n        // Note: We do NOT set hasCleanupStarted=true here because the outer catch block\n        // needs to run cleanup for ALL files (including previously-successful ones)\n        await this.runFileCleanupAndRemoveFromBatch(\n          fileCleanupHandlers,\n          abortReason,\n          this.state.abortDetails,\n        );\n\n        throw new UploadAbortError(abortReason, this.state.abortDetails);\n      }\n\n      // File processed successfully\n      this.state.processedFiles++;\n\n      // Note: Cleanup handlers for this successful file remain in this.state.cleanupHandlers\n      // This is intentional for fail-fast batch semantics - if ANY later file fails,\n      // ALL files (including this successful one) will be cleaned up to maintain transactional consistency\n\n      return {\n        fileIndex,\n        filename: file.filename,\n        data: processorResult,\n      };\n    } catch (processorError) {\n      // Clear current file stream reference\n      this.currentFileStream = null;\n\n      // Processor threw an error (storage failure, stream destroyed, etc.)\n      this.state.aborted = true;\n\n      // Sanitize error message for production (don't expose internal details)\n      const errorMessage = this.getSanitizedErrorMessage(\n        processorError,\n        'Storage error',\n      );\n\n      const failureDetails = {\n        fileIndex,\n        filename: file.filename,\n        error: errorMessage,\n      };\n\n      // Build abort details with batch mode wrapping if needed\n      const abortReason = this.buildAbortDetails(\n        maxFiles,\n        fileIndex,\n        'processor_error',\n        'PROCESSOR_ERROR',\n        failureDetails,\n      );\n\n      // Run THIS FILE's cleanup immediately (before throwing to prevent delay in batch uploads)\n      // Note: We do NOT set hasCleanupStarted=true here because the outer catch block\n      // needs to run cleanup for ALL files (including previously-successful ones)\n      await this.runFileCleanupAndRemoveFromBatch(\n        fileCleanupHandlers,\n        abortReason,\n        this.state.abortDetails,\n      );\n\n      throw new UploadAbortError(abortReason, this.state.abortDetails);\n    }\n  }\n\n  /**\n   * Handle successful upload\n   */\n  private async handleSuccess(\n    results: ProcessedFile<T>[],\n  ): Promise<UploadResult<T>> {\n    const { onComplete } = this.config;\n\n    const successResult: UploadSuccess<T> = {\n      success: true,\n      files: results,\n    };\n\n    if (onComplete) {\n      let onCompleteError: unknown = null;\n\n      await Promise.resolve()\n        .then(() => onComplete(successResult))\n        .catch((error) => {\n          onCompleteError = error;\n        });\n\n      if (onCompleteError) {\n        this.config.request.log.error(\n          { err: onCompleteError },\n          'onComplete callback failed after successful upload',\n        );\n\n        const errorMessage = this.getSanitizedErrorMessage(\n          onCompleteError,\n          'Post-processing failed',\n        );\n\n        const errorEnvelope = this.buildValidationErrorResponse(\n          500,\n          'file_upload_completion_failed',\n          'Files uploaded successfully but post-processing failed',\n          {\n            error: errorMessage,\n            filesProcessed: results.length,\n          },\n        );\n\n        return {\n          success: false,\n          errorEnvelope,\n        };\n      }\n    }\n\n    return successResult;\n  }\n\n  /**\n   * Handle upload error\n   */\n  private async handleError(error: unknown): Promise<UploadError> {\n    const { request, onComplete, maxFiles = 1 } = this.config;\n\n    // Handle FilesLimitError from @fastify/multipart\n    if (error instanceof Error && error.name === 'FilesLimitError') {\n      if (!this.hasCleanupStarted) {\n        this.hasCleanupStarted = true;\n        await this.runCleanupHandlers('files_limit_exceeded', { maxFiles });\n      }\n\n      const errorEnvelope = this.buildValidationErrorResponse(\n        413,\n        'file_max_files_exceeded',\n        'Number of files exceeds maximum allowed',\n        { maxFiles },\n      );\n\n      const errorResult: UploadError = {\n        success: false,\n        errorEnvelope,\n      };\n\n      await this.executeOnCompleteCallback(onComplete, errorResult);\n      return errorResult;\n    }\n\n    // Handle no files provided\n    if (\n      error instanceof UploadAbortError &&\n      error.reason === 'no_files_provided'\n    ) {\n      if (!this.hasCleanupStarted) {\n        this.hasCleanupStarted = true;\n        await this.runCleanupHandlers('no_files_provided', {});\n      }\n\n      const errorEnvelope = this.buildValidationErrorResponse(\n        400,\n        'file_not_provided',\n        maxFiles === 1\n          ? 'No file was provided in the request'\n          : 'No files were provided in the request',\n      );\n\n      const errorResult: UploadError = {\n        success: false,\n        errorEnvelope,\n      };\n\n      await this.executeOnCompleteCallback(onComplete, errorResult);\n      return errorResult;\n    }\n\n    // Run cleanup handlers if not already started\n    if (!this.hasCleanupStarted) {\n      this.hasCleanupStarted = true;\n      if (error instanceof UploadAbortError) {\n        await this.runCleanupHandlers(error.reason, error.details);\n      } else {\n        const errorMessage = this.getSanitizedErrorMessage(\n          error,\n          'Unknown error',\n        );\n        await this.runCleanupHandlers('processor_error', {\n          error: errorMessage,\n        });\n      }\n    }\n\n    // Build error response\n    let errorEnvelope: APIErrorResponse;\n\n    if (error instanceof UploadAbortError) {\n      errorEnvelope = this.buildAbortErrorResponse(error.reason, error.details);\n    } else {\n      request.log.error({ err: error }, 'Unexpected file upload error');\n\n      const errorMessage = this.getSanitizedErrorMessage(\n        error,\n        'Unknown error',\n      );\n\n      errorEnvelope = this.buildValidationErrorResponse(\n        500,\n        'file_upload_failed',\n        'An unexpected error occurred during file upload',\n        { error: errorMessage },\n      );\n    }\n\n    const errorResult: UploadError = {\n      success: false,\n      errorEnvelope,\n    };\n\n    await this.executeOnCompleteCallback(onComplete, errorResult);\n    return errorResult;\n  }\n\n  /**\n   * Build validation error response\n   */\n  private buildValidationErrorResponse(\n    statusCode: number,\n    errorCode: string,\n    errorMessage: string,\n    details?: Record<string, unknown>,\n  ): APIErrorResponse {\n    const helpersClass = getAPIResponseHelpersClass(this.config.request);\n\n    return helpersClass.createAPIErrorResponse({\n      request: this.config.request,\n      statusCode,\n      errorCode,\n      errorMessage,\n      errorDetails: details,\n    });\n  }\n\n  /**\n   * Build abort error response from AbortReason\n   */\n  private buildAbortErrorResponse(\n    reason: AbortReason,\n    details?: Record<string, unknown>,\n  ): APIErrorResponse {\n    let statusCode: number;\n    let errorCode: string;\n    let errorMessage: string;\n\n    switch (reason) {\n      case 'size_exceeded':\n        statusCode = 413;\n        errorCode = 'file_too_large';\n        errorMessage =\n          'File exceeded maximum size limit during streaming and was truncated';\n        break;\n\n      case 'mime_type_rejected':\n        statusCode = 415;\n        errorCode = 'file_type_not_allowed';\n        errorMessage = 'File type is not allowed for this endpoint';\n        break;\n\n      case 'connection_broken':\n        statusCode = 499;\n        errorCode = 'file_upload_connection_broken';\n        errorMessage =\n          'Client disconnected during upload, partial data discarded';\n        break;\n\n      case 'timeout':\n        statusCode = 408;\n        errorCode = 'file_upload_timeout';\n        errorMessage = 'Upload timed out, partial data discarded';\n        break;\n\n      case 'batch_file_failed':\n        statusCode = 400;\n        errorCode = 'file_batch_upload_failed';\n        errorMessage =\n          'One or more files in batch upload failed, all uploads aborted';\n        break;\n\n      case 'processor_error':\n        statusCode = 500;\n        errorCode = 'file_processor_error';\n        errorMessage = 'Failed to process file';\n        break;\n\n      case 'files_limit_exceeded':\n        statusCode = 413;\n        errorCode = 'file_max_files_exceeded';\n        errorMessage = 'Number of files exceeds maximum allowed';\n        break;\n\n      case 'no_files_provided':\n        statusCode = 400;\n        errorCode = 'file_not_provided';\n        errorMessage = 'No files were provided in the request';\n        break;\n\n      default:\n        statusCode = 500;\n        errorCode = 'file_upload_failed';\n        errorMessage = 'Upload failed for unknown reason';\n        break;\n    }\n\n    const helpersClass = getAPIResponseHelpersClass(this.config.request);\n\n    return helpersClass.createAPIErrorResponse({\n      request: this.config.request,\n      statusCode,\n      errorCode,\n      errorMessage,\n      errorDetails: details,\n    });\n  }\n\n  /**\n   * Create a byte-counting transform stream wrapper\n   *\n   * The Transform stream respects Node.js's default backpressure handling via highWaterMark.\n   * If a downstream consumer (e.g., slow object storage) cannot keep up, the stream will\n   * naturally pause until the downstream is ready, preventing excessive memory buffering.\n   */\n  private createByteCounter(): {\n    stream: Transform;\n    getBytesRead: () => number;\n  } {\n    let bytesRead = 0;\n\n    const stream = new Transform({\n      // Use Node.js default highWaterMark (16KB) which provides good backpressure handling\n      // for most upload scenarios. The stream will pause when downstream can't keep up.\n      highWaterMark: 16 * 1024,\n      transform(chunk: Buffer, _encoding, callback) {\n        bytesRead += chunk.length;\n        callback(null, chunk);\n      },\n    });\n\n    return {\n      stream,\n      getBytesRead: () => bytesRead,\n    };\n  }\n\n  /**\n   * Run all cleanup handlers\n   */\n  private async runCleanupHandlers(\n    reason: AbortReason,\n    details: Record<string, unknown> | undefined,\n  ): Promise<void> {\n    const cleanupPromises = this.state.cleanupHandlers.map(async (handler) => {\n      try {\n        await handler(reason, details);\n      } catch (cleanupError) {\n        this.config.request.log.error(\n          { err: cleanupError, reason, details },\n          'File upload cleanup handler error',\n        );\n      }\n    });\n\n    await Promise.allSettled(cleanupPromises);\n  }\n\n  /**\n   * Execute onComplete callback with error handling\n   *\n   * This method uses defensive double error handling to ensure onComplete errors\n   * during error paths don't break the error flow:\n   *\n   * 1. Inner catch: Catches synchronous errors from onComplete\n   * 2. Outer catch: Safety net for any unexpected errors in the Promise chain\n   *\n   * When onComplete throws during error handling, we log but preserve the original\n   * error for the client (rather than replacing it with the onComplete error).\n   * This behavior differs from the success path where onComplete errors become\n   * 'file_upload_completion_failed' errors - this asymmetry is intentional to\n   * ensure clients always see the root cause of upload failures.\n   */\n  private async executeOnCompleteCallback(\n    onComplete: ((result: UploadResult<T>) => Promise<void> | void) | undefined,\n    result: UploadResult<T>,\n  ): Promise<void> {\n    if (!onComplete) {\n      return;\n    }\n\n    try {\n      await Promise.resolve()\n        .then(() => onComplete(result))\n        .catch((onCompleteError) => {\n          this.config.request.log.error(\n            { err: onCompleteError },\n            'onComplete callback failed during error handling',\n          );\n        });\n    } catch (onCompleteError) {\n      this.config.request.log.error(\n        { err: onCompleteError },\n        'onComplete callback failed during error handling (outer catch)',\n      );\n    }\n  }\n\n  /**\n   * Drain remaining parts from multipart iterator\n   *\n   * When an upload fails mid-stream (e.g., MIME rejection, size exceeded), we need to\n   * consume any remaining parts from the multipart iterator to prevent it from hanging.\n   * The 1000ms timeout is chosen as a reasonable balance:\n   * - Most multipart iterators drain quickly (< 100ms) in normal conditions\n   * - 1000ms is long enough to handle slow network conditions without blocking too long\n   * - If the iterator truly hangs (network issue, client disconnect), we don't want to\n   *   wait indefinitely - better to timeout and let the connection cleanup handle it\n   */\n  private async drainMultipartIterator(\n    filesIterator: AsyncIterableIterator<{\n      file: BusboyFileStream;\n      fieldname: string;\n      filename: string;\n      encoding: string;\n      mimetype: string;\n    }>,\n    drainTimeout: number = 1000,\n  ): Promise<void> {\n    try {\n      await Promise.race([\n        (async () => {\n          for await (const remainingPart of filesIterator) {\n            if (remainingPart.file && !remainingPart.file.destroyed) {\n              remainingPart.file.destroy();\n            }\n          }\n        })(),\n        new Promise((_, reject) =>\n          setTimeout(\n            () => reject(new Error('Iterator drain timeout')),\n            drainTimeout,\n          ),\n        ),\n      ]);\n    } catch (drainError) {\n      this.config.request.log.warn(\n        { err: drainError },\n        'Failed to drain multipart iterator (timeout or error)',\n      );\n    }\n  }\n\n  /**\n   * Build abort details with batch mode wrapping if needed\n   */\n  private buildAbortDetails(\n    maxFiles: number,\n    fileIndex: number,\n    singleAbortReason: AbortReason,\n    triggerReason: string,\n    failureDetails: Record<string, unknown>,\n  ): AbortReason {\n    if (maxFiles > 1) {\n      this.state.abortReason = 'batch_file_failed';\n      this.state.abortDetails = {\n        ...failureDetails,\n        triggerReason,\n        totalFiles: fileIndex + 1,\n        processedFiles: this.state.processedFiles,\n      };\n      return 'batch_file_failed';\n    } else {\n      this.state.abortReason = singleAbortReason;\n      this.state.abortDetails = failureDetails;\n      return singleAbortReason;\n    }\n  }\n\n  /**\n   * Run file-specific cleanup handlers and remove them from batch-level list\n   */\n  private async runFileCleanupAndRemoveFromBatch(\n    fileCleanupHandlers: Array<\n      (\n        reason: AbortReason,\n        details?: Record<string, unknown>,\n      ) => Promise<void> | void\n    >,\n    reason: AbortReason,\n    details: Record<string, unknown> | undefined,\n  ): Promise<void> {\n    if (fileCleanupHandlers.length === 0) {\n      return;\n    }\n\n    // Run only THIS FILE's cleanup handlers (not all handlers)\n    const cleanupPromises = fileCleanupHandlers.map(async (handler) => {\n      try {\n        await handler(reason, details);\n      } catch (cleanupError) {\n        this.config.request.log.error(\n          { err: cleanupError, reason, details },\n          'File upload cleanup handler error',\n        );\n      }\n    });\n\n    await Promise.allSettled(cleanupPromises);\n\n    // Remove these handlers from batch-level list to prevent double-cleanup\n    const fileHandlersSet = new Set(fileCleanupHandlers);\n    this.state.cleanupHandlers = this.state.cleanupHandlers.filter(\n      (handler) => !fileHandlersSet.has(handler),\n    );\n  }\n\n  /**\n   * Get sanitized error message based on environment\n   */\n  private getSanitizedErrorMessage(error: unknown, fallback: string): string {\n    const isDevelopment = (\n      this.config.request as FastifyRequest & { isDevelopment?: boolean }\n    ).isDevelopment;\n    if (!isDevelopment) {\n      return fallback;\n    }\n\n    // Extract error message in development mode\n    if (error && typeof error === 'object' && 'message' in error) {\n      const message = (error as { message: unknown }).message;\n      return typeof message === 'string' ? message : fallback;\n    }\n    return fallback;\n  }\n}\n\n/**\n * Process file upload(s) with validation, streaming, and cleanup handlers.\n *\n * @param config - Upload configuration\n * @returns UploadResult discriminated union (success or error)\n *\n * @example Single file upload\n * ```typescript\n * const result = await processFileUpload({\n *   request,\n *   reply,\n *   maxSizePerFile: 5 * 1024 * 1024, // 5MB\n *   allowedMimeTypes: ['image/jpeg', 'image/png'],\n *   processor: async (stream, metadata, context) => {\n *     const uploadID = generateID();\n *\n *     context.onCleanup(async () => {\n *       await deleteFromObjectStorage(uploadID);\n *     });\n *\n *     const url = await uploadToObjectStorage(stream, uploadID);\n *     return { url, uploadID };\n *   },\n * });\n * if (!result.success) return result.errorEnvelope;\n * // result.files: [{ fileIndex: 0, filename: 'photo.jpg', data: { url: '...', uploadID: '...' } }]\n * ```\n *\n * @example Batch file upload\n * ```typescript\n * const result = await processFileUpload({\n *   request,\n *   reply,\n *   maxFiles: 5,\n *   maxSizePerFile: 10 * 1024 * 1024, // 10MB\n *   allowedMimeTypes: (mime) => {\n *     if (mime.startsWith('image/')) return { allowed: true };\n *     return { allowed: false, allowedTypes: ['image/*'] };\n *   },\n *   processor: async (stream, metadata, context) => {\n *     const uploadID = `${context.fileIndex}-${generateID()}`;\n *\n *     context.onCleanup(async () => {\n *       await deleteFromObjectStorage(uploadID);\n *     });\n *\n *     const url = await uploadToObjectStorage(stream, uploadID);\n *     return { url, uploadID, index: context.fileIndex };\n *   },\n * });\n * if (!result.success) return result.errorEnvelope;\n * // result.files: Array of processed files with data\n * ```\n */\nexport async function processFileUpload<T = unknown>(\n  config: FileUploadConfig<T>,\n): Promise<UploadResult<T>> {\n  const processor = new FileUploadProcessor(config);\n  return processor.execute();\n}\n","/**\n * MIME Type Utilities\n *\n * Helper functions for MIME type validation and pattern matching.\n */\n\n/**\n * Check if a MIME type matches a pattern (supports wildcards like 'image/*' or '*\\/*')\n *\n * @param mimetype - Actual MIME type to check (e.g., 'image/jpeg')\n * @param pattern - Pattern to match against (e.g., 'image/*', 'image/jpeg', '*\\/*')\n * @returns True if mimetype matches the pattern\n *\n * @example\n * ```typescript\n * matchesMimeTypePattern('image/jpeg', 'image/jpeg') // true - exact match\n * matchesMimeTypePattern('image/jpeg', 'image/*')    // true - wildcard match\n * matchesMimeTypePattern('image/png', 'image/*')     // true - wildcard match\n * matchesMimeTypePattern('video/mp4', 'image/*')     // false - different category\n * matchesMimeTypePattern('text/plain', '*\\/*')        // true - all types wildcard\n * ```\n */\nexport function matchesMimeTypePattern(\n  mimetype: string,\n  pattern: string,\n): boolean {\n  // Exact match\n  if (mimetype === pattern) {\n    return true;\n  }\n\n  // Check for wildcard patterns\n  if (pattern.includes('*')) {\n    const regexPattern = pattern\n      .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&') // Escape regex special chars\n      .replace(/\\*/g, '.*'); // Replace * with .*\n\n    return new RegExp(`^${regexPattern}$`).test(mimetype);\n  }\n\n  return false;\n}\n"],"mappings":"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","names":["fastify","path","fs","import_path","fastify","path","import_path","import_promises","DEFAULT_OPTIONS","zlibConstants","fs","crypto","options","path","fastify","fastify","path","fastify","websocket","path","multipart","import_dev_mode","fastify","qs","path","statusCode","import_path","import_dev_mode","path","clientBuildDir","import_react_router","import_context","import_react","import_context","import_jsx_runtime","import_react","import_context","import_jsx_runtime","import_jsx_runtime","React","import_jsx_runtime","import_jsx_runtime","import_react_router","import_jsx_runtime","import_dev_mode","qs","fastify","formbody","statusCode","import_domain_utils","import_path","result","path","errorEnvelope","errorResult"]}