@timber-js/app 0.2.0-alpha.6 → 0.2.0-alpha.61

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 (406) hide show
  1. package/LICENSE +8 -0
  2. package/dist/_chunks/{als-registry-B7DbZ2hS.js → als-registry-Ba7URUIn.js} +1 -1
  3. package/dist/_chunks/als-registry-Ba7URUIn.js.map +1 -0
  4. package/dist/_chunks/chunk-DYhsFzuS.js +33 -0
  5. package/dist/_chunks/{debug-gwlJkDuf.js → debug-ECi_61pb.js} +2 -2
  6. package/dist/_chunks/debug-ECi_61pb.js.map +1 -0
  7. package/dist/_chunks/define-CT98cU9c.js +121 -0
  8. package/dist/_chunks/define-CT98cU9c.js.map +1 -0
  9. package/dist/_chunks/define-TK8C1M3x.js +279 -0
  10. package/dist/_chunks/define-TK8C1M3x.js.map +1 -0
  11. package/dist/_chunks/define-cookie-BWr_52kY.js +93 -0
  12. package/dist/_chunks/define-cookie-BWr_52kY.js.map +1 -0
  13. package/dist/_chunks/error-boundary-DpZJBCqh.js +211 -0
  14. package/dist/_chunks/error-boundary-DpZJBCqh.js.map +1 -0
  15. package/dist/_chunks/{format-DviM89f0.js → format-cX7wzEp2.js} +2 -2
  16. package/dist/_chunks/{format-DviM89f0.js.map → format-cX7wzEp2.js.map} +1 -1
  17. package/dist/_chunks/{interception-BOoWmLUA.js → interception-Cey5DCGr.js} +129 -77
  18. package/dist/_chunks/interception-Cey5DCGr.js.map +1 -0
  19. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → metadata-routes-BU684ls2.js} +1 -1
  20. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js.map → metadata-routes-BU684ls2.js.map} +1 -1
  21. package/dist/_chunks/{request-context-DIkVh_jG.js → request-context-rju2rbga.js} +97 -69
  22. package/dist/_chunks/request-context-rju2rbga.js.map +1 -0
  23. package/dist/_chunks/segment-context-CyaM1mrD.js +72 -0
  24. package/dist/_chunks/segment-context-CyaM1mrD.js.map +1 -0
  25. package/dist/_chunks/stale-reload-BSSym1MJ.js +64 -0
  26. package/dist/_chunks/stale-reload-BSSym1MJ.js.map +1 -0
  27. package/dist/_chunks/{tracing-Cwn7697K.js → tracing-VYETCQsg.js} +17 -3
  28. package/dist/_chunks/{tracing-Cwn7697K.js.map → tracing-VYETCQsg.js.map} +1 -1
  29. package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-wEXY2JQB.js} +1 -1
  30. package/dist/_chunks/use-query-states-wEXY2JQB.js.map +1 -0
  31. package/dist/_chunks/wrappers-BaG1bnM3.js +63 -0
  32. package/dist/_chunks/wrappers-BaG1bnM3.js.map +1 -0
  33. package/dist/adapters/compress-module.d.ts.map +1 -1
  34. package/dist/adapters/nitro.d.ts +17 -1
  35. package/dist/adapters/nitro.d.ts.map +1 -1
  36. package/dist/adapters/nitro.js +56 -13
  37. package/dist/adapters/nitro.js.map +1 -1
  38. package/dist/cache/fast-hash.d.ts +22 -0
  39. package/dist/cache/fast-hash.d.ts.map +1 -0
  40. package/dist/cache/index.d.ts +5 -2
  41. package/dist/cache/index.d.ts.map +1 -1
  42. package/dist/cache/index.js +90 -20
  43. package/dist/cache/index.js.map +1 -1
  44. package/dist/cache/register-cached-function.d.ts.map +1 -1
  45. package/dist/cache/singleflight.d.ts +18 -1
  46. package/dist/cache/singleflight.d.ts.map +1 -1
  47. package/dist/cache/timber-cache.d.ts +1 -1
  48. package/dist/cache/timber-cache.d.ts.map +1 -1
  49. package/dist/client/error-boundary.d.ts +10 -1
  50. package/dist/client/error-boundary.d.ts.map +1 -1
  51. package/dist/client/error-boundary.js +1 -125
  52. package/dist/client/error-reconstituter.d.ts +54 -0
  53. package/dist/client/error-reconstituter.d.ts.map +1 -0
  54. package/dist/client/form.d.ts +2 -2
  55. package/dist/client/form.d.ts.map +1 -1
  56. package/dist/client/index.d.ts +3 -2
  57. package/dist/client/index.d.ts.map +1 -1
  58. package/dist/client/index.js +433 -252
  59. package/dist/client/index.js.map +1 -1
  60. package/dist/client/link-pending-store.d.ts +78 -0
  61. package/dist/client/link-pending-store.d.ts.map +1 -0
  62. package/dist/client/link.d.ts +23 -9
  63. package/dist/client/link.d.ts.map +1 -1
  64. package/dist/client/navigation-context.d.ts +2 -2
  65. package/dist/client/navigation-context.d.ts.map +1 -1
  66. package/dist/client/router.d.ts +25 -3
  67. package/dist/client/router.d.ts.map +1 -1
  68. package/dist/client/rsc-fetch.d.ts +36 -2
  69. package/dist/client/rsc-fetch.d.ts.map +1 -1
  70. package/dist/client/segment-cache.d.ts +1 -1
  71. package/dist/client/segment-cache.d.ts.map +1 -1
  72. package/dist/client/segment-context.d.ts +1 -1
  73. package/dist/client/segment-context.d.ts.map +1 -1
  74. package/dist/client/segment-merger.d.ts.map +1 -1
  75. package/dist/client/segment-outlet.d.ts +63 -0
  76. package/dist/client/segment-outlet.d.ts.map +1 -0
  77. package/dist/client/stale-reload.d.ts +15 -0
  78. package/dist/client/stale-reload.d.ts.map +1 -1
  79. package/dist/client/top-loader.d.ts +1 -1
  80. package/dist/client/top-loader.d.ts.map +1 -1
  81. package/dist/client/transition-root.d.ts +1 -1
  82. package/dist/client/transition-root.d.ts.map +1 -1
  83. package/dist/client/use-params.d.ts +3 -3
  84. package/dist/client/use-params.d.ts.map +1 -1
  85. package/dist/client/use-query-states.d.ts +1 -1
  86. package/dist/client/use-query-states.d.ts.map +1 -1
  87. package/dist/codec.d.ts +21 -0
  88. package/dist/codec.d.ts.map +1 -0
  89. package/dist/cookies/define-cookie.d.ts +34 -13
  90. package/dist/cookies/define-cookie.d.ts.map +1 -1
  91. package/dist/cookies/index.js +1 -83
  92. package/dist/fonts/css.d.ts +1 -0
  93. package/dist/fonts/css.d.ts.map +1 -1
  94. package/dist/index.d.ts +127 -35
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +665 -242
  97. package/dist/index.js.map +1 -1
  98. package/dist/params/define.d.ts +100 -0
  99. package/dist/params/define.d.ts.map +1 -0
  100. package/dist/params/index.d.ts +8 -0
  101. package/dist/params/index.d.ts.map +1 -0
  102. package/dist/params/index.js +4 -0
  103. package/dist/plugins/adapter-build.d.ts +1 -1
  104. package/dist/plugins/adapter-build.d.ts.map +1 -1
  105. package/dist/plugins/build-manifest.d.ts +2 -2
  106. package/dist/plugins/build-manifest.d.ts.map +1 -1
  107. package/dist/plugins/build-report.d.ts +3 -3
  108. package/dist/plugins/build-report.d.ts.map +1 -1
  109. package/dist/plugins/client-chunks.d.ts +32 -0
  110. package/dist/plugins/client-chunks.d.ts.map +1 -0
  111. package/dist/plugins/content.d.ts +1 -1
  112. package/dist/plugins/content.d.ts.map +1 -1
  113. package/dist/plugins/dev-browser-logs.d.ts +84 -0
  114. package/dist/plugins/dev-browser-logs.d.ts.map +1 -0
  115. package/dist/plugins/dev-error-overlay.d.ts +26 -1
  116. package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
  117. package/dist/plugins/dev-logs.d.ts +1 -1
  118. package/dist/plugins/dev-logs.d.ts.map +1 -1
  119. package/dist/plugins/dev-server.d.ts +1 -1
  120. package/dist/plugins/dev-server.d.ts.map +1 -1
  121. package/dist/plugins/entries.d.ts +1 -1
  122. package/dist/plugins/entries.d.ts.map +1 -1
  123. package/dist/plugins/fonts.d.ts +9 -2
  124. package/dist/plugins/fonts.d.ts.map +1 -1
  125. package/dist/plugins/mdx.d.ts +1 -1
  126. package/dist/plugins/mdx.d.ts.map +1 -1
  127. package/dist/plugins/routing.d.ts +1 -1
  128. package/dist/plugins/routing.d.ts.map +1 -1
  129. package/dist/plugins/server-bundle.d.ts.map +1 -1
  130. package/dist/plugins/shims.d.ts +6 -5
  131. package/dist/plugins/shims.d.ts.map +1 -1
  132. package/dist/plugins/static-build.d.ts +1 -1
  133. package/dist/plugins/static-build.d.ts.map +1 -1
  134. package/dist/routing/codegen.d.ts +2 -2
  135. package/dist/routing/codegen.d.ts.map +1 -1
  136. package/dist/routing/index.js +1 -1
  137. package/dist/routing/scanner.d.ts.map +1 -1
  138. package/dist/routing/status-file-lint.d.ts +2 -1
  139. package/dist/routing/status-file-lint.d.ts.map +1 -1
  140. package/dist/routing/types.d.ts +16 -4
  141. package/dist/routing/types.d.ts.map +1 -1
  142. package/dist/rsc-runtime/rsc.d.ts +1 -1
  143. package/dist/rsc-runtime/rsc.d.ts.map +1 -1
  144. package/dist/rsc-runtime/ssr.d.ts +12 -0
  145. package/dist/rsc-runtime/ssr.d.ts.map +1 -1
  146. package/dist/search-params/codecs.d.ts +1 -1
  147. package/dist/search-params/define.d.ts +159 -0
  148. package/dist/search-params/define.d.ts.map +1 -0
  149. package/dist/search-params/index.d.ts +4 -5
  150. package/dist/search-params/index.d.ts.map +1 -1
  151. package/dist/search-params/index.js +4 -474
  152. package/dist/search-params/registry.d.ts +1 -1
  153. package/dist/search-params/wrappers.d.ts +53 -0
  154. package/dist/search-params/wrappers.d.ts.map +1 -0
  155. package/dist/server/access-gate.d.ts +4 -0
  156. package/dist/server/access-gate.d.ts.map +1 -1
  157. package/dist/server/action-client.d.ts.map +1 -1
  158. package/dist/server/action-encryption.d.ts +76 -0
  159. package/dist/server/action-encryption.d.ts.map +1 -0
  160. package/dist/server/action-handler.d.ts.map +1 -1
  161. package/dist/server/actions.d.ts +1 -1
  162. package/dist/server/actions.d.ts.map +1 -1
  163. package/dist/server/als-registry.d.ts +25 -4
  164. package/dist/server/als-registry.d.ts.map +1 -1
  165. package/dist/server/build-manifest.d.ts +2 -2
  166. package/dist/server/build-manifest.d.ts.map +1 -1
  167. package/dist/server/debug.d.ts +1 -1
  168. package/dist/server/default-logger.d.ts +22 -0
  169. package/dist/server/default-logger.d.ts.map +1 -0
  170. package/dist/server/deny-renderer.d.ts.map +1 -1
  171. package/dist/server/early-hints.d.ts +13 -5
  172. package/dist/server/early-hints.d.ts.map +1 -1
  173. package/dist/server/error-boundary-wrapper.d.ts +4 -0
  174. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  175. package/dist/server/fallback-error.d.ts +4 -3
  176. package/dist/server/fallback-error.d.ts.map +1 -1
  177. package/dist/server/flight-injection-state.d.ts +66 -0
  178. package/dist/server/flight-injection-state.d.ts.map +1 -0
  179. package/dist/server/flight-scripts.d.ts +42 -0
  180. package/dist/server/flight-scripts.d.ts.map +1 -0
  181. package/dist/server/flush.d.ts.map +1 -1
  182. package/dist/server/form-data.d.ts +29 -0
  183. package/dist/server/form-data.d.ts.map +1 -1
  184. package/dist/server/html-injectors.d.ts +51 -11
  185. package/dist/server/html-injectors.d.ts.map +1 -1
  186. package/dist/server/index.d.ts +4 -2
  187. package/dist/server/index.d.ts.map +1 -1
  188. package/dist/server/index.js +1977 -1648
  189. package/dist/server/index.js.map +1 -1
  190. package/dist/server/logger.d.ts +25 -7
  191. package/dist/server/logger.d.ts.map +1 -1
  192. package/dist/server/node-stream-transforms.d.ts +113 -0
  193. package/dist/server/node-stream-transforms.d.ts.map +1 -0
  194. package/dist/server/pipeline-interception.d.ts +1 -1
  195. package/dist/server/pipeline-interception.d.ts.map +1 -1
  196. package/dist/server/pipeline.d.ts +20 -6
  197. package/dist/server/pipeline.d.ts.map +1 -1
  198. package/dist/server/primitives.d.ts +30 -3
  199. package/dist/server/primitives.d.ts.map +1 -1
  200. package/dist/server/render-timeout.d.ts +51 -0
  201. package/dist/server/render-timeout.d.ts.map +1 -0
  202. package/dist/server/request-context.d.ts +65 -38
  203. package/dist/server/request-context.d.ts.map +1 -1
  204. package/dist/server/route-element-builder.d.ts +7 -0
  205. package/dist/server/route-element-builder.d.ts.map +1 -1
  206. package/dist/server/route-handler.d.ts.map +1 -1
  207. package/dist/server/route-matcher.d.ts +9 -2
  208. package/dist/server/route-matcher.d.ts.map +1 -1
  209. package/dist/server/rsc-entry/api-handler.d.ts +2 -2
  210. package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
  211. package/dist/server/rsc-entry/error-renderer.d.ts +26 -13
  212. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  213. package/dist/server/rsc-entry/helpers.d.ts +48 -5
  214. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  215. package/dist/server/rsc-entry/index.d.ts +8 -3
  216. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  217. package/dist/server/rsc-entry/rsc-payload.d.ts +3 -3
  218. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  219. package/dist/server/rsc-entry/rsc-stream.d.ts +10 -1
  220. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  221. package/dist/server/rsc-entry/ssr-bridge.d.ts +1 -1
  222. package/dist/server/rsc-entry/ssr-bridge.d.ts.map +1 -1
  223. package/dist/server/rsc-entry/ssr-renderer.d.ts +19 -4
  224. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  225. package/dist/server/slot-resolver.d.ts +1 -1
  226. package/dist/server/slot-resolver.d.ts.map +1 -1
  227. package/dist/server/ssr-entry.d.ts +22 -0
  228. package/dist/server/ssr-entry.d.ts.map +1 -1
  229. package/dist/server/ssr-render.d.ts +39 -21
  230. package/dist/server/ssr-render.d.ts.map +1 -1
  231. package/dist/server/ssr-wrappers.d.ts +50 -0
  232. package/dist/server/ssr-wrappers.d.ts.map +1 -0
  233. package/dist/server/status-code-resolver.d.ts +1 -1
  234. package/dist/server/status-code-resolver.d.ts.map +1 -1
  235. package/dist/server/stream-utils.d.ts +36 -0
  236. package/dist/server/stream-utils.d.ts.map +1 -0
  237. package/dist/server/tracing.d.ts +10 -0
  238. package/dist/server/tracing.d.ts.map +1 -1
  239. package/dist/server/tree-builder.d.ts +20 -13
  240. package/dist/server/tree-builder.d.ts.map +1 -1
  241. package/dist/server/types.d.ts +1 -3
  242. package/dist/server/types.d.ts.map +1 -1
  243. package/dist/server/version-skew.d.ts +61 -0
  244. package/dist/server/version-skew.d.ts.map +1 -0
  245. package/dist/server/waituntil-bridge.d.ts.map +1 -1
  246. package/dist/shared/merge-search-params.d.ts +22 -0
  247. package/dist/shared/merge-search-params.d.ts.map +1 -0
  248. package/dist/shims/font-google.d.ts +1 -1
  249. package/dist/shims/font-google.d.ts.map +1 -1
  250. package/dist/shims/navigation-client.d.ts +1 -1
  251. package/dist/shims/navigation-client.d.ts.map +1 -1
  252. package/dist/shims/navigation.d.ts +1 -1
  253. package/dist/shims/navigation.d.ts.map +1 -1
  254. package/dist/utils/state-machine.d.ts +80 -0
  255. package/dist/utils/state-machine.d.ts.map +1 -0
  256. package/package.json +17 -17
  257. package/src/adapters/compress-module.ts +24 -4
  258. package/src/adapters/nitro.ts +58 -9
  259. package/src/cache/fast-hash.ts +34 -0
  260. package/src/cache/index.ts +5 -2
  261. package/src/cache/register-cached-function.ts +7 -3
  262. package/src/cache/singleflight.ts +62 -4
  263. package/src/cache/timber-cache.ts +40 -29
  264. package/src/cli.ts +0 -0
  265. package/src/client/browser-entry.ts +151 -99
  266. package/src/client/error-boundary.tsx +18 -1
  267. package/src/client/error-reconstituter.tsx +65 -0
  268. package/src/client/form.tsx +2 -2
  269. package/src/client/index.ts +10 -1
  270. package/src/client/link-pending-store.ts +136 -0
  271. package/src/client/link.tsx +137 -22
  272. package/src/client/navigation-context.ts +6 -5
  273. package/src/client/router.ts +117 -60
  274. package/src/client/rsc-fetch.ts +90 -2
  275. package/src/client/segment-cache.ts +1 -1
  276. package/src/client/segment-context.ts +6 -1
  277. package/src/client/segment-merger.ts +2 -8
  278. package/src/client/segment-outlet.tsx +86 -0
  279. package/src/client/stale-reload.ts +73 -6
  280. package/src/client/top-loader.tsx +10 -9
  281. package/src/client/transition-root.tsx +20 -2
  282. package/src/client/use-params.ts +4 -4
  283. package/src/client/use-query-states.ts +2 -2
  284. package/src/codec.ts +21 -0
  285. package/src/cookies/define-cookie.ts +71 -20
  286. package/src/fonts/css.ts +2 -1
  287. package/src/index.ts +297 -85
  288. package/src/params/define.ts +327 -0
  289. package/src/params/index.ts +28 -0
  290. package/src/plugins/adapter-build.ts +8 -2
  291. package/src/plugins/build-manifest.ts +13 -2
  292. package/src/plugins/build-report.ts +3 -3
  293. package/src/plugins/cache-transform.ts +1 -1
  294. package/src/plugins/client-chunks.ts +65 -0
  295. package/src/plugins/content.ts +1 -1
  296. package/src/plugins/dev-browser-logs.ts +284 -0
  297. package/src/plugins/dev-error-overlay.ts +70 -1
  298. package/src/plugins/dev-logs.ts +1 -1
  299. package/src/plugins/dev-server.ts +41 -7
  300. package/src/plugins/entries.ts +6 -8
  301. package/src/plugins/fonts.ts +102 -55
  302. package/src/plugins/mdx.ts +1 -1
  303. package/src/plugins/routing.ts +57 -17
  304. package/src/plugins/server-action-exports.ts +1 -1
  305. package/src/plugins/server-bundle.ts +32 -1
  306. package/src/plugins/shims.ts +69 -31
  307. package/src/plugins/static-build.ts +10 -6
  308. package/src/routing/codegen.ts +109 -88
  309. package/src/routing/scanner.ts +86 -7
  310. package/src/routing/status-file-lint.ts +3 -2
  311. package/src/routing/types.ts +17 -4
  312. package/src/rsc-runtime/rsc.ts +2 -0
  313. package/src/rsc-runtime/ssr.ts +50 -0
  314. package/src/rsc-runtime/vendor-types.d.ts +7 -0
  315. package/src/search-params/codecs.ts +1 -1
  316. package/src/search-params/define.ts +518 -0
  317. package/src/search-params/index.ts +12 -18
  318. package/src/search-params/registry.ts +1 -1
  319. package/src/search-params/wrappers.ts +85 -0
  320. package/src/server/access-gate.tsx +40 -9
  321. package/src/server/action-client.ts +8 -2
  322. package/src/server/action-encryption.ts +144 -0
  323. package/src/server/action-handler.ts +20 -3
  324. package/src/server/actions.ts +1 -1
  325. package/src/server/als-registry.ts +25 -4
  326. package/src/server/build-manifest.ts +10 -4
  327. package/src/server/compress.ts +25 -7
  328. package/src/server/debug.ts +1 -1
  329. package/src/server/default-logger.ts +99 -0
  330. package/src/server/deny-renderer.ts +5 -3
  331. package/src/server/early-hints.ts +36 -15
  332. package/src/server/error-boundary-wrapper.ts +58 -15
  333. package/src/server/fallback-error.ts +29 -14
  334. package/src/server/flight-injection-state.ts +113 -0
  335. package/src/server/flight-scripts.ts +62 -0
  336. package/src/server/flush.ts +2 -1
  337. package/src/server/form-data.ts +76 -0
  338. package/src/server/html-injectors.ts +277 -117
  339. package/src/server/index.ts +9 -4
  340. package/src/server/logger.ts +44 -36
  341. package/src/server/node-stream-transforms.ts +509 -0
  342. package/src/server/pipeline-interception.ts +1 -1
  343. package/src/server/pipeline.ts +148 -41
  344. package/src/server/primitives.ts +47 -5
  345. package/src/server/render-timeout.ts +108 -0
  346. package/src/server/request-context.ts +125 -119
  347. package/src/server/route-element-builder.ts +107 -115
  348. package/src/server/route-handler.ts +2 -1
  349. package/src/server/route-matcher.ts +9 -2
  350. package/src/server/rsc-entry/api-handler.ts +8 -8
  351. package/src/server/rsc-entry/error-renderer.ts +286 -81
  352. package/src/server/rsc-entry/helpers.ts +134 -5
  353. package/src/server/rsc-entry/index.ts +177 -76
  354. package/src/server/rsc-entry/rsc-payload.ts +91 -18
  355. package/src/server/rsc-entry/rsc-stream.ts +74 -18
  356. package/src/server/rsc-entry/ssr-bridge.ts +2 -2
  357. package/src/server/rsc-entry/ssr-renderer.ts +152 -34
  358. package/src/server/slot-resolver.ts +231 -220
  359. package/src/server/ssr-entry.ts +211 -32
  360. package/src/server/ssr-render.ts +289 -67
  361. package/src/server/ssr-wrappers.tsx +139 -0
  362. package/src/server/status-code-resolver.ts +1 -1
  363. package/src/server/stream-utils.ts +213 -0
  364. package/src/server/tracing.ts +23 -0
  365. package/src/server/tree-builder.ts +92 -58
  366. package/src/server/types.ts +1 -3
  367. package/src/server/version-skew.ts +104 -0
  368. package/src/server/waituntil-bridge.ts +4 -1
  369. package/src/shared/merge-search-params.ts +55 -0
  370. package/src/shims/font-google.ts +1 -1
  371. package/src/shims/navigation-client.ts +1 -1
  372. package/src/shims/navigation.ts +2 -1
  373. package/src/utils/state-machine.ts +111 -0
  374. package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
  375. package/dist/_chunks/debug-gwlJkDuf.js.map +0 -1
  376. package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
  377. package/dist/_chunks/request-context-DIkVh_jG.js.map +0 -1
  378. package/dist/_chunks/ssr-data-MjmprTmO.js +0 -88
  379. package/dist/_chunks/ssr-data-MjmprTmO.js.map +0 -1
  380. package/dist/_chunks/use-cookie-DX-l1_5E.js +0 -91
  381. package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
  382. package/dist/_chunks/use-query-states-D5KaffOK.js.map +0 -1
  383. package/dist/client/error-boundary.js.map +0 -1
  384. package/dist/client/link-status-provider.d.ts +0 -11
  385. package/dist/client/link-status-provider.d.ts.map +0 -1
  386. package/dist/cookies/index.js.map +0 -1
  387. package/dist/plugins/dynamic-transform.d.ts +0 -72
  388. package/dist/plugins/dynamic-transform.d.ts.map +0 -1
  389. package/dist/search-params/analyze.d.ts +0 -54
  390. package/dist/search-params/analyze.d.ts.map +0 -1
  391. package/dist/search-params/builtin-codecs.d.ts +0 -105
  392. package/dist/search-params/builtin-codecs.d.ts.map +0 -1
  393. package/dist/search-params/create.d.ts +0 -106
  394. package/dist/search-params/create.d.ts.map +0 -1
  395. package/dist/search-params/index.js.map +0 -1
  396. package/dist/server/prerender.d.ts +0 -77
  397. package/dist/server/prerender.d.ts.map +0 -1
  398. package/dist/server/response-cache.d.ts +0 -53
  399. package/dist/server/response-cache.d.ts.map +0 -1
  400. package/src/client/link-status-provider.tsx +0 -30
  401. package/src/plugins/dynamic-transform.ts +0 -161
  402. package/src/search-params/analyze.ts +0 -192
  403. package/src/search-params/builtin-codecs.ts +0 -228
  404. package/src/search-params/create.ts +0 -321
  405. package/src/server/prerender.ts +0 -139
  406. package/src/server/response-cache.ts +0 -277
@@ -21,6 +21,7 @@ import {
21
21
  setMutableCookieContext,
22
22
  getSetCookieHeaders,
23
23
  markResponseFlushed,
24
+ setSegmentParams,
24
25
  } from './request-context.js';
25
26
  import {
26
27
  generateTraceId,
@@ -42,10 +43,12 @@ import {
42
43
  } from './logger.js';
43
44
  import { callOnRequestError } from './instrumentation.js';
44
45
  import { RedirectSignal, DenySignal } from './primitives.js';
46
+ import { ParamCoercionError } from './route-element-builder.js';
47
+ import { checkVersionSkew, applyReloadHeaders } from './version-skew.js';
45
48
  import { serveStaticMetadataFile, serializeSitemap } from './pipeline-metadata.js';
46
49
  import { findInterceptionMatch } from './pipeline-interception.js';
47
50
  import type { MiddlewareContext } from './types.js';
48
- import type { SegmentNode } from '#/routing/types.js';
51
+ import type { SegmentNode } from '../routing/types.js';
49
52
 
50
53
  // ─── Route Match Result ────────────────────────────────────────────────────
51
54
 
@@ -115,14 +118,17 @@ export interface PipelineConfig {
115
118
  * Generated at build time from intercepting route directories.
116
119
  * See design/07-routing.md §"Intercepting Routes"
117
120
  */
118
- interceptionRewrites?: import('#/routing/interception.js').InterceptionRewrite[];
121
+ interceptionRewrites?: import('../routing/interception.js').InterceptionRewrite[];
119
122
  /**
120
- * Emit Server-Timing header on responses for Chrome DevTools visibility.
121
- * Only enable in dev mode — exposes internal timing data.
123
+ * Control Server-Timing header output.
122
124
  *
123
- * Default: false (production-safe).
125
+ * - `'detailed'` — per-phase breakdown (proxy, middleware, render).
126
+ * - `'total'` — single `total;dur=N` entry (production-safe).
127
+ * - `false` — no Server-Timing header at all.
128
+ *
129
+ * Default: `'total'`.
124
130
  */
125
- enableServerTiming?: boolean;
131
+ serverTiming?: 'detailed' | 'total' | false;
126
132
  /**
127
133
  * Dev pipeline error callback — called when a pipeline phase (proxy,
128
134
  * middleware, render) catches an unhandled error. Used to wire the error
@@ -149,6 +155,42 @@ export interface PipelineConfig {
149
155
  ) => Response | Promise<Response>;
150
156
  }
151
157
 
158
+ // ─── Param Coercion ────────────────────────────────────────────────────────
159
+
160
+ /**
161
+ * Run segment param coercion on the matched route's segments.
162
+ *
163
+ * Loads params.ts modules from segments that have them, extracts the
164
+ * segmentParams definition, and coerces raw string params through codecs.
165
+ * Throws ParamCoercionError if any codec fails (→ 404).
166
+ *
167
+ * This runs BEFORE middleware, so ctx.segmentParams is already typed.
168
+ * See design/07-routing.md §"Where Coercion Runs"
169
+ */
170
+ export async function coerceSegmentParams(match: RouteMatch): Promise<void> {
171
+ const segments = match.segments as unknown as import('./route-matcher.js').ManifestSegmentNode[];
172
+
173
+ for (const segment of segments) {
174
+ // Only process segments that have a params.ts convention file
175
+ if (!segment.params) continue;
176
+
177
+ const mod = (await segment.params.load()) as Record<string, unknown>;
178
+ const segmentParamsDef = mod.segmentParams as
179
+ | { parse(raw: Record<string, string | string[]>): Record<string, unknown> }
180
+ | undefined;
181
+
182
+ if (!segmentParamsDef || typeof segmentParamsDef.parse !== 'function') continue;
183
+
184
+ try {
185
+ const coerced = segmentParamsDef.parse(match.params);
186
+ // Merge coerced values back into match.params
187
+ Object.assign(match.params, coerced);
188
+ } catch (err) {
189
+ throw new ParamCoercionError(err instanceof Error ? err.message : String(err));
190
+ }
191
+ }
192
+ }
193
+
152
194
  // ─── Pipeline ──────────────────────────────────────────────────────────────
153
195
 
154
196
  /**
@@ -165,7 +207,7 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
165
207
  earlyHints,
166
208
  stripTrailingSlash = true,
167
209
  slowRequestMs = 3000,
168
- enableServerTiming = false,
210
+ serverTiming = 'total',
169
211
  onPipelineError,
170
212
  } = config;
171
213
 
@@ -216,25 +258,25 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
216
258
  // DevSpanProcessor reads this for tree/summary output.
217
259
  await setSpanAttribute('http.response.status_code', result.status);
218
260
 
219
- // Append Server-Timing header.
220
- // In dev mode: detailed per-phase breakdown (proxy, middleware, render).
221
- // In production: single total duration — safe to expose, no phase names.
261
+ // Append Server-Timing header based on configured mode.
222
262
  // Response.redirect() creates immutable headers, so we must
223
263
  // ensure mutability before writing Server-Timing.
224
- if (enableServerTiming) {
225
- const serverTiming = getServerTimingHeader();
226
- if (serverTiming) {
264
+ if (serverTiming === 'detailed') {
265
+ // Detailed: per-phase breakdown (proxy, middleware, render).
266
+ const timingHeader = getServerTimingHeader();
267
+ if (timingHeader) {
227
268
  result = ensureMutableResponse(result);
228
- result.headers.set('Server-Timing', serverTiming);
269
+ result.headers.set('Server-Timing', timingHeader);
229
270
  }
230
- } else {
231
- // Production: emit total request duration only.
232
- // No phase breakdown prevents information disclosure
233
- // while giving browser DevTools useful timing data.
271
+ } else if (serverTiming === 'total') {
272
+ // Total only: single `total;dur=N` no phase names.
273
+ // Prevents information disclosure while giving browser
274
+ // DevTools useful timing data.
234
275
  const totalMs = Math.round(performance.now() - startTime);
235
276
  result = ensureMutableResponse(result);
236
277
  result.headers.set('Server-Timing', `total;dur=${totalMs}`);
237
278
  }
279
+ // serverTiming === false: no header at all
238
280
 
239
281
  return result;
240
282
  }
@@ -254,7 +296,7 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
254
296
  return response;
255
297
  };
256
298
 
257
- return enableServerTiming ? runWithTimingCollector(runRequest) : runRequest();
299
+ return serverTiming === 'detailed' ? runWithTimingCollector(runRequest) : runRequest();
258
300
  });
259
301
  });
260
302
  };
@@ -272,7 +314,7 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
272
314
  }
273
315
  const proxyFn = () => runProxy(proxyExport, req, () => handleRequest(req, method, path));
274
316
  return await withSpan('timber.proxy', {}, () =>
275
- enableServerTiming ? withTiming('proxy', 'proxy.ts', proxyFn) : proxyFn()
317
+ serverTiming === 'detailed' ? withTiming('proxy', 'proxy.ts', proxyFn) : proxyFn()
276
318
  );
277
319
  } catch (error) {
278
320
  // Uncaught proxy.ts error → bare HTTP 500
@@ -283,6 +325,24 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
283
325
  }
284
326
  }
285
327
 
328
+ /**
329
+ * Build a redirect Response from a RedirectSignal.
330
+ *
331
+ * For RSC payload requests (client navigation), returns 204 + X-Timber-Redirect
332
+ * so the client router can perform a soft SPA redirect. A raw 302 would be
333
+ * turned into an opaque redirect by fetch({redirect:'manual'}), crashing
334
+ * createFromFetch. See design/19-client-navigation.md.
335
+ */
336
+ function buildRedirectResponse(signal: RedirectSignal, req: Request, headers: Headers): Response {
337
+ const isRsc = (req.headers.get('Accept') ?? '').includes('text/x-component');
338
+ if (isRsc) {
339
+ headers.set('X-Timber-Redirect', signal.location);
340
+ return new Response(null, { status: 204, headers });
341
+ }
342
+ headers.set('Location', signal.location);
343
+ return new Response(null, { status: signal.status, headers });
344
+ }
345
+
286
346
  async function handleRequest(req: Request, method: string, path: string): Promise<Response> {
287
347
  // Stage 1: URL canonicalization
288
348
  const url = new URL(req.url);
@@ -339,6 +399,21 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
339
399
  }
340
400
  }
341
401
 
402
+ // Stage 1c: Version skew detection (TIM-446).
403
+ // For RSC payload requests (client navigation), check if the client's
404
+ // deployment ID matches the current build. On mismatch, signal the
405
+ // client to do a full page reload instead of returning an RSC payload
406
+ // that references mismatched module IDs.
407
+ const isRscRequest = (req.headers.get('Accept') ?? '').includes('text/x-component');
408
+ if (isRscRequest) {
409
+ const skewCheck = checkVersionSkew(req);
410
+ if (!skewCheck.ok) {
411
+ const reloadHeaders = new Headers();
412
+ applyReloadHeaders(reloadHeaders);
413
+ return new Response(null, { status: 204, headers: reloadHeaders });
414
+ }
415
+ }
416
+
342
417
  // Stage 2: Route matching
343
418
  let match = matchRoute(canonicalPathname);
344
419
  let interception: InterceptionContext | undefined;
@@ -397,18 +472,57 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
397
472
  }
398
473
  }
399
474
 
475
+ // Stage 2c: Param coercion (before middleware)
476
+ // Load params.ts modules from matched segments and coerce raw string
477
+ // params through defineSegmentParams codecs. Coercion failure → 404
478
+ // (middleware never runs). See design/07-routing.md §"Where Coercion Runs"
479
+ try {
480
+ await coerceSegmentParams(match);
481
+ } catch (error) {
482
+ if (error instanceof ParamCoercionError) {
483
+ // For API routes (route.ts), return a bare 404 — not an HTML page.
484
+ // API consumers expect JSON/empty responses, not rendered HTML.
485
+ const leafSegment = match.segments[match.segments.length - 1];
486
+ if (
487
+ (leafSegment as { route?: unknown }).route &&
488
+ !(leafSegment as { page?: unknown }).page
489
+ ) {
490
+ return new Response(null, { status: 404 });
491
+ }
492
+ // Route through the app's 404 page (404.tsx in root layout) instead of
493
+ // returning a bare empty 404 Response. Falls back to bare 404 only if
494
+ // no renderNoMatch renderer is configured.
495
+ if (config.renderNoMatch) {
496
+ return config.renderNoMatch(req, responseHeaders);
497
+ }
498
+ return new Response(null, { status: 404 });
499
+ }
500
+ throw error;
501
+ }
502
+
503
+ // Store coerced segment params in ALS so components can access them
504
+ // via rawSegmentParams() instead of receiving them as a prop.
505
+ // See design/07-routing.md §"params.ts — Convention File for Typed Params"
506
+ setSegmentParams(match.params);
507
+
400
508
  // Stage 3: Leaf middleware.ts (only the leaf route's middleware runs)
401
509
  if (match.middleware) {
402
510
  const ctx: MiddlewareContext = {
403
511
  req,
404
512
  requestHeaders: requestHeaderOverlay,
405
513
  headers: responseHeaders,
406
- params: match.params,
407
- searchParams: new URL(req.url).searchParams,
514
+ segmentParams: match.params,
408
515
  earlyHints: (hints) => {
409
516
  for (const hint of hints) {
410
- let value = `<${hint.href}>; rel=${hint.rel}`;
411
- if (hint.as !== undefined) value += `; as=${hint.as}`;
517
+ // Match Cloudflare's cached Early Hints attribute order: `as` before `rel`.
518
+ // Cloudflare caches Link headers and re-emits them on subsequent 200s.
519
+ // If our order differs, the browser sees duplicate preloads and warns.
520
+ let value: string;
521
+ if (hint.as !== undefined) {
522
+ value = `<${hint.href}>; as=${hint.as}; rel=${hint.rel}`;
523
+ } else {
524
+ value = `<${hint.href}>; rel=${hint.rel}`;
525
+ }
412
526
  if (hint.crossOrigin !== undefined) value += `; crossorigin=${hint.crossOrigin}`;
413
527
  if (hint.fetchPriority !== undefined) value += `; fetchpriority=${hint.fetchPriority}`;
414
528
  responseHeaders.append('Link', value);
@@ -421,7 +535,9 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
421
535
  setMutableCookieContext(true);
422
536
  const middlewareFn = () => runMiddleware(match.middleware!, ctx);
423
537
  const middlewareResponse = await withSpan('timber.middleware', {}, () =>
424
- enableServerTiming ? withTiming('mw', 'middleware.ts', middlewareFn) : middlewareFn()
538
+ serverTiming === 'detailed'
539
+ ? withTiming('mw', 'middleware.ts', middlewareFn)
540
+ : middlewareFn()
425
541
  );
426
542
  setMutableCookieContext(false);
427
543
  if (middlewareResponse) {
@@ -438,20 +554,10 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
438
554
  applyRequestHeaderOverlay(requestHeaderOverlay);
439
555
  } catch (error) {
440
556
  setMutableCookieContext(false);
441
- // RedirectSignal from middleware → HTTP redirect (not an error).
442
- // For RSC payload requests (client navigation), return 204 + X-Timber-Redirect
443
- // so the client router can perform a soft SPA redirect. A raw 302 would be
444
- // turned into an opaque redirect by fetch({redirect:'manual'}), crashing
445
- // createFromFetch. See design/19-client-navigation.md.
557
+ // RedirectSignal from middleware → HTTP redirect (not an error)
446
558
  if (error instanceof RedirectSignal) {
447
559
  applyCookieJar(responseHeaders);
448
- const isRsc = (req.headers.get('Accept') ?? '').includes('text/x-component');
449
- if (isRsc) {
450
- responseHeaders.set('X-Timber-Redirect', error.location);
451
- return new Response(null, { status: 204, headers: responseHeaders });
452
- }
453
- responseHeaders.set('Location', error.location);
454
- return new Response(null, { status: error.status, headers: responseHeaders });
560
+ return buildRedirectResponse(error, req, responseHeaders);
455
561
  }
456
562
  // DenySignal from middleware → HTTP deny status
457
563
  if (error instanceof DenySignal) {
@@ -476,7 +582,9 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
476
582
  const renderFn = () =>
477
583
  render(req, match, responseHeaders, requestHeaderOverlay, interception);
478
584
  const response = await withSpan('timber.render', { 'http.route': canonicalPathname }, () =>
479
- enableServerTiming ? withTiming('render', 'RSC + SSR render', renderFn) : renderFn()
585
+ serverTiming === 'detailed'
586
+ ? withTiming('render', 'RSC + SSR render', renderFn)
587
+ : renderFn()
480
588
  );
481
589
  markResponseFlushed();
482
590
  return response;
@@ -486,10 +594,9 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
486
594
  if (error instanceof DenySignal) {
487
595
  return new Response(null, { status: error.status });
488
596
  }
489
- // RedirectSignal leaked from render — honour the redirect.
597
+ // RedirectSignal leaked from render — honour the redirect
490
598
  if (error instanceof RedirectSignal) {
491
- responseHeaders.set('Location', error.location);
492
- return new Response(null, { status: error.status, headers: responseHeaders });
599
+ return buildRedirectResponse(error, req, responseHeaders);
493
600
  }
494
601
  logRenderError({ method, path, error });
495
602
  await fireOnRequestError(error, req, 'render');
@@ -6,6 +6,8 @@
6
6
  import type { JsonSerializable } from './types.js';
7
7
  import { getWaitUntil as _getWaitUntil } from './waituntil-bridge.js';
8
8
  import { isDebug } from './debug.js';
9
+ import { getRequestSearchString } from './request-context.js';
10
+ import { mergePreservedSearchParams } from '../shared/merge-search-params.js';
9
11
 
10
12
  // ─── Dev-mode validation ────────────────────────────────────────────────────
11
13
 
@@ -209,14 +211,46 @@ export class RedirectSignal extends Error {
209
211
  /** Pattern matching absolute URLs: http(s):// or protocol-relative // */
210
212
  const ABSOLUTE_URL_RE = /^(?:[a-zA-Z][a-zA-Z\d+\-.]*:|\/\/)/;
211
213
 
214
+ /**
215
+ * Options for redirect() — alternative to passing a bare status code.
216
+ */
217
+ export interface RedirectOptions {
218
+ /** HTTP redirect status code (3xx). Defaults to 302. */
219
+ status?: number;
220
+ /**
221
+ * Preserve search params from the current request URL on the redirect target.
222
+ *
223
+ * - `true` — preserve ALL current search params (target params take precedence)
224
+ * - `string[]` — preserve only the named params (e.g. `['private', 'token']`)
225
+ *
226
+ * Target path's own query params always take precedence over preserved ones.
227
+ */
228
+ preserveSearchParams?: true | string[];
229
+ }
230
+
212
231
  /**
213
232
  * Redirect to a relative path. Rejects absolute and protocol-relative URLs.
214
233
  * Use `redirectExternal()` for external redirects with an allow-list.
215
234
  *
216
235
  * @param path - Relative path (e.g. '/login', 'settings', '/login?returnTo=/dash')
217
- * @param status - HTTP redirect status code (3xx). Defaults to 302.
236
+ * @param statusOrOptions - HTTP status code (3xx, default 302) or options object.
237
+ *
238
+ * @example
239
+ * // Simple redirect
240
+ * redirect('/login');
241
+ *
242
+ * // With status code
243
+ * redirect('/login', 301);
244
+ *
245
+ * // With preserved search params
246
+ * redirect(`/docs/${version}/${slug}`, { preserveSearchParams: ['foo'] });
218
247
  */
219
- export function redirect(path: string, status: number = 302): never {
248
+ export function redirect(path: string, statusOrOptions?: number | RedirectOptions): never {
249
+ const status =
250
+ typeof statusOrOptions === 'number' ? statusOrOptions : (statusOrOptions?.status ?? 302);
251
+ const preserveSearchParams =
252
+ typeof statusOrOptions === 'object' ? statusOrOptions.preserveSearchParams : undefined;
253
+
220
254
  if (status < 300 || status > 399) {
221
255
  throw new Error(`redirect() requires a 3xx status code, got ${status}.`);
222
256
  }
@@ -226,7 +260,14 @@ export function redirect(path: string, status: number = 302): never {
226
260
  'Use redirectExternal(url, allowList) for external redirects.'
227
261
  );
228
262
  }
229
- throw new RedirectSignal(path, status);
263
+
264
+ let resolvedPath = path;
265
+ if (preserveSearchParams) {
266
+ const currentSearch = getRequestSearchString();
267
+ resolvedPath = mergePreservedSearchParams(path, currentSearch, preserveSearchParams);
268
+ }
269
+
270
+ throw new RedirectSignal(resolvedPath, status);
230
271
  }
231
272
 
232
273
  /**
@@ -236,9 +277,10 @@ export function redirect(path: string, status: number = 302): never {
236
277
  * will replay POST requests to the new location. This matches Next.js behavior.
237
278
  *
238
279
  * @param path - Relative path (e.g. '/new-page', '/dashboard')
280
+ * @param options - Optional redirect options (e.g. preserveSearchParams).
239
281
  */
240
- export function permanentRedirect(path: string): never {
241
- redirect(path, 308);
282
+ export function permanentRedirect(path: string, options?: Omit<RedirectOptions, 'status'>): never {
283
+ redirect(path, { status: 308, ...options });
242
284
  }
243
285
 
244
286
  /**
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Render timeout utilities for SSR streaming pipeline.
3
+ *
4
+ * Provides a RenderTimeoutError class and a helper to create
5
+ * timeout-guarded AbortSignals. Used to defend against hung RSC
6
+ * streams and infinite SSR renders.
7
+ *
8
+ * Design doc: 02-rendering-pipeline.md §"Streaming Constraints"
9
+ */
10
+
11
+ /**
12
+ * Error thrown when an SSR render or RSC stream read exceeds the
13
+ * configured timeout. Callers can check `instanceof RenderTimeoutError`
14
+ * to distinguish timeout from other errors and return a 504 or close
15
+ * the connection cleanly.
16
+ */
17
+ export class RenderTimeoutError extends Error {
18
+ readonly timeoutMs: number;
19
+
20
+ constructor(timeoutMs: number, context?: string) {
21
+ const message = context
22
+ ? `Render timeout after ${timeoutMs}ms: ${context}`
23
+ : `Render timeout after ${timeoutMs}ms`;
24
+ super(message);
25
+ this.name = 'RenderTimeoutError';
26
+ this.timeoutMs = timeoutMs;
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Result of createRenderTimeout — an AbortSignal that fires after
32
+ * the given duration, plus a cancel function to clear the timer
33
+ * when the render completes normally.
34
+ */
35
+ export interface RenderTimeout {
36
+ /** AbortSignal that aborts after timeoutMs. */
37
+ signal: AbortSignal;
38
+ /** Cancel the timeout timer. Call this when the render completes. */
39
+ cancel: () => void;
40
+ }
41
+
42
+ /**
43
+ * Create a render timeout that aborts after the given duration.
44
+ *
45
+ * Returns an AbortSignal and a cancel function. The signal fires
46
+ * with a RenderTimeoutError as the abort reason after `timeoutMs`.
47
+ * Call `cancel()` when the render completes to prevent the timeout
48
+ * from firing.
49
+ *
50
+ * If an existing `parentSignal` is provided, the returned signal
51
+ * aborts when either the parent signal or the timeout fires —
52
+ * whichever comes first.
53
+ */
54
+ export function createRenderTimeout(timeoutMs: number, parentSignal?: AbortSignal): RenderTimeout {
55
+ const controller = new AbortController();
56
+ const reason = new RenderTimeoutError(timeoutMs, 'RSC stream read timed out');
57
+
58
+ const timer = setTimeout(() => {
59
+ controller.abort(reason);
60
+ }, timeoutMs);
61
+
62
+ // If there's a parent signal (e.g. request abort), chain it
63
+ if (parentSignal) {
64
+ if (parentSignal.aborted) {
65
+ clearTimeout(timer);
66
+ controller.abort(parentSignal.reason);
67
+ } else {
68
+ parentSignal.addEventListener(
69
+ 'abort',
70
+ () => {
71
+ clearTimeout(timer);
72
+ controller.abort(parentSignal.reason);
73
+ },
74
+ { once: true }
75
+ );
76
+ }
77
+ }
78
+
79
+ return {
80
+ signal: controller.signal,
81
+ cancel: () => {
82
+ clearTimeout(timer);
83
+ },
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Race a promise against a timeout. Rejects with RenderTimeoutError
89
+ * if the promise does not resolve within `timeoutMs`.
90
+ *
91
+ * Used to guard individual `rscReader.read()` calls inside pullLoop.
92
+ */
93
+ export function withTimeout<T>(
94
+ promise: Promise<T>,
95
+ timeoutMs: number,
96
+ context?: string
97
+ ): Promise<T> {
98
+ let timer: ReturnType<typeof setTimeout>;
99
+ const timeoutPromise = new Promise<never>((_resolve, reject) => {
100
+ timer = setTimeout(() => {
101
+ reject(new RenderTimeoutError(timeoutMs, context));
102
+ }, timeoutMs);
103
+ });
104
+
105
+ return Promise.race([promise, timeoutPromise]).finally(() => {
106
+ clearTimeout(timer!);
107
+ });
108
+ }