@timber-js/app 0.2.0-alpha.9 → 0.2.0-alpha.90

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 (610) hide show
  1. package/dist/_chunks/actions-DLnUaR65.js +421 -0
  2. package/dist/_chunks/actions-DLnUaR65.js.map +1 -0
  3. package/dist/_chunks/{als-registry-B7DbZ2hS.js → als-registry-HS0LGUl2.js} +1 -1
  4. package/dist/_chunks/als-registry-HS0LGUl2.js.map +1 -0
  5. package/dist/_chunks/chunk-BYIpzuS7.js +39 -0
  6. package/dist/_chunks/{debug-gwlJkDuf.js → debug-ECi_61pb.js} +2 -2
  7. package/dist/_chunks/debug-ECi_61pb.js.map +1 -0
  8. package/dist/_chunks/define-C77ScO0m.js +106 -0
  9. package/dist/_chunks/define-C77ScO0m.js.map +1 -0
  10. package/dist/_chunks/define-Itxvcd7F.js +199 -0
  11. package/dist/_chunks/define-Itxvcd7F.js.map +1 -0
  12. package/dist/_chunks/define-cookie-BowvzoP0.js +94 -0
  13. package/dist/_chunks/define-cookie-BowvzoP0.js.map +1 -0
  14. package/dist/_chunks/{format-DviM89f0.js → dev-warnings-DpGRGoDi.js} +5 -44
  15. package/dist/_chunks/dev-warnings-DpGRGoDi.js.map +1 -0
  16. package/dist/_chunks/format-CYBGxKtc.js +14 -0
  17. package/dist/_chunks/format-CYBGxKtc.js.map +1 -0
  18. package/dist/_chunks/{interception-BOoWmLUA.js → interception-DRlhJWbu.js} +219 -97
  19. package/dist/_chunks/interception-DRlhJWbu.js.map +1 -0
  20. package/dist/_chunks/merge-search-params-Cm_KIWDX.js +41 -0
  21. package/dist/_chunks/merge-search-params-Cm_KIWDX.js.map +1 -0
  22. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → metadata-routes-DS3eKNmf.js} +1 -1
  23. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js.map → metadata-routes-DS3eKNmf.js.map} +1 -1
  24. package/dist/_chunks/request-context-CK5tZqIP.js +478 -0
  25. package/dist/_chunks/request-context-CK5tZqIP.js.map +1 -0
  26. package/dist/_chunks/schema-bridge-C3xl_vfb.js +86 -0
  27. package/dist/_chunks/schema-bridge-C3xl_vfb.js.map +1 -0
  28. package/dist/_chunks/segment-classify-BDNn6EzD.js +65 -0
  29. package/dist/_chunks/segment-classify-BDNn6EzD.js.map +1 -0
  30. package/dist/_chunks/segment-context-fHFLF1PE.js +34 -0
  31. package/dist/_chunks/segment-context-fHFLF1PE.js.map +1 -0
  32. package/dist/_chunks/{ssr-data-MjmprTmO.js → ssr-data-DzuI0bIV.js} +1 -1
  33. package/dist/_chunks/{ssr-data-MjmprTmO.js.map → ssr-data-DzuI0bIV.js.map} +1 -1
  34. package/dist/_chunks/stale-reload-BX5gL1r-.js +64 -0
  35. package/dist/_chunks/stale-reload-BX5gL1r-.js.map +1 -0
  36. package/dist/_chunks/{tracing-CemImE6h.js → tracing-CCYbKn5n.js} +60 -9
  37. package/dist/_chunks/tracing-CCYbKn5n.js.map +1 -0
  38. package/dist/_chunks/use-params-Br9YSUFV.js +295 -0
  39. package/dist/_chunks/use-params-Br9YSUFV.js.map +1 -0
  40. package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-BiV5GJgm.js} +7 -4
  41. package/dist/_chunks/use-query-states-BiV5GJgm.js.map +1 -0
  42. package/dist/adapters/cloudflare-dev.d.ts +109 -0
  43. package/dist/adapters/cloudflare-dev.d.ts.map +1 -0
  44. package/dist/adapters/cloudflare-dev.js +73 -0
  45. package/dist/adapters/cloudflare-dev.js.map +1 -0
  46. package/dist/adapters/cloudflare-kv-cache.d.ts +64 -0
  47. package/dist/adapters/cloudflare-kv-cache.d.ts.map +1 -0
  48. package/dist/adapters/cloudflare-kv-cache.js +95 -0
  49. package/dist/adapters/cloudflare-kv-cache.js.map +1 -0
  50. package/dist/adapters/cloudflare.d.ts +148 -12
  51. package/dist/adapters/cloudflare.d.ts.map +1 -1
  52. package/dist/adapters/cloudflare.js +135 -11
  53. package/dist/adapters/cloudflare.js.map +1 -1
  54. package/dist/adapters/compress-module.d.ts.map +1 -1
  55. package/dist/adapters/nitro.d.ts +17 -1
  56. package/dist/adapters/nitro.d.ts.map +1 -1
  57. package/dist/adapters/nitro.js +56 -13
  58. package/dist/adapters/nitro.js.map +1 -1
  59. package/dist/cache/cache-api.d.ts +24 -0
  60. package/dist/cache/cache-api.d.ts.map +1 -0
  61. package/dist/cache/handler-store.d.ts +31 -0
  62. package/dist/cache/handler-store.d.ts.map +1 -0
  63. package/dist/cache/index.d.ts +23 -7
  64. package/dist/cache/index.d.ts.map +1 -1
  65. package/dist/cache/index.js +142 -80
  66. package/dist/cache/index.js.map +1 -1
  67. package/dist/cache/singleflight.d.ts +18 -1
  68. package/dist/cache/singleflight.d.ts.map +1 -1
  69. package/dist/cache/sizeof.d.ts +22 -0
  70. package/dist/cache/sizeof.d.ts.map +1 -0
  71. package/dist/cache/timber-cache.d.ts +1 -1
  72. package/dist/cache/timber-cache.d.ts.map +1 -1
  73. package/dist/cli.d.ts +6 -1
  74. package/dist/cli.d.ts.map +1 -1
  75. package/dist/cli.js +8 -3
  76. package/dist/cli.js.map +1 -1
  77. package/dist/client/browser-dev.d.ts +27 -1
  78. package/dist/client/browser-dev.d.ts.map +1 -1
  79. package/dist/client/browser-entry/action-dispatch.d.ts +17 -0
  80. package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -0
  81. package/dist/client/browser-entry/hmr.d.ts +21 -0
  82. package/dist/client/browser-entry/hmr.d.ts.map +1 -0
  83. package/dist/client/browser-entry/hydrate.d.ts +46 -0
  84. package/dist/client/browser-entry/hydrate.d.ts.map +1 -0
  85. package/dist/client/browser-entry/index.d.ts +30 -0
  86. package/dist/client/browser-entry/index.d.ts.map +1 -0
  87. package/dist/client/browser-entry/post-hydration.d.ts +26 -0
  88. package/dist/client/browser-entry/post-hydration.d.ts.map +1 -0
  89. package/dist/client/browser-entry/router-init.d.ts +23 -0
  90. package/dist/client/browser-entry/router-init.d.ts.map +1 -0
  91. package/dist/client/browser-entry/rsc-stream.d.ts +24 -0
  92. package/dist/client/browser-entry/rsc-stream.d.ts.map +1 -0
  93. package/dist/client/browser-entry/scroll.d.ts +19 -0
  94. package/dist/client/browser-entry/scroll.d.ts.map +1 -0
  95. package/dist/client/error-boundary.d.ts +12 -5
  96. package/dist/client/error-boundary.d.ts.map +1 -1
  97. package/dist/client/error-boundary.js +10 -4
  98. package/dist/client/error-boundary.js.map +1 -1
  99. package/dist/client/error-reconstituter.d.ts +54 -0
  100. package/dist/client/error-reconstituter.d.ts.map +1 -0
  101. package/dist/client/form.d.ts +6 -3
  102. package/dist/client/form.d.ts.map +1 -1
  103. package/dist/client/history.d.ts +19 -4
  104. package/dist/client/history.d.ts.map +1 -1
  105. package/dist/client/index.d.ts +9 -21
  106. package/dist/client/index.d.ts.map +1 -1
  107. package/dist/client/index.js +229 -1018
  108. package/dist/client/index.js.map +1 -1
  109. package/dist/client/internal.d.ts +18 -0
  110. package/dist/client/internal.d.ts.map +1 -0
  111. package/dist/client/internal.js +890 -0
  112. package/dist/client/internal.js.map +1 -0
  113. package/dist/client/link-pending-store.d.ts +63 -0
  114. package/dist/client/link-pending-store.d.ts.map +1 -0
  115. package/dist/client/link.d.ts +62 -55
  116. package/dist/client/link.d.ts.map +1 -1
  117. package/dist/client/nav-link-store.d.ts +36 -0
  118. package/dist/client/nav-link-store.d.ts.map +1 -0
  119. package/dist/client/navigation-api-types.d.ts +90 -0
  120. package/dist/client/navigation-api-types.d.ts.map +1 -0
  121. package/dist/client/navigation-api.d.ts +115 -0
  122. package/dist/client/navigation-api.d.ts.map +1 -0
  123. package/dist/client/navigation-context.d.ts +13 -2
  124. package/dist/client/navigation-context.d.ts.map +1 -1
  125. package/dist/client/{transition-root.d.ts → navigation-root.d.ts} +42 -8
  126. package/dist/client/navigation-root.d.ts.map +1 -0
  127. package/dist/client/nuqs-adapter.d.ts.map +1 -1
  128. package/dist/client/router-ref.d.ts +1 -1
  129. package/dist/client/router.d.ts +70 -4
  130. package/dist/client/router.d.ts.map +1 -1
  131. package/dist/client/rsc-fetch.d.ts +38 -3
  132. package/dist/client/rsc-fetch.d.ts.map +1 -1
  133. package/dist/client/segment-cache.d.ts +1 -1
  134. package/dist/client/segment-cache.d.ts.map +1 -1
  135. package/dist/client/segment-outlet.d.ts +63 -0
  136. package/dist/client/segment-outlet.d.ts.map +1 -0
  137. package/dist/client/ssr-data.d.ts +13 -4
  138. package/dist/client/ssr-data.d.ts.map +1 -1
  139. package/dist/client/stale-reload.d.ts +15 -0
  140. package/dist/client/stale-reload.d.ts.map +1 -1
  141. package/dist/client/top-loader.d.ts +5 -5
  142. package/dist/client/top-loader.d.ts.map +1 -1
  143. package/dist/client/use-link-status.d.ts +5 -5
  144. package/dist/client/use-link-status.d.ts.map +1 -1
  145. package/dist/client/use-params.d.ts +6 -4
  146. package/dist/client/use-params.d.ts.map +1 -1
  147. package/dist/client/{use-navigation-pending.d.ts → use-pending-navigation.d.ts} +4 -4
  148. package/dist/client/use-pending-navigation.d.ts.map +1 -0
  149. package/dist/client/use-query-states.d.ts +1 -1
  150. package/dist/client/use-query-states.d.ts.map +1 -1
  151. package/dist/client/use-router.d.ts +1 -1
  152. package/dist/codec.d.ts +33 -0
  153. package/dist/codec.d.ts.map +1 -0
  154. package/dist/codec.js +2 -0
  155. package/dist/config-types.d.ts +266 -0
  156. package/dist/config-types.d.ts.map +1 -0
  157. package/dist/config-validation.d.ts +51 -0
  158. package/dist/config-validation.d.ts.map +1 -0
  159. package/dist/content/index.d.ts +1 -10
  160. package/dist/content/index.d.ts.map +1 -1
  161. package/dist/content/index.js +0 -2
  162. package/dist/cookies/define-cookie.d.ts +35 -14
  163. package/dist/cookies/define-cookie.d.ts.map +1 -1
  164. package/dist/cookies/index.js +1 -83
  165. package/dist/fonts/bundle.d.ts +48 -0
  166. package/dist/fonts/bundle.d.ts.map +1 -0
  167. package/dist/fonts/css.d.ts +1 -0
  168. package/dist/fonts/css.d.ts.map +1 -1
  169. package/dist/fonts/dev-middleware.d.ts +22 -0
  170. package/dist/fonts/dev-middleware.d.ts.map +1 -0
  171. package/dist/fonts/pipeline.d.ts +138 -0
  172. package/dist/fonts/pipeline.d.ts.map +1 -0
  173. package/dist/fonts/transform.d.ts +72 -0
  174. package/dist/fonts/transform.d.ts.map +1 -0
  175. package/dist/fonts/types.d.ts +45 -1
  176. package/dist/fonts/types.d.ts.map +1 -1
  177. package/dist/fonts/virtual-modules.d.ts +59 -0
  178. package/dist/fonts/virtual-modules.d.ts.map +1 -0
  179. package/dist/index.d.ts +45 -190
  180. package/dist/index.d.ts.map +1 -1
  181. package/dist/index.js +4294 -2453
  182. package/dist/index.js.map +1 -1
  183. package/dist/plugin-context.d.ts +107 -0
  184. package/dist/plugin-context.d.ts.map +1 -0
  185. package/dist/plugins/adapter-build.d.ts +1 -1
  186. package/dist/plugins/adapter-build.d.ts.map +1 -1
  187. package/dist/plugins/build-manifest.d.ts +2 -2
  188. package/dist/plugins/build-manifest.d.ts.map +1 -1
  189. package/dist/plugins/build-report.d.ts +3 -3
  190. package/dist/plugins/build-report.d.ts.map +1 -1
  191. package/dist/plugins/client-chunks.d.ts +32 -0
  192. package/dist/plugins/client-chunks.d.ts.map +1 -0
  193. package/dist/plugins/content.d.ts +1 -1
  194. package/dist/plugins/content.d.ts.map +1 -1
  195. package/dist/plugins/dev-404-page.d.ts +56 -0
  196. package/dist/plugins/dev-404-page.d.ts.map +1 -0
  197. package/dist/plugins/dev-browser-logs.d.ts +84 -0
  198. package/dist/plugins/dev-browser-logs.d.ts.map +1 -0
  199. package/dist/plugins/dev-error-overlay.d.ts +49 -9
  200. package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
  201. package/dist/plugins/dev-error-page.d.ts +58 -0
  202. package/dist/plugins/dev-error-page.d.ts.map +1 -0
  203. package/dist/plugins/dev-logs.d.ts +1 -1
  204. package/dist/plugins/dev-logs.d.ts.map +1 -1
  205. package/dist/plugins/dev-server.d.ts +1 -1
  206. package/dist/plugins/dev-server.d.ts.map +1 -1
  207. package/dist/plugins/dev-terminal-error.d.ts +28 -0
  208. package/dist/plugins/dev-terminal-error.d.ts.map +1 -0
  209. package/dist/plugins/entries.d.ts +1 -1
  210. package/dist/plugins/entries.d.ts.map +1 -1
  211. package/dist/plugins/fonts.d.ts +17 -73
  212. package/dist/plugins/fonts.d.ts.map +1 -1
  213. package/dist/plugins/mdx.d.ts +1 -1
  214. package/dist/plugins/mdx.d.ts.map +1 -1
  215. package/dist/plugins/routing.d.ts +1 -1
  216. package/dist/plugins/routing.d.ts.map +1 -1
  217. package/dist/plugins/server-bundle.d.ts.map +1 -1
  218. package/dist/plugins/shims.d.ts +6 -5
  219. package/dist/plugins/shims.d.ts.map +1 -1
  220. package/dist/plugins/static-build.d.ts +4 -4
  221. package/dist/plugins/static-build.d.ts.map +1 -1
  222. package/dist/routing/codegen.d.ts +2 -2
  223. package/dist/routing/codegen.d.ts.map +1 -1
  224. package/dist/routing/convention-lint.d.ts +41 -0
  225. package/dist/routing/convention-lint.d.ts.map +1 -0
  226. package/dist/routing/index.d.ts +2 -0
  227. package/dist/routing/index.d.ts.map +1 -1
  228. package/dist/routing/index.js +3 -2
  229. package/dist/routing/scanner.d.ts.map +1 -1
  230. package/dist/routing/segment-classify.d.ts +46 -0
  231. package/dist/routing/segment-classify.d.ts.map +1 -0
  232. package/dist/routing/status-file-lint.d.ts +2 -1
  233. package/dist/routing/status-file-lint.d.ts.map +1 -1
  234. package/dist/routing/types.d.ts +16 -4
  235. package/dist/routing/types.d.ts.map +1 -1
  236. package/dist/rsc-runtime/rsc.d.ts +1 -1
  237. package/dist/rsc-runtime/rsc.d.ts.map +1 -1
  238. package/dist/rsc-runtime/ssr.d.ts +12 -0
  239. package/dist/rsc-runtime/ssr.d.ts.map +1 -1
  240. package/dist/schema-bridge.d.ts +76 -0
  241. package/dist/schema-bridge.d.ts.map +1 -0
  242. package/dist/search-params/define.d.ts +139 -0
  243. package/dist/search-params/define.d.ts.map +1 -0
  244. package/dist/search-params/index.d.ts +4 -7
  245. package/dist/search-params/index.d.ts.map +1 -1
  246. package/dist/search-params/index.js +32 -441
  247. package/dist/search-params/index.js.map +1 -1
  248. package/dist/search-params/registry.d.ts +2 -2
  249. package/dist/search-params/registry.d.ts.map +1 -1
  250. package/dist/search-params/wrappers.d.ts +53 -0
  251. package/dist/search-params/wrappers.d.ts.map +1 -0
  252. package/dist/segment-params/define.d.ts +78 -0
  253. package/dist/segment-params/define.d.ts.map +1 -0
  254. package/dist/segment-params/index.d.ts +3 -0
  255. package/dist/segment-params/index.d.ts.map +1 -0
  256. package/dist/segment-params/index.js +2 -0
  257. package/dist/server/access-gate.d.ts +4 -0
  258. package/dist/server/access-gate.d.ts.map +1 -1
  259. package/dist/server/action-client.d.ts +41 -6
  260. package/dist/server/action-client.d.ts.map +1 -1
  261. package/dist/server/action-encryption.d.ts +76 -0
  262. package/dist/server/action-encryption.d.ts.map +1 -0
  263. package/dist/server/action-handler.d.ts +7 -0
  264. package/dist/server/action-handler.d.ts.map +1 -1
  265. package/dist/server/actions.d.ts +3 -6
  266. package/dist/server/actions.d.ts.map +1 -1
  267. package/dist/server/als-registry.d.ts +32 -4
  268. package/dist/server/als-registry.d.ts.map +1 -1
  269. package/dist/server/build-manifest.d.ts +2 -2
  270. package/dist/server/build-manifest.d.ts.map +1 -1
  271. package/dist/server/debug.d.ts +1 -1
  272. package/dist/server/default-logger.d.ts +22 -0
  273. package/dist/server/default-logger.d.ts.map +1 -0
  274. package/dist/server/deny-page-resolver.d.ts +52 -0
  275. package/dist/server/deny-page-resolver.d.ts.map +1 -0
  276. package/dist/server/deny-renderer.d.ts.map +1 -1
  277. package/dist/server/dev-holding-server.d.ts +52 -0
  278. package/dist/server/dev-holding-server.d.ts.map +1 -0
  279. package/dist/server/dev-source-map.d.ts +22 -0
  280. package/dist/server/dev-source-map.d.ts.map +1 -0
  281. package/dist/server/dev-warnings.d.ts +1 -21
  282. package/dist/server/dev-warnings.d.ts.map +1 -1
  283. package/dist/server/early-hints.d.ts +13 -5
  284. package/dist/server/early-hints.d.ts.map +1 -1
  285. package/dist/server/error-boundary-wrapper.d.ts +7 -1
  286. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  287. package/dist/server/fallback-error.d.ts +12 -7
  288. package/dist/server/fallback-error.d.ts.map +1 -1
  289. package/dist/server/flight-injection-state.d.ts +66 -0
  290. package/dist/server/flight-injection-state.d.ts.map +1 -0
  291. package/dist/server/flight-scripts.d.ts +42 -0
  292. package/dist/server/flight-scripts.d.ts.map +1 -0
  293. package/dist/server/flush.d.ts.map +1 -1
  294. package/dist/server/form-data.d.ts +29 -0
  295. package/dist/server/form-data.d.ts.map +1 -1
  296. package/dist/server/html-injectors.d.ts +51 -11
  297. package/dist/server/html-injectors.d.ts.map +1 -1
  298. package/dist/server/index.d.ts +5 -43
  299. package/dist/server/index.d.ts.map +1 -1
  300. package/dist/server/index.js +195 -2800
  301. package/dist/server/index.js.map +1 -1
  302. package/dist/server/internal.d.ts +46 -0
  303. package/dist/server/internal.d.ts.map +1 -0
  304. package/dist/server/internal.js +2900 -0
  305. package/dist/server/internal.js.map +1 -0
  306. package/dist/server/logger.d.ts +25 -7
  307. package/dist/server/logger.d.ts.map +1 -1
  308. package/dist/server/middleware-runner.d.ts +19 -4
  309. package/dist/server/middleware-runner.d.ts.map +1 -1
  310. package/dist/server/node-stream-transforms.d.ts +113 -0
  311. package/dist/server/node-stream-transforms.d.ts.map +1 -0
  312. package/dist/server/page-deny-boundary.d.ts +31 -0
  313. package/dist/server/page-deny-boundary.d.ts.map +1 -0
  314. package/dist/server/pipeline-interception.d.ts +1 -1
  315. package/dist/server/pipeline-interception.d.ts.map +1 -1
  316. package/dist/server/pipeline-metadata.d.ts +6 -0
  317. package/dist/server/pipeline-metadata.d.ts.map +1 -1
  318. package/dist/server/pipeline.d.ts +52 -10
  319. package/dist/server/pipeline.d.ts.map +1 -1
  320. package/dist/server/primitives.d.ts +69 -18
  321. package/dist/server/primitives.d.ts.map +1 -1
  322. package/dist/server/render-timeout.d.ts +51 -0
  323. package/dist/server/render-timeout.d.ts.map +1 -0
  324. package/dist/server/request-context.d.ts +112 -43
  325. package/dist/server/request-context.d.ts.map +1 -1
  326. package/dist/server/route-element-builder.d.ts +27 -1
  327. package/dist/server/route-element-builder.d.ts.map +1 -1
  328. package/dist/server/route-handler.d.ts.map +1 -1
  329. package/dist/server/route-matcher.d.ts +16 -2
  330. package/dist/server/route-matcher.d.ts.map +1 -1
  331. package/dist/server/rsc-entry/api-handler.d.ts +2 -2
  332. package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
  333. package/dist/server/rsc-entry/error-renderer.d.ts +26 -13
  334. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  335. package/dist/server/rsc-entry/helpers.d.ts +48 -5
  336. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  337. package/dist/server/rsc-entry/index.d.ts +20 -3
  338. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  339. package/dist/server/rsc-entry/rsc-payload.d.ts +3 -3
  340. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  341. package/dist/server/rsc-entry/rsc-stream.d.ts +14 -1
  342. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  343. package/dist/server/rsc-entry/ssr-bridge.d.ts +1 -1
  344. package/dist/server/rsc-entry/ssr-bridge.d.ts.map +1 -1
  345. package/dist/server/rsc-entry/ssr-renderer.d.ts +19 -4
  346. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  347. package/dist/server/safe-load.d.ts +46 -0
  348. package/dist/server/safe-load.d.ts.map +1 -0
  349. package/dist/server/sensitive-fields.d.ts +74 -0
  350. package/dist/server/sensitive-fields.d.ts.map +1 -0
  351. package/dist/server/sitemap-generator.d.ts +129 -0
  352. package/dist/server/sitemap-generator.d.ts.map +1 -0
  353. package/dist/server/sitemap-handler.d.ts +22 -0
  354. package/dist/server/sitemap-handler.d.ts.map +1 -0
  355. package/dist/server/slot-resolver.d.ts +1 -1
  356. package/dist/server/slot-resolver.d.ts.map +1 -1
  357. package/dist/server/ssr-entry.d.ts +23 -0
  358. package/dist/server/ssr-entry.d.ts.map +1 -1
  359. package/dist/server/ssr-render.d.ts +39 -21
  360. package/dist/server/ssr-render.d.ts.map +1 -1
  361. package/dist/server/ssr-wrappers.d.ts +50 -0
  362. package/dist/server/ssr-wrappers.d.ts.map +1 -0
  363. package/dist/server/status-code-resolver.d.ts +1 -1
  364. package/dist/server/status-code-resolver.d.ts.map +1 -1
  365. package/dist/server/stream-utils.d.ts +36 -0
  366. package/dist/server/stream-utils.d.ts.map +1 -0
  367. package/dist/server/tracing.d.ts +4 -4
  368. package/dist/server/tracing.d.ts.map +1 -1
  369. package/dist/server/tree-builder.d.ts +22 -19
  370. package/dist/server/tree-builder.d.ts.map +1 -1
  371. package/dist/server/types.d.ts +1 -4
  372. package/dist/server/types.d.ts.map +1 -1
  373. package/dist/server/version-skew.d.ts +61 -0
  374. package/dist/server/version-skew.d.ts.map +1 -0
  375. package/dist/shared/merge-search-params.d.ts +22 -0
  376. package/dist/shared/merge-search-params.d.ts.map +1 -0
  377. package/dist/shims/font-google.d.ts +1 -1
  378. package/dist/shims/font-google.d.ts.map +1 -1
  379. package/dist/shims/font-google.js +42 -0
  380. package/dist/shims/font-google.js.map +1 -0
  381. package/dist/shims/font-local.d.ts +26 -0
  382. package/dist/shims/font-local.d.ts.map +1 -0
  383. package/dist/shims/font-local.js +20 -0
  384. package/dist/shims/font-local.js.map +1 -0
  385. package/dist/shims/headers.d.ts +2 -1
  386. package/dist/shims/headers.d.ts.map +1 -1
  387. package/dist/shims/navigation-client.d.ts +1 -1
  388. package/dist/shims/navigation-client.d.ts.map +1 -1
  389. package/dist/shims/navigation.d.ts +3 -2
  390. package/dist/shims/navigation.d.ts.map +1 -1
  391. package/dist/utils/directive-parser.d.ts +5 -2
  392. package/dist/utils/directive-parser.d.ts.map +1 -1
  393. package/dist/utils/state-machine.d.ts +80 -0
  394. package/dist/utils/state-machine.d.ts.map +1 -0
  395. package/package.json +51 -16
  396. package/src/adapters/cloudflare-dev.ts +177 -0
  397. package/src/adapters/cloudflare-kv-cache.ts +142 -0
  398. package/src/adapters/cloudflare.ts +342 -28
  399. package/src/adapters/compress-module.ts +24 -4
  400. package/src/adapters/nitro.ts +52 -8
  401. package/src/adapters/wrangler.d.ts +7 -0
  402. package/src/cache/cache-api.ts +38 -0
  403. package/src/cache/handler-store.ts +68 -0
  404. package/src/cache/index.ts +81 -18
  405. package/src/cache/singleflight.ts +62 -4
  406. package/src/cache/sizeof.ts +31 -0
  407. package/src/cache/timber-cache.ts +24 -20
  408. package/src/cli.ts +16 -6
  409. package/src/client/browser-dev.ts +128 -1
  410. package/src/client/browser-entry/action-dispatch.ts +116 -0
  411. package/src/client/browser-entry/hmr.ts +81 -0
  412. package/src/client/browser-entry/hydrate.ts +145 -0
  413. package/src/client/browser-entry/index.ts +143 -0
  414. package/src/client/browser-entry/post-hydration.ts +119 -0
  415. package/src/client/browser-entry/router-init.ts +193 -0
  416. package/src/client/browser-entry/rsc-stream.ts +157 -0
  417. package/src/client/browser-entry/scroll.ts +27 -0
  418. package/src/client/error-boundary.tsx +48 -16
  419. package/src/client/error-reconstituter.tsx +65 -0
  420. package/src/client/form.tsx +14 -7
  421. package/src/client/history.ts +26 -4
  422. package/src/client/index.ts +65 -38
  423. package/src/client/internal.ts +57 -0
  424. package/src/client/link-pending-store.ts +111 -0
  425. package/src/client/link.tsx +342 -113
  426. package/src/client/nav-link-store.ts +47 -0
  427. package/src/client/navigation-api-types.ts +112 -0
  428. package/src/client/navigation-api.ts +332 -0
  429. package/src/client/navigation-context.ts +31 -6
  430. package/src/client/navigation-root.tsx +342 -0
  431. package/src/client/nuqs-adapter.tsx +16 -3
  432. package/src/client/router-ref.ts +1 -1
  433. package/src/client/router.ts +299 -72
  434. package/src/client/rsc-fetch.ts +97 -8
  435. package/src/client/segment-cache.ts +1 -1
  436. package/src/client/segment-outlet.tsx +86 -0
  437. package/src/client/ssr-data.ts +13 -5
  438. package/src/client/stale-reload.ts +72 -3
  439. package/src/client/top-loader.tsx +18 -6
  440. package/src/client/use-link-status.ts +7 -7
  441. package/src/client/use-params.ts +7 -5
  442. package/src/client/{use-navigation-pending.ts → use-pending-navigation.ts} +6 -6
  443. package/src/client/use-query-states.ts +9 -3
  444. package/src/client/use-router.ts +1 -1
  445. package/src/codec.ts +49 -0
  446. package/src/config-types.ts +264 -0
  447. package/src/config-validation.ts +303 -0
  448. package/src/content/index.ts +5 -13
  449. package/src/cookies/define-cookie.ts +78 -25
  450. package/src/cookies/index.ts +8 -0
  451. package/src/fonts/bundle.ts +142 -0
  452. package/src/fonts/css.ts +2 -1
  453. package/src/fonts/dev-middleware.ts +74 -0
  454. package/src/fonts/pipeline.ts +275 -0
  455. package/src/fonts/transform.ts +353 -0
  456. package/src/fonts/types.ts +50 -1
  457. package/src/fonts/virtual-modules.ts +159 -0
  458. package/src/index.ts +314 -355
  459. package/src/plugin-context.ts +240 -0
  460. package/src/plugins/adapter-build.ts +9 -3
  461. package/src/plugins/build-manifest.ts +13 -2
  462. package/src/plugins/build-report.ts +3 -3
  463. package/src/plugins/client-chunks.ts +65 -0
  464. package/src/plugins/content.ts +1 -1
  465. package/src/plugins/dev-404-page.ts +418 -0
  466. package/src/plugins/dev-browser-logs.ts +288 -0
  467. package/src/plugins/dev-error-overlay.ts +286 -42
  468. package/src/plugins/dev-error-page.ts +536 -0
  469. package/src/plugins/dev-logs.ts +1 -1
  470. package/src/plugins/dev-server.ts +146 -19
  471. package/src/plugins/dev-terminal-error.ts +217 -0
  472. package/src/plugins/entries.ts +111 -10
  473. package/src/plugins/fonts.ts +133 -638
  474. package/src/plugins/mdx.ts +1 -1
  475. package/src/plugins/routing.ts +213 -31
  476. package/src/plugins/server-action-exports.ts +1 -1
  477. package/src/plugins/server-bundle.ts +32 -1
  478. package/src/plugins/shims.ts +136 -35
  479. package/src/plugins/static-build.ts +17 -11
  480. package/src/routing/codegen.ts +273 -105
  481. package/src/routing/convention-lint.ts +356 -0
  482. package/src/routing/index.ts +2 -0
  483. package/src/routing/scanner.ts +93 -23
  484. package/src/routing/segment-classify.ts +89 -0
  485. package/src/routing/status-file-lint.ts +3 -2
  486. package/src/routing/types.ts +17 -4
  487. package/src/rsc-runtime/rsc.ts +2 -0
  488. package/src/rsc-runtime/ssr.ts +50 -0
  489. package/src/rsc-runtime/vendor-types.d.ts +7 -0
  490. package/src/{search-params/codecs.ts → schema-bridge.ts} +57 -20
  491. package/src/search-params/define.ts +482 -0
  492. package/src/search-params/index.ts +14 -20
  493. package/src/search-params/registry.ts +2 -2
  494. package/src/search-params/wrappers.ts +85 -0
  495. package/src/segment-params/define.ts +279 -0
  496. package/src/segment-params/index.ts +9 -0
  497. package/src/server/access-gate.tsx +70 -29
  498. package/src/server/action-client.ts +88 -15
  499. package/src/server/action-encryption.ts +144 -0
  500. package/src/server/action-handler.ts +53 -6
  501. package/src/server/actions.ts +10 -9
  502. package/src/server/als-registry.ts +34 -6
  503. package/src/server/build-manifest.ts +10 -4
  504. package/src/server/compress.ts +25 -7
  505. package/src/server/debug.ts +1 -1
  506. package/src/server/default-logger.ts +99 -0
  507. package/src/server/deny-page-resolver.ts +154 -0
  508. package/src/server/deny-renderer.ts +24 -38
  509. package/src/server/dev-holding-server.ts +185 -0
  510. package/src/server/dev-source-map.ts +31 -0
  511. package/src/server/dev-warnings.ts +4 -49
  512. package/src/server/early-hints.ts +36 -15
  513. package/src/server/error-boundary-wrapper.ts +74 -22
  514. package/src/server/fallback-error.ts +74 -102
  515. package/src/server/flight-injection-state.ts +113 -0
  516. package/src/server/flight-scripts.ts +62 -0
  517. package/src/server/flush.ts +2 -1
  518. package/src/server/form-data.ts +76 -0
  519. package/src/server/html-injectors.ts +280 -120
  520. package/src/server/index.ts +25 -177
  521. package/src/server/internal.ts +169 -0
  522. package/src/server/logger.ts +44 -36
  523. package/src/server/middleware-runner.ts +31 -4
  524. package/src/server/node-stream-transforms.ts +509 -0
  525. package/src/server/page-deny-boundary.tsx +56 -0
  526. package/src/server/pipeline-interception.ts +17 -16
  527. package/src/server/pipeline-metadata.ts +13 -0
  528. package/src/server/pipeline.ts +261 -66
  529. package/src/server/primitives.ts +111 -28
  530. package/src/server/render-timeout.ts +108 -0
  531. package/src/server/request-context.ts +293 -132
  532. package/src/server/route-element-builder.ts +283 -191
  533. package/src/server/route-handler.ts +24 -4
  534. package/src/server/route-matcher.ts +31 -20
  535. package/src/server/rsc-entry/api-handler.ts +15 -16
  536. package/src/server/rsc-entry/error-renderer.ts +305 -89
  537. package/src/server/rsc-entry/helpers.ts +134 -5
  538. package/src/server/rsc-entry/index.ts +304 -111
  539. package/src/server/rsc-entry/rsc-payload.ts +65 -18
  540. package/src/server/rsc-entry/rsc-stream.ts +81 -13
  541. package/src/server/rsc-entry/ssr-bridge.ts +14 -5
  542. package/src/server/rsc-entry/ssr-renderer.ts +171 -38
  543. package/src/server/safe-load.ts +60 -0
  544. package/src/server/sensitive-fields.ts +230 -0
  545. package/src/server/sitemap-generator.ts +338 -0
  546. package/src/server/sitemap-handler.ts +126 -0
  547. package/src/server/slot-resolver.ts +244 -229
  548. package/src/server/ssr-entry.ts +215 -32
  549. package/src/server/ssr-render.ts +289 -67
  550. package/src/server/ssr-wrappers.tsx +139 -0
  551. package/src/server/status-code-resolver.ts +1 -1
  552. package/src/server/stream-utils.ts +213 -0
  553. package/src/server/tracing.ts +20 -9
  554. package/src/server/tree-builder.ts +92 -58
  555. package/src/server/types.ts +3 -6
  556. package/src/server/version-skew.ts +104 -0
  557. package/src/shared/merge-search-params.ts +55 -0
  558. package/src/shims/font-google.ts +1 -1
  559. package/src/shims/font-local.ts +34 -0
  560. package/src/shims/headers.ts +5 -1
  561. package/src/shims/navigation-client.ts +1 -1
  562. package/src/shims/navigation.ts +7 -2
  563. package/src/utils/directive-parser.ts +5 -2
  564. package/src/utils/state-machine.ts +111 -0
  565. package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
  566. package/dist/_chunks/debug-gwlJkDuf.js.map +0 -1
  567. package/dist/_chunks/format-DviM89f0.js.map +0 -1
  568. package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
  569. package/dist/_chunks/request-context-DIkVh_jG.js +0 -330
  570. package/dist/_chunks/request-context-DIkVh_jG.js.map +0 -1
  571. package/dist/_chunks/tracing-CemImE6h.js.map +0 -1
  572. package/dist/_chunks/use-cookie-DX-l1_5E.js +0 -91
  573. package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
  574. package/dist/_chunks/use-query-states-D5KaffOK.js.map +0 -1
  575. package/dist/cache/register-cached-function.d.ts +0 -17
  576. package/dist/cache/register-cached-function.d.ts.map +0 -1
  577. package/dist/client/browser-entry.d.ts +0 -21
  578. package/dist/client/browser-entry.d.ts.map +0 -1
  579. package/dist/client/link-status-provider.d.ts +0 -11
  580. package/dist/client/link-status-provider.d.ts.map +0 -1
  581. package/dist/client/transition-root.d.ts.map +0 -1
  582. package/dist/client/use-navigation-pending.d.ts.map +0 -1
  583. package/dist/cookies/index.js.map +0 -1
  584. package/dist/plugins/cache-transform.d.ts +0 -36
  585. package/dist/plugins/cache-transform.d.ts.map +0 -1
  586. package/dist/plugins/dynamic-transform.d.ts +0 -72
  587. package/dist/plugins/dynamic-transform.d.ts.map +0 -1
  588. package/dist/search-params/analyze.d.ts +0 -54
  589. package/dist/search-params/analyze.d.ts.map +0 -1
  590. package/dist/search-params/builtin-codecs.d.ts +0 -105
  591. package/dist/search-params/builtin-codecs.d.ts.map +0 -1
  592. package/dist/search-params/codecs.d.ts +0 -53
  593. package/dist/search-params/codecs.d.ts.map +0 -1
  594. package/dist/search-params/create.d.ts +0 -106
  595. package/dist/search-params/create.d.ts.map +0 -1
  596. package/dist/server/prerender.d.ts +0 -77
  597. package/dist/server/prerender.d.ts.map +0 -1
  598. package/dist/server/response-cache.d.ts +0 -54
  599. package/dist/server/response-cache.d.ts.map +0 -1
  600. package/src/cache/register-cached-function.ts +0 -103
  601. package/src/client/browser-entry.ts +0 -678
  602. package/src/client/link-status-provider.tsx +0 -30
  603. package/src/client/transition-root.tsx +0 -166
  604. package/src/plugins/cache-transform.ts +0 -199
  605. package/src/plugins/dynamic-transform.ts +0 -161
  606. package/src/search-params/analyze.ts +0 -192
  607. package/src/search-params/builtin-codecs.ts +0 -228
  608. package/src/search-params/create.ts +0 -321
  609. package/src/server/prerender.ts +0 -139
  610. package/src/server/response-cache.ts +0 -410
@@ -0,0 +1,2900 @@
1
+ import { n as isDevMode, t as isDebug } from "../_chunks/debug-ECi_61pb.js";
2
+ import { a as warnRedirectInSuspense, c as warnSuspenseWrappingChildren, i as warnRedirectInAccess, n as setViteServer, o as warnSlowSlotWithoutSuspense, r as warnDenyInSuspense, s as warnStaticRequestApi, t as WarningId } from "../_chunks/dev-warnings-DpGRGoDi.js";
3
+ import { t as classifyUrlSegment } from "../_chunks/segment-classify-BDNn6EzD.js";
4
+ import { i as getMetadataRouteServePath, n as classifyMetadataRoute, r as getMetadataRouteAutoLink, t as METADATA_ROUTE_CONVENTIONS } from "../_chunks/metadata-routes-DS3eKNmf.js";
5
+ import { a as timingAls, r as requestContextAls, t as earlyHintsSenderAls } from "../_chunks/als-registry-HS0LGUl2.js";
6
+ import { f as runWithRequestContext, l as getSetCookieHeaders, m as setSegmentParams, p as setMutableCookieContext, t as applyRequestHeaderOverlay, u as markResponseFlushed } from "../_chunks/request-context-CK5tZqIP.js";
7
+ import { l as RenderError, n as executeAction, o as DenySignal, r as isRscActionRequest, s as RedirectSignal, t as buildNoJsResponse } from "../_chunks/actions-DLnUaR65.js";
8
+ import { c as replaceTraceId, d as withSpan, i as getOtelTraceId, l as runWithTraceId, o as getTraceId, r as generateTraceId, s as getTraceStore, u as setSpanAttribute } from "../_chunks/tracing-CCYbKn5n.js";
9
+ import "../client/error-boundary.js";
10
+ import "../_chunks/segment-context-fHFLF1PE.js";
11
+ import { readFile } from "node:fs/promises";
12
+ import { createElement } from "react";
13
+ //#region src/server/canonicalize.ts
14
+ /**
15
+ * Encoded separators that produce a 400 rejection.
16
+ * %2f (/) and %5c (\) cause path-confusion attacks.
17
+ */
18
+ var ENCODED_SEPARATOR_RE = /%2f|%5c/i;
19
+ /** Null byte — rejected. */
20
+ var NULL_BYTE_RE = /%00/i;
21
+ /**
22
+ * Canonicalize a URL pathname.
23
+ *
24
+ * 1. Reject encoded separators (%2f, %5c) and null bytes (%00)
25
+ * 2. Single percent-decode
26
+ * 3. Collapse // → /
27
+ * 4. Resolve .. segments (reject if escaping root)
28
+ * 5. Strip trailing slash (except root "/")
29
+ *
30
+ * @param rawPathname - The raw pathname from the request URL (percent-encoded)
31
+ * @param stripTrailingSlash - Whether to strip trailing slashes. Default: true.
32
+ */
33
+ function canonicalize(rawPathname, stripTrailingSlash = true) {
34
+ if (ENCODED_SEPARATOR_RE.test(rawPathname)) return {
35
+ ok: false,
36
+ status: 400
37
+ };
38
+ if (NULL_BYTE_RE.test(rawPathname)) return {
39
+ ok: false,
40
+ status: 400
41
+ };
42
+ let decoded;
43
+ try {
44
+ decoded = decodeURIComponent(rawPathname);
45
+ } catch {
46
+ return {
47
+ ok: false,
48
+ status: 400
49
+ };
50
+ }
51
+ if (decoded.includes("\0")) return {
52
+ ok: false,
53
+ status: 400
54
+ };
55
+ let pathname = decoded.replace(/\/\/+/g, "/");
56
+ const segments = pathname.split("/");
57
+ const resolved = [];
58
+ for (const seg of segments) if (seg === "..") {
59
+ if (resolved.length <= 1) return {
60
+ ok: false,
61
+ status: 400
62
+ };
63
+ resolved.pop();
64
+ } else if (seg !== ".") resolved.push(seg);
65
+ pathname = resolved.join("/") || "/";
66
+ if (stripTrailingSlash && pathname.length > 1 && pathname.endsWith("/")) pathname = pathname.slice(0, -1);
67
+ return {
68
+ ok: true,
69
+ pathname
70
+ };
71
+ }
72
+ //#endregion
73
+ //#region src/server/proxy.ts
74
+ /**
75
+ * Run the proxy pipeline.
76
+ *
77
+ * @param proxyExport - The default export from proxy.ts (function or array)
78
+ * @param req - The incoming request
79
+ * @param next - The continuation that proceeds to route matching and rendering
80
+ * @returns The final response
81
+ */
82
+ async function runProxy(proxyExport, req, next) {
83
+ const fns = Array.isArray(proxyExport) ? proxyExport : [proxyExport];
84
+ let i = fns.length;
85
+ let composed = next;
86
+ while (i--) {
87
+ const fn = fns[i];
88
+ const downstream = composed;
89
+ composed = () => Promise.resolve(fn(req, downstream));
90
+ }
91
+ return composed();
92
+ }
93
+ //#endregion
94
+ //#region src/server/middleware-runner.ts
95
+ /**
96
+ * Run a route's middleware function.
97
+ *
98
+ * @param middlewareFn - The default export from the route's middleware.ts
99
+ * @param ctx - The middleware context (req, params, headers, requestHeaders, searchParams)
100
+ * @returns A Response if middleware short-circuited, or undefined to continue
101
+ */
102
+ async function runMiddleware(middlewareFn, ctx) {
103
+ const result = await middlewareFn(ctx);
104
+ if (result instanceof Response) return result;
105
+ }
106
+ /**
107
+ * Run all middleware functions in the segment chain, root to leaf.
108
+ *
109
+ * Execution is top-down: root middleware runs first, leaf middleware runs last.
110
+ * All middleware share the same MiddlewareContext — a parent that sets
111
+ * ctx.requestHeaders makes it visible to child middleware and downstream components.
112
+ *
113
+ * Short-circuits on the first middleware that returns a Response.
114
+ * Remaining middleware in the chain do not execute.
115
+ *
116
+ * @param chain - Middleware functions ordered root-to-leaf
117
+ * @param ctx - Shared middleware context
118
+ * @returns A Response if any middleware short-circuited, or undefined to continue
119
+ */
120
+ async function runMiddlewareChain(chain, ctx) {
121
+ for (const fn of chain) {
122
+ const result = await fn(ctx);
123
+ if (result instanceof Response) return result;
124
+ }
125
+ }
126
+ //#endregion
127
+ //#region src/server/server-timing.ts
128
+ /**
129
+ * Server-Timing header — dev-mode timing breakdowns for Chrome DevTools.
130
+ *
131
+ * Collects timing entries per request using ALS. Each pipeline phase
132
+ * (proxy, middleware, render, SSR, access, fetch) records an entry.
133
+ * Before response flush, entries are formatted into a Server-Timing header.
134
+ *
135
+ * Only active in dev mode — zero overhead in production.
136
+ *
137
+ * See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Server-Timing
138
+ * Task: LOCAL-290
139
+ */
140
+ /**
141
+ * Run a callback with a per-request timing collector.
142
+ * Must be called at the top of the request pipeline (wraps the full request).
143
+ */
144
+ function runWithTimingCollector(fn) {
145
+ return timingAls.run({ entries: [] }, fn);
146
+ }
147
+ /**
148
+ * Run a function and automatically record its duration as a timing entry.
149
+ * Returns the function's result. No-ops the recording if outside a collector.
150
+ */
151
+ async function withTiming(name, desc, fn) {
152
+ const store = timingAls.getStore();
153
+ if (!store) return fn();
154
+ const start = performance.now();
155
+ try {
156
+ return await fn();
157
+ } finally {
158
+ const dur = Math.round(performance.now() - start);
159
+ store.entries.push({
160
+ name,
161
+ dur,
162
+ desc
163
+ });
164
+ }
165
+ }
166
+ /**
167
+ * Get the Server-Timing header value for the current request.
168
+ * Returns null if no entries exist or outside a collector.
169
+ *
170
+ * Format: `name;dur=123;desc="description", name2;dur=456`
171
+ * See RFC 6797 / Server-Timing spec for format details.
172
+ */
173
+ function getServerTimingHeader() {
174
+ const store = timingAls.getStore();
175
+ if (!store || store.entries.length === 0) return null;
176
+ const nameCounts = /* @__PURE__ */ new Map();
177
+ const parts = store.entries.map((entry) => {
178
+ const count = nameCounts.get(entry.name) ?? 0;
179
+ nameCounts.set(entry.name, count + 1);
180
+ const uniqueName = count > 0 ? `${entry.name}-${count}` : entry.name;
181
+ return {
182
+ ...entry,
183
+ name: uniqueName
184
+ };
185
+ }).map((entry) => {
186
+ let part = `${entry.name};dur=${entry.dur}`;
187
+ if (entry.desc) {
188
+ const safeDesc = entry.desc.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
189
+ part += `;desc="${safeDesc}"`;
190
+ }
191
+ return part;
192
+ });
193
+ const MAX_HEADER_SIZE = 4096;
194
+ let result = "";
195
+ for (let i = 0; i < parts.length; i++) {
196
+ const candidate = result ? `${result}, ${parts[i]}` : parts[i];
197
+ if (candidate.length > MAX_HEADER_SIZE) break;
198
+ result = candidate;
199
+ }
200
+ return result || null;
201
+ }
202
+ //#endregion
203
+ //#region src/server/error-formatter.ts
204
+ /**
205
+ * Error Formatter — rewrites SSR/RSC error messages to surface user code.
206
+ *
207
+ * When React or Vite throw errors during SSR, stack traces reference
208
+ * vendored dependency paths (e.g. `.vite/deps_ssr/@vitejs_plugin-rsc_vendor_...`)
209
+ * and mangled export names (`__vite_ssr_export_default__`). This module
210
+ * rewrites error messages and stack traces to point at user code instead.
211
+ *
212
+ * Dev-only — in production, errors go through the structured logger
213
+ * without formatting.
214
+ */
215
+ /**
216
+ * Patterns that identify internal Vite/RSC vendor paths in stack traces.
217
+ * These are replaced with human-readable labels.
218
+ */
219
+ var VENDOR_PATH_PATTERNS = [
220
+ {
221
+ pattern: /node_modules\/\.vite\/deps_ssr\/@vitejs_plugin-rsc_vendor_react-server-dom[^\s)]+/g,
222
+ replacement: "<react-server-dom>"
223
+ },
224
+ {
225
+ pattern: /node_modules\/\.vite\/deps_ssr\/@vitejs_plugin-rsc_vendor[^\s)]+/g,
226
+ replacement: "<rsc-vendor>"
227
+ },
228
+ {
229
+ pattern: /node_modules\/\.vite\/deps_ssr\/[^\s)]+/g,
230
+ replacement: "<vite-dep>"
231
+ },
232
+ {
233
+ pattern: /node_modules\/\.vite\/deps\/[^\s)]+/g,
234
+ replacement: "<vite-dep>"
235
+ }
236
+ ];
237
+ /**
238
+ * Patterns that identify Vite-mangled export names in error messages.
239
+ */
240
+ var MANGLED_NAME_PATTERNS = [{
241
+ pattern: /__vite_ssr_export_default__/g,
242
+ replacement: "<default export>"
243
+ }, {
244
+ pattern: /__vite_ssr_export_(\w+)__/g,
245
+ replacement: "<export $1>"
246
+ }];
247
+ /**
248
+ * Rewrite an error's message and stack to replace internal Vite paths
249
+ * and mangled names with human-readable labels.
250
+ */
251
+ function formatSsrError(error) {
252
+ if (!(error instanceof Error)) return String(error);
253
+ let message = error.message;
254
+ let stack = error.stack ?? "";
255
+ for (const { pattern, replacement } of MANGLED_NAME_PATTERNS) message = message.replace(pattern, replacement);
256
+ for (const { pattern, replacement } of VENDOR_PATH_PATTERNS) stack = stack.replace(pattern, replacement);
257
+ for (const { pattern, replacement } of MANGLED_NAME_PATTERNS) stack = stack.replace(pattern, replacement);
258
+ const hint = extractErrorHint(error.message);
259
+ const parts = [];
260
+ parts.push(message);
261
+ if (hint) parts.push(` → ${hint}`);
262
+ const userFrames = extractUserFrames(stack);
263
+ if (userFrames.length > 0) {
264
+ parts.push("");
265
+ parts.push(" User code in stack:");
266
+ for (const frame of userFrames) parts.push(` ${frame}`);
267
+ }
268
+ return parts.join("\n");
269
+ }
270
+ /**
271
+ * Extract a human-readable hint from common React/RSC error messages.
272
+ *
273
+ * React error messages contain useful information but the surrounding
274
+ * context (vendor paths, mangled names) obscures it. This extracts the
275
+ * actionable part as a one-line hint.
276
+ */
277
+ function extractErrorHint(message) {
278
+ if (message.match(/Functions cannot be passed directly to Client Components/)) {
279
+ const propMatch = message.match(/<[^>]*?\s(\w+)=\{function/);
280
+ if (propMatch) return `Prop "${propMatch[1]}" is a function — mark it "use server" or call it before passing`;
281
+ return "A function prop was passed to a Client Component — mark it \"use server\" or call it before passing";
282
+ }
283
+ if (message.includes("Objects are not valid as a React child")) return "An object was rendered as JSX children — convert to string or extract the value";
284
+ const nullRefMatch = message.match(/Cannot read propert(?:y|ies) of (undefined|null) \(reading '(\w+)'\)/);
285
+ if (nullRefMatch) return `Accessed .${nullRefMatch[2]} on ${nullRefMatch[1]} — check that the value exists`;
286
+ const notFnMatch = message.match(/(\w+) is not a function/);
287
+ if (notFnMatch) return `"${notFnMatch[1]}" is not a function — check imports and exports`;
288
+ if (message.includes("Element type is invalid")) return "A component resolved to undefined/null — check default exports and import paths";
289
+ if (message.includes("Invalid hook call")) return "A hook was called outside of a React component render. If this is a 'use client' component, ensure the directive is at the very top of the file (before any imports) and that @vitejs/plugin-rsc is loaded correctly. Barrel re-exports from non-'use client' files do not propagate the directive.";
290
+ return null;
291
+ }
292
+ /**
293
+ * Extract stack frames that reference user code (not node_modules,
294
+ * not framework internals).
295
+ *
296
+ * Returns at most 5 frames to keep output concise.
297
+ */
298
+ function extractUserFrames(stack) {
299
+ const lines = stack.split("\n");
300
+ const userFrames = [];
301
+ for (const line of lines) {
302
+ const trimmed = line.trim();
303
+ if (!trimmed.startsWith("at ")) continue;
304
+ if (trimmed.includes("node_modules") || trimmed.includes("<react-server-dom>") || trimmed.includes("<rsc-vendor>") || trimmed.includes("<vite-dep>") || trimmed.includes("node:internal")) continue;
305
+ userFrames.push(trimmed);
306
+ if (userFrames.length >= 5) break;
307
+ }
308
+ return userFrames;
309
+ }
310
+ //#endregion
311
+ //#region src/server/default-logger.ts
312
+ /**
313
+ * DefaultLogger — human-readable stderr logging when no custom logger is configured.
314
+ *
315
+ * Ships as the fallback so production deployments always have error visibility,
316
+ * even without an `instrumentation.ts` logger export. Output is one line per
317
+ * event, designed for `fly logs`, `kubectl logs`, Cloudflare dashboard tails, etc.
318
+ *
319
+ * Format:
320
+ * [timber] ERROR message key=value key=value trace_id=4bf92f35
321
+ * [timber] WARN message key=value key=value trace_id=4bf92f35
322
+ * [timber] INFO message method=GET path=/dashboard status=200 durationMs=43 trace_id=4bf92f35
323
+ *
324
+ * Behavior:
325
+ * - Suppressed entirely in dev mode (dev logging handles all output)
326
+ * - `debug` suppressed unless TIMBER_DEBUG is set
327
+ * - Replaced entirely when a custom logger is set via `setLogger()`
328
+ *
329
+ * See design/17-logging.md §"DefaultLogger"
330
+ */
331
+ /**
332
+ * Format data fields as `key=value` pairs for human-readable output.
333
+ * - `error` key is serialized via formatSsrError for stack trace cleanup
334
+ * - `trace_id` is truncated to 8 chars for readability (full ID in OTEL)
335
+ * - Other values are stringified inline
336
+ */
337
+ function formatDataFields(data) {
338
+ if (!data) return "";
339
+ const parts = [];
340
+ let traceId;
341
+ for (const [key, value] of Object.entries(data)) {
342
+ if (key === "trace_id") {
343
+ traceId = typeof value === "string" ? value : String(value);
344
+ continue;
345
+ }
346
+ if (key === "error") {
347
+ parts.push(`error=${formatSsrError(value)}`);
348
+ continue;
349
+ }
350
+ if (value === void 0 || value === null) continue;
351
+ parts.push(`${key}=${value}`);
352
+ }
353
+ if (traceId) parts.push(`trace_id=${traceId.slice(0, 8)}`);
354
+ return parts.length > 0 ? " " + parts.join(" ") : "";
355
+ }
356
+ /** Pad level string to fixed width for alignment. */
357
+ function padLevel(level) {
358
+ return level.padEnd(5);
359
+ }
360
+ function createDefaultLogger() {
361
+ return {
362
+ error(msg, data) {
363
+ const fields = formatDataFields(data);
364
+ process.stderr.write(`[timber] ${padLevel("ERROR")} ${msg}${fields}\n`);
365
+ },
366
+ warn(msg, data) {
367
+ const fields = formatDataFields(data);
368
+ process.stderr.write(`[timber] ${padLevel("WARN")} ${msg}${fields}\n`);
369
+ },
370
+ info(msg, data) {
371
+ if (isDevMode()) return;
372
+ if (!isDebug()) return;
373
+ const fields = formatDataFields(data);
374
+ process.stderr.write(`[timber] ${padLevel("INFO")} ${msg}${fields}\n`);
375
+ },
376
+ debug(msg, data) {
377
+ if (isDevMode()) return;
378
+ if (!isDebug()) return;
379
+ const fields = formatDataFields(data);
380
+ process.stderr.write(`[timber] ${padLevel("DEBUG")} ${msg}${fields}\n`);
381
+ }
382
+ };
383
+ }
384
+ //#endregion
385
+ //#region src/server/logger.ts
386
+ /**
387
+ * Logger — structured logging with environment-aware formatting.
388
+ *
389
+ * timber.js ships a DefaultLogger that writes human-readable lines to stderr
390
+ * in production. Users can export a custom logger from instrumentation.ts to
391
+ * replace it with pino, winston, or any TimberLogger-compatible object.
392
+ *
393
+ * See design/17-logging.md §"Production Logging"
394
+ */
395
+ var _logger = createDefaultLogger();
396
+ /**
397
+ * Set the user-provided logger. Called by the instrumentation loader
398
+ * when it finds a `logger` export in instrumentation.ts. Replaces
399
+ * the DefaultLogger entirely.
400
+ */
401
+ function setLogger(logger) {
402
+ _logger = logger;
403
+ }
404
+ /**
405
+ * Get the current logger. Always non-null — returns DefaultLogger when
406
+ * no custom logger is configured.
407
+ */
408
+ function getLogger() {
409
+ return _logger;
410
+ }
411
+ /**
412
+ * Inject trace_id and span_id into log data for log–trace correlation.
413
+ * Always injects trace_id (never undefined). Injects span_id only when OTEL is active.
414
+ */
415
+ function withTraceContext(data) {
416
+ const store = getTraceStore();
417
+ const enriched = { ...data };
418
+ if (store) {
419
+ enriched.trace_id = store.traceId;
420
+ if (store.spanId) enriched.span_id = store.spanId;
421
+ }
422
+ return enriched;
423
+ }
424
+ /** Log a completed request. Level: info. */
425
+ function logRequestCompleted(data) {
426
+ _logger.info("request completed", withTraceContext(data));
427
+ }
428
+ /** Log request received. Level: debug. */
429
+ function logRequestReceived(data) {
430
+ _logger.debug("request received", withTraceContext(data));
431
+ }
432
+ /** Log a slow request warning. Level: warn. */
433
+ function logSlowRequest(data) {
434
+ _logger.warn("slow request exceeded threshold", withTraceContext(data));
435
+ }
436
+ /** Log middleware short-circuit. Level: debug. */
437
+ function logMiddlewareShortCircuit(data) {
438
+ _logger.debug("middleware short-circuited", withTraceContext(data));
439
+ }
440
+ /** Log unhandled error in middleware phase. Level: error. */
441
+ function logMiddlewareError(data) {
442
+ _logger.error("unhandled error in middleware phase", withTraceContext(data));
443
+ }
444
+ /** Log unhandled render-phase error. Level: error. */
445
+ function logRenderError(data) {
446
+ _logger.error("unhandled render-phase error", withTraceContext(data));
447
+ }
448
+ /** Log proxy.ts uncaught error. Level: error. */
449
+ function logProxyError(data) {
450
+ _logger.error("proxy.ts threw uncaught error", withTraceContext(data));
451
+ }
452
+ /** Log unhandled error in route handler. Level: error. */
453
+ function logRouteError(data) {
454
+ _logger.error("unhandled route handler error", withTraceContext(data));
455
+ }
456
+ /** Log waitUntil() adapter missing (once at startup). Level: warn. */
457
+ function logWaitUntilUnsupported() {
458
+ _logger.warn("adapter does not support waitUntil()");
459
+ }
460
+ /** Log waitUntil() promise rejection. Level: warn. */
461
+ function logWaitUntilRejected(data) {
462
+ _logger.warn("waitUntil() promise rejected", withTraceContext(data));
463
+ }
464
+ /** Log staleWhileRevalidate refetch failure. Level: warn. */
465
+ function logSwrRefetchFailed(data) {
466
+ _logger.warn("staleWhileRevalidate refetch failed", withTraceContext(data));
467
+ }
468
+ /** Log cache miss. Level: debug. */
469
+ function logCacheMiss(data) {
470
+ _logger.debug("timber.cache MISS", withTraceContext(data));
471
+ }
472
+ //#endregion
473
+ //#region src/server/instrumentation.ts
474
+ /**
475
+ * Instrumentation — loads and runs the user's instrumentation.ts file.
476
+ *
477
+ * instrumentation.ts is a file convention at the project root that exports:
478
+ * - register() — called once at server startup, before the first request
479
+ * - onRequestError() — called for every unhandled server error
480
+ * - logger — any object with info/warn/error/debug methods
481
+ *
482
+ * See design/17-logging.md §"instrumentation.ts — The Entry Point"
483
+ */
484
+ var _initialized = false;
485
+ var _onRequestError = null;
486
+ /**
487
+ * Load and initialize the user's instrumentation.ts module.
488
+ *
489
+ * - Awaits register() before returning (server blocks on this).
490
+ * - Picks up the logger export and wires it into the framework logger.
491
+ * - Stores onRequestError for later invocation.
492
+ *
493
+ * @param loader - Function that dynamically imports the user's instrumentation module.
494
+ * Returns null if no instrumentation.ts exists.
495
+ */
496
+ async function loadInstrumentation(loader) {
497
+ if (_initialized) return;
498
+ _initialized = true;
499
+ let mod;
500
+ try {
501
+ mod = await loader();
502
+ } catch (error) {
503
+ console.error("[timber] Failed to load instrumentation.ts:", error);
504
+ return;
505
+ }
506
+ if (!mod) return;
507
+ if (mod.logger && typeof mod.logger.info === "function") setLogger(mod.logger);
508
+ if (typeof mod.onRequestError === "function") _onRequestError = mod.onRequestError;
509
+ if (typeof mod.register === "function") try {
510
+ await mod.register();
511
+ } catch (error) {
512
+ console.error("[timber] instrumentation.ts register() threw:", error);
513
+ throw error;
514
+ }
515
+ }
516
+ /**
517
+ * Call the user's onRequestError hook. Catches and logs any errors thrown
518
+ * by the hook itself — it must not affect the response.
519
+ */
520
+ async function callOnRequestError(error, request, context) {
521
+ if (!_onRequestError) return;
522
+ try {
523
+ await _onRequestError(error, request, context);
524
+ } catch (hookError) {
525
+ console.error("[timber] onRequestError hook threw:", hookError);
526
+ }
527
+ }
528
+ /**
529
+ * Check if onRequestError is registered.
530
+ */
531
+ function hasOnRequestError() {
532
+ return _onRequestError !== null;
533
+ }
534
+ //#endregion
535
+ //#region src/server/metadata-social.ts
536
+ /**
537
+ * Render Open Graph metadata into head element descriptors.
538
+ *
539
+ * Handles og:title, og:description, og:image (with dimensions/alt),
540
+ * og:video, og:audio, og:article:author, and other OG properties.
541
+ */
542
+ function renderOpenGraph(og, elements) {
543
+ const simpleProps = [
544
+ ["og:title", og.title],
545
+ ["og:description", og.description],
546
+ ["og:url", og.url],
547
+ ["og:site_name", og.siteName],
548
+ ["og:locale", og.locale],
549
+ ["og:type", og.type],
550
+ ["og:article:published_time", og.publishedTime],
551
+ ["og:article:modified_time", og.modifiedTime]
552
+ ];
553
+ for (const [property, content] of simpleProps) if (content) elements.push({
554
+ tag: "meta",
555
+ attrs: {
556
+ property,
557
+ content
558
+ }
559
+ });
560
+ if (og.images) if (typeof og.images === "string") elements.push({
561
+ tag: "meta",
562
+ attrs: {
563
+ property: "og:image",
564
+ content: og.images
565
+ }
566
+ });
567
+ else {
568
+ const imgList = Array.isArray(og.images) ? og.images : [og.images];
569
+ for (const img of imgList) {
570
+ elements.push({
571
+ tag: "meta",
572
+ attrs: {
573
+ property: "og:image",
574
+ content: img.url
575
+ }
576
+ });
577
+ if (img.width) elements.push({
578
+ tag: "meta",
579
+ attrs: {
580
+ property: "og:image:width",
581
+ content: String(img.width)
582
+ }
583
+ });
584
+ if (img.height) elements.push({
585
+ tag: "meta",
586
+ attrs: {
587
+ property: "og:image:height",
588
+ content: String(img.height)
589
+ }
590
+ });
591
+ if (img.alt) elements.push({
592
+ tag: "meta",
593
+ attrs: {
594
+ property: "og:image:alt",
595
+ content: img.alt
596
+ }
597
+ });
598
+ }
599
+ }
600
+ if (og.videos) for (const video of og.videos) elements.push({
601
+ tag: "meta",
602
+ attrs: {
603
+ property: "og:video",
604
+ content: video.url
605
+ }
606
+ });
607
+ if (og.audio) for (const audio of og.audio) elements.push({
608
+ tag: "meta",
609
+ attrs: {
610
+ property: "og:audio",
611
+ content: audio.url
612
+ }
613
+ });
614
+ if (og.authors) for (const author of og.authors) elements.push({
615
+ tag: "meta",
616
+ attrs: {
617
+ property: "og:article:author",
618
+ content: author
619
+ }
620
+ });
621
+ }
622
+ /**
623
+ * Render Twitter Card metadata into head element descriptors.
624
+ *
625
+ * Handles twitter:card, twitter:site, twitter:title, twitter:image,
626
+ * twitter:player, and twitter:app (per-platform name/id/url).
627
+ */
628
+ function renderTwitter(tw, elements) {
629
+ const simpleProps = [
630
+ ["twitter:card", tw.card],
631
+ ["twitter:site", tw.site],
632
+ ["twitter:site:id", tw.siteId],
633
+ ["twitter:title", tw.title],
634
+ ["twitter:description", tw.description],
635
+ ["twitter:creator", tw.creator],
636
+ ["twitter:creator:id", tw.creatorId]
637
+ ];
638
+ for (const [name, content] of simpleProps) if (content) elements.push({
639
+ tag: "meta",
640
+ attrs: {
641
+ name,
642
+ content
643
+ }
644
+ });
645
+ if (tw.images) if (typeof tw.images === "string") elements.push({
646
+ tag: "meta",
647
+ attrs: {
648
+ name: "twitter:image",
649
+ content: tw.images
650
+ }
651
+ });
652
+ else {
653
+ const imgList = Array.isArray(tw.images) ? tw.images : [tw.images];
654
+ for (const img of imgList) {
655
+ const url = typeof img === "string" ? img : img.url;
656
+ elements.push({
657
+ tag: "meta",
658
+ attrs: {
659
+ name: "twitter:image",
660
+ content: url
661
+ }
662
+ });
663
+ }
664
+ }
665
+ if (tw.players) for (const player of tw.players) {
666
+ elements.push({
667
+ tag: "meta",
668
+ attrs: {
669
+ name: "twitter:player",
670
+ content: player.playerUrl
671
+ }
672
+ });
673
+ if (player.width) elements.push({
674
+ tag: "meta",
675
+ attrs: {
676
+ name: "twitter:player:width",
677
+ content: String(player.width)
678
+ }
679
+ });
680
+ if (player.height) elements.push({
681
+ tag: "meta",
682
+ attrs: {
683
+ name: "twitter:player:height",
684
+ content: String(player.height)
685
+ }
686
+ });
687
+ if (player.streamUrl) elements.push({
688
+ tag: "meta",
689
+ attrs: {
690
+ name: "twitter:player:stream",
691
+ content: player.streamUrl
692
+ }
693
+ });
694
+ }
695
+ if (tw.app) {
696
+ const platforms = [
697
+ ["iPhone", "iphone"],
698
+ ["iPad", "ipad"],
699
+ ["googlePlay", "googleplay"]
700
+ ];
701
+ if (tw.app.name) {
702
+ for (const [key, tag] of platforms) if (tw.app.id?.[key]) elements.push({
703
+ tag: "meta",
704
+ attrs: {
705
+ name: `twitter:app:name:${tag}`,
706
+ content: tw.app.name
707
+ }
708
+ });
709
+ }
710
+ for (const [key, tag] of platforms) {
711
+ const id = tw.app.id?.[key];
712
+ if (id) elements.push({
713
+ tag: "meta",
714
+ attrs: {
715
+ name: `twitter:app:id:${tag}`,
716
+ content: id
717
+ }
718
+ });
719
+ }
720
+ for (const [key, tag] of platforms) {
721
+ const url = tw.app.url?.[key];
722
+ if (url) elements.push({
723
+ tag: "meta",
724
+ attrs: {
725
+ name: `twitter:app:url:${tag}`,
726
+ content: url
727
+ }
728
+ });
729
+ }
730
+ }
731
+ }
732
+ //#endregion
733
+ //#region src/server/metadata-platform.ts
734
+ /**
735
+ * Render icon link elements (favicon, shortcut, apple-touch-icon, custom).
736
+ */
737
+ function renderIcons(icons, elements) {
738
+ if (icons.icon) {
739
+ if (typeof icons.icon === "string") elements.push({
740
+ tag: "link",
741
+ attrs: {
742
+ rel: "icon",
743
+ href: icons.icon
744
+ }
745
+ });
746
+ else if (Array.isArray(icons.icon)) for (const icon of icons.icon) {
747
+ const attrs = {
748
+ rel: "icon",
749
+ href: icon.url
750
+ };
751
+ if (icon.sizes) attrs.sizes = icon.sizes;
752
+ if (icon.type) attrs.type = icon.type;
753
+ elements.push({
754
+ tag: "link",
755
+ attrs
756
+ });
757
+ }
758
+ }
759
+ if (icons.shortcut) {
760
+ const urls = Array.isArray(icons.shortcut) ? icons.shortcut : [icons.shortcut];
761
+ for (const url of urls) elements.push({
762
+ tag: "link",
763
+ attrs: {
764
+ rel: "shortcut icon",
765
+ href: url
766
+ }
767
+ });
768
+ }
769
+ if (icons.apple) {
770
+ if (typeof icons.apple === "string") elements.push({
771
+ tag: "link",
772
+ attrs: {
773
+ rel: "apple-touch-icon",
774
+ href: icons.apple
775
+ }
776
+ });
777
+ else if (Array.isArray(icons.apple)) for (const icon of icons.apple) {
778
+ const attrs = {
779
+ rel: "apple-touch-icon",
780
+ href: icon.url
781
+ };
782
+ if (icon.sizes) attrs.sizes = icon.sizes;
783
+ elements.push({
784
+ tag: "link",
785
+ attrs
786
+ });
787
+ }
788
+ }
789
+ if (icons.other) for (const icon of icons.other) {
790
+ const attrs = {
791
+ rel: icon.rel,
792
+ href: icon.url
793
+ };
794
+ if (icon.sizes) attrs.sizes = icon.sizes;
795
+ if (icon.type) attrs.type = icon.type;
796
+ elements.push({
797
+ tag: "link",
798
+ attrs
799
+ });
800
+ }
801
+ }
802
+ /**
803
+ * Render alternate link elements (canonical, hreflang, media, types).
804
+ */
805
+ function renderAlternates(alternates, elements) {
806
+ if (alternates.canonical) elements.push({
807
+ tag: "link",
808
+ attrs: {
809
+ rel: "canonical",
810
+ href: alternates.canonical
811
+ }
812
+ });
813
+ if (alternates.languages) for (const [lang, href] of Object.entries(alternates.languages)) elements.push({
814
+ tag: "link",
815
+ attrs: {
816
+ rel: "alternate",
817
+ hreflang: lang,
818
+ href
819
+ }
820
+ });
821
+ if (alternates.media) for (const [media, href] of Object.entries(alternates.media)) elements.push({
822
+ tag: "link",
823
+ attrs: {
824
+ rel: "alternate",
825
+ media,
826
+ href
827
+ }
828
+ });
829
+ if (alternates.types) for (const [type, href] of Object.entries(alternates.types)) elements.push({
830
+ tag: "link",
831
+ attrs: {
832
+ rel: "alternate",
833
+ type,
834
+ href
835
+ }
836
+ });
837
+ }
838
+ /**
839
+ * Render site verification meta tags (Google, Yahoo, Yandex, custom).
840
+ */
841
+ function renderVerification(verification, elements) {
842
+ const verificationProps = [
843
+ ["google-site-verification", verification.google],
844
+ ["y_key", verification.yahoo],
845
+ ["yandex-verification", verification.yandex]
846
+ ];
847
+ for (const [name, content] of verificationProps) if (content) elements.push({
848
+ tag: "meta",
849
+ attrs: {
850
+ name,
851
+ content
852
+ }
853
+ });
854
+ if (verification.other) for (const [name, value] of Object.entries(verification.other)) {
855
+ const content = Array.isArray(value) ? value.join(", ") : value;
856
+ elements.push({
857
+ tag: "meta",
858
+ attrs: {
859
+ name,
860
+ content
861
+ }
862
+ });
863
+ }
864
+ }
865
+ /**
866
+ * Render Apple Web App meta tags and startup image links.
867
+ */
868
+ function renderAppleWebApp(appleWebApp, elements) {
869
+ if (appleWebApp.capable) elements.push({
870
+ tag: "meta",
871
+ attrs: {
872
+ name: "apple-mobile-web-app-capable",
873
+ content: "yes"
874
+ }
875
+ });
876
+ if (appleWebApp.title) elements.push({
877
+ tag: "meta",
878
+ attrs: {
879
+ name: "apple-mobile-web-app-title",
880
+ content: appleWebApp.title
881
+ }
882
+ });
883
+ if (appleWebApp.statusBarStyle) elements.push({
884
+ tag: "meta",
885
+ attrs: {
886
+ name: "apple-mobile-web-app-status-bar-style",
887
+ content: appleWebApp.statusBarStyle
888
+ }
889
+ });
890
+ if (appleWebApp.startupImage) {
891
+ const images = Array.isArray(appleWebApp.startupImage) ? appleWebApp.startupImage : [{ url: appleWebApp.startupImage }];
892
+ for (const img of images) {
893
+ const attrs = {
894
+ rel: "apple-touch-startup-image",
895
+ href: typeof img === "string" ? img : img.url
896
+ };
897
+ if (typeof img === "object" && img.media) attrs.media = img.media;
898
+ elements.push({
899
+ tag: "link",
900
+ attrs
901
+ });
902
+ }
903
+ }
904
+ }
905
+ /**
906
+ * Render App Links (al:*) meta tags for deep linking across platforms.
907
+ */
908
+ function renderAppLinks(appLinks, elements) {
909
+ const platformEntries = [
910
+ ["ios", appLinks.ios],
911
+ ["android", appLinks.android],
912
+ ["windows", appLinks.windows],
913
+ ["windows_phone", appLinks.windowsPhone],
914
+ ["windows_universal", appLinks.windowsUniversal]
915
+ ];
916
+ for (const [platform, entries] of platformEntries) {
917
+ if (!entries) continue;
918
+ for (const entry of entries) for (const [key, value] of Object.entries(entry)) if (value !== void 0 && value !== null) elements.push({
919
+ tag: "meta",
920
+ attrs: {
921
+ property: `al:${platform}:${key}`,
922
+ content: String(value)
923
+ }
924
+ });
925
+ }
926
+ if (appLinks.web) {
927
+ if (appLinks.web.url) elements.push({
928
+ tag: "meta",
929
+ attrs: {
930
+ property: "al:web:url",
931
+ content: appLinks.web.url
932
+ }
933
+ });
934
+ if (appLinks.web.shouldFallback !== void 0) elements.push({
935
+ tag: "meta",
936
+ attrs: {
937
+ property: "al:web:should_fallback",
938
+ content: appLinks.web.shouldFallback ? "true" : "false"
939
+ }
940
+ });
941
+ }
942
+ }
943
+ /**
944
+ * Render Apple iTunes smart banner meta tag.
945
+ */
946
+ function renderItunes(itunes, elements) {
947
+ const parts = [`app-id=${itunes.appId}`];
948
+ if (itunes.affiliateData) parts.push(`affiliate-data=${itunes.affiliateData}`);
949
+ if (itunes.appArgument) parts.push(`app-argument=${itunes.appArgument}`);
950
+ elements.push({
951
+ tag: "meta",
952
+ attrs: {
953
+ name: "apple-itunes-app",
954
+ content: parts.join(", ")
955
+ }
956
+ });
957
+ }
958
+ //#endregion
959
+ //#region src/server/metadata-render.ts
960
+ /**
961
+ * Convert resolved metadata into an array of head element descriptors.
962
+ *
963
+ * Each descriptor has a `tag` ('title', 'meta', 'link') and either
964
+ * `content` (for <title>) or `attrs` (for <meta>/<link>).
965
+ *
966
+ * The framework's MetadataResolver component consumes these descriptors
967
+ * and renders them into the <head>.
968
+ */
969
+ function renderMetadataToElements(metadata) {
970
+ const elements = [];
971
+ if (typeof metadata.title === "string") elements.push({
972
+ tag: "title",
973
+ content: metadata.title
974
+ });
975
+ const simpleMetaProps = [
976
+ ["description", metadata.description],
977
+ ["generator", metadata.generator],
978
+ ["application-name", metadata.applicationName],
979
+ ["referrer", metadata.referrer],
980
+ ["category", metadata.category],
981
+ ["creator", metadata.creator],
982
+ ["publisher", metadata.publisher]
983
+ ];
984
+ for (const [name, content] of simpleMetaProps) if (content) elements.push({
985
+ tag: "meta",
986
+ attrs: {
987
+ name,
988
+ content
989
+ }
990
+ });
991
+ if (metadata.keywords) {
992
+ const content = Array.isArray(metadata.keywords) ? metadata.keywords.join(", ") : metadata.keywords;
993
+ elements.push({
994
+ tag: "meta",
995
+ attrs: {
996
+ name: "keywords",
997
+ content
998
+ }
999
+ });
1000
+ }
1001
+ if (metadata.robots) {
1002
+ const content = typeof metadata.robots === "string" ? metadata.robots : renderRobotsObject(metadata.robots);
1003
+ elements.push({
1004
+ tag: "meta",
1005
+ attrs: {
1006
+ name: "robots",
1007
+ content
1008
+ }
1009
+ });
1010
+ if (typeof metadata.robots === "object" && metadata.robots.googleBot) {
1011
+ const gbContent = typeof metadata.robots.googleBot === "string" ? metadata.robots.googleBot : renderRobotsObject(metadata.robots.googleBot);
1012
+ elements.push({
1013
+ tag: "meta",
1014
+ attrs: {
1015
+ name: "googlebot",
1016
+ content: gbContent
1017
+ }
1018
+ });
1019
+ }
1020
+ }
1021
+ if (metadata.openGraph) renderOpenGraph(metadata.openGraph, elements);
1022
+ if (metadata.twitter) renderTwitter(metadata.twitter, elements);
1023
+ if (metadata.icons) renderIcons(metadata.icons, elements);
1024
+ if (metadata.manifest) elements.push({
1025
+ tag: "link",
1026
+ attrs: {
1027
+ rel: "manifest",
1028
+ href: metadata.manifest
1029
+ }
1030
+ });
1031
+ if (metadata.alternates) renderAlternates(metadata.alternates, elements);
1032
+ if (metadata.verification) renderVerification(metadata.verification, elements);
1033
+ if (metadata.formatDetection) {
1034
+ const parts = [];
1035
+ if (metadata.formatDetection.telephone === false) parts.push("telephone=no");
1036
+ if (metadata.formatDetection.email === false) parts.push("email=no");
1037
+ if (metadata.formatDetection.address === false) parts.push("address=no");
1038
+ if (parts.length > 0) elements.push({
1039
+ tag: "meta",
1040
+ attrs: {
1041
+ name: "format-detection",
1042
+ content: parts.join(", ")
1043
+ }
1044
+ });
1045
+ }
1046
+ if (metadata.authors) {
1047
+ const authorList = Array.isArray(metadata.authors) ? metadata.authors : [metadata.authors];
1048
+ for (const author of authorList) {
1049
+ if (author.name) elements.push({
1050
+ tag: "meta",
1051
+ attrs: {
1052
+ name: "author",
1053
+ content: author.name
1054
+ }
1055
+ });
1056
+ if (author.url) elements.push({
1057
+ tag: "link",
1058
+ attrs: {
1059
+ rel: "author",
1060
+ href: author.url
1061
+ }
1062
+ });
1063
+ }
1064
+ }
1065
+ if (metadata.appleWebApp) renderAppleWebApp(metadata.appleWebApp, elements);
1066
+ if (metadata.appLinks) renderAppLinks(metadata.appLinks, elements);
1067
+ if (metadata.itunes) renderItunes(metadata.itunes, elements);
1068
+ if (metadata.other) for (const [name, value] of Object.entries(metadata.other)) {
1069
+ const content = Array.isArray(value) ? value.join(", ") : value;
1070
+ elements.push({
1071
+ tag: "meta",
1072
+ attrs: {
1073
+ name,
1074
+ content
1075
+ }
1076
+ });
1077
+ }
1078
+ return elements;
1079
+ }
1080
+ function renderRobotsObject(robots) {
1081
+ const parts = [];
1082
+ if (robots.index === true) parts.push("index");
1083
+ if (robots.index === false) parts.push("noindex");
1084
+ if (robots.follow === true) parts.push("follow");
1085
+ if (robots.follow === false) parts.push("nofollow");
1086
+ return parts.join(", ");
1087
+ }
1088
+ //#endregion
1089
+ //#region src/server/metadata.ts
1090
+ /**
1091
+ * Resolve a title value with an optional template.
1092
+ *
1093
+ * - string → apply template if present
1094
+ * - { absolute: '...' } → use as-is, skip template
1095
+ * - { default: '...' } → use as fallback (no template applied)
1096
+ * - undefined → undefined
1097
+ */
1098
+ function resolveTitle(title, template) {
1099
+ if (title === void 0 || title === null) return;
1100
+ if (typeof title === "string") return template ? template.replace("%s", title) : title;
1101
+ if (title.absolute !== void 0) return title.absolute;
1102
+ if (title.default !== void 0) return title.default;
1103
+ }
1104
+ /**
1105
+ * Resolve metadata from a segment chain.
1106
+ *
1107
+ * Processes entries from root layout to page (in segment order).
1108
+ * The merge algorithm:
1109
+ * 1. Shallow-merge all keys except title (later wins)
1110
+ * 2. Track the most recent title template
1111
+ * 3. Resolve the final title using the template
1112
+ *
1113
+ * In error state, the page entry is dropped and noindex is injected.
1114
+ *
1115
+ * See design/16-metadata.md §"Merge Algorithm"
1116
+ */
1117
+ function resolveMetadata(entries, options = {}) {
1118
+ const { errorState = false } = options;
1119
+ const merged = {};
1120
+ let titleTemplate;
1121
+ let lastDefault;
1122
+ let rawTitle;
1123
+ for (const { metadata, isPage } of entries) {
1124
+ if (errorState && isPage) continue;
1125
+ if (metadata.title !== void 0 && typeof metadata.title === "object") {
1126
+ if (metadata.title.template !== void 0) titleTemplate = metadata.title.template;
1127
+ if (metadata.title.default !== void 0) lastDefault = metadata.title.default;
1128
+ }
1129
+ for (const key of Object.keys(metadata)) {
1130
+ if (key === "title") continue;
1131
+ merged[key] = metadata[key];
1132
+ }
1133
+ if (metadata.title !== void 0) rawTitle = metadata.title;
1134
+ }
1135
+ if (errorState) {
1136
+ rawTitle = lastDefault !== void 0 ? { default: lastDefault } : rawTitle;
1137
+ titleTemplate = void 0;
1138
+ }
1139
+ const resolvedTitle = resolveTitle(rawTitle, titleTemplate);
1140
+ if (resolvedTitle !== void 0) merged.title = resolvedTitle;
1141
+ if (errorState) merged.robots = "noindex";
1142
+ return merged;
1143
+ }
1144
+ /**
1145
+ * Check if a string is an absolute URL.
1146
+ */
1147
+ function isAbsoluteUrl(url) {
1148
+ return url.startsWith("http://") || url.startsWith("https://") || url.startsWith("//");
1149
+ }
1150
+ /**
1151
+ * Resolve a relative URL against a base URL.
1152
+ */
1153
+ function resolveUrl(url, base) {
1154
+ if (isAbsoluteUrl(url)) return url;
1155
+ return new URL(url, base).toString();
1156
+ }
1157
+ /**
1158
+ * Resolve relative URLs in metadata fields against metadataBase.
1159
+ *
1160
+ * Returns a new metadata object with URLs resolved. Absolute URLs are not modified.
1161
+ * If metadataBase is not set, returns the metadata unchanged.
1162
+ */
1163
+ function resolveMetadataUrls(metadata) {
1164
+ const base = metadata.metadataBase;
1165
+ if (!base) return metadata;
1166
+ const result = { ...metadata };
1167
+ if (result.openGraph) {
1168
+ result.openGraph = { ...result.openGraph };
1169
+ if (typeof result.openGraph.images === "string") result.openGraph.images = resolveUrl(result.openGraph.images, base);
1170
+ else if (Array.isArray(result.openGraph.images)) result.openGraph.images = result.openGraph.images.map((img) => ({
1171
+ ...img,
1172
+ url: resolveUrl(img.url, base)
1173
+ }));
1174
+ else if (result.openGraph.images) result.openGraph.images = {
1175
+ ...result.openGraph.images,
1176
+ url: resolveUrl(result.openGraph.images.url, base)
1177
+ };
1178
+ if (result.openGraph.url && !isAbsoluteUrl(result.openGraph.url)) result.openGraph.url = resolveUrl(result.openGraph.url, base);
1179
+ }
1180
+ if (result.twitter) {
1181
+ result.twitter = { ...result.twitter };
1182
+ if (typeof result.twitter.images === "string") result.twitter.images = resolveUrl(result.twitter.images, base);
1183
+ else if (Array.isArray(result.twitter.images)) {
1184
+ const resolved = result.twitter.images.map((img) => typeof img === "string" ? resolveUrl(img, base) : {
1185
+ ...img,
1186
+ url: resolveUrl(img.url, base)
1187
+ });
1188
+ const allStrings = resolved.every((r) => typeof r === "string");
1189
+ result.twitter.images = allStrings ? resolved : resolved;
1190
+ } else if (result.twitter.images) result.twitter.images = {
1191
+ ...result.twitter.images,
1192
+ url: resolveUrl(result.twitter.images.url, base)
1193
+ };
1194
+ }
1195
+ if (result.alternates) {
1196
+ result.alternates = { ...result.alternates };
1197
+ if (result.alternates.canonical && !isAbsoluteUrl(result.alternates.canonical)) result.alternates.canonical = resolveUrl(result.alternates.canonical, base);
1198
+ if (result.alternates.languages) {
1199
+ const langs = {};
1200
+ for (const [lang, url] of Object.entries(result.alternates.languages)) langs[lang] = isAbsoluteUrl(url) ? url : resolveUrl(url, base);
1201
+ result.alternates.languages = langs;
1202
+ }
1203
+ }
1204
+ if (result.icons) {
1205
+ result.icons = { ...result.icons };
1206
+ if (typeof result.icons.icon === "string") result.icons.icon = resolveUrl(result.icons.icon, base);
1207
+ else if (Array.isArray(result.icons.icon)) result.icons.icon = result.icons.icon.map((i) => ({
1208
+ ...i,
1209
+ url: resolveUrl(i.url, base)
1210
+ }));
1211
+ if (typeof result.icons.apple === "string") result.icons.apple = resolveUrl(result.icons.apple, base);
1212
+ else if (Array.isArray(result.icons.apple)) result.icons.apple = result.icons.apple.map((i) => ({
1213
+ ...i,
1214
+ url: resolveUrl(i.url, base)
1215
+ }));
1216
+ }
1217
+ return result;
1218
+ }
1219
+ //#endregion
1220
+ //#region src/server/safe-load.ts
1221
+ /**
1222
+ * Custom error class for module load failures.
1223
+ *
1224
+ * Preserves the original error as `cause` while providing a
1225
+ * human-readable message with the file path.
1226
+ */
1227
+ var ModuleLoadError = class extends Error {
1228
+ /** The file path that failed to load. */
1229
+ filePath;
1230
+ constructor(filePath, cause) {
1231
+ const originalMessage = cause instanceof Error ? cause.message : String(cause);
1232
+ super(`[timber] Failed to load module ${filePath}\n ${originalMessage}`, { cause });
1233
+ this.name = "ModuleLoadError";
1234
+ this.filePath = filePath;
1235
+ }
1236
+ };
1237
+ /**
1238
+ * Load a route manifest module with enriched error context.
1239
+ *
1240
+ * On success: returns the module object (same as `loader.load()`).
1241
+ * On failure: throws `ModuleLoadError` with file path and original cause.
1242
+ *
1243
+ * For error rendering paths that need fallthrough instead of throwing,
1244
+ * callers should catch at the call site:
1245
+ *
1246
+ * ```ts
1247
+ * // Throwing (default) — route-element-builder, api-handler, etc.
1248
+ * const mod = await loadModule(segment.page);
1249
+ *
1250
+ * // Fallthrough — error-renderer, error-boundary-wrapper
1251
+ * const mod = await loadModule(segment.error).catch(() => null);
1252
+ * ```
1253
+ */
1254
+ async function loadModule(loader) {
1255
+ try {
1256
+ return await loader.load();
1257
+ } catch (error) {
1258
+ throw new ModuleLoadError(loader.filePath, error);
1259
+ }
1260
+ }
1261
+ //#endregion
1262
+ //#region src/server/deny-page-resolver.ts
1263
+ /**
1264
+ * Deny Page Resolver — resolves status-code file components for in-tree deny handling.
1265
+ *
1266
+ * When AccessGate or PageDenyBoundary catches a DenySignal, they need to
1267
+ * render the matching deny page (403.tsx, 4xx.tsx, error.tsx) as a normal
1268
+ * element in the React tree. This module resolves the deny page chain from
1269
+ * the segment chain — a list of fallback components ordered by specificity.
1270
+ *
1271
+ * The chain is built during buildRouteElement and passed as a prop to
1272
+ * AccessGate and PageDenyBoundary. At catch time, the first matching
1273
+ * component is rendered.
1274
+ *
1275
+ * See design/10-error-handling.md §"Status-Code Files", TIM-666.
1276
+ */
1277
+ /**
1278
+ * Find the first deny page in the chain that matches the given status code.
1279
+ * Returns a React element for the matching component, or null if no match.
1280
+ */
1281
+ function renderMatchingDenyPage(chain, status, data) {
1282
+ const h = createElement;
1283
+ for (const entry of chain) {
1284
+ if (entry.status === status) return h(entry.component, {
1285
+ status,
1286
+ dangerouslyPassData: data
1287
+ });
1288
+ if (entry.status === 400 && status >= 400 && status <= 499) return h(entry.component, {
1289
+ status,
1290
+ dangerouslyPassData: data
1291
+ });
1292
+ if (entry.status === 500 && status >= 500 && status <= 599) return h(entry.component, {
1293
+ status,
1294
+ dangerouslyPassData: data
1295
+ });
1296
+ if (entry.status === null) return h(entry.component, {
1297
+ status,
1298
+ dangerouslyPassData: data
1299
+ });
1300
+ }
1301
+ return null;
1302
+ }
1303
+ /**
1304
+ * Set the deny status in the request context ALS.
1305
+ * Called from AccessGate / PageDenyBoundary when a DenySignal is caught.
1306
+ * The pipeline reads this after render to set the HTTP status code.
1307
+ */
1308
+ function setDenyStatus(status) {
1309
+ const store = requestContextAls.getStore();
1310
+ if (store) store.denyStatus = status;
1311
+ }
1312
+ //#endregion
1313
+ //#region src/server/access-gate.tsx
1314
+ /**
1315
+ * AccessGate and SlotAccessGate — framework-injected async server components.
1316
+ *
1317
+ * AccessGate wraps each segment's layout in the element tree. It calls the
1318
+ * segment's access.ts before the layout renders. If access.ts calls deny()
1319
+ * or redirect(), the signal propagates as a render-phase throw — caught by
1320
+ * the flush controller to produce the correct HTTP status code.
1321
+ *
1322
+ * SlotAccessGate wraps parallel slot content. On denial, it renders the
1323
+ * graceful degradation chain: denied.tsx → default.tsx → null. Slot denial
1324
+ * does not affect the HTTP status code.
1325
+ *
1326
+ * See design/04-authorization.md and design/02-rendering-pipeline.md §"AccessGate"
1327
+ */
1328
+ /**
1329
+ * Framework-injected access gate for segments.
1330
+ *
1331
+ * When a pre-computed `verdict` prop is provided (from the pre-render pass
1332
+ * in route-element-builder.ts), AccessGate replays it synchronously — no
1333
+ * async, no re-execution of access.ts, immune to Suspense timing. The OTEL
1334
+ * span was already emitted during the pre-render pass.
1335
+ *
1336
+ * When no verdict is provided (backward compat with tree-builder.ts),
1337
+ * AccessGate calls accessFn directly with OTEL instrumentation.
1338
+ *
1339
+ * access.ts is a pure gate — return values are discarded. The layout below
1340
+ * gets the same data by calling the same cached functions (React.cache dedup).
1341
+ */
1342
+ function AccessGate(props) {
1343
+ const { accessFn, segmentName, verdict, denyPages, children } = props;
1344
+ if (verdict !== void 0) {
1345
+ if (verdict === "pass") return children;
1346
+ throw verdict;
1347
+ }
1348
+ return accessGateFallback(accessFn, segmentName, denyPages, children);
1349
+ }
1350
+ /**
1351
+ * Async fallback for AccessGate when no pre-computed verdict is available.
1352
+ * Calls accessFn with OTEL instrumentation.
1353
+ */
1354
+ async function accessGateFallback(accessFn, segmentName, denyPages, children) {
1355
+ try {
1356
+ await withSpan("timber.access", { "timber.segment": segmentName ?? "unknown" }, async () => {
1357
+ try {
1358
+ await accessFn();
1359
+ await setSpanAttribute("timber.result", "pass");
1360
+ } catch (error) {
1361
+ if (error instanceof DenySignal) {
1362
+ await setSpanAttribute("timber.result", "deny");
1363
+ await setSpanAttribute("timber.deny_status", error.status);
1364
+ if (error.sourceFile) await setSpanAttribute("timber.deny_file", error.sourceFile);
1365
+ } else if (error instanceof RedirectSignal) await setSpanAttribute("timber.result", "redirect");
1366
+ throw error;
1367
+ }
1368
+ });
1369
+ } catch (error) {
1370
+ if (error instanceof DenySignal && denyPages) {
1371
+ const denyElement = renderMatchingDenyPage(denyPages, error.status, error.data);
1372
+ if (denyElement) {
1373
+ setDenyStatus(error.status);
1374
+ return denyElement;
1375
+ }
1376
+ }
1377
+ throw error;
1378
+ }
1379
+ return children;
1380
+ }
1381
+ /**
1382
+ * Framework-injected access gate for parallel slots.
1383
+ *
1384
+ * On denial, graceful degradation: denied.tsx → default.tsx → null.
1385
+ * The HTTP status code is unaffected — slot denial is a UI concern, not
1386
+ * a protocol concern. The parent layout and sibling slots still render.
1387
+ *
1388
+ * DeniedComponent is passed instead of a pre-built element so that
1389
+ * DenySignal.data can be forwarded as the dangerouslyPassData prop
1390
+ * and the slot name can be passed as the slot prop. See TIM-488.
1391
+ *
1392
+ * redirect() in slot access.ts is a dev-mode error — redirecting from a
1393
+ * slot doesn't make architectural sense.
1394
+ */
1395
+ async function SlotAccessGate(props) {
1396
+ const { accessFn, DeniedComponent, slotName, createElement, defaultFallback, children } = props;
1397
+ try {
1398
+ await accessFn();
1399
+ } catch (error) {
1400
+ if (error instanceof DenySignal) return buildDeniedFallback(DeniedComponent, slotName, error.data, createElement) ?? defaultFallback ?? null;
1401
+ if (error instanceof RedirectSignal) {
1402
+ if (isDebug()) console.error("[timber] redirect() is not allowed in slot access.ts. Slots use deny() for graceful degradation — denied.tsx → default.tsx → null. If you need to redirect, move the logic to the parent segment's access.ts.");
1403
+ return buildDeniedFallback(DeniedComponent, slotName, void 0, createElement) ?? defaultFallback ?? null;
1404
+ }
1405
+ if (isDebug()) console.warn("[timber] Unhandled error in slot access.ts. Use deny() for access control, not unhandled throws.", error);
1406
+ throw error;
1407
+ }
1408
+ return children;
1409
+ }
1410
+ /**
1411
+ * Build the denied fallback element dynamically with DenySignal data.
1412
+ * Returns null if no DeniedComponent is available.
1413
+ */
1414
+ function buildDeniedFallback(DeniedComponent, slotName, data, createElement) {
1415
+ if (!DeniedComponent) return null;
1416
+ return createElement(DeniedComponent, {
1417
+ slot: slotName,
1418
+ dangerouslyPassData: data
1419
+ });
1420
+ }
1421
+ //#endregion
1422
+ //#region src/server/route-element-builder.ts
1423
+ /**
1424
+ * Thrown when a defineSegmentParams codec's parse() fails.
1425
+ * The pipeline catches this and responds with 404.
1426
+ */
1427
+ var ParamCoercionError = class extends Error {
1428
+ constructor(message) {
1429
+ super(message);
1430
+ this.name = "ParamCoercionError";
1431
+ }
1432
+ };
1433
+ //#endregion
1434
+ //#region src/server/version-skew.ts
1435
+ /**
1436
+ * Version Skew Detection — graceful recovery when stale clients hit new deployments.
1437
+ *
1438
+ * When a new version of the app is deployed, clients with open tabs still have
1439
+ * the old JavaScript bundle. Without version skew handling, these stale clients
1440
+ * will experience:
1441
+ *
1442
+ * 1. Server action calls that crash (action IDs are content-hashed)
1443
+ * 2. Chunk load failures (old filenames gone from CDN)
1444
+ * 3. RSC payload mismatches (component references differ between builds)
1445
+ *
1446
+ * This module implements deployment ID comparison:
1447
+ * - A per-build deployment ID is generated at build time (see build-manifest.ts)
1448
+ * - The client sends it via `X-Timber-Deployment-Id` header on every RSC/action request
1449
+ * - The server compares it against the current build's ID
1450
+ * - On mismatch: signal the client to reload (not crash)
1451
+ *
1452
+ * The deployment ID is always-on in production. Dev mode skips the check
1453
+ * (HMR handles code updates without full reloads).
1454
+ *
1455
+ * See design/25-production-deployments.md, TIM-446
1456
+ */
1457
+ /** Header sent by the client with every RSC/action request. */
1458
+ var DEPLOYMENT_ID_HEADER = "X-Timber-Deployment-Id";
1459
+ /** Response header that signals the client to do a full page reload. */
1460
+ var RELOAD_HEADER = "X-Timber-Reload";
1461
+ /**
1462
+ * The current build's deployment ID. Set at startup from the manifest init
1463
+ * module (globalThis.__TIMBER_DEPLOYMENT_ID__). Null in dev mode.
1464
+ */
1465
+ var currentDeploymentId = null;
1466
+ /**
1467
+ * Check if a request's deployment ID matches the current build.
1468
+ *
1469
+ * Returns `{ ok: true }` when:
1470
+ * - Dev mode (no deployment ID set — HMR handles updates)
1471
+ * - No deployment ID header (initial page load, non-RSC request)
1472
+ * - Deployment IDs match
1473
+ *
1474
+ * Returns `{ ok: false }` when:
1475
+ * - Client sends a deployment ID that differs from the current build
1476
+ */
1477
+ function checkVersionSkew(req) {
1478
+ if (!currentDeploymentId) return {
1479
+ ok: true,
1480
+ clientId: null
1481
+ };
1482
+ const clientId = req.headers.get(DEPLOYMENT_ID_HEADER);
1483
+ if (!clientId) return {
1484
+ ok: true,
1485
+ clientId: null
1486
+ };
1487
+ if (clientId === currentDeploymentId) return {
1488
+ ok: true,
1489
+ clientId
1490
+ };
1491
+ return {
1492
+ ok: false,
1493
+ clientId
1494
+ };
1495
+ }
1496
+ /**
1497
+ * Apply version skew reload headers to a response.
1498
+ * Sets X-Timber-Reload: 1 to signal the client to do a full page reload.
1499
+ */
1500
+ function applyReloadHeaders(headers) {
1501
+ headers.set(RELOAD_HEADER, "1");
1502
+ }
1503
+ //#endregion
1504
+ //#region src/server/pipeline-metadata.ts
1505
+ /**
1506
+ * Metadata route helpers for the request pipeline.
1507
+ *
1508
+ * Handles serving static metadata files and serializing sitemap responses.
1509
+ * Extracted from pipeline.ts to keep files under 500 lines.
1510
+ *
1511
+ * See design/16-metadata.md §"Metadata Routes"
1512
+ */
1513
+ /**
1514
+ * Content types that are text-based and should include charset=utf-8.
1515
+ * Binary formats (images) should not include charset.
1516
+ */
1517
+ var TEXT_CONTENT_TYPES = new Set([
1518
+ "application/xml",
1519
+ "text/plain",
1520
+ "application/json",
1521
+ "application/manifest+json",
1522
+ "image/svg+xml"
1523
+ ]);
1524
+ /**
1525
+ * Serve a static metadata file by reading it from disk.
1526
+ *
1527
+ * Static metadata route files (.xml, .txt, .json, .png, .ico, .svg, etc.)
1528
+ * are served as-is with the appropriate Content-Type header.
1529
+ * Text files include charset=utf-8; binary files do not.
1530
+ *
1531
+ * See design/16-metadata.md §"Metadata Routes"
1532
+ */
1533
+ async function serveStaticMetadataFile(metaMatch) {
1534
+ const { contentType, file } = metaMatch;
1535
+ const isText = TEXT_CONTENT_TYPES.has(contentType);
1536
+ const body = await readFile(file.filePath);
1537
+ const headers = {
1538
+ "Content-Type": isText ? `${contentType}; charset=utf-8` : contentType,
1539
+ "Content-Length": String(body.byteLength)
1540
+ };
1541
+ return new Response(body, {
1542
+ status: 200,
1543
+ headers
1544
+ });
1545
+ }
1546
+ /**
1547
+ * Serialize a sitemap array to XML.
1548
+ * Follows the sitemap.org protocol: https://www.sitemaps.org/protocol.html
1549
+ */
1550
+ function serializeSitemap(entries) {
1551
+ return `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${entries.map((e) => {
1552
+ let xml = ` <url>\n <loc>${escapeXml(e.url)}</loc>`;
1553
+ if (e.lastModified) {
1554
+ const date = e.lastModified instanceof Date ? e.lastModified.toISOString() : e.lastModified;
1555
+ xml += `\n <lastmod>${escapeXml(date)}</lastmod>`;
1556
+ }
1557
+ if (e.changeFrequency) xml += `\n <changefreq>${escapeXml(e.changeFrequency)}</changefreq>`;
1558
+ if (e.priority !== void 0) xml += `\n <priority>${e.priority}</priority>`;
1559
+ xml += "\n </url>";
1560
+ return xml;
1561
+ }).join("\n")}\n</urlset>`;
1562
+ }
1563
+ /** Escape special XML characters. */
1564
+ function escapeXml(str) {
1565
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
1566
+ }
1567
+ //#endregion
1568
+ //#region src/server/pipeline-interception.ts
1569
+ /**
1570
+ * Interception route matching for the request pipeline.
1571
+ *
1572
+ * Matches target URLs against interception rewrites to support the
1573
+ * modal route pattern (soft navigation intercepts).
1574
+ *
1575
+ * Extracted from pipeline.ts to keep files under 500 lines.
1576
+ *
1577
+ * See design/07-routing.md §"Intercepting Routes"
1578
+ */
1579
+ /**
1580
+ * Check if an intercepting route applies for this soft navigation.
1581
+ *
1582
+ * Matches the target pathname against interception rewrites, constrained
1583
+ * by the source URL (X-Timber-URL header — where the user navigates FROM).
1584
+ *
1585
+ * Returns the source pathname to re-match if interception applies, or null.
1586
+ */
1587
+ function findInterceptionMatch(targetPathname, sourceUrl, rewrites) {
1588
+ for (const rewrite of rewrites) {
1589
+ if (!sourceUrl.startsWith(rewrite.interceptingPrefix)) continue;
1590
+ if (pathnameMatchesPattern(targetPathname, rewrite.interceptedPattern)) return { sourcePathname: rewrite.interceptingPrefix };
1591
+ }
1592
+ return null;
1593
+ }
1594
+ /**
1595
+ * Check if a pathname matches a URL pattern with dynamic segments.
1596
+ *
1597
+ * Supports [param] (single segment) and [...param] (one or more segments).
1598
+ * Static segments must match exactly.
1599
+ */
1600
+ function pathnameMatchesPattern(pathname, pattern) {
1601
+ const pathParts = pathname === "/" ? [] : pathname.slice(1).split("/");
1602
+ const patternParts = pattern === "/" ? [] : pattern.slice(1).split("/");
1603
+ let pi = 0;
1604
+ for (let i = 0; i < patternParts.length; i++) {
1605
+ const seg = classifyUrlSegment(patternParts[i]);
1606
+ switch (seg.kind) {
1607
+ case "catch-all": return pi < pathParts.length;
1608
+ case "optional-catch-all": return true;
1609
+ case "dynamic":
1610
+ if (pi >= pathParts.length) return false;
1611
+ pi++;
1612
+ continue;
1613
+ case "static":
1614
+ if (pi >= pathParts.length || pathParts[pi] !== seg.value) return false;
1615
+ pi++;
1616
+ continue;
1617
+ }
1618
+ }
1619
+ return pi === pathParts.length;
1620
+ }
1621
+ //#endregion
1622
+ //#region src/server/pipeline.ts
1623
+ /**
1624
+ * Request pipeline — the central dispatch for all timber.js requests.
1625
+ *
1626
+ * Pipeline stages (in order):
1627
+ * proxy.ts → canonicalize → route match → 103 Early Hints → middleware.ts → render
1628
+ *
1629
+ * Each stage is a pure function or returns a Response to short-circuit.
1630
+ * Each request gets a trace ID, structured logging, and OTEL spans.
1631
+ *
1632
+ * See design/07-routing.md §"Request Lifecycle", design/02-rendering-pipeline.md §"Request Flow",
1633
+ * and design/17-logging.md §"Production Logging"
1634
+ */
1635
+ /** Keys that must never be merged via Object.assign — they pollute Object.prototype. */
1636
+ var DANGEROUS_KEYS = new Set([
1637
+ "__proto__",
1638
+ "constructor",
1639
+ "prototype"
1640
+ ]);
1641
+ /**
1642
+ * Shallow merge that skips prototype-polluting keys.
1643
+ *
1644
+ * Used instead of Object.assign when the source object comes from
1645
+ * user-authored codec output (segmentParams.parse), which could
1646
+ * contain __proto__, constructor, or prototype keys.
1647
+ *
1648
+ * See TIM-655, design/13-security.md
1649
+ */
1650
+ function safeMerge(target, source) {
1651
+ for (const key of Object.keys(source)) if (!DANGEROUS_KEYS.has(key)) target[key] = source[key];
1652
+ }
1653
+ /**
1654
+ * Run segment param coercion on the matched route's segments.
1655
+ *
1656
+ * Loads params.ts modules from segments that have them, extracts the
1657
+ * segmentParams definition, and coerces raw string params through codecs.
1658
+ * Throws ParamCoercionError if any codec fails (→ 404).
1659
+ *
1660
+ * This runs BEFORE middleware, so ctx.segmentParams is already typed.
1661
+ * See design/07-routing.md §"Where Coercion Runs"
1662
+ */
1663
+ async function coerceSegmentParams(match) {
1664
+ const segments = match.segments;
1665
+ for (const segment of segments) {
1666
+ if (!segment.params) continue;
1667
+ let mod;
1668
+ try {
1669
+ mod = await loadModule(segment.params);
1670
+ } catch (err) {
1671
+ throw new ParamCoercionError(`Failed to load params module for segment "${segment.segmentName}": ${err instanceof Error ? err.message : String(err)}`);
1672
+ }
1673
+ const segmentParamsDef = mod.segmentParams;
1674
+ if (!segmentParamsDef || typeof segmentParamsDef.parse !== "function") continue;
1675
+ try {
1676
+ const coerced = segmentParamsDef.parse(match.segmentParams);
1677
+ safeMerge(match.segmentParams, coerced);
1678
+ } catch (err) {
1679
+ throw new ParamCoercionError(err instanceof Error ? err.message : String(err));
1680
+ }
1681
+ }
1682
+ }
1683
+ /**
1684
+ * Create the request handler from a pipeline configuration.
1685
+ *
1686
+ * Returns a function that processes an incoming Request through all pipeline stages
1687
+ * and produces a Response. This is the top-level entry point for the server.
1688
+ */
1689
+ function createPipeline(config) {
1690
+ const { proxy, matchRoute, render, earlyHints, stripTrailingSlash = true, slowRequestMs = 3e3, serverTiming = "total", onPipelineError } = config;
1691
+ let activeRequests = 0;
1692
+ return async (req) => {
1693
+ const url = new URL(req.url);
1694
+ const method = req.method;
1695
+ const path = url.pathname;
1696
+ const startTime = performance.now();
1697
+ activeRequests++;
1698
+ return runWithTraceId(generateTraceId(), async () => {
1699
+ return runWithRequestContext(req, async () => {
1700
+ const runRequest = async () => {
1701
+ logRequestReceived({
1702
+ method,
1703
+ path
1704
+ });
1705
+ const response = await withSpan("http.server.request", {
1706
+ "http.request.method": method,
1707
+ "url.path": path
1708
+ }, async () => {
1709
+ const otelIds = await getOtelTraceId();
1710
+ if (otelIds) replaceTraceId(otelIds.traceId, otelIds.spanId);
1711
+ let result;
1712
+ if (proxy || config.proxyLoader) result = await runProxyPhase(req, method, path);
1713
+ else result = await handleRequest(req, method, path);
1714
+ await setSpanAttribute("http.response.status_code", result.status);
1715
+ if (serverTiming === "detailed") {
1716
+ const timingHeader = getServerTimingHeader();
1717
+ if (timingHeader) {
1718
+ result = ensureMutableResponse(result);
1719
+ result.headers.set("Server-Timing", timingHeader);
1720
+ }
1721
+ } else if (serverTiming === "total") {
1722
+ const totalMs = Math.round(performance.now() - startTime);
1723
+ result = ensureMutableResponse(result);
1724
+ result.headers.set("Server-Timing", `total;dur=${totalMs}`);
1725
+ }
1726
+ return result;
1727
+ });
1728
+ const durationMs = Math.round(performance.now() - startTime);
1729
+ const status = response.status;
1730
+ const concurrency = activeRequests;
1731
+ activeRequests--;
1732
+ logRequestCompleted({
1733
+ method,
1734
+ path,
1735
+ status,
1736
+ durationMs,
1737
+ concurrency
1738
+ });
1739
+ if (slowRequestMs > 0 && durationMs > slowRequestMs) logSlowRequest({
1740
+ method,
1741
+ path,
1742
+ durationMs,
1743
+ threshold: slowRequestMs,
1744
+ concurrency
1745
+ });
1746
+ return response;
1747
+ };
1748
+ return serverTiming === "detailed" ? runWithTimingCollector(runRequest) : runRequest();
1749
+ });
1750
+ });
1751
+ };
1752
+ async function runProxyPhase(req, method, path) {
1753
+ try {
1754
+ let proxyExport;
1755
+ if (config.proxyLoader) proxyExport = (await config.proxyLoader()).default;
1756
+ else proxyExport = config.proxy;
1757
+ const proxyFn = () => runProxy(proxyExport, req, () => handleRequest(req, method, path));
1758
+ return await withSpan("timber.proxy", {}, () => serverTiming === "detailed" ? withTiming("proxy", "proxy.ts", proxyFn) : proxyFn());
1759
+ } catch (error) {
1760
+ logProxyError({ error });
1761
+ await fireOnRequestError(error, req, "proxy");
1762
+ if (onPipelineError && error instanceof Error) onPipelineError(error, "proxy");
1763
+ return new Response(null, { status: 500 });
1764
+ }
1765
+ }
1766
+ /**
1767
+ * Build a redirect Response from a RedirectSignal.
1768
+ *
1769
+ * For RSC payload requests (client navigation), returns 204 + X-Timber-Redirect
1770
+ * so the client router can perform a soft SPA redirect. A raw 302 would be
1771
+ * turned into an opaque redirect by fetch({redirect:'manual'}), crashing
1772
+ * createFromFetch. See design/19-client-navigation.md.
1773
+ */
1774
+ function buildRedirectResponse(signal, req, headers) {
1775
+ if ((req.headers.get("Accept") ?? "").includes("text/x-component")) {
1776
+ headers.set("X-Timber-Redirect", signal.location);
1777
+ return new Response(null, {
1778
+ status: 204,
1779
+ headers
1780
+ });
1781
+ }
1782
+ headers.set("Location", signal.location);
1783
+ return new Response(null, {
1784
+ status: signal.status,
1785
+ headers
1786
+ });
1787
+ }
1788
+ async function handleRequest(req, method, path) {
1789
+ const result = canonicalize(new URL(req.url).pathname, stripTrailingSlash);
1790
+ if (!result.ok) return new Response(null, { status: result.status });
1791
+ const canonicalPathname = result.pathname;
1792
+ if (config.matchMetadataRoute) {
1793
+ const metaMatch = config.matchMetadataRoute(canonicalPathname);
1794
+ if (metaMatch) try {
1795
+ if (metaMatch.isStatic) return await serveStaticMetadataFile(metaMatch);
1796
+ const mod = await loadModule(metaMatch.file);
1797
+ if (typeof mod.default !== "function") return new Response("Metadata route must export a default function", { status: 500 });
1798
+ const handlerResult = await mod.default();
1799
+ if (handlerResult instanceof Response) return handlerResult;
1800
+ const contentType = metaMatch.contentType;
1801
+ let body;
1802
+ if (typeof handlerResult === "string") body = handlerResult;
1803
+ else if (contentType === "application/xml") body = serializeSitemap(handlerResult);
1804
+ else if (contentType === "application/manifest+json") body = JSON.stringify(handlerResult, null, 2);
1805
+ else body = typeof handlerResult === "string" ? handlerResult : String(handlerResult);
1806
+ return new Response(body, {
1807
+ status: 200,
1808
+ headers: { "Content-Type": `${contentType}; charset=utf-8` }
1809
+ });
1810
+ } catch (error) {
1811
+ logRenderError({
1812
+ method,
1813
+ path,
1814
+ error
1815
+ });
1816
+ if (onPipelineError && error instanceof Error) onPipelineError(error, "metadata-route");
1817
+ return new Response(null, { status: 500 });
1818
+ }
1819
+ }
1820
+ if (config.autoSitemapHandler) try {
1821
+ const sitemapResponse = await config.autoSitemapHandler(canonicalPathname);
1822
+ if (sitemapResponse) return sitemapResponse;
1823
+ } catch (error) {
1824
+ logRenderError({
1825
+ method,
1826
+ path,
1827
+ error
1828
+ });
1829
+ if (onPipelineError && error instanceof Error) onPipelineError(error, "auto-sitemap");
1830
+ return new Response(null, { status: 500 });
1831
+ }
1832
+ if ((req.headers.get("Accept") ?? "").includes("text/x-component")) {
1833
+ if (!checkVersionSkew(req).ok) {
1834
+ const reloadHeaders = new Headers();
1835
+ applyReloadHeaders(reloadHeaders);
1836
+ return new Response(null, {
1837
+ status: 204,
1838
+ headers: reloadHeaders
1839
+ });
1840
+ }
1841
+ }
1842
+ let match = matchRoute(canonicalPathname);
1843
+ let interception;
1844
+ const sourceUrl = req.headers.get("X-Timber-URL");
1845
+ if (sourceUrl && config.interceptionRewrites?.length) {
1846
+ const intercepted = findInterceptionMatch(canonicalPathname, sourceUrl, config.interceptionRewrites);
1847
+ if (intercepted) {
1848
+ const sourceMatch = matchRoute(intercepted.sourcePathname);
1849
+ if (sourceMatch) {
1850
+ match = sourceMatch;
1851
+ interception = { targetPathname: canonicalPathname };
1852
+ }
1853
+ }
1854
+ }
1855
+ if (!match) {
1856
+ if (config.renderNoMatch) {
1857
+ const responseHeaders = new Headers();
1858
+ return config.renderNoMatch(req, responseHeaders);
1859
+ }
1860
+ return new Response(null, { status: 404 });
1861
+ }
1862
+ const responseHeaders = new Headers();
1863
+ const requestHeaderOverlay = new Headers();
1864
+ responseHeaders.set("Cache-Control", "private, no-cache, no-store, max-age=0, must-revalidate");
1865
+ if (earlyHints) try {
1866
+ await earlyHints(match, req, responseHeaders);
1867
+ } catch {}
1868
+ try {
1869
+ await coerceSegmentParams(match);
1870
+ } catch (error) {
1871
+ if (error instanceof ParamCoercionError) {
1872
+ const leafSegment = match.segments[match.segments.length - 1];
1873
+ if (leafSegment.route && !leafSegment.page) return new Response(null, { status: 404 });
1874
+ if (config.renderNoMatch) return config.renderNoMatch(req, responseHeaders);
1875
+ return new Response(null, { status: 404 });
1876
+ }
1877
+ throw error;
1878
+ }
1879
+ setSegmentParams(match.segmentParams);
1880
+ if (match.middlewareChain.length > 0) {
1881
+ const ctx = {
1882
+ req,
1883
+ requestHeaders: requestHeaderOverlay,
1884
+ headers: responseHeaders,
1885
+ segmentParams: match.segmentParams,
1886
+ earlyHints: (hints) => {
1887
+ for (const hint of hints) {
1888
+ let value;
1889
+ if (hint.as !== void 0) value = `<${hint.href}>; as=${hint.as}; rel=${hint.rel}`;
1890
+ else value = `<${hint.href}>; rel=${hint.rel}`;
1891
+ if (hint.crossOrigin !== void 0) value += `; crossorigin=${hint.crossOrigin}`;
1892
+ if (hint.fetchPriority !== void 0) value += `; fetchpriority=${hint.fetchPriority}`;
1893
+ responseHeaders.append("Link", value);
1894
+ }
1895
+ }
1896
+ };
1897
+ try {
1898
+ setMutableCookieContext(true);
1899
+ const chainFn = () => runMiddlewareChain(match.middlewareChain, ctx);
1900
+ const middlewareResponse = await withSpan("timber.middleware", {}, () => serverTiming === "detailed" ? withTiming("mw", "middleware.ts", chainFn) : chainFn());
1901
+ setMutableCookieContext(false);
1902
+ if (middlewareResponse) {
1903
+ const finalResponse = ensureMutableResponse(middlewareResponse);
1904
+ applyCookieJar(finalResponse.headers);
1905
+ const existingKeys = new Set([...finalResponse.headers.keys()].map((k) => k.toLowerCase()));
1906
+ for (const [key, value] of responseHeaders.entries()) if (!existingKeys.has(key.toLowerCase())) finalResponse.headers.append(key, value);
1907
+ logMiddlewareShortCircuit({
1908
+ method,
1909
+ path,
1910
+ status: finalResponse.status
1911
+ });
1912
+ return finalResponse;
1913
+ }
1914
+ applyRequestHeaderOverlay(requestHeaderOverlay);
1915
+ } catch (error) {
1916
+ setMutableCookieContext(false);
1917
+ if (error instanceof RedirectSignal) {
1918
+ applyCookieJar(responseHeaders);
1919
+ return buildRedirectResponse(error, req, responseHeaders);
1920
+ }
1921
+ if (error instanceof DenySignal) {
1922
+ applyCookieJar(responseHeaders);
1923
+ if (config.renderDenyFallback) try {
1924
+ return await config.renderDenyFallback(error, req, responseHeaders);
1925
+ } catch {}
1926
+ return new Response(null, {
1927
+ status: error.status,
1928
+ headers: responseHeaders
1929
+ });
1930
+ }
1931
+ logMiddlewareError({
1932
+ method,
1933
+ path,
1934
+ error
1935
+ });
1936
+ await fireOnRequestError(error, req, "handler");
1937
+ if (onPipelineError && error instanceof Error) onPipelineError(error, "middleware");
1938
+ return new Response(null, { status: 500 });
1939
+ }
1940
+ }
1941
+ applyCookieJar(responseHeaders);
1942
+ try {
1943
+ const renderFn = () => render(req, match, responseHeaders, requestHeaderOverlay, interception);
1944
+ const response = await withSpan("timber.render", { "http.route": canonicalPathname }, () => serverTiming === "detailed" ? withTiming("render", "RSC + SSR render", renderFn) : renderFn());
1945
+ markResponseFlushed();
1946
+ return response;
1947
+ } catch (error) {
1948
+ if (error instanceof DenySignal) {
1949
+ if (config.renderDenyFallback) try {
1950
+ return await config.renderDenyFallback(error, req, responseHeaders);
1951
+ } catch {}
1952
+ return new Response(null, {
1953
+ status: error.status,
1954
+ headers: responseHeaders
1955
+ });
1956
+ }
1957
+ if (error instanceof RedirectSignal) return buildRedirectResponse(error, req, responseHeaders);
1958
+ logRenderError({
1959
+ method,
1960
+ path,
1961
+ error
1962
+ });
1963
+ await fireOnRequestError(error, req, "render");
1964
+ if (onPipelineError && error instanceof Error) onPipelineError(error, "render");
1965
+ if (config.renderFallbackError) try {
1966
+ return await config.renderFallbackError(error, req, responseHeaders);
1967
+ } catch {}
1968
+ return new Response(null, { status: 500 });
1969
+ }
1970
+ }
1971
+ }
1972
+ /**
1973
+ * Fire the user's onRequestError hook with request context.
1974
+ * Extracts request info from the Request object and calls the instrumentation hook.
1975
+ */
1976
+ async function fireOnRequestError(error, req, phase) {
1977
+ const url = new URL(req.url);
1978
+ const headersObj = {};
1979
+ req.headers.forEach((v, k) => {
1980
+ headersObj[k] = v;
1981
+ });
1982
+ await callOnRequestError(error, {
1983
+ method: req.method,
1984
+ path: url.pathname,
1985
+ headers: headersObj
1986
+ }, {
1987
+ phase,
1988
+ routePath: url.pathname,
1989
+ routeType: "page",
1990
+ traceId: getTraceId()
1991
+ });
1992
+ }
1993
+ /**
1994
+ * Apply all Set-Cookie headers from the cookie jar to a Headers object.
1995
+ * Each cookie gets its own Set-Cookie header per RFC 6265 §4.1.
1996
+ */
1997
+ function applyCookieJar(headers) {
1998
+ for (const value of getSetCookieHeaders()) headers.append("Set-Cookie", value);
1999
+ }
2000
+ /**
2001
+ * Ensure a Response has mutable headers so the pipeline can safely append
2002
+ * Set-Cookie and Server-Timing entries.
2003
+ *
2004
+ * `Response.redirect()` and some platform-level responses return objects
2005
+ * with immutable headers. Calling `.set()` or `.append()` on them throws
2006
+ * `TypeError: immutable`. This helper detects the immutable case by
2007
+ * attempting a no-op write and, on failure, clones into a fresh Response
2008
+ * with mutable headers.
2009
+ */
2010
+ function ensureMutableResponse(response) {
2011
+ try {
2012
+ response.headers.set("X-Timber-Probe", "1");
2013
+ response.headers.delete("X-Timber-Probe");
2014
+ return response;
2015
+ } catch {
2016
+ return new Response(response.body, {
2017
+ status: response.status,
2018
+ statusText: response.statusText,
2019
+ headers: new Headers(response.headers)
2020
+ });
2021
+ }
2022
+ }
2023
+ //#endregion
2024
+ //#region src/server/build-manifest.ts
2025
+ /**
2026
+ * Collect all CSS files needed for a matched route's segment chain.
2027
+ *
2028
+ * Walks segments root → leaf, collecting CSS for each layout and page.
2029
+ * Deduplicates while preserving order (root layout CSS first).
2030
+ */
2031
+ function collectRouteCss(segments, manifest) {
2032
+ const seen = /* @__PURE__ */ new Set();
2033
+ const result = [];
2034
+ for (const segment of segments) for (const file of [segment.layout, segment.page]) {
2035
+ if (!file) continue;
2036
+ const cssFiles = manifest.css[file.filePath];
2037
+ if (!cssFiles) continue;
2038
+ for (const url of cssFiles) if (!seen.has(url)) {
2039
+ seen.add(url);
2040
+ result.push(url);
2041
+ }
2042
+ }
2043
+ return result;
2044
+ }
2045
+ /**
2046
+ * Collect all font entries needed for a matched route's segment chain.
2047
+ *
2048
+ * Walks segments root → leaf, collecting fonts for each layout and page.
2049
+ * Deduplicates by href while preserving order.
2050
+ */
2051
+ function collectRouteFonts(segments, manifest) {
2052
+ const seen = /* @__PURE__ */ new Set();
2053
+ const result = [];
2054
+ for (const segment of segments) for (const file of [segment.layout, segment.page]) {
2055
+ if (!file) continue;
2056
+ const fonts = manifest.fonts[file.filePath];
2057
+ if (!fonts) continue;
2058
+ for (const entry of fonts) if (!seen.has(entry.href)) {
2059
+ seen.add(entry.href);
2060
+ result.push(entry);
2061
+ }
2062
+ }
2063
+ return result;
2064
+ }
2065
+ /**
2066
+ * Collect modulepreload URLs for a matched route's segment chain.
2067
+ *
2068
+ * Walks segments root → leaf, collecting transitive JS dependencies
2069
+ * for each layout and page. Deduplicates across segments.
2070
+ */
2071
+ function collectRouteModulepreloads(segments, manifest) {
2072
+ const seen = /* @__PURE__ */ new Set();
2073
+ const result = [];
2074
+ for (const segment of segments) for (const file of [segment.layout, segment.page]) {
2075
+ if (!file) continue;
2076
+ const preloads = manifest.modulepreload[file.filePath];
2077
+ if (!preloads) continue;
2078
+ for (const url of preloads) if (!seen.has(url)) {
2079
+ seen.add(url);
2080
+ result.push(url);
2081
+ }
2082
+ }
2083
+ return result;
2084
+ }
2085
+ //#endregion
2086
+ //#region src/server/early-hints.ts
2087
+ /**
2088
+ * 103 Early Hints utilities.
2089
+ *
2090
+ * Early Hints are sent before the final response to let the browser
2091
+ * start fetching critical resources (CSS, fonts, JS) while the server
2092
+ * is still rendering.
2093
+ *
2094
+ * The framework collects hints from two sources:
2095
+ * 1. Build manifest — CSS, fonts, and JS chunks known at route-match time
2096
+ * 2. ctx.earlyHints() — explicit hints added by middleware or route handlers
2097
+ *
2098
+ * Both are emitted as Link headers. Cloudflare CDN automatically converts
2099
+ * Link headers into 103 Early Hints responses.
2100
+ *
2101
+ * Design docs: 02-rendering-pipeline.md §"Early Hints (103)"
2102
+ */
2103
+ /**
2104
+ * Format a single EarlyHint as a Link header value.
2105
+ *
2106
+ * Attribute order: `as` before `rel` to match Cloudflare CDN's cached
2107
+ * Early Hints format. Cloudflare caches Link headers from 200 responses
2108
+ * and re-emits them as 103 Early Hints on subsequent requests. If our
2109
+ * attribute order differs from Cloudflare's cached copy, the browser
2110
+ * sees two preload headers for the same URL (different attribute order)
2111
+ * and warns "Preload was ignored." Matching the order ensures the
2112
+ * browser deduplicates them correctly.
2113
+ *
2114
+ * Examples:
2115
+ * `</styles/root.css>; as=style; rel=preload`
2116
+ * `</fonts/inter.woff2>; as=font; rel=preload; crossorigin=anonymous`
2117
+ * `</_timber/client.js>; rel=modulepreload`
2118
+ * `<https://fonts.googleapis.com>; rel=preconnect`
2119
+ */
2120
+ function formatLinkHeader(hint) {
2121
+ if (hint.as !== void 0) {
2122
+ let value = `<${hint.href}>; as=${hint.as}; rel=${hint.rel}`;
2123
+ if (hint.crossOrigin !== void 0) value += `; crossorigin=${hint.crossOrigin}`;
2124
+ if (hint.fetchPriority !== void 0) value += `; fetchpriority=${hint.fetchPriority}`;
2125
+ return value;
2126
+ }
2127
+ let value = `<${hint.href}>; rel=${hint.rel}`;
2128
+ if (hint.crossOrigin !== void 0) value += `; crossorigin=${hint.crossOrigin}`;
2129
+ if (hint.fetchPriority !== void 0) value += `; fetchpriority=${hint.fetchPriority}`;
2130
+ return value;
2131
+ }
2132
+ /**
2133
+ * Collect all Link header strings for a matched route's segment chain.
2134
+ *
2135
+ * Walks the build manifest to emit hints for:
2136
+ * - CSS stylesheets (as=style; rel=preload)
2137
+ * - Font assets (as=font; rel=preload; crossorigin)
2138
+ * - JS modulepreload hints (rel=modulepreload) — unless skipJs is set
2139
+ *
2140
+ * Also emits global CSS from the `_global` manifest key. Route files
2141
+ * are server components that don't appear in the client bundle, so
2142
+ * per-route CSS keying doesn't work with the RSC plugin. The `_global`
2143
+ * key contains all CSS assets from the client build — fine for early
2144
+ * hints since they're just prefetch signals.
2145
+ *
2146
+ * Returns formatted Link header strings, deduplicated by URL, root → leaf order.
2147
+ * Returns an empty array in dev mode (manifest is empty).
2148
+ */
2149
+ function collectEarlyHintHeaders(segments, manifest, options) {
2150
+ const result = [];
2151
+ const seenUrls = /* @__PURE__ */ new Set();
2152
+ const add = (url, header) => {
2153
+ if (!seenUrls.has(url)) {
2154
+ seenUrls.add(url);
2155
+ result.push(header);
2156
+ }
2157
+ };
2158
+ for (const url of collectRouteCss(segments, manifest)) add(url, formatLinkHeader({
2159
+ href: url,
2160
+ rel: "preload",
2161
+ as: "style"
2162
+ }));
2163
+ for (const url of manifest.css["_global"] ?? []) add(url, formatLinkHeader({
2164
+ href: url,
2165
+ rel: "preload",
2166
+ as: "style"
2167
+ }));
2168
+ for (const font of collectRouteFonts(segments, manifest)) add(font.href, formatLinkHeader({
2169
+ href: font.href,
2170
+ rel: "preload",
2171
+ as: "font",
2172
+ crossOrigin: "anonymous"
2173
+ }));
2174
+ if (!options?.skipJs) for (const url of collectRouteModulepreloads(segments, manifest)) add(url, formatLinkHeader({
2175
+ href: url,
2176
+ rel: "modulepreload"
2177
+ }));
2178
+ return result;
2179
+ }
2180
+ //#endregion
2181
+ //#region src/server/early-hints-sender.ts
2182
+ /**
2183
+ * Per-request 103 Early Hints sender — ALS bridge for platform adapters.
2184
+ *
2185
+ * The pipeline collects Link headers for CSS, fonts, and JS chunks at
2186
+ * route-match time. On platforms that support it (Node.js v18.11+, Bun),
2187
+ * the adapter can send these as a 103 Early Hints interim response before
2188
+ * the final response is ready.
2189
+ *
2190
+ * This module provides an ALS-based bridge: the generated entry point
2191
+ * (e.g., the Nitro entry) wraps the handler with `runWithEarlyHintsSender`,
2192
+ * binding a per-request sender function. The pipeline calls
2193
+ * `sendEarlyHints103()` to fire the 103 if a sender is available.
2194
+ *
2195
+ * On platforms where 103 is handled at the CDN level (e.g., Cloudflare
2196
+ * converts Link headers into 103 automatically), no sender is installed
2197
+ * and `sendEarlyHints103()` is a no-op.
2198
+ *
2199
+ * Design doc: 02-rendering-pipeline.md §"Early Hints (103)"
2200
+ */
2201
+ /**
2202
+ * Run a function with a per-request early hints sender installed.
2203
+ *
2204
+ * Called by generated entry points (e.g., Nitro node-server/bun) to
2205
+ * bind the platform's writeEarlyHints capability for the request duration.
2206
+ */
2207
+ function runWithEarlyHintsSender(sender, fn) {
2208
+ return earlyHintsSenderAls.run(sender, fn);
2209
+ }
2210
+ /**
2211
+ * Send collected Link headers as a 103 Early Hints response.
2212
+ *
2213
+ * No-op if no sender is installed for the current request (e.g., on
2214
+ * Cloudflare where the CDN handles 103 automatically, or in dev mode).
2215
+ *
2216
+ * Non-fatal: errors from the sender are caught and silently ignored.
2217
+ */
2218
+ function sendEarlyHints103(links) {
2219
+ if (!links.length) return;
2220
+ const sender = earlyHintsSenderAls.getStore();
2221
+ if (!sender) return;
2222
+ try {
2223
+ sender(links);
2224
+ } catch {}
2225
+ }
2226
+ //#endregion
2227
+ //#region src/server/tree-builder.ts
2228
+ /**
2229
+ * Build the unified element tree from a matched segment chain.
2230
+ *
2231
+ * Construction is bottom-up:
2232
+ * 1. Start with the page component (leaf segment)
2233
+ * 2. Wrap in status-code error boundaries (fallback chain)
2234
+ * 3. Wrap in AccessGate (if segment has access.ts)
2235
+ * 4. Pass as children to the segment's layout
2236
+ * 5. Repeat up the segment chain to root
2237
+ *
2238
+ * Parallel slots are resolved at each layout level and composed as named props.
2239
+ */
2240
+ async function buildElementTree(config) {
2241
+ const { segments, loadModule, createElement, errorBoundaryComponent } = config;
2242
+ if (segments.length === 0) throw new Error("[timber] buildElementTree: empty segment chain");
2243
+ const leaf = segments[segments.length - 1];
2244
+ if (leaf.route && !leaf.page) return {
2245
+ tree: null,
2246
+ isApiRoute: true
2247
+ };
2248
+ const PageComponent = (leaf.page ? await loadModule(leaf.page) : null)?.default;
2249
+ if (!PageComponent) throw new Error(`[timber] No page component found for route at ${leaf.urlPath}. Each route must have a page.tsx or route.ts.`);
2250
+ let element = createElement(PageComponent, {});
2251
+ for (let i = segments.length - 1; i >= 0; i--) {
2252
+ const segment = segments[i];
2253
+ element = await wrapWithErrorBoundaries(segment, element, loadModule, createElement, errorBoundaryComponent);
2254
+ if (segment.access) {
2255
+ const accessFn = (await loadModule(segment.access)).default;
2256
+ element = createElement("timber:access-gate", {
2257
+ accessFn,
2258
+ segmentName: segment.segmentName,
2259
+ children: element
2260
+ });
2261
+ }
2262
+ if (segment.layout) {
2263
+ const LayoutComponent = (await loadModule(segment.layout)).default;
2264
+ if (LayoutComponent) {
2265
+ const slotProps = {};
2266
+ if (segment.slots.size > 0) for (const [slotName, slotNode] of segment.slots) slotProps[slotName] = await buildSlotElement(slotNode, loadModule, createElement, errorBoundaryComponent);
2267
+ element = createElement(LayoutComponent, {
2268
+ ...slotProps,
2269
+ children: element
2270
+ });
2271
+ }
2272
+ }
2273
+ }
2274
+ return {
2275
+ tree: element,
2276
+ isApiRoute: false
2277
+ };
2278
+ }
2279
+ /**
2280
+ * Build the element tree for a parallel slot.
2281
+ *
2282
+ * Slots have their own access.ts (SlotAccessGate) and error boundaries.
2283
+ * On access denial: denied.tsx → default.tsx → null (graceful degradation).
2284
+ */
2285
+ async function buildSlotElement(slotNode, loadModule, createElement, errorBoundaryComponent) {
2286
+ const PageComponent = (slotNode.page ? await loadModule(slotNode.page) : null)?.default;
2287
+ const DefaultComponent = (slotNode.default ? await loadModule(slotNode.default) : null)?.default;
2288
+ if (!PageComponent) return DefaultComponent ? createElement(DefaultComponent, {}) : null;
2289
+ let element = createElement(PageComponent, {});
2290
+ element = await wrapWithErrorBoundaries(slotNode, element, loadModule, createElement, errorBoundaryComponent);
2291
+ if (slotNode.access) {
2292
+ const accessFn = (await loadModule(slotNode.access)).default;
2293
+ const DeniedComponent = (slotNode.denied ? await loadModule(slotNode.denied) : null)?.default ?? null;
2294
+ const defaultFallback = DefaultComponent ? createElement(DefaultComponent, {}) : null;
2295
+ element = createElement("timber:slot-access-gate", {
2296
+ accessFn,
2297
+ DeniedComponent,
2298
+ slotName: slotNode.segmentName.replace(/^@/, ""),
2299
+ createElement,
2300
+ defaultFallback,
2301
+ children: element
2302
+ });
2303
+ }
2304
+ return element;
2305
+ }
2306
+ /** MDX/markdown extensions — these are server components that cannot be passed as function props. */
2307
+ var MDX_EXTENSIONS = new Set(["mdx", "md"]);
2308
+ /**
2309
+ * Check if a route file is an MDX/markdown file based on its extension.
2310
+ * MDX components are server components by default and cannot cross the
2311
+ * RSC→client boundary as function props. They must be pre-rendered as
2312
+ * elements and passed as fallbackElement instead of fallbackComponent.
2313
+ */
2314
+ function isMdxFile(file) {
2315
+ return MDX_EXTENSIONS.has(file.extension);
2316
+ }
2317
+ /**
2318
+ * Wrap an element with error boundaries from a segment's status-code files.
2319
+ *
2320
+ * Wrapping order (innermost to outermost):
2321
+ * 1. Specific status files (503.tsx, 429.tsx, etc.)
2322
+ * 2. Category catch-alls (4xx.tsx, 5xx.tsx)
2323
+ * 3. error.tsx (general error boundary)
2324
+ *
2325
+ * This creates the fallback chain described in design/10-error-handling.md.
2326
+ *
2327
+ * MDX status files are server components and cannot be passed as function
2328
+ * props to TimberErrorBoundary (a 'use client' component). Instead, they
2329
+ * are pre-rendered as elements and passed as fallbackElement. The error
2330
+ * boundary renders the element directly when an error is caught.
2331
+ * See TIM-503.
2332
+ */
2333
+ async function wrapWithErrorBoundaries(segment, element, loadModule, createElement, errorBoundaryComponent) {
2334
+ if (segment.statusFiles) {
2335
+ for (const [key, file] of segment.statusFiles) if (key !== "4xx" && key !== "5xx") {
2336
+ const status = parseInt(key, 10);
2337
+ if (!isNaN(status)) {
2338
+ const Component = (await loadModule(file)).default;
2339
+ if (Component) element = createElement(errorBoundaryComponent, isMdxFile(file) ? {
2340
+ fallbackElement: createElement(Component, { status }),
2341
+ status,
2342
+ children: element
2343
+ } : {
2344
+ fallbackComponent: Component,
2345
+ status,
2346
+ children: element
2347
+ });
2348
+ }
2349
+ }
2350
+ for (const [key, file] of segment.statusFiles) if (key === "4xx" || key === "5xx") {
2351
+ const Component = (await loadModule(file)).default;
2352
+ if (Component) {
2353
+ const categoryStatus = key === "4xx" ? 400 : 500;
2354
+ element = createElement(errorBoundaryComponent, isMdxFile(file) ? {
2355
+ fallbackElement: createElement(Component, {}),
2356
+ status: categoryStatus,
2357
+ children: element
2358
+ } : {
2359
+ fallbackComponent: Component,
2360
+ status: categoryStatus,
2361
+ children: element
2362
+ });
2363
+ }
2364
+ }
2365
+ }
2366
+ if (segment.error) {
2367
+ const ErrorComponent = (await loadModule(segment.error)).default;
2368
+ if (ErrorComponent) element = createElement(errorBoundaryComponent, isMdxFile(segment.error) ? {
2369
+ fallbackElement: createElement(ErrorComponent, {}),
2370
+ children: element
2371
+ } : {
2372
+ fallbackComponent: ErrorComponent,
2373
+ children: element
2374
+ });
2375
+ }
2376
+ return element;
2377
+ }
2378
+ //#endregion
2379
+ //#region src/server/status-code-resolver.ts
2380
+ /**
2381
+ * Maps legacy file convention names to their corresponding HTTP status codes.
2382
+ * Only used in the 4xx component fallback chain.
2383
+ */
2384
+ var LEGACY_FILE_TO_STATUS = {
2385
+ "not-found": 404,
2386
+ "forbidden": 403,
2387
+ "unauthorized": 401
2388
+ };
2389
+ /**
2390
+ * Resolve the status-code file to render for a given HTTP status code.
2391
+ *
2392
+ * Walks the segment chain from leaf to root following the fallback chain
2393
+ * defined in design/10-error-handling.md. Returns null if no file is found
2394
+ * (caller should render the framework default).
2395
+ *
2396
+ * @param status - The HTTP status code (4xx or 5xx).
2397
+ * @param segments - The matched segment chain from root (index 0) to leaf (last).
2398
+ * @param format - The response format family ('component' or 'json'). Defaults to 'component'.
2399
+ */
2400
+ function resolveStatusFile(status, segments, format = "component") {
2401
+ if (status >= 400 && status <= 499) return format === "json" ? resolve4xxJson(status, segments) : resolve4xx(status, segments);
2402
+ if (status >= 500 && status <= 599) return format === "json" ? resolve5xxJson(status, segments) : resolve5xx(status, segments);
2403
+ return null;
2404
+ }
2405
+ /**
2406
+ * 4xx component fallback chain (three separate passes):
2407
+ * Pass 1 — status files (leaf → root): {status}.tsx → 4xx.tsx
2408
+ * Pass 2 — legacy compat (leaf → root): not-found.tsx / forbidden.tsx / unauthorized.tsx
2409
+ * Pass 3 — error.tsx (leaf → root)
2410
+ */
2411
+ function resolve4xx(status, segments) {
2412
+ const statusStr = String(status);
2413
+ for (let i = segments.length - 1; i >= 0; i--) {
2414
+ const segment = segments[i];
2415
+ if (!segment.statusFiles) continue;
2416
+ const exact = segment.statusFiles.get(statusStr);
2417
+ if (exact) return {
2418
+ file: exact,
2419
+ status,
2420
+ kind: "exact",
2421
+ segmentIndex: i
2422
+ };
2423
+ const category = segment.statusFiles.get("4xx");
2424
+ if (category) return {
2425
+ file: category,
2426
+ status,
2427
+ kind: "category",
2428
+ segmentIndex: i
2429
+ };
2430
+ }
2431
+ for (let i = segments.length - 1; i >= 0; i--) {
2432
+ const segment = segments[i];
2433
+ if (!segment.legacyStatusFiles) continue;
2434
+ for (const [name, legacyStatus] of Object.entries(LEGACY_FILE_TO_STATUS)) if (legacyStatus === status) {
2435
+ const file = segment.legacyStatusFiles.get(name);
2436
+ if (file) return {
2437
+ file,
2438
+ status,
2439
+ kind: "legacy",
2440
+ segmentIndex: i
2441
+ };
2442
+ }
2443
+ }
2444
+ for (let i = segments.length - 1; i >= 0; i--) if (segments[i].error) return {
2445
+ file: segments[i].error,
2446
+ status,
2447
+ kind: "error",
2448
+ segmentIndex: i
2449
+ };
2450
+ return null;
2451
+ }
2452
+ /**
2453
+ * 4xx JSON fallback chain (single pass):
2454
+ * Pass 1 — json status files (leaf → root): {status}.json → 4xx.json
2455
+ * No legacy compat, no error.tsx — JSON chain terminates at category catch-all.
2456
+ */
2457
+ function resolve4xxJson(status, segments) {
2458
+ const statusStr = String(status);
2459
+ for (let i = segments.length - 1; i >= 0; i--) {
2460
+ const segment = segments[i];
2461
+ if (!segment.jsonStatusFiles) continue;
2462
+ const exact = segment.jsonStatusFiles.get(statusStr);
2463
+ if (exact) return {
2464
+ file: exact,
2465
+ status,
2466
+ kind: "exact",
2467
+ segmentIndex: i
2468
+ };
2469
+ const category = segment.jsonStatusFiles.get("4xx");
2470
+ if (category) return {
2471
+ file: category,
2472
+ status,
2473
+ kind: "category",
2474
+ segmentIndex: i
2475
+ };
2476
+ }
2477
+ return null;
2478
+ }
2479
+ /**
2480
+ * 5xx component fallback chain (single pass, per-segment):
2481
+ * At each segment (leaf → root): {status}.tsx → 5xx.tsx → error.tsx
2482
+ */
2483
+ function resolve5xx(status, segments) {
2484
+ const statusStr = String(status);
2485
+ for (let i = segments.length - 1; i >= 0; i--) {
2486
+ const segment = segments[i];
2487
+ if (segment.statusFiles) {
2488
+ const exact = segment.statusFiles.get(statusStr);
2489
+ if (exact) return {
2490
+ file: exact,
2491
+ status,
2492
+ kind: "exact",
2493
+ segmentIndex: i
2494
+ };
2495
+ const category = segment.statusFiles.get("5xx");
2496
+ if (category) return {
2497
+ file: category,
2498
+ status,
2499
+ kind: "category",
2500
+ segmentIndex: i
2501
+ };
2502
+ }
2503
+ if (segment.error) return {
2504
+ file: segment.error,
2505
+ status,
2506
+ kind: "error",
2507
+ segmentIndex: i
2508
+ };
2509
+ }
2510
+ return null;
2511
+ }
2512
+ /**
2513
+ * 5xx JSON fallback chain (single pass):
2514
+ * At each segment (leaf → root): {status}.json → 5xx.json
2515
+ * No error.tsx equivalent — JSON chain terminates at category catch-all.
2516
+ */
2517
+ function resolve5xxJson(status, segments) {
2518
+ const statusStr = String(status);
2519
+ for (let i = segments.length - 1; i >= 0; i--) {
2520
+ const segment = segments[i];
2521
+ if (!segment.jsonStatusFiles) continue;
2522
+ const exact = segment.jsonStatusFiles.get(statusStr);
2523
+ if (exact) return {
2524
+ file: exact,
2525
+ status,
2526
+ kind: "exact",
2527
+ segmentIndex: i
2528
+ };
2529
+ const category = segment.jsonStatusFiles.get("5xx");
2530
+ if (category) return {
2531
+ file: category,
2532
+ status,
2533
+ kind: "category",
2534
+ segmentIndex: i
2535
+ };
2536
+ }
2537
+ return null;
2538
+ }
2539
+ /**
2540
+ * Resolve the denial file for a parallel route slot.
2541
+ *
2542
+ * Slot denial is graceful degradation — no HTTP status on the wire.
2543
+ * Fallback chain: denied.tsx → default.tsx → null.
2544
+ *
2545
+ * @param slotNode - The segment node for the slot (segmentType === 'slot').
2546
+ */
2547
+ function resolveSlotDenied(slotNode) {
2548
+ const slotName = slotNode.segmentName.replace(/^@/, "");
2549
+ if (slotNode.denied) return {
2550
+ file: slotNode.denied,
2551
+ slotName,
2552
+ kind: "denied"
2553
+ };
2554
+ if (slotNode.default) return {
2555
+ file: slotNode.default,
2556
+ slotName,
2557
+ kind: "default"
2558
+ };
2559
+ return null;
2560
+ }
2561
+ //#endregion
2562
+ //#region src/server/flush.ts
2563
+ /**
2564
+ * Flush controller for timber.js rendering.
2565
+ *
2566
+ * Holds the response until `onShellReady` fires, then commits the HTTP status
2567
+ * code and flushes the shell. Render-phase signals (deny, redirect, unhandled
2568
+ * throws) caught before flush produce correct HTTP status codes.
2569
+ *
2570
+ * See design/02-rendering-pipeline.md §"The Flush Point" and §"The Hold Window"
2571
+ */
2572
+ /**
2573
+ * Execute a render and hold the response until the shell is ready.
2574
+ *
2575
+ * The flush controller:
2576
+ * 1. Calls the render function to start renderToReadableStream
2577
+ * 2. Waits for shellReady (onShellReady)
2578
+ * 3. If a render-phase signal was thrown (deny, redirect, error), produces
2579
+ * the correct HTTP status code
2580
+ * 4. If the shell rendered successfully, commits the status and streams
2581
+ *
2582
+ * Render-phase signals caught before flush:
2583
+ * - `DenySignal` → HTTP 4xx with appropriate status code
2584
+ * - `RedirectSignal` → HTTP 3xx with Location header
2585
+ * - `RenderError` → HTTP status from error (default 500)
2586
+ * - Unhandled error → HTTP 500
2587
+ *
2588
+ * @param renderFn - Function that starts the React render.
2589
+ * @param options - Flush configuration.
2590
+ * @returns The committed HTTP Response.
2591
+ */
2592
+ async function flushResponse(renderFn, options = {}) {
2593
+ const { responseHeaders = new Headers(), defaultStatus = 200 } = options;
2594
+ let renderResult;
2595
+ try {
2596
+ renderResult = await renderFn();
2597
+ } catch (error) {
2598
+ return handleSignal(error, responseHeaders);
2599
+ }
2600
+ try {
2601
+ await renderResult.shellReady;
2602
+ } catch (error) {
2603
+ return handleSignal(error, responseHeaders);
2604
+ }
2605
+ responseHeaders.set("Content-Type", "text/html; charset=utf-8");
2606
+ return {
2607
+ response: new Response(renderResult.stream, {
2608
+ status: defaultStatus,
2609
+ headers: responseHeaders
2610
+ }),
2611
+ status: defaultStatus,
2612
+ isRedirect: false,
2613
+ isDenial: false
2614
+ };
2615
+ }
2616
+ /**
2617
+ * Handle a render-phase signal and produce the correct HTTP response.
2618
+ */
2619
+ function handleSignal(error, responseHeaders) {
2620
+ if (error instanceof RedirectSignal) {
2621
+ responseHeaders.set("Location", error.location);
2622
+ return {
2623
+ response: new Response(null, {
2624
+ status: error.status,
2625
+ headers: responseHeaders
2626
+ }),
2627
+ status: error.status,
2628
+ isRedirect: true,
2629
+ isDenial: false
2630
+ };
2631
+ }
2632
+ if (error instanceof DenySignal) return {
2633
+ response: new Response(null, {
2634
+ status: error.status,
2635
+ headers: responseHeaders
2636
+ }),
2637
+ status: error.status,
2638
+ isRedirect: false,
2639
+ isDenial: true
2640
+ };
2641
+ if (error instanceof RenderError) return {
2642
+ response: new Response(null, {
2643
+ status: error.status,
2644
+ headers: responseHeaders
2645
+ }),
2646
+ status: error.status,
2647
+ isRedirect: false,
2648
+ isDenial: false
2649
+ };
2650
+ logRenderError({
2651
+ method: "",
2652
+ path: "",
2653
+ error
2654
+ });
2655
+ return {
2656
+ response: new Response(null, {
2657
+ status: 500,
2658
+ headers: responseHeaders
2659
+ }),
2660
+ status: 500,
2661
+ isRedirect: false,
2662
+ isDenial: false
2663
+ };
2664
+ }
2665
+ //#endregion
2666
+ //#region src/server/csrf.ts
2667
+ /** HTTP methods that are considered safe (no mutation). */
2668
+ var SAFE_METHODS = new Set([
2669
+ "GET",
2670
+ "HEAD",
2671
+ "OPTIONS"
2672
+ ]);
2673
+ /**
2674
+ * Validate the Origin header against the request's Host.
2675
+ *
2676
+ * For mutation methods (POST, PUT, PATCH, DELETE):
2677
+ * - If `csrf: false`, skip validation.
2678
+ * - If `allowedOrigins` is set, Origin must match one exactly (no wildcards).
2679
+ * - Otherwise, Origin's host must match the request's Host header.
2680
+ *
2681
+ * Safe methods (GET, HEAD, OPTIONS) always pass.
2682
+ */
2683
+ function validateCsrf(req, config) {
2684
+ if (SAFE_METHODS.has(req.method)) return { ok: true };
2685
+ if (config.csrf === false) return { ok: true };
2686
+ const origin = req.headers.get("Origin");
2687
+ if (!origin) return {
2688
+ ok: false,
2689
+ status: 403
2690
+ };
2691
+ if (config.allowedOrigins) return config.allowedOrigins.includes(origin) ? { ok: true } : {
2692
+ ok: false,
2693
+ status: 403
2694
+ };
2695
+ const host = req.headers.get("Host");
2696
+ if (!host) return {
2697
+ ok: false,
2698
+ status: 403
2699
+ };
2700
+ let originHost;
2701
+ try {
2702
+ originHost = new URL(origin).host;
2703
+ } catch {
2704
+ return {
2705
+ ok: false,
2706
+ status: 403
2707
+ };
2708
+ }
2709
+ return originHost === host ? { ok: true } : {
2710
+ ok: false,
2711
+ status: 403
2712
+ };
2713
+ }
2714
+ //#endregion
2715
+ //#region src/server/body-limits.ts
2716
+ var KB = 1024;
2717
+ var MB = 1024 * KB;
2718
+ var GB = 1024 * MB;
2719
+ var DEFAULT_LIMITS = {
2720
+ actionBodySize: 1 * MB,
2721
+ uploadBodySize: 10 * MB,
2722
+ maxFields: 100
2723
+ };
2724
+ var SIZE_PATTERN = /^(\d+(?:\.\d+)?)\s*(kb|mb|gb)?$/i;
2725
+ /** Parse a human-readable size string ("1mb", "512kb", "1024") into bytes. */
2726
+ function parseBodySize(size) {
2727
+ const match = SIZE_PATTERN.exec(size.trim());
2728
+ if (!match) throw new Error(`Invalid body size format: "${size}". Expected format like "1mb", "512kb", or "1024".`);
2729
+ const value = Number.parseFloat(match[1]);
2730
+ const unit = (match[2] ?? "").toLowerCase();
2731
+ switch (unit) {
2732
+ case "kb": return Math.floor(value * KB);
2733
+ case "mb": return Math.floor(value * MB);
2734
+ case "gb": return Math.floor(value * GB);
2735
+ case "": return Math.floor(value);
2736
+ default: throw new Error(`Unknown size unit: "${unit}"`);
2737
+ }
2738
+ }
2739
+ /** Check whether a request body exceeds the configured size limit (stateless, no ALS). */
2740
+ function enforceBodyLimits(req, kind, config) {
2741
+ const contentLength = req.headers.get("Content-Length");
2742
+ if (!contentLength) return {
2743
+ ok: false,
2744
+ status: 411
2745
+ };
2746
+ const bodySize = Number.parseInt(contentLength, 10);
2747
+ if (Number.isNaN(bodySize)) return {
2748
+ ok: false,
2749
+ status: 411
2750
+ };
2751
+ return bodySize <= resolveLimit(kind, config) ? { ok: true } : {
2752
+ ok: false,
2753
+ status: 413
2754
+ };
2755
+ }
2756
+ /**
2757
+ * Resolve the byte limit for a given body kind, using config overrides or defaults.
2758
+ */
2759
+ function resolveLimit(kind, config) {
2760
+ const userLimits = config.limits;
2761
+ if (kind === "action") return userLimits?.actionBodySize ? parseBodySize(userLimits.actionBodySize) : DEFAULT_LIMITS.actionBodySize;
2762
+ return userLimits?.uploadBodySize ? parseBodySize(userLimits.uploadBodySize) : DEFAULT_LIMITS.uploadBodySize;
2763
+ }
2764
+ //#endregion
2765
+ //#region src/server/route-handler.ts
2766
+ /** All recognized HTTP method export names. */
2767
+ var HTTP_METHODS = [
2768
+ "GET",
2769
+ "POST",
2770
+ "PUT",
2771
+ "PATCH",
2772
+ "DELETE",
2773
+ "HEAD",
2774
+ "OPTIONS"
2775
+ ];
2776
+ /**
2777
+ * Resolve the full list of allowed methods for a route module.
2778
+ *
2779
+ * Includes:
2780
+ * - All explicitly exported methods
2781
+ * - HEAD (implicit when GET is exported)
2782
+ * - OPTIONS (always implicit)
2783
+ */
2784
+ function resolveAllowedMethods(mod) {
2785
+ const methods = [];
2786
+ for (const method of HTTP_METHODS) {
2787
+ if (method === "HEAD" || method === "OPTIONS") continue;
2788
+ if (mod[method]) methods.push(method);
2789
+ }
2790
+ if (mod.GET && !mod.HEAD) methods.push("HEAD");
2791
+ else if (mod.HEAD) methods.push("HEAD");
2792
+ if (!mod.OPTIONS) methods.push("OPTIONS");
2793
+ else methods.push("OPTIONS");
2794
+ return methods;
2795
+ }
2796
+ /**
2797
+ * Handle an incoming request against a route.ts module.
2798
+ *
2799
+ * Dispatches to the named method handler, auto-generates 405/OPTIONS,
2800
+ * and merges response headers from ctx.headers.
2801
+ */
2802
+ async function handleRouteRequest(mod, ctx) {
2803
+ const method = ctx.req.method.toUpperCase();
2804
+ const allowHeader = resolveAllowedMethods(mod).join(", ");
2805
+ if (method === "OPTIONS") {
2806
+ if (mod.OPTIONS) return runHandler(mod.OPTIONS, ctx);
2807
+ return new Response(null, {
2808
+ status: 204,
2809
+ headers: { Allow: allowHeader }
2810
+ });
2811
+ }
2812
+ if (method === "HEAD") {
2813
+ if (mod.HEAD) return runHandler(mod.HEAD, ctx);
2814
+ if (mod.GET) {
2815
+ const res = await runHandler(mod.GET, ctx);
2816
+ return new Response(null, {
2817
+ status: res.status,
2818
+ headers: res.headers
2819
+ });
2820
+ }
2821
+ }
2822
+ const handler = mod[method];
2823
+ if (!handler) return new Response(null, {
2824
+ status: 405,
2825
+ headers: { Allow: allowHeader }
2826
+ });
2827
+ return runHandler(handler, ctx);
2828
+ }
2829
+ /**
2830
+ * Run a handler, merge ctx.headers into the response, and catch errors.
2831
+ */
2832
+ async function runHandler(handler, ctx) {
2833
+ try {
2834
+ return mergeResponseHeaders(await handler(ctx), ctx.headers);
2835
+ } catch (error) {
2836
+ logRouteError({
2837
+ method: ctx.req.method,
2838
+ path: new URL(ctx.req.url).pathname,
2839
+ error
2840
+ });
2841
+ return new Response(null, { status: 500 });
2842
+ }
2843
+ }
2844
+ /**
2845
+ * Merge response headers from ctx.headers into the handler's response.
2846
+ * ctx.headers (set by middleware or the handler) are applied to the final response.
2847
+ * Handler-set headers take precedence over ctx.headers.
2848
+ */
2849
+ function mergeResponseHeaders(res, ctxHeaders) {
2850
+ let hasCtxHeaders = false;
2851
+ ctxHeaders.forEach(() => {
2852
+ hasCtxHeaders = true;
2853
+ });
2854
+ if (!hasCtxHeaders) return res;
2855
+ const merged = new Headers();
2856
+ ctxHeaders.forEach((value, key) => {
2857
+ if (key.toLowerCase() === "set-cookie") merged.append(key, value);
2858
+ else merged.set(key, value);
2859
+ });
2860
+ const resCookies = res.headers.getSetCookie();
2861
+ for (const cookie of resCookies) merged.append("Set-Cookie", cookie);
2862
+ res.headers.forEach((value, key) => {
2863
+ if (key.toLowerCase() !== "set-cookie") merged.set(key, value);
2864
+ });
2865
+ return new Response(res.body, {
2866
+ status: res.status,
2867
+ statusText: res.statusText,
2868
+ headers: merged
2869
+ });
2870
+ }
2871
+ //#endregion
2872
+ //#region src/server/render-timeout.ts
2873
+ /**
2874
+ * Render timeout utilities for SSR streaming pipeline.
2875
+ *
2876
+ * Provides a RenderTimeoutError class and a helper to create
2877
+ * timeout-guarded AbortSignals. Used to defend against hung RSC
2878
+ * streams and infinite SSR renders.
2879
+ *
2880
+ * Design doc: 02-rendering-pipeline.md §"Streaming Constraints"
2881
+ */
2882
+ /**
2883
+ * Error thrown when an SSR render or RSC stream read exceeds the
2884
+ * configured timeout. Callers can check `instanceof RenderTimeoutError`
2885
+ * to distinguish timeout from other errors and return a 504 or close
2886
+ * the connection cleanly.
2887
+ */
2888
+ var RenderTimeoutError = class extends Error {
2889
+ timeoutMs;
2890
+ constructor(timeoutMs, context) {
2891
+ const message = context ? `Render timeout after ${timeoutMs}ms: ${context}` : `Render timeout after ${timeoutMs}ms`;
2892
+ super(message);
2893
+ this.name = "RenderTimeoutError";
2894
+ this.timeoutMs = timeoutMs;
2895
+ }
2896
+ };
2897
+ //#endregion
2898
+ export { AccessGate, DEFAULT_LIMITS, DenySignal, METADATA_ROUTE_CONVENTIONS, RedirectSignal, RenderError, RenderTimeoutError, SlotAccessGate, WarningId, buildElementTree, buildNoJsResponse, callOnRequestError, canonicalize, classifyMetadataRoute, collectEarlyHintHeaders, createPipeline, enforceBodyLimits, executeAction, flushResponse, formatLinkHeader, generateTraceId, getLogger, getMetadataRouteAutoLink, getMetadataRouteServePath, getSetCookieHeaders, handleRouteRequest, hasOnRequestError, isRscActionRequest, loadInstrumentation, logCacheMiss, logMiddlewareError, logMiddlewareShortCircuit, logProxyError, logRenderError, logRequestCompleted, logRequestReceived, logSlowRequest, logSwrRefetchFailed, logWaitUntilRejected, logWaitUntilUnsupported, markResponseFlushed, parseBodySize, renderMetadataToElements, replaceTraceId, resolveAllowedMethods, resolveMetadata, resolveMetadataUrls, resolveSlotDenied, resolveStatusFile, resolveTitle, runMiddleware, runProxy, runWithEarlyHintsSender, runWithRequestContext, runWithTraceId, sendEarlyHints103, setLogger, setMutableCookieContext, setSegmentParams, setViteServer, validateCsrf, warnDenyInSuspense, warnRedirectInAccess, warnRedirectInSuspense, warnSlowSlotWithoutSuspense, warnStaticRequestApi, warnSuspenseWrappingChildren };
2899
+
2900
+ //# sourceMappingURL=internal.js.map