@nifrajs/web 5.5.1 → 5.7.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 (481) hide show
  1. package/CHANGELOG.md +243 -0
  2. package/README.md +21 -11
  3. package/dist/boundary.d.ts +67 -7
  4. package/dist/boundary.d.ts.map +1 -1
  5. package/dist/boundary.js +6 -4
  6. package/dist/boundary.js.map +1 -1
  7. package/dist/build-plan.d.ts +3 -0
  8. package/dist/build-plan.d.ts.map +1 -1
  9. package/dist/build-plan.js.map +1 -1
  10. package/dist/build-vite.d.ts.map +1 -1
  11. package/dist/build-vite.js +2 -0
  12. package/dist/build-vite.js.map +1 -1
  13. package/dist/build.d.ts +9 -2
  14. package/dist/build.d.ts.map +1 -1
  15. package/dist/build.js +10 -0
  16. package/dist/build.js.map +1 -1
  17. package/dist/client.d.ts +3 -1
  18. package/dist/client.d.ts.map +1 -1
  19. package/dist/client.js +174 -23
  20. package/dist/client.js.map +1 -1
  21. package/dist/csp.d.ts +8 -0
  22. package/dist/csp.d.ts.map +1 -1
  23. package/dist/csp.js +11 -2
  24. package/dist/csp.js.map +1 -1
  25. package/dist/deferred.d.ts +6 -4
  26. package/dist/deferred.d.ts.map +1 -1
  27. package/dist/deferred.js +10 -6
  28. package/dist/deferred.js.map +1 -1
  29. package/dist/dev-feed.d.ts.map +1 -1
  30. package/dist/dev-feed.js +11 -2
  31. package/dist/dev-feed.js.map +1 -1
  32. package/dist/dev-session.d.ts.map +1 -1
  33. package/dist/dev-session.js +13 -6
  34. package/dist/dev-session.js.map +1 -1
  35. package/dist/dev.d.ts +4 -1
  36. package/dist/dev.d.ts.map +1 -1
  37. package/dist/dev.js +12 -5
  38. package/dist/dev.js.map +1 -1
  39. package/dist/diagnostic-catalog.d.ts +4 -0
  40. package/dist/diagnostic-catalog.d.ts.map +1 -1
  41. package/dist/diagnostic-catalog.js +48 -0
  42. package/dist/diagnostic-catalog.js.map +1 -1
  43. package/dist/diagnostic.d.ts +1 -1
  44. package/dist/diagnostic.d.ts.map +1 -1
  45. package/dist/diagnostic.js +1 -1
  46. package/dist/diagnostic.js.map +1 -1
  47. package/dist/fn.d.ts +5 -3
  48. package/dist/fn.d.ts.map +1 -1
  49. package/dist/fn.js +20 -5
  50. package/dist/fn.js.map +1 -1
  51. package/dist/fs.d.ts.map +1 -1
  52. package/dist/fs.js +13 -3
  53. package/dist/fs.js.map +1 -1
  54. package/dist/identity.d.ts +22 -0
  55. package/dist/identity.d.ts.map +1 -0
  56. package/dist/identity.js +60 -0
  57. package/dist/identity.js.map +1 -0
  58. package/dist/index.d.ts +8 -6
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +6 -3
  61. package/dist/index.js.map +1 -1
  62. package/dist/internal/cdn/cloudflare.d.ts +2 -1
  63. package/dist/internal/cdn/cloudflare.d.ts.map +1 -1
  64. package/dist/internal/cdn/cloudflare.js +2 -1
  65. package/dist/internal/cdn/cloudflare.js.map +1 -1
  66. package/dist/internal/certified-components.d.ts +1 -1
  67. package/dist/internal/certified-components.js +1 -1
  68. package/dist/internal/codegen.d.ts +11 -10
  69. package/dist/internal/codegen.d.ts.map +1 -1
  70. package/dist/internal/codegen.js +73 -10
  71. package/dist/internal/codegen.js.map +1 -1
  72. package/dist/internal/dev-websocket.d.ts +73 -0
  73. package/dist/internal/dev-websocket.d.ts.map +1 -0
  74. package/dist/internal/dev-websocket.js +145 -0
  75. package/dist/internal/dev-websocket.js.map +1 -0
  76. package/dist/internal/hole-runtime.d.ts +66 -0
  77. package/dist/internal/hole-runtime.d.ts.map +1 -0
  78. package/dist/internal/hole-runtime.js +211 -0
  79. package/dist/internal/hole-runtime.js.map +1 -0
  80. package/dist/internal/identity-epoch.d.ts +50 -0
  81. package/dist/internal/identity-epoch.d.ts.map +1 -0
  82. package/dist/internal/identity-epoch.js +180 -0
  83. package/dist/internal/identity-epoch.js.map +1 -0
  84. package/dist/internal/invalidation-contract.d.ts +23 -0
  85. package/dist/internal/invalidation-contract.d.ts.map +1 -0
  86. package/dist/internal/invalidation-contract.js +11 -0
  87. package/dist/internal/invalidation-contract.js.map +1 -0
  88. package/dist/internal/invalidation.d.ts +62 -0
  89. package/dist/internal/invalidation.d.ts.map +1 -0
  90. package/dist/internal/invalidation.js +149 -0
  91. package/dist/internal/invalidation.js.map +1 -0
  92. package/dist/internal/island-bindings.d.ts +51 -0
  93. package/dist/internal/island-bindings.d.ts.map +1 -0
  94. package/dist/internal/island-bindings.js +147 -0
  95. package/dist/internal/island-bindings.js.map +1 -0
  96. package/dist/internal/island-list.d.ts +26 -0
  97. package/dist/internal/island-list.d.ts.map +1 -0
  98. package/dist/internal/island-list.js +78 -0
  99. package/dist/internal/island-list.js.map +1 -0
  100. package/dist/internal/island-mount.d.ts +4 -0
  101. package/dist/internal/island-mount.d.ts.map +1 -0
  102. package/dist/internal/island-mount.js +66 -0
  103. package/dist/internal/island-mount.js.map +1 -0
  104. package/dist/internal/island-owner.d.ts +5 -0
  105. package/dist/internal/island-owner.d.ts.map +1 -0
  106. package/dist/internal/island-owner.js +20 -0
  107. package/dist/internal/island-owner.js.map +1 -0
  108. package/dist/internal/island-resource.d.ts +30 -0
  109. package/dist/internal/island-resource.d.ts.map +1 -0
  110. package/dist/internal/island-resource.js +57 -0
  111. package/dist/internal/island-resource.js.map +1 -0
  112. package/dist/internal/island-signals.d.ts +51 -0
  113. package/dist/internal/island-signals.d.ts.map +1 -0
  114. package/dist/internal/island-signals.js +220 -0
  115. package/dist/internal/island-signals.js.map +1 -0
  116. package/dist/internal/keep-alive-table.d.ts +23 -0
  117. package/dist/internal/keep-alive-table.d.ts.map +1 -0
  118. package/dist/internal/keep-alive-table.js +53 -0
  119. package/dist/internal/keep-alive-table.js.map +1 -0
  120. package/dist/internal/live-contract.d.ts +101 -0
  121. package/dist/internal/live-contract.d.ts.map +1 -0
  122. package/dist/internal/live-contract.js +5 -0
  123. package/dist/internal/live-contract.js.map +1 -0
  124. package/dist/internal/live-queries.d.ts +22 -0
  125. package/dist/internal/live-queries.d.ts.map +1 -0
  126. package/dist/internal/live-queries.js +54 -0
  127. package/dist/internal/live-queries.js.map +1 -0
  128. package/dist/internal/page-events.d.ts +10 -0
  129. package/dist/internal/page-events.d.ts.map +1 -0
  130. package/dist/internal/page-events.js +26 -0
  131. package/dist/internal/page-events.js.map +1 -0
  132. package/dist/internal/page-execution.d.ts +16 -1
  133. package/dist/internal/page-execution.d.ts.map +1 -1
  134. package/dist/internal/page-execution.js +432 -31
  135. package/dist/internal/page-execution.js.map +1 -1
  136. package/dist/internal/parity.d.ts +3 -0
  137. package/dist/internal/parity.d.ts.map +1 -1
  138. package/dist/internal/parity.js +28 -5
  139. package/dist/internal/parity.js.map +1 -1
  140. package/dist/internal/ppr/admission.d.ts +83 -0
  141. package/dist/internal/ppr/admission.d.ts.map +1 -0
  142. package/dist/internal/ppr/admission.js +221 -0
  143. package/dist/internal/ppr/admission.js.map +1 -0
  144. package/dist/internal/ppr/canary.d.ts +24 -0
  145. package/dist/internal/ppr/canary.d.ts.map +1 -0
  146. package/dist/internal/ppr/canary.js +102 -0
  147. package/dist/internal/ppr/canary.js.map +1 -0
  148. package/dist/internal/ppr/compile-shell.d.ts +39 -0
  149. package/dist/internal/ppr/compile-shell.d.ts.map +1 -0
  150. package/dist/internal/ppr/compile-shell.js +298 -0
  151. package/dist/internal/ppr/compile-shell.js.map +1 -0
  152. package/dist/internal/ppr/contract.d.ts +177 -0
  153. package/dist/internal/ppr/contract.d.ts.map +1 -0
  154. package/dist/internal/ppr/contract.js +80 -0
  155. package/dist/internal/ppr/contract.js.map +1 -0
  156. package/dist/internal/ppr/frames.d.ts +71 -0
  157. package/dist/internal/ppr/frames.d.ts.map +1 -0
  158. package/dist/internal/ppr/frames.js +119 -0
  159. package/dist/internal/ppr/frames.js.map +1 -0
  160. package/dist/internal/ppr/integration.d.ts +19 -0
  161. package/dist/internal/ppr/integration.d.ts.map +1 -0
  162. package/dist/internal/ppr/integration.js +106 -0
  163. package/dist/internal/ppr/integration.js.map +1 -0
  164. package/dist/internal/ppr/options.d.ts +96 -0
  165. package/dist/internal/ppr/options.d.ts.map +1 -0
  166. package/dist/internal/ppr/options.js +125 -0
  167. package/dist/internal/ppr/options.js.map +1 -0
  168. package/dist/internal/ppr/route-rules.d.ts +12 -0
  169. package/dist/internal/ppr/route-rules.d.ts.map +1 -0
  170. package/dist/internal/ppr/route-rules.js +53 -0
  171. package/dist/internal/ppr/route-rules.js.map +1 -0
  172. package/dist/internal/ppr/runtime.d.ts +29 -0
  173. package/dist/internal/ppr/runtime.d.ts.map +1 -0
  174. package/dist/internal/ppr/runtime.js +35 -0
  175. package/dist/internal/ppr/runtime.js.map +1 -0
  176. package/dist/internal/ppr/server.d.ts +29 -0
  177. package/dist/internal/ppr/server.d.ts.map +1 -0
  178. package/dist/internal/ppr/server.js +1409 -0
  179. package/dist/internal/ppr/server.js.map +1 -0
  180. package/dist/internal/ppr/shell-store.d.ts +39 -0
  181. package/dist/internal/ppr/shell-store.d.ts.map +1 -0
  182. package/dist/internal/ppr/shell-store.js +106 -0
  183. package/dist/internal/ppr/shell-store.js.map +1 -0
  184. package/dist/internal/prerendered-pages.d.ts +1 -1
  185. package/dist/internal/prerendered-pages.d.ts.map +1 -1
  186. package/dist/internal/prerendered-pages.js +7 -4
  187. package/dist/internal/prerendered-pages.js.map +1 -1
  188. package/dist/internal/present.d.ts +11 -0
  189. package/dist/internal/present.d.ts.map +1 -0
  190. package/dist/internal/present.js +13 -0
  191. package/dist/internal/present.js.map +1 -0
  192. package/dist/internal/query-digest.d.ts +9 -0
  193. package/dist/internal/query-digest.d.ts.map +1 -0
  194. package/dist/internal/query-digest.js +23 -0
  195. package/dist/internal/query-digest.js.map +1 -0
  196. package/dist/internal/query-runtime.d.ts +6 -1
  197. package/dist/internal/query-runtime.d.ts.map +1 -1
  198. package/dist/internal/query-runtime.js +9 -0
  199. package/dist/internal/query-runtime.js.map +1 -1
  200. package/dist/internal/query-seam.d.ts +29 -0
  201. package/dist/internal/query-seam.d.ts.map +1 -0
  202. package/dist/internal/query-seam.js +23 -0
  203. package/dist/internal/query-seam.js.map +1 -0
  204. package/dist/internal/render-document.d.ts +34 -64
  205. package/dist/internal/render-document.d.ts.map +1 -1
  206. package/dist/internal/render-document.js +82 -64
  207. package/dist/internal/render-document.js.map +1 -1
  208. package/dist/internal/request-abort.d.ts +8 -0
  209. package/dist/internal/request-abort.d.ts.map +1 -0
  210. package/dist/internal/request-abort.js +22 -0
  211. package/dist/internal/request-abort.js.map +1 -0
  212. package/dist/internal/server-boundary.d.ts +2 -0
  213. package/dist/internal/server-boundary.d.ts.map +1 -1
  214. package/dist/internal/server-boundary.js +8 -1
  215. package/dist/internal/server-boundary.js.map +1 -1
  216. package/dist/internal/share-data.d.ts +10 -0
  217. package/dist/internal/share-data.d.ts.map +1 -0
  218. package/dist/internal/share-data.js +135 -0
  219. package/dist/internal/share-data.js.map +1 -0
  220. package/dist/internal/status-page.d.ts +68 -0
  221. package/dist/internal/status-page.d.ts.map +1 -0
  222. package/dist/internal/status-page.js +66 -0
  223. package/dist/internal/status-page.js.map +1 -0
  224. package/dist/internal/store-signature.d.ts +15 -0
  225. package/dist/internal/store-signature.d.ts.map +1 -0
  226. package/dist/internal/store-signature.js +114 -0
  227. package/dist/internal/store-signature.js.map +1 -0
  228. package/dist/internal/vite-plugin-compat.d.ts +2 -2
  229. package/dist/internal/vite-plugin-compat.js +2 -2
  230. package/dist/internal/web-app.d.ts +26 -0
  231. package/dist/internal/web-app.d.ts.map +1 -1
  232. package/dist/internal/web-app.js +79 -3
  233. package/dist/internal/web-app.js.map +1 -1
  234. package/dist/islands-swap.d.ts +41 -0
  235. package/dist/islands-swap.d.ts.map +1 -0
  236. package/dist/islands-swap.js +270 -0
  237. package/dist/islands-swap.js.map +1 -0
  238. package/dist/islands.d.ts +86 -24
  239. package/dist/islands.d.ts.map +1 -1
  240. package/dist/islands.js +138 -78
  241. package/dist/islands.js.map +1 -1
  242. package/dist/isr.d.ts +67 -13
  243. package/dist/isr.d.ts.map +1 -1
  244. package/dist/isr.js +247 -99
  245. package/dist/isr.js.map +1 -1
  246. package/dist/keep-alive.d.ts +147 -0
  247. package/dist/keep-alive.d.ts.map +1 -0
  248. package/dist/keep-alive.js +1102 -0
  249. package/dist/keep-alive.js.map +1 -0
  250. package/dist/live/bus.d.ts +70 -0
  251. package/dist/live/bus.d.ts.map +1 -0
  252. package/dist/live/bus.js +55 -0
  253. package/dist/live/bus.js.map +1 -0
  254. package/dist/live/core.d.ts +25 -0
  255. package/dist/live/core.d.ts.map +1 -0
  256. package/dist/live/core.js +29 -0
  257. package/dist/live/core.js.map +1 -0
  258. package/dist/live/history.d.ts +38 -0
  259. package/dist/live/history.d.ts.map +1 -0
  260. package/dist/live/history.js +104 -0
  261. package/dist/live/history.js.map +1 -0
  262. package/dist/live/hub-protocol.d.ts +46 -0
  263. package/dist/live/hub-protocol.d.ts.map +1 -0
  264. package/dist/live/hub-protocol.js +108 -0
  265. package/dist/live/hub-protocol.js.map +1 -0
  266. package/dist/live/hub.d.ts +185 -0
  267. package/dist/live/hub.d.ts.map +1 -0
  268. package/dist/live/hub.js +544 -0
  269. package/dist/live/hub.js.map +1 -0
  270. package/dist/live/keyring.d.ts +92 -0
  271. package/dist/live/keyring.d.ts.map +1 -0
  272. package/dist/live/keyring.js +265 -0
  273. package/dist/live/keyring.js.map +1 -0
  274. package/dist/live/read-capped.d.ts +6 -0
  275. package/dist/live/read-capped.d.ts.map +1 -0
  276. package/dist/live/read-capped.js +42 -0
  277. package/dist/live/read-capped.js.map +1 -0
  278. package/dist/live/revocations.d.ts +52 -0
  279. package/dist/live/revocations.d.ts.map +1 -0
  280. package/dist/live/revocations.js +116 -0
  281. package/dist/live/revocations.js.map +1 -0
  282. package/dist/live/settings.d.ts +14 -0
  283. package/dist/live/settings.d.ts.map +1 -0
  284. package/dist/live/settings.js +38 -0
  285. package/dist/live/settings.js.map +1 -0
  286. package/dist/live-client.d.ts +34 -0
  287. package/dist/live-client.d.ts.map +1 -0
  288. package/dist/live-client.js +878 -0
  289. package/dist/live-client.js.map +1 -0
  290. package/dist/live-hub.d.ts +130 -0
  291. package/dist/live-hub.d.ts.map +1 -0
  292. package/dist/live-hub.js +507 -0
  293. package/dist/live-hub.js.map +1 -0
  294. package/dist/live-redis.d.ts +60 -0
  295. package/dist/live-redis.d.ts.map +1 -0
  296. package/dist/live-redis.js +212 -0
  297. package/dist/live-redis.js.map +1 -0
  298. package/dist/live.d.ts +150 -0
  299. package/dist/live.d.ts.map +1 -0
  300. package/dist/live.js +972 -0
  301. package/dist/live.js.map +1 -0
  302. package/dist/manifest.d.ts +113 -2
  303. package/dist/manifest.d.ts.map +1 -1
  304. package/dist/manifest.js +109 -5
  305. package/dist/manifest.js.map +1 -1
  306. package/dist/navigation.d.ts +9 -3
  307. package/dist/navigation.d.ts.map +1 -1
  308. package/dist/navigation.js +13 -3
  309. package/dist/navigation.js.map +1 -1
  310. package/dist/plugins/vite-zone-guard.d.ts +6 -0
  311. package/dist/plugins/vite-zone-guard.d.ts.map +1 -1
  312. package/dist/plugins/vite-zone-guard.js +8 -2
  313. package/dist/plugins/vite-zone-guard.js.map +1 -1
  314. package/dist/ppr.d.ts +73 -0
  315. package/dist/ppr.d.ts.map +1 -0
  316. package/dist/ppr.js +76 -0
  317. package/dist/ppr.js.map +1 -0
  318. package/dist/query.d.ts +8 -13
  319. package/dist/query.d.ts.map +1 -1
  320. package/dist/query.js +112 -18
  321. package/dist/query.js.map +1 -1
  322. package/dist/render-seam.d.ts +32 -0
  323. package/dist/render-seam.d.ts.map +1 -1
  324. package/dist/render-seam.js +7 -0
  325. package/dist/render-seam.js.map +1 -1
  326. package/dist/route-types.d.ts.map +1 -1
  327. package/dist/route-types.js +2 -2
  328. package/dist/route-types.js.map +1 -1
  329. package/dist/router.d.ts +72 -4
  330. package/dist/router.d.ts.map +1 -1
  331. package/dist/router.js +416 -129
  332. package/dist/router.js.map +1 -1
  333. package/dist/server-components/build.d.ts.map +1 -1
  334. package/dist/server-components/build.js +3 -3
  335. package/dist/server-components/build.js.map +1 -1
  336. package/dist/server-components/client.d.ts +23 -1
  337. package/dist/server-components/client.d.ts.map +1 -1
  338. package/dist/server-components/client.js +92 -5
  339. package/dist/server-components/client.js.map +1 -1
  340. package/dist/server-components/definition.d.ts.map +1 -1
  341. package/dist/server-components/definition.js +2 -1
  342. package/dist/server-components/definition.js.map +1 -1
  343. package/dist/server-components/hole.d.ts +8 -0
  344. package/dist/server-components/hole.d.ts.map +1 -0
  345. package/dist/server-components/hole.js +20 -0
  346. package/dist/server-components/hole.js.map +1 -0
  347. package/dist/server-components/html-policy.d.ts.map +1 -1
  348. package/dist/server-components/html-policy.js +62 -4
  349. package/dist/server-components/html-policy.js.map +1 -1
  350. package/dist/server-components/html-scan.d.ts +17 -0
  351. package/dist/server-components/html-scan.d.ts.map +1 -0
  352. package/dist/server-components/html-scan.js +180 -0
  353. package/dist/server-components/html-scan.js.map +1 -0
  354. package/dist/server-components/html.d.ts.map +1 -1
  355. package/dist/server-components/html.js +19 -4
  356. package/dist/server-components/html.js.map +1 -1
  357. package/dist/server-components/router-integration.d.ts +4 -1
  358. package/dist/server-components/router-integration.d.ts.map +1 -1
  359. package/dist/server-components/router-integration.js +23 -2
  360. package/dist/server-components/router-integration.js.map +1 -1
  361. package/dist/server-components/runtime.d.ts +6 -1
  362. package/dist/server-components/runtime.d.ts.map +1 -1
  363. package/dist/server-components/runtime.js +88 -29
  364. package/dist/server-components/runtime.js.map +1 -1
  365. package/dist/server-components/state.d.ts +3 -1
  366. package/dist/server-components/state.d.ts.map +1 -1
  367. package/dist/server-components/state.js +50 -38
  368. package/dist/server-components/state.js.map +1 -1
  369. package/dist/server-components/types.d.ts +9 -0
  370. package/dist/server-components/types.d.ts.map +1 -1
  371. package/dist/server-components/vite.d.ts.map +1 -1
  372. package/dist/server-components/vite.js +26 -9
  373. package/dist/server-components/vite.js.map +1 -1
  374. package/dist/vite.d.ts +5 -1
  375. package/dist/vite.d.ts.map +1 -1
  376. package/dist/vite.js +58 -4
  377. package/dist/vite.js.map +1 -1
  378. package/package.json +48 -8
  379. package/src/boundary.ts +77 -5
  380. package/src/build-plan.ts +3 -0
  381. package/src/build-vite.ts +2 -0
  382. package/src/build.ts +20 -1
  383. package/src/client.ts +183 -23
  384. package/src/csp.ts +16 -2
  385. package/src/deferred.ts +11 -5
  386. package/src/dev-feed.ts +11 -2
  387. package/src/dev-session.ts +22 -11
  388. package/src/dev.ts +29 -8
  389. package/src/diagnostic-catalog.ts +53 -0
  390. package/src/diagnostic.ts +1 -0
  391. package/src/fn.ts +24 -7
  392. package/src/fs.ts +22 -8
  393. package/src/identity.ts +61 -0
  394. package/src/index.ts +20 -4
  395. package/src/internal/cdn/cloudflare.ts +2 -1
  396. package/src/internal/certified-components.ts +1 -1
  397. package/src/internal/codegen.ts +95 -9
  398. package/src/internal/dev-websocket.ts +230 -0
  399. package/src/internal/hole-runtime.ts +286 -0
  400. package/src/internal/identity-epoch.ts +239 -0
  401. package/src/internal/invalidation-contract.ts +35 -0
  402. package/src/internal/invalidation.ts +213 -0
  403. package/src/internal/island-bindings.ts +191 -0
  404. package/src/internal/island-list.ts +92 -0
  405. package/src/internal/island-mount.ts +68 -0
  406. package/src/internal/island-owner.ts +20 -0
  407. package/src/internal/island-resource.ts +76 -0
  408. package/src/internal/island-signals.ts +249 -0
  409. package/src/internal/keep-alive-table.ts +72 -0
  410. package/src/internal/live-contract.ts +111 -0
  411. package/src/internal/live-queries.ts +60 -0
  412. package/src/internal/page-events.ts +27 -0
  413. package/src/internal/page-execution.ts +605 -45
  414. package/src/internal/parity.ts +35 -6
  415. package/src/internal/ppr/admission.ts +300 -0
  416. package/src/internal/ppr/canary.ts +122 -0
  417. package/src/internal/ppr/compile-shell.ts +379 -0
  418. package/src/internal/ppr/contract.ts +265 -0
  419. package/src/internal/ppr/frames.ts +170 -0
  420. package/src/internal/ppr/integration.ts +137 -0
  421. package/src/internal/ppr/options.ts +221 -0
  422. package/src/internal/ppr/route-rules.ts +77 -0
  423. package/src/internal/ppr/runtime.ts +39 -0
  424. package/src/internal/ppr/server.ts +1766 -0
  425. package/src/internal/ppr/shell-store.ts +152 -0
  426. package/src/internal/prerendered-pages.ts +7 -4
  427. package/src/internal/present.ts +20 -0
  428. package/src/internal/query-digest.ts +34 -0
  429. package/src/internal/query-runtime.ts +14 -1
  430. package/src/internal/query-seam.ts +48 -0
  431. package/src/internal/render-document.ts +158 -113
  432. package/src/internal/request-abort.ts +26 -0
  433. package/src/internal/server-boundary.ts +10 -1
  434. package/src/internal/share-data.ts +136 -0
  435. package/src/internal/status-page.ts +95 -0
  436. package/src/internal/store-signature.ts +149 -0
  437. package/src/internal/vite-plugin-compat.ts +2 -2
  438. package/src/internal/web-app.ts +123 -3
  439. package/src/islands-swap.ts +283 -0
  440. package/src/islands.ts +169 -72
  441. package/src/isr.ts +348 -106
  442. package/src/keep-alive.ts +1354 -0
  443. package/src/live/bus.ts +104 -0
  444. package/src/live/core.ts +52 -0
  445. package/src/live/history.ts +109 -0
  446. package/src/live/hub-protocol.ts +133 -0
  447. package/src/live/hub.ts +620 -0
  448. package/src/live/keyring.ts +384 -0
  449. package/src/live/read-capped.ts +36 -0
  450. package/src/live/revocations.ts +155 -0
  451. package/src/live/settings.ts +46 -0
  452. package/src/live-client.ts +1011 -0
  453. package/src/live-hub.ts +658 -0
  454. package/src/live-redis.ts +284 -0
  455. package/src/live.ts +1257 -0
  456. package/src/manifest.ts +274 -6
  457. package/src/navigation.ts +14 -2
  458. package/src/plugins/vite-zone-guard.ts +15 -2
  459. package/src/ppr.ts +153 -0
  460. package/src/query.ts +147 -20
  461. package/src/render-seam.ts +37 -0
  462. package/src/route-types.ts +3 -1
  463. package/src/router.ts +534 -132
  464. package/src/server-components/build.ts +9 -3
  465. package/src/server-components/client.ts +89 -5
  466. package/src/server-components/definition.ts +2 -1
  467. package/src/server-components/hole.ts +23 -0
  468. package/src/server-components/html-policy.ts +63 -4
  469. package/src/server-components/html-scan.ts +159 -0
  470. package/src/server-components/html.ts +18 -4
  471. package/src/server-components/router-integration.ts +21 -2
  472. package/src/server-components/runtime.ts +114 -31
  473. package/src/server-components/state.ts +66 -44
  474. package/src/server-components/types.ts +16 -0
  475. package/src/server-components/vite.ts +33 -14
  476. package/src/vite.ts +75 -6
  477. package/dist/nano.d.ts +0 -125
  478. package/dist/nano.d.ts.map +0 -1
  479. package/dist/nano.js +0 -190
  480. package/dist/nano.js.map +0 -1
  481. package/src/nano.ts +0 -246
@@ -10,21 +10,24 @@ import {
10
10
  status as statusResult,
11
11
  } from "@nifrajs/core/server"
12
12
  import {
13
+ type BoundaryRegistration,
13
14
  type BoundaryRequestCtx,
14
15
  type BoundaryStates,
15
16
  boundaryDescriptors,
16
17
  resolveStaticBoundaries,
18
+ type ShellCtx,
17
19
  type StaticBoundaryCache,
18
20
  startDynamicBoundaries,
19
21
  } from "../boundary.ts"
20
22
  import type { CspPolicy } from "../csp.ts"
21
23
  import { type CssLoadingMode, DEFAULT_CSS_LOADING, normalizeCssLoading } from "../css-contract.ts"
22
- import { defer, ndjsonStream, prepareDeferred } from "../deferred.ts"
24
+ import { type Deferred, defer, ndjsonStream, prepareDeferred } from "../deferred.ts"
23
25
  import { isDraftEnabled } from "../draft.ts"
24
26
  import { routeTags } from "../isr.ts"
25
27
  import type {
26
28
  LayoutEntry,
27
29
  LoaderContext,
30
+ LoaderResponseControls,
28
31
  Manifest,
29
32
  Meta,
30
33
  MetaArgs,
@@ -35,10 +38,14 @@ import type {
35
38
  } from "../manifest.ts"
36
39
  import type { NonceResolver } from "../nonce.ts"
37
40
  import type { MatchChain, PageLocale, PageLocales, RenderAdapter } from "../render-seam.ts"
41
+ import { SWAP_HEADER } from "../render-seam.ts"
38
42
  import {
39
43
  createMatcher,
40
44
  DATA_HEADER,
45
+ HOLE_FILL_HEADER,
46
+ KEEP_HOLES_HEADER,
41
47
  NAV_FROM_HEADER,
48
+ PREFETCH_HEADER,
42
49
  REDIRECT_HEADER,
43
50
  RETAIN_HEADER,
44
51
  REVALIDATE_HEADER,
@@ -46,25 +53,36 @@ import {
46
53
  } from "../router.ts"
47
54
  import { searchOf, searchOfChain } from "../search.ts"
48
55
  import { mergeHeads, resolveChainMeta } from "./head-merge.ts"
56
+ import { keepAliveTable, slotDepths } from "./keep-alive-table.ts"
57
+ import {
58
+ holeOwner,
59
+ type LiveEnvelope,
60
+ type LiveFinish,
61
+ type LiveLoad,
62
+ type LiveRouteMatch,
63
+ } from "./live-contract.ts"
49
64
  import { prepareModules } from "./module-preparation.ts"
65
+ import {
66
+ type HoleLive,
67
+ isHole,
68
+ type PprIntegration,
69
+ type PprRequest,
70
+ serverHoleComponent,
71
+ } from "./ppr/contract.ts"
50
72
  import {
51
73
  EMPTY_RETAIN,
52
- type HeadersLike,
53
74
  isControlFlow,
54
75
  isResponseResult,
55
- isStatusSignal,
76
+ isRevalidateResult,
56
77
  type LoadedLayoutModules,
57
78
  layoutErrorId,
58
- notFound,
59
79
  type RenderAssemblyCache,
60
80
  type RenderedPage,
61
81
  type RenderPageOptions,
62
82
  type RevalidateResult,
63
83
  redirect,
64
84
  renderPageResult,
65
- STATUS_SIGNAL,
66
85
  STATUS_TEXT,
67
- type StatusSignal,
68
86
  scopeParams,
69
87
  tagLayoutError,
70
88
  withDuplicateInstanceHint,
@@ -82,6 +100,13 @@ import {
82
100
  privateOutcome,
83
101
  varyOnDataHeader,
84
102
  } from "./response-controls.ts"
103
+ import {
104
+ type HeadersLike,
105
+ isStatusSignal,
106
+ notFound,
107
+ STATUS_SIGNAL,
108
+ type StatusSignal,
109
+ } from "./status-page.ts"
85
110
 
86
111
  export type { NonceResolver } from "../nonce.ts"
87
112
 
@@ -101,13 +126,145 @@ export interface PageRouteContext<Env = unknown> {
101
126
  }
102
127
 
103
128
  /** Whether `c` is a client navigation's data request. On Node, `req.headers` builds every header. */
129
+ // The slot name is echoed in a response header, so only a plain identifier is honored.
130
+ const SLOT_NAME = /^[A-Za-z][\w-]{0,63}$/
131
+
132
+ /** The slot an `x-nifra-swap` request names, when it is a valid slot name. */
133
+ function swapSlotOf(req: Request): string | undefined {
134
+ const name = req.headers.get(SWAP_HEADER)
135
+ return name !== null && SLOT_NAME.test(name) ? name : undefined
136
+ }
137
+
138
+ /** The `swapSlot` render option for `req`, empty when it is not a swap request. */
139
+ function swapOption(req: Request): { readonly swapSlot?: string } {
140
+ const swapSlot = swapSlotOf(req)
141
+ return swapSlot === undefined ? {} : { swapSlot }
142
+ }
143
+
144
+ const headerOf = (c: PageRouteContext<unknown>, name: string): string | null =>
145
+ c.header !== undefined ? c.header(name) : c.req.headers.get(name)
146
+
104
147
  function isDataRequest(c: PageRouteContext<unknown>): boolean {
105
- return (c.header !== undefined ? c.header(DATA_HEADER) : c.req.headers.get(DATA_HEADER)) !== null
148
+ return headerOf(c, DATA_HEADER) !== null
149
+ }
150
+
151
+ /** The live owners of one response's holes, settled once the last hole that ran does. */
152
+ interface HoleOwners {
153
+ readonly kept: readonly string[]
154
+ readonly loaded: Promise<readonly string[]>
155
+ }
156
+
157
+ /**
158
+ * A data request's hole refresh: the hosts the client holds, which reload as fills, the holes it
159
+ * keeps, and in a live load each hole's owner. A name the route does not declare is a 400.
160
+ */
161
+ function holeRefresh(
162
+ c: PageRouteContext<unknown>,
163
+ request: PprRequest,
164
+ live: LiveLoad | undefined,
165
+ ): { readonly request: PprRequest; readonly owners?: HoleOwners } | Response {
166
+ if (!isDataRequest(c)) return { request }
167
+ const declared = new Set(request.holes.map((hole) => hole.name))
168
+ const namesOf = (header: string): Set<string> | undefined => {
169
+ const value = headerOf(c, header)
170
+ const names = new Set<string>()
171
+ if (value === null) return names
172
+ for (const name of value.split(",")) {
173
+ const trimmed = name.trim()
174
+ if (declared.has(trimmed)) names.add(trimmed)
175
+ // A live load's names come from a grant this server sealed and the page's own client: one
176
+ // naming a hole an older build declared is skipped.
177
+ else if (live === undefined) return undefined
178
+ }
179
+ return names
180
+ }
181
+ const kept = namesOf(KEEP_HOLES_HEADER)
182
+ const fill = namesOf(HOLE_FILL_HEADER)
183
+ if (kept === undefined || fill === undefined) {
184
+ return new Response("Bad Request: the route has no such hole", {
185
+ status: 400,
186
+ headers: { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" },
187
+ })
188
+ }
189
+ const refreshed: PprRequest = {
190
+ ...request,
191
+ ...(fill.size === 0 ? {} : { fill }),
192
+ ...(kept.size === 0 ? {} : { kept }),
193
+ }
194
+ if (live === undefined) return { request: refreshed }
195
+ const routeId = request.routeId
196
+ let finish: (loaded: readonly string[]) => void = () => {}
197
+ const loaded = new Promise<readonly string[]>((resolve) => {
198
+ finish = resolve
199
+ })
200
+ const holeLive: HoleLive = {
201
+ collector: (hole) => live.collector(holeOwner(hole, routeId)),
202
+ seal: (hole) => live.seal(holeOwner(hole, routeId)),
203
+ done: (names) => finish(names.map((hole) => holeOwner(hole, routeId))),
204
+ }
205
+ return {
206
+ request: { ...refreshed, live: holeLive },
207
+ owners: { kept: [...kept].map((hole) => holeOwner(hole, routeId)), loaded },
208
+ }
106
209
  }
107
210
 
108
211
  /** The page slot of an `ssr = false` route with no `HydrateFallback`: a component that renders nothing. */
109
212
  const EMPTY_LEAF = (): null => null
110
213
 
214
+ /** `ctx.live` outside a live load: keys are declared for nobody. */
215
+ const NO_LIVE = (): void => {}
216
+
217
+ const headersSent = (): never => {
218
+ throw new TypeError(
219
+ "[nifra/web] ppr: a hole's server component cannot set a cookie: the shell already sent the headers",
220
+ )
221
+ }
222
+
223
+ /** A hole's response headers went out with the shell, so its server component sets none. */
224
+ const SENT_CONTROLS: LoaderResponseControls = Object.freeze({
225
+ headers: Object.freeze({}),
226
+ cookie: headersSent,
227
+ deleteCookie: headersSent,
228
+ })
229
+
230
+ /**
231
+ * The envelope's `live` member. With holes running it trails them: one grant, issued once the last hole
232
+ * settled, holding each hole that loaded and each the client kept.
233
+ */
234
+ const liveEnvelopeOf = (
235
+ live: LiveLoad,
236
+ owners: LiveFinish,
237
+ holes: HoleOwners | undefined,
238
+ ): Promise<LiveEnvelope> | Deferred<LiveEnvelope> =>
239
+ holes === undefined
240
+ ? live.finish(owners)
241
+ : defer(
242
+ holes.loaded.then((loaded) =>
243
+ live.finish({
244
+ ...owners,
245
+ fresh: [...owners.fresh, ...loaded],
246
+ retained: [...owners.retained, ...holes.kept],
247
+ }),
248
+ ),
249
+ )
250
+
251
+ /** Run one owner's loader with its `ctx.live`, closing the collector once the loader settles. */
252
+ const runLiveLoader = (
253
+ live: LiveLoad,
254
+ owner: string,
255
+ loader: (ctx: LoaderContext) => unknown,
256
+ ctx: LoaderContext,
257
+ ): Promise<unknown> => {
258
+ let value: unknown
259
+ try {
260
+ value = loader({ ...ctx, live: live.collector(owner) })
261
+ } catch (err) {
262
+ live.seal(owner)
263
+ return Promise.reject(err)
264
+ }
265
+ return Promise.resolve(value).finally(() => live.seal(owner))
266
+ }
267
+
111
268
  /** The request-scoping seam of an in-process `api` (see `inProcessClient`), if it carries one. */
112
269
  function platformBinderOf(api: unknown): BackendPlatformBinder | undefined {
113
270
  if ((typeof api !== "object" && typeof api !== "function") || api === null) return undefined
@@ -244,6 +401,18 @@ export interface PageExecutionOptions<Env = unknown> {
244
401
  readonly csp?: CspPolicy
245
402
  /** Set once a cache wrapper attaches; until then a route's freshness and tags stay off the response. */
246
403
  readonly cacheChannel?: { readonly enabled: boolean }
404
+ /**
405
+ * Run, or hand to the cache wrapper, what an action's `revalidate()` made stale. A promise is awaited
406
+ * before the action answers; `undefined` means nothing runs it. Never throws, never changes the answer.
407
+ */
408
+ readonly invalidate?: (
409
+ revalidation: RevalidateResult<unknown>,
410
+ request: Request,
411
+ ) => Promise<void> | undefined
412
+ /** The live load a request belongs to; only set when the app configures `live`. */
413
+ readonly liveLoadOf?: ((request: Request) => LiveLoad | undefined) | undefined
414
+ /** The seed a rendered document collects its keys into; only set when the app configures `live`. */
415
+ readonly liveSeedOf?: ((request: Request, routeId: string) => LiveLoad | undefined) | undefined
247
416
  readonly onLoaderError?: (
248
417
  error: unknown,
249
418
  ctx: {
@@ -255,6 +424,8 @@ export interface PageExecutionOptions<Env = unknown> {
255
424
  /** Logs a failure an `_error` boundary answered. Without a boundary the error propagates and the
256
425
  * server logs it, so this runs only where the server never sees the throw. */
257
426
  readonly logBoundaryError?: (error: unknown, request: Request) => void
427
+ /** Experimental partial prerendering: routes with a `shellLoader` stream a shared shell, then holes. */
428
+ readonly ppr?: PprIntegration
258
429
  }
259
430
 
260
431
  type PageResponse = Response | ResponseResult | RenderedPage
@@ -264,6 +435,8 @@ export interface PageRequestExecutor<Env = unknown> {
264
435
  post(route: RouteEntry): (ctx: PageRouteContext<Env>) => Promise<PageResponse>
265
436
  /** Answer a URL no route matched: the nearest nested `_404` for it, else the root one. */
266
437
  fallback(ctx: PageRouteContext<Env>): Promise<PageResponse>
438
+ /** The route a `pathname + search` renders, with its layout chain. */
439
+ matchRoute(path: string): LiveRouteMatch | null
267
440
  }
268
441
 
269
442
  /** One URL pattern a nested `_404` answers, as the executor indexes it. */
@@ -355,11 +528,13 @@ export function createPageRequestExecutor<Env = unknown>(
355
528
  const { i18n } = options
356
529
  const pageLocales = new Map(i18n?.locales.locales.map((locale) => [locale.key, locale]))
357
530
  /** The request's locale for a document, with the served locales the client names locales from. */
358
- const localeOption = (req: Request): { locale?: PageLocale; locales?: PageLocales } => {
531
+ const localeFor = (key: string): { locale?: PageLocale; locales?: PageLocales } => {
359
532
  if (i18n === undefined) return {}
360
- const locale = pageLocales.get(i18n.localeOf(pathOf(req)))
533
+ const locale = pageLocales.get(key)
361
534
  return locale === undefined ? { locales: i18n.locales } : { locale, locales: i18n.locales }
362
535
  }
536
+ const localeOption = (req: Request): { locale?: PageLocale; locales?: PageLocales } =>
537
+ i18n === undefined ? {} : localeFor(i18n.localeOf(pathOf(req)))
363
538
  /** Whether a route sits under a `[lang]` or `[[lang]]` directory, which the locale routing checks. */
364
539
  const localeGuarded = (route: RouteEntry): boolean => {
365
540
  if (i18n === undefined) return false
@@ -372,6 +547,52 @@ export function createPageRequestExecutor<Env = unknown>(
372
547
  manifest.routes.map((route) => ({ routeId: route.id, pattern: route.pattern })),
373
548
  )
374
549
  const routeById = new Map(manifest.routes.map((route) => [route.id, route]))
550
+ const liveLoadOf = options.liveLoadOf
551
+ const liveSeedOf = options.liveSeedOf
552
+
553
+ /** A live load holds one admission slot from before its middleware until its body is complete. */
554
+ const admitLive =
555
+ (run: (c: PageRouteContext<Env>, controls: PageResponseControls) => Promise<PageResponse>) =>
556
+ async (c: PageRouteContext<Env>, controls: PageResponseControls): Promise<PageResponse> => {
557
+ const live = liveLoadOf?.(c.req)
558
+ if (live === undefined) return run(c, controls)
559
+ const refused = live.admit()
560
+ if (refused !== undefined) return refused
561
+ let outcome: PageResponse
562
+ try {
563
+ outcome = await run(c, controls)
564
+ } catch (err) {
565
+ live.release()
566
+ throw err
567
+ }
568
+ if (outcome instanceof Response) return live.settle(outcome)
569
+ live.release()
570
+ return outcome
571
+ }
572
+
573
+ /** Which owners a successful live load ran and which it kept, by their wire ids. */
574
+ const liveOwners = (
575
+ route: RouteEntry,
576
+ modules: LoadedLayoutModules | undefined,
577
+ layoutData: readonly unknown[] | undefined,
578
+ retained: readonly number[],
579
+ mod: RouteModule,
580
+ skipPage: boolean,
581
+ ctx: LoaderContext,
582
+ ): LiveFinish => {
583
+ const fresh: string[] = []
584
+ const kept: string[] = []
585
+ for (let i = 0; i < (modules?.length ?? 0); i++) {
586
+ if (modules?.[i]?.loader === undefined) continue
587
+ const owner = `L:${route.layoutIds[i]}`
588
+ if (retained.includes(i)) kept.push(owner)
589
+ // A layout that answered with a status declared keys for data nobody is shown.
590
+ else if (!isStatusSignal(layoutData?.[i])) fresh.push(owner)
591
+ }
592
+ if (skipPage) kept.push(`P:${route.id}`)
593
+ else if (mod.loader !== undefined) fresh.push(`P:${route.id}`)
594
+ return { fresh, retained: kept, ctx }
595
+ }
375
596
 
376
597
  // A nested `_404` answers an unmatched URL from inside the catch-all, never as a route of its own:
377
598
  // a registered `/admin/*` would take URLs from a route the router only reaches by backtracking.
@@ -419,6 +640,19 @@ export function createPageRequestExecutor<Env = unknown>(
419
640
  ? Promise.resolve(false)
420
641
  : isDraftEnabled(req, options.draftSecret)
421
642
 
643
+ // Where the browser's keep-alive view puts slot wrappers, so the document it hydrates has them:
644
+ // nothing outside an app with a `keepAlive` route.
645
+ const keepTable = keepAliveTable(manifest)
646
+ const slotsFor = (
647
+ layoutIds: readonly string[],
648
+ routeId: string,
649
+ ): { slots?: readonly number[] } => {
650
+ // Every page of a keep-alive app renders through the adapter's keep-alive root, wrappers or none,
651
+ // as the browser hydrates it.
652
+ if (keepTable === undefined) return {}
653
+ return { slots: slotDepths(keepTable, layoutIds, routeId) }
654
+ }
655
+
422
656
  const preloadOf = (id: string): { preload?: readonly string[] } => {
423
657
  const chunks = options.routePreload?.[id]
424
658
  return chunks ? { preload: chunks } : {}
@@ -521,6 +755,7 @@ export function createPageRequestExecutor<Env = unknown>(
521
755
  controls: PageResponseControls,
522
756
  retain?: RetainContext,
523
757
  gated?: GatedLayouts,
758
+ live?: LiveLoad,
524
759
  ): Promise<LayoutRun> => {
525
760
  if (route.layoutIds.length === 0) return emptyLayoutRun
526
761
  const modules = gated?.modules ?? (await loadLayoutModules(route))
@@ -552,14 +787,25 @@ export function createPageRequestExecutor<Env = unknown>(
552
787
  params: scopeParams(ctx.params, route.layoutParams?.[i]),
553
788
  set: controls.scope(i),
554
789
  }
790
+ const layoutId = route.layoutIds[i] as string
555
791
  if (modules[i]?.gate === true) {
556
- results[i] = gated === undefined ? await runGate(route, i, loader, scoped) : gated.gates[i]
792
+ results[i] =
793
+ gated !== undefined
794
+ ? gated.gates[i]
795
+ : await runGate(
796
+ route,
797
+ i,
798
+ live === undefined
799
+ ? loader
800
+ : (gateCtx) => runLiveLoader(live, `L:${layoutId}`, loader, gateCtx),
801
+ scoped,
802
+ )
557
803
  continue
558
804
  }
559
- const layoutId = route.layoutIds[i] as string
560
805
  const index = i
561
806
  try {
562
- const value = loader(scoped)
807
+ const value =
808
+ live === undefined ? loader(scoped) : runLiveLoader(live, `L:${layoutId}`, loader, scoped)
563
809
  // Scalars settle synchronously. Objects keep await's intrinsic promise/thenable
564
810
  // assimilation: property-presence and prototype probes are unsafe for proxies.
565
811
  if (value !== null && (typeof value === "object" || typeof value === "function")) {
@@ -792,6 +1038,12 @@ export function createPageRequestExecutor<Env = unknown>(
792
1038
  const dirOfId = (id: string, suffix: string): string =>
793
1039
  id === suffix ? "" : id.slice(0, id.length - suffix.length - 1)
794
1040
 
1041
+ /** The server's error log for one request, where a rejected `defer()` value is written. */
1042
+ const deferredLogOf = (req: Request): ((error: unknown) => void) | undefined => {
1043
+ const log = options.logBoundaryError
1044
+ return log === undefined ? undefined : (error) => log(error, req)
1045
+ }
1046
+
795
1047
  const boundaryFor = (route: RouteEntry, err: unknown): string | undefined => {
796
1048
  const ids = route.errorIds ?? []
797
1049
  const failing = layoutErrorId(err)
@@ -859,6 +1111,8 @@ export function createPageRequestExecutor<Env = unknown>(
859
1111
  routeId: errorId,
860
1112
  status: 500,
861
1113
  hydrate: false,
1114
+ signal: req.signal,
1115
+ logError: deferredLogOf(req),
862
1116
  ...(layoutData === undefined ? {} : { layoutData }),
863
1117
  ...(personalized || layoutData !== undefined ? { headers: PRIVATE_PAGE_HEADERS } : {}),
864
1118
  ...(nonce === undefined ? {} : { nonce }),
@@ -952,6 +1206,7 @@ export function createPageRequestExecutor<Env = unknown>(
952
1206
  draft,
953
1207
  search: loaderSearch(page.searchSchema, c.req),
954
1208
  set: controls.scope(PAGE_SCOPE),
1209
+ live: NO_LIVE,
955
1210
  }
956
1211
  if (route.middlewareIds !== undefined) await runMiddleware(route, ctx, controls)
957
1212
  const run = await runLayoutChain(route, ctx, controls)
@@ -1007,6 +1262,8 @@ export function createPageRequestExecutor<Env = unknown>(
1007
1262
  search: searchChainOf(modules, loaded, req),
1008
1263
  path: pathOf(req),
1009
1264
  status,
1265
+ signal: req.signal,
1266
+ logError: deferredLogOf(req),
1010
1267
  // The browser renders this page inside the root layout, so a page that had to render alone
1011
1268
  // stays server-only, as a nested `_404` does.
1012
1269
  ...(wraps && chain === undefined ? { hydrate: false } : {}),
@@ -1021,6 +1278,7 @@ export function createPageRequestExecutor<Env = unknown>(
1021
1278
  ...copyOption,
1022
1279
  ...localeOption(req),
1023
1280
  ...titleOption,
1281
+ ...slotsFor(layoutIds, routeId),
1024
1282
  }
1025
1283
  if (chain === undefined) return renderPageResult(statusPage)
1026
1284
  try {
@@ -1091,6 +1349,8 @@ export function createPageRequestExecutor<Env = unknown>(
1091
1349
  responseHeaders: Record<string, string> | undefined,
1092
1350
  personalized: boolean,
1093
1351
  serverComponents?: import("../server-components/types.ts").ComponentState,
1352
+ live?: LiveEnvelope | Deferred<LiveEnvelope>,
1353
+ liveSeed?: LiveEnvelope,
1094
1354
  ): Promise<PageResponse> => {
1095
1355
  // Every layout loader settled before the page rendered, so a status or error page can reuse them.
1096
1356
  const settled: LayoutChainSource = async (_page, count) =>
@@ -1113,8 +1373,14 @@ export function createPageRequestExecutor<Env = unknown>(
1113
1373
  ...deferred,
1114
1374
  ...(boundarySplit === undefined ? [] : boundarySplit.deferred),
1115
1375
  ]
1376
+ // Deferred when holes run: their owners are known only once the last one settles.
1377
+ const liveSplit = live === undefined ? undefined : prepareDeferred(live, allDeferred.length)
1378
+ if (liveSplit !== undefined) allDeferred.push(...liveSplit.deferred)
1116
1379
  const payload =
1117
- layoutData === undefined && boundarySplit === undefined && serverComponents === undefined
1380
+ layoutData === undefined &&
1381
+ boundarySplit === undefined &&
1382
+ serverComponents === undefined &&
1383
+ live === undefined
1118
1384
  ? pageSplit.forClient
1119
1385
  : {
1120
1386
  v: 1 as const,
@@ -1123,16 +1389,20 @@ export function createPageRequestExecutor<Env = unknown>(
1123
1389
  retained: layoutRetained,
1124
1390
  ...(boundarySplit === undefined ? {} : { boundaries: boundarySplit.forClient }),
1125
1391
  ...(serverComponents === undefined ? {} : { serverComponents }),
1392
+ ...(liveSplit === undefined ? {} : { live: liveSplit.forClient }),
1126
1393
  }
1127
1394
  if (allDeferred.length === 0) {
1128
1395
  return Response.json(payload ?? null, { headers: DATA_RESPONSE_HEADERS })
1129
1396
  }
1130
- return new Response(ndjsonStream(payload, allDeferred), {
1131
- headers: {
1132
- "content-type": "application/x-ndjson; charset=utf-8",
1133
- ...DATA_RESPONSE_HEADERS,
1397
+ return new Response(
1398
+ ndjsonStream(payload, allDeferred, undefined, req.signal, deferredLogOf(req)),
1399
+ {
1400
+ headers: {
1401
+ "content-type": "application/x-ndjson; charset=utf-8",
1402
+ ...DATA_RESPONSE_HEADERS,
1403
+ },
1134
1404
  },
1135
- })
1405
+ )
1136
1406
  }
1137
1407
 
1138
1408
  const nonce = options.nonce === undefined ? undefined : await resolveNonce(req, env)
@@ -1164,13 +1434,19 @@ export function createPageRequestExecutor<Env = unknown>(
1164
1434
  search: searchChainOf(layoutModules, mod, req),
1165
1435
  matchChain: matchChainOf(route.layoutIds, layoutModules, route.id, mod),
1166
1436
  hydrate: mod.hydrate !== false,
1437
+ signal: req.signal,
1438
+ logError: deferredLogOf(req),
1167
1439
  ...preloadOf(route.id),
1168
1440
  ...stylesOf(route.id),
1169
1441
  ...appDocumentOptions,
1170
1442
  ...localeOption(req),
1171
1443
  }
1172
1444
  if (serverComponents !== undefined) page.serverComponents = serverComponents
1445
+ if (liveSeed?.grant !== undefined) page.live = liveSeed
1173
1446
  if (progressiveActionData.has(req)) page.actionData = progressiveActionData.get(req)
1447
+ const swapSlot = swapSlotOf(req)
1448
+ if (swapSlot !== undefined) page.swapSlot = swapSlot
1449
+ if (keepTable !== undefined) Object.assign(page, slotsFor(route.layoutIds, route.id))
1174
1450
  if (layoutData !== undefined) page.layoutData = layoutData
1175
1451
  if (boundaryStates !== undefined) page.boundaries = boundaryStates
1176
1452
  if (responseHeaders !== undefined) page.headers = responseHeaders
@@ -1293,6 +1569,8 @@ export function createPageRequestExecutor<Env = unknown>(
1293
1569
  matchChain: matchChainOf(page.layoutIds, layoutModules, id, mod),
1294
1570
  status: 404,
1295
1571
  hydrate: false,
1572
+ signal: req.signal,
1573
+ logError: deferredLogOf(req),
1296
1574
  ...localeOption(req),
1297
1575
  ...(layoutData !== undefined ? { layoutData } : {}),
1298
1576
  ...stylesOf(id),
@@ -1382,6 +1660,113 @@ export function createPageRequestExecutor<Env = unknown>(
1382
1660
  }
1383
1661
  }
1384
1662
 
1663
+ const pprRequestFor = (
1664
+ c: PageRouteContext<Env>,
1665
+ route: RouteEntry,
1666
+ mod: RouteModule,
1667
+ layouts: LoadedLayoutModules,
1668
+ boundaries: readonly BoundaryRegistration[],
1669
+ shellLoader: NonNullable<RouteModule["shellLoader"]>,
1670
+ requestApi: unknown,
1671
+ draft: boolean,
1672
+ search: Record<string, unknown>,
1673
+ ): PprRequest => {
1674
+ const path = urlPartsFor(c.req).pathname
1675
+ const locale = i18n?.localeOf(path)
1676
+ const params = c.params
1677
+ const shellOrigin = options.ppr?.origin
1678
+ const variations =
1679
+ options.ppr?.variationsOf(mod.shellVariations, urlPartsFor(c.req).search) ?? {}
1680
+ const shellSearch = options.ppr?.variationQuery(variations) ?? ""
1681
+ // What a shared entry's tags read: its key's parts, never the Host or an undeclared query, which
1682
+ // the first visitor of a key could set so a later purge misses the entry.
1683
+ const shellUrl = (): URL => {
1684
+ const url = new URL(shellOrigin ?? "http://shell.invalid")
1685
+ url.pathname = path
1686
+ url.search = shellSearch
1687
+ return url
1688
+ }
1689
+ // The unbound `api`: a shell is built for every visitor, so no visitor's platform reaches it.
1690
+ const shellContext: Omit<ShellCtx, "signal"> =
1691
+ shellOrigin === undefined
1692
+ ? { phase: "shell", params, locale, variations, api }
1693
+ : { phase: "shell", params, locale, variations, api, origin: shellOrigin }
1694
+ return {
1695
+ routeId: route.id,
1696
+ path,
1697
+ locale,
1698
+ variations,
1699
+ request: c.req,
1700
+ holes: boundaries.filter(isHole),
1701
+ holeContext: { request: c.req, params, api: requestApi, env: c.env, draft, search },
1702
+ shellContext,
1703
+ revalidate: mod.revalidate,
1704
+ waitUntil: c.waitUntil,
1705
+ tags: () =>
1706
+ mod.revalidateTags === undefined
1707
+ ? []
1708
+ : routeTags(mod.revalidateTags, { params, url: shellUrl() }, route.id),
1709
+ holeTags: (hole) =>
1710
+ hole.revalidateTags === undefined
1711
+ ? []
1712
+ : routeTags(hole.revalidateTags, { params, url: shellUrl() }, `${route.id}#${hole.name}`),
1713
+ loadShell: async (signal) => shellLoader({ ...shellContext, signal }),
1714
+ renderShellDocument: async (shellData, states) => {
1715
+ const statics = boundaries.some((boundary) => boundary.mode === "static")
1716
+ ? await resolveStaticBoundaries(
1717
+ boundaries,
1718
+ { phase: "build" },
1719
+ options.staticBoundaryCache,
1720
+ )
1721
+ : undefined
1722
+ const { chain, head } = resolveChainAndHead(
1723
+ layouts,
1724
+ undefined,
1725
+ mod,
1726
+ // A Host header is the client's to choose, so a shared shell's meta gets the configured one.
1727
+ { data: shellData, pageData: shellData, params, origin: shellOrigin ?? "" },
1728
+ serverLeafOf(route, mod),
1729
+ )
1730
+ // Nothing here reads the request beyond its pathname: no origin, search, nonce or headers.
1731
+ const page: DocumentInput = {
1732
+ adapter,
1733
+ chain,
1734
+ data: shellData,
1735
+ head,
1736
+ clientEntry,
1737
+ routeId: route.id,
1738
+ params,
1739
+ path,
1740
+ search: searchOfChain(
1741
+ [...layouts.map((m) => m.searchSchema), mod.searchSchema],
1742
+ shellSearch === "" ? "" : `?${shellSearch}`,
1743
+ ),
1744
+ matchChain: matchChainOf(route.layoutIds, layouts, route.id, mod),
1745
+ hydrate: mod.hydrate !== false,
1746
+ boundaries: { ...statics, ...states },
1747
+ ...preloadOf(route.id),
1748
+ ...stylesOf(route.id),
1749
+ ...appDocumentOptions,
1750
+ ...(locale === undefined ? {} : localeFor(locale)),
1751
+ ...slotsFor(route.layoutIds, route.id),
1752
+ }
1753
+ if (mod.islandScripts !== undefined) page.islandScripts = mod.islandScripts
1754
+ const result = await renderPageResult(page)
1755
+ // A buffered page hands over its string: a Response round trip to read it back cost more.
1756
+ const rendered = result.toNodeBody?.() ?? result.toResponse()
1757
+ if (rendered.status !== 200) {
1758
+ throw new Error(
1759
+ `[nifra/web] ppr: the shell of "${route.id}" rendered status ${rendered.status}`,
1760
+ )
1761
+ }
1762
+ if (rendered instanceof Response) return rendered.text()
1763
+ return typeof rendered.body === "string"
1764
+ ? rendered.body
1765
+ : new TextDecoder().decode(rendered.body)
1766
+ },
1767
+ }
1768
+ }
1769
+
1385
1770
  return {
1386
1771
  get: (route) => {
1387
1772
  const is404Fallback = options.staticFallbacks?.[route.pattern] === "404"
@@ -1414,7 +1799,11 @@ export function createPageRequestExecutor<Env = unknown>(
1414
1799
  return slot
1415
1800
  }
1416
1801
 
1417
- return withResponseControls(async (c, controls) => {
1802
+ const page = async (
1803
+ c: PageRouteContext<Env>,
1804
+ controls: PageResponseControls,
1805
+ ): Promise<PageResponse> => {
1806
+ let live = liveLoadOf?.(c.req)
1418
1807
  const componentRuntime = options.serverComponents
1419
1808
  const componentOwner = componentRuntime?.owns(route.id) === true
1420
1809
  let componentRequest:
@@ -1431,6 +1820,17 @@ export function createPageRequestExecutor<Env = unknown>(
1431
1820
  }
1432
1821
  const loaded = loadModule(route)
1433
1822
  const mod = cacheModules && !(loaded instanceof Promise) ? loaded : await loaded
1823
+ // A document's loaders declare their keys into a seed, which the page exchanges for a grant
1824
+ // once its socket opens, instead of loading itself again. Partially prerendered pages, whose
1825
+ // holes settle after the shell, and server-component routes load as before.
1826
+ const seeding =
1827
+ live === undefined &&
1828
+ liveSeedOf !== undefined &&
1829
+ !componentOwner &&
1830
+ componentRequest === undefined &&
1831
+ (options.ppr === undefined || mod.shellLoader === undefined) &&
1832
+ !isDataRequest(c)
1833
+ if (seeding) live = liveSeedOf(c.req, route.id)
1434
1834
  const draft = await draftFlag(c.req)
1435
1835
  // Unpublished content is the editor's alone: whatever cache-control a loader sets, no cache keeps it.
1436
1836
  if (draft) controls.personalize()
@@ -1443,6 +1843,9 @@ export function createPageRequestExecutor<Env = unknown>(
1443
1843
  let componentState: import("../server-components/types.ts").ComponentState | undefined
1444
1844
  const requestApi = apiFor(c)
1445
1845
  let run: LayoutRun | undefined
1846
+ let skipPage = false
1847
+ let liveCtx: LoaderContext
1848
+ let holeOwners: HoleOwners | undefined
1446
1849
  try {
1447
1850
  const ctx: LoaderContext = {
1448
1851
  params: c.params,
@@ -1453,15 +1856,26 @@ export function createPageRequestExecutor<Env = unknown>(
1453
1856
  draft,
1454
1857
  search: loaderSearch(mod.searchSchema, c.req),
1455
1858
  set: controls.scope(PAGE_SCOPE),
1859
+ live: NO_LIVE,
1456
1860
  }
1457
1861
  if (route.middlewareIds !== undefined || localeGuarded(route)) {
1458
1862
  await runMiddleware(route, ctx, controls)
1459
1863
  }
1864
+ // After middleware, before any loader: a grant issued to another viewer keeps nothing.
1865
+ const retains = live === undefined || (await live.bind(ctx))
1866
+ skipPage =
1867
+ live !== undefined &&
1868
+ retains &&
1869
+ !componentOwner &&
1870
+ live.retainPage &&
1871
+ live.routeId === route.id
1460
1872
  run = await runLayoutChain(
1461
1873
  route,
1462
1874
  ctx,
1463
1875
  controls,
1464
- componentOwner ? undefined : retainContextOf(c),
1876
+ componentOwner || !retains ? undefined : retainContextOf(c),
1877
+ undefined,
1878
+ live,
1465
1879
  )
1466
1880
  layoutModules = run.modules
1467
1881
  layoutRetained = run.retained
@@ -1474,8 +1888,89 @@ export function createPageRequestExecutor<Env = unknown>(
1474
1888
  : []
1475
1889
  if (boundaryDefinitions.length > 0) boundaryDescriptors(boundaryDefinitions)
1476
1890
  const effectiveSearch = searchChainOf(run.modules, mod, c.req)
1891
+ const ppr = options.ppr
1892
+ const shellLoader =
1893
+ componentRequest === undefined && !componentOwner ? mod.shellLoader : undefined
1894
+ let pprRequest: PprRequest | undefined
1895
+ // How a document that renders whole gets its holes: complete, unless no shell exists yet.
1896
+ let wholeHoles: "settled" | "deferred" = "settled"
1897
+ if (ppr !== undefined && shellLoader !== undefined) {
1898
+ ppr.assertRoute(route, run.modules, boundaryDefinitions)
1899
+ pprRequest = pprRequestFor(
1900
+ c,
1901
+ route,
1902
+ mod,
1903
+ run.modules,
1904
+ boundaryDefinitions,
1905
+ shellLoader,
1906
+ requestApi,
1907
+ draft,
1908
+ effectiveSearch,
1909
+ )
1910
+ const refresh = holeRefresh(c, pprRequest, live)
1911
+ if (refresh instanceof Response) return refresh
1912
+ pprRequest = refresh.request
1913
+ holeOwners = refresh.owners
1914
+ const components = options.serverComponents
1915
+ const renderHole = components?.hosts?.(route.id) === true ? components.hole : undefined
1916
+ if (
1917
+ renderHole === undefined &&
1918
+ pprRequest.holes.some((hole) => serverHoleComponent(hole.render) !== undefined)
1919
+ ) {
1920
+ throw new Error(
1921
+ `[nifra/web] ppr: route "${route.id}" has a hole that shows a server component; list the component under createServerComponents({ holes }) for this route`,
1922
+ )
1923
+ }
1924
+ if (renderHole !== undefined) {
1925
+ const loaderCtx = ctx
1926
+ pprRequest = {
1927
+ ...pprRequest,
1928
+ // The owner path's context, but with the hole's signal and no response to change.
1929
+ component: (name, component, input, signal) =>
1930
+ renderHole.call(
1931
+ components,
1932
+ route.id,
1933
+ component,
1934
+ name,
1935
+ input,
1936
+ Object.create(
1937
+ c,
1938
+ Object.getOwnPropertyDescriptors({
1939
+ ...loaderCtx,
1940
+ search: effectiveSearch,
1941
+ set: SENT_CONTROLS,
1942
+ signal,
1943
+ data: undefined,
1944
+ layoutData: [],
1945
+ }),
1946
+ ),
1947
+ ),
1948
+ }
1949
+ }
1950
+ // A draft is the editor's alone, so it never reads or fills the shared shell.
1951
+ if (!draft && !isDataRequest(c)) {
1952
+ const streamed = await ppr.stream(pprRequest, () => controls.documentHeaders())
1953
+ if (streamed instanceof Response) return streamed
1954
+ wholeHoles = streamed
1955
+ }
1956
+ // Its holes are one visitor's, so no cache may store the page they render into.
1957
+ controls.personalize()
1958
+ }
1959
+ // Every PPR hole runs through the PPR server, so each has admission and its own deadline.
1960
+ const holeStates =
1961
+ pprRequest === undefined || ppr === undefined
1962
+ ? undefined
1963
+ : ppr.server.holeStates(
1964
+ pprRequest,
1965
+ !isDataRequest(c)
1966
+ ? wholeHoles
1967
+ : c.req.headers.get(PREFETCH_HEADER) === null
1968
+ ? "deferred"
1969
+ : "omitted",
1970
+ c.req.signal,
1971
+ )
1477
1972
  const boundaryBatch =
1478
- boundaryDefinitions.length === 0
1973
+ boundaryDefinitions.length === 0 || holeStates !== undefined
1479
1974
  ? undefined
1480
1975
  : startDynamicBoundaries(boundaryDefinitions, {
1481
1976
  request: c.req,
@@ -1485,6 +1980,7 @@ export function createPageRequestExecutor<Env = unknown>(
1485
1980
  draft,
1486
1981
  search: effectiveSearch,
1487
1982
  signal: c.req.signal,
1983
+ live: NO_LIVE,
1488
1984
  } satisfies BoundaryRequestCtx)
1489
1985
  const staticBoundaryPromise =
1490
1986
  boundaryDefinitions.length === 0
@@ -1494,7 +1990,20 @@ export function createPageRequestExecutor<Env = unknown>(
1494
1990
  { phase: "build" },
1495
1991
  options.staticBoundaryCache,
1496
1992
  )
1497
- const pageResult = mod.loader ? mod.loader({ ...ctx, search: effectiveSearch }) : null
1993
+ const pageResult =
1994
+ pprRequest !== undefined && ppr !== undefined
1995
+ ? // A key with no shell yet must not build one through its data either.
1996
+ draft || wholeHoles === "deferred"
1997
+ ? pprRequest.loadShell(c.req.signal)
1998
+ : ppr.shellData(pprRequest, c.req.signal)
1999
+ : mod.loader === undefined || skipPage
2000
+ ? null
2001
+ : live === undefined
2002
+ ? mod.loader({ ...ctx, search: effectiveSearch })
2003
+ : runLiveLoader(live, `P:${route.id}`, mod.loader, {
2004
+ ...ctx,
2005
+ search: effectiveSearch,
2006
+ })
1498
2007
  const [pageData, , staticBoundaries] =
1499
2008
  run.pending === undefined && staticBoundaryPromise === undefined
1500
2009
  ? ([await pageResult, undefined, undefined] as const)
@@ -1502,7 +2011,9 @@ export function createPageRequestExecutor<Env = unknown>(
1502
2011
  ? await Promise.all([pageResult, run.pending])
1503
2012
  : await Promise.all([pageResult, run.pending, staticBoundaryPromise])
1504
2013
  data = pageData
1505
- layoutData = run.layoutData
2014
+ // A gate on a PPR route runs for its redirect or refusal only. Its data reaches no response,
2015
+ // so a soft navigation renders the layouts exactly as the shared shell did.
2016
+ layoutData = pprRequest === undefined ? run.layoutData : undefined
1506
2017
  if (componentOwner) {
1507
2018
  for (let i = 0; i < (layoutData?.length ?? 0); i++) {
1508
2019
  const outcome = layoutData?.[i]
@@ -1531,8 +2042,15 @@ export function createPageRequestExecutor<Env = unknown>(
1531
2042
  options.nonce === undefined ? undefined : await resolveNonce(c.req, c.env),
1532
2043
  )
1533
2044
  }
1534
- if (boundaryBatch !== undefined) {
1535
- const streamed = { ...boundaryBatch.initial, ...(staticBoundaries ?? {}) }
2045
+ if (holeStates !== undefined) {
2046
+ // The static resolver names every boundary, leaving the dynamic ones to the holes.
2047
+ boundaryStates = { ...staticBoundaries, ...(await holeStates) }
2048
+ } else if (boundaryBatch !== undefined) {
2049
+ // Each resolver returns a state for every boundary and marks the other's modes
2050
+ // `unresolved`, so a slot takes its state from the resolver of its own mode.
2051
+ const streamed = { ...boundaryBatch.initial }
2052
+ for (const [name, state] of Object.entries(staticBoundaries ?? {}))
2053
+ if (state.mode === "static") streamed[name] = state
1536
2054
  for (const entry of boundaryBatch.pending) {
1537
2055
  const initial = streamed[entry.name]
1538
2056
  if (initial !== undefined)
@@ -1545,6 +2063,7 @@ export function createPageRequestExecutor<Env = unknown>(
1545
2063
  // Inside the loader `try`: a header the response cannot carry is a loader error, rendered
1546
2064
  // by the same boundary as one.
1547
2065
  responseHeaders = controls.documentHeaders()
2066
+ liveCtx = ctx
1548
2067
  } catch (err) {
1549
2068
  if (componentRequest !== undefined) return componentRuntime!.failure(err)
1550
2069
  if (isStatusSignal(err)) {
@@ -1591,9 +2110,26 @@ export function createPageRequestExecutor<Env = unknown>(
1591
2110
  )
1592
2111
  if (nested !== undefined) return nested
1593
2112
  }
2113
+ const isData = isDataRequest(c)
2114
+ const liveEnvelope =
2115
+ live === undefined || (!isData && !seeding) || isStatusSignal(data) || isControlFlow(data)
2116
+ ? undefined
2117
+ : await liveEnvelopeOf(
2118
+ live,
2119
+ liveOwners(
2120
+ route,
2121
+ layoutModules,
2122
+ layoutData,
2123
+ layoutRetained,
2124
+ mod,
2125
+ skipPage,
2126
+ liveCtx,
2127
+ ),
2128
+ holeOwners,
2129
+ )
1594
2130
  return handleDataOrDocument(
1595
2131
  c.req,
1596
- isDataRequest(c),
2132
+ isData,
1597
2133
  c.env,
1598
2134
  route,
1599
2135
  c.params,
@@ -1607,8 +2143,13 @@ export function createPageRequestExecutor<Env = unknown>(
1607
2143
  responseHeaders,
1608
2144
  controls.personalized,
1609
2145
  componentState,
2146
+ seeding ? undefined : liveEnvelope,
2147
+ seeding && liveEnvelope !== undefined && !("__nifra_deferred" in liveEnvelope)
2148
+ ? liveEnvelope
2149
+ : undefined,
1610
2150
  )
1611
- })
2151
+ }
2152
+ return withResponseControls(liveLoadOf === undefined ? page : admitLive(page))
1612
2153
  },
1613
2154
 
1614
2155
  post: (route) =>
@@ -1659,6 +2200,7 @@ export function createPageRequestExecutor<Env = unknown>(
1659
2200
  draft,
1660
2201
  search: loaderSearch(mod.searchSchema, c.req),
1661
2202
  set: controls.scope(ACTION_SCOPE),
2203
+ live: NO_LIVE,
1662
2204
  }
1663
2205
  let gated: GatedLayouts
1664
2206
  let result: unknown
@@ -1672,16 +2214,17 @@ export function createPageRequestExecutor<Env = unknown>(
1672
2214
  if (isControlFlow(err)) return err
1673
2215
  throw err
1674
2216
  }
1675
- const isRevalidate =
1676
- result !== null && typeof result === "object" && "__nifraRevalidate" in result
1677
- const actionResult = isRevalidate ? (result as RevalidateResult<unknown>).data : result
1678
- const revalidateHeader: Record<string, string> = isRevalidate
1679
- ? {
1680
- [REVALIDATE_HEADER]: (result as RevalidateResult<unknown>).__nifraRevalidate.join(
1681
- ",",
1682
- ),
1683
- }
1684
- : {}
2217
+ const revalidation = isRevalidateResult(result) ? result : undefined
2218
+ const actionResult = revalidation === undefined ? result : revalidation.data
2219
+ const revalidateHeader: Record<string, string> =
2220
+ revalidation === undefined
2221
+ ? {}
2222
+ : { [REVALIDATE_HEADER]: revalidation.__nifraRevalidate.join(",") }
2223
+ // The write is done, whatever the action answers with (a redirect included): what it made
2224
+ // stale is invalidated before the answer leaves.
2225
+ const settling =
2226
+ revalidation === undefined ? undefined : options.invalidate?.(revalidation, c.req)
2227
+ if (settling !== undefined) await settling
1685
2228
  if (isControlFlow(actionResult)) return actionResult
1686
2229
  if (isDataRequest) {
1687
2230
  // Headers shape the document only, but a bad one fails here too, so a navigation and a full
@@ -1693,13 +2236,16 @@ export function createPageRequestExecutor<Env = unknown>(
1693
2236
  headers: { ...DATA_RESPONSE_HEADERS, ...revalidateHeader },
1694
2237
  })
1695
2238
  }
1696
- return new Response(ndjsonStream(forClient, deferred), {
1697
- headers: {
1698
- "content-type": "application/x-ndjson; charset=utf-8",
1699
- ...DATA_RESPONSE_HEADERS,
1700
- ...revalidateHeader,
2239
+ return new Response(
2240
+ ndjsonStream(forClient, deferred, undefined, c.req.signal, deferredLogOf(c.req)),
2241
+ {
2242
+ headers: {
2243
+ "content-type": "application/x-ndjson; charset=utf-8",
2244
+ ...DATA_RESPONSE_HEADERS,
2245
+ ...revalidateHeader,
2246
+ },
1701
2247
  },
1702
- })
2248
+ )
1703
2249
  }
1704
2250
  if (options.serverComponents?.owns(route.id)) {
1705
2251
  if (options.rerenderOwnerGET === undefined) throw new Error("component_contract")
@@ -1768,6 +2314,7 @@ export function createPageRequestExecutor<Env = unknown>(
1768
2314
  draft,
1769
2315
  search,
1770
2316
  set: controls.scope(PAGE_SCOPE),
2317
+ live: NO_LIVE,
1771
2318
  })
1772
2319
  : null
1773
2320
  const [pageData] =
@@ -1837,7 +2384,7 @@ export function createPageRequestExecutor<Env = unknown>(
1837
2384
  },
1838
2385
  serverLeafOf(route, mod),
1839
2386
  )
1840
- return renderPageResult({
2387
+ const page: DocumentInput = {
1841
2388
  adapter,
1842
2389
  chain,
1843
2390
  data,
@@ -1850,6 +2397,8 @@ export function createPageRequestExecutor<Env = unknown>(
1850
2397
  search,
1851
2398
  matchChain: matchChainOf(route.layoutIds, layoutModules, route.id, mod),
1852
2399
  hydrate: mod.hydrate !== false,
2400
+ signal: c.req.signal,
2401
+ logError: deferredLogOf(c.req),
1853
2402
  ...layoutProps,
1854
2403
  ...preloadOf(route.id),
1855
2404
  ...stylesOf(route.id),
@@ -1860,9 +2409,19 @@ export function createPageRequestExecutor<Env = unknown>(
1860
2409
  ...(nonce === undefined ? {} : { nonce }),
1861
2410
  ...cspOption,
1862
2411
  ...titleOption,
1863
- })
2412
+ ...swapOption(c.req),
2413
+ ...slotsFor(route.layoutIds, route.id),
2414
+ }
2415
+ if (mod.islandScripts !== undefined) page.islandScripts = mod.islandScripts
2416
+ return renderPageResult(page)
1864
2417
  }),
1865
2418
 
2419
+ matchRoute: (path) => {
2420
+ const matched = matchManifestRoute(path)
2421
+ const route = matched === null ? undefined : routeById.get(matched.routeId)
2422
+ return route === undefined ? null : { routeId: route.id, layoutIds: route.layoutIds }
2423
+ },
2424
+
1866
2425
  fallback: withResponseControls(async (c, controls) => {
1867
2426
  const match = matchNotFoundScope?.(pathOf(c.req)) ?? null
1868
2427
  const target = match === null ? undefined : notFoundTargets[Number(match.routeId)]
@@ -1887,6 +2446,7 @@ export function createPageRequestExecutor<Env = unknown>(
1887
2446
  draft,
1888
2447
  search: loaderSearch(mod.searchSchema, c.req),
1889
2448
  set: controls.scope(PAGE_SCOPE),
2449
+ live: NO_LIVE,
1890
2450
  }
1891
2451
  if (target.route.middlewareIds !== undefined) {
1892
2452
  await runMiddleware(target.route, ctx, controls)