@timber-js/app 0.1.44 → 0.1.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/compress-module.d.ts.map +1 -1
- package/dist/adapters/nitro.js +8 -30
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/index.js +13 -38
- package/dist/index.js.map +1 -1
- package/dist/server/compress.d.ts +11 -6
- package/dist/server/compress.d.ts.map +1 -1
- package/dist/server/index.js +20 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/logger.d.ts +4 -0
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/adapters/compress-module.ts +11 -31
- package/src/server/compress.ts +18 -62
- package/src/server/logger.ts +4 -0
- package/src/server/pipeline.ts +29 -5
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/server/primitives.ts","../../src/server/canonicalize.ts","../../src/server/proxy.ts","../../src/server/middleware-runner.ts","../../src/server/server-timing.ts","../../src/server/error-formatter.ts","../../src/server/logger.ts","../../src/server/instrumentation.ts","../../src/server/pipeline-metadata.ts","../../src/server/pipeline-interception.ts","../../src/server/pipeline.ts","../../src/server/build-manifest.ts","../../src/server/early-hints.ts","../../src/server/early-hints-sender.ts","../../src/server/tree-builder.ts","../../src/server/access-gate.tsx","../../src/server/status-code-resolver.ts","../../src/server/flush.ts","../../src/server/csrf.ts","../../src/server/body-limits.ts","../../src/server/metadata-social.ts","../../src/server/metadata-platform.ts","../../src/server/metadata-render.ts","../../src/server/metadata.ts","../../src/server/form-data.ts","../../src/server/action-client.ts","../../src/server/form-flash.ts","../../src/server/actions.ts","../../src/server/route-handler.ts"],"sourcesContent":["// Server-side primitives: deny, redirect, redirectExternal, RenderError, waitUntil, SsrStreamError\n//\n// These are the core runtime signals that components, middleware, and access gates\n// use to control request flow. See design/10-error-handling.md.\n\nimport type { JsonSerializable } from './types.js';\n\n// ─── Dev-mode validation ────────────────────────────────────────────────────\n\n/**\n * Check if a value is JSON-serializable without data loss.\n * Returns a description of the first non-serializable value found, or null if OK.\n *\n * @internal Exported for testing only.\n */\nexport function findNonSerializable(value: unknown, path = 'data'): string | null {\n if (value === null || value === undefined) return null;\n\n switch (typeof value) {\n case 'string':\n case 'number':\n case 'boolean':\n return null;\n case 'bigint':\n return `${path} contains a BigInt — BigInt throws in JSON.stringify`;\n case 'function':\n return `${path} is a function — functions are not JSON-serializable`;\n case 'symbol':\n return `${path} is a symbol — symbols are not JSON-serializable`;\n case 'object':\n break;\n default:\n return `${path} has unsupported type \"${typeof value}\"`;\n }\n\n if (value instanceof Date) {\n return `${path} is a Date — Dates silently coerce to strings in JSON.stringify`;\n }\n if (value instanceof Map) {\n return `${path} is a Map — Maps serialize as {} in JSON.stringify (data loss)`;\n }\n if (value instanceof Set) {\n return `${path} is a Set — Sets serialize as {} in JSON.stringify (data loss)`;\n }\n if (value instanceof RegExp) {\n return `${path} is a RegExp — RegExps serialize as {} in JSON.stringify`;\n }\n if (value instanceof Error) {\n return `${path} is an Error — Errors serialize as {} in JSON.stringify`;\n }\n\n if (Array.isArray(value)) {\n for (let i = 0; i < value.length; i++) {\n const result = findNonSerializable(value[i], `${path}[${i}]`);\n if (result) return result;\n }\n return null;\n }\n\n // Plain object — only Object.prototype is safe. Null-prototype objects\n // (Object.create(null)) survive JSON.stringify but React Flight rejects\n // them with \"Classes or null prototypes are not supported\", so the\n // pre-flush deny path (renderDenyPage → renderToReadableStream) would throw.\n const proto = Object.getPrototypeOf(value);\n if (proto === null) {\n return `${path} is a null-prototype object — React Flight rejects null prototypes`;\n }\n if (proto !== Object.prototype) {\n const name = (value as object).constructor?.name ?? 'unknown';\n return `${path} is a ${name} instance — class instances may lose data in JSON.stringify`;\n }\n\n for (const key of Object.keys(value as Record<string, unknown>)) {\n const result = findNonSerializable(\n (value as Record<string, unknown>)[key],\n `${path}.${key}`\n );\n if (result) return result;\n }\n return null;\n}\n\n/**\n * Emit a dev-mode warning if data is not JSON-serializable.\n * No-op in production.\n */\nfunction warnIfNotSerializable(data: unknown, callerName: string): void {\n if (process.env.NODE_ENV === 'production') return;\n if (data === undefined) return;\n\n const issue = findNonSerializable(data);\n if (issue) {\n console.warn(\n `[timber] ${callerName}: ${issue}. ` +\n 'Data passed to deny() or RenderError must be JSON-serializable because ' +\n 'the post-flush path uses JSON.stringify, not React Flight.'\n );\n }\n}\n\n// ─── DenySignal ─────────────────────────────────────────────────────────────\n\n/**\n * Render-phase signal thrown by `deny()`. Caught by the framework to produce\n * the correct HTTP status code (segment context) or graceful degradation (slot context).\n */\nexport class DenySignal extends Error {\n readonly status: number;\n readonly data: JsonSerializable | undefined;\n\n constructor(status: number, data?: JsonSerializable) {\n super(`Access denied with status ${status}`);\n this.name = 'DenySignal';\n this.status = status;\n this.data = data;\n }\n\n /**\n * Extract the file that called deny() from the stack trace.\n * Returns a short path (e.g. \"app/auth/access.ts\") or undefined if\n * the stack can't be parsed. Dev-only — used for dev log output.\n */\n get sourceFile(): string | undefined {\n if (!this.stack) return undefined;\n const frames = this.stack.split('\\n');\n // Skip the Error line and the deny() frame — the caller is the 3rd line.\n // Stack format: \" at FnName (file:line:col)\" or \" at file:line:col\"\n for (let i = 2; i < frames.length; i++) {\n const frame = frames[i];\n if (!frame) continue;\n // Skip framework internals\n if (frame.includes('primitives.ts') || frame.includes('node_modules')) continue;\n // Extract file path from the frame\n const match =\n frame.match(/\\(([^)]+?)(?::\\d+:\\d+)\\)/) ?? frame.match(/at\\s+([^\\s]+?)(?::\\d+:\\d+)/);\n if (match?.[1]) {\n // Shorten to app-relative path\n const full = match[1];\n const appIdx = full.indexOf('/app/');\n return appIdx >= 0 ? full.slice(appIdx + 1) : full;\n }\n }\n return undefined;\n }\n}\n\n/**\n * Universal denial primitive. Throws a `DenySignal` that the framework catches.\n *\n * - In segment context (outside Suspense): produces HTTP status code\n * - In slot context: graceful degradation → denied.tsx → default.tsx → null\n * - Inside Suspense (hold window): promoted to pre-flush behavior\n * - Inside Suspense (after flush): error boundary + noindex meta\n *\n * @param status - Any 4xx HTTP status code. Defaults to 403.\n * @param data - Optional JSON-serializable data passed as `dangerouslyPassData` prop to status-code files.\n */\nexport function deny(status: number = 403, data?: JsonSerializable): never {\n if (status < 400 || status > 499) {\n throw new Error(\n `deny() requires a 4xx status code, got ${status}. ` +\n 'For 5xx errors, throw a RenderError instead.'\n );\n }\n warnIfNotSerializable(data, 'deny()');\n throw new DenySignal(status, data);\n}\n\n/**\n * Convenience alias for `deny(404)`.\n *\n * Provided for Next.js API compatibility — libraries and user code that\n * call `notFound()` from `next/navigation` get the same behavior as\n * `deny(404)` in timber.\n */\nexport function notFound(): never {\n throw new DenySignal(404);\n}\n\n/**\n * Next.js redirect type discriminator.\n *\n * Provided for API compatibility with libraries that import `RedirectType`\n * from `next/navigation`. In timber, `redirect()` always uses `replace`\n * semantics (no history entry for the redirect itself).\n */\nexport const RedirectType = {\n push: 'push',\n replace: 'replace',\n} as const;\n\n// ─── RedirectSignal ─────────────────────────────────────────────────────────\n\n/**\n * Render-phase signal thrown by `redirect()` and `redirectExternal()`.\n * Caught by the framework to produce a 3xx response or client-side navigation.\n */\nexport class RedirectSignal extends Error {\n readonly location: string;\n readonly status: number;\n\n constructor(location: string, status: number) {\n super(`Redirect to ${location}`);\n this.name = 'RedirectSignal';\n this.location = location;\n this.status = status;\n }\n}\n\n/** Pattern matching absolute URLs: http(s):// or protocol-relative // */\nconst ABSOLUTE_URL_RE = /^(?:[a-zA-Z][a-zA-Z\\d+\\-.]*:|\\/\\/)/;\n\n/**\n * Redirect to a relative path. Rejects absolute and protocol-relative URLs.\n * Use `redirectExternal()` for external redirects with an allow-list.\n *\n * @param path - Relative path (e.g. '/login', 'settings', '/login?returnTo=/dash')\n * @param status - HTTP redirect status code (3xx). Defaults to 302.\n */\nexport function redirect(path: string, status: number = 302): never {\n if (status < 300 || status > 399) {\n throw new Error(`redirect() requires a 3xx status code, got ${status}.`);\n }\n if (ABSOLUTE_URL_RE.test(path)) {\n throw new Error(\n `redirect() only accepts relative URLs. Got absolute URL: \"${path}\". ` +\n 'Use redirectExternal(url, allowList) for external redirects.'\n );\n }\n throw new RedirectSignal(path, status);\n}\n\n/**\n * Permanent redirect to a relative path. Shorthand for `redirect(path, 308)`.\n *\n * Uses 308 (Permanent Redirect) which preserves the HTTP method — the browser\n * will replay POST requests to the new location. This matches Next.js behavior.\n *\n * @param path - Relative path (e.g. '/new-page', '/dashboard')\n */\nexport function permanentRedirect(path: string): never {\n redirect(path, 308);\n}\n\n/**\n * Redirect to an external URL. The hostname must be in the provided allow-list.\n *\n * @param url - Absolute URL to redirect to.\n * @param allowList - Array of allowed hostnames (e.g. ['example.com', 'auth.example.com']).\n * @param status - HTTP redirect status code (3xx). Defaults to 302.\n */\nexport function redirectExternal(url: string, allowList: string[], status: number = 302): never {\n if (status < 300 || status > 399) {\n throw new Error(`redirectExternal() requires a 3xx status code, got ${status}.`);\n }\n\n let hostname: string;\n try {\n hostname = new URL(url).hostname;\n } catch {\n throw new Error(`redirectExternal() received an invalid URL: \"${url}\"`);\n }\n\n if (!allowList.includes(hostname)) {\n throw new Error(\n `redirectExternal() target \"${hostname}\" is not in the allow-list. ` +\n `Allowed: [${allowList.join(', ')}]`\n );\n }\n\n throw new RedirectSignal(url, status);\n}\n\n// ─── RenderError ────────────────────────────────────────────────────────────\n\n/**\n * Typed digest that crosses the RSC → client boundary.\n * The `code` identifies the error class; `data` carries JSON-serializable context.\n */\nexport interface RenderErrorDigest<\n TCode extends string = string,\n TData extends JsonSerializable = JsonSerializable,\n> {\n code: TCode;\n data: TData;\n}\n\n/**\n * Typed throw for render-phase errors that carry structured context to error boundaries.\n *\n * The `digest` (code + data) is serialized into the RSC stream separately from the\n * Error instance — only the digest crosses the RSC → client boundary.\n *\n * @example\n * ```ts\n * throw new RenderError('PRODUCT_NOT_FOUND', {\n * title: 'Product not found',\n * resourceId: params.id,\n * })\n * ```\n */\nexport class RenderError<\n TCode extends string = string,\n TData extends JsonSerializable = JsonSerializable,\n> extends Error {\n readonly code: TCode;\n readonly digest: RenderErrorDigest<TCode, TData>;\n readonly status: number;\n\n constructor(code: TCode, data: TData, options?: { status?: number }) {\n super(`RenderError: ${code}`);\n this.name = 'RenderError';\n this.code = code;\n this.digest = { code, data };\n\n warnIfNotSerializable(data, 'RenderError');\n\n const status = options?.status ?? 500;\n if (status < 400 || status > 599) {\n throw new Error(`RenderError status must be 4xx or 5xx, got ${status}.`);\n }\n this.status = status;\n }\n}\n\n// ─── waitUntil ──────────────────────────────────────────────────────────────\n\n/** Minimal interface for adapters that support background work. */\nexport interface WaitUntilAdapter {\n waitUntil?(promise: Promise<unknown>): void;\n}\n\n// Intentional per-app singleton — warn-once flag that persists for the\n// lifetime of the process/isolate. Not per-request; do not migrate to ALS.\nlet _waitUntilWarned = false;\n\n/**\n * Register a promise to be kept alive after the response is sent.\n * Maps to `ctx.waitUntil()` on Cloudflare Workers and similar platforms.\n *\n * If the adapter does not support `waitUntil`, a warning is logged once\n * and the promise is left to resolve (or reject) without being tracked.\n *\n * @param promise - The background work to keep alive.\n * @param adapter - The platform adapter (injected by the framework at runtime).\n */\nexport function waitUntil(promise: Promise<unknown>, adapter: WaitUntilAdapter): void {\n if (typeof adapter.waitUntil === 'function') {\n adapter.waitUntil(promise);\n return;\n }\n\n if (!_waitUntilWarned) {\n _waitUntilWarned = true;\n console.warn(\n '[timber] waitUntil() is not supported by the current adapter. ' +\n 'Background work will not be tracked. This warning is shown once.'\n );\n }\n}\n\n/**\n * Reset the waitUntil warning state. Exported for testing only.\n * @internal\n */\nexport function _resetWaitUntilWarning(): void {\n _waitUntilWarned = false;\n}\n\n// ─── SsrStreamError ─────────────────────────────────────────────────────────\n\n/**\n * Error thrown when SSR's renderToReadableStream fails due to an error\n * in the decoded RSC stream (e.g., uncontained slot errors).\n *\n * The RSC entry checks for this error type in its catch block to avoid\n * re-executing server components via renderDenyPage. Instead, it renders\n * a bare deny/error page without layout wrapping.\n *\n * Defined in primitives.ts (not ssr-entry.ts) because ssr-entry.ts imports\n * react-dom/server which cannot be loaded in the RSC environment.\n */\nexport class SsrStreamError extends Error {\n constructor(\n message: string,\n public readonly cause: unknown\n ) {\n super(message);\n this.name = 'SsrStreamError';\n }\n}\n","/**\n * URL canonicalization — runs once at the request boundary.\n *\n * Every layer (proxy.ts, middleware.ts, access.ts, components) sees the same\n * canonical path. No re-decoding occurs at any later stage.\n *\n * See design/07-routing.md §\"URL Canonicalization & Security\"\n */\n\n/** Result of canonicalization — either a clean path or a rejection. */\nexport type CanonicalizeResult = { ok: true; pathname: string } | { ok: false; status: 400 };\n\n/**\n * Encoded separators that produce a 400 rejection.\n * %2f (/) and %5c (\\) cause path-confusion attacks.\n */\nconst ENCODED_SEPARATOR_RE = /%2f|%5c/i;\n\n/** Null byte — rejected. */\nconst NULL_BYTE_RE = /%00/i;\n\n/**\n * Canonicalize a URL pathname.\n *\n * 1. Reject encoded separators (%2f, %5c) and null bytes (%00)\n * 2. Single percent-decode\n * 3. Collapse // → /\n * 4. Resolve .. segments (reject if escaping root)\n * 5. Strip trailing slash (except root \"/\")\n *\n * @param rawPathname - The raw pathname from the request URL (percent-encoded)\n * @param stripTrailingSlash - Whether to strip trailing slashes. Default: true.\n */\nexport function canonicalize(rawPathname: string, stripTrailingSlash = true): CanonicalizeResult {\n // Step 1: Reject dangerous encoded sequences BEFORE decoding.\n // This must happen on the raw input so %252f doesn't bypass after a single decode.\n if (ENCODED_SEPARATOR_RE.test(rawPathname)) {\n return { ok: false, status: 400 };\n }\n if (NULL_BYTE_RE.test(rawPathname)) {\n return { ok: false, status: 400 };\n }\n\n // Step 2: Single percent-decode.\n // Double-encoded input (%2561 → %61) stays as %61 — not decoded again.\n let decoded: string;\n try {\n decoded = decodeURIComponent(rawPathname);\n } catch {\n // Malformed percent-encoding → 400\n return { ok: false, status: 400 };\n }\n\n // Reject null bytes that appeared after decoding (from valid %00-like sequences\n // that weren't caught above — belt and suspenders).\n if (decoded.includes('\\0')) {\n return { ok: false, status: 400 };\n }\n\n // Backslash is NOT a path separator — keep as literal character.\n // But reject if it would create // after normalization (e.g., /\\evil.com).\n // We do NOT convert \\ to / — it stays as a literal.\n\n // Step 3: Collapse consecutive slashes.\n let pathname = decoded.replace(/\\/\\/+/g, '/');\n\n // Step 4: Resolve .. and . segments.\n const segments = pathname.split('/');\n const resolved: string[] = [];\n for (const seg of segments) {\n if (seg === '..') {\n if (resolved.length <= 1) {\n // Trying to escape root — 400\n return { ok: false, status: 400 };\n }\n resolved.pop();\n } else if (seg !== '.') {\n resolved.push(seg);\n }\n }\n\n pathname = resolved.join('/') || '/';\n\n // Step 5: Strip trailing slash (except root \"/\").\n if (stripTrailingSlash && pathname.length > 1 && pathname.endsWith('/')) {\n pathname = pathname.slice(0, -1);\n }\n\n return { ok: true, pathname };\n}\n","/**\n * Proxy runner — executes app/proxy.ts before route matching.\n *\n * Supports two forms:\n * - Function: (req, next) => Promise<Response>\n * - Array: middleware functions composed left-to-right\n *\n * See design/07-routing.md §\"proxy.ts — Global Middleware\"\n */\n\n/** Signature for a single proxy middleware function. */\nexport type ProxyFn = (req: Request, next: () => Promise<Response>) => Response | Promise<Response>;\n\n/** The proxy.ts default export — either a function or an array of functions. */\nexport type ProxyExport = ProxyFn | ProxyFn[];\n\n/**\n * Run the proxy pipeline.\n *\n * @param proxyExport - The default export from proxy.ts (function or array)\n * @param req - The incoming request\n * @param next - The continuation that proceeds to route matching and rendering\n * @returns The final response\n */\nexport async function runProxy(\n proxyExport: ProxyExport,\n req: Request,\n next: () => Promise<Response>\n): Promise<Response> {\n const fns = Array.isArray(proxyExport) ? proxyExport : [proxyExport];\n\n // Compose left-to-right: first item's next() calls the second, etc.\n // The last item's next() calls the original `next` (route matching + render).\n let i = fns.length;\n let composed = next;\n while (i--) {\n const fn = fns[i]!;\n const downstream = composed;\n composed = () => Promise.resolve(fn(req, downstream));\n }\n\n return composed();\n}\n","/**\n * Middleware runner — executes a route's middleware.ts before rendering.\n *\n * Only the leaf route's middleware runs. There is no middleware chain.\n * Middleware does NOT have next() — it either short-circuits with a Response\n * or returns void to continue to access checks + render.\n *\n * See design/07-routing.md §\"middleware.ts\"\n */\n\nimport type { MiddlewareContext } from './types.js';\n\n/** Signature of a middleware.ts default export. */\nexport type MiddlewareFn = (ctx: MiddlewareContext) => Response | void | Promise<Response | void>;\n\n/**\n * Run a route's middleware function.\n *\n * @param middlewareFn - The default export from the route's middleware.ts\n * @param ctx - The middleware context (req, params, headers, requestHeaders, searchParams)\n * @returns A Response if middleware short-circuited, or undefined to continue\n */\nexport async function runMiddleware(\n middlewareFn: MiddlewareFn,\n ctx: MiddlewareContext\n): Promise<Response | undefined> {\n const result = await middlewareFn(ctx);\n if (result instanceof Response) {\n return result;\n }\n return undefined;\n}\n","/**\n * Server-Timing header — dev-mode timing breakdowns for Chrome DevTools.\n *\n * Collects timing entries per request using ALS. Each pipeline phase\n * (proxy, middleware, render, SSR, access, fetch) records an entry.\n * Before response flush, entries are formatted into a Server-Timing header.\n *\n * Only active in dev mode — zero overhead in production.\n *\n * See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Server-Timing\n * Task: LOCAL-290\n */\n\nimport { timingAls } from './als-registry.js';\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\nexport interface TimingEntry {\n /** Metric name (alphanumeric + hyphens, no spaces). */\n name: string;\n /** Duration in milliseconds. */\n dur: number;\n /** Human-readable description (shown in DevTools). */\n desc?: string;\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * Run a callback with a per-request timing collector.\n * Must be called at the top of the request pipeline (wraps the full request).\n */\nexport function runWithTimingCollector<T>(fn: () => T): T {\n return timingAls.run({ entries: [] }, fn);\n}\n\n/**\n * Record a timing entry for the current request.\n * No-ops if called outside a timing collector (e.g. in production).\n */\nexport function recordTiming(entry: TimingEntry): void {\n const store = timingAls.getStore();\n if (!store) return;\n store.entries.push(entry);\n}\n\n/**\n * Run a function and automatically record its duration as a timing entry.\n * Returns the function's result. No-ops the recording if outside a collector.\n */\nexport async function withTiming<T>(\n name: string,\n desc: string | undefined,\n fn: () => T | Promise<T>\n): Promise<T> {\n const store = timingAls.getStore();\n if (!store) return fn();\n\n const start = performance.now();\n try {\n return await fn();\n } finally {\n const dur = Math.round(performance.now() - start);\n store.entries.push({ name, dur, desc });\n }\n}\n\n/**\n * Get the Server-Timing header value for the current request.\n * Returns null if no entries exist or outside a collector.\n *\n * Format: `name;dur=123;desc=\"description\", name2;dur=456`\n * See RFC 6797 / Server-Timing spec for format details.\n */\nexport function getServerTimingHeader(): string | null {\n const store = timingAls.getStore();\n if (!store || store.entries.length === 0) return null;\n\n // Deduplicate names — if a name appears multiple times, suffix with index\n const nameCounts = new Map<string, number>();\n const entries = store.entries.map((entry) => {\n const count = nameCounts.get(entry.name) ?? 0;\n nameCounts.set(entry.name, count + 1);\n const uniqueName = count > 0 ? `${entry.name}-${count}` : entry.name;\n return { ...entry, name: uniqueName };\n });\n\n const parts = entries.map((entry) => {\n let part = `${entry.name};dur=${entry.dur}`;\n if (entry.desc) {\n // Escape quotes in desc per Server-Timing spec\n const safeDesc = entry.desc.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n part += `;desc=\"${safeDesc}\"`;\n }\n return part;\n });\n\n // Respect header size limits — browsers typically handle up to 8KB headers.\n // Truncate if the header exceeds 4KB to leave room for other headers.\n const MAX_HEADER_SIZE = 4096;\n let result = '';\n for (let i = 0; i < parts.length; i++) {\n const candidate = result ? `${result}, ${parts[i]}` : parts[i]!;\n if (candidate.length > MAX_HEADER_SIZE) break;\n result = candidate;\n }\n\n return result || null;\n}\n\n/**\n * Sanitize a URL for use in Server-Timing desc.\n * Strips query params and truncates long paths to avoid information leakage.\n */\nexport function sanitizeUrlForTiming(url: string): string {\n try {\n const parsed = new URL(url);\n const origin = parsed.host;\n let path = parsed.pathname;\n // Truncate long paths\n if (path.length > 50) {\n path = path.slice(0, 47) + '...';\n }\n return `${origin}${path}`;\n } catch {\n // Not a valid URL — truncate raw string\n if (url.length > 60) {\n return url.slice(0, 57) + '...';\n }\n return url;\n }\n}\n","/**\n * Error Formatter — rewrites SSR/RSC error messages to surface user code.\n *\n * When React or Vite throw errors during SSR, stack traces reference\n * vendored dependency paths (e.g. `.vite/deps_ssr/@vitejs_plugin-rsc_vendor_...`)\n * and mangled export names (`__vite_ssr_export_default__`). This module\n * rewrites error messages and stack traces to point at user code instead.\n *\n * Dev-only — in production, errors go through the structured logger\n * without formatting.\n */\n\n// ─── Stack Trace Rewriting ──────────────────────────────────────────────────\n\n/**\n * Patterns that identify internal Vite/RSC vendor paths in stack traces.\n * These are replaced with human-readable labels.\n */\nconst VENDOR_PATH_PATTERNS: Array<{ pattern: RegExp; replacement: string }> = [\n {\n pattern: /node_modules\\/\\.vite\\/deps_ssr\\/@vitejs_plugin-rsc_vendor_react-server-dom[^\\s)]+/g,\n replacement: '<react-server-dom>',\n },\n {\n pattern: /node_modules\\/\\.vite\\/deps_ssr\\/@vitejs_plugin-rsc_vendor[^\\s)]+/g,\n replacement: '<rsc-vendor>',\n },\n {\n pattern: /node_modules\\/\\.vite\\/deps_ssr\\/[^\\s)]+/g,\n replacement: '<vite-dep>',\n },\n {\n pattern: /node_modules\\/\\.vite\\/deps\\/[^\\s)]+/g,\n replacement: '<vite-dep>',\n },\n];\n\n/**\n * Patterns that identify Vite-mangled export names in error messages.\n */\nconst MANGLED_NAME_PATTERNS: Array<{ pattern: RegExp; replacement: string }> = [\n {\n pattern: /__vite_ssr_export_default__/g,\n replacement: '<default export>',\n },\n {\n pattern: /__vite_ssr_export_(\\w+)__/g,\n replacement: '<export $1>',\n },\n];\n\n/**\n * Rewrite an error's message and stack to replace internal Vite paths\n * and mangled names with human-readable labels.\n */\nexport function formatSsrError(error: unknown): string {\n if (!(error instanceof Error)) {\n return String(error);\n }\n\n let message = error.message;\n let stack = error.stack ?? '';\n\n // Rewrite mangled names in the message\n for (const { pattern, replacement } of MANGLED_NAME_PATTERNS) {\n message = message.replace(pattern, replacement);\n }\n\n // Rewrite vendor paths in the stack\n for (const { pattern, replacement } of VENDOR_PATH_PATTERNS) {\n stack = stack.replace(pattern, replacement);\n }\n\n // Rewrite mangled names in the stack too\n for (const { pattern, replacement } of MANGLED_NAME_PATTERNS) {\n stack = stack.replace(pattern, replacement);\n }\n\n // Extract hints from React-specific error patterns\n const hint = extractErrorHint(error.message);\n\n // Build formatted output: cleaned message, hint (if any), then cleaned stack\n const parts: string[] = [];\n parts.push(message);\n if (hint) {\n parts.push(` → ${hint}`);\n }\n\n // Include only the user-code frames from the stack (skip the first line\n // which is the message itself, and filter out vendor-only frames)\n const userFrames = extractUserFrames(stack);\n if (userFrames.length > 0) {\n parts.push('');\n parts.push(' User code in stack:');\n for (const frame of userFrames) {\n parts.push(` ${frame}`);\n }\n }\n\n return parts.join('\\n');\n}\n\n// ─── Error Hint Extraction ──────────────────────────────────────────────────\n\n/**\n * Extract a human-readable hint from common React/RSC error messages.\n *\n * React error messages contain useful information but the surrounding\n * context (vendor paths, mangled names) obscures it. This extracts the\n * actionable part as a one-line hint.\n */\nfunction extractErrorHint(message: string): string | null {\n // \"Functions cannot be passed directly to Client Components\"\n // Extract the component and prop name from the JSX-like syntax in the message\n const fnPassedMatch = message.match(/Functions cannot be passed directly to Client Components/);\n if (fnPassedMatch) {\n // Try to extract the prop name from the message\n // React formats: <... propName={function ...} ...>\n const propMatch = message.match(/<[^>]*?\\s(\\w+)=\\{function/);\n if (propMatch) {\n return `Prop \"${propMatch[1]}\" is a function — mark it \"use server\" or call it before passing`;\n }\n return 'A function prop was passed to a Client Component — mark it \"use server\" or call it before passing';\n }\n\n // \"Objects are not valid as a React child\"\n if (message.includes('Objects are not valid as a React child')) {\n return 'An object was rendered as JSX children — convert to string or extract the value';\n }\n\n // \"Cannot read properties of undefined/null\"\n const nullRefMatch = message.match(\n /Cannot read propert(?:y|ies) of (undefined|null) \\(reading '(\\w+)'\\)/\n );\n if (nullRefMatch) {\n return `Accessed .${nullRefMatch[2]} on ${nullRefMatch[1]} — check that the value exists`;\n }\n\n // \"X is not a function\"\n const notFnMatch = message.match(/(\\w+) is not a function/);\n if (notFnMatch) {\n return `\"${notFnMatch[1]}\" is not a function — check imports and exports`;\n }\n\n // \"Element type is invalid\"\n if (message.includes('Element type is invalid')) {\n return 'A component resolved to undefined/null — check default exports and import paths';\n }\n\n // \"Invalid hook call\" — hooks called outside React's render context.\n // In RSC, this typically means a 'use client' component was executed as a\n // server component instead of being serialized as a client reference.\n if (message.includes('Invalid hook call')) {\n return (\n 'A hook was called outside of a React component render. ' +\n \"If this is a 'use client' component, ensure the directive is at the very top of the file \" +\n '(before any imports) and that @vitejs/plugin-rsc is loaded correctly. ' +\n \"Barrel re-exports from non-'use client' files do not propagate the directive.\"\n );\n }\n\n return null;\n}\n\n// ─── Stack Frame Filtering ──────────────────────────────────────────────────\n\n/**\n * Extract stack frames that reference user code (not node_modules,\n * not framework internals).\n *\n * Returns at most 5 frames to keep output concise.\n */\nfunction extractUserFrames(stack: string): string[] {\n const lines = stack.split('\\n');\n const userFrames: string[] = [];\n\n for (const line of lines) {\n const trimmed = line.trim();\n // Skip non-frame lines\n if (!trimmed.startsWith('at ')) continue;\n // Skip node_modules, vendor, and internal frames\n if (\n trimmed.includes('node_modules') ||\n trimmed.includes('<react-server-dom>') ||\n trimmed.includes('<rsc-vendor>') ||\n trimmed.includes('<vite-dep>') ||\n trimmed.includes('node:internal')\n ) {\n continue;\n }\n userFrames.push(trimmed);\n if (userFrames.length >= 5) break;\n }\n\n return userFrames;\n}\n","/**\n * Logger — structured logging with environment-aware formatting.\n *\n * timber.js does not ship a logger. Users export any object with\n * info/warn/error/debug methods from instrumentation.ts and the framework\n * picks it up. Silent if no logger export is present.\n *\n * See design/17-logging.md §\"Production Logging\"\n */\n\nimport { getTraceStore } from './tracing.js';\nimport { formatSsrError } from './error-formatter.js';\n\n// ─── Logger Interface ─────────────────────────────────────────────────────\n\n/** Any object with standard log methods satisfies this — pino, winston, consola, console. */\nexport interface TimberLogger {\n info(msg: string, data?: Record<string, unknown>): void;\n warn(msg: string, data?: Record<string, unknown>): void;\n error(msg: string, data?: Record<string, unknown>): void;\n debug(msg: string, data?: Record<string, unknown>): void;\n}\n\n// ─── Logger Registry ──────────────────────────────────────────────────────\n\nlet _logger: TimberLogger | null = null;\n\n/**\n * Set the user-provided logger. Called by the instrumentation loader\n * when it finds a `logger` export in instrumentation.ts.\n */\nexport function setLogger(logger: TimberLogger): void {\n _logger = logger;\n}\n\n/**\n * Get the current logger, or null if none configured.\n * Framework-internal — used at framework event points to emit structured logs.\n */\nexport function getLogger(): TimberLogger | null {\n return _logger;\n}\n\n// ─── Framework Log Helpers ────────────────────────────────────────────────\n\n/**\n * Inject trace_id and span_id into log data for log–trace correlation.\n * Always injects trace_id (never undefined). Injects span_id only when OTEL is active.\n */\nfunction withTraceContext(data?: Record<string, unknown>): Record<string, unknown> {\n const store = getTraceStore();\n const enriched: Record<string, unknown> = { ...data };\n if (store) {\n enriched.trace_id = store.traceId;\n if (store.spanId) {\n enriched.span_id = store.spanId;\n }\n }\n return enriched;\n}\n\n// ─── Framework Event Emitters ─────────────────────────────────────────────\n\n/** Log a completed request. Level: info. */\nexport function logRequestCompleted(data: {\n method: string;\n path: string;\n status: number;\n durationMs: number;\n}): void {\n _logger?.info('request completed', withTraceContext(data));\n}\n\n/** Log request received. Level: debug. */\nexport function logRequestReceived(data: { method: string; path: string }): void {\n _logger?.debug('request received', withTraceContext(data));\n}\n\n/** Log a slow request warning. Level: warn. */\nexport function logSlowRequest(data: {\n method: string;\n path: string;\n durationMs: number;\n threshold: number;\n}): void {\n _logger?.warn('slow request exceeded threshold', withTraceContext(data));\n}\n\n/** Log middleware short-circuit. Level: debug. */\nexport function logMiddlewareShortCircuit(data: {\n method: string;\n path: string;\n status: number;\n}): void {\n _logger?.debug('middleware short-circuited', withTraceContext(data));\n}\n\n/** Log unhandled error in middleware phase. Level: error. */\nexport function logMiddlewareError(data: { method: string; path: string; error: unknown }): void {\n if (_logger) {\n _logger.error('unhandled error in middleware phase', withTraceContext(data));\n } else if (process.env.NODE_ENV !== 'production') {\n console.error('[timber] middleware error', data.error);\n }\n}\n\n/** Log unhandled render-phase error. Level: error. */\nexport function logRenderError(data: { method: string; path: string; error: unknown }): void {\n if (_logger) {\n _logger.error('unhandled render-phase error', withTraceContext(data));\n } else if (process.env.NODE_ENV !== 'production') {\n // No logger configured — fall back to console.error in dev with\n // cleaned-up error messages (vendor paths rewritten, hints added).\n console.error('[timber] render error:', formatSsrError(data.error));\n }\n}\n\n/** Log proxy.ts uncaught error. Level: error. */\nexport function logProxyError(data: { error: unknown }): void {\n if (_logger) {\n _logger.error('proxy.ts threw uncaught error', withTraceContext(data));\n } else if (process.env.NODE_ENV !== 'production') {\n console.error('[timber] proxy error', data.error);\n }\n}\n\n/** Log waitUntil() adapter missing (once at startup). Level: warn. */\nexport function logWaitUntilUnsupported(): void {\n _logger?.warn('adapter does not support waitUntil()');\n}\n\n/** Log waitUntil() promise rejection. Level: warn. */\nexport function logWaitUntilRejected(data: { error: unknown }): void {\n _logger?.warn('waitUntil() promise rejected', withTraceContext(data));\n}\n\n/** Log staleWhileRevalidate refetch failure. Level: warn. */\nexport function logSwrRefetchFailed(data: { cacheKey: string; error: unknown }): void {\n _logger?.warn('staleWhileRevalidate refetch failed', withTraceContext(data));\n}\n\n/** Log cache miss. Level: debug. */\nexport function logCacheMiss(data: { cacheKey: string }): void {\n _logger?.debug('timber.cache MISS', withTraceContext(data));\n}\n","/**\n * Instrumentation — loads and runs the user's instrumentation.ts file.\n *\n * instrumentation.ts is a file convention at the project root that exports:\n * - register() — called once at server startup, before the first request\n * - onRequestError() — called for every unhandled server error\n * - logger — any object with info/warn/error/debug methods\n *\n * See design/17-logging.md §\"instrumentation.ts — The Entry Point\"\n */\n\nimport { setLogger, type TimberLogger } from './logger.js';\n\n// ─── Instrumentation Types ────────────────────────────────────────────────\n\nexport type InstrumentationOnRequestError = (\n error: unknown,\n request: InstrumentationRequestInfo,\n context: InstrumentationErrorContext\n) => void | Promise<void>;\n\nexport interface InstrumentationRequestInfo {\n /** HTTP method: 'GET', 'POST', etc. */\n method: string;\n /** Request path: '/dashboard/projects/123' */\n path: string;\n /** Request headers as a plain object. */\n headers: Record<string, string>;\n}\n\nexport interface InstrumentationErrorContext {\n /** Which pipeline phase the error occurred in. */\n phase: 'proxy' | 'handler' | 'render' | 'action' | 'route';\n /** The route pattern: '/dashboard/projects/[id]' */\n routePath: string;\n /** Type of route that was matched. */\n routeType: 'page' | 'route' | 'action';\n /** Always set — OTEL trace ID or UUID fallback. */\n traceId: string;\n}\n\n// ─── Instrumentation Module Shape ─────────────────────────────────────────\n\ninterface InstrumentationModule {\n register?: () => void | Promise<void>;\n onRequestError?: InstrumentationOnRequestError;\n logger?: TimberLogger;\n}\n\n// ─── State ────────────────────────────────────────────────────────────────\n//\n// Intentional per-app singletons (not per-request). Instrumentation loads\n// once at server startup and persists for the lifetime of the process/isolate.\n// These must NOT be migrated to ALS — they are correctly scoped to the app.\n\nlet _initialized = false;\nlet _onRequestError: InstrumentationOnRequestError | null = null;\n\n/**\n * Load and initialize the user's instrumentation.ts module.\n *\n * - Awaits register() before returning (server blocks on this).\n * - Picks up the logger export and wires it into the framework logger.\n * - Stores onRequestError for later invocation.\n *\n * @param loader - Function that dynamically imports the user's instrumentation module.\n * Returns null if no instrumentation.ts exists.\n */\nexport async function loadInstrumentation(\n loader: () => Promise<InstrumentationModule | null>\n): Promise<void> {\n if (_initialized) return;\n _initialized = true;\n\n let mod: InstrumentationModule | null;\n try {\n mod = await loader();\n } catch (error) {\n console.error('[timber] Failed to load instrumentation.ts:', error);\n return;\n }\n\n if (!mod) return;\n\n // Wire up the logger export\n if (mod.logger && typeof mod.logger.info === 'function') {\n setLogger(mod.logger);\n }\n\n // Store onRequestError for later\n if (typeof mod.onRequestError === 'function') {\n _onRequestError = mod.onRequestError;\n }\n\n // Await register() — server does not accept requests until this resolves\n if (typeof mod.register === 'function') {\n try {\n await mod.register();\n } catch (error) {\n console.error('[timber] instrumentation.ts register() threw:', error);\n throw error;\n }\n }\n}\n\n/**\n * Call the user's onRequestError hook. Catches and logs any errors thrown\n * by the hook itself — it must not affect the response.\n */\nexport async function callOnRequestError(\n error: unknown,\n request: InstrumentationRequestInfo,\n context: InstrumentationErrorContext\n): Promise<void> {\n if (!_onRequestError) return;\n try {\n await _onRequestError(error, request, context);\n } catch (hookError) {\n console.error('[timber] onRequestError hook threw:', hookError);\n }\n}\n\n/**\n * Check if onRequestError is registered.\n */\nexport function hasOnRequestError(): boolean {\n return _onRequestError !== null;\n}\n\n/**\n * Reset instrumentation state. Test-only.\n */\nexport function resetInstrumentation(): void {\n _initialized = false;\n _onRequestError = null;\n}\n","/**\n * Metadata route helpers for the request pipeline.\n *\n * Handles serving static metadata files and serializing sitemap responses.\n * Extracted from pipeline.ts to keep files under 500 lines.\n *\n * See design/16-metadata.md §\"Metadata Routes\"\n */\n\nimport { readFile } from 'node:fs/promises';\n\n/**\n * Content types that are text-based and should include charset=utf-8.\n * Binary formats (images) should not include charset.\n */\nconst TEXT_CONTENT_TYPES = new Set([\n 'application/xml',\n 'text/plain',\n 'application/json',\n 'application/manifest+json',\n 'image/svg+xml',\n]);\n\n/**\n * Serve a static metadata file by reading it from disk.\n *\n * Static metadata route files (.xml, .txt, .json, .png, .ico, .svg, etc.)\n * are served as-is with the appropriate Content-Type header.\n * Text files include charset=utf-8; binary files do not.\n *\n * See design/16-metadata.md §\"Metadata Routes\"\n */\nexport async function serveStaticMetadataFile(\n metaMatch: import('./route-matcher.js').MetadataRouteMatch\n): Promise<Response> {\n const { contentType, file } = metaMatch;\n const isText = TEXT_CONTENT_TYPES.has(contentType);\n\n const body = await readFile(file.filePath);\n\n const headers: Record<string, string> = {\n 'Content-Type': isText ? `${contentType}; charset=utf-8` : contentType,\n 'Content-Length': String(body.byteLength),\n };\n\n return new Response(body, { status: 200, headers });\n}\n\n/**\n * Serialize a sitemap array to XML.\n * Follows the sitemap.org protocol: https://www.sitemaps.org/protocol.html\n */\nexport function serializeSitemap(\n entries: Array<{\n url: string;\n lastModified?: string | Date;\n changeFrequency?: string;\n priority?: number;\n }>\n): string {\n const urls = entries\n .map((e) => {\n let xml = ` <url>\\n <loc>${escapeXml(e.url)}</loc>`;\n if (e.lastModified) {\n const date = e.lastModified instanceof Date ? e.lastModified.toISOString() : e.lastModified;\n xml += `\\n <lastmod>${escapeXml(date)}</lastmod>`;\n }\n if (e.changeFrequency) {\n xml += `\\n <changefreq>${escapeXml(e.changeFrequency)}</changefreq>`;\n }\n if (e.priority !== undefined) {\n xml += `\\n <priority>${e.priority}</priority>`;\n }\n xml += '\\n </url>';\n return xml;\n })\n .join('\\n');\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n${urls}\\n</urlset>`;\n}\n\n/** Escape special XML characters. */\nexport function escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n","/**\n * Interception route matching for the request pipeline.\n *\n * Matches target URLs against interception rewrites to support the\n * modal route pattern (soft navigation intercepts).\n *\n * Extracted from pipeline.ts to keep files under 500 lines.\n *\n * See design/07-routing.md §\"Intercepting Routes\"\n */\n\n/** Result of a successful interception match. */\nexport interface InterceptionMatchResult {\n /** The pathname to re-match (the source/intercepting route's parent). */\n sourcePathname: string;\n}\n\n/**\n * Check if an intercepting route applies for this soft navigation.\n *\n * Matches the target pathname against interception rewrites, constrained\n * by the source URL (X-Timber-URL header — where the user navigates FROM).\n *\n * Returns the source pathname to re-match if interception applies, or null.\n */\nexport function findInterceptionMatch(\n targetPathname: string,\n sourceUrl: string,\n rewrites: import('#/routing/interception.js').InterceptionRewrite[]\n): InterceptionMatchResult | null {\n for (const rewrite of rewrites) {\n // Check if the source URL starts with the intercepting prefix\n if (!sourceUrl.startsWith(rewrite.interceptingPrefix)) continue;\n\n // Check if the target URL matches the intercepted pattern.\n // Dynamic segments in the pattern match any single URL segment.\n if (pathnameMatchesPattern(targetPathname, rewrite.interceptedPattern)) {\n return { sourcePathname: rewrite.interceptingPrefix };\n }\n }\n return null;\n}\n\n/**\n * Check if a pathname matches a URL pattern with dynamic segments.\n *\n * Supports [param] (single segment) and [...param] (one or more segments).\n * Static segments must match exactly.\n */\nexport function pathnameMatchesPattern(pathname: string, pattern: string): boolean {\n const pathParts = pathname === '/' ? [] : pathname.slice(1).split('/');\n const patternParts = pattern === '/' ? [] : pattern.slice(1).split('/');\n\n let pi = 0;\n for (let i = 0; i < patternParts.length; i++) {\n const segment = patternParts[i];\n\n // Catch-all: [...param] or [[...param]] — matches rest of URL\n if (segment.startsWith('[...') || segment.startsWith('[[...')) {\n return pi < pathParts.length || segment.startsWith('[[...');\n }\n\n // Dynamic: [param] — matches any single segment\n if (segment.startsWith('[') && segment.endsWith(']')) {\n if (pi >= pathParts.length) return false;\n pi++;\n continue;\n }\n\n // Static — must match exactly\n if (pi >= pathParts.length || pathParts[pi] !== segment) return false;\n pi++;\n }\n\n return pi === pathParts.length;\n}\n","/**\n * Request pipeline — the central dispatch for all timber.js requests.\n *\n * Pipeline stages (in order):\n * proxy.ts → canonicalize → route match → 103 Early Hints → middleware.ts → render\n *\n * Each stage is a pure function or returns a Response to short-circuit.\n * Each request gets a trace ID, structured logging, and OTEL spans.\n *\n * See design/07-routing.md §\"Request Lifecycle\", design/02-rendering-pipeline.md §\"Request Flow\",\n * and design/17-logging.md §\"Production Logging\"\n */\n\nimport { canonicalize } from './canonicalize.js';\nimport { runProxy, type ProxyExport } from './proxy.js';\nimport { runMiddleware, type MiddlewareFn } from './middleware-runner.js';\nimport {\n runWithTimingCollector,\n withTiming,\n getServerTimingHeader,\n} from './server-timing.js';\nimport {\n runWithRequestContext,\n applyRequestHeaderOverlay,\n setMutableCookieContext,\n getSetCookieHeaders,\n markResponseFlushed,\n} from './request-context.js';\nimport {\n generateTraceId,\n runWithTraceId,\n getOtelTraceId,\n replaceTraceId,\n withSpan,\n setSpanAttribute,\n traceId,\n} from './tracing.js';\nimport {\n logRequestReceived,\n logRequestCompleted,\n logSlowRequest,\n logProxyError,\n logMiddlewareError,\n logMiddlewareShortCircuit,\n logRenderError,\n} from './logger.js';\nimport { callOnRequestError } from './instrumentation.js';\nimport { RedirectSignal, DenySignal } from './primitives.js';\nimport { serveStaticMetadataFile, serializeSitemap } from './pipeline-metadata.js';\nimport { findInterceptionMatch } from './pipeline-interception.js';\nimport type { MiddlewareContext } from './types.js';\nimport type { SegmentNode } from '#/routing/types.js';\n\n// ─── Route Match Result ────────────────────────────────────────────────────\n\n/** Result of matching a canonical pathname against the route tree. */\nexport interface RouteMatch {\n /** The matched segment chain from root to leaf. */\n segments: SegmentNode[];\n /** Extracted route params (catch-all segments produce string[]). */\n params: Record<string, string | string[]>;\n /** The leaf segment's middleware.ts export, if any. */\n middleware?: MiddlewareFn;\n}\n\n/** Function that matches a canonical pathname to a route. */\nexport type RouteMatcher = (pathname: string) => RouteMatch | null;\n\n/** Function that matches a canonical pathname to a metadata route. */\nexport type MetadataRouteMatcher = (\n pathname: string\n) => import('./route-matcher.js').MetadataRouteMatch | null;\n\n/** Context for intercepting route resolution (modal pattern). */\nexport interface InterceptionContext {\n /** The URL the user is navigating TO (the intercepted route). */\n targetPathname: string;\n}\n\n/** Function that renders a matched route into a Response. */\nexport type RouteRenderer = (\n req: Request,\n match: RouteMatch,\n responseHeaders: Headers,\n requestHeaderOverlay: Headers,\n interception?: InterceptionContext\n) => Response | Promise<Response>;\n\n/** Function that sends 103 Early Hints for a matched route. */\nexport type EarlyHintsEmitter = (\n match: RouteMatch,\n req: Request,\n responseHeaders: Headers\n) => void | Promise<void>;\n\n// ─── Pipeline Configuration ────────────────────────────────────────────────\n\nexport interface PipelineConfig {\n /** The proxy.ts default export (function or array). Undefined if no proxy.ts. */\n proxy?: ProxyExport;\n /** Lazy loader for proxy.ts — called per-request so HMR updates take effect. */\n proxyLoader?: () => Promise<{ default: ProxyExport }>;\n /** Route matcher — resolves a canonical pathname to a RouteMatch. */\n matchRoute: RouteMatcher;\n /** Metadata route matcher — resolves metadata route pathnames (sitemap.xml, robots.txt, etc.) */\n matchMetadataRoute?: MetadataRouteMatcher;\n /** Renderer — produces the final Response for a matched route. */\n render: RouteRenderer;\n /** Renderer for no-match 404 — renders 404.tsx in root layout. */\n renderNoMatch?: (req: Request, responseHeaders: Headers) => Response | Promise<Response>;\n /** Early hints emitter — fires 103 hints after route match, before middleware. */\n earlyHints?: EarlyHintsEmitter;\n /** Whether to strip trailing slashes during canonicalization. Default: true. */\n stripTrailingSlash?: boolean;\n /** Slow request threshold in ms. Requests exceeding this emit a warning. 0 to disable. Default: 3000. */\n slowRequestMs?: number;\n /**\n * Interception rewrites — conditional routes for the modal pattern.\n * Generated at build time from intercepting route directories.\n * See design/07-routing.md §\"Intercepting Routes\"\n */\n interceptionRewrites?: import('#/routing/interception.js').InterceptionRewrite[];\n /**\n * Emit Server-Timing header on responses for Chrome DevTools visibility.\n * Only enable in dev mode — exposes internal timing data.\n *\n * Default: false (production-safe).\n */\n enableServerTiming?: boolean;\n /**\n * Dev pipeline error callback — called when a pipeline phase (proxy,\n * middleware, render) catches an unhandled error. Used to wire the error\n * into the Vite browser error overlay in dev mode.\n *\n * Undefined in production — zero overhead.\n */\n onPipelineError?: (error: Error, phase: string) => void;\n /**\n * Fallback error renderer — called when a catastrophic error escapes the\n * render phase. Produces an HTML Response instead of a bare empty 500.\n *\n * In dev mode, this renders a styled error page with the error message\n * and stack trace. In production, this attempts to render the app's\n * error.tsx / 5xx.tsx / 500.tsx from the root segment.\n *\n * If this function throws, the pipeline falls back to a bare\n * `new Response(null, { status: 500 })`.\n */\n renderFallbackError?: (\n error: unknown,\n req: Request,\n responseHeaders: Headers\n ) => Response | Promise<Response>;\n}\n\n// ─── Pipeline ──────────────────────────────────────────────────────────────\n\n/**\n * Create the request handler from a pipeline configuration.\n *\n * Returns a function that processes an incoming Request through all pipeline stages\n * and produces a Response. This is the top-level entry point for the server.\n */\nexport function createPipeline(config: PipelineConfig): (req: Request) => Promise<Response> {\n const {\n proxy,\n matchRoute,\n render,\n earlyHints,\n stripTrailingSlash = true,\n slowRequestMs = 3000,\n enableServerTiming = false,\n onPipelineError,\n } = config;\n\n return async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n const method = req.method;\n const path = url.pathname;\n const startTime = performance.now();\n\n // Establish per-request trace ID scope (design/17-logging.md §\"trace_id is Always Set\").\n // This runs before runWithRequestContext so traceId() is available from the\n // very first line of proxy.ts, middleware.ts, and all server code.\n const traceIdValue = generateTraceId();\n\n return runWithTraceId(traceIdValue, async () => {\n // Establish request context ALS scope so headers() and cookies() work\n // throughout the entire request lifecycle (proxy, middleware, render).\n return runWithRequestContext(req, async () => {\n // In dev mode, wrap with timing collector for Server-Timing header.\n // The collector uses ALS so timing entries are per-request.\n const runRequest = async () => {\n logRequestReceived({ method, path });\n\n const response = await withSpan(\n 'http.server.request',\n { 'http.request.method': method, 'url.path': path },\n async () => {\n // If OTEL is active, the root span now exists — replace the UUID\n // fallback with the real OTEL trace ID for log–trace correlation.\n const otelIds = await getOtelTraceId();\n if (otelIds) {\n replaceTraceId(otelIds.traceId, otelIds.spanId);\n }\n\n let result: Response;\n if (proxy || config.proxyLoader) {\n result = await runProxyPhase(req, method, path);\n } else {\n result = await handleRequest(req, method, path);\n }\n\n // Set response status on the root span before it ends —\n // DevSpanProcessor reads this for tree/summary output.\n await setSpanAttribute('http.response.status_code', result.status);\n\n // Append Server-Timing header in dev mode.\n // At this point, pre-flush phases (proxy, middleware, render)\n // have completed and their timing entries are collected.\n // Response.redirect() creates immutable headers, so we must\n // ensure mutability before writing Server-Timing.\n if (enableServerTiming) {\n const serverTiming = getServerTimingHeader();\n if (serverTiming) {\n result = ensureMutableResponse(result);\n result.headers.set('Server-Timing', serverTiming);\n }\n }\n\n return result;\n }\n );\n\n // Post-span: structured production logging\n const durationMs = Math.round(performance.now() - startTime);\n const status = response.status;\n logRequestCompleted({ method, path, status, durationMs });\n\n if (slowRequestMs > 0 && durationMs > slowRequestMs) {\n logSlowRequest({ method, path, durationMs, threshold: slowRequestMs });\n }\n\n return response;\n };\n\n return enableServerTiming\n ? runWithTimingCollector(runRequest)\n : runRequest();\n });\n });\n };\n\n async function runProxyPhase(req: Request, method: string, path: string): Promise<Response> {\n try {\n // Resolve the proxy export. When a proxyLoader is provided (lazy import),\n // it is called per-request so HMR updates in dev take effect immediately.\n let proxyExport: ProxyExport;\n if (config.proxyLoader) {\n const mod = await config.proxyLoader();\n proxyExport = mod.default;\n } else {\n proxyExport = config.proxy!;\n }\n const proxyFn = () =>\n runProxy(proxyExport, req, () => handleRequest(req, method, path));\n return await withSpan('timber.proxy', {}, () =>\n enableServerTiming\n ? withTiming('proxy', 'proxy.ts', proxyFn)\n : proxyFn()\n );\n } catch (error) {\n // Uncaught proxy.ts error → bare HTTP 500\n logProxyError({ error });\n await fireOnRequestError(error, req, 'proxy');\n if (onPipelineError && error instanceof Error) onPipelineError(error, 'proxy');\n return new Response(null, { status: 500 });\n }\n }\n\n async function handleRequest(req: Request, method: string, path: string): Promise<Response> {\n // Stage 1: URL canonicalization\n const url = new URL(req.url);\n const result = canonicalize(url.pathname, stripTrailingSlash);\n if (!result.ok) {\n return new Response(null, { status: result.status });\n }\n const canonicalPathname = result.pathname;\n\n // Stage 1b: Metadata route matching — runs before regular route matching.\n // Metadata routes skip middleware.ts and access.ts (public endpoints for crawlers).\n // See design/16-metadata.md §\"Pipeline Integration\"\n if (config.matchMetadataRoute) {\n const metaMatch = config.matchMetadataRoute(canonicalPathname);\n if (metaMatch) {\n try {\n // Static metadata files (.xml, .txt, .png, .ico, etc.) are served\n // directly from disk. Dynamic metadata routes (.ts, .tsx) export a\n // handler function that generates the response.\n if (metaMatch.isStatic) {\n return await serveStaticMetadataFile(metaMatch);\n }\n\n const mod = (await metaMatch.file.load()) as { default?: Function };\n if (typeof mod.default !== 'function') {\n return new Response('Metadata route must export a default function', { status: 500 });\n }\n const handlerResult = await mod.default();\n // If the handler returns a Response, use it directly\n if (handlerResult instanceof Response) {\n return handlerResult;\n }\n // Otherwise, serialize based on content type\n const contentType = metaMatch.contentType;\n let body: string;\n if (typeof handlerResult === 'string') {\n body = handlerResult;\n } else if (contentType === 'application/xml') {\n body = serializeSitemap(handlerResult);\n } else if (contentType === 'application/manifest+json') {\n body = JSON.stringify(handlerResult, null, 2);\n } else {\n body = typeof handlerResult === 'string' ? handlerResult : String(handlerResult);\n }\n return new Response(body, {\n status: 200,\n headers: { 'Content-Type': `${contentType}; charset=utf-8` },\n });\n } catch (error) {\n logRenderError({ method, path, error });\n if (onPipelineError && error instanceof Error) onPipelineError(error, 'metadata-route');\n return new Response(null, { status: 500 });\n }\n }\n }\n\n // Stage 2: Route matching\n let match = matchRoute(canonicalPathname);\n let interception: InterceptionContext | undefined;\n\n // Stage 2a: Intercepting route resolution (modal pattern).\n // On soft navigation, check if an intercepting route should render instead.\n // The client sends X-Timber-URL with the current pathname (where they're\n // navigating FROM). If a rewrite matches, re-route to the source URL so\n // the source layout renders with the intercepted content in the slot.\n const sourceUrl = req.headers.get('X-Timber-URL');\n if (sourceUrl && config.interceptionRewrites?.length) {\n const intercepted = findInterceptionMatch(\n canonicalPathname,\n sourceUrl,\n config.interceptionRewrites\n );\n if (intercepted) {\n const sourceMatch = matchRoute(intercepted.sourcePathname);\n if (sourceMatch) {\n match = sourceMatch;\n interception = { targetPathname: canonicalPathname };\n }\n }\n }\n\n if (!match) {\n // No route matched — render 404.tsx in root layout if available,\n // otherwise fall back to a bare 404 Response.\n if (config.renderNoMatch) {\n const responseHeaders = new Headers();\n return config.renderNoMatch(req, responseHeaders);\n }\n return new Response(null, { status: 404 });\n }\n\n // Response and request header containers — created before early hints so\n // the emitter can append Link headers (e.g. for Cloudflare CDN → 103).\n const responseHeaders = new Headers();\n const requestHeaderOverlay = new Headers();\n\n // Stage 2b: 103 Early Hints (before middleware, after match)\n // Fires before middleware so the browser can begin fetching critical\n // assets while middleware runs. Non-fatal — a failing emitter never\n // blocks the request.\n if (earlyHints) {\n try {\n await earlyHints(match, req, responseHeaders);\n } catch {\n // Early hints failure is non-fatal\n }\n }\n\n // Stage 3: Leaf middleware.ts (only the leaf route's middleware runs)\n if (match.middleware) {\n const ctx: MiddlewareContext = {\n req,\n requestHeaders: requestHeaderOverlay,\n headers: responseHeaders,\n params: match.params,\n searchParams: new URL(req.url).searchParams,\n earlyHints: (hints) => {\n for (const hint of hints) {\n let value = `<${hint.href}>; rel=${hint.rel}`;\n if (hint.as !== undefined) value += `; as=${hint.as}`;\n if (hint.crossOrigin !== undefined) value += `; crossorigin=${hint.crossOrigin}`;\n if (hint.fetchPriority !== undefined) value += `; fetchpriority=${hint.fetchPriority}`;\n responseHeaders.append('Link', value);\n }\n },\n };\n\n try {\n // Enable cookie mutation during middleware (design/29-cookies.md §\"Context Tracking\")\n setMutableCookieContext(true);\n const middlewareFn = () => runMiddleware(match.middleware!, ctx);\n const middlewareResponse = await withSpan('timber.middleware', {}, () =>\n enableServerTiming\n ? withTiming('mw', 'middleware.ts', middlewareFn)\n : middlewareFn()\n );\n setMutableCookieContext(false);\n if (middlewareResponse) {\n // Apply cookie jar to short-circuit response.\n // Response.redirect() creates immutable headers, so ensure\n // mutability before appending Set-Cookie entries.\n const finalResponse = ensureMutableResponse(middlewareResponse);\n applyCookieJar(finalResponse.headers);\n logMiddlewareShortCircuit({ method, path, status: finalResponse.status });\n return finalResponse;\n }\n // Middleware succeeded without short-circuiting — apply any\n // injected request headers so headers() returns them downstream.\n applyRequestHeaderOverlay(requestHeaderOverlay);\n } catch (error) {\n setMutableCookieContext(false);\n // RedirectSignal from middleware → HTTP redirect (not an error).\n // For RSC payload requests (client navigation), return 204 + X-Timber-Redirect\n // so the client router can perform a soft SPA redirect. A raw 302 would be\n // turned into an opaque redirect by fetch({redirect:'manual'}), crashing\n // createFromFetch. See design/19-client-navigation.md.\n if (error instanceof RedirectSignal) {\n applyCookieJar(responseHeaders);\n const isRsc = (req.headers.get('Accept') ?? '').includes('text/x-component');\n if (isRsc) {\n responseHeaders.set('X-Timber-Redirect', error.location);\n return new Response(null, { status: 204, headers: responseHeaders });\n }\n responseHeaders.set('Location', error.location);\n return new Response(null, { status: error.status, headers: responseHeaders });\n }\n // DenySignal from middleware → HTTP deny status\n if (error instanceof DenySignal) {\n return new Response(null, { status: error.status });\n }\n // Middleware throw → HTTP 500 (middleware runs before rendering,\n // no error boundary to catch it)\n logMiddlewareError({ method, path, error });\n await fireOnRequestError(error, req, 'handler');\n if (onPipelineError && error instanceof Error) onPipelineError(error, 'middleware');\n return new Response(null, { status: 500 });\n }\n }\n\n // Apply cookie jar to response headers before render commits them.\n // Middleware may have set cookies; they need to be on responseHeaders\n // before flushResponse creates the Response object.\n applyCookieJar(responseHeaders);\n\n // Stage 4: Render (access gates + element tree + renderToReadableStream)\n try {\n const renderFn = () =>\n render(req, match, responseHeaders, requestHeaderOverlay, interception);\n const response = await withSpan('timber.render', { 'http.route': canonicalPathname }, () =>\n enableServerTiming\n ? withTiming('render', 'RSC + SSR render', renderFn)\n : renderFn()\n );\n markResponseFlushed();\n return response;\n } catch (error) {\n logRenderError({ method, path, error });\n await fireOnRequestError(error, req, 'render');\n if (onPipelineError && error instanceof Error) onPipelineError(error, 'render');\n // Try fallback error page before bare 500\n if (config.renderFallbackError) {\n try {\n return await config.renderFallbackError(error, req, responseHeaders);\n } catch {\n // Fallback rendering itself failed — fall through to bare 500\n }\n }\n return new Response(null, { status: 500 });\n }\n }\n}\n\n/**\n * Fire the user's onRequestError hook with request context.\n * Extracts request info from the Request object and calls the instrumentation hook.\n */\nasync function fireOnRequestError(\n error: unknown,\n req: Request,\n phase: 'proxy' | 'handler' | 'render' | 'action' | 'route'\n): Promise<void> {\n const url = new URL(req.url);\n const headersObj: Record<string, string> = {};\n req.headers.forEach((v, k) => {\n headersObj[k] = v;\n });\n\n await callOnRequestError(\n error,\n { method: req.method, path: url.pathname, headers: headersObj },\n { phase, routePath: url.pathname, routeType: 'page', traceId: traceId() }\n );\n}\n\n\n\n// ─── Cookie Helpers ──────────────────────────────────────────────────────\n\n/**\n * Apply all Set-Cookie headers from the cookie jar to a Headers object.\n * Each cookie gets its own Set-Cookie header per RFC 6265 §4.1.\n */\nfunction applyCookieJar(headers: Headers): void {\n for (const value of getSetCookieHeaders()) {\n headers.append('Set-Cookie', value);\n }\n}\n\n// ─── Immutable Response Helpers ──────────────────────────────────────────\n\n/**\n * Ensure a Response has mutable headers so the pipeline can safely append\n * Set-Cookie and Server-Timing entries.\n *\n * `Response.redirect()` and some platform-level responses return objects\n * with immutable headers. Calling `.set()` or `.append()` on them throws\n * `TypeError: immutable`. This helper detects the immutable case by\n * attempting a no-op write and, on failure, clones into a fresh Response\n * with mutable headers.\n */\nfunction ensureMutableResponse(response: Response): Response {\n try {\n // Probe mutability with a benign operation that we immediately undo.\n // We pick a header name that is extremely unlikely to collide with\n // anything meaningful and delete it right away.\n response.headers.set('X-Timber-Probe', '1');\n response.headers.delete('X-Timber-Probe');\n return response;\n } catch {\n // Headers are immutable — rebuild with mutable headers.\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: new Headers(response.headers),\n });\n }\n}\n\n\n","/**\n * Build manifest types and utilities for CSS and JS asset tracking.\n *\n * The build manifest maps route segment file paths to their output\n * chunks from Vite's client build. This enables:\n * - <link rel=\"stylesheet\"> injection in HTML <head>\n * - <script type=\"module\"> with hashed URLs in production\n * - <link rel=\"modulepreload\"> for client chunk dependencies\n * - Link preload headers for Early Hints (103)\n *\n * In dev mode, Vite's HMR client handles CSS/JS injection, so the build\n * manifest is empty. In production, it's populated from Vite's\n * .vite/manifest.json after the client build.\n *\n * Design docs: 18-build-system.md §\"Build Manifest\", 02-rendering-pipeline.md §\"Early Hints\"\n */\n\n/** A font asset entry in the build manifest. */\nexport interface ManifestFontEntry {\n /** URL path to the font file (e.g. `/_timber/fonts/inter-latin-400-abc123.woff2`). */\n href: string;\n /** Font format (e.g. `woff2`). */\n format: string;\n /** Crossorigin attribute — always `anonymous` for fonts. */\n crossOrigin: string;\n}\n\n/** Build manifest mapping input file paths to output asset URLs. */\nexport interface BuildManifest {\n /** Map from input file path (relative to project root) to output CSS URLs. */\n css: Record<string, string[]>;\n /** Map from input file path to output JS chunk URL (hashed filename). */\n js: Record<string, string>;\n /** Map from input file path to transitive JS dependency URLs for modulepreload. */\n modulepreload: Record<string, string[]>;\n /** Map from input file path to font assets used by that module. */\n fonts: Record<string, ManifestFontEntry[]>;\n}\n\n/** Empty build manifest used in dev mode. */\nexport const EMPTY_BUILD_MANIFEST: BuildManifest = {\n css: {},\n js: {},\n modulepreload: {},\n fonts: {},\n};\n\n/** Segment shape expected by collectRouteCss (matches ManifestSegmentNode). */\ninterface SegmentWithFiles {\n layout?: { filePath: string };\n page?: { filePath: string };\n}\n\n/**\n * Collect all CSS files needed for a matched route's segment chain.\n *\n * Walks segments root → leaf, collecting CSS for each layout and page.\n * Deduplicates while preserving order (root layout CSS first).\n */\nexport function collectRouteCss(segments: SegmentWithFiles[], manifest: BuildManifest): string[] {\n const seen = new Set<string>();\n const result: string[] = [];\n\n for (const segment of segments) {\n for (const file of [segment.layout, segment.page]) {\n if (!file) continue;\n const cssFiles = manifest.css[file.filePath];\n if (!cssFiles) continue;\n for (const url of cssFiles) {\n if (!seen.has(url)) {\n seen.add(url);\n result.push(url);\n }\n }\n }\n }\n\n return result;\n}\n\n/**\n * Generate <link rel=\"stylesheet\"> tags for CSS URLs.\n *\n * Returns an HTML string to prepend to headHtml for injection\n * via injectHead() before </head>.\n */\nexport function buildCssLinkTags(cssUrls: string[]): string {\n return cssUrls.map((url) => `<link rel=\"stylesheet\" href=\"${url}\">`).join('');\n}\n\n/**\n * Generate a Link header value for CSS preload hints.\n *\n * Cloudflare CDN automatically converts Link headers with rel=preload\n * into 103 Early Hints responses. This avoids platform-specific 103\n * sending code.\n *\n * Example output: `</assets/root.css>; rel=preload; as=style, </assets/page.css>; rel=preload; as=style`\n */\nexport function buildLinkHeaders(cssUrls: string[]): string {\n return cssUrls.map((url) => `<${url}>; rel=preload; as=style`).join(', ');\n}\n\n// ─── Font utilities ──────────────────────────────────────────────────────\n\n/**\n * Collect all font entries needed for a matched route's segment chain.\n *\n * Walks segments root → leaf, collecting fonts for each layout and page.\n * Deduplicates by href while preserving order.\n */\nexport function collectRouteFonts(\n segments: SegmentWithFiles[],\n manifest: BuildManifest\n): ManifestFontEntry[] {\n const seen = new Set<string>();\n const result: ManifestFontEntry[] = [];\n\n for (const segment of segments) {\n for (const file of [segment.layout, segment.page]) {\n if (!file) continue;\n const fonts = manifest.fonts[file.filePath];\n if (!fonts) continue;\n for (const entry of fonts) {\n if (!seen.has(entry.href)) {\n seen.add(entry.href);\n result.push(entry);\n }\n }\n }\n }\n\n return result;\n}\n\n/**\n * Generate <link rel=\"preload\"> tags for font assets.\n *\n * Font preloads use `as=font` and always include `crossorigin` (required\n * for font preloads even for same-origin resources per the spec).\n */\nexport function buildFontPreloadTags(fonts: ManifestFontEntry[]): string {\n return fonts\n .map(\n (f) =>\n `<link rel=\"preload\" href=\"${f.href}\" as=\"font\" type=\"font/${f.format}\" crossorigin=\"${f.crossOrigin}\">`\n )\n .join('');\n}\n\n/**\n * Generate Link header values for font preload hints.\n *\n * Cloudflare CDN converts Link headers with rel=preload into 103 Early Hints.\n *\n * Example: `</fonts/inter.woff2>; rel=preload; as=font; crossorigin`\n */\nexport function buildFontLinkHeaders(fonts: ManifestFontEntry[]): string {\n return fonts.map((f) => `<${f.href}>; rel=preload; as=font; crossorigin`).join(', ');\n}\n\n// ─── JS chunk utilities ──────────────────────────────────────────────────\n\n/**\n * Collect JS chunk URLs for a matched route's segment chain.\n *\n * Walks segments root → leaf, collecting the JS chunk for each layout\n * and page. Deduplicates while preserving order.\n */\nexport function collectRouteJs(segments: SegmentWithFiles[], manifest: BuildManifest): string[] {\n const seen = new Set<string>();\n const result: string[] = [];\n\n for (const segment of segments) {\n for (const file of [segment.layout, segment.page]) {\n if (!file) continue;\n const jsUrl = manifest.js[file.filePath];\n if (!jsUrl) continue;\n if (!seen.has(jsUrl)) {\n seen.add(jsUrl);\n result.push(jsUrl);\n }\n }\n }\n\n return result;\n}\n\n/**\n * Collect modulepreload URLs for a matched route's segment chain.\n *\n * Walks segments root → leaf, collecting transitive JS dependencies\n * for each layout and page. Deduplicates across segments.\n */\nexport function collectRouteModulepreloads(\n segments: SegmentWithFiles[],\n manifest: BuildManifest\n): string[] {\n const seen = new Set<string>();\n const result: string[] = [];\n\n for (const segment of segments) {\n for (const file of [segment.layout, segment.page]) {\n if (!file) continue;\n const preloads = manifest.modulepreload[file.filePath];\n if (!preloads) continue;\n for (const url of preloads) {\n if (!seen.has(url)) {\n seen.add(url);\n result.push(url);\n }\n }\n }\n }\n\n return result;\n}\n\n/**\n * Generate <link rel=\"modulepreload\"> tags for JS dependency URLs.\n *\n * Modulepreload hints tell the browser to fetch and parse JS modules\n * before they're needed, reducing waterfall latency for dynamic imports.\n */\nexport function buildModulepreloadTags(urls: string[]): string {\n return urls.map((url) => `<link rel=\"modulepreload\" href=\"${url}\">`).join('');\n}\n\n/**\n * Generate a <script type=\"module\"> tag for a JS entry point.\n */\nexport function buildEntryScriptTag(url: string): string {\n return `<script type=\"module\" src=\"${url}\"></script>`;\n}\n","/**\n * 103 Early Hints utilities.\n *\n * Early Hints are sent before the final response to let the browser\n * start fetching critical resources (CSS, fonts, JS) while the server\n * is still rendering.\n *\n * The framework collects hints from two sources:\n * 1. Build manifest — CSS, fonts, and JS chunks known at route-match time\n * 2. ctx.earlyHints() — explicit hints added by middleware or route handlers\n *\n * Both are emitted as Link headers. Cloudflare CDN automatically converts\n * Link headers into 103 Early Hints responses.\n *\n * Design docs: 02-rendering-pipeline.md §\"Early Hints (103)\"\n */\n\nimport {\n collectRouteCss,\n collectRouteFonts,\n collectRouteModulepreloads,\n} from './build-manifest.js';\nimport type { BuildManifest } from './build-manifest.js';\n\n/** Minimal segment shape needed for early hint collection. */\ninterface SegmentWithFiles {\n layout?: { filePath: string };\n page?: { filePath: string };\n}\n\n// ─── EarlyHint type ───────────────────────────────────────────────────────\n\n/**\n * A single Link header hint for 103 Early Hints.\n *\n * ```ts\n * ctx.earlyHints([\n * { href: '/styles/critical.css', rel: 'preload', as: 'style' },\n * { href: 'https://fonts.googleapis.com', rel: 'preconnect' },\n * ])\n * ```\n */\nexport interface EarlyHint {\n /** The resource URL (absolute or root-relative). */\n href: string;\n /** Link relation — `preload`, `modulepreload`, or `preconnect`. */\n rel: 'preload' | 'modulepreload' | 'preconnect';\n /** Resource type for `preload` hints (omit for `modulepreload` / `preconnect`). */\n as?: 'style' | 'script' | 'font' | 'image' | 'fetch' | 'document';\n /** Crossorigin attribute — required for font preloads per spec. */\n crossOrigin?: 'anonymous' | 'use-credentials';\n /** Fetch priority hint — `high`, `low`, or `auto`. */\n fetchPriority?: 'high' | 'low' | 'auto';\n}\n\n// ─── formatLinkHeader ─────────────────────────────────────────────────────\n\n/**\n * Format a single EarlyHint as a Link header value.\n *\n * Examples:\n * `</styles/root.css>; rel=preload; as=style`\n * `</fonts/inter.woff2>; rel=preload; as=font; crossorigin=anonymous`\n * `</_timber/client.js>; rel=modulepreload`\n * `<https://fonts.googleapis.com>; rel=preconnect`\n */\nexport function formatLinkHeader(hint: EarlyHint): string {\n let value = `<${hint.href}>; rel=${hint.rel}`;\n if (hint.as !== undefined) value += `; as=${hint.as}`;\n if (hint.crossOrigin !== undefined) value += `; crossorigin=${hint.crossOrigin}`;\n if (hint.fetchPriority !== undefined) value += `; fetchpriority=${hint.fetchPriority}`;\n return value;\n}\n\n// ─── collectEarlyHintHeaders ──────────────────────────────────────────────\n\n/** Options for early hint collection. */\nexport interface EarlyHintOptions {\n /** Skip JS modulepreload hints (e.g. when client JavaScript is disabled). */\n skipJs?: boolean;\n}\n\n/**\n * Collect all Link header strings for a matched route's segment chain.\n *\n * Walks the build manifest to emit hints for:\n * - CSS stylesheets (rel=preload; as=style)\n * - Font assets (rel=preload; as=font; crossorigin)\n * - JS modulepreload hints (rel=modulepreload) — unless skipJs is set\n *\n * Also emits global CSS from the `_global` manifest key. Route files\n * are server components that don't appear in the client bundle, so\n * per-route CSS keying doesn't work with the RSC plugin. The `_global`\n * key contains all CSS assets from the client build — fine for early\n * hints since they're just prefetch signals.\n *\n * Returns formatted Link header strings, deduplicated, root → leaf order.\n * Returns an empty array in dev mode (manifest is empty).\n */\nexport function collectEarlyHintHeaders(\n segments: SegmentWithFiles[],\n manifest: BuildManifest,\n options?: EarlyHintOptions\n): string[] {\n const result: string[] = [];\n const seen = new Set<string>();\n\n const add = (header: string) => {\n if (!seen.has(header)) {\n seen.add(header);\n result.push(header);\n }\n };\n\n // Per-route CSS — rel=preload; as=style\n for (const url of collectRouteCss(segments, manifest)) {\n add(formatLinkHeader({ href: url, rel: 'preload', as: 'style' }));\n }\n\n // Global CSS — all CSS assets from the client bundle.\n // Covers CSS that the RSC plugin injects via data-rsc-css-href,\n // which isn't keyed to route segments in our manifest.\n for (const url of manifest.css['_global'] ?? []) {\n add(formatLinkHeader({ href: url, rel: 'preload', as: 'style' }));\n }\n\n // Fonts — rel=preload; as=font; crossorigin (crossorigin required per spec)\n for (const font of collectRouteFonts(segments, manifest)) {\n add(\n formatLinkHeader({ href: font.href, rel: 'preload', as: 'font', crossOrigin: 'anonymous' })\n );\n }\n\n // JS chunks — rel=modulepreload (skip when client JS is disabled)\n if (!options?.skipJs) {\n for (const url of collectRouteModulepreloads(segments, manifest)) {\n add(formatLinkHeader({ href: url, rel: 'modulepreload' }));\n }\n }\n\n return result;\n}\n","/**\n * Per-request 103 Early Hints sender — ALS bridge for platform adapters.\n *\n * The pipeline collects Link headers for CSS, fonts, and JS chunks at\n * route-match time. On platforms that support it (Node.js v18.11+, Bun),\n * the adapter can send these as a 103 Early Hints interim response before\n * the final response is ready.\n *\n * This module provides an ALS-based bridge: the generated entry point\n * (e.g., the Nitro entry) wraps the handler with `runWithEarlyHintsSender`,\n * binding a per-request sender function. The pipeline calls\n * `sendEarlyHints103()` to fire the 103 if a sender is available.\n *\n * On platforms where 103 is handled at the CDN level (e.g., Cloudflare\n * converts Link headers into 103 automatically), no sender is installed\n * and `sendEarlyHints103()` is a no-op.\n *\n * Design doc: 02-rendering-pipeline.md §\"Early Hints (103)\"\n */\n\nimport { earlyHintsSenderAls } from './als-registry.js';\n\n/** Function that sends Link header values as a 103 Early Hints response. */\nexport type EarlyHintsSenderFn = (links: string[]) => void;\n\n/**\n * Run a function with a per-request early hints sender installed.\n *\n * Called by generated entry points (e.g., Nitro node-server/bun) to\n * bind the platform's writeEarlyHints capability for the request duration.\n */\nexport function runWithEarlyHintsSender<T>(sender: EarlyHintsSenderFn, fn: () => T): T {\n return earlyHintsSenderAls.run(sender, fn);\n}\n\n/**\n * Send collected Link headers as a 103 Early Hints response.\n *\n * No-op if no sender is installed for the current request (e.g., on\n * Cloudflare where the CDN handles 103 automatically, or in dev mode).\n *\n * Non-fatal: errors from the sender are caught and silently ignored.\n */\nexport function sendEarlyHints103(links: string[]): void {\n if (!links.length) return;\n const sender = earlyHintsSenderAls.getStore();\n if (!sender) return;\n try {\n sender(links);\n } catch {\n // Sending 103 is best-effort — failure never blocks the request.\n }\n}\n","/**\n * Element tree construction for timber.js rendering.\n *\n * Builds a unified React element tree from a matched segment chain, bottom-up:\n * page → status-code error boundaries → access gates → layout → repeat up segment chain\n *\n * The tree is rendered via a single `renderToReadableStream` call,\n * giving one `React.cache` scope for the entire route.\n *\n * See design/02-rendering-pipeline.md §\"Element Tree Construction\"\n */\n\nimport type { SegmentNode, RouteFile } from '#/routing/types.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** A loaded module for a route file convention. */\nexport interface LoadedModule {\n /** The default export (component, access function, etc.) */\n default?: unknown;\n /** Named exports (for route.ts method handlers, metadata, etc.) */\n [key: string]: unknown;\n}\n\n/** Function that loads a route file's module. */\nexport type ModuleLoader = (file: RouteFile) => LoadedModule | Promise<LoadedModule>;\n\n/** A React element — kept opaque to avoid a React dependency in this module. */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type ReactElement = any;\n\n/** Function that creates a React element. Matches React.createElement signature. */\nexport type CreateElement = (\n type: unknown,\n props: Record<string, unknown> | null,\n ...children: unknown[]\n) => ReactElement;\n\n/**\n * Resolved slot content for a layout.\n * Key is slot name (without @), value is the element tree for that slot.\n */\nexport type SlotElements = Map<string, ReactElement>;\n\n/** Configuration for the tree builder. */\nexport interface TreeBuilderConfig {\n /** The matched segment chain from root to leaf. */\n segments: SegmentNode[];\n /** Route params extracted by the matcher (catch-all segments produce string[]). */\n params: Record<string, string | string[]>;\n /** Parsed search params (typed or URLSearchParams). */\n searchParams: unknown;\n /** Loads a route file's module. */\n loadModule: ModuleLoader;\n /** React.createElement or equivalent. */\n createElement: CreateElement;\n /**\n * Error boundary component for wrapping segments.\n *\n * This is injected by the caller rather than imported directly to avoid\n * pulling 'use client' code into the server barrel (@timber-js/app/server).\n * In the RSC environment, the RSC plugin transforms this import to a\n * client reference proxy — the caller handles the import so the server\n * barrel stays free of client dependencies.\n */\n errorBoundaryComponent?: unknown;\n}\n\n// ─── Component wrappers ──────────────────────────────────────────────────────\n\n/**\n * Framework-injected access gate component.\n *\n * When `verdict` is provided (from the pre-render pass), AccessGate replays\n * the stored result synchronously — no re-execution, no async, immune to\n * Suspense timing. When `verdict` is absent, falls back to calling `accessFn`\n * (backward compat for tree-builder.ts which doesn't run a pre-render pass).\n */\nexport interface AccessGateProps {\n accessFn: (ctx: { params: Record<string, string | string[]>; searchParams: unknown }) => unknown;\n params: Record<string, string | string[]>;\n searchParams: unknown;\n /** Segment name for dev logging (e.g. \"authenticated\", \"dashboard\"). */\n segmentName?: string;\n /**\n * Pre-computed verdict from the pre-render pass. When set, AccessGate\n * replays this verdict synchronously instead of calling accessFn.\n * - 'pass': render children\n * - DenySignal/RedirectSignal: throw synchronously\n */\n verdict?:\n | 'pass'\n | import('./primitives.js').DenySignal\n | import('./primitives.js').RedirectSignal;\n children: ReactElement;\n}\n\n/**\n * Framework-injected slot access gate component.\n * On denial, renders denied.tsx → default.tsx → null instead of failing the page.\n */\nexport interface SlotAccessGateProps {\n accessFn: (ctx: { params: Record<string, string | string[]>; searchParams: unknown }) => unknown;\n params: Record<string, string | string[]>;\n searchParams: unknown;\n deniedFallback: ReactElement | null;\n defaultFallback: ReactElement | null;\n children: ReactElement;\n}\n\n/**\n * Framework-injected error boundary wrapper.\n * Wraps content with status-code error boundary handling.\n */\nexport interface ErrorBoundaryProps {\n fallbackComponent: ReactElement | null;\n status?: number;\n children: ReactElement;\n}\n\n// ─── Tree Builder ────────────────────────────────────────────────────────────\n\n/**\n * Result of building the element tree.\n */\nexport interface TreeBuildResult {\n /** The root React element tree ready for renderToReadableStream. */\n tree: ReactElement;\n /** Whether the leaf segment is a route.ts (API endpoint) rather than a page. */\n isApiRoute: boolean;\n}\n\n/**\n * Build the unified element tree from a matched segment chain.\n *\n * Construction is bottom-up:\n * 1. Start with the page component (leaf segment)\n * 2. Wrap in status-code error boundaries (fallback chain)\n * 3. Wrap in AccessGate (if segment has access.ts)\n * 4. Pass as children to the segment's layout\n * 5. Repeat up the segment chain to root\n *\n * Parallel slots are resolved at each layout level and composed as named props.\n */\nexport async function buildElementTree(config: TreeBuilderConfig): Promise<TreeBuildResult> {\n const { segments, params, searchParams, loadModule, createElement, errorBoundaryComponent } =\n config;\n\n if (segments.length === 0) {\n throw new Error('[timber] buildElementTree: empty segment chain');\n }\n\n const leaf = segments[segments.length - 1];\n\n // API routes (route.ts) don't build a React tree\n if (leaf.route && !leaf.page) {\n return { tree: null, isApiRoute: true };\n }\n\n // Start with the page component\n const pageModule = leaf.page ? await loadModule(leaf.page) : null;\n const PageComponent = pageModule?.default as ((...args: unknown[]) => ReactElement) | undefined;\n\n if (!PageComponent) {\n throw new Error(\n `[timber] No page component found for route at ${leaf.urlPath}. ` +\n 'Each route must have a page.tsx or route.ts.'\n );\n }\n\n // Build the page element with params and searchParams props\n let element: ReactElement = createElement(PageComponent, { params, searchParams });\n\n // Build tree bottom-up: wrap page, then walk segments from leaf to root\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i];\n\n // Wrap in error boundaries (status-code files + error.tsx)\n element = await wrapWithErrorBoundaries(\n segment,\n element,\n loadModule,\n createElement,\n errorBoundaryComponent\n );\n\n // Wrap in AccessGate if segment has access.ts\n if (segment.access) {\n const accessModule = await loadModule(segment.access);\n const accessFn = accessModule.default as AccessGateProps['accessFn'];\n element = createElement('timber:access-gate', {\n accessFn,\n params,\n searchParams,\n segmentName: segment.segmentName,\n children: element,\n } satisfies AccessGateProps);\n }\n\n // Wrap in layout (if exists and not the leaf's page-level wrapping)\n if (segment.layout) {\n const layoutModule = await loadModule(segment.layout);\n const LayoutComponent = layoutModule.default as\n | ((...args: unknown[]) => ReactElement)\n | undefined;\n\n if (LayoutComponent) {\n // Resolve parallel slots for this layout\n const slotProps: Record<string, ReactElement> = {};\n if (segment.slots.size > 0) {\n for (const [slotName, slotNode] of segment.slots) {\n slotProps[slotName] = await buildSlotElement(\n slotNode,\n params,\n searchParams,\n loadModule,\n createElement,\n errorBoundaryComponent\n );\n }\n }\n\n element = createElement(LayoutComponent, {\n ...slotProps,\n params,\n searchParams,\n children: element,\n });\n }\n }\n }\n\n return { tree: element, isApiRoute: false };\n}\n\n// ─── Slot Element Builder ────────────────────────────────────────────────────\n\n/**\n * Build the element tree for a parallel slot.\n *\n * Slots have their own access.ts (SlotAccessGate) and error boundaries.\n * On access denial: denied.tsx → default.tsx → null (graceful degradation).\n */\nasync function buildSlotElement(\n slotNode: SegmentNode,\n params: Record<string, string | string[]>,\n searchParams: unknown,\n loadModule: ModuleLoader,\n createElement: CreateElement,\n errorBoundaryComponent: unknown\n): Promise<ReactElement> {\n // Load slot page\n const pageModule = slotNode.page ? await loadModule(slotNode.page) : null;\n const PageComponent = pageModule?.default as ((...args: unknown[]) => ReactElement) | undefined;\n\n // Load default.tsx fallback\n const defaultModule = slotNode.default ? await loadModule(slotNode.default) : null;\n const DefaultComponent = defaultModule?.default as\n | ((...args: unknown[]) => ReactElement)\n | undefined;\n\n // If no page, render default.tsx or null\n if (!PageComponent) {\n return DefaultComponent ? createElement(DefaultComponent, { params, searchParams }) : null;\n }\n\n let element: ReactElement = createElement(PageComponent, { params, searchParams });\n\n // Wrap in error boundaries\n element = await wrapWithErrorBoundaries(\n slotNode,\n element,\n loadModule,\n createElement,\n errorBoundaryComponent\n );\n\n // Wrap in SlotAccessGate if slot has access.ts\n if (slotNode.access) {\n const accessModule = await loadModule(slotNode.access);\n const accessFn = accessModule.default as SlotAccessGateProps['accessFn'];\n\n // Load denied.tsx\n const deniedModule = slotNode.denied ? await loadModule(slotNode.denied) : null;\n const DeniedComponent = deniedModule?.default as\n | ((...args: unknown[]) => ReactElement)\n | undefined;\n\n const deniedFallback = DeniedComponent\n ? createElement(DeniedComponent, {\n slot: slotNode.segmentName.replace(/^@/, ''),\n dangerouslyPassData: undefined,\n })\n : null;\n const defaultFallback = DefaultComponent\n ? createElement(DefaultComponent, { params, searchParams })\n : null;\n\n element = createElement('timber:slot-access-gate', {\n accessFn,\n params,\n searchParams,\n deniedFallback,\n defaultFallback,\n children: element,\n } satisfies SlotAccessGateProps);\n }\n\n return element;\n}\n\n// ─── Error Boundary Wrapping ─────────────────────────────────────────────────\n\n/**\n * Wrap an element with error boundaries from a segment's status-code files.\n *\n * Wrapping order (innermost to outermost):\n * 1. Specific status files (503.tsx, 429.tsx, etc.)\n * 2. Category catch-alls (4xx.tsx, 5xx.tsx)\n * 3. error.tsx (general error boundary)\n *\n * This creates the fallback chain described in design/10-error-handling.md.\n */\nasync function wrapWithErrorBoundaries(\n segment: SegmentNode,\n element: ReactElement,\n loadModule: ModuleLoader,\n createElement: CreateElement,\n errorBoundaryComponent: unknown\n): Promise<ReactElement> {\n // Wrapping is applied inside-out. The last wrap call produces the outermost boundary.\n // Order: specific status → category → error.tsx (outermost)\n\n if (segment.statusFiles) {\n // Wrap with specific status files (innermost — highest priority at runtime)\n for (const [key, file] of segment.statusFiles) {\n if (key !== '4xx' && key !== '5xx') {\n const status = parseInt(key, 10);\n if (!isNaN(status)) {\n const mod = await loadModule(file);\n const Component = mod.default;\n if (Component) {\n element = createElement(errorBoundaryComponent, {\n fallbackComponent: Component,\n status,\n children: element,\n } satisfies ErrorBoundaryProps);\n }\n }\n }\n }\n\n // Wrap with category catch-alls (4xx.tsx, 5xx.tsx)\n for (const [key, file] of segment.statusFiles) {\n if (key === '4xx' || key === '5xx') {\n const mod = await loadModule(file);\n const Component = mod.default;\n if (Component) {\n element = createElement(errorBoundaryComponent, {\n fallbackComponent: Component,\n status: key === '4xx' ? 400 : 500, // category marker\n children: element,\n } satisfies ErrorBoundaryProps);\n }\n }\n }\n }\n\n // Wrap with error.tsx (outermost — catches anything not matched by status files)\n if (segment.error) {\n const errorModule = await loadModule(segment.error);\n const ErrorComponent = errorModule.default;\n if (ErrorComponent) {\n element = createElement(errorBoundaryComponent, {\n fallbackComponent: ErrorComponent,\n children: element,\n } satisfies ErrorBoundaryProps);\n }\n }\n\n return element;\n}\n","/**\n * AccessGate and SlotAccessGate — framework-injected async server components.\n *\n * AccessGate wraps each segment's layout in the element tree. It calls the\n * segment's access.ts before the layout renders. If access.ts calls deny()\n * or redirect(), the signal propagates as a render-phase throw — caught by\n * the flush controller to produce the correct HTTP status code.\n *\n * SlotAccessGate wraps parallel slot content. On denial, it renders the\n * graceful degradation chain: denied.tsx → default.tsx → null. Slot denial\n * does not affect the HTTP status code.\n *\n * See design/04-authorization.md and design/02-rendering-pipeline.md §\"AccessGate\"\n */\n\nimport { DenySignal, RedirectSignal } from './primitives.js';\nimport type { AccessGateProps, SlotAccessGateProps, ReactElement } from './tree-builder.js';\nimport { withSpan, setSpanAttribute } from './tracing.js';\n\n// ─── AccessGate ─────────────────────────────────────────────────────────────\n\n/**\n * Framework-injected access gate for segments.\n *\n * When a pre-computed `verdict` prop is provided (from the pre-render pass\n * in route-element-builder.ts), AccessGate replays it synchronously — no\n * async, no re-execution of access.ts, immune to Suspense timing. The OTEL\n * span was already emitted during the pre-render pass.\n *\n * When no verdict is provided (backward compat with tree-builder.ts),\n * AccessGate calls accessFn directly with OTEL instrumentation.\n *\n * access.ts is a pure gate — return values are discarded. The layout below\n * gets the same data by calling the same cached functions (React.cache dedup).\n */\nexport function AccessGate(props: AccessGateProps): ReactElement | Promise<ReactElement> {\n const { accessFn, params, searchParams, segmentName, verdict, children } = props;\n\n // Fast path: replay pre-computed verdict from the pre-render pass.\n // This is synchronous — Suspense boundaries cannot interfere with the\n // status code because the signal throws before any async work.\n if (verdict !== undefined) {\n if (verdict === 'pass') {\n return children;\n }\n // Throw the stored DenySignal or RedirectSignal synchronously.\n // React catches this as a render-phase throw — the flush controller\n // produces the correct HTTP status code.\n throw verdict;\n }\n\n // Fallback: call accessFn directly (used by tree-builder.ts which\n // doesn't run a pre-render pass, and for backward compat).\n return accessGateFallback(accessFn, params, searchParams, segmentName, children);\n}\n\n/**\n * Async fallback for AccessGate when no pre-computed verdict is available.\n * Calls accessFn with OTEL instrumentation.\n */\nasync function accessGateFallback(\n accessFn: AccessGateProps['accessFn'],\n params: AccessGateProps['params'],\n searchParams: AccessGateProps['searchParams'],\n segmentName: AccessGateProps['segmentName'],\n children: ReactElement\n): Promise<ReactElement> {\n await withSpan('timber.access', { 'timber.segment': segmentName ?? 'unknown' }, async () => {\n try {\n await accessFn({ params, searchParams });\n await setSpanAttribute('timber.result', 'pass');\n } catch (error: unknown) {\n if (error instanceof DenySignal) {\n await setSpanAttribute('timber.result', 'deny');\n await setSpanAttribute('timber.deny_status', error.status);\n if (error.sourceFile) {\n await setSpanAttribute('timber.deny_file', error.sourceFile);\n }\n } else if (error instanceof RedirectSignal) {\n await setSpanAttribute('timber.result', 'redirect');\n }\n throw error;\n }\n });\n\n return children;\n}\n\n// ─── SlotAccessGate ─────────────────────────────────────────────────────────\n\n/**\n * Framework-injected access gate for parallel slots.\n *\n * On denial, graceful degradation: denied.tsx → default.tsx → null.\n * The HTTP status code is unaffected — slot denial is a UI concern, not\n * a protocol concern. The parent layout and sibling slots still render.\n *\n * redirect() in slot access.ts is a dev-mode error — redirecting from a\n * slot doesn't make architectural sense.\n */\nexport async function SlotAccessGate(props: SlotAccessGateProps): Promise<ReactElement> {\n const { accessFn, params, searchParams, deniedFallback, defaultFallback, children } = props;\n\n try {\n await accessFn({ params, searchParams });\n } catch (error: unknown) {\n // DenySignal → graceful degradation (denied.tsx → default.tsx → null)\n if (error instanceof DenySignal) {\n return deniedFallback ?? defaultFallback ?? null;\n }\n\n // RedirectSignal in slot access → dev-mode error.\n // Slot access should use deny(), not redirect(). Redirecting from a\n // slot would redirect the entire page, which breaks the contract that\n // slot failure is graceful degradation.\n if (error instanceof RedirectSignal) {\n if (process.env.NODE_ENV !== 'production') {\n console.error(\n '[timber] redirect() is not allowed in slot access.ts. ' +\n 'Slots use deny() for graceful degradation — denied.tsx → default.tsx → null. ' +\n \"If you need to redirect, move the logic to the parent segment's access.ts.\"\n );\n }\n // In production, treat as a deny — render fallback rather than crash.\n return deniedFallback ?? defaultFallback ?? null;\n }\n\n // Unhandled error — re-throw so error boundaries can catch it.\n // Dev-mode warning: slot access should use deny(), not throw.\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[timber] Unhandled error in slot access.ts. ' +\n 'Use deny() for access control, not unhandled throws.',\n error\n );\n }\n throw error;\n }\n\n // Access passed — render slot content.\n return children;\n}\n","/**\n * Status-code file resolver for timber.js error/denial rendering.\n *\n * Given an HTTP status code and a matched segment chain, resolves the\n * correct file to render by walking the fallback chain described in\n * design/10-error-handling.md §\"Status-Code Files\".\n *\n * Supports two format families:\n * - 'component' (default): .tsx/.jsx/.mdx status files → React rendering pipeline\n * - 'json': .json status files → raw JSON response, no React\n *\n * Fallback chains operate within the same format family (no cross-format fallback).\n *\n * **Component chain (4xx):**\n * Pass 1 — status files (leaf → root): {status}.tsx → 4xx.tsx\n * Pass 2 — legacy compat (leaf → root): not-found.tsx / forbidden.tsx / unauthorized.tsx\n * Pass 3 — error.tsx (leaf → root)\n * Pass 4 — framework default (returns null)\n *\n * **JSON chain (4xx):**\n * Pass 1 — json status files (leaf → root): {status}.json → 4xx.json\n * Pass 2 — framework default JSON (returns null, caller provides bare JSON)\n *\n * **5xx (component only):**\n * Per-segment (leaf → root): {status}.tsx → 5xx.tsx → error.tsx\n * Then global-error.tsx (future)\n * Then framework default (returns null)\n */\n\nimport type { SegmentNode, RouteFile } from '#/routing/types.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** How the status-code file was matched. */\nexport type StatusFileKind =\n | 'exact' // e.g. 403.tsx matched status 403\n | 'category' // e.g. 4xx.tsx matched status 403\n | 'legacy' // e.g. not-found.tsx matched status 404\n | 'error'; // error.tsx as last resort\n\n/** Response format family for status-code resolution. */\nexport type StatusFileFormat = 'component' | 'json';\n\n/** Result of resolving a status-code file for a segment chain. */\nexport interface StatusFileResolution {\n /** The matched route file. */\n file: RouteFile;\n /** The HTTP status code (always the original status, not the file's code). */\n status: number;\n /** How the file was matched. */\n kind: StatusFileKind;\n /** Index into the segments array where the file was found. */\n segmentIndex: number;\n}\n\n/** How a slot denial file was matched. */\nexport type SlotDeniedKind = 'denied' | 'default';\n\n/** Result of resolving a slot denied file. */\nexport interface SlotDeniedResolution {\n /** The matched route file (denied.tsx or default.tsx). */\n file: RouteFile;\n /** Slot name without @ prefix. */\n slotName: string;\n /** How the file was matched. */\n kind: SlotDeniedKind;\n}\n\n// ─── Legacy Compat Mapping ───────────────────────────────────────────────────\n\n/**\n * Maps legacy file convention names to their corresponding HTTP status codes.\n * Only used in the 4xx component fallback chain.\n */\nconst LEGACY_FILE_TO_STATUS: Record<string, number> = {\n 'not-found': 404,\n 'forbidden': 403,\n 'unauthorized': 401,\n};\n\n// ─── Resolver ────────────────────────────────────────────────────────────────\n\n/**\n * Resolve the status-code file to render for a given HTTP status code.\n *\n * Walks the segment chain from leaf to root following the fallback chain\n * defined in design/10-error-handling.md. Returns null if no file is found\n * (caller should render the framework default).\n *\n * @param status - The HTTP status code (4xx or 5xx).\n * @param segments - The matched segment chain from root (index 0) to leaf (last).\n * @param format - The response format family ('component' or 'json'). Defaults to 'component'.\n */\nexport function resolveStatusFile(\n status: number,\n segments: ReadonlyArray<SegmentNode>,\n format: StatusFileFormat = 'component'\n): StatusFileResolution | null {\n if (status >= 400 && status <= 499) {\n return format === 'json' ? resolve4xxJson(status, segments) : resolve4xx(status, segments);\n }\n if (status >= 500 && status <= 599) {\n // JSON format for 5xx uses the same json chain pattern\n return format === 'json' ? resolve5xxJson(status, segments) : resolve5xx(status, segments);\n }\n return null;\n}\n\n/**\n * 4xx component fallback chain (three separate passes):\n * Pass 1 — status files (leaf → root): {status}.tsx → 4xx.tsx\n * Pass 2 — legacy compat (leaf → root): not-found.tsx / forbidden.tsx / unauthorized.tsx\n * Pass 3 — error.tsx (leaf → root)\n */\nfunction resolve4xx(\n status: number,\n segments: ReadonlyArray<SegmentNode>\n): StatusFileResolution | null {\n const statusStr = String(status);\n\n // Pass 1: status files across all segments (leaf → root)\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i];\n if (!segment.statusFiles) continue;\n\n // Exact match first\n const exact = segment.statusFiles.get(statusStr);\n if (exact) {\n return { file: exact, status, kind: 'exact', segmentIndex: i };\n }\n\n // Category catch-all\n const category = segment.statusFiles.get('4xx');\n if (category) {\n return { file: category, status, kind: 'category', segmentIndex: i };\n }\n }\n\n // Pass 2: legacy compat files (leaf → root)\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i];\n if (!segment.legacyStatusFiles) continue;\n\n for (const [name, legacyStatus] of Object.entries(LEGACY_FILE_TO_STATUS)) {\n if (legacyStatus === status) {\n const file = segment.legacyStatusFiles.get(name);\n if (file) {\n return { file, status, kind: 'legacy', segmentIndex: i };\n }\n }\n }\n }\n\n // Pass 3: error.tsx (leaf → root)\n for (let i = segments.length - 1; i >= 0; i--) {\n if (segments[i].error) {\n return { file: segments[i].error!, status, kind: 'error', segmentIndex: i };\n }\n }\n\n return null;\n}\n\n/**\n * 4xx JSON fallback chain (single pass):\n * Pass 1 — json status files (leaf → root): {status}.json → 4xx.json\n * No legacy compat, no error.tsx — JSON chain terminates at category catch-all.\n */\nfunction resolve4xxJson(\n status: number,\n segments: ReadonlyArray<SegmentNode>\n): StatusFileResolution | null {\n const statusStr = String(status);\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i];\n if (!segment.jsonStatusFiles) continue;\n\n // Exact match first\n const exact = segment.jsonStatusFiles.get(statusStr);\n if (exact) {\n return { file: exact, status, kind: 'exact', segmentIndex: i };\n }\n\n // Category catch-all\n const category = segment.jsonStatusFiles.get('4xx');\n if (category) {\n return { file: category, status, kind: 'category', segmentIndex: i };\n }\n }\n\n return null;\n}\n\n/**\n * 5xx component fallback chain (single pass, per-segment):\n * At each segment (leaf → root): {status}.tsx → 5xx.tsx → error.tsx\n */\nfunction resolve5xx(\n status: number,\n segments: ReadonlyArray<SegmentNode>\n): StatusFileResolution | null {\n const statusStr = String(status);\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i];\n\n // Exact status file\n if (segment.statusFiles) {\n const exact = segment.statusFiles.get(statusStr);\n if (exact) {\n return { file: exact, status, kind: 'exact', segmentIndex: i };\n }\n\n // Category catch-all\n const category = segment.statusFiles.get('5xx');\n if (category) {\n return { file: category, status, kind: 'category', segmentIndex: i };\n }\n }\n\n // error.tsx at this segment level (for 5xx, checked per-segment)\n if (segment.error) {\n return { file: segment.error, status, kind: 'error', segmentIndex: i };\n }\n }\n\n return null;\n}\n\n/**\n * 5xx JSON fallback chain (single pass):\n * At each segment (leaf → root): {status}.json → 5xx.json\n * No error.tsx equivalent — JSON chain terminates at category catch-all.\n */\nfunction resolve5xxJson(\n status: number,\n segments: ReadonlyArray<SegmentNode>\n): StatusFileResolution | null {\n const statusStr = String(status);\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i];\n if (!segment.jsonStatusFiles) continue;\n\n const exact = segment.jsonStatusFiles.get(statusStr);\n if (exact) {\n return { file: exact, status, kind: 'exact', segmentIndex: i };\n }\n\n const category = segment.jsonStatusFiles.get('5xx');\n if (category) {\n return { file: category, status, kind: 'category', segmentIndex: i };\n }\n }\n\n return null;\n}\n\n// ─── Slot Denied Resolver ────────────────────────────────────────────────────\n\n/**\n * Resolve the denial file for a parallel route slot.\n *\n * Slot denial is graceful degradation — no HTTP status on the wire.\n * Fallback chain: denied.tsx → default.tsx → null.\n *\n * @param slotNode - The segment node for the slot (segmentType === 'slot').\n */\nexport function resolveSlotDenied(slotNode: SegmentNode): SlotDeniedResolution | null {\n const slotName = slotNode.segmentName.replace(/^@/, '');\n\n if (slotNode.denied) {\n return { file: slotNode.denied, slotName, kind: 'denied' };\n }\n\n if (slotNode.default) {\n return { file: slotNode.default, slotName, kind: 'default' };\n }\n\n return null;\n}\n","/**\n * Flush controller for timber.js rendering.\n *\n * Holds the response until `onShellReady` fires, then commits the HTTP status\n * code and flushes the shell. Render-phase signals (deny, redirect, unhandled\n * throws) caught before flush produce correct HTTP status codes.\n *\n * See design/02-rendering-pipeline.md §\"The Flush Point\" and §\"The Hold Window\"\n */\n\nimport { DenySignal, RedirectSignal, RenderError } from './primitives.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** The readable stream from React's renderToReadableStream. */\nexport interface ReactRenderStream {\n /** The underlying ReadableStream of HTML bytes. */\n readable: ReadableStream<Uint8Array>;\n /** Resolves when the shell has finished rendering (all non-Suspense content). */\n allReady?: Promise<void>;\n}\n\n/** Options for the flush controller. */\nexport interface FlushOptions {\n /** Response headers to include (from middleware.ts, proxy.ts, etc.). */\n responseHeaders?: Headers;\n /** Default status code when rendering succeeds. Default: 200. */\n defaultStatus?: number;\n}\n\n/** Result of the flush process. */\nexport interface FlushResult {\n /** The final HTTP Response. */\n response: Response;\n /** The status code committed. */\n status: number;\n /** Whether the response was a redirect. */\n isRedirect: boolean;\n /** Whether the response was a denial. */\n isDenial: boolean;\n}\n\n// ─── Render Function Type ────────────────────────────────────────────────────\n\n/**\n * A function that performs the React render.\n *\n * The flush controller calls this, catches any signals thrown during the\n * synchronous shell render (before onShellReady), and produces the\n * correct HTTP response.\n *\n * Must return an object with:\n * - `stream`: The ReadableStream from renderToReadableStream\n * - `shellReady`: A Promise that resolves when onShellReady fires\n */\nexport interface RenderResult {\n /** The HTML byte stream. */\n stream: ReadableStream<Uint8Array>;\n /** Resolves when the shell is ready (all non-Suspense content rendered). */\n shellReady: Promise<void>;\n}\n\nexport type RenderFn = () => RenderResult | Promise<RenderResult>;\n\n// ─── Flush Controller ────────────────────────────────────────────────────────\n\n/**\n * Execute a render and hold the response until the shell is ready.\n *\n * The flush controller:\n * 1. Calls the render function to start renderToReadableStream\n * 2. Waits for shellReady (onShellReady)\n * 3. If a render-phase signal was thrown (deny, redirect, error), produces\n * the correct HTTP status code\n * 4. If the shell rendered successfully, commits the status and streams\n *\n * Render-phase signals caught before flush:\n * - `DenySignal` → HTTP 4xx with appropriate status code\n * - `RedirectSignal` → HTTP 3xx with Location header\n * - `RenderError` → HTTP status from error (default 500)\n * - Unhandled error → HTTP 500\n *\n * @param renderFn - Function that starts the React render.\n * @param options - Flush configuration.\n * @returns The committed HTTP Response.\n */\nexport async function flushResponse(\n renderFn: RenderFn,\n options: FlushOptions = {}\n): Promise<FlushResult> {\n const { responseHeaders = new Headers(), defaultStatus = 200 } = options;\n\n let renderResult: RenderResult;\n\n // Phase 1: Start the render. The render function may throw synchronously\n // if there's an immediate error before React even starts.\n try {\n renderResult = await renderFn();\n } catch (error) {\n return handleSignal(error, responseHeaders);\n }\n\n // Phase 2: Wait for onShellReady. Render-phase signals (deny, redirect,\n // throws outside Suspense) are caught here.\n try {\n await renderResult.shellReady;\n } catch (error) {\n return handleSignal(error, responseHeaders);\n }\n\n // Phase 3: Shell rendered successfully. Commit status and stream.\n responseHeaders.set('Content-Type', 'text/html; charset=utf-8');\n\n return {\n response: new Response(renderResult.stream, {\n status: defaultStatus,\n headers: responseHeaders,\n }),\n status: defaultStatus,\n isRedirect: false,\n isDenial: false,\n };\n}\n\n// ─── Signal Handling ─────────────────────────────────────────────────────────\n\n/**\n * Handle a render-phase signal and produce the correct HTTP response.\n */\nfunction handleSignal(error: unknown, responseHeaders: Headers): FlushResult {\n // Redirect signal → HTTP 3xx\n if (error instanceof RedirectSignal) {\n responseHeaders.set('Location', error.location);\n return {\n response: new Response(null, {\n status: error.status,\n headers: responseHeaders,\n }),\n status: error.status,\n isRedirect: true,\n isDenial: false,\n };\n }\n\n // Deny signal → HTTP 4xx\n if (error instanceof DenySignal) {\n return {\n response: new Response(null, {\n status: error.status,\n headers: responseHeaders,\n }),\n status: error.status,\n isRedirect: false,\n isDenial: true,\n };\n }\n\n // RenderError → HTTP status from error\n if (error instanceof RenderError) {\n return {\n response: new Response(null, {\n status: error.status,\n headers: responseHeaders,\n }),\n status: error.status,\n isRedirect: false,\n isDenial: false,\n };\n }\n\n // Unknown error → HTTP 500\n console.error('[timber] Unhandled render-phase error:', error);\n return {\n response: new Response(null, {\n status: 500,\n headers: responseHeaders,\n }),\n status: 500,\n isRedirect: false,\n isDenial: false,\n };\n}\n","/**\n * CSRF protection — Origin header validation.\n *\n * Auto-derived from the Host header for single-origin deployments.\n * Configurable via allowedOrigins for multi-origin setups.\n * Disable with csrf: false (not recommended outside local dev).\n *\n * See design/08-forms-and-actions.md §\"CSRF Protection\"\n * See design/13-security.md §\"Security Testing Checklist\" #6\n */\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\nexport interface CsrfConfig {\n /** Explicit list of allowed origins. Replaces Host-based auto-derivation. */\n allowedOrigins?: string[];\n /** Set to false to disable CSRF validation entirely. */\n csrf?: boolean;\n}\n\nexport type CsrfResult = { ok: true } | { ok: false; status: 403 };\n\n// ─── Constants ────────────────────────────────────────────────────────────\n\n/** HTTP methods that are considered safe (no mutation). */\nconst SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);\n\n// ─── Implementation ───────────────────────────────────────────────────────\n\n/**\n * Validate the Origin header against the request's Host.\n *\n * For mutation methods (POST, PUT, PATCH, DELETE):\n * - If `csrf: false`, skip validation.\n * - If `allowedOrigins` is set, Origin must match one exactly (no wildcards).\n * - Otherwise, Origin's host must match the request's Host header.\n *\n * Safe methods (GET, HEAD, OPTIONS) always pass.\n */\nexport function validateCsrf(req: Request, config: CsrfConfig): CsrfResult {\n // Safe methods don't need CSRF protection\n if (SAFE_METHODS.has(req.method)) {\n return { ok: true };\n }\n\n // Explicitly disabled\n if (config.csrf === false) {\n return { ok: true };\n }\n\n const origin = req.headers.get('Origin');\n\n // No Origin header on a mutation → reject\n if (!origin) {\n return { ok: false, status: 403 };\n }\n\n // If allowedOrigins is configured, use that instead of Host-based derivation\n if (config.allowedOrigins) {\n const allowed = config.allowedOrigins.includes(origin);\n return allowed ? { ok: true } : { ok: false, status: 403 };\n }\n\n // Auto-derive from Host header\n const host = req.headers.get('Host');\n if (!host) {\n return { ok: false, status: 403 };\n }\n\n // Extract hostname from Origin URL and compare to Host header\n let originHost: string;\n try {\n originHost = new URL(origin).host;\n } catch {\n return { ok: false, status: 403 };\n }\n\n return originHost === host ? { ok: true } : { ok: false, status: 403 };\n}\n","/**\n * Request body size limits — returns 413 when exceeded.\n * See design/08-forms-and-actions.md §\"FormData Limits\"\n */\n\nexport interface BodyLimitsConfig {\n limits?: {\n actionBodySize?: string;\n uploadBodySize?: string;\n maxFields?: number;\n };\n}\n\nexport type BodyLimitResult = { ok: true } | { ok: false; status: 411 | 413 };\n\nexport type BodyKind = 'action' | 'upload';\n\nconst KB = 1024;\nconst MB = 1024 * KB;\nconst GB = 1024 * MB;\n\nexport const DEFAULT_LIMITS = {\n actionBodySize: 1 * MB,\n uploadBodySize: 10 * MB,\n maxFields: 100,\n} as const;\n\nconst SIZE_PATTERN = /^(\\d+(?:\\.\\d+)?)\\s*(kb|mb|gb)?$/i;\n\n/** Parse a human-readable size string (\"1mb\", \"512kb\", \"1024\") into bytes. */\nexport function parseBodySize(size: string): number {\n const match = SIZE_PATTERN.exec(size.trim());\n if (!match) {\n throw new Error(\n `Invalid body size format: \"${size}\". Expected format like \"1mb\", \"512kb\", or \"1024\".`\n );\n }\n\n const value = Number.parseFloat(match[1]);\n const unit = (match[2] ?? '').toLowerCase();\n\n switch (unit) {\n case 'kb':\n return Math.floor(value * KB);\n case 'mb':\n return Math.floor(value * MB);\n case 'gb':\n return Math.floor(value * GB);\n case '':\n return Math.floor(value);\n default:\n throw new Error(`Unknown size unit: \"${unit}\"`);\n }\n}\n\n/** Check whether a request body exceeds the configured size limit (stateless, no ALS). */\nexport function enforceBodyLimits(\n req: Request,\n kind: BodyKind,\n config: BodyLimitsConfig\n): BodyLimitResult {\n const contentLength = req.headers.get('Content-Length');\n if (!contentLength) {\n // Reject requests without Content-Length — prevents body limit bypass via\n // chunked transfer-encoding. Browsers always send Content-Length for form POSTs.\n return { ok: false, status: 411 };\n }\n\n const bodySize = Number.parseInt(contentLength, 10);\n if (Number.isNaN(bodySize)) {\n return { ok: false, status: 411 };\n }\n\n const limit = resolveLimit(kind, config);\n return bodySize <= limit ? { ok: true } : { ok: false, status: 413 };\n}\n\n/** Check whether a FormData payload exceeds the configured field count limit. */\nexport function enforceFieldLimit(formData: FormData, config: BodyLimitsConfig): BodyLimitResult {\n const maxFields = config.limits?.maxFields ?? DEFAULT_LIMITS.maxFields;\n // Count unique keys — FormData.keys() yields duplicates for multi-value fields,\n // so we use a Set to count distinct field names.\n const fieldCount = new Set(formData.keys()).size;\n return fieldCount <= maxFields ? { ok: true } : { ok: false, status: 413 };\n}\n\n/**\n * Resolve the byte limit for a given body kind, using config overrides or defaults.\n */\nfunction resolveLimit(kind: BodyKind, config: BodyLimitsConfig): number {\n const userLimits = config.limits;\n\n if (kind === 'action') {\n return userLimits?.actionBodySize\n ? parseBodySize(userLimits.actionBodySize)\n : DEFAULT_LIMITS.actionBodySize;\n }\n\n return userLimits?.uploadBodySize\n ? parseBodySize(userLimits.uploadBodySize)\n : DEFAULT_LIMITS.uploadBodySize;\n}\n","/**\n * Social metadata rendering — Open Graph and Twitter Card meta tags.\n *\n * Extracted from metadata-render.ts to keep files under 500 lines.\n *\n * See design/16-metadata.md\n */\n\nimport type { Metadata } from './types.js';\nimport type { HeadElement } from './metadata.js';\n\n/**\n * Render Open Graph metadata into head element descriptors.\n *\n * Handles og:title, og:description, og:image (with dimensions/alt),\n * og:video, og:audio, og:article:author, and other OG properties.\n */\nexport function renderOpenGraph(og: NonNullable<Metadata['openGraph']>, elements: HeadElement[]): void {\n const simpleProps: Array<[string, string | undefined]> = [\n ['og:title', og.title],\n ['og:description', og.description],\n ['og:url', og.url],\n ['og:site_name', og.siteName],\n ['og:locale', og.locale],\n ['og:type', og.type],\n ['og:article:published_time', og.publishedTime],\n ['og:article:modified_time', og.modifiedTime],\n ];\n\n for (const [property, content] of simpleProps) {\n if (content) {\n elements.push({ tag: 'meta', attrs: { property, content } });\n }\n }\n\n // Images — normalize single object to array for uniform handling\n if (og.images) {\n if (typeof og.images === 'string') {\n elements.push({ tag: 'meta', attrs: { property: 'og:image', content: og.images } });\n } else {\n const imgList = Array.isArray(og.images) ? og.images : [og.images];\n for (const img of imgList) {\n elements.push({ tag: 'meta', attrs: { property: 'og:image', content: img.url } });\n if (img.width) {\n elements.push({\n tag: 'meta',\n attrs: { property: 'og:image:width', content: String(img.width) },\n });\n }\n if (img.height) {\n elements.push({\n tag: 'meta',\n attrs: { property: 'og:image:height', content: String(img.height) },\n });\n }\n if (img.alt) {\n elements.push({ tag: 'meta', attrs: { property: 'og:image:alt', content: img.alt } });\n }\n }\n }\n }\n\n // Videos\n if (og.videos) {\n for (const video of og.videos) {\n elements.push({ tag: 'meta', attrs: { property: 'og:video', content: video.url } });\n }\n }\n\n // Audio\n if (og.audio) {\n for (const audio of og.audio) {\n elements.push({ tag: 'meta', attrs: { property: 'og:audio', content: audio.url } });\n }\n }\n\n // Authors\n if (og.authors) {\n for (const author of og.authors) {\n elements.push({\n tag: 'meta',\n attrs: { property: 'og:article:author', content: author },\n });\n }\n }\n}\n\n/**\n * Render Twitter Card metadata into head element descriptors.\n *\n * Handles twitter:card, twitter:site, twitter:title, twitter:image,\n * twitter:player, and twitter:app (per-platform name/id/url).\n */\nexport function renderTwitter(tw: NonNullable<Metadata['twitter']>, elements: HeadElement[]): void {\n const simpleProps: Array<[string, string | undefined]> = [\n ['twitter:card', tw.card],\n ['twitter:site', tw.site],\n ['twitter:site:id', tw.siteId],\n ['twitter:title', tw.title],\n ['twitter:description', tw.description],\n ['twitter:creator', tw.creator],\n ['twitter:creator:id', tw.creatorId],\n ];\n\n for (const [name, content] of simpleProps) {\n if (content) {\n elements.push({ tag: 'meta', attrs: { name, content } });\n }\n }\n\n // Images — normalize single object to array for uniform handling\n if (tw.images) {\n if (typeof tw.images === 'string') {\n elements.push({ tag: 'meta', attrs: { name: 'twitter:image', content: tw.images } });\n } else {\n const imgList = Array.isArray(tw.images) ? tw.images : [tw.images];\n for (const img of imgList) {\n const url = typeof img === 'string' ? img : img.url;\n elements.push({ tag: 'meta', attrs: { name: 'twitter:image', content: url } });\n }\n }\n }\n\n // Player card fields\n if (tw.players) {\n for (const player of tw.players) {\n elements.push({ tag: 'meta', attrs: { name: 'twitter:player', content: player.playerUrl } });\n if (player.width) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'twitter:player:width', content: String(player.width) },\n });\n }\n if (player.height) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'twitter:player:height', content: String(player.height) },\n });\n }\n if (player.streamUrl) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'twitter:player:stream', content: player.streamUrl },\n });\n }\n }\n }\n\n // App card fields — 3 platforms × 3 attributes (name, id, url)\n if (tw.app) {\n const platforms: Array<[keyof NonNullable<typeof tw.app.id>, string]> = [\n ['iPhone', 'iphone'],\n ['iPad', 'ipad'],\n ['googlePlay', 'googleplay'],\n ];\n\n // App name is shared across platforms but the spec uses per-platform names.\n // Emit for each platform that has an ID.\n if (tw.app.name) {\n for (const [key, tag] of platforms) {\n if (tw.app.id?.[key]) {\n elements.push({\n tag: 'meta',\n attrs: { name: `twitter:app:name:${tag}`, content: tw.app.name },\n });\n }\n }\n }\n\n for (const [key, tag] of platforms) {\n const id = tw.app.id?.[key];\n if (id) {\n elements.push({ tag: 'meta', attrs: { name: `twitter:app:id:${tag}`, content: id } });\n }\n }\n\n for (const [key, tag] of platforms) {\n const url = tw.app.url?.[key];\n if (url) {\n elements.push({ tag: 'meta', attrs: { name: `twitter:app:url:${tag}`, content: url } });\n }\n }\n }\n}\n","/**\n * Platform-specific metadata rendering — icons, Apple Web App, App Links, iTunes.\n *\n * Extracted from metadata-render.ts to keep files under 500 lines.\n *\n * See design/16-metadata.md\n */\n\nimport type { Metadata } from './types.js';\nimport type { HeadElement } from './metadata.js';\n\n/**\n * Render icon link elements (favicon, shortcut, apple-touch-icon, custom).\n */\nexport function renderIcons(icons: NonNullable<Metadata['icons']>, elements: HeadElement[]): void {\n // Icon\n if (icons.icon) {\n if (typeof icons.icon === 'string') {\n elements.push({ tag: 'link', attrs: { rel: 'icon', href: icons.icon } });\n } else if (Array.isArray(icons.icon)) {\n for (const icon of icons.icon) {\n const attrs: Record<string, string> = { rel: 'icon', href: icon.url };\n if (icon.sizes) attrs.sizes = icon.sizes;\n if (icon.type) attrs.type = icon.type;\n elements.push({ tag: 'link', attrs });\n }\n }\n }\n\n // Shortcut\n if (icons.shortcut) {\n const urls = Array.isArray(icons.shortcut) ? icons.shortcut : [icons.shortcut];\n for (const url of urls) {\n elements.push({ tag: 'link', attrs: { rel: 'shortcut icon', href: url } });\n }\n }\n\n // Apple\n if (icons.apple) {\n if (typeof icons.apple === 'string') {\n elements.push({ tag: 'link', attrs: { rel: 'apple-touch-icon', href: icons.apple } });\n } else if (Array.isArray(icons.apple)) {\n for (const icon of icons.apple) {\n const attrs: Record<string, string> = { rel: 'apple-touch-icon', href: icon.url };\n if (icon.sizes) attrs.sizes = icon.sizes;\n elements.push({ tag: 'link', attrs });\n }\n }\n }\n\n // Other\n if (icons.other) {\n for (const icon of icons.other) {\n const attrs: Record<string, string> = { rel: icon.rel, href: icon.url };\n if (icon.sizes) attrs.sizes = icon.sizes;\n if (icon.type) attrs.type = icon.type;\n elements.push({ tag: 'link', attrs });\n }\n }\n}\n\n/**\n * Render alternate link elements (canonical, hreflang, media, types).\n */\nexport function renderAlternates(\n alternates: NonNullable<Metadata['alternates']>,\n elements: HeadElement[]\n): void {\n if (alternates.canonical) {\n elements.push({ tag: 'link', attrs: { rel: 'canonical', href: alternates.canonical } });\n }\n\n if (alternates.languages) {\n for (const [lang, href] of Object.entries(alternates.languages)) {\n elements.push({\n tag: 'link',\n attrs: { rel: 'alternate', hreflang: lang, href },\n });\n }\n }\n\n if (alternates.media) {\n for (const [media, href] of Object.entries(alternates.media)) {\n elements.push({\n tag: 'link',\n attrs: { rel: 'alternate', media, href },\n });\n }\n }\n\n if (alternates.types) {\n for (const [type, href] of Object.entries(alternates.types)) {\n elements.push({\n tag: 'link',\n attrs: { rel: 'alternate', type, href },\n });\n }\n }\n}\n\n/**\n * Render site verification meta tags (Google, Yahoo, Yandex, custom).\n */\nexport function renderVerification(\n verification: NonNullable<Metadata['verification']>,\n elements: HeadElement[]\n): void {\n const verificationProps: Array<[string, string | undefined]> = [\n ['google-site-verification', verification.google],\n ['y_key', verification.yahoo],\n ['yandex-verification', verification.yandex],\n ];\n\n for (const [name, content] of verificationProps) {\n if (content) {\n elements.push({ tag: 'meta', attrs: { name, content } });\n }\n }\n if (verification.other) {\n for (const [name, value] of Object.entries(verification.other)) {\n const content = Array.isArray(value) ? value.join(', ') : value;\n elements.push({ tag: 'meta', attrs: { name, content } });\n }\n }\n}\n\n/**\n * Render Apple Web App meta tags and startup image links.\n */\nexport function renderAppleWebApp(\n appleWebApp: NonNullable<Metadata['appleWebApp']>,\n elements: HeadElement[]\n): void {\n if (appleWebApp.capable) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'apple-mobile-web-app-capable', content: 'yes' },\n });\n }\n if (appleWebApp.title) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'apple-mobile-web-app-title', content: appleWebApp.title },\n });\n }\n if (appleWebApp.statusBarStyle) {\n elements.push({\n tag: 'meta',\n attrs: {\n name: 'apple-mobile-web-app-status-bar-style',\n content: appleWebApp.statusBarStyle,\n },\n });\n }\n if (appleWebApp.startupImage) {\n const images = Array.isArray(appleWebApp.startupImage)\n ? appleWebApp.startupImage\n : [{ url: appleWebApp.startupImage }];\n for (const img of images) {\n const url = typeof img === 'string' ? img : img.url;\n const attrs: Record<string, string> = { rel: 'apple-touch-startup-image', href: url };\n if (typeof img === 'object' && img.media) {\n attrs.media = img.media;\n }\n elements.push({ tag: 'link', attrs });\n }\n }\n}\n\n/**\n * Render App Links (al:*) meta tags for deep linking across platforms.\n */\nexport function renderAppLinks(\n appLinks: NonNullable<Metadata['appLinks']>,\n elements: HeadElement[]\n): void {\n const platformEntries: Array<[string, Array<Record<string, unknown>> | undefined]> = [\n ['ios', appLinks.ios],\n ['android', appLinks.android],\n ['windows', appLinks.windows],\n ['windows_phone', appLinks.windowsPhone],\n ['windows_universal', appLinks.windowsUniversal],\n ];\n\n for (const [platform, entries] of platformEntries) {\n if (!entries) continue;\n for (const entry of entries) {\n for (const [key, value] of Object.entries(entry)) {\n if (value !== undefined && value !== null) {\n elements.push({\n tag: 'meta',\n attrs: { property: `al:${platform}:${key}`, content: String(value) },\n });\n }\n }\n }\n }\n\n if (appLinks.web) {\n if (appLinks.web.url) {\n elements.push({\n tag: 'meta',\n attrs: { property: 'al:web:url', content: appLinks.web.url },\n });\n }\n if (appLinks.web.shouldFallback !== undefined) {\n elements.push({\n tag: 'meta',\n attrs: {\n property: 'al:web:should_fallback',\n content: appLinks.web.shouldFallback ? 'true' : 'false',\n },\n });\n }\n }\n}\n\n/**\n * Render Apple iTunes smart banner meta tag.\n */\nexport function renderItunes(itunes: NonNullable<Metadata['itunes']>, elements: HeadElement[]): void {\n const parts = [`app-id=${itunes.appId}`];\n if (itunes.affiliateData) parts.push(`affiliate-data=${itunes.affiliateData}`);\n if (itunes.appArgument) parts.push(`app-argument=${itunes.appArgument}`);\n elements.push({\n tag: 'meta',\n attrs: { name: 'apple-itunes-app', content: parts.join(', ') },\n });\n}\n","/**\n * Metadata rendering — converts resolved Metadata into HeadElement descriptors.\n *\n * Extracted from metadata.ts to keep files under 500 lines.\n *\n * See design/16-metadata.md\n */\n\nimport type { Metadata } from './types.js';\nimport type { HeadElement } from './metadata.js';\nimport { renderOpenGraph, renderTwitter } from './metadata-social.js';\nimport {\n renderIcons,\n renderAlternates,\n renderVerification,\n renderAppleWebApp,\n renderAppLinks,\n renderItunes,\n} from './metadata-platform.js';\n\n// ─── Render to Elements ──────────────────────────────────────────────────────\n\n/**\n * Convert resolved metadata into an array of head element descriptors.\n *\n * Each descriptor has a `tag` ('title', 'meta', 'link') and either\n * `content` (for <title>) or `attrs` (for <meta>/<link>).\n *\n * The framework's MetadataResolver component consumes these descriptors\n * and renders them into the <head>.\n */\nexport function renderMetadataToElements(metadata: Metadata): HeadElement[] {\n const elements: HeadElement[] = [];\n\n // Title\n if (typeof metadata.title === 'string') {\n elements.push({ tag: 'title', content: metadata.title });\n }\n\n // Simple string meta tags\n const simpleMetaProps: Array<[string, string | undefined]> = [\n ['description', metadata.description],\n ['generator', metadata.generator],\n ['application-name', metadata.applicationName],\n ['referrer', metadata.referrer],\n ['category', metadata.category],\n ['creator', metadata.creator],\n ['publisher', metadata.publisher],\n ];\n\n for (const [name, content] of simpleMetaProps) {\n if (content) {\n elements.push({ tag: 'meta', attrs: { name, content } });\n }\n }\n\n // Keywords (array or string)\n if (metadata.keywords) {\n const content = Array.isArray(metadata.keywords)\n ? metadata.keywords.join(', ')\n : metadata.keywords;\n elements.push({ tag: 'meta', attrs: { name: 'keywords', content } });\n }\n\n // Robots\n if (metadata.robots) {\n const content =\n typeof metadata.robots === 'string' ? metadata.robots : renderRobotsObject(metadata.robots);\n elements.push({ tag: 'meta', attrs: { name: 'robots', content } });\n\n // googleBot as separate tag\n if (typeof metadata.robots === 'object' && metadata.robots.googleBot) {\n const gbContent =\n typeof metadata.robots.googleBot === 'string'\n ? metadata.robots.googleBot\n : renderRobotsObject(metadata.robots.googleBot);\n elements.push({ tag: 'meta', attrs: { name: 'googlebot', content: gbContent } });\n }\n }\n\n // Open Graph\n if (metadata.openGraph) {\n renderOpenGraph(metadata.openGraph, elements);\n }\n\n // Twitter\n if (metadata.twitter) {\n renderTwitter(metadata.twitter, elements);\n }\n\n // Icons\n if (metadata.icons) {\n renderIcons(metadata.icons, elements);\n }\n\n // Manifest\n if (metadata.manifest) {\n elements.push({ tag: 'link', attrs: { rel: 'manifest', href: metadata.manifest } });\n }\n\n // Alternates\n if (metadata.alternates) {\n renderAlternates(metadata.alternates, elements);\n }\n\n // Verification\n if (metadata.verification) {\n renderVerification(metadata.verification, elements);\n }\n\n // Format detection\n if (metadata.formatDetection) {\n const parts: string[] = [];\n if (metadata.formatDetection.telephone === false) parts.push('telephone=no');\n if (metadata.formatDetection.email === false) parts.push('email=no');\n if (metadata.formatDetection.address === false) parts.push('address=no');\n if (parts.length > 0) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'format-detection', content: parts.join(', ') },\n });\n }\n }\n\n // Authors\n if (metadata.authors) {\n const authorList = Array.isArray(metadata.authors) ? metadata.authors : [metadata.authors];\n for (const author of authorList) {\n if (author.name) {\n elements.push({ tag: 'meta', attrs: { name: 'author', content: author.name } });\n }\n if (author.url) {\n elements.push({ tag: 'link', attrs: { rel: 'author', href: author.url } });\n }\n }\n }\n\n // Apple Web App\n if (metadata.appleWebApp) {\n renderAppleWebApp(metadata.appleWebApp, elements);\n }\n\n // App Links (al:*)\n if (metadata.appLinks) {\n renderAppLinks(metadata.appLinks, elements);\n }\n\n // iTunes\n if (metadata.itunes) {\n renderItunes(metadata.itunes, elements);\n }\n\n // Other (custom meta tags)\n if (metadata.other) {\n for (const [name, value] of Object.entries(metadata.other)) {\n const content = Array.isArray(value) ? value.join(', ') : value;\n elements.push({ tag: 'meta', attrs: { name, content } });\n }\n }\n\n return elements;\n}\n\n// ─── Rendering Helpers ───────────────────────────────────────────────────────\n\nfunction renderRobotsObject(robots: Record<string, unknown>): string {\n const parts: string[] = [];\n if (robots.index === true) parts.push('index');\n if (robots.index === false) parts.push('noindex');\n if (robots.follow === true) parts.push('follow');\n if (robots.follow === false) parts.push('nofollow');\n return parts.join(', ');\n}\n","/**\n * Metadata resolution for timber.js.\n *\n * Resolves metadata from a segment chain (layouts + page), applies title\n * templates, shallow-merges entries, and produces head element descriptors.\n *\n * Resolution happens inside the render pass — React.cache is active,\n * metadata is outside Suspense, and the flush point guarantees completeness.\n *\n * Rendering (Metadata → HeadElement[]) is in metadata-render.ts.\n *\n * See design/16-metadata.md\n */\n\nimport type { Metadata } from './types.js';\n\n// Re-export renderMetadataToElements from the rendering module so existing\n// consumers (route-element-builder, tests) can keep importing from here.\nexport { renderMetadataToElements } from './metadata-render.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** A single metadata entry from a layout or page module. */\nexport interface SegmentMetadataEntry {\n /** The resolved metadata object (from static or async `metadata` export). */\n metadata: Metadata;\n /** Whether this entry is from the page (leaf) module. */\n isPage: boolean;\n}\n\n/** Options for resolveMetadata. */\nexport interface ResolveMetadataOptions {\n /**\n * When true, the page's metadata is discarded (simulating a render error)\n * and `<meta name=\"robots\" content=\"noindex\">` is injected.\n */\n errorState?: boolean;\n}\n\n/** A rendered head element descriptor. */\nexport interface HeadElement {\n tag: 'title' | 'meta' | 'link';\n content?: string;\n attrs?: Record<string, string>;\n}\n\n// ─── Title Resolution ────────────────────────────────────────────────────────\n\n/**\n * Resolve a title value with an optional template.\n *\n * - string → apply template if present\n * - { absolute: '...' } → use as-is, skip template\n * - { default: '...' } → use as fallback (no template applied)\n * - undefined → undefined\n */\nexport function resolveTitle(\n title: Metadata['title'],\n template: string | undefined\n): string | undefined {\n if (title === undefined || title === null) {\n return undefined;\n }\n\n if (typeof title === 'string') {\n return template ? template.replace('%s', title) : title;\n }\n\n // Object form\n if (title.absolute !== undefined) {\n return title.absolute;\n }\n\n if (title.default !== undefined) {\n return title.default;\n }\n\n return undefined;\n}\n\n// ─── Metadata Resolution ─────────────────────────────────────────────────────\n\n/**\n * Resolve metadata from a segment chain.\n *\n * Processes entries from root layout to page (in segment order).\n * The merge algorithm:\n * 1. Shallow-merge all keys except title (later wins)\n * 2. Track the most recent title template\n * 3. Resolve the final title using the template\n *\n * In error state, the page entry is dropped and noindex is injected.\n *\n * See design/16-metadata.md §\"Merge Algorithm\"\n */\nexport function resolveMetadata(\n entries: SegmentMetadataEntry[],\n options: ResolveMetadataOptions = {}\n): Metadata {\n const { errorState = false } = options;\n\n const merged: Metadata = {};\n let titleTemplate: string | undefined;\n let lastDefault: string | undefined;\n let rawTitle: Metadata['title'];\n\n for (const { metadata, isPage } of entries) {\n // In error state, skip the page's metadata entirely\n if (errorState && isPage) {\n continue;\n }\n\n // Track title template\n if (metadata.title !== undefined && typeof metadata.title === 'object') {\n if (metadata.title.template !== undefined) {\n titleTemplate = metadata.title.template;\n }\n if (metadata.title.default !== undefined) {\n lastDefault = metadata.title.default;\n }\n }\n\n // Shallow-merge all keys except title\n for (const key of Object.keys(metadata) as Array<keyof Metadata>) {\n if (key === 'title') continue;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (merged as any)[key] = metadata[key];\n }\n\n // Track raw title (will be resolved after the loop)\n if (metadata.title !== undefined) {\n rawTitle = metadata.title;\n }\n }\n\n // In error state, we lost page title — use the most recent default\n if (errorState) {\n rawTitle = lastDefault !== undefined ? { default: lastDefault } : rawTitle;\n // Don't apply template in error state\n titleTemplate = undefined;\n }\n\n // Resolve the final title\n const resolvedTitle = resolveTitle(rawTitle, titleTemplate);\n if (resolvedTitle !== undefined) {\n merged.title = resolvedTitle;\n }\n\n // Error state: inject noindex, overriding any user robots\n if (errorState) {\n merged.robots = 'noindex';\n }\n\n return merged;\n}\n\n// ─── URL Resolution ──────────────────────────────────────────────────────────\n\n/**\n * Check if a string is an absolute URL.\n */\nfunction isAbsoluteUrl(url: string): boolean {\n return url.startsWith('http://') || url.startsWith('https://') || url.startsWith('//');\n}\n\n/**\n * Resolve a relative URL against a base URL.\n */\nfunction resolveUrl(url: string, base: URL): string {\n if (isAbsoluteUrl(url)) return url;\n return new URL(url, base).toString();\n}\n\n/**\n * Resolve relative URLs in metadata fields against metadataBase.\n *\n * Returns a new metadata object with URLs resolved. Absolute URLs are not modified.\n * If metadataBase is not set, returns the metadata unchanged.\n */\nexport function resolveMetadataUrls(metadata: Metadata): Metadata {\n const base = metadata.metadataBase;\n if (!base) return metadata;\n\n const result = { ...metadata };\n\n // Resolve openGraph images\n if (result.openGraph) {\n result.openGraph = { ...result.openGraph };\n if (typeof result.openGraph.images === 'string') {\n result.openGraph.images = resolveUrl(result.openGraph.images, base);\n } else if (Array.isArray(result.openGraph.images)) {\n result.openGraph.images = result.openGraph.images.map((img) => ({\n ...img,\n url: resolveUrl(img.url, base),\n }));\n } else if (result.openGraph.images) {\n // Single object: { url, width?, height?, alt? }\n result.openGraph.images = {\n ...result.openGraph.images,\n url: resolveUrl(result.openGraph.images.url, base),\n };\n }\n if (result.openGraph.url && !isAbsoluteUrl(result.openGraph.url)) {\n result.openGraph.url = resolveUrl(result.openGraph.url, base);\n }\n }\n\n // Resolve twitter images\n if (result.twitter) {\n result.twitter = { ...result.twitter };\n if (typeof result.twitter.images === 'string') {\n result.twitter.images = resolveUrl(result.twitter.images, base);\n } else if (Array.isArray(result.twitter.images)) {\n // Resolve each image URL, preserving the union type structure\n const resolved = result.twitter.images.map((img) =>\n typeof img === 'string' ? resolveUrl(img, base) : { ...img, url: resolveUrl(img.url, base) }\n );\n // If all entries are strings, assign as string[]; otherwise as object[]\n const allStrings = resolved.every((r) => typeof r === 'string');\n result.twitter.images = allStrings\n ? (resolved as string[])\n : (resolved as Array<{ url: string; alt?: string; width?: number; height?: number }>);\n } else if (result.twitter.images) {\n // Single object: { url, alt?, width?, height? }\n result.twitter.images = {\n ...result.twitter.images,\n url: resolveUrl(result.twitter.images.url, base),\n };\n }\n }\n\n // Resolve alternates\n if (result.alternates) {\n result.alternates = { ...result.alternates };\n if (result.alternates.canonical && !isAbsoluteUrl(result.alternates.canonical)) {\n result.alternates.canonical = resolveUrl(result.alternates.canonical, base);\n }\n if (result.alternates.languages) {\n const langs: Record<string, string> = {};\n for (const [lang, url] of Object.entries(result.alternates.languages)) {\n langs[lang] = isAbsoluteUrl(url) ? url : resolveUrl(url, base);\n }\n result.alternates.languages = langs;\n }\n }\n\n // Resolve icon URLs\n if (result.icons) {\n result.icons = { ...result.icons };\n if (typeof result.icons.icon === 'string') {\n result.icons.icon = resolveUrl(result.icons.icon, base);\n } else if (Array.isArray(result.icons.icon)) {\n result.icons.icon = result.icons.icon.map((i) => ({ ...i, url: resolveUrl(i.url, base) }));\n }\n if (typeof result.icons.apple === 'string') {\n result.icons.apple = resolveUrl(result.icons.apple, base);\n } else if (Array.isArray(result.icons.apple)) {\n result.icons.apple = result.icons.apple.map((i) => ({ ...i, url: resolveUrl(i.url, base) }));\n }\n }\n\n return result;\n}\n","/**\n * FormData preprocessing — schema-agnostic conversion of FormData to typed objects.\n *\n * FormData is all strings. Schema validation expects typed values. This module\n * bridges the gap with intelligent coercion that runs *before* schema validation.\n *\n * Inspired by zod-form-data, but schema-agnostic — works with any Standard Schema\n * library (Zod, Valibot, ArkType).\n *\n * See design/08-forms-and-actions.md §\"parseFormData() and coerce helpers\"\n */\n\n// ─── parseFormData ───────────────────────────────────────────────────────\n\n/**\n * Convert FormData into a plain object with intelligent coercion.\n *\n * Handles:\n * - **Duplicate keys → arrays**: `tags=js&tags=ts` → `{ tags: [\"js\", \"ts\"] }`\n * - **Nested dot-paths**: `user.name=Alice` → `{ user: { name: \"Alice\" } }`\n * - **Empty strings → undefined**: Enables `.optional()` semantics in schemas\n * - **Empty Files → undefined**: File inputs with no selection become `undefined`\n * - **Strips `$ACTION_*` fields**: React's internal hidden fields are excluded\n */\nexport function parseFormData(formData: FormData): Record<string, unknown> {\n const flat: Record<string, unknown> = {};\n\n for (const key of new Set(formData.keys())) {\n // Skip React internal fields\n if (key.startsWith('$ACTION_')) continue;\n\n const values = formData.getAll(key);\n const processed = values.map(normalizeValue);\n\n if (processed.length === 1) {\n flat[key] = processed[0];\n } else {\n // Filter out undefined entries from multi-value fields\n flat[key] = processed.filter((v) => v !== undefined);\n }\n }\n\n // Expand dot-notation paths into nested objects\n return expandDotPaths(flat);\n}\n\n/**\n * Normalize a single FormData entry value.\n * - Empty strings → undefined (enables .optional() semantics)\n * - Empty File objects (no selection) → undefined\n * - Everything else passes through as-is\n */\nfunction normalizeValue(value: FormDataEntryValue): unknown {\n if (typeof value === 'string') {\n return value === '' ? undefined : value;\n }\n\n // File input with no selection: browsers submit a File with name=\"\" and size=0\n if (value instanceof File && value.size === 0 && value.name === '') {\n return undefined;\n }\n\n return value;\n}\n\n/**\n * Expand dot-notation keys into nested objects.\n * `{ \"user.name\": \"Alice\", \"user.age\": \"30\" }` → `{ user: { name: \"Alice\", age: \"30\" } }`\n *\n * Keys without dots are left as-is. Bracket notation (e.g. `items[0]`) is NOT\n * supported — use dot notation (`items.0`) instead.\n */\nfunction expandDotPaths(flat: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n let hasDotPaths = false;\n\n // First pass: check if any keys have dots\n for (const key of Object.keys(flat)) {\n if (key.includes('.')) {\n hasDotPaths = true;\n break;\n }\n }\n\n // Fast path: no dot-notation keys, return as-is\n if (!hasDotPaths) return flat;\n\n for (const [key, value] of Object.entries(flat)) {\n if (!key.includes('.')) {\n result[key] = value;\n continue;\n }\n\n const parts = key.split('.');\n let current: Record<string, unknown> = result;\n\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i];\n if (current[part] === undefined || current[part] === null) {\n current[part] = {};\n }\n // If current[part] is not an object (e.g., a string from a non-dotted key),\n // the dot-path takes precedence\n if (typeof current[part] !== 'object' || current[part] instanceof File) {\n current[part] = {};\n }\n current = current[part] as Record<string, unknown>;\n }\n\n current[parts[parts.length - 1]] = value;\n }\n\n return result;\n}\n\n// ─── Coercion Helpers ────────────────────────────────────────────────────\n\n/**\n * Schema-agnostic coercion primitives for common FormData patterns.\n *\n * These are plain transform functions — they compose with any schema library's\n * `transform`/`preprocess` pipeline:\n *\n * ```ts\n * // Zod\n * z.preprocess(coerce.number, z.number())\n * // Valibot\n * v.pipe(v.unknown(), v.transform(coerce.number), v.number())\n * ```\n */\nexport const coerce = {\n /**\n * Coerce a string to a number.\n * - `\"42\"` → `42`\n * - `\"3.14\"` → `3.14`\n * - `\"\"` / `undefined` / `null` → `undefined`\n * - Non-numeric strings → `undefined` (schema validation will catch this)\n */\n number(value: unknown): number | undefined {\n if (value === undefined || value === null || value === '') return undefined;\n if (typeof value === 'number') return value;\n if (typeof value !== 'string') return undefined;\n const num = Number(value);\n if (Number.isNaN(num)) return undefined;\n return num;\n },\n\n /**\n * Coerce a checkbox value to a boolean.\n * HTML checkboxes submit \"on\" when checked and are absent when unchecked.\n * - `\"on\"` / any truthy string → `true`\n * - `undefined` / `null` / `\"\"` → `false`\n */\n checkbox(value: unknown): boolean {\n if (value === undefined || value === null || value === '') return false;\n if (typeof value === 'boolean') return value;\n // Any non-empty string (typically \"on\") is true\n return typeof value === 'string' && value.length > 0;\n },\n\n /**\n * Parse a JSON string into an object.\n * - Valid JSON string → parsed object\n * - `\"\"` / `undefined` / `null` → `undefined`\n * - Invalid JSON → `undefined` (schema validation will catch this)\n */\n json(value: unknown): unknown {\n if (value === undefined || value === null || value === '') return undefined;\n if (typeof value !== 'string') return value;\n try {\n return JSON.parse(value);\n } catch {\n return undefined;\n }\n },\n};\n","/**\n * createActionClient — typed middleware and schema validation for server actions.\n *\n * Inspired by next-safe-action. Provides a builder API:\n * createActionClient({ middleware }) → .schema(z.object(...)) → .action(fn)\n *\n * The resulting action function satisfies both:\n * 1. Direct call: action(input) → Promise<ActionResult>\n * 2. React useActionState: (prevState, formData) => Promise<ActionResult>\n *\n * See design/08-forms-and-actions.md §\"Middleware for Server Actions\"\n */\n\n// ─── ActionError ─────────────────────────────────────────────────────────\n\n/**\n * Typed error class for server actions. Carries a string code and optional data.\n * When thrown from middleware or the action body, the action short-circuits and\n * the client receives `result.serverError`.\n *\n * In production, unexpected errors (non-ActionError) return `{ code: 'INTERNAL_ERROR' }`\n * with no message. In dev, `data.message` is included.\n */\nexport class ActionError<TCode extends string = string> extends Error {\n readonly code: TCode;\n readonly data: Record<string, unknown> | undefined;\n\n constructor(code: TCode, data?: Record<string, unknown>) {\n super(`ActionError: ${code}`);\n this.name = 'ActionError';\n this.code = code;\n this.data = data;\n }\n}\n\n// ─── Standard Schema ──────────────────────────────────────────────────────\n\n/**\n * Standard Schema v1 interface (subset).\n * Zod ≥3.24, Valibot ≥1.0, and ArkType all implement this.\n * See https://github.com/standard-schema/standard-schema\n *\n * We use permissive types here to accept all compliant libraries without\n * requiring exact structural matches on issues/path shapes.\n */\ninterface StandardSchemaV1<Output = unknown> {\n '~standard': {\n validate(value: unknown): StandardSchemaResult<Output> | Promise<StandardSchemaResult<Output>>;\n };\n}\n\ntype StandardSchemaResult<Output> =\n | { value: Output; issues?: undefined }\n | { value?: undefined; issues: ReadonlyArray<StandardSchemaIssue> };\n\ninterface StandardSchemaIssue {\n message: string;\n path?: ReadonlyArray<PropertyKey | { key: PropertyKey }>;\n}\n\n/** Check if a schema implements the Standard Schema protocol. */\nfunction isStandardSchema(schema: unknown): schema is StandardSchemaV1 {\n return (\n typeof schema === 'object' &&\n schema !== null &&\n '~standard' in schema &&\n typeof (schema as StandardSchemaV1)['~standard'].validate === 'function'\n );\n}\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\n/**\n * Minimal schema interface — compatible with Zod, Valibot, ArkType, etc.\n *\n * Accepts either:\n * - Standard Schema (preferred): any object with `~standard.validate()`\n * - Legacy parse interface: objects with `.parse()` / `.safeParse()`\n *\n * At runtime, Standard Schema is detected via `~standard` property and\n * takes priority over the legacy interface.\n */\nexport type ActionSchema<T = unknown> = StandardSchemaV1<T> | LegacyActionSchema<T>;\n\n/** Legacy schema interface with .parse() / .safeParse(). */\ninterface LegacyActionSchema<T = unknown> {\n 'parse'(data: unknown): T;\n 'safeParse'?(data: unknown): { success: true; data: T } | { success: false; error: SchemaError };\n // Exclude Standard Schema objects from matching this interface\n '~standard'?: never;\n}\n\n/** Schema validation error shape (for legacy .safeParse()/.parse() interface). */\nexport interface SchemaError {\n issues?: Array<{ path?: Array<string | number>; message: string }>;\n flatten?(): { fieldErrors: Record<string, string[]> };\n}\n\n/** Flattened validation errors keyed by field name. */\nexport type ValidationErrors = Record<string, string[]>;\n\n/** Middleware function: returns context to merge into the action body's ctx. */\nexport type ActionMiddleware<TCtx = Record<string, unknown>> = () => Promise<TCtx> | TCtx;\n\n/** The result type returned to the client. */\nexport type ActionResult<TData = unknown> =\n | { data: TData; validationErrors?: never; serverError?: never; submittedValues?: never }\n | {\n data?: never;\n validationErrors: ValidationErrors;\n serverError?: never;\n /** Raw input values on validation failure — for repopulating form fields. */\n submittedValues?: Record<string, unknown>;\n }\n | {\n data?: never;\n validationErrors?: never;\n serverError: { code: string; data?: Record<string, unknown> };\n submittedValues?: never;\n };\n\n/** Context passed to the action body. */\nexport interface ActionContext<TCtx, TInput> {\n ctx: TCtx;\n input: TInput;\n}\n\n// ─── Builder ─────────────────────────────────────────────────────────────\n\ninterface ActionClientConfig<TCtx> {\n middleware?: ActionMiddleware<TCtx> | ActionMiddleware<Record<string, unknown>>[];\n /** Max file size in bytes. Files exceeding this are rejected with validation errors. */\n fileSizeLimit?: number;\n}\n\n/** Intermediate builder returned by createActionClient(). */\nexport interface ActionBuilder<TCtx> {\n /** Declare the input schema. Validation errors are returned typed. */\n schema<TInput>(schema: ActionSchema<TInput>): ActionBuilderWithSchema<TCtx, TInput>;\n /** Define the action body without input validation. */\n action<TData>(fn: (ctx: ActionContext<TCtx, undefined>) => Promise<TData>): ActionFn<TData>;\n}\n\n/** Builder after .schema() has been called. */\nexport interface ActionBuilderWithSchema<TCtx, TInput> {\n /** Define the action body with validated input. */\n action<TData>(fn: (ctx: ActionContext<TCtx, TInput>) => Promise<TData>): ActionFn<TData>;\n}\n\n/**\n * The final action function. Callable two ways:\n * - Direct: action(input) → Promise<ActionResult<TData>>\n * - React useActionState: action(prevState, formData) → Promise<ActionResult<TData>>\n */\nexport type ActionFn<TData> = {\n (input?: unknown): Promise<ActionResult<TData>>;\n (prevState: ActionResult<TData> | null, formData: FormData): Promise<ActionResult<TData>>;\n};\n\n// ─── Implementation ──────────────────────────────────────────────────────\n\n/**\n * Run middleware array or single function. Returns merged context.\n */\nasync function runActionMiddleware<TCtx>(\n middleware: ActionMiddleware<TCtx> | ActionMiddleware<Record<string, unknown>>[] | undefined\n): Promise<TCtx> {\n if (!middleware) {\n return {} as TCtx;\n }\n\n if (Array.isArray(middleware)) {\n let merged = {} as Record<string, unknown>;\n for (const mw of middleware) {\n const result = await mw();\n merged = { ...merged, ...result };\n }\n return merged as TCtx;\n }\n\n return await middleware();\n}\n\n// Re-export parseFormData for use throughout the framework\nimport { parseFormData } from './form-data.js';\nimport { formatSize } from '#/utils/format.js';\n\n/**\n * Extract validation errors from a schema error.\n * Supports Zod's flatten() and generic issues array.\n */\nfunction extractValidationErrors(error: SchemaError): ValidationErrors {\n // Zod-style flatten\n if (typeof error.flatten === 'function') {\n return error.flatten().fieldErrors;\n }\n\n // Generic issues array\n if (error.issues) {\n const errors: ValidationErrors = {};\n for (const issue of error.issues) {\n const path = issue.path?.join('.') ?? '_root';\n if (!errors[path]) errors[path] = [];\n errors[path].push(issue.message);\n }\n return errors;\n }\n\n return { _root: ['Validation failed'] };\n}\n\n/**\n * Extract validation errors from Standard Schema issues.\n */\nfunction extractStandardSchemaErrors(issues: ReadonlyArray<StandardSchemaIssue>): ValidationErrors {\n const errors: ValidationErrors = {};\n for (const issue of issues) {\n const path =\n issue.path\n ?.map((p) => {\n // Standard Schema path items can be { key: ... } objects or bare PropertyKey values\n if (typeof p === 'object' && p !== null && 'key' in p) return String(p.key);\n return String(p);\n })\n .join('.') ?? '_root';\n if (!errors[path]) errors[path] = [];\n errors[path].push(issue.message);\n }\n return Object.keys(errors).length > 0 ? errors : { _root: ['Validation failed'] };\n}\n\n/**\n * Wrap unexpected errors into a safe server error result.\n * ActionError → typed result. Other errors → INTERNAL_ERROR (no leak).\n *\n * Exported for use by action-handler.ts to catch errors from raw 'use server'\n * functions that don't use createActionClient.\n */\nexport function handleActionError(error: unknown): ActionResult<never> {\n if (error instanceof ActionError) {\n return {\n serverError: {\n code: error.code,\n ...(error.data ? { data: error.data } : {}),\n },\n };\n }\n\n // In dev, include the message for debugging\n const isDev = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production';\n return {\n serverError: {\n code: 'INTERNAL_ERROR',\n ...(isDev && error instanceof Error ? { data: { message: error.message } } : {}),\n },\n };\n}\n\n/**\n * Create a typed action client with middleware and schema validation.\n *\n * @example\n * ```ts\n * const action = createActionClient({\n * middleware: async () => {\n * const user = await getUser()\n * if (!user) throw new ActionError('UNAUTHORIZED')\n * return { user }\n * },\n * })\n *\n * export const createTodo = action\n * .schema(z.object({ title: z.string().min(1) }))\n * .action(async ({ input, ctx }) => {\n * await db.todos.create({ ...input, userId: ctx.user.id })\n * })\n * ```\n */\nexport function createActionClient<TCtx = Record<string, never>>(\n config: ActionClientConfig<TCtx> = {}\n): ActionBuilder<TCtx> {\n function buildAction<TInput, TData>(\n schema: ActionSchema<TInput> | undefined,\n fn: (ctx: ActionContext<TCtx, TInput>) => Promise<TData>\n ): ActionFn<TData> {\n async function actionHandler(...args: unknown[]): Promise<ActionResult<TData>> {\n try {\n // Run middleware\n const ctx = await runActionMiddleware(config.middleware);\n\n // Determine input — either FormData (from useActionState) or direct arg\n let rawInput: unknown;\n if (args.length === 2 && args[1] instanceof FormData) {\n // Called as (prevState, formData) by React useActionState\n rawInput = schema ? parseFormData(args[1]) : args[1];\n } else {\n // Direct call: action(input)\n rawInput = args[0];\n }\n\n // Validate file sizes before schema validation.\n if (config.fileSizeLimit !== undefined && rawInput && typeof rawInput === 'object') {\n const fileSizeErrors = validateFileSizes(\n rawInput as Record<string, unknown>,\n config.fileSizeLimit\n );\n if (fileSizeErrors) {\n const submittedValues = stripFiles(rawInput);\n return { validationErrors: fileSizeErrors, submittedValues };\n }\n }\n\n // Capture submitted values for repopulation on validation failure.\n // Exclude File objects (can't serialize, shouldn't echo back).\n const submittedValues = schema ? stripFiles(rawInput) : undefined;\n\n // Validate with schema if provided\n let input: TInput;\n if (schema) {\n if (isStandardSchema(schema)) {\n // Standard Schema protocol (Zod ≥3.24, Valibot ≥1.0, ArkType)\n const result = schema['~standard'].validate(rawInput);\n if (result instanceof Promise) {\n throw new Error(\n '[timber] createActionClient: schema returned a Promise — only sync schemas are supported.'\n );\n }\n if (result.issues) {\n const validationErrors = extractStandardSchemaErrors(result.issues);\n logValidationFailure(validationErrors);\n return { validationErrors, submittedValues };\n }\n input = result.value;\n } else if (typeof schema.safeParse === 'function') {\n const result = schema.safeParse(rawInput);\n if (!result.success) {\n const validationErrors = extractValidationErrors(result.error);\n logValidationFailure(validationErrors);\n return { validationErrors, submittedValues };\n }\n input = result.data;\n } else {\n try {\n input = schema.parse(rawInput);\n } catch (parseError) {\n const validationErrors = extractValidationErrors(parseError as SchemaError);\n logValidationFailure(validationErrors);\n return { validationErrors, submittedValues };\n }\n }\n } else {\n input = rawInput as TInput;\n }\n\n // Execute the action body\n const data = await fn({ ctx, input });\n return { data };\n } catch (error) {\n return handleActionError(error);\n }\n }\n\n return actionHandler as ActionFn<TData>;\n }\n\n return {\n schema<TInput>(schema: ActionSchema<TInput>) {\n return {\n action<TData>(fn: (ctx: ActionContext<TCtx, TInput>) => Promise<TData>): ActionFn<TData> {\n return buildAction(schema, fn);\n },\n };\n },\n action<TData>(fn: (ctx: ActionContext<TCtx, undefined>) => Promise<TData>): ActionFn<TData> {\n return buildAction(undefined, fn as (ctx: ActionContext<TCtx, unknown>) => Promise<TData>);\n },\n };\n}\n\n// ─── validated() ────────────────────────────────────────────────────────\n\n/**\n * Convenience wrapper for the common case: validate input, run handler.\n * No middleware needed.\n *\n * @example\n * ```ts\n * 'use server'\n * import { validated } from '@timber-js/app/server'\n * import { z } from 'zod'\n *\n * export const createTodo = validated(\n * z.object({ title: z.string().min(1) }),\n * async (input) => {\n * await db.todos.create(input)\n * }\n * )\n * ```\n */\nexport function validated<TInput, TData>(\n schema: ActionSchema<TInput>,\n handler: (input: TInput) => Promise<TData>\n): ActionFn<TData> {\n return createActionClient()\n .schema(schema)\n .action(async ({ input }) => handler(input));\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────\n\n/**\n * Log validation failures in dev mode so developers can see what went wrong.\n * In production, validation errors are only returned to the client.\n */\nfunction logValidationFailure(errors: ValidationErrors): void {\n const isDev = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production';\n if (!isDev) return;\n\n const fields = Object.entries(errors)\n .map(([field, messages]) => ` ${field}: ${messages.join(', ')}`)\n .join('\\n');\n console.warn(`[timber] action schema validation failed:\\n${fields}`);\n}\n\n/**\n * Validate that all File objects in the input are within the size limit.\n * Returns validation errors keyed by field name, or null if all files are ok.\n */\nfunction validateFileSizes(input: Record<string, unknown>, limit: number): ValidationErrors | null {\n const errors: ValidationErrors = {};\n const limitKb = Math.round(limit / 1024);\n const limitLabel =\n limit >= 1024 * 1024 ? `${Math.round(limit / (1024 * 1024))}MB` : `${limitKb}KB`;\n\n for (const [key, value] of Object.entries(input)) {\n if (value instanceof File && value.size > limit) {\n errors[key] = [\n `File \"${value.name}\" (${formatSize(value.size)}) exceeds the ${limitLabel} limit`,\n ];\n } else if (Array.isArray(value)) {\n const oversized = value.filter((item) => item instanceof File && item.size > limit);\n if (oversized.length > 0) {\n errors[key] = oversized.map(\n (f: File) => `File \"${f.name}\" (${formatSize(f.size)}) exceeds the ${limitLabel} limit`\n );\n }\n }\n }\n\n return Object.keys(errors).length > 0 ? errors : null;\n}\n\n/**\n * Strip File objects from a value, returning a plain object safe for\n * serialization. File objects can't be serialized and shouldn't be echoed back.\n */\nfunction stripFiles(value: unknown): Record<string, unknown> | undefined {\n if (value === null || value === undefined) return undefined;\n if (typeof value !== 'object') return undefined;\n\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (v instanceof File) continue;\n if (Array.isArray(v)) {\n result[k] = v.filter((item) => !(item instanceof File));\n } else if (typeof v === 'object' && v !== null && !(v instanceof File)) {\n result[k] = stripFiles(v) ?? {};\n } else {\n result[k] = v;\n }\n }\n return result;\n}\n","/**\n * Form Flash — ALS-based store for no-JS form action results.\n *\n * When a no-JS form action completes, the server re-renders the page with\n * the action result injected via AsyncLocalStorage instead of redirecting\n * (which would discard the result). Server components read the flash and\n * pass it to client form components as the initial `useActionState` value.\n *\n * This follows the Remix/Rails pattern — the form component becomes the\n * single source of truth for both with-JS (React state) and no-JS (flash).\n *\n * The flash data is server-side only — never serialized to cookies or headers.\n *\n * See design/08-forms-and-actions.md §\"No-JS Error Round-Trip\"\n */\n\nimport type { ValidationErrors } from './action-client.js';\nimport { formFlashAls } from './als-registry.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\n/**\n * Flash data injected into the re-render after a no-JS form submission.\n *\n * This is the action result from the server action, stored in ALS so server\n * components can read it and pass it to client form components as the initial\n * state for `useActionState`. This makes the form component a single source\n * of truth for both with-JS and no-JS paths.\n *\n * The shape matches `ActionResult<unknown>` — it's one of:\n * - `{ data: ... }` — success\n * - `{ validationErrors, submittedValues }` — validation failure\n * - `{ serverError }` — server error\n */\nexport interface FormFlashData {\n /** Success data from the action. */\n data?: unknown;\n /** Validation errors keyed by field name. `_root` for form-level errors. */\n validationErrors?: ValidationErrors;\n /** Raw submitted values for repopulating form fields. File objects are excluded. */\n submittedValues?: Record<string, unknown>;\n /** Server error if the action threw an ActionError. */\n serverError?: { code: string; data?: Record<string, unknown> };\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────\n\n/**\n * Read the form flash data for the current request.\n *\n * Returns `null` if no flash data is present (i.e., this is a normal page\n * render, not a re-render after a no-JS form submission).\n *\n * Pass the flash as the initial state to `useActionState` so the form\n * component has a single source of truth for both with-JS and no-JS paths:\n *\n * ```tsx\n * // app/contact/page.tsx (server component)\n * import { getFormFlash } from '@timber-js/app/server'\n *\n * export default function ContactPage() {\n * const flash = getFormFlash()\n * return <ContactForm flash={flash} />\n * }\n *\n * // app/contact/form.tsx (client component)\n * export function ContactForm({ flash }) {\n * const [result, action, isPending] = useActionState(submitContact, flash)\n * // result is the single source of truth — flash seeds it on no-JS\n * }\n * ```\n */\nexport function getFormFlash(): FormFlashData | null {\n return formFlashAls.getStore() ?? null;\n}\n\n// ─── Framework-Internal ──────────────────────────────────────────────────\n\n/**\n * Run a callback with form flash data in scope.\n *\n * Used by the action handler to re-render the page with validation errors\n * available via `getFormFlash()`. Not part of the public API.\n *\n * @internal\n */\nexport function runWithFormFlash<T>(data: FormFlashData, fn: () => T): T {\n return formFlashAls.run(data, fn);\n}\n","/**\n * Server action primitives: revalidatePath, revalidateTag, and the action handler.\n *\n * - revalidatePath(path) re-renders the route at that path and returns the RSC\n * flight payload for inline reconciliation.\n * - revalidateTag(tag) invalidates cached shells and 'use cache' entries by tag.\n *\n * Both are callable from anywhere on the server — actions, API routes, handlers.\n *\n * The action handler processes incoming action requests, validates CSRF,\n * enforces body limits, executes the action, and returns the response\n * (with piggybacked RSC payload if revalidatePath was called).\n *\n * See design/08-forms-and-actions.md\n */\n\nimport type { CacheHandler } from '#/cache/index';\nimport { RedirectSignal } from './primitives';\nimport { withSpan } from './tracing';\nimport { revalidationAls, type RevalidationState } from './als-registry.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\n/** Result of rendering a revalidation — element tree before RSC serialization. */\nexport interface RevalidationResult {\n /** React element tree (pre-serialization — passed to renderToReadableStream). */\n element: unknown;\n /** Resolved head elements for metadata. */\n headElements: unknown[];\n}\n\n/** Renderer function that builds a React element tree for a given path. */\nexport type RevalidateRenderer = (path: string) => Promise<RevalidationResult>;\n\n// Re-export the type from the registry for public API consumers.\nexport type { RevalidationState } from './als-registry.js';\n\n/** Options for creating the action handler. */\nexport interface ActionHandlerConfig {\n /** Cache handler for tag invalidation. */\n cacheHandler?: CacheHandler;\n /** Renderer for producing RSC payloads during revalidation. */\n renderer?: RevalidateRenderer;\n}\n\n/** Result of handling a server action request. */\nexport interface ActionHandlerResult {\n /** The action's return value (serialized). */\n actionResult: unknown;\n /** Revalidation result if revalidatePath was called (element tree, not yet serialized). */\n revalidation?: RevalidationResult;\n /** Redirect location if a RedirectSignal was thrown during revalidation. */\n redirectTo?: string;\n /** Redirect status code. */\n redirectStatus?: number;\n}\n\n// ─── Revalidation State ──────────────────────────────────────────────────\n\n// Per-request revalidation state stored in AsyncLocalStorage (from als-registry.ts).\n// This ensures concurrent requests never share or overwrite each other's state\n// (the previous module-level global was vulnerable to cross-request pollution).\n\n/**\n * Set the revalidation state for the current action execution.\n * @internal — kept for test compatibility; prefer executeAction() which uses ALS.\n */\nexport function _setRevalidationState(state: RevalidationState): void {\n // Enter ALS scope — this is only used by tests that call revalidatePath/Tag\n // directly without going through executeAction().\n revalidationAls.enterWith(state);\n}\n\n/**\n * Clear the revalidation state after action execution.\n * @internal — kept for test compatibility.\n */\nexport function _clearRevalidationState(): void {\n revalidationAls.enterWith(undefined as unknown as RevalidationState);\n}\n\n/**\n * Get the current revalidation state. Throws if called outside an action context.\n * @internal\n */\nfunction getRevalidationState(): RevalidationState {\n const state = revalidationAls.getStore();\n if (!state) {\n throw new Error(\n 'revalidatePath/revalidateTag called outside of a server action context. ' +\n 'These functions can only be called during action execution.'\n );\n }\n return state;\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────\n\n/**\n * Re-render the route at `path` and include the RSC flight payload in the\n * action response. The client reconciles inline — no separate fetch needed.\n *\n * Can be called from server actions, API routes, or any server-side context.\n *\n * @param path - The path to re-render (e.g. '/dashboard', '/todos').\n */\nexport function revalidatePath(path: string): void {\n const state = getRevalidationState();\n if (!state.paths.includes(path)) {\n state.paths.push(path);\n }\n}\n\n/**\n * Invalidate all pre-rendered shells and 'use cache' entries tagged with `tag`.\n * Does not return a payload — the next request for an invalidated route re-renders fresh.\n *\n * @param tag - The cache tag to invalidate (e.g. 'products', 'user:123').\n */\nexport function revalidateTag(tag: string): void {\n const state = getRevalidationState();\n if (!state.tags.includes(tag)) {\n state.tags.push(tag);\n }\n}\n\n// ─── Action Handler ──────────────────────────────────────────────────────\n\n/**\n * Execute a server action and process revalidation.\n *\n * 1. Sets up revalidation state\n * 2. Calls the action function\n * 3. Processes revalidateTag calls (invalidates cache entries)\n * 4. Processes revalidatePath calls (re-renders and captures RSC payload)\n * 5. Returns the action result + optional RSC payload\n *\n * @param actionFn - The server action function to execute.\n * @param args - Arguments to pass to the action.\n * @param config - Handler configuration (cache handler, renderer).\n */\nexport async function executeAction(\n actionFn: (...args: unknown[]) => Promise<unknown>,\n args: unknown[],\n config: ActionHandlerConfig = {},\n spanMeta?: { actionFile?: string; actionName?: string }\n): Promise<ActionHandlerResult> {\n const state: RevalidationState = { paths: [], tags: [] };\n let actionResult: unknown;\n let redirectTo: string | undefined;\n let redirectStatus: number | undefined;\n\n // Run the action inside ALS scope so revalidatePath/Tag resolve to this\n // request's state object — concurrent requests each get their own scope.\n await revalidationAls.run(state, async () => {\n try {\n actionResult = await withSpan(\n 'timber.action',\n {\n ...(spanMeta?.actionFile ? { 'timber.action_file': spanMeta.actionFile } : {}),\n ...(spanMeta?.actionName ? { 'timber.action_name': spanMeta.actionName } : {}),\n },\n () => actionFn(...args)\n );\n } catch (error) {\n if (error instanceof RedirectSignal) {\n redirectTo = error.location;\n redirectStatus = error.status;\n } else {\n throw error;\n }\n }\n });\n\n // Process tag invalidation\n if (state.tags.length > 0 && config.cacheHandler) {\n await Promise.all(state.tags.map((tag) => config.cacheHandler!.invalidate({ tag })));\n }\n\n // Process path revalidation — build element tree (not yet serialized)\n let revalidation: RevalidationResult | undefined;\n if (state.paths.length > 0 && config.renderer) {\n // For now, render the first revalidated path.\n // Multiple paths could be supported via multipart streaming in the future.\n const path = state.paths[0];\n try {\n revalidation = await config.renderer(path);\n } catch (renderError) {\n if (renderError instanceof RedirectSignal) {\n // Revalidation triggered a redirect (e.g., session expired)\n redirectTo = renderError.location;\n redirectStatus = renderError.status;\n } else {\n // Log but don't fail the action — revalidation is best-effort\n console.error('[timber] revalidatePath render failed:', renderError);\n }\n }\n }\n\n return {\n actionResult,\n revalidation,\n ...(redirectTo ? { redirectTo, redirectStatus } : {}),\n };\n}\n\n/**\n * Build an HTTP Response for a no-JS form submission.\n * Standard POST → 302 redirect pattern.\n *\n * @param redirectPath - Where to redirect after the action executes.\n */\nexport function buildNoJsResponse(redirectPath: string, status: number = 302): Response {\n return new Response(null, {\n status,\n headers: { Location: redirectPath },\n });\n}\n\n/**\n * Detect whether the incoming request is an RSC action request (with JS)\n * or a plain HTML form POST (no JS).\n *\n * RSC action requests use Accept: text/x-component or Content-Type: text/x-component.\n */\nexport function isRscActionRequest(req: Request): boolean {\n const accept = req.headers.get('Accept') ?? '';\n const contentType = req.headers.get('Content-Type') ?? '';\n return accept.includes('text/x-component') || contentType.includes('text/x-component');\n}\n","/**\n * Route handler for route.ts API endpoints.\n *\n * route.ts files export named HTTP method handlers (GET, POST, etc.).\n * They share the same pipeline (proxy → match → middleware → access → handler)\n * but don't render React trees.\n *\n * See design/07-routing.md §\"route.ts — API Endpoints\"\n */\n\nimport type { RouteContext } from './types.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\n/** HTTP methods that route.ts can export as named handlers. */\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';\n\n/** A single route handler function — one-arg signature. */\nexport type RouteHandler = (ctx: RouteContext) => Response | Promise<Response>;\n\n/** A route.ts module — named exports for each supported HTTP method. */\nexport type RouteModule = {\n [K in HttpMethod]?: RouteHandler;\n};\n\n/** All recognized HTTP method export names. */\nconst HTTP_METHODS: HttpMethod[] = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];\n\n// ─── Allowed Methods ─────────────────────────────────────────────────────\n\n/**\n * Resolve the full list of allowed methods for a route module.\n *\n * Includes:\n * - All explicitly exported methods\n * - HEAD (implicit when GET is exported)\n * - OPTIONS (always implicit)\n */\nexport function resolveAllowedMethods(mod: RouteModule): HttpMethod[] {\n const methods: HttpMethod[] = [];\n\n for (const method of HTTP_METHODS) {\n if (method === 'HEAD' || method === 'OPTIONS') continue;\n if (mod[method]) {\n methods.push(method);\n }\n }\n\n // HEAD is implicit when GET is exported\n if (mod.GET && !mod.HEAD) {\n methods.push('HEAD');\n } else if (mod.HEAD) {\n methods.push('HEAD');\n }\n\n // OPTIONS is always implicit\n if (!mod.OPTIONS) {\n methods.push('OPTIONS');\n } else {\n methods.push('OPTIONS');\n }\n\n return methods;\n}\n\n// ─── Route Request Handler ───────────────────────────────────────────────\n\n/**\n * Handle an incoming request against a route.ts module.\n *\n * Dispatches to the named method handler, auto-generates 405/OPTIONS,\n * and merges response headers from ctx.headers.\n */\nexport async function handleRouteRequest(mod: RouteModule, ctx: RouteContext): Promise<Response> {\n const method = ctx.req.method.toUpperCase() as HttpMethod;\n const allowed = resolveAllowedMethods(mod);\n const allowHeader = allowed.join(', ');\n\n // Auto OPTIONS — 204 with Allow header\n if (method === 'OPTIONS') {\n if (mod.OPTIONS) {\n return runHandler(mod.OPTIONS, ctx);\n }\n return new Response(null, {\n status: 204,\n headers: { Allow: allowHeader },\n });\n }\n\n // HEAD fallback — run GET, strip body\n if (method === 'HEAD') {\n if (mod.HEAD) {\n return runHandler(mod.HEAD, ctx);\n }\n if (mod.GET) {\n const res = await runHandler(mod.GET, ctx);\n // Return headers + status but no body\n return new Response(null, {\n status: res.status,\n headers: res.headers,\n });\n }\n }\n\n // Dispatch to the named handler\n const handler = mod[method];\n if (!handler) {\n return new Response(null, {\n status: 405,\n headers: { Allow: allowHeader },\n });\n }\n\n return runHandler(handler, ctx);\n}\n\n/**\n * Run a handler, merge ctx.headers into the response, and catch errors.\n */\nasync function runHandler(handler: RouteHandler, ctx: RouteContext): Promise<Response> {\n try {\n const res = await handler(ctx);\n return mergeResponseHeaders(res, ctx.headers);\n } catch (error) {\n console.error('[timber] Uncaught error in route.ts handler:', error);\n return new Response(null, { status: 500 });\n }\n}\n\n/**\n * Merge response headers from ctx.headers into the handler's response.\n * ctx.headers (set by middleware or the handler) are applied to the final response.\n * Handler-set headers take precedence over ctx.headers.\n */\nfunction mergeResponseHeaders(res: Response, ctxHeaders: Headers): Response {\n // If no ctx headers to merge, return as-is\n let hasCtxHeaders = false;\n ctxHeaders.forEach(() => {\n hasCtxHeaders = true;\n });\n if (!hasCtxHeaders) return res;\n\n // Merge: ctx.headers first, then handler response headers override\n const merged = new Headers();\n ctxHeaders.forEach((value, key) => merged.set(key, value));\n res.headers.forEach((value, key) => merged.set(key, value));\n\n return new Response(res.body, {\n status: res.status,\n statusText: res.statusText,\n headers: merged,\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/server/primitives.ts","../../src/server/canonicalize.ts","../../src/server/proxy.ts","../../src/server/middleware-runner.ts","../../src/server/server-timing.ts","../../src/server/error-formatter.ts","../../src/server/logger.ts","../../src/server/instrumentation.ts","../../src/server/pipeline-metadata.ts","../../src/server/pipeline-interception.ts","../../src/server/pipeline.ts","../../src/server/build-manifest.ts","../../src/server/early-hints.ts","../../src/server/early-hints-sender.ts","../../src/server/tree-builder.ts","../../src/server/access-gate.tsx","../../src/server/status-code-resolver.ts","../../src/server/flush.ts","../../src/server/csrf.ts","../../src/server/body-limits.ts","../../src/server/metadata-social.ts","../../src/server/metadata-platform.ts","../../src/server/metadata-render.ts","../../src/server/metadata.ts","../../src/server/form-data.ts","../../src/server/action-client.ts","../../src/server/form-flash.ts","../../src/server/actions.ts","../../src/server/route-handler.ts"],"sourcesContent":["// Server-side primitives: deny, redirect, redirectExternal, RenderError, waitUntil, SsrStreamError\n//\n// These are the core runtime signals that components, middleware, and access gates\n// use to control request flow. See design/10-error-handling.md.\n\nimport type { JsonSerializable } from './types.js';\n\n// ─── Dev-mode validation ────────────────────────────────────────────────────\n\n/**\n * Check if a value is JSON-serializable without data loss.\n * Returns a description of the first non-serializable value found, or null if OK.\n *\n * @internal Exported for testing only.\n */\nexport function findNonSerializable(value: unknown, path = 'data'): string | null {\n if (value === null || value === undefined) return null;\n\n switch (typeof value) {\n case 'string':\n case 'number':\n case 'boolean':\n return null;\n case 'bigint':\n return `${path} contains a BigInt — BigInt throws in JSON.stringify`;\n case 'function':\n return `${path} is a function — functions are not JSON-serializable`;\n case 'symbol':\n return `${path} is a symbol — symbols are not JSON-serializable`;\n case 'object':\n break;\n default:\n return `${path} has unsupported type \"${typeof value}\"`;\n }\n\n if (value instanceof Date) {\n return `${path} is a Date — Dates silently coerce to strings in JSON.stringify`;\n }\n if (value instanceof Map) {\n return `${path} is a Map — Maps serialize as {} in JSON.stringify (data loss)`;\n }\n if (value instanceof Set) {\n return `${path} is a Set — Sets serialize as {} in JSON.stringify (data loss)`;\n }\n if (value instanceof RegExp) {\n return `${path} is a RegExp — RegExps serialize as {} in JSON.stringify`;\n }\n if (value instanceof Error) {\n return `${path} is an Error — Errors serialize as {} in JSON.stringify`;\n }\n\n if (Array.isArray(value)) {\n for (let i = 0; i < value.length; i++) {\n const result = findNonSerializable(value[i], `${path}[${i}]`);\n if (result) return result;\n }\n return null;\n }\n\n // Plain object — only Object.prototype is safe. Null-prototype objects\n // (Object.create(null)) survive JSON.stringify but React Flight rejects\n // them with \"Classes or null prototypes are not supported\", so the\n // pre-flush deny path (renderDenyPage → renderToReadableStream) would throw.\n const proto = Object.getPrototypeOf(value);\n if (proto === null) {\n return `${path} is a null-prototype object — React Flight rejects null prototypes`;\n }\n if (proto !== Object.prototype) {\n const name = (value as object).constructor?.name ?? 'unknown';\n return `${path} is a ${name} instance — class instances may lose data in JSON.stringify`;\n }\n\n for (const key of Object.keys(value as Record<string, unknown>)) {\n const result = findNonSerializable(\n (value as Record<string, unknown>)[key],\n `${path}.${key}`\n );\n if (result) return result;\n }\n return null;\n}\n\n/**\n * Emit a dev-mode warning if data is not JSON-serializable.\n * No-op in production.\n */\nfunction warnIfNotSerializable(data: unknown, callerName: string): void {\n if (process.env.NODE_ENV === 'production') return;\n if (data === undefined) return;\n\n const issue = findNonSerializable(data);\n if (issue) {\n console.warn(\n `[timber] ${callerName}: ${issue}. ` +\n 'Data passed to deny() or RenderError must be JSON-serializable because ' +\n 'the post-flush path uses JSON.stringify, not React Flight.'\n );\n }\n}\n\n// ─── DenySignal ─────────────────────────────────────────────────────────────\n\n/**\n * Render-phase signal thrown by `deny()`. Caught by the framework to produce\n * the correct HTTP status code (segment context) or graceful degradation (slot context).\n */\nexport class DenySignal extends Error {\n readonly status: number;\n readonly data: JsonSerializable | undefined;\n\n constructor(status: number, data?: JsonSerializable) {\n super(`Access denied with status ${status}`);\n this.name = 'DenySignal';\n this.status = status;\n this.data = data;\n }\n\n /**\n * Extract the file that called deny() from the stack trace.\n * Returns a short path (e.g. \"app/auth/access.ts\") or undefined if\n * the stack can't be parsed. Dev-only — used for dev log output.\n */\n get sourceFile(): string | undefined {\n if (!this.stack) return undefined;\n const frames = this.stack.split('\\n');\n // Skip the Error line and the deny() frame — the caller is the 3rd line.\n // Stack format: \" at FnName (file:line:col)\" or \" at file:line:col\"\n for (let i = 2; i < frames.length; i++) {\n const frame = frames[i];\n if (!frame) continue;\n // Skip framework internals\n if (frame.includes('primitives.ts') || frame.includes('node_modules')) continue;\n // Extract file path from the frame\n const match =\n frame.match(/\\(([^)]+?)(?::\\d+:\\d+)\\)/) ?? frame.match(/at\\s+([^\\s]+?)(?::\\d+:\\d+)/);\n if (match?.[1]) {\n // Shorten to app-relative path\n const full = match[1];\n const appIdx = full.indexOf('/app/');\n return appIdx >= 0 ? full.slice(appIdx + 1) : full;\n }\n }\n return undefined;\n }\n}\n\n/**\n * Universal denial primitive. Throws a `DenySignal` that the framework catches.\n *\n * - In segment context (outside Suspense): produces HTTP status code\n * - In slot context: graceful degradation → denied.tsx → default.tsx → null\n * - Inside Suspense (hold window): promoted to pre-flush behavior\n * - Inside Suspense (after flush): error boundary + noindex meta\n *\n * @param status - Any 4xx HTTP status code. Defaults to 403.\n * @param data - Optional JSON-serializable data passed as `dangerouslyPassData` prop to status-code files.\n */\nexport function deny(status: number = 403, data?: JsonSerializable): never {\n if (status < 400 || status > 499) {\n throw new Error(\n `deny() requires a 4xx status code, got ${status}. ` +\n 'For 5xx errors, throw a RenderError instead.'\n );\n }\n warnIfNotSerializable(data, 'deny()');\n throw new DenySignal(status, data);\n}\n\n/**\n * Convenience alias for `deny(404)`.\n *\n * Provided for Next.js API compatibility — libraries and user code that\n * call `notFound()` from `next/navigation` get the same behavior as\n * `deny(404)` in timber.\n */\nexport function notFound(): never {\n throw new DenySignal(404);\n}\n\n/**\n * Next.js redirect type discriminator.\n *\n * Provided for API compatibility with libraries that import `RedirectType`\n * from `next/navigation`. In timber, `redirect()` always uses `replace`\n * semantics (no history entry for the redirect itself).\n */\nexport const RedirectType = {\n push: 'push',\n replace: 'replace',\n} as const;\n\n// ─── RedirectSignal ─────────────────────────────────────────────────────────\n\n/**\n * Render-phase signal thrown by `redirect()` and `redirectExternal()`.\n * Caught by the framework to produce a 3xx response or client-side navigation.\n */\nexport class RedirectSignal extends Error {\n readonly location: string;\n readonly status: number;\n\n constructor(location: string, status: number) {\n super(`Redirect to ${location}`);\n this.name = 'RedirectSignal';\n this.location = location;\n this.status = status;\n }\n}\n\n/** Pattern matching absolute URLs: http(s):// or protocol-relative // */\nconst ABSOLUTE_URL_RE = /^(?:[a-zA-Z][a-zA-Z\\d+\\-.]*:|\\/\\/)/;\n\n/**\n * Redirect to a relative path. Rejects absolute and protocol-relative URLs.\n * Use `redirectExternal()` for external redirects with an allow-list.\n *\n * @param path - Relative path (e.g. '/login', 'settings', '/login?returnTo=/dash')\n * @param status - HTTP redirect status code (3xx). Defaults to 302.\n */\nexport function redirect(path: string, status: number = 302): never {\n if (status < 300 || status > 399) {\n throw new Error(`redirect() requires a 3xx status code, got ${status}.`);\n }\n if (ABSOLUTE_URL_RE.test(path)) {\n throw new Error(\n `redirect() only accepts relative URLs. Got absolute URL: \"${path}\". ` +\n 'Use redirectExternal(url, allowList) for external redirects.'\n );\n }\n throw new RedirectSignal(path, status);\n}\n\n/**\n * Permanent redirect to a relative path. Shorthand for `redirect(path, 308)`.\n *\n * Uses 308 (Permanent Redirect) which preserves the HTTP method — the browser\n * will replay POST requests to the new location. This matches Next.js behavior.\n *\n * @param path - Relative path (e.g. '/new-page', '/dashboard')\n */\nexport function permanentRedirect(path: string): never {\n redirect(path, 308);\n}\n\n/**\n * Redirect to an external URL. The hostname must be in the provided allow-list.\n *\n * @param url - Absolute URL to redirect to.\n * @param allowList - Array of allowed hostnames (e.g. ['example.com', 'auth.example.com']).\n * @param status - HTTP redirect status code (3xx). Defaults to 302.\n */\nexport function redirectExternal(url: string, allowList: string[], status: number = 302): never {\n if (status < 300 || status > 399) {\n throw new Error(`redirectExternal() requires a 3xx status code, got ${status}.`);\n }\n\n let hostname: string;\n try {\n hostname = new URL(url).hostname;\n } catch {\n throw new Error(`redirectExternal() received an invalid URL: \"${url}\"`);\n }\n\n if (!allowList.includes(hostname)) {\n throw new Error(\n `redirectExternal() target \"${hostname}\" is not in the allow-list. ` +\n `Allowed: [${allowList.join(', ')}]`\n );\n }\n\n throw new RedirectSignal(url, status);\n}\n\n// ─── RenderError ────────────────────────────────────────────────────────────\n\n/**\n * Typed digest that crosses the RSC → client boundary.\n * The `code` identifies the error class; `data` carries JSON-serializable context.\n */\nexport interface RenderErrorDigest<\n TCode extends string = string,\n TData extends JsonSerializable = JsonSerializable,\n> {\n code: TCode;\n data: TData;\n}\n\n/**\n * Typed throw for render-phase errors that carry structured context to error boundaries.\n *\n * The `digest` (code + data) is serialized into the RSC stream separately from the\n * Error instance — only the digest crosses the RSC → client boundary.\n *\n * @example\n * ```ts\n * throw new RenderError('PRODUCT_NOT_FOUND', {\n * title: 'Product not found',\n * resourceId: params.id,\n * })\n * ```\n */\nexport class RenderError<\n TCode extends string = string,\n TData extends JsonSerializable = JsonSerializable,\n> extends Error {\n readonly code: TCode;\n readonly digest: RenderErrorDigest<TCode, TData>;\n readonly status: number;\n\n constructor(code: TCode, data: TData, options?: { status?: number }) {\n super(`RenderError: ${code}`);\n this.name = 'RenderError';\n this.code = code;\n this.digest = { code, data };\n\n warnIfNotSerializable(data, 'RenderError');\n\n const status = options?.status ?? 500;\n if (status < 400 || status > 599) {\n throw new Error(`RenderError status must be 4xx or 5xx, got ${status}.`);\n }\n this.status = status;\n }\n}\n\n// ─── waitUntil ──────────────────────────────────────────────────────────────\n\n/** Minimal interface for adapters that support background work. */\nexport interface WaitUntilAdapter {\n waitUntil?(promise: Promise<unknown>): void;\n}\n\n// Intentional per-app singleton — warn-once flag that persists for the\n// lifetime of the process/isolate. Not per-request; do not migrate to ALS.\nlet _waitUntilWarned = false;\n\n/**\n * Register a promise to be kept alive after the response is sent.\n * Maps to `ctx.waitUntil()` on Cloudflare Workers and similar platforms.\n *\n * If the adapter does not support `waitUntil`, a warning is logged once\n * and the promise is left to resolve (or reject) without being tracked.\n *\n * @param promise - The background work to keep alive.\n * @param adapter - The platform adapter (injected by the framework at runtime).\n */\nexport function waitUntil(promise: Promise<unknown>, adapter: WaitUntilAdapter): void {\n if (typeof adapter.waitUntil === 'function') {\n adapter.waitUntil(promise);\n return;\n }\n\n if (!_waitUntilWarned) {\n _waitUntilWarned = true;\n console.warn(\n '[timber] waitUntil() is not supported by the current adapter. ' +\n 'Background work will not be tracked. This warning is shown once.'\n );\n }\n}\n\n/**\n * Reset the waitUntil warning state. Exported for testing only.\n * @internal\n */\nexport function _resetWaitUntilWarning(): void {\n _waitUntilWarned = false;\n}\n\n// ─── SsrStreamError ─────────────────────────────────────────────────────────\n\n/**\n * Error thrown when SSR's renderToReadableStream fails due to an error\n * in the decoded RSC stream (e.g., uncontained slot errors).\n *\n * The RSC entry checks for this error type in its catch block to avoid\n * re-executing server components via renderDenyPage. Instead, it renders\n * a bare deny/error page without layout wrapping.\n *\n * Defined in primitives.ts (not ssr-entry.ts) because ssr-entry.ts imports\n * react-dom/server which cannot be loaded in the RSC environment.\n */\nexport class SsrStreamError extends Error {\n constructor(\n message: string,\n public readonly cause: unknown\n ) {\n super(message);\n this.name = 'SsrStreamError';\n }\n}\n","/**\n * URL canonicalization — runs once at the request boundary.\n *\n * Every layer (proxy.ts, middleware.ts, access.ts, components) sees the same\n * canonical path. No re-decoding occurs at any later stage.\n *\n * See design/07-routing.md §\"URL Canonicalization & Security\"\n */\n\n/** Result of canonicalization — either a clean path or a rejection. */\nexport type CanonicalizeResult = { ok: true; pathname: string } | { ok: false; status: 400 };\n\n/**\n * Encoded separators that produce a 400 rejection.\n * %2f (/) and %5c (\\) cause path-confusion attacks.\n */\nconst ENCODED_SEPARATOR_RE = /%2f|%5c/i;\n\n/** Null byte — rejected. */\nconst NULL_BYTE_RE = /%00/i;\n\n/**\n * Canonicalize a URL pathname.\n *\n * 1. Reject encoded separators (%2f, %5c) and null bytes (%00)\n * 2. Single percent-decode\n * 3. Collapse // → /\n * 4. Resolve .. segments (reject if escaping root)\n * 5. Strip trailing slash (except root \"/\")\n *\n * @param rawPathname - The raw pathname from the request URL (percent-encoded)\n * @param stripTrailingSlash - Whether to strip trailing slashes. Default: true.\n */\nexport function canonicalize(rawPathname: string, stripTrailingSlash = true): CanonicalizeResult {\n // Step 1: Reject dangerous encoded sequences BEFORE decoding.\n // This must happen on the raw input so %252f doesn't bypass after a single decode.\n if (ENCODED_SEPARATOR_RE.test(rawPathname)) {\n return { ok: false, status: 400 };\n }\n if (NULL_BYTE_RE.test(rawPathname)) {\n return { ok: false, status: 400 };\n }\n\n // Step 2: Single percent-decode.\n // Double-encoded input (%2561 → %61) stays as %61 — not decoded again.\n let decoded: string;\n try {\n decoded = decodeURIComponent(rawPathname);\n } catch {\n // Malformed percent-encoding → 400\n return { ok: false, status: 400 };\n }\n\n // Reject null bytes that appeared after decoding (from valid %00-like sequences\n // that weren't caught above — belt and suspenders).\n if (decoded.includes('\\0')) {\n return { ok: false, status: 400 };\n }\n\n // Backslash is NOT a path separator — keep as literal character.\n // But reject if it would create // after normalization (e.g., /\\evil.com).\n // We do NOT convert \\ to / — it stays as a literal.\n\n // Step 3: Collapse consecutive slashes.\n let pathname = decoded.replace(/\\/\\/+/g, '/');\n\n // Step 4: Resolve .. and . segments.\n const segments = pathname.split('/');\n const resolved: string[] = [];\n for (const seg of segments) {\n if (seg === '..') {\n if (resolved.length <= 1) {\n // Trying to escape root — 400\n return { ok: false, status: 400 };\n }\n resolved.pop();\n } else if (seg !== '.') {\n resolved.push(seg);\n }\n }\n\n pathname = resolved.join('/') || '/';\n\n // Step 5: Strip trailing slash (except root \"/\").\n if (stripTrailingSlash && pathname.length > 1 && pathname.endsWith('/')) {\n pathname = pathname.slice(0, -1);\n }\n\n return { ok: true, pathname };\n}\n","/**\n * Proxy runner — executes app/proxy.ts before route matching.\n *\n * Supports two forms:\n * - Function: (req, next) => Promise<Response>\n * - Array: middleware functions composed left-to-right\n *\n * See design/07-routing.md §\"proxy.ts — Global Middleware\"\n */\n\n/** Signature for a single proxy middleware function. */\nexport type ProxyFn = (req: Request, next: () => Promise<Response>) => Response | Promise<Response>;\n\n/** The proxy.ts default export — either a function or an array of functions. */\nexport type ProxyExport = ProxyFn | ProxyFn[];\n\n/**\n * Run the proxy pipeline.\n *\n * @param proxyExport - The default export from proxy.ts (function or array)\n * @param req - The incoming request\n * @param next - The continuation that proceeds to route matching and rendering\n * @returns The final response\n */\nexport async function runProxy(\n proxyExport: ProxyExport,\n req: Request,\n next: () => Promise<Response>\n): Promise<Response> {\n const fns = Array.isArray(proxyExport) ? proxyExport : [proxyExport];\n\n // Compose left-to-right: first item's next() calls the second, etc.\n // The last item's next() calls the original `next` (route matching + render).\n let i = fns.length;\n let composed = next;\n while (i--) {\n const fn = fns[i]!;\n const downstream = composed;\n composed = () => Promise.resolve(fn(req, downstream));\n }\n\n return composed();\n}\n","/**\n * Middleware runner — executes a route's middleware.ts before rendering.\n *\n * Only the leaf route's middleware runs. There is no middleware chain.\n * Middleware does NOT have next() — it either short-circuits with a Response\n * or returns void to continue to access checks + render.\n *\n * See design/07-routing.md §\"middleware.ts\"\n */\n\nimport type { MiddlewareContext } from './types.js';\n\n/** Signature of a middleware.ts default export. */\nexport type MiddlewareFn = (ctx: MiddlewareContext) => Response | void | Promise<Response | void>;\n\n/**\n * Run a route's middleware function.\n *\n * @param middlewareFn - The default export from the route's middleware.ts\n * @param ctx - The middleware context (req, params, headers, requestHeaders, searchParams)\n * @returns A Response if middleware short-circuited, or undefined to continue\n */\nexport async function runMiddleware(\n middlewareFn: MiddlewareFn,\n ctx: MiddlewareContext\n): Promise<Response | undefined> {\n const result = await middlewareFn(ctx);\n if (result instanceof Response) {\n return result;\n }\n return undefined;\n}\n","/**\n * Server-Timing header — dev-mode timing breakdowns for Chrome DevTools.\n *\n * Collects timing entries per request using ALS. Each pipeline phase\n * (proxy, middleware, render, SSR, access, fetch) records an entry.\n * Before response flush, entries are formatted into a Server-Timing header.\n *\n * Only active in dev mode — zero overhead in production.\n *\n * See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Server-Timing\n * Task: LOCAL-290\n */\n\nimport { timingAls } from './als-registry.js';\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\nexport interface TimingEntry {\n /** Metric name (alphanumeric + hyphens, no spaces). */\n name: string;\n /** Duration in milliseconds. */\n dur: number;\n /** Human-readable description (shown in DevTools). */\n desc?: string;\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * Run a callback with a per-request timing collector.\n * Must be called at the top of the request pipeline (wraps the full request).\n */\nexport function runWithTimingCollector<T>(fn: () => T): T {\n return timingAls.run({ entries: [] }, fn);\n}\n\n/**\n * Record a timing entry for the current request.\n * No-ops if called outside a timing collector (e.g. in production).\n */\nexport function recordTiming(entry: TimingEntry): void {\n const store = timingAls.getStore();\n if (!store) return;\n store.entries.push(entry);\n}\n\n/**\n * Run a function and automatically record its duration as a timing entry.\n * Returns the function's result. No-ops the recording if outside a collector.\n */\nexport async function withTiming<T>(\n name: string,\n desc: string | undefined,\n fn: () => T | Promise<T>\n): Promise<T> {\n const store = timingAls.getStore();\n if (!store) return fn();\n\n const start = performance.now();\n try {\n return await fn();\n } finally {\n const dur = Math.round(performance.now() - start);\n store.entries.push({ name, dur, desc });\n }\n}\n\n/**\n * Get the Server-Timing header value for the current request.\n * Returns null if no entries exist or outside a collector.\n *\n * Format: `name;dur=123;desc=\"description\", name2;dur=456`\n * See RFC 6797 / Server-Timing spec for format details.\n */\nexport function getServerTimingHeader(): string | null {\n const store = timingAls.getStore();\n if (!store || store.entries.length === 0) return null;\n\n // Deduplicate names — if a name appears multiple times, suffix with index\n const nameCounts = new Map<string, number>();\n const entries = store.entries.map((entry) => {\n const count = nameCounts.get(entry.name) ?? 0;\n nameCounts.set(entry.name, count + 1);\n const uniqueName = count > 0 ? `${entry.name}-${count}` : entry.name;\n return { ...entry, name: uniqueName };\n });\n\n const parts = entries.map((entry) => {\n let part = `${entry.name};dur=${entry.dur}`;\n if (entry.desc) {\n // Escape quotes in desc per Server-Timing spec\n const safeDesc = entry.desc.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n part += `;desc=\"${safeDesc}\"`;\n }\n return part;\n });\n\n // Respect header size limits — browsers typically handle up to 8KB headers.\n // Truncate if the header exceeds 4KB to leave room for other headers.\n const MAX_HEADER_SIZE = 4096;\n let result = '';\n for (let i = 0; i < parts.length; i++) {\n const candidate = result ? `${result}, ${parts[i]}` : parts[i]!;\n if (candidate.length > MAX_HEADER_SIZE) break;\n result = candidate;\n }\n\n return result || null;\n}\n\n/**\n * Sanitize a URL for use in Server-Timing desc.\n * Strips query params and truncates long paths to avoid information leakage.\n */\nexport function sanitizeUrlForTiming(url: string): string {\n try {\n const parsed = new URL(url);\n const origin = parsed.host;\n let path = parsed.pathname;\n // Truncate long paths\n if (path.length > 50) {\n path = path.slice(0, 47) + '...';\n }\n return `${origin}${path}`;\n } catch {\n // Not a valid URL — truncate raw string\n if (url.length > 60) {\n return url.slice(0, 57) + '...';\n }\n return url;\n }\n}\n","/**\n * Error Formatter — rewrites SSR/RSC error messages to surface user code.\n *\n * When React or Vite throw errors during SSR, stack traces reference\n * vendored dependency paths (e.g. `.vite/deps_ssr/@vitejs_plugin-rsc_vendor_...`)\n * and mangled export names (`__vite_ssr_export_default__`). This module\n * rewrites error messages and stack traces to point at user code instead.\n *\n * Dev-only — in production, errors go through the structured logger\n * without formatting.\n */\n\n// ─── Stack Trace Rewriting ──────────────────────────────────────────────────\n\n/**\n * Patterns that identify internal Vite/RSC vendor paths in stack traces.\n * These are replaced with human-readable labels.\n */\nconst VENDOR_PATH_PATTERNS: Array<{ pattern: RegExp; replacement: string }> = [\n {\n pattern: /node_modules\\/\\.vite\\/deps_ssr\\/@vitejs_plugin-rsc_vendor_react-server-dom[^\\s)]+/g,\n replacement: '<react-server-dom>',\n },\n {\n pattern: /node_modules\\/\\.vite\\/deps_ssr\\/@vitejs_plugin-rsc_vendor[^\\s)]+/g,\n replacement: '<rsc-vendor>',\n },\n {\n pattern: /node_modules\\/\\.vite\\/deps_ssr\\/[^\\s)]+/g,\n replacement: '<vite-dep>',\n },\n {\n pattern: /node_modules\\/\\.vite\\/deps\\/[^\\s)]+/g,\n replacement: '<vite-dep>',\n },\n];\n\n/**\n * Patterns that identify Vite-mangled export names in error messages.\n */\nconst MANGLED_NAME_PATTERNS: Array<{ pattern: RegExp; replacement: string }> = [\n {\n pattern: /__vite_ssr_export_default__/g,\n replacement: '<default export>',\n },\n {\n pattern: /__vite_ssr_export_(\\w+)__/g,\n replacement: '<export $1>',\n },\n];\n\n/**\n * Rewrite an error's message and stack to replace internal Vite paths\n * and mangled names with human-readable labels.\n */\nexport function formatSsrError(error: unknown): string {\n if (!(error instanceof Error)) {\n return String(error);\n }\n\n let message = error.message;\n let stack = error.stack ?? '';\n\n // Rewrite mangled names in the message\n for (const { pattern, replacement } of MANGLED_NAME_PATTERNS) {\n message = message.replace(pattern, replacement);\n }\n\n // Rewrite vendor paths in the stack\n for (const { pattern, replacement } of VENDOR_PATH_PATTERNS) {\n stack = stack.replace(pattern, replacement);\n }\n\n // Rewrite mangled names in the stack too\n for (const { pattern, replacement } of MANGLED_NAME_PATTERNS) {\n stack = stack.replace(pattern, replacement);\n }\n\n // Extract hints from React-specific error patterns\n const hint = extractErrorHint(error.message);\n\n // Build formatted output: cleaned message, hint (if any), then cleaned stack\n const parts: string[] = [];\n parts.push(message);\n if (hint) {\n parts.push(` → ${hint}`);\n }\n\n // Include only the user-code frames from the stack (skip the first line\n // which is the message itself, and filter out vendor-only frames)\n const userFrames = extractUserFrames(stack);\n if (userFrames.length > 0) {\n parts.push('');\n parts.push(' User code in stack:');\n for (const frame of userFrames) {\n parts.push(` ${frame}`);\n }\n }\n\n return parts.join('\\n');\n}\n\n// ─── Error Hint Extraction ──────────────────────────────────────────────────\n\n/**\n * Extract a human-readable hint from common React/RSC error messages.\n *\n * React error messages contain useful information but the surrounding\n * context (vendor paths, mangled names) obscures it. This extracts the\n * actionable part as a one-line hint.\n */\nfunction extractErrorHint(message: string): string | null {\n // \"Functions cannot be passed directly to Client Components\"\n // Extract the component and prop name from the JSX-like syntax in the message\n const fnPassedMatch = message.match(/Functions cannot be passed directly to Client Components/);\n if (fnPassedMatch) {\n // Try to extract the prop name from the message\n // React formats: <... propName={function ...} ...>\n const propMatch = message.match(/<[^>]*?\\s(\\w+)=\\{function/);\n if (propMatch) {\n return `Prop \"${propMatch[1]}\" is a function — mark it \"use server\" or call it before passing`;\n }\n return 'A function prop was passed to a Client Component — mark it \"use server\" or call it before passing';\n }\n\n // \"Objects are not valid as a React child\"\n if (message.includes('Objects are not valid as a React child')) {\n return 'An object was rendered as JSX children — convert to string or extract the value';\n }\n\n // \"Cannot read properties of undefined/null\"\n const nullRefMatch = message.match(\n /Cannot read propert(?:y|ies) of (undefined|null) \\(reading '(\\w+)'\\)/\n );\n if (nullRefMatch) {\n return `Accessed .${nullRefMatch[2]} on ${nullRefMatch[1]} — check that the value exists`;\n }\n\n // \"X is not a function\"\n const notFnMatch = message.match(/(\\w+) is not a function/);\n if (notFnMatch) {\n return `\"${notFnMatch[1]}\" is not a function — check imports and exports`;\n }\n\n // \"Element type is invalid\"\n if (message.includes('Element type is invalid')) {\n return 'A component resolved to undefined/null — check default exports and import paths';\n }\n\n // \"Invalid hook call\" — hooks called outside React's render context.\n // In RSC, this typically means a 'use client' component was executed as a\n // server component instead of being serialized as a client reference.\n if (message.includes('Invalid hook call')) {\n return (\n 'A hook was called outside of a React component render. ' +\n \"If this is a 'use client' component, ensure the directive is at the very top of the file \" +\n '(before any imports) and that @vitejs/plugin-rsc is loaded correctly. ' +\n \"Barrel re-exports from non-'use client' files do not propagate the directive.\"\n );\n }\n\n return null;\n}\n\n// ─── Stack Frame Filtering ──────────────────────────────────────────────────\n\n/**\n * Extract stack frames that reference user code (not node_modules,\n * not framework internals).\n *\n * Returns at most 5 frames to keep output concise.\n */\nfunction extractUserFrames(stack: string): string[] {\n const lines = stack.split('\\n');\n const userFrames: string[] = [];\n\n for (const line of lines) {\n const trimmed = line.trim();\n // Skip non-frame lines\n if (!trimmed.startsWith('at ')) continue;\n // Skip node_modules, vendor, and internal frames\n if (\n trimmed.includes('node_modules') ||\n trimmed.includes('<react-server-dom>') ||\n trimmed.includes('<rsc-vendor>') ||\n trimmed.includes('<vite-dep>') ||\n trimmed.includes('node:internal')\n ) {\n continue;\n }\n userFrames.push(trimmed);\n if (userFrames.length >= 5) break;\n }\n\n return userFrames;\n}\n","/**\n * Logger — structured logging with environment-aware formatting.\n *\n * timber.js does not ship a logger. Users export any object with\n * info/warn/error/debug methods from instrumentation.ts and the framework\n * picks it up. Silent if no logger export is present.\n *\n * See design/17-logging.md §\"Production Logging\"\n */\n\nimport { getTraceStore } from './tracing.js';\nimport { formatSsrError } from './error-formatter.js';\n\n// ─── Logger Interface ─────────────────────────────────────────────────────\n\n/** Any object with standard log methods satisfies this — pino, winston, consola, console. */\nexport interface TimberLogger {\n info(msg: string, data?: Record<string, unknown>): void;\n warn(msg: string, data?: Record<string, unknown>): void;\n error(msg: string, data?: Record<string, unknown>): void;\n debug(msg: string, data?: Record<string, unknown>): void;\n}\n\n// ─── Logger Registry ──────────────────────────────────────────────────────\n\nlet _logger: TimberLogger | null = null;\n\n/**\n * Set the user-provided logger. Called by the instrumentation loader\n * when it finds a `logger` export in instrumentation.ts.\n */\nexport function setLogger(logger: TimberLogger): void {\n _logger = logger;\n}\n\n/**\n * Get the current logger, or null if none configured.\n * Framework-internal — used at framework event points to emit structured logs.\n */\nexport function getLogger(): TimberLogger | null {\n return _logger;\n}\n\n// ─── Framework Log Helpers ────────────────────────────────────────────────\n\n/**\n * Inject trace_id and span_id into log data for log–trace correlation.\n * Always injects trace_id (never undefined). Injects span_id only when OTEL is active.\n */\nfunction withTraceContext(data?: Record<string, unknown>): Record<string, unknown> {\n const store = getTraceStore();\n const enriched: Record<string, unknown> = { ...data };\n if (store) {\n enriched.trace_id = store.traceId;\n if (store.spanId) {\n enriched.span_id = store.spanId;\n }\n }\n return enriched;\n}\n\n// ─── Framework Event Emitters ─────────────────────────────────────────────\n\n/** Log a completed request. Level: info. */\nexport function logRequestCompleted(data: {\n method: string;\n path: string;\n status: number;\n durationMs: number;\n /** Number of concurrent in-flight requests (including this one) at completion time. */\n concurrency?: number;\n}): void {\n _logger?.info('request completed', withTraceContext(data));\n}\n\n/** Log request received. Level: debug. */\nexport function logRequestReceived(data: { method: string; path: string }): void {\n _logger?.debug('request received', withTraceContext(data));\n}\n\n/** Log a slow request warning. Level: warn. */\nexport function logSlowRequest(data: {\n method: string;\n path: string;\n durationMs: number;\n threshold: number;\n /** Number of concurrent in-flight requests at the time the slow request completed. */\n concurrency?: number;\n}): void {\n _logger?.warn('slow request exceeded threshold', withTraceContext(data));\n}\n\n/** Log middleware short-circuit. Level: debug. */\nexport function logMiddlewareShortCircuit(data: {\n method: string;\n path: string;\n status: number;\n}): void {\n _logger?.debug('middleware short-circuited', withTraceContext(data));\n}\n\n/** Log unhandled error in middleware phase. Level: error. */\nexport function logMiddlewareError(data: { method: string; path: string; error: unknown }): void {\n if (_logger) {\n _logger.error('unhandled error in middleware phase', withTraceContext(data));\n } else if (process.env.NODE_ENV !== 'production') {\n console.error('[timber] middleware error', data.error);\n }\n}\n\n/** Log unhandled render-phase error. Level: error. */\nexport function logRenderError(data: { method: string; path: string; error: unknown }): void {\n if (_logger) {\n _logger.error('unhandled render-phase error', withTraceContext(data));\n } else if (process.env.NODE_ENV !== 'production') {\n // No logger configured — fall back to console.error in dev with\n // cleaned-up error messages (vendor paths rewritten, hints added).\n console.error('[timber] render error:', formatSsrError(data.error));\n }\n}\n\n/** Log proxy.ts uncaught error. Level: error. */\nexport function logProxyError(data: { error: unknown }): void {\n if (_logger) {\n _logger.error('proxy.ts threw uncaught error', withTraceContext(data));\n } else if (process.env.NODE_ENV !== 'production') {\n console.error('[timber] proxy error', data.error);\n }\n}\n\n/** Log waitUntil() adapter missing (once at startup). Level: warn. */\nexport function logWaitUntilUnsupported(): void {\n _logger?.warn('adapter does not support waitUntil()');\n}\n\n/** Log waitUntil() promise rejection. Level: warn. */\nexport function logWaitUntilRejected(data: { error: unknown }): void {\n _logger?.warn('waitUntil() promise rejected', withTraceContext(data));\n}\n\n/** Log staleWhileRevalidate refetch failure. Level: warn. */\nexport function logSwrRefetchFailed(data: { cacheKey: string; error: unknown }): void {\n _logger?.warn('staleWhileRevalidate refetch failed', withTraceContext(data));\n}\n\n/** Log cache miss. Level: debug. */\nexport function logCacheMiss(data: { cacheKey: string }): void {\n _logger?.debug('timber.cache MISS', withTraceContext(data));\n}\n","/**\n * Instrumentation — loads and runs the user's instrumentation.ts file.\n *\n * instrumentation.ts is a file convention at the project root that exports:\n * - register() — called once at server startup, before the first request\n * - onRequestError() — called for every unhandled server error\n * - logger — any object with info/warn/error/debug methods\n *\n * See design/17-logging.md §\"instrumentation.ts — The Entry Point\"\n */\n\nimport { setLogger, type TimberLogger } from './logger.js';\n\n// ─── Instrumentation Types ────────────────────────────────────────────────\n\nexport type InstrumentationOnRequestError = (\n error: unknown,\n request: InstrumentationRequestInfo,\n context: InstrumentationErrorContext\n) => void | Promise<void>;\n\nexport interface InstrumentationRequestInfo {\n /** HTTP method: 'GET', 'POST', etc. */\n method: string;\n /** Request path: '/dashboard/projects/123' */\n path: string;\n /** Request headers as a plain object. */\n headers: Record<string, string>;\n}\n\nexport interface InstrumentationErrorContext {\n /** Which pipeline phase the error occurred in. */\n phase: 'proxy' | 'handler' | 'render' | 'action' | 'route';\n /** The route pattern: '/dashboard/projects/[id]' */\n routePath: string;\n /** Type of route that was matched. */\n routeType: 'page' | 'route' | 'action';\n /** Always set — OTEL trace ID or UUID fallback. */\n traceId: string;\n}\n\n// ─── Instrumentation Module Shape ─────────────────────────────────────────\n\ninterface InstrumentationModule {\n register?: () => void | Promise<void>;\n onRequestError?: InstrumentationOnRequestError;\n logger?: TimberLogger;\n}\n\n// ─── State ────────────────────────────────────────────────────────────────\n//\n// Intentional per-app singletons (not per-request). Instrumentation loads\n// once at server startup and persists for the lifetime of the process/isolate.\n// These must NOT be migrated to ALS — they are correctly scoped to the app.\n\nlet _initialized = false;\nlet _onRequestError: InstrumentationOnRequestError | null = null;\n\n/**\n * Load and initialize the user's instrumentation.ts module.\n *\n * - Awaits register() before returning (server blocks on this).\n * - Picks up the logger export and wires it into the framework logger.\n * - Stores onRequestError for later invocation.\n *\n * @param loader - Function that dynamically imports the user's instrumentation module.\n * Returns null if no instrumentation.ts exists.\n */\nexport async function loadInstrumentation(\n loader: () => Promise<InstrumentationModule | null>\n): Promise<void> {\n if (_initialized) return;\n _initialized = true;\n\n let mod: InstrumentationModule | null;\n try {\n mod = await loader();\n } catch (error) {\n console.error('[timber] Failed to load instrumentation.ts:', error);\n return;\n }\n\n if (!mod) return;\n\n // Wire up the logger export\n if (mod.logger && typeof mod.logger.info === 'function') {\n setLogger(mod.logger);\n }\n\n // Store onRequestError for later\n if (typeof mod.onRequestError === 'function') {\n _onRequestError = mod.onRequestError;\n }\n\n // Await register() — server does not accept requests until this resolves\n if (typeof mod.register === 'function') {\n try {\n await mod.register();\n } catch (error) {\n console.error('[timber] instrumentation.ts register() threw:', error);\n throw error;\n }\n }\n}\n\n/**\n * Call the user's onRequestError hook. Catches and logs any errors thrown\n * by the hook itself — it must not affect the response.\n */\nexport async function callOnRequestError(\n error: unknown,\n request: InstrumentationRequestInfo,\n context: InstrumentationErrorContext\n): Promise<void> {\n if (!_onRequestError) return;\n try {\n await _onRequestError(error, request, context);\n } catch (hookError) {\n console.error('[timber] onRequestError hook threw:', hookError);\n }\n}\n\n/**\n * Check if onRequestError is registered.\n */\nexport function hasOnRequestError(): boolean {\n return _onRequestError !== null;\n}\n\n/**\n * Reset instrumentation state. Test-only.\n */\nexport function resetInstrumentation(): void {\n _initialized = false;\n _onRequestError = null;\n}\n","/**\n * Metadata route helpers for the request pipeline.\n *\n * Handles serving static metadata files and serializing sitemap responses.\n * Extracted from pipeline.ts to keep files under 500 lines.\n *\n * See design/16-metadata.md §\"Metadata Routes\"\n */\n\nimport { readFile } from 'node:fs/promises';\n\n/**\n * Content types that are text-based and should include charset=utf-8.\n * Binary formats (images) should not include charset.\n */\nconst TEXT_CONTENT_TYPES = new Set([\n 'application/xml',\n 'text/plain',\n 'application/json',\n 'application/manifest+json',\n 'image/svg+xml',\n]);\n\n/**\n * Serve a static metadata file by reading it from disk.\n *\n * Static metadata route files (.xml, .txt, .json, .png, .ico, .svg, etc.)\n * are served as-is with the appropriate Content-Type header.\n * Text files include charset=utf-8; binary files do not.\n *\n * See design/16-metadata.md §\"Metadata Routes\"\n */\nexport async function serveStaticMetadataFile(\n metaMatch: import('./route-matcher.js').MetadataRouteMatch\n): Promise<Response> {\n const { contentType, file } = metaMatch;\n const isText = TEXT_CONTENT_TYPES.has(contentType);\n\n const body = await readFile(file.filePath);\n\n const headers: Record<string, string> = {\n 'Content-Type': isText ? `${contentType}; charset=utf-8` : contentType,\n 'Content-Length': String(body.byteLength),\n };\n\n return new Response(body, { status: 200, headers });\n}\n\n/**\n * Serialize a sitemap array to XML.\n * Follows the sitemap.org protocol: https://www.sitemaps.org/protocol.html\n */\nexport function serializeSitemap(\n entries: Array<{\n url: string;\n lastModified?: string | Date;\n changeFrequency?: string;\n priority?: number;\n }>\n): string {\n const urls = entries\n .map((e) => {\n let xml = ` <url>\\n <loc>${escapeXml(e.url)}</loc>`;\n if (e.lastModified) {\n const date = e.lastModified instanceof Date ? e.lastModified.toISOString() : e.lastModified;\n xml += `\\n <lastmod>${escapeXml(date)}</lastmod>`;\n }\n if (e.changeFrequency) {\n xml += `\\n <changefreq>${escapeXml(e.changeFrequency)}</changefreq>`;\n }\n if (e.priority !== undefined) {\n xml += `\\n <priority>${e.priority}</priority>`;\n }\n xml += '\\n </url>';\n return xml;\n })\n .join('\\n');\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n${urls}\\n</urlset>`;\n}\n\n/** Escape special XML characters. */\nexport function escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n","/**\n * Interception route matching for the request pipeline.\n *\n * Matches target URLs against interception rewrites to support the\n * modal route pattern (soft navigation intercepts).\n *\n * Extracted from pipeline.ts to keep files under 500 lines.\n *\n * See design/07-routing.md §\"Intercepting Routes\"\n */\n\n/** Result of a successful interception match. */\nexport interface InterceptionMatchResult {\n /** The pathname to re-match (the source/intercepting route's parent). */\n sourcePathname: string;\n}\n\n/**\n * Check if an intercepting route applies for this soft navigation.\n *\n * Matches the target pathname against interception rewrites, constrained\n * by the source URL (X-Timber-URL header — where the user navigates FROM).\n *\n * Returns the source pathname to re-match if interception applies, or null.\n */\nexport function findInterceptionMatch(\n targetPathname: string,\n sourceUrl: string,\n rewrites: import('#/routing/interception.js').InterceptionRewrite[]\n): InterceptionMatchResult | null {\n for (const rewrite of rewrites) {\n // Check if the source URL starts with the intercepting prefix\n if (!sourceUrl.startsWith(rewrite.interceptingPrefix)) continue;\n\n // Check if the target URL matches the intercepted pattern.\n // Dynamic segments in the pattern match any single URL segment.\n if (pathnameMatchesPattern(targetPathname, rewrite.interceptedPattern)) {\n return { sourcePathname: rewrite.interceptingPrefix };\n }\n }\n return null;\n}\n\n/**\n * Check if a pathname matches a URL pattern with dynamic segments.\n *\n * Supports [param] (single segment) and [...param] (one or more segments).\n * Static segments must match exactly.\n */\nexport function pathnameMatchesPattern(pathname: string, pattern: string): boolean {\n const pathParts = pathname === '/' ? [] : pathname.slice(1).split('/');\n const patternParts = pattern === '/' ? [] : pattern.slice(1).split('/');\n\n let pi = 0;\n for (let i = 0; i < patternParts.length; i++) {\n const segment = patternParts[i];\n\n // Catch-all: [...param] or [[...param]] — matches rest of URL\n if (segment.startsWith('[...') || segment.startsWith('[[...')) {\n return pi < pathParts.length || segment.startsWith('[[...');\n }\n\n // Dynamic: [param] — matches any single segment\n if (segment.startsWith('[') && segment.endsWith(']')) {\n if (pi >= pathParts.length) return false;\n pi++;\n continue;\n }\n\n // Static — must match exactly\n if (pi >= pathParts.length || pathParts[pi] !== segment) return false;\n pi++;\n }\n\n return pi === pathParts.length;\n}\n","/**\n * Request pipeline — the central dispatch for all timber.js requests.\n *\n * Pipeline stages (in order):\n * proxy.ts → canonicalize → route match → 103 Early Hints → middleware.ts → render\n *\n * Each stage is a pure function or returns a Response to short-circuit.\n * Each request gets a trace ID, structured logging, and OTEL spans.\n *\n * See design/07-routing.md §\"Request Lifecycle\", design/02-rendering-pipeline.md §\"Request Flow\",\n * and design/17-logging.md §\"Production Logging\"\n */\n\nimport { canonicalize } from './canonicalize.js';\nimport { runProxy, type ProxyExport } from './proxy.js';\nimport { runMiddleware, type MiddlewareFn } from './middleware-runner.js';\nimport {\n runWithTimingCollector,\n withTiming,\n getServerTimingHeader,\n} from './server-timing.js';\nimport {\n runWithRequestContext,\n applyRequestHeaderOverlay,\n setMutableCookieContext,\n getSetCookieHeaders,\n markResponseFlushed,\n} from './request-context.js';\nimport {\n generateTraceId,\n runWithTraceId,\n getOtelTraceId,\n replaceTraceId,\n withSpan,\n setSpanAttribute,\n traceId,\n} from './tracing.js';\nimport {\n logRequestReceived,\n logRequestCompleted,\n logSlowRequest,\n logProxyError,\n logMiddlewareError,\n logMiddlewareShortCircuit,\n logRenderError,\n} from './logger.js';\nimport { callOnRequestError } from './instrumentation.js';\nimport { RedirectSignal, DenySignal } from './primitives.js';\nimport { serveStaticMetadataFile, serializeSitemap } from './pipeline-metadata.js';\nimport { findInterceptionMatch } from './pipeline-interception.js';\nimport type { MiddlewareContext } from './types.js';\nimport type { SegmentNode } from '#/routing/types.js';\n\n// ─── Route Match Result ────────────────────────────────────────────────────\n\n/** Result of matching a canonical pathname against the route tree. */\nexport interface RouteMatch {\n /** The matched segment chain from root to leaf. */\n segments: SegmentNode[];\n /** Extracted route params (catch-all segments produce string[]). */\n params: Record<string, string | string[]>;\n /** The leaf segment's middleware.ts export, if any. */\n middleware?: MiddlewareFn;\n}\n\n/** Function that matches a canonical pathname to a route. */\nexport type RouteMatcher = (pathname: string) => RouteMatch | null;\n\n/** Function that matches a canonical pathname to a metadata route. */\nexport type MetadataRouteMatcher = (\n pathname: string\n) => import('./route-matcher.js').MetadataRouteMatch | null;\n\n/** Context for intercepting route resolution (modal pattern). */\nexport interface InterceptionContext {\n /** The URL the user is navigating TO (the intercepted route). */\n targetPathname: string;\n}\n\n/** Function that renders a matched route into a Response. */\nexport type RouteRenderer = (\n req: Request,\n match: RouteMatch,\n responseHeaders: Headers,\n requestHeaderOverlay: Headers,\n interception?: InterceptionContext\n) => Response | Promise<Response>;\n\n/** Function that sends 103 Early Hints for a matched route. */\nexport type EarlyHintsEmitter = (\n match: RouteMatch,\n req: Request,\n responseHeaders: Headers\n) => void | Promise<void>;\n\n// ─── Pipeline Configuration ────────────────────────────────────────────────\n\nexport interface PipelineConfig {\n /** The proxy.ts default export (function or array). Undefined if no proxy.ts. */\n proxy?: ProxyExport;\n /** Lazy loader for proxy.ts — called per-request so HMR updates take effect. */\n proxyLoader?: () => Promise<{ default: ProxyExport }>;\n /** Route matcher — resolves a canonical pathname to a RouteMatch. */\n matchRoute: RouteMatcher;\n /** Metadata route matcher — resolves metadata route pathnames (sitemap.xml, robots.txt, etc.) */\n matchMetadataRoute?: MetadataRouteMatcher;\n /** Renderer — produces the final Response for a matched route. */\n render: RouteRenderer;\n /** Renderer for no-match 404 — renders 404.tsx in root layout. */\n renderNoMatch?: (req: Request, responseHeaders: Headers) => Response | Promise<Response>;\n /** Early hints emitter — fires 103 hints after route match, before middleware. */\n earlyHints?: EarlyHintsEmitter;\n /** Whether to strip trailing slashes during canonicalization. Default: true. */\n stripTrailingSlash?: boolean;\n /** Slow request threshold in ms. Requests exceeding this emit a warning. 0 to disable. Default: 3000. */\n slowRequestMs?: number;\n /**\n * Interception rewrites — conditional routes for the modal pattern.\n * Generated at build time from intercepting route directories.\n * See design/07-routing.md §\"Intercepting Routes\"\n */\n interceptionRewrites?: import('#/routing/interception.js').InterceptionRewrite[];\n /**\n * Emit Server-Timing header on responses for Chrome DevTools visibility.\n * Only enable in dev mode — exposes internal timing data.\n *\n * Default: false (production-safe).\n */\n enableServerTiming?: boolean;\n /**\n * Dev pipeline error callback — called when a pipeline phase (proxy,\n * middleware, render) catches an unhandled error. Used to wire the error\n * into the Vite browser error overlay in dev mode.\n *\n * Undefined in production — zero overhead.\n */\n onPipelineError?: (error: Error, phase: string) => void;\n /**\n * Fallback error renderer — called when a catastrophic error escapes the\n * render phase. Produces an HTML Response instead of a bare empty 500.\n *\n * In dev mode, this renders a styled error page with the error message\n * and stack trace. In production, this attempts to render the app's\n * error.tsx / 5xx.tsx / 500.tsx from the root segment.\n *\n * If this function throws, the pipeline falls back to a bare\n * `new Response(null, { status: 500 })`.\n */\n renderFallbackError?: (\n error: unknown,\n req: Request,\n responseHeaders: Headers\n ) => Response | Promise<Response>;\n}\n\n// ─── Pipeline ──────────────────────────────────────────────────────────────\n\n/**\n * Create the request handler from a pipeline configuration.\n *\n * Returns a function that processes an incoming Request through all pipeline stages\n * and produces a Response. This is the top-level entry point for the server.\n */\nexport function createPipeline(config: PipelineConfig): (req: Request) => Promise<Response> {\n const {\n proxy,\n matchRoute,\n render,\n earlyHints,\n stripTrailingSlash = true,\n slowRequestMs = 3000,\n enableServerTiming = false,\n onPipelineError,\n } = config;\n\n // Concurrent request counter — tracks how many requests are in-flight.\n // Logged with each request for diagnosing resource contention.\n let activeRequests = 0;\n\n return async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n const method = req.method;\n const path = url.pathname;\n const startTime = performance.now();\n activeRequests++;\n\n // Establish per-request trace ID scope (design/17-logging.md §\"trace_id is Always Set\").\n // This runs before runWithRequestContext so traceId() is available from the\n // very first line of proxy.ts, middleware.ts, and all server code.\n const traceIdValue = generateTraceId();\n\n return runWithTraceId(traceIdValue, async () => {\n // Establish request context ALS scope so headers() and cookies() work\n // throughout the entire request lifecycle (proxy, middleware, render).\n return runWithRequestContext(req, async () => {\n // In dev mode, wrap with timing collector for Server-Timing header.\n // The collector uses ALS so timing entries are per-request.\n const runRequest = async () => {\n logRequestReceived({ method, path });\n\n const response = await withSpan(\n 'http.server.request',\n { 'http.request.method': method, 'url.path': path },\n async () => {\n // If OTEL is active, the root span now exists — replace the UUID\n // fallback with the real OTEL trace ID for log–trace correlation.\n const otelIds = await getOtelTraceId();\n if (otelIds) {\n replaceTraceId(otelIds.traceId, otelIds.spanId);\n }\n\n let result: Response;\n if (proxy || config.proxyLoader) {\n result = await runProxyPhase(req, method, path);\n } else {\n result = await handleRequest(req, method, path);\n }\n\n // Set response status on the root span before it ends —\n // DevSpanProcessor reads this for tree/summary output.\n await setSpanAttribute('http.response.status_code', result.status);\n\n // Append Server-Timing header.\n // In dev mode: detailed per-phase breakdown (proxy, middleware, render).\n // In production: single total duration — safe to expose, no phase names.\n // Response.redirect() creates immutable headers, so we must\n // ensure mutability before writing Server-Timing.\n if (enableServerTiming) {\n const serverTiming = getServerTimingHeader();\n if (serverTiming) {\n result = ensureMutableResponse(result);\n result.headers.set('Server-Timing', serverTiming);\n }\n } else {\n // Production: emit total request duration only.\n // No phase breakdown — prevents information disclosure\n // while giving browser DevTools useful timing data.\n const totalMs = Math.round(performance.now() - startTime);\n result = ensureMutableResponse(result);\n result.headers.set('Server-Timing', `total;dur=${totalMs}`);\n }\n\n return result;\n }\n );\n\n // Post-span: structured production logging\n const durationMs = Math.round(performance.now() - startTime);\n const status = response.status;\n const concurrency = activeRequests;\n activeRequests--;\n logRequestCompleted({ method, path, status, durationMs, concurrency });\n\n if (slowRequestMs > 0 && durationMs > slowRequestMs) {\n logSlowRequest({ method, path, durationMs, threshold: slowRequestMs, concurrency });\n }\n\n return response;\n };\n\n return enableServerTiming\n ? runWithTimingCollector(runRequest)\n : runRequest();\n });\n });\n };\n\n async function runProxyPhase(req: Request, method: string, path: string): Promise<Response> {\n try {\n // Resolve the proxy export. When a proxyLoader is provided (lazy import),\n // it is called per-request so HMR updates in dev take effect immediately.\n let proxyExport: ProxyExport;\n if (config.proxyLoader) {\n const mod = await config.proxyLoader();\n proxyExport = mod.default;\n } else {\n proxyExport = config.proxy!;\n }\n const proxyFn = () =>\n runProxy(proxyExport, req, () => handleRequest(req, method, path));\n return await withSpan('timber.proxy', {}, () =>\n enableServerTiming\n ? withTiming('proxy', 'proxy.ts', proxyFn)\n : proxyFn()\n );\n } catch (error) {\n // Uncaught proxy.ts error → bare HTTP 500\n logProxyError({ error });\n await fireOnRequestError(error, req, 'proxy');\n if (onPipelineError && error instanceof Error) onPipelineError(error, 'proxy');\n return new Response(null, { status: 500 });\n }\n }\n\n async function handleRequest(req: Request, method: string, path: string): Promise<Response> {\n // Stage 1: URL canonicalization\n const url = new URL(req.url);\n const result = canonicalize(url.pathname, stripTrailingSlash);\n if (!result.ok) {\n return new Response(null, { status: result.status });\n }\n const canonicalPathname = result.pathname;\n\n // Stage 1b: Metadata route matching — runs before regular route matching.\n // Metadata routes skip middleware.ts and access.ts (public endpoints for crawlers).\n // See design/16-metadata.md §\"Pipeline Integration\"\n if (config.matchMetadataRoute) {\n const metaMatch = config.matchMetadataRoute(canonicalPathname);\n if (metaMatch) {\n try {\n // Static metadata files (.xml, .txt, .png, .ico, etc.) are served\n // directly from disk. Dynamic metadata routes (.ts, .tsx) export a\n // handler function that generates the response.\n if (metaMatch.isStatic) {\n return await serveStaticMetadataFile(metaMatch);\n }\n\n const mod = (await metaMatch.file.load()) as { default?: Function };\n if (typeof mod.default !== 'function') {\n return new Response('Metadata route must export a default function', { status: 500 });\n }\n const handlerResult = await mod.default();\n // If the handler returns a Response, use it directly\n if (handlerResult instanceof Response) {\n return handlerResult;\n }\n // Otherwise, serialize based on content type\n const contentType = metaMatch.contentType;\n let body: string;\n if (typeof handlerResult === 'string') {\n body = handlerResult;\n } else if (contentType === 'application/xml') {\n body = serializeSitemap(handlerResult);\n } else if (contentType === 'application/manifest+json') {\n body = JSON.stringify(handlerResult, null, 2);\n } else {\n body = typeof handlerResult === 'string' ? handlerResult : String(handlerResult);\n }\n return new Response(body, {\n status: 200,\n headers: { 'Content-Type': `${contentType}; charset=utf-8` },\n });\n } catch (error) {\n logRenderError({ method, path, error });\n if (onPipelineError && error instanceof Error) onPipelineError(error, 'metadata-route');\n return new Response(null, { status: 500 });\n }\n }\n }\n\n // Stage 2: Route matching\n let match = matchRoute(canonicalPathname);\n let interception: InterceptionContext | undefined;\n\n // Stage 2a: Intercepting route resolution (modal pattern).\n // On soft navigation, check if an intercepting route should render instead.\n // The client sends X-Timber-URL with the current pathname (where they're\n // navigating FROM). If a rewrite matches, re-route to the source URL so\n // the source layout renders with the intercepted content in the slot.\n const sourceUrl = req.headers.get('X-Timber-URL');\n if (sourceUrl && config.interceptionRewrites?.length) {\n const intercepted = findInterceptionMatch(\n canonicalPathname,\n sourceUrl,\n config.interceptionRewrites\n );\n if (intercepted) {\n const sourceMatch = matchRoute(intercepted.sourcePathname);\n if (sourceMatch) {\n match = sourceMatch;\n interception = { targetPathname: canonicalPathname };\n }\n }\n }\n\n if (!match) {\n // No route matched — render 404.tsx in root layout if available,\n // otherwise fall back to a bare 404 Response.\n if (config.renderNoMatch) {\n const responseHeaders = new Headers();\n return config.renderNoMatch(req, responseHeaders);\n }\n return new Response(null, { status: 404 });\n }\n\n // Response and request header containers — created before early hints so\n // the emitter can append Link headers (e.g. for Cloudflare CDN → 103).\n const responseHeaders = new Headers();\n const requestHeaderOverlay = new Headers();\n\n // Stage 2b: 103 Early Hints (before middleware, after match)\n // Fires before middleware so the browser can begin fetching critical\n // assets while middleware runs. Non-fatal — a failing emitter never\n // blocks the request.\n if (earlyHints) {\n try {\n await earlyHints(match, req, responseHeaders);\n } catch {\n // Early hints failure is non-fatal\n }\n }\n\n // Stage 3: Leaf middleware.ts (only the leaf route's middleware runs)\n if (match.middleware) {\n const ctx: MiddlewareContext = {\n req,\n requestHeaders: requestHeaderOverlay,\n headers: responseHeaders,\n params: match.params,\n searchParams: new URL(req.url).searchParams,\n earlyHints: (hints) => {\n for (const hint of hints) {\n let value = `<${hint.href}>; rel=${hint.rel}`;\n if (hint.as !== undefined) value += `; as=${hint.as}`;\n if (hint.crossOrigin !== undefined) value += `; crossorigin=${hint.crossOrigin}`;\n if (hint.fetchPriority !== undefined) value += `; fetchpriority=${hint.fetchPriority}`;\n responseHeaders.append('Link', value);\n }\n },\n };\n\n try {\n // Enable cookie mutation during middleware (design/29-cookies.md §\"Context Tracking\")\n setMutableCookieContext(true);\n const middlewareFn = () => runMiddleware(match.middleware!, ctx);\n const middlewareResponse = await withSpan('timber.middleware', {}, () =>\n enableServerTiming\n ? withTiming('mw', 'middleware.ts', middlewareFn)\n : middlewareFn()\n );\n setMutableCookieContext(false);\n if (middlewareResponse) {\n // Apply cookie jar to short-circuit response.\n // Response.redirect() creates immutable headers, so ensure\n // mutability before appending Set-Cookie entries.\n const finalResponse = ensureMutableResponse(middlewareResponse);\n applyCookieJar(finalResponse.headers);\n logMiddlewareShortCircuit({ method, path, status: finalResponse.status });\n return finalResponse;\n }\n // Middleware succeeded without short-circuiting — apply any\n // injected request headers so headers() returns them downstream.\n applyRequestHeaderOverlay(requestHeaderOverlay);\n } catch (error) {\n setMutableCookieContext(false);\n // RedirectSignal from middleware → HTTP redirect (not an error).\n // For RSC payload requests (client navigation), return 204 + X-Timber-Redirect\n // so the client router can perform a soft SPA redirect. A raw 302 would be\n // turned into an opaque redirect by fetch({redirect:'manual'}), crashing\n // createFromFetch. See design/19-client-navigation.md.\n if (error instanceof RedirectSignal) {\n applyCookieJar(responseHeaders);\n const isRsc = (req.headers.get('Accept') ?? '').includes('text/x-component');\n if (isRsc) {\n responseHeaders.set('X-Timber-Redirect', error.location);\n return new Response(null, { status: 204, headers: responseHeaders });\n }\n responseHeaders.set('Location', error.location);\n return new Response(null, { status: error.status, headers: responseHeaders });\n }\n // DenySignal from middleware → HTTP deny status\n if (error instanceof DenySignal) {\n return new Response(null, { status: error.status });\n }\n // Middleware throw → HTTP 500 (middleware runs before rendering,\n // no error boundary to catch it)\n logMiddlewareError({ method, path, error });\n await fireOnRequestError(error, req, 'handler');\n if (onPipelineError && error instanceof Error) onPipelineError(error, 'middleware');\n return new Response(null, { status: 500 });\n }\n }\n\n // Apply cookie jar to response headers before render commits them.\n // Middleware may have set cookies; they need to be on responseHeaders\n // before flushResponse creates the Response object.\n applyCookieJar(responseHeaders);\n\n // Stage 4: Render (access gates + element tree + renderToReadableStream)\n try {\n const renderFn = () =>\n render(req, match, responseHeaders, requestHeaderOverlay, interception);\n const response = await withSpan('timber.render', { 'http.route': canonicalPathname }, () =>\n enableServerTiming\n ? withTiming('render', 'RSC + SSR render', renderFn)\n : renderFn()\n );\n markResponseFlushed();\n return response;\n } catch (error) {\n // DenySignal leaked from render (e.g. notFound() in metadata()).\n // Return the deny status code instead of 500.\n if (error instanceof DenySignal) {\n return new Response(null, { status: error.status });\n }\n // RedirectSignal leaked from render — honour the redirect.\n if (error instanceof RedirectSignal) {\n responseHeaders.set('Location', error.location);\n return new Response(null, { status: error.status, headers: responseHeaders });\n }\n logRenderError({ method, path, error });\n await fireOnRequestError(error, req, 'render');\n if (onPipelineError && error instanceof Error) onPipelineError(error, 'render');\n // Try fallback error page before bare 500\n if (config.renderFallbackError) {\n try {\n return await config.renderFallbackError(error, req, responseHeaders);\n } catch {\n // Fallback rendering itself failed — fall through to bare 500\n }\n }\n return new Response(null, { status: 500 });\n }\n }\n}\n\n/**\n * Fire the user's onRequestError hook with request context.\n * Extracts request info from the Request object and calls the instrumentation hook.\n */\nasync function fireOnRequestError(\n error: unknown,\n req: Request,\n phase: 'proxy' | 'handler' | 'render' | 'action' | 'route'\n): Promise<void> {\n const url = new URL(req.url);\n const headersObj: Record<string, string> = {};\n req.headers.forEach((v, k) => {\n headersObj[k] = v;\n });\n\n await callOnRequestError(\n error,\n { method: req.method, path: url.pathname, headers: headersObj },\n { phase, routePath: url.pathname, routeType: 'page', traceId: traceId() }\n );\n}\n\n\n\n// ─── Cookie Helpers ──────────────────────────────────────────────────────\n\n/**\n * Apply all Set-Cookie headers from the cookie jar to a Headers object.\n * Each cookie gets its own Set-Cookie header per RFC 6265 §4.1.\n */\nfunction applyCookieJar(headers: Headers): void {\n for (const value of getSetCookieHeaders()) {\n headers.append('Set-Cookie', value);\n }\n}\n\n// ─── Immutable Response Helpers ──────────────────────────────────────────\n\n/**\n * Ensure a Response has mutable headers so the pipeline can safely append\n * Set-Cookie and Server-Timing entries.\n *\n * `Response.redirect()` and some platform-level responses return objects\n * with immutable headers. Calling `.set()` or `.append()` on them throws\n * `TypeError: immutable`. This helper detects the immutable case by\n * attempting a no-op write and, on failure, clones into a fresh Response\n * with mutable headers.\n */\nfunction ensureMutableResponse(response: Response): Response {\n try {\n // Probe mutability with a benign operation that we immediately undo.\n // We pick a header name that is extremely unlikely to collide with\n // anything meaningful and delete it right away.\n response.headers.set('X-Timber-Probe', '1');\n response.headers.delete('X-Timber-Probe');\n return response;\n } catch {\n // Headers are immutable — rebuild with mutable headers.\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: new Headers(response.headers),\n });\n }\n}\n\n\n","/**\n * Build manifest types and utilities for CSS and JS asset tracking.\n *\n * The build manifest maps route segment file paths to their output\n * chunks from Vite's client build. This enables:\n * - <link rel=\"stylesheet\"> injection in HTML <head>\n * - <script type=\"module\"> with hashed URLs in production\n * - <link rel=\"modulepreload\"> for client chunk dependencies\n * - Link preload headers for Early Hints (103)\n *\n * In dev mode, Vite's HMR client handles CSS/JS injection, so the build\n * manifest is empty. In production, it's populated from Vite's\n * .vite/manifest.json after the client build.\n *\n * Design docs: 18-build-system.md §\"Build Manifest\", 02-rendering-pipeline.md §\"Early Hints\"\n */\n\n/** A font asset entry in the build manifest. */\nexport interface ManifestFontEntry {\n /** URL path to the font file (e.g. `/_timber/fonts/inter-latin-400-abc123.woff2`). */\n href: string;\n /** Font format (e.g. `woff2`). */\n format: string;\n /** Crossorigin attribute — always `anonymous` for fonts. */\n crossOrigin: string;\n}\n\n/** Build manifest mapping input file paths to output asset URLs. */\nexport interface BuildManifest {\n /** Map from input file path (relative to project root) to output CSS URLs. */\n css: Record<string, string[]>;\n /** Map from input file path to output JS chunk URL (hashed filename). */\n js: Record<string, string>;\n /** Map from input file path to transitive JS dependency URLs for modulepreload. */\n modulepreload: Record<string, string[]>;\n /** Map from input file path to font assets used by that module. */\n fonts: Record<string, ManifestFontEntry[]>;\n}\n\n/** Empty build manifest used in dev mode. */\nexport const EMPTY_BUILD_MANIFEST: BuildManifest = {\n css: {},\n js: {},\n modulepreload: {},\n fonts: {},\n};\n\n/** Segment shape expected by collectRouteCss (matches ManifestSegmentNode). */\ninterface SegmentWithFiles {\n layout?: { filePath: string };\n page?: { filePath: string };\n}\n\n/**\n * Collect all CSS files needed for a matched route's segment chain.\n *\n * Walks segments root → leaf, collecting CSS for each layout and page.\n * Deduplicates while preserving order (root layout CSS first).\n */\nexport function collectRouteCss(segments: SegmentWithFiles[], manifest: BuildManifest): string[] {\n const seen = new Set<string>();\n const result: string[] = [];\n\n for (const segment of segments) {\n for (const file of [segment.layout, segment.page]) {\n if (!file) continue;\n const cssFiles = manifest.css[file.filePath];\n if (!cssFiles) continue;\n for (const url of cssFiles) {\n if (!seen.has(url)) {\n seen.add(url);\n result.push(url);\n }\n }\n }\n }\n\n return result;\n}\n\n/**\n * Generate <link rel=\"stylesheet\"> tags for CSS URLs.\n *\n * Returns an HTML string to prepend to headHtml for injection\n * via injectHead() before </head>.\n */\nexport function buildCssLinkTags(cssUrls: string[]): string {\n return cssUrls.map((url) => `<link rel=\"stylesheet\" href=\"${url}\">`).join('');\n}\n\n/**\n * Generate a Link header value for CSS preload hints.\n *\n * Cloudflare CDN automatically converts Link headers with rel=preload\n * into 103 Early Hints responses. This avoids platform-specific 103\n * sending code.\n *\n * Example output: `</assets/root.css>; rel=preload; as=style, </assets/page.css>; rel=preload; as=style`\n */\nexport function buildLinkHeaders(cssUrls: string[]): string {\n return cssUrls.map((url) => `<${url}>; rel=preload; as=style`).join(', ');\n}\n\n// ─── Font utilities ──────────────────────────────────────────────────────\n\n/**\n * Collect all font entries needed for a matched route's segment chain.\n *\n * Walks segments root → leaf, collecting fonts for each layout and page.\n * Deduplicates by href while preserving order.\n */\nexport function collectRouteFonts(\n segments: SegmentWithFiles[],\n manifest: BuildManifest\n): ManifestFontEntry[] {\n const seen = new Set<string>();\n const result: ManifestFontEntry[] = [];\n\n for (const segment of segments) {\n for (const file of [segment.layout, segment.page]) {\n if (!file) continue;\n const fonts = manifest.fonts[file.filePath];\n if (!fonts) continue;\n for (const entry of fonts) {\n if (!seen.has(entry.href)) {\n seen.add(entry.href);\n result.push(entry);\n }\n }\n }\n }\n\n return result;\n}\n\n/**\n * Generate <link rel=\"preload\"> tags for font assets.\n *\n * Font preloads use `as=font` and always include `crossorigin` (required\n * for font preloads even for same-origin resources per the spec).\n */\nexport function buildFontPreloadTags(fonts: ManifestFontEntry[]): string {\n return fonts\n .map(\n (f) =>\n `<link rel=\"preload\" href=\"${f.href}\" as=\"font\" type=\"font/${f.format}\" crossorigin=\"${f.crossOrigin}\">`\n )\n .join('');\n}\n\n/**\n * Generate Link header values for font preload hints.\n *\n * Cloudflare CDN converts Link headers with rel=preload into 103 Early Hints.\n *\n * Example: `</fonts/inter.woff2>; rel=preload; as=font; crossorigin`\n */\nexport function buildFontLinkHeaders(fonts: ManifestFontEntry[]): string {\n return fonts.map((f) => `<${f.href}>; rel=preload; as=font; crossorigin`).join(', ');\n}\n\n// ─── JS chunk utilities ──────────────────────────────────────────────────\n\n/**\n * Collect JS chunk URLs for a matched route's segment chain.\n *\n * Walks segments root → leaf, collecting the JS chunk for each layout\n * and page. Deduplicates while preserving order.\n */\nexport function collectRouteJs(segments: SegmentWithFiles[], manifest: BuildManifest): string[] {\n const seen = new Set<string>();\n const result: string[] = [];\n\n for (const segment of segments) {\n for (const file of [segment.layout, segment.page]) {\n if (!file) continue;\n const jsUrl = manifest.js[file.filePath];\n if (!jsUrl) continue;\n if (!seen.has(jsUrl)) {\n seen.add(jsUrl);\n result.push(jsUrl);\n }\n }\n }\n\n return result;\n}\n\n/**\n * Collect modulepreload URLs for a matched route's segment chain.\n *\n * Walks segments root → leaf, collecting transitive JS dependencies\n * for each layout and page. Deduplicates across segments.\n */\nexport function collectRouteModulepreloads(\n segments: SegmentWithFiles[],\n manifest: BuildManifest\n): string[] {\n const seen = new Set<string>();\n const result: string[] = [];\n\n for (const segment of segments) {\n for (const file of [segment.layout, segment.page]) {\n if (!file) continue;\n const preloads = manifest.modulepreload[file.filePath];\n if (!preloads) continue;\n for (const url of preloads) {\n if (!seen.has(url)) {\n seen.add(url);\n result.push(url);\n }\n }\n }\n }\n\n return result;\n}\n\n/**\n * Generate <link rel=\"modulepreload\"> tags for JS dependency URLs.\n *\n * Modulepreload hints tell the browser to fetch and parse JS modules\n * before they're needed, reducing waterfall latency for dynamic imports.\n */\nexport function buildModulepreloadTags(urls: string[]): string {\n return urls.map((url) => `<link rel=\"modulepreload\" href=\"${url}\">`).join('');\n}\n\n/**\n * Generate a <script type=\"module\"> tag for a JS entry point.\n */\nexport function buildEntryScriptTag(url: string): string {\n return `<script type=\"module\" src=\"${url}\"></script>`;\n}\n","/**\n * 103 Early Hints utilities.\n *\n * Early Hints are sent before the final response to let the browser\n * start fetching critical resources (CSS, fonts, JS) while the server\n * is still rendering.\n *\n * The framework collects hints from two sources:\n * 1. Build manifest — CSS, fonts, and JS chunks known at route-match time\n * 2. ctx.earlyHints() — explicit hints added by middleware or route handlers\n *\n * Both are emitted as Link headers. Cloudflare CDN automatically converts\n * Link headers into 103 Early Hints responses.\n *\n * Design docs: 02-rendering-pipeline.md §\"Early Hints (103)\"\n */\n\nimport {\n collectRouteCss,\n collectRouteFonts,\n collectRouteModulepreloads,\n} from './build-manifest.js';\nimport type { BuildManifest } from './build-manifest.js';\n\n/** Minimal segment shape needed for early hint collection. */\ninterface SegmentWithFiles {\n layout?: { filePath: string };\n page?: { filePath: string };\n}\n\n// ─── EarlyHint type ───────────────────────────────────────────────────────\n\n/**\n * A single Link header hint for 103 Early Hints.\n *\n * ```ts\n * ctx.earlyHints([\n * { href: '/styles/critical.css', rel: 'preload', as: 'style' },\n * { href: 'https://fonts.googleapis.com', rel: 'preconnect' },\n * ])\n * ```\n */\nexport interface EarlyHint {\n /** The resource URL (absolute or root-relative). */\n href: string;\n /** Link relation — `preload`, `modulepreload`, or `preconnect`. */\n rel: 'preload' | 'modulepreload' | 'preconnect';\n /** Resource type for `preload` hints (omit for `modulepreload` / `preconnect`). */\n as?: 'style' | 'script' | 'font' | 'image' | 'fetch' | 'document';\n /** Crossorigin attribute — required for font preloads per spec. */\n crossOrigin?: 'anonymous' | 'use-credentials';\n /** Fetch priority hint — `high`, `low`, or `auto`. */\n fetchPriority?: 'high' | 'low' | 'auto';\n}\n\n// ─── formatLinkHeader ─────────────────────────────────────────────────────\n\n/**\n * Format a single EarlyHint as a Link header value.\n *\n * Examples:\n * `</styles/root.css>; rel=preload; as=style`\n * `</fonts/inter.woff2>; rel=preload; as=font; crossorigin=anonymous`\n * `</_timber/client.js>; rel=modulepreload`\n * `<https://fonts.googleapis.com>; rel=preconnect`\n */\nexport function formatLinkHeader(hint: EarlyHint): string {\n let value = `<${hint.href}>; rel=${hint.rel}`;\n if (hint.as !== undefined) value += `; as=${hint.as}`;\n if (hint.crossOrigin !== undefined) value += `; crossorigin=${hint.crossOrigin}`;\n if (hint.fetchPriority !== undefined) value += `; fetchpriority=${hint.fetchPriority}`;\n return value;\n}\n\n// ─── collectEarlyHintHeaders ──────────────────────────────────────────────\n\n/** Options for early hint collection. */\nexport interface EarlyHintOptions {\n /** Skip JS modulepreload hints (e.g. when client JavaScript is disabled). */\n skipJs?: boolean;\n}\n\n/**\n * Collect all Link header strings for a matched route's segment chain.\n *\n * Walks the build manifest to emit hints for:\n * - CSS stylesheets (rel=preload; as=style)\n * - Font assets (rel=preload; as=font; crossorigin)\n * - JS modulepreload hints (rel=modulepreload) — unless skipJs is set\n *\n * Also emits global CSS from the `_global` manifest key. Route files\n * are server components that don't appear in the client bundle, so\n * per-route CSS keying doesn't work with the RSC plugin. The `_global`\n * key contains all CSS assets from the client build — fine for early\n * hints since they're just prefetch signals.\n *\n * Returns formatted Link header strings, deduplicated, root → leaf order.\n * Returns an empty array in dev mode (manifest is empty).\n */\nexport function collectEarlyHintHeaders(\n segments: SegmentWithFiles[],\n manifest: BuildManifest,\n options?: EarlyHintOptions\n): string[] {\n const result: string[] = [];\n const seen = new Set<string>();\n\n const add = (header: string) => {\n if (!seen.has(header)) {\n seen.add(header);\n result.push(header);\n }\n };\n\n // Per-route CSS — rel=preload; as=style\n for (const url of collectRouteCss(segments, manifest)) {\n add(formatLinkHeader({ href: url, rel: 'preload', as: 'style' }));\n }\n\n // Global CSS — all CSS assets from the client bundle.\n // Covers CSS that the RSC plugin injects via data-rsc-css-href,\n // which isn't keyed to route segments in our manifest.\n for (const url of manifest.css['_global'] ?? []) {\n add(formatLinkHeader({ href: url, rel: 'preload', as: 'style' }));\n }\n\n // Fonts — rel=preload; as=font; crossorigin (crossorigin required per spec)\n for (const font of collectRouteFonts(segments, manifest)) {\n add(\n formatLinkHeader({ href: font.href, rel: 'preload', as: 'font', crossOrigin: 'anonymous' })\n );\n }\n\n // JS chunks — rel=modulepreload (skip when client JS is disabled)\n if (!options?.skipJs) {\n for (const url of collectRouteModulepreloads(segments, manifest)) {\n add(formatLinkHeader({ href: url, rel: 'modulepreload' }));\n }\n }\n\n return result;\n}\n","/**\n * Per-request 103 Early Hints sender — ALS bridge for platform adapters.\n *\n * The pipeline collects Link headers for CSS, fonts, and JS chunks at\n * route-match time. On platforms that support it (Node.js v18.11+, Bun),\n * the adapter can send these as a 103 Early Hints interim response before\n * the final response is ready.\n *\n * This module provides an ALS-based bridge: the generated entry point\n * (e.g., the Nitro entry) wraps the handler with `runWithEarlyHintsSender`,\n * binding a per-request sender function. The pipeline calls\n * `sendEarlyHints103()` to fire the 103 if a sender is available.\n *\n * On platforms where 103 is handled at the CDN level (e.g., Cloudflare\n * converts Link headers into 103 automatically), no sender is installed\n * and `sendEarlyHints103()` is a no-op.\n *\n * Design doc: 02-rendering-pipeline.md §\"Early Hints (103)\"\n */\n\nimport { earlyHintsSenderAls } from './als-registry.js';\n\n/** Function that sends Link header values as a 103 Early Hints response. */\nexport type EarlyHintsSenderFn = (links: string[]) => void;\n\n/**\n * Run a function with a per-request early hints sender installed.\n *\n * Called by generated entry points (e.g., Nitro node-server/bun) to\n * bind the platform's writeEarlyHints capability for the request duration.\n */\nexport function runWithEarlyHintsSender<T>(sender: EarlyHintsSenderFn, fn: () => T): T {\n return earlyHintsSenderAls.run(sender, fn);\n}\n\n/**\n * Send collected Link headers as a 103 Early Hints response.\n *\n * No-op if no sender is installed for the current request (e.g., on\n * Cloudflare where the CDN handles 103 automatically, or in dev mode).\n *\n * Non-fatal: errors from the sender are caught and silently ignored.\n */\nexport function sendEarlyHints103(links: string[]): void {\n if (!links.length) return;\n const sender = earlyHintsSenderAls.getStore();\n if (!sender) return;\n try {\n sender(links);\n } catch {\n // Sending 103 is best-effort — failure never blocks the request.\n }\n}\n","/**\n * Element tree construction for timber.js rendering.\n *\n * Builds a unified React element tree from a matched segment chain, bottom-up:\n * page → status-code error boundaries → access gates → layout → repeat up segment chain\n *\n * The tree is rendered via a single `renderToReadableStream` call,\n * giving one `React.cache` scope for the entire route.\n *\n * See design/02-rendering-pipeline.md §\"Element Tree Construction\"\n */\n\nimport type { SegmentNode, RouteFile } from '#/routing/types.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** A loaded module for a route file convention. */\nexport interface LoadedModule {\n /** The default export (component, access function, etc.) */\n default?: unknown;\n /** Named exports (for route.ts method handlers, metadata, etc.) */\n [key: string]: unknown;\n}\n\n/** Function that loads a route file's module. */\nexport type ModuleLoader = (file: RouteFile) => LoadedModule | Promise<LoadedModule>;\n\n/** A React element — kept opaque to avoid a React dependency in this module. */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type ReactElement = any;\n\n/** Function that creates a React element. Matches React.createElement signature. */\nexport type CreateElement = (\n type: unknown,\n props: Record<string, unknown> | null,\n ...children: unknown[]\n) => ReactElement;\n\n/**\n * Resolved slot content for a layout.\n * Key is slot name (without @), value is the element tree for that slot.\n */\nexport type SlotElements = Map<string, ReactElement>;\n\n/** Configuration for the tree builder. */\nexport interface TreeBuilderConfig {\n /** The matched segment chain from root to leaf. */\n segments: SegmentNode[];\n /** Route params extracted by the matcher (catch-all segments produce string[]). */\n params: Record<string, string | string[]>;\n /** Parsed search params (typed or URLSearchParams). */\n searchParams: unknown;\n /** Loads a route file's module. */\n loadModule: ModuleLoader;\n /** React.createElement or equivalent. */\n createElement: CreateElement;\n /**\n * Error boundary component for wrapping segments.\n *\n * This is injected by the caller rather than imported directly to avoid\n * pulling 'use client' code into the server barrel (@timber-js/app/server).\n * In the RSC environment, the RSC plugin transforms this import to a\n * client reference proxy — the caller handles the import so the server\n * barrel stays free of client dependencies.\n */\n errorBoundaryComponent?: unknown;\n}\n\n// ─── Component wrappers ──────────────────────────────────────────────────────\n\n/**\n * Framework-injected access gate component.\n *\n * When `verdict` is provided (from the pre-render pass), AccessGate replays\n * the stored result synchronously — no re-execution, no async, immune to\n * Suspense timing. When `verdict` is absent, falls back to calling `accessFn`\n * (backward compat for tree-builder.ts which doesn't run a pre-render pass).\n */\nexport interface AccessGateProps {\n accessFn: (ctx: { params: Record<string, string | string[]>; searchParams: unknown }) => unknown;\n params: Record<string, string | string[]>;\n searchParams: unknown;\n /** Segment name for dev logging (e.g. \"authenticated\", \"dashboard\"). */\n segmentName?: string;\n /**\n * Pre-computed verdict from the pre-render pass. When set, AccessGate\n * replays this verdict synchronously instead of calling accessFn.\n * - 'pass': render children\n * - DenySignal/RedirectSignal: throw synchronously\n */\n verdict?:\n | 'pass'\n | import('./primitives.js').DenySignal\n | import('./primitives.js').RedirectSignal;\n children: ReactElement;\n}\n\n/**\n * Framework-injected slot access gate component.\n * On denial, renders denied.tsx → default.tsx → null instead of failing the page.\n */\nexport interface SlotAccessGateProps {\n accessFn: (ctx: { params: Record<string, string | string[]>; searchParams: unknown }) => unknown;\n params: Record<string, string | string[]>;\n searchParams: unknown;\n deniedFallback: ReactElement | null;\n defaultFallback: ReactElement | null;\n children: ReactElement;\n}\n\n/**\n * Framework-injected error boundary wrapper.\n * Wraps content with status-code error boundary handling.\n */\nexport interface ErrorBoundaryProps {\n fallbackComponent: ReactElement | null;\n status?: number;\n children: ReactElement;\n}\n\n// ─── Tree Builder ────────────────────────────────────────────────────────────\n\n/**\n * Result of building the element tree.\n */\nexport interface TreeBuildResult {\n /** The root React element tree ready for renderToReadableStream. */\n tree: ReactElement;\n /** Whether the leaf segment is a route.ts (API endpoint) rather than a page. */\n isApiRoute: boolean;\n}\n\n/**\n * Build the unified element tree from a matched segment chain.\n *\n * Construction is bottom-up:\n * 1. Start with the page component (leaf segment)\n * 2. Wrap in status-code error boundaries (fallback chain)\n * 3. Wrap in AccessGate (if segment has access.ts)\n * 4. Pass as children to the segment's layout\n * 5. Repeat up the segment chain to root\n *\n * Parallel slots are resolved at each layout level and composed as named props.\n */\nexport async function buildElementTree(config: TreeBuilderConfig): Promise<TreeBuildResult> {\n const { segments, params, searchParams, loadModule, createElement, errorBoundaryComponent } =\n config;\n\n if (segments.length === 0) {\n throw new Error('[timber] buildElementTree: empty segment chain');\n }\n\n const leaf = segments[segments.length - 1];\n\n // API routes (route.ts) don't build a React tree\n if (leaf.route && !leaf.page) {\n return { tree: null, isApiRoute: true };\n }\n\n // Start with the page component\n const pageModule = leaf.page ? await loadModule(leaf.page) : null;\n const PageComponent = pageModule?.default as ((...args: unknown[]) => ReactElement) | undefined;\n\n if (!PageComponent) {\n throw new Error(\n `[timber] No page component found for route at ${leaf.urlPath}. ` +\n 'Each route must have a page.tsx or route.ts.'\n );\n }\n\n // Build the page element with params and searchParams props\n let element: ReactElement = createElement(PageComponent, { params, searchParams });\n\n // Build tree bottom-up: wrap page, then walk segments from leaf to root\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i];\n\n // Wrap in error boundaries (status-code files + error.tsx)\n element = await wrapWithErrorBoundaries(\n segment,\n element,\n loadModule,\n createElement,\n errorBoundaryComponent\n );\n\n // Wrap in AccessGate if segment has access.ts\n if (segment.access) {\n const accessModule = await loadModule(segment.access);\n const accessFn = accessModule.default as AccessGateProps['accessFn'];\n element = createElement('timber:access-gate', {\n accessFn,\n params,\n searchParams,\n segmentName: segment.segmentName,\n children: element,\n } satisfies AccessGateProps);\n }\n\n // Wrap in layout (if exists and not the leaf's page-level wrapping)\n if (segment.layout) {\n const layoutModule = await loadModule(segment.layout);\n const LayoutComponent = layoutModule.default as\n | ((...args: unknown[]) => ReactElement)\n | undefined;\n\n if (LayoutComponent) {\n // Resolve parallel slots for this layout\n const slotProps: Record<string, ReactElement> = {};\n if (segment.slots.size > 0) {\n for (const [slotName, slotNode] of segment.slots) {\n slotProps[slotName] = await buildSlotElement(\n slotNode,\n params,\n searchParams,\n loadModule,\n createElement,\n errorBoundaryComponent\n );\n }\n }\n\n element = createElement(LayoutComponent, {\n ...slotProps,\n params,\n searchParams,\n children: element,\n });\n }\n }\n }\n\n return { tree: element, isApiRoute: false };\n}\n\n// ─── Slot Element Builder ────────────────────────────────────────────────────\n\n/**\n * Build the element tree for a parallel slot.\n *\n * Slots have their own access.ts (SlotAccessGate) and error boundaries.\n * On access denial: denied.tsx → default.tsx → null (graceful degradation).\n */\nasync function buildSlotElement(\n slotNode: SegmentNode,\n params: Record<string, string | string[]>,\n searchParams: unknown,\n loadModule: ModuleLoader,\n createElement: CreateElement,\n errorBoundaryComponent: unknown\n): Promise<ReactElement> {\n // Load slot page\n const pageModule = slotNode.page ? await loadModule(slotNode.page) : null;\n const PageComponent = pageModule?.default as ((...args: unknown[]) => ReactElement) | undefined;\n\n // Load default.tsx fallback\n const defaultModule = slotNode.default ? await loadModule(slotNode.default) : null;\n const DefaultComponent = defaultModule?.default as\n | ((...args: unknown[]) => ReactElement)\n | undefined;\n\n // If no page, render default.tsx or null\n if (!PageComponent) {\n return DefaultComponent ? createElement(DefaultComponent, { params, searchParams }) : null;\n }\n\n let element: ReactElement = createElement(PageComponent, { params, searchParams });\n\n // Wrap in error boundaries\n element = await wrapWithErrorBoundaries(\n slotNode,\n element,\n loadModule,\n createElement,\n errorBoundaryComponent\n );\n\n // Wrap in SlotAccessGate if slot has access.ts\n if (slotNode.access) {\n const accessModule = await loadModule(slotNode.access);\n const accessFn = accessModule.default as SlotAccessGateProps['accessFn'];\n\n // Load denied.tsx\n const deniedModule = slotNode.denied ? await loadModule(slotNode.denied) : null;\n const DeniedComponent = deniedModule?.default as\n | ((...args: unknown[]) => ReactElement)\n | undefined;\n\n const deniedFallback = DeniedComponent\n ? createElement(DeniedComponent, {\n slot: slotNode.segmentName.replace(/^@/, ''),\n dangerouslyPassData: undefined,\n })\n : null;\n const defaultFallback = DefaultComponent\n ? createElement(DefaultComponent, { params, searchParams })\n : null;\n\n element = createElement('timber:slot-access-gate', {\n accessFn,\n params,\n searchParams,\n deniedFallback,\n defaultFallback,\n children: element,\n } satisfies SlotAccessGateProps);\n }\n\n return element;\n}\n\n// ─── Error Boundary Wrapping ─────────────────────────────────────────────────\n\n/**\n * Wrap an element with error boundaries from a segment's status-code files.\n *\n * Wrapping order (innermost to outermost):\n * 1. Specific status files (503.tsx, 429.tsx, etc.)\n * 2. Category catch-alls (4xx.tsx, 5xx.tsx)\n * 3. error.tsx (general error boundary)\n *\n * This creates the fallback chain described in design/10-error-handling.md.\n */\nasync function wrapWithErrorBoundaries(\n segment: SegmentNode,\n element: ReactElement,\n loadModule: ModuleLoader,\n createElement: CreateElement,\n errorBoundaryComponent: unknown\n): Promise<ReactElement> {\n // Wrapping is applied inside-out. The last wrap call produces the outermost boundary.\n // Order: specific status → category → error.tsx (outermost)\n\n if (segment.statusFiles) {\n // Wrap with specific status files (innermost — highest priority at runtime)\n for (const [key, file] of segment.statusFiles) {\n if (key !== '4xx' && key !== '5xx') {\n const status = parseInt(key, 10);\n if (!isNaN(status)) {\n const mod = await loadModule(file);\n const Component = mod.default;\n if (Component) {\n element = createElement(errorBoundaryComponent, {\n fallbackComponent: Component,\n status,\n children: element,\n } satisfies ErrorBoundaryProps);\n }\n }\n }\n }\n\n // Wrap with category catch-alls (4xx.tsx, 5xx.tsx)\n for (const [key, file] of segment.statusFiles) {\n if (key === '4xx' || key === '5xx') {\n const mod = await loadModule(file);\n const Component = mod.default;\n if (Component) {\n element = createElement(errorBoundaryComponent, {\n fallbackComponent: Component,\n status: key === '4xx' ? 400 : 500, // category marker\n children: element,\n } satisfies ErrorBoundaryProps);\n }\n }\n }\n }\n\n // Wrap with error.tsx (outermost — catches anything not matched by status files)\n if (segment.error) {\n const errorModule = await loadModule(segment.error);\n const ErrorComponent = errorModule.default;\n if (ErrorComponent) {\n element = createElement(errorBoundaryComponent, {\n fallbackComponent: ErrorComponent,\n children: element,\n } satisfies ErrorBoundaryProps);\n }\n }\n\n return element;\n}\n","/**\n * AccessGate and SlotAccessGate — framework-injected async server components.\n *\n * AccessGate wraps each segment's layout in the element tree. It calls the\n * segment's access.ts before the layout renders. If access.ts calls deny()\n * or redirect(), the signal propagates as a render-phase throw — caught by\n * the flush controller to produce the correct HTTP status code.\n *\n * SlotAccessGate wraps parallel slot content. On denial, it renders the\n * graceful degradation chain: denied.tsx → default.tsx → null. Slot denial\n * does not affect the HTTP status code.\n *\n * See design/04-authorization.md and design/02-rendering-pipeline.md §\"AccessGate\"\n */\n\nimport { DenySignal, RedirectSignal } from './primitives.js';\nimport type { AccessGateProps, SlotAccessGateProps, ReactElement } from './tree-builder.js';\nimport { withSpan, setSpanAttribute } from './tracing.js';\n\n// ─── AccessGate ─────────────────────────────────────────────────────────────\n\n/**\n * Framework-injected access gate for segments.\n *\n * When a pre-computed `verdict` prop is provided (from the pre-render pass\n * in route-element-builder.ts), AccessGate replays it synchronously — no\n * async, no re-execution of access.ts, immune to Suspense timing. The OTEL\n * span was already emitted during the pre-render pass.\n *\n * When no verdict is provided (backward compat with tree-builder.ts),\n * AccessGate calls accessFn directly with OTEL instrumentation.\n *\n * access.ts is a pure gate — return values are discarded. The layout below\n * gets the same data by calling the same cached functions (React.cache dedup).\n */\nexport function AccessGate(props: AccessGateProps): ReactElement | Promise<ReactElement> {\n const { accessFn, params, searchParams, segmentName, verdict, children } = props;\n\n // Fast path: replay pre-computed verdict from the pre-render pass.\n // This is synchronous — Suspense boundaries cannot interfere with the\n // status code because the signal throws before any async work.\n if (verdict !== undefined) {\n if (verdict === 'pass') {\n return children;\n }\n // Throw the stored DenySignal or RedirectSignal synchronously.\n // React catches this as a render-phase throw — the flush controller\n // produces the correct HTTP status code.\n throw verdict;\n }\n\n // Fallback: call accessFn directly (used by tree-builder.ts which\n // doesn't run a pre-render pass, and for backward compat).\n return accessGateFallback(accessFn, params, searchParams, segmentName, children);\n}\n\n/**\n * Async fallback for AccessGate when no pre-computed verdict is available.\n * Calls accessFn with OTEL instrumentation.\n */\nasync function accessGateFallback(\n accessFn: AccessGateProps['accessFn'],\n params: AccessGateProps['params'],\n searchParams: AccessGateProps['searchParams'],\n segmentName: AccessGateProps['segmentName'],\n children: ReactElement\n): Promise<ReactElement> {\n await withSpan('timber.access', { 'timber.segment': segmentName ?? 'unknown' }, async () => {\n try {\n await accessFn({ params, searchParams });\n await setSpanAttribute('timber.result', 'pass');\n } catch (error: unknown) {\n if (error instanceof DenySignal) {\n await setSpanAttribute('timber.result', 'deny');\n await setSpanAttribute('timber.deny_status', error.status);\n if (error.sourceFile) {\n await setSpanAttribute('timber.deny_file', error.sourceFile);\n }\n } else if (error instanceof RedirectSignal) {\n await setSpanAttribute('timber.result', 'redirect');\n }\n throw error;\n }\n });\n\n return children;\n}\n\n// ─── SlotAccessGate ─────────────────────────────────────────────────────────\n\n/**\n * Framework-injected access gate for parallel slots.\n *\n * On denial, graceful degradation: denied.tsx → default.tsx → null.\n * The HTTP status code is unaffected — slot denial is a UI concern, not\n * a protocol concern. The parent layout and sibling slots still render.\n *\n * redirect() in slot access.ts is a dev-mode error — redirecting from a\n * slot doesn't make architectural sense.\n */\nexport async function SlotAccessGate(props: SlotAccessGateProps): Promise<ReactElement> {\n const { accessFn, params, searchParams, deniedFallback, defaultFallback, children } = props;\n\n try {\n await accessFn({ params, searchParams });\n } catch (error: unknown) {\n // DenySignal → graceful degradation (denied.tsx → default.tsx → null)\n if (error instanceof DenySignal) {\n return deniedFallback ?? defaultFallback ?? null;\n }\n\n // RedirectSignal in slot access → dev-mode error.\n // Slot access should use deny(), not redirect(). Redirecting from a\n // slot would redirect the entire page, which breaks the contract that\n // slot failure is graceful degradation.\n if (error instanceof RedirectSignal) {\n if (process.env.NODE_ENV !== 'production') {\n console.error(\n '[timber] redirect() is not allowed in slot access.ts. ' +\n 'Slots use deny() for graceful degradation — denied.tsx → default.tsx → null. ' +\n \"If you need to redirect, move the logic to the parent segment's access.ts.\"\n );\n }\n // In production, treat as a deny — render fallback rather than crash.\n return deniedFallback ?? defaultFallback ?? null;\n }\n\n // Unhandled error — re-throw so error boundaries can catch it.\n // Dev-mode warning: slot access should use deny(), not throw.\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[timber] Unhandled error in slot access.ts. ' +\n 'Use deny() for access control, not unhandled throws.',\n error\n );\n }\n throw error;\n }\n\n // Access passed — render slot content.\n return children;\n}\n","/**\n * Status-code file resolver for timber.js error/denial rendering.\n *\n * Given an HTTP status code and a matched segment chain, resolves the\n * correct file to render by walking the fallback chain described in\n * design/10-error-handling.md §\"Status-Code Files\".\n *\n * Supports two format families:\n * - 'component' (default): .tsx/.jsx/.mdx status files → React rendering pipeline\n * - 'json': .json status files → raw JSON response, no React\n *\n * Fallback chains operate within the same format family (no cross-format fallback).\n *\n * **Component chain (4xx):**\n * Pass 1 — status files (leaf → root): {status}.tsx → 4xx.tsx\n * Pass 2 — legacy compat (leaf → root): not-found.tsx / forbidden.tsx / unauthorized.tsx\n * Pass 3 — error.tsx (leaf → root)\n * Pass 4 — framework default (returns null)\n *\n * **JSON chain (4xx):**\n * Pass 1 — json status files (leaf → root): {status}.json → 4xx.json\n * Pass 2 — framework default JSON (returns null, caller provides bare JSON)\n *\n * **5xx (component only):**\n * Per-segment (leaf → root): {status}.tsx → 5xx.tsx → error.tsx\n * Then global-error.tsx (future)\n * Then framework default (returns null)\n */\n\nimport type { SegmentNode, RouteFile } from '#/routing/types.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** How the status-code file was matched. */\nexport type StatusFileKind =\n | 'exact' // e.g. 403.tsx matched status 403\n | 'category' // e.g. 4xx.tsx matched status 403\n | 'legacy' // e.g. not-found.tsx matched status 404\n | 'error'; // error.tsx as last resort\n\n/** Response format family for status-code resolution. */\nexport type StatusFileFormat = 'component' | 'json';\n\n/** Result of resolving a status-code file for a segment chain. */\nexport interface StatusFileResolution {\n /** The matched route file. */\n file: RouteFile;\n /** The HTTP status code (always the original status, not the file's code). */\n status: number;\n /** How the file was matched. */\n kind: StatusFileKind;\n /** Index into the segments array where the file was found. */\n segmentIndex: number;\n}\n\n/** How a slot denial file was matched. */\nexport type SlotDeniedKind = 'denied' | 'default';\n\n/** Result of resolving a slot denied file. */\nexport interface SlotDeniedResolution {\n /** The matched route file (denied.tsx or default.tsx). */\n file: RouteFile;\n /** Slot name without @ prefix. */\n slotName: string;\n /** How the file was matched. */\n kind: SlotDeniedKind;\n}\n\n// ─── Legacy Compat Mapping ───────────────────────────────────────────────────\n\n/**\n * Maps legacy file convention names to their corresponding HTTP status codes.\n * Only used in the 4xx component fallback chain.\n */\nconst LEGACY_FILE_TO_STATUS: Record<string, number> = {\n 'not-found': 404,\n 'forbidden': 403,\n 'unauthorized': 401,\n};\n\n// ─── Resolver ────────────────────────────────────────────────────────────────\n\n/**\n * Resolve the status-code file to render for a given HTTP status code.\n *\n * Walks the segment chain from leaf to root following the fallback chain\n * defined in design/10-error-handling.md. Returns null if no file is found\n * (caller should render the framework default).\n *\n * @param status - The HTTP status code (4xx or 5xx).\n * @param segments - The matched segment chain from root (index 0) to leaf (last).\n * @param format - The response format family ('component' or 'json'). Defaults to 'component'.\n */\nexport function resolveStatusFile(\n status: number,\n segments: ReadonlyArray<SegmentNode>,\n format: StatusFileFormat = 'component'\n): StatusFileResolution | null {\n if (status >= 400 && status <= 499) {\n return format === 'json' ? resolve4xxJson(status, segments) : resolve4xx(status, segments);\n }\n if (status >= 500 && status <= 599) {\n // JSON format for 5xx uses the same json chain pattern\n return format === 'json' ? resolve5xxJson(status, segments) : resolve5xx(status, segments);\n }\n return null;\n}\n\n/**\n * 4xx component fallback chain (three separate passes):\n * Pass 1 — status files (leaf → root): {status}.tsx → 4xx.tsx\n * Pass 2 — legacy compat (leaf → root): not-found.tsx / forbidden.tsx / unauthorized.tsx\n * Pass 3 — error.tsx (leaf → root)\n */\nfunction resolve4xx(\n status: number,\n segments: ReadonlyArray<SegmentNode>\n): StatusFileResolution | null {\n const statusStr = String(status);\n\n // Pass 1: status files across all segments (leaf → root)\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i];\n if (!segment.statusFiles) continue;\n\n // Exact match first\n const exact = segment.statusFiles.get(statusStr);\n if (exact) {\n return { file: exact, status, kind: 'exact', segmentIndex: i };\n }\n\n // Category catch-all\n const category = segment.statusFiles.get('4xx');\n if (category) {\n return { file: category, status, kind: 'category', segmentIndex: i };\n }\n }\n\n // Pass 2: legacy compat files (leaf → root)\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i];\n if (!segment.legacyStatusFiles) continue;\n\n for (const [name, legacyStatus] of Object.entries(LEGACY_FILE_TO_STATUS)) {\n if (legacyStatus === status) {\n const file = segment.legacyStatusFiles.get(name);\n if (file) {\n return { file, status, kind: 'legacy', segmentIndex: i };\n }\n }\n }\n }\n\n // Pass 3: error.tsx (leaf → root)\n for (let i = segments.length - 1; i >= 0; i--) {\n if (segments[i].error) {\n return { file: segments[i].error!, status, kind: 'error', segmentIndex: i };\n }\n }\n\n return null;\n}\n\n/**\n * 4xx JSON fallback chain (single pass):\n * Pass 1 — json status files (leaf → root): {status}.json → 4xx.json\n * No legacy compat, no error.tsx — JSON chain terminates at category catch-all.\n */\nfunction resolve4xxJson(\n status: number,\n segments: ReadonlyArray<SegmentNode>\n): StatusFileResolution | null {\n const statusStr = String(status);\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i];\n if (!segment.jsonStatusFiles) continue;\n\n // Exact match first\n const exact = segment.jsonStatusFiles.get(statusStr);\n if (exact) {\n return { file: exact, status, kind: 'exact', segmentIndex: i };\n }\n\n // Category catch-all\n const category = segment.jsonStatusFiles.get('4xx');\n if (category) {\n return { file: category, status, kind: 'category', segmentIndex: i };\n }\n }\n\n return null;\n}\n\n/**\n * 5xx component fallback chain (single pass, per-segment):\n * At each segment (leaf → root): {status}.tsx → 5xx.tsx → error.tsx\n */\nfunction resolve5xx(\n status: number,\n segments: ReadonlyArray<SegmentNode>\n): StatusFileResolution | null {\n const statusStr = String(status);\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i];\n\n // Exact status file\n if (segment.statusFiles) {\n const exact = segment.statusFiles.get(statusStr);\n if (exact) {\n return { file: exact, status, kind: 'exact', segmentIndex: i };\n }\n\n // Category catch-all\n const category = segment.statusFiles.get('5xx');\n if (category) {\n return { file: category, status, kind: 'category', segmentIndex: i };\n }\n }\n\n // error.tsx at this segment level (for 5xx, checked per-segment)\n if (segment.error) {\n return { file: segment.error, status, kind: 'error', segmentIndex: i };\n }\n }\n\n return null;\n}\n\n/**\n * 5xx JSON fallback chain (single pass):\n * At each segment (leaf → root): {status}.json → 5xx.json\n * No error.tsx equivalent — JSON chain terminates at category catch-all.\n */\nfunction resolve5xxJson(\n status: number,\n segments: ReadonlyArray<SegmentNode>\n): StatusFileResolution | null {\n const statusStr = String(status);\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i];\n if (!segment.jsonStatusFiles) continue;\n\n const exact = segment.jsonStatusFiles.get(statusStr);\n if (exact) {\n return { file: exact, status, kind: 'exact', segmentIndex: i };\n }\n\n const category = segment.jsonStatusFiles.get('5xx');\n if (category) {\n return { file: category, status, kind: 'category', segmentIndex: i };\n }\n }\n\n return null;\n}\n\n// ─── Slot Denied Resolver ────────────────────────────────────────────────────\n\n/**\n * Resolve the denial file for a parallel route slot.\n *\n * Slot denial is graceful degradation — no HTTP status on the wire.\n * Fallback chain: denied.tsx → default.tsx → null.\n *\n * @param slotNode - The segment node for the slot (segmentType === 'slot').\n */\nexport function resolveSlotDenied(slotNode: SegmentNode): SlotDeniedResolution | null {\n const slotName = slotNode.segmentName.replace(/^@/, '');\n\n if (slotNode.denied) {\n return { file: slotNode.denied, slotName, kind: 'denied' };\n }\n\n if (slotNode.default) {\n return { file: slotNode.default, slotName, kind: 'default' };\n }\n\n return null;\n}\n","/**\n * Flush controller for timber.js rendering.\n *\n * Holds the response until `onShellReady` fires, then commits the HTTP status\n * code and flushes the shell. Render-phase signals (deny, redirect, unhandled\n * throws) caught before flush produce correct HTTP status codes.\n *\n * See design/02-rendering-pipeline.md §\"The Flush Point\" and §\"The Hold Window\"\n */\n\nimport { DenySignal, RedirectSignal, RenderError } from './primitives.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** The readable stream from React's renderToReadableStream. */\nexport interface ReactRenderStream {\n /** The underlying ReadableStream of HTML bytes. */\n readable: ReadableStream<Uint8Array>;\n /** Resolves when the shell has finished rendering (all non-Suspense content). */\n allReady?: Promise<void>;\n}\n\n/** Options for the flush controller. */\nexport interface FlushOptions {\n /** Response headers to include (from middleware.ts, proxy.ts, etc.). */\n responseHeaders?: Headers;\n /** Default status code when rendering succeeds. Default: 200. */\n defaultStatus?: number;\n}\n\n/** Result of the flush process. */\nexport interface FlushResult {\n /** The final HTTP Response. */\n response: Response;\n /** The status code committed. */\n status: number;\n /** Whether the response was a redirect. */\n isRedirect: boolean;\n /** Whether the response was a denial. */\n isDenial: boolean;\n}\n\n// ─── Render Function Type ────────────────────────────────────────────────────\n\n/**\n * A function that performs the React render.\n *\n * The flush controller calls this, catches any signals thrown during the\n * synchronous shell render (before onShellReady), and produces the\n * correct HTTP response.\n *\n * Must return an object with:\n * - `stream`: The ReadableStream from renderToReadableStream\n * - `shellReady`: A Promise that resolves when onShellReady fires\n */\nexport interface RenderResult {\n /** The HTML byte stream. */\n stream: ReadableStream<Uint8Array>;\n /** Resolves when the shell is ready (all non-Suspense content rendered). */\n shellReady: Promise<void>;\n}\n\nexport type RenderFn = () => RenderResult | Promise<RenderResult>;\n\n// ─── Flush Controller ────────────────────────────────────────────────────────\n\n/**\n * Execute a render and hold the response until the shell is ready.\n *\n * The flush controller:\n * 1. Calls the render function to start renderToReadableStream\n * 2. Waits for shellReady (onShellReady)\n * 3. If a render-phase signal was thrown (deny, redirect, error), produces\n * the correct HTTP status code\n * 4. If the shell rendered successfully, commits the status and streams\n *\n * Render-phase signals caught before flush:\n * - `DenySignal` → HTTP 4xx with appropriate status code\n * - `RedirectSignal` → HTTP 3xx with Location header\n * - `RenderError` → HTTP status from error (default 500)\n * - Unhandled error → HTTP 500\n *\n * @param renderFn - Function that starts the React render.\n * @param options - Flush configuration.\n * @returns The committed HTTP Response.\n */\nexport async function flushResponse(\n renderFn: RenderFn,\n options: FlushOptions = {}\n): Promise<FlushResult> {\n const { responseHeaders = new Headers(), defaultStatus = 200 } = options;\n\n let renderResult: RenderResult;\n\n // Phase 1: Start the render. The render function may throw synchronously\n // if there's an immediate error before React even starts.\n try {\n renderResult = await renderFn();\n } catch (error) {\n return handleSignal(error, responseHeaders);\n }\n\n // Phase 2: Wait for onShellReady. Render-phase signals (deny, redirect,\n // throws outside Suspense) are caught here.\n try {\n await renderResult.shellReady;\n } catch (error) {\n return handleSignal(error, responseHeaders);\n }\n\n // Phase 3: Shell rendered successfully. Commit status and stream.\n responseHeaders.set('Content-Type', 'text/html; charset=utf-8');\n\n return {\n response: new Response(renderResult.stream, {\n status: defaultStatus,\n headers: responseHeaders,\n }),\n status: defaultStatus,\n isRedirect: false,\n isDenial: false,\n };\n}\n\n// ─── Signal Handling ─────────────────────────────────────────────────────────\n\n/**\n * Handle a render-phase signal and produce the correct HTTP response.\n */\nfunction handleSignal(error: unknown, responseHeaders: Headers): FlushResult {\n // Redirect signal → HTTP 3xx\n if (error instanceof RedirectSignal) {\n responseHeaders.set('Location', error.location);\n return {\n response: new Response(null, {\n status: error.status,\n headers: responseHeaders,\n }),\n status: error.status,\n isRedirect: true,\n isDenial: false,\n };\n }\n\n // Deny signal → HTTP 4xx\n if (error instanceof DenySignal) {\n return {\n response: new Response(null, {\n status: error.status,\n headers: responseHeaders,\n }),\n status: error.status,\n isRedirect: false,\n isDenial: true,\n };\n }\n\n // RenderError → HTTP status from error\n if (error instanceof RenderError) {\n return {\n response: new Response(null, {\n status: error.status,\n headers: responseHeaders,\n }),\n status: error.status,\n isRedirect: false,\n isDenial: false,\n };\n }\n\n // Unknown error → HTTP 500\n console.error('[timber] Unhandled render-phase error:', error);\n return {\n response: new Response(null, {\n status: 500,\n headers: responseHeaders,\n }),\n status: 500,\n isRedirect: false,\n isDenial: false,\n };\n}\n","/**\n * CSRF protection — Origin header validation.\n *\n * Auto-derived from the Host header for single-origin deployments.\n * Configurable via allowedOrigins for multi-origin setups.\n * Disable with csrf: false (not recommended outside local dev).\n *\n * See design/08-forms-and-actions.md §\"CSRF Protection\"\n * See design/13-security.md §\"Security Testing Checklist\" #6\n */\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\nexport interface CsrfConfig {\n /** Explicit list of allowed origins. Replaces Host-based auto-derivation. */\n allowedOrigins?: string[];\n /** Set to false to disable CSRF validation entirely. */\n csrf?: boolean;\n}\n\nexport type CsrfResult = { ok: true } | { ok: false; status: 403 };\n\n// ─── Constants ────────────────────────────────────────────────────────────\n\n/** HTTP methods that are considered safe (no mutation). */\nconst SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);\n\n// ─── Implementation ───────────────────────────────────────────────────────\n\n/**\n * Validate the Origin header against the request's Host.\n *\n * For mutation methods (POST, PUT, PATCH, DELETE):\n * - If `csrf: false`, skip validation.\n * - If `allowedOrigins` is set, Origin must match one exactly (no wildcards).\n * - Otherwise, Origin's host must match the request's Host header.\n *\n * Safe methods (GET, HEAD, OPTIONS) always pass.\n */\nexport function validateCsrf(req: Request, config: CsrfConfig): CsrfResult {\n // Safe methods don't need CSRF protection\n if (SAFE_METHODS.has(req.method)) {\n return { ok: true };\n }\n\n // Explicitly disabled\n if (config.csrf === false) {\n return { ok: true };\n }\n\n const origin = req.headers.get('Origin');\n\n // No Origin header on a mutation → reject\n if (!origin) {\n return { ok: false, status: 403 };\n }\n\n // If allowedOrigins is configured, use that instead of Host-based derivation\n if (config.allowedOrigins) {\n const allowed = config.allowedOrigins.includes(origin);\n return allowed ? { ok: true } : { ok: false, status: 403 };\n }\n\n // Auto-derive from Host header\n const host = req.headers.get('Host');\n if (!host) {\n return { ok: false, status: 403 };\n }\n\n // Extract hostname from Origin URL and compare to Host header\n let originHost: string;\n try {\n originHost = new URL(origin).host;\n } catch {\n return { ok: false, status: 403 };\n }\n\n return originHost === host ? { ok: true } : { ok: false, status: 403 };\n}\n","/**\n * Request body size limits — returns 413 when exceeded.\n * See design/08-forms-and-actions.md §\"FormData Limits\"\n */\n\nexport interface BodyLimitsConfig {\n limits?: {\n actionBodySize?: string;\n uploadBodySize?: string;\n maxFields?: number;\n };\n}\n\nexport type BodyLimitResult = { ok: true } | { ok: false; status: 411 | 413 };\n\nexport type BodyKind = 'action' | 'upload';\n\nconst KB = 1024;\nconst MB = 1024 * KB;\nconst GB = 1024 * MB;\n\nexport const DEFAULT_LIMITS = {\n actionBodySize: 1 * MB,\n uploadBodySize: 10 * MB,\n maxFields: 100,\n} as const;\n\nconst SIZE_PATTERN = /^(\\d+(?:\\.\\d+)?)\\s*(kb|mb|gb)?$/i;\n\n/** Parse a human-readable size string (\"1mb\", \"512kb\", \"1024\") into bytes. */\nexport function parseBodySize(size: string): number {\n const match = SIZE_PATTERN.exec(size.trim());\n if (!match) {\n throw new Error(\n `Invalid body size format: \"${size}\". Expected format like \"1mb\", \"512kb\", or \"1024\".`\n );\n }\n\n const value = Number.parseFloat(match[1]);\n const unit = (match[2] ?? '').toLowerCase();\n\n switch (unit) {\n case 'kb':\n return Math.floor(value * KB);\n case 'mb':\n return Math.floor(value * MB);\n case 'gb':\n return Math.floor(value * GB);\n case '':\n return Math.floor(value);\n default:\n throw new Error(`Unknown size unit: \"${unit}\"`);\n }\n}\n\n/** Check whether a request body exceeds the configured size limit (stateless, no ALS). */\nexport function enforceBodyLimits(\n req: Request,\n kind: BodyKind,\n config: BodyLimitsConfig\n): BodyLimitResult {\n const contentLength = req.headers.get('Content-Length');\n if (!contentLength) {\n // Reject requests without Content-Length — prevents body limit bypass via\n // chunked transfer-encoding. Browsers always send Content-Length for form POSTs.\n return { ok: false, status: 411 };\n }\n\n const bodySize = Number.parseInt(contentLength, 10);\n if (Number.isNaN(bodySize)) {\n return { ok: false, status: 411 };\n }\n\n const limit = resolveLimit(kind, config);\n return bodySize <= limit ? { ok: true } : { ok: false, status: 413 };\n}\n\n/** Check whether a FormData payload exceeds the configured field count limit. */\nexport function enforceFieldLimit(formData: FormData, config: BodyLimitsConfig): BodyLimitResult {\n const maxFields = config.limits?.maxFields ?? DEFAULT_LIMITS.maxFields;\n // Count unique keys — FormData.keys() yields duplicates for multi-value fields,\n // so we use a Set to count distinct field names.\n const fieldCount = new Set(formData.keys()).size;\n return fieldCount <= maxFields ? { ok: true } : { ok: false, status: 413 };\n}\n\n/**\n * Resolve the byte limit for a given body kind, using config overrides or defaults.\n */\nfunction resolveLimit(kind: BodyKind, config: BodyLimitsConfig): number {\n const userLimits = config.limits;\n\n if (kind === 'action') {\n return userLimits?.actionBodySize\n ? parseBodySize(userLimits.actionBodySize)\n : DEFAULT_LIMITS.actionBodySize;\n }\n\n return userLimits?.uploadBodySize\n ? parseBodySize(userLimits.uploadBodySize)\n : DEFAULT_LIMITS.uploadBodySize;\n}\n","/**\n * Social metadata rendering — Open Graph and Twitter Card meta tags.\n *\n * Extracted from metadata-render.ts to keep files under 500 lines.\n *\n * See design/16-metadata.md\n */\n\nimport type { Metadata } from './types.js';\nimport type { HeadElement } from './metadata.js';\n\n/**\n * Render Open Graph metadata into head element descriptors.\n *\n * Handles og:title, og:description, og:image (with dimensions/alt),\n * og:video, og:audio, og:article:author, and other OG properties.\n */\nexport function renderOpenGraph(og: NonNullable<Metadata['openGraph']>, elements: HeadElement[]): void {\n const simpleProps: Array<[string, string | undefined]> = [\n ['og:title', og.title],\n ['og:description', og.description],\n ['og:url', og.url],\n ['og:site_name', og.siteName],\n ['og:locale', og.locale],\n ['og:type', og.type],\n ['og:article:published_time', og.publishedTime],\n ['og:article:modified_time', og.modifiedTime],\n ];\n\n for (const [property, content] of simpleProps) {\n if (content) {\n elements.push({ tag: 'meta', attrs: { property, content } });\n }\n }\n\n // Images — normalize single object to array for uniform handling\n if (og.images) {\n if (typeof og.images === 'string') {\n elements.push({ tag: 'meta', attrs: { property: 'og:image', content: og.images } });\n } else {\n const imgList = Array.isArray(og.images) ? og.images : [og.images];\n for (const img of imgList) {\n elements.push({ tag: 'meta', attrs: { property: 'og:image', content: img.url } });\n if (img.width) {\n elements.push({\n tag: 'meta',\n attrs: { property: 'og:image:width', content: String(img.width) },\n });\n }\n if (img.height) {\n elements.push({\n tag: 'meta',\n attrs: { property: 'og:image:height', content: String(img.height) },\n });\n }\n if (img.alt) {\n elements.push({ tag: 'meta', attrs: { property: 'og:image:alt', content: img.alt } });\n }\n }\n }\n }\n\n // Videos\n if (og.videos) {\n for (const video of og.videos) {\n elements.push({ tag: 'meta', attrs: { property: 'og:video', content: video.url } });\n }\n }\n\n // Audio\n if (og.audio) {\n for (const audio of og.audio) {\n elements.push({ tag: 'meta', attrs: { property: 'og:audio', content: audio.url } });\n }\n }\n\n // Authors\n if (og.authors) {\n for (const author of og.authors) {\n elements.push({\n tag: 'meta',\n attrs: { property: 'og:article:author', content: author },\n });\n }\n }\n}\n\n/**\n * Render Twitter Card metadata into head element descriptors.\n *\n * Handles twitter:card, twitter:site, twitter:title, twitter:image,\n * twitter:player, and twitter:app (per-platform name/id/url).\n */\nexport function renderTwitter(tw: NonNullable<Metadata['twitter']>, elements: HeadElement[]): void {\n const simpleProps: Array<[string, string | undefined]> = [\n ['twitter:card', tw.card],\n ['twitter:site', tw.site],\n ['twitter:site:id', tw.siteId],\n ['twitter:title', tw.title],\n ['twitter:description', tw.description],\n ['twitter:creator', tw.creator],\n ['twitter:creator:id', tw.creatorId],\n ];\n\n for (const [name, content] of simpleProps) {\n if (content) {\n elements.push({ tag: 'meta', attrs: { name, content } });\n }\n }\n\n // Images — normalize single object to array for uniform handling\n if (tw.images) {\n if (typeof tw.images === 'string') {\n elements.push({ tag: 'meta', attrs: { name: 'twitter:image', content: tw.images } });\n } else {\n const imgList = Array.isArray(tw.images) ? tw.images : [tw.images];\n for (const img of imgList) {\n const url = typeof img === 'string' ? img : img.url;\n elements.push({ tag: 'meta', attrs: { name: 'twitter:image', content: url } });\n }\n }\n }\n\n // Player card fields\n if (tw.players) {\n for (const player of tw.players) {\n elements.push({ tag: 'meta', attrs: { name: 'twitter:player', content: player.playerUrl } });\n if (player.width) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'twitter:player:width', content: String(player.width) },\n });\n }\n if (player.height) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'twitter:player:height', content: String(player.height) },\n });\n }\n if (player.streamUrl) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'twitter:player:stream', content: player.streamUrl },\n });\n }\n }\n }\n\n // App card fields — 3 platforms × 3 attributes (name, id, url)\n if (tw.app) {\n const platforms: Array<[keyof NonNullable<typeof tw.app.id>, string]> = [\n ['iPhone', 'iphone'],\n ['iPad', 'ipad'],\n ['googlePlay', 'googleplay'],\n ];\n\n // App name is shared across platforms but the spec uses per-platform names.\n // Emit for each platform that has an ID.\n if (tw.app.name) {\n for (const [key, tag] of platforms) {\n if (tw.app.id?.[key]) {\n elements.push({\n tag: 'meta',\n attrs: { name: `twitter:app:name:${tag}`, content: tw.app.name },\n });\n }\n }\n }\n\n for (const [key, tag] of platforms) {\n const id = tw.app.id?.[key];\n if (id) {\n elements.push({ tag: 'meta', attrs: { name: `twitter:app:id:${tag}`, content: id } });\n }\n }\n\n for (const [key, tag] of platforms) {\n const url = tw.app.url?.[key];\n if (url) {\n elements.push({ tag: 'meta', attrs: { name: `twitter:app:url:${tag}`, content: url } });\n }\n }\n }\n}\n","/**\n * Platform-specific metadata rendering — icons, Apple Web App, App Links, iTunes.\n *\n * Extracted from metadata-render.ts to keep files under 500 lines.\n *\n * See design/16-metadata.md\n */\n\nimport type { Metadata } from './types.js';\nimport type { HeadElement } from './metadata.js';\n\n/**\n * Render icon link elements (favicon, shortcut, apple-touch-icon, custom).\n */\nexport function renderIcons(icons: NonNullable<Metadata['icons']>, elements: HeadElement[]): void {\n // Icon\n if (icons.icon) {\n if (typeof icons.icon === 'string') {\n elements.push({ tag: 'link', attrs: { rel: 'icon', href: icons.icon } });\n } else if (Array.isArray(icons.icon)) {\n for (const icon of icons.icon) {\n const attrs: Record<string, string> = { rel: 'icon', href: icon.url };\n if (icon.sizes) attrs.sizes = icon.sizes;\n if (icon.type) attrs.type = icon.type;\n elements.push({ tag: 'link', attrs });\n }\n }\n }\n\n // Shortcut\n if (icons.shortcut) {\n const urls = Array.isArray(icons.shortcut) ? icons.shortcut : [icons.shortcut];\n for (const url of urls) {\n elements.push({ tag: 'link', attrs: { rel: 'shortcut icon', href: url } });\n }\n }\n\n // Apple\n if (icons.apple) {\n if (typeof icons.apple === 'string') {\n elements.push({ tag: 'link', attrs: { rel: 'apple-touch-icon', href: icons.apple } });\n } else if (Array.isArray(icons.apple)) {\n for (const icon of icons.apple) {\n const attrs: Record<string, string> = { rel: 'apple-touch-icon', href: icon.url };\n if (icon.sizes) attrs.sizes = icon.sizes;\n elements.push({ tag: 'link', attrs });\n }\n }\n }\n\n // Other\n if (icons.other) {\n for (const icon of icons.other) {\n const attrs: Record<string, string> = { rel: icon.rel, href: icon.url };\n if (icon.sizes) attrs.sizes = icon.sizes;\n if (icon.type) attrs.type = icon.type;\n elements.push({ tag: 'link', attrs });\n }\n }\n}\n\n/**\n * Render alternate link elements (canonical, hreflang, media, types).\n */\nexport function renderAlternates(\n alternates: NonNullable<Metadata['alternates']>,\n elements: HeadElement[]\n): void {\n if (alternates.canonical) {\n elements.push({ tag: 'link', attrs: { rel: 'canonical', href: alternates.canonical } });\n }\n\n if (alternates.languages) {\n for (const [lang, href] of Object.entries(alternates.languages)) {\n elements.push({\n tag: 'link',\n attrs: { rel: 'alternate', hreflang: lang, href },\n });\n }\n }\n\n if (alternates.media) {\n for (const [media, href] of Object.entries(alternates.media)) {\n elements.push({\n tag: 'link',\n attrs: { rel: 'alternate', media, href },\n });\n }\n }\n\n if (alternates.types) {\n for (const [type, href] of Object.entries(alternates.types)) {\n elements.push({\n tag: 'link',\n attrs: { rel: 'alternate', type, href },\n });\n }\n }\n}\n\n/**\n * Render site verification meta tags (Google, Yahoo, Yandex, custom).\n */\nexport function renderVerification(\n verification: NonNullable<Metadata['verification']>,\n elements: HeadElement[]\n): void {\n const verificationProps: Array<[string, string | undefined]> = [\n ['google-site-verification', verification.google],\n ['y_key', verification.yahoo],\n ['yandex-verification', verification.yandex],\n ];\n\n for (const [name, content] of verificationProps) {\n if (content) {\n elements.push({ tag: 'meta', attrs: { name, content } });\n }\n }\n if (verification.other) {\n for (const [name, value] of Object.entries(verification.other)) {\n const content = Array.isArray(value) ? value.join(', ') : value;\n elements.push({ tag: 'meta', attrs: { name, content } });\n }\n }\n}\n\n/**\n * Render Apple Web App meta tags and startup image links.\n */\nexport function renderAppleWebApp(\n appleWebApp: NonNullable<Metadata['appleWebApp']>,\n elements: HeadElement[]\n): void {\n if (appleWebApp.capable) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'apple-mobile-web-app-capable', content: 'yes' },\n });\n }\n if (appleWebApp.title) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'apple-mobile-web-app-title', content: appleWebApp.title },\n });\n }\n if (appleWebApp.statusBarStyle) {\n elements.push({\n tag: 'meta',\n attrs: {\n name: 'apple-mobile-web-app-status-bar-style',\n content: appleWebApp.statusBarStyle,\n },\n });\n }\n if (appleWebApp.startupImage) {\n const images = Array.isArray(appleWebApp.startupImage)\n ? appleWebApp.startupImage\n : [{ url: appleWebApp.startupImage }];\n for (const img of images) {\n const url = typeof img === 'string' ? img : img.url;\n const attrs: Record<string, string> = { rel: 'apple-touch-startup-image', href: url };\n if (typeof img === 'object' && img.media) {\n attrs.media = img.media;\n }\n elements.push({ tag: 'link', attrs });\n }\n }\n}\n\n/**\n * Render App Links (al:*) meta tags for deep linking across platforms.\n */\nexport function renderAppLinks(\n appLinks: NonNullable<Metadata['appLinks']>,\n elements: HeadElement[]\n): void {\n const platformEntries: Array<[string, Array<Record<string, unknown>> | undefined]> = [\n ['ios', appLinks.ios],\n ['android', appLinks.android],\n ['windows', appLinks.windows],\n ['windows_phone', appLinks.windowsPhone],\n ['windows_universal', appLinks.windowsUniversal],\n ];\n\n for (const [platform, entries] of platformEntries) {\n if (!entries) continue;\n for (const entry of entries) {\n for (const [key, value] of Object.entries(entry)) {\n if (value !== undefined && value !== null) {\n elements.push({\n tag: 'meta',\n attrs: { property: `al:${platform}:${key}`, content: String(value) },\n });\n }\n }\n }\n }\n\n if (appLinks.web) {\n if (appLinks.web.url) {\n elements.push({\n tag: 'meta',\n attrs: { property: 'al:web:url', content: appLinks.web.url },\n });\n }\n if (appLinks.web.shouldFallback !== undefined) {\n elements.push({\n tag: 'meta',\n attrs: {\n property: 'al:web:should_fallback',\n content: appLinks.web.shouldFallback ? 'true' : 'false',\n },\n });\n }\n }\n}\n\n/**\n * Render Apple iTunes smart banner meta tag.\n */\nexport function renderItunes(itunes: NonNullable<Metadata['itunes']>, elements: HeadElement[]): void {\n const parts = [`app-id=${itunes.appId}`];\n if (itunes.affiliateData) parts.push(`affiliate-data=${itunes.affiliateData}`);\n if (itunes.appArgument) parts.push(`app-argument=${itunes.appArgument}`);\n elements.push({\n tag: 'meta',\n attrs: { name: 'apple-itunes-app', content: parts.join(', ') },\n });\n}\n","/**\n * Metadata rendering — converts resolved Metadata into HeadElement descriptors.\n *\n * Extracted from metadata.ts to keep files under 500 lines.\n *\n * See design/16-metadata.md\n */\n\nimport type { Metadata } from './types.js';\nimport type { HeadElement } from './metadata.js';\nimport { renderOpenGraph, renderTwitter } from './metadata-social.js';\nimport {\n renderIcons,\n renderAlternates,\n renderVerification,\n renderAppleWebApp,\n renderAppLinks,\n renderItunes,\n} from './metadata-platform.js';\n\n// ─── Render to Elements ──────────────────────────────────────────────────────\n\n/**\n * Convert resolved metadata into an array of head element descriptors.\n *\n * Each descriptor has a `tag` ('title', 'meta', 'link') and either\n * `content` (for <title>) or `attrs` (for <meta>/<link>).\n *\n * The framework's MetadataResolver component consumes these descriptors\n * and renders them into the <head>.\n */\nexport function renderMetadataToElements(metadata: Metadata): HeadElement[] {\n const elements: HeadElement[] = [];\n\n // Title\n if (typeof metadata.title === 'string') {\n elements.push({ tag: 'title', content: metadata.title });\n }\n\n // Simple string meta tags\n const simpleMetaProps: Array<[string, string | undefined]> = [\n ['description', metadata.description],\n ['generator', metadata.generator],\n ['application-name', metadata.applicationName],\n ['referrer', metadata.referrer],\n ['category', metadata.category],\n ['creator', metadata.creator],\n ['publisher', metadata.publisher],\n ];\n\n for (const [name, content] of simpleMetaProps) {\n if (content) {\n elements.push({ tag: 'meta', attrs: { name, content } });\n }\n }\n\n // Keywords (array or string)\n if (metadata.keywords) {\n const content = Array.isArray(metadata.keywords)\n ? metadata.keywords.join(', ')\n : metadata.keywords;\n elements.push({ tag: 'meta', attrs: { name: 'keywords', content } });\n }\n\n // Robots\n if (metadata.robots) {\n const content =\n typeof metadata.robots === 'string' ? metadata.robots : renderRobotsObject(metadata.robots);\n elements.push({ tag: 'meta', attrs: { name: 'robots', content } });\n\n // googleBot as separate tag\n if (typeof metadata.robots === 'object' && metadata.robots.googleBot) {\n const gbContent =\n typeof metadata.robots.googleBot === 'string'\n ? metadata.robots.googleBot\n : renderRobotsObject(metadata.robots.googleBot);\n elements.push({ tag: 'meta', attrs: { name: 'googlebot', content: gbContent } });\n }\n }\n\n // Open Graph\n if (metadata.openGraph) {\n renderOpenGraph(metadata.openGraph, elements);\n }\n\n // Twitter\n if (metadata.twitter) {\n renderTwitter(metadata.twitter, elements);\n }\n\n // Icons\n if (metadata.icons) {\n renderIcons(metadata.icons, elements);\n }\n\n // Manifest\n if (metadata.manifest) {\n elements.push({ tag: 'link', attrs: { rel: 'manifest', href: metadata.manifest } });\n }\n\n // Alternates\n if (metadata.alternates) {\n renderAlternates(metadata.alternates, elements);\n }\n\n // Verification\n if (metadata.verification) {\n renderVerification(metadata.verification, elements);\n }\n\n // Format detection\n if (metadata.formatDetection) {\n const parts: string[] = [];\n if (metadata.formatDetection.telephone === false) parts.push('telephone=no');\n if (metadata.formatDetection.email === false) parts.push('email=no');\n if (metadata.formatDetection.address === false) parts.push('address=no');\n if (parts.length > 0) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'format-detection', content: parts.join(', ') },\n });\n }\n }\n\n // Authors\n if (metadata.authors) {\n const authorList = Array.isArray(metadata.authors) ? metadata.authors : [metadata.authors];\n for (const author of authorList) {\n if (author.name) {\n elements.push({ tag: 'meta', attrs: { name: 'author', content: author.name } });\n }\n if (author.url) {\n elements.push({ tag: 'link', attrs: { rel: 'author', href: author.url } });\n }\n }\n }\n\n // Apple Web App\n if (metadata.appleWebApp) {\n renderAppleWebApp(metadata.appleWebApp, elements);\n }\n\n // App Links (al:*)\n if (metadata.appLinks) {\n renderAppLinks(metadata.appLinks, elements);\n }\n\n // iTunes\n if (metadata.itunes) {\n renderItunes(metadata.itunes, elements);\n }\n\n // Other (custom meta tags)\n if (metadata.other) {\n for (const [name, value] of Object.entries(metadata.other)) {\n const content = Array.isArray(value) ? value.join(', ') : value;\n elements.push({ tag: 'meta', attrs: { name, content } });\n }\n }\n\n return elements;\n}\n\n// ─── Rendering Helpers ───────────────────────────────────────────────────────\n\nfunction renderRobotsObject(robots: Record<string, unknown>): string {\n const parts: string[] = [];\n if (robots.index === true) parts.push('index');\n if (robots.index === false) parts.push('noindex');\n if (robots.follow === true) parts.push('follow');\n if (robots.follow === false) parts.push('nofollow');\n return parts.join(', ');\n}\n","/**\n * Metadata resolution for timber.js.\n *\n * Resolves metadata from a segment chain (layouts + page), applies title\n * templates, shallow-merges entries, and produces head element descriptors.\n *\n * Resolution happens inside the render pass — React.cache is active,\n * metadata is outside Suspense, and the flush point guarantees completeness.\n *\n * Rendering (Metadata → HeadElement[]) is in metadata-render.ts.\n *\n * See design/16-metadata.md\n */\n\nimport type { Metadata } from './types.js';\n\n// Re-export renderMetadataToElements from the rendering module so existing\n// consumers (route-element-builder, tests) can keep importing from here.\nexport { renderMetadataToElements } from './metadata-render.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** A single metadata entry from a layout or page module. */\nexport interface SegmentMetadataEntry {\n /** The resolved metadata object (from static or async `metadata` export). */\n metadata: Metadata;\n /** Whether this entry is from the page (leaf) module. */\n isPage: boolean;\n}\n\n/** Options for resolveMetadata. */\nexport interface ResolveMetadataOptions {\n /**\n * When true, the page's metadata is discarded (simulating a render error)\n * and `<meta name=\"robots\" content=\"noindex\">` is injected.\n */\n errorState?: boolean;\n}\n\n/** A rendered head element descriptor. */\nexport interface HeadElement {\n tag: 'title' | 'meta' | 'link';\n content?: string;\n attrs?: Record<string, string>;\n}\n\n// ─── Title Resolution ────────────────────────────────────────────────────────\n\n/**\n * Resolve a title value with an optional template.\n *\n * - string → apply template if present\n * - { absolute: '...' } → use as-is, skip template\n * - { default: '...' } → use as fallback (no template applied)\n * - undefined → undefined\n */\nexport function resolveTitle(\n title: Metadata['title'],\n template: string | undefined\n): string | undefined {\n if (title === undefined || title === null) {\n return undefined;\n }\n\n if (typeof title === 'string') {\n return template ? template.replace('%s', title) : title;\n }\n\n // Object form\n if (title.absolute !== undefined) {\n return title.absolute;\n }\n\n if (title.default !== undefined) {\n return title.default;\n }\n\n return undefined;\n}\n\n// ─── Metadata Resolution ─────────────────────────────────────────────────────\n\n/**\n * Resolve metadata from a segment chain.\n *\n * Processes entries from root layout to page (in segment order).\n * The merge algorithm:\n * 1. Shallow-merge all keys except title (later wins)\n * 2. Track the most recent title template\n * 3. Resolve the final title using the template\n *\n * In error state, the page entry is dropped and noindex is injected.\n *\n * See design/16-metadata.md §\"Merge Algorithm\"\n */\nexport function resolveMetadata(\n entries: SegmentMetadataEntry[],\n options: ResolveMetadataOptions = {}\n): Metadata {\n const { errorState = false } = options;\n\n const merged: Metadata = {};\n let titleTemplate: string | undefined;\n let lastDefault: string | undefined;\n let rawTitle: Metadata['title'];\n\n for (const { metadata, isPage } of entries) {\n // In error state, skip the page's metadata entirely\n if (errorState && isPage) {\n continue;\n }\n\n // Track title template\n if (metadata.title !== undefined && typeof metadata.title === 'object') {\n if (metadata.title.template !== undefined) {\n titleTemplate = metadata.title.template;\n }\n if (metadata.title.default !== undefined) {\n lastDefault = metadata.title.default;\n }\n }\n\n // Shallow-merge all keys except title\n for (const key of Object.keys(metadata) as Array<keyof Metadata>) {\n if (key === 'title') continue;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (merged as any)[key] = metadata[key];\n }\n\n // Track raw title (will be resolved after the loop)\n if (metadata.title !== undefined) {\n rawTitle = metadata.title;\n }\n }\n\n // In error state, we lost page title — use the most recent default\n if (errorState) {\n rawTitle = lastDefault !== undefined ? { default: lastDefault } : rawTitle;\n // Don't apply template in error state\n titleTemplate = undefined;\n }\n\n // Resolve the final title\n const resolvedTitle = resolveTitle(rawTitle, titleTemplate);\n if (resolvedTitle !== undefined) {\n merged.title = resolvedTitle;\n }\n\n // Error state: inject noindex, overriding any user robots\n if (errorState) {\n merged.robots = 'noindex';\n }\n\n return merged;\n}\n\n// ─── URL Resolution ──────────────────────────────────────────────────────────\n\n/**\n * Check if a string is an absolute URL.\n */\nfunction isAbsoluteUrl(url: string): boolean {\n return url.startsWith('http://') || url.startsWith('https://') || url.startsWith('//');\n}\n\n/**\n * Resolve a relative URL against a base URL.\n */\nfunction resolveUrl(url: string, base: URL): string {\n if (isAbsoluteUrl(url)) return url;\n return new URL(url, base).toString();\n}\n\n/**\n * Resolve relative URLs in metadata fields against metadataBase.\n *\n * Returns a new metadata object with URLs resolved. Absolute URLs are not modified.\n * If metadataBase is not set, returns the metadata unchanged.\n */\nexport function resolveMetadataUrls(metadata: Metadata): Metadata {\n const base = metadata.metadataBase;\n if (!base) return metadata;\n\n const result = { ...metadata };\n\n // Resolve openGraph images\n if (result.openGraph) {\n result.openGraph = { ...result.openGraph };\n if (typeof result.openGraph.images === 'string') {\n result.openGraph.images = resolveUrl(result.openGraph.images, base);\n } else if (Array.isArray(result.openGraph.images)) {\n result.openGraph.images = result.openGraph.images.map((img) => ({\n ...img,\n url: resolveUrl(img.url, base),\n }));\n } else if (result.openGraph.images) {\n // Single object: { url, width?, height?, alt? }\n result.openGraph.images = {\n ...result.openGraph.images,\n url: resolveUrl(result.openGraph.images.url, base),\n };\n }\n if (result.openGraph.url && !isAbsoluteUrl(result.openGraph.url)) {\n result.openGraph.url = resolveUrl(result.openGraph.url, base);\n }\n }\n\n // Resolve twitter images\n if (result.twitter) {\n result.twitter = { ...result.twitter };\n if (typeof result.twitter.images === 'string') {\n result.twitter.images = resolveUrl(result.twitter.images, base);\n } else if (Array.isArray(result.twitter.images)) {\n // Resolve each image URL, preserving the union type structure\n const resolved = result.twitter.images.map((img) =>\n typeof img === 'string' ? resolveUrl(img, base) : { ...img, url: resolveUrl(img.url, base) }\n );\n // If all entries are strings, assign as string[]; otherwise as object[]\n const allStrings = resolved.every((r) => typeof r === 'string');\n result.twitter.images = allStrings\n ? (resolved as string[])\n : (resolved as Array<{ url: string; alt?: string; width?: number; height?: number }>);\n } else if (result.twitter.images) {\n // Single object: { url, alt?, width?, height? }\n result.twitter.images = {\n ...result.twitter.images,\n url: resolveUrl(result.twitter.images.url, base),\n };\n }\n }\n\n // Resolve alternates\n if (result.alternates) {\n result.alternates = { ...result.alternates };\n if (result.alternates.canonical && !isAbsoluteUrl(result.alternates.canonical)) {\n result.alternates.canonical = resolveUrl(result.alternates.canonical, base);\n }\n if (result.alternates.languages) {\n const langs: Record<string, string> = {};\n for (const [lang, url] of Object.entries(result.alternates.languages)) {\n langs[lang] = isAbsoluteUrl(url) ? url : resolveUrl(url, base);\n }\n result.alternates.languages = langs;\n }\n }\n\n // Resolve icon URLs\n if (result.icons) {\n result.icons = { ...result.icons };\n if (typeof result.icons.icon === 'string') {\n result.icons.icon = resolveUrl(result.icons.icon, base);\n } else if (Array.isArray(result.icons.icon)) {\n result.icons.icon = result.icons.icon.map((i) => ({ ...i, url: resolveUrl(i.url, base) }));\n }\n if (typeof result.icons.apple === 'string') {\n result.icons.apple = resolveUrl(result.icons.apple, base);\n } else if (Array.isArray(result.icons.apple)) {\n result.icons.apple = result.icons.apple.map((i) => ({ ...i, url: resolveUrl(i.url, base) }));\n }\n }\n\n return result;\n}\n","/**\n * FormData preprocessing — schema-agnostic conversion of FormData to typed objects.\n *\n * FormData is all strings. Schema validation expects typed values. This module\n * bridges the gap with intelligent coercion that runs *before* schema validation.\n *\n * Inspired by zod-form-data, but schema-agnostic — works with any Standard Schema\n * library (Zod, Valibot, ArkType).\n *\n * See design/08-forms-and-actions.md §\"parseFormData() and coerce helpers\"\n */\n\n// ─── parseFormData ───────────────────────────────────────────────────────\n\n/**\n * Convert FormData into a plain object with intelligent coercion.\n *\n * Handles:\n * - **Duplicate keys → arrays**: `tags=js&tags=ts` → `{ tags: [\"js\", \"ts\"] }`\n * - **Nested dot-paths**: `user.name=Alice` → `{ user: { name: \"Alice\" } }`\n * - **Empty strings → undefined**: Enables `.optional()` semantics in schemas\n * - **Empty Files → undefined**: File inputs with no selection become `undefined`\n * - **Strips `$ACTION_*` fields**: React's internal hidden fields are excluded\n */\nexport function parseFormData(formData: FormData): Record<string, unknown> {\n const flat: Record<string, unknown> = {};\n\n for (const key of new Set(formData.keys())) {\n // Skip React internal fields\n if (key.startsWith('$ACTION_')) continue;\n\n const values = formData.getAll(key);\n const processed = values.map(normalizeValue);\n\n if (processed.length === 1) {\n flat[key] = processed[0];\n } else {\n // Filter out undefined entries from multi-value fields\n flat[key] = processed.filter((v) => v !== undefined);\n }\n }\n\n // Expand dot-notation paths into nested objects\n return expandDotPaths(flat);\n}\n\n/**\n * Normalize a single FormData entry value.\n * - Empty strings → undefined (enables .optional() semantics)\n * - Empty File objects (no selection) → undefined\n * - Everything else passes through as-is\n */\nfunction normalizeValue(value: FormDataEntryValue): unknown {\n if (typeof value === 'string') {\n return value === '' ? undefined : value;\n }\n\n // File input with no selection: browsers submit a File with name=\"\" and size=0\n if (value instanceof File && value.size === 0 && value.name === '') {\n return undefined;\n }\n\n return value;\n}\n\n/**\n * Expand dot-notation keys into nested objects.\n * `{ \"user.name\": \"Alice\", \"user.age\": \"30\" }` → `{ user: { name: \"Alice\", age: \"30\" } }`\n *\n * Keys without dots are left as-is. Bracket notation (e.g. `items[0]`) is NOT\n * supported — use dot notation (`items.0`) instead.\n */\nfunction expandDotPaths(flat: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n let hasDotPaths = false;\n\n // First pass: check if any keys have dots\n for (const key of Object.keys(flat)) {\n if (key.includes('.')) {\n hasDotPaths = true;\n break;\n }\n }\n\n // Fast path: no dot-notation keys, return as-is\n if (!hasDotPaths) return flat;\n\n for (const [key, value] of Object.entries(flat)) {\n if (!key.includes('.')) {\n result[key] = value;\n continue;\n }\n\n const parts = key.split('.');\n let current: Record<string, unknown> = result;\n\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i];\n if (current[part] === undefined || current[part] === null) {\n current[part] = {};\n }\n // If current[part] is not an object (e.g., a string from a non-dotted key),\n // the dot-path takes precedence\n if (typeof current[part] !== 'object' || current[part] instanceof File) {\n current[part] = {};\n }\n current = current[part] as Record<string, unknown>;\n }\n\n current[parts[parts.length - 1]] = value;\n }\n\n return result;\n}\n\n// ─── Coercion Helpers ────────────────────────────────────────────────────\n\n/**\n * Schema-agnostic coercion primitives for common FormData patterns.\n *\n * These are plain transform functions — they compose with any schema library's\n * `transform`/`preprocess` pipeline:\n *\n * ```ts\n * // Zod\n * z.preprocess(coerce.number, z.number())\n * // Valibot\n * v.pipe(v.unknown(), v.transform(coerce.number), v.number())\n * ```\n */\nexport const coerce = {\n /**\n * Coerce a string to a number.\n * - `\"42\"` → `42`\n * - `\"3.14\"` → `3.14`\n * - `\"\"` / `undefined` / `null` → `undefined`\n * - Non-numeric strings → `undefined` (schema validation will catch this)\n */\n number(value: unknown): number | undefined {\n if (value === undefined || value === null || value === '') return undefined;\n if (typeof value === 'number') return value;\n if (typeof value !== 'string') return undefined;\n const num = Number(value);\n if (Number.isNaN(num)) return undefined;\n return num;\n },\n\n /**\n * Coerce a checkbox value to a boolean.\n * HTML checkboxes submit \"on\" when checked and are absent when unchecked.\n * - `\"on\"` / any truthy string → `true`\n * - `undefined` / `null` / `\"\"` → `false`\n */\n checkbox(value: unknown): boolean {\n if (value === undefined || value === null || value === '') return false;\n if (typeof value === 'boolean') return value;\n // Any non-empty string (typically \"on\") is true\n return typeof value === 'string' && value.length > 0;\n },\n\n /**\n * Parse a JSON string into an object.\n * - Valid JSON string → parsed object\n * - `\"\"` / `undefined` / `null` → `undefined`\n * - Invalid JSON → `undefined` (schema validation will catch this)\n */\n json(value: unknown): unknown {\n if (value === undefined || value === null || value === '') return undefined;\n if (typeof value !== 'string') return value;\n try {\n return JSON.parse(value);\n } catch {\n return undefined;\n }\n },\n};\n","/**\n * createActionClient — typed middleware and schema validation for server actions.\n *\n * Inspired by next-safe-action. Provides a builder API:\n * createActionClient({ middleware }) → .schema(z.object(...)) → .action(fn)\n *\n * The resulting action function satisfies both:\n * 1. Direct call: action(input) → Promise<ActionResult>\n * 2. React useActionState: (prevState, formData) => Promise<ActionResult>\n *\n * See design/08-forms-and-actions.md §\"Middleware for Server Actions\"\n */\n\n// ─── ActionError ─────────────────────────────────────────────────────────\n\n/**\n * Typed error class for server actions. Carries a string code and optional data.\n * When thrown from middleware or the action body, the action short-circuits and\n * the client receives `result.serverError`.\n *\n * In production, unexpected errors (non-ActionError) return `{ code: 'INTERNAL_ERROR' }`\n * with no message. In dev, `data.message` is included.\n */\nexport class ActionError<TCode extends string = string> extends Error {\n readonly code: TCode;\n readonly data: Record<string, unknown> | undefined;\n\n constructor(code: TCode, data?: Record<string, unknown>) {\n super(`ActionError: ${code}`);\n this.name = 'ActionError';\n this.code = code;\n this.data = data;\n }\n}\n\n// ─── Standard Schema ──────────────────────────────────────────────────────\n\n/**\n * Standard Schema v1 interface (subset).\n * Zod ≥3.24, Valibot ≥1.0, and ArkType all implement this.\n * See https://github.com/standard-schema/standard-schema\n *\n * We use permissive types here to accept all compliant libraries without\n * requiring exact structural matches on issues/path shapes.\n */\ninterface StandardSchemaV1<Output = unknown> {\n '~standard': {\n validate(value: unknown): StandardSchemaResult<Output> | Promise<StandardSchemaResult<Output>>;\n };\n}\n\ntype StandardSchemaResult<Output> =\n | { value: Output; issues?: undefined }\n | { value?: undefined; issues: ReadonlyArray<StandardSchemaIssue> };\n\ninterface StandardSchemaIssue {\n message: string;\n path?: ReadonlyArray<PropertyKey | { key: PropertyKey }>;\n}\n\n/** Check if a schema implements the Standard Schema protocol. */\nfunction isStandardSchema(schema: unknown): schema is StandardSchemaV1 {\n return (\n typeof schema === 'object' &&\n schema !== null &&\n '~standard' in schema &&\n typeof (schema as StandardSchemaV1)['~standard'].validate === 'function'\n );\n}\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\n/**\n * Minimal schema interface — compatible with Zod, Valibot, ArkType, etc.\n *\n * Accepts either:\n * - Standard Schema (preferred): any object with `~standard.validate()`\n * - Legacy parse interface: objects with `.parse()` / `.safeParse()`\n *\n * At runtime, Standard Schema is detected via `~standard` property and\n * takes priority over the legacy interface.\n */\nexport type ActionSchema<T = unknown> = StandardSchemaV1<T> | LegacyActionSchema<T>;\n\n/** Legacy schema interface with .parse() / .safeParse(). */\ninterface LegacyActionSchema<T = unknown> {\n 'parse'(data: unknown): T;\n 'safeParse'?(data: unknown): { success: true; data: T } | { success: false; error: SchemaError };\n // Exclude Standard Schema objects from matching this interface\n '~standard'?: never;\n}\n\n/** Schema validation error shape (for legacy .safeParse()/.parse() interface). */\nexport interface SchemaError {\n issues?: Array<{ path?: Array<string | number>; message: string }>;\n flatten?(): { fieldErrors: Record<string, string[]> };\n}\n\n/** Flattened validation errors keyed by field name. */\nexport type ValidationErrors = Record<string, string[]>;\n\n/** Middleware function: returns context to merge into the action body's ctx. */\nexport type ActionMiddleware<TCtx = Record<string, unknown>> = () => Promise<TCtx> | TCtx;\n\n/** The result type returned to the client. */\nexport type ActionResult<TData = unknown> =\n | { data: TData; validationErrors?: never; serverError?: never; submittedValues?: never }\n | {\n data?: never;\n validationErrors: ValidationErrors;\n serverError?: never;\n /** Raw input values on validation failure — for repopulating form fields. */\n submittedValues?: Record<string, unknown>;\n }\n | {\n data?: never;\n validationErrors?: never;\n serverError: { code: string; data?: Record<string, unknown> };\n submittedValues?: never;\n };\n\n/** Context passed to the action body. */\nexport interface ActionContext<TCtx, TInput> {\n ctx: TCtx;\n input: TInput;\n}\n\n// ─── Builder ─────────────────────────────────────────────────────────────\n\ninterface ActionClientConfig<TCtx> {\n middleware?: ActionMiddleware<TCtx> | ActionMiddleware<Record<string, unknown>>[];\n /** Max file size in bytes. Files exceeding this are rejected with validation errors. */\n fileSizeLimit?: number;\n}\n\n/** Intermediate builder returned by createActionClient(). */\nexport interface ActionBuilder<TCtx> {\n /** Declare the input schema. Validation errors are returned typed. */\n schema<TInput>(schema: ActionSchema<TInput>): ActionBuilderWithSchema<TCtx, TInput>;\n /** Define the action body without input validation. */\n action<TData>(fn: (ctx: ActionContext<TCtx, undefined>) => Promise<TData>): ActionFn<TData>;\n}\n\n/** Builder after .schema() has been called. */\nexport interface ActionBuilderWithSchema<TCtx, TInput> {\n /** Define the action body with validated input. */\n action<TData>(fn: (ctx: ActionContext<TCtx, TInput>) => Promise<TData>): ActionFn<TData>;\n}\n\n/**\n * The final action function. Callable two ways:\n * - Direct: action(input) → Promise<ActionResult<TData>>\n * - React useActionState: action(prevState, formData) → Promise<ActionResult<TData>>\n */\nexport type ActionFn<TData> = {\n (input?: unknown): Promise<ActionResult<TData>>;\n (prevState: ActionResult<TData> | null, formData: FormData): Promise<ActionResult<TData>>;\n};\n\n// ─── Implementation ──────────────────────────────────────────────────────\n\n/**\n * Run middleware array or single function. Returns merged context.\n */\nasync function runActionMiddleware<TCtx>(\n middleware: ActionMiddleware<TCtx> | ActionMiddleware<Record<string, unknown>>[] | undefined\n): Promise<TCtx> {\n if (!middleware) {\n return {} as TCtx;\n }\n\n if (Array.isArray(middleware)) {\n let merged = {} as Record<string, unknown>;\n for (const mw of middleware) {\n const result = await mw();\n merged = { ...merged, ...result };\n }\n return merged as TCtx;\n }\n\n return await middleware();\n}\n\n// Re-export parseFormData for use throughout the framework\nimport { parseFormData } from './form-data.js';\nimport { formatSize } from '#/utils/format.js';\n\n/**\n * Extract validation errors from a schema error.\n * Supports Zod's flatten() and generic issues array.\n */\nfunction extractValidationErrors(error: SchemaError): ValidationErrors {\n // Zod-style flatten\n if (typeof error.flatten === 'function') {\n return error.flatten().fieldErrors;\n }\n\n // Generic issues array\n if (error.issues) {\n const errors: ValidationErrors = {};\n for (const issue of error.issues) {\n const path = issue.path?.join('.') ?? '_root';\n if (!errors[path]) errors[path] = [];\n errors[path].push(issue.message);\n }\n return errors;\n }\n\n return { _root: ['Validation failed'] };\n}\n\n/**\n * Extract validation errors from Standard Schema issues.\n */\nfunction extractStandardSchemaErrors(issues: ReadonlyArray<StandardSchemaIssue>): ValidationErrors {\n const errors: ValidationErrors = {};\n for (const issue of issues) {\n const path =\n issue.path\n ?.map((p) => {\n // Standard Schema path items can be { key: ... } objects or bare PropertyKey values\n if (typeof p === 'object' && p !== null && 'key' in p) return String(p.key);\n return String(p);\n })\n .join('.') ?? '_root';\n if (!errors[path]) errors[path] = [];\n errors[path].push(issue.message);\n }\n return Object.keys(errors).length > 0 ? errors : { _root: ['Validation failed'] };\n}\n\n/**\n * Wrap unexpected errors into a safe server error result.\n * ActionError → typed result. Other errors → INTERNAL_ERROR (no leak).\n *\n * Exported for use by action-handler.ts to catch errors from raw 'use server'\n * functions that don't use createActionClient.\n */\nexport function handleActionError(error: unknown): ActionResult<never> {\n if (error instanceof ActionError) {\n return {\n serverError: {\n code: error.code,\n ...(error.data ? { data: error.data } : {}),\n },\n };\n }\n\n // In dev, include the message for debugging\n const isDev = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production';\n return {\n serverError: {\n code: 'INTERNAL_ERROR',\n ...(isDev && error instanceof Error ? { data: { message: error.message } } : {}),\n },\n };\n}\n\n/**\n * Create a typed action client with middleware and schema validation.\n *\n * @example\n * ```ts\n * const action = createActionClient({\n * middleware: async () => {\n * const user = await getUser()\n * if (!user) throw new ActionError('UNAUTHORIZED')\n * return { user }\n * },\n * })\n *\n * export const createTodo = action\n * .schema(z.object({ title: z.string().min(1) }))\n * .action(async ({ input, ctx }) => {\n * await db.todos.create({ ...input, userId: ctx.user.id })\n * })\n * ```\n */\nexport function createActionClient<TCtx = Record<string, never>>(\n config: ActionClientConfig<TCtx> = {}\n): ActionBuilder<TCtx> {\n function buildAction<TInput, TData>(\n schema: ActionSchema<TInput> | undefined,\n fn: (ctx: ActionContext<TCtx, TInput>) => Promise<TData>\n ): ActionFn<TData> {\n async function actionHandler(...args: unknown[]): Promise<ActionResult<TData>> {\n try {\n // Run middleware\n const ctx = await runActionMiddleware(config.middleware);\n\n // Determine input — either FormData (from useActionState) or direct arg\n let rawInput: unknown;\n if (args.length === 2 && args[1] instanceof FormData) {\n // Called as (prevState, formData) by React useActionState\n rawInput = schema ? parseFormData(args[1]) : args[1];\n } else {\n // Direct call: action(input)\n rawInput = args[0];\n }\n\n // Validate file sizes before schema validation.\n if (config.fileSizeLimit !== undefined && rawInput && typeof rawInput === 'object') {\n const fileSizeErrors = validateFileSizes(\n rawInput as Record<string, unknown>,\n config.fileSizeLimit\n );\n if (fileSizeErrors) {\n const submittedValues = stripFiles(rawInput);\n return { validationErrors: fileSizeErrors, submittedValues };\n }\n }\n\n // Capture submitted values for repopulation on validation failure.\n // Exclude File objects (can't serialize, shouldn't echo back).\n const submittedValues = schema ? stripFiles(rawInput) : undefined;\n\n // Validate with schema if provided\n let input: TInput;\n if (schema) {\n if (isStandardSchema(schema)) {\n // Standard Schema protocol (Zod ≥3.24, Valibot ≥1.0, ArkType)\n const result = schema['~standard'].validate(rawInput);\n if (result instanceof Promise) {\n throw new Error(\n '[timber] createActionClient: schema returned a Promise — only sync schemas are supported.'\n );\n }\n if (result.issues) {\n const validationErrors = extractStandardSchemaErrors(result.issues);\n logValidationFailure(validationErrors);\n return { validationErrors, submittedValues };\n }\n input = result.value;\n } else if (typeof schema.safeParse === 'function') {\n const result = schema.safeParse(rawInput);\n if (!result.success) {\n const validationErrors = extractValidationErrors(result.error);\n logValidationFailure(validationErrors);\n return { validationErrors, submittedValues };\n }\n input = result.data;\n } else {\n try {\n input = schema.parse(rawInput);\n } catch (parseError) {\n const validationErrors = extractValidationErrors(parseError as SchemaError);\n logValidationFailure(validationErrors);\n return { validationErrors, submittedValues };\n }\n }\n } else {\n input = rawInput as TInput;\n }\n\n // Execute the action body\n const data = await fn({ ctx, input });\n return { data };\n } catch (error) {\n return handleActionError(error);\n }\n }\n\n return actionHandler as ActionFn<TData>;\n }\n\n return {\n schema<TInput>(schema: ActionSchema<TInput>) {\n return {\n action<TData>(fn: (ctx: ActionContext<TCtx, TInput>) => Promise<TData>): ActionFn<TData> {\n return buildAction(schema, fn);\n },\n };\n },\n action<TData>(fn: (ctx: ActionContext<TCtx, undefined>) => Promise<TData>): ActionFn<TData> {\n return buildAction(undefined, fn as (ctx: ActionContext<TCtx, unknown>) => Promise<TData>);\n },\n };\n}\n\n// ─── validated() ────────────────────────────────────────────────────────\n\n/**\n * Convenience wrapper for the common case: validate input, run handler.\n * No middleware needed.\n *\n * @example\n * ```ts\n * 'use server'\n * import { validated } from '@timber-js/app/server'\n * import { z } from 'zod'\n *\n * export const createTodo = validated(\n * z.object({ title: z.string().min(1) }),\n * async (input) => {\n * await db.todos.create(input)\n * }\n * )\n * ```\n */\nexport function validated<TInput, TData>(\n schema: ActionSchema<TInput>,\n handler: (input: TInput) => Promise<TData>\n): ActionFn<TData> {\n return createActionClient()\n .schema(schema)\n .action(async ({ input }) => handler(input));\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────\n\n/**\n * Log validation failures in dev mode so developers can see what went wrong.\n * In production, validation errors are only returned to the client.\n */\nfunction logValidationFailure(errors: ValidationErrors): void {\n const isDev = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production';\n if (!isDev) return;\n\n const fields = Object.entries(errors)\n .map(([field, messages]) => ` ${field}: ${messages.join(', ')}`)\n .join('\\n');\n console.warn(`[timber] action schema validation failed:\\n${fields}`);\n}\n\n/**\n * Validate that all File objects in the input are within the size limit.\n * Returns validation errors keyed by field name, or null if all files are ok.\n */\nfunction validateFileSizes(input: Record<string, unknown>, limit: number): ValidationErrors | null {\n const errors: ValidationErrors = {};\n const limitKb = Math.round(limit / 1024);\n const limitLabel =\n limit >= 1024 * 1024 ? `${Math.round(limit / (1024 * 1024))}MB` : `${limitKb}KB`;\n\n for (const [key, value] of Object.entries(input)) {\n if (value instanceof File && value.size > limit) {\n errors[key] = [\n `File \"${value.name}\" (${formatSize(value.size)}) exceeds the ${limitLabel} limit`,\n ];\n } else if (Array.isArray(value)) {\n const oversized = value.filter((item) => item instanceof File && item.size > limit);\n if (oversized.length > 0) {\n errors[key] = oversized.map(\n (f: File) => `File \"${f.name}\" (${formatSize(f.size)}) exceeds the ${limitLabel} limit`\n );\n }\n }\n }\n\n return Object.keys(errors).length > 0 ? errors : null;\n}\n\n/**\n * Strip File objects from a value, returning a plain object safe for\n * serialization. File objects can't be serialized and shouldn't be echoed back.\n */\nfunction stripFiles(value: unknown): Record<string, unknown> | undefined {\n if (value === null || value === undefined) return undefined;\n if (typeof value !== 'object') return undefined;\n\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (v instanceof File) continue;\n if (Array.isArray(v)) {\n result[k] = v.filter((item) => !(item instanceof File));\n } else if (typeof v === 'object' && v !== null && !(v instanceof File)) {\n result[k] = stripFiles(v) ?? {};\n } else {\n result[k] = v;\n }\n }\n return result;\n}\n","/**\n * Form Flash — ALS-based store for no-JS form action results.\n *\n * When a no-JS form action completes, the server re-renders the page with\n * the action result injected via AsyncLocalStorage instead of redirecting\n * (which would discard the result). Server components read the flash and\n * pass it to client form components as the initial `useActionState` value.\n *\n * This follows the Remix/Rails pattern — the form component becomes the\n * single source of truth for both with-JS (React state) and no-JS (flash).\n *\n * The flash data is server-side only — never serialized to cookies or headers.\n *\n * See design/08-forms-and-actions.md §\"No-JS Error Round-Trip\"\n */\n\nimport type { ValidationErrors } from './action-client.js';\nimport { formFlashAls } from './als-registry.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\n/**\n * Flash data injected into the re-render after a no-JS form submission.\n *\n * This is the action result from the server action, stored in ALS so server\n * components can read it and pass it to client form components as the initial\n * state for `useActionState`. This makes the form component a single source\n * of truth for both with-JS and no-JS paths.\n *\n * The shape matches `ActionResult<unknown>` — it's one of:\n * - `{ data: ... }` — success\n * - `{ validationErrors, submittedValues }` — validation failure\n * - `{ serverError }` — server error\n */\nexport interface FormFlashData {\n /** Success data from the action. */\n data?: unknown;\n /** Validation errors keyed by field name. `_root` for form-level errors. */\n validationErrors?: ValidationErrors;\n /** Raw submitted values for repopulating form fields. File objects are excluded. */\n submittedValues?: Record<string, unknown>;\n /** Server error if the action threw an ActionError. */\n serverError?: { code: string; data?: Record<string, unknown> };\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────\n\n/**\n * Read the form flash data for the current request.\n *\n * Returns `null` if no flash data is present (i.e., this is a normal page\n * render, not a re-render after a no-JS form submission).\n *\n * Pass the flash as the initial state to `useActionState` so the form\n * component has a single source of truth for both with-JS and no-JS paths:\n *\n * ```tsx\n * // app/contact/page.tsx (server component)\n * import { getFormFlash } from '@timber-js/app/server'\n *\n * export default function ContactPage() {\n * const flash = getFormFlash()\n * return <ContactForm flash={flash} />\n * }\n *\n * // app/contact/form.tsx (client component)\n * export function ContactForm({ flash }) {\n * const [result, action, isPending] = useActionState(submitContact, flash)\n * // result is the single source of truth — flash seeds it on no-JS\n * }\n * ```\n */\nexport function getFormFlash(): FormFlashData | null {\n return formFlashAls.getStore() ?? null;\n}\n\n// ─── Framework-Internal ──────────────────────────────────────────────────\n\n/**\n * Run a callback with form flash data in scope.\n *\n * Used by the action handler to re-render the page with validation errors\n * available via `getFormFlash()`. Not part of the public API.\n *\n * @internal\n */\nexport function runWithFormFlash<T>(data: FormFlashData, fn: () => T): T {\n return formFlashAls.run(data, fn);\n}\n","/**\n * Server action primitives: revalidatePath, revalidateTag, and the action handler.\n *\n * - revalidatePath(path) re-renders the route at that path and returns the RSC\n * flight payload for inline reconciliation.\n * - revalidateTag(tag) invalidates cached shells and 'use cache' entries by tag.\n *\n * Both are callable from anywhere on the server — actions, API routes, handlers.\n *\n * The action handler processes incoming action requests, validates CSRF,\n * enforces body limits, executes the action, and returns the response\n * (with piggybacked RSC payload if revalidatePath was called).\n *\n * See design/08-forms-and-actions.md\n */\n\nimport type { CacheHandler } from '#/cache/index';\nimport { RedirectSignal } from './primitives';\nimport { withSpan } from './tracing';\nimport { revalidationAls, type RevalidationState } from './als-registry.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\n/** Result of rendering a revalidation — element tree before RSC serialization. */\nexport interface RevalidationResult {\n /** React element tree (pre-serialization — passed to renderToReadableStream). */\n element: unknown;\n /** Resolved head elements for metadata. */\n headElements: unknown[];\n}\n\n/** Renderer function that builds a React element tree for a given path. */\nexport type RevalidateRenderer = (path: string) => Promise<RevalidationResult>;\n\n// Re-export the type from the registry for public API consumers.\nexport type { RevalidationState } from './als-registry.js';\n\n/** Options for creating the action handler. */\nexport interface ActionHandlerConfig {\n /** Cache handler for tag invalidation. */\n cacheHandler?: CacheHandler;\n /** Renderer for producing RSC payloads during revalidation. */\n renderer?: RevalidateRenderer;\n}\n\n/** Result of handling a server action request. */\nexport interface ActionHandlerResult {\n /** The action's return value (serialized). */\n actionResult: unknown;\n /** Revalidation result if revalidatePath was called (element tree, not yet serialized). */\n revalidation?: RevalidationResult;\n /** Redirect location if a RedirectSignal was thrown during revalidation. */\n redirectTo?: string;\n /** Redirect status code. */\n redirectStatus?: number;\n}\n\n// ─── Revalidation State ──────────────────────────────────────────────────\n\n// Per-request revalidation state stored in AsyncLocalStorage (from als-registry.ts).\n// This ensures concurrent requests never share or overwrite each other's state\n// (the previous module-level global was vulnerable to cross-request pollution).\n\n/**\n * Set the revalidation state for the current action execution.\n * @internal — kept for test compatibility; prefer executeAction() which uses ALS.\n */\nexport function _setRevalidationState(state: RevalidationState): void {\n // Enter ALS scope — this is only used by tests that call revalidatePath/Tag\n // directly without going through executeAction().\n revalidationAls.enterWith(state);\n}\n\n/**\n * Clear the revalidation state after action execution.\n * @internal — kept for test compatibility.\n */\nexport function _clearRevalidationState(): void {\n revalidationAls.enterWith(undefined as unknown as RevalidationState);\n}\n\n/**\n * Get the current revalidation state. Throws if called outside an action context.\n * @internal\n */\nfunction getRevalidationState(): RevalidationState {\n const state = revalidationAls.getStore();\n if (!state) {\n throw new Error(\n 'revalidatePath/revalidateTag called outside of a server action context. ' +\n 'These functions can only be called during action execution.'\n );\n }\n return state;\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────\n\n/**\n * Re-render the route at `path` and include the RSC flight payload in the\n * action response. The client reconciles inline — no separate fetch needed.\n *\n * Can be called from server actions, API routes, or any server-side context.\n *\n * @param path - The path to re-render (e.g. '/dashboard', '/todos').\n */\nexport function revalidatePath(path: string): void {\n const state = getRevalidationState();\n if (!state.paths.includes(path)) {\n state.paths.push(path);\n }\n}\n\n/**\n * Invalidate all pre-rendered shells and 'use cache' entries tagged with `tag`.\n * Does not return a payload — the next request for an invalidated route re-renders fresh.\n *\n * @param tag - The cache tag to invalidate (e.g. 'products', 'user:123').\n */\nexport function revalidateTag(tag: string): void {\n const state = getRevalidationState();\n if (!state.tags.includes(tag)) {\n state.tags.push(tag);\n }\n}\n\n// ─── Action Handler ──────────────────────────────────────────────────────\n\n/**\n * Execute a server action and process revalidation.\n *\n * 1. Sets up revalidation state\n * 2. Calls the action function\n * 3. Processes revalidateTag calls (invalidates cache entries)\n * 4. Processes revalidatePath calls (re-renders and captures RSC payload)\n * 5. Returns the action result + optional RSC payload\n *\n * @param actionFn - The server action function to execute.\n * @param args - Arguments to pass to the action.\n * @param config - Handler configuration (cache handler, renderer).\n */\nexport async function executeAction(\n actionFn: (...args: unknown[]) => Promise<unknown>,\n args: unknown[],\n config: ActionHandlerConfig = {},\n spanMeta?: { actionFile?: string; actionName?: string }\n): Promise<ActionHandlerResult> {\n const state: RevalidationState = { paths: [], tags: [] };\n let actionResult: unknown;\n let redirectTo: string | undefined;\n let redirectStatus: number | undefined;\n\n // Run the action inside ALS scope so revalidatePath/Tag resolve to this\n // request's state object — concurrent requests each get their own scope.\n await revalidationAls.run(state, async () => {\n try {\n actionResult = await withSpan(\n 'timber.action',\n {\n ...(spanMeta?.actionFile ? { 'timber.action_file': spanMeta.actionFile } : {}),\n ...(spanMeta?.actionName ? { 'timber.action_name': spanMeta.actionName } : {}),\n },\n () => actionFn(...args)\n );\n } catch (error) {\n if (error instanceof RedirectSignal) {\n redirectTo = error.location;\n redirectStatus = error.status;\n } else {\n throw error;\n }\n }\n });\n\n // Process tag invalidation\n if (state.tags.length > 0 && config.cacheHandler) {\n await Promise.all(state.tags.map((tag) => config.cacheHandler!.invalidate({ tag })));\n }\n\n // Process path revalidation — build element tree (not yet serialized)\n let revalidation: RevalidationResult | undefined;\n if (state.paths.length > 0 && config.renderer) {\n // For now, render the first revalidated path.\n // Multiple paths could be supported via multipart streaming in the future.\n const path = state.paths[0];\n try {\n revalidation = await config.renderer(path);\n } catch (renderError) {\n if (renderError instanceof RedirectSignal) {\n // Revalidation triggered a redirect (e.g., session expired)\n redirectTo = renderError.location;\n redirectStatus = renderError.status;\n } else {\n // Log but don't fail the action — revalidation is best-effort\n console.error('[timber] revalidatePath render failed:', renderError);\n }\n }\n }\n\n return {\n actionResult,\n revalidation,\n ...(redirectTo ? { redirectTo, redirectStatus } : {}),\n };\n}\n\n/**\n * Build an HTTP Response for a no-JS form submission.\n * Standard POST → 302 redirect pattern.\n *\n * @param redirectPath - Where to redirect after the action executes.\n */\nexport function buildNoJsResponse(redirectPath: string, status: number = 302): Response {\n return new Response(null, {\n status,\n headers: { Location: redirectPath },\n });\n}\n\n/**\n * Detect whether the incoming request is an RSC action request (with JS)\n * or a plain HTML form POST (no JS).\n *\n * RSC action requests use Accept: text/x-component or Content-Type: text/x-component.\n */\nexport function isRscActionRequest(req: Request): boolean {\n const accept = req.headers.get('Accept') ?? '';\n const contentType = req.headers.get('Content-Type') ?? '';\n return accept.includes('text/x-component') || contentType.includes('text/x-component');\n}\n","/**\n * Route handler for route.ts API endpoints.\n *\n * route.ts files export named HTTP method handlers (GET, POST, etc.).\n * They share the same pipeline (proxy → match → middleware → access → handler)\n * but don't render React trees.\n *\n * See design/07-routing.md §\"route.ts — API Endpoints\"\n */\n\nimport type { RouteContext } from './types.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\n/** HTTP methods that route.ts can export as named handlers. */\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';\n\n/** A single route handler function — one-arg signature. */\nexport type RouteHandler = (ctx: RouteContext) => Response | Promise<Response>;\n\n/** A route.ts module — named exports for each supported HTTP method. */\nexport type RouteModule = {\n [K in HttpMethod]?: RouteHandler;\n};\n\n/** All recognized HTTP method export names. */\nconst HTTP_METHODS: HttpMethod[] = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];\n\n// ─── Allowed Methods ─────────────────────────────────────────────────────\n\n/**\n * Resolve the full list of allowed methods for a route module.\n *\n * Includes:\n * - All explicitly exported methods\n * - HEAD (implicit when GET is exported)\n * - OPTIONS (always implicit)\n */\nexport function resolveAllowedMethods(mod: RouteModule): HttpMethod[] {\n const methods: HttpMethod[] = [];\n\n for (const method of HTTP_METHODS) {\n if (method === 'HEAD' || method === 'OPTIONS') continue;\n if (mod[method]) {\n methods.push(method);\n }\n }\n\n // HEAD is implicit when GET is exported\n if (mod.GET && !mod.HEAD) {\n methods.push('HEAD');\n } else if (mod.HEAD) {\n methods.push('HEAD');\n }\n\n // OPTIONS is always implicit\n if (!mod.OPTIONS) {\n methods.push('OPTIONS');\n } else {\n methods.push('OPTIONS');\n }\n\n return methods;\n}\n\n// ─── Route Request Handler ───────────────────────────────────────────────\n\n/**\n * Handle an incoming request against a route.ts module.\n *\n * Dispatches to the named method handler, auto-generates 405/OPTIONS,\n * and merges response headers from ctx.headers.\n */\nexport async function handleRouteRequest(mod: RouteModule, ctx: RouteContext): Promise<Response> {\n const method = ctx.req.method.toUpperCase() as HttpMethod;\n const allowed = resolveAllowedMethods(mod);\n const allowHeader = allowed.join(', ');\n\n // Auto OPTIONS — 204 with Allow header\n if (method === 'OPTIONS') {\n if (mod.OPTIONS) {\n return runHandler(mod.OPTIONS, ctx);\n }\n return new Response(null, {\n status: 204,\n headers: { Allow: allowHeader },\n });\n }\n\n // HEAD fallback — run GET, strip body\n if (method === 'HEAD') {\n if (mod.HEAD) {\n return runHandler(mod.HEAD, ctx);\n }\n if (mod.GET) {\n const res = await runHandler(mod.GET, ctx);\n // Return headers + status but no body\n return new Response(null, {\n status: res.status,\n headers: res.headers,\n });\n }\n }\n\n // Dispatch to the named handler\n const handler = mod[method];\n if (!handler) {\n return new Response(null, {\n status: 405,\n headers: { Allow: allowHeader },\n });\n }\n\n return runHandler(handler, ctx);\n}\n\n/**\n * Run a handler, merge ctx.headers into the response, and catch errors.\n */\nasync function runHandler(handler: RouteHandler, ctx: RouteContext): Promise<Response> {\n try {\n const res = await handler(ctx);\n return mergeResponseHeaders(res, ctx.headers);\n } catch (error) {\n console.error('[timber] Uncaught error in route.ts handler:', error);\n return new Response(null, { status: 500 });\n }\n}\n\n/**\n * Merge response headers from ctx.headers into the handler's response.\n * ctx.headers (set by middleware or the handler) are applied to the final response.\n * Handler-set headers take precedence over ctx.headers.\n */\nfunction mergeResponseHeaders(res: Response, ctxHeaders: Headers): Response {\n // If no ctx headers to merge, return as-is\n let hasCtxHeaders = false;\n ctxHeaders.forEach(() => {\n hasCtxHeaders = true;\n });\n if (!hasCtxHeaders) return res;\n\n // Merge: ctx.headers first, then handler response headers override\n const merged = new Headers();\n ctxHeaders.forEach((value, key) => merged.set(key, value));\n res.headers.forEach((value, key) => merged.set(key, value));\n\n return new Response(res.body, {\n status: res.status,\n statusText: res.statusText,\n headers: merged,\n });\n}\n"],"mappings":"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"}
|