{"version":3,"file":"runtime-ayz-XTe8.cjs","names":[],"sources":["../../src/cache.ts","../../src/runtime/static.ts","../../src/runtime/context.ts","../../src/runtime/security-headers.ts","../../src/runtime/handler.ts"],"sourcesContent":["import { mkdir, readFile, rename, rm, writeFile } from \"node:fs/promises\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { dirname, join } from \"node:path\";\n\nexport interface CacheEntry {\n  html: string;\n  generatedAt: number;\n  revalidate: number;\n}\n\nexport interface CacheOptions {\n  cacheDir: string;\n  defaultRevalidate?: number;\n}\n\nfunction cachePath(cacheDir: string, pathname: string): string {\n  const key = createHash(\"sha256\").update(pathname).digest(\"hex\");\n  return join(cacheDir, `${key}.html.json`);\n}\n\nexport async function getCachedHtml(\n  cacheDir: string,\n  pathname: string,\n): Promise<CacheEntry | undefined> {\n  const path = cachePath(cacheDir, pathname);\n  try {\n    const raw = await readFile(path, \"utf8\");\n    const entry = JSON.parse(raw) as CacheEntry;\n    if (Date.now() - entry.generatedAt < entry.revalidate * 1000) {\n      return entry;\n    }\n  } catch {\n    // cache miss or invalid\n  }\n  return undefined;\n}\n\nexport async function setCachedHtml(\n  cacheDir: string,\n  pathname: string,\n  html: string,\n  revalidate: number,\n): Promise<void> {\n  const path = cachePath(cacheDir, pathname);\n  await mkdir(dirname(path), { recursive: true });\n  const entry: CacheEntry = { html, generatedAt: Date.now(), revalidate };\n  const temporaryPath = `${path}.${process.pid}.${randomUUID()}.tmp`;\n  try {\n    await writeFile(temporaryPath, JSON.stringify(entry), \"utf8\");\n    await rename(temporaryPath, path);\n  } finally {\n    await rm(temporaryPath, { force: true });\n  }\n}\n\nexport async function isStale(cacheDir: string, pathname: string): Promise<boolean> {\n  const path = cachePath(cacheDir, pathname);\n  try {\n    const raw = await readFile(path, \"utf8\");\n    const entry = JSON.parse(raw) as CacheEntry;\n    return Date.now() - entry.generatedAt >= entry.revalidate * 1000;\n  } catch {\n    return true;\n  }\n}\n\nexport async function clearCache(cacheDir: string): Promise<void> {\n  try {\n    await rm(cacheDir, { recursive: true, force: true });\n  } catch {\n    // ignore\n  }\n}\n","import { realpath, stat } from \"node:fs/promises\";\nimport { extname, resolve, sep } from \"node:path\";\n\nfunction isInside(root: string, candidate: string): boolean {\n  return candidate === root || candidate.startsWith(`${root}${sep}`);\n}\n\nfunction decodePathname(pathname: string): string | null {\n  try {\n    const decoded = decodeURIComponent(pathname);\n    if (decoded.includes(\"\\0\") || decoded.includes(\"\\\\\") || /%(?:00|2e|2f|5c)/i.test(decoded)) return null;\n    if (decoded.split(\"/\").some((segment) => segment === \"..\")) return null;\n    return decoded;\n  } catch {\n    return null;\n  }\n}\n\nexport async function resolveStaticFile(root: string, pathname: string): Promise<string | null> {\n  const decoded = decodePathname(pathname);\n  if (decoded === null) return null;\n\n  const resolvedRoot = resolve(root);\n  const relativePath = decoded.replace(/^\\/+/, \"\");\n  let candidate = resolve(resolvedRoot, relativePath);\n  if (!isInside(resolvedRoot, candidate)) return null;\n\n  try {\n    const candidateStat = await stat(candidate);\n    if (candidateStat.isDirectory()) candidate = resolve(candidate, \"index.html\");\n  } catch {\n    if (decoded.endsWith(\"/\") || extname(decoded) === \"\") candidate = resolve(candidate, \"index.html\");\n  }\n\n  if (!isInside(resolvedRoot, candidate)) return null;\n\n  try {\n    const [canonicalRoot, canonicalCandidate, candidateStat] = await Promise.all([\n      realpath(resolvedRoot),\n      realpath(candidate),\n      stat(candidate),\n    ]);\n    if (!candidateStat.isFile() || !isInside(canonicalRoot, canonicalCandidate)) return null;\n    return canonicalCandidate;\n  } catch {\n    return null;\n  }\n}\n","import type { ResolvedNixConfig } from \"../config/index.js\";\nimport { randomUUID } from \"node:crypto\";\n\n// --- RequestContext: unified per-request runtime context ---\n//\n// Every runtime path (SSR server, CLI preview/dev, adapters, Vite plugin)\n// eventually funnels through a single Web handler that receives a Web Request\n// and returns a Web Response. RequestContext carries the resolved config,\n// route tables, action registry and request-scoped state so handlers do not\n// re-derive this information on every request.\n//\n// Design goals (runtime-security §4):\n//   * One type used by every runtime entry point.\n//   * No Node-specific APIs on the type — only Web standards.\n//   * Carries per-request state: params, locals, cookies, signal, requestId.\n//   * response.headers supports multiple Set-Cookie without collapsing them.\n//   * signal aborts when the host disconnects (when the platform allows it).\n//   * Middleware/loaders/actions share the same context or readonly views.\n\nexport interface RouteTable {\n  pages: import(\"../router/route-scanner.js\").PageRoute[];\n  api: import(\"../router/route-scanner.js\").ApiRoute[];\n  error404?: import(\"../router/route-scanner.js\").PageRoute;\n  error500?: import(\"../router/route-scanner.js\").PageRoute;\n}\n\n// --- CookieJar: read cookies from request, write to response ---\n\n/** Read-only access to request cookies. */\nexport interface CookieJar {\n  /** Gets a cookie value by name, or undefined if not present. */\n  get(name: string): string | undefined;\n  /** Returns all cookie name-value pairs. */\n  getAll(): Record<string, string>;\n  /** Checks if a cookie exists. */\n  has(name: string): boolean;\n}\n\n/** Write access to response cookies (Set-Cookie headers). */\nexport interface ResponseCookieJar {\n  /** Sets a Set-Cookie header. */\n  set(name: string, value: string, options?: CookieOptions): void;\n  /** Removes a cookie by setting it expired. */\n  clear(name: string, options?: CookieOptions): void;\n  /** Returns all Set-Cookie header values accumulated so far. */\n  getAll(): string[];\n}\n\nexport interface CookieOptions {\n  httpOnly?: boolean;\n  secure?: boolean;\n  sameSite?: \"strict\" | \"lax\" | \"none\";\n  maxAge?: number;\n  expires?: Date;\n  path?: string;\n  domain?: string;\n}\n\n/** Mutable response state accumulated during the request lifecycle. */\nexport interface ResponseState {\n  status?: number;\n  headers: Headers;\n  cookies: ResponseCookieJar;\n}\n\n// --- Cookie implementation ---\n\nclass RequestCookieJar implements CookieJar {\n  private cookies: Record<string, string>;\n\n  constructor(request: Request) {\n    this.cookies = parseCookies(request.headers.get(\"Cookie\") ?? \"\");\n  }\n\n  get(name: string): string | undefined {\n    return this.cookies[name];\n  }\n\n  getAll(): Record<string, string> {\n    return { ...this.cookies };\n  }\n\n  has(name: string): boolean {\n    return name in this.cookies;\n  }\n}\n\nclass MutableResponseCookieJar implements ResponseCookieJar {\n  private entries: string[] = [];\n\n  set(name: string, value: string, options: CookieOptions = {}): void {\n    this.entries.push(serializeCookie(name, value, options));\n  }\n\n  clear(name: string, options: CookieOptions = {}): void {\n    this.entries.push(serializeCookie(name, \"\", { ...options, maxAge: 0, expires: new Date(0) }));\n  }\n\n  getAll(): string[] {\n    return [...this.entries];\n  }\n}\n\nfunction parseCookies(header: string): Record<string, string> {\n  const result: Record<string, string> = {};\n  if (!header) return result;\n  for (const pair of header.split(\";\")) {\n    const idx = pair.indexOf(\"=\");\n    if (idx === -1) continue;\n    const name = pair.slice(0, idx).trim();\n    const value = pair.slice(idx + 1).trim();\n    result[name] = value;\n  }\n  return result;\n}\n\nfunction serializeCookie(name: string, value: string, options: CookieOptions): string {\n  const parts = [`${name}=${value}`];\n  if (options.httpOnly) parts.push(\"HttpOnly\");\n  if (options.secure) parts.push(\"Secure\");\n  if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);\n  if (options.maxAge !== undefined) parts.push(`Max-Age=${options.maxAge}`);\n  if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);\n  if (options.path) parts.push(`Path=${options.path}`);\n  if (options.domain) parts.push(`Domain=${options.domain}`);\n  return parts.join(\"; \");\n}\n\nexport interface RequestContextOptions {\n  request: Request;\n  config: ResolvedNixConfig;\n  routes: RouteTable;\n  actions: import(\"../action/scan.js\").ActionRegistry;\n  /** Public action names serialized into the HTML shell. */\n  publicActions: Record<string, string[]>;\n  /** Optional module loader for adapter-bundled entries. */\n  importer?: (path: string) => unknown | Promise<unknown>;\n  /** Whether the render endpoint (/__nix-js/render) is available. */\n  renderEndpoint?: boolean;\n  /** Whether to bypass the ISR cache (dev mode). */\n  noCache?: boolean;\n  /** ISR cache directory (absolute). */\n  cacheDir?: string;\n  /** Default ISR revalidate interval in seconds. */\n  defaultRevalidate?: number;\n  /** Route params (populated after route matching). */\n  params?: Record<string, string | string[] | undefined>;\n  /** Per-request locals (populated by middleware). */\n  locals?: Record<string, unknown>;\n  /** Abort signal for the request (from host disconnect). */\n  signal?: AbortSignal;\n  /** Request ID (auto-generated if not provided). */\n  requestId?: string;\n  /** Platform-specific context (e.g. Vercel, Netlify). */\n  platform?: unknown;\n  /** Matched route (populated after route matching). */\n  route?: import(\"../router/route-scanner.js\").PageRoute | import(\"../router/route-scanner.js\").ApiRoute;\n}\n\nexport class RequestContext {\n  readonly request: Request;\n  readonly url: URL;\n  readonly config: ResolvedNixConfig;\n  readonly routes: RouteTable;\n  readonly actions: import(\"../action/scan.js\").ActionRegistry;\n  readonly publicActions: Record<string, string[]>;\n  readonly importer?: (path: string) => unknown | Promise<unknown>;\n  readonly renderEndpoint: boolean;\n  readonly noCache: boolean;\n  readonly cacheDir?: string;\n  readonly defaultRevalidate?: number;\n\n  // Per-request state (runtime-security §4)\n  /** Route params derived from the matched route. */\n  params: Readonly<Record<string, string | string[] | undefined>>;\n  /** Per-request locals, populated by middleware. Not global. */\n  locals: Record<string, unknown>;\n  /** Read-only access to request cookies. */\n  readonly cookies: CookieJar;\n  /** Abort signal (from host disconnect when platform allows). */\n  readonly signal: AbortSignal;\n  /** Unique request ID for logging/correlation. */\n  readonly requestId: string;\n  /** Platform-specific context (Vercel, Netlify, etc.). */\n  readonly platform: unknown;\n  /** Matched route after route matching. */\n  route?: import(\"../router/route-scanner.js\").PageRoute | import(\"../router/route-scanner.js\").ApiRoute;\n  /** Mutable response state accumulated during the request. */\n  readonly response: ResponseState;\n\n  constructor(options: RequestContextOptions) {\n    this.request = options.request;\n    this.url = new URL(options.request.url);\n    this.config = options.config;\n    this.routes = options.routes;\n    this.actions = options.actions;\n    this.publicActions = options.publicActions;\n    this.importer = options.importer;\n    this.renderEndpoint = options.renderEndpoint ?? true;\n    this.noCache = options.noCache ?? false;\n    this.cacheDir = options.cacheDir;\n    this.defaultRevalidate = options.defaultRevalidate;\n\n    // Per-request state\n    this.params = options.params ?? {};\n    this.locals = options.locals ?? {};\n    this.cookies = new RequestCookieJar(options.request);\n    this.signal = options.signal ?? new AbortController().signal;\n    this.requestId = options.requestId ?? randomUUID();\n    this.platform = options.platform;\n    this.route = options.route;\n    this.response = {\n      status: undefined,\n      headers: new Headers(),\n      cookies: new MutableResponseCookieJar(),\n    };\n  }\n\n  /** The pathname without a query string. */\n  get pathname(): string {\n    return this.url.pathname;\n  }\n\n  /** The HTTP method, uppercased. */\n  get method(): string {\n    return (this.request.method ?? \"GET\").toUpperCase();\n  }\n\n  /** Whether the request accepts JSON. */\n  get wantsJson(): boolean {\n    return (this.request.headers.get(\"Accept\") ?? \"\").includes(\"application/json\");\n  }\n\n  /** Search params from the request URL. */\n  get searchParams(): URLSearchParams {\n    return this.url.searchParams;\n  }\n\n  /** Render config passed to renderPage/renderErrorPage. */\n  get renderConfig(): { lang?: string; clientEntry?: string; renderEndpoint?: boolean } {\n    return {\n      lang: undefined,\n      clientEntry: undefined,\n      renderEndpoint: this.renderEndpoint,\n    };\n  }\n\n  /** Applies accumulated response state (headers, cookies, status) to a Response. */\n  applyToResponse(response: Response): Response {\n    const headers = new Headers(response.headers);\n    // Merge accumulated headers\n    for (const [key, value] of this.response.headers.entries()) {\n      headers.set(key, value);\n    }\n    // Append Set-Cookie values (multiple allowed)\n    for (const cookie of this.response.cookies.getAll()) {\n      headers.append(\"Set-Cookie\", cookie);\n    }\n    const status = this.response.status ?? response.status;\n    return new Response(response.body, {\n      status,\n      statusText: response.statusText,\n      headers,\n    });\n  }\n}\n\n// --- ResponseBuilder: small helpers for consistent Web Responses ---\n\nexport function htmlResponse(body: string, status = 200, headers?: HeadersInit): Response {\n  return new Response(body, {\n    status,\n    headers: { \"Content-Type\": \"text/html; charset=utf-8\", ...headers as Record<string, string> },\n  });\n}\n\nexport function jsonResponse(data: unknown, status = 200, headers?: HeadersInit): Response {\n  return new Response(JSON.stringify(data), {\n    status,\n    headers: { \"Content-Type\": \"application/json; charset=utf-8\", ...headers as Record<string, string> },\n  });\n}\n\nexport function textResponse(body: string, status = 200, headers?: HeadersInit): Response {\n  return new Response(body, {\n    status,\n    headers: { \"Content-Type\": \"text/plain; charset=utf-8\", ...headers as Record<string, string> },\n  });\n}\n\nexport function notFound(body = \"Not Found\"): Response {\n  return textResponse(body, 404);\n}\n\nexport function methodNotAllowed(method: string): Response {\n  return textResponse(`Method not allowed: ${method}`, 405);\n}\n\nexport function serverError(body: string): Response {\n  return textResponse(body, 500);\n}\n\n// --- Content-type guessing (shared by all static-serving paths) ---\n\nexport function guessContentType(filePath: string): string {\n  switch (filePath.slice(filePath.lastIndexOf(\".\") + 1).toLowerCase()) {\n    case \"html\": return \"text/html; charset=utf-8\";\n    case \"js\": return \"application/javascript; charset=utf-8\";\n    case \"mjs\": return \"application/javascript; charset=utf-8\";\n    case \"css\": return \"text/css; charset=utf-8\";\n    case \"json\": return \"application/json; charset=utf-8\";\n    case \"svg\": return \"image/svg+xml\";\n    case \"png\": return \"image/png\";\n    case \"jpg\":\n    case \"jpeg\": return \"image/jpeg\";\n    case \"webp\": return \"image/webp\";\n    case \"avif\": return \"image/avif\";\n    case \"ico\": return \"image/x-icon\";\n    case \"woff\": return \"font/woff\";\n    case \"woff2\": return \"font/woff2\";\n    case \"wasm\": return \"application/wasm\";\n    case \"txt\": return \"text/plain; charset=utf-8\";\n    default: return \"application/octet-stream\";\n  }\n}\n\n// --- Static file serving as a Web handler (reuses resolveStaticFile) ---\n\nimport { readFile, stat } from \"node:fs/promises\";\nimport { createHash } from \"node:crypto\";\nimport { resolveStaticFile } from \"./static.js\";\n\n/**\n * Serves a static file from the root directory with full conditional and\n * range support:\n *\n * - ETag / Last-Modified with If-None-Match / If-Modified-Since → 304.\n * - `Range` with `If-Range` (ETag or date) → 206 with `Content-Range`.\n * - HEAD → same headers as GET without a body.\n * - Invalid/unsatisfiable ranges → 416 with a `Content-Range: bytes (asterisk)/size` header.\n *\n * Files with content hashes in their names (e.g. `app-abc123.js`) get\n * `Cache-Control: public, max-age=31536000, immutable`.\n *\n * @param root Static file root (absolute path).\n * @param pathname Request pathname.\n * @param request Optional request for conditional/range/HEAD handling.\n */\nexport async function serveStaticFile(\n  root: string,\n  pathname: string,\n  request?: Request,\n): Promise<Response | null> {\n  const filePath = await resolveStaticFile(root, pathname);\n  if (!filePath) return null;\n  try {\n    const [data, stats] = await Promise.all([\n      readFile(filePath),\n      stat(filePath),\n    ]);\n\n    const contentType = guessContentType(filePath);\n    const etag = `\"${createHash(\"sha1\").update(data).digest(\"hex\").slice(0, 16)}\"`;\n    const lastModified = stats.mtime.toUTCString();\n    const isHead = request?.method === \"HEAD\";\n    const size = data.byteLength;\n\n    const baseHeaders: Record<string, string> = {\n      \"Content-Type\": contentType,\n      \"Content-Length\": String(size),\n      ETag: etag,\n      \"Last-Modified\": lastModified,\n      \"Accept-Ranges\": \"bytes\",\n    };\n\n    // Determine Cache-Control: hashed assets get immutable, others get a\n    // short revalidation window.\n    const baseName = filePath.split(\"/\").pop() ?? \"\";\n    const isHashed = /[a-f0-9]{8,}\\.(js|css|woff2?|wasm|png|jpg|jpeg|webp|avif|svg)$/i.test(baseName);\n    baseHeaders[\"Cache-Control\"] = isHashed\n      ? \"public, max-age=31536000, immutable\"\n      : \"public, max-age=0, must-revalidate\";\n\n    // Conditional requests (If-None-Match takes precedence).\n    const ifNoneMatch = request?.headers.get(\"If-None-Match\");\n    if (ifNoneMatch && etagListMatches(ifNoneMatch, etag)) {\n      return new Response(null, { status: 304, headers: baseHeaders });\n    }\n    const ifModifiedSince = request?.headers.get(\"If-Modified-Since\");\n    if (ifModifiedSince) {\n      const since = Date.parse(ifModifiedSince);\n      if (!isNaN(since) && Math.floor(stats.mtime.getTime() / 1000) <= Math.floor(since / 1000)) {\n        return new Response(null, { status: 304, headers: baseHeaders });\n      }\n    }\n\n    // Range support with If-Range validation.\n    const rangeHeader = request?.headers.get(\"Range\");\n    const ifRange = request?.headers.get(\"If-Range\");\n    if (rangeHeader && (!ifRange || ifRangeMatches(ifRange, etag, stats.mtime))) {\n      const range = parseRange(rangeHeader, size);\n      if (range === null) {\n        return new Response(null, {\n          status: 416,\n          headers: { ...baseHeaders, \"Content-Range\": `bytes */${size}` },\n        });\n      }\n      if (range) {\n        const [start, end] = range;\n        const chunk = data.subarray(start, end + 1);\n        const headers: Record<string, string> = {\n          ...baseHeaders,\n          \"Content-Length\": String(chunk.byteLength),\n          \"Content-Range\": `bytes ${start}-${end}/${size}`,\n        };\n        if (isHead) return new Response(null, { status: 206, headers });\n        return new Response(chunk, { status: 206, headers });\n      }\n    }\n\n    if (isHead) return new Response(null, { status: 200, headers: baseHeaders });\n    return new Response(data, { status: 200, headers: baseHeaders });\n  } catch {\n    return null;\n  }\n}\n\nfunction etagListMatches(ifNoneMatch: string, etag: string): boolean {\n  return ifNoneMatch\n    .split(\",\")\n    .map((value) => value.trim())\n    .some((value) => value === \"*\" || value === etag);\n}\n\nfunction ifRangeMatches(ifRange: string, etag: string, mtime: Date): boolean {\n  if (ifRange.startsWith('\"') || ifRange.startsWith(\"W/\")) return ifRange === etag;\n  const date = Date.parse(ifRange);\n  return !isNaN(date) && Math.floor(mtime.getTime() / 1000) <= Math.floor(date / 1000);\n}\n\n/**\n * Parses a single `Range: bytes=...` header. Returns:\n * - `[start, end]` for a satisfiable range.\n * - `null` when the header is malformed or unsatisfiable (→ 416).\n * - `undefined` when the header is valid but the whole resource is requested\n *   (e.g. `bytes=0-` for an empty file) — serve the full body.\n */\nfunction parseRange(rangeHeader: string, size: number): [number, number] | null | undefined {\n  const match = /^bytes=(\\d*)-(\\d*)$/.exec(rangeHeader.trim());\n  if (!match) return null;\n  const startText = match[1];\n  const endText = match[2];\n\n  if (startText === \"\" && endText === \"\") return null;\n  if (startText === \"\") {\n    // Suffix range: last N bytes.\n    const suffix = Number(endText);\n    if (!Number.isSafeInteger(suffix) || suffix <= 0) return null;\n    const start = Math.max(0, size - suffix);\n    if (size === 0) return undefined;\n    return [start, size - 1];\n  }\n\n  const start = Number(startText);\n  if (!Number.isSafeInteger(start) || start < 0 || start >= size) return null;\n  const end = endText === \"\" ? size - 1 : Number(endText);\n  if (!Number.isSafeInteger(end) || end < start) return null;\n  return [start, Math.min(end, size - 1)];\n}\n","// --- Security response headers (runtime-security §14) ---\n//\n// Applies configurable security headers to responses. Defaults are safe and\n// compatible: X-Content-Type-Options, Referrer-Policy, frame-ancestors.\n// HSTS is only applied under HTTPS or when explicitly configured.\n// CSP supports a \"nonce\" placeholder replaced per-request.\n// User-set headers on the response are never overwritten without explicit\n// merge rules.\n\nimport type { SecurityHeadersConfig } from \"../config/index.js\";\n\n/** Default security headers applied when `security.headers` is not `false`. */\nexport const DEFAULT_SECURITY_HEADERS: Required<\n  Omit<SecurityHeadersConfig, \"contentSecurityPolicy\" | \"hsts\" | \"permissionsPolicy\">\n> = {\n  noSniff: true,\n  referrerPolicy: \"strict-origin-when-cross-origin\",\n  frameAncestors: \"SAMEORIGIN\",\n};\n\n/**\n * Builds the security headers map from the resolved config.\n * Returns an empty map if headers are disabled.\n */\nexport function buildSecurityHeaders(\n  config: SecurityHeadersConfig | false,\n  isHttps: boolean,\n  nonce?: string,\n): Record<string, string> {\n  if (config === false) return {};\n\n  const headers: Record<string, string> = {};\n  const merged = { ...DEFAULT_SECURITY_HEADERS, ...config };\n\n  if (merged.noSniff) {\n    headers[\"X-Content-Type-Options\"] = \"nosniff\";\n  }\n\n  if (merged.referrerPolicy) {\n    headers[\"Referrer-Policy\"] = merged.referrerPolicy;\n  }\n\n  // Frame policy: prefer CSP frame-ancestors if CSP is set, otherwise\n  // X-Frame-Options for broader compatibility.\n  if (merged.contentSecurityPolicy) {\n    let csp = merged.contentSecurityPolicy;\n    if (nonce) {\n      csp = csp.replace(/\\bnonce\\b/g, `'nonce-${nonce}'`);\n    }\n    headers[\"Content-Security-Policy\"] = csp;\n  } else if (merged.frameAncestors) {\n    // Without CSP, use X-Frame-Options for frame protection.\n    const fa = merged.frameAncestors;\n    if (fa === \"NONE\") {\n      headers[\"X-Frame-Options\"] = \"DENY\";\n    } else if (fa === \"SAMEORIGIN\") {\n      headers[\"X-Frame-Options\"] = \"SAMEORIGIN\";\n    } else {\n      headers[\"X-Frame-Options\"] = fa;\n    }\n  }\n\n  // HSTS: only under HTTPS or when explicitly set as a string.\n  if (merged.hsts === true && isHttps) {\n    headers[\"Strict-Transport-Security\"] = \"max-age=15552000; includeSubDomains\";\n  } else if (typeof merged.hsts === \"string\") {\n    headers[\"Strict-Transport-Security\"] = merged.hsts;\n  }\n\n  if (merged.permissionsPolicy) {\n    headers[\"Permissions-Policy\"] = merged.permissionsPolicy;\n  }\n\n  return headers;\n}\n\n/**\n * Applies security headers to an existing Response, preserving any\n * user-set headers unless overridden by security config.\n */\nexport function applySecurityHeaders(\n  response: Response,\n  headers: Record<string, string>,\n): Response {\n  if (Object.keys(headers).length === 0) return response;\n\n  const newHeaders = new Headers(response.headers);\n  for (const [key, value] of Object.entries(headers)) {\n    // Don't overwrite a header the response already set explicitly.\n    if (!newHeaders.has(key)) {\n      newHeaders.set(key, value);\n    }\n  }\n\n  return new Response(response.body, {\n    status: response.status,\n    statusText: response.statusText,\n    headers: newHeaders,\n  });\n}\n","import { matchRoute, matchApiRoute } from \"../ssr/match.js\";\nimport { handleActionRequest, type ActionResolver } from \"../action/server.js\";\nimport { renderPage, renderErrorPage } from \"../ssr/render.js\";\nimport { renderPageBody, RouteNotFoundError } from \"../ssr/stream.js\";\nimport { actionNames } from \"../action/scan.js\";\nimport { serveStaticFile, htmlResponse, jsonResponse, notFound, methodNotAllowed } from \"./context.js\";\nimport { publicErrorResponse } from \"../errors.js\";\nimport { getCachedHtml, setCachedHtml } from \"../cache.js\";\nimport { shouldCachePublic, type CachePolicy } from \"../cache/policy.js\";\nimport { buildSecurityHeaders, applySecurityHeaders } from \"./security-headers.js\";\nimport type { SecurityHeadersConfig } from \"../config/index.js\";\n\n// --- Unified Web handler ---\n//\n// A single function that turns a Web Request into a Web Response. Every\n// runtime entry point (Node CLI, Bun adapter, Vercel, Netlify, Vite dev)\n// eventually calls this handler so behavior is identical across platforms.\n//\n// Responsibilities (in order):\n//   1. Server actions endpoint (/__nix-js/actions).\n//   2. SPA render endpoint (/__nix-js/render).\n//   3. API routes.\n//   4. Static files from the output directory.\n//   5. Dynamic SSR rendering for unmatched paths.\n//   6. 404 / 500 error pages.\n//\n// The handler is pure: it does not import Node HTTP types and can be used in\n// Bun, Deno, Cloudflare Workers, Vercel Edge, etc.\n\nexport interface WebHandlerOptions {\n  /** Static file root (absolute path). Usually the build output directory. */\n  staticRoot: string;\n  /** Whether to bypass the ISR cache (dev mode). */\n  noCache?: boolean;\n  /** ISR cache directory (absolute). */\n  cacheDir?: string;\n  /** Default ISR revalidate interval in seconds. */\n  defaultRevalidate?: number;\n  /** Optional module loader for adapter-bundled entries. */\n  importer?: (path: string) => Promise<unknown>;\n  /** HTML lang attribute. */\n  lang?: string;\n  /** Client entry path. */\n  clientEntry?: string;\n  /** Whether the render endpoint exists. */\n  renderEndpoint?: boolean;\n  /** Security headers config (runtime-security §14). `false` disables. */\n  securityHeaders?: SecurityHeadersConfig | false;\n}\n\nexport interface WebHandlerRouteTable {\n  pages: import(\"../router/route-scanner.js\").PageRoute[];\n  api: import(\"../router/route-scanner.js\").ApiRoute[];\n  error404?: import(\"../router/route-scanner.js\").PageRoute;\n  error500?: import(\"../router/route-scanner.js\").PageRoute;\n}\n\nexport interface WebHandlerActionRegistry {\n  [pagePath: string]: Record<string, string>;\n}\n\nexport interface CreateWebHandlerResult {\n  (request: Request): Promise<Response>;\n}\n\n/**\n * Create a unified Web handler from scanned routes, actions and options.\n *\n * The returned function is the single entry point for all runtimes.\n */\nexport function createWebHandler(\n  routes: WebHandlerRouteTable,\n  actions: WebHandlerActionRegistry,\n  options: WebHandlerOptions,\n): CreateWebHandlerResult {\n  const publicActions = actionNames(actions);\n  const lang = options.lang ?? \"es\";\n  const clientEntry = options.clientEntry;\n  const renderEndpoint = options.renderEndpoint ?? true;\n  const noCache = options.noCache ?? false;\n  const cacheDir = options.cacheDir;\n  const defaultRevalidate = options.defaultRevalidate;\n\n  const renderConfig = { lang, clientEntry, renderEndpoint };\n  const securityHeadersConfig = options.securityHeaders ?? {};\n\n  function createActionResolver(): ActionResolver {\n    return async (name: string, page?: string) => {\n      const pageKey = page\n        ? routes.pages.some((route) => route.path === page)\n          ? page\n          : (matchRoute(page, routes.pages)?.route.path ?? page)\n        : undefined;\n      const pageActions = pageKey ? actions[pageKey] : Object.values(actions).find((p) => p[name]) ?? undefined;\n      const actionPath = pageActions ? pageActions[name] : undefined;\n      if (!actionPath) return undefined;\n      if (options.importer) {\n        const mod = (await options.importer(actionPath)) as Record<string, unknown>;\n        const action = mod[name];\n        if (typeof action === \"function\") return action as (...args: unknown[]) => unknown;\n        return undefined;\n      }\n      const mod = (await import(actionPath)) as Record<string, unknown>;\n      const action = mod[name];\n      if (typeof action === \"function\") return action as (...args: unknown[]) => unknown;\n      return undefined;\n    };\n  }\n\n  const actionResolver = createActionResolver();\n\n  async function handleActions(request: Request): Promise<Response> {\n    try {\n      return await handleActionRequest(request, actionResolver);\n    } catch (err) {\n      console.error(\"[nix-js-kit] action error:\", err);\n      return publicErrorResponse(err, { includeDetail: noCache });\n    }\n  }\n\n  async function handleRenderEndpoint(request: Request, url: URL): Promise<Response> {\n    const page = url.searchParams.get(\"page\") ?? \"/\";\n    const search = url.searchParams.get(\"search\") ?? \"\";\n    const wantsJson = (request.headers.get(\"Accept\") ?? \"\").includes(\"application/json\");\n    try {\n      const { body, title } = await renderPageBody({\n        routes,\n        pathname: page,\n        searchParams: new URLSearchParams(search),\n        config: renderConfig,\n        actions: publicActions,\n        request,\n        importer: options.importer,\n      });\n      if (wantsJson) return jsonResponse({ title, body });\n      return htmlResponse(body);\n    } catch (err) {\n      if (err instanceof RouteNotFoundError) return notFound(\"Not Found\");\n      // A thrown Response from a loader is a first-class response (A-22).\n      if (err instanceof Response) return err;\n      console.error(\"[nix-js-kit] render endpoint error:\", err);\n      return publicErrorResponse(err, { includeDetail: noCache });\n    }\n  }\n\n  async function handleApiRoute(\n    request: Request,\n    pathname: string,\n  ): Promise<Response | null> {\n    const apiMatch = matchApiRoute(pathname, routes.api);\n    if (!apiMatch) return null;\n    try {\n      let mod: Record<string, unknown>;\n      if (options.importer) {\n        mod = (await options.importer(apiMatch.route.routePath as unknown as string)) as Record<string, unknown>;\n      } else {\n        mod = (await import(apiMatch.route.routePath)) as Record<string, unknown>;\n      }\n      const handler = mod[request.method ?? \"GET\"];\n      if (typeof handler !== \"function\") return methodNotAllowed(request.method ?? \"GET\");\n      // Pass params and a writable locals object to the API handler\n      // (runtime-security §4: params derived from the effective route).\n      const ctx = { params: apiMatch.params, locals: {} as Record<string, unknown> };\n      const response = (await (handler as (req: Request, ctx?: { params: Record<string, string | string[]>; locals: Record<string, unknown> }) => unknown)(request, ctx)) as Response;\n      return response;\n    } catch (err) {\n      console.error(\"[nix-js-kit] API route error:\", err);\n      return publicErrorResponse(err, { includeDetail: noCache });\n    }\n  }\n\n  async function handleStatic(pathname: string, request: Request): Promise<Response | null> {\n    const response = await serveStaticFile(options.staticRoot, pathname, request);\n    if (response && noCache) {\n      const ct = response.headers.get(\"Content-Type\") ?? \"\";\n      if (ct.includes(\"text/html\")) {\n        // Dev mode: strip the render-endpoint marker so the client router uses\n        // the live /__nix-js/render endpoint for fast SPA navigation.\n        const stripped = (await response.text())\n          .replace('<meta name=\"nix-js:render-endpoint\" content=\"off\" />', \"\");\n        return new Response(stripped, {\n          status: response.status,\n          headers: { \"Content-Type\": ct, \"Cache-Control\": \"no-store, must-revalidate\" },\n        });\n      }\n      return new Response(response.body, {\n        status: response.status,\n        headers: { ...Object.fromEntries(response.headers.entries()), \"Cache-Control\": \"no-store, must-revalidate\" },\n      });\n    }\n    if (response && renderEndpoint) {\n      const ct = response.headers.get(\"Content-Type\") ?? \"\";\n      if (ct.includes(\"text/html\")) {\n        const headers = Object.fromEntries(response.headers.entries());\n        delete headers[\"content-length\"];\n        const body = await response.text();\n        if (body.includes('nix-js:render-endpoint\" content=\"off\"')) {\n          // The SSG build baked `render-endpoint content=\"off\"` so static\n          // deployments never probe the endpoint. This server exposes\n          // /__nix-js/render, so advertise it: SPA navigations fetch live\n          // server-rendered content instead of the stale static file.\n          const rewritten = body.replace(\n            '<meta name=\"nix-js:render-endpoint\" content=\"off\" />',\n            '<meta name=\"nix-js:render-endpoint\" content=\"on\" />',\n          );\n          return new Response(rewritten, { status: response.status, headers });\n        }\n        return new Response(body, { status: response.status, headers });\n      }\n    }\n    return response;\n  }\n\n  async function handleDynamicRender(request: Request, pathname: string): Promise<Response> {\n    const match = matchRoute(pathname, routes.pages);\n    if (!match) {\n      const errorResult = await renderErrorPage({\n        routes,\n        status: 404,\n        config: renderConfig,\n        actions: publicActions,\n        importer: options.importer,\n      });\n      if (errorResult) return htmlResponse(errorResult.html, errorResult.status);\n      return notFound(`Not found: ${pathname}`);\n    }\n\n    // ISR cache check (only when caching is enabled and the request is\n    // cacheable — no cookies, no authorization header).\n    const cacheable = !noCache && cacheDir && isCacheable(request);\n    if (cacheable && cacheDir) {\n      const cached = await getCachedHtml(cacheDir, pathname);\n      if (cached) return htmlResponse(cached.html);\n    }\n\n    try {\n      const result = await renderPage({\n        route: match.route,\n        params: match.params,\n        searchParams: new URLSearchParams(request.url.split(\"?\")[1] ?? \"\"),\n        config: renderConfig,\n        actions: publicActions,\n        request,\n        importer: options.importer,\n      });\n\n      // If a loader threw a Response (redirect, 404, etc.), return it\n      // as a first-class response (A-22).\n      if (result.response) {\n        return result.response;\n      }\n\n      if (cacheable && cacheDir && isResultCacheable(result, request)) {\n        const revalidateSeconds = result.revalidate ?? defaultRevalidate ?? 0;\n        if (revalidateSeconds > 0) {\n          await setCachedHtml(cacheDir, pathname, result.html, revalidateSeconds);\n        }\n      }\n\n      return htmlResponse(result.html);\n    } catch (err) {\n      // A thrown Response from a loader is a first-class response (A-22).\n      if (err instanceof Response) return err;\n      console.error(\"[nix-js-kit] SSR render error:\", err);\n      const errorResult = await renderErrorPage({\n        routes,\n        status: 500,\n        error: err,\n        config: renderConfig,\n        actions: publicActions,\n        importer: options.importer,\n      }).catch(() => undefined);\n      if (errorResult) return htmlResponse(errorResult.html, errorResult.status);\n      return publicErrorResponse(err, { includeDetail: noCache });\n    }\n  }\n\n  return async function handler(request: Request): Promise<Response> {\n    const url = new URL(request.url);\n    const pathname = url.pathname;\n    const isHttps = url.protocol === \"https:\";\n\n    // Determine security headers (rebuild if nonce is needed).\n    // HSTS is only applied under HTTPS; other headers apply always.\n    const secHeaders = securityHeadersConfig === false\n      ? {}\n      : buildSecurityHeaders(securityHeadersConfig, isHttps);\n\n    // 1. Server actions endpoint.\n    if (pathname === \"/__nix-js/actions\" && request.method === \"POST\") {\n      const response = await handleActions(request);\n      return applySecurityHeaders(response, secHeaders);\n    }\n\n    // 2. SPA render endpoint.\n    if (pathname === \"/__nix-js/render\" && renderEndpoint) {\n      const response = await handleRenderEndpoint(request, url);\n      return applySecurityHeaders(response, secHeaders);\n    }\n\n    // 3. API routes.\n    const apiResponse = await handleApiRoute(request, pathname);\n    if (apiResponse) return applySecurityHeaders(apiResponse, secHeaders);\n\n    // 4. Static files.\n    const staticResponse = await handleStatic(pathname, request);\n    if (staticResponse) return applySecurityHeaders(staticResponse, secHeaders);\n\n    // 5. Dynamic SSR rendering.\n    const dynamicResponse = await handleDynamicRender(request, pathname);\n    return applySecurityHeaders(dynamicResponse, secHeaders);\n  };\n}\n\nfunction isCacheable(request: Request): boolean {\n  if (request.method !== \"GET\" && request.method !== \"HEAD\") return false;\n  if (request.headers.get(\"Cookie\")) return false;\n  if (request.headers.get(\"Authorization\")) return false;\n  return true;\n}\n\n/**\n * Checks whether a rendered page result is cacheable as public ISR.\n * Per runtime-security §9.1: uses the route's cache policy and checks\n * for personalized content markers.\n */\nfunction isResultCacheable(\n  result: { revalidate?: number; html: string; cachePolicy?: CachePolicy },\n  request: Request,\n): boolean {\n  // If the HTML contains action error markers, it's personalized.\n  if (result.html.includes(\"__nix_js_action_error\")) return false;\n  // Use the route's cache policy if declared.\n  if (result.cachePolicy) {\n    return shouldCachePublic(result.cachePolicy, request);\n  }\n  // Fallback: cacheable only if revalidate > 0 and request is clean.\n  if (!result.revalidate || result.revalidate <= 0) return false;\n  if (request.headers.get(\"Cookie\")) return false;\n  if (request.headers.get(\"Authorization\")) return false;\n  return true;\n}\n"],"mappings":"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"}