@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,92 @@
1
+ import { effect, untrack } from "./island-signals.ts"
2
+
3
+ /** The node surface `bindList` uses; an `Element` has it. */
4
+ export interface ListNode {
5
+ remove(): void
6
+ }
7
+
8
+ /** The container surface `bindList` uses; an `Element` has it. */
9
+ export interface ListContainer<E extends ListNode> {
10
+ replaceChildren(...nodes: E[]): void
11
+ insertBefore(node: E, before: E | null): unknown
12
+ }
13
+
14
+ /** How `bindList` turns items into keyed DOM. */
15
+ export interface BindListOptions<T, E extends ListNode = HTMLElement> {
16
+ /** Stable and unique per item - never the array index, or a removal reuses the wrong node. */
17
+ key(item: T): string | number
18
+ create(item: T): E
19
+ /** Brings a node in line with its item: runs after `create` and on every later change. */
20
+ update?(el: E, item: T): void
21
+ }
22
+
23
+ /**
24
+ * Render a list signal (or any getter) into `container` with keyed reconciliation. A surviving item
25
+ * keeps its node, so focus, selection and input state survive a reorder. A render with no earlier
26
+ * nodes replaces the container's children; later renders move only the nodes outside the longest
27
+ * run already in order. `key`, `create` and `update` run untracked: a signal they read does not
28
+ * re-render the list. Returns the disposer; inside an island the binding also stops on unmount.
29
+ */
30
+ export function bindList<T, E extends ListNode = HTMLElement>(
31
+ source: () => readonly T[],
32
+ // A DOM container's generic methods would drive E to `ListNode`; the row type comes from `create`.
33
+ container: ListContainer<NoInfer<E>>,
34
+ options: BindListOptions<T, E>,
35
+ ): () => void {
36
+ let nodes = new Map<string | number, E>()
37
+ // Each node's position in the last render; empty until a render leaves nodes to keep.
38
+ let at = new Map<E, number>()
39
+ return effect(() => {
40
+ const items = source()
41
+ untrack(() => {
42
+ const next = new Map<string | number, E>()
43
+ for (const item of items) {
44
+ const key = options.key(item)
45
+ if (next.has(key)) console.warn("[nifra/islands] bindList: duplicate key skipped", key)
46
+ else {
47
+ const el = nodes.get(key) ?? options.create(item)
48
+ options.update?.(el, item)
49
+ next.set(key, el)
50
+ }
51
+ }
52
+ const els = [...next.values()]
53
+ if (at.size === 0) container.replaceChildren(...els)
54
+ else {
55
+ for (const [key, el] of nodes) if (!next.has(key)) el.remove()
56
+ const stays = inOrder(els.map((el) => at.get(el) ?? -1))
57
+ let anchor: E | null = null
58
+ for (let i = els.length - 1; i >= 0; i--) {
59
+ const el = els[i]
60
+ if (el === undefined) continue
61
+ if (!stays.has(i)) container.insertBefore(el, anchor)
62
+ anchor = el
63
+ }
64
+ }
65
+ at = new Map(els.map((el, i) => [el, i]))
66
+ nodes = next
67
+ })
68
+ })
69
+ }
70
+
71
+ // The indexes of `old` (each node's previous position, -1 for a new node) on a longest increasing
72
+ // run: those nodes already sit in order, and every other node is moved.
73
+ function inOrder(old: readonly number[]): Set<number> {
74
+ // tails[n] is the index ending the best run of length n + 1; back links each run.
75
+ const tails: number[] = []
76
+ const back: number[] = []
77
+ old.forEach((o, i) => {
78
+ if (o < 0) return
79
+ let lo = 0
80
+ let hi = tails.length
81
+ while (lo < hi) {
82
+ const mid = (lo + hi) >> 1
83
+ if ((old[tails[mid] ?? 0] ?? 0) < o) lo = mid + 1
84
+ else hi = mid
85
+ }
86
+ back[i] = tails[lo - 1] ?? -1
87
+ tails[lo] = i
88
+ })
89
+ const stays = new Set<number>()
90
+ for (let i = tails.at(-1) ?? -1; i >= 0; i = back[i] ?? -1) stays.add(i)
91
+ return stays
92
+ }
@@ -0,0 +1,68 @@
1
+ import {
2
+ type IslandStrategy,
3
+ MAX_MEDIA_QUERY_LENGTH,
4
+ scheduleTrigger,
5
+ } from "@nifrajs/island-trigger"
6
+ import type { IslandCleanup, IslandEnhancer } from "../islands.ts"
7
+ import { owned } from "./island-owner.ts"
8
+
9
+ function readProps(el: HTMLElement): unknown {
10
+ const raw = el.dataset.islandProps
11
+ if (raw === undefined || raw === "") return undefined
12
+ try {
13
+ return JSON.parse(raw)
14
+ } catch {
15
+ return undefined
16
+ }
17
+ }
18
+
19
+ /** Own one host so removing a subtree can stop its islands without disturbing retained siblings. */
20
+ export function mountIsland(
21
+ el: HTMLElement,
22
+ enhancers: Readonly<Record<string, IslandEnhancer<never>>>,
23
+ ): IslandCleanup | undefined {
24
+ const id = el.dataset.island
25
+ if (id === undefined || el.dataset.islandMounted !== undefined) return undefined
26
+ const enhancer = Object.hasOwn(enhancers, id) ? enhancers[id] : undefined
27
+ if (enhancer === undefined) return undefined
28
+ const strategyAttr = el.dataset.islandStrategy
29
+ const media = el.dataset.islandMedia
30
+ const strategy: IslandStrategy | undefined =
31
+ strategyAttr === "idle" || strategyAttr === "visible" || strategyAttr === "load"
32
+ ? strategyAttr
33
+ : strategyAttr === "media" &&
34
+ media !== undefined &&
35
+ media.length > 0 &&
36
+ media.length <= MAX_MEDIA_QUERY_LENGTH
37
+ ? { media }
38
+ : strategyAttr === undefined
39
+ ? "load"
40
+ : undefined
41
+ // Invalid markers must stay inert, including untrusted oversized media queries.
42
+ if (strategy === undefined) return undefined
43
+ const disposers: Array<() => void> = []
44
+ el.dataset.islandMounted = ""
45
+ disposers.push(() => {
46
+ delete el.dataset.islandMounted
47
+ })
48
+ const props = readProps(el)
49
+ const run = (): void => {
50
+ try {
51
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: enhancer props are the authored SSR contract, as in defineIsland
52
+ const cleanup = owned(disposers, () => enhancer(el, props as never))
53
+ if (typeof cleanup === "function") disposers.push(cleanup)
54
+ } catch (err) {
55
+ console.error(`[nifra/islands] enhancer "${id}" failed:`, err)
56
+ }
57
+ }
58
+ disposers.push(scheduleTrigger(strategy, run, { target: el }))
59
+ return () => {
60
+ for (const dispose of disposers.splice(0)) {
61
+ try {
62
+ dispose()
63
+ } catch {
64
+ // Best-effort teardown: one failing cleanup must not block the rest.
65
+ }
66
+ }
67
+ }
68
+ }
@@ -0,0 +1,20 @@
1
+ // The disposer list of the island whose enhancer is running, so every effect, computed, list binding
2
+ // and resource it creates stops when the island unmounts. A separate module so an imperative-only
3
+ // island bundle carries no signal code.
4
+ let owner: Array<() => void> | undefined
5
+
6
+ /** Run `fn` with every disposer it registers pushed onto `kids`. */
7
+ export function owned<T>(kids: Array<() => void>, fn: () => T): T {
8
+ const prev = owner
9
+ owner = kids
10
+ try {
11
+ return fn()
12
+ } finally {
13
+ owner = prev
14
+ }
15
+ }
16
+
17
+ /** Register `stop` with the running island, if any. Outside an enhancer it stays the caller's. */
18
+ export function adopt(stop: () => void): void {
19
+ owner?.push(stop)
20
+ }
@@ -0,0 +1,76 @@
1
+ import { adopt } from "./island-owner.ts"
2
+ import { effect, signal, untrack } from "./island-signals.ts"
3
+
4
+ /** An async value's state. `status` narrows `value` and `error`, so no branch can be skipped. */
5
+ export type ResourceState<T> =
6
+ | { readonly status: "pending"; readonly value: undefined; readonly error: undefined }
7
+ | { readonly status: "ready"; readonly value: T; readonly error: undefined }
8
+ | { readonly status: "error"; readonly value: undefined; readonly error: unknown }
9
+
10
+ /** Call it to read the current state (tracking, like a signal). */
11
+ export type Resource<T> = {
12
+ (): ResourceState<T>
13
+ /** Fetch the current key again; once disposed, nothing. */
14
+ refetch(): void
15
+ /** Stop refetching and abort the request in flight; its result is dropped. */
16
+ dispose(): void
17
+ }
18
+
19
+ const PENDING = { status: "pending", value: undefined, error: undefined } as const
20
+
21
+ /**
22
+ * An async value keyed by `source`. `source` runs tracked, so a write to any signal it reads starts a
23
+ * new fetch; `fetcher` runs untracked with that key, so nothing it reads (before or after an `await`)
24
+ * is a dependency. A newer fetch aborts the older one and only the newest result lands. Inside an
25
+ * island, unmounting disposes it: the request in flight is aborted and its late result dropped.
26
+ */
27
+ export function resource<K, T>(
28
+ source: () => K,
29
+ fetcher: (key: K, abort: AbortSignal) => Promise<T>,
30
+ ): Resource<T> {
31
+ const state = signal<ResourceState<T>>(PENDING)
32
+ let generation = 0
33
+ let live = true
34
+ let controller: AbortController | undefined
35
+ let key: K
36
+ const run = (): void => {
37
+ const mine = ++generation
38
+ controller?.abort()
39
+ const active = new AbortController()
40
+ controller = active
41
+ state.set(PENDING)
42
+ let pending: Promise<T>
43
+ try {
44
+ pending = fetcher(key, active.signal)
45
+ } catch (error) {
46
+ pending = Promise.reject(error)
47
+ }
48
+ pending.then(
49
+ (value) => {
50
+ if (mine === generation) state.set({ status: "ready", value, error: undefined })
51
+ },
52
+ (error) => {
53
+ // A superseded or aborted fetch is not an error the reader should see.
54
+ if (mine === generation && !active.signal.aborted)
55
+ state.set({ status: "error", value: undefined, error })
56
+ },
57
+ )
58
+ }
59
+ const stop = effect(() => {
60
+ key = source()
61
+ untrack(run)
62
+ })
63
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: `refetch` and `dispose` are assigned below
64
+ const read = (() => state()) as Resource<T>
65
+ read.refetch = () => {
66
+ if (live) untrack(run)
67
+ }
68
+ read.dispose = () => {
69
+ live = false
70
+ stop()
71
+ generation++
72
+ controller?.abort()
73
+ }
74
+ adopt(read.dispose)
75
+ return read
76
+ }
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Fine-grained signals - the reactive core of `@nifrajs/web/islands`. Pull-based auto-tracking with
3
+ * per-run re-tracking (a branch switch drops the stale branch's subscriptions), `Object.is`
4
+ * equality skips, and an explicit synchronous {@link batch} that coalesces effect re-runs.
5
+ *
6
+ * Deliberately tiny and synchronous: a write recomputes the computeds it feeds at once and runs its
7
+ * effects before `set` returns, each once, after every computed has settled - predictable for
8
+ * island-scale code (a counter, a filter drawer), and the whole core stays well under a kilobyte.
9
+ * This is NOT a general app framework - just island-scale reactivity.
10
+ */
11
+
12
+ import { adopt } from "./island-owner.ts"
13
+
14
+ interface EffectNode {
15
+ run: () => void
16
+ /** Each source's reader set, mapped to the run (`v`) that last read it. */
17
+ readonly deps: Map<Set<EffectNode>, number>
18
+ v: number
19
+ /** Sources read so far in this run. */
20
+ n: number
21
+ /** Running now: a write during the run requeues it only for a source this run already read. */
22
+ r?: boolean
23
+ /** Set on a computed's node: it recomputes on the write instead of queueing. */
24
+ readonly c?: boolean | undefined
25
+ /** Queued for the current flush. */
26
+ q?: boolean
27
+ }
28
+
29
+ let currentEffect: EffectNode | undefined
30
+
31
+ /** Run `fn` without subscribing the running effect or computed to the signals it reads. */
32
+ export function untrack<T>(fn: () => T): T {
33
+ const prev = currentEffect
34
+ currentEffect = undefined
35
+ try {
36
+ return fn()
37
+ } finally {
38
+ currentEffect = prev
39
+ }
40
+ }
41
+
42
+ let batchDepth = 0
43
+ const queue: EffectNode[] = []
44
+
45
+ /** A readable/writable reactive value: call it to read (tracking), `.set` to write. */
46
+ export type Signal<T> = {
47
+ (): T
48
+ set(next: T | ((prev: T) => T)): void
49
+ }
50
+
51
+ /**
52
+ * Batch writes: effects triggered inside `fn` run ONCE after it returns, deduplicated - so
53
+ * `setA(); setB()` updates the DOM once, not twice. Re-entrant; an effect re-queued during the
54
+ * flush runs in the same flush.
55
+ */
56
+ export function batch<T>(fn: () => T): T {
57
+ batchDepth++
58
+ try {
59
+ return fn()
60
+ } finally {
61
+ flush()
62
+ }
63
+ }
64
+
65
+ // Closes one batch level; the outermost runs the queue.
66
+ function flush(): void {
67
+ let i = 0
68
+ try {
69
+ // The flush still counts as a batch: a write an effect makes here queues, so none runs twice.
70
+ while (batchDepth === 1 && i < queue.length) {
71
+ const next = queue[i++]
72
+ if (next?.q) {
73
+ next.q = false
74
+ next.run()
75
+ }
76
+ }
77
+ } finally {
78
+ // A throwing effect leaves the rest queued for the next flush.
79
+ if (i === queue.length) queue.length = 0
80
+ else queue.splice(0, i)
81
+ batchDepth--
82
+ }
83
+ }
84
+
85
+ // Each plain signal's reader set, so `watch` can subscribe without tracking.
86
+ const readers = new WeakMap<object, Set<EffectNode>>()
87
+
88
+ /** Create a signal. Reads inside an {@link effect} (or {@link computed}) subscribe automatically. */
89
+ export function signal<T>(initial: T): Signal<T> {
90
+ const subs = new Set<EffectNode>()
91
+ const s = readable(initial, subs)
92
+ readers.set(s, subs)
93
+ return s
94
+ }
95
+
96
+ /**
97
+ * Call `fn` with `source`'s value now and after each change, queued and batched like an effect but
98
+ * subscribed once instead of tracking reads. Any other getter (a computed, a derived function) is
99
+ * tracked as an effect. Either way `fn` runs untracked. Internal to the binding walker, whose bindings each read one named signal,
100
+ * and to `bindList`; not exported from `@nifrajs/web/islands`.
101
+ */
102
+ export function watch<T>(source: () => T, fn: (value: T) => void): () => void {
103
+ const subs = readers.get(source)
104
+ if (!subs)
105
+ return effect(() => {
106
+ const value = source()
107
+ untrack(() => fn(value))
108
+ })
109
+ const e: EffectNode = {
110
+ deps: new Map([[subs, 0]]),
111
+ v: 0,
112
+ n: 1,
113
+ run: () => {
114
+ const prev = currentEffect
115
+ currentEffect = undefined
116
+ try {
117
+ fn(source())
118
+ } finally {
119
+ currentEffect = prev
120
+ }
121
+ },
122
+ }
123
+ const stop = (): void => {
124
+ subs.delete(e)
125
+ e.q = false
126
+ }
127
+ subs.add(e)
128
+ adopt(stop)
129
+ batch(e.run)
130
+ return stop
131
+ }
132
+
133
+ // A signal whose readers are `subs`, so a computed can see whether anything still reads it.
134
+ function readable<T>(initial: T, subs: Set<EffectNode>): Signal<T> {
135
+ let value = initial
136
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: `set` is assigned below, before the signal is returned
137
+ const read = (() => {
138
+ const e = currentEffect
139
+ if (e) {
140
+ const seen = e.deps.get(subs)
141
+ if (seen !== e.v) {
142
+ if (seen === undefined) subs.add(e)
143
+ e.deps.set(subs, e.v)
144
+ e.n++
145
+ }
146
+ }
147
+ return value
148
+ }) as Signal<T>
149
+ read.set = (next) => {
150
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: a function passed to `set` is always the updater form
151
+ const resolved = typeof next === "function" ? (next as (prev: T) => T)(value) : next
152
+ if (Object.is(resolved, value)) return
153
+ value = resolved
154
+ // Snapshot: an effect created while this write flushes must not run in it.
155
+ batchDepth++
156
+ try {
157
+ for (const e of [...subs]) {
158
+ // A running effect is requeued only by a source its current run has already read.
159
+ if (e.r && e.deps.get(subs) !== e.v) continue
160
+ if (e.c) e.run()
161
+ else if (!e.q) {
162
+ e.q = true
163
+ queue.push(e)
164
+ }
165
+ }
166
+ } finally {
167
+ flush()
168
+ }
169
+ }
170
+ return read
171
+ }
172
+
173
+ /** A derived value: call it to read (tracking), `.dispose()` to stop recomputing it. */
174
+ export type Computed<T> = {
175
+ (): T
176
+ dispose(): void
177
+ }
178
+
179
+ /**
180
+ * Derived value, cached into a signal and computed on first read. While an effect (or another
181
+ * computed) reads it, it recomputes when its tracked inputs change. Once nothing reads it, the next
182
+ * input change stops it instead of recomputing, so a dropped computed costs nothing; reading it
183
+ * again starts it afresh. `dispose()` stops it at once.
184
+ */
185
+ export function computed<T>(fn: () => T): Computed<T> {
186
+ const subs = new Set<EffectNode>()
187
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: the first read computes it before returning
188
+ const out = readable<T>(undefined as T, subs)
189
+ let stop: (() => void) | undefined
190
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: `dispose` is assigned below
191
+ const read = (() => {
192
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: the implementation takes the computed flag its public overload hides
193
+ stop ??= (effect as (fn: () => void, c: boolean) => () => void)(() => {
194
+ if (!stop || subs.size) out.set(fn())
195
+ else read.dispose()
196
+ }, true)
197
+ return out()
198
+ }) as Computed<T>
199
+ read.dispose = () => {
200
+ stop?.()
201
+ stop = undefined
202
+ }
203
+ return read
204
+ }
205
+
206
+ /**
207
+ * Run `fn` now and again whenever any signal it read changes. Returns a disposer. Dependencies
208
+ * re-track on every run, so conditional reads subscribe to exactly the live branch. Every run,
209
+ * the first included, finishes before the effects its writes trigger run, itself among them.
210
+ */
211
+ export function effect(fn: () => void): () => void
212
+ export function effect(fn: () => void, c?: boolean): () => void {
213
+ const stop = (): void => {
214
+ for (const subs of e.deps.keys()) subs.delete(e)
215
+ e.deps.clear()
216
+ e.q = false
217
+ }
218
+ const e: EffectNode = {
219
+ c,
220
+ deps: new Map(),
221
+ v: 0,
222
+ n: 0,
223
+ // Subscriptions persist across runs; a source this run did not read is dropped afterwards.
224
+ run: () => {
225
+ ++e.v
226
+ e.n = 0
227
+ e.q = false
228
+ e.r = true
229
+ const prev = currentEffect
230
+ currentEffect = e
231
+ try {
232
+ fn()
233
+ } finally {
234
+ currentEffect = prev
235
+ e.r = false
236
+ if (e.n < e.deps.size)
237
+ for (const [subs, seen] of e.deps)
238
+ if (seen !== e.v) {
239
+ subs.delete(e)
240
+ e.deps.delete(subs)
241
+ }
242
+ }
243
+ },
244
+ }
245
+ adopt(stop)
246
+ // Batched, so a write in the first run cannot re-enter it before it finishes.
247
+ batch(e.run)
248
+ return stop
249
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Where the slot wrappers of kept pages go, decided once from the route table so that the server's
3
+ * markup and every client render agree: a wrapper that appeared or vanished with what is kept would
4
+ * remount the page inside it, and one the server did not render fails hydration.
5
+ */
6
+ import type { Manifest } from "../manifest.ts"
7
+
8
+ /** What the server and the browser both read to place the slot wrappers. */
9
+ export interface KeepAliveTable {
10
+ /** Routes whose page renders inside the slot wrappers: the build found a `keepAlive` export. */
11
+ readonly routes: readonly string[]
12
+ /** Layouts rendered inside the slot wrappers, because another chain the browser can render shares
13
+ * the layouts above one of them and not that one: a kept page then sits there beside the page on
14
+ * screen. A layout every chain shares is never wrapped. */
15
+ readonly layouts: readonly string[]
16
+ }
17
+
18
+ const sharesAbove = (a: readonly string[], b: readonly string[], depth: number): boolean => {
19
+ for (let i = 0; i < depth; i++) if (a[i] !== b[i]) return false
20
+ return true
21
+ }
22
+
23
+ /** The table for `manifest`, or `undefined` when no route keeps its page alive. */
24
+ export function keepAliveTable(manifest: Manifest): KeepAliveTable | undefined {
25
+ const kept = manifest.routes.filter((route) => route.keepAlive === true)
26
+ if (kept.length === 0) return undefined
27
+ // Every layout chain the browser renders: each route's, a root status page's (inside the root
28
+ // layout unless the page opts out, which only its module says), and a `_loading` page's, which takes
29
+ // the layouts the two pages of a navigation share, so any prefix of a route under it.
30
+ const chains: (readonly string[])[] = manifest.routes.map((route) => route.layoutIds)
31
+ if (manifest.notFound !== undefined || Object.keys(manifest.statusPages ?? {}).length > 0) {
32
+ chains.push(manifest.layouts._layout === undefined ? [] : ["_layout"])
33
+ }
34
+ for (const [id, loading] of Object.entries(manifest.loadings ?? {})) {
35
+ for (const route of manifest.routes) {
36
+ if (!route.loadingIds?.includes(id)) continue
37
+ for (let n = loading.layoutIds.length; n <= route.layoutIds.length; n++) {
38
+ chains.push(route.layoutIds.slice(0, n))
39
+ }
40
+ }
41
+ }
42
+ const layouts = new Set<string>()
43
+ for (const route of kept) {
44
+ route.layoutIds.forEach((layout, depth) => {
45
+ if (layouts.has(layout)) return
46
+ if (
47
+ chains.some(
48
+ (chain) => chain[depth] !== layout && sharesAbove(chain, route.layoutIds, depth),
49
+ )
50
+ )
51
+ layouts.add(layout)
52
+ })
53
+ }
54
+ return { routes: [...new Set(kept.map((route) => route.id))], layouts: [...layouts] }
55
+ }
56
+
57
+ /**
58
+ * The chain indices to render inside slot wrappers for a chain of `layoutIds` and then the page of
59
+ * `routeId`: each wrapped layout, and the page when its route keeps alive.
60
+ */
61
+ export function slotDepths(
62
+ table: KeepAliveTable,
63
+ layoutIds: readonly string[],
64
+ routeId: string | undefined,
65
+ ): readonly number[] {
66
+ const depths: number[] = []
67
+ layoutIds.forEach((layout, depth) => {
68
+ if (table.layouts.includes(layout)) depths.push(depth)
69
+ })
70
+ if (routeId !== undefined && table.routes.includes(routeId)) depths.push(layoutIds.length)
71
+ return depths
72
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * The seam between page execution and a configured live runtime (`@nifrajs/web/live`): types and one
3
+ * symbol. An app without `live` imports nothing from the live module itself.
4
+ */
5
+ import type { IdentityPlugin, WebSocketHandler } from "@nifrajs/core/server"
6
+ import type { LoaderContext } from "../manifest.ts"
7
+ import type { LiveClientEntry } from "./codegen.ts"
8
+
9
+ /** What `createWebApp` tells the runtime about its routes. */
10
+ export interface LiveRouteMatch {
11
+ readonly routeId: string
12
+ readonly layoutIds: readonly string[]
13
+ }
14
+
15
+ /** The app the live socket registers on. */
16
+ export interface LiveHostApp {
17
+ use(plugin: IdentityPlugin): unknown
18
+ ws<Data>(path: string, handler: WebSocketHandler<Data>): unknown
19
+ publish(topic: string, data: string): void
20
+ }
21
+
22
+ export interface LiveAttachInput {
23
+ /** The route a canonical `pathname + search` renders, or `null`. */
24
+ readonly matchRoute: (path: string) => LiveRouteMatch | null
25
+ readonly app: LiveHostApp
26
+ }
27
+
28
+ export interface LiveAttachment {
29
+ /** Admission and dispatch for the live load endpoint; `undefined` for every other request. */
30
+ readonly onRequest: (req: Request) => Promise<Request | Response> | undefined
31
+ /** The live load a page request belongs to, or `undefined` for every request that is not one. */
32
+ readonly loadOf: (req: Request) => LiveLoad | undefined
33
+ }
34
+
35
+ /** One live load, from the page handler's side. */
36
+ export interface LiveLoad {
37
+ /** The route the endpoint mapped the retain list against. */
38
+ readonly routeId: string
39
+ /** `undefined` when admitted; else the 503 to answer with. An admitted load is released once. */
40
+ admit(): Response | undefined
41
+ /** Release the admission slot once the response is complete: at once, or when its stream ends. */
42
+ settle(response: Response): Response
43
+ release(): void
44
+ /**
45
+ * After middleware: resolve the viewer's subject. `false` when the presented grant's retention no
46
+ * longer applies (another subject), so every owner loads fresh.
47
+ */
48
+ bind(ctx: LoaderContext): Promise<boolean>
49
+ /** Skip the page loader: the page owner is retained. Read after {@link bind}. */
50
+ readonly retainPage: boolean
51
+ /** `ctx.live` for one owner. */
52
+ collector(owner: string): (...keys: string[]) => void
53
+ /** Close an owner's collector once its loader has settled. */
54
+ seal(owner: string): void
55
+ /** The envelope's `live` member, built after every loader settled without a throw or status. */
56
+ finish(input: LiveFinish): Promise<LiveEnvelope>
57
+ }
58
+
59
+ export interface LiveFinish {
60
+ /** Owners whose loaders ran and resolved. */
61
+ readonly fresh: readonly string[]
62
+ /** Owners whose loaders were skipped: their keys come from the presented grant. */
63
+ readonly retained: readonly string[]
64
+ readonly ctx: LoaderContext
65
+ }
66
+
67
+ export interface LiveEnvelope {
68
+ /**
69
+ * Opaque and stable per viewer, one per listed secret, newest first. A load sharing none with the
70
+ * last one is another user, so client caches are cleared; a rotation always leaves one in common.
71
+ */
72
+ readonly subjects: readonly string[]
73
+ /** Absent when no grant could be issued; the client then polls. */
74
+ readonly grant?: string
75
+ /** Wire tag to the frame's owners that watch its key. */
76
+ readonly tags?: Readonly<Record<string, readonly string[]>>
77
+ /** The page loader did not run: the client keeps its page data. */
78
+ readonly page?: "retained"
79
+ /** Seconds the grant stays valid from now; the client renews a minute before. */
80
+ readonly ttl?: number
81
+ /** For query hooks: with `queryKeys`, and in development so the page can warn. */
82
+ readonly query?: LiveQueryKeys
83
+ }
84
+
85
+ export interface LiveQueryKeys {
86
+ /** Fresh per load; absent when `queryKeys` is off. */
87
+ readonly salt?: string
88
+ /** Wire tag to its key's digest under `salt`. */
89
+ readonly keys?: Readonly<Record<string, string>>
90
+ /** The app runs in development: the page warns about query keys no grant holds. */
91
+ readonly dev?: true
92
+ }
93
+
94
+ /** The owner a hole of a partially prerendered page is in its page's live frame. */
95
+ export const holeOwner = (hole: string, routeId: string): string => `B:${hole}:${routeId}`
96
+
97
+ /** `createWebApp` reads the runtime through this symbol, so it is not public API on the object. */
98
+ export const LIVE_ATTACH: unique symbol = Symbol.for("nifra.web.live.attach")
99
+
100
+ export interface LiveRuntimeHandle {
101
+ readonly [LIVE_ATTACH]: (input: LiveAttachInput) => LiveAttachment
102
+ /** The client entry's `live` option for this runtime: the endpoints it serves, where not the default. */
103
+ readonly clientEntry: LiveClientEntry
104
+ /** Announce changed keys; resolves once this process applied the change. */
105
+ publish(...keys: string[]): Promise<void>
106
+ }