@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
@@ -0,0 +1,136 @@
1
+ const isPlain = (value: unknown): value is Record<string, unknown> =>
2
+ typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype
3
+
4
+ const TOO_DEEP = Symbol("data too deep")
5
+
6
+ // Client loaders may return graphs, unlike JSON. Check the whole graph before comparing: a cycle
7
+ // inside a new subtree or beyond the array sample must not acquire copied parents. Shared references
8
+ // and accessors also keep the fresh result intact, rather than changing aliases or invoking getters.
9
+ function isTree(value: unknown, seen: Set<object>, depth: number): boolean {
10
+ if (!Array.isArray(value) && !isPlain(value)) return true
11
+ if (depth >= 128 || seen.has(value)) return false
12
+ seen.add(value)
13
+ for (const key of Reflect.ownKeys(value)) {
14
+ const field = Object.getOwnPropertyDescriptor(value, key)
15
+ if (
16
+ field === undefined ||
17
+ !("value" in field) ||
18
+ (!field.enumerable && !(Array.isArray(value) && key === "length")) ||
19
+ !isTree(field.value, seen, depth + 1)
20
+ )
21
+ return false
22
+ }
23
+ return true
24
+ }
25
+
26
+ /**
27
+ * Structural sharing for reloaded route data. Plain objects and arrays compare by content and Dates
28
+ * by time; other values and symbol-bearing objects come from `next`. Non-tree client data stays
29
+ * intact. `plainJson` is only for freshly parsed JSON, before any client-loader transformation.
30
+ *
31
+ * Array elements with an `id` pair by it before position, preserving unchanged rows on a prepend or
32
+ * reorder. An array whose first 32 elements keep nothing is taken whole, without a full old-row index.
33
+ */
34
+ export function shareData<T>(prev: unknown, next: T, plainJson?: boolean): T
35
+ export function shareData(prev: unknown, next: unknown, plainJson = false): unknown {
36
+ if (Object.is(prev, next)) return prev
37
+ if (!plainJson && !isTree(next, new Set(), 0)) return next
38
+ try {
39
+ return share(prev, next, 0, plainJson)
40
+ } catch (error) {
41
+ if (error === TOO_DEEP) return next
42
+ throw error
43
+ }
44
+ }
45
+
46
+ // Client loaders can attach metadata that an enumerable-string-key copy would lose. Parsed JSON has
47
+ // no symbol keys, and the check would cost a third of a reload that changed one row.
48
+ const hasSymbols = (a: object, b: object): boolean =>
49
+ Object.getOwnPropertySymbols(a).length > 0 || Object.getOwnPropertySymbols(b).length > 0
50
+
51
+ function share(prev: unknown, next: unknown, depth: number, json: boolean): unknown {
52
+ if (Object.is(prev, next)) return prev
53
+ if (prev instanceof Date)
54
+ return next instanceof Date && prev.getTime() === next.getTime() ? prev : next
55
+ if (Array.isArray(next)) {
56
+ if (!Array.isArray(prev) || (!json && hasSymbols(prev, next))) return next
57
+ if (depth >= 128) throw TOO_DEEP
58
+ const out: unknown[] = []
59
+ let same = prev.length === next.length
60
+ let byId: Map<unknown, number> | undefined
61
+ let completeIndex = false
62
+ // Which old elements a new one took, from the first id lookup on: each pairs at most once, so
63
+ // duplicate ids never put one old object at two places. Every element before that lookup took
64
+ // its own position.
65
+ let taken: Uint8Array | undefined
66
+ let kept = 0
67
+ let i = 0
68
+ for (const item of next) {
69
+ let old: unknown = taken?.[i] === 1 ? undefined : prev[i]
70
+ const id = isPlain(item) ? item.id : undefined
71
+ if (id !== undefined && !(isPlain(old) && old.id === id)) {
72
+ if (taken === undefined) {
73
+ taken = new Uint8Array(prev.length)
74
+ taken.fill(1, 0, i)
75
+ }
76
+ if (byId === undefined || (i >= 32 && !completeIndex)) {
77
+ // Index only IDs needed by the sample until a kept row makes a full index worthwhile.
78
+ const wanted =
79
+ i < 32
80
+ ? new Set(next.slice(0, 32).map((value) => (isPlain(value) ? value.id : undefined)))
81
+ : undefined
82
+ completeIndex = wanted === undefined
83
+ let min = Number.POSITIVE_INFINITY
84
+ let max = Number.NEGATIVE_INFINITY
85
+ for (const key of wanted ?? []) {
86
+ if (typeof key !== "number" || Number.isNaN(key)) {
87
+ min = Number.NEGATIVE_INFINITY
88
+ max = Number.POSITIVE_INFINITY
89
+ break
90
+ }
91
+ min = Math.min(min, key)
92
+ max = Math.max(max, key)
93
+ }
94
+ byId = new Map()
95
+ for (let at = 0; at < prev.length; at++) {
96
+ const entry = prev[at]
97
+ if (!isPlain(entry)) continue
98
+ const entryId = entry.id
99
+ if (
100
+ wanted === undefined ||
101
+ ((typeof entryId !== "number" || !(entryId < min || entryId > max)) &&
102
+ wanted.has(entryId))
103
+ )
104
+ byId.set(entryId, at)
105
+ }
106
+ }
107
+ const at = byId.get(id)
108
+ old = at === undefined || taken[at] === 1 ? undefined : prev[at]
109
+ if (at !== undefined) taken[at] = 1
110
+ } else if (taken !== undefined && i < taken.length && old !== undefined) {
111
+ taken[i] = 1
112
+ }
113
+ const value = share(old, item, depth + 1, json)
114
+ out.push(value)
115
+ if (value === old) kept++
116
+ else if (i === 31 && kept === 0) return next
117
+ if (value !== prev[i]) same = false
118
+ i++
119
+ }
120
+ return same ? prev : out
121
+ }
122
+ if (!isPlain(prev) || !isPlain(next) || (!json && hasSymbols(prev, next))) return next
123
+ if (depth >= 128) throw TOO_DEEP
124
+ const keys = Object.keys(next)
125
+ let same = keys.length === Object.keys(prev).length
126
+ const out: Record<string, unknown> = {}
127
+ for (const key of keys) {
128
+ // A parsed payload can own a `__proto__` key, which a copy would turn into its prototype.
129
+ if (key === "__proto__") return next
130
+ const old = prev[key]
131
+ const value = share(old, next[key], depth + 1, json)
132
+ if (value !== old || (old === undefined && !Object.hasOwn(prev, key))) same = false
133
+ out[key] = value
134
+ }
135
+ return same ? prev : out
136
+ }
@@ -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,14 @@ 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`, Better Auth and Auth.js count
303
+ * under their default names; name any other in `identityCookies`. Development reports, once per
304
+ * name, an `HttpOnly` cookie that changes and is in neither list (`NIFRA_KEEPALIVE_IDENTITY_COOKIE`).
305
+ */
306
+ readonly keepAlive?: KeepAliveServerOptions
279
307
  /**
280
308
  * The files the build emitted, by request path: `buildAssets` from the generated server manifest.
281
309
  * A {@link hostPolicy} rule's `buildAssets` token matches exactly these. `nifra dev` serves the
@@ -330,6 +358,16 @@ export interface CreateWebAppOptions<Env = unknown> {
330
358
  * The `Request` is rebuilt rather than mutated (`url` is read-only), preserving method, headers, body,
331
359
  * and duplex streaming - the body is passed through unread, so a large or streamed upload is untouched.
332
360
  */
361
+ /** Publish what a write named, for an app with `live` and no cache wrapper to run the pipeline. */
362
+ async function publishOwn(
363
+ op: InvalidationOp,
364
+ request: Request,
365
+ publish: (keys: readonly string[]) => Promise<void>,
366
+ ): Promise<void> {
367
+ const { settleInvalidation } = await import("./invalidation.ts")
368
+ await settleInvalidation(op, { publish }, request, undefined)
369
+ }
370
+
333
371
  /**
334
372
  * Resolve the explicit symbol-keyed backend mount interface. The symbol seam forwards platform
335
373
  * context without making web depend on client.
@@ -421,15 +459,23 @@ export function createWebApp<Env = unknown>(
421
459
  }
422
460
  app.use(hostPolicyGate(options.hostPolicy, options.buildAssets))
423
461
  }
462
+ // Before every app hook: a live load becomes the target page's GET here, so each hook, middleware,
463
+ // gate and loader sees that path once, as a navigation would.
464
+ const liveAttachment: LiveAttachment | undefined = options.live?.[LIVE_ATTACH]({
465
+ matchRoute: (path) => pageExecutor.matchRoute(path),
466
+ app,
467
+ })
468
+ if (liveAttachment !== undefined) app.onRequest(liveAttachment.onRequest)
424
469
  // Before the mount hooks and before every page route, so the order-scoped hooks (`derive`,
425
470
  // `beforeHandle`, assurance evidence) actually bind to them - see the `use` option. A caller
426
471
  // cannot do this after the fact: by the time this function returns, the routes are declared.
427
472
  options.use?.(app)
428
473
  if (options.serverComponents !== undefined) {
429
474
  const runtime = options.serverComponents
475
+ // A route whose holes show server components is one visitor's too.
430
476
  const match = createMatcher(
431
477
  manifest.routes
432
- .filter((route) => runtime.owns(route.id))
478
+ .filter((route) => runtime.owns(route.id) || runtime.hosts?.(route.id) === true)
433
479
  .map((route) => ({ routeId: route.id, pattern: route.pattern })),
434
480
  )
435
481
  const privateRoute = (request: Request): boolean =>
@@ -473,6 +519,44 @@ export function createWebApp<Env = unknown>(
473
519
  cacheChannel.enabled = true
474
520
  },
475
521
  })
522
+ // One owner per write. A cache wrapper that opened the channel runs the whole pipeline and takes
523
+ // the operation here, by the request it passed in; without one, the app publishes the write's keys
524
+ // and tags itself, and a write with nothing to publish needs nothing at all.
525
+ const handedOver = new WeakMap<Request, InvalidationOp>()
526
+ const live = options.live
527
+ const publish =
528
+ live === undefined ? undefined : (keys: readonly string[]) => live.publish(...keys)
529
+ Object.defineProperty(app, INVALIDATION, {
530
+ value: {
531
+ take: (request: Request): InvalidationOp | undefined => {
532
+ const op = handedOver.get(request)
533
+ handedOver.delete(request)
534
+ return op
535
+ },
536
+ publish,
537
+ },
538
+ })
539
+ const invalidate = (
540
+ revalidation: RevalidateResult<unknown>,
541
+ request: Request,
542
+ ): Promise<void> | undefined => {
543
+ if (cacheChannel.enabled) {
544
+ const op = invalidationOf(revalidation)
545
+ // Every request in a hook's replacement chain: the wrapper holds the first one.
546
+ for (
547
+ let held: Request | undefined = request;
548
+ held !== undefined;
549
+ held = replacedRequestOf(held)
550
+ )
551
+ handedOver.set(held, op)
552
+ return undefined
553
+ }
554
+ // Paths alone refresh only the submitting page, which the response header already does.
555
+ const named = revalidation.__nifraInvalidate
556
+ if (publish === undefined || named === undefined || named.keys.length + named.tags.length === 0)
557
+ return undefined
558
+ return publishOwn(invalidationOf(revalidation), request, publish)
559
+ }
476
560
  // Mount the in-process backend over HTTP at `apiPrefix`, when one is given, BEFORE page routing.
477
561
  // Core's pre-route mount seam runs before the page wildcard `/*`; parent request hooks run first,
478
562
  // child request hooks run inside the mount, and parent response hooks still wrap the result.
@@ -511,6 +595,14 @@ export function createWebApp<Env = unknown>(
511
595
  const prerenderedList = ownPatterns.has(PRERENDERED_LIST_PATH)
512
596
  ? undefined
513
597
  : servedPrerenderedList(options.prerenderedPaths)
598
+ const ppr = options.experimental?.ppr?.[PPR_SETUP]({
599
+ adapter,
600
+ // biome-ignore lint/plugin/noConditionalEmptyObjectSpread: partial prerendering lists its scripts by hash only under an app's own policy.
601
+ ...(options.csp === undefined ? {} : { csp: options.csp }),
602
+ // The content-hashed entry changes with every client build; the store is per process.
603
+ deployment: clientEntry,
604
+ perRequestNonce: options.nonce !== undefined,
605
+ })
514
606
  const pageExecutor = createPageRequestExecutor<Env>({
515
607
  ...(options.cacheRouteModules === undefined
516
608
  ? {}
@@ -558,7 +650,17 @@ export function createWebApp<Env = unknown>(
558
650
  ...(options.onLoaderError === undefined ? {} : { onLoaderError: options.onLoaderError }),
559
651
  logBoundaryError: boundaryErrorLog(options.server),
560
652
  cacheChannel,
653
+ // biome-ignore lint/plugin/noConditionalEmptyObjectSpread: the executor takes a route's shell path only when partial prerendering is set up.
654
+ ...(ppr === undefined ? {} : { ppr }),
655
+ invalidate,
656
+ liveLoadOf: liveAttachment?.loadOf,
561
657
  })
658
+ if (ppr !== undefined) {
659
+ Object.defineProperty(app, PPR_SERVER, { value: ppr.server })
660
+ app.register("POST", ppr.cancelPath, undefined, (c: { readonly req: Request }) =>
661
+ ppr.cancel(c.req),
662
+ )
663
+ }
562
664
 
563
665
  // A nonce app's documents authorize their scripts per request, which a file rendered once cannot.
564
666
  const prerendered =
@@ -573,7 +675,8 @@ export function createWebApp<Env = unknown>(
573
675
  app.register(
574
676
  "GET",
575
677
  pattern,
576
- options.serverComponents?.owns(route.id)
678
+ options.serverComponents?.owns(route.id) ||
679
+ options.serverComponents?.hosts?.(route.id) === true
577
680
  ? { capabilities: options.serverComponents.capabilities(route.id) }
578
681
  : undefined,
579
682
  prerendered === undefined ? render : prerendered(render),
@@ -632,6 +735,21 @@ export function createWebApp<Env = unknown>(
632
735
  // and the router refuses a second `/*` under another param name.
633
736
  if (!ownsRootCatchAll) app.register("GET", "/*", undefined, pageExecutor.fallback)
634
737
 
738
+ // Last, after every hook the caller's `use` added: a cookie any of them sets is seen here.
739
+ if (manifest.routes.some((route) => route.keepAlive === true)) {
740
+ // As for error details: only a run that says it is development or test reports, and an unset
741
+ // NODE_ENV (an unbuilt `bun app.ts`, a Worker) counts as production.
742
+ const nodeEnv = typeof process === "undefined" ? undefined : process.env?.NODE_ENV
743
+ app.use(
744
+ withResponseObserver({
745
+ onResponseHeaders: identityEpochHook(
746
+ options.keepAlive ?? {},
747
+ nodeEnv === "development" || nodeEnv === "test",
748
+ ),
749
+ }),
750
+ )
751
+ }
752
+
635
753
  const evidenceProvider: ComposedEvidenceProvider = async (passed) => {
636
754
  const { traverseEvidence } = await import("./evidence-traversal.ts")
637
755
  return traverseEvidence(evidenceProvider, passed, async (ancestry) => {