@nifrajs/web 5.5.1 → 5.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (481) hide show
  1. package/CHANGELOG.md +243 -0
  2. package/README.md +21 -11
  3. package/dist/boundary.d.ts +67 -7
  4. package/dist/boundary.d.ts.map +1 -1
  5. package/dist/boundary.js +6 -4
  6. package/dist/boundary.js.map +1 -1
  7. package/dist/build-plan.d.ts +3 -0
  8. package/dist/build-plan.d.ts.map +1 -1
  9. package/dist/build-plan.js.map +1 -1
  10. package/dist/build-vite.d.ts.map +1 -1
  11. package/dist/build-vite.js +2 -0
  12. package/dist/build-vite.js.map +1 -1
  13. package/dist/build.d.ts +9 -2
  14. package/dist/build.d.ts.map +1 -1
  15. package/dist/build.js +10 -0
  16. package/dist/build.js.map +1 -1
  17. package/dist/client.d.ts +3 -1
  18. package/dist/client.d.ts.map +1 -1
  19. package/dist/client.js +174 -23
  20. package/dist/client.js.map +1 -1
  21. package/dist/csp.d.ts +8 -0
  22. package/dist/csp.d.ts.map +1 -1
  23. package/dist/csp.js +11 -2
  24. package/dist/csp.js.map +1 -1
  25. package/dist/deferred.d.ts +6 -4
  26. package/dist/deferred.d.ts.map +1 -1
  27. package/dist/deferred.js +10 -6
  28. package/dist/deferred.js.map +1 -1
  29. package/dist/dev-feed.d.ts.map +1 -1
  30. package/dist/dev-feed.js +11 -2
  31. package/dist/dev-feed.js.map +1 -1
  32. package/dist/dev-session.d.ts.map +1 -1
  33. package/dist/dev-session.js +13 -6
  34. package/dist/dev-session.js.map +1 -1
  35. package/dist/dev.d.ts +4 -1
  36. package/dist/dev.d.ts.map +1 -1
  37. package/dist/dev.js +12 -5
  38. package/dist/dev.js.map +1 -1
  39. package/dist/diagnostic-catalog.d.ts +4 -0
  40. package/dist/diagnostic-catalog.d.ts.map +1 -1
  41. package/dist/diagnostic-catalog.js +48 -0
  42. package/dist/diagnostic-catalog.js.map +1 -1
  43. package/dist/diagnostic.d.ts +1 -1
  44. package/dist/diagnostic.d.ts.map +1 -1
  45. package/dist/diagnostic.js +1 -1
  46. package/dist/diagnostic.js.map +1 -1
  47. package/dist/fn.d.ts +5 -3
  48. package/dist/fn.d.ts.map +1 -1
  49. package/dist/fn.js +20 -5
  50. package/dist/fn.js.map +1 -1
  51. package/dist/fs.d.ts.map +1 -1
  52. package/dist/fs.js +13 -3
  53. package/dist/fs.js.map +1 -1
  54. package/dist/identity.d.ts +22 -0
  55. package/dist/identity.d.ts.map +1 -0
  56. package/dist/identity.js +60 -0
  57. package/dist/identity.js.map +1 -0
  58. package/dist/index.d.ts +8 -6
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +6 -3
  61. package/dist/index.js.map +1 -1
  62. package/dist/internal/cdn/cloudflare.d.ts +2 -1
  63. package/dist/internal/cdn/cloudflare.d.ts.map +1 -1
  64. package/dist/internal/cdn/cloudflare.js +2 -1
  65. package/dist/internal/cdn/cloudflare.js.map +1 -1
  66. package/dist/internal/certified-components.d.ts +1 -1
  67. package/dist/internal/certified-components.js +1 -1
  68. package/dist/internal/codegen.d.ts +11 -10
  69. package/dist/internal/codegen.d.ts.map +1 -1
  70. package/dist/internal/codegen.js +73 -10
  71. package/dist/internal/codegen.js.map +1 -1
  72. package/dist/internal/dev-websocket.d.ts +73 -0
  73. package/dist/internal/dev-websocket.d.ts.map +1 -0
  74. package/dist/internal/dev-websocket.js +145 -0
  75. package/dist/internal/dev-websocket.js.map +1 -0
  76. package/dist/internal/hole-runtime.d.ts +66 -0
  77. package/dist/internal/hole-runtime.d.ts.map +1 -0
  78. package/dist/internal/hole-runtime.js +211 -0
  79. package/dist/internal/hole-runtime.js.map +1 -0
  80. package/dist/internal/identity-epoch.d.ts +50 -0
  81. package/dist/internal/identity-epoch.d.ts.map +1 -0
  82. package/dist/internal/identity-epoch.js +180 -0
  83. package/dist/internal/identity-epoch.js.map +1 -0
  84. package/dist/internal/invalidation-contract.d.ts +23 -0
  85. package/dist/internal/invalidation-contract.d.ts.map +1 -0
  86. package/dist/internal/invalidation-contract.js +11 -0
  87. package/dist/internal/invalidation-contract.js.map +1 -0
  88. package/dist/internal/invalidation.d.ts +62 -0
  89. package/dist/internal/invalidation.d.ts.map +1 -0
  90. package/dist/internal/invalidation.js +149 -0
  91. package/dist/internal/invalidation.js.map +1 -0
  92. package/dist/internal/island-bindings.d.ts +51 -0
  93. package/dist/internal/island-bindings.d.ts.map +1 -0
  94. package/dist/internal/island-bindings.js +147 -0
  95. package/dist/internal/island-bindings.js.map +1 -0
  96. package/dist/internal/island-list.d.ts +26 -0
  97. package/dist/internal/island-list.d.ts.map +1 -0
  98. package/dist/internal/island-list.js +78 -0
  99. package/dist/internal/island-list.js.map +1 -0
  100. package/dist/internal/island-mount.d.ts +4 -0
  101. package/dist/internal/island-mount.d.ts.map +1 -0
  102. package/dist/internal/island-mount.js +66 -0
  103. package/dist/internal/island-mount.js.map +1 -0
  104. package/dist/internal/island-owner.d.ts +5 -0
  105. package/dist/internal/island-owner.d.ts.map +1 -0
  106. package/dist/internal/island-owner.js +20 -0
  107. package/dist/internal/island-owner.js.map +1 -0
  108. package/dist/internal/island-resource.d.ts +30 -0
  109. package/dist/internal/island-resource.d.ts.map +1 -0
  110. package/dist/internal/island-resource.js +57 -0
  111. package/dist/internal/island-resource.js.map +1 -0
  112. package/dist/internal/island-signals.d.ts +51 -0
  113. package/dist/internal/island-signals.d.ts.map +1 -0
  114. package/dist/internal/island-signals.js +220 -0
  115. package/dist/internal/island-signals.js.map +1 -0
  116. package/dist/internal/keep-alive-table.d.ts +23 -0
  117. package/dist/internal/keep-alive-table.d.ts.map +1 -0
  118. package/dist/internal/keep-alive-table.js +53 -0
  119. package/dist/internal/keep-alive-table.js.map +1 -0
  120. package/dist/internal/live-contract.d.ts +101 -0
  121. package/dist/internal/live-contract.d.ts.map +1 -0
  122. package/dist/internal/live-contract.js +5 -0
  123. package/dist/internal/live-contract.js.map +1 -0
  124. package/dist/internal/live-queries.d.ts +22 -0
  125. package/dist/internal/live-queries.d.ts.map +1 -0
  126. package/dist/internal/live-queries.js +54 -0
  127. package/dist/internal/live-queries.js.map +1 -0
  128. package/dist/internal/page-events.d.ts +10 -0
  129. package/dist/internal/page-events.d.ts.map +1 -0
  130. package/dist/internal/page-events.js +26 -0
  131. package/dist/internal/page-events.js.map +1 -0
  132. package/dist/internal/page-execution.d.ts +16 -1
  133. package/dist/internal/page-execution.d.ts.map +1 -1
  134. package/dist/internal/page-execution.js +432 -31
  135. package/dist/internal/page-execution.js.map +1 -1
  136. package/dist/internal/parity.d.ts +3 -0
  137. package/dist/internal/parity.d.ts.map +1 -1
  138. package/dist/internal/parity.js +28 -5
  139. package/dist/internal/parity.js.map +1 -1
  140. package/dist/internal/ppr/admission.d.ts +83 -0
  141. package/dist/internal/ppr/admission.d.ts.map +1 -0
  142. package/dist/internal/ppr/admission.js +221 -0
  143. package/dist/internal/ppr/admission.js.map +1 -0
  144. package/dist/internal/ppr/canary.d.ts +24 -0
  145. package/dist/internal/ppr/canary.d.ts.map +1 -0
  146. package/dist/internal/ppr/canary.js +102 -0
  147. package/dist/internal/ppr/canary.js.map +1 -0
  148. package/dist/internal/ppr/compile-shell.d.ts +39 -0
  149. package/dist/internal/ppr/compile-shell.d.ts.map +1 -0
  150. package/dist/internal/ppr/compile-shell.js +298 -0
  151. package/dist/internal/ppr/compile-shell.js.map +1 -0
  152. package/dist/internal/ppr/contract.d.ts +177 -0
  153. package/dist/internal/ppr/contract.d.ts.map +1 -0
  154. package/dist/internal/ppr/contract.js +80 -0
  155. package/dist/internal/ppr/contract.js.map +1 -0
  156. package/dist/internal/ppr/frames.d.ts +71 -0
  157. package/dist/internal/ppr/frames.d.ts.map +1 -0
  158. package/dist/internal/ppr/frames.js +119 -0
  159. package/dist/internal/ppr/frames.js.map +1 -0
  160. package/dist/internal/ppr/integration.d.ts +19 -0
  161. package/dist/internal/ppr/integration.d.ts.map +1 -0
  162. package/dist/internal/ppr/integration.js +106 -0
  163. package/dist/internal/ppr/integration.js.map +1 -0
  164. package/dist/internal/ppr/options.d.ts +96 -0
  165. package/dist/internal/ppr/options.d.ts.map +1 -0
  166. package/dist/internal/ppr/options.js +125 -0
  167. package/dist/internal/ppr/options.js.map +1 -0
  168. package/dist/internal/ppr/route-rules.d.ts +12 -0
  169. package/dist/internal/ppr/route-rules.d.ts.map +1 -0
  170. package/dist/internal/ppr/route-rules.js +53 -0
  171. package/dist/internal/ppr/route-rules.js.map +1 -0
  172. package/dist/internal/ppr/runtime.d.ts +29 -0
  173. package/dist/internal/ppr/runtime.d.ts.map +1 -0
  174. package/dist/internal/ppr/runtime.js +35 -0
  175. package/dist/internal/ppr/runtime.js.map +1 -0
  176. package/dist/internal/ppr/server.d.ts +29 -0
  177. package/dist/internal/ppr/server.d.ts.map +1 -0
  178. package/dist/internal/ppr/server.js +1409 -0
  179. package/dist/internal/ppr/server.js.map +1 -0
  180. package/dist/internal/ppr/shell-store.d.ts +39 -0
  181. package/dist/internal/ppr/shell-store.d.ts.map +1 -0
  182. package/dist/internal/ppr/shell-store.js +106 -0
  183. package/dist/internal/ppr/shell-store.js.map +1 -0
  184. package/dist/internal/prerendered-pages.d.ts +1 -1
  185. package/dist/internal/prerendered-pages.d.ts.map +1 -1
  186. package/dist/internal/prerendered-pages.js +7 -4
  187. package/dist/internal/prerendered-pages.js.map +1 -1
  188. package/dist/internal/present.d.ts +11 -0
  189. package/dist/internal/present.d.ts.map +1 -0
  190. package/dist/internal/present.js +13 -0
  191. package/dist/internal/present.js.map +1 -0
  192. package/dist/internal/query-digest.d.ts +9 -0
  193. package/dist/internal/query-digest.d.ts.map +1 -0
  194. package/dist/internal/query-digest.js +23 -0
  195. package/dist/internal/query-digest.js.map +1 -0
  196. package/dist/internal/query-runtime.d.ts +6 -1
  197. package/dist/internal/query-runtime.d.ts.map +1 -1
  198. package/dist/internal/query-runtime.js +9 -0
  199. package/dist/internal/query-runtime.js.map +1 -1
  200. package/dist/internal/query-seam.d.ts +29 -0
  201. package/dist/internal/query-seam.d.ts.map +1 -0
  202. package/dist/internal/query-seam.js +23 -0
  203. package/dist/internal/query-seam.js.map +1 -0
  204. package/dist/internal/render-document.d.ts +34 -64
  205. package/dist/internal/render-document.d.ts.map +1 -1
  206. package/dist/internal/render-document.js +82 -64
  207. package/dist/internal/render-document.js.map +1 -1
  208. package/dist/internal/request-abort.d.ts +8 -0
  209. package/dist/internal/request-abort.d.ts.map +1 -0
  210. package/dist/internal/request-abort.js +22 -0
  211. package/dist/internal/request-abort.js.map +1 -0
  212. package/dist/internal/server-boundary.d.ts +2 -0
  213. package/dist/internal/server-boundary.d.ts.map +1 -1
  214. package/dist/internal/server-boundary.js +8 -1
  215. package/dist/internal/server-boundary.js.map +1 -1
  216. package/dist/internal/share-data.d.ts +10 -0
  217. package/dist/internal/share-data.d.ts.map +1 -0
  218. package/dist/internal/share-data.js +135 -0
  219. package/dist/internal/share-data.js.map +1 -0
  220. package/dist/internal/status-page.d.ts +68 -0
  221. package/dist/internal/status-page.d.ts.map +1 -0
  222. package/dist/internal/status-page.js +66 -0
  223. package/dist/internal/status-page.js.map +1 -0
  224. package/dist/internal/store-signature.d.ts +15 -0
  225. package/dist/internal/store-signature.d.ts.map +1 -0
  226. package/dist/internal/store-signature.js +114 -0
  227. package/dist/internal/store-signature.js.map +1 -0
  228. package/dist/internal/vite-plugin-compat.d.ts +2 -2
  229. package/dist/internal/vite-plugin-compat.js +2 -2
  230. package/dist/internal/web-app.d.ts +26 -0
  231. package/dist/internal/web-app.d.ts.map +1 -1
  232. package/dist/internal/web-app.js +79 -3
  233. package/dist/internal/web-app.js.map +1 -1
  234. package/dist/islands-swap.d.ts +41 -0
  235. package/dist/islands-swap.d.ts.map +1 -0
  236. package/dist/islands-swap.js +270 -0
  237. package/dist/islands-swap.js.map +1 -0
  238. package/dist/islands.d.ts +86 -24
  239. package/dist/islands.d.ts.map +1 -1
  240. package/dist/islands.js +138 -78
  241. package/dist/islands.js.map +1 -1
  242. package/dist/isr.d.ts +67 -13
  243. package/dist/isr.d.ts.map +1 -1
  244. package/dist/isr.js +247 -99
  245. package/dist/isr.js.map +1 -1
  246. package/dist/keep-alive.d.ts +147 -0
  247. package/dist/keep-alive.d.ts.map +1 -0
  248. package/dist/keep-alive.js +1102 -0
  249. package/dist/keep-alive.js.map +1 -0
  250. package/dist/live/bus.d.ts +70 -0
  251. package/dist/live/bus.d.ts.map +1 -0
  252. package/dist/live/bus.js +55 -0
  253. package/dist/live/bus.js.map +1 -0
  254. package/dist/live/core.d.ts +25 -0
  255. package/dist/live/core.d.ts.map +1 -0
  256. package/dist/live/core.js +29 -0
  257. package/dist/live/core.js.map +1 -0
  258. package/dist/live/history.d.ts +38 -0
  259. package/dist/live/history.d.ts.map +1 -0
  260. package/dist/live/history.js +104 -0
  261. package/dist/live/history.js.map +1 -0
  262. package/dist/live/hub-protocol.d.ts +46 -0
  263. package/dist/live/hub-protocol.d.ts.map +1 -0
  264. package/dist/live/hub-protocol.js +108 -0
  265. package/dist/live/hub-protocol.js.map +1 -0
  266. package/dist/live/hub.d.ts +185 -0
  267. package/dist/live/hub.d.ts.map +1 -0
  268. package/dist/live/hub.js +544 -0
  269. package/dist/live/hub.js.map +1 -0
  270. package/dist/live/keyring.d.ts +92 -0
  271. package/dist/live/keyring.d.ts.map +1 -0
  272. package/dist/live/keyring.js +265 -0
  273. package/dist/live/keyring.js.map +1 -0
  274. package/dist/live/read-capped.d.ts +6 -0
  275. package/dist/live/read-capped.d.ts.map +1 -0
  276. package/dist/live/read-capped.js +42 -0
  277. package/dist/live/read-capped.js.map +1 -0
  278. package/dist/live/revocations.d.ts +52 -0
  279. package/dist/live/revocations.d.ts.map +1 -0
  280. package/dist/live/revocations.js +116 -0
  281. package/dist/live/revocations.js.map +1 -0
  282. package/dist/live/settings.d.ts +14 -0
  283. package/dist/live/settings.d.ts.map +1 -0
  284. package/dist/live/settings.js +38 -0
  285. package/dist/live/settings.js.map +1 -0
  286. package/dist/live-client.d.ts +34 -0
  287. package/dist/live-client.d.ts.map +1 -0
  288. package/dist/live-client.js +878 -0
  289. package/dist/live-client.js.map +1 -0
  290. package/dist/live-hub.d.ts +130 -0
  291. package/dist/live-hub.d.ts.map +1 -0
  292. package/dist/live-hub.js +507 -0
  293. package/dist/live-hub.js.map +1 -0
  294. package/dist/live-redis.d.ts +60 -0
  295. package/dist/live-redis.d.ts.map +1 -0
  296. package/dist/live-redis.js +212 -0
  297. package/dist/live-redis.js.map +1 -0
  298. package/dist/live.d.ts +150 -0
  299. package/dist/live.d.ts.map +1 -0
  300. package/dist/live.js +972 -0
  301. package/dist/live.js.map +1 -0
  302. package/dist/manifest.d.ts +113 -2
  303. package/dist/manifest.d.ts.map +1 -1
  304. package/dist/manifest.js +109 -5
  305. package/dist/manifest.js.map +1 -1
  306. package/dist/navigation.d.ts +9 -3
  307. package/dist/navigation.d.ts.map +1 -1
  308. package/dist/navigation.js +13 -3
  309. package/dist/navigation.js.map +1 -1
  310. package/dist/plugins/vite-zone-guard.d.ts +6 -0
  311. package/dist/plugins/vite-zone-guard.d.ts.map +1 -1
  312. package/dist/plugins/vite-zone-guard.js +8 -2
  313. package/dist/plugins/vite-zone-guard.js.map +1 -1
  314. package/dist/ppr.d.ts +73 -0
  315. package/dist/ppr.d.ts.map +1 -0
  316. package/dist/ppr.js +76 -0
  317. package/dist/ppr.js.map +1 -0
  318. package/dist/query.d.ts +8 -13
  319. package/dist/query.d.ts.map +1 -1
  320. package/dist/query.js +112 -18
  321. package/dist/query.js.map +1 -1
  322. package/dist/render-seam.d.ts +32 -0
  323. package/dist/render-seam.d.ts.map +1 -1
  324. package/dist/render-seam.js +7 -0
  325. package/dist/render-seam.js.map +1 -1
  326. package/dist/route-types.d.ts.map +1 -1
  327. package/dist/route-types.js +2 -2
  328. package/dist/route-types.js.map +1 -1
  329. package/dist/router.d.ts +72 -4
  330. package/dist/router.d.ts.map +1 -1
  331. package/dist/router.js +416 -129
  332. package/dist/router.js.map +1 -1
  333. package/dist/server-components/build.d.ts.map +1 -1
  334. package/dist/server-components/build.js +3 -3
  335. package/dist/server-components/build.js.map +1 -1
  336. package/dist/server-components/client.d.ts +23 -1
  337. package/dist/server-components/client.d.ts.map +1 -1
  338. package/dist/server-components/client.js +92 -5
  339. package/dist/server-components/client.js.map +1 -1
  340. package/dist/server-components/definition.d.ts.map +1 -1
  341. package/dist/server-components/definition.js +2 -1
  342. package/dist/server-components/definition.js.map +1 -1
  343. package/dist/server-components/hole.d.ts +8 -0
  344. package/dist/server-components/hole.d.ts.map +1 -0
  345. package/dist/server-components/hole.js +20 -0
  346. package/dist/server-components/hole.js.map +1 -0
  347. package/dist/server-components/html-policy.d.ts.map +1 -1
  348. package/dist/server-components/html-policy.js +62 -4
  349. package/dist/server-components/html-policy.js.map +1 -1
  350. package/dist/server-components/html-scan.d.ts +17 -0
  351. package/dist/server-components/html-scan.d.ts.map +1 -0
  352. package/dist/server-components/html-scan.js +180 -0
  353. package/dist/server-components/html-scan.js.map +1 -0
  354. package/dist/server-components/html.d.ts.map +1 -1
  355. package/dist/server-components/html.js +19 -4
  356. package/dist/server-components/html.js.map +1 -1
  357. package/dist/server-components/router-integration.d.ts +4 -1
  358. package/dist/server-components/router-integration.d.ts.map +1 -1
  359. package/dist/server-components/router-integration.js +23 -2
  360. package/dist/server-components/router-integration.js.map +1 -1
  361. package/dist/server-components/runtime.d.ts +6 -1
  362. package/dist/server-components/runtime.d.ts.map +1 -1
  363. package/dist/server-components/runtime.js +88 -29
  364. package/dist/server-components/runtime.js.map +1 -1
  365. package/dist/server-components/state.d.ts +3 -1
  366. package/dist/server-components/state.d.ts.map +1 -1
  367. package/dist/server-components/state.js +50 -38
  368. package/dist/server-components/state.js.map +1 -1
  369. package/dist/server-components/types.d.ts +9 -0
  370. package/dist/server-components/types.d.ts.map +1 -1
  371. package/dist/server-components/vite.d.ts.map +1 -1
  372. package/dist/server-components/vite.js +26 -9
  373. package/dist/server-components/vite.js.map +1 -1
  374. package/dist/vite.d.ts +5 -1
  375. package/dist/vite.d.ts.map +1 -1
  376. package/dist/vite.js +58 -4
  377. package/dist/vite.js.map +1 -1
  378. package/package.json +48 -8
  379. package/src/boundary.ts +77 -5
  380. package/src/build-plan.ts +3 -0
  381. package/src/build-vite.ts +2 -0
  382. package/src/build.ts +20 -1
  383. package/src/client.ts +183 -23
  384. package/src/csp.ts +16 -2
  385. package/src/deferred.ts +11 -5
  386. package/src/dev-feed.ts +11 -2
  387. package/src/dev-session.ts +22 -11
  388. package/src/dev.ts +29 -8
  389. package/src/diagnostic-catalog.ts +53 -0
  390. package/src/diagnostic.ts +1 -0
  391. package/src/fn.ts +24 -7
  392. package/src/fs.ts +22 -8
  393. package/src/identity.ts +61 -0
  394. package/src/index.ts +20 -4
  395. package/src/internal/cdn/cloudflare.ts +2 -1
  396. package/src/internal/certified-components.ts +1 -1
  397. package/src/internal/codegen.ts +95 -9
  398. package/src/internal/dev-websocket.ts +230 -0
  399. package/src/internal/hole-runtime.ts +286 -0
  400. package/src/internal/identity-epoch.ts +239 -0
  401. package/src/internal/invalidation-contract.ts +35 -0
  402. package/src/internal/invalidation.ts +213 -0
  403. package/src/internal/island-bindings.ts +191 -0
  404. package/src/internal/island-list.ts +92 -0
  405. package/src/internal/island-mount.ts +68 -0
  406. package/src/internal/island-owner.ts +20 -0
  407. package/src/internal/island-resource.ts +76 -0
  408. package/src/internal/island-signals.ts +249 -0
  409. package/src/internal/keep-alive-table.ts +72 -0
  410. package/src/internal/live-contract.ts +111 -0
  411. package/src/internal/live-queries.ts +60 -0
  412. package/src/internal/page-events.ts +27 -0
  413. package/src/internal/page-execution.ts +605 -45
  414. package/src/internal/parity.ts +35 -6
  415. package/src/internal/ppr/admission.ts +300 -0
  416. package/src/internal/ppr/canary.ts +122 -0
  417. package/src/internal/ppr/compile-shell.ts +379 -0
  418. package/src/internal/ppr/contract.ts +265 -0
  419. package/src/internal/ppr/frames.ts +170 -0
  420. package/src/internal/ppr/integration.ts +137 -0
  421. package/src/internal/ppr/options.ts +221 -0
  422. package/src/internal/ppr/route-rules.ts +77 -0
  423. package/src/internal/ppr/runtime.ts +39 -0
  424. package/src/internal/ppr/server.ts +1766 -0
  425. package/src/internal/ppr/shell-store.ts +152 -0
  426. package/src/internal/prerendered-pages.ts +7 -4
  427. package/src/internal/present.ts +20 -0
  428. package/src/internal/query-digest.ts +34 -0
  429. package/src/internal/query-runtime.ts +14 -1
  430. package/src/internal/query-seam.ts +48 -0
  431. package/src/internal/render-document.ts +158 -113
  432. package/src/internal/request-abort.ts +26 -0
  433. package/src/internal/server-boundary.ts +10 -1
  434. package/src/internal/share-data.ts +136 -0
  435. package/src/internal/status-page.ts +95 -0
  436. package/src/internal/store-signature.ts +149 -0
  437. package/src/internal/vite-plugin-compat.ts +2 -2
  438. package/src/internal/web-app.ts +123 -3
  439. package/src/islands-swap.ts +283 -0
  440. package/src/islands.ts +169 -72
  441. package/src/isr.ts +348 -106
  442. package/src/keep-alive.ts +1354 -0
  443. package/src/live/bus.ts +104 -0
  444. package/src/live/core.ts +52 -0
  445. package/src/live/history.ts +109 -0
  446. package/src/live/hub-protocol.ts +133 -0
  447. package/src/live/hub.ts +620 -0
  448. package/src/live/keyring.ts +384 -0
  449. package/src/live/read-capped.ts +36 -0
  450. package/src/live/revocations.ts +155 -0
  451. package/src/live/settings.ts +46 -0
  452. package/src/live-client.ts +1011 -0
  453. package/src/live-hub.ts +658 -0
  454. package/src/live-redis.ts +284 -0
  455. package/src/live.ts +1257 -0
  456. package/src/manifest.ts +274 -6
  457. package/src/navigation.ts +14 -2
  458. package/src/plugins/vite-zone-guard.ts +15 -2
  459. package/src/ppr.ts +153 -0
  460. package/src/query.ts +147 -20
  461. package/src/render-seam.ts +37 -0
  462. package/src/route-types.ts +3 -1
  463. package/src/router.ts +534 -132
  464. package/src/server-components/build.ts +9 -3
  465. package/src/server-components/client.ts +89 -5
  466. package/src/server-components/definition.ts +2 -1
  467. package/src/server-components/hole.ts +23 -0
  468. package/src/server-components/html-policy.ts +63 -4
  469. package/src/server-components/html-scan.ts +159 -0
  470. package/src/server-components/html.ts +18 -4
  471. package/src/server-components/router-integration.ts +21 -2
  472. package/src/server-components/runtime.ts +114 -31
  473. package/src/server-components/state.ts +66 -44
  474. package/src/server-components/types.ts +16 -0
  475. package/src/server-components/vite.ts +33 -14
  476. package/src/vite.ts +75 -6
  477. package/dist/nano.d.ts +0 -125
  478. package/dist/nano.d.ts.map +0 -1
  479. package/dist/nano.js +0 -190
  480. package/dist/nano.js.map +0 -1
  481. package/src/nano.ts +0 -246
@@ -10,12 +10,14 @@ import {
10
10
  DATA_GLOBAL,
11
11
  HANDOVER_ID,
12
12
  LAYOUT_DATA_GLOBAL,
13
+ LIVE_GLOBAL,
13
14
  LOCALES_GLOBAL,
14
15
  ROOT_ATTRIBUTE,
15
16
  ROUTE_GLOBAL,
16
17
  } from "../render-seam.ts"
17
18
  import { PRERENDERED_GLOBAL } from "../router.ts"
18
19
  import { jsStringLiteral, jsValueLiteral } from "./js-string.ts"
20
+ import { keepAliveTable } from "./keep-alive-table.ts"
19
21
 
20
22
  /** The refusal for a server build given no client entry, shared by the codegen and `buildServer`. */
21
23
  export function missingClientEntry(clientEntry: unknown): TypeError {
@@ -53,6 +55,22 @@ export interface GenerateClientEntryOptions {
53
55
  /** The build moved copy tables into per-locale chunks: the handover carries the chunk URLs, and a
54
56
  * navigation loads the copy of the URL it goes to beside the route's code. */
55
57
  readonly copyChunks?: boolean
58
+ /**
59
+ * The server runs `createWebApp({ live })`: route loads go through its live load endpoint and the
60
+ * entry keeps what is on screen fresh (`@nifrajs/web/live-client`). Absent, the entry is exactly the
61
+ * non-live one.
62
+ */
63
+ readonly live?: LiveClientEntry | undefined
64
+ }
65
+
66
+ // The router as the entry names it: in a keep-alive app, the keep-alive layer over `router`.
67
+ const keepAround = (keep: unknown, router: string): string =>
68
+ keep === undefined ? `const router = ${router}` : `const router = __keep.attach(${router})`
69
+
70
+ /** A client entry for a live server: `{}`, or the endpoints when the runtime moves them. */
71
+ export interface LiveClientEntry {
72
+ readonly path?: string
73
+ readonly loadPath?: string
56
74
  }
57
75
 
58
76
  /**
@@ -65,11 +83,31 @@ export interface GenerateClientEntryOptions {
65
83
  * Bun has no `import.meta.glob`, so file-based routing needs this. Write the result to a file and
66
84
  * bundle it with `buildClient` / `Bun.build` (+ the adapter's transform).
67
85
  */
86
+ /** The client modules whose package has a keep-alive mount beside them, at `<package>/keep-alive`. */
87
+ const KEEP_ALIVE_ADAPTERS: readonly string[] = [
88
+ "@nifrajs/web-react/client",
89
+ "@nifrajs/web-svelte/client",
90
+ "@nifrajs/web-preact/client",
91
+ "@nifrajs/web-solid/client",
92
+ "@nifrajs/web-vue/client",
93
+ ]
94
+
68
95
  export function generateClientEntry(
69
96
  manifest: Manifest,
70
97
  options: GenerateClientEntryOptions,
71
98
  ): string {
72
- const { clientModule, resolve } = options
99
+ const { resolve } = options
100
+ const keep = keepAliveTable(manifest)
101
+ if (keep !== undefined && !KEEP_ALIVE_ADAPTERS.includes(options.clientModule)) {
102
+ throw new Error(
103
+ `[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.`,
104
+ )
105
+ }
106
+ // A keep-alive app mounts through the adapter's keep-alive module, which re-exports its client.
107
+ const clientModule =
108
+ keep === undefined
109
+ ? options.clientModule
110
+ : options.clientModule.replace(/\/client$/, "/keep-alive")
73
111
 
74
112
  const loaderRows: string[] = []
75
113
  const patternRows: string[] = []
@@ -165,6 +203,13 @@ export function generateClientEntry(
165
203
  )
166
204
  }
167
205
 
206
+ const live = options.live
207
+ const liveFetch =
208
+ keep !== undefined
209
+ ? ", fetchData: __keep.fetchData"
210
+ : live === undefined
211
+ ? ""
212
+ : ", fetchData: __live.fetchData"
168
213
  return `${[
169
214
  // `/client`, never the root: the root's graph carries the server (renderPage, the static-file
170
215
  // server), and Vite's dev server evaluates what it is given instead of tree-shaking it.
@@ -173,6 +218,8 @@ export function generateClientEntry(
173
218
  ...(options.serverComponents
174
219
  ? ['import { bindComponentRouter } from "@nifrajs/web/server-components/client"']
175
220
  : []),
221
+ ...(live === undefined ? [] : ['import { liveClient } from "@nifrajs/web/live-client"']),
222
+ ...(keep === undefined ? [] : ['import { keepAliveClient } from "@nifrajs/web/keep-alive"']),
176
223
  // Namespace import: `errorBoundary` is optional (an adapter may not export it). A namespace member
177
224
  // access yields `undefined` if absent - unlike a named import, which would be a link error.
178
225
  ...(loadings.length > 0
@@ -235,7 +282,22 @@ export function generateClientEntry(
235
282
  "const holds = {}",
236
283
  // routeId → the ids and `handle` exports `useMatches` reports (layouts, then the page).
237
284
  `const layoutIdsOf = ${jsValueLiteral(layoutIdsOf)}`,
285
+ ...(live === undefined
286
+ ? []
287
+ : [
288
+ `const __live = liveClient({ ${[
289
+ "layoutIdsOf",
290
+ ...(live.path === undefined ? [] : [`path: ${jsStringLiteral(live.path)}`]),
291
+ ...(live.loadPath === undefined ? [] : [`loadPath: ${jsStringLiteral(live.loadPath)}`]),
292
+ ].join(", ")} })`,
293
+ ]),
238
294
  "const matchChains = {}",
295
+ ...(keep === undefined
296
+ ? []
297
+ : [
298
+ `const keepAliveTable = ${jsValueLiteral(keep)}`,
299
+ `const __keep = keepAliveClient({ table: keepAliveTable, routeHooks, matchChains${live === undefined ? "" : ", fetchData: __live.fetchData"} })`,
300
+ ]),
239
301
  `const ${options.copyChunks === true ? "loadRoute" : "loadModule"} = async (id) => {`,
240
302
  " if (chains[id]) return",
241
303
  " const mods = await loaders[id]()",
@@ -271,7 +333,7 @@ export function generateClientEntry(
271
333
  // backend half, which no browser bundle contains.
272
334
  " const boundaryMods = errorRouteIds.has(id) ? mods.slice(0, -1) : mods",
273
335
  " 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 }",
336
+ ` routeHooks[id] = { clientLoader: page.clientLoader, clientAction: page.clientAction, shareData: page.shareData, ${keep === undefined ? "" : "keepAlive: page.keepAlive, "}boundaries }`,
275
337
  " if (page.ssr === false) holds[id] = [...chains[id].slice(0, -1), page.HydrateFallback ?? (() => null)]",
276
338
  "}",
277
339
  ...(options.copyChunks === true
@@ -293,7 +355,11 @@ export function generateClientEntry(
293
355
  "const handover = handovers[handovers.length - 1]",
294
356
  "if (handover !== undefined) {",
295
357
  ' const state = JSON.parse(handover.textContent || "{}")',
296
- ` for (const key of ${jsValueLiteral(options.copyChunks === true ? [...HANDOVER_GLOBALS, COPY_CHUNKS_GLOBAL] : HANDOVER_GLOBALS)}) {`,
358
+ ` for (const key of ${jsValueLiteral([
359
+ ...HANDOVER_GLOBALS,
360
+ ...(options.copyChunks === true ? [COPY_CHUNKS_GLOBAL] : []),
361
+ ...(live === undefined ? [] : [LIVE_GLOBAL]),
362
+ ])}) {`,
297
363
  " if (Object.prototype.hasOwnProperty.call(state, key)) Reflect.set(window, key, state[key])",
298
364
  " }",
299
365
  "}",
@@ -325,6 +391,8 @@ export function generateClientEntry(
325
391
  ` layoutData: window.${LAYOUT_DATA_GLOBAL} && window.${LAYOUT_DATA_GLOBAL}.map(mapDeferred),`,
326
392
  ` boundaries: mapDeferred(window.${BOUNDARY_GLOBAL}),`,
327
393
  ` serverComponents: window.${COMPONENT_GLOBAL},`,
394
+ // The seed the server rendered the page with: the live client exchanges it for its first grant.
395
+ ...(live === undefined ? [] : [` live: window.${LIVE_GLOBAL},`]),
328
396
  // actionData (only set after a form POST) is in the initial state so the binding hydrates
329
397
  // consistently with the server-rendered markup; mapped through `mapDeferred` too so a deferred
330
398
  // action's placeholders become registry markers (a no-op when the action didn't defer).
@@ -334,9 +402,18 @@ export function generateClientEntry(
334
402
  ...loadingLines,
335
403
  // With `_loading` pages the store is wrapped: a slow navigation then names a loading chain (the
336
404
  // 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 })`,
405
+ keepAround(
406
+ keep,
407
+ loadings.length > 0
408
+ ? `withLoading(createClientRouter({ patterns, initial, loadModule, statusRoutes${routerStatusOption}, searchClientKeys, routeHooks${liveFetch} }), { routes: loadingRoutes, modules: loadingModules, chains, searchSchemas, matchChains })`
409
+ : `createClientRouter({ patterns, initial, loadModule, statusRoutes${routerStatusOption}, searchClientKeys, routeHooks${liveFetch} })`,
410
+ ),
411
+ // Kept pages hold the code they were rendered with: a hot update drops them.
412
+ ...(keep === undefined
413
+ ? []
414
+ : [
415
+ 'if (import.meta.hot) for (const event of ["bun:afterUpdate", "vite:afterUpdate"]) import.meta.hot.on(event, () => router.dropKept("hot-update"))',
416
+ ]),
340
417
  ...(options.serverComponents ? ["bindComponentRouter(router)"] : []),
341
418
  "installHistory(router)",
342
419
  "installForms(router)",
@@ -392,11 +469,13 @@ export function generateClientEntry(
392
469
  " for (const listener of [...listeners]) listener()",
393
470
  " }, 0)",
394
471
  " }",
395
- " mountRouter({ router: view, routes: chains, searchSchemas, matchChains, container: root })",
472
+ ` mountRouter({ router: view, routes: chains, searchSchemas, matchChains, container: root${keep === undefined ? "" : ", keepAlive: keepAliveTable"} })`,
396
473
  // Run the optional client loader only after the adapter has mounted the SSR tree. The initial
397
474
  // server data is already in `window.__NIFRA_DATA__`, so `serverLoader()` reuses it and cannot
398
475
  // duplicate the first request.
399
476
  " router.hydrate().catch((error) => console.error('[nifra/web] clientLoader failed:', error))",
477
+ // Once mounted: the first live load and the socket must not race the hydration.
478
+ ...(live === undefined ? [] : [" __live.attach(router)"]),
400
479
  // Next frame: the adapter has committed its initial hydration, so handlers are attached. Flip the
401
480
  // document to interactive (`data-nifra-hydrated` + `nifra:hydrated`) for apps to gate pre-hydration UI.
402
481
  " requestAnimationFrame(signalHydrated)",
@@ -521,6 +600,13 @@ function moduleSpecifier(resolved: string): string {
521
600
  // A route's namespace is not assignable to `RouteModule` under a strict `tsc` (a backend half has no
522
601
  // `default`, a typed `meta` narrows its data), so the table holds plain objects and the cast happens at
523
602
  // `buildManifest`'s boundary - the same cast `discoverRoutes` applies to its dynamic import.
603
+ // The routes the scan found exporting `keepAlive`, which a disk-less server cannot scan again.
604
+ const keepAliveArg = (manifest: Manifest): string => {
605
+ const files = [
606
+ ...new Set(manifest.routes.filter((route) => route.keepAlive).map((route) => route.file)),
607
+ ]
608
+ return files.length === 0 ? "" : `, { keepAlive: ${jsValueLiteral(files)} }`
609
+ }
524
610
  const LAZY_IMPORTER = "(file) => loaders[file] as () => Promise<RouteModule>"
525
611
  const EAGER_IMPORTER = "(file) => () => Promise.resolve(modules[file] as RouteModule)"
526
612
 
@@ -604,7 +690,7 @@ export function generateServerManifest(
604
690
  cssLoadingLine,
605
691
  ...copyChunksLines,
606
692
  ...buildAssetsLines,
607
- `export const manifest = buildManifest(Object.keys(loaders), ${LAZY_IMPORTER})`,
693
+ `export const manifest = buildManifest(Object.keys(loaders), ${LAZY_IMPORTER}${keepAliveArg(manifest)})`,
608
694
  ].join("\n")}\n`
609
695
  }
610
696
  // Eager: `import * as` per route (all bundled into the entry, parsed at boot). Index-based
@@ -625,6 +711,6 @@ export function generateServerManifest(
625
711
  cssLoadingLine,
626
712
  ...copyChunksLines,
627
713
  ...buildAssetsLines,
628
- `export const manifest = buildManifest(Object.keys(modules), ${EAGER_IMPORTER})`,
714
+ `export const manifest = buildManifest(Object.keys(modules), ${EAGER_IMPORTER}${keepAliveArg(manifest)})`,
629
715
  ].join("\n")}\n`
630
716
  }
@@ -0,0 +1,230 @@
1
+ /**
2
+ * The app's WebSocket routes under the dev servers. Neither dev server is the app's own listener, so
3
+ * neither gets the upgrades `app.listen()` and the adapters wire: each hands the handshake to the
4
+ * app's `resolveWebSocketUpgrade` seam - the route's `upgrade()` guard and Origin check included - and
5
+ * wires an accepted socket with the runtime's own `attach`, so a dev socket behaves as a served one.
6
+ */
7
+ import type { WebSocketUpgradeOutcome } from "@nifrajs/core"
8
+
9
+ type Upgrade = Extract<WebSocketUpgradeOutcome, { readonly kind: "upgrade" }>
10
+
11
+ /** A nifra app's handshake seam; a plain `{ fetch }` handler has none and takes no upgrades. */
12
+ export interface UpgradableApp {
13
+ resolveWebSocketUpgrade?(
14
+ request: Request,
15
+ ): WebSocketUpgradeOutcome | Promise<WebSocketUpgradeOutcome>
16
+ }
17
+
18
+ export function isWebSocketUpgrade(request: Request): boolean {
19
+ return request.headers.get("upgrade")?.toLowerCase() === "websocket"
20
+ }
21
+
22
+ type Listener = (event: never) => void
23
+
24
+ /** A standard-socket view `attach` wires its listeners to; the dev server feeds it events. */
25
+ function socketView(native: {
26
+ send(data: string | ArrayBufferView | ArrayBuffer): unknown
27
+ close(code?: number, reason?: string): void
28
+ readonly readyState: number
29
+ }) {
30
+ const listeners = new Map<string, Listener[]>()
31
+ return {
32
+ view: {
33
+ send: (data: string | ArrayBufferView | ArrayBuffer): void => {
34
+ native.send(data)
35
+ },
36
+ close: (code?: number, reason?: string) => native.close(code, reason),
37
+ get readyState() {
38
+ return native.readyState
39
+ },
40
+ addEventListener(type: string, listener: Listener): void {
41
+ listeners.set(type, [...(listeners.get(type) ?? []), listener])
42
+ },
43
+ },
44
+ emit(type: string, event: unknown): void {
45
+ for (const listener of listeners.get(type) ?? []) Reflect.apply(listener, undefined, [event])
46
+ },
47
+ }
48
+ }
49
+
50
+ function attachView(view: ReturnType<typeof socketView>["view"], outcome: Upgrade): void {
51
+ outcome.attach(view, outcome.handler, outcome.data, {
52
+ openNow: true,
53
+ pubsub: outcome.pubsub,
54
+ maxPayloadBytes: outcome.maxPayloadBytes,
55
+ })
56
+ }
57
+
58
+ // --- Bun ----------------------------------------------------------------------------------------
59
+
60
+ /** What a dev socket carries from its handshake to `open`, and the view its events go to after. */
61
+ export interface BunDevSocketData {
62
+ readonly outcome: Upgrade
63
+ emit?: (type: string, event: unknown) => void
64
+ }
65
+
66
+ /** The part of Bun's `ServerWebSocket` used here. */
67
+ interface BunDevSocket {
68
+ send(data: string | ArrayBufferView | ArrayBuffer): unknown
69
+ close(code?: number, reason?: string): void
70
+ readonly readyState: number
71
+ readonly data: BunDevSocketData
72
+ }
73
+
74
+ export interface BunUpgradeServer {
75
+ upgrade(request: Request, options: { data: BunDevSocketData }): boolean
76
+ }
77
+
78
+ /** `Bun.serve`'s `websocket` option for a dev server whose upgrades go through {@link upgradeOnBun}. */
79
+ export const bunDevWebSocket = {
80
+ open(socket: BunDevSocket): void {
81
+ const { view, emit } = socketView(socket)
82
+ socket.data.emit = emit
83
+ attachView(view, socket.data.outcome)
84
+ },
85
+ message(socket: BunDevSocket, message: string | Uint8Array): void {
86
+ socket.data.emit?.("message", { data: message })
87
+ },
88
+ close(socket: BunDevSocket, code: number, reason: string): void {
89
+ socket.data.emit?.("close", { code, reason })
90
+ },
91
+ }
92
+
93
+ /**
94
+ * Run the handshake through the app. `undefined` when the socket was upgraded; a `Response` for a
95
+ * refusal; `"pass"` when no WebSocket route takes the path, for the dev server to answer as HTTP.
96
+ */
97
+ export async function upgradeOnBun(
98
+ app: UpgradableApp,
99
+ request: Request,
100
+ server: BunUpgradeServer,
101
+ ): Promise<Response | "pass" | undefined> {
102
+ if (app.resolveWebSocketUpgrade === undefined) return "pass"
103
+ const outcome = await app.resolveWebSocketUpgrade(request)
104
+ if (outcome.kind === "pass") return "pass"
105
+ if (outcome.kind === "reject") return outcome.response
106
+ return server.upgrade(request, { data: { outcome } })
107
+ ? undefined
108
+ : new Response("upgrade_required", { status: 426 })
109
+ }
110
+
111
+ // --- Node (the Vite dev server) -------------------------------------------------------------------
112
+
113
+ /** The part of a Node socket an upgrade writes to. */
114
+ export interface UpgradeSocket {
115
+ write(chunk: string): unknown
116
+ destroy(): void
117
+ on(event: "error", listener: () => void): unknown
118
+ }
119
+
120
+ /** The part of the `ws` package's `WebSocketServer` used here (no `@types/ws` dependency). */
121
+ export interface WsServer {
122
+ handleUpgrade(
123
+ request: unknown,
124
+ socket: UpgradeSocket,
125
+ head: unknown,
126
+ done: (ws: WsSocket) => void,
127
+ ): void
128
+ }
129
+
130
+ /** The part of a `ws` socket used here. Its own events, not its `addEventListener`: Bun's `ws`
131
+ * shim hands text frames to that as bytes. The shim also gives a close reason as a string where
132
+ * `ws` gives bytes. */
133
+ interface WsSocket {
134
+ send(data: string | ArrayBufferView | ArrayBuffer): void
135
+ close(code?: number, reason?: string): void
136
+ readonly readyState: number
137
+ on(event: "message", listener: (data: string | Uint8Array, isBinary: boolean) => void): void
138
+ on(event: "close", listener: (code: number, reason: string | Uint8Array) => void): void
139
+ on(event: "error", listener: (error: Error) => void): void
140
+ }
141
+
142
+ const TEXT = new TextDecoder()
143
+ const textOf = (data: string | Uint8Array): string =>
144
+ typeof data === "string" ? data : TEXT.decode(data)
145
+
146
+ /** A non-literal specifier: `ws` is an optional peer, typed structurally above. */
147
+ const WS_MODULE = "ws"
148
+
149
+ /** A noServer `ws` server from `specifier`; undefined when it cannot be loaded or has none. */
150
+ export async function wsServerFrom(specifier: string): Promise<WsServer | undefined> {
151
+ try {
152
+ const mod: {
153
+ WebSocketServer?: new (options: { noServer: true }) => WsServer
154
+ default?: { WebSocketServer?: new (options: { noServer: true }) => WsServer }
155
+ } = await import(specifier)
156
+ const Server = mod.WebSocketServer ?? mod.default?.WebSocketServer
157
+ return Server === undefined ? undefined : new Server({ noServer: true })
158
+ } catch {
159
+ return undefined
160
+ }
161
+ }
162
+
163
+ let wsServer: Promise<WsServer | undefined> | undefined
164
+
165
+ /** One server for every dev upgrade. */
166
+ const loadWsServer = (): Promise<WsServer | undefined> => {
167
+ wsServer ??= wsServerFrom(WS_MODULE)
168
+ return wsServer
169
+ }
170
+
171
+ /**
172
+ * Answer a Node `upgrade` event through the app. Resolves `false` when no WebSocket route takes the
173
+ * path, leaving the socket to the other listeners (Vite's HMR among them).
174
+ */
175
+ export async function upgradeOnNode(
176
+ app: UpgradableApp,
177
+ request: Request,
178
+ nodeRequest: unknown,
179
+ socket: UpgradeSocket,
180
+ head: unknown,
181
+ wsServerOf: () => Promise<WsServer | undefined> = loadWsServer,
182
+ ): Promise<boolean> {
183
+ if (app.resolveWebSocketUpgrade === undefined) return false
184
+ // A peer can reset the socket while the guard is awaited; with no listener that error ends the process.
185
+ socket.on("error", () => {})
186
+ let outcome: WebSocketUpgradeOutcome
187
+ try {
188
+ outcome = await app.resolveWebSocketUpgrade(request)
189
+ } catch {
190
+ await writeRefusal(socket, new Response("internal_error", { status: 500 }))
191
+ return true
192
+ }
193
+ if (outcome.kind === "pass") return false
194
+ if (outcome.kind === "reject") {
195
+ await writeRefusal(socket, outcome.response)
196
+ return true
197
+ }
198
+ const wss = await wsServerOf()
199
+ if (wss === undefined) {
200
+ await writeRefusal(
201
+ socket,
202
+ new Response("[nifra] install `ws` to serve app.ws() routes under the Vite dev server\n", {
203
+ status: 501,
204
+ }),
205
+ )
206
+ return true
207
+ }
208
+ const upgrade = outcome
209
+ wss.handleUpgrade(nodeRequest, socket, head, (ws) => {
210
+ const { view, emit } = socketView(ws)
211
+ ws.on("message", (data, isBinary) => emit("message", { data: isBinary ? data : textOf(data) }))
212
+ // Bun's `node:http` stops listening when a socket's close listener throws.
213
+ ws.on("close", (code, reason) => emit("close", { code, reason: textOf(reason) }))
214
+ ws.on("error", (error) => emit("error", error))
215
+ attachView(view, upgrade)
216
+ })
217
+ return true
218
+ }
219
+
220
+ /** A refusal written to the raw socket, then closed. */
221
+ async function writeRefusal(socket: UpgradeSocket, response: Response): Promise<void> {
222
+ const body = await response.text()
223
+ let head = `HTTP/1.1 ${response.status} ${response.statusText || "Refused"}\r\n`
224
+ response.headers.forEach((value, key) => {
225
+ if (key !== "content-length" && key !== "connection") head += `${key}: ${value}\r\n`
226
+ })
227
+ head += `content-length: ${new TextEncoder().encode(body).byteLength}\r\nconnection: close\r\n\r\n`
228
+ socket.write(head + body)
229
+ socket.destroy()
230
+ }