@nifrajs/web 4.0.0 → 5.0.0

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 (388) hide show
  1. package/README.md +11 -0
  2. package/dist/build-vite.d.ts +19 -0
  3. package/dist/build-vite.d.ts.map +1 -1
  4. package/dist/build-vite.js +31 -10
  5. package/dist/build-vite.js.map +1 -1
  6. package/dist/build.d.ts +8 -10
  7. package/dist/build.d.ts.map +1 -1
  8. package/dist/build.js +90 -31
  9. package/dist/build.js.map +1 -1
  10. package/dist/cdn.d.ts +8 -0
  11. package/dist/cdn.d.ts.map +1 -1
  12. package/dist/cdn.js +14 -15
  13. package/dist/cdn.js.map +1 -1
  14. package/dist/client.d.ts +1 -1
  15. package/dist/client.d.ts.map +1 -1
  16. package/dist/client.js +49 -26
  17. package/dist/client.js.map +1 -1
  18. package/dist/deferred.d.ts +2 -2
  19. package/dist/deferred.d.ts.map +1 -1
  20. package/dist/deferred.js +28 -7
  21. package/dist/deferred.js.map +1 -1
  22. package/dist/dev-feed.d.ts.map +1 -1
  23. package/dist/dev-feed.js +24 -5
  24. package/dist/dev-feed.js.map +1 -1
  25. package/dist/dev-session.d.ts.map +1 -1
  26. package/dist/dev-session.js +4 -14
  27. package/dist/dev-session.js.map +1 -1
  28. package/dist/dev.d.ts +7 -17
  29. package/dist/dev.d.ts.map +1 -1
  30. package/dist/dev.js +13 -1
  31. package/dist/dev.js.map +1 -1
  32. package/dist/draft.d.ts +8 -5
  33. package/dist/draft.d.ts.map +1 -1
  34. package/dist/draft.js +30 -13
  35. package/dist/draft.js.map +1 -1
  36. package/dist/fonts-google.d.ts.map +1 -1
  37. package/dist/fonts-google.js +7 -0
  38. package/dist/fonts-google.js.map +1 -1
  39. package/dist/index.d.ts +2 -2
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +2 -2
  42. package/dist/index.js.map +1 -1
  43. package/dist/internal/codegen.d.ts +1 -0
  44. package/dist/internal/codegen.d.ts.map +1 -1
  45. package/dist/internal/codegen.js +56 -29
  46. package/dist/internal/codegen.js.map +1 -1
  47. package/dist/internal/disposable-dir.d.ts +8 -0
  48. package/dist/internal/disposable-dir.d.ts.map +1 -0
  49. package/dist/internal/disposable-dir.js +25 -0
  50. package/dist/internal/disposable-dir.js.map +1 -0
  51. package/dist/internal/entry-chunks.d.ts +9 -0
  52. package/dist/internal/entry-chunks.d.ts.map +1 -0
  53. package/dist/internal/entry-chunks.js +15 -0
  54. package/dist/internal/entry-chunks.js.map +1 -0
  55. package/dist/internal/evidence-traversal.d.ts +12 -0
  56. package/dist/internal/evidence-traversal.d.ts.map +1 -0
  57. package/dist/internal/evidence-traversal.js +48 -0
  58. package/dist/internal/evidence-traversal.js.map +1 -0
  59. package/dist/internal/head-merge.d.ts +6 -0
  60. package/dist/internal/head-merge.d.ts.map +1 -1
  61. package/dist/internal/head-merge.js +9 -0
  62. package/dist/internal/head-merge.js.map +1 -1
  63. package/dist/internal/js-string.d.ts +2 -0
  64. package/dist/internal/js-string.d.ts.map +1 -1
  65. package/dist/internal/js-string.js +5 -1
  66. package/dist/internal/js-string.js.map +1 -1
  67. package/dist/internal/mount-shadow.js +1 -1
  68. package/dist/internal/mount-shadow.js.map +1 -1
  69. package/dist/internal/output-guard.d.ts +7 -0
  70. package/dist/internal/output-guard.d.ts.map +1 -1
  71. package/dist/internal/output-guard.js +80 -8
  72. package/dist/internal/output-guard.js.map +1 -1
  73. package/dist/internal/page-execution.d.ts +6 -0
  74. package/dist/internal/page-execution.d.ts.map +1 -1
  75. package/dist/internal/page-execution.js +377 -108
  76. package/dist/internal/page-execution.js.map +1 -1
  77. package/dist/internal/progressive-cookies.d.ts +14 -0
  78. package/dist/internal/progressive-cookies.d.ts.map +1 -0
  79. package/dist/internal/progressive-cookies.js +43 -0
  80. package/dist/internal/progressive-cookies.js.map +1 -0
  81. package/dist/internal/public-env.d.ts +2 -0
  82. package/dist/internal/public-env.d.ts.map +1 -0
  83. package/dist/internal/public-env.js +13 -0
  84. package/dist/internal/public-env.js.map +1 -0
  85. package/dist/internal/render-document.d.ts +1 -0
  86. package/dist/internal/render-document.d.ts.map +1 -1
  87. package/dist/internal/render-document.js +28 -5
  88. package/dist/internal/render-document.js.map +1 -1
  89. package/dist/internal/response-controls.d.ts +2 -0
  90. package/dist/internal/response-controls.d.ts.map +1 -1
  91. package/dist/internal/response-controls.js +26 -16
  92. package/dist/internal/response-controls.js.map +1 -1
  93. package/dist/internal/revive-dates.d.ts +16 -0
  94. package/dist/internal/revive-dates.d.ts.map +1 -0
  95. package/dist/internal/revive-dates.js +32 -0
  96. package/dist/internal/revive-dates.js.map +1 -0
  97. package/dist/internal/route-path.d.ts +8 -0
  98. package/dist/internal/route-path.d.ts.map +1 -0
  99. package/dist/internal/route-path.js +8 -0
  100. package/dist/internal/route-path.js.map +1 -0
  101. package/dist/internal/secret-scan.d.ts.map +1 -1
  102. package/dist/internal/secret-scan.js +105 -19
  103. package/dist/internal/secret-scan.js.map +1 -1
  104. package/dist/internal/server-boundary.d.ts.map +1 -1
  105. package/dist/internal/server-boundary.js +33 -5
  106. package/dist/internal/server-boundary.js.map +1 -1
  107. package/dist/internal/web-app.d.ts +21 -16
  108. package/dist/internal/web-app.d.ts.map +1 -1
  109. package/dist/internal/web-app.js +73 -21
  110. package/dist/internal/web-app.js.map +1 -1
  111. package/dist/internal/wire-date.d.ts +3 -0
  112. package/dist/internal/wire-date.d.ts.map +1 -0
  113. package/dist/internal/wire-date.js +14 -0
  114. package/dist/internal/wire-date.js.map +1 -0
  115. package/dist/internal/zone-graph.d.ts +4 -0
  116. package/dist/internal/zone-graph.d.ts.map +1 -1
  117. package/dist/internal/zone-graph.js +54 -14
  118. package/dist/internal/zone-graph.js.map +1 -1
  119. package/dist/isr.d.ts +58 -2
  120. package/dist/isr.d.ts.map +1 -1
  121. package/dist/isr.js +296 -39
  122. package/dist/isr.js.map +1 -1
  123. package/dist/llms-txt.d.ts +2 -0
  124. package/dist/llms-txt.d.ts.map +1 -1
  125. package/dist/llms-txt.js +29 -3
  126. package/dist/llms-txt.js.map +1 -1
  127. package/dist/manifest.d.ts +28 -4
  128. package/dist/manifest.d.ts.map +1 -1
  129. package/dist/manifest.js +14 -3
  130. package/dist/manifest.js.map +1 -1
  131. package/dist/open-resolved.d.ts +18 -0
  132. package/dist/open-resolved.d.ts.map +1 -0
  133. package/dist/open-resolved.js +62 -0
  134. package/dist/open-resolved.js.map +1 -0
  135. package/dist/plugins/kit.d.ts +1 -0
  136. package/dist/plugins/kit.d.ts.map +1 -1
  137. package/dist/plugins/kit.js +26 -11
  138. package/dist/plugins/kit.js.map +1 -1
  139. package/dist/plugins/vite-leak-guard.d.ts +3 -1
  140. package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
  141. package/dist/plugins/vite-leak-guard.js +4 -0
  142. package/dist/plugins/vite-leak-guard.js.map +1 -1
  143. package/dist/plugins/zone-guard.d.ts.map +1 -1
  144. package/dist/plugins/zone-guard.js +10 -2
  145. package/dist/plugins/zone-guard.js.map +1 -1
  146. package/dist/public-dir.d.ts +8 -1
  147. package/dist/public-dir.d.ts.map +1 -1
  148. package/dist/public-dir.js +17 -6
  149. package/dist/public-dir.js.map +1 -1
  150. package/dist/query.d.ts +13 -4
  151. package/dist/query.d.ts.map +1 -1
  152. package/dist/query.js +89 -26
  153. package/dist/query.js.map +1 -1
  154. package/dist/render-seam.d.ts +2 -0
  155. package/dist/render-seam.d.ts.map +1 -1
  156. package/dist/render-seam.js +1 -0
  157. package/dist/render-seam.js.map +1 -1
  158. package/dist/route-types.d.ts.map +1 -1
  159. package/dist/route-types.js +20 -9
  160. package/dist/route-types.js.map +1 -1
  161. package/dist/router.d.ts +9 -2
  162. package/dist/router.d.ts.map +1 -1
  163. package/dist/router.js +39 -46
  164. package/dist/router.js.map +1 -1
  165. package/dist/server-components/build-info.d.ts +3 -0
  166. package/dist/server-components/build-info.d.ts.map +1 -0
  167. package/dist/server-components/build-info.js +7 -0
  168. package/dist/server-components/build-info.js.map +1 -0
  169. package/dist/server-components/build-target.d.ts +12 -0
  170. package/dist/server-components/build-target.d.ts.map +1 -0
  171. package/dist/server-components/build-target.js +153 -0
  172. package/dist/server-components/build-target.js.map +1 -0
  173. package/dist/server-components/build.d.ts +24 -0
  174. package/dist/server-components/build.d.ts.map +1 -0
  175. package/dist/server-components/build.js +366 -0
  176. package/dist/server-components/build.js.map +1 -0
  177. package/dist/server-components/bundle-proof.d.ts +3 -0
  178. package/dist/server-components/bundle-proof.d.ts.map +1 -0
  179. package/dist/server-components/bundle-proof.js +23 -0
  180. package/dist/server-components/bundle-proof.js.map +1 -0
  181. package/dist/server-components/client.d.ts +46 -0
  182. package/dist/server-components/client.d.ts.map +1 -0
  183. package/dist/server-components/client.js +594 -0
  184. package/dist/server-components/client.js.map +1 -0
  185. package/dist/server-components/definition.d.ts +11 -0
  186. package/dist/server-components/definition.d.ts.map +1 -0
  187. package/dist/server-components/definition.js +94 -0
  188. package/dist/server-components/definition.js.map +1 -0
  189. package/dist/server-components/dev-http.d.ts +5 -0
  190. package/dist/server-components/dev-http.d.ts.map +1 -0
  191. package/dist/server-components/dev-http.js +71 -0
  192. package/dist/server-components/dev-http.js.map +1 -0
  193. package/dist/server-components/error.d.ts +9 -0
  194. package/dist/server-components/error.d.ts.map +1 -0
  195. package/dist/server-components/error.js +24 -0
  196. package/dist/server-components/error.js.map +1 -0
  197. package/dist/server-components/frames.d.ts +7 -0
  198. package/dist/server-components/frames.d.ts.map +1 -0
  199. package/dist/server-components/frames.js +169 -0
  200. package/dist/server-components/frames.js.map +1 -0
  201. package/dist/server-components/html-policy.d.ts +6 -0
  202. package/dist/server-components/html-policy.d.ts.map +1 -0
  203. package/dist/server-components/html-policy.js +75 -0
  204. package/dist/server-components/html-policy.js.map +1 -0
  205. package/dist/server-components/html.d.ts +4 -0
  206. package/dist/server-components/html.d.ts.map +1 -0
  207. package/dist/server-components/html.js +67 -0
  208. package/dist/server-components/html.js.map +1 -0
  209. package/dist/server-components/islands.d.ts +44 -0
  210. package/dist/server-components/islands.d.ts.map +1 -0
  211. package/dist/server-components/islands.js +42 -0
  212. package/dist/server-components/islands.js.map +1 -0
  213. package/dist/server-components/native.d.ts +4 -0
  214. package/dist/server-components/native.d.ts.map +1 -0
  215. package/dist/server-components/native.js +81 -0
  216. package/dist/server-components/native.js.map +1 -0
  217. package/dist/server-components/portal.d.ts +6 -0
  218. package/dist/server-components/portal.d.ts.map +1 -0
  219. package/dist/server-components/portal.js +35 -0
  220. package/dist/server-components/portal.js.map +1 -0
  221. package/dist/server-components/props.d.ts +3 -0
  222. package/dist/server-components/props.d.ts.map +1 -0
  223. package/dist/server-components/props.js +14 -0
  224. package/dist/server-components/props.js.map +1 -0
  225. package/dist/server-components/protocol.d.ts +44 -0
  226. package/dist/server-components/protocol.d.ts.map +1 -0
  227. package/dist/server-components/protocol.js +139 -0
  228. package/dist/server-components/protocol.js.map +1 -0
  229. package/dist/server-components/range.d.ts +24 -0
  230. package/dist/server-components/range.d.ts.map +1 -0
  231. package/dist/server-components/range.js +263 -0
  232. package/dist/server-components/range.js.map +1 -0
  233. package/dist/server-components/registry.d.ts +6 -0
  234. package/dist/server-components/registry.d.ts.map +1 -0
  235. package/dist/server-components/registry.js +30 -0
  236. package/dist/server-components/registry.js.map +1 -0
  237. package/dist/server-components/response.d.ts +4 -0
  238. package/dist/server-components/response.d.ts.map +1 -0
  239. package/dist/server-components/response.js +32 -0
  240. package/dist/server-components/response.js.map +1 -0
  241. package/dist/server-components/router-integration.d.ts +9 -0
  242. package/dist/server-components/router-integration.d.ts.map +1 -0
  243. package/dist/server-components/router-integration.js +29 -0
  244. package/dist/server-components/router-integration.js.map +1 -0
  245. package/dist/server-components/runtime.d.ts +14 -0
  246. package/dist/server-components/runtime.d.ts.map +1 -0
  247. package/dist/server-components/runtime.js +276 -0
  248. package/dist/server-components/runtime.js.map +1 -0
  249. package/dist/server-components/scheduler.d.ts +14 -0
  250. package/dist/server-components/scheduler.d.ts.map +1 -0
  251. package/dist/server-components/scheduler.js +74 -0
  252. package/dist/server-components/scheduler.js.map +1 -0
  253. package/dist/server-components/schema.d.ts +4 -0
  254. package/dist/server-components/schema.d.ts.map +1 -0
  255. package/dist/server-components/schema.js +77 -0
  256. package/dist/server-components/schema.js.map +1 -0
  257. package/dist/server-components/server-entry.d.ts +12 -0
  258. package/dist/server-components/server-entry.d.ts.map +1 -0
  259. package/dist/server-components/server-entry.js +90 -0
  260. package/dist/server-components/server-entry.js.map +1 -0
  261. package/dist/server-components/source.d.ts +15 -0
  262. package/dist/server-components/source.d.ts.map +1 -0
  263. package/dist/server-components/source.js +269 -0
  264. package/dist/server-components/source.js.map +1 -0
  265. package/dist/server-components/state.d.ts +4 -0
  266. package/dist/server-components/state.d.ts.map +1 -0
  267. package/dist/server-components/state.js +123 -0
  268. package/dist/server-components/state.js.map +1 -0
  269. package/dist/server-components/stream.d.ts +6 -0
  270. package/dist/server-components/stream.d.ts.map +1 -0
  271. package/dist/server-components/stream.js +70 -0
  272. package/dist/server-components/stream.js.map +1 -0
  273. package/dist/server-components/trusted-html.d.ts +3 -0
  274. package/dist/server-components/trusted-html.d.ts.map +1 -0
  275. package/dist/server-components/trusted-html.js +14 -0
  276. package/dist/server-components/trusted-html.js.map +1 -0
  277. package/dist/server-components/types.d.ts +107 -0
  278. package/dist/server-components/types.d.ts.map +1 -0
  279. package/dist/server-components/types.js +2 -0
  280. package/dist/server-components/types.js.map +1 -0
  281. package/dist/server-components/typescript.d.ts +11 -0
  282. package/dist/server-components/typescript.d.ts.map +1 -0
  283. package/dist/server-components/typescript.js +28 -0
  284. package/dist/server-components/typescript.js.map +1 -0
  285. package/dist/server-components/value.d.ts +5 -0
  286. package/dist/server-components/value.d.ts.map +1 -0
  287. package/dist/server-components/value.js +77 -0
  288. package/dist/server-components/value.js.map +1 -0
  289. package/dist/server-components/vite.d.ts +12 -0
  290. package/dist/server-components/vite.d.ts.map +1 -0
  291. package/dist/server-components/vite.js +197 -0
  292. package/dist/server-components/vite.js.map +1 -0
  293. package/dist/server-components.d.ts +5 -0
  294. package/dist/server-components.d.ts.map +1 -0
  295. package/dist/server-components.js +4 -0
  296. package/dist/server-components.js.map +1 -0
  297. package/dist/service-worker.d.ts +8 -1
  298. package/dist/service-worker.d.ts.map +1 -1
  299. package/dist/service-worker.js +20 -6
  300. package/dist/service-worker.js.map +1 -1
  301. package/dist/vite.d.ts +12 -0
  302. package/dist/vite.d.ts.map +1 -1
  303. package/dist/vite.js +77 -17
  304. package/dist/vite.js.map +1 -1
  305. package/dist/zones.d.ts +40 -0
  306. package/dist/zones.d.ts.map +1 -1
  307. package/dist/zones.js +170 -6
  308. package/dist/zones.js.map +1 -1
  309. package/package.json +97 -4
  310. package/src/build-vite.ts +31 -9
  311. package/src/build.ts +109 -31
  312. package/src/cdn.ts +18 -12
  313. package/src/client.ts +48 -23
  314. package/src/deferred.ts +27 -9
  315. package/src/dev-feed.ts +24 -5
  316. package/src/dev-session.ts +4 -13
  317. package/src/dev.ts +29 -1
  318. package/src/draft.ts +35 -14
  319. package/src/fonts-google.ts +10 -0
  320. package/src/index.ts +3 -2
  321. package/src/internal/codegen.ts +60 -29
  322. package/src/internal/disposable-dir.ts +24 -0
  323. package/src/internal/entry-chunks.ts +18 -0
  324. package/src/internal/evidence-traversal.ts +57 -0
  325. package/src/internal/head-merge.ts +16 -0
  326. package/src/internal/js-string.ts +6 -1
  327. package/src/internal/mount-shadow.ts +1 -1
  328. package/src/internal/output-guard.ts +88 -7
  329. package/src/internal/page-execution.ts +506 -108
  330. package/src/internal/progressive-cookies.ts +60 -0
  331. package/src/internal/public-env.ts +14 -0
  332. package/src/internal/render-document.ts +26 -11
  333. package/src/internal/response-controls.ts +31 -14
  334. package/src/internal/revive-dates.ts +35 -0
  335. package/src/internal/route-path.ts +8 -0
  336. package/src/internal/secret-scan.ts +104 -17
  337. package/src/internal/server-boundary.ts +29 -5
  338. package/src/internal/web-app.ts +116 -37
  339. package/src/internal/wire-date.ts +16 -0
  340. package/src/internal/zone-graph.ts +59 -19
  341. package/src/isr.ts +351 -43
  342. package/src/llms-txt.ts +32 -3
  343. package/src/manifest.ts +43 -7
  344. package/src/open-resolved.ts +64 -0
  345. package/src/plugins/kit.ts +31 -11
  346. package/src/plugins/vite-leak-guard.ts +5 -1
  347. package/src/plugins/zone-guard.ts +11 -2
  348. package/src/public-dir.ts +27 -8
  349. package/src/query.ts +107 -26
  350. package/src/render-seam.ts +2 -0
  351. package/src/route-types.ts +34 -7
  352. package/src/router.ts +58 -55
  353. package/src/server-components/build-info.ts +11 -0
  354. package/src/server-components/build-target.ts +194 -0
  355. package/src/server-components/build.ts +419 -0
  356. package/src/server-components/bundle-proof.ts +33 -0
  357. package/src/server-components/client.ts +663 -0
  358. package/src/server-components/definition.ts +125 -0
  359. package/src/server-components/dev-http.ts +75 -0
  360. package/src/server-components/error.ts +24 -0
  361. package/src/server-components/frames.ts +181 -0
  362. package/src/server-components/html-policy.ts +94 -0
  363. package/src/server-components/html.ts +81 -0
  364. package/src/server-components/islands.ts +111 -0
  365. package/src/server-components/native.ts +101 -0
  366. package/src/server-components/portal.ts +42 -0
  367. package/src/server-components/props.ts +19 -0
  368. package/src/server-components/protocol.ts +167 -0
  369. package/src/server-components/range.ts +313 -0
  370. package/src/server-components/registry.ts +34 -0
  371. package/src/server-components/response.ts +44 -0
  372. package/src/server-components/router-integration.ts +40 -0
  373. package/src/server-components/runtime.ts +376 -0
  374. package/src/server-components/scheduler.ts +84 -0
  375. package/src/server-components/schema.ts +69 -0
  376. package/src/server-components/server-entry.ts +98 -0
  377. package/src/server-components/source.ts +324 -0
  378. package/src/server-components/state.ts +143 -0
  379. package/src/server-components/stream.ts +71 -0
  380. package/src/server-components/trusted-html.ts +21 -0
  381. package/src/server-components/types.ts +138 -0
  382. package/src/server-components/typescript.ts +39 -0
  383. package/src/server-components/value.ts +82 -0
  384. package/src/server-components/vite.ts +242 -0
  385. package/src/server-components.ts +32 -0
  386. package/src/service-worker.ts +24 -7
  387. package/src/vite.ts +68 -17
  388. package/src/zones.ts +208 -7
@@ -40,7 +40,7 @@ import {
40
40
  STATUS_HEADER,
41
41
  } from "../router.ts"
42
42
  import { searchOf, searchOfChain } from "../search.ts"
43
- import { mergeHeads, resolveMeta } from "./head-merge.ts"
43
+ import { mergeHeads, resolveChainMeta } from "./head-merge.ts"
44
44
  import {
45
45
  EMPTY_RETAIN,
46
46
  type HeadersLike,
@@ -78,6 +78,7 @@ export type { NonceResolver } from "../nonce.ts"
78
78
 
79
79
  /** Structural context supplied by the core server to one page route handler. */
80
80
  export interface PageRouteContext<Env = unknown> {
81
+ readonly signal?: AbortSignal
81
82
  readonly params: Record<string, string>
82
83
  readonly req: Request
83
84
  readonly env: Env
@@ -172,6 +173,11 @@ function appTarget(location: string, requestUrl: string): string {
172
173
  }
173
174
 
174
175
  export interface PageExecutionOptions<Env = unknown> {
176
+ readonly serverComponents?: import("../server-components/types.ts").ServerComponentRuntime
177
+ readonly rerenderOwnerGET?: (
178
+ request: Request,
179
+ context: PageRouteContext<Env>,
180
+ ) => Promise<Response>
175
181
  readonly adapter: RenderAdapter
176
182
  readonly manifest: Manifest
177
183
  readonly clientEntry: string
@@ -201,6 +207,9 @@ export interface PageExecutionOptions<Env = unknown> {
201
207
  readonly route: string
202
208
  },
203
209
  ) => void
210
+ /** Logs a failure an `_error` boundary answered. Without a boundary the error propagates and the
211
+ * server logs it, so this runs only where the server never sees the throw. */
212
+ readonly logBoundaryError?: (error: unknown, request: Request) => void
204
213
  }
205
214
 
206
215
  type PageResponse = Response | ResponseResult | RenderedPage
@@ -238,6 +247,24 @@ type LayoutRun = {
238
247
  readonly pending: Promise<unknown> | undefined
239
248
  }
240
249
 
250
+ /** A route's layouts after only their gates ran: `gates[i]` is gate `i`'s value. */
251
+ type GatedLayouts = {
252
+ readonly modules: LoadedLayoutModules
253
+ readonly gates: readonly unknown[]
254
+ }
255
+
256
+ /** Layouts whose loaders settled for this request: what a status or error page renders in. */
257
+ type LayoutChain = {
258
+ readonly modules: LoadedLayoutModules
259
+ readonly layoutData: readonly unknown[] | undefined
260
+ }
261
+
262
+ /**
263
+ * Not called for a page that exports `inLayouts = false`, so such a page runs no layout loader.
264
+ * `undefined` renders the page alone.
265
+ */
266
+ type LayoutChainSource = (page: RouteModule, count: number) => Promise<LayoutChain | undefined>
267
+
241
268
  /**
242
269
  * Own the page request program behind one private seam.
243
270
  *
@@ -251,6 +278,8 @@ export function createPageRequestExecutor<Env = unknown>(
251
278
  options: PageExecutionOptions<Env>,
252
279
  ): PageRequestExecutor<Env> {
253
280
  const { adapter, manifest, clientEntry, api } = options
281
+ // The action result is request-local; a browser cannot manufacture this handoff.
282
+ const progressiveActionData = new WeakMap<Request, unknown>()
254
283
  // Bound once per render, so every loader, action and boundary in that render shares one view of the
255
284
  // in-process client carrying the visitor's platform. An `api` without the seam passes through as-is.
256
285
  const bindApi = platformBinderOf(api)
@@ -327,9 +356,9 @@ export function createPageRequestExecutor<Env = unknown>(
327
356
  ): string | undefined | Promise<string | undefined> =>
328
357
  options.nonce === undefined ? undefined : options.nonce({ request, env })
329
358
 
330
- const stylesOf = (id: string): { styles?: readonly string[]; cssLoading?: CssLoadingMode } => {
331
- const perRoute = options.routeStyles?.[id]
332
- const styles = perRoute ?? options.styles
359
+ const stylesFrom = (
360
+ styles: readonly string[] | undefined,
361
+ ): { styles?: readonly string[]; cssLoading?: CssLoadingMode } => {
333
362
  if (styles === undefined || styles.length === 0) return {}
334
363
  return {
335
364
  styles,
@@ -337,6 +366,9 @@ export function createPageRequestExecutor<Env = unknown>(
337
366
  }
338
367
  }
339
368
 
369
+ const stylesOf = (id: string): { styles?: readonly string[]; cssLoading?: CssLoadingMode } =>
370
+ stylesFrom(options.routeStyles?.[id] ?? options.styles)
371
+
340
372
  // Load a route's layout modules once for the current execution. The render path needs each whole
341
373
  // module for its component + meta, while the data path also needs loader/gate/boundary declarations.
342
374
  const loadLayoutModules = async (route: RouteEntry): Promise<LoadedLayoutModules> => {
@@ -361,15 +393,41 @@ export function createPageRequestExecutor<Env = unknown>(
361
393
  pending: undefined,
362
394
  }
363
395
 
364
- /** Run layout loaders, awaiting gates before starting the page loader and retaining only safe data. */
396
+ /**
397
+ * Await one gate. A returned redirect, status or Response refuses the request exactly as a thrown
398
+ * one does: stored as layout data it would let the page loader and the action run.
399
+ */
400
+ const runGate = async (
401
+ route: RouteEntry,
402
+ index: number,
403
+ loader: NonNullable<LoadedLayoutModules[number]["loader"]>,
404
+ ctx: LoaderContext,
405
+ ): Promise<unknown> => {
406
+ let value: unknown
407
+ try {
408
+ value = await loader(ctx)
409
+ } catch (err) {
410
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The loaded layout array is derived from these same manifest IDs in the same order.
411
+ throw tagLayoutError(err, route.layoutIds[index] as string)
412
+ }
413
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The loaded layout array is derived from these same manifest IDs in the same order.
414
+ if (isControlFlow(value)) throw tagLayoutError(value, route.layoutIds[index] as string)
415
+ return value
416
+ }
417
+
418
+ /**
419
+ * Run layout loaders, awaiting gates before starting the page loader and retaining only safe data.
420
+ * `gated` carries gates that already ran for this request, so none runs twice.
421
+ */
365
422
  const runLayoutChain = async (
366
423
  route: RouteEntry,
367
424
  ctx: LoaderContext,
368
425
  controls: PageResponseControls,
369
426
  retain?: RetainContext,
427
+ gated?: GatedLayouts,
370
428
  ): Promise<LayoutRun> => {
371
429
  if (route.layoutIds.length === 0) return emptyLayoutRun
372
- const modules = await loadLayoutModules(route)
430
+ const modules = gated?.modules ?? (await loadLayoutModules(route))
373
431
  if (!modules.some((m) => m.loader !== undefined)) {
374
432
  return { modules, layoutData: undefined, retained: [], pending: undefined }
375
433
  }
@@ -385,6 +443,7 @@ export function createPageRequestExecutor<Env = unknown>(
385
443
  try {
386
444
  keep = keepsLayoutData(route, i, layout, ctx.params, retain)
387
445
  } catch (err) {
446
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The loaded layout array is derived from these same manifest IDs in the same order.
388
447
  throw tagLayoutError(err, route.layoutIds[i] as string)
389
448
  }
390
449
  if (keep) {
@@ -398,11 +457,7 @@ export function createPageRequestExecutor<Env = unknown>(
398
457
  set: controls.scope(i),
399
458
  }
400
459
  if (modules[i]?.gate === true) {
401
- try {
402
- results[i] = await loader(scoped)
403
- } catch (err) {
404
- throw tagLayoutError(err, route.layoutIds[i] as string)
405
- }
460
+ results[i] = gated === undefined ? await runGate(route, i, loader, scoped) : gated.gates[i]
406
461
  continue
407
462
  }
408
463
  const layoutId = route.layoutIds[i] as string
@@ -441,22 +496,19 @@ export function createPageRequestExecutor<Env = unknown>(
441
496
  route: RouteEntry,
442
497
  ctx: LoaderContext,
443
498
  controls: PageResponseControls,
444
- ): Promise<LoadedLayoutModules> => {
499
+ ): Promise<GatedLayouts> => {
445
500
  const modules = await loadLayoutModules(route)
501
+ const gates: unknown[] = []
446
502
  for (let i = 0; i < modules.length; i++) {
447
503
  const mod = modules[i]
448
504
  if (mod?.gate !== true || mod.loader === undefined) continue
449
- try {
450
- await mod.loader({
451
- ...ctx,
452
- params: scopeParams(ctx.params, route.layoutParams?.[i]),
453
- set: controls.scope(i),
454
- })
455
- } catch (err) {
456
- throw tagLayoutError(err, route.layoutIds[i] as string)
457
- }
505
+ gates[i] = await runGate(route, i, mod.loader, {
506
+ ...ctx,
507
+ params: scopeParams(ctx.params, route.layoutParams?.[i]),
508
+ set: controls.scope(i),
509
+ })
458
510
  }
459
- return modules
511
+ return { modules, gates }
460
512
  }
461
513
 
462
514
  /** Outermost first; a failure carries its directory so the nearest `_error` above it renders. */
@@ -594,16 +646,14 @@ export function createPageRequestExecutor<Env = unknown>(
594
646
 
595
647
  const resolveChainAndHead = (
596
648
  layoutModules: LoadedLayoutModules,
649
+ layoutData: readonly unknown[] | undefined,
597
650
  page: RouteModule,
598
651
  metaArgs: MetaArgs,
599
652
  leaf: unknown = page.default,
600
653
  ): { chain: unknown[]; head: Meta } => {
601
654
  const chain = [...layoutModules.map((m) => m.default), leaf]
602
- const heads = [
603
- ...layoutModules.map((m) => resolveMeta(m.meta, metaArgs)),
604
- resolveMeta(page.meta, metaArgs),
605
- ]
606
- return { chain, head: mergeHeads(heads) }
655
+ const metas = [...layoutModules.map((m) => m.meta), page.meta]
656
+ return { chain, head: mergeHeads(resolveChainMeta(metas, layoutData, metaArgs)) }
607
657
  }
608
658
 
609
659
  /** What `useMatches` reports for a chain: the layouts' ids and `handle`s, then the page's. */
@@ -649,49 +699,160 @@ export function createPageRequestExecutor<Env = unknown>(
649
699
  }
650
700
  }
651
701
 
702
+ /** What a status or error page's `meta` receives: no route params, and its own data. */
703
+ const statusMetaArgs = (req: Request, data: unknown, nonce: string | undefined): MetaArgs =>
704
+ nonce === undefined
705
+ ? { data, pageData: data, params: {}, origin: originOf(req) }
706
+ : { data, pageData: data, params: {}, origin: originOf(req), nonce }
707
+
652
708
  const renderError = async (
709
+ req: Request,
653
710
  route: RouteEntry,
654
711
  errorId: string,
655
712
  err: unknown,
656
713
  personalized: boolean,
657
714
  nonce?: string,
715
+ layouts?: LayoutChainSource,
658
716
  ): Promise<PageResponse> => {
659
717
  const errDir = dirOfId(errorId, "_error")
660
718
  const keptLayoutIds = route.layoutIds.filter((id) => {
661
719
  const ld = dirOfId(id, "_layout")
662
720
  return ld === "" || ld === errDir || errDir.startsWith(`${ld}/`)
663
721
  })
664
- const layouts = await Promise.all(
722
+ const errorModule = await (manifest.errors?.[errorId] as LayoutEntry).load()
723
+ const e = err instanceof Error ? err : new Error(String(err))
724
+ // An error's text can carry a connection string or a query, so only a run that says it is
725
+ // development or test shows it; an unset NODE_ENV (an unbuilt `bun app.ts`) counts as production.
726
+ const nodeEnv = typeof process === "undefined" ? undefined : process.env.NODE_ENV
727
+ const data =
728
+ nodeEnv === "development" || nodeEnv === "test"
729
+ ? { name: e.name, message: e.message }
730
+ : { name: "Error", message: "Internal Server Error" }
731
+ const inLayouts = errorModule.inLayouts !== false
732
+ const chain =
733
+ inLayouts && layouts !== undefined && keptLayoutIds.length > 0
734
+ ? await layouts(errorModule, keptLayoutIds.length)
735
+ : undefined
736
+ const render = (components: unknown[], wrapped?: { head: Meta; chain: LayoutChain }) => {
737
+ const layoutData = wrapped?.chain.layoutData
738
+ return renderPageResult({
739
+ adapter,
740
+ chain: components,
741
+ data,
742
+ ...(wrapped === undefined ? {} : { head: wrapped.head }),
743
+ clientEntry,
744
+ routeId: errorId,
745
+ status: 500,
746
+ hydrate: false,
747
+ ...(layoutData === undefined ? {} : { layoutData }),
748
+ ...(personalized || layoutData !== undefined ? { headers: PRIVATE_PAGE_HEADERS } : {}),
749
+ ...(nonce === undefined ? {} : { nonce }),
750
+ ...cspOption,
751
+ ...titleOption,
752
+ })
753
+ }
754
+ if (chain !== undefined) {
755
+ const resolved = resolveChainAndHead(
756
+ chain.modules,
757
+ chain.layoutData,
758
+ errorModule,
759
+ statusMetaArgs(req, data, nonce),
760
+ )
761
+ try {
762
+ return await render(resolved.chain, { head: resolved.head, chain })
763
+ } catch {
764
+ // A layout that fails to render leaves the error page alone, never a second error.
765
+ return render([errorModule.default])
766
+ }
767
+ }
768
+ // Layouts whose data did not settle still frame the error page, without their data.
769
+ const layoutComponents = await Promise.all(
665
770
  keptLayoutIds.map((id) =>
666
771
  (manifest.layouts[id] as LayoutEntry).load().then((m) => m.default),
667
772
  ),
668
773
  )
669
- const { default: errComp } = await (manifest.errors?.[errorId] as LayoutEntry).load()
670
- const e = err instanceof Error ? err : new Error(String(err))
671
- // An error's text can carry a connection string or a query, so a production page gets neither.
672
- const production = typeof process === "undefined" || process.env.NODE_ENV === "production"
673
- return renderPageResult({
674
- adapter,
675
- chain: [...layouts, errComp],
676
- data: production
677
- ? { name: "Error", message: "Internal Server Error" }
678
- : { name: e.name, message: e.message },
679
- clientEntry,
680
- routeId: errorId,
681
- status: 500,
682
- hydrate: false,
683
- ...(personalized ? { headers: PRIVATE_PAGE_HEADERS } : {}),
684
- ...(nonce === undefined ? {} : { nonce }),
685
- ...cspOption,
686
- ...titleOption,
687
- })
774
+ return render([...layoutComponents, errorModule.default])
688
775
  }
689
776
 
777
+ /** The leading `count` layouts of `modules`, unless one of them answered with a status or redirect. */
778
+ const settledLayouts = (
779
+ modules: LoadedLayoutModules,
780
+ layoutData: readonly unknown[] | undefined,
781
+ count: number,
782
+ ): LayoutChain | undefined => {
783
+ if (modules.length < count) return undefined
784
+ const data = layoutData?.slice(0, count)
785
+ if (data?.some((value) => isControlFlow(value) || isStatusSignal(value))) return undefined
786
+ return { modules: modules.slice(0, count), layoutData: data }
787
+ }
788
+
789
+ /** A run's leading layouts once all of its loaders settle - never when a layout or middleware refused. */
790
+ const leadingLayouts =
791
+ (run: LayoutRun | undefined, err?: unknown): LayoutChainSource =>
792
+ async (_page, count) => {
793
+ if (run === undefined || (err !== undefined && layoutErrorId(err) !== undefined)) {
794
+ return undefined
795
+ }
796
+ try {
797
+ await run.pending
798
+ } catch {
799
+ return undefined
800
+ }
801
+ return settledLayouts(run.modules, run.layoutData, count)
802
+ }
803
+
804
+ const rootLayoutIds: readonly string[] = manifest.layouts._layout === undefined ? [] : ["_layout"]
805
+ const rootMiddlewareIds: readonly string[] =
806
+ manifest.middlewares?._middleware === undefined ? [] : ["_middleware"]
807
+ /**
808
+ * Run the root middleware and layouts for a request no route ran them for (an unmatched URL), as a
809
+ * nested `_404` runs its own. Any refusal or failure renders the page alone.
810
+ */
811
+ const rootLayoutsFor =
812
+ (c: PageRouteContext<Env>, controls: PageResponseControls): LayoutChainSource =>
813
+ async (page) => {
814
+ const entry = manifest.statusPages?.["404"] ?? manifest.notFound
815
+ if (rootLayoutIds.length === 0 || entry === undefined) return undefined
816
+ const layoutsOnly: RouteEntry = {
817
+ id: "_404",
818
+ pattern: "/*",
819
+ layoutIds: rootLayoutIds,
820
+ errorIds: [],
821
+ file: entry.file,
822
+ load: entry.load,
823
+ }
824
+ const route: RouteEntry =
825
+ rootMiddlewareIds.length === 0
826
+ ? layoutsOnly
827
+ : { ...layoutsOnly, middlewareIds: rootMiddlewareIds }
828
+ try {
829
+ const draft = await draftFlag(c.req)
830
+ if (draft) controls.personalize()
831
+ const ctx: LoaderContext = {
832
+ params: {},
833
+ request: c.req,
834
+ req: c.req,
835
+ api: apiFor(c),
836
+ env: c.env,
837
+ draft,
838
+ search: loaderSearch(page.searchSchema, c.req),
839
+ set: controls.scope(PAGE_SCOPE),
840
+ }
841
+ if (route.middlewareIds !== undefined) await runMiddleware(route, ctx, controls)
842
+ const run = await runLayoutChain(route, ctx, controls)
843
+ await run.pending
844
+ return settledLayouts(run.modules, run.layoutData, rootLayoutIds.length)
845
+ } catch {
846
+ return undefined
847
+ }
848
+ }
849
+
690
850
  const renderTerminalStatus = async (
691
851
  status: number,
692
- path?: string,
852
+ req: Request,
693
853
  extraHeaders?: HeadersLike,
694
854
  nonce?: string,
855
+ layouts?: LayoutChainSource,
695
856
  ): Promise<PageResponse> => {
696
857
  const page = manifest.statusPages?.[String(status)] ?? manifest.notFound
697
858
  if (page === undefined) {
@@ -700,36 +861,57 @@ export function createPageRequestExecutor<Env = unknown>(
700
861
  return new Response(STATUS_TEXT[status] ?? "Error", { status, headers })
701
862
  }
702
863
  const routeId = manifest.statusPages?.[String(status)] !== undefined ? `_${status}` : "_404"
703
- const loaded = (await page.load()) as {
704
- readonly default: unknown
705
- readonly searchSchema?: RouteModule["searchSchema"]
706
- readonly handle?: unknown
864
+ const loaded = await page.load()
865
+ const wraps = loaded.inLayouts !== false && rootLayoutIds.length > 0
866
+ const chain =
867
+ wraps && layouts !== undefined ? await layouts(loaded, rootLayoutIds.length) : undefined
868
+ const modules = chain?.modules ?? []
869
+ const layoutIds = chain === undefined ? [] : rootLayoutIds
870
+ const layoutData = chain?.layoutData
871
+ const { chain: components, head } = resolveChainAndHead(
872
+ modules,
873
+ layoutData,
874
+ loaded,
875
+ statusMetaArgs(req, null, nonce),
876
+ )
877
+ let headers = extraHeaders
878
+ if (layoutData !== undefined) {
879
+ // The page now shows what a layout loaded for this visitor.
880
+ const privateHeaders = new Headers(extraHeaders)
881
+ privateHeaders.set("cache-control", PRIVATE_NO_STORE)
882
+ headers = privateHeaders
707
883
  }
708
- return renderPageResult({
884
+ const statusPage = {
709
885
  adapter,
710
- chain: [loaded.default],
886
+ chain: components,
711
887
  data: null,
888
+ head,
712
889
  clientEntry,
713
890
  routeId,
714
- matchChain: { ids: [routeId], handles: [loaded.handle] },
715
- ...(path !== undefined
716
- ? {
717
- search: searchOf(
718
- loaded.searchSchema,
719
- path.includes("?") ? path.slice(path.indexOf("?")) : "",
720
- ),
721
- }
722
- : {}),
723
- ...(path !== undefined ? { path } : {}),
891
+ matchChain: matchChainOf(layoutIds, modules, routeId, loaded),
892
+ search: searchChainOf(modules, loaded, req),
893
+ path: pathOf(req),
724
894
  status,
725
- ...(extraHeaders !== undefined ? { headers: extraHeaders } : {}),
895
+ // The browser renders this page inside the root layout, so a page that had to render alone
896
+ // stays server-only, as a nested `_404` does.
897
+ ...(wraps && chain === undefined ? { hydrate: false } : {}),
898
+ ...(layoutData === undefined ? {} : { layoutData }),
899
+ ...(headers !== undefined ? { headers } : {}),
726
900
  ...(nonce === undefined ? {} : { nonce }),
727
901
  ...cspOption,
728
902
  ...preloadOf(routeId),
729
- ...stylesOf(routeId),
903
+ // The build keys stylesheets by route, so a page inside the root layout links the whole-app one.
904
+ ...(chain === undefined ? stylesOf(routeId) : stylesFrom(options.styles)),
730
905
  ...prerenderedHandover,
731
906
  ...titleOption,
732
- })
907
+ }
908
+ if (chain === undefined) return renderPageResult(statusPage)
909
+ try {
910
+ return await renderPageResult(statusPage)
911
+ } catch {
912
+ // A layout that fails to render leaves the status page alone, never a second error.
913
+ return renderTerminalStatus(status, req, extraHeaders, nonce)
914
+ }
733
915
  }
734
916
 
735
917
  const renderStatusSignal = async (
@@ -737,6 +919,7 @@ export function createPageRequestExecutor<Env = unknown>(
737
919
  env: Env,
738
920
  signal: StatusSignal,
739
921
  personalized: boolean,
922
+ layouts?: LayoutChainSource,
740
923
  ): Promise<PageResponse> => {
741
924
  const { status, headers } = signal[STATUS_SIGNAL]
742
925
  if (req.headers.get(DATA_HEADER) !== null) {
@@ -750,7 +933,7 @@ export function createPageRequestExecutor<Env = unknown>(
750
933
  }
751
934
  const nonce = await resolveNonce(req, env)
752
935
  if (headers === undefined && !personalized) {
753
- return renderTerminalStatus(status, pathOf(req), undefined, nonce)
936
+ return renderTerminalStatus(status, req, undefined, nonce, layouts)
754
937
  }
755
938
  const documentHeaders = new Headers(headers)
756
939
  if (headers !== undefined) {
@@ -758,7 +941,7 @@ export function createPageRequestExecutor<Env = unknown>(
758
941
  }
759
942
  // A cookie queued before the signal makes the status page specific to this visitor.
760
943
  if (personalized) documentHeaders.set("cache-control", PRIVATE_NO_STORE)
761
- return renderTerminalStatus(status, pathOf(req), documentHeaders, nonce)
944
+ return renderTerminalStatus(status, req, documentHeaders, nonce, layouts)
762
945
  }
763
946
 
764
947
  const reportLoaderError = (
@@ -789,8 +972,12 @@ export function createPageRequestExecutor<Env = unknown>(
789
972
  assemblyCache: RenderAssemblyCache | undefined,
790
973
  responseHeaders: Record<string, string> | undefined,
791
974
  personalized: boolean,
975
+ serverComponents?: import("../server-components/types.ts").ComponentState,
792
976
  ): Promise<PageResponse> => {
793
- if (isStatusSignal(data)) return renderStatusSignal(req, env, data, personalized)
977
+ // Every layout loader settled before the page rendered, so a status or error page can reuse them.
978
+ const settled: LayoutChainSource = async (_page, count) =>
979
+ settledLayouts(layoutModules, layoutData, count)
980
+ if (isStatusSignal(data)) return renderStatusSignal(req, env, data, personalized, settled)
794
981
  if (isControlFlow(data)) return data
795
982
  if (req.headers.get(DATA_HEADER) !== null) {
796
983
  const pageSplit = prepareDeferred(data)
@@ -809,7 +996,7 @@ export function createPageRequestExecutor<Env = unknown>(
809
996
  ...(boundarySplit === undefined ? [] : boundarySplit.deferred),
810
997
  ]
811
998
  const payload =
812
- layoutData === undefined && boundarySplit === undefined
999
+ layoutData === undefined && boundarySplit === undefined && serverComponents === undefined
813
1000
  ? pageSplit.forClient
814
1001
  : {
815
1002
  v: 1 as const,
@@ -817,6 +1004,7 @@ export function createPageRequestExecutor<Env = unknown>(
817
1004
  layoutData: layoutSplits.map((split) => split.forClient),
818
1005
  retained: layoutRetained,
819
1006
  ...(boundarySplit === undefined ? {} : { boundaries: boundarySplit.forClient }),
1007
+ ...(serverComponents === undefined ? {} : { serverComponents }),
820
1008
  }
821
1009
  if (allDeferred.length === 0) {
822
1010
  return Response.json(payload ?? null, { headers: DATA_RESPONSE_HEADERS })
@@ -832,9 +1020,11 @@ export function createPageRequestExecutor<Env = unknown>(
832
1020
  const nonce = options.nonce === undefined ? undefined : await resolveNonce(req, env)
833
1021
  const { chain, head } = resolveChainAndHead(
834
1022
  layoutModules,
1023
+ layoutData,
835
1024
  mod,
836
1025
  {
837
1026
  data,
1027
+ pageData: data,
838
1028
  params,
839
1029
  origin: originOf(req),
840
1030
  ...(nonce === undefined ? {} : { nonce }),
@@ -853,6 +1043,8 @@ export function createPageRequestExecutor<Env = unknown>(
853
1043
  path: pathOf(req),
854
1044
  search: searchChainOf(layoutModules, mod, req),
855
1045
  matchChain: matchChainOf(route.layoutIds, layoutModules, route.id, mod),
1046
+ ...(serverComponents === undefined ? {} : { serverComponents }),
1047
+ ...(progressiveActionData.has(req) ? { actionData: progressiveActionData.get(req) } : {}),
856
1048
  hydrate: mod.hydrate !== false,
857
1049
  ...(layoutData !== undefined ? { layoutData } : {}),
858
1050
  ...(boundaryStates !== undefined ? { boundaries: boundaryStates } : {}),
@@ -882,12 +1074,21 @@ export function createPageRequestExecutor<Env = unknown>(
882
1074
  ...(assemblyCache === undefined ? {} : { assemblyCache }),
883
1075
  })
884
1076
  } catch (err) {
885
- if (isStatusSignal(err)) return renderStatusSignal(req, env, err, personalized)
1077
+ if (isStatusSignal(err)) return renderStatusSignal(req, env, err, personalized, settled)
886
1078
  if (isControlFlow(err)) throw err
887
1079
  reportLoaderError(route, req, { ...params }, err)
888
1080
  const errorId = boundaryFor(route, err)
889
1081
  if (errorId === undefined) throw err
890
- return renderError(route, errorId, withDuplicateInstanceHint(err), personalized, nonce)
1082
+ options.logBoundaryError?.(err, req)
1083
+ return renderError(
1084
+ req,
1085
+ route,
1086
+ errorId,
1087
+ withDuplicateInstanceHint(err),
1088
+ personalized,
1089
+ nonce,
1090
+ settled,
1091
+ )
891
1092
  }
892
1093
  }
893
1094
 
@@ -899,11 +1100,13 @@ export function createPageRequestExecutor<Env = unknown>(
899
1100
  params: Record<string, string>,
900
1101
  err: unknown,
901
1102
  personalized: boolean,
1103
+ layouts?: LayoutChainSource,
902
1104
  ): Promise<PageResponse> => {
903
1105
  if (isControlFlow(err)) throw err
904
1106
  reportLoaderError(route, req, params, err)
905
1107
  const errorId = boundaryFor(route, err)
906
1108
  if (errorId === undefined) throw err
1109
+ options.logBoundaryError?.(err, req)
907
1110
  if (req.headers.get(DATA_HEADER) !== null) {
908
1111
  return new Response("Internal Server Error", {
909
1112
  status: 500,
@@ -911,11 +1114,13 @@ export function createPageRequestExecutor<Env = unknown>(
911
1114
  })
912
1115
  }
913
1116
  return renderError(
1117
+ req,
914
1118
  route,
915
1119
  errorId,
916
1120
  withDuplicateInstanceHint(err),
917
1121
  personalized,
918
1122
  options.nonce === undefined ? undefined : await resolveNonce(req, env),
1123
+ layouts,
919
1124
  )
920
1125
  }
921
1126
 
@@ -946,8 +1151,9 @@ export function createPageRequestExecutor<Env = unknown>(
946
1151
  }
947
1152
  const mod = await page.load()
948
1153
  const nonce = options.nonce === undefined ? undefined : await resolveNonce(req, env)
949
- const { chain, head } = resolveChainAndHead(layoutModules, mod, {
1154
+ const { chain, head } = resolveChainAndHead(layoutModules, layoutData, mod, {
950
1155
  data: null,
1156
+ pageData: null,
951
1157
  params,
952
1158
  origin: originOf(req),
953
1159
  ...(nonce === undefined ? {} : { nonce }),
@@ -1018,17 +1224,17 @@ export function createPageRequestExecutor<Env = unknown>(
1018
1224
  }
1019
1225
 
1020
1226
  const renderNotFound = async (
1021
- request: Request,
1022
- env: Env,
1023
- path?: string,
1227
+ c: PageRouteContext<Env>,
1228
+ controls: PageResponseControls,
1024
1229
  ): Promise<PageResponse> => {
1025
1230
  const page = manifest.statusPages?.["404"] ?? manifest.notFound
1026
- if (page === undefined) return renderTerminalStatus(404, path)
1231
+ if (page === undefined) return renderTerminalStatus(404, c.req)
1027
1232
  return renderTerminalStatus(
1028
1233
  404,
1029
- path,
1234
+ c.req,
1030
1235
  undefined,
1031
- options.nonce === undefined ? undefined : await resolveNonce(request, env),
1236
+ options.nonce === undefined ? undefined : await resolveNonce(c.req, c.env),
1237
+ rootLayoutsFor(c, controls),
1032
1238
  )
1033
1239
  }
1034
1240
 
@@ -1084,8 +1290,19 @@ export function createPageRequestExecutor<Env = unknown>(
1084
1290
  }
1085
1291
 
1086
1292
  return withResponseControls(async (c, controls) => {
1087
- if (is404Fallback && !prerenderedSet.has(urlPartsFor(c.req).pathname)) {
1088
- return renderNotFound(c.req, c.env, pathOf(c.req))
1293
+ const componentRuntime = options.serverComponents
1294
+ const componentOwner = componentRuntime?.owns(route.id) === true
1295
+ let componentRequest:
1296
+ | import("../server-components/protocol.ts").ComponentRequest
1297
+ | undefined
1298
+ try {
1299
+ componentRequest = componentRuntime?.classify(c.req, route.id)
1300
+ } catch (error) {
1301
+ return componentRuntime!.failure(error)
1302
+ }
1303
+ if (componentOwner || componentRequest !== undefined) controls.personalize()
1304
+ if (!componentOwner && is404Fallback && !prerenderedSet.has(urlPartsFor(c.req).pathname)) {
1305
+ return renderNotFound(c, controls)
1089
1306
  }
1090
1307
  const mod = await route.load()
1091
1308
  const draft = await draftFlag(c.req)
@@ -1097,6 +1314,7 @@ export function createPageRequestExecutor<Env = unknown>(
1097
1314
  let layoutModules: LoadedLayoutModules | undefined
1098
1315
  let layoutRetained: readonly number[] = []
1099
1316
  let responseHeaders: Record<string, string> | undefined
1317
+ let componentState: import("../server-components/types.ts").ComponentState | undefined
1100
1318
  const requestApi = apiFor(c)
1101
1319
  let run: LayoutRun | undefined
1102
1320
  try {
@@ -1111,13 +1329,21 @@ export function createPageRequestExecutor<Env = unknown>(
1111
1329
  set: controls.scope(PAGE_SCOPE),
1112
1330
  }
1113
1331
  if (route.middlewareIds !== undefined) await runMiddleware(route, ctx, controls)
1114
- run = await runLayoutChain(route, ctx, controls, retainContextOf(c.req))
1332
+ run = await runLayoutChain(
1333
+ route,
1334
+ ctx,
1335
+ controls,
1336
+ componentOwner ? undefined : retainContextOf(c.req),
1337
+ )
1115
1338
  layoutModules = run.modules
1116
1339
  layoutRetained = run.retained
1117
- const boundaryDefinitions = [
1118
- ...run.modules.flatMap((layout) => layout.boundaries ?? []),
1119
- ...(mod.boundaries ?? []),
1120
- ]
1340
+ const boundaryDefinitions =
1341
+ componentRequest === undefined
1342
+ ? [
1343
+ ...run.modules.flatMap((layout) => layout.boundaries ?? []),
1344
+ ...(mod.boundaries ?? []),
1345
+ ]
1346
+ : []
1121
1347
  if (boundaryDefinitions.length > 0) boundaryDescriptors(boundaryDefinitions)
1122
1348
  const effectiveSearch = searchChainOf(run.modules, mod, c.req)
1123
1349
  const boundaryBatch =
@@ -1149,6 +1375,34 @@ export function createPageRequestExecutor<Env = unknown>(
1149
1375
  : await Promise.all([pageResult, run.pending, staticBoundaryPromise])
1150
1376
  data = pageData
1151
1377
  layoutData = run.layoutData
1378
+ if (componentOwner) {
1379
+ for (let i = 0; i < (layoutData?.length ?? 0); i++) {
1380
+ const outcome = layoutData?.[i]
1381
+ if (isControlFlow(outcome) || isStatusSignal(outcome))
1382
+ throw tagLayoutError(outcome, route.layoutIds[i] as string)
1383
+ }
1384
+ }
1385
+ if (componentRequest !== undefined && (isControlFlow(data) || isStatusSignal(data)))
1386
+ return componentRuntime!.failure(data)
1387
+ if (componentOwner && !isControlFlow(data) && !isStatusSignal(data)) {
1388
+ const componentContext = Object.create(
1389
+ c,
1390
+ Object.getOwnPropertyDescriptors({
1391
+ ...ctx,
1392
+ search: effectiveSearch,
1393
+ signal:
1394
+ c.signal === undefined ? c.req.signal : AbortSignal.any([c.signal, c.req.signal]),
1395
+ data,
1396
+ layoutData: layoutData ?? [],
1397
+ }),
1398
+ )
1399
+ componentState = await componentRuntime!.execute(
1400
+ route.id,
1401
+ componentContext,
1402
+ componentRequest,
1403
+ options.nonce === undefined ? undefined : await resolveNonce(c.req, c.env),
1404
+ )
1405
+ }
1152
1406
  if (boundaryBatch !== undefined) {
1153
1407
  const streamed = { ...boundaryBatch.initial, ...(staticBoundaries ?? {}) }
1154
1408
  for (const entry of boundaryBatch.pending) {
@@ -1164,6 +1418,7 @@ export function createPageRequestExecutor<Env = unknown>(
1164
1418
  // by the same boundary as one.
1165
1419
  responseHeaders = controls.documentHeaders()
1166
1420
  } catch (err) {
1421
+ if (componentRequest !== undefined) return componentRuntime!.failure(err)
1167
1422
  if (isStatusSignal(err)) {
1168
1423
  return (
1169
1424
  (await renderRouteNotFound(
@@ -1174,10 +1429,27 @@ export function createPageRequestExecutor<Env = unknown>(
1174
1429
  err,
1175
1430
  run,
1176
1431
  controls.personalized,
1177
- )) ?? renderStatusSignal(c.req, c.env, err, controls.personalized)
1432
+ )) ??
1433
+ renderStatusSignal(c.req, c.env, err, controls.personalized, leadingLayouts(run, err))
1178
1434
  )
1179
1435
  }
1180
- return renderLoaderFailure(route, c.req, c.env, c.params, err, controls.personalized)
1436
+ return renderLoaderFailure(
1437
+ route,
1438
+ c.req,
1439
+ c.env,
1440
+ c.params,
1441
+ err,
1442
+ controls.personalized,
1443
+ leadingLayouts(run, err),
1444
+ )
1445
+ }
1446
+ if (componentRequest !== undefined && componentState !== undefined) {
1447
+ const answer = componentRuntime!.response(componentState, componentRequest)
1448
+ const headers = new Headers(answer.headers)
1449
+ for (const [name, value] of Object.entries(responseHeaders ?? {}))
1450
+ if (name !== "cache-control" && name !== "content-type" && name !== "vary")
1451
+ headers.set(name, value)
1452
+ return new Response(answer.body, { status: answer.status, headers })
1181
1453
  }
1182
1454
  if (isStatusSignal(data)) {
1183
1455
  const nested = await renderRouteNotFound(
@@ -1205,6 +1477,7 @@ export function createPageRequestExecutor<Env = unknown>(
1205
1477
  layoutModules === undefined ? undefined : assemblyCacheFor(mod, layoutModules),
1206
1478
  responseHeaders,
1207
1479
  controls.personalized,
1480
+ componentState,
1208
1481
  )
1209
1482
  })
1210
1483
  },
@@ -1232,11 +1505,11 @@ export function createPageRequestExecutor<Env = unknown>(
1232
1505
  set: controls.scope(ACTION_SCOPE),
1233
1506
  }
1234
1507
  const isDataRequest = c.req.headers.get(DATA_HEADER) !== null
1235
- let layoutModules: LoadedLayoutModules
1508
+ let gated: GatedLayouts
1236
1509
  let result: unknown
1237
1510
  try {
1238
1511
  if (route.middlewareIds !== undefined) await runMiddleware(route, actionContext, controls)
1239
- layoutModules = await runLayoutGates(route, actionContext, controls)
1512
+ gated = await runLayoutGates(route, actionContext, controls)
1240
1513
  result = await mod.action(actionContext)
1241
1514
  } catch (err) {
1242
1515
  if (isControlFlow(err)) return err
@@ -1271,26 +1544,136 @@ export function createPageRequestExecutor<Env = unknown>(
1271
1544
  },
1272
1545
  })
1273
1546
  }
1274
- const search = searchChainOf(layoutModules, mod, c.req)
1275
- const data = mod.loader
1276
- ? await mod.loader({
1277
- params: c.params,
1278
- request: c.req,
1279
- req: c.req,
1280
- api: requestApi,
1281
- env: c.env,
1282
- draft,
1283
- search,
1284
- set: controls.scope(PAGE_SCOPE),
1547
+ if (options.serverComponents?.owns(route.id)) {
1548
+ if (options.rerenderOwnerGET === undefined) throw new Error("component_contract")
1549
+ const actionHeaders = controls.documentHeaders()
1550
+ if (controls.progressiveRedirect(c.req)) {
1551
+ // Relative, so a browser behind a TLS terminator stays on its own scheme. A `//` path
1552
+ // would read as another host, so that one keeps the absolute URL.
1553
+ const { pathname, search } = new URL(c.req.url)
1554
+ const location = pathname.startsWith("//") ? c.req.url : pathname + search
1555
+ return new Response(null, {
1556
+ status: 303,
1557
+ headers: { ...actionHeaders, location, "cache-control": PRIVATE_NO_STORE },
1558
+ })
1559
+ }
1560
+ const headers = new Headers(c.req.headers)
1561
+ for (const name of [
1562
+ DATA_HEADER,
1563
+ RETAIN_HEADER,
1564
+ NAV_FROM_HEADER,
1565
+ "x-nifra-component",
1566
+ "content-type",
1567
+ "content-length",
1568
+ "transfer-encoding",
1569
+ ])
1570
+ headers.delete(name)
1571
+ const cookie = controls.progressiveCookies(c.req)
1572
+ if (cookie) headers.set("cookie", cookie)
1573
+ else headers.delete("cookie")
1574
+ const request = new Request(c.req.url, {
1575
+ method: "GET",
1576
+ headers,
1577
+ signal:
1578
+ c.signal === undefined ? c.req.signal : AbortSignal.any([c.signal, c.req.signal]),
1579
+ })
1580
+ progressiveActionData.set(request, actionResult)
1581
+ try {
1582
+ const response = await options.rerenderOwnerGET(request, c)
1583
+ const merged = new Headers(response.headers)
1584
+ for (const [name, value] of Object.entries(actionHeaders ?? {}))
1585
+ if (!merged.has(name)) merged.set(name, value)
1586
+ merged.set("cache-control", PRIVATE_NO_STORE)
1587
+ return new Response(response.body, {
1588
+ status: response.status,
1589
+ statusText: response.statusText,
1590
+ headers: merged,
1285
1591
  })
1286
- : null
1287
- const responseHeaders = controls.documentHeaders()
1592
+ } finally {
1593
+ progressiveActionData.delete(request)
1594
+ }
1595
+ }
1596
+ const layoutModules = gated.modules
1597
+ const search = searchChainOf(layoutModules, mod, c.req)
1598
+ let run: LayoutRun | undefined
1599
+ let data: unknown
1600
+ let responseHeaders: Record<string, string> | undefined
1601
+ // The ordinary layout loaders run now, after the mutation, as a GET of this page would.
1602
+ try {
1603
+ run = await runLayoutChain(route, actionContext, controls, undefined, gated)
1604
+ const pageResult = mod.loader
1605
+ ? mod.loader({
1606
+ params: c.params,
1607
+ request: c.req,
1608
+ req: c.req,
1609
+ api: requestApi,
1610
+ env: c.env,
1611
+ draft,
1612
+ search,
1613
+ set: controls.scope(PAGE_SCOPE),
1614
+ })
1615
+ : null
1616
+ const [pageData] =
1617
+ run.pending === undefined
1618
+ ? ([await pageResult] as const)
1619
+ : await Promise.all([pageResult, run.pending])
1620
+ data = pageData
1621
+ responseHeaders = controls.documentHeaders()
1622
+ } catch (err) {
1623
+ if (isStatusSignal(err)) {
1624
+ return (
1625
+ (await renderRouteNotFound(
1626
+ c.req,
1627
+ c.env,
1628
+ route,
1629
+ c.params,
1630
+ err,
1631
+ run,
1632
+ controls.personalized,
1633
+ )) ??
1634
+ renderStatusSignal(c.req, c.env, err, controls.personalized, leadingLayouts(run, err))
1635
+ )
1636
+ }
1637
+ return renderLoaderFailure(
1638
+ route,
1639
+ c.req,
1640
+ c.env,
1641
+ c.params,
1642
+ err,
1643
+ controls.personalized,
1644
+ leadingLayouts(run, err),
1645
+ )
1646
+ }
1647
+ // The re-render answers a returned redirect or status the way the page's GET does.
1648
+ if (isStatusSignal(data))
1649
+ return (
1650
+ (await renderRouteNotFound(
1651
+ c.req,
1652
+ c.env,
1653
+ route,
1654
+ c.params,
1655
+ data,
1656
+ run,
1657
+ controls.personalized,
1658
+ )) ??
1659
+ renderStatusSignal(c.req, c.env, data, controls.personalized, leadingLayouts(run, data))
1660
+ )
1661
+ if (isControlFlow(data)) return data
1662
+ const layoutData = run.layoutData
1663
+ const layoutProps: { layoutData?: Exclude<typeof layoutData, undefined> } = {}
1664
+ if (layoutData !== undefined) {
1665
+ layoutProps.layoutData = layoutData
1666
+ // The page now shows what a layout loaded for this visitor.
1667
+ responseHeaders = { ...responseHeaders, ...PRIVATE_PAGE_HEADERS }
1668
+ }
1288
1669
  const nonce = options.nonce === undefined ? undefined : await resolveNonce(c.req, c.env)
1289
1670
  const { chain, head } = resolveChainAndHead(
1290
1671
  layoutModules,
1672
+ layoutData,
1291
1673
  mod,
1292
1674
  {
1293
1675
  data,
1676
+ pageData: data,
1294
1677
  params: c.params,
1295
1678
  origin: originOf(c.req),
1296
1679
  ...(nonce === undefined ? {} : { nonce }),
@@ -1310,6 +1693,7 @@ export function createPageRequestExecutor<Env = unknown>(
1310
1693
  search,
1311
1694
  matchChain: matchChainOf(route.layoutIds, layoutModules, route.id, mod),
1312
1695
  hydrate: mod.hydrate !== false,
1696
+ ...layoutProps,
1313
1697
  ...preloadOf(route.id),
1314
1698
  ...stylesOf(route.id),
1315
1699
  ...prerenderedHandover,
@@ -1324,7 +1708,7 @@ export function createPageRequestExecutor<Env = unknown>(
1324
1708
  const match = matchNotFoundScope?.(pathOf(c.req)) ?? null
1325
1709
  const target = match === null ? undefined : notFoundTargets[Number(match.routeId)]
1326
1710
  if (match === null || target === undefined) {
1327
- return renderNotFound(c.req, c.env, pathOf(c.req))
1711
+ return renderNotFound(c, controls)
1328
1712
  }
1329
1713
  const params: Record<string, string> = {}
1330
1714
  for (let i = 0; i < target.names.length; i++) {
@@ -1333,7 +1717,7 @@ export function createPageRequestExecutor<Env = unknown>(
1333
1717
  const mod = await target.page.load()
1334
1718
  const draft = await draftFlag(c.req)
1335
1719
  if (draft) controls.personalize()
1336
- let run: LayoutRun
1720
+ let run: LayoutRun | undefined
1337
1721
  try {
1338
1722
  const ctx: LoaderContext = {
1339
1723
  params,
@@ -1352,9 +1736,23 @@ export function createPageRequestExecutor<Env = unknown>(
1352
1736
  await run.pending
1353
1737
  } catch (err) {
1354
1738
  if (isStatusSignal(err)) {
1355
- return renderStatusSignal(c.req, c.env, err, controls.personalized)
1739
+ return renderStatusSignal(
1740
+ c.req,
1741
+ c.env,
1742
+ err,
1743
+ controls.personalized,
1744
+ leadingLayouts(run, err),
1745
+ )
1356
1746
  }
1357
- return renderLoaderFailure(target.route, c.req, c.env, params, err, controls.personalized)
1747
+ return renderLoaderFailure(
1748
+ target.route,
1749
+ c.req,
1750
+ c.env,
1751
+ params,
1752
+ err,
1753
+ controls.personalized,
1754
+ leadingLayouts(run, err),
1755
+ )
1358
1756
  }
1359
1757
  return renderNestedNotFound(
1360
1758
  c.req,