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