@timber-js/app 0.2.0-alpha.12 → 0.2.0-alpha.121

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