@timber-js/app 0.2.0-alpha.207 → 0.2.0-alpha.209

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 (526) hide show
  1. package/dist/_chunks/{primitives-DLAnvsrk.js → actions-BerlqoXA.js} +640 -410
  2. package/dist/_chunks/actions-BerlqoXA.js.map +1 -0
  3. package/dist/_chunks/{als-registry-C6kcfprT.js → als-registry-DaxkVjt5.js} +6 -2
  4. package/dist/_chunks/als-registry-DaxkVjt5.js.map +1 -0
  5. package/dist/_chunks/{build-manifest-DTmSGLRz.js → build-manifest-DWppEdLB.js} +2 -51
  6. package/dist/_chunks/build-manifest-DWppEdLB.js.map +1 -0
  7. package/dist/_chunks/{build-output-helper-9dqCv1pZ.js → build-output-helper-RV_BRcF1.js} +50 -53
  8. package/dist/_chunks/build-output-helper-RV_BRcF1.js.map +1 -0
  9. package/dist/_chunks/{cache-api-CUD9Ezaq.js → cache-api-CR23J_NC.js} +64 -32
  10. package/dist/_chunks/cache-api-CR23J_NC.js.map +1 -0
  11. package/dist/_chunks/{chains-B4bgOl2g.js → chains-CpFg56UB.js} +2 -2
  12. package/dist/_chunks/{chains-B4bgOl2g.js.map → chains-CpFg56UB.js.map} +1 -1
  13. package/dist/_chunks/{cli-check-G8o30ldg.js → cli-check-C6Ev6wBO.js} +11 -52
  14. package/dist/_chunks/cli-check-C6Ev6wBO.js.map +1 -0
  15. package/dist/_chunks/cli-context-By7k2xVo.js +70 -0
  16. package/dist/_chunks/cli-context-By7k2xVo.js.map +1 -0
  17. package/dist/_chunks/{cli-schema-sync--LZKs8Zn.js → cli-schema-sync-CbT2AUUI.js} +3 -3
  18. package/dist/_chunks/{cli-schema-sync--LZKs8Zn.js.map → cli-schema-sync-CbT2AUUI.js.map} +1 -1
  19. package/dist/_chunks/{cloudflare-BFb__LYG.js → cloudflare-Byxl8FWP.js} +96 -53
  20. package/dist/_chunks/{cloudflare-BFb__LYG.js.map → cloudflare-Byxl8FWP.js.map} +1 -1
  21. package/dist/_chunks/{convention-lint-5KzoMg_A.js → convention-lint-jKTwKwPe.js} +21 -6
  22. package/dist/_chunks/convention-lint-jKTwKwPe.js.map +1 -0
  23. package/dist/_chunks/{dev-server-BzBxgYAV.js → dev-server-C4WZdB7L.js} +15 -167
  24. package/dist/_chunks/dev-server-C4WZdB7L.js.map +1 -0
  25. package/dist/_chunks/{error-boundary-BndF-3Td.js → error-boundary-tA7kVfs4.js} +84 -104
  26. package/dist/_chunks/error-boundary-tA7kVfs4.js.map +1 -0
  27. package/dist/_chunks/{fs-identity-D7vb6GdI.js → fs-identity-BRKuL215.js} +3 -4
  28. package/dist/_chunks/fs-identity-BRKuL215.js.map +1 -0
  29. package/dist/_chunks/{graph-cache-CP4GEmf9.js → graph-cache-CYiUS2aS.js} +4 -5
  30. package/dist/_chunks/graph-cache-CYiUS2aS.js.map +1 -0
  31. package/dist/_chunks/{json-lossy-check-ClNvBM_3.js → json-lossy-check-ip0Qi0MT.js} +6 -3
  32. package/dist/_chunks/json-lossy-check-ip0Qi0MT.js.map +1 -0
  33. package/dist/_chunks/{live-graph-Bm5WTvcr.js → live-graph-Dv-JJCZw.js} +3 -3
  34. package/dist/_chunks/{live-graph-Bm5WTvcr.js.map → live-graph-Dv-JJCZw.js.map} +1 -1
  35. package/dist/_chunks/{logger-k2DQ4EUf.js → logger-DiDt5ppH.js} +192 -51
  36. package/dist/_chunks/logger-DiDt5ppH.js.map +1 -0
  37. package/dist/_chunks/{navigation-root-mHSK9psY.js → navigation-root-BQfo1-kG.js} +2 -2
  38. package/dist/_chunks/{navigation-root-mHSK9psY.js.map → navigation-root-BQfo1-kG.js.map} +1 -1
  39. package/dist/_chunks/{plugin-context-8idfR3UZ.js → plugin-context-BPBPc3lL.js} +28 -4
  40. package/dist/_chunks/plugin-context-BPBPc3lL.js.map +1 -0
  41. package/dist/_chunks/{poison-scan-DG5eI2_z.js → poison-scan-CpeT6_OJ.js} +2 -2
  42. package/dist/_chunks/{poison-scan-DG5eI2_z.js.map → poison-scan-CpeT6_OJ.js.map} +1 -1
  43. package/dist/_chunks/render-timeout-DOf_BBcO.js +46 -0
  44. package/dist/_chunks/render-timeout-DOf_BBcO.js.map +1 -0
  45. package/dist/_chunks/{rsc-payload-path-B_LBodc2.js → rsc-payload-path-dffcs4jp.js} +9 -3
  46. package/dist/_chunks/{rsc-payload-path-B_LBodc2.js.map → rsc-payload-path-dffcs4jp.js.map} +1 -1
  47. package/dist/_chunks/{scanner-BQ5sPIJ7.js → scanner-Bw0oq1HB.js} +2 -2
  48. package/dist/_chunks/{scanner-BQ5sPIJ7.js.map → scanner-Bw0oq1HB.js.map} +1 -1
  49. package/dist/_chunks/{navigation-context-D0TU0Jog.js → segment-context-xtPUGfdq.js} +36 -34
  50. package/dist/_chunks/segment-context-xtPUGfdq.js.map +1 -0
  51. package/dist/_chunks/{singleflight-2lUWfcAk.js → singleflight-BeVMMKi0.js} +4 -3
  52. package/dist/_chunks/{singleflight-2lUWfcAk.js.map → singleflight-BeVMMKi0.js.map} +1 -1
  53. package/dist/_chunks/{sizeof-lXffQ1Vr.js → sizeof-UwzwB1uM.js} +3 -2
  54. package/dist/_chunks/sizeof-UwzwB1uM.js.map +1 -0
  55. package/dist/_chunks/ssr-data-nA3I70_n.js +49 -0
  56. package/dist/_chunks/ssr-data-nA3I70_n.js.map +1 -0
  57. package/dist/_chunks/{state-FippDgxN.js → state-BhNGPsdi.js} +6 -8
  58. package/dist/_chunks/state-BhNGPsdi.js.map +1 -0
  59. package/dist/_chunks/{status-page-marker-DwQBrLBz.js → status-page-marker-gaihi0KZ.js} +140 -28
  60. package/dist/_chunks/status-page-marker-gaihi0KZ.js.map +1 -0
  61. package/dist/_chunks/{walkers-CN2N_bA-.js → walkers-BXExhzzk.js} +2 -2
  62. package/dist/_chunks/{walkers-CN2N_bA-.js.map → walkers-BXExhzzk.js.map} +1 -1
  63. package/dist/_chunks/write-file-atomic-D1qWqHb4.js +32 -0
  64. package/dist/_chunks/write-file-atomic-D1qWqHb4.js.map +1 -0
  65. package/dist/adapters/build-output-helper.d.ts +7 -7
  66. package/dist/adapters/build-output-helper.d.ts.map +1 -1
  67. package/dist/adapters/canonicalize-module.d.ts +8 -0
  68. package/dist/adapters/canonicalize-module.d.ts.map +1 -0
  69. package/dist/adapters/cloudflare-dev.js +1 -1
  70. package/dist/adapters/cloudflare-kv-cache.js +2 -2
  71. package/dist/adapters/cloudflare.d.ts +30 -8
  72. package/dist/adapters/cloudflare.d.ts.map +1 -1
  73. package/dist/adapters/cloudflare.js +2 -2
  74. package/dist/adapters/compress-module.d.ts +8 -0
  75. package/dist/adapters/compress-module.d.ts.map +1 -1
  76. package/dist/adapters/fs-identity.d.ts +2 -3
  77. package/dist/adapters/fs-identity.d.ts.map +1 -1
  78. package/dist/adapters/nitro-presets.d.ts +0 -6
  79. package/dist/adapters/nitro-presets.d.ts.map +1 -1
  80. package/dist/adapters/nitro-preview.d.ts +43 -11
  81. package/dist/adapters/nitro-preview.d.ts.map +1 -1
  82. package/dist/adapters/nitro.d.ts +1 -13
  83. package/dist/adapters/nitro.d.ts.map +1 -1
  84. package/dist/adapters/nitro.js +291 -176
  85. package/dist/adapters/nitro.js.map +1 -1
  86. package/dist/adapters/shared.d.ts +7 -2
  87. package/dist/adapters/shared.d.ts.map +1 -1
  88. package/dist/adapters/types.d.ts +105 -21
  89. package/dist/adapters/types.d.ts.map +1 -1
  90. package/dist/adapters/types.js +0 -0
  91. package/dist/analyze/crawl-entry.js +3 -3
  92. package/dist/analyze/graph-cache.d.ts.map +1 -1
  93. package/dist/analyze/graph-command.js +4 -4
  94. package/dist/cache/index.js +2 -2
  95. package/dist/cache/json-lossy-check.d.ts.map +1 -1
  96. package/dist/cache/singleflight.d.ts.map +1 -1
  97. package/dist/cache/sizeof.d.ts.map +1 -1
  98. package/dist/cache/stores/memory.js +1 -1
  99. package/dist/cache/tag-aware-handler.d.ts.map +1 -1
  100. package/dist/cdn/workers-cache-purge.js +1 -1
  101. package/dist/cli-check.d.ts +7 -37
  102. package/dist/cli-check.d.ts.map +1 -1
  103. package/dist/cli-context.d.ts +54 -0
  104. package/dist/cli-context.d.ts.map +1 -0
  105. package/dist/cli.d.ts +9 -9
  106. package/dist/cli.d.ts.map +1 -1
  107. package/dist/cli.js +20 -24
  108. package/dist/cli.js.map +1 -1
  109. package/dist/client/browser-dev.d.ts +1 -1
  110. package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
  111. package/dist/client/browser-entry/hydrate.d.ts +19 -29
  112. package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
  113. package/dist/client/browser-entry/index.d.ts +7 -7
  114. package/dist/client/browser-entry/post-hydration.d.ts +6 -9
  115. package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
  116. package/dist/client/browser-entry/router-init.d.ts +6 -11
  117. package/dist/client/browser-entry/router-init.d.ts.map +1 -1
  118. package/dist/client/error-boundary.d.ts +4 -3
  119. package/dist/client/error-boundary.d.ts.map +1 -1
  120. package/dist/client/error-boundary.js +1 -1
  121. package/dist/client/error-reconstituter.d.ts +3 -9
  122. package/dist/client/error-reconstituter.d.ts.map +1 -1
  123. package/dist/client/history.d.ts +20 -11
  124. package/dist/client/history.d.ts.map +1 -1
  125. package/dist/client/index.d.ts +0 -1
  126. package/dist/client/index.d.ts.map +1 -1
  127. package/dist/client/index.js +5 -6
  128. package/dist/client/index.js.map +1 -1
  129. package/dist/client/internal.d.ts +3 -3
  130. package/dist/client/internal.d.ts.map +1 -1
  131. package/dist/client/internal.js +95 -108
  132. package/dist/client/internal.js.map +1 -1
  133. package/dist/client/navigation-commit.d.ts +20 -7
  134. package/dist/client/navigation-commit.d.ts.map +1 -1
  135. package/dist/client/navigation-context.d.ts +3 -5
  136. package/dist/client/navigation-context.d.ts.map +1 -1
  137. package/dist/client/react-root.d.ts +3 -2
  138. package/dist/client/react-root.d.ts.map +1 -1
  139. package/dist/client/router-pipeline.d.ts +3 -8
  140. package/dist/client/router-pipeline.d.ts.map +1 -1
  141. package/dist/client/router-types.d.ts +45 -39
  142. package/dist/client/router-types.d.ts.map +1 -1
  143. package/dist/client/router-url.d.ts +24 -0
  144. package/dist/client/router-url.d.ts.map +1 -0
  145. package/dist/client/router.d.ts.map +1 -1
  146. package/dist/client/rsc-fetch.d.ts +8 -4
  147. package/dist/client/rsc-fetch.d.ts.map +1 -1
  148. package/dist/client/segment-cache.d.ts +44 -4
  149. package/dist/client/segment-cache.d.ts.map +1 -1
  150. package/dist/client/ssr-data.d.ts +18 -64
  151. package/dist/client/ssr-data.d.ts.map +1 -1
  152. package/dist/client/state.d.ts +2 -5
  153. package/dist/client/state.d.ts.map +1 -1
  154. package/dist/client/use-pathname.d.ts +1 -1
  155. package/dist/config-types.d.ts +2 -1
  156. package/dist/config-types.d.ts.map +1 -1
  157. package/dist/config-validation.d.ts.map +1 -1
  158. package/dist/cookies/index.js +1 -1
  159. package/dist/dev-tools/dev-page-shell.d.ts +17 -0
  160. package/dist/dev-tools/dev-page-shell.d.ts.map +1 -1
  161. package/dist/dev-tools/index.d.ts +1 -1
  162. package/dist/dev-tools/index.d.ts.map +1 -1
  163. package/dist/dev-tools/instrumentation.d.ts +17 -2
  164. package/dist/dev-tools/instrumentation.d.ts.map +1 -1
  165. package/dist/dev-tools/overlay.d.ts +6 -27
  166. package/dist/dev-tools/overlay.d.ts.map +1 -1
  167. package/dist/index.d.ts.map +1 -1
  168. package/dist/index.js +632 -373
  169. package/dist/index.js.map +1 -1
  170. package/dist/plugin-context.d.ts +26 -40
  171. package/dist/plugin-context.d.ts.map +1 -1
  172. package/dist/plugins/build-manifest.d.ts.map +1 -1
  173. package/dist/plugins/build-report.d.ts +15 -2
  174. package/dist/plugins/build-report.d.ts.map +1 -1
  175. package/dist/plugins/dev-server.d.ts.map +1 -1
  176. package/dist/plugins/entries.d.ts.map +1 -1
  177. package/dist/plugins/fonts.d.ts +19 -1
  178. package/dist/plugins/fonts.d.ts.map +1 -1
  179. package/dist/plugins/post-build.d.ts +110 -0
  180. package/dist/plugins/post-build.d.ts.map +1 -0
  181. package/dist/plugins/prebuilt-capture.d.ts +11 -4
  182. package/dist/plugins/prebuilt-capture.d.ts.map +1 -1
  183. package/dist/plugins/prebuilt.d.ts +17 -1
  184. package/dist/plugins/prebuilt.d.ts.map +1 -1
  185. package/dist/plugins/prerender-sugar.d.ts.map +1 -1
  186. package/dist/plugins/react-resolve.d.ts +49 -0
  187. package/dist/plugins/react-resolve.d.ts.map +1 -0
  188. package/dist/plugins/rsc-assets-manifest.d.ts +33 -0
  189. package/dist/plugins/rsc-assets-manifest.d.ts.map +1 -0
  190. package/dist/plugins/shims.d.ts.map +1 -1
  191. package/dist/plugins/static-build.d.ts +32 -9
  192. package/dist/plugins/static-build.d.ts.map +1 -1
  193. package/dist/routing/codegen-write.d.ts +0 -9
  194. package/dist/routing/codegen-write.d.ts.map +1 -1
  195. package/dist/routing/index.js +2 -2
  196. package/dist/routing/segment-codegen.d.ts.map +1 -1
  197. package/dist/server/access-gate.d.ts.map +1 -1
  198. package/dist/server/action-handler.d.ts +5 -4
  199. package/dist/server/action-handler.d.ts.map +1 -1
  200. package/dist/server/actions.d.ts +46 -7
  201. package/dist/server/actions.d.ts.map +1 -1
  202. package/dist/server/als-registry.d.ts +58 -5
  203. package/dist/server/als-registry.d.ts.map +1 -1
  204. package/dist/server/children-interception.d.ts.map +1 -1
  205. package/dist/server/cookie-context.d.ts +2 -23
  206. package/dist/server/cookie-context.d.ts.map +1 -1
  207. package/dist/server/csp-nonce.d.ts +0 -9
  208. package/dist/server/csp-nonce.d.ts.map +1 -1
  209. package/dist/server/csrf.d.ts +27 -4
  210. package/dist/server/csrf.d.ts.map +1 -1
  211. package/dist/server/debug.d.ts +0 -1
  212. package/dist/server/debug.d.ts.map +1 -1
  213. package/dist/server/default-logger.d.ts +2 -1
  214. package/dist/server/default-logger.d.ts.map +1 -1
  215. package/dist/server/deny-boundary.d.ts +12 -6
  216. package/dist/server/deny-boundary.d.ts.map +1 -1
  217. package/dist/server/deny-renderer.d.ts +2 -2
  218. package/dist/server/deny-renderer.d.ts.map +1 -1
  219. package/dist/server/error-boundary-wrapper.d.ts +7 -3
  220. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  221. package/dist/server/fallback-error.d.ts +9 -2
  222. package/dist/server/fallback-error.d.ts.map +1 -1
  223. package/dist/server/head-response.d.ts +4 -4
  224. package/dist/server/html-injectors.d.ts.map +1 -1
  225. package/dist/server/index.d.ts +0 -1
  226. package/dist/server/index.d.ts.map +1 -1
  227. package/dist/server/index.js +3 -4
  228. package/dist/server/index.js.map +1 -1
  229. package/dist/server/instrumentation.d.ts +10 -3
  230. package/dist/server/instrumentation.d.ts.map +1 -1
  231. package/dist/server/internal.d.ts +1 -4
  232. package/dist/server/internal.d.ts.map +1 -1
  233. package/dist/server/internal.js +961 -581
  234. package/dist/server/internal.js.map +1 -1
  235. package/dist/server/json-status-response.d.ts +34 -0
  236. package/dist/server/json-status-response.d.ts.map +1 -0
  237. package/dist/server/logger.d.ts +26 -0
  238. package/dist/server/logger.d.ts.map +1 -1
  239. package/dist/server/param-coercion.d.ts +11 -6
  240. package/dist/server/param-coercion.d.ts.map +1 -1
  241. package/dist/server/pipeline-helpers.d.ts +95 -7
  242. package/dist/server/pipeline-helpers.d.ts.map +1 -1
  243. package/dist/server/pipeline-interception.d.ts +11 -0
  244. package/dist/server/pipeline-interception.d.ts.map +1 -1
  245. package/dist/server/pipeline-outcome.d.ts +11 -1
  246. package/dist/server/pipeline-outcome.d.ts.map +1 -1
  247. package/dist/server/pipeline-phases.d.ts +2 -2
  248. package/dist/server/pipeline-phases.d.ts.map +1 -1
  249. package/dist/server/pipeline.d.ts +23 -35
  250. package/dist/server/pipeline.d.ts.map +1 -1
  251. package/dist/server/prebuilt/capture-render.d.ts +25 -0
  252. package/dist/server/prebuilt/capture-render.d.ts.map +1 -0
  253. package/dist/server/prebuilt/capture-routes.d.ts +37 -0
  254. package/dist/server/prebuilt/capture-routes.d.ts.map +1 -0
  255. package/dist/server/prebuilt/capture-state.d.ts +79 -35
  256. package/dist/server/prebuilt/capture-state.d.ts.map +1 -1
  257. package/dist/server/prebuilt/lifetime.d.ts +38 -0
  258. package/dist/server/prebuilt/lifetime.d.ts.map +1 -0
  259. package/dist/server/prebuilt/slots.d.ts +7 -0
  260. package/dist/server/prebuilt/slots.d.ts.map +1 -1
  261. package/dist/server/prebuilt/synthetic-store.d.ts.map +1 -1
  262. package/dist/server/prebuilt-builder.d.ts.map +1 -1
  263. package/dist/server/prebuilt-runtime.d.ts.map +1 -1
  264. package/dist/server/primitives.d.ts +6 -81
  265. package/dist/server/primitives.d.ts.map +1 -1
  266. package/dist/server/react-cache-bridge.d.ts +59 -0
  267. package/dist/server/react-cache-bridge.d.ts.map +1 -0
  268. package/dist/server/react-cache-scope.d.ts +49 -0
  269. package/dist/server/react-cache-scope.d.ts.map +1 -0
  270. package/dist/server/render-timeout.d.ts +15 -0
  271. package/dist/server/render-timeout.d.ts.map +1 -1
  272. package/dist/server/render-utils.d.ts +9 -0
  273. package/dist/server/render-utils.d.ts.map +1 -1
  274. package/dist/server/request-context.d.ts +26 -5
  275. package/dist/server/request-context.d.ts.map +1 -1
  276. package/dist/server/route-element-builder.d.ts.map +1 -1
  277. package/dist/server/route-handler.d.ts.map +1 -1
  278. package/dist/server/route-matcher.d.ts +7 -0
  279. package/dist/server/route-matcher.d.ts.map +1 -1
  280. package/dist/server/rsc-entry/action-middleware-runner.d.ts +22 -22
  281. package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -1
  282. package/dist/server/rsc-entry/api-handler.d.ts +7 -0
  283. package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
  284. package/dist/server/rsc-entry/deny-fallback.d.ts +32 -6
  285. package/dist/server/rsc-entry/deny-fallback.d.ts.map +1 -1
  286. package/dist/server/rsc-entry/error-renderer.d.ts +11 -18
  287. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  288. package/dist/server/rsc-entry/helpers.d.ts +0 -1
  289. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  290. package/dist/server/rsc-entry/index.d.ts +5 -15
  291. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  292. package/dist/server/rsc-entry/render-route.d.ts +5 -6
  293. package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
  294. package/dist/server/rsc-entry/render-signals.d.ts +65 -0
  295. package/dist/server/rsc-entry/render-signals.d.ts.map +1 -0
  296. package/dist/server/rsc-entry/revalidate-renderer.d.ts +6 -16
  297. package/dist/server/rsc-entry/revalidate-renderer.d.ts.map +1 -1
  298. package/dist/server/rsc-entry/rsc-payload.d.ts +7 -3
  299. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  300. package/dist/server/rsc-entry/rsc-stream.d.ts +8 -53
  301. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  302. package/dist/server/rsc-entry/ssr-bridge.d.ts +2 -2
  303. package/dist/server/rsc-entry/ssr-bridge.d.ts.map +1 -1
  304. package/dist/server/rsc-entry/ssr-renderer.d.ts +9 -4
  305. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  306. package/dist/server/rsc-entry/wrap-action-dispatch.d.ts +1 -1
  307. package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +1 -1
  308. package/dist/server/signal-identity.d.ts +43 -0
  309. package/dist/server/signal-identity.d.ts.map +1 -0
  310. package/dist/server/slot-resolver.d.ts +4 -3
  311. package/dist/server/slot-resolver.d.ts.map +1 -1
  312. package/dist/server/slot-subtree-contain.d.ts.map +1 -1
  313. package/dist/server/ssr-bridge-types.d.ts +91 -53
  314. package/dist/server/ssr-bridge-types.d.ts.map +1 -1
  315. package/dist/server/ssr-entry.d.ts +8 -5
  316. package/dist/server/ssr-entry.d.ts.map +1 -1
  317. package/dist/server/ssr-render.d.ts +26 -40
  318. package/dist/server/ssr-render.d.ts.map +1 -1
  319. package/dist/server/ssr-wrappers.d.ts +1 -1
  320. package/dist/server/static-generator.d.ts.map +1 -1
  321. package/dist/server/stream-utils.d.ts +12 -0
  322. package/dist/server/stream-utils.d.ts.map +1 -1
  323. package/dist/server/tracing.d.ts +12 -16
  324. package/dist/server/tracing.d.ts.map +1 -1
  325. package/dist/server/types.d.ts +1 -1
  326. package/dist/server/version-skew.d.ts +3 -3
  327. package/dist/shared/csrf-reject.d.ts +16 -0
  328. package/dist/shared/csrf-reject.d.ts.map +1 -0
  329. package/dist/shared/error-digest.d.ts +28 -0
  330. package/dist/shared/error-digest.d.ts.map +1 -0
  331. package/dist/shared/payload-root.d.ts +11 -10
  332. package/dist/shared/payload-root.d.ts.map +1 -1
  333. package/dist/shared/rsc-payload-path.d.ts +6 -0
  334. package/dist/shared/rsc-payload-path.d.ts.map +1 -1
  335. package/dist/utils/write-file-atomic.d.ts +17 -0
  336. package/dist/utils/write-file-atomic.d.ts.map +1 -0
  337. package/docs/api/30-api-server.mdx +17 -14
  338. package/docs/api/32-api-cache.mdx +51 -23
  339. package/docs/api/34-api-config.mdx +34 -6
  340. package/docs/api/36-cli.mdx +11 -1
  341. package/docs/learn/03-fetching-data.mdx +17 -1
  342. package/docs/learn/04-access-control.mdx +1 -1
  343. package/docs/learn/10-caching.mdx +29 -11
  344. package/docs/learn/11-middleware.mdx +8 -2
  345. package/docs/learn/12-error-handling.mdx +6 -1
  346. package/package.json +7 -3
  347. package/src/adapters/build-output-helper.ts +55 -53
  348. package/src/adapters/canonicalize-module.ts +29 -0
  349. package/src/adapters/cloudflare.ts +105 -63
  350. package/src/adapters/compress-module.ts +16 -5
  351. package/src/adapters/fs-identity.ts +2 -3
  352. package/src/adapters/nitro-presets.ts +0 -15
  353. package/src/adapters/nitro-preview.ts +272 -136
  354. package/src/adapters/nitro.ts +119 -82
  355. package/src/adapters/shared.ts +23 -14
  356. package/src/adapters/types.ts +110 -21
  357. package/src/analyze/graph-cache.ts +3 -4
  358. package/src/cache/json-lossy-check.ts +3 -9
  359. package/src/cache/singleflight.ts +5 -2
  360. package/src/cache/sizeof.ts +3 -1
  361. package/src/cache/tag-aware-handler.ts +43 -26
  362. package/src/cli-check.ts +8 -98
  363. package/src/cli-context.ts +119 -0
  364. package/src/cli.ts +19 -28
  365. package/src/client/browser-dev.ts +1 -1
  366. package/src/client/browser-entry/action-dispatch.ts +26 -4
  367. package/src/client/browser-entry/hydrate.ts +53 -58
  368. package/src/client/browser-entry/index.ts +16 -27
  369. package/src/client/browser-entry/post-hydration.ts +6 -40
  370. package/src/client/browser-entry/router-init.ts +66 -84
  371. package/src/client/error-boundary.tsx +9 -41
  372. package/src/client/error-reconstituter.tsx +10 -5
  373. package/src/client/history.ts +35 -21
  374. package/src/client/index.ts +0 -1
  375. package/src/client/internal.ts +2 -7
  376. package/src/client/navigation-commit.ts +41 -18
  377. package/src/client/navigation-context.ts +3 -39
  378. package/src/client/react-root.ts +3 -2
  379. package/src/client/router-pipeline.ts +96 -132
  380. package/src/client/router-types.ts +46 -44
  381. package/src/client/router-url.ts +32 -0
  382. package/src/client/router.ts +62 -21
  383. package/src/client/rsc-fetch.ts +117 -155
  384. package/src/client/segment-cache.ts +110 -11
  385. package/src/client/ssr-data.ts +19 -80
  386. package/src/client/state.ts +2 -9
  387. package/src/client/use-pathname.ts +1 -1
  388. package/src/config-types.ts +2 -1
  389. package/src/config-validation.ts +2 -1
  390. package/src/dev-tools/dev-page-shell.ts +18 -0
  391. package/src/dev-tools/index.ts +0 -2
  392. package/src/dev-tools/instrumentation.ts +50 -2
  393. package/src/dev-tools/overlay.ts +8 -238
  394. package/src/index.ts +26 -14
  395. package/src/plugin-context.ts +39 -40
  396. package/src/plugins/build-manifest.ts +4 -11
  397. package/src/plugins/build-report.ts +70 -70
  398. package/src/plugins/dev-server.ts +24 -44
  399. package/src/plugins/entries.ts +2 -1
  400. package/src/plugins/fonts.ts +35 -7
  401. package/src/plugins/post-build.ts +327 -0
  402. package/src/plugins/prebuilt-capture.ts +28 -32
  403. package/src/plugins/prebuilt.ts +54 -39
  404. package/src/plugins/prerender-sugar.ts +0 -4
  405. package/src/plugins/react-resolve.ts +149 -0
  406. package/src/plugins/{adapter-build.ts → rsc-assets-manifest.ts} +20 -109
  407. package/src/plugins/shims.ts +1 -0
  408. package/src/plugins/static-build.ts +42 -74
  409. package/src/routing/codegen-write.ts +25 -3
  410. package/src/routing/segment-codegen.ts +3 -2
  411. package/src/rsc-runtime/vendor-types.d.ts +8 -0
  412. package/src/server/access-gate.tsx +2 -1
  413. package/src/server/action-client.ts +1 -1
  414. package/src/server/action-handler.ts +253 -110
  415. package/src/server/actions.ts +73 -25
  416. package/src/server/als-registry.ts +73 -5
  417. package/src/server/chain-url-parts.ts +1 -1
  418. package/src/server/children-interception.ts +2 -1
  419. package/src/server/cookie-context.ts +18 -48
  420. package/src/server/csp-nonce.ts +0 -12
  421. package/src/server/csrf.ts +140 -8
  422. package/src/server/debug.ts +0 -1
  423. package/src/server/default-logger.ts +2 -1
  424. package/src/server/deny-boundary.ts +25 -13
  425. package/src/server/deny-renderer.ts +30 -97
  426. package/src/server/error-boundary-wrapper.ts +7 -3
  427. package/src/server/fallback-error.ts +26 -11
  428. package/src/server/flight-scripts.ts +1 -1
  429. package/src/server/head-response.ts +4 -4
  430. package/src/server/html-injector-core.ts +2 -2
  431. package/src/server/html-injectors.ts +2 -4
  432. package/src/server/index.ts +0 -4
  433. package/src/server/instrumentation.ts +10 -3
  434. package/src/server/internal.ts +1 -6
  435. package/src/server/json-status-response.ts +105 -0
  436. package/src/server/logger.ts +39 -3
  437. package/src/server/param-coercion.ts +15 -12
  438. package/src/server/pipeline-helpers.ts +257 -12
  439. package/src/server/pipeline-interception.ts +133 -0
  440. package/src/server/pipeline-outcome.ts +103 -31
  441. package/src/server/pipeline-phases.ts +127 -237
  442. package/src/server/pipeline.ts +204 -207
  443. package/src/server/prebuilt/capture-render.ts +118 -0
  444. package/src/server/prebuilt/capture-routes.ts +104 -0
  445. package/src/server/prebuilt/capture-state.ts +155 -48
  446. package/src/server/prebuilt/lifetime.ts +151 -0
  447. package/src/server/prebuilt/slots.ts +14 -2
  448. package/src/server/prebuilt/synthetic-store.ts +3 -0
  449. package/src/server/prebuilt-builder.ts +30 -320
  450. package/src/server/prebuilt-runtime.ts +125 -129
  451. package/src/server/primitives.ts +8 -168
  452. package/src/server/react-cache-bridge.ts +124 -0
  453. package/src/server/react-cache-scope.ts +104 -0
  454. package/src/server/render-timeout.ts +20 -0
  455. package/src/server/render-utils.ts +11 -1
  456. package/src/server/request-context.ts +36 -9
  457. package/src/server/route-element-builder.ts +11 -6
  458. package/src/server/route-handler.ts +36 -23
  459. package/src/server/route-matcher.ts +15 -1
  460. package/src/server/rsc-entry/action-middleware-runner.ts +79 -123
  461. package/src/server/rsc-entry/api-handler.ts +27 -103
  462. package/src/server/rsc-entry/deny-fallback.ts +93 -26
  463. package/src/server/rsc-entry/error-renderer.ts +39 -91
  464. package/src/server/rsc-entry/helpers.ts +2 -4
  465. package/src/server/rsc-entry/index.ts +64 -58
  466. package/src/server/rsc-entry/render-route.ts +15 -80
  467. package/src/server/rsc-entry/render-signals.ts +108 -0
  468. package/src/server/rsc-entry/revalidate-renderer.ts +56 -57
  469. package/src/server/rsc-entry/rsc-payload.ts +28 -61
  470. package/src/server/rsc-entry/rsc-stream.ts +113 -213
  471. package/src/server/rsc-entry/ssr-bridge.ts +2 -2
  472. package/src/server/rsc-entry/ssr-renderer.ts +182 -263
  473. package/src/server/rsc-entry/wrap-action-dispatch.ts +12 -14
  474. package/src/server/signal-identity.ts +112 -0
  475. package/src/server/slot-resolver.ts +18 -18
  476. package/src/server/slot-subtree-contain.ts +2 -1
  477. package/src/server/ssr-bridge-types.ts +92 -56
  478. package/src/server/ssr-entry.ts +87 -111
  479. package/src/server/ssr-render.ts +55 -64
  480. package/src/server/ssr-wrappers.tsx +2 -2
  481. package/src/server/static-generator.ts +7 -42
  482. package/src/server/static-not-found.ts +3 -3
  483. package/src/server/stream-utils.ts +51 -0
  484. package/src/server/tracing.ts +58 -75
  485. package/src/server/types.ts +1 -1
  486. package/src/server/version-skew.ts +5 -5
  487. package/src/shared/csrf-reject.ts +15 -0
  488. package/src/shared/error-digest.ts +46 -0
  489. package/src/shared/payload-root.ts +11 -10
  490. package/src/shared/rsc-payload-path.ts +8 -1
  491. package/src/utils/write-file-atomic.ts +32 -0
  492. package/dist/_chunks/actions-jNrXdzgs.js +0 -346
  493. package/dist/_chunks/actions-jNrXdzgs.js.map +0 -1
  494. package/dist/_chunks/als-registry-C6kcfprT.js.map +0 -1
  495. package/dist/_chunks/build-manifest-DTmSGLRz.js.map +0 -1
  496. package/dist/_chunks/build-output-helper-9dqCv1pZ.js.map +0 -1
  497. package/dist/_chunks/cache-api-CUD9Ezaq.js.map +0 -1
  498. package/dist/_chunks/cli-check-G8o30ldg.js.map +0 -1
  499. package/dist/_chunks/convention-lint-5KzoMg_A.js.map +0 -1
  500. package/dist/_chunks/dev-server-BzBxgYAV.js.map +0 -1
  501. package/dist/_chunks/error-boundary-BndF-3Td.js.map +0 -1
  502. package/dist/_chunks/fs-identity-D7vb6GdI.js.map +0 -1
  503. package/dist/_chunks/graph-cache-CP4GEmf9.js.map +0 -1
  504. package/dist/_chunks/json-lossy-check-ClNvBM_3.js.map +0 -1
  505. package/dist/_chunks/logger-k2DQ4EUf.js.map +0 -1
  506. package/dist/_chunks/navigation-context-D0TU0Jog.js.map +0 -1
  507. package/dist/_chunks/plugin-context-8idfR3UZ.js.map +0 -1
  508. package/dist/_chunks/primitives-DLAnvsrk.js.map +0 -1
  509. package/dist/_chunks/segment-context-D9_89u34.js +0 -34
  510. package/dist/_chunks/segment-context-D9_89u34.js.map +0 -1
  511. package/dist/_chunks/sizeof-lXffQ1Vr.js.map +0 -1
  512. package/dist/_chunks/ssr-data-D6T6Y3ef.js +0 -60
  513. package/dist/_chunks/ssr-data-D6T6Y3ef.js.map +0 -1
  514. package/dist/_chunks/state-FippDgxN.js.map +0 -1
  515. package/dist/_chunks/status-page-marker-DwQBrLBz.js.map +0 -1
  516. package/dist/client/types.d.ts +0 -14
  517. package/dist/client/types.d.ts.map +0 -1
  518. package/dist/plugins/adapter-build.d.ts +0 -38
  519. package/dist/plugins/adapter-build.d.ts.map +0 -1
  520. package/dist/server/asset-headers.d.ts +0 -32
  521. package/dist/server/asset-headers.d.ts.map +0 -1
  522. package/dist/server/dev-source-map.d.ts +0 -22
  523. package/dist/server/dev-source-map.d.ts.map +0 -1
  524. package/src/client/types.ts +0 -21
  525. package/src/server/asset-headers.ts +0 -59
  526. package/src/server/dev-source-map.ts +0 -31
@@ -1 +0,0 @@
1
- {"version":3,"file":"primitives-DLAnvsrk.js","names":[],"sources":["../../src/server/waituntil-bridge.ts","../../src/server/debug.ts","../../src/server/cookie-parsing.ts","../../src/server/cookie-context.ts","../../src/server/request-context.ts","../../src/server/primitives.ts"],"sourcesContent":["/**\n * Per-request waitUntil bridge — ALS bridge for platform adapters.\n *\n * The generated entry point (Nitro, Cloudflare) wraps the handler with\n * `runWithWaitUntil`, binding the platform's lifecycle extension function\n * (e.g., h3's `event.waitUntil()` or CF's `ctx.waitUntil()`) for the\n * request duration. The `waitUntil()` primitive reads from this ALS to\n * dispatch background work to the correct platform API.\n *\n * Design doc: design/11-platform.md §\"waitUntil()\"\n */\n\nimport { waitUntilAls } from './als-registry.ts';\n\n/**\n * Run a function with a per-request waitUntil handler installed.\n *\n * Called by generated entry points (Nitro node-server/bun, Cloudflare)\n * to bind the platform's lifecycle extension for the request duration.\n */\nexport function runWithWaitUntil<T>(\n waitUntilFn: (promise: Promise<unknown>) => void,\n fn: () => T\n): T {\n return waitUntilAls.run(waitUntilFn, fn);\n}\n\n/**\n * Get the current request's waitUntil function, if available.\n *\n * Returns undefined when no platform adapter has installed a waitUntil\n * handler for the current request (e.g., on platforms that don't support\n * lifecycle extension, or outside a request context).\n */\nexport function getWaitUntil(): ((promise: Promise<unknown>) => void) | undefined {\n return waitUntilAls.getStore();\n}\n","/**\n * Runtime debug flag for timber.js.\n *\n * Two distinct functions for two distinct security levels:\n *\n * ## `isDebug()` — server-side logging only\n *\n * Returns true when timber's debug/warning messages should be written to\n * stderr / the server console. This NEVER affects what is sent to the\n * client (no error details, no timing headers, no stack traces).\n *\n * Active when any of:\n * - `NODE_ENV !== 'production'` (standard dev mode)\n * - `TIMBER_DEBUG` env var is set to a truthy value at runtime\n * - `timber.config.ts` has `debug: true`\n *\n * ## `isDevMode()` — client-visible dev behavior\n *\n * Returns true ONLY when `NODE_ENV !== 'production'`. This gates anything\n * that changes what clients can observe:\n * - Dev error pages with stack traces (fallback-error.ts)\n * - Detailed Server-Timing headers (pipeline.ts)\n * - Error messages in action INTERNAL_ERROR payloads (action-client.ts)\n * - Pipeline error handler wiring (Vite overlay)\n *\n * `isDevMode()` is statically replaced in production builds → the guarded\n * code is tree-shaken to zero bytes. TIMBER_DEBUG cannot enable it.\n *\n * Usage:\n * In Cloudflare Workers wrangler.toml:\n * [vars]\n * TIMBER_DEBUG = \"1\"\n *\n * In Node.js:\n * TIMBER_DEBUG=1 node server.js\n *\n * In timber.config.ts:\n * export default { debug: true }\n *\n * See design/13-security.md for the security taxonomy.\n * See design/18-build-system.md for build pipeline details.\n */\n\n// ─── Dev Mode (client-visible) ──────────────────────────────────────────────\n\n/**\n * Check if the application is running in development mode.\n *\n * This is the ONLY function that should gate client-visible dev behavior:\n * - Dev error pages with stack traces\n * - Server-Timing mode default (`'detailed'` in dev, `'total'` in prod)\n * - Error messages in action `INTERNAL_ERROR` payloads\n * - Pipeline error handler wiring (Vite overlay)\n *\n * Returns `process.env.NODE_ENV !== 'production'`, which is statically\n * replaced by the bundler in production builds. Code guarded by this\n * function is tree-shaken to zero bytes in production.\n *\n * TIMBER_DEBUG does NOT enable this — that would leak server internals\n * to clients. Use `isDebug()` for server-side-only logging.\n */\nexport function isDevMode(): boolean {\n return process.env.NODE_ENV !== 'production';\n}\n\n// ─── Debug Flag (server-side logging only) ──────────────────────────────────\n\n/**\n * Config-level debug override. Set via `setDebugFromConfig()` during\n * initialization when timber.config.ts has `debug: true`.\n */\nlet _configDebug = false;\n\n/**\n * Set the debug flag from timber.config.ts.\n * Called during handler initialization.\n */\nexport function setDebugFromConfig(debug: boolean): void {\n _configDebug = debug;\n}\n\n/**\n * Check if timber debug logging is active (server-side only).\n *\n * Returns true if ANY of these conditions hold:\n * - NODE_ENV is not 'production' (standard dev mode)\n * - TIMBER_DEBUG environment variable is set to a truthy value at runtime\n * - timber.config.ts has `debug: true`\n *\n * This function controls ONLY server-side logging — messages written to\n * stderr or the server console. It NEVER affects client-visible behavior\n * (error pages, response headers, action payloads). For client-visible\n * behavior, use `isDevMode()`.\n *\n * The TIMBER_DEBUG check is deliberately written as a dynamic property\n * access so bundlers cannot statically replace it.\n */\nexport function isDebug(): boolean {\n // Fast path: dev mode (statically replaced to `true` in dev, `false` in prod)\n if (process.env.NODE_ENV !== 'production') return true;\n\n // Config override\n if (_configDebug) return true;\n\n // Runtime env var check — uses dynamic access to prevent static replacement.\n // In production builds, process.env.NODE_ENV is statically replaced, but\n // TIMBER_DEBUG must survive as a runtime check. The dynamic key access\n // pattern ensures the bundler treats this as opaque.\n return _readTimberDebugEnv();\n}\n\n/**\n * Read TIMBER_DEBUG from the environment at runtime.\n *\n * Extracted to a separate function to:\n * 1. Prevent bundler inlining (cross-module function calls are not inlined)\n * 2. Handle platforms where `process` may not exist (Cloudflare Workers)\n * 3. Support globalThis.__TIMBER_DEBUG for programmatic control\n */\nfunction _readTimberDebugEnv(): boolean {\n // globalThis override — useful for programmatic control and testing\n if ((globalThis as Record<string, unknown>).__TIMBER_DEBUG) return true;\n\n // process.env — works in Node.js and platforms that polyfill process\n try {\n const key = 'TIMBER_DEBUG';\n const val =\n typeof process !== 'undefined' && process.env\n ? (process.env as Record<string, string | undefined>)[key]\n : undefined;\n if (val && val !== '0' && val !== 'false') return true;\n } catch {\n // process may not exist or env may throw — safe to ignore\n }\n\n return false;\n}\n","/**\n * Cookie parsing and serialization helpers — pure string ↔ structure\n * functions with no ALS dependency. Split out of `cookie-context.ts`\n * (TIM-853) so the API surface and the wire-format codecs can each be\n * read on their own.\n *\n * The functions in this module are total over arbitrary input. They\n * never throw and never call `assertValid*` (the security validators\n * live in the API surface — `cookie-context.ts` invokes them at every\n * jar entry point so the smuggling-primitive invariant from TIM-868\n * is enforced regardless of which path produced the bytes).\n *\n * Delegates to the `cookie` package (RFC 6265, dependency-free,\n * browser-safe) for wire codecs. Adapts at the boundary to preserve\n * timber's Map-based API and never-throw contract.\n */\n\nimport { parseCookie, parseSetCookie as upstreamParseSetCookie, stringifySetCookie } from 'cookie';\n\nimport type { CookieEntry } from './als-registry.ts';\nimport type { CookieOptions } from './cookie-context.ts';\n\n/**\n * Parse a Cookie header string into a Map of name → value pairs.\n * Follows RFC 6265 §4.2.1: cookies are semicolon-separated key=value pairs.\n *\n * Values are auto-decoded with `decodeURIComponent` so they round-trip\n * losslessly with `getCookies().set()` (which auto-encodes). Malformed\n * `%`-escapes from third-party cookies fall back to the raw byte sequence\n * — the parser must be total over arbitrary inbound headers, including\n * non-conforming values from other servers, browser extensions, etc.\n */\nexport function parseCookieHeader(header: string): Map<string, string> {\n const map = new Map<string, string>();\n if (!header) return map;\n\n // cookie.parseCookie returns a plain object with first-wins semantics\n // and uses safeDecodeURIComponent by default (try/catch around\n // decodeURIComponent, falls back to raw). This matches our contract.\n const parsed = parseCookie(header);\n for (const name in parsed) {\n const value = parsed[name];\n if (value !== undefined) map.set(name, value);\n }\n\n return map;\n}\n\n/**\n * Decode a single cookie value with `decodeURIComponent`, falling back to\n * the raw byte sequence if the input contains a malformed `%`-escape.\n *\n * Used by both `parseCookieHeader` (incoming Cookie: header) and the\n * `setRaw` forwarding path (outgoing Set-Cookie from upstream services).\n * Total — never throws.\n *\n * cookie's internal decode has identical semantics but is not exported,\n * so we keep this standalone helper.\n */\nexport function safeDecodeCookieValue(raw: string): string {\n try {\n return decodeURIComponent(raw);\n } catch {\n return raw;\n }\n}\n\n/**\n * Serialize a CookieEntry into a Set-Cookie header value.\n *\n * Total — never throws. If the cookie package's serializer rejects\n * the input (e.g. non-integer maxAge, forbidden chars in name/value),\n * falls back to a minimal hand-rolled serialization so the response\n * path is never interrupted by a codec error.\n */\nexport function serializeCookieEntry(entry: CookieEntry): string {\n try {\n return stringifySetCookie(\n {\n name: entry.name,\n value: entry.value,\n domain: entry.options.domain,\n path: entry.options.path,\n expires: entry.options.expires,\n maxAge: entry.options.maxAge,\n httpOnly: entry.options.httpOnly,\n secure: entry.options.secure,\n sameSite: entry.options.sameSite,\n partitioned: entry.options.partitioned,\n },\n // timber pre-encodes values at the API surface (set() calls\n // encodeURIComponent), so pass identity to avoid double-encoding.\n { encode: (v: string) => v }\n );\n } catch {\n // Fallback: hand-roll a minimal Set-Cookie so we never drop the\n // header entirely. The entry-point validators in cookie-context.ts\n // already reject truly dangerous input — this path only fires for\n // edge-case formatting the upstream serializer is strict about.\n return serializeCookieEntryFallback(entry);\n }\n}\n\nfunction serializeCookieEntryFallback(entry: CookieEntry): string {\n const parts = [`${entry.name}=${entry.value}`];\n const opts = entry.options;\n\n if (opts.domain) parts.push(`Domain=${opts.domain}`);\n if (opts.path) parts.push(`Path=${opts.path}`);\n if (opts.expires) parts.push(`Expires=${opts.expires.toUTCString()}`);\n if (opts.maxAge !== undefined) parts.push(`Max-Age=${opts.maxAge}`);\n if (opts.httpOnly) parts.push('HttpOnly');\n if (opts.secure) parts.push('Secure');\n if (opts.sameSite) {\n parts.push(`SameSite=${opts.sameSite.charAt(0).toUpperCase()}${opts.sameSite.slice(1)}`);\n }\n if (opts.partitioned) parts.push('Partitioned');\n\n return parts.join('; ');\n}\n\n/**\n * Parse a raw `Set-Cookie` header string into name, value, and options.\n * Handles all standard attributes: Path, Domain, Max-Age, Expires,\n * SameSite, Secure, HttpOnly, Partitioned.\n *\n * Does NOT apply DEFAULT_COOKIE_OPTIONS — the caller decides whether\n * to merge defaults (e.g. `set()` does, but `setRaw()` should preserve\n * the original header's intent).\n */\nexport function parseSetCookie(\n header: string\n): { name: string; value: string; options: CookieOptions } | null {\n const parsed = upstreamParseSetCookie(header, {\n // Don't decode — setRaw expects the wire-form value so it can\n // re-emit verbatim and validate against cookie-octet.\n decode: (v: string) => v,\n });\n\n // cookie.parseSetCookie always returns a SetCookie object, but\n // with name/value as empty strings when the header has no `=`.\n if (!parsed.name) return null;\n\n const options: CookieOptions = {};\n\n if (parsed.path !== undefined) options.path = parsed.path || '/';\n if (parsed.domain !== undefined) options.domain = parsed.domain;\n if (parsed.maxAge !== undefined && Number.isFinite(parsed.maxAge)) {\n options.maxAge = parsed.maxAge;\n }\n if (parsed.expires !== undefined) options.expires = parsed.expires;\n if (parsed.sameSite !== undefined && parsed.sameSite !== true) {\n const sameSite = (parsed.sameSite as string).toLowerCase();\n if (sameSite === 'strict' || sameSite === 'lax' || sameSite === 'none') {\n options.sameSite = sameSite;\n }\n }\n if (parsed.secure) options.secure = true;\n if (parsed.httpOnly) options.httpOnly = true;\n if (parsed.partitioned) options.partitioned = true;\n\n return { name: parsed.name, value: parsed.value ?? '', options };\n}\n","/**\n * Cookie Context — per-request cookie API and on-the-wire helpers.\n *\n * Split out of `request-context.ts` (TIM-853) so the cookie subsystem\n * — encoding contract, options grammar, parser, serializer, RYW map,\n * and rerender seed — lives in one file. The headers/scope/params APIs\n * stay in `request-context.ts` and call into this module via the\n * exported helpers.\n *\n * See design/29-cookies.md for the encoding contract and read-your-own-\n * writes semantics. See ONGOING_SECURITY.md H-3 (TIM-868) for the\n * smuggling primitive that the encoding contract closes.\n */\n\nimport { requestContextAls, type RequestContextStore } from './als-registry.ts';\nimport { isDebug } from './debug.ts';\nimport {\n assertValidCookieName,\n assertValidCookieValue,\n assertValidCookieOptions,\n} from '../cookies/validation.ts';\nimport {\n parseCookieHeader,\n parseSetCookie,\n safeDecodeCookieValue,\n serializeCookieEntry,\n} from './cookie-parsing.ts';\n\n// Re-export the validators so framework-internal consumers and tests can\n// import the canonical implementation from the same module that hosts\n// `getCookies()`. The pure shared module lives in `cookies/validation.ts`\n// so the client `useCookie` hook can use the same checks without pulling\n// in the server ALS code.\nexport { assertValidCookieName, assertValidCookieValue, assertValidCookieOptions };\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * Returns a cookie accessor for the current request.\n *\n * Available in middleware, access checks, server components, and server actions.\n * Throws if called outside a request context (security principle #2: no global fallback).\n *\n * Read methods (.get, .has, .getAll) are always available and reflect\n * read-your-own-writes from .set() calls in the same request.\n *\n * Mutation methods (.set, .delete, .clear) are only available in mutable\n * contexts (middleware.ts, server actions, route.ts handlers). Calling them\n * in read-only contexts (access.ts, server components) throws.\n *\n * This is the escape hatch for direct cookie jar operations. For typed\n * cookie access, use `defineCookie()` instead.\n *\n * See design/29-cookies.md\n */\nexport function getCookieJar(): RequestCookies {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] getCookieJar() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n\n // Parse cookies lazily on first access\n if (!store.parsedCookies) {\n store.parsedCookies = parseCookieHeader(store.cookieHeader);\n }\n\n const map = store.parsedCookies;\n return {\n get(name: string): string | undefined {\n return map.get(name);\n },\n has(name: string): boolean {\n return map.has(name);\n },\n getAll(): Array<{ name: string; value: string }> {\n return Array.from(map.entries()).map(([name, value]) => ({ name, value }));\n },\n get size(): number {\n return map.size;\n },\n\n set(name: string, value: string, options?: SetCookieOptions): void {\n assertMutable(store, 'set');\n // Validate the name first — names cannot be URL-encoded (RFC 7230\n // token grammar is strict), so a bad name is always a bug.\n assertValidCookieName(name);\n // Type guard with a guiding error. The single most common mistake\n // is passing a non-string (object, number, Date) and expecting the\n // framework to JSON-encode. Point developers at jsonCookieCodec.\n if (typeof value !== 'string') {\n throw new Error(\n `[timber] getCookieJar().set(${JSON.stringify(name)}, …): value must be a string, got ${typeof value}.\\n` +\n ` To store a JSON-serializable value, use defineCookie + jsonCookieCodec:\\n` +\n `\\n` +\n ` import { defineCookie, jsonCookieCodec } from '@timber-js/app/cookies';\\n` +\n `\\n` +\n ` export const ${name}Cookie = defineCookie(${JSON.stringify(name)}, {\\n` +\n ` codec: jsonCookieCodec(),\\n` +\n ` });\\n` +\n `\\n` +\n ` ${name}Cookie.set(value);\\n` +\n `\\n` +\n ` See design/29-cookies.md §\"Typed Cookies with Schema Validation\".`\n );\n }\n // Encode the value so the on-the-wire bytes always satisfy\n // RFC 6265 §4.1.1 cookie-octet. encodeURIComponent's output is a\n // strict subset of cookie-octet (only `A-Z a-z 0-9 ! ' ( ) * - . _\n // ~ %`), so the encoded form can never carry the H-3 smuggling\n // primitive — `;` becomes `%3B`, CR/LF become `%0D`/`%0A`, etc.\n // The round-trip is lossless: parseCookieHeader auto-decodes on\n // read, so `cookies().get(name)` returns exactly `value`. See\n // ONGOING_SECURITY.md H-3 (TIM-868) and design/29-cookies.md\n // §\"Encoding Contract\".\n //\n // The `{ raw: true }` opt-out skips the encoder for callers who\n // need exact byte control (e.g. forwarding pre-encoded cookies\n // from an upstream service). The opt-out goes through the strict\n // cookie-octet validator instead — the smuggling primitive cannot\n // sneak in via the escape hatch.\n const raw = options?.raw === true;\n const wireValue = raw ? value : encodeURIComponent(value);\n if (raw) {\n assertValidCookieValue(name, wireValue);\n }\n if (store.flushed) {\n if (isDebug()) {\n console.warn(\n `[timber] warn: getCookieJar().set('${name}') called after response headers were committed.\\n` +\n ` The cookie will NOT be sent. Move cookie mutations to middleware.ts, a server action,\\n` +\n ` or a route.ts handler.`\n );\n }\n return;\n }\n // Strip the framework-only `raw` flag before persisting — it is\n // not an HTTP cookie attribute and must not leak into the jar.\n const { raw: _raw, ...attributeOptions } = options ?? {};\n void _raw;\n const opts = { ...DEFAULT_COOKIE_OPTIONS, ...attributeOptions };\n assertValidCookieOptions(opts);\n store.cookieJar.set(name, { name, value: wireValue, options: opts });\n // Read-your-own-writes: store the DECODED logical value so that\n // subsequent `cookies().get(name)` in the same request returns\n // exactly what the developer wrote — never the encoded form.\n // For `{ raw: true }`, the wire form IS the logical form.\n map.set(name, raw ? wireValue : value);\n },\n\n setFromHeaders(headers: Headers): void {\n assertMutable(store, 'setFromHeaders');\n if (store.flushed) {\n console.warn(\n `[timber] warn: getCookieJar().setFromHeaders() called after response headers were committed.\\n` +\n ` The cookies will NOT be sent. Move cookie mutations to middleware.ts, a server action,\\n` +\n ` or a route.ts handler.`\n );\n return;\n }\n // Headers.getSetCookie() returns individual Set-Cookie strings,\n // avoiding the fragile comma-splitting that raw .get() requires.\n for (const raw of headers.getSetCookie()) {\n const parsed = parseSetCookie(raw);\n if (parsed) {\n // Use setRaw to preserve the original header's attributes without\n // merging DEFAULT_COOKIE_OPTIONS (parseSetCookie intentionally\n // does not apply defaults — see its doc comment).\n setRaw(store, map, parsed.name, parsed.value, parsed.options);\n }\n }\n },\n\n delete(name: string, options?: Pick<CookieOptions, 'path' | 'domain'>): void {\n assertMutable(store, 'delete');\n // Validate the name even though delete writes an empty value — a\n // smuggled name (`;` or CR/LF) would corrupt the Set-Cookie header\n // we emit. See ONGOING_SECURITY.md H-3 (TIM-868).\n assertValidCookieName(name);\n if (store.flushed) {\n if (isDebug()) {\n console.warn(\n `[timber] warn: getCookieJar().delete('${name}') called after response headers were committed.\\n` +\n ` The cookie will NOT be deleted. Move cookie mutations to middleware.ts, a server action,\\n` +\n ` or a route.ts handler.`\n );\n }\n return;\n }\n const opts: CookieOptions = {\n ...DEFAULT_COOKIE_OPTIONS,\n ...options,\n maxAge: 0,\n expires: new Date(0),\n };\n // delete() is a jar entry point like set() — caller-supplied\n // path/domain are serialized verbatim into the Set-Cookie header,\n // so they must pass the same attribute-injection guard. See\n // design/29-cookies.md §\"Cookie Attribute Validation\" (TIM-1026).\n assertValidCookieOptions(opts);\n store.cookieJar.set(name, { name, value: '', options: opts });\n // Remove from read view\n map.delete(name);\n },\n\n clear(): void {\n assertMutable(store, 'clear');\n if (store.flushed) return;\n // Delete every incoming cookie\n for (const name of Array.from(map.keys())) {\n store.cookieJar.set(name, {\n name,\n value: '',\n options: { ...DEFAULT_COOKIE_OPTIONS, maxAge: 0, expires: new Date(0) },\n });\n }\n map.clear();\n },\n\n toString(): string {\n // Re-encode values when serializing as a Cookie header — the\n // RYW map holds decoded logical values, but a Cookie header has\n // to satisfy `cookie-octet`. Mirror the auto-encode contract on\n // `set()` so toString() round-trips losslessly with parseCookieHeader.\n return Array.from(map.entries())\n .map(([name, value]) => `${name}=${encodeURIComponent(value)}`)\n .join('; ');\n },\n };\n}\n\n/**\n * Returns the value of a single cookie, or undefined if absent.\n *\n * @internal — not part of the public API. Use `defineCookie().get()` or `getCookieJar().get()` instead.\n */\nexport function getCookie(name: string): string | undefined {\n const jar = getCookieJar();\n return jar.get(name);\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\n/**\n * Per-call options for `getCookies().set()`. Extends the persistent\n * `CookieOptions` (HTTP cookie attributes) with framework-only flags\n * that are NOT serialized into the Set-Cookie header.\n *\n * The `raw` flag is the escape hatch for the auto-encoding contract.\n * See design/29-cookies.md §\"Encoding Contract\".\n */\nexport interface SetCookieOptions extends CookieOptions {\n /**\n * Skip the framework's `encodeURIComponent` pass and store the value\n * verbatim. The value is then validated against the strict RFC 6265\n * §4.1.1 `cookie-octet` grammar — the H-3 smuggling primitive cannot\n * sneak in via this opt-out.\n *\n * Use this when forwarding a cookie value that is already in its\n * intended on-the-wire form (e.g. mirroring an upstream service's\n * Set-Cookie). Default: `false` (auto-encode).\n */\n raw?: boolean;\n}\n\n/** Options for setting a cookie. See design/29-cookies.md. */\nexport interface CookieOptions {\n /** Domain scope. Default: omitted (current domain only). */\n domain?: string;\n /** URL path scope. Default: '/'. */\n path?: string;\n /** Expiration date. Mutually exclusive with maxAge. */\n expires?: Date;\n /** Max age in seconds. Mutually exclusive with expires. */\n maxAge?: number;\n /** Prevent client-side JS access. Default: true. */\n httpOnly?: boolean;\n /** Only send over HTTPS. Default: true. */\n secure?: boolean;\n /** Cross-site request policy. Default: 'lax'. */\n sameSite?: 'strict' | 'lax' | 'none';\n /** Partitioned (CHIPS) — isolate cookie per top-level site. Default: false. */\n partitioned?: boolean;\n}\n\n/**\n * Cookie accessor returned by `getCookies()`.\n *\n * Read methods are always available. Mutation methods throw in read-only\n * contexts (access.ts, server components).\n */\nexport interface RequestCookies {\n /** Get a cookie value by name. Returns undefined if not present. */\n get(name: string): string | undefined;\n /** Check if a cookie exists. */\n has(name: string): boolean;\n /** Get all cookies as an array of { name, value } pairs. */\n getAll(): Array<{ name: string; value: string }>;\n /** Number of cookies. */\n readonly size: number;\n /**\n * Set a cookie. Only available in mutable contexts (middleware, actions,\n * route handlers).\n *\n * The value is auto-encoded with `encodeURIComponent` so the on-the-wire\n * bytes always satisfy RFC 6265 §4.1.1 cookie-octet — `cookies().get()`\n * returns the same logical value the developer wrote. Pass `{ raw: true }`\n * to skip the encoder; the raw path validates against the strict\n * cookie-octet grammar instead. See design/29-cookies.md §\"Encoding\n * Contract\" and ONGOING_SECURITY.md H-3 (TIM-868).\n */\n set(name: string, value: string, options?: SetCookieOptions): void;\n /**\n * Copy all `Set-Cookie` headers from a `Headers` object.\n * Parses each header and forwards name, value, and all attributes\n * (path, domain, max-age, expires, sameSite, secure, httpOnly, partitioned).\n *\n * Useful when forwarding cookies from an internal `fetch()` or auth handler:\n * ```ts\n * const response = await auth.handler(req);\n * getCookies().then(c => c.setFromHeaders(response.headers));\n * ```\n */\n setFromHeaders(headers: Headers): void;\n /** Delete a cookie. Only available in mutable contexts. */\n delete(name: string, options?: Pick<CookieOptions, 'path' | 'domain'>): void;\n /** Delete all cookies. Only available in mutable contexts. */\n clear(): void;\n /** Serialize cookies as a Cookie header string. */\n toString(): string;\n}\n\nconst DEFAULT_COOKIE_OPTIONS: CookieOptions = {\n path: '/',\n httpOnly: true,\n secure: true,\n sameSite: 'lax',\n};\n\n// ─── Framework-Internal Helpers ───────────────────────────────────────────\n\n/**\n * Per-request seed map of cookie name → value, registered out-of-band so the\n * pipeline's `runWithRequestContext` call can pick it up without changing\n * the function's calling convention. Stored in a WeakMap keyed by the\n * synthetic Request object built for the rerender path, so the seed lives\n * exactly as long as the request and is collected with it.\n *\n * The seed exists to eliminate the parse/serialize round-trip on the no-JS\n * form-rerender path — see ONGOING_SECURITY.md H-3 (TIM-868). The action's\n * post-mutation RYW snapshot is threaded directly into the rerender scope's\n * `parsedCookies` map, bypassing `parseCookieHeader` entirely.\n */\nconst seededRequestCookies = new WeakMap<Request, Map<string, string>>();\n\n/**\n * Register a pre-parsed cookie map to use as the request context seed for\n * the next `runWithRequestContext(req, …)` call with this exact `req`.\n *\n * Used by the no-JS form-rerender dispatcher in\n * `rsc-entry/wrap-action-dispatch.ts` to thread the action's post-mutation\n * cookie state into the rerender scope without serializing back through a\n * `Cookie:` header. See TIM-868 / TIM-837.\n *\n * @internal — framework use only.\n */\nexport function seedRequestCookies(req: Request, cookies: Map<string, string>): void {\n // Defensive copy: callers must not be able to mutate the rerender scope's\n // map after seeding, and the rerender scope must not mutate the snapshot\n // we hand back to other observers.\n seededRequestCookies.set(req, new Map(cookies));\n}\n\n/**\n * Pop the seed (if any) for `req` and return it. Called from\n * `runWithRequestContext` exactly once per request — the seed is consumed\n * eagerly so it cannot leak into a hypothetical future re-use of the same\n * Request reference.\n *\n * @internal — framework use only.\n */\nexport function consumeSeededCookies(req: Request): Map<string, string> | undefined {\n const seed = seededRequestCookies.get(req);\n if (seed) seededRequestCookies.delete(req);\n return seed;\n}\n\n/**\n * Build a Map of cookie name → value reflecting the current request's\n * read-your-own-writes state. Includes incoming cookies plus any\n * mutations from getCookies().set() / getCookies().delete() in the same request.\n *\n * Used by SSR renderers to populate NavContext.cookies so that\n * useCookie()'s server snapshot matches the actual response state.\n *\n * See design/29-cookies.md §\"Read-Your-Own-Writes\"\n * See design/triage/TIM-441-cookie-api-triage.md §4\n */\nexport function getCookiesForSsr(): Map<string, string> {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error('[timber] getCookiesForSsr() called outside of a request context.');\n }\n\n // Trigger lazy parsing if not yet done\n if (!store.parsedCookies) {\n store.parsedCookies = parseCookieHeader(store.cookieHeader);\n }\n\n // The parsedCookies map already reflects read-your-own-writes:\n // - getCookies().set() updates the map via map.set(name, value)\n // - getCookies().delete() removes from the map via map.delete(name)\n // Return a copy so callers can't mutate the internal map.\n return new Map(store.parsedCookies);\n}\n\n/**\n * Collect all Set-Cookie headers from the cookie jar.\n * Called by the framework at flush time to apply cookies to the response.\n *\n * Returns an array of serialized Set-Cookie header values.\n */\nexport function getSetCookieHeaders(): string[] {\n const store = requestContextAls.getStore();\n if (!store) return [];\n return Array.from(store.cookieJar.values()).map(serializeCookieEntry);\n}\n\n// ─── Cookie Helpers ───────────────────────────────────────────────────────\n\n/** Throw if cookie mutation is attempted in a read-only context. */\nfunction assertMutable(store: RequestContextStore, method: string): void {\n if (!store.mutableContext) {\n throw new Error(\n `[timber] getCookieJar().${method}() cannot be called in this context.\\n` +\n ` Set cookies in middleware.ts, server actions, or route.ts handlers.`\n );\n }\n}\n\n/**\n * Write a cookie to the jar WITHOUT merging DEFAULT_COOKIE_OPTIONS.\n * Used by setFromHeaders to preserve the original header's attributes exactly.\n *\n * For deletion cookies (maxAge=0), the jar entry is still created so the\n * Set-Cookie header is emitted, but the cookie is NOT added to the read map\n * (it would be misleading — the cookie is being deleted).\n */\nfunction setRaw(\n store: RequestContextStore,\n readMap: Map<string, string>,\n name: string,\n value: string,\n options: CookieOptions\n): void {\n // setRaw is the forwarding path for upstream Set-Cookie headers\n // (`getCookies().setFromHeaders(response.headers)`). The value comes\n // out of `parseSetCookie` in its on-the-wire form — already encoded\n // by whoever produced it — so we re-emit it verbatim into our jar.\n // We DO validate against the strict cookie-octet grammar, both as\n // defense-in-depth against a malicious upstream and to keep the\n // ONGOING_SECURITY.md H-3 invariant intact at every entry point into\n // the cookie jar / RYW map.\n assertValidCookieName(name);\n assertValidCookieValue(name, value);\n // Options get the same entry-point guard as set()/delete(). For the\n // setFromHeaders path this can only fire on attributes parseSetCookie\n // cannot produce (it splits on `;` and normalizes Max-Age/SameSite),\n // but setRaw is the jar entry point — the invariant lives here, not in\n // the caller. See design/29-cookies.md §\"Cookie Attribute Validation\".\n assertValidCookieOptions(options);\n store.cookieJar.set(name, { name, value, options });\n // Deletion cookies (Max-Age=0) should not appear in the read map.\n if (options.maxAge === 0) {\n readMap.delete(name);\n } else {\n // Decode for the RYW map so consumers see the same logical bytes\n // they would see if the upstream cookie had arrived on the next\n // request. Mirrors `parseCookieHeader`'s auto-decode.\n readMap.set(name, safeDecodeCookieValue(value));\n }\n}\n","/**\n * Request Context — per-request ALS store for headers, search params,\n * segment params, and request scope lifecycle.\n *\n * Follows the same pattern as tracing.ts: a module-level AsyncLocalStorage\n * instance, public accessor functions that throw outside request scope,\n * and a framework-internal `runWithRequestContext()` to establish scope.\n *\n * Cookie state lives in `cookie-context.ts` (split out in TIM-853). The\n * scope set up here owns the cookie jar / parsedCookies fields on the\n * store, but the cookie API and helpers are in the cookie module.\n *\n * See design/04-authorization.md §\"AccessContext does not include cookies or headers\"\n * and design/11-platform.md §\"AsyncLocalStorage\".\n */\n\nimport type { CoercedParams } from '../shared/param-value.ts';\nimport { requestContextAls, type RequestContextStore } from './als-registry.ts';\nimport { _setGetSearchParamsFn, _setGetSegmentParamsFn } from '../shared/als-slots.ts';\nimport { consumeSeededCookies } from './cookie-context.ts';\nimport { appVisibleSearch } from '../shared/rsc-cache-key.ts';\nimport {\n mergeSlotParams,\n resolveSegmentParams,\n type SlotParamsRecord,\n} from '../shared/slot-params.ts';\nimport { isDebug } from './debug.ts';\n\n// Re-export the ALS for framework-internal consumers that need direct access.\nexport { requestContextAls };\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * Returns a read-only view of the current request's headers.\n *\n * Available in middleware, access checks, server components, and server actions.\n * Throws if called outside a request context (security principle #2: no global fallback).\n */\nexport function getHeaders(): ReadonlyHeaders {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] getHeaders() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n\n return store.headers;\n}\n\n/**\n * Returns the value of a single request header, or undefined if absent.\n *\n * Thin wrapper over `getHeaders().get(name)` for the common case where\n * you need exactly one header.\n *\n * @internal — not part of the public API. Use `getHeaders().get(name)` instead.\n */\nexport function getHeader(name: string): string | undefined {\n const headers = getHeaders();\n return headers.get(name) ?? undefined;\n}\n\n/**\n * Returns the current request's raw URLSearchParams.\n *\n * @internal — not part of the public API. Use `defineSearchParams().get()` instead.\n */\nexport function getSearchParams(): URLSearchParams {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] getSearchParams() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n\n return store.searchParams;\n}\n\n// Eagerly register getSearchParams with the search-params module so\n// searchParams.get() can call it without a dynamic import.\n// Dynamic imports lose ALS context in React's RSC Flight renderer,\n// breaking getSearchParams() in parallel slot pages. See TIM-523.\n_setGetSearchParamsFn(getSearchParams);\n\n// Eagerly register getSegmentParams with the segment-params module so\n// segmentParams.get() can call it without a dynamic import.\n// Same pattern as search params — dynamic imports lose ALS context. See TIM-523.\n_setGetSegmentParamsFn(getSegmentParams);\n\n/**\n * Returns the current request's coerced segment params.\n *\n * The optional `segmentPath` argument exists only for TypeScript narrowing —\n * it does not affect the runtime return value. Pass a segment path\n * (e.g. `'/(browse)/[artistSlug]/[year]'`) to narrow the return type to the\n * exact params shape for that segment.\n *\n * Without an argument, returns all coerced params with optional types.\n *\n * See design/41-global-params.md §Function Signatures\n */\nexport function getSegmentParams(segmentPath?: string): CoercedParams {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] getSegmentParams() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n if (!store.segmentParams) {\n throw new Error(\n '[timber] getSegmentParams() called before route matching completed. ' +\n 'Segment params are not available until after the route is matched.'\n );\n }\n\n // Parallel slots may derive a param the main route never had, or the same\n // name with a different type (catch-all [...year] → string[] vs dynamic\n // [year] → string). `resolveSegmentParams` is the shared definition the\n // client's useSegmentParams() also reads, so the two halves of one\n // documented API cannot drift (TIM-1285).\n const slotResolved = resolveSegmentParams(store.segmentParams, store.slotParams, segmentPath);\n if (slotResolved !== store.segmentParams) return slotResolved;\n\n // Dev-mode validation: warn when the segment path expects dynamic\n // segments that don't exist on the actual matched segment. This catches\n // bugs like passing a child segment's $segment to a parent layout.\n // Skip for slot paths — they're validated against their own chain.\n // See design/41-global-params.md §Runtime Validation\n if (segmentPath && isDebug() && store.matchedSegmentPath) {\n const expected = extractDynamicSegments(segmentPath);\n const actual = extractDynamicSegments(store.matchedSegmentPath);\n const missing = expected.filter((s) => !actual.includes(s));\n\n if (missing.length > 0) {\n console.warn(\n `[timber] getSegmentParams('${segmentPath}') called but current segment is '${store.matchedSegmentPath}'\\n` +\n ` Missing params: ${missing.join(', ')}\\n` +\n ` These will be undefined at runtime despite the type annotation.`\n );\n }\n }\n\n return store.segmentParams;\n}\n\n/**\n * Set the segment params on the current request context.\n * Called by the pipeline after route matching and param coercion.\n *\n * @internal — framework use only\n */\nexport function setSegmentParams(params: CoercedParams): void {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error('[timber] setSegmentParams() called outside of a request context.');\n }\n store.segmentParams = params;\n}\n\n/**\n * Store per-slot coerced segment params in the current request context.\n * Called by the route element builder when resolving parallel slots.\n *\n * @param segmentPath — The slot's full tree path (e.g. '/(browse)/@shows/[artistSlug]/[...year]')\n * @param params — The slot's coerced segment params\n * @internal — framework use only\n */\nexport function setSlotParams(segmentPath: string, params: CoercedParams): void {\n const store = requestContextAls.getStore();\n if (!store) return; // non-throwing — optional diagnostic\n if (!store.slotParams) store.slotParams = Object.create(null) as SlotParamsRecord;\n store.slotParams[segmentPath] = params;\n}\n\n/**\n * Run `fn` with the segment params a parallel slot's own chain produced, so\n * a no-arg `getSegmentParams()` inside that slot returns the slot's params\n * rather than the main route's.\n *\n * ALS is the only channel available: the slot's page and layouts are plain\n * components that receive no params prop and cannot know their own tree\n * path, so they have no way to ask for the keyed overload. The slot resolver\n * invokes each of them itself (page through `SafeSlotPage`, layouts and\n * access functions through their wrappers), and this scopes those calls.\n *\n * The store is wrapped in a Proxy rather than cloned. Everything a slot\n * renders can write to the *live* request store — `setDenyStatus` from an\n * access gate, cookie writes, the flush flag — and a clone would silently\n * strand those writes in a copy the pipeline never reads. Only the two\n * params fields are shadowed; every other read and every write passes\n * straight through to the real store.\n *\n * Returns `fn()`'s result unchanged, and no-ops outside a request context or\n * before route matching has set the main params. See TIM-1283.\n *\n * @internal — framework use only\n */\nexport function runWithSlotSegmentParams<T>(\n slotSegmentPath: string | undefined,\n slotParams: CoercedParams,\n fn: () => T\n): T {\n const store = requestContextAls.getStore();\n if (!store?.segmentParams) return fn();\n\n const merged = mergeSlotParams(store.segmentParams, slotParams);\n const matchedSegmentPath = slotSegmentPath ?? store.matchedSegmentPath;\n const scoped = new Proxy(store, {\n get(target, prop, receiver) {\n if (prop === 'segmentParams') return merged;\n if (prop === 'matchedSegmentPath') return matchedSegmentPath;\n return Reflect.get(target, prop, receiver);\n },\n });\n return requestContextAls.run(scoped, fn);\n}\n\n/**\n * The main route's coerced params, or an empty record before route matching.\n *\n * Non-throwing, unlike `getSegmentParams()`: the deny payload path publishes\n * params from wherever the pipeline happened to fail, which may be before\n * matching completed.\n *\n * @internal — framework use only. See TIM-1285.\n */\nexport function getSegmentParamsForClient(): CoercedParams {\n return requestContextAls.getStore()?.segmentParams ?? {};\n}\n\n/**\n * The per-slot params to publish to the client, or `undefined` when the\n * request rendered no slot with params of its own.\n *\n * The client cannot derive these: a slot matches the URL through its own\n * sub-tree, and the browser only ever sees the main route's record. Reading\n * the map here — rather than threading it back out of the element builder —\n * keeps one producer, since `setSlotParams` already writes it during slot\n * resolution and every serialization site runs after that completes.\n *\n * @internal — framework use only. See TIM-1285.\n */\nexport function getSlotParamsForClient(): SlotParamsRecord | undefined {\n const store = requestContextAls.getStore();\n if (!store?.slotParams) return undefined;\n return Object.keys(store.slotParams).length > 0 ? store.slotParams : undefined;\n}\n\n/**\n * Store the matched segment path (e.g. '/(browse)/[artistSlug]/[year]') for\n * dev-mode validation in getSegmentParams().\n *\n * @internal — framework use only\n */\nexport function setMatchedSegmentPath(segmentPath: string): void {\n const store = requestContextAls.getStore();\n if (!store) return; // non-throwing — optional diagnostic\n store.matchedSegmentPath = segmentPath;\n}\n\n/**\n * Extract dynamic segment names from a route pattern.\n * '/[artistSlug]/[year]' → ['artistSlug', 'year']\n * '/docs/[...slug]' → ['slug']\n */\nfunction extractDynamicSegments(route: string): string[] {\n const segments: string[] = [];\n // Match [paramName], [...paramName], [[...paramName]]\n const re = /\\[{1,2}\\.{0,3}(\\w+)\\]{1,2}/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(route)) !== null) {\n segments.push(m[1]);\n }\n return segments;\n}\n\n/**\n * Returns the raw search string from the current request URL (e.g. \"?foo=bar\").\n * Synchronous — safe for use in `redirect()` which throws synchronously.\n *\n * Returns empty string if called outside a request context (non-throwing for\n * use in redirect's optional preserveSearchParams path).\n *\n * @internal — used by redirect() for preserveSearchParams support.\n */\nexport function getRequestSearchString(): string {\n const store = requestContextAls.getStore();\n return store?.searchString ?? '';\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\n/**\n * Read-only Headers interface. The standard Headers class is mutable;\n * this type narrows it to read-only methods. The underlying object is\n * still a Headers instance, but user code should not mutate it.\n */\nexport type ReadonlyHeaders = Pick<\n Headers,\n 'get' | 'has' | 'entries' | 'keys' | 'values' | 'forEach' | typeof Symbol.iterator\n>;\n\n// ─── Framework-Internal Helpers ───────────────────────────────────────────\n\n/**\n * Run a callback within a request context. Used by the pipeline to establish\n * per-request ALS scope so that `getHeaders()` and `getCookies()` work.\n *\n * If the request was previously registered via `seedRequestCookies`, the\n * resulting context's `parsedCookies` map is initialized from the seed and\n * the raw `cookieHeader` is left empty — `parseCookieHeader` is never called\n * for that request. This is the no-JS form-rerender path. See TIM-868.\n *\n * @param req - The incoming Request object.\n * @param fn - The function to run within the request context.\n */\nexport function runWithRequestContext<T>(req: Request, fn: () => T): T {\n const originalCopy = new Headers(req.headers);\n const appVisible = appVisibleSearch(new URL(req.url));\n const seed = consumeSeededCookies(req);\n const store: RequestContextStore = {\n headers: freezeHeaders(req.headers),\n originalHeaders: originalCopy,\n // When seeded, leave the raw header empty — parseCookieHeader is the\n // exact code path the smuggling primitive abused, and lazy parsing is\n // gated on `parsedCookies` being undefined.\n cookieHeader: seed ? '' : (req.headers.get('cookie') ?? ''),\n parsedCookies: seed,\n // Not `parsedUrl.searchParams` / `parsedUrl.search`: this store is what\n // `getSearchParams()` and `redirect({ preserveSearchParams })` read, and\n // the RSC payload URL carries the framework's `_rsc` cache key that no\n // other request has (TIM-1272).\n searchParams: appVisible.params,\n searchString: appVisible.search,\n cookieJar: new Map(),\n flushed: false,\n mutableContext: false,\n };\n return requestContextAls.run(store, fn);\n}\n\n/**\n * Enable cookie mutation for the current context. Called by the framework\n * when entering middleware.ts, server actions, or route.ts handlers.\n *\n * See design/29-cookies.md §\"Context Tracking\"\n */\nexport function setMutableCookieContext(mutable: boolean): void {\n const store = requestContextAls.getStore();\n if (store) {\n store.mutableContext = mutable;\n }\n}\n\n/**\n * Mark the response as flushed (headers committed). After this point,\n * cookie mutations log a warning instead of throwing.\n *\n * See design/29-cookies.md §\"Streaming Constraint: Post-Flush Cookie Warning\"\n */\nexport function markResponseFlushed(): void {\n const store = requestContextAls.getStore();\n if (store) {\n store.flushed = true;\n }\n}\n\n/**\n * Apply middleware-injected request headers to the current request context.\n *\n * Called by the pipeline after middleware.ts runs. Merges overlay headers\n * on top of the original request headers so downstream code (access.ts,\n * server components, server actions) sees them via `getHeaders()`.\n *\n * The original request headers are never mutated — a new frozen Headers\n * object is created with the overlay applied on top.\n *\n * See design/07-routing.md §\"Request Header Injection\"\n */\nexport function applyRequestHeaderOverlay(overlay: Headers): void {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error('[timber] applyRequestHeaderOverlay() called outside of a request context.');\n }\n\n // Check if the overlay has any headers — skip if empty\n let hasOverlay = false;\n overlay.forEach(() => {\n hasOverlay = true;\n });\n if (!hasOverlay) return;\n\n // Merge: start with original headers, overlay on top\n const merged = new Headers(store.originalHeaders);\n overlay.forEach((value, key) => {\n merged.set(key, value);\n });\n store.headers = freezeHeaders(merged);\n}\n\n// ─── Read-Only Headers ────────────────────────────────────────────────────\n\nconst MUTATING_METHODS = new Set(['set', 'append', 'delete']);\n\n/**\n * Wrap a Headers object in a Proxy that throws on mutating methods.\n * Object.freeze doesn't work on Headers (native internal slots), so we\n * intercept property access and reject set/append/delete at runtime.\n *\n * Read methods (get, has, entries, etc.) must be bound to the underlying\n * Headers instance because they access private #headersList slots.\n */\nfunction freezeHeaders(source: Headers): Headers {\n const copy = new Headers(source);\n return new Proxy(copy, {\n get(target, prop) {\n if (typeof prop === 'string' && MUTATING_METHODS.has(prop)) {\n return () => {\n throw new Error(\n `[timber] getHeaders() returns a read-only Headers object. ` +\n `Calling .${prop}() is not allowed. ` +\n `Use ctx.requestHeaders in middleware to inject headers for downstream components.`\n );\n };\n }\n const value = Reflect.get(target, prop);\n // Bind methods to the real Headers instance so private slot access works\n if (typeof value === 'function') {\n return value.bind(target);\n }\n return value;\n },\n });\n}\n","// Server-side primitives: deny, redirect, redirectExternal, RenderError, waitUntil, SsrStreamError\n//\n// These are the core runtime signals that components, middleware, and access gates\n// use to control request flow. See design/10-error-handling.md.\n\nimport type { JsonSerializable } from './types.ts';\nimport { getWaitUntil as _getWaitUntil } from './waituntil-bridge.ts';\nimport { isDebug } from './debug.ts';\nimport { getRequestSearchString } from './request-context.ts';\nimport { mergePreservedSearchParams } from '../shared/merge-search-params.ts';\nimport {\n assertRelativeRedirectPath,\n validateExternalRedirectUrl,\n} from '../shared/href-validation.ts';\n\n// ─── Dev-mode validation ────────────────────────────────────────────────────\n\n/**\n * Check if a value is JSON-serializable without data loss.\n * Returns a description of the first non-serializable value found, or null if OK.\n *\n * @internal Exported for testing only.\n */\nexport function findNonSerializable(value: unknown, path = 'data'): string | null {\n if (value === null || value === undefined) return null;\n\n switch (typeof value) {\n case 'string':\n case 'number':\n case 'boolean':\n return null;\n case 'bigint':\n return `${path} contains a BigInt — BigInt throws in JSON.stringify`;\n case 'function':\n return `${path} is a function — functions are not JSON-serializable`;\n case 'symbol':\n return `${path} is a symbol — symbols are not JSON-serializable`;\n case 'object':\n break;\n default:\n return `${path} has unsupported type \"${typeof value}\"`;\n }\n\n if (value instanceof Date) {\n return `${path} is a Date — Dates silently coerce to strings in JSON.stringify`;\n }\n if (value instanceof Map) {\n return `${path} is a Map — Maps serialize as {} in JSON.stringify (data loss)`;\n }\n if (value instanceof Set) {\n return `${path} is a Set — Sets serialize as {} in JSON.stringify (data loss)`;\n }\n if (value instanceof RegExp) {\n return `${path} is a RegExp — RegExps serialize as {} in JSON.stringify`;\n }\n if (value instanceof Error) {\n return `${path} is an Error — Errors serialize as {} in JSON.stringify`;\n }\n\n if (Array.isArray(value)) {\n for (let i = 0; i < value.length; i++) {\n const result = findNonSerializable(value[i], `${path}[${i}]`);\n if (result) return result;\n }\n return null;\n }\n\n // Plain object — only Object.prototype is safe. Null-prototype objects\n // (Object.create(null)) survive JSON.stringify but React Flight rejects\n // them with \"Classes or null prototypes are not supported\", so the\n // pre-flush deny path (renderDenyPage → renderToReadableStream) would throw.\n const proto = Object.getPrototypeOf(value);\n if (proto === null) {\n return `${path} is a null-prototype object — React Flight rejects null prototypes`;\n }\n if (proto !== Object.prototype) {\n const name = (value as object).constructor?.name ?? 'unknown';\n return `${path} is a ${name} instance — class instances may lose data in JSON.stringify`;\n }\n\n for (const key of Object.keys(value as Record<string, unknown>)) {\n const result = findNonSerializable((value as Record<string, unknown>)[key], `${path}.${key}`);\n if (result) return result;\n }\n return null;\n}\n\n/**\n * Emit a dev-mode warning if data is not JSON-serializable.\n * No-op in production.\n */\nfunction warnIfNotSerializable(data: unknown, callerName: string): void {\n if (!isDebug()) return;\n if (data === undefined) return;\n\n const issue = findNonSerializable(data);\n if (issue) {\n console.warn(\n `[timber] ${callerName}: ${issue}. ` +\n 'Data passed to deny() or RenderError must be JSON-serializable because ' +\n 'the post-flush path uses JSON.stringify, not React Flight.'\n );\n }\n}\n\n// ─── Signal branding ────────────────────────────────────────────────────────\n//\n// Catch sites must NOT use `instanceof` on these classes. In dev, an HMR\n// program reload clears the RSC module cache; a request that is mid-render\n// still holds the catch sites from the old evaluation of this module while\n// user code loaded after the reload throws from the new one. The two\n// evaluations are distinct classes, so `instanceof` fails and a correct\n// deny() falls through every catch site as an unhandled 500 (TIM-1499).\n//\n// Each class stamps a `Symbol.for` brand on its instances in the constructor.\n// `Symbol.for` keys live in the realm-wide symbol registry, so every\n// evaluation of this module — and every module graph in the same process —\n// agrees on the key. The `is*` predicates below check the brand and are the\n// only supported way to detect a signal. The classes themselves are NOT\n// anchored on globalThis: a re-evaluated module must hand out its own\n// (possibly updated) class, not a stale one.\n//\n// See design/18-build-system.md §\"Dev Mode: Signal Identity Across Program Reloads\".\n\nconst DENY_BRAND: unique symbol = Symbol.for('timber:signal.deny');\nconst REDIRECT_BRAND: unique symbol = Symbol.for('timber:signal.redirect');\nconst RENDER_ERROR_BRAND: unique symbol = Symbol.for('timber:signal.render-error');\nconst SSR_STREAM_ERROR_BRAND: unique symbol = Symbol.for('timber:signal.ssr-stream-error');\n\nfunction hasBrand(value: unknown, brand: symbol): boolean {\n return typeof value === 'object' && value !== null && brand in value;\n}\n\n/** True for any `DenySignal`, regardless of which module evaluation created it. */\nexport function isDenySignal(error: unknown): error is DenySignal {\n return hasBrand(error, DENY_BRAND);\n}\n\n/** True for any `RedirectSignal`, regardless of which module evaluation created it. */\nexport function isRedirectSignal(error: unknown): error is RedirectSignal {\n return hasBrand(error, REDIRECT_BRAND);\n}\n\n/** True for any `RenderError`, regardless of which module evaluation created it. */\nexport function isRenderError(error: unknown): error is RenderError {\n return hasBrand(error, RENDER_ERROR_BRAND);\n}\n\n/**\n * True for any `SsrStreamError`. This one also crosses Vite environments:\n * it is thrown in SSR and caught in RSC, which are separate module graphs in\n * the same process — the brand is the only identity the two share.\n */\nexport function isSsrStreamError(error: unknown): error is SsrStreamError {\n return hasBrand(error, SSR_STREAM_ERROR_BRAND);\n}\n\n// ─── DenySignal ─────────────────────────────────────────────────────────────\n\n/**\n * Render-phase signal thrown by `deny()`. Caught by the framework to produce\n * the correct HTTP status code (segment context) or graceful degradation (slot context).\n *\n * Detect with `isDenySignal()`, never `instanceof` — see \"Signal branding\" above.\n */\nexport class DenySignal extends Error {\n readonly [DENY_BRAND] = true;\n readonly status: number;\n readonly data: JsonSerializable | undefined;\n\n /**\n * Ordered list of segment keys that own matching deny pages for this\n * signal, best match first. Present = addressed (a boundary should\n * render it); absent = unplaced (the re-render fallback serves it).\n *\n * In-tree hoists stamp a single-element list (`[entry.ownerKey]`) —\n * identical to the pre-TIM-1450 `ownerKey`. Late addressing (TIM-1450)\n * stamps the full candidate list so the client can find the first\n * *reachable* owner via its ancestry context, eliminating the\n * owner-below-throw-site escape.\n *\n * See design/04-authorization.md §\"Where a Deny Page Renders\", TIM-1356.\n */\n owners?: string[];\n\n /**\n * When true, the pipeline must respond with `rscErrorEnvelope` so the\n * client hard-navigates instead of attempting to render a deny page\n * from the RSC payload. Set by AccessGate when a skipped segment\n * denies — rendering the deny page in-tree would appear inside the\n * client's cached layout chrome (TIM-1074). See TIM-1363.\n */\n _hardNavigate?: boolean;\n\n constructor(status: number, data?: JsonSerializable) {\n super(`Access denied with status ${status}`);\n this.name = 'DenySignal';\n this.status = status;\n this.data = data;\n }\n\n /**\n * Extract the file that called deny() from the stack trace.\n * Returns a short path (e.g. \"app/auth/access.ts\") or undefined if\n * the stack can't be parsed. Dev-only — used for dev log output.\n */\n get sourceFile(): string | undefined {\n if (!this.stack) return undefined;\n const frames = this.stack.split('\\n');\n // Skip the Error line and the deny() frame — the caller is the 3rd line.\n // Stack format: \" at FnName (file:line:col)\" or \" at file:line:col\"\n for (let i = 2; i < frames.length; i++) {\n const frame = frames[i];\n if (!frame) continue;\n // Skip framework internals\n if (frame.includes('primitives.ts') || frame.includes('node_modules')) continue;\n // Extract file path from the frame\n const match =\n frame.match(/\\(([^)]+?)(?::\\d+:\\d+)\\)/) ?? frame.match(/at\\s+([^\\s]+?)(?::\\d+:\\d+)/);\n if (match?.[1]) {\n // Shorten to app-relative path\n const full = match[1];\n const appIdx = full.indexOf('/app/');\n return appIdx >= 0 ? full.slice(appIdx + 1) : full;\n }\n }\n return undefined;\n }\n}\n\n/** Options for deny(). */\nexport interface DenyOptions {\n /** Human-readable message (logged server-side, not sent to client). */\n message?: string;\n /**\n * JSON-serializable data forwarded as the `data` prop to status-code files\n * and `denied.tsx`. Named `dangerouslyPassData` because this data crosses\n * the RSC → client serialization boundary — do not pass sensitive server\n * state (tokens, internal IDs, database rows).\n */\n dangerouslyPassData?: JsonSerializable;\n}\n\n/**\n * Universal denial/error primitive. Throws a `DenySignal` that the framework catches.\n *\n * - In segment context (outside Suspense): produces HTTP status code\n * - In slot context: graceful degradation → denied.tsx → default.tsx → null\n * - Inside Suspense (hold window): promoted to pre-flush behavior\n * - Inside Suspense (after flush): error boundary + noindex meta\n *\n * ```ts\n * deny() // 403 (default)\n * deny(404) // 404\n * deny(503, { message: 'Maintenance' }) // server-only log message\n * deny(404, { dangerouslyPassData: { resourceId: params.id } }) // explicit client opt-in\n * ```\n *\n * Accepts any 4xx or 5xx status code. This replaces the need for\n * `throw new RenderError(...)` in user code — RenderError is now an\n * internal pipeline detail.\n *\n * @param status - HTTP status code (4xx or 5xx). Default: 403.\n * @param options - Optional message and/or data to pass to the client.\n */\nexport function deny(status?: number, options?: DenyOptions): never {\n const resolvedStatus = status ?? 403;\n const resolvedData = options?.dangerouslyPassData;\n\n if (resolvedStatus < 400 || resolvedStatus > 599) {\n throw new Error(`deny() requires a 4xx or 5xx status code, got ${resolvedStatus}.`);\n }\n warnIfNotSerializable(resolvedData, 'deny()');\n throw new DenySignal(resolvedStatus, resolvedData);\n}\n\n// Single source of truth shared with the client next/navigation shim —\n// see shared/redirect-type.ts.\nexport { RedirectType } from '../shared/redirect-type.ts';\n\n// ─── RedirectSignal ─────────────────────────────────────────────────────────\n\n/**\n * Render-phase signal thrown by `redirect()` and `redirectExternal()`.\n * Caught by the framework to produce a 3xx response or client-side navigation.\n *\n * Detect with `isRedirectSignal()`, never `instanceof` — see \"Signal branding\" above.\n */\nexport class RedirectSignal extends Error {\n readonly [REDIRECT_BRAND] = true;\n readonly location: string;\n readonly status: number;\n\n constructor(location: string, status: number) {\n super(`Redirect to ${location}`);\n this.name = 'RedirectSignal';\n this.location = location;\n this.status = status;\n }\n}\n\n// ─── Signal Detection ───────────────────────────────────────────────────────\n\n/**\n * Returns true if the error is a framework control-flow signal (RedirectSignal\n * or DenySignal) rather than a genuine application error. Works both for direct\n * instances (RSC-side) and deserialized digest errors (SSR cross-boundary).\n *\n * See also: isFrameworkSignalError() in client/browser-dev.ts (client-only).\n */\nexport function isControlFlowSignal(error: unknown): boolean {\n if (isDenySignal(error) || isRedirectSignal(error)) {\n return true;\n }\n if (error && typeof error === 'object') {\n const digest = (error as { digest?: unknown }).digest;\n if (typeof digest === 'string') {\n try {\n const parsed = JSON.parse(digest) as { type?: unknown } | null;\n if (parsed && typeof parsed === 'object') {\n return parsed.type === 'redirect' || parsed.type === 'deny';\n }\n } catch {\n // Not valid digest JSON\n }\n }\n }\n return false;\n}\n\n/**\n * Options for redirect() — alternative to passing a bare status code.\n */\nexport interface RedirectOptions {\n /** HTTP redirect status code (3xx). Defaults to 302 (or 308 when `permanent: true`). */\n status?: number;\n /**\n * When true, defaults the status to 308 (Permanent Redirect, preserves HTTP method).\n * If `status` is also provided, `status` takes precedence.\n *\n * @example\n * redirect('/new-path', { permanent: true }); // 308\n * redirect('/new-path', { permanent: true, status: 301 }); // 301\n */\n permanent?: boolean;\n /**\n * Preserve search params from the current request URL on the redirect target.\n *\n * - `true` — preserve ALL current search params (target params take precedence)\n * - `string[]` — preserve only the named params (e.g. `['private', 'token']`)\n *\n * Target path's own query params always take precedence over preserved ones.\n */\n preserveSearchParams?: true | string[];\n}\n\n/**\n * Redirect to a relative path. Rejects absolute and protocol-relative URLs.\n * Use `redirectExternal()` for external redirects with an allow-list.\n *\n * @param path - Relative path (e.g. '/login', 'settings', '/login?returnTo=/dash')\n * @param statusOrOptions - HTTP status code (3xx, default 302) or options object.\n *\n * @example\n * // Simple redirect\n * redirect('/login');\n *\n * // With status code\n * redirect('/login', 301);\n *\n * // With preserved search params\n * redirect(`/docs/${version}/${slug}`, { preserveSearchParams: ['foo'] });\n */\nexport function redirect(path: string, statusOrOptions?: number | RedirectOptions): never {\n let status: number;\n let preserveSearchParams: true | string[] | undefined;\n\n if (typeof statusOrOptions === 'number') {\n status = statusOrOptions;\n } else if (statusOrOptions) {\n // Explicit status wins. Otherwise permanent: true → 308, default → 302.\n status = statusOrOptions.status ?? (statusOrOptions.permanent ? 308 : 302);\n preserveSearchParams = statusOrOptions.preserveSearchParams;\n } else {\n status = 302;\n }\n\n if (status < 300 || status > 399) {\n throw new Error(`redirect() requires a 3xx status code, got ${status}.`);\n }\n // Relative-only validation shared with the client next/navigation shim —\n // strips C0 controls and normalizes backslashes before the scheme check.\n assertRelativeRedirectPath(path);\n\n let resolvedPath = path;\n if (preserveSearchParams) {\n const currentSearch = getRequestSearchString();\n resolvedPath = mergePreservedSearchParams(path, currentSearch, preserveSearchParams);\n }\n\n throw new RedirectSignal(resolvedPath, status);\n}\n\n/**\n * Redirect to an external URL. The origin must be in the provided allow-list.\n *\n * Only http: and https: schemes are permitted. The allow-list is matched\n * against full origins (scheme + host + port), not bare hostnames.\n *\n * @param url - Absolute URL to redirect to.\n * @param allowList - Array of allowed origins (e.g. ['https://example.com', 'https://auth.example.com']).\n * @param status - HTTP redirect status code (3xx). Defaults to 302.\n */\nexport function redirectExternal(url: string, allowList: string[], status: number = 302): never {\n if (status < 300 || status > 399) {\n throw new Error(`redirectExternal() requires a 3xx status code, got ${status}.`);\n }\n\n const canonicalUrl = validateExternalRedirectUrl(url, allowList);\n throw new RedirectSignal(canonicalUrl, status);\n}\n\n// ─── RenderError ────────────────────────────────────────────────────────────\n\n/**\n * Typed digest that crosses the RSC → client boundary.\n * The `code` identifies the error class; `data` carries JSON-serializable context.\n */\nexport interface RenderErrorDigest<\n TCode extends string = string,\n TData extends JsonSerializable = JsonSerializable,\n> {\n code: TCode;\n data: TData;\n}\n\n/**\n * Typed throw for render-phase errors that carry structured context to error boundaries.\n *\n * The `digest` (code + data) is serialized into the RSC stream separately from the\n * Error instance — only the digest crosses the RSC → client boundary.\n *\n * @example\n * ```ts\n * throw new RenderError('PRODUCT_NOT_FOUND', {\n * title: 'Product not found',\n * resourceId: params.id,\n * })\n * ```\n *\n * Detect with `isRenderError()`, never `instanceof` — see \"Signal branding\" above.\n */\nexport class RenderError<\n TCode extends string = string,\n TData extends JsonSerializable = JsonSerializable,\n> extends Error {\n readonly [RENDER_ERROR_BRAND] = true;\n readonly code: TCode;\n readonly digest: RenderErrorDigest<TCode, TData>;\n readonly status: number;\n\n constructor(code: TCode, data: TData, options?: { status?: number }) {\n super(`RenderError: ${code}`);\n this.name = 'RenderError';\n this.code = code;\n this.digest = { code, data };\n\n warnIfNotSerializable(data, 'RenderError');\n\n const status = options?.status ?? 500;\n if (status < 400 || status > 599) {\n throw new Error(`RenderError status must be 4xx or 5xx, got ${status}.`);\n }\n this.status = status;\n }\n}\n\n// ─── waitUntil ──────────────────────────────────────────────────────────────\n\n// Intentional per-app singleton — warn-once flag that persists for the\n// lifetime of the process/isolate. Not per-request; do not migrate to ALS.\nlet _waitUntilWarned = false;\n\n/**\n * Register a promise to be kept alive after the response is sent.\n * Maps to `ctx.waitUntil()` on Cloudflare Workers and similar platforms.\n *\n * The platform adapter installs a per-request waitUntil function via ALS\n * (see waituntil-bridge.ts). If no ALS handler is available, a warning\n * is logged once and the promise is left to resolve (or reject) without\n * being tracked.\n *\n * @param promise - The background work to keep alive.\n */\nexport function waitUntil(promise: Promise<unknown>): void {\n const alsFn = _getWaitUntil();\n if (alsFn) {\n alsFn(promise);\n return;\n }\n\n if (!_waitUntilWarned) {\n _waitUntilWarned = true;\n console.warn(\n '[timber] waitUntil() is not supported by the current adapter. ' +\n 'Background work will not be tracked. This warning is shown once.'\n );\n }\n}\n\n/**\n * Reset the waitUntil warning state. Exported for testing only.\n * @internal\n */\nexport function _resetWaitUntilWarning(): void {\n _waitUntilWarned = false;\n}\n\n// ─── SsrStreamError ─────────────────────────────────────────────────────────\n\n/**\n * Error thrown when SSR's renderToReadableStream fails due to an error\n * in the decoded RSC stream (e.g., uncontained slot errors).\n *\n * The RSC entry checks for this error type in its catch block to avoid\n * re-executing server components via renderDenyPage. Instead, it renders\n * a bare deny/error page without layout wrapping.\n *\n * Defined in primitives.ts (not ssr-entry.ts) because ssr-entry.ts imports\n * react-dom/server which cannot be loaded in the RSC environment.\n *\n * Detect with `isSsrStreamError()` — it is thrown in the SSR environment and\n * caught in RSC, so `instanceof` never matches. See \"Signal branding\" above.\n */\nexport class SsrStreamError extends Error {\n readonly [SSR_STREAM_ERROR_BRAND] = true;\n override readonly cause: unknown;\n\n constructor(message: string, cause: unknown) {\n super(message);\n this.name = 'SsrStreamError';\n this.cause = cause;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAkCA,SAAgB,eAAkE;CAChF,OAAO,aAAa,SAAS;AAC/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACyBA,SAAgB,YAAqB;CACnC,OAAA,QAAA,IAAA,aAAgC;AAClC;;;;;AAQA,IAAI,eAAe;;;;;;;;;;;;;;;;;AA0BnB,SAAgB,UAAmB;CAEjC,IAAA,QAAA,IAAA,aAA6B,cAAc,OAAO;CAGlD,IAAI,cAAc,OAAO;CAMzB,OAAO,oBAAoB;AAC7B;;;;;;;;;AAUA,SAAS,sBAA+B;CAEtC,IAAK,WAAuC,gBAAgB,OAAO;CAGnE,IAAI;EAEF,MAAM,MACJ,OAAO,YAAY,eAAe,QAAQ,MACrC,QAAQ,IAA2C,kBACpD,KAAA;EACN,IAAI,OAAO,QAAQ,OAAO,QAAQ,SAAS,OAAO;CACpD,QAAQ,CAER;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACxGA,SAAgB,kBAAkB,QAAqC;CACrE,MAAM,sBAAM,IAAI,IAAoB;CACpC,IAAI,CAAC,QAAQ,OAAO;CAKpB,MAAM,SAAS,YAAY,MAAM;CACjC,KAAK,MAAM,QAAQ,QAAQ;EACzB,MAAM,QAAQ,OAAO;EACrB,IAAI,UAAU,KAAA,GAAW,IAAI,IAAI,MAAM,KAAK;CAC9C;CAEA,OAAO;AACT;;;;;;;;;;;;AAaA,SAAgB,sBAAsB,KAAqB;CACzD,IAAI;EACF,OAAO,mBAAmB,GAAG;CAC/B,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;AAUA,SAAgB,qBAAqB,OAA4B;CAC/D,IAAI;EACF,OAAO,mBACL;GACE,MAAM,MAAM;GACZ,OAAO,MAAM;GACb,QAAQ,MAAM,QAAQ;GACtB,MAAM,MAAM,QAAQ;GACpB,SAAS,MAAM,QAAQ;GACvB,QAAQ,MAAM,QAAQ;GACtB,UAAU,MAAM,QAAQ;GACxB,QAAQ,MAAM,QAAQ;GACtB,UAAU,MAAM,QAAQ;GACxB,aAAa,MAAM,QAAQ;EAC7B,GAGA,EAAE,SAAS,MAAc,EAAE,CAC7B;CACF,QAAQ;EAKN,OAAO,6BAA6B,KAAK;CAC3C;AACF;AAEA,SAAS,6BAA6B,OAA4B;CAChE,MAAM,QAAQ,CAAC,GAAG,MAAM,KAAK,GAAG,MAAM,OAAO;CAC7C,MAAM,OAAO,MAAM;CAEnB,IAAI,KAAK,QAAQ,MAAM,KAAK,UAAU,KAAK,QAAQ;CACnD,IAAI,KAAK,MAAM,MAAM,KAAK,QAAQ,KAAK,MAAM;CAC7C,IAAI,KAAK,SAAS,MAAM,KAAK,WAAW,KAAK,QAAQ,YAAY,GAAG;CACpE,IAAI,KAAK,WAAW,KAAA,GAAW,MAAM,KAAK,WAAW,KAAK,QAAQ;CAClE,IAAI,KAAK,UAAU,MAAM,KAAK,UAAU;CACxC,IAAI,KAAK,QAAQ,MAAM,KAAK,QAAQ;CACpC,IAAI,KAAK,UACP,MAAM,KAAK,YAAY,KAAK,SAAS,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,KAAK,SAAS,MAAM,CAAC,GAAG;CAEzF,IAAI,KAAK,aAAa,MAAM,KAAK,aAAa;CAE9C,OAAO,MAAM,KAAK,IAAI;AACxB;;;;;;;;;;AAWA,SAAgB,eACd,QACgE;CAChE,MAAM,SAAS,iBAAuB,QAAQ,EAG5C,SAAS,MAAc,EACzB,CAAC;CAID,IAAI,CAAC,OAAO,MAAM,OAAO;CAEzB,MAAM,UAAyB,CAAC;CAEhC,IAAI,OAAO,SAAS,KAAA,GAAW,QAAQ,OAAO,OAAO,QAAQ;CAC7D,IAAI,OAAO,WAAW,KAAA,GAAW,QAAQ,SAAS,OAAO;CACzD,IAAI,OAAO,WAAW,KAAA,KAAa,OAAO,SAAS,OAAO,MAAM,GAC9D,QAAQ,SAAS,OAAO;CAE1B,IAAI,OAAO,YAAY,KAAA,GAAW,QAAQ,UAAU,OAAO;CAC3D,IAAI,OAAO,aAAa,KAAA,KAAa,OAAO,aAAa,MAAM;EAC7D,MAAM,WAAY,OAAO,SAAoB,YAAY;EACzD,IAAI,aAAa,YAAY,aAAa,SAAS,aAAa,QAC9D,QAAQ,WAAW;CAEvB;CACA,IAAI,OAAO,QAAQ,QAAQ,SAAS;CACpC,IAAI,OAAO,UAAU,QAAQ,WAAW;CACxC,IAAI,OAAO,aAAa,QAAQ,cAAc;CAE9C,OAAO;EAAE,MAAM,OAAO;EAAM,OAAO,OAAO,SAAS;EAAI;CAAQ;AACjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC3GA,SAAgB,eAA+B;CAC7C,MAAM,QAAQ,kBAAkB,SAAS;CACzC,IAAI,CAAC,OACH,MAAM,IAAI,MACR,uJAEF;CAIF,IAAI,CAAC,MAAM,eACT,MAAM,gBAAgB,kBAAkB,MAAM,YAAY;CAG5D,MAAM,MAAM,MAAM;CAClB,OAAO;EACL,IAAI,MAAkC;GACpC,OAAO,IAAI,IAAI,IAAI;EACrB;EACA,IAAI,MAAuB;GACzB,OAAO,IAAI,IAAI,IAAI;EACrB;EACA,SAAiD;GAC/C,OAAO,MAAM,KAAK,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,YAAY;IAAE;IAAM;GAAM,EAAE;EAC3E;EACA,IAAI,OAAe;GACjB,OAAO,IAAI;EACb;EAEA,IAAI,MAAc,OAAe,SAAkC;GACjE,cAAc,OAAO,KAAK;GAG1B,sBAAsB,IAAI;GAI1B,IAAI,OAAO,UAAU,UACnB,MAAM,IAAI,MACR,+BAA+B,KAAK,UAAU,IAAI,EAAE,oCAAoC,OAAO,MAAM,kLAK/E,KAAK,wBAAwB,KAAK,UAAU,IAAI,EAAE,uDAI/D,KAAK,0FAGhB;GAiBF,MAAM,MAAM,SAAS,QAAQ;GAC7B,MAAM,YAAY,MAAM,QAAQ,mBAAmB,KAAK;GACxD,IAAI,KACF,uBAAuB,MAAM,SAAS;GAExC,IAAI,MAAM,SAAS;IACjB,IAAI,QAAQ,GACV,QAAQ,KACN,sCAAsC,KAAK,oKAG7C;IAEF;GACF;GAGA,MAAM,EAAE,KAAK,MAAM,GAAG,qBAAqB,WAAW,CAAC;GAEvD,MAAM,OAAO;IAAE,GAAG;IAAwB,GAAG;GAAiB;GAC9D,yBAAyB,IAAI;GAC7B,MAAM,UAAU,IAAI,MAAM;IAAE;IAAM,OAAO;IAAW,SAAS;GAAK,CAAC;GAKnE,IAAI,IAAI,MAAM,MAAM,YAAY,KAAK;EACvC;EAEA,eAAe,SAAwB;GACrC,cAAc,OAAO,gBAAgB;GACrC,IAAI,MAAM,SAAS;IACjB,QAAQ,KACN,kNAGF;IACA;GACF;GAGA,KAAK,MAAM,OAAO,QAAQ,aAAa,GAAG;IACxC,MAAM,SAAS,eAAe,GAAG;IACjC,IAAI,QAIF,OAAO,OAAO,KAAK,OAAO,MAAM,OAAO,OAAO,OAAO,OAAO;GAEhE;EACF;EAEA,OAAO,MAAc,SAAwD;GAC3E,cAAc,OAAO,QAAQ;GAI7B,sBAAsB,IAAI;GAC1B,IAAI,MAAM,SAAS;IACjB,IAAI,QAAQ,GACV,QAAQ,KACN,yCAAyC,KAAK,uKAGhD;IAEF;GACF;GACA,MAAM,OAAsB;IAC1B,GAAG;IACH,GAAG;IACH,QAAQ;IACR,yBAAS,IAAI,KAAK,CAAC;GACrB;GAKA,yBAAyB,IAAI;GAC7B,MAAM,UAAU,IAAI,MAAM;IAAE;IAAM,OAAO;IAAI,SAAS;GAAK,CAAC;GAE5D,IAAI,OAAO,IAAI;EACjB;EAEA,QAAc;GACZ,cAAc,OAAO,OAAO;GAC5B,IAAI,MAAM,SAAS;GAEnB,KAAK,MAAM,QAAQ,MAAM,KAAK,IAAI,KAAK,CAAC,GACtC,MAAM,UAAU,IAAI,MAAM;IACxB;IACA,OAAO;IACP,SAAS;KAAE,GAAG;KAAwB,QAAQ;KAAG,yBAAS,IAAI,KAAK,CAAC;IAAE;GACxE,CAAC;GAEH,IAAI,MAAM;EACZ;EAEA,WAAmB;GAKjB,OAAO,MAAM,KAAK,IAAI,QAAQ,CAAC,CAAC,CAC7B,KAAK,CAAC,MAAM,WAAW,GAAG,KAAK,GAAG,mBAAmB,KAAK,GAAG,CAAC,CAC9D,KAAK,IAAI;EACd;CACF;AACF;;;;;;AAOA,SAAgB,UAAU,MAAkC;CAE1D,OADY,aACL,CAAA,CAAI,IAAI,IAAI;AACrB;AA6FA,IAAM,yBAAwC;CAC5C,MAAM;CACN,UAAU;CACV,QAAQ;CACR,UAAU;AACZ;;;;;;;;;;;;;AAgBA,IAAM,uCAAuB,IAAI,QAAsC;;;;;;;;;AA4BvE,SAAgB,qBAAqB,KAA+C;CAClF,MAAM,OAAO,qBAAqB,IAAI,GAAG;CACzC,IAAI,MAAM,qBAAqB,OAAO,GAAG;CACzC,OAAO;AACT;;;;;;;AAqCA,SAAgB,sBAAgC;CAC9C,MAAM,QAAQ,kBAAkB,SAAS;CACzC,IAAI,CAAC,OAAO,OAAO,CAAC;CACpB,OAAO,MAAM,KAAK,MAAM,UAAU,OAAO,CAAC,CAAC,CAAC,IAAI,oBAAoB;AACtE;;AAKA,SAAS,cAAc,OAA4B,QAAsB;CACvE,IAAI,CAAC,MAAM,gBACT,MAAM,IAAI,MACR,2BAA2B,OAAO,4GAEpC;AAEJ;;;;;;;;;AAUA,SAAS,OACP,OACA,SACA,MACA,OACA,SACM;CASN,sBAAsB,IAAI;CAC1B,uBAAuB,MAAM,KAAK;CAMlC,yBAAyB,OAAO;CAChC,MAAM,UAAU,IAAI,MAAM;EAAE;EAAM;EAAO;CAAQ,CAAC;CAElD,IAAI,QAAQ,WAAW,GACrB,QAAQ,OAAO,IAAI;MAKnB,QAAQ,IAAI,MAAM,sBAAsB,KAAK,CAAC;AAElD;;;;;;;;;AC5bA,SAAgB,aAA8B;CAC5C,MAAM,QAAQ,kBAAkB,SAAS;CACzC,IAAI,CAAC,OACH,MAAM,IAAI,MACR,qJAEF;CAGF,OAAO,MAAM;AACf;;;;;;;;;AAUA,SAAgB,UAAU,MAAkC;CAE1D,OADgB,WACT,CAAA,CAAQ,IAAI,IAAI,KAAK,KAAA;AAC9B;;;;;;AAOA,SAAgB,kBAAmC;CACjD,MAAM,QAAQ,kBAAkB,SAAS;CACzC,IAAI,CAAC,OACH,MAAM,IAAI,MACR,0JAEF;CAGF,OAAO,MAAM;AACf;AAMA,sBAAsB,eAAe;AAKrC,uBAAuB,gBAAgB;;;;;;;;;;;;;AAcvC,SAAgB,iBAAiB,aAAqC;CACpE,MAAM,QAAQ,kBAAkB,SAAS;CACzC,IAAI,CAAC,OACH,MAAM,IAAI,MACR,2JAEF;CAEF,IAAI,CAAC,MAAM,eACT,MAAM,IAAI,MACR,wIAEF;CAQF,MAAM,eAAe,qBAAqB,MAAM,eAAe,MAAM,YAAY,WAAW;CAC5F,IAAI,iBAAiB,MAAM,eAAe,OAAO;CAOjD,IAAI,eAAe,QAAQ,KAAK,MAAM,oBAAoB;EACxD,MAAM,WAAW,uBAAuB,WAAW;EACnD,MAAM,SAAS,uBAAuB,MAAM,kBAAkB;EAC9D,MAAM,UAAU,SAAS,QAAQ,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC;EAE1D,IAAI,QAAQ,SAAS,GACnB,QAAQ,KACN,8BAA8B,YAAY,oCAAoC,MAAM,mBAAmB,uBAChF,QAAQ,KAAK,IAAI,EAAE,oEAE5C;CAEJ;CAEA,OAAO,MAAM;AACf;;;;;;;AAQA,SAAgB,iBAAiB,QAA6B;CAC5D,MAAM,QAAQ,kBAAkB,SAAS;CACzC,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,kEAAkE;CAEpF,MAAM,gBAAgB;AACxB;;;;;;;AAiGA,SAAgB,sBAAsB,aAA2B;CAC/D,MAAM,QAAQ,kBAAkB,SAAS;CACzC,IAAI,CAAC,OAAO;CACZ,MAAM,qBAAqB;AAC7B;;;;;;AAOA,SAAS,uBAAuB,OAAyB;CACvD,MAAM,WAAqB,CAAC;CAE5B,MAAM,KAAK;CACX,IAAI;CACJ,QAAQ,IAAI,GAAG,KAAK,KAAK,OAAO,MAC9B,SAAS,KAAK,EAAE,EAAE;CAEpB,OAAO;AACT;;;;;;;;;;AAWA,SAAgB,yBAAiC;CAE/C,OADc,kBAAkB,SACzB,CAAA,EAAO,gBAAgB;AAChC;;;;;;;;;;;;;AA4BA,SAAgB,sBAAyB,KAAc,IAAgB;CACrE,MAAM,eAAe,IAAI,QAAQ,IAAI,OAAO;CAC5C,MAAM,aAAa,iBAAiB,IAAI,IAAI,IAAI,GAAG,CAAC;CACpD,MAAM,OAAO,qBAAqB,GAAG;CACrC,MAAM,QAA6B;EACjC,SAAS,cAAc,IAAI,OAAO;EAClC,iBAAiB;EAIjB,cAAc,OAAO,KAAM,IAAI,QAAQ,IAAI,QAAQ,KAAK;EACxD,eAAe;EAKf,cAAc,WAAW;EACzB,cAAc,WAAW;EACzB,2BAAW,IAAI,IAAI;EACnB,SAAS;EACT,gBAAgB;CAClB;CACA,OAAO,kBAAkB,IAAI,OAAO,EAAE;AACxC;;;;;;;AAQA,SAAgB,wBAAwB,SAAwB;CAC9D,MAAM,QAAQ,kBAAkB,SAAS;CACzC,IAAI,OACF,MAAM,iBAAiB;AAE3B;;;;;;;AAQA,SAAgB,sBAA4B;CAC1C,MAAM,QAAQ,kBAAkB,SAAS;CACzC,IAAI,OACF,MAAM,UAAU;AAEpB;;;;;;;;;;;;;AAcA,SAAgB,0BAA0B,SAAwB;CAChE,MAAM,QAAQ,kBAAkB,SAAS;CACzC,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,2EAA2E;CAI7F,IAAI,aAAa;CACjB,QAAQ,cAAc;EACpB,aAAa;CACf,CAAC;CACD,IAAI,CAAC,YAAY;CAGjB,MAAM,SAAS,IAAI,QAAQ,MAAM,eAAe;CAChD,QAAQ,SAAS,OAAO,QAAQ;EAC9B,OAAO,IAAI,KAAK,KAAK;CACvB,CAAC;CACD,MAAM,UAAU,cAAc,MAAM;AACtC;AAIA,IAAM,mCAAmB,IAAI,IAAI;CAAC;CAAO;CAAU;AAAQ,CAAC;;;;;;;;;AAU5D,SAAS,cAAc,QAA0B;CAC/C,MAAM,OAAO,IAAI,QAAQ,MAAM;CAC/B,OAAO,IAAI,MAAM,MAAM,EACrB,IAAI,QAAQ,MAAM;EAChB,IAAI,OAAO,SAAS,YAAY,iBAAiB,IAAI,IAAI,GACvD,aAAa;GACX,MAAM,IAAI,MACR,sEACc,KAAK,qGAErB;EACF;EAEF,MAAM,QAAQ,QAAQ,IAAI,QAAQ,IAAI;EAEtC,IAAI,OAAO,UAAU,YACnB,OAAO,MAAM,KAAK,MAAM;EAE1B,OAAO;CACT,EACF,CAAC;AACH;;;;;;;;;AC9ZA,SAAgB,oBAAoB,OAAgB,OAAO,QAAuB;CAChF,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;CAElD,QAAQ,OAAO,OAAf;EACE,KAAK;EACL,KAAK;EACL,KAAK,WACH,OAAO;EACT,KAAK,UACH,OAAO,GAAG,KAAK;EACjB,KAAK,YACH,OAAO,GAAG,KAAK;EACjB,KAAK,UACH,OAAO,GAAG,KAAK;EACjB,KAAK,UACH;EACF,SACE,OAAO,GAAG,KAAK,yBAAyB,OAAO,MAAM;CACzD;CAEA,IAAI,iBAAiB,MACnB,OAAO,GAAG,KAAK;CAEjB,IAAI,iBAAiB,KACnB,OAAO,GAAG,KAAK;CAEjB,IAAI,iBAAiB,KACnB,OAAO,GAAG,KAAK;CAEjB,IAAI,iBAAiB,QACnB,OAAO,GAAG,KAAK;CAEjB,IAAI,iBAAiB,OACnB,OAAO,GAAG,KAAK;CAGjB,IAAI,MAAM,QAAQ,KAAK,GAAG;EACxB,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;GACrC,MAAM,SAAS,oBAAoB,MAAM,IAAI,GAAG,KAAK,GAAG,EAAE,EAAE;GAC5D,IAAI,QAAQ,OAAO;EACrB;EACA,OAAO;CACT;CAMA,MAAM,QAAQ,OAAO,eAAe,KAAK;CACzC,IAAI,UAAU,MACZ,OAAO,GAAG,KAAK;CAEjB,IAAI,UAAU,OAAO,WAEnB,OAAO,GAAG,KAAK,QADD,MAAiB,aAAa,QAAQ,UACxB;CAG9B,KAAK,MAAM,OAAO,OAAO,KAAK,KAAgC,GAAG;EAC/D,MAAM,SAAS,oBAAqB,MAAkC,MAAM,GAAG,KAAK,GAAG,KAAK;EAC5F,IAAI,QAAQ,OAAO;CACrB;CACA,OAAO;AACT;;;;;AAMA,SAAS,sBAAsB,MAAe,YAA0B;CACtE,IAAI,CAAC,QAAQ,GAAG;CAChB,IAAI,SAAS,KAAA,GAAW;CAExB,MAAM,QAAQ,oBAAoB,IAAI;CACtC,IAAI,OACF,QAAQ,KACN,YAAY,WAAW,IAAI,MAAM,oIAGnC;AAEJ;AAqBA,IAAM,aAA4B,OAAO,IAAI,oBAAoB;AACjE,IAAM,iBAAgC,OAAO,IAAI,wBAAwB;AACzE,IAAM,qBAAoC,OAAO,IAAI,4BAA4B;AACjF,IAAM,yBAAwC,OAAO,IAAI,gCAAgC;AAEzF,SAAS,SAAS,OAAgB,OAAwB;CACxD,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,SAAS;AACjE;;AAGA,SAAgB,aAAa,OAAqC;CAChE,OAAO,SAAS,OAAO,UAAU;AACnC;;AAGA,SAAgB,iBAAiB,OAAyC;CACxE,OAAO,SAAS,OAAO,cAAc;AACvC;;;;;;AAYA,SAAgB,iBAAiB,OAAyC;CACxE,OAAO,SAAS,OAAO,sBAAsB;AAC/C;;;;;;;AAUA,IAAa,aAAb,cAAgC,MAAM;CACpC,CAAU,cAAc;CACxB;CACA;;;;;;;;;;;;;;CAeA;;;;;;;;CASA;CAEA,YAAY,QAAgB,MAAyB;EACnD,MAAM,6BAA6B,QAAQ;EAC3C,KAAK,OAAO;EACZ,KAAK,SAAS;EACd,KAAK,OAAO;CACd;;;;;;CAOA,IAAI,aAAiC;EACnC,IAAI,CAAC,KAAK,OAAO,OAAO,KAAA;EACxB,MAAM,SAAS,KAAK,MAAM,MAAM,IAAI;EAGpC,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;GACtC,MAAM,QAAQ,OAAO;GACrB,IAAI,CAAC,OAAO;GAEZ,IAAI,MAAM,SAAS,eAAe,KAAK,MAAM,SAAS,cAAc,GAAG;GAEvE,MAAM,QACJ,MAAM,MAAM,0BAA0B,KAAK,MAAM,MAAM,4BAA4B;GACrF,IAAI,QAAQ,IAAI;IAEd,MAAM,OAAO,MAAM;IACnB,MAAM,SAAS,KAAK,QAAQ,OAAO;IACnC,OAAO,UAAU,IAAI,KAAK,MAAM,SAAS,CAAC,IAAI;GAChD;EACF;CAEF;AACF;;;;;;;;;;;;;;;;;;;;;;;AAqCA,SAAgB,KAAK,QAAiB,SAA8B;CAClE,MAAM,iBAAiB,UAAU;CACjC,MAAM,eAAe,SAAS;CAE9B,IAAI,iBAAiB,OAAO,iBAAiB,KAC3C,MAAM,IAAI,MAAM,iDAAiD,eAAe,EAAE;CAEpF,sBAAsB,cAAc,QAAQ;CAC5C,MAAM,IAAI,WAAW,gBAAgB,YAAY;AACnD;;;;;;;AAcA,IAAa,iBAAb,cAAoC,MAAM;CACxC,CAAU,kBAAkB;CAC5B;CACA;CAEA,YAAY,UAAkB,QAAgB;EAC5C,MAAM,eAAe,UAAU;EAC/B,KAAK,OAAO;EACZ,KAAK,WAAW;EAChB,KAAK,SAAS;CAChB;AACF;;;;;;;;AAWA,SAAgB,oBAAoB,OAAyB;CAC3D,IAAI,aAAa,KAAK,KAAK,iBAAiB,KAAK,GAC/C,OAAO;CAET,IAAI,SAAS,OAAO,UAAU,UAAU;EACtC,MAAM,SAAU,MAA+B;EAC/C,IAAI,OAAO,WAAW,UACpB,IAAI;GACF,MAAM,SAAS,KAAK,MAAM,MAAM;GAChC,IAAI,UAAU,OAAO,WAAW,UAC9B,OAAO,OAAO,SAAS,cAAc,OAAO,SAAS;EAEzD,QAAQ,CAER;CAEJ;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;;AA6CA,SAAgB,SAAS,MAAc,iBAAmD;CACxF,IAAI;CACJ,IAAI;CAEJ,IAAI,OAAO,oBAAoB,UAC7B,SAAS;MACJ,IAAI,iBAAiB;EAE1B,SAAS,gBAAgB,WAAW,gBAAgB,YAAY,MAAM;EACtE,uBAAuB,gBAAgB;CACzC,OACE,SAAS;CAGX,IAAI,SAAS,OAAO,SAAS,KAC3B,MAAM,IAAI,MAAM,8CAA8C,OAAO,EAAE;CAIzE,2BAA2B,IAAI;CAE/B,IAAI,eAAe;CACnB,IAAI,sBAAsB;EACxB,MAAM,gBAAgB,uBAAuB;EAC7C,eAAe,2BAA2B,MAAM,eAAe,oBAAoB;CACrF;CAEA,MAAM,IAAI,eAAe,cAAc,MAAM;AAC/C;;;;;;;;;;;AAYA,SAAgB,iBAAiB,KAAa,WAAqB,SAAiB,KAAY;CAC9F,IAAI,SAAS,OAAO,SAAS,KAC3B,MAAM,IAAI,MAAM,sDAAsD,OAAO,EAAE;CAIjF,MAAM,IAAI,eADW,4BAA4B,KAAK,SAC7B,GAAc,MAAM;AAC/C;;;;;;;;;;;;;;;;;AAgCA,IAAa,cAAb,cAGU,MAAM;CACd,CAAU,sBAAsB;CAChC;CACA;CACA;CAEA,YAAY,MAAa,MAAa,SAA+B;EACnE,MAAM,gBAAgB,MAAM;EAC5B,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,KAAK,SAAS;GAAE;GAAM;EAAK;EAE3B,sBAAsB,MAAM,aAAa;EAEzC,MAAM,SAAS,SAAS,UAAU;EAClC,IAAI,SAAS,OAAO,SAAS,KAC3B,MAAM,IAAI,MAAM,8CAA8C,OAAO,EAAE;EAEzE,KAAK,SAAS;CAChB;AACF;AAMA,IAAI,mBAAmB;;;;;;;;;;;;AAavB,SAAgB,UAAU,SAAiC;CACzD,MAAM,QAAQ,aAAc;CAC5B,IAAI,OAAO;EACT,MAAM,OAAO;EACb;CACF;CAEA,IAAI,CAAC,kBAAkB;EACrB,mBAAmB;EACnB,QAAQ,KACN,gIAEF;CACF;AACF"}
@@ -1,34 +0,0 @@
1
- import { createContext, createElement, useContext, useMemo } from "react";
2
- //#region src/client/segment-context.ts
3
- /**
4
- * Segment Context — provides layout segment position for useSelectedLayoutSegment hooks.
5
- *
6
- * Each layout in the segment tree is wrapped with a SegmentProvider that stores
7
- * the URL segments from root to the current layout level. The hooks read this
8
- * context to determine which child segments are active below the calling layout.
9
- *
10
- * The context value is intentionally minimal: just the segment path array and
11
- * parallel route keys. No internal cache details are exposed.
12
- *
13
- * Design docs: design/19-client-navigation.md, design/14-ecosystem.md
14
- */
15
- var SegmentContext = createContext(null);
16
- /** Read the segment context. Returns null if no provider is above this component. */
17
- function useSegmentContext() {
18
- return useContext(SegmentContext);
19
- }
20
- /**
21
- * Wraps each layout to provide segment position context.
22
- * Injected by rsc-entry.ts during element tree construction.
23
- */
24
- function SegmentProvider({ segments, segmentId: _segmentId, parallelRouteKeys, children }) {
25
- const value = useMemo(() => ({
26
- segments,
27
- parallelRouteKeys
28
- }), [segments, parallelRouteKeys]);
29
- return createElement(SegmentContext.Provider, { value }, children);
30
- }
31
- //#endregion
32
- export { useSegmentContext as n, SegmentProvider as t };
33
-
34
- //# sourceMappingURL=segment-context-D9_89u34.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"segment-context-D9_89u34.js","names":[],"sources":["../../src/client/segment-context.ts"],"sourcesContent":["/**\n * Segment Context — provides layout segment position for useSelectedLayoutSegment hooks.\n *\n * Each layout in the segment tree is wrapped with a SegmentProvider that stores\n * the URL segments from root to the current layout level. The hooks read this\n * context to determine which child segments are active below the calling layout.\n *\n * The context value is intentionally minimal: just the segment path array and\n * parallel route keys. No internal cache details are exposed.\n *\n * Design docs: design/19-client-navigation.md, design/14-ecosystem.md\n */\n\n'use client';\n\nimport { createContext, useContext, createElement, useMemo } from 'react';\n\n// ─── Types ───────────────────────────────────────────────────────\n\nexport interface SegmentContextValue {\n /** URL segments from root to this layout (e.g. ['', 'dashboard', 'settings']) */\n segments: string[];\n /** Parallel route slot keys available at this layout level (e.g. ['sidebar', 'modal']) */\n parallelRouteKeys: string[];\n}\n\n// ─── Context ─────────────────────────────────────────────────────\n\nconst SegmentContext = createContext<SegmentContextValue | null>(null);\n\n/** Read the segment context. Returns null if no provider is above this component. */\nexport function useSegmentContext(): SegmentContextValue | null {\n return useContext(SegmentContext);\n}\n\n// ─── Provider ────────────────────────────────────────────────────\n\ninterface SegmentProviderProps {\n segments: string[];\n /**\n * Unique identifier for this segment, used by the client-side segment\n * merger for element caching. For route groups this includes the group\n * name (e.g., \"/(marketing)\") since groups share their parent's urlPath.\n * Falls back to the reconstructed path from `segments` if not provided.\n */\n segmentId?: string;\n parallelRouteKeys: string[];\n children: React.ReactNode;\n}\n\n/**\n * Wraps each layout to provide segment position context.\n * Injected by rsc-entry.ts during element tree construction.\n */\nexport function SegmentProvider({\n segments,\n segmentId: _segmentId,\n parallelRouteKeys,\n children,\n}: SegmentProviderProps) {\n const value = useMemo(\n () => ({ segments, parallelRouteKeys }),\n // segments and parallelRouteKeys are static per layout — the layout's\n // position in the tree is fixed. The arrays are used directly as deps\n // (React Compiler requires simple expressions here). Referential stability\n // of the context value doesn't matter: every consumer\n // (useSelectedLayoutSegment/useSelectedLayoutSegments) also calls\n // usePathname(), so it re-renders on every navigation regardless.\n [segments, parallelRouteKeys]\n );\n return createElement(SegmentContext.Provider, { value }, children);\n}\n"],"mappings":";;;;;;;;;;;;;;AA4BA,IAAM,iBAAiB,cAA0C,IAAI;;AAGrE,SAAgB,oBAAgD;CAC9D,OAAO,WAAW,cAAc;AAClC;;;;;AAqBA,SAAgB,gBAAgB,EAC9B,UACA,WAAW,YACX,mBACA,YACuB;CACvB,MAAM,QAAQ,eACL;EAAE;EAAU;CAAkB,IAOrC,CAAC,UAAU,iBAAiB,CAC9B;CACA,OAAO,cAAc,eAAe,UAAU,EAAE,MAAM,GAAG,QAAQ;AACnE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"sizeof-lXffQ1Vr.js","names":[],"sources":["../../src/cache/sizeof.ts"],"sourcesContent":["/**\n * Lightweight byte-size estimation for cache entries.\n *\n * Estimates the in-memory byte cost of a JavaScript value using\n * JSON.stringify().length * 2 (UTF-16 char width). This is a rough\n * approximation — it doesn't account for V8 object overhead, Map\n * metadata, or non-serializable values — but it's fast and good\n * enough for cache budget enforcement.\n *\n * Values that fail JSON serialization (circular references, BigInt,\n * etc.) return 0, allowing the entry to be cached without counting\n * toward the byte budget. This is a conservative choice: it's better\n * to cache and undercount than to reject the entry.\n */\n\n/**\n * Estimate the byte size of a value.\n *\n * Uses `JSON.stringify(value).length * 2` to approximate UTF-16\n * in-memory size. Returns 0 if the value is not serializable.\n */\nexport function estimateByteSize(value: unknown): number {\n try {\n const json = JSON.stringify(value);\n if (json === undefined) return 0;\n // Each JS string character is 2 bytes (UTF-16)\n return json.length * 2;\n } catch (e) {\n // Circular reference or non-serializable value\n console.debug('[timber] sizeof estimation failed:', e instanceof Error ? e.message : e);\n return 0;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,iBAAiB,OAAwB;CACvD,IAAI;EACF,MAAM,OAAO,KAAK,UAAU,KAAK;EACjC,IAAI,SAAS,KAAA,GAAW,OAAO;EAE/B,OAAO,KAAK,SAAS;CACvB,SAAS,GAAG;EAEV,QAAQ,MAAM,sCAAsC,aAAa,QAAQ,EAAE,UAAU,CAAC;EACtF,OAAO;CACT;AACF"}
@@ -1,60 +0,0 @@
1
- import { f as ssrDataProvider, l as currentSsrData, n as _setCurrentSsrData } from "./state-FippDgxN.js";
2
- //#region src/client/ssr-data.ts
3
- /**
4
- * SSR Data — per-request state for client hooks during server-side rendering.
5
- *
6
- * RSC and SSR are separate Vite module graphs (see design/18-build-system.md),
7
- * so the RSC environment's request-context ALS is not visible to SSR modules.
8
- * This module provides getter/setter functions that ssr-entry.ts uses to
9
- * populate per-request data for React's render.
10
- *
11
- * Request isolation: On the server, ssr-entry.ts registers an ALS-backed
12
- * provider via registerSsrDataProvider(). getSsrData() reads from the ALS
13
- * store, ensuring correct per-request data even when Suspense boundaries
14
- * resolve asynchronously across concurrent requests. The module-level
15
- * setSsrData/clearSsrData functions are kept as a fallback for tests
16
- * and environments without ALS.
17
- *
18
- * IMPORTANT: This module must NOT import node:async_hooks or any Node.js-only
19
- * APIs, as it's imported by 'use client' hooks that are bundled for the browser.
20
- * The ALS instance lives in ssr-entry.ts (server-only); this module only holds
21
- * a reference to the provider function.
22
- *
23
- * All mutable state is delegated to client/state.ts for singleton guarantees.
24
- * See design/18-build-system.md §"Singleton State Registry"
25
- */
26
- /**
27
- * Set the SSR data for the current request via module-level state.
28
- *
29
- * In production, ssr-entry.ts uses ALS (runWithSsrData) instead.
30
- * This function is retained for tests and as a fallback.
31
- */
32
- function setSsrData(data) {
33
- _setCurrentSsrData(data);
34
- }
35
- /**
36
- * Clear the SSR data after rendering completes.
37
- *
38
- * In production, ALS scope handles cleanup automatically.
39
- * This function is retained for tests and as a fallback.
40
- */
41
- function clearSsrData() {
42
- _setCurrentSsrData(void 0);
43
- }
44
- /**
45
- * Read the current request's SSR data. Returns undefined when called
46
- * outside an SSR render (i.e. on the client after hydration).
47
- *
48
- * Prefers the ALS-backed provider when registered (server-side),
49
- * falling back to module-level state (tests, legacy).
50
- *
51
- * Used by client hooks' server snapshot functions.
52
- */
53
- function getSsrData() {
54
- if (ssrDataProvider) return ssrDataProvider();
55
- return currentSsrData;
56
- }
57
- //#endregion
58
- export { getSsrData as n, setSsrData as r, clearSsrData as t };
59
-
60
- //# sourceMappingURL=ssr-data-D6T6Y3ef.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ssr-data-D6T6Y3ef.js","names":[],"sources":["../../src/client/ssr-data.ts"],"sourcesContent":["/**\n * SSR Data — per-request state for client hooks during server-side rendering.\n *\n * RSC and SSR are separate Vite module graphs (see design/18-build-system.md),\n * so the RSC environment's request-context ALS is not visible to SSR modules.\n * This module provides getter/setter functions that ssr-entry.ts uses to\n * populate per-request data for React's render.\n *\n * Request isolation: On the server, ssr-entry.ts registers an ALS-backed\n * provider via registerSsrDataProvider(). getSsrData() reads from the ALS\n * store, ensuring correct per-request data even when Suspense boundaries\n * resolve asynchronously across concurrent requests. The module-level\n * setSsrData/clearSsrData functions are kept as a fallback for tests\n * and environments without ALS.\n *\n * IMPORTANT: This module must NOT import node:async_hooks or any Node.js-only\n * APIs, as it's imported by 'use client' hooks that are bundled for the browser.\n * The ALS instance lives in ssr-entry.ts (server-only); this module only holds\n * a reference to the provider function.\n *\n * All mutable state is delegated to client/state.ts for singleton guarantees.\n * See design/18-build-system.md §\"Singleton State Registry\"\n */\n\nimport {\n ssrDataProvider,\n currentSsrData,\n _setSsrDataProvider,\n _setCurrentSsrData,\n} from './state.ts';\nimport type { CoercedParams } from '../shared/param-value.ts';\nimport type { SlotParamsRecord } from '../shared/slot-params.ts';\n\n// ─── Types ────────────────────────────────────────────────────────\n\nexport interface SsrData {\n /** The request's URL pathname (e.g. '/dashboard/settings') */\n pathname: string;\n /**\n * The request's raw app-visible query string (`''` or `?…`), matching what\n * `window.location.search` will hold after hydration. The raw string is\n * the only representation carried here: it preserves repeated keys\n * (`?tag=a&tag=b`), which a collapsed `Record<string, string>` cannot\n * (TIM-1428). Parse with `new URLSearchParams(search)` where needed.\n */\n search: string;\n /** The request's cookies as name→value pairs */\n cookies: Map<string, string>;\n /** The request's route params (e.g. { id: '123' }) */\n params: CoercedParams;\n /**\n * Per-slot params keyed by slot tree path, absent when the request rendered\n * no slot with params of its own (TIM-1285).\n */\n slotParams?: SlotParamsRecord;\n /**\n * Mutable reference to NavContext for error boundary → pipeline communication.\n *\n * When TimberErrorBoundary catches a DenySignal during SSR, it:\n * 1. Sets `statusCode` to the deny status (e.g., 403) — so the HTTP\n * Response has the correct status code without a re-render.\n * 2. Sets `_denyHandledByBoundary = true` — so the pipeline skips\n * the redundant renderDenyPage() re-render.\n *\n * This runs synchronously during Fizz rendering, BEFORE onShellReady,\n * so the status code is committed before any bytes are sent.\n *\n * See TIM-664, design/04-authorization.md §\"React.cache Scope in Deny/Error Re-renders\"\n */\n _navContext?: { statusCode?: number; _denyHandledByBoundary?: boolean };\n}\n\n// ─── ALS-Backed Provider ─────────────────────────────────────────\n//\n// Server-side code (ssr-entry.ts) registers a provider that reads\n// from AsyncLocalStorage. This avoids importing node:async_hooks\n// in this browser-bundled module.\n//\n// Module singleton guarantee: In Vite's SSR environment, both\n// ssr-entry.ts (via #/client/ssr-data.js) and client component hooks\n// (via @timber-js/app/client) must resolve to the SAME module instance\n// of this file. The timber-shims plugin ensures this by remapping\n// @timber-js/app/client → src/client/index.ts in the SSR environment.\n// Without this remap, @timber-js/app/client resolves to dist/ (via\n// package.json exports), creating a split where registerSsrDataProvider\n// writes to one instance but getSsrData reads from another.\n// See timber-shims plugin resolveId for details.\n\n/**\n * Register an ALS-backed SSR data provider. Called once at module load\n * by ssr-entry.ts to wire up per-request data via AsyncLocalStorage.\n *\n * When registered, getSsrData() reads from the provider (ALS store)\n * instead of module-level state, ensuring correct isolation for\n * concurrent requests with streaming Suspense.\n */\nexport function registerSsrDataProvider(provider: () => SsrData | undefined): void {\n _setSsrDataProvider(provider);\n}\n\n// ─── Module-Level Fallback ────────────────────────────────────────\n//\n// Used by tests and as a fallback when no ALS provider is registered.\n\n/**\n * Set the SSR data for the current request via module-level state.\n *\n * In production, ssr-entry.ts uses ALS (runWithSsrData) instead.\n * This function is retained for tests and as a fallback.\n */\nexport function setSsrData(data: SsrData): void {\n _setCurrentSsrData(data);\n}\n\n/**\n * Clear the SSR data after rendering completes.\n *\n * In production, ALS scope handles cleanup automatically.\n * This function is retained for tests and as a fallback.\n */\nexport function clearSsrData(): void {\n _setCurrentSsrData(undefined);\n}\n\n/**\n * Read the current request's SSR data. Returns undefined when called\n * outside an SSR render (i.e. on the client after hydration).\n *\n * Prefers the ALS-backed provider when registered (server-side),\n * falling back to module-level state (tests, legacy).\n *\n * Used by client hooks' server snapshot functions.\n */\nexport function getSsrData(): SsrData | undefined {\n if (ssrDataProvider) {\n return ssrDataProvider();\n }\n return currentSsrData;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8GA,SAAgB,WAAW,MAAqB;CAC9C,mBAAmB,IAAI;AACzB;;;;;;;AAQA,SAAgB,eAAqB;CACnC,mBAAmB,KAAA,CAAS;AAC9B;;;;;;;;;;AAWA,SAAgB,aAAkC;CAChD,IAAI,iBACF,OAAO,gBAAgB;CAEzB,OAAO;AACT"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"state-FippDgxN.js","names":[],"sources":["../../src/client/state.ts"],"sourcesContent":["/**\n * Centralized client singleton state registry.\n *\n * ALL mutable module-level state that must have singleton semantics across\n * the client bundle lives here. Individual modules (router-ref.ts, ssr-data.ts,\n * use-segment-params.ts, use-search-params.ts, unload-guard.ts) import from this file\n * and re-export thin wrapper functions.\n *\n * Why: In Vite dev, a module is instantiated separately if reached via different\n * import paths (e.g., relative `./foo.js` vs barrel `@timber-js/app/client`).\n * By centralizing all mutable state in a single module that is always reached\n * through the same dependency chain (barrel → wrapper → state.ts), we guarantee\n * a single instance of every piece of shared state.\n *\n * DO NOT import this file from outside client/. Server code must never depend\n * on client state. The barrel (client/index.ts) is the public entry point.\n *\n * See design/18-build-system.md §\"Module Singleton Strategy\" and\n * §\"Singleton State Registry\".\n */\n\nimport type { RouterInstance } from './router-types.ts';\nimport type { SsrData } from './ssr-data.ts';\n\n// ─── Router (from router-ref.ts) ──────────────────────────────────────────\n\n/** The global router singleton — set once during bootstrap. */\nexport let globalRouter: RouterInstance | null = null;\n\nexport function _setGlobalRouter(router: RouterInstance | null): void {\n globalRouter = router;\n}\n\n// ─── SSR Data Provider (from ssr-data.ts) ──────────────────────────────────\n\n/**\n * ALS-backed SSR data provider. When registered, getSsrData() reads from\n * this function (ALS store) instead of module-level currentSsrData.\n */\nexport let ssrDataProvider: (() => SsrData | undefined) | undefined;\n\nexport function _setSsrDataProvider(provider: (() => SsrData | undefined) | undefined): void {\n ssrDataProvider = provider;\n}\n\n/** Fallback SSR data for tests and environments without ALS. */\nexport let currentSsrData: SsrData | undefined;\n\nexport function _setCurrentSsrData(data: SsrData | undefined): void {\n currentSsrData = data;\n}\n\n// ─── Search Params Cache (from use-search-params.ts) ────────────────────────\n\n/** Cached search string — avoids reparsing when URL hasn't changed. */\nexport let cachedSearch = '';\nexport let cachedSearchParams = new URLSearchParams();\n\nexport function _setCachedSearch(search: string, params: URLSearchParams): void {\n cachedSearch = search;\n cachedSearchParams = params;\n}\n\n// ─── Unload Guard (from unload-guard.ts) ─────────────────────────────────────\n\n/** Whether the page is currently being unloaded. */\nexport let unloading = false;\n\nexport function _setUnloading(value: boolean): void {\n unloading = value;\n}\n\n// ─── Status Page Commit Flag (from navigation-commit.ts) ────────────────────\n\n/**\n * Whether a status page for the main route has rendered since the last\n * navigation was prepared. Raised by `StatusPageMarker`, consumed by the\n * navigation's commit. See navigation-commit.ts §\"What a status page\n * teaches the cache\".\n */\nexport let statusPageRendered = false;\n\nexport function _setStatusPageRendered(value: boolean): void {\n statusPageRendered = value;\n}\n\n/**\n * What a status page report does beyond raising the flag: registered by the\n * router's committer so the report can clear the segment cache at once, for\n * the commits no navigation consumes (a row arriving inside Suspense after\n * its navigation published, a status document being hydrated).\n */\nexport let onStatusPageRendered: (() => void) | undefined;\n\nexport function _setOnStatusPageRendered(handler: (() => void) | undefined): void {\n onStatusPageRendered = handler;\n}\n"],"mappings":";;AA2BA,IAAW,eAAsC;AAEjD,SAAgB,iBAAiB,QAAqC;CACpE,eAAe;AACjB;;;;;AAQA,IAAW;;AAOX,IAAW;AAEX,SAAgB,mBAAmB,MAAiC;CAClE,iBAAiB;AACnB;;AAKA,IAAW,eAAe;AAC1B,IAAW,qBAAqB,IAAI,gBAAgB;AAEpD,SAAgB,iBAAiB,QAAgB,QAA+B;CAC9E,eAAe;CACf,qBAAqB;AACvB;;AAKA,IAAW,YAAY;AAEvB,SAAgB,cAAc,OAAsB;CAClD,YAAY;AACd;;;;;;;AAUA,IAAW,qBAAqB;AAEhC,SAAgB,uBAAuB,OAAsB;CAC3D,qBAAqB;AACvB;;;;;;;AAQA,IAAW;AAEX,SAAgB,yBAAyB,SAAyC;CAChF,uBAAuB;AACzB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"status-page-marker-DwQBrLBz.js","names":[],"sources":["../../src/shared/rsc-error-envelope.ts","../../src/client/segment-cache.ts","../../src/client/navigation-commit.ts","../../src/client/status-page-marker.tsx"],"sourcesContent":["/**\n * The hard-navigate error envelope shared by the server and the client router.\n *\n * A client-navigation (RSC) request cannot carry an error *page* — the response\n * body must be Flight data or the decoder crashes. Instead the server answers\n * with this envelope: an `X-Timber-Error: 1` header and a minimal JSON body.\n * The client treats it as \"give up on SPA navigation and hard-navigate\", and\n * the resulting document request renders the real error page as HTML.\n *\n * The header name lives here so the four server producers and the single\n * client consumer cannot drift apart. See design/10-error-handling.md\n * §\"Error Page Rendering for Client Navigation\".\n */\n\n/** Header the client checks to decide whether to hard-navigate. */\nexport const TIMBER_ERROR_HEADER = 'X-Timber-Error';\n","// Segment Cache — stores the mounted segment tree and prefetched payloads\n// See design/19-client-navigation.md for architecture details.\n\nimport type { PublishedParams } from '../shared/payload-root.ts';\nimport type { SegmentInfo, StateTree } from '../shared/segment-info.ts';\nimport { createSingleflight } from '../cache/singleflight.ts';\n\n// ─── Types ───────────────────────────────────────────────────────\n\n/** A prefetched RSC result with optional segment metadata. */\nexport interface PrefetchResult {\n payload: unknown;\n /**\n * Completion of the full Flight stream, when the prefetch is still decoding.\n * Carried through to the navigation that consumes the entry so a decode\n * failure reaches *that* navigation's recovery rather than dying with the\n * prefetch (TIM-1275).\n */\n decodePromise?: Promise<void> | null;\n /** Segment metadata from X-Timber-Segments header for populating the segment cache. */\n segmentInfo?: SegmentInfo[] | null;\n /**\n * The params published beside `payload`, split off the same payload root.\n * Carried so a consumed prefetch publishes the params of the tree it is\n * about to render, and not the ones already on screen.\n */\n params?: PublishedParams | Promise<PublishedParams>;\n /** Segment paths skipped by the server (for client-side merging). */\n skippedSegments?: string[] | null;\n}\n\n/**\n * A node in the client-side segment tree. Each node represents a mounted\n * layout or page segment with its RSC flight payload.\n */\nexport interface SegmentNode {\n /** The segment's URL pattern (e.g., \"/\", \"/dashboard\", \"/projects/[id]\") */\n segment: string;\n /**\n * The segment's `app/` directory path, groups included (e.g. `/(browse)/feed`).\n * Undefined for slot nodes and for servers that predate the field.\n */\n treePath?: string;\n /** The RSC flight payload for this segment (opaque to the cache) */\n payload: unknown;\n /**\n * Whether the segment/slot is request-dependent (calls getHeaders,\n * getSearchParams, cookies, etc.). Request-dependent segments always\n * re-render on navigation. For segments, this is still based on the\n * AsyncFunction heuristic (to be replaced separately). For slots,\n * this is taint-tracked via ALS.\n */\n isRequestDependent: boolean;\n /** Child segments keyed by segment path */\n children: Map<string, SegmentNode>;\n /** Parallel route slots keyed by slot path (e.g., \"/@sidebar\") */\n slots?: Map<string, SegmentNode>;\n /** Whether this slot's access.ts denied on its last render. */\n denied?: boolean;\n /**\n * Content key for slot nodes — opaque string the server computed from\n * owner parts, slot name, entry file, and slot params. Advertised back\n * to the server so it can decide skips by key membership (TIM-1370).\n */\n contentKey?: string;\n}\n\n// ─── Segment Cache ───────────────────────────────────────────────\n\n/**\n * Maintains the client-side segment tree representing currently mounted\n * layouts and pages. Used for navigation reconciliation — the router diffs\n * new routes against this tree to determine which segments to re-fetch.\n */\nexport class SegmentCache {\n private root: SegmentNode | undefined;\n\n get(segment: string): SegmentNode | undefined {\n if (segment === '/' || segment === this.root?.segment) {\n return this.root;\n }\n return undefined;\n }\n\n set(segment: string, node: SegmentNode): void {\n if (segment === '/' || !this.root) {\n this.root = node;\n }\n }\n\n clear(): void {\n this.root = undefined;\n }\n\n /**\n * Serialize the mounted segment tree for the X-Timber-State-Tree header.\n * Only includes sync segments — async segments are excluded because the\n * server must always re-render them (they may depend on request context).\n *\n * When mergeableFilter is provided, only segments whose paths are in the\n * set are included. This ensures the server only skips segments that the\n * client can actually merge (i.e., segments whose cached element tree\n * contains an inner SegmentProvider the merger can splice into).\n *\n * `treePaths` is collected UNFILTERED, unlike `segments` and `slots`. Those\n * two answer \"what may the server skip re-rendering?\", so every reason a\n * segment cannot be reused is a reason to leave it out. `treePaths` answers\n * \"what is mounted right now?\" for interception scoping — a request-dependent\n * layout is still on screen, and dropping it would silently narrow the scope\n * the server sees and cancel a modal that should open (TIM-1282).\n *\n * This is a performance optimization only, NOT a security boundary.\n * The server always runs all access.ts files regardless of the state tree.\n */\n serializeStateTree(mergeableFilter?: Set<string>): StateTree {\n const segments: string[] = [];\n const slots: string[] = [];\n const treePaths: string[] = [];\n if (this.root) {\n collectSyncSegments(this.root, segments, mergeableFilter);\n collectSyncSlots(this.root, slots);\n collectTreePaths(this.root, treePaths);\n }\n const tree: StateTree = { segments };\n if (slots.length > 0) {\n tree.slots = slots;\n }\n if (treePaths.length > 0) {\n tree.treePaths = treePaths;\n }\n return tree;\n }\n}\n\n/** Recursively collect sync segment paths from the tree */\nfunction collectSyncSegments(\n node: SegmentNode,\n out: string[],\n mergeableFilter?: Set<string>\n): void {\n if (!node.isRequestDependent && (!mergeableFilter || mergeableFilter.has(node.segment))) {\n out.push(node.segment);\n }\n for (const child of node.children.values()) {\n collectSyncSegments(child, out, mergeableFilter);\n }\n}\n\n/**\n * Recursively collect the `app/` directory path of every mounted segment.\n *\n * Slots are not walked: a slot's own sub-tree can never own an intercepting\n * slot's scope, because scopes are always ordinary segments (`interception.ts`\n * derives one from the slot owner's ancestor chain).\n */\nfunction collectTreePaths(node: SegmentNode, out: string[]): void {\n if (node.treePath) out.push(node.treePath);\n for (const child of node.children.values()) {\n collectTreePaths(child, out);\n }\n}\n\n/**\n * Recursively collect content keys from cacheable slots.\n *\n * The server advertises a content key per slot on every render (TIM-1370),\n * encoding the owner's URL parts, the slot name, the matched entry file,\n * and the slot's params. The client stores this key and sends it back on\n * the next navigation so the server can decide skips by key membership —\n * no departing URL reconstruction needed.\n */\nfunction collectSyncSlots(node: SegmentNode, out: string[]): void {\n if (node.slots) {\n for (const slot of node.slots.values()) {\n if (!slot.isRequestDependent && !slot.denied && slot.contentKey) {\n out.push(slot.contentKey);\n }\n }\n }\n for (const child of node.children.values()) {\n collectSyncSlots(child, out);\n }\n}\n\n// ─── Segment Tree Builder ────────────────────────────────────────\n\n/**\n * Build a SegmentNode tree from flat segment metadata.\n *\n * Takes an ordered list of segment descriptors (root → leaf) from the\n * server's X-Timber-Segments header and constructs the hierarchical\n * tree structure that SegmentCache expects.\n *\n * Each segment is nested as a child of the previous one, forming a\n * linear chain from root to leaf. The leaf segment (page) is excluded\n * from the tree — pages are never cached across navigations.\n */\nexport function buildSegmentTree(segments: SegmentInfo[]): SegmentNode | undefined {\n // Need at least a root segment to build a tree\n if (segments.length === 0) return undefined;\n\n // Separate slot entries from segment entries. Slots are attached to\n // their parent segment node after the main chain is built.\n const segmentEntries: SegmentInfo[] = [];\n const slotEntries: SegmentInfo[] = [];\n for (const info of segments) {\n if (info.slot) {\n slotEntries.push(info);\n } else {\n segmentEntries.push(info);\n }\n }\n\n // Build the main segment chain.\n let root: SegmentNode | undefined;\n let parent: SegmentNode | undefined;\n const nodeById = new Map<string, SegmentNode>();\n\n for (const info of segmentEntries) {\n const id = info.segmentId ?? info.path;\n const node: SegmentNode = {\n segment: id,\n treePath: info.treePath,\n payload: null,\n isRequestDependent: info.isRequestDependent,\n children: new Map(),\n };\n\n nodeById.set(id, node);\n\n if (!root) {\n root = node;\n }\n\n if (parent) {\n parent.children.set(id, node);\n }\n\n parent = node;\n }\n\n // Attach slot entries to their parent segment nodes.\n for (const slotInfo of slotEntries) {\n const parentId = slotInfo.parentSegment;\n const parentNode = parentId ? nodeById.get(parentId) : root;\n if (!parentNode) continue;\n\n const slotId = slotInfo.segmentId ?? slotInfo.path;\n const slotNode: SegmentNode = {\n segment: slotId,\n payload: null,\n isRequestDependent: slotInfo.isRequestDependent,\n children: new Map(),\n denied: slotInfo.denied,\n contentKey: slotInfo.contentKey,\n };\n\n if (!parentNode.slots) {\n parentNode.slots = new Map();\n }\n parentNode.slots.set(slotId, slotNode);\n }\n\n return root;\n}\n\n// ─── Prefetch Cache ──────────────────────────────────────────────\n\ninterface ReadyEntry {\n result: PrefetchResult;\n expiresAt: number;\n}\n\n/** Sentinel value for negative cache entries (URL is not a route). */\nconst NEGATIVE_ENTRY: PrefetchResult = Object.freeze({ payload: null });\n\n/**\n * Timeout for the in-flight singleflight (TIM-1438). Per CLAUDE.md's\n * singleflight rule: \"Never write a coalescing Map without a timeout.\"\n * A hung prefetch cannot block clicks or suppress hovers forever.\n */\nconst PREFETCH_SINGLEFLIGHT_TIMEOUT_MS = 5_000;\n\n/**\n * Identifies a prefetched payload. A payload is addressed by *every* input that\n * changes the server's answer, not by the destination alone.\n *\n * The server's answer for a destination depends on where the request came\n * from — that is the whole mechanism behind intercepting routes, and it also\n * drives slot-skip resolution. A prefetch issued from `/gallery` is a modal\n * payload; the same URL prefetched from `/` is the full page. Keying on the\n * destination alone let the first click after a hover be answered with\n * whichever variant happened to be fetched (TIM-1265).\n *\n * `from` alone stopped being enough in TIM-1282. It is a pathname, and once\n * interception scope comes from the rendered tree rather than the address, one\n * pathname no longer names one source tree: `/photo/1` is the address both for\n * a modal open over `(browse)` and for `(landing)`'s canonical page. A key\n * without the scope hands the modal payload to the canonical page's click,\n * which renders it without the server's scope check ever running.\n *\n * The two reads happen at different instants — `scope` is sampled at hover and\n * again at click — which is why a tree that changed in between produces a\n * *miss* and a refetch rather than a wrong hit.\n */\nexport interface PrefetchKey {\n /** Destination URL — hash-less `pathname + search`, as navigate() fetches it. */\n url: string;\n /** Departing pathname — the `X-Timber-URL` the prefetch was issued with. */\n from: string;\n /**\n * The rendered scope the prefetch was issued with — the `treePaths` half of\n * `X-Timber-State-Tree`, joined. Empty when none crossed.\n */\n scope: string;\n}\n\n/**\n * Compose the map key. Length-prefixed rather than delimiter-joined for the\n * same reason `shared/rsc-cache-key.ts` is: any delimiter is forgeable by a\n * value containing it, and a URL may contain any character a delimiter could.\n */\nfunction prefetchMapKey(key: PrefetchKey): string {\n return `${key.from.length}:${key.from}:${key.scope.length}:${key.scope}:${key.url}`;\n}\n\n/**\n * The `scope` half of a `PrefetchKey`, derived from the state tree that will\n * actually be sent. Taking it from the header value rather than re-walking the\n * cache is the point: the key varies by exactly what the request varies by.\n */\nexport function prefetchScopeOf(stateTree: StateTree | undefined): string {\n return stateTree?.treePaths?.join('\\u0000') ?? '';\n}\n\n/** Outcome of a prefetch flight — discriminated so NonRscResponse is a\n * success (stores a negative entry) rather than a rejection. */\nexport type FlightOutcome = { kind: 'ready'; result: PrefetchResult } | { kind: 'non-route' };\n\n/**\n * Short-lived cache for hover-triggered prefetches. Entries expire after\n * 30 seconds. When a link is clicked, the prefetched payload is consumed\n * (moved to the history stack) and removed from this cache.\n *\n * In-flight dedup (TIM-1438): concurrent fetches for the same key are\n * coalesced by a `createSingleflight` instance. A hover starts a flight;\n * a click for the same key joins it instead of issuing a duplicate. The\n * singleflight enforces a 5-second timeout and cleans up automatically\n * on settlement.\n *\n * timber.js does NOT prefetch on viewport intersection — only explicit\n * hover on <Link prefetch> triggers a prefetch.\n */\nexport class PrefetchCache {\n private static readonly TTL_MS = 30_000;\n private entries = new Map<string, ReadyEntry>();\n private flights = createSingleflight({ timeoutMs: PREFETCH_SINGLEFLIGHT_TIMEOUT_MS });\n private evictionGen = 0;\n\n set(key: PrefetchKey, result: PrefetchResult): void {\n this.entries.set(prefetchMapKey(key), {\n result,\n expiresAt: Date.now() + PrefetchCache.TTL_MS,\n });\n }\n\n get(key: PrefetchKey): PrefetchResult | undefined {\n const mapKey = prefetchMapKey(key);\n const entry = this.entries.get(mapKey);\n if (!entry) return undefined;\n if (Date.now() >= entry.expiresAt) {\n this.entries.delete(mapKey);\n return undefined;\n }\n return entry.result;\n }\n\n /** True if a ready or negative entry exists for this key. */\n has(key: PrefetchKey): boolean {\n return this.get(key) !== undefined;\n }\n\n /** Get and remove the entry (used when navigation consumes a prefetch) */\n consume(key: PrefetchKey): PrefetchResult | undefined {\n const result = this.get(key);\n if (result !== undefined) {\n this.entries.delete(prefetchMapKey(key));\n }\n return result;\n }\n\n /**\n * Fetch or coalesce with an in-flight fetch for this key (TIM-1438).\n *\n * Concurrent callers (hover + click, repeated hovers) get the same\n * promise. On success the result is stored as a ready entry. On\n * NonRscResponse a negative entry is stored and the outcome is\n * `{ kind: 'non-route' }`. Other errors (network, version skew)\n * reject — the singleflight cleans up the key and subsequent callers\n * retry.\n *\n * The singleflight signal is passed to `doFetch` so a timed-out flight\n * aborts the underlying fetch rather than leaving it running.\n */\n fetchOrCoalesce(\n key: PrefetchKey,\n doFetch: (signal: AbortSignal) => Promise<PrefetchResult>,\n isNonRoute: (error: unknown) => boolean\n ): Promise<FlightOutcome> {\n // Capture the eviction generation before the flight starts. If an\n // action evicts caches while this flight is in progress, the gen\n // will have bumped and the stale result is discarded (TIM-1476).\n const genAtStart = this.evictionGen;\n return this.flights.do(prefetchMapKey(key), async (signal) => {\n try {\n const result = await doFetch(signal);\n if (!signal.aborted && this.evictionGen === genAtStart) this.set(key, result);\n return { kind: 'ready' as const, result };\n } catch (err) {\n if (isNonRoute(err)) {\n if (!signal.aborted && this.evictionGen === genAtStart) this.setNegative(key);\n return { kind: 'non-route' as const };\n }\n throw err;\n }\n });\n }\n\n /**\n * Join an in-flight singleflight fetch if one exists for this key.\n * Returns the in-flight promise or undefined. Used by the click path to\n * coalesce with a hover prefetch without starting a new flight — when no\n * flight exists, the click issues its own fetch with the navigation's\n * abort signal so superseded navigations abort immediately (TIM-1438).\n */\n joinInflight(key: PrefetchKey): Promise<FlightOutcome> | undefined {\n return this.flights.get(prefetchMapKey(key)) as Promise<FlightOutcome> | undefined;\n }\n\n /**\n * Store a negative entry — the URL is not a route (non-RSC Content-Type).\n *\n * Keyed like every other entry even though \"not a route\" does not actually\n * vary by departing URL: a source-independent negative would be a second\n * keying rule to keep correct, and the only cost of the uniform one is a\n * repeated fetch for a link hovered from a second page.\n */\n setNegative(key: PrefetchKey): void {\n this.set(key, NEGATIVE_ENTRY);\n }\n\n /** Check if the entry is a negative cache entry (URL is not a route). */\n isNegative(key: PrefetchKey): boolean {\n return this.get(key) === NEGATIVE_ENTRY;\n }\n\n /**\n * Invalidate all entries whose destination URL matches (TIM-1454).\n * Used by `revalidatePath` on a non-current route so the next navigation\n * fetches fresh data instead of replaying a stale prefetch.\n */\n invalidateUrl(url: string): void {\n for (const [mapKey] of this.entries) {\n if (mapKey.endsWith(':' + url)) {\n this.entries.delete(mapKey);\n }\n }\n }\n\n /**\n * Evict all ready entries and fence in-flight singleflights (TIM-1476).\n * Called after a server action that revalidated data — the mutation may\n * affect any route, not just the paths the server named. In-flight\n * singleflights are not aborted (they may carry post-mutation data),\n * but their results are discarded if they complete after eviction:\n * `fetchOrCoalesce` captures the generation before the flight and\n * skips the store when it has been bumped.\n *\n * Consumers that join an in-flight flight via `joinInflight` must\n * check `getEvictionGen()` before and after the await: if the gen\n * changed, the result predates the eviction and must be discarded\n * (codex on #1131 round 2).\n */\n clearReady(): void {\n this.entries.clear();\n this.evictionGen++;\n }\n\n getEvictionGen(): number {\n return this.evictionGen;\n }\n}\n","/**\n * What it means for a page to be \"current\" on the client.\n *\n * One module owns every write that makes a navigation's destination the\n * current page, and the read that describes the current page back to the\n * server. Keeping them together is the point: `X-Timber-State-Tree` is\n * derived from the segment cache, so the thing that publishes and the thing\n * that reports have to agree about when a page becomes current.\n *\n * See design/19-client-navigation.md §\"State Update Invariants\".\n */\n\nimport { buildSegmentTree, type SegmentCache } from './segment-cache.ts';\nimport type { HistoryStack } from './history.ts';\nimport type { SegmentInfo, StateTree } from '../shared/segment-info.ts';\nimport type { ParamsSource } from '../shared/payload-root.ts';\nimport { setNavigationState, type NavigationState } from './navigation-context.ts';\nimport {\n statusPageRendered,\n _setStatusPageRendered,\n onStatusPageRendered,\n _setOnStatusPageRendered,\n} from './state.ts';\n\n// ─── What a status page teaches the cache ─────────────────────────────────\n//\n// A status page replaces a subtree: a deny page lands at the segment owning\n// the matched file and nothing below it mounts; an `error.tsx` fallback\n// discards everything under the boundary that rendered it. `X-Timber-Segments`\n// nonetheless describes the whole chain the payload was built against — it\n// must, because a partial navigation picks its merge point out of it — so the\n// cache has to be told not to learn from such a tree.\n//\n// The HTTP status used to say so, and could not be trusted to: an RSC payload\n// response commits one macrotask after its first chunk, and a nested\n// component that denies after I/O puts its row in the stream after the\n// status line has gone out (TIM-1489). The only thing that reliably knows a\n// status page rendered is the status page itself, and the only moment that\n// matters is the commit that puts it on screen — so every status page the\n// framework produces is wrapped in `StatusPageMarker`, whose layout effect\n// reports on each commit that renders it. Layout effects run child-before-\n// parent, so the report lands before `NavigationRoot`'s publish of that\n// same commit, and never for a render React discards. Without Suspense\n// around the row, the transition cannot commit until the row has arrived, so\n// this covers the delayed nested case too, which no status ever could.\n//\n// The report also clears the cache on the spot, because not every commit\n// has a navigation to consume it: a deny row arriving inside a Suspense\n// boundary after its navigation published, or a status document being\n// hydrated, would otherwise leave a tree in the cache that is no longer on\n// screen. Consuming the flag at commit is what keeps the same navigation from\n// re-seeding it a moment later. Both are over-approximations — a page-level\n// deny leaves every layout standing — at the cost of one full render on the\n// next navigation.\n//\n// Slot boundaries do not report: a slot degrading is the page staying, and\n// the chain it mounted is the chain the header describes. See TIM-1356,\n// TIM-1495, design/04-authorization.md §\"Where a Deny Page Renders\".\n\n/**\n * Record that a status page for the main route is on screen. Called from\n * `StatusPageMarker`'s layout effect; consumed by the commit of the\n * navigation whose tree it belongs to, and acted on immediately for the\n * commits that have none.\n */\nexport function reportStatusPageRendered(): void {\n _setStatusPageRendered(true);\n onStatusPageRendered?.();\n}\n\n/** Everything a commit needs to describe the page it makes current. */\nexport interface NavigationCommitInput {\n payload: unknown;\n /** The params that payload published — replayed with it on popstate. */\n params: ParamsSource;\n /**\n * The rendered chain's metadata, or null/undefined when this commit\n * carries none at all — a revalidation payload, or a history entry\n * stored before the channel existed.\n *\n * An empty *array* is a different statement from an absent one: the\n * server rendered a route with no layout-bearing segments (a layoutless\n * app — the missing-root-layout check is a warning, not an error), so\n * the correct tree is the empty one. Collapsing the two left the\n * departing route's tree in place, which `treePaths` then reported as\n * mounted — enough to select an intercepting rewrite whose slot is not\n * on screen (TIM-1282).\n */\n segmentInfo?: SegmentInfo[] | null;\n /** When true, clear the segment cache if segmentInfo is absent\n * (popstate replay for entries without layout metadata). */\n clearSegmentCacheOnEmpty?: boolean;\n /**\n * Layouts the server skipped on this response (`X-Timber-Skipped-Segments`),\n * when it came from a fetch. Omitted by replays and revalidation.\n *\n * Together with `skipped` slot entries in `segmentInfo`, this decides\n * whether the payload can be *replayed*. A partial payload only renders\n * merged over the tree on screen, and a `skip=true` outlet resolves against\n * the slot content cache at render time — so a stored copy of either,\n * replayed later, shows whatever the client holds *then*, not what was on\n * screen when the entry committed. Such entries store a null payload and\n * traversals to them fetch instead. Decided here, not at the fetch call\n * sites, because there are three of those (`performNavigationFetch`,\n * `refresh()`, and uncached traversal) and a rule applied at one of them\n * holds by accident: `performNavigationFetch` had the check and\n * `fetchCommitAndRender` did not, so a back/forward fetch that was\n * answered with skips stored the holes (TIM-1432).\n */\n skippedSegments?: string[] | null;\n}\n\n/** Whether a response skipped layouts — its payload merges over the tree on screen. */\nexport function isPartialNavigation(skippedSegments: string[] | null | undefined): boolean {\n return skippedSegments != null && skippedSegments.length > 0;\n}\n\n/**\n * Whether a payload can stand on its own when replayed from the history\n * stack. See `NavigationCommitInput.skippedSegments`.\n */\nfunction isReplayable(opts: NavigationCommitInput): boolean {\n return !isPartialNavigation(opts.skippedSegments) && !hasSkippedSlot(opts.segmentInfo);\n}\n\nfunction hasSkippedSlot(segmentInfo: SegmentInfo[] | null | undefined): boolean {\n return segmentInfo?.some((s) => s.slot && s.skipped) ?? false;\n}\n\n/**\n * The metadata a history entry keeps. `skipped` describes the *response*\n * — \"this slot's content was omitted\" — and the entry stores no such\n * payload (it stores null). Left on the entry, the flag outlives the\n * response: `applyActionResult()` reuses the entry's metadata beside a full\n * re-render and would have that judged non-replayable too.\n */\nfunction storedSegmentInfo(segmentInfo: SegmentInfo[] | null | undefined) {\n if (!hasSkippedSlot(segmentInfo)) return segmentInfo;\n return segmentInfo!.map(({ skipped: _skipped, ...rest }) => rest);\n}\n\n/**\n * A navigation's destination, split into what a render needs and what a\n * commit publishes.\n */\nexport interface PreparedNavigation {\n /** The state the incoming tree renders with. Pure — safe before winning. */\n navState: NavigationState;\n /** Publishes the destination. Call only once the render has won. */\n commit: () => void;\n}\n\nexport interface NavigationCommitter {\n prepareNavigation(url: string, opts: NavigationCommitInput): PreparedNavigation;\n currentStateTree(): StateTree | undefined;\n updateSegmentCache(segmentInfo: SegmentInfo[] | null | undefined): void;\n}\n\n/**\n * Derive the navigation state (pathname + search) a URL renders with.\n *\n * Pure: publishing it — the module-level fallback for tests and SSR, and the\n * globalThis bridge — is `prepareNavigation`'s commit, which runs only once\n * the render is known to have won (TIM-1301). Callers pass the returned value\n * explicitly to renderRoot/wrapPayload, so a render never depends on the\n * publish having happened first.\n */\nexport function deriveNavigationState(url: string): NavigationState {\n // Params are not part of it. They travel with the payload and are published\n // by the one client-owned `ParamsProvider` as the new tree renders, which is\n // also what clears the previous route's — a route with no params of its own\n // still publishes an empty record (TIM-1294/TIM-1297, and the clearing\n // requirement from TIM-1285).\n const parsed = new URL(url, 'http://localhost');\n return { pathname: parsed.pathname || '/', search: parsed.search };\n}\n\nexport function createNavigationCommitter(deps: {\n segmentCache: SegmentCache;\n historyStack: HistoryStack;\n /** Read at call time — the router's `clientSegmentCache` opt-in. */\n clientSegmentCache: () => boolean | undefined;\n}): NavigationCommitter {\n const { segmentCache, historyStack } = deps;\n\n // See `reportStatusPageRendered`: a status page that commits with no\n // navigation to consume the report still empties the cache.\n _setOnStatusPageRendered(() => segmentCache.clear());\n\n /**\n * Update the segment cache from server-provided segment metadata.\n *\n * Runs whether or not `clientSegmentCache` is enabled: the tree is also\n * where the mounted tree paths come from, and those cross on every\n * navigation. What the flag gates is which parts get serialized — see\n * `currentStateTree`.\n */\n function updateSegmentCache(segmentInfo: SegmentInfo[] | null | undefined): void {\n if (!segmentInfo || segmentInfo.length === 0) return;\n const tree = buildSegmentTree(segmentInfo);\n if (tree) {\n segmentCache.set('/', tree);\n }\n }\n\n return {\n updateSegmentCache,\n\n /**\n * The X-Timber-State-Tree to send with the next RSC request.\n *\n * Two different things travel on one header. `segments`/`slots` are the\n * caching opt-in and are omitted unless `clientSegmentCache` is on.\n * `treePaths` is how the server learns which route is actually mounted,\n * which stops being derivable from the address bar as soon as a modal is\n * open — so it crosses regardless (TIM-1282). Undefined when there is\n * nothing to say, so a no-op header never enters the `_rsc` cache key.\n *\n * Reads what the last *committed* navigation published, so a superseded\n * navigation's destination can never end up describing the mounted tree\n * (TIM-1301).\n */\n currentStateTree(): StateTree | undefined {\n const tree = segmentCache.serializeStateTree();\n if (deps.clientSegmentCache()) return tree;\n return tree.treePaths ? { segments: [], treePaths: tree.treePaths } : undefined;\n },\n\n /**\n * Prepare all navigation-owned state for a new page — without publishing\n * any of it. Every code path that changes the \"current page\" must go\n * through this function, which is what makes \"forgot a field\" impossible\n * by construction.\n *\n * Returns the destination's `NavigationState`, which is a pure function of\n * the URL and is what the incoming tree is *rendered* with, plus a\n * `commit` thunk that performs the writes that make a page current:\n * 1. Segment cache — update from server-provided segment metadata\n * 2. Navigation state — pathname/search for usePathname/useSearchParams\n * 3. History stack — store the payload for instant back/forward replay\n *\n * Nothing is written until `commit()` runs, and on a transitioned\n * navigation it runs only once the transition is known to have won — see\n * `renderViaTransition` and `NavigationRoot`. A navigation superseded\n * while its payload was in flight never calls it, so the cache, the\n * pathname the client reports and the history stack all keep describing\n * the route still on screen (TIM-1301).\n *\n * Callers with state of their own to publish — the address bar, the\n * client's record of the mounted tree — wrap this thunk rather than\n * commit beside it, so there stays exactly one moment at which a page\n * becomes current.\n */\n prepareNavigation(url: string, opts: NavigationCommitInput): PreparedNavigation {\n const navState = deriveNavigationState(url);\n const payload = isReplayable(opts) ? opts.payload : null;\n\n // The tree this navigation renders has not rendered yet. Whatever a\n // boundary reported before now belongs to a commit that had no\n // navigation to consume it — a hydration, a row that arrived inside a\n // Suspense boundary after its navigation had published — and must not\n // be charged to this one.\n _setStatusPageRendered(false);\n\n return {\n navState,\n commit() {\n // A tree in which a boundary rendered a status page is not one to\n // cache — see the module note. Read at commit rather than at\n // prepare because that is when the boundary has spoken: the row it\n // renders may not have existed when the response headers were read.\n // The history entry records the same thing, or a traversal back to\n // this URL would replay the metadata this drops.\n const cacheable = !statusPageRendered;\n _setStatusPageRendered(false);\n const segmentInfo = cacheable ? opts.segmentInfo : [];\n\n if (segmentInfo && segmentInfo.length > 0) {\n updateSegmentCache(segmentInfo);\n } else if (segmentInfo?.length === 0 || opts.clearSegmentCacheOnEmpty) {\n segmentCache.clear();\n }\n\n setNavigationState(navState);\n\n historyStack.push(url, {\n payload,\n params: opts.params,\n segmentInfo: storedSegmentInfo(segmentInfo),\n });\n },\n };\n },\n };\n}\n","'use client';\n\n/**\n * The one way a status page tells the router it is on screen.\n *\n * A status page replaces a subtree, and the segment cache must not learn a\n * chain that never mounted (see `navigation-commit.ts` §\"What a status page\n * teaches the cache\"). Status pages are produced in three places — an\n * in-tree catch site rendering the page it owns, the server's fallback\n * renderers, and a client boundary rendering its fallback for a Flight row —\n * and every one of them wraps the page in this marker, so the report does\n * not depend on which producer it was. In particular the in-tree case, the\n * common `access.ts` deny, never involves a client boundary at all.\n *\n * A layout effect, on every commit that renders the marker rather than on\n * mount only: React reuses the fiber when one status page is replaced by\n * another at the same position, and the second one must report too. Layout\n * effects run child-before-parent, so the report lands before the ancestor\n * `NavigationRoot` publishes the same commit, and never for a render React\n * discards. Idempotent, so StrictMode's double invocation and a status page\n * re-rendering on its own state are harmless. See TIM-1495.\n */\n\nimport { useLayoutEffect, type ReactNode } from 'react';\nimport { reportStatusPageRendered } from './navigation-commit.ts';\n\nexport function StatusPageMarker({ children }: { children: ReactNode }): ReactNode {\n useLayoutEffect(() => {\n reportStatusPageRendered();\n });\n return children;\n}\n"],"mappings":"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"}
@@ -1,14 +0,0 @@
1
- /**
2
- * A value that is safe to pass through `JSON.stringify` without data loss.
3
- *
4
- * Mirrors the server-side type. Defined separately to avoid importing server
5
- * modules into the client bundle.
6
- */
7
- export type JsonSerializable = string | number | boolean | null | JsonSerializable[] | {
8
- [key: string]: JsonSerializable;
9
- };
10
- export interface RenderErrorDigest<Code extends string = string, Data extends JsonSerializable = JsonSerializable> {
11
- code: Code;
12
- data: Data;
13
- }
14
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GACxB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,gBAAgB,EAAE,GAClB;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAAA;CAAE,CAAC;AAExC,MAAM,WAAW,iBAAiB,CAChC,IAAI,SAAS,MAAM,GAAG,MAAM,EAC5B,IAAI,SAAS,gBAAgB,GAAG,gBAAgB;IAEhD,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,EAAE,IAAI,CAAC;CACZ"}
@@ -1,38 +0,0 @@
1
- /**
2
- * timber-adapter-build — Invoke the adapter's buildOutput after vite build.
3
- *
4
- * After all environments are built and the RSC plugin has written its
5
- * asset manifests, calls `adapter.buildOutput()` to transform the output
6
- * into a deployable artifact (e.g., Cloudflare Workers entry + wrangler.jsonc).
7
- *
8
- * Uses a `buildApp` hook with `order: 'post'` so that Vite calls the
9
- * RSC plugin's buildApp (which orchestrates all environment builds and
10
- * writes asset manifests) first, then runs this handler after everything
11
- * is complete.
12
- *
13
- * Design docs: design/11-platform.md, design/25-production-deployments.md
14
- */
15
- import type { Plugin } from 'vite';
16
- import type { PluginContext } from '../plugin-context.ts';
17
- import type { BuildManifest } from '../server/build-manifest.ts';
18
- export declare function timberAdapterBuild(ctx: PluginContext): Plugin;
19
- /**
20
- * Strip JS references from the RSC plugin's assets manifest files.
21
- *
22
- * The RSC plugin writes `__vite_rsc_assets_manifest.js` to rsc/ and ssr/
23
- * as standalone files (not Rollup chunks), so generateBundle can't
24
- * intercept them. This rewrites the files on disk after all builds
25
- * complete but before the adapter copies them to the output directory.
26
- */
27
- /**
28
- * Read the RSC plugin's assets manifest and merge per-route CSS and
29
- * per-route JS modulepreloads into the build manifest.
30
- *
31
- * CSS: `serverResources` maps server component paths to CSS URLs.
32
- * JS: `clientReferenceDeps` maps reference hashes to JS dep URLs.
33
- * Combined with `routeClientRefKeys` (from the RSC generateBundle
34
- * module graph walk) to produce per-route modulepreload entries.
35
- */
36
- export declare function mergeRscAssetsManifest(buildDir: string, root: string, buildManifest: BuildManifest, routeClientRefKeys?: Map<string, Set<string>>): Promise<void>;
37
- export declare function stripJsFromRscAssetsManifests(buildDir: string): Promise<void>;
38
- //# sourceMappingURL=adapter-build.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"adapter-build.d.ts","sourceRoot":"","sources":["../../src/plugins/adapter-build.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAInC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAIjE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAgF7D;AAgHD;;;;;;;GAOG;AACH;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,aAAa,EAC5B,kBAAkB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,GAC5C,OAAO,CAAC,IAAI,CAAC,CAmDf;AAED,wBAAsB,6BAA6B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA+CnF"}
@@ -1,32 +0,0 @@
1
- /**
2
- * Static asset cache header utilities.
3
- *
4
- * Hashed assets (e.g. /assets/layout-abc123.css) are immutable — the hash
5
- * changes when content changes. These get long-lived cache headers:
6
- * Cache-Control: public, max-age=31536000, immutable
7
- *
8
- * Unhashed assets (e.g. /favicon.ico) get a shorter cache with revalidation:
9
- * Cache-Control: public, max-age=3600, must-revalidate
10
- *
11
- * The `_headers` file lists hashed asset paths (/assets/*, /_timber/fonts/*) as
12
- * wildcard rules and unhashed files as per-file rules. At runtime, adapters
13
- * that see static file requests (nitro, preview) also set Cache-Control via
14
- * `getAssetCacheControl()`. On Cloudflare Workers Static Assets, the worker
15
- * never sees static file requests — the `_headers` file is the only mechanism.
16
- * Page responses are NOT affected — those are controlled by the developer
17
- * via middleware.ts or proxy.ts.
18
- *
19
- * Design docs: 18-build-system.md, 06-caching.md
20
- */
21
- export { IMMUTABLE_CACHE, STATIC_CACHE, generateHeadersFile } from '../adapters/shared.ts';
22
- /**
23
- * Check if a URL path looks like a hashed asset.
24
- */
25
- export declare function isHashedAsset(pathname: string): boolean;
26
- /**
27
- * Get the appropriate Cache-Control header for a static asset path.
28
- *
29
- * Returns `immutable` for hashed assets, short-lived for unhashed.
30
- */
31
- export declare function getAssetCacheControl(pathname: string): string;
32
- //# sourceMappingURL=asset-headers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"asset-headers.d.ts","sourceRoot":"","sources":["../../src/server/asset-headers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAQH,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAiB3F;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE7D"}
@@ -1,22 +0,0 @@
1
- /**
2
- * Dev-only error source-mapping bridge.
3
- *
4
- * Stores a callback that rewrites an error's stack trace using the Vite
5
- * dev server's module graph. Set by the RSC entry on startup (via
6
- * setDevSourceMapHandler), consumed by error renderers before generating
7
- * error pages.
8
- *
9
- * In production, the callback is never set — all calls are no-ops.
10
- *
11
- * See TIM-811.
12
- */
13
- /**
14
- * Set the source-map callback. Called once during dev server initialization.
15
- */
16
- export declare function setSourceMapCallback(fn: (error: Error) => void): void;
17
- /**
18
- * Source-map an error's stack trace in-place if the callback is available.
19
- * No-op in production or before the dev server initializes.
20
- */
21
- export declare function sourceMapError(error: unknown): void;
22
- //# sourceMappingURL=dev-source-map.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dev-source-map.d.ts","sourceRoot":"","sources":["../../src/server/dev-source-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAErE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAInD"}
@@ -1,21 +0,0 @@
1
- /**
2
- * A value that is safe to pass through `JSON.stringify` without data loss.
3
- *
4
- * Mirrors the server-side type. Defined separately to avoid importing server
5
- * modules into the client bundle.
6
- */
7
- export type JsonSerializable =
8
- | string
9
- | number
10
- | boolean
11
- | null
12
- | JsonSerializable[]
13
- | { [key: string]: JsonSerializable };
14
-
15
- export interface RenderErrorDigest<
16
- Code extends string = string,
17
- Data extends JsonSerializable = JsonSerializable,
18
- > {
19
- code: Code;
20
- data: Data;
21
- }