@nifrajs/web 5.5.1 → 5.6.0

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