@timber-js/app 0.2.0-alpha.185 → 0.2.0-alpha.187

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 (570) hide show
  1. package/dist/_chunks/{actions-O_LsyCE4.js → actions-35jnMdeJ.js} +4 -3
  2. package/dist/_chunks/{actions-O_LsyCE4.js.map → actions-35jnMdeJ.js.map} +1 -1
  3. package/dist/_chunks/als-registry-C6kcfprT.js +41 -0
  4. package/dist/_chunks/als-registry-C6kcfprT.js.map +1 -0
  5. package/dist/_chunks/als-slots-BEEIPKYm.js +26 -0
  6. package/dist/_chunks/als-slots-BEEIPKYm.js.map +1 -0
  7. package/dist/_chunks/base-path-DaQrzbez.js +80 -0
  8. package/dist/_chunks/base-path-DaQrzbez.js.map +1 -0
  9. package/dist/_chunks/build-manifest-DWppEdLB.js +86 -0
  10. package/dist/_chunks/build-manifest-DWppEdLB.js.map +1 -0
  11. package/dist/_chunks/build-output-helper-C3DrfzZR.js +369 -0
  12. package/dist/_chunks/build-output-helper-C3DrfzZR.js.map +1 -0
  13. package/dist/_chunks/{cache-api-B-lhk9p4.js → cache-api-DjNrIWRR.js} +8 -14
  14. package/dist/_chunks/cache-api-DjNrIWRR.js.map +1 -0
  15. package/dist/_chunks/cli-check-CpmN7Nh-.js +256 -0
  16. package/dist/_chunks/cli-check-CpmN7Nh-.js.map +1 -0
  17. package/dist/_chunks/cli-schema-sync-CGMp_Psg.js +298 -0
  18. package/dist/_chunks/cli-schema-sync-CGMp_Psg.js.map +1 -0
  19. package/dist/_chunks/{cloudflare-CnT5Lr7U.js → cloudflare-CGP6BZKO.js} +92 -14
  20. package/dist/_chunks/{cloudflare-CnT5Lr7U.js.map → cloudflare-CGP6BZKO.js.map} +1 -1
  21. package/dist/_chunks/convention-lint-kXsgc_-7.js +784 -0
  22. package/dist/_chunks/convention-lint-kXsgc_-7.js.map +1 -0
  23. package/dist/_chunks/error-boundary-D-ODYX41.js +353 -0
  24. package/dist/_chunks/error-boundary-D-ODYX41.js.map +1 -0
  25. package/dist/_chunks/{fast-hash-D6hIVt1Y.js → fast-hash-C-tVYrLb.js} +6 -1
  26. package/dist/_chunks/fast-hash-C-tVYrLb.js.map +1 -0
  27. package/dist/_chunks/file-cache-DmX7OqZP.js +454 -0
  28. package/dist/_chunks/file-cache-DmX7OqZP.js.map +1 -0
  29. package/dist/_chunks/fs-identity-D7vb6GdI.js +208 -0
  30. package/dist/_chunks/fs-identity-D7vb6GdI.js.map +1 -0
  31. package/dist/_chunks/{logger-AWfuX-KJ.js → logger-D8xJZXIN.js} +34 -51
  32. package/dist/_chunks/logger-D8xJZXIN.js.map +1 -0
  33. package/dist/_chunks/mdx-file-C005ay-P.js +25 -0
  34. package/dist/_chunks/mdx-file-C005ay-P.js.map +1 -0
  35. package/dist/_chunks/{plugin-context---kTF5v8.js → plugin-context-rCinWLiE.js} +8 -2
  36. package/dist/_chunks/plugin-context-rCinWLiE.js.map +1 -0
  37. package/dist/_chunks/purge-store-Byr8XOjU.js +14 -0
  38. package/dist/_chunks/purge-store-Byr8XOjU.js.map +1 -0
  39. package/dist/_chunks/{resolve-schema-Dz3fcFUo.js → resolve-schema-5ma5pp1b.js} +2 -2
  40. package/dist/_chunks/{resolve-schema-Dz3fcFUo.js.map → resolve-schema-5ma5pp1b.js.map} +1 -1
  41. package/dist/_chunks/{router-ref-CtmF-aPv.js → router-ref-DuYuV_0Q.js} +2 -2
  42. package/dist/_chunks/router-ref-DuYuV_0Q.js.map +1 -0
  43. package/dist/_chunks/rsc-cache-key-DD0fl_-s.js +243 -0
  44. package/dist/_chunks/rsc-cache-key-DD0fl_-s.js.map +1 -0
  45. package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js +40 -0
  46. package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js.map +1 -0
  47. package/dist/_chunks/rsc-media-type-DRqE_lD_.js +46 -0
  48. package/dist/_chunks/rsc-media-type-DRqE_lD_.js.map +1 -0
  49. package/dist/_chunks/rsc-payload-path-B_LBodc2.js +48 -0
  50. package/dist/_chunks/rsc-payload-path-B_LBodc2.js.map +1 -0
  51. package/dist/_chunks/scanner-C8b0Gcw3.js +1564 -0
  52. package/dist/_chunks/scanner-C8b0Gcw3.js.map +1 -0
  53. package/dist/_chunks/schema-bridge-Cc2Gngu1.js +199 -0
  54. package/dist/_chunks/schema-bridge-Cc2Gngu1.js.map +1 -0
  55. package/dist/_chunks/{segment-classify-Byy425ng.js → segment-classify-C539Pa2O.js} +23 -2
  56. package/dist/_chunks/segment-classify-C539Pa2O.js.map +1 -0
  57. package/dist/_chunks/segment-context-CjOlyB8Y.js +97 -0
  58. package/dist/_chunks/segment-context-CjOlyB8Y.js.map +1 -0
  59. package/dist/_chunks/{canonicalize-DQHyFClh.js → segment-keys-BawYuNFO.js} +61 -2
  60. package/dist/_chunks/segment-keys-BawYuNFO.js.map +1 -0
  61. package/dist/_chunks/slot-params-BCTmZkQB.js +76 -0
  62. package/dist/_chunks/slot-params-BCTmZkQB.js.map +1 -0
  63. package/dist/_chunks/{ssr-data-BOWsq18U.js → ssr-data-14MXm7Pj.js} +17 -2
  64. package/dist/_chunks/ssr-data-14MXm7Pj.js.map +1 -0
  65. package/dist/_chunks/{use-query-states-DFvWd-EA.js → use-query-states-BbU5Ge1V.js} +74 -26
  66. package/dist/_chunks/use-query-states-BbU5Ge1V.js.map +1 -0
  67. package/dist/_chunks/use-segment-params-C4r4BD9T.js +398 -0
  68. package/dist/_chunks/use-segment-params-C4r4BD9T.js.map +1 -0
  69. package/dist/_chunks/walkers-RzN6AFjr.js +141 -0
  70. package/dist/_chunks/walkers-RzN6AFjr.js.map +1 -0
  71. package/dist/adapters/build-output-helper.d.ts +37 -0
  72. package/dist/adapters/build-output-helper.d.ts.map +1 -1
  73. package/dist/adapters/cloudflare-dev.js +1 -1
  74. package/dist/adapters/cloudflare-kv-cache.js +1 -1
  75. package/dist/adapters/cloudflare.d.ts.map +1 -1
  76. package/dist/adapters/cloudflare.js +1 -1
  77. package/dist/adapters/compress-module.d.ts +12 -0
  78. package/dist/adapters/compress-module.d.ts.map +1 -1
  79. package/dist/adapters/fs-identity.d.ts +64 -0
  80. package/dist/adapters/fs-identity.d.ts.map +1 -0
  81. package/dist/adapters/nitro.d.ts.map +1 -1
  82. package/dist/adapters/nitro.js +56 -5
  83. package/dist/adapters/nitro.js.map +1 -1
  84. package/dist/adapters/shared.d.ts +57 -1
  85. package/dist/adapters/shared.d.ts.map +1 -1
  86. package/dist/adapters/types.d.ts +13 -1
  87. package/dist/adapters/types.d.ts.map +1 -1
  88. package/dist/cache/fast-hash.d.ts +5 -0
  89. package/dist/cache/fast-hash.d.ts.map +1 -1
  90. package/dist/cache/index.js +1 -1
  91. package/dist/cdn/cloudflare-purge.js +30 -0
  92. package/dist/cdn/cloudflare-purge.js.map +1 -0
  93. package/dist/cdn/fastly-purge.js +33 -0
  94. package/dist/cdn/fastly-purge.js.map +1 -0
  95. package/dist/cdn/index.js +94 -0
  96. package/dist/cdn/index.js.map +1 -0
  97. package/dist/cdn/workers-cache-purge.js +35 -0
  98. package/dist/cdn/workers-cache-purge.js.map +1 -0
  99. package/dist/cli-check.d.ts +153 -0
  100. package/dist/cli-check.d.ts.map +1 -0
  101. package/dist/cli.d.ts +34 -8
  102. package/dist/cli.d.ts.map +1 -1
  103. package/dist/cli.js +46 -21
  104. package/dist/cli.js.map +1 -1
  105. package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
  106. package/dist/client/browser-entry/hydrate.d.ts +20 -9
  107. package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
  108. package/dist/client/browser-entry/index.d.ts +1 -1
  109. package/dist/client/browser-entry/index.d.ts.map +1 -1
  110. package/dist/client/browser-entry/post-hydration.d.ts +4 -1
  111. package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
  112. package/dist/client/browser-entry/router-init.d.ts +3 -0
  113. package/dist/client/browser-entry/router-init.d.ts.map +1 -1
  114. package/dist/client/browser-entry/rsc-stream.d.ts +8 -0
  115. package/dist/client/browser-entry/rsc-stream.d.ts.map +1 -1
  116. package/dist/client/error-boundary.d.ts +7 -0
  117. package/dist/client/error-boundary.d.ts.map +1 -1
  118. package/dist/client/error-boundary.js +1 -162
  119. package/dist/client/history.d.ts +13 -6
  120. package/dist/client/history.d.ts.map +1 -1
  121. package/dist/client/index.d.ts +1 -0
  122. package/dist/client/index.d.ts.map +1 -1
  123. package/dist/client/index.js +42 -41
  124. package/dist/client/index.js.map +1 -1
  125. package/dist/client/internal.d.ts +4 -3
  126. package/dist/client/internal.d.ts.map +1 -1
  127. package/dist/client/internal.js +877 -311
  128. package/dist/client/internal.js.map +1 -1
  129. package/dist/client/link.d.ts +1 -7
  130. package/dist/client/link.d.ts.map +1 -1
  131. package/dist/client/navigation-api.d.ts +2 -2
  132. package/dist/client/navigation-commit.d.ts +87 -0
  133. package/dist/client/navigation-commit.d.ts.map +1 -0
  134. package/dist/client/navigation-context.d.ts +37 -20
  135. package/dist/client/navigation-context.d.ts.map +1 -1
  136. package/dist/client/navigation-root.d.ts +63 -23
  137. package/dist/client/navigation-root.d.ts.map +1 -1
  138. package/dist/client/params-context.d.ts +81 -0
  139. package/dist/client/params-context.d.ts.map +1 -0
  140. package/dist/client/router-effects.d.ts +129 -0
  141. package/dist/client/router-effects.d.ts.map +1 -0
  142. package/dist/client/router-lifecycle.d.ts +55 -0
  143. package/dist/client/router-lifecycle.d.ts.map +1 -0
  144. package/dist/client/router-pipeline.d.ts +84 -0
  145. package/dist/client/router-pipeline.d.ts.map +1 -0
  146. package/dist/client/router-ref.d.ts +1 -1
  147. package/dist/client/router-ref.d.ts.map +1 -1
  148. package/dist/client/router-skew.d.ts +21 -0
  149. package/dist/client/router-skew.d.ts.map +1 -0
  150. package/dist/client/router-types.d.ts +207 -0
  151. package/dist/client/router-types.d.ts.map +1 -0
  152. package/dist/client/router.d.ts +1 -182
  153. package/dist/client/router.d.ts.map +1 -1
  154. package/dist/client/rsc-fetch.d.ts +47 -26
  155. package/dist/client/rsc-fetch.d.ts.map +1 -1
  156. package/dist/client/segment-cache.d.ts +87 -34
  157. package/dist/client/segment-cache.d.ts.map +1 -1
  158. package/dist/client/ssr-data.d.ts +6 -0
  159. package/dist/client/ssr-data.d.ts.map +1 -1
  160. package/dist/client/stale-client.d.ts +69 -0
  161. package/dist/client/stale-client.d.ts.map +1 -0
  162. package/dist/client/state.d.ts +10 -1
  163. package/dist/client/state.d.ts.map +1 -1
  164. package/dist/client/top-loader.d.ts +2 -1
  165. package/dist/client/top-loader.d.ts.map +1 -1
  166. package/dist/client/unload-guard.d.ts +5 -0
  167. package/dist/client/unload-guard.d.ts.map +1 -1
  168. package/dist/client/use-cookie.d.ts.map +1 -1
  169. package/dist/client/use-query-states.d.ts +9 -3
  170. package/dist/client/use-query-states.d.ts.map +1 -1
  171. package/dist/client/use-router.d.ts +1 -1
  172. package/dist/client/use-segment-params.d.ts +18 -8
  173. package/dist/client/use-segment-params.d.ts.map +1 -1
  174. package/dist/codec.js +1 -1
  175. package/dist/cookies/define-cookie.d.ts.map +1 -1
  176. package/dist/cookies/index.js +5 -5
  177. package/dist/cookies/index.js.map +1 -1
  178. package/dist/index.d.ts +4 -1
  179. package/dist/index.d.ts.map +1 -1
  180. package/dist/index.js +1009 -759
  181. package/dist/index.js.map +1 -1
  182. package/dist/params/index.js +1 -1
  183. package/dist/plugin-context.d.ts +46 -0
  184. package/dist/plugin-context.d.ts.map +1 -1
  185. package/dist/plugins/adapter-build.d.ts.map +1 -1
  186. package/dist/plugins/build-manifest.d.ts +1 -1
  187. package/dist/plugins/build-manifest.d.ts.map +1 -1
  188. package/dist/plugins/entries.d.ts.map +1 -1
  189. package/dist/plugins/fonts.d.ts.map +1 -1
  190. package/dist/plugins/request-dep/analysis.d.ts +106 -0
  191. package/dist/plugins/request-dep/analysis.d.ts.map +1 -0
  192. package/dist/plugins/request-dep/index.d.ts +75 -0
  193. package/dist/plugins/request-dep/index.d.ts.map +1 -0
  194. package/dist/plugins/request-dep/origin.d.ts +61 -0
  195. package/dist/plugins/request-dep/origin.d.ts.map +1 -0
  196. package/dist/plugins/routing.d.ts.map +1 -1
  197. package/dist/plugins/shims.d.ts.map +1 -1
  198. package/dist/plugins/static-build.d.ts +160 -8
  199. package/dist/plugins/static-build.d.ts.map +1 -1
  200. package/dist/routing/codegen-shared.d.ts +3 -44
  201. package/dist/routing/codegen-shared.d.ts.map +1 -1
  202. package/dist/routing/codegen-types.d.ts +28 -31
  203. package/dist/routing/codegen-types.d.ts.map +1 -1
  204. package/dist/routing/codegen-write.d.ts +51 -0
  205. package/dist/routing/codegen-write.d.ts.map +1 -0
  206. package/dist/routing/codegen.d.ts.map +1 -1
  207. package/dist/routing/collision-probe.d.ts +104 -0
  208. package/dist/routing/collision-probe.d.ts.map +1 -0
  209. package/dist/routing/collision-spaces.d.ts +26 -0
  210. package/dist/routing/collision-spaces.d.ts.map +1 -0
  211. package/dist/routing/convention-lint.d.ts +18 -4
  212. package/dist/routing/convention-lint.d.ts.map +1 -1
  213. package/dist/routing/export-detect.d.ts +16 -0
  214. package/dist/routing/export-detect.d.ts.map +1 -1
  215. package/dist/routing/index.js +4 -3
  216. package/dist/routing/interception.d.ts +235 -6
  217. package/dist/routing/interception.d.ts.map +1 -1
  218. package/dist/routing/link-codegen.d.ts +19 -4
  219. package/dist/routing/link-codegen.d.ts.map +1 -1
  220. package/dist/routing/manifest-codegen.d.ts +1 -7
  221. package/dist/routing/manifest-codegen.d.ts.map +1 -1
  222. package/dist/routing/scanner.d.ts.map +1 -1
  223. package/dist/routing/segment-classify.d.ts +33 -0
  224. package/dist/routing/segment-classify.d.ts.map +1 -1
  225. package/dist/routing/segment-keys.d.ts +95 -0
  226. package/dist/routing/segment-keys.d.ts.map +1 -0
  227. package/dist/routing/slot-placement.d.ts +48 -0
  228. package/dist/routing/slot-placement.d.ts.map +1 -0
  229. package/dist/routing/types.d.ts +0 -6
  230. package/dist/routing/types.d.ts.map +1 -1
  231. package/dist/routing/walkers.d.ts +6 -0
  232. package/dist/routing/walkers.d.ts.map +1 -1
  233. package/dist/schema-bridge.d.ts +60 -9
  234. package/dist/schema-bridge.d.ts.map +1 -1
  235. package/dist/search-params/define.d.ts +62 -14
  236. package/dist/search-params/define.d.ts.map +1 -1
  237. package/dist/search-params/index.d.ts +0 -1
  238. package/dist/search-params/index.d.ts.map +1 -1
  239. package/dist/search-params/index.js +221 -10
  240. package/dist/search-params/index.js.map +1 -1
  241. package/dist/search-params/parse-total.d.ts +70 -0
  242. package/dist/search-params/parse-total.d.ts.map +1 -0
  243. package/dist/search-params/wrappers.d.ts +26 -3
  244. package/dist/search-params/wrappers.d.ts.map +1 -1
  245. package/dist/server/access-gate.d.ts +19 -8
  246. package/dist/server/access-gate.d.ts.map +1 -1
  247. package/dist/server/action-handler.d.ts.map +1 -1
  248. package/dist/server/actions.d.ts +6 -3
  249. package/dist/server/actions.d.ts.map +1 -1
  250. package/dist/server/als-registry.d.ts +18 -1
  251. package/dist/server/als-registry.d.ts.map +1 -1
  252. package/dist/server/build-manifest.d.ts +13 -1
  253. package/dist/server/build-manifest.d.ts.map +1 -1
  254. package/dist/server/chain-url-parts.d.ts +82 -0
  255. package/dist/server/chain-url-parts.d.ts.map +1 -0
  256. package/dist/server/children-interception.d.ts +49 -0
  257. package/dist/server/children-interception.d.ts.map +1 -0
  258. package/dist/server/compress.d.ts.map +1 -1
  259. package/dist/server/default-status-page.d.ts +45 -0
  260. package/dist/server/default-status-page.d.ts.map +1 -0
  261. package/dist/server/deny-boundary.d.ts +148 -15
  262. package/dist/server/deny-boundary.d.ts.map +1 -1
  263. package/dist/server/deny-renderer.d.ts +15 -4
  264. package/dist/server/deny-renderer.d.ts.map +1 -1
  265. package/dist/server/error-boundary-wrapper.d.ts +85 -15
  266. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  267. package/dist/server/head-response.d.ts +35 -0
  268. package/dist/server/head-response.d.ts.map +1 -0
  269. package/dist/server/index.d.ts +0 -1
  270. package/dist/server/index.d.ts.map +1 -1
  271. package/dist/server/index.js +3 -2
  272. package/dist/server/index.js.map +1 -1
  273. package/dist/server/internal.d.ts +3 -1
  274. package/dist/server/internal.d.ts.map +1 -1
  275. package/dist/server/internal.js +712 -418
  276. package/dist/server/internal.js.map +1 -1
  277. package/dist/server/metadata-collector.d.ts +52 -0
  278. package/dist/server/metadata-collector.d.ts.map +1 -0
  279. package/dist/server/param-coercion.d.ts +22 -6
  280. package/dist/server/param-coercion.d.ts.map +1 -1
  281. package/dist/server/pipeline-helpers.d.ts +0 -18
  282. package/dist/server/pipeline-helpers.d.ts.map +1 -1
  283. package/dist/server/pipeline-interception.d.ts +59 -9
  284. package/dist/server/pipeline-interception.d.ts.map +1 -1
  285. package/dist/server/pipeline-outcome.d.ts.map +1 -1
  286. package/dist/server/pipeline-phases.d.ts.map +1 -1
  287. package/dist/server/pipeline.d.ts +11 -0
  288. package/dist/server/pipeline.d.ts.map +1 -1
  289. package/dist/server/primitives.d.ts +23 -0
  290. package/dist/server/primitives.d.ts.map +1 -1
  291. package/dist/server/publish-params.d.ts +33 -0
  292. package/dist/server/publish-params.d.ts.map +1 -0
  293. package/dist/server/request-context.d.ts +48 -0
  294. package/dist/server/request-context.d.ts.map +1 -1
  295. package/dist/server/route-element-builder.d.ts +22 -3
  296. package/dist/server/route-element-builder.d.ts.map +1 -1
  297. package/dist/server/route-handler.d.ts.map +1 -1
  298. package/dist/server/route-matcher.d.ts +13 -0
  299. package/dist/server/route-matcher.d.ts.map +1 -1
  300. package/dist/server/rsc-cache-key-guard.d.ts +78 -0
  301. package/dist/server/rsc-cache-key-guard.d.ts.map +1 -0
  302. package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
  303. package/dist/server/rsc-entry/deny-fallback.d.ts.map +1 -1
  304. package/dist/server/rsc-entry/error-renderer.d.ts +15 -3
  305. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  306. package/dist/server/rsc-entry/helpers.d.ts +2 -17
  307. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  308. package/dist/server/rsc-entry/index.d.ts +0 -1
  309. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  310. package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
  311. package/dist/server/rsc-entry/revalidate-renderer.d.ts.map +1 -1
  312. package/dist/server/rsc-entry/rsc-payload.d.ts +1 -2
  313. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  314. package/dist/server/rsc-entry/rsc-stream.d.ts +15 -2
  315. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  316. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  317. package/dist/server/skippable-prefix.d.ts +23 -0
  318. package/dist/server/skippable-prefix.d.ts.map +1 -0
  319. package/dist/server/slot-interception.d.ts +80 -0
  320. package/dist/server/slot-interception.d.ts.map +1 -0
  321. package/dist/server/slot-resolver.d.ts +70 -8
  322. package/dist/server/slot-resolver.d.ts.map +1 -1
  323. package/dist/server/ssr-bridge-types.d.ts +17 -0
  324. package/dist/server/ssr-bridge-types.d.ts.map +1 -1
  325. package/dist/server/ssr-entry.d.ts +0 -1
  326. package/dist/server/ssr-entry.d.ts.map +1 -1
  327. package/dist/server/ssr-wrappers.d.ts +24 -13
  328. package/dist/server/ssr-wrappers.d.ts.map +1 -1
  329. package/dist/server/state-tree-diff.d.ts +6 -27
  330. package/dist/server/state-tree-diff.d.ts.map +1 -1
  331. package/dist/server/static-generator.d.ts +6 -0
  332. package/dist/server/static-generator.d.ts.map +1 -1
  333. package/dist/server/static-not-found.d.ts +52 -0
  334. package/dist/server/static-not-found.d.ts.map +1 -0
  335. package/dist/server/status-code-resolver.d.ts +8 -1
  336. package/dist/server/status-code-resolver.d.ts.map +1 -1
  337. package/dist/server/tree-builder.d.ts +28 -36
  338. package/dist/server/tree-builder.d.ts.map +1 -1
  339. package/dist/server/types.d.ts +12 -7
  340. package/dist/server/types.d.ts.map +1 -1
  341. package/dist/server/utils/element-type.d.ts +40 -0
  342. package/dist/server/utils/element-type.d.ts.map +1 -0
  343. package/dist/shared/als-slots.d.ts +41 -0
  344. package/dist/shared/als-slots.d.ts.map +1 -0
  345. package/dist/shared/base-path.d.ts +56 -0
  346. package/dist/shared/base-path.d.ts.map +1 -0
  347. package/dist/shared/param-value.d.ts +80 -0
  348. package/dist/shared/param-value.d.ts.map +1 -0
  349. package/dist/shared/payload-root.d.ts +108 -0
  350. package/dist/shared/payload-root.d.ts.map +1 -0
  351. package/dist/shared/rsc-cache-key.d.ts +163 -0
  352. package/dist/shared/rsc-cache-key.d.ts.map +1 -0
  353. package/dist/shared/rsc-error-envelope.d.ts +25 -0
  354. package/dist/shared/rsc-error-envelope.d.ts.map +1 -0
  355. package/dist/shared/rsc-manifest.d.ts +28 -0
  356. package/dist/shared/rsc-manifest.d.ts.map +1 -0
  357. package/dist/shared/rsc-media-type.d.ts +40 -0
  358. package/dist/shared/rsc-media-type.d.ts.map +1 -0
  359. package/dist/shared/rsc-payload-path.d.ts +42 -0
  360. package/dist/shared/rsc-payload-path.d.ts.map +1 -0
  361. package/dist/shared/segment-info.d.ts +59 -0
  362. package/dist/shared/segment-info.d.ts.map +1 -0
  363. package/dist/shared/slot-params.d.ts +51 -0
  364. package/dist/shared/slot-params.d.ts.map +1 -0
  365. package/dist/shared/static-platform-files.d.ts +71 -0
  366. package/dist/shared/static-platform-files.d.ts.map +1 -0
  367. package/docs/api/30-api-server.mdx +1 -1
  368. package/docs/api/31-api-client.mdx +4 -2
  369. package/docs/api/33-api-search-params.mdx +38 -16
  370. package/docs/api/35-api-typescript.mdx +3 -3
  371. package/docs/api/36-cli.mdx +34 -7
  372. package/docs/learn/00-introduction.mdx +1 -1
  373. package/docs/learn/02-pages-and-layouts.mdx +2 -2
  374. package/docs/learn/05-typed-params.mdx +8 -8
  375. package/docs/learn/07-typed-routes.mdx +12 -7
  376. package/docs/learn/11-error-handling.mdx +18 -0
  377. package/docs/learn/12-client-navigation.mdx +2 -2
  378. package/docs/learn/14-deploying.mdx +23 -0
  379. package/docs/more/01-advanced-routing.mdx +173 -3
  380. package/docs/more/03-coming-from-nextjs.mdx +2 -2
  381. package/docs/more/04-metadata-and-fonts.mdx +1 -1
  382. package/docs/more/50-ai-agent-instructions.mdx +6 -4
  383. package/package.json +9 -6
  384. package/src/adapters/build-output-helper.ts +116 -3
  385. package/src/adapters/cloudflare.ts +90 -12
  386. package/src/adapters/compress-module.ts +79 -1
  387. package/src/adapters/fs-identity.ts +156 -0
  388. package/src/adapters/nitro.ts +6 -6
  389. package/src/adapters/shared.ts +196 -21
  390. package/src/adapters/types.ts +13 -1
  391. package/src/cache/fast-hash.ts +5 -0
  392. package/src/cli-check.ts +458 -0
  393. package/src/cli.ts +59 -24
  394. package/src/client/browser-entry/action-dispatch.ts +11 -5
  395. package/src/client/browser-entry/hydrate.ts +40 -29
  396. package/src/client/browser-entry/index.ts +15 -6
  397. package/src/client/browser-entry/post-hydration.ts +10 -9
  398. package/src/client/browser-entry/router-init.ts +104 -48
  399. package/src/client/browser-entry/rsc-stream.ts +11 -2
  400. package/src/client/error-boundary.tsx +85 -16
  401. package/src/client/history.ts +13 -6
  402. package/src/client/index.ts +14 -3
  403. package/src/client/internal.ts +4 -3
  404. package/src/client/link.tsx +120 -72
  405. package/src/client/navigation-api.ts +3 -3
  406. package/src/client/navigation-commit.ts +197 -0
  407. package/src/client/navigation-context.ts +40 -67
  408. package/src/client/navigation-root.tsx +326 -101
  409. package/src/client/params-context.ts +192 -0
  410. package/src/client/router-effects.ts +231 -0
  411. package/src/client/router-lifecycle.ts +288 -0
  412. package/src/client/router-pipeline.ts +393 -0
  413. package/src/client/router-ref.ts +1 -1
  414. package/src/client/router-skew.ts +30 -0
  415. package/src/client/router-types.ts +230 -0
  416. package/src/client/router.ts +243 -707
  417. package/src/client/rsc-fetch.ts +183 -106
  418. package/src/client/segment-cache.ts +129 -44
  419. package/src/client/ssr-data.ts +6 -0
  420. package/src/client/stale-client.ts +191 -0
  421. package/src/client/state.ts +14 -1
  422. package/src/client/top-loader.tsx +2 -1
  423. package/src/client/unload-guard.ts +34 -2
  424. package/src/client/use-cookie.ts +9 -2
  425. package/src/client/use-query-states.ts +102 -39
  426. package/src/client/use-router.ts +1 -1
  427. package/src/client/use-segment-params.ts +41 -18
  428. package/src/cookies/define-cookie.ts +6 -1
  429. package/src/index.ts +25 -4
  430. package/src/plugin-context.ts +58 -0
  431. package/src/plugins/adapter-build.ts +4 -0
  432. package/src/plugins/build-manifest.ts +34 -13
  433. package/src/plugins/entries.ts +4 -0
  434. package/src/plugins/fonts.ts +11 -8
  435. package/src/plugins/request-dep/analysis.ts +447 -0
  436. package/src/plugins/request-dep/index.ts +376 -0
  437. package/src/plugins/request-dep/origin.ts +310 -0
  438. package/src/plugins/routing.ts +97 -102
  439. package/src/plugins/shims.ts +0 -1
  440. package/src/plugins/static-build.ts +391 -95
  441. package/src/routing/codegen-shared.ts +3 -79
  442. package/src/routing/codegen-types.ts +28 -31
  443. package/src/routing/codegen-write.ts +139 -0
  444. package/src/routing/codegen.ts +86 -172
  445. package/src/routing/collision-probe.ts +438 -0
  446. package/src/routing/collision-spaces.ts +281 -0
  447. package/src/routing/convention-lint.ts +139 -40
  448. package/src/routing/export-detect.ts +151 -7
  449. package/src/routing/interception.ts +896 -50
  450. package/src/routing/link-codegen.ts +32 -65
  451. package/src/routing/manifest-codegen.ts +6 -62
  452. package/src/routing/scanner.ts +117 -92
  453. package/src/routing/segment-classify.ts +57 -0
  454. package/src/routing/segment-keys.ts +146 -0
  455. package/src/routing/slot-placement.ts +97 -0
  456. package/src/routing/types.ts +0 -6
  457. package/src/routing/walkers.ts +8 -0
  458. package/src/schema-bridge.ts +180 -58
  459. package/src/search-params/define.ts +108 -66
  460. package/src/search-params/index.ts +0 -1
  461. package/src/search-params/parse-total.ts +78 -0
  462. package/src/search-params/wrappers.ts +60 -11
  463. package/src/server/access-gate.tsx +60 -40
  464. package/src/server/action-handler.ts +4 -5
  465. package/src/server/actions.ts +6 -3
  466. package/src/server/als-registry.ts +18 -1
  467. package/src/server/build-manifest.ts +14 -1
  468. package/src/server/chain-url-parts.ts +163 -0
  469. package/src/server/children-interception.ts +115 -0
  470. package/src/server/compress.ts +9 -1
  471. package/src/server/default-status-page.ts +127 -0
  472. package/src/server/deny-boundary.ts +269 -41
  473. package/src/server/deny-renderer.ts +111 -39
  474. package/src/server/error-boundary-wrapper.ts +166 -79
  475. package/src/server/head-response.ts +54 -0
  476. package/src/server/index.ts +1 -3
  477. package/src/server/internal.ts +2 -2
  478. package/src/server/metadata-collector.ts +115 -0
  479. package/src/server/param-coercion.ts +51 -69
  480. package/src/server/pipeline-helpers.ts +2 -47
  481. package/src/server/pipeline-interception.ts +81 -32
  482. package/src/server/pipeline-outcome.ts +4 -5
  483. package/src/server/pipeline-phases.ts +77 -25
  484. package/src/server/pipeline.ts +57 -10
  485. package/src/server/prebuilt-runtime.ts +4 -4
  486. package/src/server/primitives.ts +25 -0
  487. package/src/server/publish-params.ts +42 -0
  488. package/src/server/request-context.ts +97 -18
  489. package/src/server/route-element-builder.ts +241 -253
  490. package/src/server/route-handler.ts +8 -8
  491. package/src/server/route-matcher.ts +21 -9
  492. package/src/server/rsc-cache-key-guard.ts +164 -0
  493. package/src/server/rsc-entry/api-handler.ts +6 -8
  494. package/src/server/rsc-entry/deny-fallback.ts +3 -2
  495. package/src/server/rsc-entry/error-renderer.ts +94 -35
  496. package/src/server/rsc-entry/helpers.ts +33 -28
  497. package/src/server/rsc-entry/index.ts +36 -33
  498. package/src/server/rsc-entry/render-route.ts +18 -10
  499. package/src/server/rsc-entry/revalidate-renderer.ts +3 -2
  500. package/src/server/rsc-entry/rsc-payload.ts +113 -40
  501. package/src/server/rsc-entry/rsc-stream.ts +29 -5
  502. package/src/server/rsc-entry/ssr-renderer.ts +38 -22
  503. package/src/server/skippable-prefix.ts +92 -0
  504. package/src/server/slot-interception.ts +168 -0
  505. package/src/server/slot-resolver.ts +263 -158
  506. package/src/server/ssr-bridge-types.ts +18 -0
  507. package/src/server/ssr-entry.ts +20 -10
  508. package/src/server/ssr-wrappers.tsx +33 -32
  509. package/src/server/state-tree-diff.ts +14 -55
  510. package/src/server/static-generator.ts +68 -29
  511. package/src/server/static-not-found.ts +134 -0
  512. package/src/server/status-code-resolver.ts +28 -11
  513. package/src/server/tree-builder.ts +35 -218
  514. package/src/server/types.ts +12 -7
  515. package/src/server/utils/element-type.ts +72 -0
  516. package/src/shared/als-slots.ts +72 -0
  517. package/src/shared/base-path.ts +80 -0
  518. package/src/shared/param-value.ts +244 -0
  519. package/src/shared/payload-root.ts +151 -0
  520. package/src/shared/rsc-cache-key.ts +269 -0
  521. package/src/shared/rsc-error-envelope.ts +31 -0
  522. package/src/shared/rsc-manifest.ts +29 -0
  523. package/src/shared/rsc-media-type.ts +43 -0
  524. package/src/shared/rsc-payload-path.ts +45 -0
  525. package/src/shared/segment-info.ts +60 -0
  526. package/src/shared/slot-params.ts +111 -0
  527. package/src/shared/static-platform-files.ts +75 -0
  528. package/dist/_chunks/build-output-helper-DOGYFb_X.js +0 -196
  529. package/dist/_chunks/build-output-helper-DOGYFb_X.js.map +0 -1
  530. package/dist/_chunks/cache-api-B-lhk9p4.js.map +0 -1
  531. package/dist/_chunks/canonicalize-DQHyFClh.js.map +0 -1
  532. package/dist/_chunks/cli-schema-sync-BTWEKJXo.js +0 -668
  533. package/dist/_chunks/cli-schema-sync-BTWEKJXo.js.map +0 -1
  534. package/dist/_chunks/define-COtkxMRT.js +0 -191
  535. package/dist/_chunks/define-COtkxMRT.js.map +0 -1
  536. package/dist/_chunks/define-c4au4I9R.js +0 -82
  537. package/dist/_chunks/define-c4au4I9R.js.map +0 -1
  538. package/dist/_chunks/fast-hash-D6hIVt1Y.js.map +0 -1
  539. package/dist/_chunks/logger-AWfuX-KJ.js.map +0 -1
  540. package/dist/_chunks/navigation-root-bUDZLw74.js +0 -124
  541. package/dist/_chunks/navigation-root-bUDZLw74.js.map +0 -1
  542. package/dist/_chunks/plugin-context---kTF5v8.js.map +0 -1
  543. package/dist/_chunks/registry-DbJPKoBp.js +0 -20
  544. package/dist/_chunks/registry-DbJPKoBp.js.map +0 -1
  545. package/dist/_chunks/router-ref-CtmF-aPv.js.map +0 -1
  546. package/dist/_chunks/schema-bridge-DT_Tn0Xf.js +0 -119
  547. package/dist/_chunks/schema-bridge-DT_Tn0Xf.js.map +0 -1
  548. package/dist/_chunks/segment-classify-Byy425ng.js.map +0 -1
  549. package/dist/_chunks/segment-context-ZDnXDkbz.js +0 -34
  550. package/dist/_chunks/segment-context-ZDnXDkbz.js.map +0 -1
  551. package/dist/_chunks/ssr-data-BOWsq18U.js.map +0 -1
  552. package/dist/_chunks/use-query-states-DFvWd-EA.js.map +0 -1
  553. package/dist/_chunks/use-segment-params-D1CzlgBo.js +0 -151
  554. package/dist/_chunks/use-segment-params-D1CzlgBo.js.map +0 -1
  555. package/dist/_chunks/walkers-_6zKFlch.js +0 -1002
  556. package/dist/_chunks/walkers-_6zKFlch.js.map +0 -1
  557. package/dist/client/error-boundary.js.map +0 -1
  558. package/dist/plugins/request-dep.d.ts +0 -32
  559. package/dist/plugins/request-dep.d.ts.map +0 -1
  560. package/dist/search-params/registry.d.ts +0 -20
  561. package/dist/search-params/registry.d.ts.map +0 -1
  562. package/dist/segment-params/define.d.ts +0 -89
  563. package/dist/segment-params/define.d.ts.map +0 -1
  564. package/dist/segment-params/index.d.ts +0 -3
  565. package/dist/segment-params/index.d.ts.map +0 -1
  566. package/dist/segment-params/index.js +0 -2
  567. package/src/plugins/request-dep.ts +0 -499
  568. package/src/search-params/registry.ts +0 -31
  569. package/src/segment-params/define.ts +0 -249
  570. package/src/segment-params/index.ts +0 -9
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Render-time metadata resolution components.
3
+ *
4
+ * MetadataCollector runs below AccessGate for each segment, collecting
5
+ * metadata entries into a request-scoped array. MetadataHead runs at
6
+ * the page level, merging all collected entries and rendering Float-
7
+ * hoisted head elements.
8
+ *
9
+ * This structural approach replaces the eager pre-render metadata
10
+ * resolution: a denied segment's AccessGate prevents MetadataCollector
11
+ * from rendering, so metadata() never executes for denied segments.
12
+ * No `firstDeniedIndex` bookkeeping needed.
13
+ *
14
+ * See TIM-1363, design/16-metadata.md.
15
+ */
16
+
17
+ import { createElement, Fragment } from 'react';
18
+ import type { ReactNode } from 'react';
19
+
20
+ import { requestContextAls } from './als-registry.js';
21
+ import type { Metadata } from './types.js';
22
+ import type { ManifestSegmentNode } from './route-matcher.js';
23
+ import { withSpan } from './tracing.js';
24
+ import { resolveMetadata, renderMetadataToElements, headElementsToReact } from './metadata.js';
25
+ import type { SegmentMetadataEntry } from './metadata.js';
26
+ import { collectMetadataRouteHeadElements } from './metadata-routes.js';
27
+
28
+ // ─── Request-scoped metadata entry store ────────────────────────────────
29
+
30
+ function getMetadataEntries(): SegmentMetadataEntry[] {
31
+ const store = requestContextAls.getStore();
32
+ if (!store) return [];
33
+ store._metadataEntries ??= [];
34
+ return store._metadataEntries;
35
+ }
36
+
37
+ // ─── MetadataCollector ──────────────────────────────────────────────────
38
+
39
+ export interface MetadataCollectorProps {
40
+ metadataExport: Metadata | (() => Promise<Metadata>);
41
+ segmentName: string;
42
+ isPage: boolean;
43
+ onSettled?: () => void;
44
+ children: ReactNode;
45
+ }
46
+
47
+ /**
48
+ * Async server component that collects one segment's metadata during
49
+ * render. Placed between AccessGate and the layout/page. When
50
+ * AccessGate denies, this component never renders, so dynamic
51
+ * metadata() never executes for denied segments (TIM-1027).
52
+ *
53
+ * Static metadata objects are pushed synchronously (no side effects).
54
+ * Dynamic metadata functions are awaited with OTEL tracing.
55
+ */
56
+ export async function MetadataCollector(props: MetadataCollectorProps): Promise<ReactNode> {
57
+ const { metadataExport, segmentName, isPage, onSettled, children } = props;
58
+
59
+ try {
60
+ let metadata: Metadata | null = null;
61
+ if (typeof metadataExport === 'function') {
62
+ metadata =
63
+ (await withSpan('timber.metadata', { 'timber.segment': segmentName }, () =>
64
+ (metadataExport as () => Promise<Metadata>)()
65
+ )) ?? null;
66
+ } else {
67
+ metadata = metadataExport;
68
+ }
69
+
70
+ if (metadata) {
71
+ getMetadataEntries().push({ metadata, isPage });
72
+ }
73
+
74
+ return children;
75
+ } finally {
76
+ onSettled?.();
77
+ }
78
+ }
79
+
80
+ // ─── MetadataHead ───────────────────────────────────────────────────────
81
+
82
+ export interface MetadataHeadProps {
83
+ segments: ManifestSegmentNode[];
84
+ requestUrl: string;
85
+ metadataRouteHashes?: Record<string, string>;
86
+ children: ReactNode;
87
+ }
88
+
89
+ /**
90
+ * Server component at the page level. Reads all metadata entries
91
+ * collected by MetadataCollector components above it, merges them,
92
+ * auto-links metadata routes, and renders Float-hoisted head elements.
93
+ *
94
+ * Placed as a sibling of the page element inside a Fragment so the
95
+ * Float elements are always at the page segment level (re-rendered
96
+ * on every SPA navigation, never skipped by segment tree diffing).
97
+ */
98
+ export function MetadataHead(props: MetadataHeadProps): ReactNode {
99
+ const { segments, requestUrl, metadataRouteHashes, children } = props;
100
+ const entries = getMetadataEntries();
101
+ const resolved = resolveMetadata(entries);
102
+ const headElements = renderMetadataToElements(resolved);
103
+
104
+ const autoLinked = collectMetadataRouteHeadElements(
105
+ segments,
106
+ Infinity,
107
+ resolved,
108
+ new URL(requestUrl),
109
+ metadataRouteHashes
110
+ );
111
+ headElements.push(...autoLinked);
112
+
113
+ const metadataElement = headElementsToReact(headElements);
114
+ return createElement(Fragment, null, metadataElement, children);
115
+ }
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Segment param coercion — runs the matched route's `params.ts` codecs
3
- * over the raw matcher output before middleware and rendering.
2
+ * Segment param coercion — runs the app/schema.ts codecs over the raw
3
+ * matcher output before middleware and rendering.
4
4
  *
5
5
  * Lifted out of `pipeline-phases.ts` (TIM-853) so the coercer can be
6
6
  * imported directly by other entry points (the action-dispatch wrapper,
@@ -13,10 +13,11 @@
13
13
  */
14
14
 
15
15
  import type { Codec } from '../codec.js';
16
+ import type { SegmentType } from '../routing/types.js';
17
+ import { effectiveUrlSegment } from '../routing/segment-classify.js';
16
18
  import { toBracketKey } from '../params/resolve-schema.js';
17
19
  import type { RouteMatch } from './pipeline.js';
18
- import { sanitizeParamValue } from './pipeline-helpers.js';
19
- import { loadModule } from './safe-load.js';
20
+ import { normalizeParamValue } from '../shared/param-value.js';
20
21
  import { ParamCoercionError } from './route-element-builder.js';
21
22
  import { isDebug } from './debug.js';
22
23
 
@@ -44,6 +45,12 @@ export function setGlobalSchemaCodecs(codecs: Record<string, Codec<unknown>> | n
44
45
  * exists, the raw value is parsed through it. If no codec exists or no
45
46
  * global codecs are registered, the raw value passes through unchanged.
46
47
  *
48
+ * Segments are read through `effectiveUrlSegment` because an intercepting
49
+ * node's own `segmentType` is `'intercepting'` and its `paramName` is unset
50
+ * — the param it binds lives in the intercepted name (`(.)[id]` → `[id]`).
51
+ * The slot resolver matches through the same helper, so the key a param is
52
+ * coerced under is always the key it was matched under (TIM-1281).
53
+ *
47
54
  * Unlike main route coercion, this does NOT throw ParamCoercionError on
48
55
  * failure — slots degrade gracefully. A failed coercion logs a warning
49
56
  * in dev mode and keeps the raw value.
@@ -51,7 +58,12 @@ export function setGlobalSchemaCodecs(codecs: Record<string, Codec<unknown>> | n
51
58
  * @internal — framework use only
52
59
  */
53
60
  export function coerceSlotParams(
54
- slotChain: Array<{ segmentType: string; paramName?: string }>,
61
+ slotChain: Array<{
62
+ segmentName: string;
63
+ segmentType: SegmentType;
64
+ paramName?: string;
65
+ interceptedSegmentName?: string;
66
+ }>,
55
67
  rawParams: Record<string, string | string[]>
56
68
  ): Record<string, string | string[]> {
57
69
  const globalCodecs = _globalCodecs;
@@ -62,7 +74,8 @@ export function coerceSlotParams(
62
74
  result[key] = rawParams[key];
63
75
  }
64
76
 
65
- for (const segment of slotChain) {
77
+ for (const node of slotChain) {
78
+ const segment = effectiveUrlSegment(node);
66
79
  if (!segment.paramName) continue;
67
80
  const bracketKey = toBracketKey(segment.segmentType, segment.paramName);
68
81
  const codec = globalCodecs[bracketKey];
@@ -71,10 +84,9 @@ export function coerceSlotParams(
71
84
  const key = segment.paramName;
72
85
  if (!(key in result)) continue;
73
86
 
87
+ let parsed: unknown;
74
88
  try {
75
- result[key] = sanitizeParamValue(codec.parse(result[key] as string | string[])) as
76
- | string
77
- | string[];
89
+ parsed = codec.parse(result[key] as string | string[]);
78
90
  } catch (err) {
79
91
  // Slot param coercion failures are non-fatal — keep the raw value.
80
92
  // The main route already validated the URL; the slot just interprets
@@ -88,7 +100,9 @@ export function coerceSlotParams(
88
100
  ` Keeping raw value.`
89
101
  );
90
102
  }
103
+ continue;
91
104
  }
105
+ result[key] = normalizeParamValue(parsed, key) as string | string[];
92
106
  }
93
107
 
94
108
  return result as Record<string, string | string[]>;
@@ -97,16 +111,24 @@ export function coerceSlotParams(
97
111
  /**
98
112
  * Run segment param coercion on the matched route's segments.
99
113
  *
100
- * When `globalCodecs` is provided (from app/schema.ts), uses the global
101
- * codec map keyed by bare param name. Otherwise falls back to loading
102
- * per-segment params.ts modules.
114
+ * Codecs come from `app/schema.ts` and nowhere else (TIM-1342 completed
115
+ * design/41-global-params.md migration step 9). A route with no schema, or
116
+ * a param the schema does not declare, keeps its raw string — which is the
117
+ * honest type for an uninterpreted URL segment.
103
118
  *
104
119
  * Throws ParamCoercionError if any codec fails (→ 404).
105
120
  *
106
121
  * This runs BEFORE middleware, so ctx.segmentParams is already typed.
107
122
  * See design/07-routing.md §"Where Coercion Runs"
108
123
  * See design/41-global-params.md §"Pipeline Integration"
124
+ *
125
+ * Stays `async` with nothing to await: it is injected as a
126
+ * `CoerceSegmentParamsFn` into the revalidation renderer and the action
127
+ * middleware runner, both of which await it. Narrowing the return type to
128
+ * `void` would break those contracts and remove a microtask boundary the
129
+ * pipeline's ordering has always had, for no gain.
109
130
  */
131
+ // eslint-disable-next-line @typescript-eslint/require-await -- see above
110
132
  export async function coerceSegmentParams(match: RouteMatch): Promise<void> {
111
133
  const globalCodecs = _globalCodecs;
112
134
  // Unconditionally install a null-prototype target so the invariant
@@ -125,7 +147,17 @@ export async function coerceSegmentParams(match: RouteMatch): Promise<void> {
125
147
  // by bracket name (e.g. '[id]', '[...slug]') to avoid collisions between
126
148
  // distinct segment types that share a param name.
127
149
  if (globalCodecs) {
128
- for (const segment of match.segments) {
150
+ for (const node of match.segments) {
151
+ // Read through `effectiveUrlSegment` for the same reason
152
+ // `coerceSlotParams` does: an intercepting node's own `segmentType` is
153
+ // 'intercepting' and its `paramName` is unset — the param it binds lives
154
+ // in the intercepted name (`(.)[id]` → `[id]`). Until TIM-1280 an
155
+ // intercepting node could only appear in a *slot* chain, so only that
156
+ // path needed it; a children-path interception puts one in the main
157
+ // chain, and reading the node directly would silently skip coercion for
158
+ // exactly the param the intercepting directory declares — so the swapped
159
+ // route and the canonical one would disagree about the param's type.
160
+ const segment = effectiveUrlSegment(node);
129
161
  if (!segment.paramName) continue;
130
162
  const bracketKey = toBracketKey(segment.segmentType, segment.paramName);
131
163
  const codec = globalCodecs[bracketKey];
@@ -133,8 +165,9 @@ export async function coerceSegmentParams(match: RouteMatch): Promise<void> {
133
165
 
134
166
  const key = segment.paramName;
135
167
  if (!(key in mergeTarget)) continue;
168
+ let parsed: unknown;
136
169
  try {
137
- mergeTarget[key] = sanitizeParamValue(codec.parse(mergeTarget[key] as string | string[]));
170
+ parsed = codec.parse(mergeTarget[key] as string | string[]);
138
171
  } catch (err) {
139
172
  const message = err instanceof Error ? err.message : String(err);
140
173
  if (isDebug()) {
@@ -147,61 +180,10 @@ export async function coerceSegmentParams(match: RouteMatch): Promise<void> {
147
180
  }
148
181
  throw new ParamCoercionError(message);
149
182
  }
150
- }
151
- return;
152
- }
153
-
154
- // Legacy path: per-segment params.ts coercion
155
- for (const segment of match.segments) {
156
- // Only process segments that have a params.ts convention file
157
- if (!segment.params) continue;
158
-
159
- let mod: Record<string, unknown>;
160
- try {
161
- mod = await loadModule(segment.params);
162
- } catch (err) {
163
- const message = `Failed to load params module for segment "${segment.segmentName}": ${err instanceof Error ? err.message : String(err)}`;
164
- if (isDebug()) {
165
- console.warn(
166
- `[timber] Param coercion error: ${message}\n` +
167
- ` Segment: ${segment.segmentName}\n` +
168
- ` Params file: ${segment.params}`
169
- );
170
- }
171
- throw new ParamCoercionError(message);
172
- }
173
-
174
- const segmentParamsDef = mod.segmentParams as
175
- | { parse(raw: Record<string, string | string[]>): Record<string, unknown> }
176
- | undefined;
177
-
178
- if (!segmentParamsDef || typeof segmentParamsDef.parse !== 'function') continue;
179
-
180
- try {
181
- const coerced = segmentParamsDef.parse(match.segmentParams);
182
-
183
- // Deep-sanitize codec output: every nested plain object becomes
184
- // null-prototype with dangerous keys stripped at every depth.
185
- // See TIM-873, design/13-security.md
186
- for (const key of Object.keys(coerced as Record<string, unknown>)) {
187
- if (key !== '__proto__') {
188
- mergeTarget[key] = sanitizeParamValue((coerced as Record<string, unknown>)[key]);
189
- }
190
- }
191
- } catch (err) {
192
- const message = err instanceof Error ? err.message : String(err);
193
- if (isDebug()) {
194
- const rawKeys = Object.keys(match.segmentParams).join(', ');
195
- console.warn(
196
- `[timber] Param codec rejected values for segment "${segment.segmentName}"\n` +
197
- ` Error: ${message}\n` +
198
- ` Available raw params: { ${rawKeys} }\n` +
199
- ` Params file: ${segment.params}\n` +
200
- ` Hint: this usually means a codec threw for the raw URL value.\n` +
201
- ` Check that the regex/schema accepts the actual path segment string.`
202
- );
203
- }
204
- throw new ParamCoercionError(message);
183
+ // Outside the catch: a codec that rejects a URL value is a 404, a codec
184
+ // that returns an unrepresentable one is a bug in the app. Normalizing
185
+ // inside would report the second as the first.
186
+ mergeTarget[key] = normalizeParamValue(parsed, key);
205
187
  }
206
188
  }
207
189
  }
@@ -15,54 +15,10 @@ import { callOnRequestError } from './instrumentation.js';
15
15
  import { getTraceId } from './tracing.js';
16
16
  import { RedirectSignal } from './primitives.js';
17
17
  import type { ProxyConfig } from './pipeline.js';
18
+ import { isRscRequest } from '../shared/rsc-media-type.js';
18
19
 
19
20
  // ─── Prototype-Pollution-Safe Sanitizer ────────────────────────────────────
20
21
 
21
- /**
22
- * Only __proto__ needs stripping — it has a language-level setter that
23
- * changes the prototype chain of spread copies. constructor and prototype
24
- * are harmless own properties on null-prototype objects.
25
- */
26
- const DANGEROUS_KEYS = new Set(['__proto__']);
27
-
28
- /**
29
- * Deep-walk a value returned by a segment param codec, producing a
30
- * sanitized copy where every plain object is null-prototype and
31
- * dangerous keys (__proto__, constructor, prototype) are stripped at
32
- * every depth.
33
- *
34
- * Non-plain objects (Date, Map, class instances, etc.) are returned
35
- * as-is — they cannot be poisoned by `{...x}` spread and may carry
36
- * author-intended prototype methods.
37
- *
38
- * Arrays are walked element-wise.
39
- *
40
- * Performance: URL params are bounded by URL length (~8 KB). Realistic
41
- * trees are <1 KB. The recursive walk is sub-microsecond.
42
- *
43
- * See TIM-655, TIM-855, TIM-873, design/13-security.md
44
- */
45
- export function sanitizeParamValue(value: unknown): unknown {
46
- if (value === null || typeof value !== 'object') return value;
47
-
48
- if (Array.isArray(value)) {
49
- return value.map(sanitizeParamValue);
50
- }
51
-
52
- // Only walk plain objects — anything with a custom prototype (Date, Map,
53
- // class instances) is left untouched.
54
- const proto = Object.getPrototypeOf(value);
55
- if (proto !== Object.prototype && proto !== null) return value;
56
-
57
- const out: Record<string, unknown> = Object.create(null);
58
- for (const key of Object.keys(value as Record<string, unknown>)) {
59
- if (!DANGEROUS_KEYS.has(key)) {
60
- out[key] = sanitizeParamValue((value as Record<string, unknown>)[key]);
61
- }
62
- }
63
- return out;
64
- }
65
-
66
22
  // ─── Proxy Resolver ────────────────────────────────────────────────────────
67
23
 
68
24
  /**
@@ -170,8 +126,7 @@ export function buildRedirectResponse(
170
126
  req: Request,
171
127
  headers: Headers
172
128
  ): Response {
173
- const isRsc = (req.headers.get('Accept') ?? '').includes('text/x-component');
174
- if (isRsc) {
129
+ if (isRscRequest(req)) {
175
130
  headers.set('X-Timber-Redirect', signal.location);
176
131
  return new Response(null, { status: 204, headers });
177
132
  }
@@ -10,47 +10,96 @@
10
10
  */
11
11
 
12
12
  import { classifyUrlSegment } from '../routing/segment-classify.js';
13
-
14
- /** Result of a successful interception match. */
15
- export interface InterceptionMatchResult {
16
- /** The pathname to re-match (the source/intercepting route's parent). */
17
- sourcePathname: string;
18
- }
19
-
20
- /**
21
- * Check if a pathname starts with a prefix on a segment boundary.
22
- *
23
- * Prevents /feed from matching /feed-private — the prefix must be
24
- * followed by '/' or be an exact match.
25
- */
26
- function hasSegmentPrefix(pathname: string, prefix: string): boolean {
27
- if (prefix === '/') return true;
28
- if (!pathname.startsWith(prefix)) return false;
29
- return pathname.length === prefix.length || pathname[prefix.length] === '/';
30
- }
13
+ import type { InterceptionRewrite } from '../routing/interception.js';
31
14
 
32
15
  /**
33
16
  * Check if an intercepting route applies for this soft navigation.
34
17
  *
35
- * Matches the target pathname against interception rewrites, constrained
36
- * by the source URL (X-Timber-URL header — where the user navigates FROM).
18
+ * Two conditions must hold:
19
+ *
20
+ * 1. The **target** pathname matches the rewrite's intercepted pattern.
21
+ * 2. The **source** route — the page the user is navigating FROM, per the
22
+ * validated X-Timber-URL header — renders the segment that owns the slot
23
+ * holding the intercepting page (`interceptingScope`). Only then does that
24
+ * slot exist in the rendered tree, so only then can the intercepting page
25
+ * render.
37
26
  *
38
- * Returns the source pathname to re-match if interception applies, or null.
27
+ * Condition 2 replaced a URL-prefix check (TIM-1279). A prefix cannot
28
+ * distinguish a route-group-scoped interception from a genuinely root-level
29
+ * one — route groups are invisible in `urlPath`, so both collapse to "/" and
30
+ * the guard degenerates to always-true. Tree paths carry group identity.
31
+ * The scope check also subsumes the old segment-boundary rule: a source route
32
+ * under /feed-private has no "/feed" in its chain.
33
+ *
34
+ * A third condition lives with the caller, which owns route matching, and it
35
+ * applies to the `'slot'` shape only: the route produced by re-matching
36
+ * `interceptingPrefix` must pass through the scope too. Conditions 1–2 are
37
+ * about the route the user is leaving; the slot has to exist in the tree that
38
+ * actually renders. The `'children'` shape resolves its owner from the scope
39
+ * directly rather than from a pathname, so it satisfies that by construction.
40
+ *
41
+ * `resolveSourceScopes` is a thunk so its inputs are only resolved when some
42
+ * rewrite's pattern actually matches the target — the common case is no match
43
+ * at all. It returns the two client-supplied answers to "what is on screen?"
44
+ * as **separate sets, most authoritative first**: the tree paths the client
45
+ * says it has mounted, then those of the route the departing URL matches. It
46
+ * returns null only when neither yields anything, which is not a soft
47
+ * navigation this can reason about.
48
+ *
49
+ * They are two sets rather than one union because ranking needs a chain
50
+ * (TIM-1318). Each channel describes a single rendered chain, so within one the
51
+ * scopes are strictly nested and the deepest is the innermost owner. Unioned
52
+ * they are not: once a modal is open the address bar holds the *intercepted*
53
+ * URL, so the second channel describes the canonical branch — possibly an
54
+ * exclusive route group the client never mounted — and a deeper scope from
55
+ * there would outrank the owner actually on screen. Trying the mounted channel
56
+ * to exhaustion first keeps the fallback the union was there to provide while
57
+ * ranking only within one chain.
58
+ *
59
+ * Returns the winning build-time rewrite, or null. Only build-time values ever
60
+ * reach the renderer: the client-supplied header selects a rewrite but never
61
+ * becomes a rendered route or pathname.
62
+ *
63
+ * The **first** matching rewrite wins, and the caller does not fall back to the
64
+ * next one when the winner fails to resolve. That is deliberate and pre-dates
65
+ * the two shapes: a rewrite that matched the target and the scope described the
66
+ * author's intent, and quietly rendering some other author's interception
67
+ * instead would be harder to explain than rendering the canonical route.
68
+ *
69
+ * "First" is meaningful because the list arrives **deepest scope first** —
70
+ * `collectInterceptionRewrites` sorts it at build time, so the deepest owner a
71
+ * single chain passes through wins and nothing here has to score (TIM-1318).
72
+ * Tree-walk order used to decide it, which meant renaming a route group flipped
73
+ * which modal opened. The precondition is a property of the manifest this reads
74
+ * rather than something it can re-derive, so it is asserted at its source:
75
+ * `tests/interception.test.ts` §"rewrite order is decided by scope depth".
39
76
  */
40
77
  export function findInterceptionMatch(
41
78
  targetPathname: string,
42
- sourceUrl: string,
43
- rewrites: import('../routing/interception.js').InterceptionRewrite[]
44
- ): InterceptionMatchResult | null {
45
- for (const rewrite of rewrites) {
46
- // Check if the source URL starts with the intercepting prefix,
47
- // enforcing segment boundary to prevent /feed matching /feed-private.
48
- if (!hasSegmentPrefix(sourceUrl, rewrite.interceptingPrefix)) continue;
79
+ resolveSourceScopes: () => ReadonlySet<string>[] | null,
80
+ rewrites: InterceptionRewrite[]
81
+ ): InterceptionRewrite | null {
82
+ // Dynamic segments in the pattern match any single URL segment. Collected up
83
+ // front rather than inside the channel loop so a pattern is matched once
84
+ // however many channels are tried — and the common case, no rewrite matching
85
+ // at all, costs exactly the same scan it always did and resolves no scope.
86
+ const candidates = rewrites.filter((rewrite) =>
87
+ pathnameMatchesPattern(targetPathname, rewrite.interceptedPattern)
88
+ );
89
+ if (candidates.length === 0) return null;
90
+
91
+ // Nothing at all is known about the source tree — no scope can contain
92
+ // anything, so no rewrite can apply.
93
+ const scopes = resolveSourceScopes();
94
+ if (!scopes) return null;
49
95
 
50
- // Check if the target URL matches the intercepted pattern.
51
- // Dynamic segments in the pattern match any single URL segment.
52
- if (pathnameMatchesPattern(targetPathname, rewrite.interceptedPattern)) {
53
- return { sourcePathname: rewrite.interceptingPrefix };
96
+ // `candidates` is deepest scope first (`collectInterceptionRewrites`), so
97
+ // within one channel the first hit is the innermost owner that channel
98
+ // reports. Channels are tried in order, so a scope the client says it has
99
+ // mounted always beats one only the address bar implies.
100
+ for (const scope of scopes) {
101
+ for (const rewrite of candidates) {
102
+ if (scope.has(rewrite.interceptingScope)) return rewrite;
54
103
  }
55
104
  }
56
105
  return null;
@@ -26,16 +26,15 @@ import {
26
26
  import { markResponseFlushed } from './request-context.js';
27
27
  import { RedirectSignal, DenySignal } from './primitives.js';
28
28
  import { isDebug } from './debug.js';
29
+ import { rscErrorEnvelope } from '../shared/rsc-error-envelope.js';
30
+ import { isRscRequest } from '../shared/rsc-media-type.js';
29
31
  import type { PipelineConfig, RouteMatch } from './pipeline.js';
30
32
 
31
33
  // ─── Helpers ───────────────────────────────────────────────────────────────
32
34
 
33
35
  function rscErrorResponse(isRsc: boolean, status: number, headers?: Headers): Response {
34
36
  if (!isRsc) return new Response(null, { status });
35
- const h = headers ?? new Headers();
36
- h.set('X-Timber-Error', '1');
37
- h.set('content-type', 'application/json; charset=utf-8');
38
- return new Response(JSON.stringify({ error: true, status }), { status, headers: h });
37
+ return rscErrorEnvelope(status, headers);
39
38
  }
40
39
 
41
40
  // ─── Phase Outcome ─────────────────────────────────────────────────────────
@@ -143,7 +142,7 @@ export async function outcomeToResponse(
143
142
  // Signal the error via X-Timber-Error so the client hard-navigates
144
143
  // to the server-rendered error page instead of feeding HTML to the
145
144
  // Flight decoder (which crashes with "enqueueModel is not a function").
146
- const isRsc = (ctx.req.headers.get('Accept') ?? '').includes('text/x-component');
145
+ const isRsc = isRscRequest(ctx.req);
147
146
 
148
147
  if (outcome.phase === 'proxy') {
149
148
  logProxyError({ error: outcome.error });