@nifrajs/web 5.5.0 → 5.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (457) hide show
  1. package/CHANGELOG.md +162 -0
  2. package/dist/boundary.d.ts +67 -7
  3. package/dist/boundary.d.ts.map +1 -1
  4. package/dist/boundary.js +6 -4
  5. package/dist/boundary.js.map +1 -1
  6. package/dist/build-plan.d.ts +3 -0
  7. package/dist/build-plan.d.ts.map +1 -1
  8. package/dist/build-plan.js.map +1 -1
  9. package/dist/build-vite.d.ts.map +1 -1
  10. package/dist/build-vite.js +2 -0
  11. package/dist/build-vite.js.map +1 -1
  12. package/dist/build.d.ts +9 -2
  13. package/dist/build.d.ts.map +1 -1
  14. package/dist/build.js +9 -0
  15. package/dist/build.js.map +1 -1
  16. package/dist/client.d.ts +2 -1
  17. package/dist/client.d.ts.map +1 -1
  18. package/dist/client.js +140 -12
  19. package/dist/client.js.map +1 -1
  20. package/dist/csp.d.ts +8 -0
  21. package/dist/csp.d.ts.map +1 -1
  22. package/dist/csp.js +11 -2
  23. package/dist/csp.js.map +1 -1
  24. package/dist/deferred.d.ts +6 -4
  25. package/dist/deferred.d.ts.map +1 -1
  26. package/dist/deferred.js +10 -6
  27. package/dist/deferred.js.map +1 -1
  28. package/dist/dev-feed.d.ts.map +1 -1
  29. package/dist/dev-feed.js +11 -2
  30. package/dist/dev-feed.js.map +1 -1
  31. package/dist/dev-session.d.ts.map +1 -1
  32. package/dist/dev-session.js +13 -6
  33. package/dist/dev-session.js.map +1 -1
  34. package/dist/dev.d.ts +4 -1
  35. package/dist/dev.d.ts.map +1 -1
  36. package/dist/dev.js +12 -5
  37. package/dist/dev.js.map +1 -1
  38. package/dist/diagnostic-catalog.d.ts +4 -0
  39. package/dist/diagnostic-catalog.d.ts.map +1 -1
  40. package/dist/diagnostic-catalog.js +48 -0
  41. package/dist/diagnostic-catalog.js.map +1 -1
  42. package/dist/diagnostic.d.ts +1 -1
  43. package/dist/diagnostic.d.ts.map +1 -1
  44. package/dist/diagnostic.js +1 -1
  45. package/dist/diagnostic.js.map +1 -1
  46. package/dist/fn.d.ts +5 -3
  47. package/dist/fn.d.ts.map +1 -1
  48. package/dist/fn.js +20 -5
  49. package/dist/fn.js.map +1 -1
  50. package/dist/fs.d.ts.map +1 -1
  51. package/dist/fs.js +13 -3
  52. package/dist/fs.js.map +1 -1
  53. package/dist/identity.d.ts +22 -0
  54. package/dist/identity.d.ts.map +1 -0
  55. package/dist/identity.js +60 -0
  56. package/dist/identity.js.map +1 -0
  57. package/dist/index.d.ts +7 -6
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +4 -2
  60. package/dist/index.js.map +1 -1
  61. package/dist/internal/cdn/cloudflare.d.ts +2 -1
  62. package/dist/internal/cdn/cloudflare.d.ts.map +1 -1
  63. package/dist/internal/cdn/cloudflare.js +2 -1
  64. package/dist/internal/cdn/cloudflare.js.map +1 -1
  65. package/dist/internal/certified-components.d.ts +1 -1
  66. package/dist/internal/certified-components.js +1 -1
  67. package/dist/internal/codegen.d.ts +11 -10
  68. package/dist/internal/codegen.d.ts.map +1 -1
  69. package/dist/internal/codegen.js +65 -8
  70. package/dist/internal/codegen.js.map +1 -1
  71. package/dist/internal/dev-websocket.d.ts +73 -0
  72. package/dist/internal/dev-websocket.d.ts.map +1 -0
  73. package/dist/internal/dev-websocket.js +145 -0
  74. package/dist/internal/dev-websocket.js.map +1 -0
  75. package/dist/internal/hole-runtime.d.ts +66 -0
  76. package/dist/internal/hole-runtime.d.ts.map +1 -0
  77. package/dist/internal/hole-runtime.js +211 -0
  78. package/dist/internal/hole-runtime.js.map +1 -0
  79. package/dist/internal/identity-epoch.d.ts +50 -0
  80. package/dist/internal/identity-epoch.d.ts.map +1 -0
  81. package/dist/internal/identity-epoch.js +179 -0
  82. package/dist/internal/identity-epoch.js.map +1 -0
  83. package/dist/internal/invalidation-contract.d.ts +23 -0
  84. package/dist/internal/invalidation-contract.d.ts.map +1 -0
  85. package/dist/internal/invalidation-contract.js +11 -0
  86. package/dist/internal/invalidation-contract.js.map +1 -0
  87. package/dist/internal/invalidation.d.ts +62 -0
  88. package/dist/internal/invalidation.d.ts.map +1 -0
  89. package/dist/internal/invalidation.js +149 -0
  90. package/dist/internal/invalidation.js.map +1 -0
  91. package/dist/internal/island-bindings.d.ts +51 -0
  92. package/dist/internal/island-bindings.d.ts.map +1 -0
  93. package/dist/internal/island-bindings.js +147 -0
  94. package/dist/internal/island-bindings.js.map +1 -0
  95. package/dist/internal/island-list.d.ts +26 -0
  96. package/dist/internal/island-list.d.ts.map +1 -0
  97. package/dist/internal/island-list.js +78 -0
  98. package/dist/internal/island-list.js.map +1 -0
  99. package/dist/internal/island-mount.d.ts +4 -0
  100. package/dist/internal/island-mount.d.ts.map +1 -0
  101. package/dist/internal/island-mount.js +66 -0
  102. package/dist/internal/island-mount.js.map +1 -0
  103. package/dist/internal/island-owner.d.ts +5 -0
  104. package/dist/internal/island-owner.d.ts.map +1 -0
  105. package/dist/internal/island-owner.js +20 -0
  106. package/dist/internal/island-owner.js.map +1 -0
  107. package/dist/internal/island-resource.d.ts +30 -0
  108. package/dist/internal/island-resource.d.ts.map +1 -0
  109. package/dist/internal/island-resource.js +57 -0
  110. package/dist/internal/island-resource.js.map +1 -0
  111. package/dist/internal/island-signals.d.ts +51 -0
  112. package/dist/internal/island-signals.d.ts.map +1 -0
  113. package/dist/internal/island-signals.js +220 -0
  114. package/dist/internal/island-signals.js.map +1 -0
  115. package/dist/internal/keep-alive-table.d.ts +23 -0
  116. package/dist/internal/keep-alive-table.d.ts.map +1 -0
  117. package/dist/internal/keep-alive-table.js +53 -0
  118. package/dist/internal/keep-alive-table.js.map +1 -0
  119. package/dist/internal/live-contract.d.ts +96 -0
  120. package/dist/internal/live-contract.d.ts.map +1 -0
  121. package/dist/internal/live-contract.js +5 -0
  122. package/dist/internal/live-contract.js.map +1 -0
  123. package/dist/internal/live-queries.d.ts +22 -0
  124. package/dist/internal/live-queries.d.ts.map +1 -0
  125. package/dist/internal/live-queries.js +54 -0
  126. package/dist/internal/live-queries.js.map +1 -0
  127. package/dist/internal/page-events.d.ts +10 -0
  128. package/dist/internal/page-events.d.ts.map +1 -0
  129. package/dist/internal/page-events.js +26 -0
  130. package/dist/internal/page-events.js.map +1 -0
  131. package/dist/internal/page-execution.d.ts +14 -1
  132. package/dist/internal/page-execution.d.ts.map +1 -1
  133. package/dist/internal/page-execution.js +415 -31
  134. package/dist/internal/page-execution.js.map +1 -1
  135. package/dist/internal/ppr/admission.d.ts +83 -0
  136. package/dist/internal/ppr/admission.d.ts.map +1 -0
  137. package/dist/internal/ppr/admission.js +221 -0
  138. package/dist/internal/ppr/admission.js.map +1 -0
  139. package/dist/internal/ppr/canary.d.ts +24 -0
  140. package/dist/internal/ppr/canary.d.ts.map +1 -0
  141. package/dist/internal/ppr/canary.js +102 -0
  142. package/dist/internal/ppr/canary.js.map +1 -0
  143. package/dist/internal/ppr/compile-shell.d.ts +39 -0
  144. package/dist/internal/ppr/compile-shell.d.ts.map +1 -0
  145. package/dist/internal/ppr/compile-shell.js +298 -0
  146. package/dist/internal/ppr/compile-shell.js.map +1 -0
  147. package/dist/internal/ppr/contract.d.ts +177 -0
  148. package/dist/internal/ppr/contract.d.ts.map +1 -0
  149. package/dist/internal/ppr/contract.js +80 -0
  150. package/dist/internal/ppr/contract.js.map +1 -0
  151. package/dist/internal/ppr/frames.d.ts +71 -0
  152. package/dist/internal/ppr/frames.d.ts.map +1 -0
  153. package/dist/internal/ppr/frames.js +119 -0
  154. package/dist/internal/ppr/frames.js.map +1 -0
  155. package/dist/internal/ppr/integration.d.ts +19 -0
  156. package/dist/internal/ppr/integration.d.ts.map +1 -0
  157. package/dist/internal/ppr/integration.js +106 -0
  158. package/dist/internal/ppr/integration.js.map +1 -0
  159. package/dist/internal/ppr/options.d.ts +96 -0
  160. package/dist/internal/ppr/options.d.ts.map +1 -0
  161. package/dist/internal/ppr/options.js +125 -0
  162. package/dist/internal/ppr/options.js.map +1 -0
  163. package/dist/internal/ppr/route-rules.d.ts +12 -0
  164. package/dist/internal/ppr/route-rules.d.ts.map +1 -0
  165. package/dist/internal/ppr/route-rules.js +53 -0
  166. package/dist/internal/ppr/route-rules.js.map +1 -0
  167. package/dist/internal/ppr/runtime.d.ts +29 -0
  168. package/dist/internal/ppr/runtime.d.ts.map +1 -0
  169. package/dist/internal/ppr/runtime.js +32 -0
  170. package/dist/internal/ppr/runtime.js.map +1 -0
  171. package/dist/internal/ppr/server.d.ts +29 -0
  172. package/dist/internal/ppr/server.d.ts.map +1 -0
  173. package/dist/internal/ppr/server.js +1407 -0
  174. package/dist/internal/ppr/server.js.map +1 -0
  175. package/dist/internal/ppr/shell-store.d.ts +38 -0
  176. package/dist/internal/ppr/shell-store.d.ts.map +1 -0
  177. package/dist/internal/ppr/shell-store.js +90 -0
  178. package/dist/internal/ppr/shell-store.js.map +1 -0
  179. package/dist/internal/prerendered-pages.d.ts +1 -1
  180. package/dist/internal/prerendered-pages.d.ts.map +1 -1
  181. package/dist/internal/prerendered-pages.js +7 -4
  182. package/dist/internal/prerendered-pages.js.map +1 -1
  183. package/dist/internal/query-digest.d.ts +9 -0
  184. package/dist/internal/query-digest.d.ts.map +1 -0
  185. package/dist/internal/query-digest.js +23 -0
  186. package/dist/internal/query-digest.js.map +1 -0
  187. package/dist/internal/query-runtime.d.ts +6 -1
  188. package/dist/internal/query-runtime.d.ts.map +1 -1
  189. package/dist/internal/query-runtime.js +9 -0
  190. package/dist/internal/query-runtime.js.map +1 -1
  191. package/dist/internal/query-seam.d.ts +29 -0
  192. package/dist/internal/query-seam.d.ts.map +1 -0
  193. package/dist/internal/query-seam.js +23 -0
  194. package/dist/internal/query-seam.js.map +1 -0
  195. package/dist/internal/render-document.d.ts +28 -1
  196. package/dist/internal/render-document.d.ts.map +1 -1
  197. package/dist/internal/render-document.js +83 -18
  198. package/dist/internal/render-document.js.map +1 -1
  199. package/dist/internal/request-abort.d.ts +8 -0
  200. package/dist/internal/request-abort.d.ts.map +1 -0
  201. package/dist/internal/request-abort.js +22 -0
  202. package/dist/internal/request-abort.js.map +1 -0
  203. package/dist/internal/server-boundary.d.ts +2 -0
  204. package/dist/internal/server-boundary.d.ts.map +1 -1
  205. package/dist/internal/server-boundary.js +8 -1
  206. package/dist/internal/server-boundary.js.map +1 -1
  207. package/dist/internal/share-data.d.ts +10 -0
  208. package/dist/internal/share-data.d.ts.map +1 -0
  209. package/dist/internal/share-data.js +135 -0
  210. package/dist/internal/share-data.js.map +1 -0
  211. package/dist/internal/store-signature.d.ts +15 -0
  212. package/dist/internal/store-signature.d.ts.map +1 -0
  213. package/dist/internal/store-signature.js +114 -0
  214. package/dist/internal/store-signature.js.map +1 -0
  215. package/dist/internal/vite-plugin-compat.d.ts +2 -2
  216. package/dist/internal/vite-plugin-compat.js +2 -2
  217. package/dist/internal/web-app.d.ts +25 -0
  218. package/dist/internal/web-app.d.ts.map +1 -1
  219. package/dist/internal/web-app.js +78 -3
  220. package/dist/internal/web-app.js.map +1 -1
  221. package/dist/islands-swap.d.ts +41 -0
  222. package/dist/islands-swap.d.ts.map +1 -0
  223. package/dist/islands-swap.js +270 -0
  224. package/dist/islands-swap.js.map +1 -0
  225. package/dist/islands.d.ts +86 -24
  226. package/dist/islands.d.ts.map +1 -1
  227. package/dist/islands.js +138 -78
  228. package/dist/islands.js.map +1 -1
  229. package/dist/isr.d.ts +67 -13
  230. package/dist/isr.d.ts.map +1 -1
  231. package/dist/isr.js +247 -99
  232. package/dist/isr.js.map +1 -1
  233. package/dist/keep-alive.d.ts +147 -0
  234. package/dist/keep-alive.d.ts.map +1 -0
  235. package/dist/keep-alive.js +1069 -0
  236. package/dist/keep-alive.js.map +1 -0
  237. package/dist/live/bus.d.ts +56 -0
  238. package/dist/live/bus.d.ts.map +1 -0
  239. package/dist/live/bus.js +32 -0
  240. package/dist/live/bus.js.map +1 -0
  241. package/dist/live/core.d.ts +25 -0
  242. package/dist/live/core.d.ts.map +1 -0
  243. package/dist/live/core.js +29 -0
  244. package/dist/live/core.js.map +1 -0
  245. package/dist/live/history.d.ts +38 -0
  246. package/dist/live/history.d.ts.map +1 -0
  247. package/dist/live/history.js +104 -0
  248. package/dist/live/history.js.map +1 -0
  249. package/dist/live/hub-protocol.d.ts +46 -0
  250. package/dist/live/hub-protocol.d.ts.map +1 -0
  251. package/dist/live/hub-protocol.js +108 -0
  252. package/dist/live/hub-protocol.js.map +1 -0
  253. package/dist/live/hub.d.ts +181 -0
  254. package/dist/live/hub.d.ts.map +1 -0
  255. package/dist/live/hub.js +539 -0
  256. package/dist/live/hub.js.map +1 -0
  257. package/dist/live/keyring.d.ts +87 -0
  258. package/dist/live/keyring.d.ts.map +1 -0
  259. package/dist/live/keyring.js +260 -0
  260. package/dist/live/keyring.js.map +1 -0
  261. package/dist/live/read-capped.d.ts +6 -0
  262. package/dist/live/read-capped.d.ts.map +1 -0
  263. package/dist/live/read-capped.js +42 -0
  264. package/dist/live/read-capped.js.map +1 -0
  265. package/dist/live/revocations.d.ts +52 -0
  266. package/dist/live/revocations.d.ts.map +1 -0
  267. package/dist/live/revocations.js +116 -0
  268. package/dist/live/revocations.js.map +1 -0
  269. package/dist/live/settings.d.ts +14 -0
  270. package/dist/live/settings.d.ts.map +1 -0
  271. package/dist/live/settings.js +38 -0
  272. package/dist/live/settings.js.map +1 -0
  273. package/dist/live-client.d.ts +34 -0
  274. package/dist/live-client.d.ts.map +1 -0
  275. package/dist/live-client.js +840 -0
  276. package/dist/live-client.js.map +1 -0
  277. package/dist/live-hub.d.ts +130 -0
  278. package/dist/live-hub.d.ts.map +1 -0
  279. package/dist/live-hub.js +507 -0
  280. package/dist/live-hub.js.map +1 -0
  281. package/dist/live-redis.d.ts +48 -0
  282. package/dist/live-redis.d.ts.map +1 -0
  283. package/dist/live-redis.js +201 -0
  284. package/dist/live-redis.js.map +1 -0
  285. package/dist/live.d.ts +94 -0
  286. package/dist/live.d.ts.map +1 -0
  287. package/dist/live.js +869 -0
  288. package/dist/live.js.map +1 -0
  289. package/dist/manifest.d.ts +113 -2
  290. package/dist/manifest.d.ts.map +1 -1
  291. package/dist/manifest.js +109 -5
  292. package/dist/manifest.js.map +1 -1
  293. package/dist/navigation.d.ts +9 -3
  294. package/dist/navigation.d.ts.map +1 -1
  295. package/dist/navigation.js +13 -3
  296. package/dist/navigation.js.map +1 -1
  297. package/dist/plugins/vite-zone-guard.d.ts +6 -0
  298. package/dist/plugins/vite-zone-guard.d.ts.map +1 -1
  299. package/dist/plugins/vite-zone-guard.js +8 -2
  300. package/dist/plugins/vite-zone-guard.js.map +1 -1
  301. package/dist/ppr.d.ts +73 -0
  302. package/dist/ppr.d.ts.map +1 -0
  303. package/dist/ppr.js +76 -0
  304. package/dist/ppr.js.map +1 -0
  305. package/dist/query.d.ts +8 -13
  306. package/dist/query.d.ts.map +1 -1
  307. package/dist/query.js +112 -18
  308. package/dist/query.js.map +1 -1
  309. package/dist/render-seam.d.ts +30 -0
  310. package/dist/render-seam.d.ts.map +1 -1
  311. package/dist/render-seam.js +5 -0
  312. package/dist/render-seam.js.map +1 -1
  313. package/dist/route-types.d.ts.map +1 -1
  314. package/dist/route-types.js +2 -2
  315. package/dist/route-types.js.map +1 -1
  316. package/dist/router.d.ts +72 -4
  317. package/dist/router.d.ts.map +1 -1
  318. package/dist/router.js +374 -99
  319. package/dist/router.js.map +1 -1
  320. package/dist/server-components/build.d.ts.map +1 -1
  321. package/dist/server-components/build.js +3 -3
  322. package/dist/server-components/build.js.map +1 -1
  323. package/dist/server-components/client.d.ts +23 -1
  324. package/dist/server-components/client.d.ts.map +1 -1
  325. package/dist/server-components/client.js +92 -5
  326. package/dist/server-components/client.js.map +1 -1
  327. package/dist/server-components/hole.d.ts +8 -0
  328. package/dist/server-components/hole.d.ts.map +1 -0
  329. package/dist/server-components/hole.js +20 -0
  330. package/dist/server-components/hole.js.map +1 -0
  331. package/dist/server-components/html-policy.d.ts.map +1 -1
  332. package/dist/server-components/html-policy.js +62 -4
  333. package/dist/server-components/html-policy.js.map +1 -1
  334. package/dist/server-components/html-scan.d.ts +17 -0
  335. package/dist/server-components/html-scan.d.ts.map +1 -0
  336. package/dist/server-components/html-scan.js +180 -0
  337. package/dist/server-components/html-scan.js.map +1 -0
  338. package/dist/server-components/html.d.ts.map +1 -1
  339. package/dist/server-components/html.js +19 -4
  340. package/dist/server-components/html.js.map +1 -1
  341. package/dist/server-components/router-integration.d.ts +4 -1
  342. package/dist/server-components/router-integration.d.ts.map +1 -1
  343. package/dist/server-components/router-integration.js +23 -2
  344. package/dist/server-components/router-integration.js.map +1 -1
  345. package/dist/server-components/runtime.d.ts +6 -1
  346. package/dist/server-components/runtime.d.ts.map +1 -1
  347. package/dist/server-components/runtime.js +86 -28
  348. package/dist/server-components/runtime.js.map +1 -1
  349. package/dist/server-components/state.d.ts +3 -1
  350. package/dist/server-components/state.d.ts.map +1 -1
  351. package/dist/server-components/state.js +50 -38
  352. package/dist/server-components/state.js.map +1 -1
  353. package/dist/server-components/types.d.ts +9 -0
  354. package/dist/server-components/types.d.ts.map +1 -1
  355. package/dist/vite.d.ts +5 -1
  356. package/dist/vite.d.ts.map +1 -1
  357. package/dist/vite.js +58 -4
  358. package/dist/vite.js.map +1 -1
  359. package/package.json +48 -8
  360. package/src/boundary.ts +77 -5
  361. package/src/build-plan.ts +3 -0
  362. package/src/build-vite.ts +2 -0
  363. package/src/build.ts +19 -1
  364. package/src/client.ts +147 -10
  365. package/src/csp.ts +16 -2
  366. package/src/deferred.ts +11 -5
  367. package/src/dev-feed.ts +11 -2
  368. package/src/dev-session.ts +22 -11
  369. package/src/dev.ts +29 -8
  370. package/src/diagnostic-catalog.ts +53 -0
  371. package/src/diagnostic.ts +1 -0
  372. package/src/fn.ts +24 -7
  373. package/src/fs.ts +22 -8
  374. package/src/identity.ts +61 -0
  375. package/src/index.ts +19 -0
  376. package/src/internal/cdn/cloudflare.ts +2 -1
  377. package/src/internal/certified-components.ts +1 -1
  378. package/src/internal/codegen.ts +87 -8
  379. package/src/internal/dev-websocket.ts +230 -0
  380. package/src/internal/hole-runtime.ts +286 -0
  381. package/src/internal/identity-epoch.ts +235 -0
  382. package/src/internal/invalidation-contract.ts +35 -0
  383. package/src/internal/invalidation.ts +213 -0
  384. package/src/internal/island-bindings.ts +191 -0
  385. package/src/internal/island-list.ts +92 -0
  386. package/src/internal/island-mount.ts +68 -0
  387. package/src/internal/island-owner.ts +20 -0
  388. package/src/internal/island-resource.ts +76 -0
  389. package/src/internal/island-signals.ts +249 -0
  390. package/src/internal/keep-alive-table.ts +72 -0
  391. package/src/internal/live-contract.ts +106 -0
  392. package/src/internal/live-queries.ts +60 -0
  393. package/src/internal/page-events.ts +27 -0
  394. package/src/internal/page-execution.ts +579 -40
  395. package/src/internal/ppr/admission.ts +300 -0
  396. package/src/internal/ppr/canary.ts +122 -0
  397. package/src/internal/ppr/compile-shell.ts +379 -0
  398. package/src/internal/ppr/contract.ts +265 -0
  399. package/src/internal/ppr/frames.ts +170 -0
  400. package/src/internal/ppr/integration.ts +137 -0
  401. package/src/internal/ppr/options.ts +221 -0
  402. package/src/internal/ppr/route-rules.ts +77 -0
  403. package/src/internal/ppr/runtime.ts +36 -0
  404. package/src/internal/ppr/server.ts +1764 -0
  405. package/src/internal/ppr/shell-store.ts +139 -0
  406. package/src/internal/prerendered-pages.ts +7 -4
  407. package/src/internal/query-digest.ts +34 -0
  408. package/src/internal/query-runtime.ts +14 -1
  409. package/src/internal/query-seam.ts +48 -0
  410. package/src/internal/render-document.ts +149 -22
  411. package/src/internal/request-abort.ts +26 -0
  412. package/src/internal/server-boundary.ts +10 -1
  413. package/src/internal/share-data.ts +136 -0
  414. package/src/internal/store-signature.ts +149 -0
  415. package/src/internal/vite-plugin-compat.ts +2 -2
  416. package/src/internal/web-app.ts +121 -3
  417. package/src/islands-swap.ts +283 -0
  418. package/src/islands.ts +169 -72
  419. package/src/isr.ts +348 -106
  420. package/src/keep-alive.ts +1315 -0
  421. package/src/live/bus.ts +77 -0
  422. package/src/live/core.ts +52 -0
  423. package/src/live/history.ts +109 -0
  424. package/src/live/hub-protocol.ts +133 -0
  425. package/src/live/hub.ts +615 -0
  426. package/src/live/keyring.ts +379 -0
  427. package/src/live/read-capped.ts +36 -0
  428. package/src/live/revocations.ts +155 -0
  429. package/src/live/settings.ts +46 -0
  430. package/src/live-client.ts +973 -0
  431. package/src/live-hub.ts +658 -0
  432. package/src/live-redis.ts +259 -0
  433. package/src/live.ts +1096 -0
  434. package/src/manifest.ts +274 -6
  435. package/src/navigation.ts +14 -2
  436. package/src/plugins/vite-zone-guard.ts +15 -2
  437. package/src/ppr.ts +153 -0
  438. package/src/query.ts +147 -20
  439. package/src/render-seam.ts +35 -0
  440. package/src/route-types.ts +3 -1
  441. package/src/router.ts +494 -101
  442. package/src/server-components/build.ts +9 -3
  443. package/src/server-components/client.ts +89 -5
  444. package/src/server-components/hole.ts +23 -0
  445. package/src/server-components/html-policy.ts +63 -4
  446. package/src/server-components/html-scan.ts +159 -0
  447. package/src/server-components/html.ts +18 -4
  448. package/src/server-components/router-integration.ts +21 -2
  449. package/src/server-components/runtime.ts +112 -30
  450. package/src/server-components/state.ts +66 -44
  451. package/src/server-components/types.ts +16 -0
  452. package/src/vite.ts +75 -6
  453. package/dist/nano.d.ts +0 -125
  454. package/dist/nano.d.ts.map +0 -1
  455. package/dist/nano.js +0 -190
  456. package/dist/nano.js.map +0 -1
  457. package/src/nano.ts +0 -246
package/dist/live.js ADDED
@@ -0,0 +1,869 @@
1
+ /**
2
+ * Live data refresh, server side. Loaders declare what their data depends on with `ctx.live(...keys)`;
3
+ * a live load returns an encrypted grant for those keys, and the client reloads through the same
4
+ * authorized path whenever one of them changes. Data never travels with a change notice.
5
+ *
6
+ * ```ts
7
+ * import { live } from "@nifrajs/web/live"
8
+ *
9
+ * const liveRuntime = live({ secret: process.env.LIVE_SECRET, subject: (ctx) => sessionUserId(ctx.request) })
10
+ * createWebApp({ ...options, live: liveRuntime })
11
+ * ```
12
+ *
13
+ * Every live load is a `POST` to the load endpoint that runs the target page as a `GET` through the
14
+ * whole app: every hook, middleware, gate and loader sees the target path, exactly as a navigation.
15
+ * Changes reach open pages over one WebSocket per tab, as opaque tags of the keys they watch.
16
+ */
17
+ import { isSameOriginForm, replacedRequestOf } from "@nifrajs/core/server";
18
+ import { websocket } from "@nifrajs/core/ws";
19
+ import { LIVE_ATTACH, } from "./internal/live-contract.js";
20
+ import { queryDigester, querySalt } from "./internal/query-digest.js";
21
+ import { memoryBus } from "./live/bus.js";
22
+ import { applyEvent } from "./live/core.js";
23
+ import { History, keyHasher } from "./live/history.js";
24
+ import { CLOSE_CAPACITY, LiveHub, tagCache, unref } from "./live/hub.js";
25
+ import { HUB_APPEND_PATH, HUB_AUTHORITY_PATH, HUB_CONFIG_HEADER, HUB_ORIGIN, hubConfigOf, hubName, MAX_APPEND_KEYS, parseAuthorityReply, parseSeq, } from "./live/hub-protocol.js";
26
+ import { assertLiveSecrets, createKeyring, KEY_PATTERN, MAX_GRANT_CHARS, MAX_KEYS_PER_FRAME, SID_PATTERN, toBase64Url, } from "./live/keyring.js";
27
+ import { readCapped } from "./live/read-capped.js";
28
+ import { Revocations, RevocationView } from "./live/revocations.js";
29
+ import { appIdOf, DEFAULT_MAX_SOCKETS, DEFAULT_RELOAD_RATE, grantTtlOf, positive, spreadFloorOf, } from "./live/settings.js";
30
+ import { DATA_HEADER, HOLE_FILL_HEADER, KEEP_HOLES_HEADER, NAV_FROM_HEADER, RETAIN_HEADER, } from "./router.js";
31
+ export { memoryBus, nextSeq } from "./live/bus.js";
32
+ const DEFAULT_LOAD_PATH = "/_nifra/live/load";
33
+ const DEFAULT_SOCKET_PATH = "/_nifra/live";
34
+ const DEFAULT_MAX_LOADS = 64;
35
+ const MAX_BODY_BYTES = 65_536;
36
+ const MAX_PATH_CHARS = 2048;
37
+ const MAX_GRANTS = 3;
38
+ const MAX_RETAIN = 64;
39
+ const MAX_OWNER_CHARS = 512;
40
+ /** A partially prerendered page has at most 32 holes, each named in at most 32 characters. */
41
+ const MAX_HOSTS = 32;
42
+ const MAX_HOST_CHARS = 32;
43
+ const HOST = /^[a-z0-9-]+$/;
44
+ const PAGE_FRAME = "page";
45
+ /** Renewals re-signed without loaders before one runs them all. */
46
+ const MAX_QUIET_RENEWALS = 2;
47
+ /** How long a publish waits to read its own event back; the event is in the log either way. */
48
+ const APPLY_WAIT_MS = 2000;
49
+ const HOP_BY_HOP = [
50
+ "connection",
51
+ "keep-alive",
52
+ "proxy-connection",
53
+ "te",
54
+ "trailer",
55
+ "transfer-encoding",
56
+ "upgrade",
57
+ ];
58
+ const BODY_HEADERS = ["content-length", "content-type", "content-encoding"];
59
+ const TOKEN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
60
+ const NO_STORE = "private, no-store";
61
+ const REFUSE_ALL = { grantRevoked: () => true, keyRevoked: () => true };
62
+ /** Create the live runtime for one app. */
63
+ export function live(options) {
64
+ if (typeof options.subject !== "function") {
65
+ throw new TypeError("[nifra/web] live: `subject` must be a function returning a string or null");
66
+ }
67
+ const ttl = grantTtlOf(options.grantTtl);
68
+ const maxLoads = positive(options.maxConcurrentLoads, DEFAULT_MAX_LOADS, "maxConcurrentLoads");
69
+ const maxSockets = positive(options.maxSockets, DEFAULT_MAX_SOCKETS, "maxSockets");
70
+ const instances = positive(options.instances, 1, "instances");
71
+ const reloadRate = positive(options.reloadRate, DEFAULT_RELOAD_RATE, "reloadRate");
72
+ const spreadFloor = spreadFloorOf(options.spreadFloor);
73
+ const maxRssBytes = options.maxRssBytes;
74
+ if (maxRssBytes !== undefined)
75
+ positive(maxRssBytes, 1, "maxRssBytes");
76
+ const loadPath = endpointPath(options.loadPath ?? DEFAULT_LOAD_PATH, "loadPath");
77
+ const socketPath = endpointPath(options.path ?? DEFAULT_SOCKET_PATH, "path");
78
+ if (socketPath === loadPath) {
79
+ throw new TypeError("[nifra/web] live: `path` and `loadPath` must differ");
80
+ }
81
+ const appId = appIdOf(options.appId);
82
+ const hubNamespace = options.hub;
83
+ if (hubNamespace !== undefined && noSecret(options.secret)) {
84
+ // A per-process fallback secret would differ from the hub's: no grant would ever open there.
85
+ throw new TypeError("[nifra/web] live: `hub` needs `secret`, the same one the hub is given");
86
+ }
87
+ const secrets = secretsOf(options.secret);
88
+ assertLiveSecrets(secrets);
89
+ const trusted = new Set(options.trustedOrigins ?? []);
90
+ const authorize = options.authorize;
91
+ const visible = options.visible;
92
+ const subjectOf = options.subject;
93
+ const queryKeys = options.queryKeys === true;
94
+ const production = isProduction();
95
+ if (hubNamespace !== undefined) {
96
+ if (options.bus !== undefined) {
97
+ throw new TypeError("[nifra/web] live: `hub` and `bus` are exclusive; the hub owns the log");
98
+ }
99
+ if (typeof hubNamespace?.idFromName !== "function" || typeof hubNamespace.get !== "function") {
100
+ throw new TypeError("[nifra/web] live: `hub` must be the live hub's Durable Object namespace");
101
+ }
102
+ }
103
+ const bus = options.bus ?? memoryBus();
104
+ const hash = keyHasher();
105
+ const publishers = new Set();
106
+ // Bus events wait here until the core exists, then apply in seq order: their tags are derived
107
+ // first (async), then each is recorded and fanned out in one synchronous step.
108
+ const queue = [];
109
+ let draining = false;
110
+ let core;
111
+ /** The last bus seq this process applied: a load reads it before any loader runs. */
112
+ let seen = 0;
113
+ const waiters = [];
114
+ /** A publish already found this process behind the bus; the next applied event clears it. */
115
+ let lagging = false;
116
+ const keyring = createKeyring({ secrets, appId, ttl });
117
+ // One cache for the tags a load hands out and the tags this process's sockets join.
118
+ const tags = keyring.then((ring) => tagCache(ring));
119
+ tags.catch(() => { });
120
+ const started = (async () => {
121
+ const ring = await keyring;
122
+ // On Workers the hub holds the log: this isolate reads nothing.
123
+ if (hubNamespace !== undefined)
124
+ return new Promise(() => { });
125
+ const subscription = await bus.subscribe({
126
+ event: (event) => enqueue({ kind: "event", event }),
127
+ gap: (head) => enqueue({ kind: "gap", head }),
128
+ down: () => enqueue({ kind: "down" }),
129
+ });
130
+ seen = subscription.head;
131
+ const history = new History(subscription.head);
132
+ const revocations = new Revocations(subscription.head, ttl * 1000);
133
+ const hub = new LiveHub(ring, history, revocations, {
134
+ appId,
135
+ trusted,
136
+ maxSockets,
137
+ maxRssBytes,
138
+ reloadRate,
139
+ instances,
140
+ spreadFloor,
141
+ hash,
142
+ tags: await tags,
143
+ }, publishers);
144
+ core = { ring, history, revocations, hub };
145
+ void drain();
146
+ return core;
147
+ })();
148
+ // `ready` carries a failed start; nothing else may leave it unhandled.
149
+ started.catch(() => { });
150
+ /** What a load checks its grants against: this process's own core, or the Workers hub. */
151
+ const authority = hubNamespace === undefined
152
+ ? started.then((c) => ({
153
+ ring: c.ring,
154
+ check: async () => ({ seq: seen, down: c.hub.down, revocations: c.revocations }),
155
+ }))
156
+ : keyring.then((ring) => ({ ring, check: (claims) => hubCheck(hubNamespace, ring, claims) }));
157
+ authority.catch(() => { });
158
+ /** One call through the binding; `undefined` when the hub is unreachable or answers wrong. */
159
+ async function hubCall(namespace, ring, path, body) {
160
+ try {
161
+ const stub = namespace.get(namespace.idFromName(hubName(appId)));
162
+ const res = await stub.fetch(new Request(`${HUB_ORIGIN}${path}`, {
163
+ method: "POST",
164
+ headers: { "content-type": "application/json", [HUB_CONFIG_HEADER]: hubConfigOf(ring) },
165
+ body: JSON.stringify(body),
166
+ }));
167
+ if (!res.ok) {
168
+ await res.body?.cancel();
169
+ return undefined;
170
+ }
171
+ return await res.json();
172
+ }
173
+ catch {
174
+ return undefined;
175
+ }
176
+ }
177
+ async function hubCheck(namespace, ring, claims) {
178
+ const subjects = [
179
+ ...new Set(claims.flatMap((claim) => (claim.sub === null ? [] : [claim.sub]))),
180
+ ];
181
+ const keys = [
182
+ ...new Set(claims.flatMap((claim) => Object.values(claim.owners).flatMap((owner) => owner.k))),
183
+ ];
184
+ const reply = parseAuthorityReply(await hubCall(namespace, ring, HUB_AUTHORITY_PATH, { subjects, keys }));
185
+ // Unreachable: revocations are unknown, so nothing presented is honored and no grant is issued.
186
+ if (reply === undefined)
187
+ return { seq: 0, down: true, revocations: REFUSE_ALL };
188
+ return { seq: reply.seq, down: false, revocations: new RevocationView(reply) };
189
+ }
190
+ /** The handshake goes to the hub, which holds every socket; it checks the Origin itself. */
191
+ async function forwardSocket(namespace, req) {
192
+ try {
193
+ return await namespace.get(namespace.idFromName(hubName(appId))).fetch(req);
194
+ }
195
+ catch {
196
+ return Response.json({ error: "unavailable" }, { status: 503 });
197
+ }
198
+ }
199
+ function enqueue(item) {
200
+ queue.push(item);
201
+ void drain();
202
+ }
203
+ async function drain() {
204
+ if (draining || core === undefined)
205
+ return;
206
+ draining = true;
207
+ try {
208
+ for (let item = queue.shift(); item !== undefined; item = queue.shift()) {
209
+ await apply(core, item);
210
+ }
211
+ }
212
+ finally {
213
+ draining = false;
214
+ }
215
+ }
216
+ async function apply(c, item) {
217
+ if (item.kind === "down") {
218
+ c.hub.down = true;
219
+ c.hub.closeAll(CLOSE_CAPACITY, "unavailable");
220
+ return;
221
+ }
222
+ if (item.kind === "gap")
223
+ return forgetUntil(c, item.head);
224
+ const event = item.event;
225
+ // At-least-once delivery: a repeat is already applied.
226
+ if (!Number.isSafeInteger(event.seq) || event.seq <= seen)
227
+ return;
228
+ try {
229
+ await applyEvent(c, event, hash);
230
+ settle(event.seq);
231
+ }
232
+ catch (err) {
233
+ // An event this process could not apply leaves its history and revocations unknown.
234
+ console.error("[nifra/web] live: a bus event could not be applied; closing live sockets", err);
235
+ forgetUntil(c, event.seq);
236
+ }
237
+ }
238
+ /** Everything up to `head` is unknown: refuse older grants, report all owners stale, reconnect. */
239
+ function forgetUntil(c, head) {
240
+ // Never behind what this process applied: loads already issued watermarks up to `seen`.
241
+ const at = Math.max(head, seen);
242
+ c.history.reset(at);
243
+ c.revocations.raise(at);
244
+ c.hub.down = false;
245
+ c.hub.closeAll(CLOSE_CAPACITY, "resync");
246
+ settle(at);
247
+ }
248
+ function settle(seq) {
249
+ if (seq > seen)
250
+ seen = seq;
251
+ lagging = false;
252
+ for (let i = waiters.length - 1; i >= 0; i--) {
253
+ const waiter = waiters[i];
254
+ if (waiter !== undefined && waiter.seq <= seen) {
255
+ waiters.splice(i, 1);
256
+ waiter.resolve();
257
+ }
258
+ }
259
+ }
260
+ /** Append to the bus, then wait until this process has applied it, at most {@link APPLY_WAIT_MS}. */
261
+ async function append(input) {
262
+ if (hubNamespace !== undefined)
263
+ return appendToHub(hubNamespace, input);
264
+ await started;
265
+ const seq = await bus.append(input);
266
+ if (seq <= seen)
267
+ return;
268
+ await new Promise((resolve) => {
269
+ const waiter = {
270
+ seq,
271
+ resolve: () => {
272
+ clearTimeout(timer);
273
+ resolve();
274
+ },
275
+ };
276
+ // A reader that stalled would hold every publish, and every action awaiting one, for good.
277
+ const timer = unref(setTimeout(() => {
278
+ const at = waiters.indexOf(waiter);
279
+ if (at !== -1)
280
+ waiters.splice(at, 1);
281
+ if (!lagging) {
282
+ lagging = true;
283
+ console.error(`[nifra/web] live: this process did not read a publish back from the bus within ${APPLY_WAIT_MS} ms; its sockets miss changes until the bus reader recovers`);
284
+ }
285
+ resolve();
286
+ }, APPLY_WAIT_MS));
287
+ waiters.push(waiter);
288
+ });
289
+ }
290
+ /** The hub takes at most {@link MAX_APPEND_KEYS} keys a call: a larger change goes as several. */
291
+ async function appendToHub(namespace, input) {
292
+ const ring = await keyring;
293
+ const parts = [];
294
+ if (input.type === "change") {
295
+ for (let at = 0; at < input.keys.length; at += MAX_APPEND_KEYS) {
296
+ parts.push({ type: "change", keys: input.keys.slice(at, at + MAX_APPEND_KEYS) });
297
+ }
298
+ }
299
+ else
300
+ parts.push(input);
301
+ for (let at = 0; at < parts.length; at++) {
302
+ if (parseSeq(await hubCall(namespace, ring, HUB_APPEND_PATH, parts[at])) === undefined) {
303
+ const published = at * MAX_APPEND_KEYS;
304
+ throw new Error(published === 0
305
+ ? "[nifra/web] live: the hub did not take the event; nothing was published"
306
+ : `[nifra/web] live: the hub did not take the event; the first ${published} keys were published, the rest were not`);
307
+ }
308
+ }
309
+ }
310
+ let inflight = 0;
311
+ let entering = 0;
312
+ const attach = (input) => {
313
+ const loads = new WeakMap();
314
+ const app = input.app;
315
+ if (hubNamespace === undefined)
316
+ registerSocket(app);
317
+ function registerSocket(app) {
318
+ publishers.add(weakPublisher(app, publishers));
319
+ // The socket carries no authority before a watch, so it is registered before any app guard.
320
+ app.use(websocket());
321
+ app.ws(socketPath, {
322
+ // Decided in `upgrade`, which has the request: a missing or `null` Origin is refused there.
323
+ allowedOrigins: () => true,
324
+ upgrade: async (c) => (await started).hub.upgrade(c.req),
325
+ // A socket opens only after its `upgrade` awaited the start, so the core exists by then.
326
+ open: (ws) => core?.hub.open(ws),
327
+ message: (ws, data) => core?.hub.message(ws, data),
328
+ close: (ws) => core?.hub.close(ws),
329
+ });
330
+ }
331
+ const admitLoad = async (req) => {
332
+ const origin = req.headers.get("origin");
333
+ if (origin === null ||
334
+ !(trusted.has(origin) || isSameOriginForm(origin, req.headers.get("sec-fetch-site"), req))) {
335
+ return refusal(403, "forbidden_origin");
336
+ }
337
+ const type = req.headers.get("content-type");
338
+ if (type === null || mediaType(type) !== "application/json") {
339
+ return refusal(415, "unsupported_media_type");
340
+ }
341
+ if (inflight + entering >= maxLoads)
342
+ return busy();
343
+ // Reading the body and opening grants count too: forged grants cannot buy unbounded key
344
+ // derivation.
345
+ entering++;
346
+ try {
347
+ return await prepareLoad(req);
348
+ }
349
+ finally {
350
+ entering--;
351
+ }
352
+ };
353
+ const prepareLoad = async (req) => {
354
+ const text = await readCapped(req, MAX_BODY_BYTES);
355
+ if (typeof text === "number")
356
+ return refusal(text, text === 413 ? "too_large" : "bad_request");
357
+ const body = parseBody(text);
358
+ if (body === undefined)
359
+ return refusal(400, "bad_request");
360
+ const path = canonicalPath(body.path, req.url);
361
+ if (path === undefined)
362
+ return refusal(400, "bad_path");
363
+ const auth = await authority;
364
+ const match = input.matchRoute(path);
365
+ const now = Date.now();
366
+ const presented = [];
367
+ for (const token of body.grants) {
368
+ const opened = await auth.ring.open(token, now);
369
+ if (opened === undefined)
370
+ continue;
371
+ const claims = opened.claims;
372
+ if (claims.sid !== body.sid || claims.frame !== body.frame)
373
+ continue;
374
+ // A navigation presents the grant of the page it leaves: only its layouts can carry over, and
375
+ // only where the layout itself would keep its data. A renewal is for the same page.
376
+ if (claims.path !== path && body.mode === "renew")
377
+ continue;
378
+ presented.push(opened);
379
+ }
380
+ // Read before any hook, middleware or loader runs: every owner loaded fresh is at least this new.
381
+ const state = await auth.check(presented.map((opened) => opened.claims));
382
+ const loadSeq = state.seq;
383
+ // With the log unreachable, revocations are unknown: nothing presented is honored.
384
+ const grant = state.down
385
+ ? undefined
386
+ : presented.find((opened) => !state.revocations.grantRevoked(opened.claims));
387
+ const requested = grant === undefined
388
+ ? []
389
+ : body.mode === "load"
390
+ ? body.retain
391
+ : authorize === undefined || grant.claims.r >= MAX_QUIET_RENEWALS
392
+ ? []
393
+ : Object.keys(grant.claims.owners);
394
+ const layouts = [];
395
+ const holes = [];
396
+ let retainPage = false;
397
+ if (grant !== undefined && match !== null) {
398
+ const claims = grant.claims;
399
+ for (const owner of requested) {
400
+ const held = Object.hasOwn(claims.owners, owner) ? claims.owners[owner] : undefined;
401
+ if (held === undefined)
402
+ continue;
403
+ // A key revoked since issuance is never kept or re-signed: its loader runs and decides again.
404
+ if (held.k.some((key) => state.revocations.keyRevoked(key, claims)))
405
+ continue;
406
+ if (owner.startsWith("L:")) {
407
+ const index = match.layoutIds.indexOf(owner.slice(2));
408
+ if (index !== -1 && !layouts.includes(index))
409
+ layouts.push(index);
410
+ }
411
+ else if (owner === `P:${match.routeId}` && claims.path === path) {
412
+ retainPage = true;
413
+ }
414
+ else if (claims.path === path) {
415
+ const hole = holeOf(owner, match.routeId);
416
+ if (hole !== undefined && !holes.includes(hole))
417
+ holes.push(hole);
418
+ }
419
+ }
420
+ }
421
+ const headers = internalHeaders(req.headers);
422
+ if (grant !== undefined && layouts.length > 0) {
423
+ // The page the grant was issued for: the retention rule compares each layout's params there
424
+ // with the target's, exactly as for a router navigation.
425
+ headers.set(RETAIN_HEADER, layouts.sort((a, b) => a - b).join(","));
426
+ headers.set(NAV_FROM_HEADER, grant.claims.path);
427
+ }
428
+ if (holes.length > 0)
429
+ headers.set(KEEP_HOLES_HEADER, holes.join(","));
430
+ if (body.fill.length > 0)
431
+ headers.set(HOLE_FILL_HEADER, body.fill.join(","));
432
+ const target = new Request(new URL(path, req.url), {
433
+ method: "GET",
434
+ headers,
435
+ signal: req.signal,
436
+ });
437
+ loads.set(target, new LoadState(auth.ring, state.down, body, path, loadSeq, grant, match?.routeId ?? "", retainPage));
438
+ return target;
439
+ };
440
+ return {
441
+ onRequest: (req) => {
442
+ const pathname = pathnameOf(req.url);
443
+ if (req.method === "POST" && pathname === loadPath)
444
+ return admitLoad(req);
445
+ if (hubNamespace !== undefined && req.method === "GET" && pathname === socketPath) {
446
+ return forwardSocket(hubNamespace, req);
447
+ }
448
+ return undefined;
449
+ },
450
+ loadOf: (req) => {
451
+ // An app hook after the endpoint may hand on a replacement; the load is its original's.
452
+ let current = req;
453
+ for (let hops = 0; current !== undefined && hops < 16; hops++) {
454
+ const load = loads.get(current);
455
+ if (load !== undefined)
456
+ return load;
457
+ current = replacedRequestOf(current);
458
+ }
459
+ return undefined;
460
+ },
461
+ };
462
+ };
463
+ class LoadState {
464
+ ring;
465
+ down;
466
+ body;
467
+ path;
468
+ seq;
469
+ grant;
470
+ routeId;
471
+ retainPage;
472
+ subject = null;
473
+ admitted = false;
474
+ released = false;
475
+ declared = 0;
476
+ /** Keys past the frame budget: counted for the log, never stored. */
477
+ dropped = 0;
478
+ collectors = new Map();
479
+ constructor(ring,
480
+ /** No grant is issued: no socket would accept one, and revocations are unknown. */
481
+ down, body, path, seq, grant, routeId, retainPage) {
482
+ this.ring = ring;
483
+ this.down = down;
484
+ this.body = body;
485
+ this.path = path;
486
+ this.seq = seq;
487
+ this.grant = grant;
488
+ this.routeId = routeId;
489
+ this.retainPage = retainPage;
490
+ }
491
+ admit() {
492
+ if (inflight >= maxLoads)
493
+ return busy();
494
+ inflight++;
495
+ this.admitted = true;
496
+ return undefined;
497
+ }
498
+ release() {
499
+ if (!this.admitted || this.released)
500
+ return;
501
+ this.released = true;
502
+ inflight--;
503
+ }
504
+ settle(response) {
505
+ if (response.body !== null &&
506
+ mediaType(response.headers.get("content-type") ?? "") === "application/x-ndjson") {
507
+ // Deferred values are still loading while the body streams: the slot is held until it ends.
508
+ return new Response(releasing(response.body, () => this.release()), response);
509
+ }
510
+ this.release();
511
+ return response;
512
+ }
513
+ async bind(ctx) {
514
+ const subject = await subjectOf(ctx);
515
+ if (subject !== null && typeof subject !== "string") {
516
+ throw new TypeError("[nifra/web] live: `subject` must return a string or null");
517
+ }
518
+ this.subject = subject;
519
+ if (this.grant === undefined || this.grant.claims.sub === subject)
520
+ return true;
521
+ this.retainPage = false;
522
+ return false;
523
+ }
524
+ collector(owner) {
525
+ let entry = this.collectors.get(owner);
526
+ if (entry === undefined) {
527
+ entry = { keys: new Set(), sealed: false };
528
+ this.collectors.set(owner, entry);
529
+ }
530
+ const collected = entry;
531
+ return (...keys) => {
532
+ if (collected.sealed) {
533
+ throw new TypeError(`[nifra/web] ctx.live() was called after the loader of "${owner}" settled; declare keys before the loader returns`);
534
+ }
535
+ for (const key of keys) {
536
+ if (typeof key !== "string" || !KEY_PATTERN.test(key)) {
537
+ throw new TypeError("[nifra/web] ctx.live() keys are a letter followed by up to 127 of A-Z a-z 0-9 . _ : / -");
538
+ }
539
+ if (collected.keys.has(key))
540
+ continue;
541
+ if (this.declared >= MAX_KEYS_PER_FRAME) {
542
+ if (!production) {
543
+ throw new RangeError(`[nifra/web] a live frame holds at most ${MAX_KEYS_PER_FRAME} keys; "${owner}" declared one more`);
544
+ }
545
+ this.dropped++;
546
+ continue;
547
+ }
548
+ collected.keys.add(key);
549
+ this.declared++;
550
+ }
551
+ };
552
+ }
553
+ seal(owner) {
554
+ const entry = this.collectors.get(owner);
555
+ if (entry !== undefined)
556
+ entry.sealed = true;
557
+ }
558
+ /** A renewal that kept owners counts one more; a load that ran every loader starts over. */
559
+ renewals(kept) {
560
+ const previous = this.grant?.claims.r ?? 0;
561
+ if (!kept)
562
+ return 0;
563
+ return this.body.mode === "renew" ? previous + 1 : previous;
564
+ }
565
+ async finish(input) {
566
+ for (const entry of this.collectors.values())
567
+ entry.sealed = true;
568
+ const ring = this.ring;
569
+ const subjects = await ring.subjectTags(this.subject);
570
+ const pageRetained = input.retained.some((owner) => owner.startsWith("P:"));
571
+ const bare = pageRetained ? { subjects, page: "retained" } : { subjects };
572
+ // No socket would accept a grant now, and revocations are unknown: the client polls.
573
+ if (this.down || (core?.hub.down ?? false))
574
+ return bare;
575
+ const owners = {};
576
+ const carried = new Set();
577
+ for (const owner of input.retained) {
578
+ const held = this.grant !== undefined && Object.hasOwn(this.grant.claims.owners, owner)
579
+ ? this.grant.claims.owners[owner]
580
+ : undefined;
581
+ // Kept data with no grant behind it cannot be vouched for.
582
+ if (held === undefined)
583
+ return bare;
584
+ owners[owner] = { k: held.k, w: held.w };
585
+ for (const key of held.k)
586
+ carried.add(key);
587
+ }
588
+ if (carried.size > 0 &&
589
+ authorize !== undefined &&
590
+ !(await authorize([...carried], input.ctx))) {
591
+ return bare;
592
+ }
593
+ let count = this.dropped;
594
+ for (const owner of input.fresh) {
595
+ const keys = [...(this.collectors.get(owner)?.keys ?? [])];
596
+ owners[owner] = { k: keys, w: this.seq };
597
+ }
598
+ for (const owner of Object.values(owners))
599
+ count += owner.k.length;
600
+ if (count > MAX_KEYS_PER_FRAME) {
601
+ if (!production) {
602
+ throw new RangeError(`[nifra/web] a live frame holds at most ${MAX_KEYS_PER_FRAME} keys; route "${this.routeId}" declared ${count}`);
603
+ }
604
+ console.error(`[nifra/web] live: route "${this.routeId}" declared ${count} keys, over the ${MAX_KEYS_PER_FRAME}-key frame budget; no grant issued, the client polls`);
605
+ return bare;
606
+ }
607
+ const grant = await ring.seal({
608
+ sid: this.body.sid,
609
+ sub: this.subject,
610
+ path: this.path,
611
+ frame: this.body.frame,
612
+ owners,
613
+ s: this.seq,
614
+ r: this.renewals(input.retained.length > 0),
615
+ }, Date.now());
616
+ if (grant === undefined) {
617
+ console.error(`[nifra/web] live: no grant issued for route "${this.routeId}" (over ${MAX_GRANT_CHARS} characters, or the subkey's seal limit); the client polls`);
618
+ return bare;
619
+ }
620
+ // Each key to the owners holding it, in owner order.
621
+ const owning = new Map();
622
+ for (const [name, owner] of Object.entries(owners)) {
623
+ for (const key of owner.k) {
624
+ const list = owning.get(key);
625
+ if (list === undefined)
626
+ owning.set(key, [name]);
627
+ else if (list.at(-1) !== name)
628
+ list.push(name);
629
+ }
630
+ }
631
+ const keys = [...owning.keys()];
632
+ const salt = queryKeys ? querySalt() : undefined;
633
+ const tagOf = await tags;
634
+ const [wire, digested] = await Promise.all([
635
+ Promise.all(keys.map((key) => tagOf(key, 0))),
636
+ salt === undefined
637
+ ? undefined
638
+ : queryDigester(salt).then((digest) => Promise.all(keys.map(digest))),
639
+ ]);
640
+ const tagMap = {};
641
+ const digests = {};
642
+ keys.forEach((key, at) => {
643
+ const tag = wire[at] ?? "";
644
+ tagMap[tag] = owning.get(key) ?? [];
645
+ const digest = digested?.[at];
646
+ if (digest !== undefined)
647
+ digests[tag] = digest;
648
+ });
649
+ const envelope = { ...bare, grant, tags: tagMap, ttl };
650
+ if (salt !== undefined) {
651
+ return {
652
+ ...envelope,
653
+ query: production ? { salt, keys: digests } : { salt, keys: digests, dev: true },
654
+ };
655
+ }
656
+ return production ? envelope : { ...envelope, query: { dev: true } };
657
+ }
658
+ }
659
+ const checkKey = (key, method) => {
660
+ if (typeof key !== "string" || !KEY_PATTERN.test(key)) {
661
+ throw new TypeError(`[nifra/web] live.${method} takes keys in the ctx.live() charset`);
662
+ }
663
+ return key;
664
+ };
665
+ const clientEntry = {};
666
+ if (socketPath !== DEFAULT_SOCKET_PATH)
667
+ clientEntry.path = socketPath;
668
+ if (loadPath !== DEFAULT_LOAD_PATH)
669
+ clientEntry.loadPath = loadPath;
670
+ return {
671
+ clientEntry: Object.freeze(clientEntry),
672
+ ready: authority.then(() => undefined),
673
+ async publish(...keys) {
674
+ for (const key of keys)
675
+ checkKey(key, "publish");
676
+ if (keys.length === 0)
677
+ return;
678
+ await visible?.();
679
+ await append({ type: "change", keys: [...new Set(keys)] });
680
+ },
681
+ async revoke(subject) {
682
+ if (typeof subject !== "string")
683
+ throw new TypeError("[nifra/web] live.revoke takes a subject");
684
+ await append({ type: "revoke", sub: subject, key: null });
685
+ },
686
+ async revokeAll() {
687
+ await append({ type: "revoke", sub: null, key: null });
688
+ },
689
+ async revokeKey(key, subject) {
690
+ checkKey(key, "revokeKey");
691
+ if (subject !== undefined && typeof subject !== "string") {
692
+ throw new TypeError("[nifra/web] live.revokeKey takes an optional subject string");
693
+ }
694
+ await append({ type: "revoke", sub: subject ?? null, key });
695
+ },
696
+ [LIVE_ATTACH]: attach,
697
+ };
698
+ }
699
+ /**
700
+ * `app.publish`, holding the app weakly: a dev server builds a new app on every edit, and a discarded
701
+ * one must not stay reachable, nor keep being published to, through the runtime. Built out here so
702
+ * the closure's scope holds nothing else of the app.
703
+ */
704
+ function weakPublisher(app, publishers) {
705
+ const held = new WeakRef(app);
706
+ const publisher = (topic, frame) => {
707
+ const attached = held.deref();
708
+ if (attached === undefined)
709
+ publishers.delete(publisher);
710
+ else
711
+ attached.publish(topic, frame);
712
+ };
713
+ return publisher;
714
+ }
715
+ function endpointPath(path, name) {
716
+ if (typeof path !== "string" ||
717
+ !path.startsWith("/") ||
718
+ path.startsWith("//") ||
719
+ /[?#\s]/.test(path)) {
720
+ throw new TypeError(`[nifra/web] live: \`${name}\` must be an absolute path with no query`);
721
+ }
722
+ return path;
723
+ }
724
+ const noSecret = (secret) => secret === undefined || (Array.isArray(secret) && secret.length === 0) || secret === "";
725
+ function secretsOf(secret) {
726
+ if (secret !== undefined && !noSecret(secret))
727
+ return typeof secret === "string" ? [secret] : secret;
728
+ if (isProduction()) {
729
+ throw new TypeError("[nifra/web] live: `secret` is required in production. Generate one with: openssl rand -base64 32");
730
+ }
731
+ return [toBase64Url(crypto.getRandomValues(new Uint8Array(32)))];
732
+ }
733
+ /**
734
+ * An environment this cannot read counts as production, so a secret is required: Deno throws on
735
+ * `process.env` without `--allow-env`, and a runtime with no `process` says nothing either way.
736
+ */
737
+ function isProduction() {
738
+ try {
739
+ return typeof process === "undefined" || process.env?.NODE_ENV === "production";
740
+ }
741
+ catch {
742
+ return true;
743
+ }
744
+ }
745
+ const mediaType = (value) => {
746
+ const end = value.indexOf(";");
747
+ return (end === -1 ? value : value.slice(0, end)).trim().toLowerCase();
748
+ };
749
+ function pathnameOf(url) {
750
+ const start = url.indexOf("/", url.indexOf("//") + 2);
751
+ if (start === -1)
752
+ return "/";
753
+ const query = url.indexOf("?", start);
754
+ return query === -1 ? url.slice(start) : url.slice(start, query);
755
+ }
756
+ function refusal(status, error) {
757
+ return Response.json({ error }, { status, headers: { "cache-control": NO_STORE } });
758
+ }
759
+ function busy() {
760
+ return Response.json({ error: "live_load_busy" }, {
761
+ status: 503,
762
+ headers: {
763
+ "cache-control": NO_STORE,
764
+ "retry-after": String(1 + Math.floor(Math.random() * 3)),
765
+ },
766
+ });
767
+ }
768
+ /** The hole a `B:<hole>:<routeId>` owner of route `routeId` names. */
769
+ function holeOf(owner, routeId) {
770
+ const suffix = `:${routeId}`;
771
+ if (!owner.startsWith("B:") || !owner.endsWith(suffix))
772
+ return undefined;
773
+ const hole = owner.slice(2, owner.length - suffix.length);
774
+ return hole === "" || hole.includes(":") || hole.includes(",") ? undefined : hole;
775
+ }
776
+ function parseBody(text) {
777
+ let value;
778
+ try {
779
+ value = JSON.parse(text);
780
+ }
781
+ catch {
782
+ return undefined;
783
+ }
784
+ if (!isRecord(value))
785
+ return undefined;
786
+ const { path, sid, frame, mode, retain = [], grants = [], fill = [] } = value;
787
+ if (typeof path !== "string" || typeof sid !== "string" || !SID_PATTERN.test(sid))
788
+ return undefined;
789
+ // Only the page frame is served; boundary frames arrive with the client's frame support.
790
+ if (frame !== PAGE_FRAME || (mode !== "load" && mode !== "renew"))
791
+ return undefined;
792
+ if (!isStringList(retain, MAX_RETAIN, MAX_OWNER_CHARS))
793
+ return undefined;
794
+ if (!isStringList(grants, MAX_GRANTS, MAX_GRANT_CHARS))
795
+ return undefined;
796
+ if (!isStringList(fill, MAX_HOSTS, MAX_HOST_CHARS) || !fill.every((name) => HOST.test(name)))
797
+ return undefined;
798
+ return { path, sid, frame, mode, retain, grants, fill };
799
+ }
800
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
801
+ const isStringList = (value, max, chars) => Array.isArray(value) &&
802
+ value.length <= max &&
803
+ value.every((item) => typeof item === "string" && item.length <= chars);
804
+ /** `path` when it is already the canonical same-origin `pathname + search`; anything else is refused. */
805
+ function canonicalPath(path, base) {
806
+ if (path.length > MAX_PATH_CHARS || !path.startsWith("/") || path.startsWith("//"))
807
+ return undefined;
808
+ // An absolute path against a request URL always parses: the parser normalises, it never throws.
809
+ const url = new URL(path, base);
810
+ const canonical = url.pathname + url.search;
811
+ return url.origin === new URL(base).origin && canonical === path ? canonical : undefined;
812
+ }
813
+ /**
814
+ * The headers the router's own data request for the target would carry: the POST's, without the
815
+ * hop-by-hop, body and `origin` headers a same-origin GET does not send, and without any `x-nifra-*`
816
+ * the client sent. `sec-fetch-*` pass through: a browser's fetch sends the same values for both.
817
+ */
818
+ function internalHeaders(post) {
819
+ const headers = new Headers(post);
820
+ const nominated = headers.get("connection");
821
+ if (nominated !== null) {
822
+ for (const name of nominated.split(",")) {
823
+ const trimmed = name.trim();
824
+ if (TOKEN.test(trimmed))
825
+ headers.delete(trimmed);
826
+ }
827
+ }
828
+ for (const name of HOP_BY_HOP)
829
+ headers.delete(name);
830
+ for (const name of BODY_HEADERS)
831
+ headers.delete(name);
832
+ headers.delete("origin");
833
+ const own = [];
834
+ headers.forEach((_value, name) => {
835
+ if (name.startsWith("x-nifra-"))
836
+ own.push(name);
837
+ });
838
+ for (const name of own)
839
+ headers.delete(name);
840
+ headers.set(DATA_HEADER, "1");
841
+ return headers;
842
+ }
843
+ function releasing(body, done) {
844
+ const reader = body.getReader();
845
+ return new ReadableStream({
846
+ async pull(controller) {
847
+ // Released on the last chunk and on a read that throws: only an enqueue keeps the slot.
848
+ let finished = true;
849
+ try {
850
+ const chunk = await reader.read();
851
+ if (chunk.done)
852
+ controller.close();
853
+ else {
854
+ finished = false;
855
+ controller.enqueue(chunk.value);
856
+ }
857
+ }
858
+ finally {
859
+ if (finished)
860
+ done();
861
+ }
862
+ },
863
+ cancel(reason) {
864
+ done();
865
+ return reader.cancel(reason);
866
+ },
867
+ });
868
+ }
869
+ //# sourceMappingURL=live.js.map