@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/src/fn.ts CHANGED
@@ -54,6 +54,7 @@ import {
54
54
  type StandardSchemaV1,
55
55
  } from "@nifrajs/core/server"
56
56
  import { guardChannel, outputGuard } from "./internal/output-guard.ts"
57
+ import { SERVER_FN_NAMESPACE } from "./internal/server-boundary.ts"
57
58
 
58
59
  /** The URL prefix every mounted function lives under. Namespaced per mount, then by export name. */
59
60
  export const SERVER_FN_PREFIX = "/_nifra/fn"
@@ -78,8 +79,9 @@ export interface ServerFnConfig<Input> {
78
79
 
79
80
  /**
80
81
  * A declared server function. Callable directly on the server (the same value your own server-side
81
- * code can await); on the client, phase 2's build transform replaces this module with typed stubs
82
- * that POST to the mounted route.
82
+ * code can await); in a client build, this module becomes typed stubs that POST under its
83
+ * file's name (`todos.fn.ts` posts under `todos`), so {@link serverFunctions} must mount it
84
+ * under that name.
83
85
  *
84
86
  * This is the SERVER declaration type, so context is required. Client builds replace the module with
85
87
  * a one-argument {@link ClientServerFn}; UI bindings accept either shape and adapt at that boundary.
@@ -123,8 +125,6 @@ export function serverFn<Input = void, Output = unknown>(
123
125
  const isServerFn = (value: unknown): value is ServerFn<unknown, unknown> =>
124
126
  typeof value === "function" && SERVER_FN in (value as object)
125
127
 
126
- /** A namespace segment: lowercase, dot/dash separated. Constrained so a mount cannot invent a path. */
127
- const NAMESPACE = /^[a-z0-9]+(?:[-.][a-z0-9]+)*$/
128
128
  /** An export name that is safe in a URL path without escaping. */
129
129
  const EXPORT_NAME = /^[A-Za-z_$][A-Za-z0-9_$]*$/
130
130
 
@@ -156,12 +156,13 @@ export type ServerFnModule = Readonly<Record<string, unknown>>
156
156
  *
157
157
  * The namespace is explicit rather than derived from the file path: a path-derived one would put the
158
158
  * build machine's layout in a public URL, and this keeps the route readable in logs and greppable in
159
- * the codebase. Phase 2's transform supplies it automatically from the file's location in the project.
159
+ * the codebase. The client build's stubs call the namespace named by the module's file (`todos.fn.ts`
160
+ * calls `todos`), so pass that same word here.
160
161
  *
161
162
  * Exports that are not server functions are ignored, so `export type` and helpers can live alongside.
162
163
  */
163
164
  export function serverFunctions(namespace: string, module: ServerFnModule): IdentityPlugin {
164
- if (!NAMESPACE.test(namespace)) {
165
+ if (!SERVER_FN_NAMESPACE.test(namespace)) {
165
166
  throw new Error(
166
167
  `[nifra/fn] invalid server-function namespace ${JSON.stringify(namespace)} - use lowercase dot/dash segments (it becomes a URL path).`,
167
168
  )
@@ -213,5 +214,21 @@ export function serverFunctions(namespace: string, module: ServerFnModule): Iden
213
214
  }
214
215
  return app
215
216
  }
216
- return defineIdentityPlugin(`nifra:server-fn:${namespace}`, apply)
217
+ // A plugin name per module: the same module mounted twice stays one mount, while a second module
218
+ // under a taken namespace mounts too, so its functions are served, or a name both modules export
219
+ // fails as a duplicate route instead of the second module silently answering nothing.
220
+ const indexes = MODULE_INDEXES.get(module) ?? new Map<string, number>()
221
+ MODULE_INDEXES.set(module, indexes)
222
+ let index = indexes.get(namespace)
223
+ if (index === undefined) {
224
+ index = NEXT_MODULE_INDEX.get(namespace) ?? 0
225
+ NEXT_MODULE_INDEX.set(namespace, index + 1)
226
+ indexes.set(namespace, index)
227
+ }
228
+ const suffix = index === 0 ? "" : `#${index + 1}`
229
+ return defineIdentityPlugin(`nifra:server-fn:${namespace}${suffix}`, apply)
217
230
  }
231
+
232
+ // Weak, so a module an app no longer uses (a dev reload, a discarded test app) is not held here.
233
+ const MODULE_INDEXES = new WeakMap<ServerFnModule, Map<string, number>>()
234
+ const NEXT_MODULE_INDEX = new Map<string, number>()
package/src/fs.ts CHANGED
@@ -3,10 +3,11 @@
3
3
  * (and the rest of @nifrajs/web) stays fs-free + edge-portable: this runs at build/startup
4
4
  * only. It scans a routes dir and feeds the pure `buildManifest`.
5
5
  */
6
- import { readdirSync } from "node:fs"
6
+ import { readdirSync, readFileSync } from "node:fs"
7
7
  import { join } from "node:path"
8
8
  import {
9
9
  buildManifest,
10
+ exportsKeepAlive,
10
11
  isMiddlewareFile,
11
12
  isRouteBackendFile,
12
13
  type Manifest,
@@ -45,12 +46,25 @@ export function discoverRoutes(dir: string, options: DiscoverRoutesOptions = {})
45
46
  )
46
47
  const query = options.importQuery ? `?${options.importQuery}` : ""
47
48
  const load = options.load
48
- return buildManifest(files, (file) =>
49
- load === undefined
50
- ? () => import(`${dir}/${file}${query}`) as Promise<RouteModule>
51
- : // No `importQuery` here: an injected loader owns its own invalidation (Vite's module graph
52
- // re-evaluates on change), and appending a cache-buster would defeat it by minting a new
53
- // module id per request.
54
- () => load(join(dir, file)) as Promise<RouteModule>,
49
+ return buildManifest(
50
+ files,
51
+ (file) =>
52
+ load === undefined
53
+ ? () => import(`${dir}/${file}${query}`) as Promise<RouteModule>
54
+ : // No `importQuery` here: an injected loader owns its own invalidation (Vite's module graph
55
+ // re-evaluates on change), and appending a cache-buster would defeat it by minting a new
56
+ // module id per request.
57
+ () => load(join(dir, file)) as Promise<RouteModule>,
58
+ { keepAlive: files.filter((file) => keepsAlive(join(dir, file))) },
55
59
  )
56
60
  }
61
+
62
+ const keepsAlive = (path: string): boolean => {
63
+ if (isRouteBackendFile(path) || path.slice(path.lastIndexOf("/") + 1).startsWith("_"))
64
+ return false
65
+ try {
66
+ return exportsKeepAlive(readFileSync(path, "utf8"))
67
+ } catch {
68
+ return false
69
+ }
70
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The identity epoch: a value the server rotates whenever a response signs a visitor in or out, so a
3
+ * page kept mounted for one visitor is never shown to the next. Browser-safe and free of module-level
4
+ * state, so a client bundle that never calls it carries none of it.
5
+ */
6
+
7
+ /** The readable cookie holding the epoch on an `https:` origin. */
8
+ export const EPOCH_COOKIE = "__Host-nifra-epoch"
9
+ /** The same cookie on local `http:` dev, where a `__Host-` cookie cannot be set. */
10
+ export const LOCAL_EPOCH_COOKIE = "nifra-epoch"
11
+ /** The channel every tab of an origin hears a sign-in or sign-out on. */
12
+ export const IDENTITY_CHANNEL = "nifra-identity-v1"
13
+
14
+ type Listeners = Set<() => void>
15
+
16
+ // Looked up per call: a module-level `Symbol.for` would be code every client bundle keeps.
17
+ const listenersOf = (): Listeners => {
18
+ const key = Symbol.for("nifra.identity.listeners")
19
+ let listeners: unknown = Reflect.get(globalThis, key)
20
+ if (!(listeners instanceof Set)) {
21
+ listeners = new Set<() => void>()
22
+ Reflect.set(globalThis, key, listeners)
23
+ }
24
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: only this module writes the slot, always a set of listeners
25
+ return listeners as Listeners
26
+ }
27
+
28
+ /** The epoch in a `document.cookie` string, or `""` when it carries none. */
29
+ export function epochOf(cookie: string): string {
30
+ let local = ""
31
+ for (const part of cookie.split(";")) {
32
+ const at = part.indexOf("=")
33
+ if (at === -1) continue
34
+ const name = part.slice(0, at).trim()
35
+ if (name === EPOCH_COOKIE) return part.slice(at + 1).trim()
36
+ if (name === LOCAL_EPOCH_COOKIE) local = part.slice(at + 1).trim()
37
+ }
38
+ return local
39
+ }
40
+
41
+ /** Run `listener` when this document hears that the visitor changed; returns its removal. */
42
+ export function onIdentityChange(listener: () => void): () => void {
43
+ const listeners = listenersOf()
44
+ listeners.add(listener)
45
+ return () => {
46
+ listeners.delete(listener)
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Tell this tab and every other tab of the origin that the visitor signed in or out. Call it at login
52
+ * and logout when identity lives in client storage (a bearer token) rather than in a cookie, which the
53
+ * server's epoch cookie already covers: every page kept alive for the previous visitor is dropped.
54
+ */
55
+ export function identityChanged(): void {
56
+ for (const listener of [...listenersOf()]) listener()
57
+ if (typeof BroadcastChannel === "undefined") return
58
+ const channel = new BroadcastChannel(IDENTITY_CHANNEL)
59
+ channel.postMessage("changed")
60
+ channel.close()
61
+ }
package/src/index.ts CHANGED
@@ -29,6 +29,9 @@ export {
29
29
  type PendingBoundary,
30
30
  resolveDynamicBoundaries,
31
31
  resolveStaticBoundaries,
32
+ type SharedBoundary,
33
+ type SharedHoleCtx,
34
+ type ShellCtx,
32
35
  type StaticBoundary,
33
36
  type StaticBoundaryCache,
34
37
  type StaticBoundaryImportEdge,
@@ -93,8 +96,15 @@ export {
93
96
  generateClientEntry,
94
97
  generateRouteSearchTypes,
95
98
  generateServerManifest,
99
+ type LiveClientEntry,
96
100
  } from "./internal/codegen.ts"
97
101
  export { mergeHeads, resolveChainMeta, resolveMeta } from "./internal/head-merge.ts"
102
+ // The server half of the identity epoch that drops kept pages on a sign-in or sign-out.
103
+ export {
104
+ type IdentityEpochContext,
105
+ type KeepAliveServerOptions,
106
+ rotateIdentityEpoch,
107
+ } from "./internal/identity-epoch.ts"
98
108
  export type { PrerenderedPages } from "./internal/prerendered-pages.ts"
99
109
  export {
100
110
  gone,
@@ -104,6 +114,7 @@ export {
104
114
  type RenderedPage,
105
115
  type RenderPageInput,
106
116
  type RenderPageOptions,
117
+ type RevalidateOptions,
107
118
  type RevalidateResult,
108
119
  redirect,
109
120
  renderPage,
@@ -163,6 +174,8 @@ export {
163
174
  // File-based routing manifest - pure + fs-free. `discoverRoutes` (fs) lives in `@nifrajs/web/fs`.
164
175
  export {
165
176
  type Action,
177
+ type BoundaryLoaderEntry,
178
+ type BoundaryLoadersOf,
166
179
  buildManifest,
167
180
  type ClientAction,
168
181
  type ClientActionArgs,
@@ -176,6 +189,7 @@ export {
176
189
  filePathToPatterns,
177
190
  type GetStaticPaths,
178
191
  type InertScriptType,
192
+ type KeepAliveOptions,
179
193
  type LayoutEntry,
180
194
  type LinkDescriptor,
181
195
  type Loader,
@@ -210,6 +224,7 @@ export {
210
224
  type BlockerLocation,
211
225
  type BlockerState,
212
226
  type BrowserNavigate,
227
+ closeBoundary,
213
228
  getBrowserNavigate,
214
229
  IDLE_BLOCKER,
215
230
  type NavigateFunction,
@@ -292,11 +307,15 @@ export {
292
307
  createClientRouter,
293
308
  createMatcher,
294
309
  DATA_HEADER,
310
+ type DataText,
295
311
  type Fetcher,
296
312
  type FetcherState,
297
313
  type FetchRouteData,
314
+ type Intercepted,
315
+ type KeptPage,
298
316
  type MountRouterOptions,
299
317
  NAV_FROM_HEADER,
318
+ PREFETCH_HEADER,
300
319
  REDIRECT_HEADER,
301
320
  RETAIN_HEADER,
302
321
  REVALIDATE_HEADER,
@@ -32,7 +32,8 @@ export interface CloudflareZoneOptions {
32
32
  /**
33
33
  * A Cloudflare zone proxying the origin. HTML is only cached by a Cache Rule that makes it eligible,
34
34
  * and the zone's cache ignores `Vary`: the rule must also bypass the cache when the request carries
35
- * `x-nifra-data` (soft navigations fetch the page URL with that header). `nifra cdn-check` tests both.
35
+ * `x-nifra-data` or `x-nifra-swap` (soft navigations and slot swaps fetch the page URL with those
36
+ * headers). `nifra cdn-check` tests all three.
36
37
  */
37
38
  export function cloudflareZone(options: CloudflareZoneOptions): CdnProvider {
38
39
  if (!/^[a-f0-9]{32}$/.test(options.zoneId)) {
@@ -3,5 +3,5 @@
3
3
  * guard and the consumer check read these; `component-certification.test.ts` holds the exact pins
4
4
  * in every manifest that runs the pipeline to them, so a range bump cannot drift past the guard.
5
5
  */
6
- export const CERTIFIED_COMPONENT_VITE = "8.2.1"
6
+ export const CERTIFIED_COMPONENT_VITE = "8.3.4"
7
7
  export const CERTIFIED_COMPONENT_REACT = "19.3.1"
@@ -16,6 +16,7 @@ import {
16
16
  } from "../render-seam.ts"
17
17
  import { PRERENDERED_GLOBAL } from "../router.ts"
18
18
  import { jsStringLiteral, jsValueLiteral } from "./js-string.ts"
19
+ import { keepAliveTable } from "./keep-alive-table.ts"
19
20
 
20
21
  /** The refusal for a server build given no client entry, shared by the codegen and `buildServer`. */
21
22
  export function missingClientEntry(clientEntry: unknown): TypeError {
@@ -53,6 +54,22 @@ export interface GenerateClientEntryOptions {
53
54
  /** The build moved copy tables into per-locale chunks: the handover carries the chunk URLs, and a
54
55
  * navigation loads the copy of the URL it goes to beside the route's code. */
55
56
  readonly copyChunks?: boolean
57
+ /**
58
+ * The server runs `createWebApp({ live })`: route loads go through its live load endpoint and the
59
+ * entry keeps what is on screen fresh (`@nifrajs/web/live-client`). Absent, the entry is exactly the
60
+ * non-live one.
61
+ */
62
+ readonly live?: LiveClientEntry | undefined
63
+ }
64
+
65
+ // The router as the entry names it: in a keep-alive app, the keep-alive layer over `router`.
66
+ const keepAround = (keep: unknown, router: string): string =>
67
+ keep === undefined ? `const router = ${router}` : `const router = __keep.attach(${router})`
68
+
69
+ /** A client entry for a live server: `{}`, or the endpoints when the runtime moves them. */
70
+ export interface LiveClientEntry {
71
+ readonly path?: string
72
+ readonly loadPath?: string
56
73
  }
57
74
 
58
75
  /**
@@ -65,11 +82,31 @@ export interface GenerateClientEntryOptions {
65
82
  * Bun has no `import.meta.glob`, so file-based routing needs this. Write the result to a file and
66
83
  * bundle it with `buildClient` / `Bun.build` (+ the adapter's transform).
67
84
  */
85
+ /** The client modules whose package has a keep-alive mount beside them, at `<package>/keep-alive`. */
86
+ const KEEP_ALIVE_ADAPTERS: readonly string[] = [
87
+ "@nifrajs/web-react/client",
88
+ "@nifrajs/web-svelte/client",
89
+ "@nifrajs/web-preact/client",
90
+ "@nifrajs/web-solid/client",
91
+ "@nifrajs/web-vue/client",
92
+ ]
93
+
68
94
  export function generateClientEntry(
69
95
  manifest: Manifest,
70
96
  options: GenerateClientEntryOptions,
71
97
  ): string {
72
- const { clientModule, resolve } = options
98
+ const { resolve } = options
99
+ const keep = keepAliveTable(manifest)
100
+ if (keep !== undefined && !KEEP_ALIVE_ADAPTERS.includes(options.clientModule)) {
101
+ throw new Error(
102
+ `[nifra/web] ${keep.routes.map((id) => `"${id}"`).join(", ")} export keepAlive, which needs the React, Preact, Solid, Svelte or Vue adapter; this app's client module is ${jsStringLiteral(options.clientModule)}. Remove the keepAlive export or switch the adapter.`,
103
+ )
104
+ }
105
+ // A keep-alive app mounts through the adapter's keep-alive module, which re-exports its client.
106
+ const clientModule =
107
+ keep === undefined
108
+ ? options.clientModule
109
+ : options.clientModule.replace(/\/client$/, "/keep-alive")
73
110
 
74
111
  const loaderRows: string[] = []
75
112
  const patternRows: string[] = []
@@ -165,6 +202,13 @@ export function generateClientEntry(
165
202
  )
166
203
  }
167
204
 
205
+ const live = options.live
206
+ const liveFetch =
207
+ keep !== undefined
208
+ ? ", fetchData: __keep.fetchData"
209
+ : live === undefined
210
+ ? ""
211
+ : ", fetchData: __live.fetchData"
168
212
  return `${[
169
213
  // `/client`, never the root: the root's graph carries the server (renderPage, the static-file
170
214
  // server), and Vite's dev server evaluates what it is given instead of tree-shaking it.
@@ -173,6 +217,8 @@ export function generateClientEntry(
173
217
  ...(options.serverComponents
174
218
  ? ['import { bindComponentRouter } from "@nifrajs/web/server-components/client"']
175
219
  : []),
220
+ ...(live === undefined ? [] : ['import { liveClient } from "@nifrajs/web/live-client"']),
221
+ ...(keep === undefined ? [] : ['import { keepAliveClient } from "@nifrajs/web/keep-alive"']),
176
222
  // Namespace import: `errorBoundary` is optional (an adapter may not export it). A namespace member
177
223
  // access yields `undefined` if absent - unlike a named import, which would be a link error.
178
224
  ...(loadings.length > 0
@@ -235,7 +281,22 @@ export function generateClientEntry(
235
281
  "const holds = {}",
236
282
  // routeId → the ids and `handle` exports `useMatches` reports (layouts, then the page).
237
283
  `const layoutIdsOf = ${jsValueLiteral(layoutIdsOf)}`,
284
+ ...(live === undefined
285
+ ? []
286
+ : [
287
+ `const __live = liveClient({ ${[
288
+ "layoutIdsOf",
289
+ ...(live.path === undefined ? [] : [`path: ${jsStringLiteral(live.path)}`]),
290
+ ...(live.loadPath === undefined ? [] : [`loadPath: ${jsStringLiteral(live.loadPath)}`]),
291
+ ].join(", ")} })`,
292
+ ]),
238
293
  "const matchChains = {}",
294
+ ...(keep === undefined
295
+ ? []
296
+ : [
297
+ `const keepAliveTable = ${jsValueLiteral(keep)}`,
298
+ `const __keep = keepAliveClient({ table: keepAliveTable, routeHooks, matchChains${live === undefined ? "" : ", fetchData: __live.fetchData"} })`,
299
+ ]),
239
300
  `const ${options.copyChunks === true ? "loadRoute" : "loadModule"} = async (id) => {`,
240
301
  " if (chains[id]) return",
241
302
  " const mods = await loaders[id]()",
@@ -271,7 +332,7 @@ export function generateClientEntry(
271
332
  // backend half, which no browser bundle contains.
272
333
  " const boundaryMods = errorRouteIds.has(id) ? mods.slice(0, -1) : mods",
273
334
  " const boundaries = boundaryMods.flatMap((m) => (m.boundaries ?? []).map((b) => ({ name: b.name, mode: b.mode, ...(b.errorId === undefined ? {} : { errorId: b.errorId }) })))",
274
- " routeHooks[id] = { clientLoader: page.clientLoader, clientAction: page.clientAction, boundaries }",
335
+ ` routeHooks[id] = { clientLoader: page.clientLoader, clientAction: page.clientAction, shareData: page.shareData, ${keep === undefined ? "" : "keepAlive: page.keepAlive, "}boundaries }`,
275
336
  " if (page.ssr === false) holds[id] = [...chains[id].slice(0, -1), page.HydrateFallback ?? (() => null)]",
276
337
  "}",
277
338
  ...(options.copyChunks === true
@@ -334,9 +395,18 @@ export function generateClientEntry(
334
395
  ...loadingLines,
335
396
  // With `_loading` pages the store is wrapped: a slow navigation then names a loading chain (the
336
397
  // shared layouts + the `_loading` component) for the mounted view to render in the meantime.
337
- loadings.length > 0
338
- ? `const router = withLoading(createClientRouter({ patterns, initial, loadModule, statusRoutes${routerStatusOption}, searchClientKeys, routeHooks }), { routes: loadingRoutes, modules: loadingModules, chains, searchSchemas, matchChains })`
339
- : `const router = createClientRouter({ patterns, initial, loadModule, statusRoutes${routerStatusOption}, searchClientKeys, routeHooks })`,
398
+ keepAround(
399
+ keep,
400
+ loadings.length > 0
401
+ ? `withLoading(createClientRouter({ patterns, initial, loadModule, statusRoutes${routerStatusOption}, searchClientKeys, routeHooks${liveFetch} }), { routes: loadingRoutes, modules: loadingModules, chains, searchSchemas, matchChains })`
402
+ : `createClientRouter({ patterns, initial, loadModule, statusRoutes${routerStatusOption}, searchClientKeys, routeHooks${liveFetch} })`,
403
+ ),
404
+ // Kept pages hold the code they were rendered with: a hot update drops them.
405
+ ...(keep === undefined
406
+ ? []
407
+ : [
408
+ 'if (import.meta.hot) for (const event of ["bun:afterUpdate", "vite:afterUpdate"]) import.meta.hot.on(event, () => router.dropKept("hot-update"))',
409
+ ]),
340
410
  ...(options.serverComponents ? ["bindComponentRouter(router)"] : []),
341
411
  "installHistory(router)",
342
412
  "installForms(router)",
@@ -392,11 +462,13 @@ export function generateClientEntry(
392
462
  " for (const listener of [...listeners]) listener()",
393
463
  " }, 0)",
394
464
  " }",
395
- " mountRouter({ router: view, routes: chains, searchSchemas, matchChains, container: root })",
465
+ ` mountRouter({ router: view, routes: chains, searchSchemas, matchChains, container: root${keep === undefined ? "" : ", keepAlive: keepAliveTable"} })`,
396
466
  // Run the optional client loader only after the adapter has mounted the SSR tree. The initial
397
467
  // server data is already in `window.__NIFRA_DATA__`, so `serverLoader()` reuses it and cannot
398
468
  // duplicate the first request.
399
469
  " router.hydrate().catch((error) => console.error('[nifra/web] clientLoader failed:', error))",
470
+ // Once mounted: the first live load and the socket must not race the hydration.
471
+ ...(live === undefined ? [] : [" __live.attach(router)"]),
400
472
  // Next frame: the adapter has committed its initial hydration, so handlers are attached. Flip the
401
473
  // document to interactive (`data-nifra-hydrated` + `nifra:hydrated`) for apps to gate pre-hydration UI.
402
474
  " requestAnimationFrame(signalHydrated)",
@@ -521,6 +593,13 @@ function moduleSpecifier(resolved: string): string {
521
593
  // A route's namespace is not assignable to `RouteModule` under a strict `tsc` (a backend half has no
522
594
  // `default`, a typed `meta` narrows its data), so the table holds plain objects and the cast happens at
523
595
  // `buildManifest`'s boundary - the same cast `discoverRoutes` applies to its dynamic import.
596
+ // The routes the scan found exporting `keepAlive`, which a disk-less server cannot scan again.
597
+ const keepAliveArg = (manifest: Manifest): string => {
598
+ const files = [
599
+ ...new Set(manifest.routes.filter((route) => route.keepAlive).map((route) => route.file)),
600
+ ]
601
+ return files.length === 0 ? "" : `, { keepAlive: ${jsValueLiteral(files)} }`
602
+ }
524
603
  const LAZY_IMPORTER = "(file) => loaders[file] as () => Promise<RouteModule>"
525
604
  const EAGER_IMPORTER = "(file) => () => Promise.resolve(modules[file] as RouteModule)"
526
605
 
@@ -604,7 +683,7 @@ export function generateServerManifest(
604
683
  cssLoadingLine,
605
684
  ...copyChunksLines,
606
685
  ...buildAssetsLines,
607
- `export const manifest = buildManifest(Object.keys(loaders), ${LAZY_IMPORTER})`,
686
+ `export const manifest = buildManifest(Object.keys(loaders), ${LAZY_IMPORTER}${keepAliveArg(manifest)})`,
608
687
  ].join("\n")}\n`
609
688
  }
610
689
  // Eager: `import * as` per route (all bundled into the entry, parsed at boot). Index-based
@@ -625,6 +704,6 @@ export function generateServerManifest(
625
704
  cssLoadingLine,
626
705
  ...copyChunksLines,
627
706
  ...buildAssetsLines,
628
- `export const manifest = buildManifest(Object.keys(modules), ${EAGER_IMPORTER})`,
707
+ `export const manifest = buildManifest(Object.keys(modules), ${EAGER_IMPORTER}${keepAliveArg(manifest)})`,
629
708
  ].join("\n")}\n`
630
709
  }