@timber-js/app 0.2.0-alpha.200 → 0.2.0-alpha.202

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.
Files changed (143) hide show
  1. package/bin/timber.mjs +15 -1
  2. package/dist/_chunks/{actions-d1hCqnU3.js → actions-HUdJADAD.js} +3 -3
  3. package/dist/_chunks/{actions-d1hCqnU3.js.map → actions-HUdJADAD.js.map} +1 -1
  4. package/dist/_chunks/{build-manifest-DTmSGLRz.js → build-manifest-DWppEdLB.js} +2 -51
  5. package/dist/_chunks/build-manifest-DWppEdLB.js.map +1 -0
  6. package/dist/_chunks/{cache-api-ByagcC-J.js → cache-api-CAPbZTga.js} +2 -2
  7. package/dist/_chunks/{cache-api-ByagcC-J.js.map → cache-api-CAPbZTga.js.map} +1 -1
  8. package/dist/_chunks/{chains-Bpb0W4ax.js → chains-CBNA0Ozj.js} +3 -3
  9. package/dist/_chunks/{chains-Bpb0W4ax.js.map → chains-CBNA0Ozj.js.map} +1 -1
  10. package/dist/_chunks/{cli-check-D6VolrDV.js → cli-check-BzMGuIH6.js} +3 -3
  11. package/dist/_chunks/{cli-check-D6VolrDV.js.map → cli-check-BzMGuIH6.js.map} +1 -1
  12. package/dist/_chunks/{cli-schema-sync-D6rO-VcS.js → cli-schema-sync-DnXqcIIj.js} +2 -2
  13. package/dist/_chunks/{cli-schema-sync-D6rO-VcS.js.map → cli-schema-sync-DnXqcIIj.js.map} +1 -1
  14. package/dist/_chunks/{cloudflare-BFb__LYG.js → cloudflare-DxX1SU0g.js} +3 -3
  15. package/dist/_chunks/{cloudflare-BFb__LYG.js.map → cloudflare-DxX1SU0g.js.map} +1 -1
  16. package/dist/_chunks/{convention-lint-fRkwVwEH.js → convention-lint-DHOFvX5s.js} +5 -95
  17. package/dist/_chunks/convention-lint-DHOFvX5s.js.map +1 -0
  18. package/dist/_chunks/csp-nonce-hOGniaG4.js +227 -0
  19. package/dist/_chunks/csp-nonce-hOGniaG4.js.map +1 -0
  20. package/dist/_chunks/dev-server-DioP7tkQ.js +2288 -0
  21. package/dist/_chunks/dev-server-DioP7tkQ.js.map +1 -0
  22. package/dist/_chunks/{error-boundary-BfPHZjm0.js → error-boundary-BQKxl6EX.js} +11 -17
  23. package/dist/_chunks/{error-boundary-BfPHZjm0.js.map → error-boundary-BQKxl6EX.js.map} +1 -1
  24. package/dist/_chunks/{graph-cache-CP4GEmf9.js → graph-cache-Cv3njEH8.js} +2 -2
  25. package/dist/_chunks/{graph-cache-CP4GEmf9.js.map → graph-cache-Cv3njEH8.js.map} +1 -1
  26. package/dist/_chunks/{live-graph-D_2D32Ad.js → live-graph-VjHFF5EV.js} +3 -3
  27. package/dist/_chunks/{live-graph-D_2D32Ad.js.map → live-graph-VjHFF5EV.js.map} +1 -1
  28. package/dist/_chunks/{logger-uLBuGKDI.js → logger-DqJ2VoAY.js} +450 -458
  29. package/dist/_chunks/logger-DqJ2VoAY.js.map +1 -0
  30. package/dist/_chunks/{segment-keys-lqtdookO.js → metadata-routes-DSDjM_hJ.js} +2 -61
  31. package/dist/_chunks/metadata-routes-DSDjM_hJ.js.map +1 -0
  32. package/dist/_chunks/{poison-scan-Bm9Yyqk9.js → poison-scan-lEbz4pQE.js} +2 -2
  33. package/dist/_chunks/{poison-scan-Bm9Yyqk9.js.map → poison-scan-lEbz4pQE.js.map} +1 -1
  34. package/dist/_chunks/{scanner-AiazgH_f.js → scanner-B_tnqFcF.js} +37 -137
  35. package/dist/_chunks/scanner-B_tnqFcF.js.map +1 -0
  36. package/dist/_chunks/segment-keys-D5hu1hz4.js +62 -0
  37. package/dist/_chunks/segment-keys-D5hu1hz4.js.map +1 -0
  38. package/dist/_chunks/tree-match-CdbvYTBz.js +122 -0
  39. package/dist/_chunks/tree-match-CdbvYTBz.js.map +1 -0
  40. package/dist/_chunks/{walkers-B6XUtmqK.js → walkers-Cm3PC5JT.js} +2 -2
  41. package/dist/_chunks/{walkers-B6XUtmqK.js.map → walkers-Cm3PC5JT.js.map} +1 -1
  42. package/dist/adapters/cloudflare-dev.js +1 -1
  43. package/dist/adapters/cloudflare-kv-cache.js +1 -1
  44. package/dist/adapters/cloudflare.js +1 -1
  45. package/dist/analyze/crawl-entry.js +2 -2
  46. package/dist/analyze/graph-command.js +3 -3
  47. package/dist/cache/index.js +1 -1
  48. package/dist/cli.d.ts +7 -2
  49. package/dist/cli.d.ts.map +1 -1
  50. package/dist/cli.js +44 -7
  51. package/dist/cli.js.map +1 -1
  52. package/dist/client/browser-dev.d.ts +5 -1
  53. package/dist/client/browser-dev.d.ts.map +1 -1
  54. package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
  55. package/dist/client/browser-entry/action-queue.d.ts +20 -1
  56. package/dist/client/browser-entry/action-queue.d.ts.map +1 -1
  57. package/dist/client/browser-entry/rsc-stream.d.ts.map +1 -1
  58. package/dist/client/error-boundary.d.ts +3 -15
  59. package/dist/client/error-boundary.d.ts.map +1 -1
  60. package/dist/client/error-boundary.js +1 -1
  61. package/dist/client/error-reconstituter.d.ts +4 -4
  62. package/dist/client/error-reconstituter.d.ts.map +1 -1
  63. package/dist/client/internal.js +1 -1
  64. package/dist/dev-tools/debug-channel.d.ts +55 -0
  65. package/dist/dev-tools/debug-channel.d.ts.map +1 -0
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +302 -2245
  68. package/dist/index.js.map +1 -1
  69. package/dist/plugins/dev-server.d.ts +7 -0
  70. package/dist/plugins/dev-server.d.ts.map +1 -1
  71. package/dist/plugins/mdx.d.ts.map +1 -1
  72. package/dist/routing/convention-lint.d.ts.map +1 -1
  73. package/dist/routing/index.js +2 -2
  74. package/dist/routing/scanner.d.ts +4 -4
  75. package/dist/routing/scanner.d.ts.map +1 -1
  76. package/dist/server/access-gate.d.ts +2 -2
  77. package/dist/server/deny-boundary.d.ts +3 -5
  78. package/dist/server/deny-boundary.d.ts.map +1 -1
  79. package/dist/server/deny-renderer.d.ts +0 -1
  80. package/dist/server/deny-renderer.d.ts.map +1 -1
  81. package/dist/server/error-boundary-wrapper.d.ts +5 -12
  82. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  83. package/dist/server/index.js +2 -2
  84. package/dist/server/internal.js +20 -195
  85. package/dist/server/internal.js.map +1 -1
  86. package/dist/server/pipeline.d.ts +8 -0
  87. package/dist/server/pipeline.d.ts.map +1 -1
  88. package/dist/server/primitives.d.ts +15 -13
  89. package/dist/server/primitives.d.ts.map +1 -1
  90. package/dist/server/rsc-entry/error-renderer.d.ts +3 -7
  91. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  92. package/dist/server/rsc-entry/helpers.d.ts +6 -27
  93. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  94. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  95. package/dist/server/rsc-entry/render-route.d.ts +1 -0
  96. package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
  97. package/dist/server/rsc-entry/rsc-stream.d.ts +4 -1
  98. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  99. package/dist/server/rsc-entry/ssr-renderer.d.ts +1 -0
  100. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  101. package/dist/server/slot-resolver.d.ts.map +1 -1
  102. package/dist/server/slot-subtree-contain.d.ts +1 -1
  103. package/dist/server/slot-subtree-contain.d.ts.map +1 -1
  104. package/docs/api/30-api-server.mdx +5 -5
  105. package/docs/api/34-api-config.mdx +2 -2
  106. package/docs/api/36-cli.mdx +1 -1
  107. package/docs/learn/12-error-handling.mdx +7 -8
  108. package/package.json +2 -3
  109. package/src/cli.ts +62 -8
  110. package/src/client/browser-dev.ts +35 -3
  111. package/src/client/browser-entry/action-dispatch.ts +12 -0
  112. package/src/client/browser-entry/action-queue.ts +70 -8
  113. package/src/client/browser-entry/rsc-stream.ts +78 -22
  114. package/src/client/error-boundary.tsx +13 -39
  115. package/src/client/error-reconstituter.tsx +5 -5
  116. package/src/dev-tools/debug-channel.ts +151 -0
  117. package/src/index.ts +8 -12
  118. package/src/plugins/dev-server.ts +58 -7
  119. package/src/plugins/mdx.ts +2 -1
  120. package/src/routing/convention-lint.ts +4 -111
  121. package/src/routing/scanner.ts +33 -22
  122. package/src/server/access-gate.tsx +3 -3
  123. package/src/server/deny-boundary.ts +5 -23
  124. package/src/server/deny-renderer.ts +4 -7
  125. package/src/server/error-boundary-wrapper.ts +11 -35
  126. package/src/server/pipeline.ts +9 -0
  127. package/src/server/primitives.ts +20 -26
  128. package/src/server/rsc-entry/error-renderer.ts +15 -43
  129. package/src/server/rsc-entry/helpers.ts +10 -67
  130. package/src/server/rsc-entry/index.ts +1 -0
  131. package/src/server/rsc-entry/render-route.ts +12 -1
  132. package/src/server/rsc-entry/rsc-stream.ts +27 -24
  133. package/src/server/rsc-entry/ssr-renderer.ts +7 -1
  134. package/src/server/slot-resolver.ts +29 -7
  135. package/src/server/slot-subtree-contain.ts +13 -4
  136. package/dist/_chunks/build-manifest-DTmSGLRz.js.map +0 -1
  137. package/dist/_chunks/convention-lint-fRkwVwEH.js.map +0 -1
  138. package/dist/_chunks/logger-uLBuGKDI.js.map +0 -1
  139. package/dist/_chunks/scanner-AiazgH_f.js.map +0 -1
  140. package/dist/_chunks/segment-keys-lqtdookO.js.map +0 -1
  141. package/dist/server/utils/mdx-file.d.ts +0 -17
  142. package/dist/server/utils/mdx-file.d.ts.map +0 -1
  143. package/src/server/utils/mdx-file.ts +0 -22
@@ -1 +0,0 @@
1
- {"version":3,"file":"convention-lint-fRkwVwEH.js","names":[],"sources":["../../src/server/utils/mdx-file.ts","../../src/routing/segment-codegen.ts","../../src/routing/codegen-write.ts","../../src/routing/export-detect.ts","../../src/routing/convention-lint.ts"],"sourcesContent":["/**\n * MDX file-path detection shared by error/deny rendering paths.\n *\n * MDX status and error files are server components — they cannot be passed\n * as function props across the RSC→client boundary (e.g. to\n * TimberErrorBoundary or ErrorReconstituter) and must be pre-rendered as\n * elements with plain props instead.\n *\n * See design/10-error-handling.md §\"Status-Code File Variants\".\n */\n\n/** MDX/markdown extensions (dotted) — the canonical list, also used by plugins/mdx.ts. */\nexport const MDX_EXTENSIONS = new Set(['.mdx', '.md']);\n\n/**\n * Check if a file path ends with an MDX/markdown extension.\n */\nexport function isMdxFilePath(filePath: string): boolean {\n const dotIndex = filePath.lastIndexOf('.');\n if (dotIndex === -1) return false;\n return MDX_EXTENSIONS.has(filePath.slice(dotIndex));\n}\n","/**\n * $segment type generation and directory-to-route mapping.\n *\n * Generates per-route $segment.d.ts files in .timber/types/ that mirror\n * the app/ directory structure. TypeScript's rootDirs feature makes these\n * importable as './$segment' from route files.\n *\n * Design doc: design/41-global-params.md §The $segment Module\n */\n\nimport { writeFile, mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { RouteTree, SegmentNode } from './types.ts';\n\n/** Info carried per directory for type generation. */\nexport interface SegmentInfo {\n /** The URL route pattern (groups and slots stripped). e.g. '/[artistSlug]/[year]' */\n route: string;\n /** The full tree path including groups and slots — uniquely identifies a segment.\n * e.g. '/(browse)/[artistSlug]/@shows/[year]' */\n segmentPath: string;\n}\n\n/**\n * Build a map of absolute filesystem directory → segment info.\n * Used by resolveId to map a $segment import's importer to the correct route,\n * and by writeSegmentTypes to generate per-route $segment.d.ts files.\n *\n * Walks every segment (not just leaves) so layouts and middleware\n * at any depth can import $segment.\n */\nexport function buildDirToRouteMap(tree: RouteTree, appDir: string): Map<string, SegmentInfo> {\n const map = new Map<string, SegmentInfo>();\n\n function walk(node: SegmentNode, fsPath: string, treePath: string): void {\n map.set(fsPath, {\n route: node.urlPath || '/',\n segmentPath: treePath || '/',\n });\n\n for (const child of node.children) {\n const childDir = join(fsPath, child.segmentName);\n const childTreePath = `${treePath}/${child.segmentName}`;\n walk(child, childDir, childTreePath);\n }\n for (const [slotName, slotNode] of Object.entries(node.slots)) {\n const slotDir = join(fsPath, `@${slotName}`);\n const slotTreePath = `${treePath}/@${slotName}`;\n walk(slotNode, slotDir, slotTreePath);\n }\n }\n\n walk(tree.root, appDir, '');\n return map;\n}\n\n/**\n * Generate the full $segment.d.ts content for a segment.\n */\nexport function generateSegmentDts(info: SegmentInfo): string {\n return [\n `export const ROUTE: '${info.route}';`,\n `export const SEGMENT_PATH: '${info.segmentPath}';`,\n '',\n ].join('\\n');\n}\n\n/**\n * Generate per-route $segment.d.ts files in .timber/types/ mirroring\n * the app/ directory structure. These files are importable via TypeScript's\n * rootDirs feature.\n */\nexport async function writeSegmentTypes(\n routeTree: RouteTree,\n appDir: string,\n root: string\n): Promise<void> {\n // Compute the relative prefix from project root to appDir.\n // e.g. if appDir is /project/src/app, relative is 'src/app'\n const relative = appDir.slice(root.length + 1);\n const typesBase = join(root, '.timber', 'types', relative);\n const map = buildDirToRouteMap(routeTree, appDir);\n\n const writes: Promise<void>[] = [];\n for (const [fsDir, info] of map) {\n // Mirror the app/ structure under .timber/types/\n const relDir = fsDir.slice(appDir.length); // e.g. '/[artistSlug]/[year]'\n const segmentDir = join(typesBase, relDir);\n const segmentFile = join(segmentDir, '$segment.d.ts');\n const content = generateSegmentDts(info);\n\n writes.push(\n mkdir(segmentDir, { recursive: true }).then(() => writeFile(segmentFile, content, 'utf-8'))\n );\n }\n\n await Promise.all(writes);\n}\n","/**\n * Route-map codegen writer.\n *\n * Materializes the generated route types on disk: `.timber/timber-routes.d.ts`,\n * `.timber/segment-types.d.ts` (via `writeSegmentTypes`), and the root\n * `timber-env.d.ts` that references them.\n *\n * Lives outside `plugins/routing.ts` because it has two callers with two\n * different failure policies: the dev/build plugin starts the write off the hot\n * path and logs failures, while `timber check` awaits it and treats a failure as\n * fatal — a type check run against absent or stale route types reports errors\n * about code that is fine. One implementation, two policies, so the bytes on\n * disk cannot drift between the two commands.\n *\n * Design docs: 09-typescript.md §\"Typed Routes\", 18-build-system.md\n */\n\nimport { writeFile, mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { generateRouteMap } from './codegen.ts';\nimport { writeSegmentTypes } from './segment-codegen.ts';\nimport type { RouteTree } from './types.ts';\n\n/**\n * Path (relative to the project root) of the generated route map declaration\n * file, and the name of the root file that `/// <reference>`s it.\n *\n * Exported because `timber check` has to answer whether the user's tsconfig\n * pulls these into the program — a question about these exact paths. It derives\n * them from here rather than restating them, so a rename cannot leave the check\n * looking for a file nothing writes.\n */\nexport const CODEGEN_OUTPUT = '.timber/timber-routes.d.ts';\n\n/** Root-level file that references the generated declarations. */\nexport const ENV_DTS_FILENAME = 'timber-env.d.ts';\n\n/**\n * Content of the timber-env.d.ts file written to the project root.\n *\n * Uses a triple-slash reference so TypeScript auto-discovers the generated\n * route map without the user needing to edit tsconfig.json — same pattern\n * as Next.js's next-env.d.ts.\n *\n * Also references `vite/client` so side-effect imports like\n * `import './globals.css'` type-check without ts(2882), and so\n * `import.meta.env`, `import.meta.hot`, `import.meta.glob`, asset\n * imports (`*.png`, `*.svg`, …), CSS modules, and `?url`/`?raw`/\n * `?inline` query variants all resolve without the user editing\n * tsconfig.json themselves. Next.js's next-env.d.ts plays the same\n * role via `next/types/global`.\n *\n * `vite` is a peer dep of `@timber-js/app`, so the reference always\n * resolves in a correctly-installed project.\n */\nconst TIMBER_ENV_DTS = [\n '// This file is auto-generated by timber.js. Do not edit.',\n '// It ensures TypeScript picks up the generated route types and',\n '// Vite-provided ambient types (CSS, assets, import.meta.env, HMR).',\n '/* eslint-disable @typescript-eslint/triple-slash-reference */',\n '/// <reference path=\".timber/timber-routes.d.ts\" />',\n '/// <reference types=\"vite/client\" />',\n '/// <reference types=\"react/canary\" />',\n '/// <reference types=\"react-dom/canary\" />',\n '',\n].join('\\n');\n\n/**\n * Codegen writes that have been started but have not yet landed on disk.\n *\n * `writeRouteCodegen` is deliberately unawaited on the dev/build hot path,\n * which leaves that caller no way to know the files exist yet. Anything that\n * tears down the project root — a build process exiting, a test removing its\n * temp root — then races the writes: `rmSync` empties a directory that a\n * pending `mkdir` immediately recreates, and the removal fails with ENOTEMPTY.\n *\n * Every started write registers here and deregisters when it settles, so\n * `flushCodegenWrites()` can await them instead of guessing at a delay.\n */\nconst pendingCodegenWrites = new Set<Promise<void>>();\n\n/**\n * Resolve once every codegen write started so far has landed on disk.\n *\n * Safe to call at any time and from anywhere — writes are tracked per process,\n * not per plugin instance, so a caller does not need a handle on the plugin\n * that started them. Never rejects: the tracked copy of each write has its\n * rejection swallowed, so a failing write cannot turn a teardown barrier into\n * an unhandled rejection. Callers that care about failure await the promise\n * `writeRouteCodegen` returned instead.\n */\nexport async function flushCodegenWrites(): Promise<void> {\n // Loop rather than a single Promise.all: a write started while we were\n // awaiting would otherwise be missed, and the set would still be non-empty\n // on return.\n //\n // Each round drops what it awaited rather than trusting the per-write\n // `finally` to have run, so the two removal paths are independent — a\n // deregistration that stopped firing would leak memory in dev, but it can\n // never turn this into a spin.\n while (pendingCodegenWrites.size > 0) {\n const inFlight = [...pendingCodegenWrites];\n await Promise.all(inFlight);\n for (const write of inFlight) pendingCodegenWrites.delete(write);\n }\n}\n\n/**\n * Write the generated route map, segment types, and the timber-env.d.ts\n * reference file for `tree`.\n *\n * Returns a promise that **rejects** if any write fails. The dev/build plugin\n * does not await it (types are a developer convenience there, not a runtime\n * dependency) and logs instead; `timber check` awaits it and fails the command.\n * Either way the write is registered with `flushCodegenWrites()`.\n */\nexport function writeRouteCodegen(tree: RouteTree, appDir: string, root: string): Promise<void> {\n const timberDir = join(root, '.timber');\n const content = generateRouteMap(tree, { appDir, outputDir: timberDir });\n const routesPath = join(root, CODEGEN_OUTPUT);\n const envPath = join(root, ENV_DTS_FILENAME);\n\n const done = mkdir(timberDir, { recursive: true })\n .then(() =>\n Promise.all([\n writeFile(routesPath, content, 'utf-8'),\n writeFile(envPath, TIMBER_ENV_DTS, 'utf-8'),\n writeSegmentTypes(tree, appDir, root),\n ])\n )\n .then(() => undefined);\n\n // The tracked copy swallows rejection so `flushCodegenWrites` — a teardown\n // barrier — never rejects and never leaves an unhandled rejection behind.\n // The returned promise keeps its rejection for callers that want it.\n const tracked = done.catch(() => undefined);\n pendingCodegenWrites.add(tracked);\n void tracked.finally(() => pendingCodegenWrites.delete(tracked));\n\n return done;\n}\n","/**\n * AST-based export detection for route files.\n *\n * Uses Vite's `parseAst` (backed by oxc) to precisely detect named and\n * default exports, replacing the regex-based approach that was fragile\n * with TypeScript syntax (type exports, `as` aliases, comments, etc.).\n */\n\nimport { existsSync } from 'node:fs';\nimport { parseAst } from 'vite';\nimport { readFileCached } from './file-cache.ts';\n\ninterface AstNode {\n type: string;\n exportKind?: string;\n declaration?: AstNode;\n specifiers?: Array<{\n exported?: { name: string };\n local?: { name: string };\n exportKind?: string;\n }>;\n id?: { name: string };\n declarations?: Array<{ id?: { name: string }; init?: AstNode }>;\n /** Set on re-exports: `export { X } from './y'`. Absent on local exports. */\n source?: { value?: string } | null;\n /** Set on runtime-transparent wrappers: `X as T`, `X satisfies T`, `(X)`, `X!`. */\n expression?: AstNode;\n /** Set on `Identifier` nodes. */\n name?: string;\n}\n\ninterface ProgramNode {\n body: AstNode[];\n}\n\nfunction tryParse(source: string): ProgramNode | null {\n try {\n return parseAst(source, { lang: 'tsx' }) as unknown as ProgramNode;\n } catch {\n return null;\n }\n}\n\n/**\n * Collect all named export identifiers from a parsed program.\n * Handles: `export function X`, `export const X`, `export { X }`,\n * `export { X } from '...'`, `export { Y as X }`.\n */\nfunction collectNamedExports(program: ProgramNode): Set<string> {\n const names = new Set<string>();\n for (const stmt of program.body) {\n if (stmt.type !== 'ExportNamedDeclaration') continue;\n // Skip `export type ...` declarations — they're erased at runtime\n if (stmt.exportKind === 'type') continue;\n\n if (stmt.declaration) {\n if (stmt.declaration.id?.name) {\n names.add(stmt.declaration.id.name);\n }\n if (stmt.declaration.declarations) {\n for (const decl of stmt.declaration.declarations) {\n if (decl.id?.name) names.add(decl.id.name);\n }\n }\n }\n\n if (stmt.specifiers) {\n for (const spec of stmt.specifiers) {\n // Skip `export { type X }` — per-specifier type exports\n if (spec.exportKind === 'type') continue;\n if (spec.exported?.name && spec.exported.name !== 'default') {\n names.add(spec.exported.name);\n }\n }\n }\n }\n return names;\n}\n\n/**\n * The specifier exporting a *runtime* default from this statement, if any.\n *\n * Type-only exports erase, so they are not default exports at runtime — and\n * TypeScript spells that two ways, on two different nodes: `export type { X as\n * default }` marks the **statement**, `export { type X as default }` marks the\n * **specifier**. Both have to be checked, or a type alias reads as a component:\n * a status file whose only default is a type has nothing for the error boundary\n * to receive (the renderer's `mod?.default` skips it), so treating it as one\n * would fail a build over a file that cannot break anything.\n *\n * One implementation because two callers must agree on what \"has a default\n * export\" means: `hasDefaultExport` decides whether to warn that a page has\n * none, and `fileDefaultExportIsLocalValue` decides whether to fail the build.\n */\nfunction defaultSpecifier(stmt: AstNode): NonNullable<AstNode['specifiers']>[number] | null {\n if (stmt.type !== 'ExportNamedDeclaration' || !stmt.specifiers) return null;\n if (stmt.exportKind === 'type') return null;\n for (const spec of stmt.specifiers) {\n if (spec.exportKind === 'type') continue;\n if (spec.exported?.name === 'default') return spec;\n }\n return null;\n}\n\n/**\n * Check whether a program has a default export.\n * Handles: `export default ...`, `export { X as default }`,\n * `export { default } from '...'`. Type-only defaults do not count —\n * see `defaultSpecifier`.\n */\nfunction hasDefaultExport(program: ProgramNode): boolean {\n for (const stmt of program.body) {\n if (stmt.type === 'ExportDefaultDeclaration') return true;\n if (defaultSpecifier(stmt)) return true;\n }\n return false;\n}\n\n/**\n * Check whether a program has any bare `export * from '...'` declarations.\n * Excludes `export * as X from '...'` (namespace re-exports) since those\n * create a namespace object, not individual top-level exports.\n */\nfunction hasStarExport(program: ProgramNode): boolean {\n return program.body.some(\n (stmt) =>\n stmt.type === 'ExportAllDeclaration' && !(stmt as any).exported && stmt.exportKind !== 'type'\n );\n}\n\n/**\n * Check if a file exports a specific named export.\n * Returns false if the file doesn't exist or can't be parsed.\n */\nexport function fileHasExport(filePath: string, exportName: string): boolean {\n if (!existsSync(filePath)) return false;\n try {\n const source = readFileCached(filePath);\n const program = tryParse(source);\n if (!program) return false;\n return collectNamedExports(program).has(exportName);\n } catch {\n return false;\n }\n}\n\n/**\n * Check if a file has any of the given named exports.\n * Returns the set of matching export names.\n */\nexport function fileHasAnyExport(filePath: string, exportNames: readonly string[]): Set<string> {\n const matches = new Set<string>();\n try {\n const source = readFileCached(filePath);\n const program = tryParse(source);\n if (!program) return matches;\n const exports = collectNamedExports(program);\n for (const name of exportNames) {\n if (exports.has(name)) matches.add(name);\n }\n } catch {\n // Graceful fallback: return empty set\n }\n return matches;\n}\n\n/**\n * Check if a file has a default export.\n * Returns false if the file doesn't exist or can't be parsed.\n */\nexport function fileHasDefaultExport(filePath: string): boolean {\n if (!existsSync(filePath)) return false;\n try {\n const source = readFileCached(filePath);\n const program = tryParse(source);\n if (!program) return false;\n return hasDefaultExport(program);\n } catch {\n return false;\n }\n}\n\n/** Node types whose value is unambiguously constructed by this module. */\nconst FUNCTION_LIKE = new Set([\n 'FunctionDeclaration',\n 'FunctionExpression',\n 'ArrowFunctionExpression',\n 'ClassDeclaration',\n 'ClassExpression',\n]);\n\n/**\n * Expression wrappers that erase at runtime and so cannot change where a value\n * was constructed: `X as T`, `X satisfies T`, `<T>X`, `X!`, `X<T>`, `(X)`.\n */\nconst TRANSPARENT_WRAPPERS = new Set([\n 'TSAsExpression',\n 'TSSatisfiesExpression',\n 'TSTypeAssertion',\n 'TSNonNullExpression',\n 'TSInstantiationExpression',\n 'ParenthesizedExpression',\n]);\n\n/**\n * Strip runtime-transparent wrappers so the node underneath can be classified.\n *\n * Without this, `(() => <h1 />) satisfies React.FC` reads as a\n * `TSSatisfiesExpression` rather than the arrow function it compiles to, and a\n * provably-local component is misread as one whose origin cannot be\n * determined — turning a build error into a warning.\n */\nfunction unwrapTransparent(node: AstNode): AstNode {\n let current = node;\n while (TRANSPARENT_WRAPPERS.has(current.type) && current.expression) {\n current = current.expression;\n }\n return current;\n}\n\n/**\n * Whether `name` is bound at module top level to a function or class written\n * in this file. An imported binding can never match: imports are\n * `ImportDeclaration` specifiers, not function/class/variable declarations,\n * and a module-level name cannot be both.\n */\nfunction bindsLocalFunction(program: ProgramNode, name: string): boolean {\n for (const stmt of program.body) {\n // `export function X() {}` binds X locally too — unwrap the export.\n const decl = stmt.type === 'ExportNamedDeclaration' ? stmt.declaration : stmt;\n if (!decl) continue;\n if (\n (decl.type === 'FunctionDeclaration' || decl.type === 'ClassDeclaration') &&\n decl.id?.name === name\n ) {\n return true;\n }\n if (decl.type === 'VariableDeclaration') {\n for (const d of decl.declarations ?? []) {\n if (d.id?.name === name && d.init && FUNCTION_LIKE.has(unwrapTransparent(d.init).type)) {\n return true;\n }\n }\n }\n }\n return false;\n}\n\n/**\n * Whether the default export is *provably* a value constructed by this module:\n * `export default function/class/() => {}`, or a name bound to a function or\n * class declared here — whether it leaves via `export default Name` or\n * `export { Name as default }`. Both spellings are the same fact about where\n * the value was built, so both must answer the same way.\n *\n * This is the sound half of \"is this a server component?\". A file with no\n * `'use client'` directive whose default export is built here is a server\n * component, full stop. Everything else — `export { default } from\n * './client-thing'`, `export default Imported`, `export default memo(X)`,\n * an unparseable file — returns false, because the exported value may have\n * been minted in a module that *does* carry the directive. Callers must not\n * infer \"server component\" from a missing directive alone.\n */\nexport function fileDefaultExportIsLocalValue(filePath: string): boolean {\n if (!existsSync(filePath)) return false;\n try {\n const source = readFileCached(filePath);\n const program = tryParse(source);\n if (!program) return false;\n for (const stmt of program.body) {\n if (stmt.type === 'ExportDefaultDeclaration') {\n const decl = stmt.declaration && unwrapTransparent(stmt.declaration);\n if (!decl) return false;\n if (FUNCTION_LIKE.has(decl.type)) return true;\n if (decl.type === 'Identifier' && decl.name) {\n return bindsLocalFunction(program, decl.name);\n }\n return false;\n }\n\n // `export { Name as default }` — the same default export by another\n // spelling. `hasDefaultExport` already counts it, so an analysis that\n // skipped it here would let the exact defect this powers slip through\n // as a warning.\n const spec = defaultSpecifier(stmt);\n if (spec) {\n // `export { X as default } from './client-thing'` re-exports another\n // module's value — the directive governing it lives over there.\n if (stmt.source) return false;\n return spec.local?.name ? bindsLocalFunction(program, spec.local.name) : false;\n }\n }\n return false;\n } catch {\n return false;\n }\n}\n\n/**\n * Check if a file has any `export * from '...'` declarations.\n * Returns false if the file doesn't exist or can't be parsed.\n */\nexport function fileHasStarExport(filePath: string): boolean {\n if (!existsSync(filePath)) return false;\n try {\n const source = readFileCached(filePath);\n const program = tryParse(source);\n if (!program) return false;\n return hasStarExport(program);\n } catch {\n return false;\n }\n}\n\n/**\n * Parsed value of `export const prerender` from a route file.\n *\n * - `true` / `false` — literal boolean\n * - `{ ttl?, tags? }` — ISR options object (prerender: true is implied)\n * - `undefined` — no `export const prerender` found\n *\n * See design/45-cache-lifetimes.md §\"Route-Level Static\".\n */\nexport type PrerenderExportValue = boolean | { ttl?: number; tags?: string[] };\n\nfunction extractLiteralValue(node: AstNode): unknown {\n if (!node) return undefined;\n const n = node as any;\n if (n.type === 'Literal') return n.value;\n if (n.type === 'UnaryExpression' && n.operator === '-' && n.argument?.type === 'Literal') {\n return -(n.argument.value as number);\n }\n if (n.type === 'ArrayExpression') {\n const elements = n.elements as AstNode[] | undefined;\n if (!elements) return undefined;\n const result: unknown[] = [];\n for (const el of elements) {\n const v = extractLiteralValue(el);\n if (v === undefined) return undefined;\n result.push(v);\n }\n return result;\n }\n return undefined;\n}\n\nfunction extractObjectLiteral(node: AstNode): Record<string, unknown> | undefined {\n if ((node as any).type !== 'ObjectExpression') return undefined;\n const props = (node as any).properties as AstNode[] | undefined;\n if (!props) return undefined;\n const result: Record<string, unknown> = {};\n for (const prop of props) {\n const p = prop as any;\n if (p.type === 'SpreadElement') return undefined;\n if (p.type !== 'Property' || p.computed) return undefined;\n const key =\n p.key?.type === 'Identifier'\n ? p.key.name\n : p.key?.type === 'Literal'\n ? String(p.key.value)\n : undefined;\n if (!key) return undefined;\n const value = extractLiteralValue(p.value);\n if (value === undefined) return undefined;\n result[key] = value;\n }\n return result;\n}\n\n/**\n * Extract the value of `export const prerender` from a file.\n * Returns undefined if the file doesn't export `prerender` or can't be parsed.\n *\n * Supports:\n * export const prerender = true;\n * export const prerender = false;\n * export const prerender = { ttl: 3600, tags: ['docs'] };\n */\nexport function getPrerenderExport(filePath: string): PrerenderExportValue | undefined {\n if (!existsSync(filePath)) return undefined;\n try {\n const source = readFileCached(filePath);\n const program = tryParse(source);\n if (!program) return undefined;\n\n for (const stmt of program.body) {\n if (stmt.type !== 'ExportNamedDeclaration') continue;\n if (stmt.exportKind === 'type') continue;\n if (!stmt.declaration) continue;\n const decl = stmt.declaration;\n if (decl.type !== 'VariableDeclaration') continue;\n const declarations = decl.declarations as\n | Array<{\n id?: { name?: string };\n init?: AstNode;\n }>\n | undefined;\n if (!declarations) continue;\n for (const d of declarations) {\n if (d.id?.name !== 'prerender' || !d.init) continue;\n const init = d.init as any;\n if (init.type === 'Literal' && typeof init.value === 'boolean') {\n return init.value;\n }\n if (init.type === 'ObjectExpression') {\n const obj = extractObjectLiteral(init);\n if (!obj) return undefined;\n const result: { ttl?: number; tags?: string[] } = {};\n if ('ttl' in obj && typeof obj.ttl === 'number') result.ttl = obj.ttl;\n if (\n 'tags' in obj &&\n Array.isArray(obj.tags) &&\n obj.tags.every((t: unknown) => typeof t === 'string')\n ) {\n result.tags = obj.tags as string[];\n }\n return result;\n }\n return undefined;\n }\n }\n return undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Check if a file starts with a specific directive (e.g. \"use client\").\n * Directives are string literal expression statements at the top of the file.\n */\nexport function fileHasDirective(filePath: string, directive: string): boolean {\n if (!existsSync(filePath)) return false;\n try {\n const source = readFileCached(filePath);\n const program = tryParse(source);\n if (!program) return false;\n for (const stmt of program.body) {\n if (stmt.type !== 'ExpressionStatement') break;\n const expr = (stmt as any).expression;\n if (expr?.type !== 'Literal' || typeof expr.value !== 'string') break;\n if (expr.value === directive) return true;\n }\n return false;\n } catch {\n return false;\n }\n}\n","/**\n * Convention linter — validates common misconfigurations in the route tree.\n *\n * Runs at scan time (build and dev startup). Each check produces a warning\n * with the file path, what's wrong, and what to do about it.\n *\n * Most checks are warnings — they don't block the build. The goal is to catch\n * issues that would otherwise produce cryptic runtime behavior (silent 404s,\n * empty pages, confusing React errors).\n *\n * A check emits `level: 'error'` only when the misconfiguration is *proven*\n * (never inferred from an absence the analysis cannot see through) and fatal\n * to rendering. `plugins/routing.ts` turns those into a thrown build error,\n * so a false positive there refuses a working app — see\n * `classifyClientBoundaryFile` for how that bar is met.\n *\n * Design doc: 07-routing.md, 10-error-handling.md\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport type { RouteFile, RouteTree, SegmentNode } from './types.ts';\nimport { swallow } from '../server/logger.ts';\nimport { isMdxFilePath } from '../server/utils/mdx-file.ts';\nimport {\n fileDefaultExportIsLocalValue,\n fileHasAnyExport,\n fileHasDefaultExport,\n fileHasDirective,\n fileHasStarExport,\n} from './export-detect.ts';\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\nexport interface ConventionWarning {\n /** Warning ID for deduplication and filtering. */\n id: string;\n /** Human-readable single-line summary. */\n summary: string;\n /** Multi-line details with file path and fix suggestion. */\n details: string;\n /** Severity: 'warn' for potential issues, 'error' for definite misconfigurations. */\n level: 'warn' | 'error';\n}\n\n// ─── Lint Rules ─────────────────────────────────────────────────────────────\n\n/**\n * Run all convention lint checks on a route tree.\n *\n * Returns an array of warnings. Empty array means everything looks good.\n */\nexport function lintConventions(tree: RouteTree, appDir: string): ConventionWarning[] {\n const warnings: ConventionWarning[] = [];\n\n // Check 1: app/ directory exists (caller should check before scanning,\n // but we validate the root has at least one routable file)\n checkEmptyApp(tree.root, appDir, warnings);\n\n // Check 2: route.ts without recognized HTTP method exports\n checkRouteExports(tree.root, warnings);\n\n // Check 3: Segments with layout but no page/route anywhere in subtree\n // (not a misconfiguration per se — layouts without pages are valid for\n // route groups. Skip this check.)\n\n // Check 4: Root segment has no layout.tsx (no HTML shell)\n checkRootLayout(tree.root, warnings);\n\n // Check 5: page.tsx / layout.tsx without default export\n checkDefaultExports(tree.root, warnings);\n\n // Check 6: Client layouts cannot suppress server children before Flight\n // serializes them. They remain valid UI boundaries, but not authorization\n // or data-hiding boundaries.\n checkClientLayouts(tree.root, warnings);\n\n // Check 7: error.tsx / 404.tsx / 4xx.tsx / … without 'use client' directive\n checkClientBoundaryFiles(tree.root, warnings);\n if (tree.globalError) {\n checkFileUseClientDirective(tree.globalError, 'global-error', warnings);\n }\n\n // Check 8: Wildcard MDX status files (4xx.mdx, 5xx.mdx, error.mdx) that\n // reference `props` — props resolve on deny and throw paths but are empty\n // on the boundary path. See design/10-error-handling.md §\"MDX Props by Path\".\n checkWildcardMdxProps(tree.root, warnings);\n\n return warnings;\n}\n\nfunction checkClientLayouts(node: SegmentNode, warnings: ConventionWarning[]): void {\n if (node.layout && isScriptExtension(node.layout.extension)) {\n try {\n const isClient = fileHasDirective(node.layout.filePath, 'use client');\n const isProvablyServer = fileDefaultExportIsLocalValue(node.layout.filePath);\n if (isClient || !isProvablyServer) {\n const certainty = isClient\n ? `This layout is a client component.`\n : `Timber cannot prove this layout is a server component because its default export comes from another module or an opaque expression.`;\n warnings.push({\n id: `CLIENT_LAYOUT_NOT_AUTH_BOUNDARY:${node.layout.filePath}`,\n summary: isClient\n ? `Client layout cannot securely suppress server children: ${node.layout.filePath}`\n : `Layout may resolve to a client component and cannot be trusted as an auth boundary: ${node.layout.filePath}`,\n details:\n ` File: ${node.layout.filePath}\\n\\n` +\n ` ${certainty}\\n` +\n ` React Flight resolves server content passed to a client layout before the layout runs in the browser.\\n` +\n ` Conditionally omitting children here does not prevent descendant server components from executing or being serialized.\\n\\n` +\n ` To fix: Put authorization in access.ts or a provably server layout with a locally defined default component, and keep client components inside that boundary.\\n`,\n level: 'warn',\n });\n }\n } catch (err) {\n swallow(err, `convention-lint: unreadable layout file ${node.layout.filePath}`);\n }\n }\n for (const child of node.children) checkClientLayouts(child, warnings);\n for (const slot of Object.values(node.slots)) checkClientLayouts(slot, warnings);\n}\n\n// ─── Check: Empty App ───────────────────────────────────────────────────────\n\n/**\n * Warn when the app/ directory has no routable files at all.\n *\n * This catches the \"I created app/ but didn't add any pages\" case where\n * every request silently 404s with no guidance.\n */\nfunction checkEmptyApp(root: SegmentNode, appDir: string, warnings: ConventionWarning[]): void {\n if (hasAnyRoutable(root)) return;\n\n warnings.push({\n id: 'EMPTY_APP',\n summary: 'No pages or route handlers found in app/',\n details:\n ` Directory: ${appDir}\\n\\n` +\n ' Your app/ directory has no page.tsx or route.ts files.\\n' +\n ' Every request will return 404.\\n\\n' +\n ' To fix: Create app/page.tsx with a default export:\\n\\n' +\n ' export default function Home() {\\n return <h1>Hello</h1>;\\n }\\n',\n level: 'warn',\n });\n}\n\n/**\n * Check if a segment tree has any routable files (page or route) anywhere.\n */\nfunction hasAnyRoutable(node: SegmentNode): boolean {\n if (node.page || node.route) return true;\n for (const child of node.children) {\n if (hasAnyRoutable(child)) return true;\n }\n for (const slot of Object.values(node.slots)) {\n if (hasAnyRoutable(slot)) return true;\n }\n return false;\n}\n\n/**\n * Check if a segment tree has any page files (not just route handlers).\n * Used by checkRootLayout to avoid false positives for API-only apps (TIM-794).\n * API-only apps (just route.ts handlers) don't need a root layout.\n */\nfunction hasAnyPage(node: SegmentNode): boolean {\n if (node.page) return true;\n for (const child of node.children) {\n if (hasAnyPage(child)) return true;\n }\n for (const slot of Object.values(node.slots)) {\n if (hasAnyPage(slot)) return true;\n }\n return false;\n}\n\n// ─── Check: Route Exports ───────────────────────────────────────────────────\n\n/** HTTP methods that route.ts can export. */\nconst HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'] as const;\n\n/**\n * Warn when a route.ts file doesn't appear to export any recognized HTTP methods.\n *\n * Uses AST-based detection via parseAst for precise matching.\n * Falls back silently when the file can't be parsed (syntax errors produce\n * no spurious warning — the TS compiler will catch those).\n */\nfunction checkRouteExports(node: SegmentNode, warnings: ConventionWarning[]): void {\n if (node.route) {\n const filePath = node.route.filePath;\n try {\n const methods = fileHasAnyExport(filePath, HTTP_METHODS);\n if (methods.size === 0 && !fileHasStarExport(filePath)) {\n const hasDefault = fileHasDefaultExport(filePath);\n const hint = hasDefault\n ? ' It looks like you have a default export. route.ts uses named exports\\n' +\n ' for HTTP methods (GET, POST, etc.), not a default export.\\n'\n : '';\n\n warnings.push({\n id: 'ROUTE_NO_METHODS',\n summary: `route.ts has no HTTP method exports: ${filePath}`,\n details:\n ` File: ${filePath}\\n\\n` +\n ' route.ts files must export named HTTP method handlers.\\n' +\n ' Without them, the route matches but returns 405 Method Not Allowed.\\n\\n' +\n hint +\n ' Example:\\n\\n' +\n ' export function GET(ctx: RouteContext) {\\n' +\n \" return Response.json({ hello: 'world' });\\n\" +\n ' }\\n',\n level: 'warn',\n });\n }\n } catch (err) {\n swallow(err, `convention-lint: unreadable route.ts ${filePath}`);\n }\n }\n\n // Recurse\n for (const child of node.children) {\n checkRouteExports(child, warnings);\n }\n for (const slot of Object.values(node.slots)) {\n checkRouteExports(slot, warnings);\n }\n}\n\n// ─── Check: Root Layout ─────────────────────────────────────────────────────\n\n/**\n * Warn when the root segment has no layout.tsx.\n *\n * Without a root layout, there's no HTML shell (<html>, <body>).\n * The page will render but may have hydration issues or no proper document structure.\n */\nfunction checkRootLayout(root: SegmentNode, warnings: ConventionWarning[]): void {\n if (root.layout) return;\n\n // Only warn if there are pages — API-only apps (just route.ts handlers)\n // don't need a root layout (TIM-794).\n if (!hasAnyPage(root)) return;\n\n warnings.push({\n id: 'NO_ROOT_LAYOUT',\n summary: 'No root layout.tsx found',\n details:\n ' Your app has pages but no root layout.\\n' +\n ' Without app/layout.tsx, pages have no <html> or <body> wrapper.\\n\\n' +\n ' To fix: Create app/layout.tsx:\\n\\n' +\n ' export default function RootLayout({ children }: { children: React.ReactNode }) {\\n' +\n ' return (\\n' +\n ' <html lang=\"en\">\\n' +\n ' <body>{children}</body>\\n' +\n ' </html>\\n' +\n ' );\\n' +\n ' }\\n',\n level: 'warn',\n });\n}\n\n// ─── Check: Default Exports ───────────────────────────────────────────\n\n/**\n * Warn when page.tsx or layout.tsx files don't have a default export.\n *\n * Without a default export:\n * - page.tsx: the page renders nothing (empty content)\n * - layout.tsx: the layout module loads but has no component to render\n *\n * Uses AST-based detection via parseAst for precise matching. Falls back\n * silently when the file can't be parsed (no spurious warning on syntax errors).\n */\nfunction checkDefaultExports(node: SegmentNode, warnings: ConventionWarning[]): void {\n // Check page files (tsx/ts/jsx/js only — not mdx, which is default-exported by the mdx compiler)\n if (node.page && isScriptExtension(node.page.extension)) {\n checkFileDefaultExport(node.page.filePath, 'page', warnings);\n }\n\n // Check layout files\n if (node.layout && isScriptExtension(node.layout.extension)) {\n checkFileDefaultExport(node.layout.filePath, 'layout', warnings);\n }\n\n // Recurse\n for (const child of node.children) {\n checkDefaultExports(child, warnings);\n }\n for (const slot of Object.values(node.slots)) {\n checkDefaultExports(slot, warnings);\n }\n}\n\nfunction isScriptExtension(ext: string): boolean {\n return ext === 'tsx' || ext === 'ts' || ext === 'jsx' || ext === 'js';\n}\n\nfunction checkFileDefaultExport(\n filePath: string,\n fileType: string,\n warnings: ConventionWarning[]\n): void {\n try {\n if (!fileHasDefaultExport(filePath)) {\n warnings.push({\n id: `NO_DEFAULT_EXPORT:${filePath}`,\n summary: `${fileType}.tsx has no default export: ${filePath}`,\n details:\n ` File: ${filePath}\\n\\n` +\n ` ${fileType}.tsx files must export a default React component.\\n` +\n ` Without a default export, the ${fileType === 'page' ? 'page renders nothing' : 'layout has no component to wrap children'}.\\n\\n` +\n ` To fix: Add a default export:\\n\\n` +\n ` export default function My${fileType === 'page' ? 'Page' : 'Layout'}(${fileType === 'layout' ? '{ children }' : ''}) {\\n` +\n ` return ${fileType === 'layout' ? '<div>{children}</div>' : '<h1>Hello</h1>'};\\n` +\n ` }\\n`,\n level: 'warn',\n });\n }\n } catch (err) {\n swallow(err, `convention-lint: unreadable ${fileType} file ${filePath}`);\n }\n}\n\n// ─── Check: Client Boundary Directive ───────────────────────────────────────\n\n/**\n * Check every file the renderer hands to `TimberErrorBoundary` as a component\n * prop: `error.tsx`, `global-error.tsx`, and the status-code family\n * (`404.tsx`, `4xx.tsx`, `503.tsx`, …).\n *\n * `TimberErrorBoundary` is a client component, so a server component passed as\n * `fallbackComponent` is a function crossing the RSC → client boundary. React\n * Flight refuses to serialize it, and because the boundary wraps the segment's\n * *children*, the failure hits every page under that segment — not just the\n * error path. The raw message (\"Functions cannot be passed directly to Client\n * Components\") names neither the file nor the fix (TIM-1329).\n *\n * MDX status files are exempt: they are server components by design and the\n * renderer pre-renders them into `fallbackElement` instead. See TIM-503 and\n * `server/error-boundary-wrapper.ts`.\n *\n * `.json` status files never reach React at all, and are not in `statusFiles`.\n */\nfunction checkClientBoundaryFiles(node: SegmentNode, warnings: ConventionWarning[]): void {\n if (node.error) {\n checkFileUseClientDirective(node.error, 'error', warnings);\n }\n\n for (const [key, file] of Object.entries(node.statusFiles ?? {})) {\n checkFileUseClientDirective(file, key, warnings);\n }\n\n for (const child of node.children) {\n checkClientBoundaryFiles(child, warnings);\n }\n for (const slot of Object.values(node.slots)) {\n checkClientBoundaryFiles(slot, warnings);\n }\n}\n\n/**\n * `'client'` — carries the directive.\n * `'server'` — proven server component: no directive, and the default export is\n * a function or class built by this very module, so no other file's directive\n * can be governing it.\n * `'unknown'` — no directive, but the exported value may come from elsewhere\n * (`export { default } from './client-thing'`), or the file could not be\n * read or parsed. Never escalated to a build error.\n */\ntype ClientBoundaryVerdict = 'client' | 'server' | 'unknown';\n\nfunction classifyClientBoundaryFile(filePath: string): ClientBoundaryVerdict {\n if (fileHasDirective(filePath, 'use client')) return 'client';\n if (fileDefaultExportIsLocalValue(filePath)) return 'server';\n return 'unknown';\n}\n\n/** Example component name for the fix snippet, keyed by convention name. */\nfunction exampleComponentName(convention: string): string {\n if (convention === 'global-error') return 'GlobalError';\n if (convention === 'error') return 'ErrorBoundary';\n if (convention === '404') return 'NotFound';\n return 'StatusPage';\n}\n\n/**\n * @param file the route file (its extension decides whether the check applies)\n * @param convention the convention name — `error`, `global-error`, `404`, `4xx`, …\n */\nfunction checkFileUseClientDirective(\n file: RouteFile,\n convention: string,\n warnings: ConventionWarning[]\n): void {\n // MDX/markdown status and error files are server components by design — the\n // renderer pre-renders them into `fallbackElement`, so they never cross the\n // boundary as a function.\n //\n // The exemption is the *complement* of the renderer's own test, using the\n // renderer's own helper: everything `error-boundary-wrapper.ts` and\n // `error-renderer.ts` do not treat as MDX, they pass as a component. An\n // independent \"is this a script extension?\" list here would be a second\n // expression of that fact, free to drift into a gap where a file is a\n // component to the renderer and exempt to the linter.\n if (isMdxFilePath(file.filePath)) return;\n\n const filePath = file.filePath;\n const label = `${convention}.${file.extension}`;\n let verdict: ClientBoundaryVerdict;\n try {\n verdict = classifyClientBoundaryFile(filePath);\n } catch (err) {\n swallow(err, `convention-lint: unreadable ${label} file ${filePath}`);\n return;\n }\n if (verdict === 'client') return;\n\n const isError = verdict === 'server';\n const name = exampleComponentName(convention);\n const props =\n convention === 'error' || convention === 'global-error' ? '{ error, reset }' : '{ status }';\n\n warnings.push({\n id: `NEEDS_USE_CLIENT:${filePath}`,\n summary: isError\n ? `${label} is a server component and must be a client component: ${filePath}`\n : `${label} may be missing 'use client': ${filePath}`,\n details:\n ` File: ${filePath}\\n\\n` +\n ` ${label} is handed to timber's error boundary — a client\\n` +\n ' component — as a prop. A server component cannot cross that boundary:\\n' +\n ' React fails with \"Functions cannot be passed directly to Client\\n' +\n ' Components\", and every page under this segment fails to render.\\n\\n' +\n (isError\n ? \" To fix: Add 'use client' as the first line of the file:\\n\\n\" +\n \" 'use client';\\n\\n\" +\n ` export default function ${name}(${props}) {\\n` +\n ' return <h1>…</h1>;\\n' +\n ' }\\n'\n : // The default export comes from somewhere this analysis cannot follow\n // — a re-export, an imported binding, a wrapper call. It may well\n // already be a client component, so say what was checked rather than\n // assert a break.\n \" This file has no 'use client' directive, and its default export\\n\" +\n ' comes from elsewhere, so timber cannot tell which it is. If the\\n' +\n \" module it re-exports starts with 'use client', nothing is wrong\\n\" +\n ' and you can ignore this. Otherwise add the directive there.\\n'),\n level: isError ? 'error' : 'warn',\n });\n}\n\n// ─── Check: Wildcard MDX Props ──────────────────────────────────────────────\n\n/**\n * Wildcard status file keys in `statusFiles` whose props differ by path.\n * `error` is not here — the scanner stores it in `node.error`, not\n * `node.statusFiles`, and the check handles it separately below.\n */\nconst WILDCARD_MDX_STATUS_KEYS = new Set(['4xx', '5xx']);\n\n/**\n * Warn when a wildcard MDX status file (`4xx.mdx`, `5xx.mdx`, `error.mdx`)\n * appears to reference `props` in its source.\n *\n * These files are pre-rendered into `fallbackElement` on the error-boundary\n * path, before the error happens, so they receive empty or partial props.\n * On the deny and error-renderer paths they receive the real props. An author\n * writing `{props.status}` gets the number on some paths and `undefined` on\n * others — same source, different output.\n *\n * A text scan for `props` is acceptable here because the check is `level:\n * 'warn'` — a false positive costs a printed line, not a refused build. A\n * sound check would need the MDX compiler, which is optional, async, and\n * scoped to the RSC environment. See TIM-1333.\n *\n * Exact-code MDX files (e.g. `503.mdx`) are unaffected — all four paths\n * agree on their status.\n */\nfunction checkWildcardMdxProps(node: SegmentNode, warnings: ConventionWarning[]): void {\n if (node.statusFiles) {\n for (const [key, file] of Object.entries(node.statusFiles)) {\n if (WILDCARD_MDX_STATUS_KEYS.has(key) && isMdxFilePath(file.filePath)) {\n checkMdxFileForProps(file.filePath, key, warnings);\n }\n }\n }\n if (node.error && isMdxFilePath(node.error.filePath)) {\n checkMdxFileForProps(node.error.filePath, 'error', warnings);\n }\n\n for (const child of node.children) {\n checkWildcardMdxProps(child, warnings);\n }\n for (const slot of Object.values(node.slots)) {\n checkWildcardMdxProps(slot, warnings);\n }\n}\n\n/**\n * Check if an MDX file's source appears to reference `props`.\n *\n * MDX passes a `props` object to the default export function. A reference to\n * `props.status` or `props.dangerouslyPassData` in a wildcard file works on\n * the deny/throw paths but renders `undefined` on the boundary path.\n */\nfunction checkMdxFileForProps(\n filePath: string,\n convention: string,\n warnings: ConventionWarning[]\n): void {\n let source: string;\n try {\n source = readFileSync(filePath, 'utf-8');\n } catch {\n return;\n }\n\n // MDX exposes props via the default export: export default function(props) { ... }\n // and via JSX expressions: {props.status}. A text scan is intentionally\n // broad — false positives are cheap at warn level.\n if (!/\\bprops\\b/.test(source)) return;\n\n const label = `${convention}.${filePath.endsWith('.md') ? 'md' : 'mdx'}`;\n warnings.push({\n id: `MDX_WILDCARD_PROPS:${filePath}`,\n summary: `${label} references props, which are empty on the error boundary path: ${filePath}`,\n details:\n ` File: ${filePath}\\n\\n` +\n ` Wildcard MDX status files (${convention}.mdx) are pre-rendered into\\n` +\n ' fallbackElement before the error occurs. On that path, props are\\n' +\n ` empty${convention === 'error' ? '' : ' (no status, no dangerouslyPassData)'}. On the deny() and error-renderer\\n` +\n ' paths, props.status resolves correctly.\\n\\n' +\n ' If this file uses only static content and \"props\" appears in prose,\\n' +\n ' this warning is a false positive — ignore it.\\n\\n' +\n ' To fix: Use only static content in wildcard MDX status files, or\\n' +\n ' use an exact-code file (e.g. 404.mdx) where all paths agree.\\n',\n level: 'warn',\n });\n}\n\n// ─── Check: App Directory Exists ────────────────────────────────────────────\n\n/**\n * Check if the app/ directory exists. Called before scanning.\n * Returns a warning if missing, or null if the directory exists.\n */\nexport function checkAppDirExists(appDir: string): ConventionWarning | null {\n if (existsSync(appDir)) return null;\n\n return {\n id: 'NO_APP_DIR',\n summary: 'No app/ directory found',\n details:\n ` Expected: ${appDir}\\n\\n` +\n ' timber.js requires an app/ directory for file-system routing.\\n' +\n ' Every request will return 404 until you create it.\\n\\n' +\n ' To fix: Create the app/ directory with a page:\\n\\n' +\n ' mkdir -p app\\n' +\n ' # Create app/layout.tsx and app/page.tsx\\n',\n level: 'error',\n };\n}\n\n// ─── Formatting ─────────────────────────────────────────────────────────────\n\nimport type { InspectColor } from 'node:util';\nimport { styleText } from 'node:util';\n\ntype Format = InspectColor | readonly InspectColor[];\nconst noValidate = { validateStream: false } as const;\n\nexport interface FormatOptions {\n /**\n * Emit ANSI escapes. Off when the text becomes an `Error` message —\n * whoever prints it decides its formatting, and colored escapes inside\n * an exception leak into logs and stack traces.\n */\n color?: boolean;\n}\n\n/**\n * Format warnings for terminal output.\n *\n * Groups by severity, uses colors, and includes fix suggestions.\n */\nexport function formatConventionWarnings(\n warnings: ConventionWarning[],\n options: FormatOptions = {}\n): string {\n const { color = true } = options;\n const style = (fmt: Format, text: string) => (color ? styleText(fmt, text, noValidate) : text);\n if (warnings.length === 0) return '';\n\n const errors = warnings.filter((w) => w.level === 'error');\n const warns = warnings.filter((w) => w.level === 'warn');\n\n const lines: string[] = [];\n\n if (errors.length > 0) {\n lines.push(\n `${style(['red', 'bold'], '[timber]')} ${style('red', `${errors.length} configuration error${errors.length !== 1 ? 's' : ''}:`)}`\n );\n for (const e of errors) {\n lines.push('');\n lines.push(` ${style('red', '✗')} ${e.summary}`);\n lines.push(style('dim', e.details));\n }\n }\n\n if (warns.length > 0) {\n if (errors.length > 0) lines.push('');\n lines.push(\n `${style(['yellow', 'bold'], '[timber]')} ${style('yellow', `${warns.length} configuration warning${warns.length !== 1 ? 's' : ''}:`)}`\n );\n for (const w of warns) {\n lines.push('');\n lines.push(` ${style('yellow', '⚠')} ${w.summary}`);\n lines.push(style('dim', w.details));\n }\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"logger-uLBuGKDI.js","names":[],"sources":["../../src/server/waituntil-bridge.ts","../../src/server/debug.ts","../../src/server/cookie-parsing.ts","../../src/server/cookie-context.ts","../../src/server/request-context.ts","../../src/server/primitives.ts","../../src/server/tracing.ts","../../src/server/error-formatter.ts","../../src/server/default-logger.ts","../../src/server/logger.ts"],"sourcesContent":["/**\n * Per-request waitUntil bridge — ALS bridge for platform adapters.\n *\n * The generated entry point (Nitro, Cloudflare) wraps the handler with\n * `runWithWaitUntil`, binding the platform's lifecycle extension function\n * (e.g., h3's `event.waitUntil()` or CF's `ctx.waitUntil()`) for the\n * request duration. The `waitUntil()` primitive reads from this ALS to\n * dispatch background work to the correct platform API.\n *\n * Design doc: design/11-platform.md §\"waitUntil()\"\n */\n\nimport { waitUntilAls } from './als-registry.ts';\n\n/**\n * Run a function with a per-request waitUntil handler installed.\n *\n * Called by generated entry points (Nitro node-server/bun, Cloudflare)\n * to bind the platform's lifecycle extension for the request duration.\n */\nexport function runWithWaitUntil<T>(\n waitUntilFn: (promise: Promise<unknown>) => void,\n fn: () => T\n): T {\n return waitUntilAls.run(waitUntilFn, fn);\n}\n\n/**\n * Get the current request's waitUntil function, if available.\n *\n * Returns undefined when no platform adapter has installed a waitUntil\n * handler for the current request (e.g., on platforms that don't support\n * lifecycle extension, or outside a request context).\n */\nexport function getWaitUntil(): ((promise: Promise<unknown>) => void) | undefined {\n return waitUntilAls.getStore();\n}\n","/**\n * Runtime debug flag for timber.js.\n *\n * Two distinct functions for two distinct security levels:\n *\n * ## `isDebug()` — server-side logging only\n *\n * Returns true when timber's debug/warning messages should be written to\n * stderr / the server console. This NEVER affects what is sent to the\n * client (no error details, no timing headers, no stack traces).\n *\n * Active when any of:\n * - `NODE_ENV !== 'production'` (standard dev mode)\n * - `TIMBER_DEBUG` env var is set to a truthy value at runtime\n * - `timber.config.ts` has `debug: true`\n *\n * ## `isDevMode()` — client-visible dev behavior\n *\n * Returns true ONLY when `NODE_ENV !== 'production'`. This gates anything\n * that changes what clients can observe:\n * - Dev error pages with stack traces (fallback-error.ts)\n * - Detailed Server-Timing headers (pipeline.ts)\n * - Error messages in action INTERNAL_ERROR payloads (action-client.ts)\n * - Pipeline error handler wiring (Vite overlay)\n *\n * `isDevMode()` is statically replaced in production builds → the guarded\n * code is tree-shaken to zero bytes. TIMBER_DEBUG cannot enable it.\n *\n * Usage:\n * In Cloudflare Workers wrangler.toml:\n * [vars]\n * TIMBER_DEBUG = \"1\"\n *\n * In Node.js:\n * TIMBER_DEBUG=1 node server.js\n *\n * In timber.config.ts:\n * export default { debug: true }\n *\n * See design/13-security.md for the security taxonomy.\n * See design/18-build-system.md for build pipeline details.\n */\n\n// ─── Dev Mode (client-visible) ──────────────────────────────────────────────\n\n/**\n * Check if the application is running in development mode.\n *\n * This is the ONLY function that should gate client-visible dev behavior:\n * - Dev error pages with stack traces\n * - Server-Timing mode default (`'detailed'` in dev, `'total'` in prod)\n * - Error messages in action `INTERNAL_ERROR` payloads\n * - Pipeline error handler wiring (Vite overlay)\n *\n * Returns `process.env.NODE_ENV !== 'production'`, which is statically\n * replaced by the bundler in production builds. Code guarded by this\n * function is tree-shaken to zero bytes in production.\n *\n * TIMBER_DEBUG does NOT enable this — that would leak server internals\n * to clients. Use `isDebug()` for server-side-only logging.\n */\nexport function isDevMode(): boolean {\n return process.env.NODE_ENV !== 'production';\n}\n\n// ─── Debug Flag (server-side logging only) ──────────────────────────────────\n\n/**\n * Config-level debug override. Set via `setDebugFromConfig()` during\n * initialization when timber.config.ts has `debug: true`.\n */\nlet _configDebug = false;\n\n/**\n * Set the debug flag from timber.config.ts.\n * Called during handler initialization.\n */\nexport function setDebugFromConfig(debug: boolean): void {\n _configDebug = debug;\n}\n\n/**\n * Check if timber debug logging is active (server-side only).\n *\n * Returns true if ANY of these conditions hold:\n * - NODE_ENV is not 'production' (standard dev mode)\n * - TIMBER_DEBUG environment variable is set to a truthy value at runtime\n * - timber.config.ts has `debug: true`\n *\n * This function controls ONLY server-side logging — messages written to\n * stderr or the server console. It NEVER affects client-visible behavior\n * (error pages, response headers, action payloads). For client-visible\n * behavior, use `isDevMode()`.\n *\n * The TIMBER_DEBUG check is deliberately written as a dynamic property\n * access so bundlers cannot statically replace it.\n */\nexport function isDebug(): boolean {\n // Fast path: dev mode (statically replaced to `true` in dev, `false` in prod)\n if (process.env.NODE_ENV !== 'production') return true;\n\n // Config override\n if (_configDebug) return true;\n\n // Runtime env var check — uses dynamic access to prevent static replacement.\n // In production builds, process.env.NODE_ENV is statically replaced, but\n // TIMBER_DEBUG must survive as a runtime check. The dynamic key access\n // pattern ensures the bundler treats this as opaque.\n return _readTimberDebugEnv();\n}\n\n/**\n * Read TIMBER_DEBUG from the environment at runtime.\n *\n * Extracted to a separate function to:\n * 1. Prevent bundler inlining (cross-module function calls are not inlined)\n * 2. Handle platforms where `process` may not exist (Cloudflare Workers)\n * 3. Support globalThis.__TIMBER_DEBUG for programmatic control\n */\nfunction _readTimberDebugEnv(): boolean {\n // globalThis override — useful for programmatic control and testing\n if ((globalThis as Record<string, unknown>).__TIMBER_DEBUG) return true;\n\n // process.env — works in Node.js and platforms that polyfill process\n try {\n const key = 'TIMBER_DEBUG';\n const val =\n typeof process !== 'undefined' && process.env\n ? (process.env as Record<string, string | undefined>)[key]\n : undefined;\n if (val && val !== '0' && val !== 'false') return true;\n } catch {\n // process may not exist or env may throw — safe to ignore\n }\n\n return false;\n}\n","/**\n * Cookie parsing and serialization helpers — pure string ↔ structure\n * functions with no ALS dependency. Split out of `cookie-context.ts`\n * (TIM-853) so the API surface and the wire-format codecs can each be\n * read on their own.\n *\n * The functions in this module are total over arbitrary input. They\n * never throw and never call `assertValid*` (the security validators\n * live in the API surface — `cookie-context.ts` invokes them at every\n * jar entry point so the smuggling-primitive invariant from TIM-868\n * is enforced regardless of which path produced the bytes).\n *\n * Delegates to the `cookie` package (RFC 6265, dependency-free,\n * browser-safe) for wire codecs. Adapts at the boundary to preserve\n * timber's Map-based API and never-throw contract.\n */\n\nimport { parseCookie, parseSetCookie as upstreamParseSetCookie, stringifySetCookie } from 'cookie';\n\nimport type { CookieEntry } from './als-registry.ts';\nimport type { CookieOptions } from './cookie-context.ts';\n\n/**\n * Parse a Cookie header string into a Map of name → value pairs.\n * Follows RFC 6265 §4.2.1: cookies are semicolon-separated key=value pairs.\n *\n * Values are auto-decoded with `decodeURIComponent` so they round-trip\n * losslessly with `getCookies().set()` (which auto-encodes). Malformed\n * `%`-escapes from third-party cookies fall back to the raw byte sequence\n * — the parser must be total over arbitrary inbound headers, including\n * non-conforming values from other servers, browser extensions, etc.\n */\nexport function parseCookieHeader(header: string): Map<string, string> {\n const map = new Map<string, string>();\n if (!header) return map;\n\n // cookie.parseCookie returns a plain object with first-wins semantics\n // and uses safeDecodeURIComponent by default (try/catch around\n // decodeURIComponent, falls back to raw). This matches our contract.\n const parsed = parseCookie(header);\n for (const name in parsed) {\n const value = parsed[name];\n if (value !== undefined) map.set(name, value);\n }\n\n return map;\n}\n\n/**\n * Decode a single cookie value with `decodeURIComponent`, falling back to\n * the raw byte sequence if the input contains a malformed `%`-escape.\n *\n * Used by both `parseCookieHeader` (incoming Cookie: header) and the\n * `setRaw` forwarding path (outgoing Set-Cookie from upstream services).\n * Total — never throws.\n *\n * cookie's internal decode has identical semantics but is not exported,\n * so we keep this standalone helper.\n */\nexport function safeDecodeCookieValue(raw: string): string {\n try {\n return decodeURIComponent(raw);\n } catch {\n return raw;\n }\n}\n\n/**\n * Serialize a CookieEntry into a Set-Cookie header value.\n *\n * Total — never throws. If the cookie package's serializer rejects\n * the input (e.g. non-integer maxAge, forbidden chars in name/value),\n * falls back to a minimal hand-rolled serialization so the response\n * path is never interrupted by a codec error.\n */\nexport function serializeCookieEntry(entry: CookieEntry): string {\n try {\n return stringifySetCookie(\n {\n name: entry.name,\n value: entry.value,\n domain: entry.options.domain,\n path: entry.options.path,\n expires: entry.options.expires,\n maxAge: entry.options.maxAge,\n httpOnly: entry.options.httpOnly,\n secure: entry.options.secure,\n sameSite: entry.options.sameSite,\n partitioned: entry.options.partitioned,\n },\n // timber pre-encodes values at the API surface (set() calls\n // encodeURIComponent), so pass identity to avoid double-encoding.\n { encode: (v: string) => v }\n );\n } catch {\n // Fallback: hand-roll a minimal Set-Cookie so we never drop the\n // header entirely. The entry-point validators in cookie-context.ts\n // already reject truly dangerous input — this path only fires for\n // edge-case formatting the upstream serializer is strict about.\n return serializeCookieEntryFallback(entry);\n }\n}\n\nfunction serializeCookieEntryFallback(entry: CookieEntry): string {\n const parts = [`${entry.name}=${entry.value}`];\n const opts = entry.options;\n\n if (opts.domain) parts.push(`Domain=${opts.domain}`);\n if (opts.path) parts.push(`Path=${opts.path}`);\n if (opts.expires) parts.push(`Expires=${opts.expires.toUTCString()}`);\n if (opts.maxAge !== undefined) parts.push(`Max-Age=${opts.maxAge}`);\n if (opts.httpOnly) parts.push('HttpOnly');\n if (opts.secure) parts.push('Secure');\n if (opts.sameSite) {\n parts.push(`SameSite=${opts.sameSite.charAt(0).toUpperCase()}${opts.sameSite.slice(1)}`);\n }\n if (opts.partitioned) parts.push('Partitioned');\n\n return parts.join('; ');\n}\n\n/**\n * Parse a raw `Set-Cookie` header string into name, value, and options.\n * Handles all standard attributes: Path, Domain, Max-Age, Expires,\n * SameSite, Secure, HttpOnly, Partitioned.\n *\n * Does NOT apply DEFAULT_COOKIE_OPTIONS — the caller decides whether\n * to merge defaults (e.g. `set()` does, but `setRaw()` should preserve\n * the original header's intent).\n */\nexport function parseSetCookie(\n header: string\n): { name: string; value: string; options: CookieOptions } | null {\n const parsed = upstreamParseSetCookie(header, {\n // Don't decode — setRaw expects the wire-form value so it can\n // re-emit verbatim and validate against cookie-octet.\n decode: (v: string) => v,\n });\n\n // cookie.parseSetCookie always returns a SetCookie object, but\n // with name/value as empty strings when the header has no `=`.\n if (!parsed.name) return null;\n\n const options: CookieOptions = {};\n\n if (parsed.path !== undefined) options.path = parsed.path || '/';\n if (parsed.domain !== undefined) options.domain = parsed.domain;\n if (parsed.maxAge !== undefined && Number.isFinite(parsed.maxAge)) {\n options.maxAge = parsed.maxAge;\n }\n if (parsed.expires !== undefined) options.expires = parsed.expires;\n if (parsed.sameSite !== undefined && parsed.sameSite !== true) {\n const sameSite = (parsed.sameSite as string).toLowerCase();\n if (sameSite === 'strict' || sameSite === 'lax' || sameSite === 'none') {\n options.sameSite = sameSite;\n }\n }\n if (parsed.secure) options.secure = true;\n if (parsed.httpOnly) options.httpOnly = true;\n if (parsed.partitioned) options.partitioned = true;\n\n return { name: parsed.name, value: parsed.value ?? '', options };\n}\n","/**\n * Cookie Context — per-request cookie API and on-the-wire helpers.\n *\n * Split out of `request-context.ts` (TIM-853) so the cookie subsystem\n * — encoding contract, options grammar, parser, serializer, RYW map,\n * and rerender seed — lives in one file. The headers/scope/params APIs\n * stay in `request-context.ts` and call into this module via the\n * exported helpers.\n *\n * See design/29-cookies.md for the encoding contract and read-your-own-\n * writes semantics. See ONGOING_SECURITY.md H-3 (TIM-868) for the\n * smuggling primitive that the encoding contract closes.\n */\n\nimport { requestContextAls, type RequestContextStore } from './als-registry.ts';\nimport { isDebug } from './debug.ts';\nimport {\n assertValidCookieName,\n assertValidCookieValue,\n assertValidCookieOptions,\n} from '../cookies/validation.ts';\nimport {\n parseCookieHeader,\n parseSetCookie,\n safeDecodeCookieValue,\n serializeCookieEntry,\n} from './cookie-parsing.ts';\n\n// Re-export the validators so framework-internal consumers and tests can\n// import the canonical implementation from the same module that hosts\n// `getCookies()`. The pure shared module lives in `cookies/validation.ts`\n// so the client `useCookie` hook can use the same checks without pulling\n// in the server ALS code.\nexport { assertValidCookieName, assertValidCookieValue, assertValidCookieOptions };\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * Returns a cookie accessor for the current request.\n *\n * Available in middleware, access checks, server components, and server actions.\n * Throws if called outside a request context (security principle #2: no global fallback).\n *\n * Read methods (.get, .has, .getAll) are always available and reflect\n * read-your-own-writes from .set() calls in the same request.\n *\n * Mutation methods (.set, .delete, .clear) are only available in mutable\n * contexts (middleware.ts, server actions, route.ts handlers). Calling them\n * in read-only contexts (access.ts, server components) throws.\n *\n * This is the escape hatch for direct cookie jar operations. For typed\n * cookie access, use `defineCookie()` instead.\n *\n * See design/29-cookies.md\n */\nexport function getCookieJar(): RequestCookies {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] getCookieJar() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n\n // Parse cookies lazily on first access\n if (!store.parsedCookies) {\n store.parsedCookies = parseCookieHeader(store.cookieHeader);\n }\n\n const map = store.parsedCookies;\n return {\n get(name: string): string | undefined {\n return map.get(name);\n },\n has(name: string): boolean {\n return map.has(name);\n },\n getAll(): Array<{ name: string; value: string }> {\n return Array.from(map.entries()).map(([name, value]) => ({ name, value }));\n },\n get size(): number {\n return map.size;\n },\n\n set(name: string, value: string, options?: SetCookieOptions): void {\n assertMutable(store, 'set');\n // Validate the name first — names cannot be URL-encoded (RFC 7230\n // token grammar is strict), so a bad name is always a bug.\n assertValidCookieName(name);\n // Type guard with a guiding error. The single most common mistake\n // is passing a non-string (object, number, Date) and expecting the\n // framework to JSON-encode. Point developers at jsonCookieCodec.\n if (typeof value !== 'string') {\n throw new Error(\n `[timber] getCookieJar().set(${JSON.stringify(name)}, …): value must be a string, got ${typeof value}.\\n` +\n ` To store a JSON-serializable value, use defineCookie + jsonCookieCodec:\\n` +\n `\\n` +\n ` import { defineCookie, jsonCookieCodec } from '@timber-js/app/cookies';\\n` +\n `\\n` +\n ` export const ${name}Cookie = defineCookie(${JSON.stringify(name)}, {\\n` +\n ` codec: jsonCookieCodec(),\\n` +\n ` });\\n` +\n `\\n` +\n ` ${name}Cookie.set(value);\\n` +\n `\\n` +\n ` See design/29-cookies.md §\"Typed Cookies with Schema Validation\".`\n );\n }\n // Encode the value so the on-the-wire bytes always satisfy\n // RFC 6265 §4.1.1 cookie-octet. encodeURIComponent's output is a\n // strict subset of cookie-octet (only `A-Z a-z 0-9 ! ' ( ) * - . _\n // ~ %`), so the encoded form can never carry the H-3 smuggling\n // primitive — `;` becomes `%3B`, CR/LF become `%0D`/`%0A`, etc.\n // The round-trip is lossless: parseCookieHeader auto-decodes on\n // read, so `cookies().get(name)` returns exactly `value`. See\n // ONGOING_SECURITY.md H-3 (TIM-868) and design/29-cookies.md\n // §\"Encoding Contract\".\n //\n // The `{ raw: true }` opt-out skips the encoder for callers who\n // need exact byte control (e.g. forwarding pre-encoded cookies\n // from an upstream service). The opt-out goes through the strict\n // cookie-octet validator instead — the smuggling primitive cannot\n // sneak in via the escape hatch.\n const raw = options?.raw === true;\n const wireValue = raw ? value : encodeURIComponent(value);\n if (raw) {\n assertValidCookieValue(name, wireValue);\n }\n if (store.flushed) {\n if (isDebug()) {\n console.warn(\n `[timber] warn: getCookieJar().set('${name}') called after response headers were committed.\\n` +\n ` The cookie will NOT be sent. Move cookie mutations to middleware.ts, a server action,\\n` +\n ` or a route.ts handler.`\n );\n }\n return;\n }\n // Strip the framework-only `raw` flag before persisting — it is\n // not an HTTP cookie attribute and must not leak into the jar.\n const { raw: _raw, ...attributeOptions } = options ?? {};\n void _raw;\n const opts = { ...DEFAULT_COOKIE_OPTIONS, ...attributeOptions };\n assertValidCookieOptions(opts);\n store.cookieJar.set(name, { name, value: wireValue, options: opts });\n // Read-your-own-writes: store the DECODED logical value so that\n // subsequent `cookies().get(name)` in the same request returns\n // exactly what the developer wrote — never the encoded form.\n // For `{ raw: true }`, the wire form IS the logical form.\n map.set(name, raw ? wireValue : value);\n },\n\n setFromHeaders(headers: Headers): void {\n assertMutable(store, 'setFromHeaders');\n if (store.flushed) {\n console.warn(\n `[timber] warn: getCookieJar().setFromHeaders() called after response headers were committed.\\n` +\n ` The cookies will NOT be sent. Move cookie mutations to middleware.ts, a server action,\\n` +\n ` or a route.ts handler.`\n );\n return;\n }\n // Headers.getSetCookie() returns individual Set-Cookie strings,\n // avoiding the fragile comma-splitting that raw .get() requires.\n for (const raw of headers.getSetCookie()) {\n const parsed = parseSetCookie(raw);\n if (parsed) {\n // Use setRaw to preserve the original header's attributes without\n // merging DEFAULT_COOKIE_OPTIONS (parseSetCookie intentionally\n // does not apply defaults — see its doc comment).\n setRaw(store, map, parsed.name, parsed.value, parsed.options);\n }\n }\n },\n\n delete(name: string, options?: Pick<CookieOptions, 'path' | 'domain'>): void {\n assertMutable(store, 'delete');\n // Validate the name even though delete writes an empty value — a\n // smuggled name (`;` or CR/LF) would corrupt the Set-Cookie header\n // we emit. See ONGOING_SECURITY.md H-3 (TIM-868).\n assertValidCookieName(name);\n if (store.flushed) {\n if (isDebug()) {\n console.warn(\n `[timber] warn: getCookieJar().delete('${name}') called after response headers were committed.\\n` +\n ` The cookie will NOT be deleted. Move cookie mutations to middleware.ts, a server action,\\n` +\n ` or a route.ts handler.`\n );\n }\n return;\n }\n const opts: CookieOptions = {\n ...DEFAULT_COOKIE_OPTIONS,\n ...options,\n maxAge: 0,\n expires: new Date(0),\n };\n // delete() is a jar entry point like set() — caller-supplied\n // path/domain are serialized verbatim into the Set-Cookie header,\n // so they must pass the same attribute-injection guard. See\n // design/29-cookies.md §\"Cookie Attribute Validation\" (TIM-1026).\n assertValidCookieOptions(opts);\n store.cookieJar.set(name, { name, value: '', options: opts });\n // Remove from read view\n map.delete(name);\n },\n\n clear(): void {\n assertMutable(store, 'clear');\n if (store.flushed) return;\n // Delete every incoming cookie\n for (const name of Array.from(map.keys())) {\n store.cookieJar.set(name, {\n name,\n value: '',\n options: { ...DEFAULT_COOKIE_OPTIONS, maxAge: 0, expires: new Date(0) },\n });\n }\n map.clear();\n },\n\n toString(): string {\n // Re-encode values when serializing as a Cookie header — the\n // RYW map holds decoded logical values, but a Cookie header has\n // to satisfy `cookie-octet`. Mirror the auto-encode contract on\n // `set()` so toString() round-trips losslessly with parseCookieHeader.\n return Array.from(map.entries())\n .map(([name, value]) => `${name}=${encodeURIComponent(value)}`)\n .join('; ');\n },\n };\n}\n\n/**\n * Returns the value of a single cookie, or undefined if absent.\n *\n * @internal — not part of the public API. Use `defineCookie().get()` or `getCookieJar().get()` instead.\n */\nexport function getCookie(name: string): string | undefined {\n const jar = getCookieJar();\n return jar.get(name);\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\n/**\n * Per-call options for `getCookies().set()`. Extends the persistent\n * `CookieOptions` (HTTP cookie attributes) with framework-only flags\n * that are NOT serialized into the Set-Cookie header.\n *\n * The `raw` flag is the escape hatch for the auto-encoding contract.\n * See design/29-cookies.md §\"Encoding Contract\".\n */\nexport interface SetCookieOptions extends CookieOptions {\n /**\n * Skip the framework's `encodeURIComponent` pass and store the value\n * verbatim. The value is then validated against the strict RFC 6265\n * §4.1.1 `cookie-octet` grammar — the H-3 smuggling primitive cannot\n * sneak in via this opt-out.\n *\n * Use this when forwarding a cookie value that is already in its\n * intended on-the-wire form (e.g. mirroring an upstream service's\n * Set-Cookie). Default: `false` (auto-encode).\n */\n raw?: boolean;\n}\n\n/** Options for setting a cookie. See design/29-cookies.md. */\nexport interface CookieOptions {\n /** Domain scope. Default: omitted (current domain only). */\n domain?: string;\n /** URL path scope. Default: '/'. */\n path?: string;\n /** Expiration date. Mutually exclusive with maxAge. */\n expires?: Date;\n /** Max age in seconds. Mutually exclusive with expires. */\n maxAge?: number;\n /** Prevent client-side JS access. Default: true. */\n httpOnly?: boolean;\n /** Only send over HTTPS. Default: true. */\n secure?: boolean;\n /** Cross-site request policy. Default: 'lax'. */\n sameSite?: 'strict' | 'lax' | 'none';\n /** Partitioned (CHIPS) — isolate cookie per top-level site. Default: false. */\n partitioned?: boolean;\n}\n\n/**\n * Cookie accessor returned by `getCookies()`.\n *\n * Read methods are always available. Mutation methods throw in read-only\n * contexts (access.ts, server components).\n */\nexport interface RequestCookies {\n /** Get a cookie value by name. Returns undefined if not present. */\n get(name: string): string | undefined;\n /** Check if a cookie exists. */\n has(name: string): boolean;\n /** Get all cookies as an array of { name, value } pairs. */\n getAll(): Array<{ name: string; value: string }>;\n /** Number of cookies. */\n readonly size: number;\n /**\n * Set a cookie. Only available in mutable contexts (middleware, actions,\n * route handlers).\n *\n * The value is auto-encoded with `encodeURIComponent` so the on-the-wire\n * bytes always satisfy RFC 6265 §4.1.1 cookie-octet — `cookies().get()`\n * returns the same logical value the developer wrote. Pass `{ raw: true }`\n * to skip the encoder; the raw path validates against the strict\n * cookie-octet grammar instead. See design/29-cookies.md §\"Encoding\n * Contract\" and ONGOING_SECURITY.md H-3 (TIM-868).\n */\n set(name: string, value: string, options?: SetCookieOptions): void;\n /**\n * Copy all `Set-Cookie` headers from a `Headers` object.\n * Parses each header and forwards name, value, and all attributes\n * (path, domain, max-age, expires, sameSite, secure, httpOnly, partitioned).\n *\n * Useful when forwarding cookies from an internal `fetch()` or auth handler:\n * ```ts\n * const response = await auth.handler(req);\n * getCookies().then(c => c.setFromHeaders(response.headers));\n * ```\n */\n setFromHeaders(headers: Headers): void;\n /** Delete a cookie. Only available in mutable contexts. */\n delete(name: string, options?: Pick<CookieOptions, 'path' | 'domain'>): void;\n /** Delete all cookies. Only available in mutable contexts. */\n clear(): void;\n /** Serialize cookies as a Cookie header string. */\n toString(): string;\n}\n\nconst DEFAULT_COOKIE_OPTIONS: CookieOptions = {\n path: '/',\n httpOnly: true,\n secure: true,\n sameSite: 'lax',\n};\n\n// ─── Framework-Internal Helpers ───────────────────────────────────────────\n\n/**\n * Per-request seed map of cookie name → value, registered out-of-band so the\n * pipeline's `runWithRequestContext` call can pick it up without changing\n * the function's calling convention. Stored in a WeakMap keyed by the\n * synthetic Request object built for the rerender path, so the seed lives\n * exactly as long as the request and is collected with it.\n *\n * The seed exists to eliminate the parse/serialize round-trip on the no-JS\n * form-rerender path — see ONGOING_SECURITY.md H-3 (TIM-868). The action's\n * post-mutation RYW snapshot is threaded directly into the rerender scope's\n * `parsedCookies` map, bypassing `parseCookieHeader` entirely.\n */\nconst seededRequestCookies = new WeakMap<Request, Map<string, string>>();\n\n/**\n * Register a pre-parsed cookie map to use as the request context seed for\n * the next `runWithRequestContext(req, …)` call with this exact `req`.\n *\n * Used by the no-JS form-rerender dispatcher in\n * `rsc-entry/wrap-action-dispatch.ts` to thread the action's post-mutation\n * cookie state into the rerender scope without serializing back through a\n * `Cookie:` header. See TIM-868 / TIM-837.\n *\n * @internal — framework use only.\n */\nexport function seedRequestCookies(req: Request, cookies: Map<string, string>): void {\n // Defensive copy: callers must not be able to mutate the rerender scope's\n // map after seeding, and the rerender scope must not mutate the snapshot\n // we hand back to other observers.\n seededRequestCookies.set(req, new Map(cookies));\n}\n\n/**\n * Pop the seed (if any) for `req` and return it. Called from\n * `runWithRequestContext` exactly once per request — the seed is consumed\n * eagerly so it cannot leak into a hypothetical future re-use of the same\n * Request reference.\n *\n * @internal — framework use only.\n */\nexport function consumeSeededCookies(req: Request): Map<string, string> | undefined {\n const seed = seededRequestCookies.get(req);\n if (seed) seededRequestCookies.delete(req);\n return seed;\n}\n\n/**\n * Build a Map of cookie name → value reflecting the current request's\n * read-your-own-writes state. Includes incoming cookies plus any\n * mutations from getCookies().set() / getCookies().delete() in the same request.\n *\n * Used by SSR renderers to populate NavContext.cookies so that\n * useCookie()'s server snapshot matches the actual response state.\n *\n * See design/29-cookies.md §\"Read-Your-Own-Writes\"\n * See design/triage/TIM-441-cookie-api-triage.md §4\n */\nexport function getCookiesForSsr(): Map<string, string> {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error('[timber] getCookiesForSsr() called outside of a request context.');\n }\n\n // Trigger lazy parsing if not yet done\n if (!store.parsedCookies) {\n store.parsedCookies = parseCookieHeader(store.cookieHeader);\n }\n\n // The parsedCookies map already reflects read-your-own-writes:\n // - getCookies().set() updates the map via map.set(name, value)\n // - getCookies().delete() removes from the map via map.delete(name)\n // Return a copy so callers can't mutate the internal map.\n return new Map(store.parsedCookies);\n}\n\n/**\n * Collect all Set-Cookie headers from the cookie jar.\n * Called by the framework at flush time to apply cookies to the response.\n *\n * Returns an array of serialized Set-Cookie header values.\n */\nexport function getSetCookieHeaders(): string[] {\n const store = requestContextAls.getStore();\n if (!store) return [];\n return Array.from(store.cookieJar.values()).map(serializeCookieEntry);\n}\n\n// ─── Cookie Helpers ───────────────────────────────────────────────────────\n\n/** Throw if cookie mutation is attempted in a read-only context. */\nfunction assertMutable(store: RequestContextStore, method: string): void {\n if (!store.mutableContext) {\n throw new Error(\n `[timber] getCookieJar().${method}() cannot be called in this context.\\n` +\n ` Set cookies in middleware.ts, server actions, or route.ts handlers.`\n );\n }\n}\n\n/**\n * Write a cookie to the jar WITHOUT merging DEFAULT_COOKIE_OPTIONS.\n * Used by setFromHeaders to preserve the original header's attributes exactly.\n *\n * For deletion cookies (maxAge=0), the jar entry is still created so the\n * Set-Cookie header is emitted, but the cookie is NOT added to the read map\n * (it would be misleading — the cookie is being deleted).\n */\nfunction setRaw(\n store: RequestContextStore,\n readMap: Map<string, string>,\n name: string,\n value: string,\n options: CookieOptions\n): void {\n // setRaw is the forwarding path for upstream Set-Cookie headers\n // (`getCookies().setFromHeaders(response.headers)`). The value comes\n // out of `parseSetCookie` in its on-the-wire form — already encoded\n // by whoever produced it — so we re-emit it verbatim into our jar.\n // We DO validate against the strict cookie-octet grammar, both as\n // defense-in-depth against a malicious upstream and to keep the\n // ONGOING_SECURITY.md H-3 invariant intact at every entry point into\n // the cookie jar / RYW map.\n assertValidCookieName(name);\n assertValidCookieValue(name, value);\n // Options get the same entry-point guard as set()/delete(). For the\n // setFromHeaders path this can only fire on attributes parseSetCookie\n // cannot produce (it splits on `;` and normalizes Max-Age/SameSite),\n // but setRaw is the jar entry point — the invariant lives here, not in\n // the caller. See design/29-cookies.md §\"Cookie Attribute Validation\".\n assertValidCookieOptions(options);\n store.cookieJar.set(name, { name, value, options });\n // Deletion cookies (Max-Age=0) should not appear in the read map.\n if (options.maxAge === 0) {\n readMap.delete(name);\n } else {\n // Decode for the RYW map so consumers see the same logical bytes\n // they would see if the upstream cookie had arrived on the next\n // request. Mirrors `parseCookieHeader`'s auto-decode.\n readMap.set(name, safeDecodeCookieValue(value));\n }\n}\n","/**\n * Request Context — per-request ALS store for headers, search params,\n * segment params, and request scope lifecycle.\n *\n * Follows the same pattern as tracing.ts: a module-level AsyncLocalStorage\n * instance, public accessor functions that throw outside request scope,\n * and a framework-internal `runWithRequestContext()` to establish scope.\n *\n * Cookie state lives in `cookie-context.ts` (split out in TIM-853). The\n * scope set up here owns the cookie jar / parsedCookies fields on the\n * store, but the cookie API and helpers are in the cookie module.\n *\n * See design/04-authorization.md §\"AccessContext does not include cookies or headers\"\n * and design/11-platform.md §\"AsyncLocalStorage\".\n */\n\nimport type { CoercedParams } from '../shared/param-value.ts';\nimport { requestContextAls, type RequestContextStore } from './als-registry.ts';\nimport { _setGetSearchParamsFn, _setGetSegmentParamsFn } from '../shared/als-slots.ts';\nimport { consumeSeededCookies } from './cookie-context.ts';\nimport { appVisibleSearch } from '../shared/rsc-cache-key.ts';\nimport {\n mergeSlotParams,\n resolveSegmentParams,\n type SlotParamsRecord,\n} from '../shared/slot-params.ts';\nimport { isDebug } from './debug.ts';\n\n// Re-export the ALS for framework-internal consumers that need direct access.\nexport { requestContextAls };\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * Returns a read-only view of the current request's headers.\n *\n * Available in middleware, access checks, server components, and server actions.\n * Throws if called outside a request context (security principle #2: no global fallback).\n */\nexport function getHeaders(): ReadonlyHeaders {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] getHeaders() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n\n return store.headers;\n}\n\n/**\n * Returns the value of a single request header, or undefined if absent.\n *\n * Thin wrapper over `getHeaders().get(name)` for the common case where\n * you need exactly one header.\n *\n * @internal — not part of the public API. Use `getHeaders().get(name)` instead.\n */\nexport function getHeader(name: string): string | undefined {\n const headers = getHeaders();\n return headers.get(name) ?? undefined;\n}\n\n/**\n * Returns the current request's raw URLSearchParams.\n *\n * @internal — not part of the public API. Use `defineSearchParams().get()` instead.\n */\nexport function getSearchParams(): URLSearchParams {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] getSearchParams() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n\n return store.searchParams;\n}\n\n// Eagerly register getSearchParams with the search-params module so\n// searchParams.get() can call it without a dynamic import.\n// Dynamic imports lose ALS context in React's RSC Flight renderer,\n// breaking getSearchParams() in parallel slot pages. See TIM-523.\n_setGetSearchParamsFn(getSearchParams);\n\n// Eagerly register getSegmentParams with the segment-params module so\n// segmentParams.get() can call it without a dynamic import.\n// Same pattern as search params — dynamic imports lose ALS context. See TIM-523.\n_setGetSegmentParamsFn(getSegmentParams);\n\n/**\n * Returns the current request's coerced segment params.\n *\n * The optional `segmentPath` argument exists only for TypeScript narrowing —\n * it does not affect the runtime return value. Pass a segment path\n * (e.g. `'/(browse)/[artistSlug]/[year]'`) to narrow the return type to the\n * exact params shape for that segment.\n *\n * Without an argument, returns all coerced params with optional types.\n *\n * See design/41-global-params.md §Function Signatures\n */\nexport function getSegmentParams(segmentPath?: string): CoercedParams {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] getSegmentParams() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n if (!store.segmentParams) {\n throw new Error(\n '[timber] getSegmentParams() called before route matching completed. ' +\n 'Segment params are not available until after the route is matched.'\n );\n }\n\n // Parallel slots may derive a param the main route never had, or the same\n // name with a different type (catch-all [...year] → string[] vs dynamic\n // [year] → string). `resolveSegmentParams` is the shared definition the\n // client's useSegmentParams() also reads, so the two halves of one\n // documented API cannot drift (TIM-1285).\n const slotResolved = resolveSegmentParams(store.segmentParams, store.slotParams, segmentPath);\n if (slotResolved !== store.segmentParams) return slotResolved;\n\n // Dev-mode validation: warn when the segment path expects dynamic\n // segments that don't exist on the actual matched segment. This catches\n // bugs like passing a child segment's $segment to a parent layout.\n // Skip for slot paths — they're validated against their own chain.\n // See design/41-global-params.md §Runtime Validation\n if (segmentPath && isDebug() && store.matchedSegmentPath) {\n const expected = extractDynamicSegments(segmentPath);\n const actual = extractDynamicSegments(store.matchedSegmentPath);\n const missing = expected.filter((s) => !actual.includes(s));\n\n if (missing.length > 0) {\n console.warn(\n `[timber] getSegmentParams('${segmentPath}') called but current segment is '${store.matchedSegmentPath}'\\n` +\n ` Missing params: ${missing.join(', ')}\\n` +\n ` These will be undefined at runtime despite the type annotation.`\n );\n }\n }\n\n return store.segmentParams;\n}\n\n/**\n * Set the segment params on the current request context.\n * Called by the pipeline after route matching and param coercion.\n *\n * @internal — framework use only\n */\nexport function setSegmentParams(params: CoercedParams): void {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error('[timber] setSegmentParams() called outside of a request context.');\n }\n store.segmentParams = params;\n}\n\n/**\n * Store per-slot coerced segment params in the current request context.\n * Called by the route element builder when resolving parallel slots.\n *\n * @param segmentPath — The slot's full tree path (e.g. '/(browse)/@shows/[artistSlug]/[...year]')\n * @param params — The slot's coerced segment params\n * @internal — framework use only\n */\nexport function setSlotParams(segmentPath: string, params: CoercedParams): void {\n const store = requestContextAls.getStore();\n if (!store) return; // non-throwing — optional diagnostic\n if (!store.slotParams) store.slotParams = Object.create(null) as SlotParamsRecord;\n store.slotParams[segmentPath] = params;\n}\n\n/**\n * Run `fn` with the segment params a parallel slot's own chain produced, so\n * a no-arg `getSegmentParams()` inside that slot returns the slot's params\n * rather than the main route's.\n *\n * ALS is the only channel available: the slot's page and layouts are plain\n * components that receive no params prop and cannot know their own tree\n * path, so they have no way to ask for the keyed overload. The slot resolver\n * invokes each of them itself (page through `SafeSlotPage`, layouts and\n * access functions through their wrappers), and this scopes those calls.\n *\n * The store is wrapped in a Proxy rather than cloned. Everything a slot\n * renders can write to the *live* request store — `setDenyStatus` from an\n * access gate, cookie writes, the flush flag — and a clone would silently\n * strand those writes in a copy the pipeline never reads. Only the two\n * params fields are shadowed; every other read and every write passes\n * straight through to the real store.\n *\n * Returns `fn()`'s result unchanged, and no-ops outside a request context or\n * before route matching has set the main params. See TIM-1283.\n *\n * @internal — framework use only\n */\nexport function runWithSlotSegmentParams<T>(\n slotSegmentPath: string | undefined,\n slotParams: CoercedParams,\n fn: () => T\n): T {\n const store = requestContextAls.getStore();\n if (!store?.segmentParams) return fn();\n\n const merged = mergeSlotParams(store.segmentParams, slotParams);\n const matchedSegmentPath = slotSegmentPath ?? store.matchedSegmentPath;\n const scoped = new Proxy(store, {\n get(target, prop, receiver) {\n if (prop === 'segmentParams') return merged;\n if (prop === 'matchedSegmentPath') return matchedSegmentPath;\n return Reflect.get(target, prop, receiver);\n },\n });\n return requestContextAls.run(scoped, fn);\n}\n\n/**\n * The main route's coerced params, or an empty record before route matching.\n *\n * Non-throwing, unlike `getSegmentParams()`: the deny payload path publishes\n * params from wherever the pipeline happened to fail, which may be before\n * matching completed.\n *\n * @internal — framework use only. See TIM-1285.\n */\nexport function getSegmentParamsForClient(): CoercedParams {\n return requestContextAls.getStore()?.segmentParams ?? {};\n}\n\n/**\n * The per-slot params to publish to the client, or `undefined` when the\n * request rendered no slot with params of its own.\n *\n * The client cannot derive these: a slot matches the URL through its own\n * sub-tree, and the browser only ever sees the main route's record. Reading\n * the map here — rather than threading it back out of the element builder —\n * keeps one producer, since `setSlotParams` already writes it during slot\n * resolution and every serialization site runs after that completes.\n *\n * @internal — framework use only. See TIM-1285.\n */\nexport function getSlotParamsForClient(): SlotParamsRecord | undefined {\n const store = requestContextAls.getStore();\n if (!store?.slotParams) return undefined;\n return Object.keys(store.slotParams).length > 0 ? store.slotParams : undefined;\n}\n\n/**\n * Store the matched segment path (e.g. '/(browse)/[artistSlug]/[year]') for\n * dev-mode validation in getSegmentParams().\n *\n * @internal — framework use only\n */\nexport function setMatchedSegmentPath(segmentPath: string): void {\n const store = requestContextAls.getStore();\n if (!store) return; // non-throwing — optional diagnostic\n store.matchedSegmentPath = segmentPath;\n}\n\n/**\n * Extract dynamic segment names from a route pattern.\n * '/[artistSlug]/[year]' → ['artistSlug', 'year']\n * '/docs/[...slug]' → ['slug']\n */\nfunction extractDynamicSegments(route: string): string[] {\n const segments: string[] = [];\n // Match [paramName], [...paramName], [[...paramName]]\n const re = /\\[{1,2}\\.{0,3}(\\w+)\\]{1,2}/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(route)) !== null) {\n segments.push(m[1]);\n }\n return segments;\n}\n\n/**\n * Returns the raw search string from the current request URL (e.g. \"?foo=bar\").\n * Synchronous — safe for use in `redirect()` which throws synchronously.\n *\n * Returns empty string if called outside a request context (non-throwing for\n * use in redirect's optional preserveSearchParams path).\n *\n * @internal — used by redirect() for preserveSearchParams support.\n */\nexport function getRequestSearchString(): string {\n const store = requestContextAls.getStore();\n return store?.searchString ?? '';\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\n/**\n * Read-only Headers interface. The standard Headers class is mutable;\n * this type narrows it to read-only methods. The underlying object is\n * still a Headers instance, but user code should not mutate it.\n */\nexport type ReadonlyHeaders = Pick<\n Headers,\n 'get' | 'has' | 'entries' | 'keys' | 'values' | 'forEach' | typeof Symbol.iterator\n>;\n\n// ─── Framework-Internal Helpers ───────────────────────────────────────────\n\n/**\n * Run a callback within a request context. Used by the pipeline to establish\n * per-request ALS scope so that `getHeaders()` and `getCookies()` work.\n *\n * If the request was previously registered via `seedRequestCookies`, the\n * resulting context's `parsedCookies` map is initialized from the seed and\n * the raw `cookieHeader` is left empty — `parseCookieHeader` is never called\n * for that request. This is the no-JS form-rerender path. See TIM-868.\n *\n * @param req - The incoming Request object.\n * @param fn - The function to run within the request context.\n */\nexport function runWithRequestContext<T>(req: Request, fn: () => T): T {\n const originalCopy = new Headers(req.headers);\n const appVisible = appVisibleSearch(new URL(req.url));\n const seed = consumeSeededCookies(req);\n const store: RequestContextStore = {\n headers: freezeHeaders(req.headers),\n originalHeaders: originalCopy,\n // When seeded, leave the raw header empty — parseCookieHeader is the\n // exact code path the smuggling primitive abused, and lazy parsing is\n // gated on `parsedCookies` being undefined.\n cookieHeader: seed ? '' : (req.headers.get('cookie') ?? ''),\n parsedCookies: seed,\n // Not `parsedUrl.searchParams` / `parsedUrl.search`: this store is what\n // `getSearchParams()` and `redirect({ preserveSearchParams })` read, and\n // the RSC payload URL carries the framework's `_rsc` cache key that no\n // other request has (TIM-1272).\n searchParams: appVisible.params,\n searchString: appVisible.search,\n cookieJar: new Map(),\n flushed: false,\n mutableContext: false,\n };\n return requestContextAls.run(store, fn);\n}\n\n/**\n * Enable cookie mutation for the current context. Called by the framework\n * when entering middleware.ts, server actions, or route.ts handlers.\n *\n * See design/29-cookies.md §\"Context Tracking\"\n */\nexport function setMutableCookieContext(mutable: boolean): void {\n const store = requestContextAls.getStore();\n if (store) {\n store.mutableContext = mutable;\n }\n}\n\n/**\n * Mark the response as flushed (headers committed). After this point,\n * cookie mutations log a warning instead of throwing.\n *\n * See design/29-cookies.md §\"Streaming Constraint: Post-Flush Cookie Warning\"\n */\nexport function markResponseFlushed(): void {\n const store = requestContextAls.getStore();\n if (store) {\n store.flushed = true;\n }\n}\n\n/**\n * Apply middleware-injected request headers to the current request context.\n *\n * Called by the pipeline after middleware.ts runs. Merges overlay headers\n * on top of the original request headers so downstream code (access.ts,\n * server components, server actions) sees them via `getHeaders()`.\n *\n * The original request headers are never mutated — a new frozen Headers\n * object is created with the overlay applied on top.\n *\n * See design/07-routing.md §\"Request Header Injection\"\n */\nexport function applyRequestHeaderOverlay(overlay: Headers): void {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error('[timber] applyRequestHeaderOverlay() called outside of a request context.');\n }\n\n // Check if the overlay has any headers — skip if empty\n let hasOverlay = false;\n overlay.forEach(() => {\n hasOverlay = true;\n });\n if (!hasOverlay) return;\n\n // Merge: start with original headers, overlay on top\n const merged = new Headers(store.originalHeaders);\n overlay.forEach((value, key) => {\n merged.set(key, value);\n });\n store.headers = freezeHeaders(merged);\n}\n\n// ─── Read-Only Headers ────────────────────────────────────────────────────\n\nconst MUTATING_METHODS = new Set(['set', 'append', 'delete']);\n\n/**\n * Wrap a Headers object in a Proxy that throws on mutating methods.\n * Object.freeze doesn't work on Headers (native internal slots), so we\n * intercept property access and reject set/append/delete at runtime.\n *\n * Read methods (get, has, entries, etc.) must be bound to the underlying\n * Headers instance because they access private #headersList slots.\n */\nfunction freezeHeaders(source: Headers): Headers {\n const copy = new Headers(source);\n return new Proxy(copy, {\n get(target, prop) {\n if (typeof prop === 'string' && MUTATING_METHODS.has(prop)) {\n return () => {\n throw new Error(\n `[timber] getHeaders() returns a read-only Headers object. ` +\n `Calling .${prop}() is not allowed. ` +\n `Use ctx.requestHeaders in middleware to inject headers for downstream components.`\n );\n };\n }\n const value = Reflect.get(target, prop);\n // Bind methods to the real Headers instance so private slot access works\n if (typeof value === 'function') {\n return value.bind(target);\n }\n return value;\n },\n });\n}\n","// 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.ts';\nimport { getWaitUntil as _getWaitUntil } from './waituntil-bridge.ts';\nimport { isDebug } from './debug.ts';\nimport { getRequestSearchString } from './request-context.ts';\nimport { mergePreservedSearchParams } from '../shared/merge-search-params.ts';\nimport {\n assertRelativeRedirectPath,\n validateExternalRedirectUrl,\n} from '../shared/href-validation.ts';\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((value as Record<string, unknown>)[key], `${path}.${key}`);\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 (!isDebug()) 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// ─── Signal branding ────────────────────────────────────────────────────────\n//\n// Catch sites must NOT use `instanceof` on these classes. In dev, an HMR\n// program reload clears the RSC module cache; a request that is mid-render\n// still holds the catch sites from the old evaluation of this module while\n// user code loaded after the reload throws from the new one. The two\n// evaluations are distinct classes, so `instanceof` fails and a correct\n// deny() falls through every catch site as an unhandled 500 (TIM-1499).\n//\n// Each class stamps a `Symbol.for` brand on its instances in the constructor.\n// `Symbol.for` keys live in the realm-wide symbol registry, so every\n// evaluation of this module — and every module graph in the same process —\n// agrees on the key. The `is*` predicates below check the brand and are the\n// only supported way to detect a signal. The classes themselves are NOT\n// anchored on globalThis: a re-evaluated module must hand out its own\n// (possibly updated) class, not a stale one.\n//\n// See design/18-build-system.md §\"Dev Mode: Signal Identity Across Program Reloads\".\n\nconst DENY_BRAND: unique symbol = Symbol.for('timber:signal.deny');\nconst REDIRECT_BRAND: unique symbol = Symbol.for('timber:signal.redirect');\nconst RENDER_ERROR_BRAND: unique symbol = Symbol.for('timber:signal.render-error');\nconst SSR_STREAM_ERROR_BRAND: unique symbol = Symbol.for('timber:signal.ssr-stream-error');\n\nfunction hasBrand(value: unknown, brand: symbol): boolean {\n return typeof value === 'object' && value !== null && brand in value;\n}\n\n/** True for any `DenySignal`, regardless of which module evaluation created it. */\nexport function isDenySignal(error: unknown): error is DenySignal {\n return hasBrand(error, DENY_BRAND);\n}\n\n/** True for any `RedirectSignal`, regardless of which module evaluation created it. */\nexport function isRedirectSignal(error: unknown): error is RedirectSignal {\n return hasBrand(error, REDIRECT_BRAND);\n}\n\n/** True for any `RenderError`, regardless of which module evaluation created it. */\nexport function isRenderError(error: unknown): error is RenderError {\n return hasBrand(error, RENDER_ERROR_BRAND);\n}\n\n/**\n * True for any `SsrStreamError`. This one also crosses Vite environments:\n * it is thrown in SSR and caught in RSC, which are separate module graphs in\n * the same process — the brand is the only identity the two share.\n */\nexport function isSsrStreamError(error: unknown): error is SsrStreamError {\n return hasBrand(error, SSR_STREAM_ERROR_BRAND);\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 *\n * Detect with `isDenySignal()`, never `instanceof` — see \"Signal branding\" above.\n */\nexport class DenySignal extends Error {\n readonly [DENY_BRAND] = true;\n readonly status: number;\n readonly data: JsonSerializable | undefined;\n\n /**\n * Ordered list of segment keys that own matching deny pages for this\n * signal, best match first. Present = addressed (a boundary should\n * render it); absent = unplaced (the re-render fallback serves it).\n *\n * In-tree hoists stamp a single-element list (`[entry.ownerKey]`) —\n * identical to the pre-TIM-1450 `ownerKey`. Late addressing (TIM-1450)\n * stamps the full candidate list so the client can find the first\n * *reachable* owner via its ancestry context, eliminating the\n * owner-below-throw-site escape.\n *\n * See design/04-authorization.md §\"Where a Deny Page Renders\", TIM-1356.\n */\n owners?: string[];\n\n /**\n * When true, the pipeline must respond with `rscErrorEnvelope` so the\n * client hard-navigates instead of attempting to render a deny page\n * from the RSC payload. Set by AccessGate when a skipped segment\n * denies — rendering the deny page in-tree would appear inside the\n * client's cached layout chrome (TIM-1074). See TIM-1363.\n */\n _hardNavigate?: boolean;\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/** Options for deny() when using the object form. */\nexport interface DenyOptions {\n /** HTTP status code (4xx or 5xx). Default: 403. */\n status?: number;\n /** Human-readable message (logged server-side, not sent to client). */\n message?: string;\n /** JSON-serializable data passed as `dangerouslyPassData` prop to status-code files. */\n data?: JsonSerializable;\n}\n\n/**\n * Universal denial/error 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 * Supports both positional and object signatures:\n * ```ts\n * deny() // 403 (default)\n * deny(404) // 404\n * deny(503, { retry: true }) // 503 with data\n * deny({ status: 503, message: 'Maintenance' }) // object form\n * ```\n *\n * Accepts any 4xx or 5xx status code. This replaces the need for\n * `throw new RenderError(...)` in user code — RenderError is now an\n * internal pipeline detail.\n *\n * @param statusOrOptions - Status code (number) or options object. Default: 403.\n * @param data - Optional JSON-serializable data (positional form only).\n */\nexport function deny(statusOrOptions?: number | DenyOptions, data?: JsonSerializable): never {\n let status: number;\n let resolvedData: JsonSerializable | undefined;\n\n if (typeof statusOrOptions === 'object' && statusOrOptions !== null) {\n status = statusOrOptions.status ?? 403;\n resolvedData = statusOrOptions.data;\n } else {\n status = statusOrOptions ?? 403;\n resolvedData = data;\n }\n\n if (status < 400 || status > 599) {\n throw new Error(`deny() requires a 4xx or 5xx status code, got ${status}.`);\n }\n warnIfNotSerializable(resolvedData, 'deny()');\n throw new DenySignal(status, resolvedData);\n}\n\n// Single source of truth shared with the client next/navigation shim —\n// see shared/redirect-type.ts.\nexport { RedirectType } from '../shared/redirect-type.ts';\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 *\n * Detect with `isRedirectSignal()`, never `instanceof` — see \"Signal branding\" above.\n */\nexport class RedirectSignal extends Error {\n readonly [REDIRECT_BRAND] = true;\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// ─── Signal Detection ───────────────────────────────────────────────────────\n\n/**\n * Returns true if the error is a framework control-flow signal (RedirectSignal\n * or DenySignal) rather than a genuine application error. Works both for direct\n * instances (RSC-side) and deserialized digest errors (SSR cross-boundary).\n *\n * See also: isFrameworkSignalError() in client/browser-dev.ts (client-only).\n */\nexport function isControlFlowSignal(error: unknown): boolean {\n if (isDenySignal(error) || isRedirectSignal(error)) {\n return true;\n }\n if (error && typeof error === 'object') {\n const digest = (error as { digest?: unknown }).digest;\n if (typeof digest === 'string') {\n try {\n const parsed = JSON.parse(digest) as { type?: unknown } | null;\n if (parsed && typeof parsed === 'object') {\n return parsed.type === 'redirect' || parsed.type === 'deny';\n }\n } catch {\n // Not valid digest JSON\n }\n }\n }\n return false;\n}\n\n/**\n * Options for redirect() — alternative to passing a bare status code.\n */\nexport interface RedirectOptions {\n /** HTTP redirect status code (3xx). Defaults to 302 (or 308 when `permanent: true`). */\n status?: number;\n /**\n * When true, defaults the status to 308 (Permanent Redirect, preserves HTTP method).\n * If `status` is also provided, `status` takes precedence.\n *\n * @example\n * redirect('/new-path', { permanent: true }); // 308\n * redirect('/new-path', { permanent: true, status: 301 }); // 301\n */\n permanent?: boolean;\n /**\n * Preserve search params from the current request URL on the redirect target.\n *\n * - `true` — preserve ALL current search params (target params take precedence)\n * - `string[]` — preserve only the named params (e.g. `['private', 'token']`)\n *\n * Target path's own query params always take precedence over preserved ones.\n */\n preserveSearchParams?: true | string[];\n}\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 statusOrOptions - HTTP status code (3xx, default 302) or options object.\n *\n * @example\n * // Simple redirect\n * redirect('/login');\n *\n * // With status code\n * redirect('/login', 301);\n *\n * // With preserved search params\n * redirect(`/docs/${version}/${slug}`, { preserveSearchParams: ['foo'] });\n */\nexport function redirect(path: string, statusOrOptions?: number | RedirectOptions): never {\n let status: number;\n let preserveSearchParams: true | string[] | undefined;\n\n if (typeof statusOrOptions === 'number') {\n status = statusOrOptions;\n } else if (statusOrOptions) {\n // Explicit status wins. Otherwise permanent: true → 308, default → 302.\n status = statusOrOptions.status ?? (statusOrOptions.permanent ? 308 : 302);\n preserveSearchParams = statusOrOptions.preserveSearchParams;\n } else {\n status = 302;\n }\n\n if (status < 300 || status > 399) {\n throw new Error(`redirect() requires a 3xx status code, got ${status}.`);\n }\n // Relative-only validation shared with the client next/navigation shim —\n // strips C0 controls and normalizes backslashes before the scheme check.\n assertRelativeRedirectPath(path);\n\n let resolvedPath = path;\n if (preserveSearchParams) {\n const currentSearch = getRequestSearchString();\n resolvedPath = mergePreservedSearchParams(path, currentSearch, preserveSearchParams);\n }\n\n throw new RedirectSignal(resolvedPath, status);\n}\n\n/**\n * Redirect to an external URL. The origin must be in the provided allow-list.\n *\n * Only http: and https: schemes are permitted. The allow-list is matched\n * against full origins (scheme + host + port), not bare hostnames.\n *\n * @param url - Absolute URL to redirect to.\n * @param allowList - Array of allowed origins (e.g. ['https://example.com', 'https://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 const canonicalUrl = validateExternalRedirectUrl(url, allowList);\n throw new RedirectSignal(canonicalUrl, 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 *\n * Detect with `isRenderError()`, never `instanceof` — see \"Signal branding\" above.\n */\nexport class RenderError<\n TCode extends string = string,\n TData extends JsonSerializable = JsonSerializable,\n> extends Error {\n readonly [RENDER_ERROR_BRAND] = true;\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// 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 * The platform adapter installs a per-request waitUntil function via ALS\n * (see waituntil-bridge.ts). If no ALS handler is available, a warning\n * is logged once and the promise is left to resolve (or reject) without\n * being tracked.\n *\n * @param promise - The background work to keep alive.\n */\nexport function waitUntil(promise: Promise<unknown>): void {\n const alsFn = _getWaitUntil();\n if (alsFn) {\n alsFn(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 *\n * Detect with `isSsrStreamError()` — it is thrown in the SSR environment and\n * caught in RSC, so `instanceof` never matches. See \"Signal branding\" above.\n */\nexport class SsrStreamError extends Error {\n readonly [SSR_STREAM_ERROR_BRAND] = true;\n override readonly cause: unknown;\n\n constructor(message: string, cause: unknown) {\n super(message);\n this.name = 'SsrStreamError';\n this.cause = cause;\n }\n}\n","/**\n * Tracing — per-request trace ID via AsyncLocalStorage, OTEL span helpers.\n *\n * getTraceId() is always available in server code (middleware, access, components, actions).\n * Returns a 32-char lowercase hex string — the OTEL trace ID when an SDK is active,\n * or a crypto.randomUUID()-derived fallback otherwise.\n *\n * See design/17-logging.md §\"trace_id is Always Set\"\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { traceAls, type TraceStore } from './als-registry.ts';\n\n// Re-export the TraceStore type for public API consumers.\nexport type { TraceStore } from './als-registry.ts';\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * Returns the current request's trace ID — always a 32-char lowercase hex string.\n *\n * With OTEL: the real OTEL trace ID (matches Jaeger/Honeycomb/Datadog).\n * Without OTEL: crypto.randomUUID() with hyphens stripped.\n *\n * Throws if called outside a request context (no ALS store).\n */\nexport function getTraceId(): string {\n const store = traceAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] getTraceId() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n return store.traceId;\n}\n\n/**\n * Returns the current OTEL span ID if available, undefined otherwise.\n */\nexport function getSpanId(): string | undefined {\n return traceAls.getStore()?.spanId;\n}\n\n// ─── Framework-Internal Helpers ───────────────────────────────────────────\n\n/**\n * Generate a 32-char lowercase hex ID from crypto.randomUUID().\n * Same format as OTEL trace IDs — zero-friction upgrade path.\n */\nexport function generateTraceId(): string {\n return randomUUID().replace(/-/g, '');\n}\n\n/**\n * Run a callback within a trace context. Used by the pipeline to establish\n * per-request ALS scope.\n */\nexport function runWithTraceId<T>(id: string, fn: () => T): T {\n return traceAls.run({ traceId: id }, fn);\n}\n\n/**\n * Replace the trace ID in the current ALS store. Used when OTEL creates\n * a root span and we want to switch from the UUID fallback to the real\n * OTEL trace ID.\n */\nexport function replaceTraceId(newTraceId: string, newSpanId?: string): void {\n const store = traceAls.getStore();\n if (store) {\n store.traceId = newTraceId;\n store.spanId = newSpanId;\n }\n}\n\n/**\n * Update the span ID in the current ALS store. Used when entering a new\n * OTEL span to keep log–trace correlation accurate.\n */\nexport function updateSpanId(newSpanId: string | undefined): void {\n const store = traceAls.getStore();\n if (store) {\n store.spanId = newSpanId;\n }\n}\n\n/**\n * Get the current trace store, or undefined if outside a request context.\n * Framework-internal — use getTraceId()/getSpanId() in user code.\n */\nexport function getTraceStore(): TraceStore | undefined {\n return traceAls.getStore();\n}\n\n// ─── Dev-Mode OTEL Auto-Init ─────────────────────────────────────────────\n\n/**\n * Well-known key marking dev tracing as initialized.\n *\n * The RSC entry module re-evaluates on every HMR invalidation and calls\n * initDevTracing() again — without this guard, each call would register a\n * fresh provider/processor and duplicate every span's output. Symbol.for()\n * survives module re-evaluation. See TIM-1067, B49.\n */\nconst DEV_TRACING_INIT_KEY = Symbol.for('timber.dev.tracing-initialized');\n\n/**\n * Initialize a minimal OTEL SDK in dev mode so spans are recorded and\n * fed to the DevSpanProcessor for dev log output.\n *\n * If the user already configured an OTEL SDK in register(), we add\n * our DevSpanProcessor alongside theirs. If no SDK is configured,\n * we create a BasicTracerProvider with our processor.\n *\n * Idempotent across module re-evaluations (HMR) — only the first call\n * registers. Only called in dev mode — zero overhead in production.\n */\nexport async function initDevTracing(\n config: import('../dev-tools/logger.ts').DevLoggerConfig\n): Promise<void> {\n const globals = globalThis as Record<symbol, unknown>;\n if (globals[DEV_TRACING_INIT_KEY]) return;\n\n const api = await getOtelApi();\n if (!api) return;\n\n let DevSpanProcessor: typeof import('../dev-tools/instrumentation.ts').DevSpanProcessor;\n let BasicTracerProvider: typeof import('@opentelemetry/sdk-trace-base').BasicTracerProvider;\n let AsyncLocalStorageContextManager: typeof import('@opentelemetry/context-async-hooks').AsyncLocalStorageContextManager;\n\n try {\n ({ DevSpanProcessor } = await import('../dev-tools/instrumentation.ts'));\n ({ BasicTracerProvider } = await import('@opentelemetry/sdk-trace-base'));\n ({ AsyncLocalStorageContextManager } = await import('@opentelemetry/context-async-hooks'));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[timber] Dev tracing disabled — failed to load OTEL packages:\\n ${msg}`);\n return;\n }\n\n const processor = new DevSpanProcessor(config);\n\n // Register a context manager so OTEL can propagate the active span\n // across async boundaries. Without this, startActiveSpan can't make\n // spans \"active\" — child spans get random trace IDs and getActiveSpan()\n // returns undefined.\n const contextManager = new AsyncLocalStorageContextManager();\n contextManager.enable();\n api.context.setGlobalContextManager(contextManager);\n\n // Create a minimal TracerProvider with our DevSpanProcessor.\n // If the user also configures an SDK in register(), their provider\n // will coexist — the global provider set last wins for new tracers,\n // but our processor captures all spans from the timber.js tracer.\n const provider = new BasicTracerProvider({\n spanProcessors: [processor],\n });\n api.trace.setGlobalTracerProvider(provider);\n\n // Reset cached tracer so next getTracer() picks up the new provider\n _tracer = undefined;\n\n globals[DEV_TRACING_INIT_KEY] = true;\n}\n\n// ─── Platform Tracer ─────────────────────────────────────────────────────\n\n/**\n * A native platform span. Mirrors the subset of the Cloudflare Workers\n * `Span` API that timber uses. See design/17-logging.md §\"Cloudflare Native\n * Spans\" and TIM-1135.\n */\nexport interface PlatformSpan {\n setAttribute(key: string, value: string | number | boolean): void;\n}\n\n/**\n * Adapter-provided native tracer. Callback-scoped like Cloudflare's\n * `tracing.enterSpan()` — the span starts when the callback is invoked and\n * ends when it returns or its promise settles. Nesting follows the\n * platform's async context.\n *\n * When registered, withSpan() wraps every framework span in a native span\n * *in addition to* the OTEL emission — dual emission, so external OTEL\n * collectors keep working alongside the platform's native trace view.\n *\n * Register via setPlatformTracer(), or by writing the\n * Symbol.for('timber:platform-tracer') key on globalThis from\n * adapter-generated entry code (what the Cloudflare _worker.js does).\n */\nexport interface PlatformTracer {\n enterSpan<T>(name: string, fn: (span: PlatformSpan) => T): T;\n}\n\n// globalThis + Symbol.for so a tracer registered by adapter-generated entry\n// code (a separate module instance) is visible in both the RSC and SSR\n// environments — same pattern as the cf-bindings ALS.\nconst PLATFORM_TRACER_KEY = Symbol.for('timber:platform-tracer');\n\n/** Register (or clear) the native platform tracer for this runtime. */\nexport function setPlatformTracer(tracer: PlatformTracer | undefined): void {\n (globalThis as Record<symbol, unknown>)[PLATFORM_TRACER_KEY] = tracer;\n}\n\n/** The registered native platform tracer, if any. */\nexport function getPlatformTracer(): PlatformTracer | undefined {\n return (globalThis as Record<symbol, unknown>)[PLATFORM_TRACER_KEY] as PlatformTracer | undefined;\n}\n\n// ─── OTEL Span Helpers ───────────────────────────────────────────────────\n\n/**\n * Attempt to get the @opentelemetry/api tracer. Returns undefined if the\n * package is not installed or no SDK is registered.\n *\n * timber.js depends on @opentelemetry/api as the vendor-neutral interface.\n * The API is a no-op by default — spans are only emitted when the developer\n * initializes an SDK in register().\n */\nlet _otelApi: typeof import('@opentelemetry/api') | null | undefined;\n\nasync function getOtelApi(): Promise<typeof import('@opentelemetry/api') | null> {\n if (_otelApi === undefined) {\n try {\n _otelApi = await import('@opentelemetry/api');\n } catch {\n _otelApi = null;\n }\n }\n return _otelApi;\n}\n\n/** OTEL tracer instance, lazily created. */\nlet _tracer: import('@opentelemetry/api').Tracer | null | undefined;\n\n/**\n * Get the timber.js OTEL tracer. Returns null if @opentelemetry/api is not available.\n */\nexport async function getTracer(): Promise<import('@opentelemetry/api').Tracer | null> {\n if (_tracer === undefined) {\n const api = await getOtelApi();\n if (api) {\n _tracer = api.trace.getTracer('timber.js');\n } else {\n _tracer = null;\n }\n }\n return _tracer;\n}\n\n/**\n * Run a function within a framework span. Composes two emission channels:\n *\n * - **Native platform span** — when an adapter registered a PlatformTracer\n * (Cloudflare Workers), the fn is wrapped in platformTracer.enterSpan()\n * so it appears in the platform's native trace view.\n * - **OTEL span** — when an OTEL SDK is active, the fn also runs inside an\n * OTEL span (dual emission). No SDK and no platform tracer = zero overhead.\n *\n * Automatically:\n * - Creates the span as a child of the current context\n * - Updates the ALS span ID for log–trace correlation\n * - Ends the span when the function completes\n * - Records exceptions on error (OTEL channel)\n */\nexport async function withSpan<T>(\n name: string,\n attributes: Record<string, string | number | boolean>,\n fn: () => T | Promise<T>\n): Promise<T> {\n const platformTracer = getPlatformTracer();\n if (!platformTracer) {\n return runOtelSpan(name, attributes, fn);\n }\n\n // Native platform span wraps the OTEL emission (dual emission). The\n // innermost platform span is tracked on the trace store so\n // setSpanAttribute() can reach it after creation — the platform API has\n // no getActiveSpan() equivalent.\n return platformTracer.enterSpan(name, async (span) => {\n for (const key of Object.keys(attributes)) {\n span.setAttribute(key, attributes[key]);\n }\n const store = traceAls.getStore();\n const prevPlatformSpan = store?.platformSpan;\n if (store) store.platformSpan = span;\n try {\n return await runOtelSpan(name, attributes, fn);\n } finally {\n if (store) store.platformSpan = prevPlatformSpan;\n }\n });\n}\n\n/** The OTEL half of withSpan() — no-op passthrough when no SDK is active. */\nasync function runOtelSpan<T>(\n name: string,\n attributes: Record<string, string | number | boolean>,\n fn: () => T | Promise<T>\n): Promise<T> {\n const tracer = await getTracer();\n if (!tracer) {\n return fn();\n }\n\n const api = (await getOtelApi())!;\n return tracer.startActiveSpan(name, { attributes }, async (span) => {\n const prevSpanId = getSpanId();\n updateSpanId(span.spanContext().spanId);\n try {\n const result = await fn();\n span.setStatus({ code: api.SpanStatusCode.OK });\n return result;\n } catch (error) {\n span.setStatus({ code: api.SpanStatusCode.ERROR });\n if (error instanceof Error) {\n span.recordException(error);\n }\n throw error;\n } finally {\n span.end();\n updateSpanId(prevSpanId);\n }\n });\n}\n\n/**\n * Set an attribute on the current active span (if any).\n * Used for setting span attributes after span creation (e.g. timber.result on access spans).\n */\nexport async function setSpanAttribute(\n key: string,\n value: string | number | boolean\n): Promise<void> {\n // Forward to the innermost active native platform span, if any.\n const platformSpan = traceAls.getStore()?.platformSpan;\n if (platformSpan) {\n platformSpan.setAttribute(key, value);\n }\n\n const api = await getOtelApi();\n if (!api) return;\n\n const activeSpan = api.trace.getActiveSpan();\n if (activeSpan) {\n activeSpan.setAttribute(key, value);\n }\n}\n\n/**\n * Add a span event to the current active span (if any).\n * Used for timber.cache HIT/MISS events — recorded as span events, not child spans.\n */\nexport async function addSpanEvent(\n name: string,\n attributes?: Record<string, string | number | boolean>\n): Promise<void> {\n const api = await getOtelApi();\n if (!api) return;\n\n const activeSpan = api.trace.getActiveSpan();\n if (activeSpan) {\n activeSpan.addEvent(name, attributes);\n }\n}\n\n/**\n * Fire-and-forget span event — no await, no microtask overhead.\n *\n * Used on the cache hot path where awaiting addSpanEvent creates an\n * unnecessary microtask per cache operation. If OTEL is not loaded yet,\n * the event is silently dropped (acceptable for diagnostics).\n *\n * See TIM-370 for perf motivation.\n */\nexport function addSpanEventSync(\n name: string,\n attributes?: Record<string, string | number | boolean>\n): void {\n // Fast path: if OTEL API hasn't been loaded yet, skip entirely.\n // _otelApi is undefined (not yet loaded), null (failed to load), or the module.\n if (!_otelApi) return;\n\n const activeSpan = _otelApi.trace.getActiveSpan();\n if (activeSpan) {\n activeSpan.addEvent(name, attributes);\n }\n}\n\n/**\n * Try to extract the OTEL trace ID from the current active span context.\n * Returns undefined if OTEL is not active or no span exists.\n */\nexport async function getOtelTraceId(): Promise<{ traceId: string; spanId: string } | undefined> {\n const api = await getOtelApi();\n if (!api) return undefined;\n\n const activeSpan = api.trace.getActiveSpan();\n if (!activeSpan) return undefined;\n\n const ctx = activeSpan.spanContext();\n // OTEL uses \"0000000000000000\" as invalid trace IDs\n if (!ctx.traceId || ctx.traceId === '00000000000000000000000000000000') {\n return undefined;\n }\n\n return { traceId: ctx.traceId, spanId: ctx.spanId };\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 * DefaultLogger — human-readable stderr logging when no custom logger is configured.\n *\n * Ships as the fallback so production deployments always have error visibility,\n * even without an `instrumentation.ts` logger export. Output is one line per\n * event, designed for `fly logs`, `kubectl logs`, Cloudflare dashboard tails, etc.\n *\n * Format:\n * [timber] ERROR message key=value key=value trace_id=4bf92f35\n * [timber] WARN message key=value key=value trace_id=4bf92f35\n * [timber] INFO message method=GET path=/dashboard status=200 durationMs=43 trace_id=4bf92f35\n *\n * Behavior:\n * - Suppressed entirely in dev mode (dev logging handles all output)\n * - `debug` suppressed unless TIMBER_DEBUG is set\n * - Replaced entirely when a custom logger is set via `setLogger()`\n *\n * See design/17-logging.md §\"DefaultLogger\"\n */\n\nimport { isDevMode, isDebug } from './debug.ts';\nimport { formatSsrError } from './error-formatter.ts';\nimport type { TimberLogger } from './logger.ts';\n\n/**\n * Format data fields as `key=value` pairs for human-readable output.\n * - `error` key is serialized via formatSsrError for stack trace cleanup\n * - `trace_id` is truncated to 8 chars for readability (full ID in OTEL)\n * - Other values are stringified inline\n */\nfunction formatDataFields(data?: Record<string, unknown>): string {\n if (!data) return '';\n\n const parts: string[] = [];\n let traceId: string | undefined;\n\n for (const [key, value] of Object.entries(data)) {\n if (key === 'trace_id') {\n // Defer trace_id to the end\n traceId = typeof value === 'string' ? value : String(value);\n continue;\n }\n if (key === 'error') {\n // Serialize errors with formatSsrError for clean output\n parts.push(`error=${formatSsrError(value)}`);\n continue;\n }\n if (value === undefined || value === null) continue;\n parts.push(`${key}=${value}`);\n }\n\n // trace_id always last, truncated to 8 chars for readability\n if (traceId) {\n parts.push(`trace_id=${traceId.slice(0, 8)}`);\n }\n\n return parts.length > 0 ? ' ' + parts.join(' ') : '';\n}\n\n/** Pad level string to fixed width for alignment. */\nfunction padLevel(level: string): string {\n return level.padEnd(5);\n}\n\nexport function createDefaultLogger(): TimberLogger {\n return {\n error(msg: string, data?: Record<string, unknown>): void {\n // Errors are ALWAYS logged, including dev mode. Suppressing errors\n // in dev causes silent 500s with no stack trace, making route.ts\n // and render errors impossible to debug. See TIM-555.\n const fields = formatDataFields(data);\n process.stderr.write(`[timber] ${padLevel('ERROR')} ${msg}${fields}\\n`);\n },\n\n warn(msg: string, data?: Record<string, unknown>): void {\n // Warnings are always logged — same rationale as errors.\n const fields = formatDataFields(data);\n process.stderr.write(`[timber] ${padLevel('WARN')} ${msg}${fields}\\n`);\n },\n\n info(msg: string, data?: Record<string, unknown>): void {\n // info is suppressed by default — per-request lines are too noisy\n // without a custom logger. Enable with TIMBER_DEBUG.\n if (isDevMode()) return;\n if (!isDebug()) return;\n const fields = formatDataFields(data);\n process.stderr.write(`[timber] ${padLevel('INFO')} ${msg}${fields}\\n`);\n },\n\n debug(msg: string, data?: Record<string, unknown>): void {\n // debug is suppressed in dev (dev logger handles it) and in\n // production unless TIMBER_DEBUG is explicitly set.\n if (isDevMode()) return;\n if (!isDebug()) return;\n const fields = formatDataFields(data);\n process.stderr.write(`[timber] ${padLevel('DEBUG')} ${msg}${fields}\\n`);\n },\n };\n}\n","/**\n * Logger — structured logging with environment-aware formatting.\n *\n * timber.js ships a DefaultLogger that writes human-readable lines to stderr\n * in production. Users can export a custom logger from instrumentation.ts to\n * replace it with pino, winston, or any TimberLogger-compatible object.\n *\n * See design/17-logging.md §\"Production Logging\"\n */\n\nimport { getTraceStore } from './tracing.ts';\nimport { createDefaultLogger } from './default-logger.ts';\nimport { isDevMode } from './debug.ts';\nimport { isControlFlowSignal } from './primitives.ts';\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\n// Initialize with DefaultLogger so production errors are never silent.\n// Replaced when setLogger() is called from instrumentation.ts.\nlet _logger: TimberLogger = createDefaultLogger();\n\n/**\n * Set the user-provided logger. Called by the instrumentation loader\n * when it finds a `logger` export in instrumentation.ts. Replaces\n * the DefaultLogger entirely.\n */\nexport function setLogger(logger: TimberLogger): void {\n _logger = logger;\n}\n\n/**\n * Get the current logger. Always non-null — returns DefaultLogger when\n * no custom logger is configured.\n */\nexport function getLogger(): TimberLogger {\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 (isControlFlowSignal(data.error)) return;\n _logger.error('unhandled error in middleware phase', withTraceContext(data));\n}\n\n/** Log unhandled render-phase error. Level: error. */\nexport function logRenderError(data: {\n method: string;\n path: string;\n error: unknown;\n errorId?: string;\n}): void {\n if (isControlFlowSignal(data.error)) return;\n _logger.error('unhandled render-phase error', withTraceContext(data));\n}\n\n/** Log proxy.ts uncaught error. Level: error. */\nexport function logProxyError(data: { error: unknown }): void {\n _logger.error('proxy.ts threw uncaught error', withTraceContext(data));\n}\n\n/** Log unhandled error in server action. Level: error. */\nexport function logActionError(data: { method: string; path: string; error: unknown }): void {\n if (isControlFlowSignal(data.error)) return;\n _logger.error('unhandled server action error', withTraceContext(data));\n}\n\n/** Log unhandled error in route handler. Level: error. */\nexport function logRouteError(data: { method: string; path: string; error: unknown }): void {\n if (isControlFlowSignal(data.error)) return;\n _logger.error('unhandled route handler error', withTraceContext(data));\n}\n\n/** Log SSR streaming error (post-shell). Level: error. */\nexport function logStreamingError(data: { error: unknown }): void {\n _logger.error('SSR streaming error (post-shell)', withTraceContext(data));\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// ─── Swallow Helper ───────────────────────────────────────────────────────\n\n/**\n * Log an intentionally swallowed error. Provides observability into catch\n * blocks that are deliberately empty — the error is consumed, never rethrown.\n *\n * Default level: `warn` in dev (so the overlay surfaces patterns), `debug`\n * in production (low noise unless TIMBER_DEBUG is set). Pass `opts.level`\n * to override.\n *\n * **Infallible** — swallow() itself never throws, even if the logger is\n * broken. A thrown swallow would turn a benign catch into a crash.\n */\nexport function swallow(err: unknown, reason: string, opts?: { level?: 'debug' | 'warn' }): void {\n try {\n const level = opts?.level ?? (isDevMode() ? 'warn' : 'debug');\n _logger[level](`swallowed: ${reason}`, withTraceContext({ error: err }));\n } catch {\n // swallow() must never throw.\n }\n}\n"],"mappings":"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"}