@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
@@ -5,19 +5,125 @@ import { DEFAULT_CSS_LOADING, normalizeCssLoading } from "../css-contract.js";
5
5
  import { defer, ndjsonStream, prepareDeferred } from "../deferred.js";
6
6
  import { isDraftEnabled } from "../draft.js";
7
7
  import { routeTags } from "../isr.js";
8
- import { createMatcher, DATA_HEADER, NAV_FROM_HEADER, REDIRECT_HEADER, RETAIN_HEADER, REVALIDATE_HEADER, STATUS_HEADER, } from "../router.js";
8
+ import { SWAP_HEADER } from "../render-seam.js";
9
+ import { createMatcher, DATA_HEADER, HOLE_FILL_HEADER, KEEP_HOLES_HEADER, NAV_FROM_HEADER, PREFETCH_HEADER, REDIRECT_HEADER, RETAIN_HEADER, REVALIDATE_HEADER, STATUS_HEADER, } from "../router.js";
9
10
  import { searchOf, searchOfChain } from "../search.js";
10
11
  import { mergeHeads, resolveChainMeta } from "./head-merge.js";
12
+ import { keepAliveTable, slotDepths } from "./keep-alive-table.js";
13
+ import { holeOwner, } from "./live-contract.js";
11
14
  import { prepareModules } from "./module-preparation.js";
12
- import { EMPTY_RETAIN, isControlFlow, isResponseResult, isStatusSignal, layoutErrorId, notFound, redirect, renderPageResult, STATUS_SIGNAL, STATUS_TEXT, scopeParams, tagLayoutError, withDuplicateInstanceHint, } from "./render-document.js";
15
+ import { isHole, serverHoleComponent, } from "./ppr/contract.js";
16
+ import { EMPTY_RETAIN, isControlFlow, isResponseResult, isRevalidateResult, layoutErrorId, redirect, renderPageResult, STATUS_TEXT, scopeParams, tagLayoutError, withDuplicateInstanceHint, } from "./render-document.js";
13
17
  import { originFor, urlPartsFor } from "./request-url.js";
14
18
  import { ACTION_SCOPE, DATA_RESPONSE_HEADERS, MIDDLEWARE_SCOPE, PAGE_SCOPE, PageResponseControls, PRIVATE_NO_STORE, PRIVATE_PAGE_HEADERS, privateOutcome, varyOnDataHeader, } from "./response-controls.js";
19
+ import { isStatusSignal, notFound, STATUS_SIGNAL, } from "./status-page.js";
15
20
  /** Whether `c` is a client navigation's data request. On Node, `req.headers` builds every header. */
21
+ // The slot name is echoed in a response header, so only a plain identifier is honored.
22
+ const SLOT_NAME = /^[A-Za-z][\w-]{0,63}$/;
23
+ /** The slot an `x-nifra-swap` request names, when it is a valid slot name. */
24
+ function swapSlotOf(req) {
25
+ const name = req.headers.get(SWAP_HEADER);
26
+ return name !== null && SLOT_NAME.test(name) ? name : undefined;
27
+ }
28
+ /** The `swapSlot` render option for `req`, empty when it is not a swap request. */
29
+ function swapOption(req) {
30
+ const swapSlot = swapSlotOf(req);
31
+ return swapSlot === undefined ? {} : { swapSlot };
32
+ }
33
+ const headerOf = (c, name) => c.header !== undefined ? c.header(name) : c.req.headers.get(name);
16
34
  function isDataRequest(c) {
17
- return (c.header !== undefined ? c.header(DATA_HEADER) : c.req.headers.get(DATA_HEADER)) !== null;
35
+ return headerOf(c, DATA_HEADER) !== null;
36
+ }
37
+ /**
38
+ * A data request's hole refresh: the hosts the client holds, which reload as fills, the holes it
39
+ * keeps, and in a live load each hole's owner. A name the route does not declare is a 400.
40
+ */
41
+ function holeRefresh(c, request, live) {
42
+ if (!isDataRequest(c))
43
+ return { request };
44
+ const declared = new Set(request.holes.map((hole) => hole.name));
45
+ const namesOf = (header) => {
46
+ const value = headerOf(c, header);
47
+ const names = new Set();
48
+ if (value === null)
49
+ return names;
50
+ for (const name of value.split(",")) {
51
+ const trimmed = name.trim();
52
+ if (declared.has(trimmed))
53
+ names.add(trimmed);
54
+ // A live load's names come from a grant this server sealed and the page's own client: one
55
+ // naming a hole an older build declared is skipped.
56
+ else if (live === undefined)
57
+ return undefined;
58
+ }
59
+ return names;
60
+ };
61
+ const kept = namesOf(KEEP_HOLES_HEADER);
62
+ const fill = namesOf(HOLE_FILL_HEADER);
63
+ if (kept === undefined || fill === undefined) {
64
+ return new Response("Bad Request: the route has no such hole", {
65
+ status: 400,
66
+ headers: { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" },
67
+ });
68
+ }
69
+ const refreshed = {
70
+ ...request,
71
+ ...(fill.size === 0 ? {} : { fill }),
72
+ ...(kept.size === 0 ? {} : { kept }),
73
+ };
74
+ if (live === undefined)
75
+ return { request: refreshed };
76
+ const routeId = request.routeId;
77
+ let finish = () => { };
78
+ const loaded = new Promise((resolve) => {
79
+ finish = resolve;
80
+ });
81
+ const holeLive = {
82
+ collector: (hole) => live.collector(holeOwner(hole, routeId)),
83
+ seal: (hole) => live.seal(holeOwner(hole, routeId)),
84
+ done: (names) => finish(names.map((hole) => holeOwner(hole, routeId))),
85
+ };
86
+ return {
87
+ request: { ...refreshed, live: holeLive },
88
+ owners: { kept: [...kept].map((hole) => holeOwner(hole, routeId)), loaded },
89
+ };
18
90
  }
19
91
  /** The page slot of an `ssr = false` route with no `HydrateFallback`: a component that renders nothing. */
20
92
  const EMPTY_LEAF = () => null;
93
+ /** `ctx.live` outside a live load: keys are declared for nobody. */
94
+ const NO_LIVE = () => { };
95
+ const headersSent = () => {
96
+ throw new TypeError("[nifra/web] ppr: a hole's server component cannot set a cookie: the shell already sent the headers");
97
+ };
98
+ /** A hole's response headers went out with the shell, so its server component sets none. */
99
+ const SENT_CONTROLS = Object.freeze({
100
+ headers: Object.freeze({}),
101
+ cookie: headersSent,
102
+ deleteCookie: headersSent,
103
+ });
104
+ /**
105
+ * The envelope's `live` member. With holes running it trails them: one grant, issued once the last hole
106
+ * settled, holding each hole that loaded and each the client kept.
107
+ */
108
+ const liveEnvelopeOf = (live, owners, holes) => holes === undefined
109
+ ? live.finish(owners)
110
+ : defer(holes.loaded.then((loaded) => live.finish({
111
+ ...owners,
112
+ fresh: [...owners.fresh, ...loaded],
113
+ retained: [...owners.retained, ...holes.kept],
114
+ })));
115
+ /** Run one owner's loader with its `ctx.live`, closing the collector once the loader settles. */
116
+ const runLiveLoader = (live, owner, loader, ctx) => {
117
+ let value;
118
+ try {
119
+ value = loader({ ...ctx, live: live.collector(owner) });
120
+ }
121
+ catch (err) {
122
+ live.seal(owner);
123
+ return Promise.reject(err);
124
+ }
125
+ return Promise.resolve(value).finally(() => live.seal(owner));
126
+ };
21
127
  /** The request-scoping seam of an in-process `api` (see `inProcessClient`), if it carries one. */
22
128
  function platformBinderOf(api) {
23
129
  if ((typeof api !== "object" && typeof api !== "function") || api === null)
@@ -140,12 +246,13 @@ export function createPageRequestExecutor(options) {
140
246
  const { i18n } = options;
141
247
  const pageLocales = new Map(i18n?.locales.locales.map((locale) => [locale.key, locale]));
142
248
  /** The request's locale for a document, with the served locales the client names locales from. */
143
- const localeOption = (req) => {
249
+ const localeFor = (key) => {
144
250
  if (i18n === undefined)
145
251
  return {};
146
- const locale = pageLocales.get(i18n.localeOf(pathOf(req)));
252
+ const locale = pageLocales.get(key);
147
253
  return locale === undefined ? { locales: i18n.locales } : { locale, locales: i18n.locales };
148
254
  };
255
+ const localeOption = (req) => i18n === undefined ? {} : localeFor(i18n.localeOf(pathOf(req)));
149
256
  /** Whether a route sits under a `[lang]` or `[[lang]]` directory, which the locale routing checks. */
150
257
  const localeGuarded = (route) => {
151
258
  if (i18n === undefined)
@@ -158,6 +265,49 @@ export function createPageRequestExecutor(options) {
158
265
  };
159
266
  const matchManifestRoute = createMatcher(manifest.routes.map((route) => ({ routeId: route.id, pattern: route.pattern })));
160
267
  const routeById = new Map(manifest.routes.map((route) => [route.id, route]));
268
+ const liveLoadOf = options.liveLoadOf;
269
+ const liveSeedOf = options.liveSeedOf;
270
+ /** A live load holds one admission slot from before its middleware until its body is complete. */
271
+ const admitLive = (run) => async (c, controls) => {
272
+ const live = liveLoadOf?.(c.req);
273
+ if (live === undefined)
274
+ return run(c, controls);
275
+ const refused = live.admit();
276
+ if (refused !== undefined)
277
+ return refused;
278
+ let outcome;
279
+ try {
280
+ outcome = await run(c, controls);
281
+ }
282
+ catch (err) {
283
+ live.release();
284
+ throw err;
285
+ }
286
+ if (outcome instanceof Response)
287
+ return live.settle(outcome);
288
+ live.release();
289
+ return outcome;
290
+ };
291
+ /** Which owners a successful live load ran and which it kept, by their wire ids. */
292
+ const liveOwners = (route, modules, layoutData, retained, mod, skipPage, ctx) => {
293
+ const fresh = [];
294
+ const kept = [];
295
+ for (let i = 0; i < (modules?.length ?? 0); i++) {
296
+ if (modules?.[i]?.loader === undefined)
297
+ continue;
298
+ const owner = `L:${route.layoutIds[i]}`;
299
+ if (retained.includes(i))
300
+ kept.push(owner);
301
+ // A layout that answered with a status declared keys for data nobody is shown.
302
+ else if (!isStatusSignal(layoutData?.[i]))
303
+ fresh.push(owner);
304
+ }
305
+ if (skipPage)
306
+ kept.push(`P:${route.id}`);
307
+ else if (mod.loader !== undefined)
308
+ fresh.push(`P:${route.id}`);
309
+ return { fresh, retained: kept, ctx };
310
+ };
161
311
  // A nested `_404` answers an unmatched URL from inside the catch-all, never as a route of its own:
162
312
  // a registered `/admin/*` would take URLs from a route the router only reaches by backtracking.
163
313
  // Param names are indexed by position, so two directories that name one segment differently share
@@ -197,6 +347,16 @@ export function createPageRequestExecutor(options) {
197
347
  const draftFlag = (req) => options.draftSecret === undefined
198
348
  ? Promise.resolve(false)
199
349
  : isDraftEnabled(req, options.draftSecret);
350
+ // Where the browser's keep-alive view puts slot wrappers, so the document it hydrates has them:
351
+ // nothing outside an app with a `keepAlive` route.
352
+ const keepTable = keepAliveTable(manifest);
353
+ const slotsFor = (layoutIds, routeId) => {
354
+ // Every page of a keep-alive app renders through the adapter's keep-alive root, wrappers or none,
355
+ // as the browser hydrates it.
356
+ if (keepTable === undefined)
357
+ return {};
358
+ return { slots: slotDepths(keepTable, layoutIds, routeId) };
359
+ };
200
360
  const preloadOf = (id) => {
201
361
  const chunks = options.routePreload?.[id];
202
362
  return chunks ? { preload: chunks } : {};
@@ -274,7 +434,7 @@ export function createPageRequestExecutor(options) {
274
434
  * Run layout loaders, awaiting gates before starting the page loader and retaining only safe data.
275
435
  * `gated` carries gates that already ran for this request, so none runs twice.
276
436
  */
277
- const runLayoutChain = async (route, ctx, controls, retain, gated) => {
437
+ const runLayoutChain = async (route, ctx, controls, retain, gated, live) => {
278
438
  if (route.layoutIds.length === 0)
279
439
  return emptyLayoutRun;
280
440
  const modules = gated?.modules ?? (await loadLayoutModules(route));
@@ -308,14 +468,19 @@ export function createPageRequestExecutor(options) {
308
468
  params: scopeParams(ctx.params, route.layoutParams?.[i]),
309
469
  set: controls.scope(i),
310
470
  };
471
+ const layoutId = route.layoutIds[i];
311
472
  if (modules[i]?.gate === true) {
312
- results[i] = gated === undefined ? await runGate(route, i, loader, scoped) : gated.gates[i];
473
+ results[i] =
474
+ gated !== undefined
475
+ ? gated.gates[i]
476
+ : await runGate(route, i, live === undefined
477
+ ? loader
478
+ : (gateCtx) => runLiveLoader(live, `L:${layoutId}`, loader, gateCtx), scoped);
313
479
  continue;
314
480
  }
315
- const layoutId = route.layoutIds[i];
316
481
  const index = i;
317
482
  try {
318
- const value = loader(scoped);
483
+ const value = live === undefined ? loader(scoped) : runLiveLoader(live, `L:${layoutId}`, loader, scoped);
319
484
  // Scalars settle synchronously. Objects keep await's intrinsic promise/thenable
320
485
  // assimilation: property-presence and prototype probes are unsafe for proxies.
321
486
  if (value !== null && (typeof value === "object" || typeof value === "function")) {
@@ -512,6 +677,11 @@ export function createPageRequestExecutor(options) {
512
677
  handles: [...layoutModules.map((layout) => layout.handle), page.handle],
513
678
  });
514
679
  const dirOfId = (id, suffix) => id === suffix ? "" : id.slice(0, id.length - suffix.length - 1);
680
+ /** The server's error log for one request, where a rejected `defer()` value is written. */
681
+ const deferredLogOf = (req) => {
682
+ const log = options.logBoundaryError;
683
+ return log === undefined ? undefined : (error) => log(error, req);
684
+ };
515
685
  const boundaryFor = (route, err) => {
516
686
  const ids = route.errorIds ?? [];
517
687
  const failing = layoutErrorId(err);
@@ -568,6 +738,8 @@ export function createPageRequestExecutor(options) {
568
738
  routeId: errorId,
569
739
  status: 500,
570
740
  hydrate: false,
741
+ signal: req.signal,
742
+ logError: deferredLogOf(req),
571
743
  ...(layoutData === undefined ? {} : { layoutData }),
572
744
  ...(personalized || layoutData !== undefined ? { headers: PRIVATE_PAGE_HEADERS } : {}),
573
745
  ...(nonce === undefined ? {} : { nonce }),
@@ -645,6 +817,7 @@ export function createPageRequestExecutor(options) {
645
817
  draft,
646
818
  search: loaderSearch(page.searchSchema, c.req),
647
819
  set: controls.scope(PAGE_SCOPE),
820
+ live: NO_LIVE,
648
821
  };
649
822
  if (route.middlewareIds !== undefined)
650
823
  await runMiddleware(route, ctx, controls);
@@ -689,6 +862,8 @@ export function createPageRequestExecutor(options) {
689
862
  search: searchChainOf(modules, loaded, req),
690
863
  path: pathOf(req),
691
864
  status,
865
+ signal: req.signal,
866
+ logError: deferredLogOf(req),
692
867
  // The browser renders this page inside the root layout, so a page that had to render alone
693
868
  // stays server-only, as a nested `_404` does.
694
869
  ...(wraps && chain === undefined ? { hydrate: false } : {}),
@@ -703,6 +878,7 @@ export function createPageRequestExecutor(options) {
703
878
  ...copyOption,
704
879
  ...localeOption(req),
705
880
  ...titleOption,
881
+ ...slotsFor(layoutIds, routeId),
706
882
  };
707
883
  if (chain === undefined)
708
884
  return renderPageResult(statusPage);
@@ -748,7 +924,7 @@ export function createPageRequestExecutor(options) {
748
924
  // A faulty reporter must never break error rendering.
749
925
  }
750
926
  };
751
- const handleDataOrDocument = async (req, isData, env, route, params, mod, data, layoutData, layoutModules, layoutRetained, boundaryStates, assemblyCache, responseHeaders, personalized, serverComponents) => {
927
+ const handleDataOrDocument = async (req, isData, env, route, params, mod, data, layoutData, layoutModules, layoutRetained, boundaryStates, assemblyCache, responseHeaders, personalized, serverComponents, live, liveSeed) => {
752
928
  // Every layout loader settled before the page rendered, so a status or error page can reuse them.
753
929
  const settled = async (_page, count) => settledLayouts(layoutModules, layoutData, count);
754
930
  if (isStatusSignal(data))
@@ -770,7 +946,14 @@ export function createPageRequestExecutor(options) {
770
946
  ...deferred,
771
947
  ...(boundarySplit === undefined ? [] : boundarySplit.deferred),
772
948
  ];
773
- const payload = layoutData === undefined && boundarySplit === undefined && serverComponents === undefined
949
+ // Deferred when holes run: their owners are known only once the last one settles.
950
+ const liveSplit = live === undefined ? undefined : prepareDeferred(live, allDeferred.length);
951
+ if (liveSplit !== undefined)
952
+ allDeferred.push(...liveSplit.deferred);
953
+ const payload = layoutData === undefined &&
954
+ boundarySplit === undefined &&
955
+ serverComponents === undefined &&
956
+ live === undefined
774
957
  ? pageSplit.forClient
775
958
  : {
776
959
  v: 1,
@@ -779,11 +962,12 @@ export function createPageRequestExecutor(options) {
779
962
  retained: layoutRetained,
780
963
  ...(boundarySplit === undefined ? {} : { boundaries: boundarySplit.forClient }),
781
964
  ...(serverComponents === undefined ? {} : { serverComponents }),
965
+ ...(liveSplit === undefined ? {} : { live: liveSplit.forClient }),
782
966
  };
783
967
  if (allDeferred.length === 0) {
784
968
  return Response.json(payload ?? null, { headers: DATA_RESPONSE_HEADERS });
785
969
  }
786
- return new Response(ndjsonStream(payload, allDeferred), {
970
+ return new Response(ndjsonStream(payload, allDeferred, undefined, req.signal, deferredLogOf(req)), {
787
971
  headers: {
788
972
  "content-type": "application/x-ndjson; charset=utf-8",
789
973
  ...DATA_RESPONSE_HEADERS,
@@ -813,6 +997,8 @@ export function createPageRequestExecutor(options) {
813
997
  search: searchChainOf(layoutModules, mod, req),
814
998
  matchChain: matchChainOf(route.layoutIds, layoutModules, route.id, mod),
815
999
  hydrate: mod.hydrate !== false,
1000
+ signal: req.signal,
1001
+ logError: deferredLogOf(req),
816
1002
  ...preloadOf(route.id),
817
1003
  ...stylesOf(route.id),
818
1004
  ...appDocumentOptions,
@@ -820,8 +1006,15 @@ export function createPageRequestExecutor(options) {
820
1006
  };
821
1007
  if (serverComponents !== undefined)
822
1008
  page.serverComponents = serverComponents;
1009
+ if (liveSeed?.grant !== undefined)
1010
+ page.live = liveSeed;
823
1011
  if (progressiveActionData.has(req))
824
1012
  page.actionData = progressiveActionData.get(req);
1013
+ const swapSlot = swapSlotOf(req);
1014
+ if (swapSlot !== undefined)
1015
+ page.swapSlot = swapSlot;
1016
+ if (keepTable !== undefined)
1017
+ Object.assign(page, slotsFor(route.layoutIds, route.id));
825
1018
  if (layoutData !== undefined)
826
1019
  page.layoutData = layoutData;
827
1020
  if (boundaryStates !== undefined)
@@ -919,6 +1112,8 @@ export function createPageRequestExecutor(options) {
919
1112
  matchChain: matchChainOf(page.layoutIds, layoutModules, id, mod),
920
1113
  status: 404,
921
1114
  hydrate: false,
1115
+ signal: req.signal,
1116
+ logError: deferredLogOf(req),
922
1117
  ...localeOption(req),
923
1118
  ...(layoutData !== undefined ? { layoutData } : {}),
924
1119
  ...stylesOf(id),
@@ -984,6 +1179,86 @@ export function createPageRequestExecutor(options) {
984
1179
  controls.seal();
985
1180
  }
986
1181
  };
1182
+ const pprRequestFor = (c, route, mod, layouts, boundaries, shellLoader, requestApi, draft, search) => {
1183
+ const path = urlPartsFor(c.req).pathname;
1184
+ const locale = i18n?.localeOf(path);
1185
+ const params = c.params;
1186
+ const shellOrigin = options.ppr?.origin;
1187
+ const variations = options.ppr?.variationsOf(mod.shellVariations, urlPartsFor(c.req).search) ?? {};
1188
+ const shellSearch = options.ppr?.variationQuery(variations) ?? "";
1189
+ // What a shared entry's tags read: its key's parts, never the Host or an undeclared query, which
1190
+ // the first visitor of a key could set so a later purge misses the entry.
1191
+ const shellUrl = () => {
1192
+ const url = new URL(shellOrigin ?? "http://shell.invalid");
1193
+ url.pathname = path;
1194
+ url.search = shellSearch;
1195
+ return url;
1196
+ };
1197
+ // The unbound `api`: a shell is built for every visitor, so no visitor's platform reaches it.
1198
+ const shellContext = shellOrigin === undefined
1199
+ ? { phase: "shell", params, locale, variations, api }
1200
+ : { phase: "shell", params, locale, variations, api, origin: shellOrigin };
1201
+ return {
1202
+ routeId: route.id,
1203
+ path,
1204
+ locale,
1205
+ variations,
1206
+ request: c.req,
1207
+ holes: boundaries.filter(isHole),
1208
+ holeContext: { request: c.req, params, api: requestApi, env: c.env, draft, search },
1209
+ shellContext,
1210
+ revalidate: mod.revalidate,
1211
+ waitUntil: c.waitUntil,
1212
+ tags: () => mod.revalidateTags === undefined
1213
+ ? []
1214
+ : routeTags(mod.revalidateTags, { params, url: shellUrl() }, route.id),
1215
+ holeTags: (hole) => hole.revalidateTags === undefined
1216
+ ? []
1217
+ : routeTags(hole.revalidateTags, { params, url: shellUrl() }, `${route.id}#${hole.name}`),
1218
+ loadShell: async (signal) => shellLoader({ ...shellContext, signal }),
1219
+ renderShellDocument: async (shellData, states) => {
1220
+ const statics = boundaries.some((boundary) => boundary.mode === "static")
1221
+ ? await resolveStaticBoundaries(boundaries, { phase: "build" }, options.staticBoundaryCache)
1222
+ : undefined;
1223
+ const { chain, head } = resolveChainAndHead(layouts, undefined, mod,
1224
+ // A Host header is the client's to choose, so a shared shell's meta gets the configured one.
1225
+ { data: shellData, pageData: shellData, params, origin: shellOrigin ?? "" }, serverLeafOf(route, mod));
1226
+ // Nothing here reads the request beyond its pathname: no origin, search, nonce or headers.
1227
+ const page = {
1228
+ adapter,
1229
+ chain,
1230
+ data: shellData,
1231
+ head,
1232
+ clientEntry,
1233
+ routeId: route.id,
1234
+ params,
1235
+ path,
1236
+ search: searchOfChain([...layouts.map((m) => m.searchSchema), mod.searchSchema], shellSearch === "" ? "" : `?${shellSearch}`),
1237
+ matchChain: matchChainOf(route.layoutIds, layouts, route.id, mod),
1238
+ hydrate: mod.hydrate !== false,
1239
+ boundaries: { ...statics, ...states },
1240
+ ...preloadOf(route.id),
1241
+ ...stylesOf(route.id),
1242
+ ...appDocumentOptions,
1243
+ ...(locale === undefined ? {} : localeFor(locale)),
1244
+ ...slotsFor(route.layoutIds, route.id),
1245
+ };
1246
+ if (mod.islandScripts !== undefined)
1247
+ page.islandScripts = mod.islandScripts;
1248
+ const result = await renderPageResult(page);
1249
+ // A buffered page hands over its string: a Response round trip to read it back cost more.
1250
+ const rendered = result.toNodeBody?.() ?? result.toResponse();
1251
+ if (rendered.status !== 200) {
1252
+ throw new Error(`[nifra/web] ppr: the shell of "${route.id}" rendered status ${rendered.status}`);
1253
+ }
1254
+ if (rendered instanceof Response)
1255
+ return rendered.text();
1256
+ return typeof rendered.body === "string"
1257
+ ? rendered.body
1258
+ : new TextDecoder().decode(rendered.body);
1259
+ },
1260
+ };
1261
+ };
987
1262
  return {
988
1263
  get: (route) => {
989
1264
  const is404Fallback = options.staticFallbacks?.[route.pattern] === "404";
@@ -1013,7 +1288,8 @@ export function createPageRequestExecutor(options) {
1013
1288
  }
1014
1289
  return slot;
1015
1290
  };
1016
- return withResponseControls(async (c, controls) => {
1291
+ const page = async (c, controls) => {
1292
+ let live = liveLoadOf?.(c.req);
1017
1293
  const componentRuntime = options.serverComponents;
1018
1294
  const componentOwner = componentRuntime?.owns(route.id) === true;
1019
1295
  let componentRequest;
@@ -1030,6 +1306,17 @@ export function createPageRequestExecutor(options) {
1030
1306
  }
1031
1307
  const loaded = loadModule(route);
1032
1308
  const mod = cacheModules && !(loaded instanceof Promise) ? loaded : await loaded;
1309
+ // A document's loaders declare their keys into a seed, which the page exchanges for a grant
1310
+ // once its socket opens, instead of loading itself again. Partially prerendered pages, whose
1311
+ // holes settle after the shell, and server-component routes load as before.
1312
+ const seeding = live === undefined &&
1313
+ liveSeedOf !== undefined &&
1314
+ !componentOwner &&
1315
+ componentRequest === undefined &&
1316
+ (options.ppr === undefined || mod.shellLoader === undefined) &&
1317
+ !isDataRequest(c);
1318
+ if (seeding)
1319
+ live = liveSeedOf(c.req, route.id);
1033
1320
  const draft = await draftFlag(c.req);
1034
1321
  // Unpublished content is the editor's alone: whatever cache-control a loader sets, no cache keeps it.
1035
1322
  if (draft)
@@ -1043,6 +1330,9 @@ export function createPageRequestExecutor(options) {
1043
1330
  let componentState;
1044
1331
  const requestApi = apiFor(c);
1045
1332
  let run;
1333
+ let skipPage = false;
1334
+ let liveCtx;
1335
+ let holeOwners;
1046
1336
  try {
1047
1337
  const ctx = {
1048
1338
  params: c.params,
@@ -1053,11 +1343,20 @@ export function createPageRequestExecutor(options) {
1053
1343
  draft,
1054
1344
  search: loaderSearch(mod.searchSchema, c.req),
1055
1345
  set: controls.scope(PAGE_SCOPE),
1346
+ live: NO_LIVE,
1056
1347
  };
1057
1348
  if (route.middlewareIds !== undefined || localeGuarded(route)) {
1058
1349
  await runMiddleware(route, ctx, controls);
1059
1350
  }
1060
- run = await runLayoutChain(route, ctx, controls, componentOwner ? undefined : retainContextOf(c));
1351
+ // After middleware, before any loader: a grant issued to another viewer keeps nothing.
1352
+ const retains = live === undefined || (await live.bind(ctx));
1353
+ skipPage =
1354
+ live !== undefined &&
1355
+ retains &&
1356
+ !componentOwner &&
1357
+ live.retainPage &&
1358
+ live.routeId === route.id;
1359
+ run = await runLayoutChain(route, ctx, controls, componentOwner || !retains ? undefined : retainContextOf(c), undefined, live);
1061
1360
  layoutModules = run.modules;
1062
1361
  layoutRetained = run.retained;
1063
1362
  const boundaryDefinitions = componentRequest === undefined
@@ -1069,7 +1368,59 @@ export function createPageRequestExecutor(options) {
1069
1368
  if (boundaryDefinitions.length > 0)
1070
1369
  boundaryDescriptors(boundaryDefinitions);
1071
1370
  const effectiveSearch = searchChainOf(run.modules, mod, c.req);
1072
- const boundaryBatch = boundaryDefinitions.length === 0
1371
+ const ppr = options.ppr;
1372
+ const shellLoader = componentRequest === undefined && !componentOwner ? mod.shellLoader : undefined;
1373
+ let pprRequest;
1374
+ // How a document that renders whole gets its holes: complete, unless no shell exists yet.
1375
+ let wholeHoles = "settled";
1376
+ if (ppr !== undefined && shellLoader !== undefined) {
1377
+ ppr.assertRoute(route, run.modules, boundaryDefinitions);
1378
+ pprRequest = pprRequestFor(c, route, mod, run.modules, boundaryDefinitions, shellLoader, requestApi, draft, effectiveSearch);
1379
+ const refresh = holeRefresh(c, pprRequest, live);
1380
+ if (refresh instanceof Response)
1381
+ return refresh;
1382
+ pprRequest = refresh.request;
1383
+ holeOwners = refresh.owners;
1384
+ const components = options.serverComponents;
1385
+ const renderHole = components?.hosts?.(route.id) === true ? components.hole : undefined;
1386
+ if (renderHole === undefined &&
1387
+ pprRequest.holes.some((hole) => serverHoleComponent(hole.render) !== undefined)) {
1388
+ throw new Error(`[nifra/web] ppr: route "${route.id}" has a hole that shows a server component; list the component under createServerComponents({ holes }) for this route`);
1389
+ }
1390
+ if (renderHole !== undefined) {
1391
+ const loaderCtx = ctx;
1392
+ pprRequest = {
1393
+ ...pprRequest,
1394
+ // The owner path's context, but with the hole's signal and no response to change.
1395
+ component: (name, component, input, signal) => renderHole.call(components, route.id, component, name, input, Object.create(c, Object.getOwnPropertyDescriptors({
1396
+ ...loaderCtx,
1397
+ search: effectiveSearch,
1398
+ set: SENT_CONTROLS,
1399
+ signal,
1400
+ data: undefined,
1401
+ layoutData: [],
1402
+ }))),
1403
+ };
1404
+ }
1405
+ // A draft is the editor's alone, so it never reads or fills the shared shell.
1406
+ if (!draft && !isDataRequest(c)) {
1407
+ const streamed = await ppr.stream(pprRequest, () => controls.documentHeaders());
1408
+ if (streamed instanceof Response)
1409
+ return streamed;
1410
+ wholeHoles = streamed;
1411
+ }
1412
+ // Its holes are one visitor's, so no cache may store the page they render into.
1413
+ controls.personalize();
1414
+ }
1415
+ // Every PPR hole runs through the PPR server, so each has admission and its own deadline.
1416
+ const holeStates = pprRequest === undefined || ppr === undefined
1417
+ ? undefined
1418
+ : ppr.server.holeStates(pprRequest, !isDataRequest(c)
1419
+ ? wholeHoles
1420
+ : c.req.headers.get(PREFETCH_HEADER) === null
1421
+ ? "deferred"
1422
+ : "omitted", c.req.signal);
1423
+ const boundaryBatch = boundaryDefinitions.length === 0 || holeStates !== undefined
1073
1424
  ? undefined
1074
1425
  : startDynamicBoundaries(boundaryDefinitions, {
1075
1426
  request: c.req,
@@ -1079,18 +1430,33 @@ export function createPageRequestExecutor(options) {
1079
1430
  draft,
1080
1431
  search: effectiveSearch,
1081
1432
  signal: c.req.signal,
1433
+ live: NO_LIVE,
1082
1434
  });
1083
1435
  const staticBoundaryPromise = boundaryDefinitions.length === 0
1084
1436
  ? undefined
1085
1437
  : resolveStaticBoundaries(boundaryDefinitions, { phase: "build" }, options.staticBoundaryCache);
1086
- const pageResult = mod.loader ? mod.loader({ ...ctx, search: effectiveSearch }) : null;
1438
+ const pageResult = pprRequest !== undefined && ppr !== undefined
1439
+ ? // A key with no shell yet must not build one through its data either.
1440
+ draft || wholeHoles === "deferred"
1441
+ ? pprRequest.loadShell(c.req.signal)
1442
+ : ppr.shellData(pprRequest, c.req.signal)
1443
+ : mod.loader === undefined || skipPage
1444
+ ? null
1445
+ : live === undefined
1446
+ ? mod.loader({ ...ctx, search: effectiveSearch })
1447
+ : runLiveLoader(live, `P:${route.id}`, mod.loader, {
1448
+ ...ctx,
1449
+ search: effectiveSearch,
1450
+ });
1087
1451
  const [pageData, , staticBoundaries] = run.pending === undefined && staticBoundaryPromise === undefined
1088
1452
  ? [await pageResult, undefined, undefined]
1089
1453
  : staticBoundaryPromise === undefined
1090
1454
  ? await Promise.all([pageResult, run.pending])
1091
1455
  : await Promise.all([pageResult, run.pending, staticBoundaryPromise]);
1092
1456
  data = pageData;
1093
- layoutData = run.layoutData;
1457
+ // A gate on a PPR route runs for its redirect or refusal only. Its data reaches no response,
1458
+ // so a soft navigation renders the layouts exactly as the shared shell did.
1459
+ layoutData = pprRequest === undefined ? run.layoutData : undefined;
1094
1460
  if (componentOwner) {
1095
1461
  for (let i = 0; i < (layoutData?.length ?? 0); i++) {
1096
1462
  const outcome = layoutData?.[i];
@@ -1110,8 +1476,17 @@ export function createPageRequestExecutor(options) {
1110
1476
  }));
1111
1477
  componentState = await componentRuntime.execute(route.id, componentContext, componentRequest, options.nonce === undefined ? undefined : await resolveNonce(c.req, c.env));
1112
1478
  }
1113
- if (boundaryBatch !== undefined) {
1114
- const streamed = { ...boundaryBatch.initial, ...(staticBoundaries ?? {}) };
1479
+ if (holeStates !== undefined) {
1480
+ // The static resolver names every boundary, leaving the dynamic ones to the holes.
1481
+ boundaryStates = { ...staticBoundaries, ...(await holeStates) };
1482
+ }
1483
+ else if (boundaryBatch !== undefined) {
1484
+ // Each resolver returns a state for every boundary and marks the other's modes
1485
+ // `unresolved`, so a slot takes its state from the resolver of its own mode.
1486
+ const streamed = { ...boundaryBatch.initial };
1487
+ for (const [name, state] of Object.entries(staticBoundaries ?? {}))
1488
+ if (state.mode === "static")
1489
+ streamed[name] = state;
1115
1490
  for (const entry of boundaryBatch.pending) {
1116
1491
  const initial = streamed[entry.name];
1117
1492
  if (initial !== undefined)
@@ -1125,6 +1500,7 @@ export function createPageRequestExecutor(options) {
1125
1500
  // Inside the loader `try`: a header the response cannot carry is a loader error, rendered
1126
1501
  // by the same boundary as one.
1127
1502
  responseHeaders = controls.documentHeaders();
1503
+ liveCtx = ctx;
1128
1504
  }
1129
1505
  catch (err) {
1130
1506
  if (componentRequest !== undefined)
@@ -1148,8 +1524,15 @@ export function createPageRequestExecutor(options) {
1148
1524
  if (nested !== undefined)
1149
1525
  return nested;
1150
1526
  }
1151
- return handleDataOrDocument(c.req, isDataRequest(c), c.env, route, c.params, mod, data, layoutData, layoutModules ?? (await loadLayoutModules(route)), layoutRetained, boundaryStates, layoutModules === undefined ? undefined : assemblyCacheFor(mod, layoutModules, c.req), responseHeaders, controls.personalized, componentState);
1152
- });
1527
+ const isData = isDataRequest(c);
1528
+ const liveEnvelope = live === undefined || (!isData && !seeding) || isStatusSignal(data) || isControlFlow(data)
1529
+ ? undefined
1530
+ : await liveEnvelopeOf(live, liveOwners(route, layoutModules, layoutData, layoutRetained, mod, skipPage, liveCtx), holeOwners);
1531
+ return handleDataOrDocument(c.req, isData, c.env, route, c.params, mod, data, layoutData, layoutModules ?? (await loadLayoutModules(route)), layoutRetained, boundaryStates, layoutModules === undefined ? undefined : assemblyCacheFor(mod, layoutModules, c.req), responseHeaders, controls.personalized, componentState, seeding ? undefined : liveEnvelope, seeding && liveEnvelope !== undefined && !("__nifra_deferred" in liveEnvelope)
1532
+ ? liveEnvelope
1533
+ : undefined);
1534
+ };
1535
+ return withResponseControls(liveLoadOf === undefined ? page : admitLive(page));
1153
1536
  },
1154
1537
  post: (route) => withResponseControls(async (c, controls) => {
1155
1538
  const loaded = loadModule(route);
@@ -1195,6 +1578,7 @@ export function createPageRequestExecutor(options) {
1195
1578
  draft,
1196
1579
  search: loaderSearch(mod.searchSchema, c.req),
1197
1580
  set: controls.scope(ACTION_SCOPE),
1581
+ live: NO_LIVE,
1198
1582
  };
1199
1583
  let gated;
1200
1584
  let result;
@@ -1210,13 +1594,16 @@ export function createPageRequestExecutor(options) {
1210
1594
  return err;
1211
1595
  throw err;
1212
1596
  }
1213
- const isRevalidate = result !== null && typeof result === "object" && "__nifraRevalidate" in result;
1214
- const actionResult = isRevalidate ? result.data : result;
1215
- const revalidateHeader = isRevalidate
1216
- ? {
1217
- [REVALIDATE_HEADER]: result.__nifraRevalidate.join(","),
1218
- }
1219
- : {};
1597
+ const revalidation = isRevalidateResult(result) ? result : undefined;
1598
+ const actionResult = revalidation === undefined ? result : revalidation.data;
1599
+ const revalidateHeader = revalidation === undefined
1600
+ ? {}
1601
+ : { [REVALIDATE_HEADER]: revalidation.__nifraRevalidate.join(",") };
1602
+ // The write is done, whatever the action answers with (a redirect included): what it made
1603
+ // stale is invalidated before the answer leaves.
1604
+ const settling = revalidation === undefined ? undefined : options.invalidate?.(revalidation, c.req);
1605
+ if (settling !== undefined)
1606
+ await settling;
1220
1607
  if (isControlFlow(actionResult))
1221
1608
  return actionResult;
1222
1609
  if (isDataRequest) {
@@ -1229,7 +1616,7 @@ export function createPageRequestExecutor(options) {
1229
1616
  headers: { ...DATA_RESPONSE_HEADERS, ...revalidateHeader },
1230
1617
  });
1231
1618
  }
1232
- return new Response(ndjsonStream(forClient, deferred), {
1619
+ return new Response(ndjsonStream(forClient, deferred, undefined, c.req.signal, deferredLogOf(c.req)), {
1233
1620
  headers: {
1234
1621
  "content-type": "application/x-ndjson; charset=utf-8",
1235
1622
  ...DATA_RESPONSE_HEADERS,
@@ -1308,6 +1695,7 @@ export function createPageRequestExecutor(options) {
1308
1695
  draft,
1309
1696
  search,
1310
1697
  set: controls.scope(PAGE_SCOPE),
1698
+ live: NO_LIVE,
1311
1699
  })
1312
1700
  : null;
1313
1701
  const [pageData] = run.pending === undefined
@@ -1344,7 +1732,7 @@ export function createPageRequestExecutor(options) {
1344
1732
  origin: originFor(c.req),
1345
1733
  ...(nonce === undefined ? {} : { nonce }),
1346
1734
  }, serverLeafOf(route, mod));
1347
- return renderPageResult({
1735
+ const page = {
1348
1736
  adapter,
1349
1737
  chain,
1350
1738
  data,
@@ -1357,6 +1745,8 @@ export function createPageRequestExecutor(options) {
1357
1745
  search,
1358
1746
  matchChain: matchChainOf(route.layoutIds, layoutModules, route.id, mod),
1359
1747
  hydrate: mod.hydrate !== false,
1748
+ signal: c.req.signal,
1749
+ logError: deferredLogOf(c.req),
1360
1750
  ...layoutProps,
1361
1751
  ...preloadOf(route.id),
1362
1752
  ...stylesOf(route.id),
@@ -1367,8 +1757,18 @@ export function createPageRequestExecutor(options) {
1367
1757
  ...(nonce === undefined ? {} : { nonce }),
1368
1758
  ...cspOption,
1369
1759
  ...titleOption,
1370
- });
1760
+ ...swapOption(c.req),
1761
+ ...slotsFor(route.layoutIds, route.id),
1762
+ };
1763
+ if (mod.islandScripts !== undefined)
1764
+ page.islandScripts = mod.islandScripts;
1765
+ return renderPageResult(page);
1371
1766
  }),
1767
+ matchRoute: (path) => {
1768
+ const matched = matchManifestRoute(path);
1769
+ const route = matched === null ? undefined : routeById.get(matched.routeId);
1770
+ return route === undefined ? null : { routeId: route.id, layoutIds: route.layoutIds };
1771
+ },
1372
1772
  fallback: withResponseControls(async (c, controls) => {
1373
1773
  const match = matchNotFoundScope?.(pathOf(c.req)) ?? null;
1374
1774
  const target = match === null ? undefined : notFoundTargets[Number(match.routeId)];
@@ -1394,6 +1794,7 @@ export function createPageRequestExecutor(options) {
1394
1794
  draft,
1395
1795
  search: loaderSearch(mod.searchSchema, c.req),
1396
1796
  set: controls.scope(PAGE_SCOPE),
1797
+ live: NO_LIVE,
1397
1798
  };
1398
1799
  if (target.route.middlewareIds !== undefined) {
1399
1800
  await runMiddleware(target.route, ctx, controls);