@timber-js/app 0.2.0-alpha.98 → 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 (322) hide show
  1. package/LICENSE +8 -0
  2. package/dist/_chunks/actions-CQ8Z8VGL.js +1061 -0
  3. package/dist/_chunks/actions-CQ8Z8VGL.js.map +1 -0
  4. package/dist/_chunks/build-output-helper-DXnW0qjz.js +61 -0
  5. package/dist/_chunks/build-output-helper-DXnW0qjz.js.map +1 -0
  6. package/dist/_chunks/{define-Itxvcd7F.js → define-B-Q_UMOD.js} +19 -23
  7. package/dist/_chunks/define-B-Q_UMOD.js.map +1 -0
  8. package/dist/_chunks/{define-C77ScO0m.js → define-CfBPoJb0.js} +24 -7
  9. package/dist/_chunks/define-CfBPoJb0.js.map +1 -0
  10. package/dist/_chunks/define-cookie-BjpIt4UC.js +194 -0
  11. package/dist/_chunks/define-cookie-BjpIt4UC.js.map +1 -0
  12. package/dist/_chunks/{format-CYBGxKtc.js → format-Bcn-Iv1x.js} +1 -1
  13. package/dist/_chunks/{format-CYBGxKtc.js.map → format-Bcn-Iv1x.js.map} +1 -1
  14. package/dist/_chunks/handler-store-B-lqaGyh.js +54 -0
  15. package/dist/_chunks/handler-store-B-lqaGyh.js.map +1 -0
  16. package/dist/_chunks/logger-0m8MsKdc.js +291 -0
  17. package/dist/_chunks/logger-0m8MsKdc.js.map +1 -0
  18. package/dist/_chunks/merge-search-params-BphMdht_.js +122 -0
  19. package/dist/_chunks/merge-search-params-BphMdht_.js.map +1 -0
  20. package/dist/_chunks/navigation-root-BCYczjml.js +96 -0
  21. package/dist/_chunks/navigation-root-BCYczjml.js.map +1 -0
  22. package/dist/_chunks/registry-I2ss-lvy.js +20 -0
  23. package/dist/_chunks/registry-I2ss-lvy.js.map +1 -0
  24. package/dist/_chunks/router-ref-h3-UaCQv.js +28 -0
  25. package/dist/_chunks/router-ref-h3-UaCQv.js.map +1 -0
  26. package/dist/_chunks/{schema-bridge-C3xl_vfb.js → schema-bridge-Cxu4l-7p.js} +1 -1
  27. package/dist/_chunks/{schema-bridge-C3xl_vfb.js.map → schema-bridge-Cxu4l-7p.js.map} +1 -1
  28. package/dist/_chunks/{segment-context-fHFLF1PE.js → segment-context-Dx_OizxD.js} +1 -1
  29. package/dist/_chunks/{segment-context-fHFLF1PE.js.map → segment-context-Dx_OizxD.js.map} +1 -1
  30. package/dist/_chunks/{router-ref-C8OCm7g7.js → ssr-data-B4CdH7rE.js} +2 -26
  31. package/dist/_chunks/ssr-data-B4CdH7rE.js.map +1 -0
  32. package/dist/_chunks/{stale-reload-BX5gL1r-.js → stale-reload-Bab885FO.js} +1 -1
  33. package/dist/_chunks/{stale-reload-BX5gL1r-.js.map → stale-reload-Bab885FO.js.map} +1 -1
  34. package/dist/_chunks/tracing-C8V-YGsP.js +329 -0
  35. package/dist/_chunks/tracing-C8V-YGsP.js.map +1 -0
  36. package/dist/_chunks/{use-query-states-BiV5GJgm.js → use-query-states-B2XTqxDR.js} +3 -19
  37. package/dist/_chunks/use-query-states-B2XTqxDR.js.map +1 -0
  38. package/dist/_chunks/{use-params-IOPu7E8t.js → use-segment-params-BkpKAQ7D.js} +9 -95
  39. package/dist/_chunks/use-segment-params-BkpKAQ7D.js.map +1 -0
  40. package/dist/_chunks/{walkers-VOXgavMF.js → walkers-Tg0Alwcg.js} +6 -3
  41. package/dist/_chunks/walkers-Tg0Alwcg.js.map +1 -0
  42. package/dist/_chunks/{dev-warnings-DpGRGoDi.js → warnings-Cg47l5sk.js} +3 -3
  43. package/dist/_chunks/warnings-Cg47l5sk.js.map +1 -0
  44. package/dist/adapters/build-output-helper.d.ts +28 -0
  45. package/dist/adapters/build-output-helper.d.ts.map +1 -0
  46. package/dist/adapters/cloudflare.d.ts.map +1 -1
  47. package/dist/adapters/cloudflare.js +8 -28
  48. package/dist/adapters/cloudflare.js.map +1 -1
  49. package/dist/adapters/nitro.d.ts.map +1 -1
  50. package/dist/adapters/nitro.js +8 -26
  51. package/dist/adapters/nitro.js.map +1 -1
  52. package/dist/adapters/shared.d.ts +16 -0
  53. package/dist/adapters/shared.d.ts.map +1 -0
  54. package/dist/cache/index.js +9 -2
  55. package/dist/cache/index.js.map +1 -1
  56. package/dist/cache/timber-cache.d.ts.map +1 -1
  57. package/dist/client/error-boundary.js +2 -1
  58. package/dist/client/error-boundary.js.map +1 -1
  59. package/dist/client/form.d.ts +10 -24
  60. package/dist/client/form.d.ts.map +1 -1
  61. package/dist/client/index.d.ts +1 -5
  62. package/dist/client/index.d.ts.map +1 -1
  63. package/dist/client/index.js +40 -90
  64. package/dist/client/index.js.map +1 -1
  65. package/dist/client/internal.d.ts +2 -1
  66. package/dist/client/internal.d.ts.map +1 -1
  67. package/dist/client/internal.js +81 -7
  68. package/dist/client/internal.js.map +1 -1
  69. package/dist/client/rsc-fetch.d.ts.map +1 -1
  70. package/dist/client/state.d.ts +1 -1
  71. package/dist/client/use-cookie.d.ts +8 -0
  72. package/dist/client/use-cookie.d.ts.map +1 -1
  73. package/dist/client/{use-params.d.ts → use-segment-params.d.ts} +1 -1
  74. package/dist/client/use-segment-params.d.ts.map +1 -0
  75. package/dist/codec.d.ts +1 -1
  76. package/dist/codec.d.ts.map +1 -1
  77. package/dist/codec.js +2 -2
  78. package/dist/config-types.d.ts +28 -0
  79. package/dist/config-types.d.ts.map +1 -1
  80. package/dist/cookies/define-cookie.d.ts +87 -35
  81. package/dist/cookies/define-cookie.d.ts.map +1 -1
  82. package/dist/cookies/index.d.ts +2 -1
  83. package/dist/cookies/index.d.ts.map +1 -1
  84. package/dist/cookies/index.js +48 -2
  85. package/dist/cookies/index.js.map +1 -0
  86. package/dist/cookies/json-cookie.d.ts +64 -0
  87. package/dist/cookies/json-cookie.d.ts.map +1 -0
  88. package/dist/cookies/validation.d.ts +46 -0
  89. package/dist/cookies/validation.d.ts.map +1 -0
  90. package/dist/{plugins/dev-404-page.d.ts → dev-tools/404-page.d.ts} +1 -1
  91. package/dist/dev-tools/404-page.d.ts.map +1 -0
  92. package/dist/{plugins/dev-browser-logs.d.ts → dev-tools/browser-logs.d.ts} +1 -1
  93. package/dist/dev-tools/browser-logs.d.ts.map +1 -0
  94. package/dist/{plugins/dev-error-page.d.ts → dev-tools/error-page.d.ts} +2 -2
  95. package/dist/dev-tools/error-page.d.ts.map +1 -0
  96. package/dist/{server/dev-holding-server.d.ts → dev-tools/holding-server.d.ts} +1 -1
  97. package/dist/dev-tools/holding-server.d.ts.map +1 -0
  98. package/dist/dev-tools/index.d.ts +31 -0
  99. package/dist/dev-tools/index.d.ts.map +1 -0
  100. package/dist/{server/dev-span-processor.d.ts → dev-tools/instrumentation.d.ts} +26 -6
  101. package/dist/dev-tools/instrumentation.d.ts.map +1 -0
  102. package/dist/{server/dev-logger.d.ts → dev-tools/logger.d.ts} +1 -1
  103. package/dist/dev-tools/logger.d.ts.map +1 -0
  104. package/dist/{plugins/dev-logs.d.ts → dev-tools/logs.d.ts} +1 -1
  105. package/dist/dev-tools/logs.d.ts.map +1 -0
  106. package/dist/{plugins/dev-error-overlay.d.ts → dev-tools/overlay.d.ts} +3 -12
  107. package/dist/dev-tools/overlay.d.ts.map +1 -0
  108. package/dist/dev-tools/stack-classifier.d.ts +34 -0
  109. package/dist/dev-tools/stack-classifier.d.ts.map +1 -0
  110. package/dist/{plugins/dev-terminal-error.d.ts → dev-tools/terminal.d.ts} +2 -2
  111. package/dist/dev-tools/terminal.d.ts.map +1 -0
  112. package/dist/{server/dev-warnings.d.ts → dev-tools/warnings.d.ts} +1 -1
  113. package/dist/dev-tools/warnings.d.ts.map +1 -0
  114. package/dist/index.d.ts +1 -0
  115. package/dist/index.d.ts.map +1 -1
  116. package/dist/index.js +97 -72
  117. package/dist/index.js.map +1 -1
  118. package/dist/plugin-context.d.ts +1 -1
  119. package/dist/plugin-context.d.ts.map +1 -1
  120. package/dist/plugins/adapter-build.d.ts.map +1 -1
  121. package/dist/routing/convention-lint.d.ts.map +1 -1
  122. package/dist/routing/index.js +1 -1
  123. package/dist/routing/scanner.d.ts.map +1 -1
  124. package/dist/routing/status-file-lint.d.ts.map +1 -1
  125. package/dist/search-params/define.d.ts +25 -7
  126. package/dist/search-params/define.d.ts.map +1 -1
  127. package/dist/search-params/index.js +5 -3
  128. package/dist/search-params/index.js.map +1 -1
  129. package/dist/search-params/wrappers.d.ts +2 -2
  130. package/dist/search-params/wrappers.d.ts.map +1 -1
  131. package/dist/segment-params/define.d.ts +23 -6
  132. package/dist/segment-params/define.d.ts.map +1 -1
  133. package/dist/segment-params/index.js +1 -1
  134. package/dist/server/access-gate.d.ts +4 -3
  135. package/dist/server/access-gate.d.ts.map +1 -1
  136. package/dist/server/action-handler.d.ts +15 -6
  137. package/dist/server/action-handler.d.ts.map +1 -1
  138. package/dist/server/als-registry.d.ts +5 -5
  139. package/dist/server/als-registry.d.ts.map +1 -1
  140. package/dist/server/asset-headers.d.ts +1 -15
  141. package/dist/server/asset-headers.d.ts.map +1 -1
  142. package/dist/server/cookie-context.d.ts +170 -0
  143. package/dist/server/cookie-context.d.ts.map +1 -0
  144. package/dist/server/cookie-parsing.d.ts +51 -0
  145. package/dist/server/cookie-parsing.d.ts.map +1 -0
  146. package/dist/server/deny-boundary.d.ts +90 -0
  147. package/dist/server/deny-boundary.d.ts.map +1 -0
  148. package/dist/server/deny-renderer.d.ts.map +1 -1
  149. package/dist/server/early-hints-sender.d.ts.map +1 -1
  150. package/dist/server/index.d.ts +5 -4
  151. package/dist/server/index.d.ts.map +1 -1
  152. package/dist/server/index.js +4 -149
  153. package/dist/server/index.js.map +1 -1
  154. package/dist/server/internal.d.ts +6 -4
  155. package/dist/server/internal.d.ts.map +1 -1
  156. package/dist/server/internal.js +261 -408
  157. package/dist/server/internal.js.map +1 -1
  158. package/dist/server/logger.d.ts +14 -0
  159. package/dist/server/logger.d.ts.map +1 -1
  160. package/dist/server/middleware-runner.d.ts +17 -0
  161. package/dist/server/middleware-runner.d.ts.map +1 -1
  162. package/dist/server/param-coercion.d.ts +26 -0
  163. package/dist/server/param-coercion.d.ts.map +1 -0
  164. package/dist/server/pipeline-helpers.d.ts +14 -7
  165. package/dist/server/pipeline-helpers.d.ts.map +1 -1
  166. package/dist/server/pipeline-outcome.d.ts +49 -0
  167. package/dist/server/pipeline-outcome.d.ts.map +1 -0
  168. package/dist/server/pipeline-phases.d.ts +4 -49
  169. package/dist/server/pipeline-phases.d.ts.map +1 -1
  170. package/dist/server/pipeline.d.ts +0 -2
  171. package/dist/server/pipeline.d.ts.map +1 -1
  172. package/dist/server/request-context.d.ts +22 -159
  173. package/dist/server/request-context.d.ts.map +1 -1
  174. package/dist/server/route-element-builder.d.ts.map +1 -1
  175. package/dist/server/rsc-entry/action-middleware-runner.d.ts +66 -0
  176. package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -0
  177. package/dist/server/rsc-entry/helpers.d.ts +1 -1
  178. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  179. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  180. package/dist/server/rsc-entry/render-route.d.ts +50 -0
  181. package/dist/server/rsc-entry/render-route.d.ts.map +1 -0
  182. package/dist/server/rsc-entry/wrap-action-dispatch.d.ts +59 -14
  183. package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +1 -1
  184. package/dist/server/state-tree-diff.d.ts.map +1 -1
  185. package/dist/server/tracing.d.ts +1 -1
  186. package/dist/server/tracing.d.ts.map +1 -1
  187. package/dist/server/tree-builder.d.ts +45 -16
  188. package/dist/server/tree-builder.d.ts.map +1 -1
  189. package/dist/server/types.d.ts +48 -0
  190. package/dist/server/types.d.ts.map +1 -1
  191. package/dist/server/utils/escape-html.d.ts +14 -0
  192. package/dist/server/utils/escape-html.d.ts.map +1 -0
  193. package/dist/shims/headers.d.ts +2 -2
  194. package/dist/shims/headers.d.ts.map +1 -1
  195. package/dist/shims/navigation-client.d.ts +3 -1
  196. package/dist/shims/navigation-client.d.ts.map +1 -1
  197. package/dist/shims/navigation.d.ts +9 -4
  198. package/dist/shims/navigation.d.ts.map +1 -1
  199. package/package.json +6 -7
  200. package/src/adapters/build-output-helper.ts +77 -0
  201. package/src/adapters/cloudflare.ts +10 -50
  202. package/src/adapters/nitro.ts +11 -45
  203. package/src/adapters/shared.ts +40 -0
  204. package/src/cache/timber-cache.ts +3 -2
  205. package/src/cli.ts +0 -0
  206. package/src/client/form.tsx +17 -25
  207. package/src/client/index.ts +16 -9
  208. package/src/client/internal.ts +3 -2
  209. package/src/client/router.ts +1 -1
  210. package/src/client/rsc-fetch.ts +15 -0
  211. package/src/client/state.ts +2 -2
  212. package/src/client/use-cookie.ts +29 -0
  213. package/src/codec.ts +3 -7
  214. package/src/config-types.ts +28 -0
  215. package/src/cookies/define-cookie.ts +271 -78
  216. package/src/cookies/index.ts +11 -8
  217. package/src/cookies/json-cookie.ts +105 -0
  218. package/src/cookies/validation.ts +134 -0
  219. package/src/{plugins/dev-404-page.ts → dev-tools/404-page.ts} +2 -7
  220. package/src/{plugins/dev-error-page.ts → dev-tools/error-page.ts} +5 -32
  221. package/src/dev-tools/index.ts +90 -0
  222. package/src/dev-tools/instrumentation.ts +176 -0
  223. package/src/{plugins/dev-logs.ts → dev-tools/logs.ts} +2 -2
  224. package/src/{plugins/dev-error-overlay.ts → dev-tools/overlay.ts} +5 -23
  225. package/src/dev-tools/stack-classifier.ts +75 -0
  226. package/src/{plugins/dev-terminal-error.ts → dev-tools/terminal.ts} +4 -38
  227. package/src/{server/dev-warnings.ts → dev-tools/warnings.ts} +1 -1
  228. package/src/index.ts +11 -3
  229. package/src/plugin-context.ts +1 -1
  230. package/src/plugins/adapter-build.ts +3 -1
  231. package/src/plugins/dev-server.ts +3 -3
  232. package/src/plugins/shims.ts +1 -1
  233. package/src/plugins/static-build.ts +1 -1
  234. package/src/routing/convention-lint.ts +5 -4
  235. package/src/routing/scanner.ts +5 -2
  236. package/src/routing/status-file-lint.ts +4 -2
  237. package/src/search-params/define.ts +71 -15
  238. package/src/search-params/wrappers.ts +9 -2
  239. package/src/segment-params/define.ts +66 -13
  240. package/src/server/access-gate.tsx +9 -8
  241. package/src/server/action-handler.ts +28 -38
  242. package/src/server/als-registry.ts +5 -5
  243. package/src/server/asset-headers.ts +8 -34
  244. package/src/server/cookie-context.ts +468 -0
  245. package/src/server/cookie-parsing.ts +135 -0
  246. package/src/server/{deny-page-resolver.ts → deny-boundary.ts} +78 -14
  247. package/src/server/deny-renderer.ts +2 -7
  248. package/src/server/early-hints-sender.ts +3 -2
  249. package/src/server/fallback-error.ts +1 -1
  250. package/src/server/index.ts +13 -14
  251. package/src/server/internal.ts +10 -3
  252. package/src/server/logger.ts +23 -0
  253. package/src/server/middleware-runner.ts +44 -0
  254. package/src/server/param-coercion.ts +76 -0
  255. package/src/server/pipeline-helpers.ts +37 -13
  256. package/src/server/pipeline-outcome.ts +167 -0
  257. package/src/server/pipeline-phases.ts +27 -209
  258. package/src/server/pipeline.ts +2 -9
  259. package/src/server/request-context.ts +46 -451
  260. package/src/server/route-element-builder.ts +7 -3
  261. package/src/server/rsc-entry/action-middleware-runner.ts +167 -0
  262. package/src/server/rsc-entry/error-renderer.ts +1 -1
  263. package/src/server/rsc-entry/helpers.ts +2 -7
  264. package/src/server/rsc-entry/index.ts +34 -273
  265. package/src/server/rsc-entry/render-route.ts +304 -0
  266. package/src/server/rsc-entry/rsc-payload.ts +1 -1
  267. package/src/server/rsc-entry/ssr-renderer.ts +2 -2
  268. package/src/server/rsc-entry/wrap-action-dispatch.ts +316 -23
  269. package/src/server/ssr-entry.ts +1 -1
  270. package/src/server/state-tree-diff.ts +4 -1
  271. package/src/server/tracing.ts +3 -3
  272. package/src/server/tree-builder.ts +128 -52
  273. package/src/server/types.ts +52 -0
  274. package/src/server/utils/escape-html.ts +20 -0
  275. package/src/shims/headers.ts +3 -3
  276. package/src/shims/navigation-client.ts +4 -3
  277. package/src/shims/navigation.ts +9 -7
  278. package/dist/_chunks/actions-DLnUaR65.js +0 -421
  279. package/dist/_chunks/actions-DLnUaR65.js.map +0 -1
  280. package/dist/_chunks/als-registry-HS0LGUl2.js +0 -41
  281. package/dist/_chunks/als-registry-HS0LGUl2.js.map +0 -1
  282. package/dist/_chunks/debug-ECi_61pb.js +0 -108
  283. package/dist/_chunks/debug-ECi_61pb.js.map +0 -1
  284. package/dist/_chunks/define-C77ScO0m.js.map +0 -1
  285. package/dist/_chunks/define-Itxvcd7F.js.map +0 -1
  286. package/dist/_chunks/define-cookie-BowvzoP0.js +0 -94
  287. package/dist/_chunks/define-cookie-BowvzoP0.js.map +0 -1
  288. package/dist/_chunks/dev-warnings-DpGRGoDi.js.map +0 -1
  289. package/dist/_chunks/merge-search-params-Cm_KIWDX.js +0 -41
  290. package/dist/_chunks/merge-search-params-Cm_KIWDX.js.map +0 -1
  291. package/dist/_chunks/request-context-CK5tZqIP.js +0 -478
  292. package/dist/_chunks/request-context-CK5tZqIP.js.map +0 -1
  293. package/dist/_chunks/router-ref-C8OCm7g7.js.map +0 -1
  294. package/dist/_chunks/tracing-CCYbKn5n.js +0 -238
  295. package/dist/_chunks/tracing-CCYbKn5n.js.map +0 -1
  296. package/dist/_chunks/use-params-IOPu7E8t.js.map +0 -1
  297. package/dist/_chunks/use-query-states-BiV5GJgm.js.map +0 -1
  298. package/dist/_chunks/walkers-VOXgavMF.js.map +0 -1
  299. package/dist/client/use-params.d.ts.map +0 -1
  300. package/dist/plugins/dev-404-page.d.ts.map +0 -1
  301. package/dist/plugins/dev-browser-logs.d.ts.map +0 -1
  302. package/dist/plugins/dev-error-overlay.d.ts.map +0 -1
  303. package/dist/plugins/dev-error-page.d.ts.map +0 -1
  304. package/dist/plugins/dev-logs.d.ts.map +0 -1
  305. package/dist/plugins/dev-terminal-error.d.ts.map +0 -1
  306. package/dist/server/deny-page-resolver.d.ts +0 -52
  307. package/dist/server/deny-page-resolver.d.ts.map +0 -1
  308. package/dist/server/dev-fetch-instrumentation.d.ts +0 -22
  309. package/dist/server/dev-fetch-instrumentation.d.ts.map +0 -1
  310. package/dist/server/dev-holding-server.d.ts.map +0 -1
  311. package/dist/server/dev-logger.d.ts.map +0 -1
  312. package/dist/server/dev-span-processor.d.ts.map +0 -1
  313. package/dist/server/dev-warnings.d.ts.map +0 -1
  314. package/dist/server/page-deny-boundary.d.ts +0 -31
  315. package/dist/server/page-deny-boundary.d.ts.map +0 -1
  316. package/src/server/dev-fetch-instrumentation.ts +0 -96
  317. package/src/server/dev-span-processor.ts +0 -78
  318. package/src/server/page-deny-boundary.tsx +0 -56
  319. /package/src/client/{use-params.ts → use-segment-params.ts} +0 -0
  320. /package/src/{plugins/dev-browser-logs.ts → dev-tools/browser-logs.ts} +0 -0
  321. /package/src/{server/dev-holding-server.ts → dev-tools/holding-server.ts} +0 -0
  322. /package/src/{server/dev-logger.ts → dev-tools/logger.ts} +0 -0
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Pipeline outcome translator — converts a `PhaseOutcome` (the value
3
+ * each phase function returns) into a final `Response`.
4
+ *
5
+ * Lifted out of `pipeline-phases.ts` (TIM-853) so the per-phase try /
6
+ * catch logic and the terminal Response-building logic each live in
7
+ * their own file. The phases produce values; this module is the single
8
+ * source of truth for how those values become wire responses.
9
+ *
10
+ * See design/07-routing.md §"Request Lifecycle".
11
+ */
12
+
13
+ import {
14
+ applyCookieJar,
15
+ buildRedirectResponse,
16
+ cloneWithMutableHeaders,
17
+ fireOnRequestError,
18
+ mergeMissingHeaders,
19
+ } from './pipeline-helpers.js';
20
+ import {
21
+ logProxyError,
22
+ logMiddlewareError,
23
+ logMiddlewareShortCircuit,
24
+ logRenderError,
25
+ } from './logger.js';
26
+ import { markResponseFlushed } from './request-context.js';
27
+ import { RedirectSignal, DenySignal } from './primitives.js';
28
+ import type { PipelineConfig, RouteMatch } from './pipeline.js';
29
+
30
+ // ─── Phase Outcome ─────────────────────────────────────────────────────────
31
+
32
+ export type PhaseName = 'proxy' | 'middleware' | 'render';
33
+
34
+ export type PhaseOutcome =
35
+ | { kind: 'response'; phase: PhaseName; response: Response }
36
+ | { kind: 'redirect'; phase: PhaseName; signal: RedirectSignal }
37
+ | { kind: 'deny'; phase: PhaseName; signal: DenySignal }
38
+ | { kind: 'error'; phase: PhaseName; error: unknown };
39
+
40
+ export interface OutcomeContext {
41
+ req: Request;
42
+ method: string;
43
+ path: string;
44
+ responseHeaders?: Headers;
45
+ match?: RouteMatch;
46
+ }
47
+
48
+ // ─── Translator ────────────────────────────────────────────────────────────
49
+
50
+ /**
51
+ * Terminal outcome handler — converts a `PhaseOutcome` into a final
52
+ * `Response`, applying cookies, building redirects, rendering deny pages
53
+ * and fallback error pages, and firing instrumentation hooks.
54
+ *
55
+ * This is the single source of truth for how phase outputs become wire
56
+ * responses; the per-phase try/catch blocks now produce values, not
57
+ * Responses, so the conversion logic lives in exactly one place.
58
+ */
59
+ export async function outcomeToResponse(
60
+ config: PipelineConfig,
61
+ outcome: PhaseOutcome,
62
+ ctx: OutcomeContext
63
+ ): Promise<Response> {
64
+ switch (outcome.kind) {
65
+ case 'response': {
66
+ // Clone unconditionally so downstream code (cookie/header merge,
67
+ // Server-Timing in createPipeline) can write headers without paying
68
+ // for a try/catch immutability probe per request. User middleware,
69
+ // proxy, and route code may all return `Response.redirect()` or
70
+ // platform-level responses with frozen header bags. See TIM-866.
71
+ const finalResponse = cloneWithMutableHeaders(outcome.response);
72
+
73
+ if (outcome.phase === 'proxy') return finalResponse;
74
+
75
+ if (outcome.phase === 'middleware' && ctx.responseHeaders) {
76
+ applyCookieJar(finalResponse.headers);
77
+ mergeMissingHeaders(finalResponse.headers, ctx.responseHeaders);
78
+ logMiddlewareShortCircuit({
79
+ method: ctx.method,
80
+ path: ctx.path,
81
+ status: finalResponse.status,
82
+ });
83
+ }
84
+
85
+ if (outcome.phase === 'render') {
86
+ markResponseFlushed();
87
+ }
88
+
89
+ return finalResponse;
90
+ }
91
+
92
+ case 'redirect': {
93
+ const headers = ctx.responseHeaders ?? new Headers();
94
+ applyCookieJar(headers);
95
+ return buildRedirectResponse(outcome.signal, ctx.req, headers);
96
+ }
97
+
98
+ case 'deny': {
99
+ const headers = ctx.responseHeaders ?? new Headers();
100
+ applyCookieJar(headers);
101
+ if (config.renderDenyFallback) {
102
+ try {
103
+ // Clone user-supplied deny-page responses so downstream
104
+ // Server-Timing writes are safe against frozen header bags
105
+ // (e.g. user returned Response.redirect from the hook).
106
+ return cloneWithMutableHeaders(
107
+ await config.renderDenyFallback(outcome.signal, ctx.req, headers, ctx.match)
108
+ );
109
+ } catch (denyRenderError) {
110
+ // Deny page rendering failed — log before falling through to bare response.
111
+ // Without this, a crashing deny page produces a blank response with zero
112
+ // server-side signal. See TIM-876.
113
+ logRenderError({ method: ctx.method, path: ctx.path, error: denyRenderError });
114
+ await fireOnRequestError(denyRenderError, ctx.req, 'render');
115
+ if (config.onPipelineError && denyRenderError instanceof Error)
116
+ config.onPipelineError(denyRenderError, 'render');
117
+ }
118
+ }
119
+ return new Response(null, { status: outcome.signal.status, headers });
120
+ }
121
+
122
+ case 'error': {
123
+ if (outcome.phase === 'proxy') {
124
+ logProxyError({ error: outcome.error });
125
+ await fireOnRequestError(outcome.error, ctx.req, 'proxy');
126
+ if (config.onPipelineError && outcome.error instanceof Error)
127
+ config.onPipelineError(outcome.error, 'proxy');
128
+ return new Response(null, { status: 500 });
129
+ }
130
+
131
+ if (outcome.phase === 'middleware') {
132
+ logMiddlewareError({ method: ctx.method, path: ctx.path, error: outcome.error });
133
+ await fireOnRequestError(outcome.error, ctx.req, 'handler');
134
+ if (config.onPipelineError && outcome.error instanceof Error) {
135
+ config.onPipelineError(outcome.error, 'middleware');
136
+ }
137
+ return new Response(null, { status: 500 });
138
+ }
139
+
140
+ const headers = ctx.responseHeaders ?? new Headers();
141
+ applyCookieJar(headers);
142
+ logRenderError({ method: ctx.method, path: ctx.path, error: outcome.error });
143
+ await fireOnRequestError(outcome.error, ctx.req, 'render');
144
+ if (config.onPipelineError && outcome.error instanceof Error)
145
+ config.onPipelineError(outcome.error, 'render');
146
+ if (config.renderFallbackError) {
147
+ try {
148
+ // Clone user-supplied fallback error responses so downstream
149
+ // Server-Timing writes are safe against frozen header bags.
150
+ return cloneWithMutableHeaders(
151
+ await config.renderFallbackError(outcome.error, ctx.req, headers)
152
+ );
153
+ } catch (fallbackRenderError) {
154
+ // Fallback rendering itself failed — log the secondary error before
155
+ // falling through to bare 500. The original render error was already
156
+ // logged above; this captures the fallback renderer's own crash so it
157
+ // doesn't vanish silently. See TIM-876.
158
+ logRenderError({ method: ctx.method, path: ctx.path, error: fallbackRenderError });
159
+ await fireOnRequestError(fallbackRenderError, ctx.req, 'render');
160
+ if (config.onPipelineError && fallbackRenderError instanceof Error)
161
+ config.onPipelineError(fallbackRenderError, 'render');
162
+ }
163
+ }
164
+ return new Response(null, { status: 500 });
165
+ }
166
+ }
167
+ }
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Pipeline phase functions — module-level free functions that take their
3
3
  * dependencies as explicit parameters. Each phase returns a `PhaseOutcome`
4
- * (a discriminated union over response / redirect / deny / error). The
5
- * terminal `outcomeToResponse` translates outcomes into Responses.
4
+ * (a discriminated union over response / redirect / deny / error) defined
5
+ * in `pipeline-outcome.ts`. The terminal `outcomeToResponse` (also in
6
+ * `pipeline-outcome.ts`) translates outcomes into Responses.
6
7
  *
7
8
  * Lifted out of `createPipeline` so each phase can be unit-tested in
8
9
  * isolation. The lift is mechanical — these functions used to be closures
@@ -13,118 +14,33 @@
13
14
 
14
15
  import { canonicalize } from './canonicalize.js';
15
16
  import { runProxy } from './proxy.js';
16
- import { runMiddlewareChain } from './middleware-runner.js';
17
+ import { runMiddlewareChain, shouldBypassMiddleware } from './middleware-runner.js';
17
18
  import { withTiming } from './server-timing.js';
18
19
  import {
19
20
  applyRequestHeaderOverlay,
20
21
  setMutableCookieContext,
21
- markResponseFlushed,
22
22
  setSegmentParams,
23
23
  } from './request-context.js';
24
24
  import { withSpan } from './tracing.js';
25
- import {
26
- logProxyError,
27
- logMiddlewareError,
28
- logMiddlewareShortCircuit,
29
- logRenderError,
30
- } from './logger.js';
25
+ import { logRenderError } from './logger.js';
31
26
  import { RedirectSignal, DenySignal } from './primitives.js';
32
27
  import { ParamCoercionError } from './route-element-builder.js';
33
28
  import { checkVersionSkew, applyReloadHeaders } from './version-skew.js';
34
29
  import { serveStaticMetadataFile, serializeSitemap } from './pipeline-metadata.js';
35
30
  import { loadModule } from './safe-load.js';
36
31
  import { findInterceptionMatch } from './pipeline-interception.js';
37
- import {
38
- applyCookieJar,
39
- buildRedirectResponse,
40
- cloneWithMutableHeaders,
41
- fireOnRequestError,
42
- mergeMissingHeaders,
43
- safeMerge,
44
- type ProxyResolver,
45
- } from './pipeline-helpers.js';
32
+ import { applyCookieJar, cloneWithMutableHeaders, type ProxyResolver } from './pipeline-helpers.js';
33
+ import { coerceSegmentParams } from './param-coercion.js';
34
+ import { outcomeToResponse, type PhaseOutcome } from './pipeline-outcome.js';
46
35
  import type { InterceptionContext, PipelineConfig, RouteMatch } from './pipeline.js';
47
- import type { MiddlewareContext } from './types.js';
48
-
49
- // ─── Phase Outcome ─────────────────────────────────────────────────────────
50
-
51
- export type PhaseName = 'proxy' | 'middleware' | 'render';
52
-
53
- export type PhaseOutcome =
54
- | { kind: 'response'; phase: PhaseName; response: Response }
55
- | { kind: 'redirect'; phase: PhaseName; signal: RedirectSignal }
56
- | { kind: 'deny'; phase: PhaseName; signal: DenySignal }
57
- | { kind: 'error'; phase: PhaseName; error: unknown };
58
-
59
- export interface OutcomeContext {
60
- req: Request;
61
- method: string;
62
- path: string;
63
- responseHeaders?: Headers;
64
- match?: RouteMatch;
65
- }
36
+ import type { MetadataHandler, MetadataRoute, MiddlewareContext } from './types.js';
37
+ import { swallow } from './logger.js';
66
38
 
67
39
  interface RenderContext {
68
40
  canonicalPathname: string;
69
41
  interception?: InterceptionContext;
70
42
  }
71
43
 
72
- // ─── Param Coercion ────────────────────────────────────────────────────────
73
-
74
- /**
75
- * Run segment param coercion on the matched route's segments.
76
- *
77
- * Loads params.ts modules from segments that have them, extracts the
78
- * segmentParams definition, and coerces raw string params through codecs.
79
- * Throws ParamCoercionError if any codec fails (→ 404).
80
- *
81
- * This runs BEFORE middleware, so ctx.segmentParams is already typed.
82
- * See design/07-routing.md §"Where Coercion Runs"
83
- */
84
- export async function coerceSegmentParams(match: RouteMatch): Promise<void> {
85
- const segments = match.segments;
86
- let mergeTarget = match.segmentParams as Record<string, unknown>;
87
- let usesNullPrototypeTarget = Object.getPrototypeOf(mergeTarget) === null;
88
-
89
- for (const segment of segments) {
90
- // Only process segments that have a params.ts convention file
91
- if (!segment.params) continue;
92
-
93
- let mod: Record<string, unknown>;
94
- try {
95
- mod = await loadModule(segment.params);
96
- } catch (err) {
97
- throw new ParamCoercionError(
98
- `Failed to load params module for segment "${segment.segmentName}": ${err instanceof Error ? err.message : String(err)}`
99
- );
100
- }
101
-
102
- const segmentParamsDef = mod.segmentParams as
103
- | { parse(raw: Record<string, string | string[]>): Record<string, unknown> }
104
- | undefined;
105
-
106
- if (!segmentParamsDef || typeof segmentParamsDef.parse !== 'function') continue;
107
-
108
- try {
109
- const coerced = segmentParamsDef.parse(match.segmentParams);
110
-
111
- if (!usesNullPrototypeTarget) {
112
- mergeTarget = Object.create(null) as Record<string, unknown>;
113
- safeMerge(mergeTarget, match.segmentParams as Record<string, unknown>);
114
- match.segmentParams = mergeTarget as RouteMatch['segmentParams'];
115
- usesNullPrototypeTarget = true;
116
- }
117
-
118
- // safeMerge blocks shallow prototype-polluting keys from codec output.
119
- // The null-prototype target above provides the deeper guarantee for
120
- // nested values without paying the cost of a deep sanitizer.
121
- safeMerge(mergeTarget, coerced as Record<string, unknown>);
122
- } catch (err) {
123
- throw new ParamCoercionError(err instanceof Error ? err.message : String(err));
124
- }
125
- }
126
- }
127
-
128
44
  // ─── Phase: Proxy ──────────────────────────────────────────────────────────
129
45
 
130
46
  /**
@@ -307,7 +223,7 @@ export async function handleRequest(
307
223
  return await serveStaticMetadataFile(metaMatch);
308
224
  }
309
225
 
310
- const mod = await loadModule<{ default?: Function }>(metaMatch.file);
226
+ const mod = await loadModule<{ default?: MetadataHandler }>(metaMatch.file);
311
227
  if (typeof mod.default !== 'function') {
312
228
  return new Response('Metadata route must export a default function', { status: 500 });
313
229
  }
@@ -318,17 +234,21 @@ export async function handleRequest(
318
234
  if (handlerResult instanceof Response) {
319
235
  return cloneWithMutableHeaders(handlerResult);
320
236
  }
321
- // Otherwise, serialize based on content type
237
+ // Otherwise, serialize based on content type. The type discriminator
238
+ // here is the metadata route's declared content type (from the file
239
+ // convention), not the shape of `handlerResult` — TS can't narrow the
240
+ // `MetadataResult` union on that, so we assert against the expected
241
+ // shape at each branch.
322
242
  const contentType = metaMatch.contentType;
323
243
  let body: string;
324
244
  if (typeof handlerResult === 'string') {
325
245
  body = handlerResult;
326
246
  } else if (contentType === 'application/xml') {
327
- body = serializeSitemap(handlerResult);
247
+ body = serializeSitemap(handlerResult as MetadataRoute.Sitemap);
328
248
  } else if (contentType === 'application/manifest+json') {
329
249
  body = JSON.stringify(handlerResult, null, 2);
330
250
  } else {
331
- body = typeof handlerResult === 'string' ? handlerResult : String(handlerResult);
251
+ body = String(handlerResult);
332
252
  }
333
253
  return new Response(body, {
334
254
  status: 200,
@@ -427,8 +347,8 @@ export async function handleRequest(
427
347
  if (config.earlyHints) {
428
348
  try {
429
349
  await config.earlyHints(match, req, responseHeaders);
430
- } catch {
431
- // Early hints failure is non-fatal
350
+ } catch (err) {
351
+ swallow(err, 'early hints hook threw');
432
352
  }
433
353
  }
434
354
 
@@ -462,8 +382,14 @@ export async function handleRequest(
462
382
  // See design/07-routing.md §"params.ts — Convention File for Typed Params"
463
383
  setSegmentParams(match.segmentParams);
464
384
 
385
+ // Bypass middleware for synthetic re-render requests built by the
386
+ // action-dispatch wrapper after a no-JS form validation failure.
387
+ // The wrapper has already executed middleware once on the inbound POST;
388
+ // running it again on the rerender GET would double-execute auth, rate
389
+ // limiting, and request-header injection. See TIM-871.
390
+ const skipMiddleware = shouldBypassMiddleware(req);
465
391
  const outcome =
466
- match.middlewareChain.length > 0
392
+ !skipMiddleware && match.middlewareChain.length > 0
467
393
  ? await runMiddlewarePhase(config, req, match, responseHeaders, requestHeaderOverlay, {
468
394
  canonicalPathname,
469
395
  interception,
@@ -481,111 +407,3 @@ export async function handleRequest(
481
407
  match,
482
408
  });
483
409
  }
484
-
485
- // ─── Outcome Translation ───────────────────────────────────────────────────
486
-
487
- /**
488
- * Terminal outcome handler — converts a `PhaseOutcome` into a final
489
- * `Response`, applying cookies, building redirects, rendering deny pages
490
- * and fallback error pages, and firing instrumentation hooks.
491
- *
492
- * This is the single source of truth for how phase outputs become wire
493
- * responses; the per-phase try/catch blocks now produce values, not
494
- * Responses, so the conversion logic lives in exactly one place.
495
- */
496
- export async function outcomeToResponse(
497
- config: PipelineConfig,
498
- outcome: PhaseOutcome,
499
- ctx: OutcomeContext
500
- ): Promise<Response> {
501
- switch (outcome.kind) {
502
- case 'response': {
503
- // Clone unconditionally so downstream code (cookie/header merge,
504
- // Server-Timing in createPipeline) can write headers without paying
505
- // for a try/catch immutability probe per request. User middleware,
506
- // proxy, and route code may all return `Response.redirect()` or
507
- // platform-level responses with frozen header bags. See TIM-866.
508
- const finalResponse = cloneWithMutableHeaders(outcome.response);
509
-
510
- if (outcome.phase === 'proxy') return finalResponse;
511
-
512
- if (outcome.phase === 'middleware' && ctx.responseHeaders) {
513
- applyCookieJar(finalResponse.headers);
514
- mergeMissingHeaders(finalResponse.headers, ctx.responseHeaders);
515
- logMiddlewareShortCircuit({
516
- method: ctx.method,
517
- path: ctx.path,
518
- status: finalResponse.status,
519
- });
520
- }
521
-
522
- if (outcome.phase === 'render') {
523
- markResponseFlushed();
524
- }
525
-
526
- return finalResponse;
527
- }
528
-
529
- case 'redirect': {
530
- const headers = ctx.responseHeaders ?? new Headers();
531
- applyCookieJar(headers);
532
- return buildRedirectResponse(outcome.signal, ctx.req, headers);
533
- }
534
-
535
- case 'deny': {
536
- const headers = ctx.responseHeaders ?? new Headers();
537
- applyCookieJar(headers);
538
- if (config.renderDenyFallback) {
539
- try {
540
- // Clone user-supplied deny-page responses so downstream
541
- // Server-Timing writes are safe against frozen header bags
542
- // (e.g. user returned Response.redirect from the hook).
543
- return cloneWithMutableHeaders(
544
- await config.renderDenyFallback(outcome.signal, ctx.req, headers, ctx.match)
545
- );
546
- } catch {
547
- // Deny page rendering failed — fall through to bare response
548
- }
549
- }
550
- return new Response(null, { status: outcome.signal.status, headers });
551
- }
552
-
553
- case 'error': {
554
- if (outcome.phase === 'proxy') {
555
- logProxyError({ error: outcome.error });
556
- await fireOnRequestError(outcome.error, ctx.req, 'proxy');
557
- if (config.onPipelineError && outcome.error instanceof Error)
558
- config.onPipelineError(outcome.error, 'proxy');
559
- return new Response(null, { status: 500 });
560
- }
561
-
562
- if (outcome.phase === 'middleware') {
563
- logMiddlewareError({ method: ctx.method, path: ctx.path, error: outcome.error });
564
- await fireOnRequestError(outcome.error, ctx.req, 'handler');
565
- if (config.onPipelineError && outcome.error instanceof Error) {
566
- config.onPipelineError(outcome.error, 'middleware');
567
- }
568
- return new Response(null, { status: 500 });
569
- }
570
-
571
- const headers = ctx.responseHeaders ?? new Headers();
572
- applyCookieJar(headers);
573
- logRenderError({ method: ctx.method, path: ctx.path, error: outcome.error });
574
- await fireOnRequestError(outcome.error, ctx.req, 'render');
575
- if (config.onPipelineError && outcome.error instanceof Error)
576
- config.onPipelineError(outcome.error, 'render');
577
- if (config.renderFallbackError) {
578
- try {
579
- // Clone user-supplied fallback error responses so downstream
580
- // Server-Timing writes are safe against frozen header bags.
581
- return cloneWithMutableHeaders(
582
- await config.renderFallbackError(outcome.error, ctx.req, headers)
583
- );
584
- } catch {
585
- // Fallback rendering itself failed — fall through to bare 500
586
- }
587
- }
588
- return new Response(null, { status: 500 });
589
- }
590
- }
591
- }
@@ -31,15 +31,8 @@ import { logRequestReceived, logRequestCompleted, logSlowRequest } from './logge
31
31
  import { DenySignal } from './primitives.js';
32
32
  import type { ManifestSegmentNode } from './route-matcher.js';
33
33
  import { makeProxyResolver } from './pipeline-helpers.js';
34
- import { handleRequest, outcomeToResponse, runProxyPhase } from './pipeline-phases.js';
35
-
36
- // ─── Re-exports for backwards compatibility ────────────────────────────────
37
-
38
- // `safeMerge` and `coerceSegmentParams` were originally exported from this
39
- // module. They now live in pipeline-helpers.ts and pipeline-phases.ts; the
40
- // re-exports preserve `import { safeMerge } from './pipeline.js'` callers.
41
- export { safeMerge } from './pipeline-helpers.js';
42
- export { coerceSegmentParams } from './pipeline-phases.js';
34
+ import { handleRequest, runProxyPhase } from './pipeline-phases.js';
35
+ import { outcomeToResponse } from './pipeline-outcome.js';
43
36
 
44
37
  // ─── Route Match Result ────────────────────────────────────────────────────
45
38