@timber-js/app 0.2.0-alpha.4 → 0.2.0-alpha.41

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 (336) 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-ECi_61pb.js +108 -0
  6. package/dist/_chunks/debug-ECi_61pb.js.map +1 -0
  7. package/dist/_chunks/define-cookie-BmKbSyp0.js +93 -0
  8. package/dist/_chunks/define-cookie-BmKbSyp0.js.map +1 -0
  9. package/dist/_chunks/error-boundary-BAN3751q.js +211 -0
  10. package/dist/_chunks/error-boundary-BAN3751q.js.map +1 -0
  11. package/dist/_chunks/{format-CwdaB0_2.js → format-cX7wzEp2.js} +2 -2
  12. package/dist/_chunks/{format-CwdaB0_2.js.map → format-cX7wzEp2.js.map} +1 -1
  13. package/dist/_chunks/{interception-BOoWmLUA.js → interception-D2djYaIm.js} +112 -77
  14. package/dist/_chunks/interception-D2djYaIm.js.map +1 -0
  15. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → metadata-routes-BU684ls2.js} +1 -1
  16. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js.map → metadata-routes-BU684ls2.js.map} +1 -1
  17. package/dist/_chunks/{request-context-CZJi4CuK.js → request-context-BxYIJM24.js} +93 -69
  18. package/dist/_chunks/request-context-BxYIJM24.js.map +1 -0
  19. package/dist/_chunks/segment-context-C6byCyZU.js +69 -0
  20. package/dist/_chunks/segment-context-C6byCyZU.js.map +1 -0
  21. package/dist/_chunks/stale-reload-C0ValzG7.js +47 -0
  22. package/dist/_chunks/stale-reload-C0ValzG7.js.map +1 -0
  23. package/dist/_chunks/{tracing-Cwn7697K.js → tracing-CuXiCP5p.js} +17 -3
  24. package/dist/_chunks/{tracing-Cwn7697K.js.map → tracing-CuXiCP5p.js.map} +1 -1
  25. package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-BvW0TKDn.js} +1 -1
  26. package/dist/_chunks/{use-query-states-D5KaffOK.js.map → use-query-states-BvW0TKDn.js.map} +1 -1
  27. package/dist/_chunks/wrappers-C6J0nNji.js +331 -0
  28. package/dist/_chunks/wrappers-C6J0nNji.js.map +1 -0
  29. package/dist/adapters/compress-module.d.ts.map +1 -1
  30. package/dist/adapters/nitro.d.ts +17 -1
  31. package/dist/adapters/nitro.d.ts.map +1 -1
  32. package/dist/adapters/nitro.js +56 -13
  33. package/dist/adapters/nitro.js.map +1 -1
  34. package/dist/cache/fast-hash.d.ts +22 -0
  35. package/dist/cache/fast-hash.d.ts.map +1 -0
  36. package/dist/cache/index.d.ts +5 -2
  37. package/dist/cache/index.d.ts.map +1 -1
  38. package/dist/cache/index.js +88 -18
  39. package/dist/cache/index.js.map +1 -1
  40. package/dist/cache/register-cached-function.d.ts.map +1 -1
  41. package/dist/cache/singleflight.d.ts +18 -1
  42. package/dist/cache/singleflight.d.ts.map +1 -1
  43. package/dist/cache/timber-cache.d.ts.map +1 -1
  44. package/dist/client/error-boundary.d.ts +10 -1
  45. package/dist/client/error-boundary.d.ts.map +1 -1
  46. package/dist/client/error-boundary.js +1 -125
  47. package/dist/client/index.d.ts +3 -2
  48. package/dist/client/index.d.ts.map +1 -1
  49. package/dist/client/index.js +213 -93
  50. package/dist/client/index.js.map +1 -1
  51. package/dist/client/link.d.ts +22 -8
  52. package/dist/client/link.d.ts.map +1 -1
  53. package/dist/client/navigation-context.d.ts +2 -2
  54. package/dist/client/router.d.ts +25 -3
  55. package/dist/client/router.d.ts.map +1 -1
  56. package/dist/client/rsc-fetch.d.ts +23 -2
  57. package/dist/client/rsc-fetch.d.ts.map +1 -1
  58. package/dist/client/segment-cache.d.ts +1 -1
  59. package/dist/client/segment-cache.d.ts.map +1 -1
  60. package/dist/client/segment-context.d.ts +1 -1
  61. package/dist/client/segment-context.d.ts.map +1 -1
  62. package/dist/client/segment-merger.d.ts.map +1 -1
  63. package/dist/client/stale-reload.d.ts +15 -0
  64. package/dist/client/stale-reload.d.ts.map +1 -1
  65. package/dist/client/top-loader.d.ts +1 -1
  66. package/dist/client/top-loader.d.ts.map +1 -1
  67. package/dist/client/transition-root.d.ts +1 -1
  68. package/dist/client/transition-root.d.ts.map +1 -1
  69. package/dist/client/use-params.d.ts +2 -2
  70. package/dist/client/use-params.d.ts.map +1 -1
  71. package/dist/client/use-query-states.d.ts +1 -1
  72. package/dist/codec.d.ts +21 -0
  73. package/dist/codec.d.ts.map +1 -0
  74. package/dist/cookies/define-cookie.d.ts +33 -12
  75. package/dist/cookies/define-cookie.d.ts.map +1 -1
  76. package/dist/cookies/index.js +1 -83
  77. package/dist/fonts/css.d.ts +1 -0
  78. package/dist/fonts/css.d.ts.map +1 -1
  79. package/dist/fonts/local.d.ts +4 -2
  80. package/dist/fonts/local.d.ts.map +1 -1
  81. package/dist/index.d.ts +112 -35
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +635 -233
  84. package/dist/index.js.map +1 -1
  85. package/dist/params/define.d.ts +76 -0
  86. package/dist/params/define.d.ts.map +1 -0
  87. package/dist/params/index.d.ts +8 -0
  88. package/dist/params/index.d.ts.map +1 -0
  89. package/dist/params/index.js +104 -0
  90. package/dist/params/index.js.map +1 -0
  91. package/dist/plugins/adapter-build.d.ts.map +1 -1
  92. package/dist/plugins/build-manifest.d.ts.map +1 -1
  93. package/dist/plugins/client-chunks.d.ts +32 -0
  94. package/dist/plugins/client-chunks.d.ts.map +1 -0
  95. package/dist/plugins/dev-error-overlay.d.ts +26 -1
  96. package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
  97. package/dist/plugins/entries.d.ts +7 -0
  98. package/dist/plugins/entries.d.ts.map +1 -1
  99. package/dist/plugins/fonts.d.ts +9 -1
  100. package/dist/plugins/fonts.d.ts.map +1 -1
  101. package/dist/plugins/mdx.d.ts +6 -0
  102. package/dist/plugins/mdx.d.ts.map +1 -1
  103. package/dist/plugins/routing.d.ts.map +1 -1
  104. package/dist/plugins/server-bundle.d.ts.map +1 -1
  105. package/dist/plugins/static-build.d.ts.map +1 -1
  106. package/dist/routing/codegen.d.ts +2 -2
  107. package/dist/routing/codegen.d.ts.map +1 -1
  108. package/dist/routing/index.js +1 -1
  109. package/dist/routing/scanner.d.ts.map +1 -1
  110. package/dist/routing/status-file-lint.d.ts +2 -1
  111. package/dist/routing/status-file-lint.d.ts.map +1 -1
  112. package/dist/routing/types.d.ts +6 -4
  113. package/dist/routing/types.d.ts.map +1 -1
  114. package/dist/rsc-runtime/rsc.d.ts +1 -1
  115. package/dist/rsc-runtime/rsc.d.ts.map +1 -1
  116. package/dist/rsc-runtime/ssr.d.ts +12 -0
  117. package/dist/rsc-runtime/ssr.d.ts.map +1 -1
  118. package/dist/search-params/codecs.d.ts +1 -1
  119. package/dist/search-params/define.d.ts +153 -0
  120. package/dist/search-params/define.d.ts.map +1 -0
  121. package/dist/search-params/index.d.ts +4 -5
  122. package/dist/search-params/index.d.ts.map +1 -1
  123. package/dist/search-params/index.js +3 -474
  124. package/dist/search-params/registry.d.ts +1 -1
  125. package/dist/search-params/wrappers.d.ts +53 -0
  126. package/dist/search-params/wrappers.d.ts.map +1 -0
  127. package/dist/server/access-gate.d.ts +4 -0
  128. package/dist/server/access-gate.d.ts.map +1 -1
  129. package/dist/server/action-client.d.ts.map +1 -1
  130. package/dist/server/action-encryption.d.ts +76 -0
  131. package/dist/server/action-encryption.d.ts.map +1 -0
  132. package/dist/server/action-handler.d.ts.map +1 -1
  133. package/dist/server/als-registry.d.ts +18 -4
  134. package/dist/server/als-registry.d.ts.map +1 -1
  135. package/dist/server/build-manifest.d.ts +2 -2
  136. package/dist/server/debug.d.ts +46 -15
  137. package/dist/server/debug.d.ts.map +1 -1
  138. package/dist/server/default-logger.d.ts +22 -0
  139. package/dist/server/default-logger.d.ts.map +1 -0
  140. package/dist/server/deny-renderer.d.ts.map +1 -1
  141. package/dist/server/early-hints.d.ts +13 -5
  142. package/dist/server/early-hints.d.ts.map +1 -1
  143. package/dist/server/error-boundary-wrapper.d.ts +4 -0
  144. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  145. package/dist/server/flight-injection-state.d.ts +78 -0
  146. package/dist/server/flight-injection-state.d.ts.map +1 -0
  147. package/dist/server/flight-scripts.d.ts +39 -0
  148. package/dist/server/flight-scripts.d.ts.map +1 -0
  149. package/dist/server/flush.d.ts.map +1 -1
  150. package/dist/server/form-data.d.ts +29 -0
  151. package/dist/server/form-data.d.ts.map +1 -1
  152. package/dist/server/html-injectors.d.ts +5 -11
  153. package/dist/server/html-injectors.d.ts.map +1 -1
  154. package/dist/server/index.d.ts +4 -2
  155. package/dist/server/index.d.ts.map +1 -1
  156. package/dist/server/index.js +1975 -1649
  157. package/dist/server/index.js.map +1 -1
  158. package/dist/server/logger.d.ts +24 -7
  159. package/dist/server/logger.d.ts.map +1 -1
  160. package/dist/server/node-stream-transforms.d.ts +77 -0
  161. package/dist/server/node-stream-transforms.d.ts.map +1 -0
  162. package/dist/server/pipeline.d.ts +7 -4
  163. package/dist/server/pipeline.d.ts.map +1 -1
  164. package/dist/server/primitives.d.ts +30 -3
  165. package/dist/server/primitives.d.ts.map +1 -1
  166. package/dist/server/render-timeout.d.ts +51 -0
  167. package/dist/server/render-timeout.d.ts.map +1 -0
  168. package/dist/server/request-context.d.ts +65 -38
  169. package/dist/server/request-context.d.ts.map +1 -1
  170. package/dist/server/route-element-builder.d.ts +7 -0
  171. package/dist/server/route-element-builder.d.ts.map +1 -1
  172. package/dist/server/route-handler.d.ts.map +1 -1
  173. package/dist/server/route-matcher.d.ts +2 -2
  174. package/dist/server/route-matcher.d.ts.map +1 -1
  175. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  176. package/dist/server/rsc-entry/helpers.d.ts +46 -3
  177. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  178. package/dist/server/rsc-entry/index.d.ts +6 -1
  179. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  180. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  181. package/dist/server/rsc-entry/rsc-stream.d.ts +9 -0
  182. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  183. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  184. package/dist/server/slot-resolver.d.ts +1 -1
  185. package/dist/server/slot-resolver.d.ts.map +1 -1
  186. package/dist/server/ssr-entry.d.ts +22 -0
  187. package/dist/server/ssr-entry.d.ts.map +1 -1
  188. package/dist/server/ssr-render.d.ts +39 -21
  189. package/dist/server/ssr-render.d.ts.map +1 -1
  190. package/dist/server/tracing.d.ts +10 -0
  191. package/dist/server/tracing.d.ts.map +1 -1
  192. package/dist/server/tree-builder.d.ts +19 -12
  193. package/dist/server/tree-builder.d.ts.map +1 -1
  194. package/dist/server/types.d.ts +1 -3
  195. package/dist/server/types.d.ts.map +1 -1
  196. package/dist/server/version-skew.d.ts +61 -0
  197. package/dist/server/version-skew.d.ts.map +1 -0
  198. package/dist/server/waituntil-bridge.d.ts.map +1 -1
  199. package/dist/shared/merge-search-params.d.ts +22 -0
  200. package/dist/shared/merge-search-params.d.ts.map +1 -0
  201. package/dist/shims/navigation-client.d.ts +1 -1
  202. package/dist/shims/navigation-client.d.ts.map +1 -1
  203. package/dist/shims/navigation.d.ts +1 -1
  204. package/dist/shims/navigation.d.ts.map +1 -1
  205. package/dist/utils/state-machine.d.ts +80 -0
  206. package/dist/utils/state-machine.d.ts.map +1 -0
  207. package/package.json +17 -14
  208. package/src/adapters/compress-module.ts +24 -4
  209. package/src/adapters/nitro.ts +58 -9
  210. package/src/cache/fast-hash.ts +34 -0
  211. package/src/cache/index.ts +5 -2
  212. package/src/cache/register-cached-function.ts +7 -3
  213. package/src/cache/singleflight.ts +62 -4
  214. package/src/cache/timber-cache.ts +34 -26
  215. package/src/cli.ts +0 -0
  216. package/src/client/browser-entry.ts +94 -90
  217. package/src/client/error-boundary.tsx +18 -1
  218. package/src/client/index.ts +10 -1
  219. package/src/client/link.tsx +78 -19
  220. package/src/client/navigation-context.ts +2 -2
  221. package/src/client/router.ts +105 -60
  222. package/src/client/rsc-fetch.ts +63 -2
  223. package/src/client/segment-cache.ts +1 -1
  224. package/src/client/segment-context.ts +6 -1
  225. package/src/client/segment-merger.ts +2 -8
  226. package/src/client/stale-reload.ts +32 -6
  227. package/src/client/top-loader.tsx +10 -9
  228. package/src/client/transition-root.tsx +7 -1
  229. package/src/client/use-params.ts +3 -3
  230. package/src/client/use-query-states.ts +1 -1
  231. package/src/codec.ts +21 -0
  232. package/src/cookies/define-cookie.ts +69 -18
  233. package/src/fonts/css.ts +2 -1
  234. package/src/fonts/local.ts +7 -3
  235. package/src/index.ts +280 -85
  236. package/src/params/define.ts +260 -0
  237. package/src/params/index.ts +28 -0
  238. package/src/plugins/adapter-build.ts +6 -0
  239. package/src/plugins/build-manifest.ts +11 -0
  240. package/src/plugins/client-chunks.ts +65 -0
  241. package/src/plugins/dev-error-overlay.ts +70 -1
  242. package/src/plugins/dev-server.ts +38 -4
  243. package/src/plugins/entries.ts +12 -11
  244. package/src/plugins/fonts.ts +171 -19
  245. package/src/plugins/mdx.ts +9 -5
  246. package/src/plugins/routing.ts +40 -14
  247. package/src/plugins/server-bundle.ts +32 -1
  248. package/src/plugins/shims.ts +1 -1
  249. package/src/plugins/static-build.ts +8 -4
  250. package/src/routing/codegen.ts +109 -88
  251. package/src/routing/scanner.ts +55 -6
  252. package/src/routing/status-file-lint.ts +2 -1
  253. package/src/routing/types.ts +7 -4
  254. package/src/rsc-runtime/rsc.ts +2 -0
  255. package/src/rsc-runtime/ssr.ts +50 -0
  256. package/src/rsc-runtime/vendor-types.d.ts +7 -0
  257. package/src/search-params/codecs.ts +1 -1
  258. package/src/search-params/define.ts +504 -0
  259. package/src/search-params/index.ts +12 -18
  260. package/src/search-params/registry.ts +1 -1
  261. package/src/search-params/wrappers.ts +85 -0
  262. package/src/server/access-gate.tsx +40 -9
  263. package/src/server/action-client.ts +14 -5
  264. package/src/server/action-encryption.ts +144 -0
  265. package/src/server/action-handler.ts +19 -2
  266. package/src/server/als-registry.ts +18 -4
  267. package/src/server/build-manifest.ts +4 -4
  268. package/src/server/compress.ts +25 -7
  269. package/src/server/debug.ts +55 -17
  270. package/src/server/default-logger.ts +98 -0
  271. package/src/server/deny-renderer.ts +2 -1
  272. package/src/server/early-hints.ts +36 -15
  273. package/src/server/error-boundary-wrapper.ts +57 -14
  274. package/src/server/flight-injection-state.ts +152 -0
  275. package/src/server/flight-scripts.ts +59 -0
  276. package/src/server/flush.ts +2 -1
  277. package/src/server/form-data.ts +76 -0
  278. package/src/server/html-injectors.ts +103 -66
  279. package/src/server/index.ts +9 -4
  280. package/src/server/logger.ts +38 -35
  281. package/src/server/node-stream-transforms.ts +381 -0
  282. package/src/server/pipeline.ts +131 -39
  283. package/src/server/primitives.ts +47 -5
  284. package/src/server/render-timeout.ts +108 -0
  285. package/src/server/request-context.ts +112 -119
  286. package/src/server/route-element-builder.ts +106 -114
  287. package/src/server/route-handler.ts +2 -1
  288. package/src/server/route-matcher.ts +2 -2
  289. package/src/server/rsc-entry/error-renderer.ts +5 -3
  290. package/src/server/rsc-entry/helpers.ts +122 -3
  291. package/src/server/rsc-entry/index.ts +125 -49
  292. package/src/server/rsc-entry/rsc-payload.ts +52 -12
  293. package/src/server/rsc-entry/rsc-stream.ts +33 -8
  294. package/src/server/rsc-entry/ssr-renderer.ts +40 -13
  295. package/src/server/slot-resolver.ts +199 -210
  296. package/src/server/ssr-entry.ts +168 -22
  297. package/src/server/ssr-render.ts +289 -67
  298. package/src/server/tracing.ts +23 -0
  299. package/src/server/tree-builder.ts +91 -57
  300. package/src/server/types.ts +1 -3
  301. package/src/server/version-skew.ts +104 -0
  302. package/src/server/waituntil-bridge.ts +4 -1
  303. package/src/shared/merge-search-params.ts +48 -0
  304. package/src/shims/navigation-client.ts +1 -1
  305. package/src/shims/navigation.ts +1 -1
  306. package/src/utils/state-machine.ts +111 -0
  307. package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
  308. package/dist/_chunks/debug-B4WUeqJ-.js +0 -75
  309. package/dist/_chunks/debug-B4WUeqJ-.js.map +0 -1
  310. package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
  311. package/dist/_chunks/request-context-CZJi4CuK.js.map +0 -1
  312. package/dist/_chunks/ssr-data-MjmprTmO.js +0 -88
  313. package/dist/_chunks/ssr-data-MjmprTmO.js.map +0 -1
  314. package/dist/_chunks/use-cookie-DX-l1_5E.js +0 -91
  315. package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
  316. package/dist/client/error-boundary.js.map +0 -1
  317. package/dist/cookies/index.js.map +0 -1
  318. package/dist/plugins/dynamic-transform.d.ts +0 -72
  319. package/dist/plugins/dynamic-transform.d.ts.map +0 -1
  320. package/dist/search-params/analyze.d.ts +0 -54
  321. package/dist/search-params/analyze.d.ts.map +0 -1
  322. package/dist/search-params/builtin-codecs.d.ts +0 -105
  323. package/dist/search-params/builtin-codecs.d.ts.map +0 -1
  324. package/dist/search-params/create.d.ts +0 -106
  325. package/dist/search-params/create.d.ts.map +0 -1
  326. package/dist/search-params/index.js.map +0 -1
  327. package/dist/server/prerender.d.ts +0 -77
  328. package/dist/server/prerender.d.ts.map +0 -1
  329. package/dist/server/response-cache.d.ts +0 -53
  330. package/dist/server/response-cache.d.ts.map +0 -1
  331. package/src/plugins/dynamic-transform.ts +0 -161
  332. package/src/search-params/analyze.ts +0 -192
  333. package/src/search-params/builtin-codecs.ts +0 -228
  334. package/src/search-params/create.ts +0 -321
  335. package/src/server/prerender.ts +0 -139
  336. package/src/server/response-cache.ts +0 -277
@@ -7,6 +7,18 @@
7
7
  * Design docs: 02-rendering-pipeline.md, 18-build-system.md §"Entry Files"
8
8
  */
9
9
 
10
+ import { createMachine } from '../utils/state-machine.js';
11
+ import { flightChunkScript } from './flight-scripts.js';
12
+ import {
13
+ flightInjectionTransitions,
14
+ isSuffixStripped,
15
+ isHtmlDone,
16
+ isPullDone,
17
+ type FlightInjectionState,
18
+ type FlightInjectionEvent,
19
+ } from './flight-injection-state.js';
20
+ import { withTimeout, RenderTimeoutError } from './render-timeout.js';
21
+
10
22
  /**
11
23
  * Inject HTML content before a closing tag in the stream.
12
24
  *
@@ -95,22 +107,6 @@ export function injectScripts(
95
107
  return createInjector(stream, scriptsHtml, '</body>');
96
108
  }
97
109
 
98
- /**
99
- * Escape a string for safe embedding inside a `<script>` tag within
100
- * a JSON-encoded value.
101
- *
102
- * Only needs to prevent `</script>` from closing the tag early and
103
- * handle U+2028/U+2029 (line/paragraph separators valid in JSON but
104
- * historically problematic in JS). Since we use JSON.stringify for the
105
- * outer encoding, we only escape `<` and the line separators.
106
- */
107
- function htmlEscapeJsonString(str: string): string {
108
- return str
109
- .replace(/</g, '\\u003c')
110
- .replace(/\u2028/g, '\\u2028')
111
- .replace(/\u2029/g, '\\u2029');
112
- }
113
-
114
110
  /**
115
111
  * Transform an RSC Flight stream into a stream of inline `<script>` tags.
116
112
  *
@@ -118,42 +114,42 @@ function htmlEscapeJsonString(str: string): string {
118
114
  * transform) drives reads from the RSC stream on demand. No background
119
115
  * reader, no shared mutable arrays, no race conditions.
120
116
  *
121
- * Each RSC chunk becomes:
122
- * <script>(self.__timber_f=self.__timber_f||[]).push([1,"escaped_chunk"])</script>
123
- *
124
- * The first chunk emitted is the bootstrap signal [0] which the client
125
- * uses to initialize its buffer.
126
- *
127
- * Uses JSON-encoded typed tuples matching the pattern from Next.js:
128
- * [0] — bootstrap signal
129
- * [1, data] — RSC Flight data chunk (UTF-8 string)
117
+ * Each RSC chunk becomes a `<script>self.__timber_f.push([1,"data"])</script>`.
118
+ * The init script (which creates __timber_f) is in `<head>` via
119
+ * flightInitScript() — see flight-scripts.ts.
130
120
  */
131
121
  export function createInlinedRscStream(
132
- rscStream: ReadableStream<Uint8Array>
122
+ rscStream: ReadableStream<Uint8Array>,
123
+ renderTimeoutMs?: number
133
124
  ): ReadableStream<Uint8Array> {
134
125
  const encoder = new TextEncoder();
135
126
  const rscReader = rscStream.getReader();
136
127
  const decoder = new TextDecoder('utf-8', { fatal: true });
128
+ const timeoutMs = renderTimeoutMs ?? 30_000;
137
129
 
138
130
  return new ReadableStream<Uint8Array>({
139
- start(controller) {
140
- // Emit bootstrap signal tells the client that __timber_f is active
141
- const bootstrap = `<script>(self.__timber_f=self.__timber_f||[]).push(${htmlEscapeJsonString(JSON.stringify([0]))})</script>`;
142
- controller.enqueue(encoder.encode(bootstrap));
143
- },
131
+ // No bootstrap signal here — the init script is in <head> via
132
+ // flightInitScript() (see flight-scripts.ts). This ensures the
133
+ // __timber_f array exists before any chunk scripts execute.
144
134
  async pull(controller) {
145
135
  try {
146
- const { done, value } = await rscReader.read();
136
+ const readPromise = rscReader.read();
137
+ const { done, value } =
138
+ timeoutMs > 0
139
+ ? await withTimeout(readPromise, timeoutMs, 'RSC stream read timed out')
140
+ : await readPromise;
147
141
  if (done) {
148
142
  controller.close();
149
143
  return;
150
144
  }
151
145
  if (value) {
152
146
  const decoded = decoder.decode(value, { stream: true });
153
- const escaped = htmlEscapeJsonString(JSON.stringify([1, decoded]));
154
- controller.enqueue(encoder.encode(`<script>self.__timber_f.push(${escaped})</script>`));
147
+ controller.enqueue(encoder.encode(flightChunkScript(decoded)));
155
148
  }
156
149
  } catch (error) {
150
+ if (error instanceof RenderTimeoutError) {
151
+ rscReader.cancel(error).catch(() => {});
152
+ }
157
153
  controller.error(error);
158
154
  }
159
155
  },
@@ -179,10 +175,16 @@ export function createInlinedRscStream(
179
175
  * scanning or depth tracking needed — the suffix removal is the
180
176
  * structural guarantee.
181
177
  *
178
+ * State machine phases:
179
+ * init → streaming → body-level → flushing → done
180
+ * └──────────────→ flushing → done
181
+ * (any) → error
182
+ *
182
183
  * Inspired by Next.js createFlightDataInjectionTransformStream.
183
184
  */
184
185
  function createFlightInjectionTransform(
185
- rscScriptStream: ReadableStream<Uint8Array>
186
+ rscScriptStream: ReadableStream<Uint8Array>,
187
+ renderTimeoutMs?: number
186
188
  ): TransformStream<Uint8Array, Uint8Array> {
187
189
  const encoder = new TextEncoder();
188
190
  const decoder = new TextDecoder();
@@ -190,39 +192,59 @@ function createFlightInjectionTransform(
190
192
  const suffixBytes = encoder.encode(suffix);
191
193
 
192
194
  const rscReader = rscScriptStream.getReader();
195
+ const timeoutMs = renderTimeoutMs ?? 30_000;
196
+
197
+ const machine = createMachine<FlightInjectionState, FlightInjectionEvent>({
198
+ initial: { phase: 'init' },
199
+ transitions: flightInjectionTransitions,
200
+ });
201
+
193
202
  let pullPromise: Promise<void> | null = null;
194
- let donePulling = false;
195
- let pullError: unknown = null;
196
- // Once the suffix is stripped, all content is body-level and
197
- // scripts can safely be drained after any HTML chunk.
198
- let foundSuffix = false;
199
203
 
200
204
  // RSC script chunks waiting to be injected at the body level.
201
205
  const pending: Uint8Array[] = [];
202
206
 
203
207
  async function pullLoop(): Promise<void> {
204
- // Wait one macrotask so the HTML shell chunk flows through
205
- // transform() first. The browser needs the shell HTML before
206
- // RSC data script tags arrive.
207
- await new Promise<void>((r) => setTimeout(r, 0));
208
+ // Yield once so the first HTML shell chunk flows through
209
+ // transform() before we start reading RSC data. Uses
210
+ // setImmediate (check phase end of current event loop
211
+ // iteration) instead of setTimeout(0) (timer phase — next
212
+ // iteration). Under concurrency, setTimeout(0) yields to
213
+ // ALL pending timer callbacks from other requests, adding
214
+ // 1-4ms per yield. setImmediate fires before timers.
215
+ // Available on both Node.js and Cloudflare Workers.
216
+ await new Promise<void>((r) => setImmediate(r));
208
217
 
209
218
  try {
210
219
  for (;;) {
211
- const { done, value } = await rscReader.read();
220
+ // Guard each RSC read with a timeout so a permanently hung
221
+ // RSC stream eventually aborts. When timeoutMs <= 0, the
222
+ // guard is disabled. See design/02-rendering-pipeline.md
223
+ // §"Streaming Constraints".
224
+ const readPromise = rscReader.read();
225
+ const { done, value } =
226
+ timeoutMs > 0
227
+ ? await withTimeout(readPromise, timeoutMs, 'RSC stream read timed out')
228
+ : await readPromise;
212
229
  if (done) {
213
- donePulling = true;
230
+ machine.send({ type: 'PULL_DONE' });
214
231
  return;
215
232
  }
216
233
  pending.push(value);
217
234
  // Yield between reads so HTML chunks get a chance to flow
218
- // through transform() first. RSC and HTML are driven by the
219
- // same source each RSC chunk typically produces a
220
- // corresponding HTML chunk from SSR.
221
- await new Promise<void>((r) => setTimeout(r, 0));
235
+ // through transform() first but only while HTML is still
236
+ // streaming. Once flush() fires (all HTML emitted), drain
237
+ // remaining RSC chunks without yielding.
238
+ if (!isHtmlDone(machine.state)) {
239
+ await new Promise<void>((r) => setImmediate(r));
240
+ }
222
241
  }
223
242
  } catch (err) {
224
- pullError = err;
225
- donePulling = true;
243
+ // On timeout, cancel the RSC reader to release resources.
244
+ if (err instanceof RenderTimeoutError) {
245
+ rscReader.cancel(err).catch(() => {});
246
+ }
247
+ machine.send({ type: 'PULL_ERROR', error: err });
226
248
  }
227
249
  }
228
250
 
@@ -231,21 +253,24 @@ function createFlightInjectionTransform(
231
253
  while (pending.length > 0) {
232
254
  controller.enqueue(pending.shift()!);
233
255
  }
234
- if (pullError) {
235
- const err = pullError;
236
- pullError = null;
237
- controller.error(err);
256
+ if (machine.state.phase === 'error') {
257
+ controller.error(machine.state.error);
238
258
  }
239
259
  }
240
260
 
241
261
  return new TransformStream<Uint8Array, Uint8Array>({
242
262
  transform(chunk, controller) {
243
- // Start pulling RSC scripts into the buffer (if not started)
244
- if (!pullPromise) {
263
+ // Pull-based start: don't begin reading RSC until the first
264
+ // HTML chunk flows through. This matches Next.js's approach
265
+ // and ensures the shell HTML is enqueued before any RSC
266
+ // script tags. Without this, the pull loop starts eagerly
267
+ // and may read RSC data before the browser has any HTML.
268
+ if (machine.state.phase === 'init') {
269
+ machine.send({ type: 'FIRST_CHUNK' });
245
270
  pullPromise = pullLoop();
246
271
  }
247
272
 
248
- if (foundSuffix) {
273
+ if (isSuffixStripped(machine.state)) {
249
274
  // Post-suffix: everything is body-level (Suspense chunks).
250
275
  // Emit HTML, then drain any buffered scripts.
251
276
  controller.enqueue(chunk);
@@ -257,7 +282,7 @@ function createFlightInjectionTransform(
257
282
  const text = decoder.decode(chunk, { stream: true });
258
283
  const idx = text.indexOf(suffix);
259
284
  if (idx !== -1) {
260
- foundSuffix = true;
285
+ machine.send({ type: 'SUFFIX_FOUND' });
261
286
  // Emit everything before the suffix (still inside <body>'s
262
287
  // child elements — don't inject scripts here).
263
288
  const before = text.slice(0, idx);
@@ -274,16 +299,27 @@ function createFlightInjectionTransform(
274
299
  }
275
300
  },
276
301
  flush(controller) {
277
- // HTML stream is done drain remaining RSC chunks at body level
302
+ // All HTML chunks have been emitted. Transition to flushing
303
+ // the pull loop will stop yielding between RSC reads since
304
+ // isHtmlDone() now returns true. This eliminates ~36 macrotask
305
+ // yields per request (18 chunks × 2 yields each) that were the
306
+ // primary source of SSR overhead vs Next.js.
307
+ machine.send({ type: 'HTML_DONE' });
308
+
309
+ // Drain remaining RSC chunks at body level
278
310
  const finish = () => {
279
311
  drainPending(controller);
280
312
  // Re-emit the suffix at the very end so HTML is well-formed
281
- if (foundSuffix) {
313
+ if (machine.state.phase === 'done' && machine.state.hadSuffix) {
314
+ controller.enqueue(suffixBytes);
315
+ } else if (machine.state.phase === 'flushing' && machine.state.hadSuffix) {
316
+ // Pull was already done before flush — drainPending didn't
317
+ // transition, but we still need the suffix
282
318
  controller.enqueue(suffixBytes);
283
319
  }
284
320
  };
285
321
 
286
- if (donePulling) {
322
+ if (isPullDone(machine.state)) {
287
323
  finish();
288
324
  return;
289
325
  }
@@ -314,16 +350,17 @@ function createFlightInjectionTransform(
314
350
  */
315
351
  export function injectRscPayload(
316
352
  htmlStream: ReadableStream<Uint8Array>,
317
- rscStream: ReadableStream<Uint8Array> | undefined
353
+ rscStream: ReadableStream<Uint8Array> | undefined,
354
+ renderTimeoutMs?: number
318
355
  ): ReadableStream<Uint8Array> {
319
356
  if (!rscStream) return htmlStream;
320
357
 
321
358
  // Transform RSC binary stream → stream of <script> tags
322
- const rscScriptStream = createInlinedRscStream(rscStream);
359
+ const rscScriptStream = createInlinedRscStream(rscStream, renderTimeoutMs);
323
360
 
324
361
  // Single transform: strip </body></html>, inject RSC scripts at
325
362
  // body level, re-emit suffix at the very end.
326
- return htmlStream.pipeThrough(createFlightInjectionTransform(rscScriptStream));
363
+ return htmlStream.pipeThrough(createFlightInjectionTransform(rscScriptStream, renderTimeoutMs));
327
364
  }
328
365
 
329
366
  /**
@@ -6,19 +6,19 @@ export type { MiddlewareContext } from './types';
6
6
  export type { RouteContext } from './types';
7
7
  export type { Metadata, MetadataRoute } from './types';
8
8
 
9
- // Request Context — ALS-backed headers(), cookies(), and searchParams()
9
+ // Request Context — ALS-backed headers(), cookies(), and rawSearchParams()
10
10
  // Design doc: design/04-authorization.md §"AccessContext does not include cookies or headers"
11
11
  // Design doc: design/23-search-params.md §"Server Integration"
12
12
  export {
13
13
  headers,
14
14
  cookies,
15
- searchParams,
16
- setParsedSearchParams,
15
+ rawSearchParams,
16
+ rawSegmentParams,
17
+ setSegmentParams,
17
18
  runWithRequestContext,
18
19
  setMutableCookieContext,
19
20
  markResponseFlushed,
20
21
  getSetCookieHeaders,
21
- setCookieSecrets,
22
22
  } from './request-context';
23
23
  export type { ReadonlyHeaders, RequestCookies, CookieOptions } from './request-context';
24
24
 
@@ -34,6 +34,7 @@ export {
34
34
  waitUntil,
35
35
  DenySignal,
36
36
  RedirectSignal,
37
+ type RedirectOptions,
37
38
  } from './primitives';
38
39
  export type { RenderErrorDigest, WaitUntilAdapter } from './primitives';
39
40
  export type { JsonSerializable } from './types';
@@ -221,3 +222,7 @@ export type { DevWarningConfig } from './dev-warnings';
221
222
  // Design doc: design/07-routing.md §"route.ts — API Endpoints"
222
223
  export { handleRouteRequest, resolveAllowedMethods } from './route-handler';
223
224
  export type { RouteModule, RouteHandler, HttpMethod } from './route-handler';
225
+
226
+ // Render timeout — design doc: 02-rendering-pipeline.md §"Streaming Constraints"
227
+ export { RenderTimeoutError } from './render-timeout';
228
+ export type { RenderTimeout } from './render-timeout';
@@ -1,16 +1,15 @@
1
1
  /**
2
2
  * Logger — structured logging with environment-aware formatting.
3
3
  *
4
- * timber.js does not ship a logger. Users export any object with
5
- * info/warn/error/debug methods from instrumentation.ts and the framework
6
- * picks it up. Silent if no logger export is present.
4
+ * timber.js ships a DefaultLogger that writes human-readable lines to stderr
5
+ * in production. Users can export a custom logger from instrumentation.ts to
6
+ * replace it with pino, winston, or any TimberLogger-compatible object.
7
7
  *
8
8
  * See design/17-logging.md §"Production Logging"
9
9
  */
10
10
 
11
11
  import { getTraceStore } from './tracing.js';
12
- import { formatSsrError } from './error-formatter.js';
13
- import { isDebug } from './debug.js';
12
+ import { createDefaultLogger } from './default-logger.js';
14
13
 
15
14
  // ─── Logger Interface ─────────────────────────────────────────────────────
16
15
 
@@ -24,21 +23,24 @@ export interface TimberLogger {
24
23
 
25
24
  // ─── Logger Registry ──────────────────────────────────────────────────────
26
25
 
27
- let _logger: TimberLogger | null = null;
26
+ // Initialize with DefaultLogger so production errors are never silent.
27
+ // Replaced when setLogger() is called from instrumentation.ts.
28
+ let _logger: TimberLogger = createDefaultLogger();
28
29
 
29
30
  /**
30
31
  * Set the user-provided logger. Called by the instrumentation loader
31
- * when it finds a `logger` export in instrumentation.ts.
32
+ * when it finds a `logger` export in instrumentation.ts. Replaces
33
+ * the DefaultLogger entirely.
32
34
  */
33
35
  export function setLogger(logger: TimberLogger): void {
34
36
  _logger = logger;
35
37
  }
36
38
 
37
39
  /**
38
- * Get the current logger, or null if none configured.
39
- * Framework-internal used at framework event points to emit structured logs.
40
+ * Get the current logger. Always non-null returns DefaultLogger when
41
+ * no custom logger is configured.
40
42
  */
41
- export function getLogger(): TimberLogger | null {
43
+ export function getLogger(): TimberLogger {
42
44
  return _logger;
43
45
  }
44
46
 
@@ -71,12 +73,12 @@ export function logRequestCompleted(data: {
71
73
  /** Number of concurrent in-flight requests (including this one) at completion time. */
72
74
  concurrency?: number;
73
75
  }): void {
74
- _logger?.info('request completed', withTraceContext(data));
76
+ _logger.info('request completed', withTraceContext(data));
75
77
  }
76
78
 
77
79
  /** Log request received. Level: debug. */
78
80
  export function logRequestReceived(data: { method: string; path: string }): void {
79
- _logger?.debug('request received', withTraceContext(data));
81
+ _logger.debug('request received', withTraceContext(data));
80
82
  }
81
83
 
82
84
  /** Log a slow request warning. Level: warn. */
@@ -88,7 +90,7 @@ export function logSlowRequest(data: {
88
90
  /** Number of concurrent in-flight requests at the time the slow request completed. */
89
91
  concurrency?: number;
90
92
  }): void {
91
- _logger?.warn('slow request exceeded threshold', withTraceContext(data));
93
+ _logger.warn('slow request exceeded threshold', withTraceContext(data));
92
94
  }
93
95
 
94
96
  /** Log middleware short-circuit. Level: debug. */
@@ -97,54 +99,55 @@ export function logMiddlewareShortCircuit(data: {
97
99
  path: string;
98
100
  status: number;
99
101
  }): void {
100
- _logger?.debug('middleware short-circuited', withTraceContext(data));
102
+ _logger.debug('middleware short-circuited', withTraceContext(data));
101
103
  }
102
104
 
103
105
  /** Log unhandled error in middleware phase. Level: error. */
104
106
  export function logMiddlewareError(data: { method: string; path: string; error: unknown }): void {
105
- if (_logger) {
106
- _logger.error('unhandled error in middleware phase', withTraceContext(data));
107
- } else if (isDebug()) {
108
- console.error('[timber] middleware error', data.error);
109
- }
107
+ _logger.error('unhandled error in middleware phase', withTraceContext(data));
110
108
  }
111
109
 
112
110
  /** Log unhandled render-phase error. Level: error. */
113
111
  export function logRenderError(data: { method: string; path: string; error: unknown }): void {
114
- if (_logger) {
115
- _logger.error('unhandled render-phase error', withTraceContext(data));
116
- } else if (isDebug()) {
117
- // No logger configured — fall back to console.error in dev with
118
- // cleaned-up error messages (vendor paths rewritten, hints added).
119
- console.error('[timber] render error:', formatSsrError(data.error));
120
- }
112
+ _logger.error('unhandled render-phase error', withTraceContext(data));
121
113
  }
122
114
 
123
115
  /** Log proxy.ts uncaught error. Level: error. */
124
116
  export function logProxyError(data: { error: unknown }): void {
125
- if (_logger) {
126
- _logger.error('proxy.ts threw uncaught error', withTraceContext(data));
127
- } else if (isDebug()) {
128
- console.error('[timber] proxy error', data.error);
129
- }
117
+ _logger.error('proxy.ts threw uncaught error', withTraceContext(data));
118
+ }
119
+
120
+ /** Log unhandled error in server action. Level: error. */
121
+ export function logActionError(data: { method: string; path: string; error: unknown }): void {
122
+ _logger.error('unhandled server action error', withTraceContext(data));
123
+ }
124
+
125
+ /** Log unhandled error in route handler. Level: error. */
126
+ export function logRouteError(data: { method: string; path: string; error: unknown }): void {
127
+ _logger.error('unhandled route handler error', withTraceContext(data));
128
+ }
129
+
130
+ /** Log SSR streaming error (post-shell). Level: error. */
131
+ export function logStreamingError(data: { error: unknown }): void {
132
+ _logger.error('SSR streaming error (post-shell)', withTraceContext(data));
130
133
  }
131
134
 
132
135
  /** Log waitUntil() adapter missing (once at startup). Level: warn. */
133
136
  export function logWaitUntilUnsupported(): void {
134
- _logger?.warn('adapter does not support waitUntil()');
137
+ _logger.warn('adapter does not support waitUntil()');
135
138
  }
136
139
 
137
140
  /** Log waitUntil() promise rejection. Level: warn. */
138
141
  export function logWaitUntilRejected(data: { error: unknown }): void {
139
- _logger?.warn('waitUntil() promise rejected', withTraceContext(data));
142
+ _logger.warn('waitUntil() promise rejected', withTraceContext(data));
140
143
  }
141
144
 
142
145
  /** Log staleWhileRevalidate refetch failure. Level: warn. */
143
146
  export function logSwrRefetchFailed(data: { cacheKey: string; error: unknown }): void {
144
- _logger?.warn('staleWhileRevalidate refetch failed', withTraceContext(data));
147
+ _logger.warn('staleWhileRevalidate refetch failed', withTraceContext(data));
145
148
  }
146
149
 
147
150
  /** Log cache miss. Level: debug. */
148
151
  export function logCacheMiss(data: { cacheKey: string }): void {
149
- _logger?.debug('timber.cache MISS', withTraceContext(data));
152
+ _logger.debug('timber.cache MISS', withTraceContext(data));
150
153
  }