@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,620 @@
1
+ /**
2
+ * The live socket endpoint and its fan-out. A socket subscribes to the tags of the keys its installed
3
+ * grants hold; each change goes out as one identical `changed` frame per tag topic, so Bun can send it
4
+ * with one native publish. Frames carry tags only: never data, key text, subjects or seqs.
5
+ *
6
+ * Installing a watch and applying a bus event each run as one synchronous step (subscribe, then scan
7
+ * history, then `ready`; record in history, then publish), so every event is either reported in
8
+ * `ready.stale` or delivered after the subscription, and `ready` precedes every later frame.
9
+ */
10
+ import { isSameOriginRequest, type NifraWebSocket, type WebSocketData } from "@nifrajs/core/server"
11
+ import type { LiveRevocation } from "./bus.ts"
12
+ import type { History } from "./history.ts"
13
+ import { type Keyring, MAX_GRANT_CHARS, type OpenedGrant } from "./keyring.ts"
14
+ import type { Revocations } from "./revocations.ts"
15
+
16
+ export const CLOSE_BAD_GRANT = 4401
17
+ export const CLOSE_REVOKED = 4403
18
+ export const CLOSE_SCOPE = 4409
19
+ export const CLOSE_CAPACITY = 1013
20
+ export const CLOSE_PROTOCOL = 1008
21
+ /** The runtime closed: reconnect, most likely to another process. */
22
+ export const CLOSE_GOING_AWAY = 1001
23
+ /** Nothing to watch: a tab with no tag to hear about reconnects only once it has one. */
24
+ export const CLOSE_UNWATCHED = 1000
25
+
26
+ export const MAX_FRAME_BYTES = 32_768
27
+ export const MAX_WATCH_GRANTS = 3
28
+ export const MAX_WATCHES_PER_MINUTE = 20
29
+ /** Every inbound frame, heartbeats included: a client heartbeats twice a minute. */
30
+ export const MAX_FRAMES_PER_MINUTE = 60
31
+ export const MAX_OUTBOUND_BYTES = 65_536
32
+ export const MAX_TOPICS = 200_000
33
+ export const IDLE_MS = 150_000
34
+ /**
35
+ * How long a socket may hold no tag: from its open, or from the watch or revocation that left it
36
+ * none. A tab watches right after its first live load, so only a socket nobody listens on runs out.
37
+ */
38
+ export const FIRST_WATCH_MS = 60_000
39
+ export const SWEEP_MS = 10_000
40
+ export const COALESCE_MS = 25
41
+ /** How long after closing many sockets at once their reconnects are told to spread their reloads. */
42
+ export const RECONNECT_WINDOW_MS = 30_000
43
+ const TAG_CACHE_SIZE = 16_384
44
+ const SPREAD_STEPS = [250, 500, 1000, 2000, 5000]
45
+ const HEARTBEAT = "p"
46
+
47
+ export interface HubOptions {
48
+ readonly appId: string
49
+ readonly trusted: ReadonlySet<string>
50
+ readonly maxSockets: number
51
+ readonly maxRssBytes: number | undefined
52
+ readonly reloadRate: number
53
+ readonly instances: number
54
+ readonly spreadFloor: number
55
+ readonly hash: (key: string) => number
56
+ /** Distinct tags this process holds subscriptions for. Default {@link MAX_TOPICS}. */
57
+ readonly maxTopics?: number
58
+ /** Where key tags come from; the runtime shares one cache between its loads and this hub. */
59
+ readonly tags?: TagOf
60
+ /**
61
+ * Socket-tag subscriptions this process holds in all; past it a watch is refused with 1013. For a
62
+ * host whose memory is capped below what its sockets could ask for. Default: no budget.
63
+ */
64
+ readonly maxMemberships?: number
65
+ /**
66
+ * Where a hub that outlives its memory keeps socket state (a hibernating Durable Object): told of
67
+ * every installed watch and every socket that leaves. Its sweep runs on the host's alarm instead
68
+ * of a timer, which would keep the host awake.
69
+ */
70
+ readonly store?: HubStore
71
+ }
72
+
73
+ /** What a socket's membership is, kept so a hub can {@link LiveHub.restore} it after its memory is gone. */
74
+ export interface StoredSocket {
75
+ readonly sub: string | null
76
+ readonly exp: number
77
+ readonly tags: readonly string[]
78
+ }
79
+
80
+ export interface HubStore {
81
+ saved(ws: NifraWebSocket<LiveSocket>, socket: StoredSocket): void
82
+ removed(ws: NifraWebSocket<LiveSocket>): void
83
+ /** The last time the host answered this socket's heartbeat without waking the hub. */
84
+ lastBeat(ws: NifraWebSocket<LiveSocket>): number
85
+ }
86
+
87
+ /** Per-connection state, held in `ws.data`. */
88
+ export interface LiveSocket {
89
+ sid: string
90
+ /** The subject of the first accepted grant; `undefined` until then. */
91
+ sub: string | null | undefined
92
+ /** The highest `gen` received; a lower or equal one is ignored. */
93
+ received: number
94
+ tags: Set<string>
95
+ /** Earliest installed grant expiry, ms. */
96
+ exp: number
97
+ /** Since when it has held no tag; `undefined` while it holds one. See {@link FIRST_WATCH_MS}. */
98
+ unwatchedSince: number | undefined
99
+ window: number
100
+ frames: number
101
+ watches: number
102
+ lastSeen: number
103
+ }
104
+
105
+ interface Watch {
106
+ readonly gen: number
107
+ readonly grants: readonly string[]
108
+ }
109
+
110
+ interface Prepared {
111
+ readonly grant: OpenedGrant
112
+ readonly owners: readonly { readonly id: string; readonly w: number; readonly hashes: number[] }[]
113
+ readonly tags: readonly string[]
114
+ }
115
+
116
+ interface Pending {
117
+ tags: readonly string[]
118
+ again: boolean
119
+ }
120
+
121
+ export class LiveHub {
122
+ /** Bus unreachable: no watch is accepted and no socket kept. */
123
+ down = false
124
+ /** `changed` frames sent, one per subscribed socket: each makes a page reload. */
125
+ notified = 0
126
+ private readonly sockets = new Set<NifraWebSocket<LiveSocket>>()
127
+ /** Local subscribers per tag: the intern table, and the input to `spread`. */
128
+ private readonly members = new Map<string, number>()
129
+ /** Socket-tag subscriptions held, the sum of `members`. */
130
+ private memberships = 0
131
+ private readonly tagOf: TagOf
132
+ private readonly coalescing = new Map<string, Pending>()
133
+ /** The last time many sockets were closed at once, and how many. */
134
+ private burstAt = Number.NEGATIVE_INFINITY
135
+ private burstSize = 0
136
+ private coalesceTimer: ReturnType<typeof setTimeout> | undefined
137
+ private sweepTimer: ReturnType<typeof setInterval> | undefined
138
+
139
+ constructor(
140
+ private readonly ring: Keyring,
141
+ private readonly history: History,
142
+ private readonly revocations: Revocations,
143
+ private readonly options: HubOptions,
144
+ /** Where `changed` frames go: each attached app's `publish`. */
145
+ private readonly publishers: ReadonlySet<(topic: string, frame: string) => void>,
146
+ ) {
147
+ this.tagOf = options.tags ?? tagCache(ring)
148
+ }
149
+
150
+ get size(): number {
151
+ return this.sockets.size
152
+ }
153
+
154
+ /** The tag of `key` under each listed secret, newest first. */
155
+ tagsOf(key: string): Promise<string[]> {
156
+ const tags: Promise<string>[] = []
157
+ for (let secret = 0; secret < this.ring.secretCount; secret++)
158
+ tags.push(this.tagOf(key, secret))
159
+ return Promise.all(tags)
160
+ }
161
+
162
+ /** The handshake: a missing, `null` or untrusted Origin is refused. */
163
+ upgrade(req: Request): LiveSocket | Response {
164
+ const origin = req.headers.get("origin")
165
+ if (
166
+ origin === null ||
167
+ !(this.options.trusted.has(origin) || isSameOriginRequest(origin, req))
168
+ ) {
169
+ return Response.json({ error: "forbidden_origin" }, { status: 403 })
170
+ }
171
+ const now = Date.now()
172
+ return {
173
+ sid: randomSid(),
174
+ sub: undefined,
175
+ received: -1,
176
+ tags: new Set<string>(),
177
+ exp: Number.POSITIVE_INFINITY,
178
+ unwatchedSince: now,
179
+ window: now,
180
+ frames: 0,
181
+ watches: 0,
182
+ lastSeen: now,
183
+ }
184
+ }
185
+
186
+ open(ws: NifraWebSocket<LiveSocket>): void {
187
+ if (this.down || this.sockets.size >= this.options.maxSockets || this.overRss()) {
188
+ ws.close(CLOSE_CAPACITY, "at capacity")
189
+ return
190
+ }
191
+ this.sockets.add(ws)
192
+ this.startSweep()
193
+ const spread = Date.now() - this.burstAt < RECONNECT_WINDOW_MS ? this.spread(this.burstSize) : 0
194
+ this.send(
195
+ ws,
196
+ JSON.stringify(
197
+ spread > 0
198
+ ? { t: "welcome", sid: ws.data.sid, spread }
199
+ : { t: "welcome", sid: ws.data.sid },
200
+ ),
201
+ )
202
+ }
203
+
204
+ /** The runtime has dropped the closed socket's topics; only the counts are left to settle. */
205
+ close(ws: NifraWebSocket<LiveSocket>): void {
206
+ this.forget(ws, false)
207
+ }
208
+
209
+ /** A change, already in history: publish one frame per tag, at most once per key per window. */
210
+ change(keys: readonly string[], tags: readonly (readonly string[])[]): void {
211
+ for (let i = 0; i < keys.length; i++) {
212
+ const key = keys[i] ?? ""
213
+ const keyTags = tags[i] ?? []
214
+ // Nobody here watches it: nothing to send now or at the end of a window.
215
+ if (!keyTags.some((tag) => this.members.has(tag))) continue
216
+ const pending = this.coalescing.get(key)
217
+ if (pending !== undefined) {
218
+ pending.again = true
219
+ continue
220
+ }
221
+ this.coalescing.set(key, { tags: keyTags, again: false })
222
+ this.publishTags(keyTags)
223
+ }
224
+ if (this.coalescing.size > 0 && this.coalesceTimer === undefined) {
225
+ this.coalesceTimer = unref(setTimeout(() => this.flush(), COALESCE_MS))
226
+ }
227
+ }
228
+
229
+ /** A revocation, already stored. `tags` are the key's tags for a `revokeKey`. */
230
+ revoke(event: LiveRevocation, tags: readonly string[]): void {
231
+ if (event.key === null) {
232
+ for (const ws of [...this.sockets]) {
233
+ if (event.sub === null || ws.data.sub === event.sub) this.drop(ws, CLOSE_REVOKED, "revoked")
234
+ }
235
+ return
236
+ }
237
+ // One terminal `changed` to every subscriber, then the revoked ones leave the topics: no later
238
+ // frame for this key reaches them.
239
+ this.publishTags(tags)
240
+ for (const ws of this.sockets) {
241
+ if (event.sub !== null && ws.data.sub !== event.sub) continue
242
+ let left = false
243
+ for (const tag of tags) {
244
+ if (!ws.data.tags.has(tag)) continue
245
+ this.leave(ws, tag)
246
+ left = true
247
+ }
248
+ const state = ws.data
249
+ if (left && state.tags.size === 0) state.unwatchedSince ??= Date.now()
250
+ if (left)
251
+ this.options.store?.saved(ws, {
252
+ sub: state.sub ?? null,
253
+ exp: state.exp,
254
+ tags: [...state.tags],
255
+ })
256
+ }
257
+ }
258
+
259
+ /** Close every socket: history or revocations before now are unknown, or the bus is gone. */
260
+ closeAll(code: number, reason: string): void {
261
+ const sockets = [...this.sockets]
262
+ this.burst(sockets.length)
263
+ for (const ws of sockets) this.drop(ws, code, reason)
264
+ }
265
+
266
+ /**
267
+ * `count` sockets just closed together and will reconnect together. Each must reload under its new
268
+ * socket id, so for a while `welcome` tells them to spread those reloads as a change would.
269
+ */
270
+ burst(count: number): void {
271
+ const now = Date.now()
272
+ const recent = now - this.burstAt < RECONNECT_WINDOW_MS
273
+ this.burstSize = recent ? Math.max(this.burstSize, count) : count
274
+ this.burstAt = now
275
+ }
276
+
277
+ /** Resolves once a watch is installed or refused. */
278
+ message(ws: NifraWebSocket<LiveSocket>, data: WebSocketData): Promise<void> | undefined {
279
+ // Refused at open or already dropped: a frame still in flight gets no say.
280
+ if (!this.sockets.has(ws)) return undefined
281
+ const state = ws.data
282
+ const now = Date.now()
283
+ state.lastSeen = now
284
+ if (now - state.window >= 60_000) {
285
+ state.window = now
286
+ state.frames = 0
287
+ state.watches = 0
288
+ }
289
+ if (++state.frames > MAX_FRAMES_PER_MINUTE) {
290
+ this.drop(ws, CLOSE_PROTOCOL, "rate")
291
+ return undefined
292
+ }
293
+ if (data === HEARTBEAT) {
294
+ this.send(ws, HEARTBEAT)
295
+ return undefined
296
+ }
297
+ const watch = typeof data === "string" ? parseWatch(data) : undefined
298
+ if (watch === undefined) {
299
+ this.drop(ws, CLOSE_PROTOCOL, "protocol")
300
+ return undefined
301
+ }
302
+ if (++state.watches > MAX_WATCHES_PER_MINUTE) {
303
+ this.drop(ws, CLOSE_PROTOCOL, "rate")
304
+ return undefined
305
+ }
306
+ if (watch.gen <= state.received) return undefined
307
+ state.received = watch.gen
308
+ return this.install(ws, watch).catch(() => this.drop(ws, 1011, "error"))
309
+ }
310
+
311
+ private async install(ws: NifraWebSocket<LiveSocket>, watch: Watch): Promise<void> {
312
+ const state = ws.data
313
+ const now = Date.now()
314
+ const prepared: Prepared[] = []
315
+ // One grant per path: the page and each open boundary show different URLs.
316
+ const paths = new Set<string>()
317
+ for (const token of watch.grants) {
318
+ const grant = await this.ring.open(token, now)
319
+ if (grant === undefined || grant.claims.sid !== state.sid) {
320
+ return this.drop(ws, CLOSE_BAD_GRANT, "grant")
321
+ }
322
+ if (paths.has(grant.claims.path)) return this.drop(ws, CLOSE_PROTOCOL, "protocol")
323
+ paths.add(grant.claims.path)
324
+ const owners: { id: string; w: number; hashes: number[] }[] = []
325
+ const tags: string[] = []
326
+ for (const [id, owner] of Object.entries(grant.claims.owners)) {
327
+ owners.push({ id, w: owner.w, hashes: owner.k.map(this.options.hash) })
328
+ tags.push(...(await Promise.all(owner.k.map((key) => this.tagOf(key, grant.secret)))))
329
+ }
330
+ prepared.push({ grant, owners, tags })
331
+ }
332
+
333
+ // One synchronous step from here to `ready`.
334
+ if (!this.sockets.has(ws) || ws.readyState !== 1 || state.received !== watch.gen) return
335
+ if (this.down) return this.drop(ws, CLOSE_CAPACITY, "unavailable")
336
+ let sub = state.sub
337
+ for (const { grant } of prepared) {
338
+ const claims = grant.claims
339
+ if (this.revocations.grantRevoked(claims)) return this.drop(ws, CLOSE_REVOKED, "revoked")
340
+ for (const owner of Object.values(claims.owners)) {
341
+ for (const key of owner.k) {
342
+ if (this.revocations.keyRevoked(key, claims)) {
343
+ return this.drop(ws, CLOSE_BAD_GRANT, "grant")
344
+ }
345
+ }
346
+ }
347
+ if (sub !== undefined && sub !== claims.sub) return this.drop(ws, CLOSE_SCOPE, "scope")
348
+ sub = claims.sub
349
+ }
350
+ // At most 3 grants of 64 keys each (`parseWatch`, the keyring): 192 memberships per socket.
351
+ const next = new Set<string>()
352
+ for (const { tags } of prepared) for (const tag of tags) next.add(tag)
353
+ let added = 0
354
+ for (const tag of next) if (!this.members.has(tag)) added++
355
+ const budget = this.options.maxMemberships ?? Number.POSITIVE_INFINITY
356
+ if (
357
+ this.members.size + added > (this.options.maxTopics ?? MAX_TOPICS) ||
358
+ this.memberships - state.tags.size + next.size > budget
359
+ ) {
360
+ return this.drop(ws, CLOSE_CAPACITY, "at capacity")
361
+ }
362
+ state.sub = sub
363
+ for (const tag of state.tags) if (!next.has(tag)) this.leave(ws, tag)
364
+ for (const tag of next) {
365
+ if (state.tags.has(tag)) continue
366
+ this.join(ws, tag)
367
+ }
368
+ // An empty watch does not restart the clock: only holding a tag stops it.
369
+ state.unwatchedSince = next.size === 0 ? (state.unwatchedSince ?? now) : undefined
370
+ // Per grant, in watch order: two frames can hold an owner of the same id.
371
+ const stale: string[][] = []
372
+ let exp = Number.POSITIVE_INFINITY
373
+ for (const { grant, owners } of prepared) {
374
+ exp = Math.min(exp, grant.claims.exp * 1000)
375
+ const changed: string[] = []
376
+ for (const owner of owners) {
377
+ if (
378
+ !this.history.known(owner.w) ||
379
+ owner.hashes.some((hash) => this.history.changedSince(hash, owner.w))
380
+ )
381
+ changed.push(owner.id)
382
+ }
383
+ stale.push(changed)
384
+ }
385
+ state.exp = exp
386
+ this.options.store?.saved(ws, { sub: state.sub ?? null, exp, tags: [...state.tags] })
387
+ this.send(ws, JSON.stringify({ t: "ready", gen: watch.gen, stale }))
388
+ }
389
+
390
+ /**
391
+ * A socket whose hub lost its memory but not its connection: its membership comes back as stored.
392
+ * Every revocation since was applied to what is stored, so nothing here is checked again.
393
+ */
394
+ restore(ws: NifraWebSocket<LiveSocket>, stored: StoredSocket | undefined): void {
395
+ this.sockets.add(ws)
396
+ if (stored === undefined) return
397
+ const state = ws.data
398
+ state.sub = stored.sub
399
+ state.exp = stored.exp
400
+ for (const tag of stored.tags) if (!state.tags.has(tag)) this.join(ws, tag)
401
+ if (state.tags.size > 0) state.unwatchedSince = undefined
402
+ }
403
+
404
+ private flush(): void {
405
+ this.coalesceTimer = undefined
406
+ for (const [key, pending] of this.coalescing) {
407
+ if (!pending.again) {
408
+ this.coalescing.delete(key)
409
+ continue
410
+ }
411
+ pending.again = false
412
+ this.publishTags(pending.tags)
413
+ }
414
+ if (this.coalescing.size > 0) {
415
+ this.coalesceTimer = unref(setTimeout(() => this.flush(), COALESCE_MS))
416
+ }
417
+ }
418
+
419
+ private publishTags(tags: readonly string[]): void {
420
+ for (const tag of tags) {
421
+ const subscribers = this.members.get(tag)
422
+ if (subscribers === undefined) continue
423
+ this.notified += subscribers
424
+ const frame = JSON.stringify({ t: "changed", tag, spread: this.spread(subscribers) })
425
+ for (const publish of this.publishers) publish(this.topic(tag), frame)
426
+ }
427
+ }
428
+
429
+ /**
430
+ * How long clients spread their reloads, from this process's subscriber count, rounded up to a few
431
+ * steps so the frame shows no more than its order of magnitude.
432
+ */
433
+ private spread(subscribers: number): number {
434
+ const { instances, reloadRate, spreadFloor } = this.options
435
+ const raw = Math.max(spreadFloor, ((subscribers * instances) / reloadRate) * 1000)
436
+ if (raw <= 100) return 0
437
+ return SPREAD_STEPS.find((step) => step >= raw) ?? 5000
438
+ }
439
+
440
+ private join(ws: NifraWebSocket<LiveSocket>, tag: string): void {
441
+ ws.data.tags.add(tag)
442
+ this.members.set(tag, (this.members.get(tag) ?? 0) + 1)
443
+ this.memberships++
444
+ ws.subscribe(this.topic(tag))
445
+ }
446
+
447
+ private leave(ws: NifraWebSocket<LiveSocket>, tag: string, open = true): void {
448
+ ws.data.tags.delete(tag)
449
+ if (open) ws.unsubscribe(this.topic(tag))
450
+ this.memberships--
451
+ const count = (this.members.get(tag) ?? 1) - 1
452
+ if (count === 0) this.members.delete(tag)
453
+ else this.members.set(tag, count)
454
+ }
455
+
456
+ /** Leave every topic before closing: nothing published after this reaches the socket. */
457
+ private drop(ws: NifraWebSocket<LiveSocket>, code: number, reason: string): void {
458
+ this.forget(ws)
459
+ ws.close(code, reason)
460
+ }
461
+
462
+ private forget(ws: NifraWebSocket<LiveSocket>, open = true): void {
463
+ if (!this.sockets.delete(ws)) return
464
+ for (const tag of [...ws.data.tags]) this.leave(ws, tag, open)
465
+ this.options.store?.removed(ws)
466
+ if (this.sockets.size === 0) this.stopSweep()
467
+ }
468
+
469
+ private send(ws: NifraWebSocket<LiveSocket>, frame: string): void {
470
+ ws.send(frame)
471
+ if (bufferedOf(ws) > MAX_OUTBOUND_BYTES) this.drop(ws, CLOSE_CAPACITY, "backpressure")
472
+ }
473
+
474
+ private topic(tag: string): string {
475
+ return topicOf(this.options.appId, tag)
476
+ }
477
+
478
+ /** Read on every open, never cached: a burst of opens must not all pass on one stale reading. */
479
+ private overRss(): boolean {
480
+ const cap = this.options.maxRssBytes
481
+ return cap !== undefined && rss() > cap
482
+ }
483
+
484
+ private startSweep(): void {
485
+ if (this.options.store !== undefined) return
486
+ this.sweepTimer ??= unref(setInterval(() => this.sweep(), SWEEP_MS))
487
+ }
488
+
489
+ private stopSweep(): void {
490
+ if (this.sweepTimer === undefined) return
491
+ clearInterval(this.sweepTimer)
492
+ this.sweepTimer = undefined
493
+ }
494
+
495
+ /**
496
+ * Drop the socket when its earliest grant has expired. A host whose sweep can run late (an alarm)
497
+ * asks before every frame it sends.
498
+ */
499
+ expired(ws: NifraWebSocket<LiveSocket>, now: number): boolean {
500
+ if (ws.data.exp > now) return false
501
+ this.drop(ws, CLOSE_BAD_GRANT, "expired")
502
+ return true
503
+ }
504
+
505
+ /**
506
+ * Expired grants, silent peers, sockets with nothing to watch and slow readers. Returns the earliest
507
+ * deadline left: a grant expiry, the silence limit, or the end of {@link FIRST_WATCH_MS}; infinite
508
+ * with no socket.
509
+ */
510
+ sweep(): number {
511
+ const now = Date.now()
512
+ const store = this.options.store
513
+ let next = Number.POSITIVE_INFINITY
514
+ for (const ws of [...this.sockets]) {
515
+ const state = ws.data
516
+ const seen =
517
+ store === undefined ? state.lastSeen : Math.max(state.lastSeen, store.lastBeat(ws))
518
+ const unwatched = state.unwatchedSince
519
+ if (state.exp <= now) this.drop(ws, CLOSE_BAD_GRANT, "expired")
520
+ else if (now - seen > IDLE_MS) this.drop(ws, CLOSE_PROTOCOL, "idle")
521
+ else if (unwatched !== undefined && now - unwatched >= FIRST_WATCH_MS) {
522
+ this.drop(ws, CLOSE_UNWATCHED, "unwatched")
523
+ } else if (bufferedOf(ws) > MAX_OUTBOUND_BYTES) this.drop(ws, CLOSE_CAPACITY, "backpressure")
524
+ else {
525
+ const due = Number.isFinite(state.exp) ? state.exp : seen + IDLE_MS
526
+ next = Math.min(next, due, unwatched === undefined ? due : unwatched + FIRST_WATCH_MS)
527
+ }
528
+ }
529
+ return next
530
+ }
531
+ }
532
+
533
+ /** The pub/sub topic of one tag. */
534
+ export const topicOf = (appId: string, tag: string): string => `nifra:live:${appId}:${tag}`
535
+
536
+ /** The wire tag of a key under one listed secret. */
537
+ export type TagOf = (key: string, secret: number) => Promise<string>
538
+
539
+ /** {@link Keyring.tag}, kept: a key's tag changes only with the secrets. Oldest entry out first. */
540
+ export function tagCache(ring: Keyring, size = TAG_CACHE_SIZE): TagOf {
541
+ const cache = new Map<string, Promise<string>>()
542
+ return (key, secret) => {
543
+ const name = `${secret}:${key}`
544
+ const cached = cache.get(name)
545
+ if (cached !== undefined) return cached
546
+ if (cache.size >= size) {
547
+ const oldest = cache.keys().next().value
548
+ if (oldest !== undefined) cache.delete(oldest)
549
+ }
550
+ const tag = ring.tag(key, secret)
551
+ cache.set(name, tag)
552
+ return tag
553
+ }
554
+ }
555
+
556
+ function parseWatch(text: string): Watch | undefined {
557
+ if (text.length > MAX_FRAME_BYTES || utf8Length(text) > MAX_FRAME_BYTES) return undefined
558
+ let value: unknown
559
+ try {
560
+ value = JSON.parse(text)
561
+ } catch {
562
+ return undefined
563
+ }
564
+ if (!isRecord(value)) return undefined
565
+ const { t, gen, grants } = value
566
+ if (t !== "watch" || typeof gen !== "number" || !Number.isSafeInteger(gen) || gen < 0)
567
+ return undefined
568
+ if (!Array.isArray(grants) || grants.length > MAX_WATCH_GRANTS) return undefined
569
+ const list: string[] = []
570
+ for (const grant of grants) {
571
+ if (typeof grant !== "string" || grant.length > MAX_GRANT_CHARS) return undefined
572
+ list.push(grant)
573
+ }
574
+ return { gen, grants: list }
575
+ }
576
+
577
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
578
+ typeof value === "object" && value !== null && !Array.isArray(value)
579
+
580
+ function utf8Length(text: string): number {
581
+ let bytes = 0
582
+ for (let i = 0; i < text.length; i++) {
583
+ const code = text.charCodeAt(i)
584
+ // A surrogate pair is 4 bytes, counted as 2 + 2.
585
+ bytes += code < 0x80 ? 1 : code < 0x800 ? 2 : code >= 0xd800 && code < 0xe000 ? 2 : 3
586
+ }
587
+ return bytes
588
+ }
589
+
590
+ function randomSid(): string {
591
+ let sid = ""
592
+ for (const byte of crypto.getRandomValues(new Uint8Array(16)))
593
+ sid += byte.toString(16).padStart(2, "0")
594
+ return sid
595
+ }
596
+
597
+ /** Bytes queued for a socket: Bun's `getBufferedAmount()`, or a standard socket's `bufferedAmount`. */
598
+ function bufferedOf(ws: NifraWebSocket<LiveSocket>): number {
599
+ const raw = ws.raw
600
+ if (typeof raw !== "object" || raw === null) return 0
601
+ if ("getBufferedAmount" in raw && typeof raw.getBufferedAmount === "function") {
602
+ const amount: unknown = raw.getBufferedAmount()
603
+ return typeof amount === "number" ? amount : 0
604
+ }
605
+ if ("bufferedAmount" in raw && typeof raw.bufferedAmount === "number") return raw.bufferedAmount
606
+ return 0
607
+ }
608
+
609
+ function rss(): number {
610
+ const usage = typeof process === "undefined" ? undefined : process.memoryUsage
611
+ return typeof usage?.rss === "function" ? usage.rss() : 0
612
+ }
613
+
614
+ export function unref<T>(timer: T): T {
615
+ if (typeof timer === "object" && timer !== null && "unref" in timer) {
616
+ const release = timer.unref
617
+ if (typeof release === "function") release.call(timer)
618
+ }
619
+ return timer
620
+ }