@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
@@ -10,9 +10,9 @@
10
10
 
11
11
  import type { Plugin } from 'vite';
12
12
  import { existsSync } from 'node:fs';
13
- import { mkdir, readdir, readFile, writeFile, rename, rm } from 'node:fs/promises';
14
- import { createHash } from 'node:crypto';
15
- import { dirname, extname, join, resolve, relative } from 'node:path';
13
+ import { mkdir, readdir, readFile, writeFile, rename, rm, stat } from 'node:fs/promises';
14
+ import { createHash, randomUUID } from 'node:crypto';
15
+ import { dirname, extname, isAbsolute, join, resolve, relative } from 'node:path';
16
16
  import { pathToFileURL } from 'node:url';
17
17
  import type { PluginContext } from '../plugin-context.js';
18
18
  import type {
@@ -22,7 +22,17 @@ import type {
22
22
  StaticRedirectEntry,
23
23
  } from '../server/static-generator.js';
24
24
  import { mergeRscAssetsManifest } from './adapter-build.js';
25
+ import { isRelativeBase, normalizeBasePath, withBasePath } from '../shared/base-path.js';
26
+ import type { RscManifest, RscManifestEntry } from '../shared/rsc-manifest.js';
25
27
  import { htmlEscapeJsonString } from '../server/flight-scripts.js';
28
+ import { rscPayloadPath } from '../shared/rsc-payload-path.js';
29
+ import { REDIRECTS_FILE, RESERVED_OUTPUT_PATHS } from '../shared/static-platform-files.js';
30
+ import {
31
+ detectFsIdentity,
32
+ fsKey,
33
+ EXACT_FS_IDENTITY,
34
+ type FsIdentity,
35
+ } from '../adapters/fs-identity.js';
26
36
 
27
37
  // ---------------------------------------------------------------------------
28
38
  // Types
@@ -33,12 +43,9 @@ import { htmlEscapeJsonString } from '../server/flight-scripts.js';
33
43
  * Maps an unhashed `/_rsc/*.rsc` path to its hashed URL, with
34
44
  * optional route params inlined to avoid a sidecar fetch.
35
45
  */
36
- export interface RscManifestEntry {
37
- url: string;
38
- params?: Record<string, string | string[]>;
39
- }
40
-
41
- export type RscManifest = Record<string, RscManifestEntry>;
46
+ // Declared in shared/rsc-manifest.ts so the client that reads this manifest and
47
+ // the build that writes it cannot drift. Re-exported for existing importers.
48
+ export type { RscManifestEntry, RscManifest } from '../shared/rsc-manifest.js';
42
49
 
43
50
  export interface StaticValidationError {
44
51
  type: 'dynamic-api';
@@ -107,10 +114,23 @@ export function detectDynamicApis(code: string, fileId: string): StaticValidatio
107
114
  * Generate a `_redirects` file from collected redirect entries.
108
115
  * Uses the Cloudflare Pages / Netlify format: `/from /to statusCode`
109
116
  *
117
+ * Both sides are base-prefixed (TIM-1274). `from` is matched by the host
118
+ * against the request path and `to` is served to the browser as a `Location`,
119
+ * so both are deployed-URL space while `StaticRedirectEntry` carries route
120
+ * space — under `base: '/repo/'`, a rule `/old /new 301` would match nothing
121
+ * and, if it did, would send the browser off the deployment.
122
+ *
123
+ * An off-origin `to` is left alone: `redirectExternal()` produces an absolute
124
+ * URL, and a protocol-relative one addresses another host too. Neither is a
125
+ * path on this deployment, so neither takes the prefix.
126
+ *
127
+ * @param base Normalized deployment base (see `shared/base-path.ts`).
110
128
  * @internal Exported for testing.
111
129
  */
112
- export function generateRedirectsFile(redirects: StaticRedirectEntry[]): string {
113
- const lines = redirects.map((r) => `${r.from} ${r.to} ${r.status}`);
130
+ export function generateRedirectsFile(redirects: StaticRedirectEntry[], base: string): string {
131
+ const target = (to: string) =>
132
+ to.startsWith('/') && !to.startsWith('//') ? withBasePath(base, to) : to;
133
+ const lines = redirects.map((r) => `${withBasePath(base, r.from)} ${target(r.to)} ${r.status}`);
114
134
  return lines.join('\n') + '\n';
115
135
  }
116
136
 
@@ -129,15 +149,20 @@ async function writeFileAtomic(path: string, data: Uint8Array | string): Promise
129
149
  * Determine the output file path for a static page entry.
130
150
  *
131
151
  * Pages: /about → about/index.html, / → index.html
132
- * RSC: /about → _rsc/about.rsc, / → _rsc/index.rsc
152
+ * RSC: /about → _rsc/about/index.rsc, / → _rsc/index.rsc
133
153
  * API: /feed.xml → feed.xml, /api/data → api/data
154
+ *
155
+ * The RSC shape mirrors the HTML shape for the reason the HTML shape has it:
156
+ * a route gets a directory and the artifact is the `index` inside it, so the
157
+ * mapping is injective. See `shared/rsc-payload-path.ts` (TIM-1266).
134
158
  */
135
159
  export function staticOutputPath(entry: StaticPageEntry): string {
136
160
  const urlPath = entry.urlPath === '/' ? '' : entry.urlPath;
137
161
 
138
162
  if (entry.kind === 'rsc') {
139
- const rscPath = urlPath || '/index';
140
- return `_rsc${rscPath}.rsc`;
163
+ // Route space is `/…`; the static output path is relative to the output
164
+ // directory, so drop the leading slash.
165
+ return rscPayloadPath(entry.urlPath).slice(1);
141
166
  }
142
167
 
143
168
  if (entry.kind === 'api') {
@@ -156,6 +181,216 @@ export function staticOutputPath(entry: StaticPageEntry): string {
156
181
  return `${urlPath.startsWith('/') ? urlPath.slice(1) : urlPath}/index.html`;
157
182
  }
158
183
 
184
+ // ---------------------------------------------------------------------------
185
+ // Output path collisions (TIM-1266)
186
+ // ---------------------------------------------------------------------------
187
+
188
+ /**
189
+ * Records which entry claimed each output path and reports second claims.
190
+ *
191
+ * Two entries landing on one path is not a rendering failure — both render
192
+ * fine — so nothing downstream notices. The file is written twice, the second
193
+ * write wins, and the deployment serves one route's bytes at another route's
194
+ * URL with the same content type and the same shape (TIM-1266).
195
+ *
196
+ * The naming rules are injective by construction (a route path is a directory
197
+ * and the artifact is the `index` inside it), so a collision here means either
198
+ * two entries were enumerated for one URL or a route reached a path the
199
+ * framework reserves for itself (`404.html`). Both are build errors: there is
200
+ * no correct file to write.
201
+ *
202
+ * Two entries can also be unwritable together without claiming the same
203
+ * string: one wants a file where the other wants a directory (`about` from an
204
+ * API route, `about/index.html` from a page). The filesystem does refuse that
205
+ * one — `EISDIR` on the rename, `ENOTDIR` on the mkdir, depending on which
206
+ * lands second — but an errno names a path, and in the rename case the path
207
+ * it names is `about.tmp`. Reported here instead, so the message says which
208
+ * two routes.
209
+ *
210
+ * Collisions are **collected, not thrown**. `onEntry` runs inside the
211
+ * generator, whose RSC branch treats a throw as "this payload is unavailable,
212
+ * SPA navigation falls back to a full load" and swallows it — which for a
213
+ * collision is the wrong end state, since the two payloads are still
214
+ * ambiguous. The caller raises them once generation is done.
215
+ *
216
+ * @internal Exported for testing.
217
+ */
218
+ export class StaticOutputPaths {
219
+ private readonly claimed = new Map<string, StaticPageEntry>();
220
+ /** Every ancestor directory of a claimed path, mapped to its claimant. */
221
+ private readonly directories = new Map<string, StaticPageEntry>();
222
+ private readonly collisions: string[] = [];
223
+
224
+ /**
225
+ * @param identity How the output filesystem decides two paths name one
226
+ * file, from `detectFsIdentity`. The guard predicts whether two writes
227
+ * land on one file, which is a property of the filesystem — so it is
228
+ * asked, not assumed. Folding unconditionally would reject `/Foo` beside
229
+ * `/foo` on Linux, where both are perfectly servable files.
230
+ */
231
+ constructor(private readonly identity: FsIdentity = EXACT_FS_IDENTITY) {}
232
+
233
+ /** The identity under which the filesystem will compare this path. */
234
+ private key(relPath: string): string {
235
+ return fsKey(relPath, this.identity);
236
+ }
237
+
238
+ /**
239
+ * Claim `relPath` for `entry`. Returns false when the path cannot be
240
+ * written alongside an earlier claim, in which case the caller must not
241
+ * write the file — the losing entry is recorded and the build fails at
242
+ * the end.
243
+ */
244
+ claim(relPath: string, entry: StaticPageEntry): boolean {
245
+ // Folded like every other comparison here: on a case-insensitive
246
+ // filesystem a route rendering to `_HEADERS` is the same file the
247
+ // adapter is about to write, so a case-sensitive match would wave it
248
+ // through into the overwrite this reservation exists to stop.
249
+ if (RESERVED_OUTPUT_PATHS.some((name) => this.key(name) === this.key(relPath))) {
250
+ this.collisions.push(
251
+ ` - ${relPath}: reserved — the framework writes this file itself, ` +
252
+ `and ${entry.kind} for ${entry.urlPath} would be overwritten by it`
253
+ );
254
+ return false;
255
+ }
256
+
257
+ const key = this.key(relPath);
258
+ const conflict =
259
+ this.claimed.get(key) ??
260
+ // This entry wants a file where an earlier one wants a directory…
261
+ this.directories.get(key) ??
262
+ // …or a directory where an earlier one wants a file.
263
+ ancestors(relPath).reduce<StaticPageEntry | undefined>(
264
+ (found, dir) => found ?? this.claimed.get(this.key(dir)),
265
+ undefined
266
+ );
267
+
268
+ if (conflict) {
269
+ this.collisions.push(
270
+ ` - ${relPath}: ${conflict.kind} for ${conflict.urlPath} and ${entry.kind} for ${entry.urlPath}`
271
+ );
272
+ return false;
273
+ }
274
+
275
+ this.claimed.set(key, entry);
276
+ for (const dir of ancestors(relPath)) {
277
+ const dirKey = this.key(dir);
278
+ if (!this.directories.has(dirKey)) this.directories.set(dirKey, entry);
279
+ }
280
+ return true;
281
+ }
282
+
283
+ /** Throw if any two entries could not both be written. */
284
+ assertNoCollisions(): void {
285
+ if (this.collisions.length === 0) return;
286
+ throw new Error(
287
+ `[timber] Static generation: ${this.collisions.length} output path collision(s) — ` +
288
+ `two entries write the same path and one would silently overwrite the other. ` +
289
+ `Rename or remove one route in each pair:\n${this.collisions.join('\n')}`
290
+ );
291
+ }
292
+ }
293
+
294
+ /** Every directory an output path sits under, outermost first. */
295
+ function ancestors(relPath: string): string[] {
296
+ const parts = relPath.split('/');
297
+ parts.pop();
298
+ return parts.map((_, i) => parts.slice(0, i + 1).join('/'));
299
+ }
300
+
301
+ /**
302
+ * Writes generated entries into the static output directory, one file per
303
+ * entry, refusing any write that would land on a path another entry holds.
304
+ *
305
+ * @internal Exported for testing.
306
+ */
307
+ export class StaticSiteWriter {
308
+ /** Content-Type per URL path for extensionless API routes (TIM-1241). */
309
+ readonly contentTypes: Record<string, string> = {};
310
+ private readonly paths: StaticOutputPaths;
311
+
312
+ constructor(
313
+ private readonly staticDir: string,
314
+ identity: FsIdentity = EXACT_FS_IDENTITY
315
+ ) {
316
+ this.paths = new StaticOutputPaths(identity);
317
+ }
318
+
319
+ async write(entry: StaticPageEntry): Promise<void> {
320
+ const relPath = staticOutputPath(entry);
321
+ const fullPath = resolve(this.staticDir, relPath);
322
+
323
+ // Reject path traversal: generated params with "../" could escape
324
+ // the output directory and overwrite build artifacts.
325
+ //
326
+ // Tested on the first *segment*, not on the prefix. A slug is arbitrary
327
+ // application data, and `..draft` is an ordinary directory name whose
328
+ // relative path merely begins with two dots — a prefix test failed the
329
+ // build on it. `isAbsolute` covers the case `relative()` gives up on and
330
+ // returns an absolute path for: a different Windows drive, where the
331
+ // round-trip check below compares equal and would let it through.
332
+ const rel = relative(this.staticDir, fullPath);
333
+ const firstSegment = rel.split(/[\\/]/)[0];
334
+ if (firstSegment === '..' || isAbsolute(rel) || resolve(this.staticDir, rel) !== fullPath) {
335
+ throw new Error(
336
+ `[timber] Static generation: output path "${relPath}" escapes the ` +
337
+ `static output directory — check generateStaticSegmentParams for ` +
338
+ `path traversal characters in ${entry.urlPath}`
339
+ );
340
+ }
341
+
342
+ // Claim the *normalized* path, not the one `staticOutputPath` built. Two
343
+ // distinct strings can name one file — a generated param carrying `..`
344
+ // makes `_rsc/a/../b/index.rsc` and `_rsc/b/index.rsc` different keys for
345
+ // the same write, which is the collision this check exists to stop. The
346
+ // traversal guard above only rejects escapes; it lets an in-bounds detour
347
+ // through.
348
+ //
349
+ // A losing claim is not written: the build is about to fail, and
350
+ // clobbering the winner would make the failure state worse than the one
351
+ // being reported.
352
+ if (!this.paths.claim(rel.replace(/\\/g, '/'), entry)) return;
353
+
354
+ await mkdir(dirname(fullPath), { recursive: true });
355
+ await writeFileAtomic(fullPath, entry.body);
356
+
357
+ // Track Content-Type for extensionless API routes so adapters
358
+ // can write per-path Content-Type rules in _headers. TIM-1241.
359
+ if (entry.kind === 'api' && !extname(relPath) && entry.contentType) {
360
+ this.contentTypes[entry.urlPath] = entry.contentType;
361
+ }
362
+ }
363
+
364
+ /** Throw if any two entries could not both be written. */
365
+ assertNoCollisions(): void {
366
+ this.paths.assertNoCollisions();
367
+ }
368
+ }
369
+
370
+ /**
371
+ * Remove artifacts a generation pass produces only *conditionally*, so none
372
+ * of them can survive from an earlier build into this one.
373
+ *
374
+ * Pages are not in this set: every page is rewritten each pass, and a page
375
+ * whose route was deleted is a different problem (a stale file nothing links
376
+ * to) from the one here.
377
+ *
378
+ * - `_rsc/` because the files are content-hashed. Left in place, a previous
379
+ * build's already-hashed payload gets hashed again and accumulates.
380
+ * - `_redirects` because it is written only when this pass found redirects.
381
+ * A build whose redirects were removed would otherwise ship the previous
382
+ * build's rules — and would break the merge exemption in
383
+ * `shared/static-platform-files.ts`, which lets the pre-rendered copy
384
+ * overwrite `public/_redirects` on the grounds that it *contains* it. That
385
+ * holds only for a file the current pass produced (TIM-1266).
386
+ *
387
+ * @internal Exported for testing.
388
+ */
389
+ export async function cleanConditionalArtifacts(staticDir: string): Promise<void> {
390
+ await rm(join(staticDir, '_rsc'), { recursive: true, force: true }).catch(() => {});
391
+ await rm(join(staticDir, REDIRECTS_FILE), { force: true }).catch(() => {});
392
+ }
393
+
159
394
  // ---------------------------------------------------------------------------
160
395
  // Static generation orchestration (runs after Vite build)
161
396
  // ---------------------------------------------------------------------------
@@ -166,6 +401,55 @@ interface StaticGenerateModule {
166
401
  ) => Promise<StaticGenerationSummary>;
167
402
  }
168
403
 
404
+ /**
405
+ * A port as Node actually hands it over: `unref()` is a Node addition the DOM
406
+ * lib type does not declare. Optional rather than asserted, so a runtime whose
407
+ * `MessageChannel` has no such method is simply skipped instead of throwing.
408
+ */
409
+ type ReleasablePort = MessagePort & { unref?: () => void };
410
+
411
+ /**
412
+ * Run `fn` with `MessageChannel` instrumented, then stop every channel opened
413
+ * while it ran from holding the process open.
414
+ *
415
+ * Static generation is the only build pass that renders **HTML** in the build
416
+ * process: it imports the built app bundle and drives the full SSR pipeline
417
+ * through it. React's scheduler opens a `MessageChannel` at module scope in
418
+ * that bundle (`scheduler`'s DOM build) and never closes it, and a ref'd
419
+ * `MessagePort` keeps Node's event loop alive on its own — so `timber build`
420
+ * finished the site, printed its summary, and then hung forever. Server-mode
421
+ * builds are unaffected because nothing there renders through react-dom.
422
+ *
423
+ * `unref()` is the whole fix: it leaves the port working and only drops the
424
+ * "keep the process alive" property. It is applied **after** `fn` resolves, so
425
+ * a render in flight can never be cut short by the process exiting under it —
426
+ * an unref'd port would let Node exit while the scheduler still had a
427
+ * continuation queued, which is a silently truncated page rather than a hang.
428
+ *
429
+ * Nothing renders after this pass. If that ever changes, the release has to
430
+ * move to the end of whatever renders last.
431
+ *
432
+ * @internal Exported for testing.
433
+ */
434
+ export async function withSchedulerPortsReleased<T>(fn: () => Promise<T>): Promise<T> {
435
+ const opened: ReleasablePort[] = [];
436
+ const OriginalMessageChannel = globalThis.MessageChannel;
437
+
438
+ globalThis.MessageChannel = class extends OriginalMessageChannel {
439
+ constructor() {
440
+ super();
441
+ opened.push(this.port1, this.port2);
442
+ }
443
+ };
444
+
445
+ try {
446
+ return await fn();
447
+ } finally {
448
+ globalThis.MessageChannel = OriginalMessageChannel;
449
+ for (const port of opened) port.unref?.();
450
+ }
451
+ }
452
+
169
453
  /**
170
454
  * Load the built RSC entry and run the static generation pass.
171
455
  *
@@ -200,80 +484,50 @@ async function runStaticGeneration(ctx: PluginContext): Promise<StaticGeneration
200
484
 
201
485
  console.log('[timber] Static generation: building site…');
202
486
 
203
- let mod: StaticGenerateModule;
204
- try {
205
- const entryUrl = `${pathToFileURL(entryPath).href}?t=${Date.now()}`;
206
- mod = (await import(/* @vite-ignore */ entryUrl)) as StaticGenerateModule;
207
- } catch (error) {
208
- throw new Error(
209
- `[timber] Static generation: failed to load the built RSC entry (${entryPath}). ` +
210
- `The full request handler must initialize for static rendering.`,
211
- { cause: error }
212
- );
213
- }
214
-
215
- const generate = mod.__timber_generateStaticSite;
216
- if (typeof generate !== 'function') {
217
- throw new Error(
218
- `[timber] Static generation: built RSC entry does not export ` +
219
- `__timber_generateStaticSite — rebuild from a clean build directory`
220
- );
221
- }
222
-
223
487
  const staticDir = join(ctx.buildDir, 'static-pages');
224
- // Clean stale RSC artifacts from previous builds before generating
225
- // new ones. Without this, already-hashed files from a prior build
226
- // would get re-hashed and accumulate in the output.
227
- await rm(join(staticDir, '_rsc'), { recursive: true, force: true }).catch(() => {});
488
+ await cleanConditionalArtifacts(staticDir);
228
489
  await mkdir(staticDir, { recursive: true });
229
490
 
230
491
  const sitemapConfig = ctx.config.sitemap;
231
492
  const autoSitemapEnabled = Boolean(sitemapConfig?.enabled && sitemapConfig?.baseUrl);
232
493
 
233
- const contentTypes: Record<string, string> = {};
234
- // Collect route params from RSC entries so they can be inlined
235
- // into the manifest instead of written as .params.json sidecars.
236
- // Keyed by the unhashed /_rsc/*.rsc URL path. TIM-1255.
237
- const routeParams: Record<string, Record<string, string | string[]>> = {};
238
-
239
- const summary = await generate({
240
- autoSitemapEnabled,
241
- renderTimeoutMs: ctx.config.renderTimeoutMs,
242
- onEntry: async (entry: StaticPageEntry) => {
243
- const relPath = staticOutputPath(entry);
244
- const fullPath = resolve(staticDir, relPath);
245
-
246
- // Reject path traversal: generated params with "../" could escape
247
- // the output directory and overwrite build artifacts.
248
- const rel = relative(staticDir, fullPath);
249
- if (rel.startsWith('..') || resolve(staticDir, rel) !== fullPath) {
250
- throw new Error(
251
- `[timber] Static generation: output path "${relPath}" escapes the ` +
252
- `static output directory — check generateStaticSegmentParams for ` +
253
- `path traversal characters in ${entry.urlPath}`
254
- );
255
- }
494
+ const writer = new StaticSiteWriter(staticDir, await detectFsIdentity(staticDir));
495
+ // Route params are not collected here any more. They used to be lifted off
496
+ // the generated entry's response headers and inlined into the manifest,
497
+ // because a `.rsc` file carries no headers and the manifest was the only
498
+ // channel a static SPA navigation had (TIM-1255, TIM-1285). The params are
499
+ // now inside the payload itself — a field on its root row — so the `.rsc`
500
+ // file carries them and there is nothing to lift (TIM-1294).
501
+
502
+ // The app bundle is imported and rendered inside this wrapper, and only
503
+ // inside it: everything it opens to schedule React work is released on the
504
+ // way out so the build process can exit.
505
+ const summary = await withSchedulerPortsReleased(async () => {
506
+ let mod: StaticGenerateModule;
507
+ try {
508
+ const entryUrl = `${pathToFileURL(entryPath).href}?t=${Date.now()}`;
509
+ mod = (await import(/* @vite-ignore */ entryUrl)) as StaticGenerateModule;
510
+ } catch (error) {
511
+ throw new Error(
512
+ `[timber] Static generation: failed to load the built RSC entry (${entryPath}). ` +
513
+ `The full request handler must initialize for static rendering.`,
514
+ { cause: error }
515
+ );
516
+ }
256
517
 
257
- await mkdir(dirname(fullPath), { recursive: true });
258
- await writeFileAtomic(fullPath, entry.body);
259
-
260
- // Capture route params for RSC entries. These are inlined into
261
- // the RSC manifest (TIM-1255) instead of written as sidecars.
262
- if (entry.kind === 'rsc' && entry.headers['x-timber-params']) {
263
- const rscUrlKey = entry.urlPath === '/' ? '/_rsc/index.rsc' : `/_rsc${entry.urlPath}.rsc`;
264
- try {
265
- routeParams[rscUrlKey] = JSON.parse(entry.headers['x-timber-params']);
266
- } catch {
267
- // Malformed params header — skip
268
- }
269
- }
518
+ const generate = mod.__timber_generateStaticSite;
519
+ if (typeof generate !== 'function') {
520
+ throw new Error(
521
+ `[timber] Static generation: built RSC entry does not export ` +
522
+ `__timber_generateStaticSite — rebuild from a clean build directory`
523
+ );
524
+ }
270
525
 
271
- // Track Content-Type for extensionless API routes so adapters
272
- // can write per-path Content-Type rules in _headers. TIM-1241.
273
- if (entry.kind === 'api' && !extname(relPath) && entry.contentType) {
274
- contentTypes[entry.urlPath] = entry.contentType;
275
- }
276
- },
526
+ return generate({
527
+ autoSitemapEnabled,
528
+ renderTimeoutMs: ctx.config.renderTimeoutMs,
529
+ onEntry: (entry: StaticPageEntry) => writer.write(entry),
530
+ });
277
531
  });
278
532
 
279
533
  const totalUrls = summary.rendered + summary.errors.length;
@@ -283,15 +537,25 @@ async function runStaticGeneration(ctx: PluginContext): Promise<StaticGeneration
283
537
  (summary.redirects.length > 0 ? ` ${summary.redirects.length} redirect(s).` : '')
284
538
  );
285
539
 
540
+ // Warnings are non-fatal but must not be buried in the per-entry log stream —
541
+ // they name a degraded artifact the developer is about to deploy (TIM-1258).
542
+ for (const warning of summary.warnings) {
543
+ console.warn(`[timber] Static generation warning: ${warning}`);
544
+ }
545
+
286
546
  if (summary.errors.length > 0) {
287
547
  const details = summary.errors.map((e) => ` - ${e.urlPath}: ${e.message}`).join('\n');
288
548
  throw new Error(`[timber] Static generation failed:\n${details}`);
289
549
  }
290
550
 
551
+ // Raised after the render errors above: a collision means the site is
552
+ // ambiguous, not that a page failed, and the message names both routes.
553
+ writer.assertNoCollisions();
554
+
291
555
  // Store content-type mappings on the plugin context so adapters
292
556
  // can include them in the _headers file. TIM-1241.
293
- if (Object.keys(contentTypes).length > 0) {
294
- ctx.staticContentTypes = contentTypes;
557
+ if (Object.keys(writer.contentTypes).length > 0) {
558
+ ctx.staticContentTypes = writer.contentTypes;
295
559
  }
296
560
 
297
561
  // Hash RSC flight payloads for immutable caching (TIM-1254).
@@ -300,7 +564,7 @@ async function runStaticGeneration(ctx: PluginContext): Promise<StaticGeneration
300
564
  // Skip when client JS is disabled — no SPA navigation, no RSC fetches,
301
565
  // and injecting a <script> tag would violate the no-JS contract.
302
566
  if (!ctx.clientJavascript.disabled) {
303
- const rscManifest = await hashRscPayloads(staticDir, routeParams);
567
+ const rscManifest = await hashRscPayloads(staticDir);
304
568
  if (Object.keys(rscManifest).length > 0) {
305
569
  await injectRscManifestIntoHtml(staticDir, rscManifest);
306
570
  ctx.rscManifest = rscManifest;
@@ -313,8 +577,8 @@ async function runStaticGeneration(ctx: PluginContext): Promise<StaticGeneration
313
577
  // Write _redirects file for platform redirect rules (Cloudflare Pages,
314
578
  // Netlify, etc.). Format: /from /to status. See TIM-1245.
315
579
  if (summary.redirects.length > 0) {
316
- const generated = generateRedirectsFile(summary.redirects);
317
- const redirectsPath = join(staticDir, '_redirects');
580
+ const generated = generateRedirectsFile(summary.redirects, normalizeBasePath(ctx.base));
581
+ const redirectsPath = join(staticDir, REDIRECTS_FILE);
318
582
 
319
583
  // Merge with user-authored _redirects if present. TIM-1249.
320
584
  const userRedirects = await readUserRedirects(ctx.root);
@@ -335,6 +599,38 @@ async function runStaticGeneration(ctx: PluginContext): Promise<StaticGeneration
335
599
  return summary;
336
600
  }
337
601
 
602
+ // ---------------------------------------------------------------------------
603
+ // Base path validation (TIM-1261)
604
+ // ---------------------------------------------------------------------------
605
+
606
+ /**
607
+ * Warn when Vite's `base` is document-relative (`'./'`) in static mode.
608
+ *
609
+ * Payload URLs are addressed from the deployment root plus the base prefix.
610
+ * A relative base gives no prefix to compute (see shared/base-path.ts), so
611
+ * `/_rsc/about.rsc` is requested at the domain root — correct only when the
612
+ * site is served from there. The build still succeeds: relative bases work
613
+ * fine for assets, and for a root-served site nothing is wrong. The warning
614
+ * exists so a sub-path deployment does not silently degrade to a hard
615
+ * navigation on every link.
616
+ *
617
+ * Skipped when client JS is disabled — there is no SPA navigation and no
618
+ * payload fetch to mis-address.
619
+ *
620
+ * @internal Exported for testing.
621
+ */
622
+ export function warnUnusableBase(ctx: PluginContext): void {
623
+ if (ctx.clientJavascript.disabled) return;
624
+ if (!isRelativeBase(ctx.base)) return;
625
+ console.warn(
626
+ `[timber] Static mode: base is "${ctx.base}", which is document-relative. ` +
627
+ `RSC payload URLs are addressed from the domain root, so client-side ` +
628
+ `navigation will fall back to a full page load if the site is served ` +
629
+ `from a sub-path. Use an absolute base (e.g. base: '/repo/') to deploy ` +
630
+ `under a sub-path. See design/11-platform.md.`
631
+ );
632
+ }
633
+
338
634
  // ---------------------------------------------------------------------------
339
635
  // RSC flight payload hashing (TIM-1254)
340
636
  // ---------------------------------------------------------------------------
@@ -379,6 +675,12 @@ async function walkDir(dir: string): Promise<string[]> {
379
675
  * The manifest maps the original `_rsc/` URL to an entry object:
380
676
  * `{"/_rsc/index.rsc":{"url":"/_rsc/index-B7YxEKdN.rsc"}, ...}`
381
677
  *
678
+ * Keys and URLs are **route-space**: root-relative, never carrying Vite's
679
+ * `base`. The client applies the base once, at fetch time
680
+ * (`client/rsc-fetch.ts` → `toStaticRscUrl`), so the generated site stays
681
+ * relocatable and the base has exactly one expression at runtime rather
682
+ * than two that must agree. See TIM-1261.
683
+ *
382
684
  * Route params (from `routeParams`) are inlined into the manifest
383
685
  * entry, eliminating the need for .params.json sidecar files. TIM-1255.
384
686
  *
@@ -387,10 +689,7 @@ async function walkDir(dir: string): Promise<string[]> {
387
689
  *
388
690
  * @internal Exported for testing.
389
691
  */
390
- export async function hashRscPayloads(
391
- staticDir: string,
392
- routeParams?: Record<string, Record<string, string | string[]>>
393
- ): Promise<RscManifest> {
692
+ export async function hashRscPayloads(staticDir: string): Promise<RscManifest> {
394
693
  const rscDir = join(staticDir, '_rsc');
395
694
  const files = await walkDir(rscDir);
396
695
  const manifest: RscManifest = {};
@@ -409,11 +708,7 @@ export async function hashRscPayloads(
409
708
 
410
709
  const hashedRel = relative(staticDir, hashedPath).replace(/\\/g, '/');
411
710
  const key = `/${rel}`;
412
- const entry: RscManifestEntry = { url: `/${hashedRel}` };
413
- if (routeParams?.[key]) {
414
- entry.params = routeParams[key];
415
- }
416
- manifest[key] = entry;
711
+ manifest[key] = { url: `/${hashedRel}` };
417
712
  }
418
713
 
419
714
  return manifest;
@@ -460,7 +755,7 @@ export async function injectRscManifestIntoHtml(
460
755
  * @internal Exported for testing.
461
756
  */
462
757
  export async function readUserRedirects(root: string): Promise<string | null> {
463
- const candidates = [join(root, 'public', '_redirects')];
758
+ const candidates = [join(root, 'public', REDIRECTS_FILE)];
464
759
  for (const path of candidates) {
465
760
  try {
466
761
  return await readFile(path, 'utf-8');
@@ -542,6 +837,7 @@ export function timberStaticBuild(ctx: PluginContext): Plugin {
542
837
  const isStatic = ctx.config.output === 'static';
543
838
  if (!isStatic) return;
544
839
 
840
+ warnUnusableBase(ctx);
545
841
  await runStaticGeneration(ctx);
546
842
  },
547
843
  },