@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,249 @@
1
+ /**
2
+ * Fine-grained signals - the reactive core of `@nifrajs/web/islands`. Pull-based auto-tracking with
3
+ * per-run re-tracking (a branch switch drops the stale branch's subscriptions), `Object.is`
4
+ * equality skips, and an explicit synchronous {@link batch} that coalesces effect re-runs.
5
+ *
6
+ * Deliberately tiny and synchronous: a write recomputes the computeds it feeds at once and runs its
7
+ * effects before `set` returns, each once, after every computed has settled - predictable for
8
+ * island-scale code (a counter, a filter drawer), and the whole core stays well under a kilobyte.
9
+ * This is NOT a general app framework - just island-scale reactivity.
10
+ */
11
+
12
+ import { adopt } from "./island-owner.ts"
13
+
14
+ interface EffectNode {
15
+ run: () => void
16
+ /** Each source's reader set, mapped to the run (`v`) that last read it. */
17
+ readonly deps: Map<Set<EffectNode>, number>
18
+ v: number
19
+ /** Sources read so far in this run. */
20
+ n: number
21
+ /** Running now: a write during the run requeues it only for a source this run already read. */
22
+ r?: boolean
23
+ /** Set on a computed's node: it recomputes on the write instead of queueing. */
24
+ readonly c?: boolean | undefined
25
+ /** Queued for the current flush. */
26
+ q?: boolean
27
+ }
28
+
29
+ let currentEffect: EffectNode | undefined
30
+
31
+ /** Run `fn` without subscribing the running effect or computed to the signals it reads. */
32
+ export function untrack<T>(fn: () => T): T {
33
+ const prev = currentEffect
34
+ currentEffect = undefined
35
+ try {
36
+ return fn()
37
+ } finally {
38
+ currentEffect = prev
39
+ }
40
+ }
41
+
42
+ let batchDepth = 0
43
+ const queue: EffectNode[] = []
44
+
45
+ /** A readable/writable reactive value: call it to read (tracking), `.set` to write. */
46
+ export type Signal<T> = {
47
+ (): T
48
+ set(next: T | ((prev: T) => T)): void
49
+ }
50
+
51
+ /**
52
+ * Batch writes: effects triggered inside `fn` run ONCE after it returns, deduplicated - so
53
+ * `setA(); setB()` updates the DOM once, not twice. Re-entrant; an effect re-queued during the
54
+ * flush runs in the same flush.
55
+ */
56
+ export function batch<T>(fn: () => T): T {
57
+ batchDepth++
58
+ try {
59
+ return fn()
60
+ } finally {
61
+ flush()
62
+ }
63
+ }
64
+
65
+ // Closes one batch level; the outermost runs the queue.
66
+ function flush(): void {
67
+ let i = 0
68
+ try {
69
+ // The flush still counts as a batch: a write an effect makes here queues, so none runs twice.
70
+ while (batchDepth === 1 && i < queue.length) {
71
+ const next = queue[i++]
72
+ if (next?.q) {
73
+ next.q = false
74
+ next.run()
75
+ }
76
+ }
77
+ } finally {
78
+ // A throwing effect leaves the rest queued for the next flush.
79
+ if (i === queue.length) queue.length = 0
80
+ else queue.splice(0, i)
81
+ batchDepth--
82
+ }
83
+ }
84
+
85
+ // Each plain signal's reader set, so `watch` can subscribe without tracking.
86
+ const readers = new WeakMap<object, Set<EffectNode>>()
87
+
88
+ /** Create a signal. Reads inside an {@link effect} (or {@link computed}) subscribe automatically. */
89
+ export function signal<T>(initial: T): Signal<T> {
90
+ const subs = new Set<EffectNode>()
91
+ const s = readable(initial, subs)
92
+ readers.set(s, subs)
93
+ return s
94
+ }
95
+
96
+ /**
97
+ * Call `fn` with `source`'s value now and after each change, queued and batched like an effect but
98
+ * subscribed once instead of tracking reads. Any other getter (a computed, a derived function) is
99
+ * tracked as an effect. Either way `fn` runs untracked. Internal to the binding walker, whose bindings each read one named signal,
100
+ * and to `bindList`; not exported from `@nifrajs/web/islands`.
101
+ */
102
+ export function watch<T>(source: () => T, fn: (value: T) => void): () => void {
103
+ const subs = readers.get(source)
104
+ if (!subs)
105
+ return effect(() => {
106
+ const value = source()
107
+ untrack(() => fn(value))
108
+ })
109
+ const e: EffectNode = {
110
+ deps: new Map([[subs, 0]]),
111
+ v: 0,
112
+ n: 1,
113
+ run: () => {
114
+ const prev = currentEffect
115
+ currentEffect = undefined
116
+ try {
117
+ fn(source())
118
+ } finally {
119
+ currentEffect = prev
120
+ }
121
+ },
122
+ }
123
+ const stop = (): void => {
124
+ subs.delete(e)
125
+ e.q = false
126
+ }
127
+ subs.add(e)
128
+ adopt(stop)
129
+ batch(e.run)
130
+ return stop
131
+ }
132
+
133
+ // A signal whose readers are `subs`, so a computed can see whether anything still reads it.
134
+ function readable<T>(initial: T, subs: Set<EffectNode>): Signal<T> {
135
+ let value = initial
136
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: `set` is assigned below, before the signal is returned
137
+ const read = (() => {
138
+ const e = currentEffect
139
+ if (e) {
140
+ const seen = e.deps.get(subs)
141
+ if (seen !== e.v) {
142
+ if (seen === undefined) subs.add(e)
143
+ e.deps.set(subs, e.v)
144
+ e.n++
145
+ }
146
+ }
147
+ return value
148
+ }) as Signal<T>
149
+ read.set = (next) => {
150
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: a function passed to `set` is always the updater form
151
+ const resolved = typeof next === "function" ? (next as (prev: T) => T)(value) : next
152
+ if (Object.is(resolved, value)) return
153
+ value = resolved
154
+ // Snapshot: an effect created while this write flushes must not run in it.
155
+ batchDepth++
156
+ try {
157
+ for (const e of [...subs]) {
158
+ // A running effect is requeued only by a source its current run has already read.
159
+ if (e.r && e.deps.get(subs) !== e.v) continue
160
+ if (e.c) e.run()
161
+ else if (!e.q) {
162
+ e.q = true
163
+ queue.push(e)
164
+ }
165
+ }
166
+ } finally {
167
+ flush()
168
+ }
169
+ }
170
+ return read
171
+ }
172
+
173
+ /** A derived value: call it to read (tracking), `.dispose()` to stop recomputing it. */
174
+ export type Computed<T> = {
175
+ (): T
176
+ dispose(): void
177
+ }
178
+
179
+ /**
180
+ * Derived value, cached into a signal and computed on first read. While an effect (or another
181
+ * computed) reads it, it recomputes when its tracked inputs change. Once nothing reads it, the next
182
+ * input change stops it instead of recomputing, so a dropped computed costs nothing; reading it
183
+ * again starts it afresh. `dispose()` stops it at once.
184
+ */
185
+ export function computed<T>(fn: () => T): Computed<T> {
186
+ const subs = new Set<EffectNode>()
187
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: the first read computes it before returning
188
+ const out = readable<T>(undefined as T, subs)
189
+ let stop: (() => void) | undefined
190
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: `dispose` is assigned below
191
+ const read = (() => {
192
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: the implementation takes the computed flag its public overload hides
193
+ stop ??= (effect as (fn: () => void, c: boolean) => () => void)(() => {
194
+ if (!stop || subs.size) out.set(fn())
195
+ else read.dispose()
196
+ }, true)
197
+ return out()
198
+ }) as Computed<T>
199
+ read.dispose = () => {
200
+ stop?.()
201
+ stop = undefined
202
+ }
203
+ return read
204
+ }
205
+
206
+ /**
207
+ * Run `fn` now and again whenever any signal it read changes. Returns a disposer. Dependencies
208
+ * re-track on every run, so conditional reads subscribe to exactly the live branch. Every run,
209
+ * the first included, finishes before the effects its writes trigger run, itself among them.
210
+ */
211
+ export function effect(fn: () => void): () => void
212
+ export function effect(fn: () => void, c?: boolean): () => void {
213
+ const stop = (): void => {
214
+ for (const subs of e.deps.keys()) subs.delete(e)
215
+ e.deps.clear()
216
+ e.q = false
217
+ }
218
+ const e: EffectNode = {
219
+ c,
220
+ deps: new Map(),
221
+ v: 0,
222
+ n: 0,
223
+ // Subscriptions persist across runs; a source this run did not read is dropped afterwards.
224
+ run: () => {
225
+ ++e.v
226
+ e.n = 0
227
+ e.q = false
228
+ e.r = true
229
+ const prev = currentEffect
230
+ currentEffect = e
231
+ try {
232
+ fn()
233
+ } finally {
234
+ currentEffect = prev
235
+ e.r = false
236
+ if (e.n < e.deps.size)
237
+ for (const [subs, seen] of e.deps)
238
+ if (seen !== e.v) {
239
+ subs.delete(e)
240
+ e.deps.delete(subs)
241
+ }
242
+ }
243
+ },
244
+ }
245
+ adopt(stop)
246
+ // Batched, so a write in the first run cannot re-enter it before it finishes.
247
+ batch(e.run)
248
+ return stop
249
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Where the slot wrappers of kept pages go, decided once from the route table so that the server's
3
+ * markup and every client render agree: a wrapper that appeared or vanished with what is kept would
4
+ * remount the page inside it, and one the server did not render fails hydration.
5
+ */
6
+ import type { Manifest } from "../manifest.ts"
7
+
8
+ /** What the server and the browser both read to place the slot wrappers. */
9
+ export interface KeepAliveTable {
10
+ /** Routes whose page renders inside the slot wrappers: the build found a `keepAlive` export. */
11
+ readonly routes: readonly string[]
12
+ /** Layouts rendered inside the slot wrappers, because another chain the browser can render shares
13
+ * the layouts above one of them and not that one: a kept page then sits there beside the page on
14
+ * screen. A layout every chain shares is never wrapped. */
15
+ readonly layouts: readonly string[]
16
+ }
17
+
18
+ const sharesAbove = (a: readonly string[], b: readonly string[], depth: number): boolean => {
19
+ for (let i = 0; i < depth; i++) if (a[i] !== b[i]) return false
20
+ return true
21
+ }
22
+
23
+ /** The table for `manifest`, or `undefined` when no route keeps its page alive. */
24
+ export function keepAliveTable(manifest: Manifest): KeepAliveTable | undefined {
25
+ const kept = manifest.routes.filter((route) => route.keepAlive === true)
26
+ if (kept.length === 0) return undefined
27
+ // Every layout chain the browser renders: each route's, a root status page's (inside the root
28
+ // layout unless the page opts out, which only its module says), and a `_loading` page's, which takes
29
+ // the layouts the two pages of a navigation share, so any prefix of a route under it.
30
+ const chains: (readonly string[])[] = manifest.routes.map((route) => route.layoutIds)
31
+ if (manifest.notFound !== undefined || Object.keys(manifest.statusPages ?? {}).length > 0) {
32
+ chains.push(manifest.layouts._layout === undefined ? [] : ["_layout"])
33
+ }
34
+ for (const [id, loading] of Object.entries(manifest.loadings ?? {})) {
35
+ for (const route of manifest.routes) {
36
+ if (!route.loadingIds?.includes(id)) continue
37
+ for (let n = loading.layoutIds.length; n <= route.layoutIds.length; n++) {
38
+ chains.push(route.layoutIds.slice(0, n))
39
+ }
40
+ }
41
+ }
42
+ const layouts = new Set<string>()
43
+ for (const route of kept) {
44
+ route.layoutIds.forEach((layout, depth) => {
45
+ if (layouts.has(layout)) return
46
+ if (
47
+ chains.some(
48
+ (chain) => chain[depth] !== layout && sharesAbove(chain, route.layoutIds, depth),
49
+ )
50
+ )
51
+ layouts.add(layout)
52
+ })
53
+ }
54
+ return { routes: [...new Set(kept.map((route) => route.id))], layouts: [...layouts] }
55
+ }
56
+
57
+ /**
58
+ * The chain indices to render inside slot wrappers for a chain of `layoutIds` and then the page of
59
+ * `routeId`: each wrapped layout, and the page when its route keeps alive.
60
+ */
61
+ export function slotDepths(
62
+ table: KeepAliveTable,
63
+ layoutIds: readonly string[],
64
+ routeId: string | undefined,
65
+ ): readonly number[] {
66
+ const depths: number[] = []
67
+ layoutIds.forEach((layout, depth) => {
68
+ if (table.layouts.includes(layout)) depths.push(depth)
69
+ })
70
+ if (routeId !== undefined && table.routes.includes(routeId)) depths.push(layoutIds.length)
71
+ return depths
72
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * The seam between page execution and a configured live runtime (`@nifrajs/web/live`): types and one
3
+ * symbol. An app without `live` imports nothing from the live module itself.
4
+ */
5
+ import type { IdentityPlugin, WebSocketHandler } from "@nifrajs/core/server"
6
+ import type { LoaderContext } from "../manifest.ts"
7
+ import type { LiveClientEntry } from "./codegen.ts"
8
+
9
+ /** What `createWebApp` tells the runtime about its routes. */
10
+ export interface LiveRouteMatch {
11
+ readonly routeId: string
12
+ readonly layoutIds: readonly string[]
13
+ }
14
+
15
+ /** The app the live socket registers on. */
16
+ export interface LiveHostApp {
17
+ use(plugin: IdentityPlugin): unknown
18
+ ws<Data>(path: string, handler: WebSocketHandler<Data>): unknown
19
+ publish(topic: string, data: string): void
20
+ }
21
+
22
+ export interface LiveAttachInput {
23
+ /** The route a canonical `pathname + search` renders, or `null`. */
24
+ readonly matchRoute: (path: string) => LiveRouteMatch | null
25
+ readonly app: LiveHostApp
26
+ }
27
+
28
+ export interface LiveAttachment {
29
+ /** Admission and dispatch for the live load endpoint; `undefined` for every other request. */
30
+ readonly onRequest: (req: Request) => Promise<Request | Response> | undefined
31
+ /** The live load a page request belongs to, or `undefined` for every request that is not one. */
32
+ readonly loadOf: (req: Request) => LiveLoad | undefined
33
+ /**
34
+ * The seed a server-rendered page of `routeId` collects its keys into, or `undefined` when none can
35
+ * be issued now (the runtime not started, the bus down, Workers). Never admitted: it is no live load.
36
+ */
37
+ readonly seedOf: (req: Request, routeId: string) => LiveLoad | undefined
38
+ }
39
+
40
+ /** One live load, from the page handler's side. */
41
+ export interface LiveLoad {
42
+ /** The route the endpoint mapped the retain list against. */
43
+ readonly routeId: string
44
+ /** `undefined` when admitted; else the 503 to answer with. An admitted load is released once. */
45
+ admit(): Response | undefined
46
+ /** Release the admission slot once the response is complete: at once, or when its stream ends. */
47
+ settle(response: Response): Response
48
+ release(): void
49
+ /**
50
+ * After middleware: resolve the viewer's subject. `false` when the presented grant's retention no
51
+ * longer applies (another subject), so every owner loads fresh.
52
+ */
53
+ bind(ctx: LoaderContext): Promise<boolean>
54
+ /** Skip the page loader: the page owner is retained. Read after {@link bind}. */
55
+ readonly retainPage: boolean
56
+ /** `ctx.live` for one owner. */
57
+ collector(owner: string): (...keys: string[]) => void
58
+ /** Close an owner's collector once its loader has settled. */
59
+ seal(owner: string): void
60
+ /** The envelope's `live` member, built after every loader settled without a throw or status. */
61
+ finish(input: LiveFinish): Promise<LiveEnvelope>
62
+ }
63
+
64
+ export interface LiveFinish {
65
+ /** Owners whose loaders ran and resolved. */
66
+ readonly fresh: readonly string[]
67
+ /** Owners whose loaders were skipped: their keys come from the presented grant. */
68
+ readonly retained: readonly string[]
69
+ readonly ctx: LoaderContext
70
+ }
71
+
72
+ export interface LiveEnvelope {
73
+ /**
74
+ * Opaque and stable per viewer, one per listed secret, newest first. A load sharing none with the
75
+ * last one is another user, so client caches are cleared; a rotation always leaves one in common.
76
+ */
77
+ readonly subjects: readonly string[]
78
+ /** Absent when no grant could be issued; the client then polls. */
79
+ readonly grant?: string
80
+ /** Wire tag to the frame's owners that watch its key. */
81
+ readonly tags?: Readonly<Record<string, readonly string[]>>
82
+ /** The page loader did not run: the client keeps its page data. */
83
+ readonly page?: "retained"
84
+ /** Seconds the grant stays valid from now; the client renews a minute before. */
85
+ readonly ttl?: number
86
+ /** For query hooks: with `queryKeys`, and in development so the page can warn. */
87
+ readonly query?: LiveQueryKeys
88
+ }
89
+
90
+ export interface LiveQueryKeys {
91
+ /** Fresh per load; absent when `queryKeys` is off. */
92
+ readonly salt?: string
93
+ /** Wire tag to its key's digest under `salt`. */
94
+ readonly keys?: Readonly<Record<string, string>>
95
+ /** The app runs in development: the page warns about query keys no grant holds. */
96
+ readonly dev?: true
97
+ }
98
+
99
+ /** The owner a hole of a partially prerendered page is in its page's live frame. */
100
+ export const holeOwner = (hole: string, routeId: string): string => `B:${hole}:${routeId}`
101
+
102
+ /** `createWebApp` reads the runtime through this symbol, so it is not public API on the object. */
103
+ export const LIVE_ATTACH: unique symbol = Symbol.for("nifra.web.live.attach")
104
+
105
+ export interface LiveRuntimeHandle {
106
+ readonly [LIVE_ATTACH]: (input: LiveAttachInput) => LiveAttachment
107
+ /** The client entry's `live` option for this runtime: the endpoints it serves, where not the default. */
108
+ readonly clientEntry: LiveClientEntry
109
+ /** Announce changed keys; resolves once this process applied the change. */
110
+ publish(...keys: string[]): Promise<void>
111
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The live client's side of the query seam. A mounted query with live keys registers here, and the
3
+ * live client matches its keys to the tags each live load grants. Importing this module installs the
4
+ * seam.
5
+ */
6
+ import { installQuerySeam, type LiveQuery, queryClients } from "./query-seam.ts"
7
+
8
+ export type { LiveQuery }
9
+
10
+ let tick = 0
11
+
12
+ /** Read by a query as each fetch starts. */
13
+ export const liveTick = (): number => tick
14
+
15
+ /**
16
+ * Advanced as each live load's response arrives, and as each query registers. A query whose fetch
17
+ * started after a load arrived read the server after that load took its watermark.
18
+ */
19
+ export const advanceLiveTick = (): number => ++tick
20
+
21
+ // Query to the live tick it registered at: a change before that never reached it.
22
+ const queries = new Map<LiveQuery, number>()
23
+ const watchers = new Set<() => void>()
24
+
25
+ function changed(): void {
26
+ for (const watcher of watchers) watcher()
27
+ }
28
+
29
+ export function liveQueries(): Iterable<LiveQuery> {
30
+ return queries.keys()
31
+ }
32
+
33
+ /** The live tick `query` registered at, or `undefined` once it left. */
34
+ export function registeredAt(query: LiveQuery): number | undefined {
35
+ return queries.get(query)
36
+ }
37
+
38
+ /** Returns the function that stops watching. */
39
+ export function watchLiveQueries(watcher: () => void): () => void {
40
+ watchers.add(watcher)
41
+ return () => {
42
+ watchers.delete(watcher)
43
+ }
44
+ }
45
+
46
+ /** Another viewer: no query client keeps data loaded for the last one. */
47
+ export function clearQueryClients(): void {
48
+ for (const ref of queryClients()) ref.deref()?.clear()
49
+ }
50
+
51
+ installQuerySeam({
52
+ tick: liveTick,
53
+ track(query, mounted) {
54
+ if (mounted && query.keys().length > 0) {
55
+ if (queries.has(query)) return
56
+ queries.set(query, advanceLiveTick())
57
+ changed()
58
+ } else if (queries.delete(query)) changed()
59
+ },
60
+ })
@@ -0,0 +1,27 @@
1
+ /** Dispatched on a kept page's element as the router hides it (`keepAlive`), while it is still in the
2
+ * document and before the router's subscribers see the state that hides it. */
3
+ export const PAGE_HIDE_EVENT = "nifra:pagehide"
4
+ /** Dispatched on a kept page's element once it is shown again. */
5
+ export const PAGE_SHOW_EVENT = "nifra:pageshow"
6
+
7
+ const releases = new WeakMap<object, Set<() => void>>()
8
+
9
+ /** Run `release` once the kept page `page` leaves its view for good; the result cancels it. */
10
+ export function onPageRelease(page: object, release: () => void): () => void {
11
+ let pending = releases.get(page)
12
+ if (pending === undefined) {
13
+ pending = new Set()
14
+ releases.set(page, pending)
15
+ }
16
+ pending.add(release)
17
+ return () => {
18
+ pending.delete(release)
19
+ }
20
+ }
21
+
22
+ /** The kept page `page` left its view: what its hidden content set aside goes. */
23
+ export function releasePage(page: object): void {
24
+ const pending = releases.get(page)
25
+ releases.delete(page)
26
+ for (const release of pending ?? []) release()
27
+ }