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