@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
@@ -0,0 +1,95 @@
1
+ /**
2
+ * The terminal-status helpers and their brand, free of server imports: `@nifrajs/web/client` re-exports
3
+ * them for route helpers the browser also loads, which the root entry would drag `node:crypto` into.
4
+ */
5
+
6
+ /** Derived from the `Headers` constructor rather than the DOM lib's `HeadersInit`: `@nifrajs/web`
7
+ * is consumed from DOM-free programs (the root tsconfig runs `types: ["bun"]`), and naming the DOM
8
+ * type here would break their build. Same workaround as `SSEInit.headers` in core. */
9
+ export type HeadersLike = ConstructorParameters<typeof Headers>[0]
10
+
11
+ /**
12
+ * Brand marking a `Response` as a **terminal-status signal** rather than a response to serve verbatim.
13
+ *
14
+ * A loader that throws a bare `Response` has always been passed straight through - that is how
15
+ * `throw redirect(...)` works, and apps rely on it. So "render the `_404` boundary at 404" cannot be
16
+ * expressed by throwing a plain `Response`: the framework would have no way to tell it apart from a
17
+ * hand-rolled body the app wants served exactly as written. The brand is that distinction, and
18
+ * checking it *before* the pass-through is what keeps every existing throw working unchanged.
19
+ *
20
+ * `Symbol.for` matches core's `RESPONSE_RESULT`: a registry symbol survives two copies of this module,
21
+ * which a `unique symbol` would not - and a duplicate-install turning a 404 into a raw 404 body would
22
+ * be a maddening bug to trace.
23
+ */
24
+ export const STATUS_SIGNAL = Symbol.for("nifra.web.status-signal")
25
+
26
+ export interface StatusSignal extends Response {
27
+ readonly [STATUS_SIGNAL]: { readonly status: number; readonly headers?: HeadersLike }
28
+ }
29
+
30
+ export function isStatusSignal(value: unknown): value is StatusSignal {
31
+ return value instanceof Response && STATUS_SIGNAL in value
32
+ }
33
+
34
+ function statusSignal(status: number, options: StatusPageOptions): never {
35
+ if (!Number.isInteger(status) || status < 400 || status > 599) {
36
+ throw new Error(
37
+ `[nifra/web] statusPage(${JSON.stringify(status)}) must be an integer 4xx or 5xx status. Use redirect() for 3xx, and return data for a successful render.`,
38
+ )
39
+ }
40
+ const response = new Response(null, { status })
41
+ // Non-enumerable so the brand never lands in a structured clone or a JSON log of the response.
42
+ Object.defineProperty(response, STATUS_SIGNAL, {
43
+ value: { status, ...(options.headers !== undefined ? { headers: options.headers } : {}) },
44
+ enumerable: false,
45
+ })
46
+ throw response
47
+ }
48
+
49
+ /** Options shared by {@link notFound}, {@link gone}, and {@link statusPage}. */
50
+ export interface StatusPageOptions {
51
+ /**
52
+ * Extra headers for the rendered response - `cache-control` above all.
53
+ *
54
+ * The defaults differ by status for a reason: a 404 may be a race with publication, so it wants a
55
+ * short TTL, while a 410 is a promise that the URL is permanently gone and can be cached hard.
56
+ * Getting this wrong is the difference between a crawler dropping a dead URL and re-fetching it
57
+ * for weeks.
58
+ */
59
+ readonly headers?: HeadersLike
60
+ }
61
+
62
+ /**
63
+ * Render the nearest `_404` page at status **404**. `throw` it from a loader when the record does not
64
+ * exist.
65
+ *
66
+ * This is the fix for the soft 404: a matched route whose loader finds nothing has otherwise no way to
67
+ * answer 404, so the path of least resistance is to return empty data and render "not found" inside a
68
+ * **200**. That looks correct in a browser and is invisible in review, which is why it ships and stays
69
+ * shipped - while search engines penalise it and keep the dead URL indexed.
70
+ *
71
+ * Returns `never`: these throw, so a loader narrows without a redundant `return`, and the type states
72
+ * the thing the "loaders `throw` redirect, actions `return` it" rule already trips people on.
73
+ */
74
+ export function notFound(options: StatusPageOptions = {}): never {
75
+ return statusSignal(404, options)
76
+ }
77
+
78
+ /**
79
+ * Render a terminal page at status **410 Gone**. `throw` it from a loader for a record that existed and
80
+ * was deliberately removed - a withdrawn listing, a deleted post.
81
+ *
82
+ * 410 is not a pedantic 404: it tells a crawler to **drop** the URL rather than re-fetch it for weeks
83
+ * on the assumption the 404 was transient. Uses `_410.tsx` if the app has one, otherwise `_404`.
84
+ */
85
+ export function gone(options: StatusPageOptions = {}): never {
86
+ return statusSignal(410, options)
87
+ }
88
+
89
+ /**
90
+ * Render a terminal page at any 4xx/5xx status - the escape hatch behind {@link notFound} and
91
+ * {@link gone} (402, 451, …). Uses `_<status>.tsx` if present, otherwise `_404`.
92
+ */
93
+ export function statusPage(status: number, options: StatusPageOptions = {}): never {
94
+ return statusSignal(status, options)
95
+ }
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Signatures on entries kept in a store other processes share (Workers KV, Redis). Whoever can write
3
+ * such a store could hand every visitor a page this app never built, so each entry carries an
4
+ * HMAC-SHA-256 under the app's secret, and an entry that fails the check reads as a miss.
5
+ */
6
+ import type * as NodeCrypto from "node:crypto"
7
+ import { timingSafeEqual } from "./timing-safe-equal.ts"
8
+
9
+ const TEXT = new TextEncoder()
10
+ const MAC_HEX = /^[0-9a-f]{64}$/
11
+ // Code units of signed text a signer remembers as proven. A hot page then costs a comparison per
12
+ // hit, not a hash: about 1.5 ns a byte to hash against a fraction of that to compare.
13
+ const MAX_PROVEN_CHARS = 2 * 1024 * 1024
14
+ const MAX_SIGNERS = 4
15
+
16
+ /** Below this, a secret is too short to sign with. */
17
+ export const MIN_STORE_SECRET_LENGTH = 32
18
+
19
+ export const isStoreSecret = (value: unknown): value is string =>
20
+ typeof value === "string" && value.length >= MIN_STORE_SECRET_LENGTH
21
+
22
+ /**
23
+ * Signs a list of strings. Each part is hashed behind its length, so no two lists hash the same
24
+ * bytes, whatever the parts hold.
25
+ */
26
+ export interface StoreSigner {
27
+ /** The parts' MAC, as lowercase hex. */
28
+ sign(parts: readonly string[]): Promise<string>
29
+ /** Whether `mac` is the parts' MAC under this secret. Anything but 64 lowercase hex digits is not. */
30
+ verify(parts: readonly string[], mac: unknown): Promise<boolean>
31
+ }
32
+
33
+ type CreateHmac = typeof NodeCrypto.createHmac
34
+
35
+ interface Hasher {
36
+ sign(parts: readonly string[]): Promise<string>
37
+ /** `mac` is already 64 lowercase hex digits. */
38
+ matches(parts: readonly string[], mac: string): Promise<boolean>
39
+ }
40
+
41
+ // Looked up, never imported: a Worker without nodejs_compat cannot load node:crypto. Where it exists
42
+ // it is the cheaper path: Web Crypto answers on another thread, about 20 us a call on Bun, Node and
43
+ // Deno, more than hashing a 16 KB page.
44
+ function nodeCreateHmac(): CreateHmac | undefined {
45
+ const createHmac = globalThis.process?.getBuiltinModule?.("node:crypto")?.createHmac
46
+ return typeof createHmac === "function" ? createHmac : undefined
47
+ }
48
+
49
+ const sameParts = (a: readonly string[], b: readonly string[]): boolean =>
50
+ a.length === b.length && a.every((part, at) => part === b[at])
51
+
52
+ const lengthOf = (parts: readonly string[]): number =>
53
+ parts.reduce((sum, part) => sum + part.length, 0)
54
+
55
+ // One signer per secret for the process, so a handler built per request (a Worker reads its bindings
56
+ // in `fetch`) still finds the pages earlier requests proved.
57
+ const signers = new Map<string, StoreSigner>()
58
+
59
+ export function storeSigner(secret: string): StoreSigner {
60
+ const existing = signers.get(secret)
61
+ if (existing !== undefined) return existing
62
+ const signer = provingSigner(hasher(secret))
63
+ for (const oldest of signers.keys()) {
64
+ if (signers.size < MAX_SIGNERS) break
65
+ signers.delete(oldest)
66
+ }
67
+ signers.set(secret, signer)
68
+ return signer
69
+ }
70
+
71
+ function provingSigner(hashing: Hasher): StoreSigner {
72
+ // MAC to the parts this signer signed or verified, oldest first.
73
+ const proven = new Map<string, readonly string[]>()
74
+ let provenChars = 0
75
+ const remember = (mac: string, parts: readonly string[]): void => {
76
+ const length = lengthOf(parts)
77
+ if (length > MAX_PROVEN_CHARS / 8) return
78
+ const had = proven.get(mac)
79
+ if (had !== undefined) {
80
+ proven.delete(mac)
81
+ provenChars -= lengthOf(had)
82
+ }
83
+ proven.set(mac, parts)
84
+ provenChars += length
85
+ for (const [oldest, kept] of proven) {
86
+ if (provenChars <= MAX_PROVEN_CHARS) break
87
+ proven.delete(oldest)
88
+ provenChars -= lengthOf(kept)
89
+ }
90
+ }
91
+ return {
92
+ async sign(parts) {
93
+ const mac = await hashing.sign(parts)
94
+ remember(mac, parts)
95
+ return mac
96
+ },
97
+ async verify(parts, mac) {
98
+ if (typeof mac !== "string" || !MAC_HEX.test(mac)) return false
99
+ const known = proven.get(mac)
100
+ if (known !== undefined && sameParts(known, parts)) return true
101
+ if (!(await hashing.matches(parts, mac))) return false
102
+ remember(mac, parts)
103
+ return true
104
+ },
105
+ }
106
+ }
107
+
108
+ function hasher(secret: string): Hasher {
109
+ const createHmac = nodeCreateHmac()
110
+ if (createHmac !== undefined) {
111
+ const hex = (parts: readonly string[]): string => {
112
+ const hmac = createHmac("sha256", secret)
113
+ for (const part of parts) hmac.update(`${part.length}:`).update(part)
114
+ return hmac.digest("hex")
115
+ }
116
+ return {
117
+ sign: async (parts) => hex(parts),
118
+ matches: async (parts, mac) => timingSafeEqual(hex(parts), mac),
119
+ }
120
+ }
121
+ let signingKey: Promise<CryptoKey> | undefined
122
+ const key = (): Promise<CryptoKey> => {
123
+ signingKey ??= crypto.subtle.importKey(
124
+ "raw",
125
+ TEXT.encode(secret),
126
+ { name: "HMAC", hash: "SHA-256" },
127
+ false,
128
+ ["sign", "verify"],
129
+ )
130
+ return signingKey
131
+ }
132
+ const bytes = (parts: readonly string[]): Uint8Array<ArrayBuffer> =>
133
+ TEXT.encode(parts.map((part) => `${part.length}:${part}`).join(""))
134
+ return {
135
+ async sign(parts) {
136
+ const mac = new Uint8Array(await crypto.subtle.sign("HMAC", await key(), bytes(parts)))
137
+ let hex = ""
138
+ for (const byte of mac) hex += byte.toString(16).padStart(2, "0")
139
+ return hex
140
+ },
141
+ async matches(parts, mac) {
142
+ const expected = new Uint8Array(mac.length / 2)
143
+ for (let at = 0; at < expected.length; at++) {
144
+ expected[at] = Number.parseInt(mac.slice(at * 2, at * 2 + 2), 16)
145
+ }
146
+ return crypto.subtle.verify("HMAC", await key(), expected, bytes(parts))
147
+ },
148
+ }
149
+ }
@@ -23,8 +23,8 @@ const isRecord = (value: unknown): value is Record<string, unknown> =>
23
23
  * Only a plugin's `config` hook output is rewritten, never mutated, and only under rolldown-vite.
24
24
  * Non-rolldown Vite is passed through verbatim.
25
25
  *
26
- * FLATTEN FIRST: a Vite plugin factory may return an ARRAY of plugins - `@vitejs/plugin-react`'s `react()`
27
- * returns `[vite:react-babel, vite:react-refresh]`, and it's `react:react-babel`'s `config` hook that emits
26
+ * FLATTEN FIRST: a plugin factory may return an ARRAY of plugins - `@vitejs/plugin-react@4.x`'s `react()`
27
+ * returns `[vite:react-babel, vite:react-refresh]`, and it's `vite:react-babel`'s `config` hook that emits
28
28
  * the offending `optimizeDeps.rollupOptions.jsx`. `nifra.config.ts` writes `vitePlugins = [react()]`, so the
29
29
  * plugin list arrives NESTED (`[[babel, refresh]]`). Without flattening, `.map` sees the inner array (which
30
30
  * has no `config`), leaves it untouched, and Vite - which flattens plugin arrays itself before running them
@@ -10,8 +10,9 @@ import {
10
10
  NIFRA_BACKEND_WS_RUNTIME,
11
11
  preRouteMountPaths,
12
12
  } from "@nifrajs/core/mount"
13
+ import { withResponseObserver } from "@nifrajs/core/response-observer"
13
14
  import type { MountableApp, MountOptions } from "@nifrajs/core/server"
14
- import { type ServerOptions, server } from "@nifrajs/core/server"
15
+ import { replacedRequestOf, type ServerOptions, server } from "@nifrajs/core/server"
15
16
  import { type CspPolicy, cspNonceResolver, DOCUMENT_POLICY, isCspPolicy } from "../csp.ts"
16
17
  import type { CssLoadingMode } from "../css-contract.ts"
17
18
  import {
@@ -26,9 +27,14 @@ import type { Manifest } from "../manifest.ts"
26
27
  import type { RenderAdapter } from "../render-seam.ts"
27
28
  import { createMatcher, PRERENDERED_LIST_PATH } from "../router.ts"
28
29
  import type { EvidenceAncestry } from "./evidence-traversal.ts"
30
+ import { identityEpochHook, type KeepAliveServerOptions } from "./identity-epoch.ts"
31
+ import { INVALIDATION, type InvalidationOp, invalidationOf } from "./invalidation-contract.ts"
32
+ import { LIVE_ATTACH, type LiveAttachment, type LiveRuntimeHandle } from "./live-contract.ts"
29
33
  import { formatShadowedPages, shadowedPages } from "./mount-shadow.ts"
30
34
  import { createPageRequestExecutor, type NonceResolver, type PageI18n } from "./page-execution.ts"
35
+ import { PPR_SERVER, PPR_SETUP, type PprSetup } from "./ppr/contract.ts"
31
36
  import { type PrerenderedPages, servePrerenderedPages } from "./prerendered-pages.ts"
37
+ import type { RevalidateResult } from "./render-document.ts"
32
38
  import { requestPattern } from "./route-path.ts"
33
39
 
34
40
  export type { NonceResolver } from "./page-execution.ts"
@@ -101,6 +107,15 @@ export interface CreateWebAppOptions<Env = unknown> {
101
107
  * and stays cacheable by `withISR` or a CDN. `meta` still receives a nonce to name.
102
108
  */
103
109
  readonly csp?: CspPolicy
110
+ /** Experimental and unstable: features whose API may change in any release. */
111
+ readonly experimental?: {
112
+ /**
113
+ * Partial prerendering, from `partialPrerendering()` in `@nifrajs/web/ppr`. A route whose backend
114
+ * half exports `shellLoader` streams a shell shared by every visitor, then each `dynamic` boundary
115
+ * as it settles. Needs `csp` (hashes), not `nonce`.
116
+ */
117
+ readonly ppr?: PprSetup
118
+ }
104
119
  /**
105
120
  * Options for the underlying `server()` - `requestTimeoutMs`, `admission`, `gracefulSignals`, and
106
121
  * the rest of {@link ServerOptions}.
@@ -221,6 +236,11 @@ export interface CreateWebAppOptions<Env = unknown> {
221
236
  * signed `__nifra_draft` cookie gets `ctx.draft === true` in loaders/actions (else always `false`).
222
237
  * Pair with `withISR({ draftSecret })` so editors bypass the cache. Omit to disable draft mode. */
223
238
  readonly draftSecret?: string
239
+ /**
240
+ * Live data refresh, from `live()` in `@nifrajs/web/live`. Loaders declare keys with `ctx.live()`;
241
+ * the app answers live loads at the runtime's load endpoint. Omit and nothing live is loaded.
242
+ */
243
+ readonly live?: LiveRuntimeHandle
224
244
  /** Per-route chunk URLs (`buildClient`'s `BuildManifest.routes`) - `routeId → [layout chunks…, own
225
245
  * chunk]`. When present, each page `modulepreload`s its matched route's chunks alongside the entry,
226
246
  * so the route code downloads in parallel (no entry→route-chunk waterfall). Omit ⇒ entry-only. */
@@ -276,6 +296,15 @@ export interface CreateWebAppOptions<Env = unknown> {
276
296
  * server entry, before its static files too. A refused path answers the policy's refusal.
277
297
  */
278
298
  readonly hostPolicy?: HostPolicy
299
+ /**
300
+ * Which cookies say who is signed in, for an app with a `keepAlive` route. A response that changes
301
+ * or deletes one rotates the readable identity epoch cookie, and every tab drops the pages it kept
302
+ * for the previous visitor. The session cookies of `@nifrajs/auth` and Auth.js count under their
303
+ * default names, Better Auth's under any `cookiePrefix`; name any other in `identityCookies`. An app
304
+ * run by `nifra dev` and `nifra build` exports this from `backend/framework.ts`. Development reports, once per
305
+ * name, an `HttpOnly` cookie that changes and is in neither list (`NIFRA_KEEPALIVE_IDENTITY_COOKIE`).
306
+ */
307
+ readonly keepAlive?: KeepAliveServerOptions
279
308
  /**
280
309
  * The files the build emitted, by request path: `buildAssets` from the generated server manifest.
281
310
  * A {@link hostPolicy} rule's `buildAssets` token matches exactly these. `nifra dev` serves the
@@ -330,6 +359,16 @@ export interface CreateWebAppOptions<Env = unknown> {
330
359
  * The `Request` is rebuilt rather than mutated (`url` is read-only), preserving method, headers, body,
331
360
  * and duplex streaming - the body is passed through unread, so a large or streamed upload is untouched.
332
361
  */
362
+ /** Publish what a write named, for an app with `live` and no cache wrapper to run the pipeline. */
363
+ async function publishOwn(
364
+ op: InvalidationOp,
365
+ request: Request,
366
+ publish: (keys: readonly string[]) => Promise<void>,
367
+ ): Promise<void> {
368
+ const { settleInvalidation } = await import("./invalidation.ts")
369
+ await settleInvalidation(op, { publish }, request, undefined)
370
+ }
371
+
333
372
  /**
334
373
  * Resolve the explicit symbol-keyed backend mount interface. The symbol seam forwards platform
335
374
  * context without making web depend on client.
@@ -421,15 +460,23 @@ export function createWebApp<Env = unknown>(
421
460
  }
422
461
  app.use(hostPolicyGate(options.hostPolicy, options.buildAssets))
423
462
  }
463
+ // Before every app hook: a live load becomes the target page's GET here, so each hook, middleware,
464
+ // gate and loader sees that path once, as a navigation would.
465
+ const liveAttachment: LiveAttachment | undefined = options.live?.[LIVE_ATTACH]({
466
+ matchRoute: (path) => pageExecutor.matchRoute(path),
467
+ app,
468
+ })
469
+ if (liveAttachment !== undefined) app.onRequest(liveAttachment.onRequest)
424
470
  // Before the mount hooks and before every page route, so the order-scoped hooks (`derive`,
425
471
  // `beforeHandle`, assurance evidence) actually bind to them - see the `use` option. A caller
426
472
  // cannot do this after the fact: by the time this function returns, the routes are declared.
427
473
  options.use?.(app)
428
474
  if (options.serverComponents !== undefined) {
429
475
  const runtime = options.serverComponents
476
+ // A route whose holes show server components is one visitor's too.
430
477
  const match = createMatcher(
431
478
  manifest.routes
432
- .filter((route) => runtime.owns(route.id))
479
+ .filter((route) => runtime.owns(route.id) || runtime.hosts?.(route.id) === true)
433
480
  .map((route) => ({ routeId: route.id, pattern: route.pattern })),
434
481
  )
435
482
  const privateRoute = (request: Request): boolean =>
@@ -473,6 +520,44 @@ export function createWebApp<Env = unknown>(
473
520
  cacheChannel.enabled = true
474
521
  },
475
522
  })
523
+ // One owner per write. A cache wrapper that opened the channel runs the whole pipeline and takes
524
+ // the operation here, by the request it passed in; without one, the app publishes the write's keys
525
+ // and tags itself, and a write with nothing to publish needs nothing at all.
526
+ const handedOver = new WeakMap<Request, InvalidationOp>()
527
+ const live = options.live
528
+ const publish =
529
+ live === undefined ? undefined : (keys: readonly string[]) => live.publish(...keys)
530
+ Object.defineProperty(app, INVALIDATION, {
531
+ value: {
532
+ take: (request: Request): InvalidationOp | undefined => {
533
+ const op = handedOver.get(request)
534
+ handedOver.delete(request)
535
+ return op
536
+ },
537
+ publish,
538
+ },
539
+ })
540
+ const invalidate = (
541
+ revalidation: RevalidateResult<unknown>,
542
+ request: Request,
543
+ ): Promise<void> | undefined => {
544
+ if (cacheChannel.enabled) {
545
+ const op = invalidationOf(revalidation)
546
+ // Every request in a hook's replacement chain: the wrapper holds the first one.
547
+ for (
548
+ let held: Request | undefined = request;
549
+ held !== undefined;
550
+ held = replacedRequestOf(held)
551
+ )
552
+ handedOver.set(held, op)
553
+ return undefined
554
+ }
555
+ // Paths alone refresh only the submitting page, which the response header already does.
556
+ const named = revalidation.__nifraInvalidate
557
+ if (publish === undefined || named === undefined || named.keys.length + named.tags.length === 0)
558
+ return undefined
559
+ return publishOwn(invalidationOf(revalidation), request, publish)
560
+ }
476
561
  // Mount the in-process backend over HTTP at `apiPrefix`, when one is given, BEFORE page routing.
477
562
  // Core's pre-route mount seam runs before the page wildcard `/*`; parent request hooks run first,
478
563
  // child request hooks run inside the mount, and parent response hooks still wrap the result.
@@ -511,6 +596,14 @@ export function createWebApp<Env = unknown>(
511
596
  const prerenderedList = ownPatterns.has(PRERENDERED_LIST_PATH)
512
597
  ? undefined
513
598
  : servedPrerenderedList(options.prerenderedPaths)
599
+ const ppr = options.experimental?.ppr?.[PPR_SETUP]({
600
+ adapter,
601
+ // biome-ignore lint/plugin/noConditionalEmptyObjectSpread: partial prerendering lists its scripts by hash only under an app's own policy.
602
+ ...(options.csp === undefined ? {} : { csp: options.csp }),
603
+ // The content-hashed entry changes with every client build; the store is per process.
604
+ deployment: clientEntry,
605
+ perRequestNonce: options.nonce !== undefined,
606
+ })
514
607
  const pageExecutor = createPageRequestExecutor<Env>({
515
608
  ...(options.cacheRouteModules === undefined
516
609
  ? {}
@@ -558,7 +651,18 @@ export function createWebApp<Env = unknown>(
558
651
  ...(options.onLoaderError === undefined ? {} : { onLoaderError: options.onLoaderError }),
559
652
  logBoundaryError: boundaryErrorLog(options.server),
560
653
  cacheChannel,
654
+ // biome-ignore lint/plugin/noConditionalEmptyObjectSpread: the executor takes a route's shell path only when partial prerendering is set up.
655
+ ...(ppr === undefined ? {} : { ppr }),
656
+ invalidate,
657
+ liveLoadOf: liveAttachment?.loadOf,
658
+ liveSeedOf: liveAttachment?.seedOf,
561
659
  })
660
+ if (ppr !== undefined) {
661
+ Object.defineProperty(app, PPR_SERVER, { value: ppr.server })
662
+ app.register("POST", ppr.cancelPath, undefined, (c: { readonly req: Request }) =>
663
+ ppr.cancel(c.req),
664
+ )
665
+ }
562
666
 
563
667
  // A nonce app's documents authorize their scripts per request, which a file rendered once cannot.
564
668
  const prerendered =
@@ -573,7 +677,8 @@ export function createWebApp<Env = unknown>(
573
677
  app.register(
574
678
  "GET",
575
679
  pattern,
576
- options.serverComponents?.owns(route.id)
680
+ options.serverComponents?.owns(route.id) ||
681
+ options.serverComponents?.hosts?.(route.id) === true
577
682
  ? { capabilities: options.serverComponents.capabilities(route.id) }
578
683
  : undefined,
579
684
  prerendered === undefined ? render : prerendered(render),
@@ -632,6 +737,21 @@ export function createWebApp<Env = unknown>(
632
737
  // and the router refuses a second `/*` under another param name.
633
738
  if (!ownsRootCatchAll) app.register("GET", "/*", undefined, pageExecutor.fallback)
634
739
 
740
+ // Last, after every hook the caller's `use` added: a cookie any of them sets is seen here.
741
+ if (manifest.routes.some((route) => route.keepAlive === true)) {
742
+ // As for error details: only a run that says it is development or test reports, and an unset
743
+ // NODE_ENV (an unbuilt `bun app.ts`, a Worker) counts as production.
744
+ const nodeEnv = typeof process === "undefined" ? undefined : process.env?.NODE_ENV
745
+ app.use(
746
+ withResponseObserver({
747
+ onResponseHeaders: identityEpochHook(
748
+ options.keepAlive ?? {},
749
+ nodeEnv === "development" || nodeEnv === "test",
750
+ ),
751
+ }),
752
+ )
753
+ }
754
+
635
755
  const evidenceProvider: ComposedEvidenceProvider = async (passed) => {
636
756
  const { traverseEvidence } = await import("./evidence-traversal.ts")
637
757
  return traverseEvidence(evidenceProvider, passed, async (ancestry) => {