@timber-js/app 0.2.0-alpha.7 → 0.2.0-alpha.71

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 (500) hide show
  1. package/LICENSE +8 -0
  2. package/dist/_chunks/{als-registry-B7DbZ2hS.js → als-registry-BJARkOcu.js} +1 -1
  3. package/dist/_chunks/als-registry-BJARkOcu.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-CGuYoRHU.js +199 -0
  8. package/dist/_chunks/define-CGuYoRHU.js.map +1 -0
  9. package/dist/_chunks/define-Dz1bqwaS.js +106 -0
  10. package/dist/_chunks/define-Dz1bqwaS.js.map +1 -0
  11. package/dist/_chunks/define-cookie-B5mewxwM.js +93 -0
  12. package/dist/_chunks/define-cookie-B5mewxwM.js.map +1 -0
  13. package/dist/_chunks/error-boundary-D9hzsveV.js +216 -0
  14. package/dist/_chunks/error-boundary-D9hzsveV.js.map +1 -0
  15. package/dist/_chunks/{format-DviM89f0.js → format-Rn922VH2.js} +3 -20
  16. package/dist/_chunks/format-Rn922VH2.js.map +1 -0
  17. package/dist/_chunks/{tracing-Cwn7697K.js → handler-store-BVePM7hp.js} +68 -3
  18. package/dist/_chunks/handler-store-BVePM7hp.js.map +1 -0
  19. package/dist/_chunks/{interception-BOoWmLUA.js → interception-CEdHHviP.js} +171 -97
  20. package/dist/_chunks/interception-CEdHHviP.js.map +1 -0
  21. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → metadata-routes-DS3eKNmf.js} +1 -1
  22. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js.map → metadata-routes-DS3eKNmf.js.map} +1 -1
  23. package/dist/_chunks/{request-context-DIkVh_jG.js → request-context-CywiO4jV.js} +181 -69
  24. package/dist/_chunks/request-context-CywiO4jV.js.map +1 -0
  25. package/dist/_chunks/schema-bridge-C4SwjCQD.js +86 -0
  26. package/dist/_chunks/schema-bridge-C4SwjCQD.js.map +1 -0
  27. package/dist/_chunks/segment-classify-BDNn6EzD.js +65 -0
  28. package/dist/_chunks/segment-classify-BDNn6EzD.js.map +1 -0
  29. package/dist/_chunks/segment-context-hzuJ048X.js +72 -0
  30. package/dist/_chunks/segment-context-hzuJ048X.js.map +1 -0
  31. package/dist/_chunks/stale-reload-BLUC_Pl_.js +64 -0
  32. package/dist/_chunks/stale-reload-BLUC_Pl_.js.map +1 -0
  33. package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-DAhgj8Gx.js} +1 -1
  34. package/dist/_chunks/use-query-states-DAhgj8Gx.js.map +1 -0
  35. package/dist/_chunks/wrappers-LZbghvn0.js +63 -0
  36. package/dist/_chunks/wrappers-LZbghvn0.js.map +1 -0
  37. package/dist/adapters/cloudflare-dev.d.ts +109 -0
  38. package/dist/adapters/cloudflare-dev.d.ts.map +1 -0
  39. package/dist/adapters/cloudflare-dev.js +73 -0
  40. package/dist/adapters/cloudflare-dev.js.map +1 -0
  41. package/dist/adapters/cloudflare.d.ts +148 -12
  42. package/dist/adapters/cloudflare.d.ts.map +1 -1
  43. package/dist/adapters/cloudflare.js +135 -11
  44. package/dist/adapters/cloudflare.js.map +1 -1
  45. package/dist/adapters/compress-module.d.ts.map +1 -1
  46. package/dist/adapters/nitro.d.ts +17 -1
  47. package/dist/adapters/nitro.d.ts.map +1 -1
  48. package/dist/adapters/nitro.js +56 -13
  49. package/dist/adapters/nitro.js.map +1 -1
  50. package/dist/cache/cache-api.d.ts +24 -0
  51. package/dist/cache/cache-api.d.ts.map +1 -0
  52. package/dist/cache/fast-hash.d.ts +22 -0
  53. package/dist/cache/fast-hash.d.ts.map +1 -0
  54. package/dist/cache/handler-store.d.ts +31 -0
  55. package/dist/cache/handler-store.d.ts.map +1 -0
  56. package/dist/cache/index.d.ts +7 -5
  57. package/dist/cache/index.d.ts.map +1 -1
  58. package/dist/cache/index.js +111 -73
  59. package/dist/cache/index.js.map +1 -1
  60. package/dist/cache/singleflight.d.ts +18 -1
  61. package/dist/cache/singleflight.d.ts.map +1 -1
  62. package/dist/cache/timber-cache.d.ts +1 -1
  63. package/dist/cache/timber-cache.d.ts.map +1 -1
  64. package/dist/client/error-boundary.d.ts +12 -5
  65. package/dist/client/error-boundary.d.ts.map +1 -1
  66. package/dist/client/error-boundary.js +1 -125
  67. package/dist/client/error-reconstituter.d.ts +54 -0
  68. package/dist/client/error-reconstituter.d.ts.map +1 -0
  69. package/dist/client/form.d.ts +2 -2
  70. package/dist/client/form.d.ts.map +1 -1
  71. package/dist/client/history.d.ts +19 -4
  72. package/dist/client/history.d.ts.map +1 -1
  73. package/dist/client/index.d.ts +6 -5
  74. package/dist/client/index.d.ts.map +1 -1
  75. package/dist/client/index.js +537 -166
  76. package/dist/client/index.js.map +1 -1
  77. package/dist/client/link-pending-store.d.ts +78 -0
  78. package/dist/client/link-pending-store.d.ts.map +1 -0
  79. package/dist/client/link.d.ts +90 -32
  80. package/dist/client/link.d.ts.map +1 -1
  81. package/dist/client/nav-link-store.d.ts +36 -0
  82. package/dist/client/nav-link-store.d.ts.map +1 -0
  83. package/dist/client/navigation-api-types.d.ts +90 -0
  84. package/dist/client/navigation-api-types.d.ts.map +1 -0
  85. package/dist/client/navigation-api.d.ts +115 -0
  86. package/dist/client/navigation-api.d.ts.map +1 -0
  87. package/dist/client/navigation-context.d.ts +13 -2
  88. package/dist/client/navigation-context.d.ts.map +1 -1
  89. package/dist/client/{transition-root.d.ts → navigation-root.d.ts} +42 -8
  90. package/dist/client/navigation-root.d.ts.map +1 -0
  91. package/dist/client/nuqs-adapter.d.ts.map +1 -1
  92. package/dist/client/router.d.ts +70 -4
  93. package/dist/client/router.d.ts.map +1 -1
  94. package/dist/client/rsc-fetch.d.ts +38 -3
  95. package/dist/client/rsc-fetch.d.ts.map +1 -1
  96. package/dist/client/segment-cache.d.ts +1 -1
  97. package/dist/client/segment-cache.d.ts.map +1 -1
  98. package/dist/client/segment-context.d.ts +1 -1
  99. package/dist/client/segment-context.d.ts.map +1 -1
  100. package/dist/client/segment-merger.d.ts.map +1 -1
  101. package/dist/client/segment-outlet.d.ts +63 -0
  102. package/dist/client/segment-outlet.d.ts.map +1 -0
  103. package/dist/client/ssr-data.d.ts +13 -4
  104. package/dist/client/ssr-data.d.ts.map +1 -1
  105. package/dist/client/stale-reload.d.ts +15 -0
  106. package/dist/client/stale-reload.d.ts.map +1 -1
  107. package/dist/client/top-loader.d.ts +3 -3
  108. package/dist/client/top-loader.d.ts.map +1 -1
  109. package/dist/client/use-params.d.ts +6 -4
  110. package/dist/client/use-params.d.ts.map +1 -1
  111. package/dist/client/use-query-states.d.ts +1 -1
  112. package/dist/client/use-query-states.d.ts.map +1 -1
  113. package/dist/codec.d.ts +23 -0
  114. package/dist/codec.d.ts.map +1 -0
  115. package/dist/codec.js +2 -0
  116. package/dist/cookies/define-cookie.d.ts +35 -14
  117. package/dist/cookies/define-cookie.d.ts.map +1 -1
  118. package/dist/cookies/index.d.ts +2 -0
  119. package/dist/cookies/index.d.ts.map +1 -1
  120. package/dist/cookies/index.js +3 -84
  121. package/dist/fonts/css.d.ts +1 -0
  122. package/dist/fonts/css.d.ts.map +1 -1
  123. package/dist/index.d.ts +154 -38
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +12092 -11916
  126. package/dist/index.js.map +1 -1
  127. package/dist/plugins/adapter-build.d.ts +1 -1
  128. package/dist/plugins/adapter-build.d.ts.map +1 -1
  129. package/dist/plugins/build-manifest.d.ts +2 -2
  130. package/dist/plugins/build-manifest.d.ts.map +1 -1
  131. package/dist/plugins/build-report.d.ts +3 -3
  132. package/dist/plugins/build-report.d.ts.map +1 -1
  133. package/dist/plugins/client-chunks.d.ts +32 -0
  134. package/dist/plugins/client-chunks.d.ts.map +1 -0
  135. package/dist/plugins/content.d.ts +1 -1
  136. package/dist/plugins/content.d.ts.map +1 -1
  137. package/dist/plugins/dev-browser-logs.d.ts +84 -0
  138. package/dist/plugins/dev-browser-logs.d.ts.map +1 -0
  139. package/dist/plugins/dev-error-overlay.d.ts +26 -1
  140. package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
  141. package/dist/plugins/dev-logs.d.ts +1 -1
  142. package/dist/plugins/dev-logs.d.ts.map +1 -1
  143. package/dist/plugins/dev-server.d.ts +1 -1
  144. package/dist/plugins/dev-server.d.ts.map +1 -1
  145. package/dist/plugins/entries.d.ts +1 -1
  146. package/dist/plugins/entries.d.ts.map +1 -1
  147. package/dist/plugins/fonts.d.ts +19 -5
  148. package/dist/plugins/fonts.d.ts.map +1 -1
  149. package/dist/plugins/mdx.d.ts +1 -1
  150. package/dist/plugins/mdx.d.ts.map +1 -1
  151. package/dist/plugins/routing.d.ts +1 -1
  152. package/dist/plugins/routing.d.ts.map +1 -1
  153. package/dist/plugins/server-bundle.d.ts.map +1 -1
  154. package/dist/plugins/shims.d.ts +6 -5
  155. package/dist/plugins/shims.d.ts.map +1 -1
  156. package/dist/plugins/static-build.d.ts +1 -1
  157. package/dist/plugins/static-build.d.ts.map +1 -1
  158. package/dist/routing/codegen.d.ts +2 -2
  159. package/dist/routing/codegen.d.ts.map +1 -1
  160. package/dist/routing/index.d.ts +2 -0
  161. package/dist/routing/index.d.ts.map +1 -1
  162. package/dist/routing/index.js +3 -2
  163. package/dist/routing/scanner.d.ts.map +1 -1
  164. package/dist/routing/segment-classify.d.ts +46 -0
  165. package/dist/routing/segment-classify.d.ts.map +1 -0
  166. package/dist/routing/status-file-lint.d.ts +2 -1
  167. package/dist/routing/status-file-lint.d.ts.map +1 -1
  168. package/dist/routing/types.d.ts +16 -4
  169. package/dist/routing/types.d.ts.map +1 -1
  170. package/dist/rsc-runtime/rsc.d.ts +1 -1
  171. package/dist/rsc-runtime/rsc.d.ts.map +1 -1
  172. package/dist/rsc-runtime/ssr.d.ts +12 -0
  173. package/dist/rsc-runtime/ssr.d.ts.map +1 -1
  174. package/dist/schema-bridge.d.ts +76 -0
  175. package/dist/schema-bridge.d.ts.map +1 -0
  176. package/dist/search-params/define.d.ts +139 -0
  177. package/dist/search-params/define.d.ts.map +1 -0
  178. package/dist/search-params/index.d.ts +4 -6
  179. package/dist/search-params/index.d.ts.map +1 -1
  180. package/dist/search-params/index.js +4 -474
  181. package/dist/search-params/registry.d.ts +1 -1
  182. package/dist/search-params/wrappers.d.ts +53 -0
  183. package/dist/search-params/wrappers.d.ts.map +1 -0
  184. package/dist/segment-params/define.d.ts +78 -0
  185. package/dist/segment-params/define.d.ts.map +1 -0
  186. package/dist/segment-params/index.d.ts +7 -0
  187. package/dist/segment-params/index.d.ts.map +1 -0
  188. package/dist/segment-params/index.js +4 -0
  189. package/dist/server/access-gate.d.ts +4 -0
  190. package/dist/server/access-gate.d.ts.map +1 -1
  191. package/dist/server/action-client.d.ts +12 -1
  192. package/dist/server/action-client.d.ts.map +1 -1
  193. package/dist/server/action-encryption.d.ts +76 -0
  194. package/dist/server/action-encryption.d.ts.map +1 -0
  195. package/dist/server/action-handler.d.ts.map +1 -1
  196. package/dist/server/actions.d.ts +3 -6
  197. package/dist/server/actions.d.ts.map +1 -1
  198. package/dist/server/als-registry.d.ts +32 -4
  199. package/dist/server/als-registry.d.ts.map +1 -1
  200. package/dist/server/build-manifest.d.ts +2 -2
  201. package/dist/server/build-manifest.d.ts.map +1 -1
  202. package/dist/server/debug.d.ts +1 -1
  203. package/dist/server/default-logger.d.ts +22 -0
  204. package/dist/server/default-logger.d.ts.map +1 -0
  205. package/dist/server/deny-page-resolver.d.ts +52 -0
  206. package/dist/server/deny-page-resolver.d.ts.map +1 -0
  207. package/dist/server/deny-renderer.d.ts.map +1 -1
  208. package/dist/server/dev-warnings.d.ts +0 -14
  209. package/dist/server/dev-warnings.d.ts.map +1 -1
  210. package/dist/server/early-hints.d.ts +13 -5
  211. package/dist/server/early-hints.d.ts.map +1 -1
  212. package/dist/server/error-boundary-wrapper.d.ts +7 -1
  213. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  214. package/dist/server/fallback-error.d.ts +4 -3
  215. package/dist/server/fallback-error.d.ts.map +1 -1
  216. package/dist/server/flight-injection-state.d.ts +66 -0
  217. package/dist/server/flight-injection-state.d.ts.map +1 -0
  218. package/dist/server/flight-scripts.d.ts +42 -0
  219. package/dist/server/flight-scripts.d.ts.map +1 -0
  220. package/dist/server/flush.d.ts.map +1 -1
  221. package/dist/server/form-data.d.ts +29 -0
  222. package/dist/server/form-data.d.ts.map +1 -1
  223. package/dist/server/html-injectors.d.ts +51 -11
  224. package/dist/server/html-injectors.d.ts.map +1 -1
  225. package/dist/server/index.d.ts +5 -3
  226. package/dist/server/index.d.ts.map +1 -1
  227. package/dist/server/index.js +2176 -1663
  228. package/dist/server/index.js.map +1 -1
  229. package/dist/server/logger.d.ts +25 -7
  230. package/dist/server/logger.d.ts.map +1 -1
  231. package/dist/server/middleware-runner.d.ts +19 -4
  232. package/dist/server/middleware-runner.d.ts.map +1 -1
  233. package/dist/server/node-stream-transforms.d.ts +113 -0
  234. package/dist/server/node-stream-transforms.d.ts.map +1 -0
  235. package/dist/server/page-deny-boundary.d.ts +31 -0
  236. package/dist/server/page-deny-boundary.d.ts.map +1 -0
  237. package/dist/server/pipeline-interception.d.ts +1 -1
  238. package/dist/server/pipeline-interception.d.ts.map +1 -1
  239. package/dist/server/pipeline-metadata.d.ts +6 -0
  240. package/dist/server/pipeline-metadata.d.ts.map +1 -1
  241. package/dist/server/pipeline.d.ts +32 -10
  242. package/dist/server/pipeline.d.ts.map +1 -1
  243. package/dist/server/primitives.d.ts +30 -3
  244. package/dist/server/primitives.d.ts.map +1 -1
  245. package/dist/server/render-timeout.d.ts +51 -0
  246. package/dist/server/render-timeout.d.ts.map +1 -0
  247. package/dist/server/request-context.d.ts +76 -37
  248. package/dist/server/request-context.d.ts.map +1 -1
  249. package/dist/server/route-element-builder.d.ts +27 -1
  250. package/dist/server/route-element-builder.d.ts.map +1 -1
  251. package/dist/server/route-handler.d.ts.map +1 -1
  252. package/dist/server/route-matcher.d.ts +9 -2
  253. package/dist/server/route-matcher.d.ts.map +1 -1
  254. package/dist/server/rsc-entry/api-handler.d.ts +2 -2
  255. package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
  256. package/dist/server/rsc-entry/error-renderer.d.ts +26 -13
  257. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  258. package/dist/server/rsc-entry/helpers.d.ts +48 -5
  259. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  260. package/dist/server/rsc-entry/index.d.ts +8 -3
  261. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  262. package/dist/server/rsc-entry/rsc-payload.d.ts +3 -3
  263. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  264. package/dist/server/rsc-entry/rsc-stream.d.ts +10 -1
  265. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  266. package/dist/server/rsc-entry/ssr-bridge.d.ts +1 -1
  267. package/dist/server/rsc-entry/ssr-bridge.d.ts.map +1 -1
  268. package/dist/server/rsc-entry/ssr-renderer.d.ts +19 -4
  269. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  270. package/dist/server/safe-load.d.ts +46 -0
  271. package/dist/server/safe-load.d.ts.map +1 -0
  272. package/dist/server/sitemap-generator.d.ts +129 -0
  273. package/dist/server/sitemap-generator.d.ts.map +1 -0
  274. package/dist/server/sitemap-handler.d.ts +22 -0
  275. package/dist/server/sitemap-handler.d.ts.map +1 -0
  276. package/dist/server/slot-resolver.d.ts +1 -1
  277. package/dist/server/slot-resolver.d.ts.map +1 -1
  278. package/dist/server/ssr-entry.d.ts +22 -0
  279. package/dist/server/ssr-entry.d.ts.map +1 -1
  280. package/dist/server/ssr-render.d.ts +39 -21
  281. package/dist/server/ssr-render.d.ts.map +1 -1
  282. package/dist/server/ssr-wrappers.d.ts +50 -0
  283. package/dist/server/ssr-wrappers.d.ts.map +1 -0
  284. package/dist/server/status-code-resolver.d.ts +1 -1
  285. package/dist/server/status-code-resolver.d.ts.map +1 -1
  286. package/dist/server/stream-utils.d.ts +36 -0
  287. package/dist/server/stream-utils.d.ts.map +1 -0
  288. package/dist/server/tracing.d.ts +10 -0
  289. package/dist/server/tracing.d.ts.map +1 -1
  290. package/dist/server/tree-builder.d.ts +22 -19
  291. package/dist/server/tree-builder.d.ts.map +1 -1
  292. package/dist/server/types.d.ts +1 -4
  293. package/dist/server/types.d.ts.map +1 -1
  294. package/dist/server/version-skew.d.ts +61 -0
  295. package/dist/server/version-skew.d.ts.map +1 -0
  296. package/dist/server/waituntil-bridge.d.ts.map +1 -1
  297. package/dist/shared/merge-search-params.d.ts +22 -0
  298. package/dist/shared/merge-search-params.d.ts.map +1 -0
  299. package/dist/shims/font-google.d.ts +1 -1
  300. package/dist/shims/font-google.d.ts.map +1 -1
  301. package/dist/shims/font-google.js +42 -0
  302. package/dist/shims/font-google.js.map +1 -0
  303. package/dist/shims/font-local.d.ts +26 -0
  304. package/dist/shims/font-local.d.ts.map +1 -0
  305. package/dist/shims/font-local.js +20 -0
  306. package/dist/shims/font-local.js.map +1 -0
  307. package/dist/shims/navigation-client.d.ts +1 -1
  308. package/dist/shims/navigation-client.d.ts.map +1 -1
  309. package/dist/shims/navigation.d.ts +1 -1
  310. package/dist/shims/navigation.d.ts.map +1 -1
  311. package/dist/utils/directive-parser.d.ts +5 -2
  312. package/dist/utils/directive-parser.d.ts.map +1 -1
  313. package/dist/utils/state-machine.d.ts +80 -0
  314. package/dist/utils/state-machine.d.ts.map +1 -0
  315. package/package.json +37 -17
  316. package/src/adapters/cloudflare-dev.ts +177 -0
  317. package/src/adapters/cloudflare.ts +342 -28
  318. package/src/adapters/compress-module.ts +24 -4
  319. package/src/adapters/nitro.ts +58 -9
  320. package/src/adapters/wrangler.d.ts +7 -0
  321. package/src/cache/cache-api.ts +38 -0
  322. package/src/cache/fast-hash.ts +34 -0
  323. package/src/cache/handler-store.ts +68 -0
  324. package/src/cache/index.ts +9 -5
  325. package/src/cache/singleflight.ts +62 -4
  326. package/src/cache/timber-cache.ts +40 -29
  327. package/src/cli.ts +0 -0
  328. package/src/client/browser-entry.ts +314 -142
  329. package/src/client/error-boundary.tsx +48 -16
  330. package/src/client/error-reconstituter.tsx +65 -0
  331. package/src/client/form.tsx +2 -2
  332. package/src/client/history.ts +26 -4
  333. package/src/client/index.ts +13 -4
  334. package/src/client/link-pending-store.ts +136 -0
  335. package/src/client/link.tsx +346 -105
  336. package/src/client/nav-link-store.ts +47 -0
  337. package/src/client/navigation-api-types.ts +112 -0
  338. package/src/client/navigation-api.ts +332 -0
  339. package/src/client/navigation-context.ts +27 -6
  340. package/src/client/navigation-root.tsx +346 -0
  341. package/src/client/nuqs-adapter.tsx +16 -3
  342. package/src/client/router.ts +302 -77
  343. package/src/client/rsc-fetch.ts +93 -5
  344. package/src/client/segment-cache.ts +1 -1
  345. package/src/client/segment-context.ts +6 -1
  346. package/src/client/segment-merger.ts +2 -8
  347. package/src/client/segment-outlet.tsx +86 -0
  348. package/src/client/ssr-data.ts +13 -5
  349. package/src/client/stale-reload.ts +73 -6
  350. package/src/client/top-loader.tsx +22 -13
  351. package/src/client/use-navigation-pending.ts +1 -1
  352. package/src/client/use-params.ts +7 -5
  353. package/src/client/use-query-states.ts +2 -2
  354. package/src/codec.ts +34 -0
  355. package/src/cookies/define-cookie.ts +72 -21
  356. package/src/cookies/index.ts +7 -0
  357. package/src/fonts/css.ts +2 -1
  358. package/src/index.ts +328 -92
  359. package/src/plugins/adapter-build.ts +8 -2
  360. package/src/plugins/build-manifest.ts +13 -2
  361. package/src/plugins/build-report.ts +3 -3
  362. package/src/plugins/client-chunks.ts +65 -0
  363. package/src/plugins/content.ts +1 -1
  364. package/src/plugins/dev-browser-logs.ts +288 -0
  365. package/src/plugins/dev-error-overlay.ts +70 -1
  366. package/src/plugins/dev-logs.ts +1 -1
  367. package/src/plugins/dev-server.ts +55 -9
  368. package/src/plugins/entries.ts +70 -9
  369. package/src/plugins/fonts.ts +167 -61
  370. package/src/plugins/mdx.ts +1 -1
  371. package/src/plugins/routing.ts +57 -17
  372. package/src/plugins/server-action-exports.ts +1 -1
  373. package/src/plugins/server-bundle.ts +32 -1
  374. package/src/plugins/shims.ts +76 -33
  375. package/src/plugins/static-build.ts +10 -6
  376. package/src/routing/codegen.ts +165 -105
  377. package/src/routing/index.ts +2 -0
  378. package/src/routing/scanner.ts +93 -23
  379. package/src/routing/segment-classify.ts +89 -0
  380. package/src/routing/status-file-lint.ts +3 -2
  381. package/src/routing/types.ts +17 -4
  382. package/src/rsc-runtime/rsc.ts +2 -0
  383. package/src/rsc-runtime/ssr.ts +50 -0
  384. package/src/rsc-runtime/vendor-types.d.ts +7 -0
  385. package/src/{search-params/codecs.ts → schema-bridge.ts} +57 -20
  386. package/src/search-params/define.ts +482 -0
  387. package/src/search-params/index.ts +13 -19
  388. package/src/search-params/registry.ts +1 -1
  389. package/src/search-params/wrappers.ts +85 -0
  390. package/src/segment-params/define.ts +279 -0
  391. package/src/segment-params/index.ts +28 -0
  392. package/src/server/access-gate.tsx +70 -29
  393. package/src/server/action-client.ts +28 -3
  394. package/src/server/action-encryption.ts +144 -0
  395. package/src/server/action-handler.ts +20 -3
  396. package/src/server/actions.ts +10 -9
  397. package/src/server/als-registry.ts +32 -4
  398. package/src/server/build-manifest.ts +10 -4
  399. package/src/server/compress.ts +25 -7
  400. package/src/server/debug.ts +1 -1
  401. package/src/server/default-logger.ts +99 -0
  402. package/src/server/deny-page-resolver.ts +154 -0
  403. package/src/server/deny-renderer.ts +24 -38
  404. package/src/server/dev-warnings.ts +2 -28
  405. package/src/server/early-hints.ts +36 -15
  406. package/src/server/error-boundary-wrapper.ts +74 -22
  407. package/src/server/fallback-error.ts +31 -15
  408. package/src/server/flight-injection-state.ts +113 -0
  409. package/src/server/flight-scripts.ts +62 -0
  410. package/src/server/flush.ts +2 -1
  411. package/src/server/form-data.ts +76 -0
  412. package/src/server/html-injectors.ts +277 -117
  413. package/src/server/index.ts +9 -5
  414. package/src/server/logger.ts +44 -36
  415. package/src/server/middleware-runner.ts +31 -4
  416. package/src/server/node-stream-transforms.ts +509 -0
  417. package/src/server/page-deny-boundary.tsx +56 -0
  418. package/src/server/pipeline-interception.ts +17 -16
  419. package/src/server/pipeline-metadata.ts +13 -0
  420. package/src/server/pipeline.ts +195 -51
  421. package/src/server/primitives.ts +47 -5
  422. package/src/server/render-timeout.ts +108 -0
  423. package/src/server/request-context.ts +240 -117
  424. package/src/server/route-element-builder.ts +284 -197
  425. package/src/server/route-handler.ts +24 -4
  426. package/src/server/route-matcher.ts +24 -20
  427. package/src/server/rsc-entry/api-handler.ts +15 -16
  428. package/src/server/rsc-entry/error-renderer.ts +300 -89
  429. package/src/server/rsc-entry/helpers.ts +134 -5
  430. package/src/server/rsc-entry/index.ts +202 -113
  431. package/src/server/rsc-entry/rsc-payload.ts +100 -21
  432. package/src/server/rsc-entry/rsc-stream.ts +74 -18
  433. package/src/server/rsc-entry/ssr-bridge.ts +14 -5
  434. package/src/server/rsc-entry/ssr-renderer.ts +173 -40
  435. package/src/server/safe-load.ts +60 -0
  436. package/src/server/sitemap-generator.ts +338 -0
  437. package/src/server/sitemap-handler.ts +126 -0
  438. package/src/server/slot-resolver.ts +243 -228
  439. package/src/server/ssr-entry.ts +211 -32
  440. package/src/server/ssr-render.ts +289 -67
  441. package/src/server/ssr-wrappers.tsx +139 -0
  442. package/src/server/status-code-resolver.ts +1 -1
  443. package/src/server/stream-utils.ts +213 -0
  444. package/src/server/tracing.ts +37 -3
  445. package/src/server/tree-builder.ts +92 -58
  446. package/src/server/types.ts +3 -6
  447. package/src/server/version-skew.ts +104 -0
  448. package/src/server/waituntil-bridge.ts +4 -1
  449. package/src/shared/merge-search-params.ts +55 -0
  450. package/src/shims/font-google.ts +1 -1
  451. package/src/shims/font-local.ts +34 -0
  452. package/src/shims/navigation-client.ts +1 -1
  453. package/src/shims/navigation.ts +2 -1
  454. package/src/utils/directive-parser.ts +5 -2
  455. package/src/utils/state-machine.ts +111 -0
  456. package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
  457. package/dist/_chunks/debug-gwlJkDuf.js.map +0 -1
  458. package/dist/_chunks/format-DviM89f0.js.map +0 -1
  459. package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
  460. package/dist/_chunks/request-context-DIkVh_jG.js.map +0 -1
  461. package/dist/_chunks/ssr-data-MjmprTmO.js +0 -88
  462. package/dist/_chunks/ssr-data-MjmprTmO.js.map +0 -1
  463. package/dist/_chunks/tracing-Cwn7697K.js.map +0 -1
  464. package/dist/_chunks/use-cookie-DX-l1_5E.js +0 -91
  465. package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
  466. package/dist/_chunks/use-query-states-D5KaffOK.js.map +0 -1
  467. package/dist/cache/register-cached-function.d.ts +0 -17
  468. package/dist/cache/register-cached-function.d.ts.map +0 -1
  469. package/dist/client/error-boundary.js.map +0 -1
  470. package/dist/client/link-status-provider.d.ts +0 -11
  471. package/dist/client/link-status-provider.d.ts.map +0 -1
  472. package/dist/client/transition-root.d.ts.map +0 -1
  473. package/dist/cookies/index.js.map +0 -1
  474. package/dist/plugins/cache-transform.d.ts +0 -36
  475. package/dist/plugins/cache-transform.d.ts.map +0 -1
  476. package/dist/plugins/dynamic-transform.d.ts +0 -72
  477. package/dist/plugins/dynamic-transform.d.ts.map +0 -1
  478. package/dist/search-params/analyze.d.ts +0 -54
  479. package/dist/search-params/analyze.d.ts.map +0 -1
  480. package/dist/search-params/builtin-codecs.d.ts +0 -105
  481. package/dist/search-params/builtin-codecs.d.ts.map +0 -1
  482. package/dist/search-params/codecs.d.ts +0 -53
  483. package/dist/search-params/codecs.d.ts.map +0 -1
  484. package/dist/search-params/create.d.ts +0 -106
  485. package/dist/search-params/create.d.ts.map +0 -1
  486. package/dist/search-params/index.js.map +0 -1
  487. package/dist/server/prerender.d.ts +0 -77
  488. package/dist/server/prerender.d.ts.map +0 -1
  489. package/dist/server/response-cache.d.ts +0 -53
  490. package/dist/server/response-cache.d.ts.map +0 -1
  491. package/src/cache/register-cached-function.ts +0 -99
  492. package/src/client/link-status-provider.tsx +0 -30
  493. package/src/client/transition-root.tsx +0 -160
  494. package/src/plugins/cache-transform.ts +0 -199
  495. package/src/plugins/dynamic-transform.ts +0 -161
  496. package/src/search-params/analyze.ts +0 -192
  497. package/src/search-params/builtin-codecs.ts +0 -228
  498. package/src/search-params/create.ts +0 -321
  499. package/src/server/prerender.ts +0 -139
  500. package/src/server/response-cache.ts +0 -277
@@ -25,7 +25,6 @@ export const WarningId = {
25
25
  REDIRECT_IN_SUSPENSE: 'REDIRECT_IN_SUSPENSE',
26
26
  REDIRECT_IN_ACCESS: 'REDIRECT_IN_ACCESS',
27
27
  STATIC_REQUEST_API: 'STATIC_REQUEST_API',
28
- CACHE_REQUEST_PROPS: 'CACHE_REQUEST_PROPS',
29
28
  SLOW_SLOT_NO_SUSPENSE: 'SLOW_SLOT_NO_SUSPENSE',
30
29
  } as const;
31
30
 
@@ -197,33 +196,8 @@ export function warnStaticRequestApi(api: 'cookies' | 'headers', file: string):
197
196
  );
198
197
  }
199
198
 
200
- /**
201
- * Warn when a "use cache" component receives request-specific props.
202
- *
203
- * Cached components should not depend on per-request data — a userId or
204
- * sessionId in the props means the cache will either be ineffective
205
- * (key per user) or dangerous (serve one user's data to another).
206
- *
207
- * @param componentName - Name of the cached component
208
- * @param propName - Name of the suspicious prop
209
- * @param file - Relative path to the component file
210
- * @param line - Line number
211
- */
212
- export function warnCacheRequestProps(
213
- componentName: string,
214
- propName: string,
215
- file: string,
216
- line?: number
217
- ): void {
218
- const location = line ? `${file}:${line}` : file;
219
- emitOnce(
220
- WarningId.CACHE_REQUEST_PROPS,
221
- `${componentName}:${propName}:${location}`,
222
- 'warn',
223
- `Cached component ${componentName} receives prop "${propName}" which appears request-specific. ` +
224
- 'Cached components should not depend on per-request data.'
225
- );
226
- }
199
+ // NOTE: warnCacheRequestProps removed — 'use cache' directive is a future feature.
200
+ // See design/06-caching.md.
227
201
 
228
202
  /**
229
203
  * Warn when a parallel slot resolves slowly without a <Suspense> wrapper.
@@ -58,15 +58,31 @@ export interface EarlyHint {
58
58
  /**
59
59
  * Format a single EarlyHint as a Link header value.
60
60
  *
61
+ * Attribute order: `as` before `rel` to match Cloudflare CDN's cached
62
+ * Early Hints format. Cloudflare caches Link headers from 200 responses
63
+ * and re-emits them as 103 Early Hints on subsequent requests. If our
64
+ * attribute order differs from Cloudflare's cached copy, the browser
65
+ * sees two preload headers for the same URL (different attribute order)
66
+ * and warns "Preload was ignored." Matching the order ensures the
67
+ * browser deduplicates them correctly.
68
+ *
61
69
  * Examples:
62
- * `</styles/root.css>; rel=preload; as=style`
63
- * `</fonts/inter.woff2>; rel=preload; as=font; crossorigin=anonymous`
70
+ * `</styles/root.css>; as=style; rel=preload`
71
+ * `</fonts/inter.woff2>; as=font; rel=preload; crossorigin=anonymous`
64
72
  * `</_timber/client.js>; rel=modulepreload`
65
73
  * `<https://fonts.googleapis.com>; rel=preconnect`
66
74
  */
67
75
  export function formatLinkHeader(hint: EarlyHint): string {
76
+ // For preload hints, emit `as` before `rel` to match Cloudflare's
77
+ // cached header format and avoid duplicate preload warnings.
78
+ if (hint.as !== undefined) {
79
+ let value = `<${hint.href}>; as=${hint.as}; rel=${hint.rel}`;
80
+ if (hint.crossOrigin !== undefined) value += `; crossorigin=${hint.crossOrigin}`;
81
+ if (hint.fetchPriority !== undefined) value += `; fetchpriority=${hint.fetchPriority}`;
82
+ return value;
83
+ }
84
+ // For modulepreload / preconnect (no `as`), emit rel first.
68
85
  let value = `<${hint.href}>; rel=${hint.rel}`;
69
- if (hint.as !== undefined) value += `; as=${hint.as}`;
70
86
  if (hint.crossOrigin !== undefined) value += `; crossorigin=${hint.crossOrigin}`;
71
87
  if (hint.fetchPriority !== undefined) value += `; fetchpriority=${hint.fetchPriority}`;
72
88
  return value;
@@ -84,8 +100,8 @@ export interface EarlyHintOptions {
84
100
  * Collect all Link header strings for a matched route's segment chain.
85
101
  *
86
102
  * Walks the build manifest to emit hints for:
87
- * - CSS stylesheets (rel=preload; as=style)
88
- * - Font assets (rel=preload; as=font; crossorigin)
103
+ * - CSS stylesheets (as=style; rel=preload)
104
+ * - Font assets (as=font; rel=preload; crossorigin)
89
105
  * - JS modulepreload hints (rel=modulepreload) — unless skipJs is set
90
106
  *
91
107
  * Also emits global CSS from the `_global` manifest key. Route files
@@ -94,7 +110,7 @@ export interface EarlyHintOptions {
94
110
  * key contains all CSS assets from the client build — fine for early
95
111
  * hints since they're just prefetch signals.
96
112
  *
97
- * Returns formatted Link header strings, deduplicated, root → leaf order.
113
+ * Returns formatted Link header strings, deduplicated by URL, root → leaf order.
98
114
  * Returns an empty array in dev mode (manifest is empty).
99
115
  */
100
116
  export function collectEarlyHintHeaders(
@@ -103,30 +119,35 @@ export function collectEarlyHintHeaders(
103
119
  options?: EarlyHintOptions
104
120
  ): string[] {
105
121
  const result: string[] = [];
106
- const seen = new Set<string>();
122
+ // Dedup by URL (href), not by full formatted header string.
123
+ // Different code paths can produce the same URL with different attribute
124
+ // ordering, which would bypass a full-string dedup and produce duplicate
125
+ // Link headers that trigger browser "preload was ignored" warnings.
126
+ const seenUrls = new Set<string>();
107
127
 
108
- const add = (header: string) => {
109
- if (!seen.has(header)) {
110
- seen.add(header);
128
+ const add = (url: string, header: string) => {
129
+ if (!seenUrls.has(url)) {
130
+ seenUrls.add(url);
111
131
  result.push(header);
112
132
  }
113
133
  };
114
134
 
115
- // Per-route CSS — rel=preload; as=style
135
+ // Per-route CSS — as=style; rel=preload
116
136
  for (const url of collectRouteCss(segments, manifest)) {
117
- add(formatLinkHeader({ href: url, rel: 'preload', as: 'style' }));
137
+ add(url, formatLinkHeader({ href: url, rel: 'preload', as: 'style' }));
118
138
  }
119
139
 
120
140
  // Global CSS — all CSS assets from the client bundle.
121
141
  // Covers CSS that the RSC plugin injects via data-rsc-css-href,
122
142
  // which isn't keyed to route segments in our manifest.
123
143
  for (const url of manifest.css['_global'] ?? []) {
124
- add(formatLinkHeader({ href: url, rel: 'preload', as: 'style' }));
144
+ add(url, formatLinkHeader({ href: url, rel: 'preload', as: 'style' }));
125
145
  }
126
146
 
127
- // Fonts — rel=preload; as=font; crossorigin (crossorigin required per spec)
147
+ // Fonts — as=font; rel=preload; crossorigin (crossorigin required per spec)
128
148
  for (const font of collectRouteFonts(segments, manifest)) {
129
149
  add(
150
+ font.href,
130
151
  formatLinkHeader({ href: font.href, rel: 'preload', as: 'font', crossOrigin: 'anonymous' })
131
152
  );
132
153
  }
@@ -134,7 +155,7 @@ export function collectEarlyHintHeaders(
134
155
  // JS chunks — rel=modulepreload (skip when client JS is disabled)
135
156
  if (!options?.skipJs) {
136
157
  for (const url of collectRouteModulepreloads(segments, manifest)) {
137
- add(formatLinkHeader({ href: url, rel: 'modulepreload' }));
158
+ add(url, formatLinkHeader({ href: url, rel: 'modulepreload' }));
138
159
  }
139
160
  }
140
161
 
@@ -5,8 +5,23 @@
5
5
  * See design/10-error-handling.md.
6
6
  */
7
7
 
8
- import { TimberErrorBoundary } from '#/client/error-boundary.js';
8
+ import { TimberErrorBoundary } from '../client/error-boundary.js';
9
9
  import type { ManifestSegmentNode } from './route-matcher.js';
10
+ import { loadModule } from './safe-load.js';
11
+
12
+ /** MDX/markdown extensions — server components that cannot be passed as function props. */
13
+ const MDX_EXTENSIONS = new Set(['.mdx', '.md']);
14
+
15
+ /**
16
+ * Check if a manifest file path ends with an MDX/markdown extension.
17
+ * MDX components are server components and cannot cross the RSC→client
18
+ * boundary as function props to TimberErrorBoundary.
19
+ */
20
+ function isMdxFilePath(filePath: string): boolean {
21
+ const dotIndex = filePath.lastIndexOf('.');
22
+ if (dotIndex === -1) return false;
23
+ return MDX_EXTENSIONS.has(filePath.slice(dotIndex));
24
+ }
10
25
 
11
26
  /**
12
27
  * Wrap an element in error boundaries defined by a route segment.
@@ -15,11 +30,17 @@ import type { ManifestSegmentNode } from './route-matcher.js';
15
30
  * 1. Specific status files (e.g., 404.tsx, 500.tsx) — highest priority at runtime
16
31
  * 2. Category catch-alls (4xx.tsx, 5xx.tsx)
17
32
  * 3. error.tsx — catches anything not matched by status files
33
+ *
34
+ * MDX status files are server components and cannot be passed as function
35
+ * props to TimberErrorBoundary (a 'use client' component). Instead, they
36
+ * are pre-rendered as elements and passed as fallbackElement. See TIM-503.
18
37
  */
19
38
  export async function wrapSegmentWithErrorBoundaries(
20
39
  segment: ManifestSegmentNode,
21
40
  element: React.ReactElement,
22
- h: (...args: unknown[]) => React.ReactElement
41
+ h: (...args: unknown[]) => React.ReactElement,
42
+ /** When true, skip 4xx error boundaries — deny catching handles them in-tree. See TIM-666. */
43
+ skipDenyBoundaries = false
23
44
  ): Promise<React.ReactElement> {
24
45
  // Specific status files (innermost — highest priority at runtime)
25
46
  if (segment.statusFiles) {
@@ -27,13 +48,27 @@ export async function wrapSegmentWithErrorBoundaries(
27
48
  if (key !== '4xx' && key !== '5xx') {
28
49
  const status = parseInt(key, 10);
29
50
  if (!isNaN(status)) {
30
- const mod = (await file.load()) as Record<string, unknown>;
31
- if (mod.default) {
32
- element = h(TimberErrorBoundary, {
33
- fallbackComponent: mod.default,
34
- status,
35
- children: element,
36
- });
51
+ // Skip 4xx boundaries when deny catching is active — the deny
52
+ // page is rendered by AccessGate/PageDenyBoundary instead.
53
+ if (skipDenyBoundaries && status >= 400 && status <= 499) continue;
54
+ // .catch: error boundary construction must not fail if the
55
+ // error page module has a syntax error — skip this boundary.
56
+ const mod = await loadModule(file).catch(() => null);
57
+ if (mod?.default) {
58
+ if (isMdxFilePath(file.filePath)) {
59
+ // MDX: pre-render as element (server component can't be a function prop)
60
+ element = h(TimberErrorBoundary, {
61
+ fallbackElement: h(mod.default as never, { status }),
62
+ status,
63
+ children: element,
64
+ });
65
+ } else {
66
+ element = h(TimberErrorBoundary, {
67
+ fallbackComponent: mod.default,
68
+ status,
69
+ children: element,
70
+ });
71
+ }
37
72
  }
38
73
  }
39
74
  }
@@ -41,14 +76,24 @@ export async function wrapSegmentWithErrorBoundaries(
41
76
 
42
77
  // Category catch-alls (4xx.tsx, 5xx.tsx)
43
78
  for (const [key, file] of Object.entries(segment.statusFiles)) {
79
+ if (skipDenyBoundaries && key === '4xx') continue;
44
80
  if (key === '4xx' || key === '5xx') {
45
- const mod = (await file.load()) as Record<string, unknown>;
46
- if (mod.default) {
47
- element = h(TimberErrorBoundary, {
48
- fallbackComponent: mod.default,
49
- status: key === '4xx' ? 400 : 500,
50
- children: element,
51
- });
81
+ const mod = await loadModule(file).catch(() => null);
82
+ if (mod?.default) {
83
+ const categoryStatus = key === '4xx' ? 400 : 500;
84
+ if (isMdxFilePath(file.filePath)) {
85
+ element = h(TimberErrorBoundary, {
86
+ fallbackElement: h(mod.default as never, {}),
87
+ status: categoryStatus,
88
+ children: element,
89
+ });
90
+ } else {
91
+ element = h(TimberErrorBoundary, {
92
+ fallbackComponent: mod.default,
93
+ status: categoryStatus,
94
+ children: element,
95
+ });
96
+ }
52
97
  }
53
98
  }
54
99
  }
@@ -56,12 +101,19 @@ export async function wrapSegmentWithErrorBoundaries(
56
101
 
57
102
  // error.tsx (outermost — catches anything not matched by status files)
58
103
  if (segment.error) {
59
- const mod = (await segment.error.load()) as Record<string, unknown>;
60
- if (mod.default) {
61
- element = h(TimberErrorBoundary, {
62
- fallbackComponent: mod.default,
63
- children: element,
64
- });
104
+ const mod = await loadModule(segment.error).catch(() => null);
105
+ if (mod?.default) {
106
+ if (isMdxFilePath(segment.error.filePath)) {
107
+ element = h(TimberErrorBoundary, {
108
+ fallbackElement: h(mod.default as never, {}),
109
+ children: element,
110
+ });
111
+ } else {
112
+ element = h(TimberErrorBoundary, {
113
+ fallbackComponent: mod.default,
114
+ children: element,
115
+ });
116
+ }
65
117
  }
66
118
  }
67
119
 
@@ -10,10 +10,13 @@
10
10
  * exposed to the client (design/13-security.md principle 4).
11
11
  */
12
12
 
13
- import type { RouteMatch } from '#/server/pipeline.js';
14
- import type { ManifestSegmentNode } from '#/server/route-matcher.js';
15
- import type { ClientBootstrapConfig } from '#/server/html-injectors.js';
16
- import type { LayoutEntry } from '#/server/deny-renderer.js';
13
+ import type { RouteMatch } from './pipeline.js';
14
+ import type { ManifestSegmentNode } from './route-matcher.js';
15
+ import type { ClientBootstrapConfig } from './html-injectors.js';
16
+ import type { LayoutEntry } from './deny-renderer.js';
17
+ import type { GlobalErrorFile } from './rsc-entry/error-renderer.js';
18
+ import { logRenderError } from './logger.js';
19
+ import { loadModule } from './safe-load.js';
17
20
 
18
21
  /**
19
22
  * Render a fallback error page when the render pipeline throws.
@@ -27,25 +30,37 @@ export async function renderFallbackError(
27
30
  responseHeaders: Headers,
28
31
  isDev: boolean,
29
32
  rootSegment: ManifestSegmentNode,
30
- clientBootstrap: ClientBootstrapConfig
33
+ clientBootstrap: ClientBootstrapConfig,
34
+ globalError?: GlobalErrorFile
31
35
  ): Promise<Response> {
32
36
  if (isDev) {
33
37
  return renderDevErrorPage(error);
34
38
  }
35
39
  // Lazy import to avoid loading error-renderer in the pipeline module
36
- const { renderErrorPage } = await import('#/server/rsc-entry/error-renderer.js');
40
+ const { renderErrorPage } = await import('./rsc-entry/error-renderer.js');
37
41
  const segments = [rootSegment];
38
42
  const layoutComponents: LayoutEntry[] = [];
39
- if (rootSegment.layout) {
40
- const mod = (await rootSegment.layout.load()) as Record<string, unknown>;
41
- if (mod.default) {
42
- layoutComponents.push({
43
- component: mod.default as (...args: unknown[]) => unknown,
44
- segment: rootSegment,
45
- });
43
+ // Wrap layout loading in try/catch — if the root layout module itself
44
+ // crashes (evaluation failure, syntax error, etc.), we still want to
45
+ // reach renderErrorPage so it can fall through to global-error.tsx
46
+ // (Tier 2), which renders without any layout wrapping.
47
+ try {
48
+ if (rootSegment.layout) {
49
+ const mod = await loadModule(rootSegment.layout);
50
+ if (mod.default) {
51
+ layoutComponents.push({
52
+ component: mod.default as (...args: unknown[]) => unknown,
53
+ segment: rootSegment,
54
+ });
55
+ }
46
56
  }
57
+ } catch (layoutError) {
58
+ // Layout failed to load — proceed without it. renderErrorPage will
59
+ // attempt segment-level error pages (without layout wrapping) and
60
+ // then fall through to global-error.tsx if those also fail.
61
+ logRenderError({ method: req.method, path: new URL(req.url).pathname, error: layoutError });
47
62
  }
48
- const match: RouteMatch = { segments: segments as never, params: {} };
63
+ const match: RouteMatch = { segments: segments as never, segmentParams: {}, middlewareChain: [] };
49
64
  return renderErrorPage(
50
65
  error,
51
66
  500,
@@ -54,7 +69,8 @@ export async function renderFallbackError(
54
69
  req,
55
70
  match,
56
71
  responseHeaders,
57
- clientBootstrap
72
+ clientBootstrap,
73
+ globalError
58
74
  );
59
75
  }
60
76
 
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Shared state machine types and transitions for RSC Flight injection.
3
+ *
4
+ * Both html-injectors.ts (Web Streams) and node-stream-transforms.ts
5
+ * (Node.js streams) implement identical state transitions — only the
6
+ * I/O primitives differ. This module defines the discriminated union
7
+ * states and the transition map once.
8
+ *
9
+ * Valid state flow:
10
+ * init → streaming → flushing → done
11
+ * (any) → error
12
+ *
13
+ * Suffix stripping (</body></html>) is handled by a separate
14
+ * createMoveSuffixStream / createNodeMoveSuffixTransform upstream
15
+ * in the pipeline (TIM-530). The flight injector only interleaves
16
+ * RSC scripts between HTML chunks — no suffix tracking needed.
17
+ *
18
+ * Design doc: 02-rendering-pipeline.md
19
+ */
20
+
21
+ import type { TransitionMap } from '../utils/state-machine.js';
22
+
23
+ // ─── States ──────────────────────────────────────────────────────────────────
24
+
25
+ /** Waiting for first HTML chunk. Pull loop not started. */
26
+ export interface InitState {
27
+ phase: 'init';
28
+ }
29
+
30
+ /** HTML chunks flowing, pull loop running. */
31
+ export interface StreamingState {
32
+ phase: 'streaming';
33
+ }
34
+
35
+ /** HTML stream done (flush fired). Draining remaining RSC chunks. */
36
+ export interface FlushingState {
37
+ phase: 'flushing';
38
+ }
39
+
40
+ /** All streams consumed. Terminal state. */
41
+ export interface DoneState {
42
+ phase: 'done';
43
+ }
44
+
45
+ /** Pull loop failed. Terminal state with captured error. */
46
+ export interface ErrorState {
47
+ phase: 'error';
48
+ error: unknown;
49
+ }
50
+
51
+ export type FlightInjectionState =
52
+ | InitState
53
+ | StreamingState
54
+ | FlushingState
55
+ | DoneState
56
+ | ErrorState;
57
+
58
+ // ─── Events ──────────────────────────────────────────────────────────────────
59
+
60
+ /** First HTML chunk arrived — start the pull loop. */
61
+ export interface FirstChunkEvent {
62
+ type: 'FIRST_CHUNK';
63
+ }
64
+
65
+ /** HTML stream finished (flush/end). */
66
+ export interface HtmlDoneEvent {
67
+ type: 'HTML_DONE';
68
+ }
69
+
70
+ /** RSC pull loop completed (all chunks read or stream closed). */
71
+ export interface PullDoneEvent {
72
+ type: 'PULL_DONE';
73
+ }
74
+
75
+ /** RSC pull loop encountered an error. */
76
+ export interface PullErrorEvent {
77
+ type: 'PULL_ERROR';
78
+ error: unknown;
79
+ }
80
+
81
+ export type FlightInjectionEvent = FirstChunkEvent | HtmlDoneEvent | PullDoneEvent | PullErrorEvent;
82
+
83
+ // ─── Transitions ─────────────────────────────────────────────────────────────
84
+
85
+ export const flightInjectionTransitions: TransitionMap<FlightInjectionState, FlightInjectionEvent> =
86
+ {
87
+ init: {
88
+ FIRST_CHUNK: (): StreamingState => ({ phase: 'streaming' }),
89
+ // Edge case: HTML stream ends immediately (empty body)
90
+ HTML_DONE: (): FlushingState => ({ phase: 'flushing' }),
91
+ },
92
+ streaming: {
93
+ HTML_DONE: (): FlushingState => ({ phase: 'flushing' }),
94
+ PULL_DONE: (): StreamingState => ({ phase: 'streaming' }),
95
+ PULL_ERROR: (_s, e): ErrorState => ({ phase: 'error', error: e.error }),
96
+ },
97
+ flushing: {
98
+ PULL_DONE: (): DoneState => ({ phase: 'done' }),
99
+ PULL_ERROR: (_s, e): ErrorState => ({ phase: 'error', error: e.error }),
100
+ },
101
+ };
102
+
103
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
104
+
105
+ /** Whether the HTML stream has finished (flush/end fired). */
106
+ export function isHtmlDone(state: FlightInjectionState): boolean {
107
+ return state.phase === 'flushing' || state.phase === 'done';
108
+ }
109
+
110
+ /** Whether the RSC pull loop has completed. */
111
+ export function isPullDone(state: FlightInjectionState): boolean {
112
+ return state.phase === 'done' || state.phase === 'error';
113
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Shared Flight data script generation.
3
+ *
4
+ * Both html-injectors.ts (Web Streams / dev) and node-stream-transforms.ts
5
+ * (Node streams / production) use this module to generate inline `<script>`
6
+ * tags for RSC Flight data injection.
7
+ *
8
+ * The init script goes in `<head>` as part of the HTML shell (guaranteed
9
+ * to execute before any streaming chunk scripts). Push scripts are emitted
10
+ * as RSC chunks arrive during streaming.
11
+ *
12
+ * See design/02-rendering-pipeline.md, LOCAL-415
13
+ */
14
+
15
+ // ─── JSON Escaping ────────────────────────────────────────────────────────
16
+
17
+ /**
18
+ * Escape a JSON string for safe embedding inside an HTML `<script>` tag.
19
+ *
20
+ * Prevents XSS via `</script>` injection and handles Unicode line/paragraph
21
+ * separators that are valid JSON but invalid in JS string literals (pre-ES2019).
22
+ */
23
+ export function htmlEscapeJsonString(str: string): string {
24
+ return str
25
+ .replace(/</g, '\\u003c')
26
+ .replace(/>/g, '\\u003e')
27
+ .replace(/\u2028/g, '\\u2028')
28
+ .replace(/\u2029/g, '\\u2029');
29
+ }
30
+
31
+ // ─── Script Generation ────────────────────────────────────────────────────
32
+
33
+ /** The global variable name used for Flight data on the client. */
34
+ const FLIGHT_VAR = 'self.__timber_f';
35
+
36
+ /**
37
+ * Generate the init script that creates the Flight data array.
38
+ *
39
+ * This MUST be included in `<head>` (via headHtml) so it executes before
40
+ * any streaming chunk scripts arrive in `<body>`. The array is created
41
+ * unconditionally — no `||[]` guard needed in push scripts.
42
+ *
43
+ * Also emits the bootstrap signal [0] which tells the client that
44
+ * Flight data is active for this page.
45
+ */
46
+ export function flightInitScript(): string {
47
+ return `<script>${FLIGHT_VAR}=[${htmlEscapeJsonString(JSON.stringify([0]))}]</script>`;
48
+ }
49
+
50
+ /**
51
+ * Generate a push script for a Flight data chunk.
52
+ *
53
+ * Normally the init script runs first (it's in `<head>`), so
54
+ * `self.__timber_f` already exists. However, shell-less status pages
55
+ * (e.g. standalone error pages) may have no `<head>` tag, so the init script
56
+ * never runs. The `||[]` guard ensures the array is created on first
57
+ * push if needed, preventing a TypeError. See TIM-559.
58
+ */
59
+ export function flightChunkScript(data: string): string {
60
+ const escaped = htmlEscapeJsonString(JSON.stringify([1, data]));
61
+ return `<script>(${FLIGHT_VAR}=${FLIGHT_VAR}||[]).push(${escaped})</script>`;
62
+ }
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import { DenySignal, RedirectSignal, RenderError } from './primitives.js';
12
+ import { logRenderError } from './logger.js';
12
13
 
13
14
  // ─── Types ───────────────────────────────────────────────────────────────────
14
15
 
@@ -169,7 +170,7 @@ function handleSignal(error: unknown, responseHeaders: Headers): FlushResult {
169
170
  }
170
171
 
171
172
  // Unknown error → HTTP 500
172
- console.error('[timber] Unhandled render-phase error:', error);
173
+ logRenderError({ method: '', path: '', error });
173
174
  return {
174
175
  response: new Response(null, {
175
176
  status: 500,
@@ -173,4 +173,80 @@ export const coerce = {
173
173
  return undefined;
174
174
  }
175
175
  },
176
+
177
+ /**
178
+ * Coerce a date string to a Date object.
179
+ * Handles `<input type="date">` (`"2024-01-15"`), `<input type="datetime-local">`
180
+ * (`"2024-01-15T10:30"`), and full ISO 8601 strings.
181
+ * - Valid date string → `Date`
182
+ * - `""` / `undefined` / `null` → `undefined`
183
+ * - Invalid date strings → `undefined` (schema validation will catch this)
184
+ * - Impossible dates that `new Date()` silently normalizes (e.g. Feb 31) → `undefined`
185
+ */
186
+ date(value: unknown): Date | undefined {
187
+ if (value === undefined || value === null || value === '') return undefined;
188
+ if (value instanceof Date) return value;
189
+ if (typeof value !== 'string') return undefined;
190
+ const date = new Date(value);
191
+ if (Number.isNaN(date.getTime())) return undefined;
192
+
193
+ // Overflow detection: extract Y/M/D from the input string and verify
194
+ // they match the parsed Date components. new Date('2024-02-31') silently
195
+ // normalizes to March 2nd — we reject such inputs.
196
+ const ymdMatch = value.match(/^(\d{4})-(\d{2})-(\d{2})/);
197
+ if (ymdMatch) {
198
+ const inputYear = Number(ymdMatch[1]);
199
+ const inputMonth = Number(ymdMatch[2]);
200
+ const inputDay = Number(ymdMatch[3]);
201
+
202
+ // Use UTC methods for date-only and Z-suffixed strings to avoid
203
+ // timezone offset shifting the day. For datetime-local (no Z suffix),
204
+ // the Date constructor parses in local time, so use local methods.
205
+ const isUTC = value.length === 10 || value.endsWith('Z');
206
+ const parsedYear = isUTC ? date.getUTCFullYear() : date.getFullYear();
207
+ const parsedMonth = isUTC ? date.getUTCMonth() + 1 : date.getMonth() + 1;
208
+ const parsedDay = isUTC ? date.getUTCDate() : date.getDate();
209
+
210
+ if (inputYear !== parsedYear || inputMonth !== parsedMonth || inputDay !== parsedDay) {
211
+ return undefined;
212
+ }
213
+ }
214
+
215
+ return date;
216
+ },
217
+
218
+ /**
219
+ * Create a File coercion function with optional size and mime type validation.
220
+ * Returns the File if valid, `undefined` otherwise.
221
+ *
222
+ * ```ts
223
+ * // Basic — just checks it's a real File
224
+ * z.preprocess(coerce.file(), z.instanceof(File))
225
+ *
226
+ * // With constraints
227
+ * z.preprocess(
228
+ * coerce.file({ maxSize: 5 * 1024 * 1024, accept: ['image/png', 'image/jpeg'] }),
229
+ * z.instanceof(File)
230
+ * )
231
+ * ```
232
+ */
233
+ file(options?: { maxSize?: number; accept?: string[] }): (value: unknown) => File | undefined {
234
+ return (value: unknown): File | undefined => {
235
+ if (value === undefined || value === null || value === '') return undefined;
236
+ if (!(value instanceof File)) return undefined;
237
+
238
+ // Empty file input (no selection): browsers submit File with name="" and size=0
239
+ if (value.size === 0 && value.name === '') return undefined;
240
+
241
+ if (options?.maxSize !== undefined && value.size > options.maxSize) {
242
+ return undefined;
243
+ }
244
+
245
+ if (options?.accept !== undefined && !options.accept.includes(value.type)) {
246
+ return undefined;
247
+ }
248
+
249
+ return value;
250
+ };
251
+ },
176
252
  };