@timber-js/app 0.2.0-alpha.97 → 0.2.0-alpha.99

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 (372) hide show
  1. package/dist/_chunks/actions-CQ8Z8VGL.js +1061 -0
  2. package/dist/_chunks/actions-CQ8Z8VGL.js.map +1 -0
  3. package/dist/_chunks/build-output-helper-DXnW0qjz.js +61 -0
  4. package/dist/_chunks/build-output-helper-DXnW0qjz.js.map +1 -0
  5. package/dist/_chunks/{define-Itxvcd7F.js → define-B-Q_UMOD.js} +19 -23
  6. package/dist/_chunks/define-B-Q_UMOD.js.map +1 -0
  7. package/dist/_chunks/{define-C77ScO0m.js → define-CfBPoJb0.js} +24 -7
  8. package/dist/_chunks/define-CfBPoJb0.js.map +1 -0
  9. package/dist/_chunks/define-cookie-BjpIt4UC.js +194 -0
  10. package/dist/_chunks/define-cookie-BjpIt4UC.js.map +1 -0
  11. package/dist/_chunks/{format-CYBGxKtc.js → format-Bcn-Iv1x.js} +1 -1
  12. package/dist/_chunks/{format-CYBGxKtc.js.map → format-Bcn-Iv1x.js.map} +1 -1
  13. package/dist/_chunks/handler-store-B-lqaGyh.js +54 -0
  14. package/dist/_chunks/handler-store-B-lqaGyh.js.map +1 -0
  15. package/dist/_chunks/logger-0m8MsKdc.js +291 -0
  16. package/dist/_chunks/logger-0m8MsKdc.js.map +1 -0
  17. package/dist/_chunks/merge-search-params-BphMdht_.js +122 -0
  18. package/dist/_chunks/merge-search-params-BphMdht_.js.map +1 -0
  19. package/dist/_chunks/{metadata-routes-DS3eKNmf.js → metadata-routes-BU684ls2.js} +1 -1
  20. package/dist/_chunks/{metadata-routes-DS3eKNmf.js.map → metadata-routes-BU684ls2.js.map} +1 -1
  21. package/dist/_chunks/navigation-root-BCYczjml.js +96 -0
  22. package/dist/_chunks/navigation-root-BCYczjml.js.map +1 -0
  23. package/dist/_chunks/registry-I2ss-lvy.js +20 -0
  24. package/dist/_chunks/registry-I2ss-lvy.js.map +1 -0
  25. package/dist/_chunks/router-ref-h3-UaCQv.js +28 -0
  26. package/dist/_chunks/router-ref-h3-UaCQv.js.map +1 -0
  27. package/dist/_chunks/{schema-bridge-C3xl_vfb.js → schema-bridge-Cxu4l-7p.js} +1 -1
  28. package/dist/_chunks/{schema-bridge-C3xl_vfb.js.map → schema-bridge-Cxu4l-7p.js.map} +1 -1
  29. package/dist/_chunks/segment-classify-BjfuctV2.js +137 -0
  30. package/dist/_chunks/segment-classify-BjfuctV2.js.map +1 -0
  31. package/dist/_chunks/{segment-context-fHFLF1PE.js → segment-context-Dx_OizxD.js} +1 -1
  32. package/dist/_chunks/{segment-context-fHFLF1PE.js.map → segment-context-Dx_OizxD.js.map} +1 -1
  33. package/dist/_chunks/{router-ref-C8OCm7g7.js → ssr-data-B4CdH7rE.js} +2 -26
  34. package/dist/_chunks/ssr-data-B4CdH7rE.js.map +1 -0
  35. package/dist/_chunks/{stale-reload-BX5gL1r-.js → stale-reload-Bab885FO.js} +1 -1
  36. package/dist/_chunks/{stale-reload-BX5gL1r-.js.map → stale-reload-Bab885FO.js.map} +1 -1
  37. package/dist/_chunks/tracing-C8V-YGsP.js +329 -0
  38. package/dist/_chunks/tracing-C8V-YGsP.js.map +1 -0
  39. package/dist/_chunks/{use-query-states-BiV5GJgm.js → use-query-states-B2XTqxDR.js} +3 -19
  40. package/dist/_chunks/use-query-states-B2XTqxDR.js.map +1 -0
  41. package/dist/_chunks/{use-params-IOPu7E8t.js → use-segment-params-BkpKAQ7D.js} +9 -95
  42. package/dist/_chunks/use-segment-params-BkpKAQ7D.js.map +1 -0
  43. package/dist/_chunks/{interception-BbqMCVXa.js → walkers-Tg0Alwcg.js} +66 -87
  44. package/dist/_chunks/walkers-Tg0Alwcg.js.map +1 -0
  45. package/dist/_chunks/{dev-warnings-DpGRGoDi.js → warnings-Cg47l5sk.js} +3 -3
  46. package/dist/_chunks/warnings-Cg47l5sk.js.map +1 -0
  47. package/dist/adapters/build-output-helper.d.ts +28 -0
  48. package/dist/adapters/build-output-helper.d.ts.map +1 -0
  49. package/dist/adapters/cloudflare.d.ts.map +1 -1
  50. package/dist/adapters/cloudflare.js +8 -28
  51. package/dist/adapters/cloudflare.js.map +1 -1
  52. package/dist/adapters/nitro.d.ts.map +1 -1
  53. package/dist/adapters/nitro.js +63 -31
  54. package/dist/adapters/nitro.js.map +1 -1
  55. package/dist/adapters/shared.d.ts +16 -0
  56. package/dist/adapters/shared.d.ts.map +1 -0
  57. package/dist/cache/index.js +9 -2
  58. package/dist/cache/index.js.map +1 -1
  59. package/dist/cache/timber-cache.d.ts.map +1 -1
  60. package/dist/client/error-boundary.js +2 -1
  61. package/dist/client/error-boundary.js.map +1 -1
  62. package/dist/client/form.d.ts +10 -24
  63. package/dist/client/form.d.ts.map +1 -1
  64. package/dist/client/index.d.ts +1 -5
  65. package/dist/client/index.d.ts.map +1 -1
  66. package/dist/client/index.js +41 -91
  67. package/dist/client/index.js.map +1 -1
  68. package/dist/client/internal.d.ts +2 -1
  69. package/dist/client/internal.d.ts.map +1 -1
  70. package/dist/client/internal.js +81 -7
  71. package/dist/client/internal.js.map +1 -1
  72. package/dist/client/rsc-fetch.d.ts.map +1 -1
  73. package/dist/client/state.d.ts +1 -1
  74. package/dist/client/use-cookie.d.ts +8 -0
  75. package/dist/client/use-cookie.d.ts.map +1 -1
  76. package/dist/client/{use-params.d.ts → use-segment-params.d.ts} +1 -1
  77. package/dist/client/use-segment-params.d.ts.map +1 -0
  78. package/dist/codec.d.ts +1 -1
  79. package/dist/codec.d.ts.map +1 -1
  80. package/dist/codec.js +2 -2
  81. package/dist/config-types.d.ts +28 -0
  82. package/dist/config-types.d.ts.map +1 -1
  83. package/dist/cookies/define-cookie.d.ts +87 -35
  84. package/dist/cookies/define-cookie.d.ts.map +1 -1
  85. package/dist/cookies/index.d.ts +2 -1
  86. package/dist/cookies/index.d.ts.map +1 -1
  87. package/dist/cookies/index.js +48 -2
  88. package/dist/cookies/index.js.map +1 -0
  89. package/dist/cookies/json-cookie.d.ts +64 -0
  90. package/dist/cookies/json-cookie.d.ts.map +1 -0
  91. package/dist/cookies/validation.d.ts +46 -0
  92. package/dist/cookies/validation.d.ts.map +1 -0
  93. package/dist/{plugins/dev-404-page.d.ts → dev-tools/404-page.d.ts} +9 -19
  94. package/dist/dev-tools/404-page.d.ts.map +1 -0
  95. package/dist/{plugins/dev-browser-logs.d.ts → dev-tools/browser-logs.d.ts} +1 -1
  96. package/dist/dev-tools/browser-logs.d.ts.map +1 -0
  97. package/dist/{plugins/dev-error-page.d.ts → dev-tools/error-page.d.ts} +2 -2
  98. package/dist/dev-tools/error-page.d.ts.map +1 -0
  99. package/dist/{server/dev-holding-server.d.ts → dev-tools/holding-server.d.ts} +5 -3
  100. package/dist/dev-tools/holding-server.d.ts.map +1 -0
  101. package/dist/dev-tools/index.d.ts +31 -0
  102. package/dist/dev-tools/index.d.ts.map +1 -0
  103. package/dist/{server/dev-span-processor.d.ts → dev-tools/instrumentation.d.ts} +26 -6
  104. package/dist/dev-tools/instrumentation.d.ts.map +1 -0
  105. package/dist/{server/dev-logger.d.ts → dev-tools/logger.d.ts} +1 -1
  106. package/dist/dev-tools/logger.d.ts.map +1 -0
  107. package/dist/{plugins/dev-logs.d.ts → dev-tools/logs.d.ts} +1 -1
  108. package/dist/dev-tools/logs.d.ts.map +1 -0
  109. package/dist/{plugins/dev-error-overlay.d.ts → dev-tools/overlay.d.ts} +3 -12
  110. package/dist/dev-tools/overlay.d.ts.map +1 -0
  111. package/dist/dev-tools/stack-classifier.d.ts +34 -0
  112. package/dist/dev-tools/stack-classifier.d.ts.map +1 -0
  113. package/dist/{plugins/dev-terminal-error.d.ts → dev-tools/terminal.d.ts} +2 -2
  114. package/dist/dev-tools/terminal.d.ts.map +1 -0
  115. package/dist/{server/dev-warnings.d.ts → dev-tools/warnings.d.ts} +1 -1
  116. package/dist/dev-tools/warnings.d.ts.map +1 -0
  117. package/dist/index.d.ts +1 -0
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +285 -133
  120. package/dist/index.js.map +1 -1
  121. package/dist/plugin-context.d.ts +1 -1
  122. package/dist/plugin-context.d.ts.map +1 -1
  123. package/dist/plugins/adapter-build.d.ts.map +1 -1
  124. package/dist/plugins/build-report.d.ts +6 -4
  125. package/dist/plugins/build-report.d.ts.map +1 -1
  126. package/dist/routing/convention-lint.d.ts.map +1 -1
  127. package/dist/routing/index.d.ts +5 -3
  128. package/dist/routing/index.d.ts.map +1 -1
  129. package/dist/routing/index.js +3 -3
  130. package/dist/routing/scanner.d.ts +1 -10
  131. package/dist/routing/scanner.d.ts.map +1 -1
  132. package/dist/routing/segment-classify.d.ts +37 -8
  133. package/dist/routing/segment-classify.d.ts.map +1 -1
  134. package/dist/routing/status-file-lint.d.ts.map +1 -1
  135. package/dist/routing/types.d.ts +63 -23
  136. package/dist/routing/types.d.ts.map +1 -1
  137. package/dist/routing/walkers.d.ts +51 -0
  138. package/dist/routing/walkers.d.ts.map +1 -0
  139. package/dist/search-params/define.d.ts +25 -7
  140. package/dist/search-params/define.d.ts.map +1 -1
  141. package/dist/search-params/index.js +5 -3
  142. package/dist/search-params/index.js.map +1 -1
  143. package/dist/search-params/wrappers.d.ts +2 -2
  144. package/dist/search-params/wrappers.d.ts.map +1 -1
  145. package/dist/segment-params/define.d.ts +23 -6
  146. package/dist/segment-params/define.d.ts.map +1 -1
  147. package/dist/segment-params/index.js +1 -1
  148. package/dist/server/access-gate.d.ts +4 -3
  149. package/dist/server/access-gate.d.ts.map +1 -1
  150. package/dist/server/action-handler.d.ts +15 -6
  151. package/dist/server/action-handler.d.ts.map +1 -1
  152. package/dist/server/als-registry.d.ts +5 -5
  153. package/dist/server/als-registry.d.ts.map +1 -1
  154. package/dist/server/asset-headers.d.ts +1 -15
  155. package/dist/server/asset-headers.d.ts.map +1 -1
  156. package/dist/server/cookie-context.d.ts +170 -0
  157. package/dist/server/cookie-context.d.ts.map +1 -0
  158. package/dist/server/cookie-parsing.d.ts +51 -0
  159. package/dist/server/cookie-parsing.d.ts.map +1 -0
  160. package/dist/server/deny-boundary.d.ts +90 -0
  161. package/dist/server/deny-boundary.d.ts.map +1 -0
  162. package/dist/server/deny-renderer.d.ts.map +1 -1
  163. package/dist/server/early-hints-sender.d.ts.map +1 -1
  164. package/dist/server/html-injector-core.d.ts +212 -0
  165. package/dist/server/html-injector-core.d.ts.map +1 -0
  166. package/dist/server/html-injectors.d.ts +59 -59
  167. package/dist/server/html-injectors.d.ts.map +1 -1
  168. package/dist/server/index.d.ts +5 -4
  169. package/dist/server/index.d.ts.map +1 -1
  170. package/dist/server/index.js +4 -149
  171. package/dist/server/index.js.map +1 -1
  172. package/dist/server/internal.d.ts +6 -4
  173. package/dist/server/internal.d.ts.map +1 -1
  174. package/dist/server/internal.js +852 -852
  175. package/dist/server/internal.js.map +1 -1
  176. package/dist/server/logger.d.ts +14 -0
  177. package/dist/server/logger.d.ts.map +1 -1
  178. package/dist/server/middleware-runner.d.ts +17 -0
  179. package/dist/server/middleware-runner.d.ts.map +1 -1
  180. package/dist/server/node-stream-transforms.d.ts +46 -49
  181. package/dist/server/node-stream-transforms.d.ts.map +1 -1
  182. package/dist/server/param-coercion.d.ts +26 -0
  183. package/dist/server/param-coercion.d.ts.map +1 -0
  184. package/dist/server/pipeline-helpers.d.ts +95 -0
  185. package/dist/server/pipeline-helpers.d.ts.map +1 -0
  186. package/dist/server/pipeline-outcome.d.ts +49 -0
  187. package/dist/server/pipeline-outcome.d.ts.map +1 -0
  188. package/dist/server/pipeline-phases.d.ts +52 -0
  189. package/dist/server/pipeline-phases.d.ts.map +1 -0
  190. package/dist/server/pipeline.d.ts +51 -32
  191. package/dist/server/pipeline.d.ts.map +1 -1
  192. package/dist/server/port-resolution.d.ts +117 -0
  193. package/dist/server/port-resolution.d.ts.map +1 -0
  194. package/dist/server/request-context.d.ts +22 -159
  195. package/dist/server/request-context.d.ts.map +1 -1
  196. package/dist/server/route-element-builder.d.ts.map +1 -1
  197. package/dist/server/route-matcher.d.ts +20 -47
  198. package/dist/server/route-matcher.d.ts.map +1 -1
  199. package/dist/server/rsc-entry/action-middleware-runner.d.ts +66 -0
  200. package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -0
  201. package/dist/server/rsc-entry/helpers.d.ts +1 -1
  202. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  203. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  204. package/dist/server/rsc-entry/render-route.d.ts +50 -0
  205. package/dist/server/rsc-entry/render-route.d.ts.map +1 -0
  206. package/dist/server/rsc-entry/wrap-action-dispatch.d.ts +119 -0
  207. package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +1 -0
  208. package/dist/server/state-tree-diff.d.ts.map +1 -1
  209. package/dist/server/status-code-resolver.d.ts +16 -11
  210. package/dist/server/status-code-resolver.d.ts.map +1 -1
  211. package/dist/server/tracing.d.ts +1 -1
  212. package/dist/server/tracing.d.ts.map +1 -1
  213. package/dist/server/tree-builder.d.ts +45 -16
  214. package/dist/server/tree-builder.d.ts.map +1 -1
  215. package/dist/server/types.d.ts +48 -0
  216. package/dist/server/types.d.ts.map +1 -1
  217. package/dist/server/utils/escape-html.d.ts +14 -0
  218. package/dist/server/utils/escape-html.d.ts.map +1 -0
  219. package/dist/shims/headers.d.ts +2 -2
  220. package/dist/shims/headers.d.ts.map +1 -1
  221. package/dist/shims/navigation-client.d.ts +3 -1
  222. package/dist/shims/navigation-client.d.ts.map +1 -1
  223. package/dist/shims/navigation.d.ts +9 -4
  224. package/dist/shims/navigation.d.ts.map +1 -1
  225. package/dist/utils/directive-parser.d.ts +0 -45
  226. package/dist/utils/directive-parser.d.ts.map +1 -1
  227. package/package.json +1 -1
  228. package/src/adapters/build-output-helper.ts +77 -0
  229. package/src/adapters/cloudflare.ts +10 -50
  230. package/src/adapters/nitro.ts +66 -50
  231. package/src/adapters/shared.ts +40 -0
  232. package/src/cache/timber-cache.ts +3 -2
  233. package/src/client/form.tsx +17 -25
  234. package/src/client/index.ts +16 -9
  235. package/src/client/internal.ts +3 -2
  236. package/src/client/router.ts +1 -1
  237. package/src/client/rsc-fetch.ts +15 -0
  238. package/src/client/state.ts +2 -2
  239. package/src/client/use-cookie.ts +29 -0
  240. package/src/codec.ts +3 -7
  241. package/src/config-types.ts +28 -0
  242. package/src/cookies/define-cookie.ts +271 -78
  243. package/src/cookies/index.ts +11 -8
  244. package/src/cookies/json-cookie.ts +105 -0
  245. package/src/cookies/validation.ts +134 -0
  246. package/src/{plugins/dev-404-page.ts → dev-tools/404-page.ts} +17 -48
  247. package/src/{plugins/dev-error-page.ts → dev-tools/error-page.ts} +5 -32
  248. package/src/{server/dev-holding-server.ts → dev-tools/holding-server.ts} +4 -2
  249. package/src/dev-tools/index.ts +90 -0
  250. package/src/dev-tools/instrumentation.ts +176 -0
  251. package/src/{plugins/dev-logs.ts → dev-tools/logs.ts} +2 -2
  252. package/src/{plugins/dev-error-overlay.ts → dev-tools/overlay.ts} +5 -23
  253. package/src/dev-tools/stack-classifier.ts +75 -0
  254. package/src/{plugins/dev-terminal-error.ts → dev-tools/terminal.ts} +4 -38
  255. package/src/{server/dev-warnings.ts → dev-tools/warnings.ts} +1 -1
  256. package/src/index.ts +95 -34
  257. package/src/plugin-context.ts +1 -1
  258. package/src/plugins/adapter-build.ts +3 -1
  259. package/src/plugins/build-report.ts +13 -22
  260. package/src/plugins/dev-server.ts +3 -3
  261. package/src/plugins/routing.ts +14 -12
  262. package/src/plugins/shims.ts +1 -1
  263. package/src/plugins/static-build.ts +1 -1
  264. package/src/routing/codegen.ts +1 -1
  265. package/src/routing/convention-lint.ts +9 -8
  266. package/src/routing/index.ts +5 -3
  267. package/src/routing/interception.ts +1 -1
  268. package/src/routing/scanner.ts +22 -95
  269. package/src/routing/segment-classify.ts +107 -8
  270. package/src/routing/status-file-lint.ts +7 -5
  271. package/src/routing/types.ts +63 -23
  272. package/src/routing/walkers.ts +90 -0
  273. package/src/search-params/define.ts +71 -15
  274. package/src/search-params/wrappers.ts +9 -2
  275. package/src/segment-params/define.ts +66 -13
  276. package/src/server/access-gate.tsx +9 -8
  277. package/src/server/action-handler.ts +34 -38
  278. package/src/server/als-registry.ts +5 -5
  279. package/src/server/asset-headers.ts +8 -34
  280. package/src/server/cookie-context.ts +468 -0
  281. package/src/server/cookie-parsing.ts +135 -0
  282. package/src/server/{deny-page-resolver.ts → deny-boundary.ts} +78 -14
  283. package/src/server/deny-renderer.ts +7 -12
  284. package/src/server/early-hints-sender.ts +3 -2
  285. package/src/server/fallback-error.ts +2 -2
  286. package/src/server/html-injector-core.ts +403 -0
  287. package/src/server/html-injectors.ts +158 -297
  288. package/src/server/index.ts +13 -14
  289. package/src/server/internal.ts +10 -3
  290. package/src/server/logger.ts +23 -0
  291. package/src/server/middleware-runner.ts +44 -0
  292. package/src/server/node-stream-transforms.ts +108 -248
  293. package/src/server/param-coercion.ts +76 -0
  294. package/src/server/pipeline-helpers.ts +204 -0
  295. package/src/server/pipeline-outcome.ts +167 -0
  296. package/src/server/pipeline-phases.ts +409 -0
  297. package/src/server/pipeline.ts +70 -540
  298. package/src/server/port-resolution.ts +215 -0
  299. package/src/server/request-context.ts +46 -451
  300. package/src/server/route-element-builder.ts +8 -4
  301. package/src/server/route-matcher.ts +28 -60
  302. package/src/server/rsc-entry/action-middleware-runner.ts +167 -0
  303. package/src/server/rsc-entry/api-handler.ts +2 -2
  304. package/src/server/rsc-entry/error-renderer.ts +2 -2
  305. package/src/server/rsc-entry/helpers.ts +2 -7
  306. package/src/server/rsc-entry/index.ts +81 -366
  307. package/src/server/rsc-entry/render-route.ts +304 -0
  308. package/src/server/rsc-entry/rsc-payload.ts +1 -1
  309. package/src/server/rsc-entry/ssr-renderer.ts +2 -2
  310. package/src/server/rsc-entry/wrap-action-dispatch.ts +449 -0
  311. package/src/server/sitemap-generator.ts +1 -1
  312. package/src/server/slot-resolver.ts +1 -1
  313. package/src/server/ssr-entry.ts +1 -1
  314. package/src/server/state-tree-diff.ts +4 -1
  315. package/src/server/status-code-resolver.ts +112 -128
  316. package/src/server/tracing.ts +3 -3
  317. package/src/server/tree-builder.ts +134 -56
  318. package/src/server/types.ts +52 -0
  319. package/src/server/utils/escape-html.ts +20 -0
  320. package/src/shims/headers.ts +3 -3
  321. package/src/shims/navigation-client.ts +4 -3
  322. package/src/shims/navigation.ts +9 -7
  323. package/src/utils/directive-parser.ts +0 -392
  324. package/dist/_chunks/actions-DLnUaR65.js +0 -421
  325. package/dist/_chunks/actions-DLnUaR65.js.map +0 -1
  326. package/dist/_chunks/als-registry-HS0LGUl2.js +0 -41
  327. package/dist/_chunks/als-registry-HS0LGUl2.js.map +0 -1
  328. package/dist/_chunks/debug-ECi_61pb.js +0 -108
  329. package/dist/_chunks/debug-ECi_61pb.js.map +0 -1
  330. package/dist/_chunks/define-C77ScO0m.js.map +0 -1
  331. package/dist/_chunks/define-Itxvcd7F.js.map +0 -1
  332. package/dist/_chunks/define-cookie-BowvzoP0.js +0 -94
  333. package/dist/_chunks/define-cookie-BowvzoP0.js.map +0 -1
  334. package/dist/_chunks/dev-warnings-DpGRGoDi.js.map +0 -1
  335. package/dist/_chunks/interception-BbqMCVXa.js.map +0 -1
  336. package/dist/_chunks/merge-search-params-Cm_KIWDX.js +0 -41
  337. package/dist/_chunks/merge-search-params-Cm_KIWDX.js.map +0 -1
  338. package/dist/_chunks/request-context-CK5tZqIP.js +0 -478
  339. package/dist/_chunks/request-context-CK5tZqIP.js.map +0 -1
  340. package/dist/_chunks/router-ref-C8OCm7g7.js.map +0 -1
  341. package/dist/_chunks/segment-classify-BDNn6EzD.js +0 -65
  342. package/dist/_chunks/segment-classify-BDNn6EzD.js.map +0 -1
  343. package/dist/_chunks/tracing-CCYbKn5n.js +0 -238
  344. package/dist/_chunks/tracing-CCYbKn5n.js.map +0 -1
  345. package/dist/_chunks/use-params-IOPu7E8t.js.map +0 -1
  346. package/dist/_chunks/use-query-states-BiV5GJgm.js.map +0 -1
  347. package/dist/client/use-params.d.ts.map +0 -1
  348. package/dist/plugins/dev-404-page.d.ts.map +0 -1
  349. package/dist/plugins/dev-browser-logs.d.ts.map +0 -1
  350. package/dist/plugins/dev-error-overlay.d.ts.map +0 -1
  351. package/dist/plugins/dev-error-page.d.ts.map +0 -1
  352. package/dist/plugins/dev-logs.d.ts.map +0 -1
  353. package/dist/plugins/dev-terminal-error.d.ts.map +0 -1
  354. package/dist/server/deny-page-resolver.d.ts +0 -52
  355. package/dist/server/deny-page-resolver.d.ts.map +0 -1
  356. package/dist/server/dev-fetch-instrumentation.d.ts +0 -22
  357. package/dist/server/dev-fetch-instrumentation.d.ts.map +0 -1
  358. package/dist/server/dev-holding-server.d.ts.map +0 -1
  359. package/dist/server/dev-logger.d.ts.map +0 -1
  360. package/dist/server/dev-span-processor.d.ts.map +0 -1
  361. package/dist/server/dev-warnings.d.ts.map +0 -1
  362. package/dist/server/manifest-status-resolver.d.ts +0 -58
  363. package/dist/server/manifest-status-resolver.d.ts.map +0 -1
  364. package/dist/server/page-deny-boundary.d.ts +0 -31
  365. package/dist/server/page-deny-boundary.d.ts.map +0 -1
  366. package/src/server/dev-fetch-instrumentation.ts +0 -96
  367. package/src/server/dev-span-processor.ts +0 -78
  368. package/src/server/manifest-status-resolver.ts +0 -215
  369. package/src/server/page-deny-boundary.tsx +0 -56
  370. /package/src/client/{use-params.ts → use-segment-params.ts} +0 -0
  371. /package/src/{plugins/dev-browser-logs.ts → dev-tools/browser-logs.ts} +0 -0
  372. /package/src/{server/dev-logger.ts → dev-tools/logger.ts} +0 -0
@@ -1,129 +1,13 @@
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";
1
+ import { c as replaceTraceId, d as withSpan, f as earlyHintsSenderAls, g as timingAls, i as getOtelTraceId, l as runWithTraceId, m as requestContextAls, o as getTraceId, r as generateTraceId, u as setSpanAttribute, v as isDebug } from "../_chunks/tracing-C8V-YGsP.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/warnings-Cg47l5sk.js";
3
+ import { n as classifyUrlSegment } from "../_chunks/segment-classify-BjfuctV2.js";
4
+ import { i as getMetadataRouteServePath, n as classifyMetadataRoute, r as getMetadataRouteAutoLink, t as METADATA_ROUTE_CONVENTIONS } from "../_chunks/metadata-routes-BU684ls2.js";
5
+ import { a as logProxyError, c as logRequestReceived, d as logSwrRefetchFailed, f as logWaitUntilRejected, h as swallow, i as logMiddlewareShortCircuit, l as logRouteError, m as setLogger, n as logCacheMiss, o as logRenderError, p as logWaitUntilUnsupported, r as logMiddlewareError, s as logRequestCompleted, t as getLogger, u as logSlowRequest } from "../_chunks/logger-0m8MsKdc.js";
6
+ import { C as setMutableCookieContext, D as getSetCookieHeaders, S as runWithRequestContext, T as getCookie, _ as getHeader, b as getSegmentParams, c as DenySignal, d as RenderError, g as applyRequestHeaderOverlay, l as RedirectSignal, n as executeAction, o as coerce, r as isRscActionRequest, t as buildNoJsResponse, w as setSegmentParams, x as markResponseFlushed, y as getSearchParams } from "../_chunks/actions-CQ8Z8VGL.js";
9
7
  import "../client/error-boundary.js";
10
- import "../_chunks/segment-context-fHFLF1PE.js";
8
+ import "../_chunks/segment-context-Dx_OizxD.js";
11
9
  import { readFile } from "node:fs/promises";
12
10
  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
11
  //#region src/server/server-timing.ts
128
12
  /**
129
13
  * Server-Timing header — dev-mode timing breakdowns for Chrome DevTools.
@@ -200,276 +84,6 @@ function getServerTimingHeader() {
200
84
  return result || null;
201
85
  }
202
86
  //#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
87
  //#region src/server/instrumentation.ts
474
88
  /**
475
89
  * Instrumentation — loads and runs the user's instrumentation.ts file.
@@ -532,6 +146,292 @@ function hasOnRequestError() {
532
146
  return _onRequestError !== null;
533
147
  }
534
148
  //#endregion
149
+ //#region src/server/pipeline-helpers.ts
150
+ /**
151
+ * Only __proto__ needs stripping — it has a language-level setter that
152
+ * changes the prototype chain of spread copies. constructor and prototype
153
+ * are harmless own properties on null-prototype objects.
154
+ */
155
+ var DANGEROUS_KEYS = new Set(["__proto__"]);
156
+ /**
157
+ * Deep-walk a value returned by a segment param codec, producing a
158
+ * sanitized copy where every plain object is null-prototype and
159
+ * dangerous keys (__proto__, constructor, prototype) are stripped at
160
+ * every depth.
161
+ *
162
+ * Non-plain objects (Date, Map, class instances, etc.) are returned
163
+ * as-is — they cannot be poisoned by `{...x}` spread and may carry
164
+ * author-intended prototype methods.
165
+ *
166
+ * Arrays are walked element-wise.
167
+ *
168
+ * Performance: URL params are bounded by URL length (~8 KB). Realistic
169
+ * trees are <1 KB. The recursive walk is sub-microsecond.
170
+ *
171
+ * See TIM-655, TIM-855, TIM-873, design/13-security.md
172
+ */
173
+ function sanitizeParamValue(value) {
174
+ if (value === null || typeof value !== "object") return value;
175
+ if (Array.isArray(value)) return value.map(sanitizeParamValue);
176
+ const proto = Object.getPrototypeOf(value);
177
+ if (proto !== Object.prototype && proto !== null) return value;
178
+ const out = Object.create(null);
179
+ for (const key of Object.keys(value)) if (!DANGEROUS_KEYS.has(key)) out[key] = sanitizeParamValue(value[key]);
180
+ return out;
181
+ }
182
+ /**
183
+ * Build a proxy resolver closure from the declared source. Called exactly
184
+ * once at `createPipeline` setup time, so the hot path sees only the branch
185
+ * that corresponds to this pipeline's configured variant.
186
+ *
187
+ * Returns `null` when the app has no proxy.ts — the hot path short-circuits
188
+ * around `runProxyPhase` entirely in that case.
189
+ *
190
+ * Accepts the sugar form (a bare `ProxyExport` — function or function array)
191
+ * and normalises it to the static variant. Functions and arrays are
192
+ * structurally distinct from the tagged `{ kind: 'lazy', loader }` object,
193
+ * so discrimination is unambiguous.
194
+ */
195
+ function makeProxyResolver(proxy) {
196
+ if (proxy === void 0) return null;
197
+ if (typeof proxy === "function" || Array.isArray(proxy)) {
198
+ const exp = proxy;
199
+ return () => exp;
200
+ }
201
+ if (proxy.kind === "static") {
202
+ const exp = proxy.export;
203
+ return () => exp;
204
+ }
205
+ const loader = proxy.loader;
206
+ return async () => (await loader()).default;
207
+ }
208
+ /**
209
+ * Apply all Set-Cookie headers from the cookie jar to a Headers object.
210
+ * Each cookie gets its own Set-Cookie header per RFC 6265 §4.1.
211
+ */
212
+ function applyCookieJar(headers) {
213
+ for (const value of getSetCookieHeaders()) headers.append("Set-Cookie", value);
214
+ }
215
+ /**
216
+ * Merge framework-managed response headers onto a terminal response without
217
+ * overwriting headers the terminal response already set itself.
218
+ */
219
+ function mergeMissingHeaders(target, source) {
220
+ const existingKeys = new Set([...target.keys()].map((key) => key.toLowerCase()));
221
+ for (const [key, value] of source.entries()) if (!existingKeys.has(key.toLowerCase())) target.append(key, value);
222
+ }
223
+ /**
224
+ * Clone a Response into a fresh one whose header bag is guaranteed mutable.
225
+ *
226
+ * `Response.redirect()` and some platform-level passthrough responses (notably
227
+ * on Cloudflare Workers) return objects with frozen header bags. Calling
228
+ * `.set()` or `.append()` on them throws `TypeError: immutable`, which the
229
+ * pipeline can hit when it appends Set-Cookie or Server-Timing entries.
230
+ *
231
+ * The pipeline calls this at the producer sites where user-controlled
232
+ * responses enter the framework — `outcomeToResponse` for all phase outcomes,
233
+ * and `handleRequest` for metadata-route and auto-sitemap user handlers — so
234
+ * downstream code can write headers without runtime feature-detection.
235
+ *
236
+ * The clone is unconditional. This is a deliberate trade: we avoid a
237
+ * try/catch + thrown `TypeError` on every request (the previous probe-based
238
+ * approach paid that cost on the hot path) and accept one cheap Response
239
+ * rewrap at the framework boundary instead.
240
+ */
241
+ function cloneWithMutableHeaders(response) {
242
+ return new Response(response.body, {
243
+ status: response.status,
244
+ statusText: response.statusText,
245
+ headers: new Headers(response.headers)
246
+ });
247
+ }
248
+ /**
249
+ * Build a redirect Response from a RedirectSignal.
250
+ *
251
+ * For RSC payload requests (client navigation), returns 204 + X-Timber-Redirect
252
+ * so the client router can perform a soft SPA redirect. A raw 302 would be
253
+ * turned into an opaque redirect by fetch({redirect:'manual'}), crashing
254
+ * createFromFetch. See design/19-client-navigation.md.
255
+ */
256
+ function buildRedirectResponse(signal, req, headers) {
257
+ if ((req.headers.get("Accept") ?? "").includes("text/x-component")) {
258
+ headers.set("X-Timber-Redirect", signal.location);
259
+ return new Response(null, {
260
+ status: 204,
261
+ headers
262
+ });
263
+ }
264
+ headers.set("Location", signal.location);
265
+ return new Response(null, {
266
+ status: signal.status,
267
+ headers
268
+ });
269
+ }
270
+ /**
271
+ * Fire the user's onRequestError hook with request context.
272
+ * Extracts request info from the Request object and calls the instrumentation hook.
273
+ */
274
+ async function fireOnRequestError(error, req, phase) {
275
+ const url = new URL(req.url);
276
+ const headersObj = {};
277
+ req.headers.forEach((v, k) => {
278
+ headersObj[k] = v;
279
+ });
280
+ await callOnRequestError(error, {
281
+ method: req.method,
282
+ path: url.pathname,
283
+ headers: headersObj
284
+ }, {
285
+ phase,
286
+ routePath: url.pathname,
287
+ routeType: "page",
288
+ traceId: getTraceId()
289
+ });
290
+ }
291
+ //#endregion
292
+ //#region src/server/canonicalize.ts
293
+ /**
294
+ * Encoded separators that produce a 400 rejection.
295
+ * %2f (/) and %5c (\) cause path-confusion attacks.
296
+ */
297
+ var ENCODED_SEPARATOR_RE = /%2f|%5c/i;
298
+ /** Null byte — rejected. */
299
+ var NULL_BYTE_RE = /%00/i;
300
+ /**
301
+ * Canonicalize a URL pathname.
302
+ *
303
+ * 1. Reject encoded separators (%2f, %5c) and null bytes (%00)
304
+ * 2. Single percent-decode
305
+ * 3. Collapse // → /
306
+ * 4. Resolve .. segments (reject if escaping root)
307
+ * 5. Strip trailing slash (except root "/")
308
+ *
309
+ * @param rawPathname - The raw pathname from the request URL (percent-encoded)
310
+ * @param stripTrailingSlash - Whether to strip trailing slashes. Default: true.
311
+ */
312
+ function canonicalize(rawPathname, stripTrailingSlash = true) {
313
+ if (ENCODED_SEPARATOR_RE.test(rawPathname)) return {
314
+ ok: false,
315
+ status: 400
316
+ };
317
+ if (NULL_BYTE_RE.test(rawPathname)) return {
318
+ ok: false,
319
+ status: 400
320
+ };
321
+ let decoded;
322
+ try {
323
+ decoded = decodeURIComponent(rawPathname);
324
+ } catch {
325
+ return {
326
+ ok: false,
327
+ status: 400
328
+ };
329
+ }
330
+ if (decoded.includes("\0")) return {
331
+ ok: false,
332
+ status: 400
333
+ };
334
+ let pathname = decoded.replace(/\/\/+/g, "/");
335
+ const segments = pathname.split("/");
336
+ const resolved = [];
337
+ for (const seg of segments) if (seg === "..") {
338
+ if (resolved.length <= 1) return {
339
+ ok: false,
340
+ status: 400
341
+ };
342
+ resolved.pop();
343
+ } else if (seg !== ".") resolved.push(seg);
344
+ pathname = resolved.join("/") || "/";
345
+ if (stripTrailingSlash && pathname.length > 1 && pathname.endsWith("/")) pathname = pathname.slice(0, -1);
346
+ return {
347
+ ok: true,
348
+ pathname
349
+ };
350
+ }
351
+ //#endregion
352
+ //#region src/server/proxy.ts
353
+ /**
354
+ * Run the proxy pipeline.
355
+ *
356
+ * @param proxyExport - The default export from proxy.ts (function or array)
357
+ * @param req - The incoming request
358
+ * @param next - The continuation that proceeds to route matching and rendering
359
+ * @returns The final response
360
+ */
361
+ async function runProxy(proxyExport, req, next) {
362
+ const fns = Array.isArray(proxyExport) ? proxyExport : [proxyExport];
363
+ let i = fns.length;
364
+ let composed = next;
365
+ while (i--) {
366
+ const fn = fns[i];
367
+ const downstream = composed;
368
+ composed = () => Promise.resolve(fn(req, downstream));
369
+ }
370
+ return composed();
371
+ }
372
+ //#endregion
373
+ //#region src/server/middleware-runner.ts
374
+ /**
375
+ * Run a route's middleware function.
376
+ *
377
+ * @param middlewareFn - The default export from the route's middleware.ts
378
+ * @param ctx - The middleware context (req, params, headers, requestHeaders, searchParams)
379
+ * @returns A Response if middleware short-circuited, or undefined to continue
380
+ */
381
+ async function runMiddleware(middlewareFn, ctx) {
382
+ const result = await middlewareFn(ctx);
383
+ if (result instanceof Response) return result;
384
+ }
385
+ /**
386
+ * Run all middleware functions in the segment chain, root to leaf.
387
+ *
388
+ * Execution is top-down: root middleware runs first, leaf middleware runs last.
389
+ * All middleware share the same MiddlewareContext — a parent that sets
390
+ * ctx.requestHeaders makes it visible to child middleware and downstream components.
391
+ *
392
+ * Short-circuits on the first middleware that returns a Response.
393
+ * Remaining middleware in the chain do not execute.
394
+ *
395
+ * @param chain - Middleware functions ordered root-to-leaf
396
+ * @param ctx - Shared middleware context
397
+ * @returns A Response if any middleware short-circuited, or undefined to continue
398
+ */
399
+ async function runMiddlewareChain(chain, ctx) {
400
+ for (const fn of chain) {
401
+ const result = await fn(ctx);
402
+ if (result instanceof Response) return result;
403
+ }
404
+ }
405
+ /**
406
+ * Per-request marker for synthetic re-render requests that should NOT
407
+ * re-execute `middleware.ts`. The action-dispatch wrapper runs middleware
408
+ * once on the inbound action POST; when validation fails on the no-JS
409
+ * path, it builds a synthetic GET that flows through the normal pipeline
410
+ * to render the page with `getFormFlash()` data. Without this marker, the
411
+ * pipeline would run middleware a second time on that synthetic GET.
412
+ *
413
+ * The set is keyed by the synthetic Request object itself, so the entry
414
+ * lives exactly as long as the request and is garbage-collected with it.
415
+ * Cannot be set or detected by user code — there is no header, no URL
416
+ * parameter, nothing on the wire that an attacker could spoof.
417
+ *
418
+ * See TIM-871.
419
+ *
420
+ * @internal — framework use only.
421
+ */
422
+ var middlewareBypassRequests = /* @__PURE__ */ new WeakSet();
423
+ /**
424
+ * Check whether a request was marked to bypass middleware.
425
+ *
426
+ * Called by `handleRequest` in pipeline-phases.ts before invoking the
427
+ * middleware phase. Returns false for any request not explicitly marked.
428
+ *
429
+ * @internal
430
+ */
431
+ function shouldBypassMiddleware(req) {
432
+ return middlewareBypassRequests.has(req);
433
+ }
434
+ //#endregion
535
435
  //#region src/server/metadata-social.ts
536
436
  /**
537
437
  * Render Open Graph metadata into head element descriptors.
@@ -1259,20 +1159,36 @@ async function loadModule(loader) {
1259
1159
  }
1260
1160
  }
1261
1161
  //#endregion
1262
- //#region src/server/deny-page-resolver.ts
1162
+ //#region src/server/deny-boundary.ts
1263
1163
  /**
1264
- * Deny Page Resolverresolves status-code file components for in-tree deny handling.
1164
+ * Deny boundary subsystemthe in-tree DenySignal flow.
1165
+ *
1166
+ * Three things live together here because they form a single flow:
1167
+ *
1168
+ * 1. **Chain construction** (`buildDenyPageChain`) — walks the matched
1169
+ * segment chain at element-tree build time and produces a list of
1170
+ * `DenyPageEntry` records ordered by specificity (specific status →
1171
+ * category catch-all → `error.tsx`).
1172
+ *
1173
+ * 2. **Runtime matching** (`renderMatchingDenyPage`) — picks the first
1174
+ * chain entry whose status filter matches the thrown DenySignal and
1175
+ * returns a React element for the matching component. Used by
1176
+ * `AccessGate` and `PageDenyBoundary` when they catch a deny.
1265
1177
  *
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.
1178
+ * 3. **The page boundary itself** (`PageDenyBoundary`) the async server
1179
+ * component that wraps a server-component page, calls it, and catches
1180
+ * `DenySignal` so the deny page renders in-tree (no throw reaches
1181
+ * React Flight, single render pass).
1270
1182
  *
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.
1183
+ * Plus the ALS helpers (`setDenyStatus` / `getDenyStatus`) the boundary
1184
+ * uses to thread the matched status code back to the pipeline so the
1185
+ * HTTP status reflects the deny.
1274
1186
  *
1275
- * See design/10-error-handling.md §"Status-Code Files", TIM-666.
1187
+ * Folded into one module from the former `deny-page-resolver.ts` and
1188
+ * `page-deny-boundary.tsx` (TIM-853) — the names were misleading and the
1189
+ * three pieces only made sense together.
1190
+ *
1191
+ * See design/04-authorization.md, design/10-error-handling.md, TIM-666.
1276
1192
  */
1277
1193
  /**
1278
1194
  * Find the first deny page in the chain that matches the given status code.
@@ -1616,78 +1532,411 @@ function pathnameMatchesPattern(pathname, pattern) {
1616
1532
  continue;
1617
1533
  }
1618
1534
  }
1619
- return pi === pathParts.length;
1535
+ return pi === pathParts.length;
1536
+ }
1537
+ //#endregion
1538
+ //#region src/server/param-coercion.ts
1539
+ /**
1540
+ * Run segment param coercion on the matched route's segments.
1541
+ *
1542
+ * Loads params.ts modules from segments that have them, extracts the
1543
+ * segmentParams definition, and coerces raw string params through codecs.
1544
+ * Throws ParamCoercionError if any codec fails (→ 404).
1545
+ *
1546
+ * This runs BEFORE middleware, so ctx.segmentParams is already typed.
1547
+ * See design/07-routing.md §"Where Coercion Runs"
1548
+ */
1549
+ async function coerceSegmentParams(match) {
1550
+ const mergeTarget = Object.create(null);
1551
+ for (const key of Object.keys(match.segmentParams)) if (key !== "__proto__") mergeTarget[key] = match.segmentParams[key];
1552
+ match.segmentParams = mergeTarget;
1553
+ for (const segment of match.segments) {
1554
+ if (!segment.params) continue;
1555
+ let mod;
1556
+ try {
1557
+ mod = await loadModule(segment.params);
1558
+ } catch (err) {
1559
+ throw new ParamCoercionError(`Failed to load params module for segment "${segment.segmentName}": ${err instanceof Error ? err.message : String(err)}`);
1560
+ }
1561
+ const segmentParamsDef = mod.segmentParams;
1562
+ if (!segmentParamsDef || typeof segmentParamsDef.parse !== "function") continue;
1563
+ try {
1564
+ const coerced = segmentParamsDef.parse(match.segmentParams);
1565
+ for (const key of Object.keys(coerced)) if (key !== "__proto__") mergeTarget[key] = sanitizeParamValue(coerced[key]);
1566
+ } catch (err) {
1567
+ throw new ParamCoercionError(err instanceof Error ? err.message : String(err));
1568
+ }
1569
+ }
1570
+ }
1571
+ //#endregion
1572
+ //#region src/server/pipeline-outcome.ts
1573
+ /**
1574
+ * Pipeline outcome translator — converts a `PhaseOutcome` (the value
1575
+ * each phase function returns) into a final `Response`.
1576
+ *
1577
+ * Lifted out of `pipeline-phases.ts` (TIM-853) so the per-phase try /
1578
+ * catch logic and the terminal Response-building logic each live in
1579
+ * their own file. The phases produce values; this module is the single
1580
+ * source of truth for how those values become wire responses.
1581
+ *
1582
+ * See design/07-routing.md §"Request Lifecycle".
1583
+ */
1584
+ /**
1585
+ * Terminal outcome handler — converts a `PhaseOutcome` into a final
1586
+ * `Response`, applying cookies, building redirects, rendering deny pages
1587
+ * and fallback error pages, and firing instrumentation hooks.
1588
+ *
1589
+ * This is the single source of truth for how phase outputs become wire
1590
+ * responses; the per-phase try/catch blocks now produce values, not
1591
+ * Responses, so the conversion logic lives in exactly one place.
1592
+ */
1593
+ async function outcomeToResponse(config, outcome, ctx) {
1594
+ switch (outcome.kind) {
1595
+ case "response": {
1596
+ const finalResponse = cloneWithMutableHeaders(outcome.response);
1597
+ if (outcome.phase === "proxy") return finalResponse;
1598
+ if (outcome.phase === "middleware" && ctx.responseHeaders) {
1599
+ applyCookieJar(finalResponse.headers);
1600
+ mergeMissingHeaders(finalResponse.headers, ctx.responseHeaders);
1601
+ logMiddlewareShortCircuit({
1602
+ method: ctx.method,
1603
+ path: ctx.path,
1604
+ status: finalResponse.status
1605
+ });
1606
+ }
1607
+ if (outcome.phase === "render") markResponseFlushed();
1608
+ return finalResponse;
1609
+ }
1610
+ case "redirect": {
1611
+ const headers = ctx.responseHeaders ?? new Headers();
1612
+ applyCookieJar(headers);
1613
+ return buildRedirectResponse(outcome.signal, ctx.req, headers);
1614
+ }
1615
+ case "deny": {
1616
+ const headers = ctx.responseHeaders ?? new Headers();
1617
+ applyCookieJar(headers);
1618
+ if (config.renderDenyFallback) try {
1619
+ return cloneWithMutableHeaders(await config.renderDenyFallback(outcome.signal, ctx.req, headers, ctx.match));
1620
+ } catch (denyRenderError) {
1621
+ logRenderError({
1622
+ method: ctx.method,
1623
+ path: ctx.path,
1624
+ error: denyRenderError
1625
+ });
1626
+ await fireOnRequestError(denyRenderError, ctx.req, "render");
1627
+ if (config.onPipelineError && denyRenderError instanceof Error) config.onPipelineError(denyRenderError, "render");
1628
+ }
1629
+ return new Response(null, {
1630
+ status: outcome.signal.status,
1631
+ headers
1632
+ });
1633
+ }
1634
+ case "error": {
1635
+ if (outcome.phase === "proxy") {
1636
+ logProxyError({ error: outcome.error });
1637
+ await fireOnRequestError(outcome.error, ctx.req, "proxy");
1638
+ if (config.onPipelineError && outcome.error instanceof Error) config.onPipelineError(outcome.error, "proxy");
1639
+ return new Response(null, { status: 500 });
1640
+ }
1641
+ if (outcome.phase === "middleware") {
1642
+ logMiddlewareError({
1643
+ method: ctx.method,
1644
+ path: ctx.path,
1645
+ error: outcome.error
1646
+ });
1647
+ await fireOnRequestError(outcome.error, ctx.req, "handler");
1648
+ if (config.onPipelineError && outcome.error instanceof Error) config.onPipelineError(outcome.error, "middleware");
1649
+ return new Response(null, { status: 500 });
1650
+ }
1651
+ const headers = ctx.responseHeaders ?? new Headers();
1652
+ applyCookieJar(headers);
1653
+ logRenderError({
1654
+ method: ctx.method,
1655
+ path: ctx.path,
1656
+ error: outcome.error
1657
+ });
1658
+ await fireOnRequestError(outcome.error, ctx.req, "render");
1659
+ if (config.onPipelineError && outcome.error instanceof Error) config.onPipelineError(outcome.error, "render");
1660
+ if (config.renderFallbackError) try {
1661
+ return cloneWithMutableHeaders(await config.renderFallbackError(outcome.error, ctx.req, headers));
1662
+ } catch (fallbackRenderError) {
1663
+ logRenderError({
1664
+ method: ctx.method,
1665
+ path: ctx.path,
1666
+ error: fallbackRenderError
1667
+ });
1668
+ await fireOnRequestError(fallbackRenderError, ctx.req, "render");
1669
+ if (config.onPipelineError && fallbackRenderError instanceof Error) config.onPipelineError(fallbackRenderError, "render");
1670
+ }
1671
+ return new Response(null, { status: 500 });
1672
+ }
1673
+ }
1674
+ }
1675
+ //#endregion
1676
+ //#region src/server/pipeline-phases.ts
1677
+ /**
1678
+ * Pipeline phase functions — module-level free functions that take their
1679
+ * dependencies as explicit parameters. Each phase returns a `PhaseOutcome`
1680
+ * (a discriminated union over response / redirect / deny / error) defined
1681
+ * in `pipeline-outcome.ts`. The terminal `outcomeToResponse` (also in
1682
+ * `pipeline-outcome.ts`) translates outcomes into Responses.
1683
+ *
1684
+ * Lifted out of `createPipeline` so each phase can be unit-tested in
1685
+ * isolation. The lift is mechanical — these functions used to be closures
1686
+ * over `config`; they now take `config` as an explicit parameter.
1687
+ *
1688
+ * See design/07-routing.md §"Request Lifecycle", design/02-rendering-pipeline.md §"Request Flow".
1689
+ */
1690
+ /**
1691
+ * Run the proxy.ts phase. Calls user proxy code and uses `handleRequest` as
1692
+ * the inner `next()` continuation. The proxy resolver was picked at pipeline
1693
+ * construction time so the hot path sees no per-request branching on the
1694
+ * `ProxyConfig` discriminant.
1695
+ */
1696
+ async function runProxyPhase(config, getProxy, req, method, path) {
1697
+ const detailed = config.serverTiming === "detailed";
1698
+ try {
1699
+ const proxyExport = await getProxy();
1700
+ const proxyFn = () => runProxy(proxyExport, req, () => handleRequest(config, req, method, path));
1701
+ return {
1702
+ kind: "response",
1703
+ phase: "proxy",
1704
+ response: await withSpan("timber.proxy", {}, () => detailed ? withTiming("proxy", "proxy.ts", proxyFn) : proxyFn())
1705
+ };
1706
+ } catch (error) {
1707
+ return {
1708
+ kind: "error",
1709
+ phase: "proxy",
1710
+ error
1711
+ };
1712
+ }
1713
+ }
1714
+ /**
1715
+ * Run the middleware chain phase. If the chain short-circuits with a Response,
1716
+ * returns it as a 'response' outcome. Otherwise applies the request header
1717
+ * overlay and falls through to the render phase.
1718
+ */
1719
+ async function runMiddlewarePhase(config, req, match, responseHeaders, requestHeaderOverlay, renderContext) {
1720
+ const detailed = config.serverTiming === "detailed";
1721
+ const ctx = {
1722
+ req,
1723
+ requestHeaders: requestHeaderOverlay,
1724
+ headers: responseHeaders,
1725
+ segmentParams: match.segmentParams,
1726
+ earlyHints: (hints) => {
1727
+ for (const hint of hints) {
1728
+ let value;
1729
+ if (hint.as !== void 0) value = `<${hint.href}>; as=${hint.as}; rel=${hint.rel}`;
1730
+ else value = `<${hint.href}>; rel=${hint.rel}`;
1731
+ if (hint.crossOrigin !== void 0) value += `; crossorigin=${hint.crossOrigin}`;
1732
+ if (hint.fetchPriority !== void 0) value += `; fetchpriority=${hint.fetchPriority}`;
1733
+ responseHeaders.append("Link", value);
1734
+ }
1735
+ }
1736
+ };
1737
+ try {
1738
+ const chainFn = () => runMiddlewareChain(match.middlewareChain, ctx);
1739
+ const middlewareResponse = await (async () => {
1740
+ setMutableCookieContext(true);
1741
+ try {
1742
+ return await withSpan("timber.middleware", {}, () => detailed ? withTiming("mw", "middleware.ts", chainFn) : chainFn());
1743
+ } finally {
1744
+ setMutableCookieContext(false);
1745
+ }
1746
+ })();
1747
+ if (middlewareResponse) return {
1748
+ kind: "response",
1749
+ phase: "middleware",
1750
+ response: middlewareResponse
1751
+ };
1752
+ applyRequestHeaderOverlay(requestHeaderOverlay);
1753
+ applyCookieJar(responseHeaders);
1754
+ return runRenderPhase(config, req, match, responseHeaders, requestHeaderOverlay, renderContext);
1755
+ } catch (error) {
1756
+ if (error instanceof RedirectSignal) return {
1757
+ kind: "redirect",
1758
+ phase: "middleware",
1759
+ signal: error
1760
+ };
1761
+ if (error instanceof DenySignal) return {
1762
+ kind: "deny",
1763
+ phase: "middleware",
1764
+ signal: error
1765
+ };
1766
+ return {
1767
+ kind: "error",
1768
+ phase: "middleware",
1769
+ error
1770
+ };
1771
+ }
1620
1772
  }
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
1773
  /**
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
1774
+ * Run the render phase. Wraps the configured renderer in a span and a
1775
+ * timing scope, and translates thrown signals into outcome variants.
1649
1776
  */
1650
- function safeMerge(target, source) {
1651
- for (const key of Object.keys(source)) if (!DANGEROUS_KEYS.has(key)) target[key] = source[key];
1777
+ async function runRenderPhase(config, req, match, responseHeaders, requestHeaderOverlay, { canonicalPathname, interception }) {
1778
+ const detailed = config.serverTiming === "detailed";
1779
+ try {
1780
+ const renderFn = () => config.render(req, match, responseHeaders, requestHeaderOverlay, interception);
1781
+ return {
1782
+ kind: "response",
1783
+ phase: "render",
1784
+ response: await withSpan("timber.render", { "http.route": canonicalPathname }, () => detailed ? withTiming("render", "RSC + SSR render", renderFn) : renderFn())
1785
+ };
1786
+ } catch (error) {
1787
+ if (error instanceof DenySignal) return {
1788
+ kind: "deny",
1789
+ phase: "render",
1790
+ signal: error
1791
+ };
1792
+ if (error instanceof RedirectSignal) return {
1793
+ kind: "redirect",
1794
+ phase: "render",
1795
+ signal: error
1796
+ };
1797
+ return {
1798
+ kind: "error",
1799
+ phase: "render",
1800
+ error
1801
+ };
1802
+ }
1652
1803
  }
1653
1804
  /**
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)}`);
1805
+ * Process a single request from canonicalization through phase dispatch.
1806
+ *
1807
+ * Stages: canonicalize metadata routes auto-sitemap version skew →
1808
+ * route match interception early hints param coercion → middleware →
1809
+ * render outcome translation. Pre-routing short-circuits return Responses
1810
+ * directly; post-match dispatch goes through `outcomeToResponse`.
1811
+ *
1812
+ * Used both as the top-level entry (when no proxy.ts is configured) and as
1813
+ * the `next()` continuation passed to `runProxy()`.
1814
+ */
1815
+ async function handleRequest(config, req, method, path) {
1816
+ const stripTrailingSlash = config.stripTrailingSlash ?? true;
1817
+ const result = canonicalize(new URL(req.url).pathname, stripTrailingSlash);
1818
+ if (!result.ok) return new Response(null, { status: result.status });
1819
+ const canonicalPathname = result.pathname;
1820
+ if (config.matchMetadataRoute) {
1821
+ const metaMatch = config.matchMetadataRoute(canonicalPathname);
1822
+ if (metaMatch) try {
1823
+ if (metaMatch.isStatic) return await serveStaticMetadataFile(metaMatch);
1824
+ const mod = await loadModule(metaMatch.file);
1825
+ if (typeof mod.default !== "function") return new Response("Metadata route must export a default function", { status: 500 });
1826
+ const handlerResult = await mod.default();
1827
+ if (handlerResult instanceof Response) return cloneWithMutableHeaders(handlerResult);
1828
+ const contentType = metaMatch.contentType;
1829
+ let body;
1830
+ if (typeof handlerResult === "string") body = handlerResult;
1831
+ else if (contentType === "application/xml") body = serializeSitemap(handlerResult);
1832
+ else if (contentType === "application/manifest+json") body = JSON.stringify(handlerResult, null, 2);
1833
+ else body = String(handlerResult);
1834
+ return new Response(body, {
1835
+ status: 200,
1836
+ headers: { "Content-Type": `${contentType}; charset=utf-8` }
1837
+ });
1838
+ } catch (error) {
1839
+ logRenderError({
1840
+ method,
1841
+ path,
1842
+ error
1843
+ });
1844
+ if (config.onPipelineError && error instanceof Error) config.onPipelineError(error, "metadata-route");
1845
+ return new Response(null, { status: 500 });
1672
1846
  }
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));
1847
+ }
1848
+ if (config.autoSitemapHandler) try {
1849
+ const sitemapResponse = await config.autoSitemapHandler(canonicalPathname);
1850
+ if (sitemapResponse) return cloneWithMutableHeaders(sitemapResponse);
1851
+ } catch (error) {
1852
+ logRenderError({
1853
+ method,
1854
+ path,
1855
+ error
1856
+ });
1857
+ if (config.onPipelineError && error instanceof Error) config.onPipelineError(error, "auto-sitemap");
1858
+ return new Response(null, { status: 500 });
1859
+ }
1860
+ if ((req.headers.get("Accept") ?? "").includes("text/x-component")) {
1861
+ if (!checkVersionSkew(req).ok) {
1862
+ const reloadHeaders = new Headers();
1863
+ applyReloadHeaders(reloadHeaders);
1864
+ return new Response(null, {
1865
+ status: 204,
1866
+ headers: reloadHeaders
1867
+ });
1868
+ }
1869
+ }
1870
+ let match = config.matchRoute(canonicalPathname);
1871
+ let interception;
1872
+ const sourceUrl = req.headers.get("X-Timber-URL");
1873
+ if (sourceUrl && config.interceptionRewrites?.length) {
1874
+ const intercepted = findInterceptionMatch(canonicalPathname, sourceUrl, config.interceptionRewrites);
1875
+ if (intercepted) {
1876
+ const sourceMatch = config.matchRoute(intercepted.sourcePathname);
1877
+ if (sourceMatch) {
1878
+ match = sourceMatch;
1879
+ interception = { targetPathname: canonicalPathname };
1880
+ }
1881
+ }
1882
+ }
1883
+ if (!match) {
1884
+ if (config.renderNoMatch) {
1885
+ const responseHeaders = new Headers();
1886
+ return cloneWithMutableHeaders(await config.renderNoMatch(req, responseHeaders));
1887
+ }
1888
+ return new Response(null, { status: 404 });
1889
+ }
1890
+ const responseHeaders = new Headers();
1891
+ const requestHeaderOverlay = new Headers();
1892
+ responseHeaders.set("Cache-Control", "private, no-cache, no-store, max-age=0, must-revalidate");
1893
+ if (config.earlyHints) try {
1894
+ await config.earlyHints(match, req, responseHeaders);
1895
+ } catch (err) {
1896
+ swallow(err, "early hints hook threw");
1897
+ }
1898
+ try {
1899
+ await coerceSegmentParams(match);
1900
+ } catch (error) {
1901
+ if (error instanceof ParamCoercionError) {
1902
+ const leafSegment = match.segments[match.segments.length - 1];
1903
+ if (leafSegment.route && !leafSegment.page) return new Response(null, { status: 404 });
1904
+ if (config.renderNoMatch) return cloneWithMutableHeaders(await config.renderNoMatch(req, responseHeaders));
1905
+ return new Response(null, { status: 404 });
1680
1906
  }
1907
+ throw error;
1681
1908
  }
1909
+ setSegmentParams(match.segmentParams);
1910
+ return outcomeToResponse(config, !shouldBypassMiddleware(req) && match.middlewareChain.length > 0 ? await runMiddlewarePhase(config, req, match, responseHeaders, requestHeaderOverlay, {
1911
+ canonicalPathname,
1912
+ interception
1913
+ }) : await runRenderPhase(config, req, match, responseHeaders, requestHeaderOverlay, {
1914
+ canonicalPathname,
1915
+ interception
1916
+ }), {
1917
+ req,
1918
+ method,
1919
+ path,
1920
+ responseHeaders,
1921
+ match
1922
+ });
1682
1923
  }
1924
+ //#endregion
1925
+ //#region src/server/pipeline.ts
1683
1926
  /**
1684
1927
  * Create the request handler from a pipeline configuration.
1685
1928
  *
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.
1929
+ * Returns a function that processes an incoming Request through all pipeline
1930
+ * stages and produces a Response. This is the top-level entry point for the
1931
+ * server. The body is intentionally small — phase logic lives in
1932
+ * `pipeline-phases.ts`. This function only owns the per-request setup that
1933
+ * has to wrap the entire dispatch: trace ID, request context ALS, span
1934
+ * scope, Server-Timing header emission, and the active-request counter.
1688
1935
  */
1689
1936
  function createPipeline(config) {
1690
- const { proxy, matchRoute, render, earlyHints, stripTrailingSlash = true, slowRequestMs = 3e3, serverTiming = "total", onPipelineError } = config;
1937
+ const proxyResolver = makeProxyResolver(config.proxy);
1938
+ const slowRequestMs = config.slowRequestMs ?? 3e3;
1939
+ const serverTiming = config.serverTiming ?? "total";
1691
1940
  let activeRequests = 0;
1692
1941
  return async (req) => {
1693
1942
  const url = new URL(req.url);
@@ -1709,18 +1958,18 @@ function createPipeline(config) {
1709
1958
  const otelIds = await getOtelTraceId();
1710
1959
  if (otelIds) replaceTraceId(otelIds.traceId, otelIds.spanId);
1711
1960
  let result;
1712
- if (proxy || config.proxyLoader) result = await runProxyPhase(req, method, path);
1713
- else result = await handleRequest(req, method, path);
1961
+ if (proxyResolver) result = await outcomeToResponse(config, await runProxyPhase(config, proxyResolver, req, method, path), {
1962
+ req,
1963
+ method,
1964
+ path
1965
+ });
1966
+ else result = await handleRequest(config, req, method, path);
1714
1967
  await setSpanAttribute("http.response.status_code", result.status);
1715
1968
  if (serverTiming === "detailed") {
1716
1969
  const timingHeader = getServerTimingHeader();
1717
- if (timingHeader) {
1718
- result = ensureMutableResponse(result);
1719
- result.headers.set("Server-Timing", timingHeader);
1720
- }
1970
+ if (timingHeader) result.headers.set("Server-Timing", timingHeader);
1721
1971
  } else if (serverTiming === "total") {
1722
1972
  const totalMs = Math.round(performance.now() - startTime);
1723
- result = ensureMutableResponse(result);
1724
1973
  result.headers.set("Server-Timing", `total;dur=${totalMs}`);
1725
1974
  }
1726
1975
  return result;
@@ -1749,276 +1998,6 @@ function createPipeline(config) {
1749
1998
  });
1750
1999
  });
1751
2000
  };
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, match);
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, match);
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
2001
  }
2023
2002
  //#endregion
2024
2003
  //#region src/server/build-manifest.ts
@@ -2221,10 +2200,46 @@ function sendEarlyHints103(links) {
2221
2200
  if (!sender) return;
2222
2201
  try {
2223
2202
  sender(links);
2224
- } catch {}
2203
+ } catch (err) {
2204
+ swallow(err, "early hints 103 send failed");
2205
+ }
2225
2206
  }
2226
2207
  //#endregion
2227
2208
  //#region src/server/tree-builder.ts
2209
+ var REACT_COMPONENT_TYPE_MARKERS = new Set([
2210
+ Symbol.for("react.forward_ref"),
2211
+ Symbol.for("react.memo"),
2212
+ Symbol.for("react.lazy"),
2213
+ Symbol.for("react.provider"),
2214
+ Symbol.for("react.context"),
2215
+ Symbol.for("react.suspense"),
2216
+ Symbol.for("react.suspense_list"),
2217
+ Symbol.for("react.client.reference")
2218
+ ]);
2219
+ /**
2220
+ * Validate that a loaded module's `default` export is something React
2221
+ * accepts as the first argument to `createElement` — i.e. a valid component
2222
+ * type. React doesn't export `isValidElementType` (only `isValidElement`,
2223
+ * which checks for *elements*, not *component types*), so this mirrors
2224
+ * React's internal check:
2225
+ *
2226
+ * - functions → function or class components
2227
+ * - objects with a `$$typeof` matching one of React's known component
2228
+ * markers → exotic components (`memo`, `forwardRef`, `lazy`, context,
2229
+ * suspense, client references via `@vitejs/plugin-rsc`)
2230
+ *
2231
+ * Strings (HTML tag names) are valid for `createElement` but never appear
2232
+ * as a route module's default export, so they're not recognized here.
2233
+ *
2234
+ * Anything else (numbers, plain config objects, JSON, etc.) is rejected so
2235
+ * the boundary wrapper is skipped rather than crashing inside React.
2236
+ */
2237
+ function isValidElementType(value) {
2238
+ if (typeof value === "function") return true;
2239
+ if (typeof value !== "object" || value === null) return false;
2240
+ const marker = value.$$typeof;
2241
+ return typeof marker === "symbol" && REACT_COMPONENT_TYPE_MARKERS.has(marker);
2242
+ }
2228
2243
  /**
2229
2244
  * Build the unified element tree from a matched segment chain.
2230
2245
  *
@@ -2263,7 +2278,11 @@ async function buildElementTree(config) {
2263
2278
  const LayoutComponent = (await loadModule(segment.layout)).default;
2264
2279
  if (LayoutComponent) {
2265
2280
  const slotProps = {};
2266
- if (segment.slots.size > 0) for (const [slotName, slotNode] of segment.slots) slotProps[slotName] = await buildSlotElement(slotNode, loadModule, createElement, errorBoundaryComponent);
2281
+ const slotNames = Object.keys(segment.slots);
2282
+ if (slotNames.length > 0) for (const slotName of slotNames) {
2283
+ const slotNode = segment.slots[slotName];
2284
+ slotProps[slotName] = await buildSlotElement(slotNode, loadModule, createElement, errorBoundaryComponent);
2285
+ }
2267
2286
  element = createElement(LayoutComponent, {
2268
2287
  ...slotProps,
2269
2288
  children: element
@@ -2332,10 +2351,11 @@ function isMdxFile(file) {
2332
2351
  */
2333
2352
  async function wrapWithErrorBoundaries(segment, element, loadModule, createElement, errorBoundaryComponent) {
2334
2353
  if (segment.statusFiles) {
2335
- for (const [key, file] of segment.statusFiles) if (key !== "4xx" && key !== "5xx") {
2354
+ for (const [key, file] of Object.entries(segment.statusFiles)) if (key !== "4xx" && key !== "5xx") {
2336
2355
  const status = parseInt(key, 10);
2337
2356
  if (!isNaN(status)) {
2338
- const Component = (await loadModule(file)).default;
2357
+ const mod = await loadModule(file);
2358
+ const Component = isValidElementType(mod.default) ? mod.default : null;
2339
2359
  if (Component) element = createElement(errorBoundaryComponent, isMdxFile(file) ? {
2340
2360
  fallbackElement: createElement(Component, { status }),
2341
2361
  status,
@@ -2347,8 +2367,9 @@ async function wrapWithErrorBoundaries(segment, element, loadModule, createEleme
2347
2367
  });
2348
2368
  }
2349
2369
  }
2350
- for (const [key, file] of segment.statusFiles) if (key === "4xx" || key === "5xx") {
2351
- const Component = (await loadModule(file)).default;
2370
+ for (const [key, file] of Object.entries(segment.statusFiles)) if (key === "4xx" || key === "5xx") {
2371
+ const mod = await loadModule(file);
2372
+ const Component = isValidElementType(mod.default) ? mod.default : null;
2352
2373
  if (Component) {
2353
2374
  const categoryStatus = key === "4xx" ? 400 : 500;
2354
2375
  element = createElement(errorBoundaryComponent, isMdxFile(file) ? {
@@ -2364,7 +2385,8 @@ async function wrapWithErrorBoundaries(segment, element, loadModule, createEleme
2364
2385
  }
2365
2386
  }
2366
2387
  if (segment.error) {
2367
- const ErrorComponent = (await loadModule(segment.error)).default;
2388
+ const errorModule = await loadModule(segment.error);
2389
+ const ErrorComponent = isValidElementType(errorModule.default) ? errorModule.default : null;
2368
2390
  if (ErrorComponent) element = createElement(errorBoundaryComponent, isMdxFile(segment.error) ? {
2369
2391
  fallbackElement: createElement(ErrorComponent, {}),
2370
2392
  children: element
@@ -2377,15 +2399,54 @@ async function wrapWithErrorBoundaries(segment, element, loadModule, createEleme
2377
2399
  }
2378
2400
  //#endregion
2379
2401
  //#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 = {
2402
+ /** Reverse index: status code → legacy file name. Built once at module load. */
2403
+ var STATUS_TO_LEGACY_FILE = Object.fromEntries(Object.entries({
2385
2404
  "not-found": 404,
2386
2405
  "forbidden": 403,
2387
2406
  "unauthorized": 401
2388
- };
2407
+ }).map(([name, status]) => [status, name]));
2408
+ /**
2409
+ * Look up `{statusStr}` then `{categoryKey}` (e.g. "4xx" / "5xx") in a
2410
+ * status-file group on a single segment. Shared by all three fallback
2411
+ * chains — the only structural difference between component 4xx,
2412
+ * component 5xx, and JSON resolution is *which* group is searched and
2413
+ * how the per-segment loop is layered around it.
2414
+ */
2415
+ function lookupInGroup(group, statusStr, categoryKey, segmentIndex, status) {
2416
+ if (!group) return null;
2417
+ const exact = group[statusStr];
2418
+ if (exact) return {
2419
+ file: exact,
2420
+ status,
2421
+ kind: "exact",
2422
+ segmentIndex
2423
+ };
2424
+ const category = group[categoryKey];
2425
+ if (category) return {
2426
+ file: category,
2427
+ status,
2428
+ kind: "category",
2429
+ segmentIndex
2430
+ };
2431
+ return null;
2432
+ }
2433
+ /**
2434
+ * Look up the legacy convention file (`not-found.tsx` / `forbidden.tsx` /
2435
+ * `unauthorized.tsx`) for `status` on a single segment. Returns null if
2436
+ * `status` has no legacy mapping or the file isn't present.
2437
+ */
2438
+ function lookupLegacy(group, status, segmentIndex) {
2439
+ if (!group) return null;
2440
+ const name = STATUS_TO_LEGACY_FILE[status];
2441
+ if (!name) return null;
2442
+ const file = group[name];
2443
+ return file ? {
2444
+ file,
2445
+ status,
2446
+ kind: "legacy",
2447
+ segmentIndex
2448
+ } : null;
2449
+ }
2389
2450
  /**
2390
2451
  * Resolve the status-code file to render for a given HTTP status code.
2391
2452
  *
@@ -2398,108 +2459,58 @@ var LEGACY_FILE_TO_STATUS = {
2398
2459
  * @param format - The response format family ('component' or 'json'). Defaults to 'component'.
2399
2460
  */
2400
2461
  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;
2462
+ if (status < 400 || status > 599) return null;
2463
+ if (format === "json") return resolveJson(status, segments);
2464
+ if (status <= 499) return resolve4xx(status, segments);
2465
+ return resolve5xx(status, segments);
2404
2466
  }
2405
2467
  /**
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)
2468
+ * 4xx component fallback chain three separate full passes leaf→root.
2469
+ *
2470
+ * The passes must be separate (not interleaved per-segment) so that a
2471
+ * root-level `404.tsx` beats a leaf-level `error.tsx`. The 5xx chain
2472
+ * inverts this and is per-segment: a leaf's `error.tsx` beats a root's
2473
+ * `5xx.tsx`. This asymmetry is the only reason these two functions exist
2474
+ * separately.
2475
+ *
2476
+ * Pass 1 — {status}.tsx → 4xx.tsx (statusFiles)
2477
+ * Pass 2 — not-found / forbidden / unauthorized (legacyStatusFiles)
2478
+ * Pass 3 — error.tsx (error)
2410
2479
  */
2411
2480
  function resolve4xx(status, segments) {
2412
2481
  const statusStr = String(status);
2413
2482
  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
- };
2483
+ const r = lookupInGroup(segments[i].statusFiles, statusStr, "4xx", i, status);
2484
+ if (r) return r;
2430
2485
  }
2431
2486
  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
- }
2487
+ const r = lookupLegacy(segments[i].legacyStatusFiles, status, i);
2488
+ if (r) return r;
2443
2489
  }
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
2490
  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,
2491
+ const errorFile = segments[i].error;
2492
+ if (errorFile) return {
2493
+ file: errorFile,
2465
2494
  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",
2495
+ kind: "error",
2474
2496
  segmentIndex: i
2475
2497
  };
2476
2498
  }
2477
2499
  return null;
2478
2500
  }
2479
2501
  /**
2480
- * 5xx component fallback chain (single pass, per-segment):
2481
- * At each segment (leaf → root): {status}.tsx → 5xx.tsx → error.tsx
2502
+ * 5xx component fallback chain single pass, per-segment leaf→root.
2503
+ *
2504
+ * At each segment: {status}.tsx → 5xx.tsx → error.tsx. A leaf's
2505
+ * `error.tsx` therefore beats a root's `5xx.tsx`, which is the
2506
+ * intentional inverse of the 4xx chain.
2482
2507
  */
2483
2508
  function resolve5xx(status, segments) {
2484
2509
  const statusStr = String(status);
2485
2510
  for (let i = segments.length - 1; i >= 0; i--) {
2486
2511
  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
- }
2512
+ const r = lookupInGroup(segment.statusFiles, statusStr, "5xx", i, status);
2513
+ if (r) return r;
2503
2514
  if (segment.error) return {
2504
2515
  file: segment.error,
2505
2516
  status,
@@ -2510,29 +2521,18 @@ function resolve5xx(status, segments) {
2510
2521
  return null;
2511
2522
  }
2512
2523
  /**
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.
2524
+ * JSON fallback chain (for both 4xx and 5xx) — single pass leaf→root.
2525
+ *
2526
+ * At each segment: {status}.json {category}.json. No legacy compat,
2527
+ * no error.tsx — the JSON chain terminates at the category catch-all
2528
+ * and the caller falls back to a bare-JSON framework default.
2516
2529
  */
2517
- function resolve5xxJson(status, segments) {
2530
+ function resolveJson(status, segments) {
2518
2531
  const statusStr = String(status);
2532
+ const categoryKey = status >= 500 ? "5xx" : "4xx";
2519
2533
  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
- };
2534
+ const r = lookupInGroup(segments[i].jsonStatusFiles, statusStr, categoryKey, i, status);
2535
+ if (r) return r;
2536
2536
  }
2537
2537
  return null;
2538
2538
  }
@@ -2895,6 +2895,6 @@ var RenderTimeoutError = class extends Error {
2895
2895
  }
2896
2896
  };
2897
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 };
2898
+ export { AccessGate, DEFAULT_LIMITS, DenySignal, METADATA_ROUTE_CONVENTIONS, RedirectSignal, RenderError, RenderTimeoutError, SlotAccessGate, WarningId, buildElementTree, buildNoJsResponse, callOnRequestError, canonicalize, classifyMetadataRoute, coerce, collectEarlyHintHeaders, createPipeline, enforceBodyLimits, executeAction, flushResponse, formatLinkHeader, generateTraceId, getCookie, getHeader, getLogger, getMetadataRouteAutoLink, getMetadataRouteServePath, getSearchParams, getSegmentParams, 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
2899
 
2900
2900
  //# sourceMappingURL=internal.js.map