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

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 (418) hide show
  1. package/dist/_chunks/{actions-O_LsyCE4.js → actions-C-Rw9vPc.js} +3 -3
  2. package/dist/_chunks/{actions-O_LsyCE4.js.map → actions-C-Rw9vPc.js.map} +1 -1
  3. package/dist/_chunks/als-slots-mFweg276.js +27 -0
  4. package/dist/_chunks/als-slots-mFweg276.js.map +1 -0
  5. package/dist/_chunks/base-path-DaQrzbez.js +80 -0
  6. package/dist/_chunks/base-path-DaQrzbez.js.map +1 -0
  7. package/dist/_chunks/build-manifest-DWppEdLB.js +86 -0
  8. package/dist/_chunks/build-manifest-DWppEdLB.js.map +1 -0
  9. package/dist/_chunks/build-output-helper-C3DrfzZR.js +369 -0
  10. package/dist/_chunks/build-output-helper-C3DrfzZR.js.map +1 -0
  11. package/dist/_chunks/{cache-api-B-lhk9p4.js → cache-api-Cd0VZ_Pd.js} +3 -3
  12. package/dist/_chunks/{cache-api-B-lhk9p4.js.map → cache-api-Cd0VZ_Pd.js.map} +1 -1
  13. package/dist/_chunks/cli-schema-sync-CKgHC2MB.js +1857 -0
  14. package/dist/_chunks/cli-schema-sync-CKgHC2MB.js.map +1 -0
  15. package/dist/_chunks/{cloudflare-CnT5Lr7U.js → cloudflare-Cs0uZXea.js} +89 -12
  16. package/dist/_chunks/{cloudflare-CnT5Lr7U.js.map → cloudflare-Cs0uZXea.js.map} +1 -1
  17. package/dist/_chunks/error-boundary-DpYRI_I1.js +330 -0
  18. package/dist/_chunks/error-boundary-DpYRI_I1.js.map +1 -0
  19. package/dist/_chunks/{fast-hash-D6hIVt1Y.js → fast-hash-C-tVYrLb.js} +6 -1
  20. package/dist/_chunks/fast-hash-C-tVYrLb.js.map +1 -0
  21. package/dist/_chunks/fs-identity-D7vb6GdI.js +208 -0
  22. package/dist/_chunks/fs-identity-D7vb6GdI.js.map +1 -0
  23. package/dist/_chunks/{logger-AWfuX-KJ.js → logger-N7e5auP0.js} +9 -13
  24. package/dist/_chunks/logger-N7e5auP0.js.map +1 -0
  25. package/dist/_chunks/navigation-root-B29qg0_T.js +274 -0
  26. package/dist/_chunks/navigation-root-B29qg0_T.js.map +1 -0
  27. package/dist/_chunks/param-value-C8TNYchQ.js +66 -0
  28. package/dist/_chunks/param-value-C8TNYchQ.js.map +1 -0
  29. package/dist/_chunks/{plugin-context---kTF5v8.js → plugin-context-DEGLSJs3.js} +2 -1
  30. package/dist/_chunks/plugin-context-DEGLSJs3.js.map +1 -0
  31. package/dist/_chunks/{router-ref-CtmF-aPv.js → router-ref-DuYuV_0Q.js} +2 -2
  32. package/dist/_chunks/router-ref-DuYuV_0Q.js.map +1 -0
  33. package/dist/_chunks/rsc-cache-key-DD0fl_-s.js +243 -0
  34. package/dist/_chunks/rsc-cache-key-DD0fl_-s.js.map +1 -0
  35. package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js +40 -0
  36. package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js.map +1 -0
  37. package/dist/_chunks/rsc-payload-path-B_LBodc2.js +48 -0
  38. package/dist/_chunks/rsc-payload-path-B_LBodc2.js.map +1 -0
  39. package/dist/_chunks/{segment-classify-Byy425ng.js → segment-classify-C539Pa2O.js} +23 -2
  40. package/dist/_chunks/{segment-classify-Byy425ng.js.map → segment-classify-C539Pa2O.js.map} +1 -1
  41. package/dist/_chunks/{canonicalize-DQHyFClh.js → segment-keys-BawYuNFO.js} +61 -2
  42. package/dist/_chunks/segment-keys-BawYuNFO.js.map +1 -0
  43. package/dist/_chunks/slot-params-BCTmZkQB.js +76 -0
  44. package/dist/_chunks/slot-params-BCTmZkQB.js.map +1 -0
  45. package/dist/_chunks/{ssr-data-BOWsq18U.js → ssr-data-14MXm7Pj.js} +17 -2
  46. package/dist/_chunks/ssr-data-14MXm7Pj.js.map +1 -0
  47. package/dist/_chunks/use-segment-params-ClyUNq4d.js +128 -0
  48. package/dist/_chunks/use-segment-params-ClyUNq4d.js.map +1 -0
  49. package/dist/_chunks/{walkers-_6zKFlch.js → walkers-BhhwI9TD.js} +21 -87
  50. package/dist/_chunks/walkers-BhhwI9TD.js.map +1 -0
  51. package/dist/adapters/build-output-helper.d.ts +37 -0
  52. package/dist/adapters/build-output-helper.d.ts.map +1 -1
  53. package/dist/adapters/cloudflare-dev.js +1 -1
  54. package/dist/adapters/cloudflare-kv-cache.js +1 -1
  55. package/dist/adapters/cloudflare.d.ts.map +1 -1
  56. package/dist/adapters/cloudflare.js +1 -1
  57. package/dist/adapters/fs-identity.d.ts +64 -0
  58. package/dist/adapters/fs-identity.d.ts.map +1 -0
  59. package/dist/adapters/nitro.d.ts.map +1 -1
  60. package/dist/adapters/nitro.js +2 -3
  61. package/dist/adapters/nitro.js.map +1 -1
  62. package/dist/adapters/shared.d.ts +57 -1
  63. package/dist/adapters/shared.d.ts.map +1 -1
  64. package/dist/adapters/types.d.ts +13 -1
  65. package/dist/adapters/types.d.ts.map +1 -1
  66. package/dist/cache/fast-hash.d.ts +5 -0
  67. package/dist/cache/fast-hash.d.ts.map +1 -1
  68. package/dist/cache/index.js +1 -1
  69. package/dist/cli.js +2 -2
  70. package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
  71. package/dist/client/browser-entry/hydrate.d.ts +20 -9
  72. package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
  73. package/dist/client/browser-entry/post-hydration.d.ts +4 -1
  74. package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
  75. package/dist/client/browser-entry/router-init.d.ts +3 -0
  76. package/dist/client/browser-entry/router-init.d.ts.map +1 -1
  77. package/dist/client/browser-entry/rsc-stream.d.ts +8 -0
  78. package/dist/client/browser-entry/rsc-stream.d.ts.map +1 -1
  79. package/dist/client/error-boundary.d.ts +1 -0
  80. package/dist/client/error-boundary.d.ts.map +1 -1
  81. package/dist/client/error-boundary.js +1 -162
  82. package/dist/client/history.d.ts +13 -6
  83. package/dist/client/history.d.ts.map +1 -1
  84. package/dist/client/index.js +18 -11
  85. package/dist/client/index.js.map +1 -1
  86. package/dist/client/internal.d.ts +4 -3
  87. package/dist/client/internal.d.ts.map +1 -1
  88. package/dist/client/internal.js +865 -306
  89. package/dist/client/internal.js.map +1 -1
  90. package/dist/client/link.d.ts.map +1 -1
  91. package/dist/client/navigation-api.d.ts +2 -2
  92. package/dist/client/navigation-commit.d.ts +71 -0
  93. package/dist/client/navigation-commit.d.ts.map +1 -0
  94. package/dist/client/navigation-context.d.ts +37 -20
  95. package/dist/client/navigation-context.d.ts.map +1 -1
  96. package/dist/client/navigation-root.d.ts +63 -23
  97. package/dist/client/navigation-root.d.ts.map +1 -1
  98. package/dist/client/params-context.d.ts +81 -0
  99. package/dist/client/params-context.d.ts.map +1 -0
  100. package/dist/client/router-effects.d.ts +129 -0
  101. package/dist/client/router-effects.d.ts.map +1 -0
  102. package/dist/client/router-lifecycle.d.ts +55 -0
  103. package/dist/client/router-lifecycle.d.ts.map +1 -0
  104. package/dist/client/router-pipeline.d.ts +84 -0
  105. package/dist/client/router-pipeline.d.ts.map +1 -0
  106. package/dist/client/router-ref.d.ts +1 -1
  107. package/dist/client/router-ref.d.ts.map +1 -1
  108. package/dist/client/router-skew.d.ts +21 -0
  109. package/dist/client/router-skew.d.ts.map +1 -0
  110. package/dist/client/router-types.d.ts +207 -0
  111. package/dist/client/router-types.d.ts.map +1 -0
  112. package/dist/client/router.d.ts +1 -182
  113. package/dist/client/router.d.ts.map +1 -1
  114. package/dist/client/rsc-fetch.d.ts +38 -25
  115. package/dist/client/rsc-fetch.d.ts.map +1 -1
  116. package/dist/client/segment-cache.d.ts +79 -34
  117. package/dist/client/segment-cache.d.ts.map +1 -1
  118. package/dist/client/ssr-data.d.ts +6 -0
  119. package/dist/client/ssr-data.d.ts.map +1 -1
  120. package/dist/client/stale-client.d.ts +69 -0
  121. package/dist/client/stale-client.d.ts.map +1 -0
  122. package/dist/client/state.d.ts +10 -1
  123. package/dist/client/state.d.ts.map +1 -1
  124. package/dist/client/top-loader.d.ts +2 -1
  125. package/dist/client/top-loader.d.ts.map +1 -1
  126. package/dist/client/unload-guard.d.ts +5 -0
  127. package/dist/client/unload-guard.d.ts.map +1 -1
  128. package/dist/client/use-cookie.d.ts.map +1 -1
  129. package/dist/client/use-router.d.ts +1 -1
  130. package/dist/client/use-segment-params.d.ts +18 -8
  131. package/dist/client/use-segment-params.d.ts.map +1 -1
  132. package/dist/cookies/index.js +3 -3
  133. package/dist/cookies/index.js.map +1 -1
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +906 -321
  136. package/dist/index.js.map +1 -1
  137. package/dist/plugin-context.d.ts +31 -0
  138. package/dist/plugin-context.d.ts.map +1 -1
  139. package/dist/plugins/adapter-build.d.ts.map +1 -1
  140. package/dist/plugins/build-manifest.d.ts +1 -1
  141. package/dist/plugins/build-manifest.d.ts.map +1 -1
  142. package/dist/plugins/entries.d.ts.map +1 -1
  143. package/dist/plugins/fonts.d.ts.map +1 -1
  144. package/dist/plugins/request-dep/analysis.d.ts +106 -0
  145. package/dist/plugins/request-dep/analysis.d.ts.map +1 -0
  146. package/dist/plugins/request-dep/index.d.ts +75 -0
  147. package/dist/plugins/request-dep/index.d.ts.map +1 -0
  148. package/dist/plugins/request-dep/origin.d.ts +61 -0
  149. package/dist/plugins/request-dep/origin.d.ts.map +1 -0
  150. package/dist/plugins/routing.d.ts +9 -0
  151. package/dist/plugins/routing.d.ts.map +1 -1
  152. package/dist/plugins/static-build.d.ts +136 -8
  153. package/dist/plugins/static-build.d.ts.map +1 -1
  154. package/dist/routing/codegen-types.d.ts +18 -0
  155. package/dist/routing/codegen-types.d.ts.map +1 -1
  156. package/dist/routing/codegen.d.ts.map +1 -1
  157. package/dist/routing/collision-probe.d.ts +104 -0
  158. package/dist/routing/collision-probe.d.ts.map +1 -0
  159. package/dist/routing/collision-spaces.d.ts +26 -0
  160. package/dist/routing/collision-spaces.d.ts.map +1 -0
  161. package/dist/routing/index.js +3 -3
  162. package/dist/routing/interception.d.ts +235 -6
  163. package/dist/routing/interception.d.ts.map +1 -1
  164. package/dist/routing/manifest-codegen.d.ts.map +1 -1
  165. package/dist/routing/scanner.d.ts.map +1 -1
  166. package/dist/routing/segment-classify.d.ts +33 -0
  167. package/dist/routing/segment-classify.d.ts.map +1 -1
  168. package/dist/routing/segment-keys.d.ts +95 -0
  169. package/dist/routing/segment-keys.d.ts.map +1 -0
  170. package/dist/routing/slot-placement.d.ts +48 -0
  171. package/dist/routing/slot-placement.d.ts.map +1 -0
  172. package/dist/routing/walkers.d.ts +6 -0
  173. package/dist/routing/walkers.d.ts.map +1 -1
  174. package/dist/search-params/define.d.ts +0 -6
  175. package/dist/search-params/define.d.ts.map +1 -1
  176. package/dist/search-params/index.js +176 -2
  177. package/dist/search-params/index.js.map +1 -1
  178. package/dist/segment-params/define.d.ts +0 -6
  179. package/dist/segment-params/define.d.ts.map +1 -1
  180. package/dist/segment-params/index.js +69 -1
  181. package/dist/segment-params/index.js.map +1 -0
  182. package/dist/server/actions.d.ts +6 -3
  183. package/dist/server/actions.d.ts.map +1 -1
  184. package/dist/server/als-registry.d.ts +2 -1
  185. package/dist/server/als-registry.d.ts.map +1 -1
  186. package/dist/server/build-manifest.d.ts +13 -1
  187. package/dist/server/build-manifest.d.ts.map +1 -1
  188. package/dist/server/chain-url-parts.d.ts +82 -0
  189. package/dist/server/chain-url-parts.d.ts.map +1 -0
  190. package/dist/server/children-interception.d.ts +49 -0
  191. package/dist/server/children-interception.d.ts.map +1 -0
  192. package/dist/server/default-status-page.d.ts +45 -0
  193. package/dist/server/default-status-page.d.ts.map +1 -0
  194. package/dist/server/deny-renderer.d.ts +14 -3
  195. package/dist/server/deny-renderer.d.ts.map +1 -1
  196. package/dist/server/head-response.d.ts +35 -0
  197. package/dist/server/head-response.d.ts.map +1 -0
  198. package/dist/server/index.js +2 -2
  199. package/dist/server/internal.js +393 -212
  200. package/dist/server/internal.js.map +1 -1
  201. package/dist/server/param-coercion.d.ts +10 -1
  202. package/dist/server/param-coercion.d.ts.map +1 -1
  203. package/dist/server/pipeline-helpers.d.ts +0 -18
  204. package/dist/server/pipeline-helpers.d.ts.map +1 -1
  205. package/dist/server/pipeline-interception.d.ts +59 -9
  206. package/dist/server/pipeline-interception.d.ts.map +1 -1
  207. package/dist/server/pipeline-outcome.d.ts.map +1 -1
  208. package/dist/server/pipeline-phases.d.ts.map +1 -1
  209. package/dist/server/pipeline.d.ts +11 -0
  210. package/dist/server/pipeline.d.ts.map +1 -1
  211. package/dist/server/publish-params.d.ts +33 -0
  212. package/dist/server/publish-params.d.ts.map +1 -0
  213. package/dist/server/request-context.d.ts +48 -0
  214. package/dist/server/request-context.d.ts.map +1 -1
  215. package/dist/server/route-element-builder.d.ts +32 -2
  216. package/dist/server/route-element-builder.d.ts.map +1 -1
  217. package/dist/server/route-handler.d.ts.map +1 -1
  218. package/dist/server/route-matcher.d.ts +13 -0
  219. package/dist/server/route-matcher.d.ts.map +1 -1
  220. package/dist/server/rsc-cache-key-guard.d.ts +78 -0
  221. package/dist/server/rsc-cache-key-guard.d.ts.map +1 -0
  222. package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
  223. package/dist/server/rsc-entry/error-renderer.d.ts +14 -2
  224. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  225. package/dist/server/rsc-entry/helpers.d.ts +2 -10
  226. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  227. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  228. package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
  229. package/dist/server/rsc-entry/rsc-payload.d.ts +3 -2
  230. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  231. package/dist/server/rsc-entry/rsc-stream.d.ts +3 -2
  232. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  233. package/dist/server/rsc-entry/ssr-renderer.d.ts +2 -0
  234. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  235. package/dist/server/skippable-prefix.d.ts +23 -0
  236. package/dist/server/skippable-prefix.d.ts.map +1 -0
  237. package/dist/server/slot-interception.d.ts +80 -0
  238. package/dist/server/slot-interception.d.ts.map +1 -0
  239. package/dist/server/slot-resolver.d.ts +70 -8
  240. package/dist/server/slot-resolver.d.ts.map +1 -1
  241. package/dist/server/ssr-bridge-types.d.ts +6 -0
  242. package/dist/server/ssr-bridge-types.d.ts.map +1 -1
  243. package/dist/server/ssr-entry.d.ts.map +1 -1
  244. package/dist/server/ssr-wrappers.d.ts +24 -13
  245. package/dist/server/ssr-wrappers.d.ts.map +1 -1
  246. package/dist/server/state-tree-diff.d.ts +6 -27
  247. package/dist/server/state-tree-diff.d.ts.map +1 -1
  248. package/dist/server/static-generator.d.ts +6 -0
  249. package/dist/server/static-generator.d.ts.map +1 -1
  250. package/dist/server/static-not-found.d.ts +52 -0
  251. package/dist/server/static-not-found.d.ts.map +1 -0
  252. package/dist/shared/als-slots.d.ts +41 -0
  253. package/dist/shared/als-slots.d.ts.map +1 -0
  254. package/dist/shared/base-path.d.ts +56 -0
  255. package/dist/shared/base-path.d.ts.map +1 -0
  256. package/dist/shared/param-value.d.ts +80 -0
  257. package/dist/shared/param-value.d.ts.map +1 -0
  258. package/dist/shared/payload-root.d.ts +108 -0
  259. package/dist/shared/payload-root.d.ts.map +1 -0
  260. package/dist/shared/rsc-cache-key.d.ts +163 -0
  261. package/dist/shared/rsc-cache-key.d.ts.map +1 -0
  262. package/dist/shared/rsc-error-envelope.d.ts +25 -0
  263. package/dist/shared/rsc-error-envelope.d.ts.map +1 -0
  264. package/dist/shared/rsc-manifest.d.ts +28 -0
  265. package/dist/shared/rsc-manifest.d.ts.map +1 -0
  266. package/dist/shared/rsc-payload-path.d.ts +42 -0
  267. package/dist/shared/rsc-payload-path.d.ts.map +1 -0
  268. package/dist/shared/segment-info.d.ts +59 -0
  269. package/dist/shared/segment-info.d.ts.map +1 -0
  270. package/dist/shared/slot-params.d.ts +51 -0
  271. package/dist/shared/slot-params.d.ts.map +1 -0
  272. package/dist/shared/static-platform-files.d.ts +71 -0
  273. package/dist/shared/static-platform-files.d.ts.map +1 -0
  274. package/docs/api/31-api-client.mdx +4 -2
  275. package/docs/learn/02-pages-and-layouts.mdx +1 -1
  276. package/docs/learn/11-error-handling.mdx +2 -0
  277. package/docs/learn/12-client-navigation.mdx +2 -2
  278. package/docs/learn/14-deploying.mdx +23 -0
  279. package/docs/more/01-advanced-routing.mdx +172 -2
  280. package/docs/more/04-metadata-and-fonts.mdx +1 -1
  281. package/package.json +2 -2
  282. package/src/adapters/build-output-helper.ts +116 -3
  283. package/src/adapters/cloudflare.ts +86 -11
  284. package/src/adapters/fs-identity.ts +156 -0
  285. package/src/adapters/nitro.ts +6 -6
  286. package/src/adapters/shared.ts +196 -21
  287. package/src/adapters/types.ts +13 -1
  288. package/src/cache/fast-hash.ts +5 -0
  289. package/src/client/browser-entry/action-dispatch.ts +9 -4
  290. package/src/client/browser-entry/hydrate.ts +40 -29
  291. package/src/client/browser-entry/index.ts +15 -1
  292. package/src/client/browser-entry/post-hydration.ts +10 -9
  293. package/src/client/browser-entry/router-init.ts +104 -48
  294. package/src/client/browser-entry/rsc-stream.ts +11 -2
  295. package/src/client/error-boundary.tsx +20 -15
  296. package/src/client/history.ts +13 -6
  297. package/src/client/internal.ts +4 -3
  298. package/src/client/link.tsx +55 -8
  299. package/src/client/navigation-api.ts +3 -3
  300. package/src/client/navigation-commit.ts +174 -0
  301. package/src/client/navigation-context.ts +40 -67
  302. package/src/client/navigation-root.tsx +326 -101
  303. package/src/client/params-context.ts +192 -0
  304. package/src/client/router-effects.ts +231 -0
  305. package/src/client/router-lifecycle.ts +288 -0
  306. package/src/client/router-pipeline.ts +389 -0
  307. package/src/client/router-ref.ts +1 -1
  308. package/src/client/router-skew.ts +30 -0
  309. package/src/client/router-types.ts +230 -0
  310. package/src/client/router.ts +242 -707
  311. package/src/client/rsc-fetch.ts +169 -102
  312. package/src/client/segment-cache.ts +121 -44
  313. package/src/client/ssr-data.ts +6 -0
  314. package/src/client/stale-client.ts +191 -0
  315. package/src/client/state.ts +14 -1
  316. package/src/client/top-loader.tsx +2 -1
  317. package/src/client/unload-guard.ts +34 -2
  318. package/src/client/use-cookie.ts +9 -2
  319. package/src/client/use-router.ts +1 -1
  320. package/src/client/use-segment-params.ts +41 -18
  321. package/src/index.ts +5 -1
  322. package/src/plugin-context.ts +32 -0
  323. package/src/plugins/adapter-build.ts +4 -0
  324. package/src/plugins/build-manifest.ts +34 -13
  325. package/src/plugins/entries.ts +4 -0
  326. package/src/plugins/fonts.ts +11 -8
  327. package/src/plugins/request-dep/analysis.ts +447 -0
  328. package/src/plugins/request-dep/index.ts +376 -0
  329. package/src/plugins/request-dep/origin.ts +310 -0
  330. package/src/plugins/routing.ts +59 -2
  331. package/src/plugins/static-build.ts +315 -73
  332. package/src/routing/codegen-types.ts +18 -0
  333. package/src/routing/codegen.ts +50 -10
  334. package/src/routing/collision-probe.ts +438 -0
  335. package/src/routing/collision-spaces.ts +281 -0
  336. package/src/routing/interception.ts +896 -50
  337. package/src/routing/manifest-codegen.ts +5 -3
  338. package/src/routing/scanner.ts +114 -89
  339. package/src/routing/segment-classify.ts +57 -0
  340. package/src/routing/segment-keys.ts +146 -0
  341. package/src/routing/slot-placement.ts +97 -0
  342. package/src/routing/walkers.ts +8 -0
  343. package/src/search-params/define.ts +6 -29
  344. package/src/segment-params/define.ts +4 -27
  345. package/src/server/action-handler.ts +3 -1
  346. package/src/server/actions.ts +6 -3
  347. package/src/server/als-registry.ts +2 -1
  348. package/src/server/build-manifest.ts +14 -1
  349. package/src/server/chain-url-parts.ts +163 -0
  350. package/src/server/children-interception.ts +115 -0
  351. package/src/server/default-status-page.ts +127 -0
  352. package/src/server/deny-renderer.ts +81 -20
  353. package/src/server/head-response.ts +54 -0
  354. package/src/server/param-coercion.ts +48 -18
  355. package/src/server/pipeline-helpers.ts +0 -45
  356. package/src/server/pipeline-interception.ts +81 -32
  357. package/src/server/pipeline-outcome.ts +2 -4
  358. package/src/server/pipeline-phases.ts +68 -16
  359. package/src/server/pipeline.ts +57 -10
  360. package/src/server/prebuilt-runtime.ts +4 -4
  361. package/src/server/publish-params.ts +42 -0
  362. package/src/server/request-context.ts +97 -18
  363. package/src/server/route-element-builder.ts +89 -98
  364. package/src/server/route-handler.ts +8 -8
  365. package/src/server/route-matcher.ts +21 -9
  366. package/src/server/rsc-cache-key-guard.ts +170 -0
  367. package/src/server/rsc-entry/api-handler.ts +6 -8
  368. package/src/server/rsc-entry/error-renderer.ts +64 -25
  369. package/src/server/rsc-entry/helpers.ts +31 -16
  370. package/src/server/rsc-entry/index.ts +36 -28
  371. package/src/server/rsc-entry/render-route.ts +9 -4
  372. package/src/server/rsc-entry/revalidate-renderer.ts +1 -1
  373. package/src/server/rsc-entry/rsc-payload.ts +14 -25
  374. package/src/server/rsc-entry/rsc-stream.ts +4 -3
  375. package/src/server/rsc-entry/ssr-renderer.ts +13 -9
  376. package/src/server/skippable-prefix.ts +92 -0
  377. package/src/server/slot-interception.ts +168 -0
  378. package/src/server/slot-resolver.ts +253 -156
  379. package/src/server/ssr-bridge-types.ts +6 -0
  380. package/src/server/ssr-entry.ts +12 -4
  381. package/src/server/ssr-wrappers.tsx +33 -32
  382. package/src/server/state-tree-diff.ts +14 -55
  383. package/src/server/static-generator.ts +65 -27
  384. package/src/server/static-not-found.ts +134 -0
  385. package/src/shared/als-slots.ts +72 -0
  386. package/src/shared/base-path.ts +80 -0
  387. package/src/shared/param-value.ts +244 -0
  388. package/src/shared/payload-root.ts +151 -0
  389. package/src/shared/rsc-cache-key.ts +269 -0
  390. package/src/shared/rsc-error-envelope.ts +31 -0
  391. package/src/shared/rsc-manifest.ts +29 -0
  392. package/src/shared/rsc-payload-path.ts +45 -0
  393. package/src/shared/segment-info.ts +60 -0
  394. package/src/shared/slot-params.ts +111 -0
  395. package/src/shared/static-platform-files.ts +75 -0
  396. package/dist/_chunks/build-output-helper-DOGYFb_X.js +0 -196
  397. package/dist/_chunks/build-output-helper-DOGYFb_X.js.map +0 -1
  398. package/dist/_chunks/canonicalize-DQHyFClh.js.map +0 -1
  399. package/dist/_chunks/cli-schema-sync-BTWEKJXo.js +0 -668
  400. package/dist/_chunks/cli-schema-sync-BTWEKJXo.js.map +0 -1
  401. package/dist/_chunks/define-COtkxMRT.js +0 -191
  402. package/dist/_chunks/define-COtkxMRT.js.map +0 -1
  403. package/dist/_chunks/define-c4au4I9R.js +0 -82
  404. package/dist/_chunks/define-c4au4I9R.js.map +0 -1
  405. package/dist/_chunks/fast-hash-D6hIVt1Y.js.map +0 -1
  406. package/dist/_chunks/logger-AWfuX-KJ.js.map +0 -1
  407. package/dist/_chunks/navigation-root-bUDZLw74.js +0 -124
  408. package/dist/_chunks/navigation-root-bUDZLw74.js.map +0 -1
  409. package/dist/_chunks/plugin-context---kTF5v8.js.map +0 -1
  410. package/dist/_chunks/router-ref-CtmF-aPv.js.map +0 -1
  411. package/dist/_chunks/ssr-data-BOWsq18U.js.map +0 -1
  412. package/dist/_chunks/use-segment-params-D1CzlgBo.js +0 -151
  413. package/dist/_chunks/use-segment-params-D1CzlgBo.js.map +0 -1
  414. package/dist/_chunks/walkers-_6zKFlch.js.map +0 -1
  415. package/dist/client/error-boundary.js.map +0 -1
  416. package/dist/plugins/request-dep.d.ts +0 -32
  417. package/dist/plugins/request-dep.d.ts.map +0 -1
  418. package/src/plugins/request-dep.ts +0 -499
@@ -1,11 +1,15 @@
1
- import { a as classifyMetadataRoute, i as METADATA_ROUTE_CONVENTIONS, o as getMetadataRouteAutoLink, r as canonicalize, s as getMetadataRouteServePath } from "../_chunks/canonicalize-DQHyFClh.js";
2
- import { n as classifyUrlSegment } from "../_chunks/segment-classify-Byy425ng.js";
3
- import { $ as timingAls, A as setMatchedSegmentPath, C as applyRequestHeaderOverlay, D as getSegmentParams, E as getSearchParams, F as getSetCookieHeaders, G as replaceTraceId, H as getOtelTraceId, I as getWaitUntil, J as withSpan, K as runWithTraceId, L as isDebug, M as setSegmentParams, N as getCookie, O as markResponseFlushed, V as generateTraceId, W as getTraceId, Y as earlyHintsSenderAls, Z as requestContextAls, _ as RedirectSignal, a as logProxyError, c as logRequestReceived, d as logSwrRefetchFailed, f as logWaitUntilRejected, g as DenySignal, h as swallow, i as logMiddlewareShortCircuit, j as setMutableCookieContext, k as runWithRequestContext, l as logRouteError, m as setLogger, n as logCacheMiss, o as logRenderError, p as logWaitUntilUnsupported, q as setSpanAttribute, r as logMiddlewareError, s as logRequestCompleted, t as getLogger, u as logSlowRequest, v as RenderError, w as getHeader } from "../_chunks/logger-AWfuX-KJ.js";
4
- import { i as coerce, t as executeAction } from "../_chunks/actions-O_LsyCE4.js";
5
- import { a as getManifestEntry, b as wasInvalidatedSince, c as lookupPrebuiltPayload, d as checkVersionSkew, i as storeOverlayEntry, l as setPrebuiltPayloadSource, n as lookupOverlay, o as hasPrebuiltPayloadSource, r as overlayKey, s as isParamIndependent, u as applyReloadHeaders, v as currentInvalidationEpoch, x as createSingleflight, y as lastInvalidationEpochFor } from "../_chunks/cache-api-B-lhk9p4.js";
6
- import "../client/error-boundary.js";
1
+ import { a as canonicalize, c as getMetadataRouteAutoLink, l as getMetadataRouteServePath, o as METADATA_ROUTE_CONVENTIONS, s as classifyMetadataRoute, t as computeSegmentTreePaths } from "../_chunks/segment-keys-BawYuNFO.js";
2
+ import { n as classifyUrlSegment, r as effectiveUrlSegment } from "../_chunks/segment-classify-C539Pa2O.js";
3
+ import { $ as timingAls, A as setMatchedSegmentPath, C as applyRequestHeaderOverlay, D as getSegmentParams, E as getSearchParams, F as getSetCookieHeaders, G as replaceTraceId, H as getOtelTraceId, I as getWaitUntil, J as withSpan, K as runWithTraceId, L as isDebug, M as setSegmentParams, N as getCookie, O as markResponseFlushed, V as generateTraceId, W as getTraceId, Y as earlyHintsSenderAls, Z as requestContextAls, _ as RedirectSignal, a as logProxyError, c as logRequestReceived, d as logSwrRefetchFailed, f as logWaitUntilRejected, g as DenySignal, h as swallow, i as logMiddlewareShortCircuit, j as setMutableCookieContext, k as runWithRequestContext, l as logRouteError, m as setLogger, n as logCacheMiss, o as logRenderError, p as logWaitUntilUnsupported, q as setSpanAttribute, r as logMiddlewareError, s as logRequestCompleted, t as getLogger, u as logSlowRequest, v as RenderError, w as getHeader } from "../_chunks/logger-N7e5auP0.js";
4
+ import { i as isRscCacheKeyShape, n as RSC_KEY_PARAM, s as rscCacheKey, t as RSC_KEY_HEADERS } from "../_chunks/rsc-cache-key-DD0fl_-s.js";
5
+ import { i as collectRouteModulepreloads, n as collectRouteCss, r as collectRouteFonts } from "../_chunks/build-manifest-DWppEdLB.js";
6
+ import { i as coerce, t as executeAction } from "../_chunks/actions-C-Rw9vPc.js";
7
+ import { a as getManifestEntry, b as wasInvalidatedSince, c as lookupPrebuiltPayload, d as checkVersionSkew, i as storeOverlayEntry, l as setPrebuiltPayloadSource, n as lookupOverlay, o as hasPrebuiltPayloadSource, r as overlayKey, s as isParamIndependent, u as applyReloadHeaders, v as currentInvalidationEpoch, x as createSingleflight, y as lastInvalidationEpochFor } from "../_chunks/cache-api-Cd0VZ_Pd.js";
8
+ import "../_chunks/error-boundary-DpYRI_I1.js";
7
9
  import { n as toBracketKey } from "../_chunks/resolve-schema-Dz3fcFUo.js";
10
+ import { t as normalizeParamValue } from "../_chunks/param-value-C8TNYchQ.js";
8
11
  import "../_chunks/segment-context-ZDnXDkbz.js";
12
+ import { n as rscErrorEnvelope } from "../_chunks/rsc-error-envelope-tT5PJs4q.js";
9
13
  import { createHash } from "node:crypto";
10
14
  import { readFile } from "node:fs/promises";
11
15
  import React, { createElement, useContext } from "react";
@@ -209,38 +213,6 @@ function hasOnRequestError() {
209
213
  //#endregion
210
214
  //#region src/server/pipeline-helpers.ts
211
215
  /**
212
- * Only __proto__ needs stripping — it has a language-level setter that
213
- * changes the prototype chain of spread copies. constructor and prototype
214
- * are harmless own properties on null-prototype objects.
215
- */
216
- var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__"]);
217
- /**
218
- * Deep-walk a value returned by a segment param codec, producing a
219
- * sanitized copy where every plain object is null-prototype and
220
- * dangerous keys (__proto__, constructor, prototype) are stripped at
221
- * every depth.
222
- *
223
- * Non-plain objects (Date, Map, class instances, etc.) are returned
224
- * as-is — they cannot be poisoned by `{...x}` spread and may carry
225
- * author-intended prototype methods.
226
- *
227
- * Arrays are walked element-wise.
228
- *
229
- * Performance: URL params are bounded by URL length (~8 KB). Realistic
230
- * trees are <1 KB. The recursive walk is sub-microsecond.
231
- *
232
- * See TIM-655, TIM-855, TIM-873, design/13-security.md
233
- */
234
- function sanitizeParamValue(value) {
235
- if (value === null || typeof value !== "object") return value;
236
- if (Array.isArray(value)) return value.map(sanitizeParamValue);
237
- const proto = Object.getPrototypeOf(value);
238
- if (proto !== Object.prototype && proto !== null) return value;
239
- const out = Object.create(null);
240
- for (const key of Object.keys(value)) if (!DANGEROUS_KEYS.has(key)) out[key] = sanitizeParamValue(value[key]);
241
- return out;
242
- }
243
- /**
244
216
  * Build a proxy resolver closure from the declared source. Called exactly
245
217
  * once at `createPipeline` setup time, so the hot path sees only the branch
246
218
  * that corresponds to this pipeline's configured variant.
@@ -1766,20 +1738,23 @@ async function coerceSegmentParams(match) {
1766
1738
  for (const key of Object.keys(match.segmentParams)) if (key !== "__proto__") mergeTarget[key] = match.segmentParams[key];
1767
1739
  match.segmentParams = mergeTarget;
1768
1740
  if (globalCodecs) {
1769
- for (const segment of match.segments) {
1741
+ for (const node of match.segments) {
1742
+ const segment = effectiveUrlSegment(node);
1770
1743
  if (!segment.paramName) continue;
1771
1744
  const bracketKey = toBracketKey(segment.segmentType, segment.paramName);
1772
1745
  const codec = globalCodecs[bracketKey];
1773
1746
  if (!codec) continue;
1774
1747
  const key = segment.paramName;
1775
1748
  if (!(key in mergeTarget)) continue;
1749
+ let parsed;
1776
1750
  try {
1777
- mergeTarget[key] = sanitizeParamValue(codec.parse(mergeTarget[key]));
1751
+ parsed = codec.parse(mergeTarget[key]);
1778
1752
  } catch (err) {
1779
1753
  const message = err instanceof Error ? err.message : String(err);
1780
1754
  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}'.`);
1781
1755
  throw new ParamCoercionError(message);
1782
1756
  }
1757
+ mergeTarget[key] = normalizeParamValue(parsed, key);
1783
1758
  }
1784
1759
  return;
1785
1760
  }
@@ -1795,9 +1770,9 @@ async function coerceSegmentParams(match) {
1795
1770
  }
1796
1771
  const segmentParamsDef = mod.segmentParams;
1797
1772
  if (!segmentParamsDef || typeof segmentParamsDef.parse !== "function") continue;
1773
+ let coerced;
1798
1774
  try {
1799
- const coerced = segmentParamsDef.parse(match.segmentParams);
1800
- for (const key of Object.keys(coerced)) if (key !== "__proto__") mergeTarget[key] = sanitizeParamValue(coerced[key]);
1775
+ coerced = segmentParamsDef.parse(match.segmentParams);
1801
1776
  } catch (err) {
1802
1777
  const message = err instanceof Error ? err.message : String(err);
1803
1778
  if (isDebug()) {
@@ -1806,9 +1781,129 @@ async function coerceSegmentParams(match) {
1806
1781
  }
1807
1782
  throw new ParamCoercionError(message);
1808
1783
  }
1784
+ for (const key of Object.keys(coerced)) {
1785
+ if (key === "__proto__") continue;
1786
+ mergeTarget[key] = normalizeParamValue(coerced[key], key);
1787
+ }
1809
1788
  }
1810
1789
  }
1811
1790
  //#endregion
1791
+ //#region src/server/pipeline-interception.ts
1792
+ /**
1793
+ * Interception route matching for the request pipeline.
1794
+ *
1795
+ * Matches target URLs against interception rewrites to support the
1796
+ * modal route pattern (soft navigation intercepts).
1797
+ *
1798
+ * Extracted from pipeline.ts to keep files under 500 lines.
1799
+ *
1800
+ * See design/07-routing.md §"Intercepting Routes"
1801
+ */
1802
+ /**
1803
+ * Check if an intercepting route applies for this soft navigation.
1804
+ *
1805
+ * Two conditions must hold:
1806
+ *
1807
+ * 1. The **target** pathname matches the rewrite's intercepted pattern.
1808
+ * 2. The **source** route — the page the user is navigating FROM, per the
1809
+ * validated X-Timber-URL header — renders the segment that owns the slot
1810
+ * holding the intercepting page (`interceptingScope`). Only then does that
1811
+ * slot exist in the rendered tree, so only then can the intercepting page
1812
+ * render.
1813
+ *
1814
+ * Condition 2 replaced a URL-prefix check (TIM-1279). A prefix cannot
1815
+ * distinguish a route-group-scoped interception from a genuinely root-level
1816
+ * one — route groups are invisible in `urlPath`, so both collapse to "/" and
1817
+ * the guard degenerates to always-true. Tree paths carry group identity.
1818
+ * The scope check also subsumes the old segment-boundary rule: a source route
1819
+ * under /feed-private has no "/feed" in its chain.
1820
+ *
1821
+ * A third condition lives with the caller, which owns route matching, and it
1822
+ * applies to the `'slot'` shape only: the route produced by re-matching
1823
+ * `interceptingPrefix` must pass through the scope too. Conditions 1–2 are
1824
+ * about the route the user is leaving; the slot has to exist in the tree that
1825
+ * actually renders. The `'children'` shape resolves its owner from the scope
1826
+ * directly rather than from a pathname, so it satisfies that by construction.
1827
+ *
1828
+ * `resolveSourceScopes` is a thunk so its inputs are only resolved when some
1829
+ * rewrite's pattern actually matches the target — the common case is no match
1830
+ * at all. It returns the two client-supplied answers to "what is on screen?"
1831
+ * as **separate sets, most authoritative first**: the tree paths the client
1832
+ * says it has mounted, then those of the route the departing URL matches. It
1833
+ * returns null only when neither yields anything, which is not a soft
1834
+ * navigation this can reason about.
1835
+ *
1836
+ * They are two sets rather than one union because ranking needs a chain
1837
+ * (TIM-1318). Each channel describes a single rendered chain, so within one the
1838
+ * scopes are strictly nested and the deepest is the innermost owner. Unioned
1839
+ * they are not: once a modal is open the address bar holds the *intercepted*
1840
+ * URL, so the second channel describes the canonical branch — possibly an
1841
+ * exclusive route group the client never mounted — and a deeper scope from
1842
+ * there would outrank the owner actually on screen. Trying the mounted channel
1843
+ * to exhaustion first keeps the fallback the union was there to provide while
1844
+ * ranking only within one chain.
1845
+ *
1846
+ * Returns the winning build-time rewrite, or null. Only build-time values ever
1847
+ * reach the renderer: the client-supplied header selects a rewrite but never
1848
+ * becomes a rendered route or pathname.
1849
+ *
1850
+ * The **first** matching rewrite wins, and the caller does not fall back to the
1851
+ * next one when the winner fails to resolve. That is deliberate and pre-dates
1852
+ * the two shapes: a rewrite that matched the target and the scope described the
1853
+ * author's intent, and quietly rendering some other author's interception
1854
+ * instead would be harder to explain than rendering the canonical route.
1855
+ *
1856
+ * "First" is meaningful because the list arrives **deepest scope first** —
1857
+ * `collectInterceptionRewrites` sorts it at build time, so the deepest owner a
1858
+ * single chain passes through wins and nothing here has to score (TIM-1318).
1859
+ * Tree-walk order used to decide it, which meant renaming a route group flipped
1860
+ * which modal opened. The precondition is a property of the manifest this reads
1861
+ * rather than something it can re-derive, so it is asserted at its source:
1862
+ * `tests/interception.test.ts` §"rewrite order is decided by scope depth".
1863
+ */
1864
+ function findInterceptionMatch(targetPathname, resolveSourceScopes, rewrites) {
1865
+ const candidates = rewrites.filter((rewrite) => pathnameMatchesPattern(targetPathname, rewrite.interceptedPattern));
1866
+ if (candidates.length === 0) return null;
1867
+ const scopes = resolveSourceScopes();
1868
+ if (!scopes) return null;
1869
+ for (const scope of scopes) for (const rewrite of candidates) if (scope.has(rewrite.interceptingScope)) return rewrite;
1870
+ return null;
1871
+ }
1872
+ /**
1873
+ * Check if a pathname matches a URL pattern with dynamic segments.
1874
+ *
1875
+ * Supports [param] (single segment) and [...param] (one or more segments).
1876
+ * Static segments must match exactly.
1877
+ */
1878
+ function pathnameMatchesPattern(pathname, pattern) {
1879
+ const pathParts = pathname === "/" ? [] : pathname.slice(1).split("/");
1880
+ const patternParts = pattern === "/" ? [] : pattern.slice(1).split("/");
1881
+ let pi = 0;
1882
+ for (let i = 0; i < patternParts.length; i++) {
1883
+ const seg = classifyUrlSegment(patternParts[i]);
1884
+ switch (seg.kind) {
1885
+ case "catch-all": return pi < pathParts.length;
1886
+ case "optional-catch-all": return true;
1887
+ case "dynamic": {
1888
+ if (pi >= pathParts.length) return false;
1889
+ const prefix = seg.prefix ?? "";
1890
+ const suffix = seg.suffix ?? "";
1891
+ const pathPart = pathParts[pi];
1892
+ if (prefix || suffix) {
1893
+ if (pathPart.length <= prefix.length + suffix.length || prefix && !pathPart.startsWith(prefix) || suffix && !pathPart.endsWith(suffix)) return false;
1894
+ }
1895
+ pi++;
1896
+ continue;
1897
+ }
1898
+ case "static":
1899
+ if (pi >= pathParts.length || pathParts[pi] !== seg.value) return false;
1900
+ pi++;
1901
+ continue;
1902
+ }
1903
+ }
1904
+ return pi === pathParts.length;
1905
+ }
1906
+ //#endregion
1812
1907
  //#region src/client/segment-update-context.ts
1813
1908
  /**
1814
1909
  * SegmentUpdateContext — React context for partial navigation updates.
@@ -1865,6 +1960,51 @@ getOrCreateContext();
1865
1960
  * See design/13-security.md §"State tree manipulation".
1866
1961
  */
1867
1962
  //#endregion
1963
+ //#region src/server/state-tree-diff.ts
1964
+ /**
1965
+ * State Tree Diffing — Server-side parsing and diffing of X-Timber-State-Tree.
1966
+ *
1967
+ * The client sends X-Timber-State-Tree on navigation requests, listing
1968
+ * the sync segments it has cached. The server diffs this against the
1969
+ * target route's segments to skip re-rendering unchanged sync layouts.
1970
+ *
1971
+ * This is a performance optimization only — NOT a security boundary.
1972
+ * All access.ts files run regardless of the state tree content.
1973
+ * A fabricated state tree can only cause extra rendering work or stale
1974
+ * layouts — never auth bypass.
1975
+ *
1976
+ * See design/19-client-navigation.md §"X-Timber-State-Tree Header"
1977
+ * See design/13-security.md §"State tree manipulation"
1978
+ */
1979
+ /**
1980
+ * Parse the X-Timber-State-Tree header from a request.
1981
+ *
1982
+ * Returns a Set of segment paths the client has cached (with an
1983
+ * additional `slots` property for cached slot paths), or null if
1984
+ * the header is missing, malformed, or empty. Parsing happens before
1985
+ * renderToReadableStream — not inside the React render pass.
1986
+ *
1987
+ * @returns ClientStateTree with segments and slots, or null if no valid state tree
1988
+ */
1989
+ function parseClientStateTree(req) {
1990
+ const header = req.headers.get("X-Timber-State-Tree");
1991
+ if (!header) return null;
1992
+ try {
1993
+ const parsed = JSON.parse(header);
1994
+ const hasSegments = Array.isArray(parsed.segments) && parsed.segments.length > 0;
1995
+ const hasSlots = Array.isArray(parsed.slots) && parsed.slots.length > 0;
1996
+ const hasTreePaths = Array.isArray(parsed.treePaths) && parsed.treePaths.length > 0;
1997
+ if (!hasSegments && !hasSlots && !hasTreePaths) return null;
1998
+ const result = new Set(hasSegments ? parsed.segments : []);
1999
+ result.slots = hasSlots ? new Set(parsed.slots) : null;
2000
+ result.treePaths = hasTreePaths ? new Set(parsed.treePaths) : null;
2001
+ return result;
2002
+ } catch (err) {
2003
+ swallow(err, "malformed X-Timber-State-Tree header");
2004
+ return null;
2005
+ }
2006
+ }
2007
+ //#endregion
1868
2008
  //#region src/server/route-element-builder.ts
1869
2009
  /**
1870
2010
  * Thrown when a defineSegmentParams codec's parse() fails.
@@ -1942,78 +2082,6 @@ function escapeXml(str) {
1942
2082
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
1943
2083
  }
1944
2084
  //#endregion
1945
- //#region src/server/pipeline-interception.ts
1946
- /**
1947
- * Interception route matching for the request pipeline.
1948
- *
1949
- * Matches target URLs against interception rewrites to support the
1950
- * modal route pattern (soft navigation intercepts).
1951
- *
1952
- * Extracted from pipeline.ts to keep files under 500 lines.
1953
- *
1954
- * See design/07-routing.md §"Intercepting Routes"
1955
- */
1956
- /**
1957
- * Check if a pathname starts with a prefix on a segment boundary.
1958
- *
1959
- * Prevents /feed from matching /feed-private — the prefix must be
1960
- * followed by '/' or be an exact match.
1961
- */
1962
- function hasSegmentPrefix(pathname, prefix) {
1963
- if (prefix === "/") return true;
1964
- if (!pathname.startsWith(prefix)) return false;
1965
- return pathname.length === prefix.length || pathname[prefix.length] === "/";
1966
- }
1967
- /**
1968
- * Check if an intercepting route applies for this soft navigation.
1969
- *
1970
- * Matches the target pathname against interception rewrites, constrained
1971
- * by the source URL (X-Timber-URL header — where the user navigates FROM).
1972
- *
1973
- * Returns the source pathname to re-match if interception applies, or null.
1974
- */
1975
- function findInterceptionMatch(targetPathname, sourceUrl, rewrites) {
1976
- for (const rewrite of rewrites) {
1977
- if (!hasSegmentPrefix(sourceUrl, rewrite.interceptingPrefix)) continue;
1978
- if (pathnameMatchesPattern(targetPathname, rewrite.interceptedPattern)) return { sourcePathname: rewrite.interceptingPrefix };
1979
- }
1980
- return null;
1981
- }
1982
- /**
1983
- * Check if a pathname matches a URL pattern with dynamic segments.
1984
- *
1985
- * Supports [param] (single segment) and [...param] (one or more segments).
1986
- * Static segments must match exactly.
1987
- */
1988
- function pathnameMatchesPattern(pathname, pattern) {
1989
- const pathParts = pathname === "/" ? [] : pathname.slice(1).split("/");
1990
- const patternParts = pattern === "/" ? [] : pattern.slice(1).split("/");
1991
- let pi = 0;
1992
- for (let i = 0; i < patternParts.length; i++) {
1993
- const seg = classifyUrlSegment(patternParts[i]);
1994
- switch (seg.kind) {
1995
- case "catch-all": return pi < pathParts.length;
1996
- case "optional-catch-all": return true;
1997
- case "dynamic": {
1998
- if (pi >= pathParts.length) return false;
1999
- const prefix = seg.prefix ?? "";
2000
- const suffix = seg.suffix ?? "";
2001
- const pathPart = pathParts[pi];
2002
- if (prefix || suffix) {
2003
- if (pathPart.length <= prefix.length + suffix.length || prefix && !pathPart.startsWith(prefix) || suffix && !pathPart.endsWith(suffix)) return false;
2004
- }
2005
- pi++;
2006
- continue;
2007
- }
2008
- case "static":
2009
- if (pi >= pathParts.length || pathParts[pi] !== seg.value) return false;
2010
- pi++;
2011
- continue;
2012
- }
2013
- }
2014
- return pi === pathParts.length;
2015
- }
2016
- //#endregion
2017
2085
  //#region src/server/pipeline-outcome.ts
2018
2086
  /**
2019
2087
  * Pipeline outcome translator — converts a `PhaseOutcome` (the value
@@ -2028,16 +2096,7 @@ function pathnameMatchesPattern(pathname, pattern) {
2028
2096
  */
2029
2097
  function rscErrorResponse(isRsc, status, headers) {
2030
2098
  if (!isRsc) return new Response(null, { status });
2031
- const h = headers ?? new Headers();
2032
- h.set("X-Timber-Error", "1");
2033
- h.set("content-type", "application/json; charset=utf-8");
2034
- return new Response(JSON.stringify({
2035
- error: true,
2036
- status
2037
- }), {
2038
- status,
2039
- headers: h
2040
- });
2099
+ return rscErrorEnvelope(status, headers);
2041
2100
  }
2042
2101
  /**
2043
2102
  * Terminal outcome handler — converts a `PhaseOutcome` into a final
@@ -2334,15 +2393,6 @@ async function handleRequest(config, req, method, path, pathIsCanonical) {
2334
2393
  }
2335
2394
  const handlerResult = await mod.default();
2336
2395
  if (handlerResult instanceof Response) {
2337
- if (method === "HEAD") {
2338
- const headHeaders = new Headers(handlerResult.headers);
2339
- if (handlerResult.ok && !headHeaders.has("Cache-Control")) headHeaders.set("Cache-Control", "public, max-age=14400, must-revalidate");
2340
- return new Response(null, {
2341
- status: handlerResult.status,
2342
- statusText: handlerResult.statusText,
2343
- headers: headHeaders
2344
- });
2345
- }
2346
2396
  const res = cloneWithMutableHeaders(handlerResult);
2347
2397
  if (res.ok && !res.headers.has("Cache-Control")) res.headers.set("Cache-Control", "public, max-age=14400, must-revalidate");
2348
2398
  return res;
@@ -2404,10 +2454,19 @@ async function handleRequest(config, req, method, path, pathIsCanonical) {
2404
2454
  const rawSourceUrl = req.headers.get("X-Timber-URL");
2405
2455
  const validatedSourceUrl = rawSourceUrl ? validateInterceptionHeader(rawSourceUrl, stripTrailingSlash) : null;
2406
2456
  if (validatedSourceUrl) {
2407
- const intercepted = findInterceptionMatch(canonicalPathname, validatedSourceUrl, config.interceptionRewrites);
2408
- if (intercepted) {
2409
- const sourceMatch = config.matchRoute(intercepted.sourcePathname);
2410
- if (sourceMatch) {
2457
+ const resolveSourceScopes = () => {
2458
+ const mounted = new Set(parseClientStateTree(req)?.treePaths ?? []);
2459
+ const sourceRoute = config.matchRoute(validatedSourceUrl);
2460
+ const scopes = [mounted, new Set(sourceRoute ? computeSegmentTreePaths(sourceRoute.segments) : [])].filter((scope) => scope.size > 0);
2461
+ return scopes.length > 0 ? scopes : null;
2462
+ };
2463
+ const rewrite = findInterceptionMatch(canonicalPathname, resolveSourceScopes, config.interceptionRewrites);
2464
+ if (rewrite?.mode === "children") {
2465
+ const interceptedMatch = config.matchInterceptedRoute?.(canonicalPathname, rewrite.interceptingScope);
2466
+ if (interceptedMatch) match = interceptedMatch;
2467
+ } else if (rewrite) {
2468
+ const sourceMatch = config.matchRoute(rewrite.interceptingPrefix);
2469
+ if (sourceMatch !== null && computeSegmentTreePaths(sourceMatch.segments).includes(rewrite.interceptingScope)) {
2411
2470
  match = sourceMatch;
2412
2471
  interception = { targetPathname: canonicalPathname };
2413
2472
  }
@@ -2466,6 +2525,194 @@ async function handleRequest(config, req, method, path, pathIsCanonical) {
2466
2525
  });
2467
2526
  }
2468
2527
  //#endregion
2528
+ //#region src/server/rsc-cache-key-guard.ts
2529
+ /**
2530
+ * Origin-side binding of the `_rsc` cache key to the request that produced
2531
+ * the response (TIM-1268).
2532
+ *
2533
+ * The key is deterministic, and deliberately so — that is what makes RSC
2534
+ * payloads cacheable. But deterministic also means *predictable*: anyone can
2535
+ * compute the key another client will request. Without this guard, a caller
2536
+ * could request `/about?_rsc=<victim's key>` while sending its own
2537
+ * `X-Timber-State-Tree`, and a shared cache that keys by URL — the exact
2538
+ * environment this feature targets — would store that variant under the
2539
+ * victim's key. Every subsequent legitimate navigation would then be served
2540
+ * a payload rendered for someone else's segment tree or deployment.
2541
+ *
2542
+ * The guard is a *storability* rule, not a rejection: if the claimed key
2543
+ * does not match the key derived from the received headers, the response
2544
+ * loses its shared-cache directives. Mismatched requests are still served
2545
+ * normally, they just cannot be stored for anyone else.
2546
+ *
2547
+ * Why not Next.js's 307 self-correction: a redirect is cacheable in its own
2548
+ * right, costs a round trip on every legitimate miss, and would make the
2549
+ * server's behaviour depend on a parameter it otherwise never reads. This
2550
+ * rule closes the same hole by making the poisoned entry unstorable at the
2551
+ * source, in the response the developer already has to opt into sharing.
2552
+ *
2553
+ * See design/19-client-navigation.md §"The `_rsc` cache key".
2554
+ */
2555
+ /**
2556
+ * Legacy shared-cache control fields that predate RFC 9213 and so do not
2557
+ * follow the `<target>-Cache-Control` naming pattern — the one part of this
2558
+ * rule that cannot be matched by shape. `X-Accel-Expires` is nginx
2559
+ * `proxy_cache`, which gives it precedence over `Cache-Control`.
2560
+ *
2561
+ * A closed historical set, unlike the vendor `*-Cache-Control` names, which
2562
+ * keep being minted and are handled by pattern below.
2563
+ */
2564
+ var LEGACY_CDN_CACHE_HEADERS = [
2565
+ "Surrogate-Control",
2566
+ "Edge-Control",
2567
+ "X-Accel-Expires"
2568
+ ];
2569
+ /**
2570
+ * Whether a response header overrides `Cache-Control` at a shared cache.
2571
+ *
2572
+ * Matched by *shape*, not by vendor name. RFC 9213 defines targeted fields
2573
+ * as `<target>-Cache-Control`, and every CDN spells its own: `CDN-`,
2574
+ * `Cloudflare-CDN-`, `Netlify-CDN-`, `Vercel-CDN-`, and whichever ships
2575
+ * next. A vendor list here is a denylist with an unbounded tail — it was
2576
+ * extended twice during review, each time by one name, which is the signal
2577
+ * that enumeration is the wrong shape. The suffix rule is total.
2578
+ *
2579
+ * `Cache-Control` itself does not match (no `-` prefix) and is replaced
2580
+ * separately.
2581
+ */
2582
+ function isSharedCacheOverride(name) {
2583
+ const lowered = name.toLowerCase();
2584
+ return lowered.endsWith("-cache-control") || LEGACY_CDN_CACHE_HEADERS.some((legacy) => legacy.toLowerCase() === lowered);
2585
+ }
2586
+ /**
2587
+ * Whether the request's `_rsc` parameter matches the key derived from its
2588
+ * own headers.
2589
+ *
2590
+ * Requests without the parameter are not claiming a key at all, so they are
2591
+ * left alone — the bare document URL is the developer's declared territory
2592
+ * (see design/32-response-caching.md, TIM-1264). Neither is a parameter
2593
+ * whose value could not be one of our digests: `_rsc` is not reserved, and
2594
+ * an application that already uses it for its own search state
2595
+ * (`/search?_rsc=foo`) must not have its cache policy silently revoked by a
2596
+ * guard that mistook that for a forgery.
2597
+ */
2598
+ async function hasValidRscCacheKey(req) {
2599
+ if (!req.url.includes("?")) return true;
2600
+ let values;
2601
+ try {
2602
+ values = new URL(req.url).searchParams.getAll(RSC_KEY_PARAM);
2603
+ } catch {
2604
+ return false;
2605
+ }
2606
+ const claims = values.filter(isRscCacheKeyShape);
2607
+ if (claims.length === 0) return true;
2608
+ const expected = await rscCacheKey((name) => req.headers.get(name));
2609
+ if (expected === null) return false;
2610
+ return claims.every((claim) => claim === expected);
2611
+ }
2612
+ /**
2613
+ * Media type of a Flight payload. Also spelled in `rsc-entry/helpers.ts`,
2614
+ * `deny-renderer.ts`, `action-handler.ts`, and `client/rsc-fetch.ts` — that
2615
+ * duplication predates this module and is tracked separately.
2616
+ */
2617
+ var RSC_MEDIA_TYPE = "text/x-component";
2618
+ /**
2619
+ * Ensure a Flight payload can never be interpreted as a document.
2620
+ *
2621
+ * Applied here, at the pipeline's single exit, rather than where each
2622
+ * payload response is built. Six places build one — the success path, three
2623
+ * in `deny-renderer.ts`, the action paths — and hardening them one at a time
2624
+ * is how you end up hardening five of six. The response is Flight if it says
2625
+ * it is; that is the only test that covers every producer, including ones
2626
+ * added later.
2627
+ *
2628
+ * Matters because the payload URL is observable and, on a shared-cache hit,
2629
+ * the origin is never consulted — so a top-level navigation can be handed
2630
+ * these bytes with no request-time check available. See design/13-security.md
2631
+ * #36c.
2632
+ */
2633
+ function hardenRscPayloadResponse(response) {
2634
+ const contentType = response.headers.get("content-type");
2635
+ if (!contentType) return;
2636
+ if (contentType.split(";")[0].trim().toLowerCase() !== RSC_MEDIA_TYPE) return;
2637
+ response.headers.set("X-Content-Type-Options", "nosniff");
2638
+ }
2639
+ /**
2640
+ * Make a response unstorable by any shared cache when its request claimed an
2641
+ * `_rsc` key that does not match its own headers.
2642
+ *
2643
+ * The end state is unconditional and therefore statable: **a mismatched
2644
+ * response is never stored by any cache.** Every targeted cache-control
2645
+ * field is removed and `Cache-Control` is replaced outright, rather than
2646
+ * downgrading only when a shared directive is spotted. Conditional variants
2647
+ * of this were tried and were wrong twice over — they missed the CDN-
2648
+ * specific headers, and leaving `Cache-Control` alone when it looked
2649
+ * harmless lets a CDN apply heuristic caching to a response that has none.
2650
+ *
2651
+ * The cost is that a mismatched request loses developer-chosen *browser*
2652
+ * caching too. That is a request no timber client ever makes.
2653
+ *
2654
+ * Purge tags (`Surrogate-Key`, `Cache-Tag`) are deliberately left alone:
2655
+ * they address stored entries, and this response is not stored.
2656
+ *
2657
+ * Mutates `response.headers` in place; the pipeline guarantees mutable
2658
+ * headers at this point (see TIM-866).
2659
+ */
2660
+ async function enforceRscCacheKey(req, response) {
2661
+ if (await hasValidRscCacheKey(req)) return;
2662
+ const overrides = [];
2663
+ for (const name of response.headers.keys()) if (isSharedCacheOverride(name)) overrides.push(name);
2664
+ for (const name of overrides) response.headers.delete(name);
2665
+ response.headers.set("Cache-Control", "private, no-store");
2666
+ }
2667
+ //#endregion
2668
+ //#region src/server/head-response.ts
2669
+ /**
2670
+ * HEAD body stripping — RFC 9110 §9.3.2.
2671
+ *
2672
+ * "The HEAD method is identical to GET except that the server MUST NOT send
2673
+ * content in the response." The headers, however, must be the ones the
2674
+ * corresponding GET would have produced — which is why timber renders the
2675
+ * route in full and discards the body here rather than short-circuiting the
2676
+ * render. Content-Type, Content-Length, Set-Cookie, cache directives and
2677
+ * everything else ride through untouched.
2678
+ *
2679
+ * Node's `ServerResponse` happens to discard writes for HEAD, so the wire is
2680
+ * usually correct even without this. That is a property of one adapter, not of
2681
+ * the `Response` object: static generation, the Cloudflare and Nitro adapters,
2682
+ * tests, and anything calling the pipeline handler as a function all observe
2683
+ * the `Response` directly and saw the full body before TIM-1324.
2684
+ *
2685
+ * The pipeline applies this at its single exit so every response shape is
2686
+ * covered — page render, API route, metadata route, deny page, error page,
2687
+ * proxy short-circuit. The two unit-level producers that are also reachable as
2688
+ * standalone functions (`handleRouteRequest`, `handleApiRoute`) call it too, so
2689
+ * each honours HEAD on its own; they share this implementation rather than
2690
+ * repeating it, and applying it twice is a no-op.
2691
+ */
2692
+ /**
2693
+ * Return a bodyless copy of `response` when `method` is HEAD.
2694
+ *
2695
+ * A no-op for every other method and for responses that already carry no body
2696
+ * (204, 304, redirects), so callers do not need to pre-check either.
2697
+ *
2698
+ * The discarded stream is cancelled: an SSR render that reached this point has
2699
+ * a live producer attached, and dropping the reference without cancelling
2700
+ * leaks it for the lifetime of the render (design/13-security.md A4).
2701
+ */
2702
+ function stripHeadBody(response, method) {
2703
+ if (method.toUpperCase() !== "HEAD") return response;
2704
+ const body = response.body;
2705
+ if (body === null) return response;
2706
+ try {
2707
+ body.cancel().catch(() => {});
2708
+ } catch {}
2709
+ return new Response(null, {
2710
+ status: response.status,
2711
+ statusText: response.statusText,
2712
+ headers: response.headers
2713
+ });
2714
+ }
2715
+ //#endregion
2469
2716
  //#region src/server/pipeline.ts
2470
2717
  /**
2471
2718
  * Create the request handler from a pipeline configuration.
@@ -2529,12 +2776,14 @@ function createPipeline(config) {
2529
2776
  });
2530
2777
  else result = await innerHandler();
2531
2778
  await setSpanAttribute("http.response.status_code", result.status);
2532
- const varyTokens = ["Accept"];
2533
- if (config.interceptionRewrites?.length) varyTokens.push("X-Timber-URL");
2779
+ const varyTokens = [...RSC_KEY_HEADERS];
2534
2780
  const existingVary = result.headers.get("Vary");
2535
2781
  const existingTokens = existingVary ? existingVary.toLowerCase().split(",").map((t) => t.trim()) : [];
2536
2782
  const newTokens = varyTokens.filter((t) => !existingTokens.includes(t.toLowerCase()));
2537
2783
  if (newTokens.length > 0) result.headers.set("Vary", existingVary ? `${existingVary}, ${newTokens.join(", ")}` : newTokens.join(", "));
2784
+ await enforceRscCacheKey(canonicalReq, result);
2785
+ hardenRscPayloadResponse(result);
2786
+ result = stripHeadBody(result, method);
2538
2787
  if (serverTiming === "detailed") {
2539
2788
  const timingHeader = getServerTimingHeader();
2540
2789
  if (timingHeader) result.headers.set("Server-Timing", timingHeader);
@@ -2576,68 +2825,6 @@ function createPipeline(config) {
2576
2825
  return pipelineFn;
2577
2826
  }
2578
2827
  //#endregion
2579
- //#region src/server/build-manifest.ts
2580
- /**
2581
- * Collect all CSS files needed for a matched route's segment chain.
2582
- *
2583
- * Walks segments root → leaf, collecting CSS for each layout and page.
2584
- * Deduplicates while preserving order (root layout CSS first).
2585
- */
2586
- function collectRouteCss(segments, manifest) {
2587
- const seen = /* @__PURE__ */ new Set();
2588
- const result = [];
2589
- for (const segment of segments) for (const file of [segment.layout, segment.page]) {
2590
- if (!file) continue;
2591
- const cssFiles = manifest.css[file.filePath];
2592
- if (!cssFiles) continue;
2593
- for (const url of cssFiles) if (!seen.has(url)) {
2594
- seen.add(url);
2595
- result.push(url);
2596
- }
2597
- }
2598
- return result;
2599
- }
2600
- /**
2601
- * Collect all font entries needed for a matched route's segment chain.
2602
- *
2603
- * Walks segments root → leaf, collecting fonts for each layout and page.
2604
- * Deduplicates by href while preserving order.
2605
- */
2606
- function collectRouteFonts(segments, manifest) {
2607
- const seen = /* @__PURE__ */ new Set();
2608
- const result = [];
2609
- for (const segment of segments) for (const file of [segment.layout, segment.page]) {
2610
- if (!file) continue;
2611
- const fonts = manifest.fonts[file.filePath];
2612
- if (!fonts) continue;
2613
- for (const entry of fonts) if (!seen.has(entry.href)) {
2614
- seen.add(entry.href);
2615
- result.push(entry);
2616
- }
2617
- }
2618
- return result;
2619
- }
2620
- /**
2621
- * Collect modulepreload URLs for a matched route's segment chain.
2622
- *
2623
- * Walks segments root → leaf, collecting transitive JS dependencies
2624
- * for each layout and page. Deduplicates across segments.
2625
- */
2626
- function collectRouteModulepreloads(segments, manifest) {
2627
- const seen = /* @__PURE__ */ new Set();
2628
- const result = [];
2629
- for (const segment of segments) for (const file of [segment.layout, segment.page]) {
2630
- if (!file) continue;
2631
- const preloads = manifest.modulepreload[file.filePath];
2632
- if (!preloads) continue;
2633
- for (const url of preloads) if (!seen.has(url)) {
2634
- seen.add(url);
2635
- result.push(url);
2636
- }
2637
- }
2638
- return result;
2639
- }
2640
- //#endregion
2641
2828
  //#region src/server/early-hints.ts
2642
2829
  /**
2643
2830
  * 103 Early Hints utilities.
@@ -3277,14 +3464,8 @@ async function handleRouteRequest(mod, ctx) {
3277
3464
  });
3278
3465
  }
3279
3466
  if (method === "HEAD") {
3280
- if (mod.HEAD) return runHandler(mod.HEAD, ctx);
3281
- if (mod.GET) {
3282
- const res = await runHandler(mod.GET, ctx);
3283
- return new Response(null, {
3284
- status: res.status,
3285
- headers: res.headers
3286
- });
3287
- }
3467
+ if (mod.HEAD) return stripHeadBody(await runHandler(mod.HEAD, ctx), method);
3468
+ if (mod.GET) return stripHeadBody(await runHandler(mod.GET, ctx), method);
3288
3469
  }
3289
3470
  const handler = mod[method];
3290
3471
  if (!handler) return new Response(null, {
@@ -3511,11 +3692,11 @@ var registry = /* @__PURE__ */ new Map();
3511
3692
  */
3512
3693
  function resolveKeyParams(store) {
3513
3694
  const main = store.segmentParams ?? {};
3514
- const map = store.slotParamsMap;
3515
- if (!map || map.size === 0) return main;
3695
+ const slots = store.slotParams;
3696
+ if (!slots || Object.keys(slots).length === 0) return main;
3516
3697
  const mainCanonical = tryCanonicalize({ ...main });
3517
3698
  if (mainCanonical === null) return null;
3518
- for (const slotParams of map.values()) {
3699
+ for (const slotParams of Object.values(slots)) {
3519
3700
  const merged = tryCanonicalize({
3520
3701
  ...main,
3521
3702
  ...slotParams