@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
@@ -1,5 +1,6 @@
1
1
  import type { CapabilityDefinition } from "@nifrajs/core/capabilities"
2
- import { isResponseResult, isStatusSignal, STATUS_SIGNAL } from "../internal/render-document.ts"
2
+ import { isResponseResult } from "../internal/render-document.ts"
3
+ import { isStatusSignal, STATUS_SIGNAL } from "../internal/status-page.ts"
3
4
  import {
4
5
  authorizeServerComponent,
5
6
  isServerComponent,
@@ -20,9 +21,11 @@ import {
20
21
  import { BoundedComponentTasks, componentAbort } from "./scheduler.ts"
21
22
  import { componentValue } from "./schema.ts"
22
23
  import type {
24
+ ComponentHoles,
23
25
  ComponentInstance,
24
26
  ComponentOwner,
25
27
  ComponentRecord,
28
+ ComponentRender,
26
29
  ServerComponent,
27
30
  ServerComponentContext,
28
31
  ServerComponentHandle,
@@ -42,6 +45,11 @@ export interface ServerComponentsOptions {
42
45
  readonly build: string
43
46
  readonly adapter: ServerComponentRenderAdapter
44
47
  readonly owners: Readonly<Record<string, ComponentOwner>>
48
+ /**
49
+ * Experimental: route id to the server components its partially prerendered holes may show, each
50
+ * through `serverHole`. A route is an owner or hosts holes, never both.
51
+ */
52
+ readonly holes?: Readonly<Record<string, ComponentHoles>>
45
53
  readonly capabilities?: readonly CapabilityDefinition[]
46
54
  readonly assets?: readonly string[]
47
55
  }
@@ -103,20 +111,19 @@ export function createServerComponents(options: ServerComponentsOptions): Server
103
111
  )
104
112
  if (capabilities.size !== (options.capabilities?.length ?? 0))
105
113
  throw new ServerComponentError("component_contract")
106
- for (const [routeId, owner] of Object.entries(options.owners)) {
107
- if (
108
- typeof owner.instances !== "function" ||
109
- (owner.scope !== "anonymous" && typeof owner.scope !== "function")
110
- )
111
- throw new ServerComponentError("component_contract")
114
+ /** A route's component allowlist, by reference, each within the capabilities the route grants. */
115
+ const allowlist = (
116
+ list: readonly ServerComponentHandle[],
117
+ granted: readonly string[] = [],
118
+ ): ReadonlyMap<string, ServerComponentHandle> => {
112
119
  const components = new Map<string, ServerComponentHandle>()
113
- const allowed = new Set(owner.capabilities ?? [])
120
+ const allowed = new Set(granted)
114
121
  for (const capability of allowed) {
115
122
  const declared = capabilities.get(capability)
116
123
  if (declared === undefined || (declared.zone === "domain" && declared.access !== "read"))
117
124
  throw new ServerComponentError("component_contract")
118
125
  }
119
- for (const component of owner.components) {
126
+ for (const component of list) {
120
127
  if (
121
128
  !isServerComponent(component) ||
122
129
  component.reference === undefined ||
@@ -128,11 +135,106 @@ export function createServerComponents(options: ServerComponentsOptions): Server
128
135
  if (serverComponentCapabilities(component).some((capability) => !allowed.has(capability)))
129
136
  throw new ServerComponentError("component_contract")
130
137
  }
131
- owners.set(routeId, { owner, components })
138
+ return components
139
+ }
140
+ for (const [routeId, owner] of Object.entries(options.owners)) {
141
+ if (
142
+ typeof owner.instances !== "function" ||
143
+ (owner.scope !== "anonymous" && typeof owner.scope !== "function")
144
+ )
145
+ throw new ServerComponentError("component_contract")
146
+ owners.set(routeId, { owner, components: allowlist(owner.components, owner.capabilities) })
147
+ }
148
+ const holes = new Map<
149
+ string,
150
+ { capabilities: readonly string[]; components: ReadonlyMap<string, ServerComponentHandle> }
151
+ >()
152
+ for (const [routeId, entry] of Object.entries(options.holes ?? {})) {
153
+ // An owner route renders whole, so its holes never run.
154
+ if (owners.has(routeId) || !Array.isArray(entry.components))
155
+ throw new ServerComponentError("component_contract")
156
+ holes.set(routeId, {
157
+ capabilities: entry.capabilities ?? [],
158
+ components: allowlist(entry.components, entry.capabilities),
159
+ })
160
+ }
161
+ /** A rendered instance as a record: its range HTML checked, its payload and island props public. */
162
+ const recordOf = (
163
+ instance: {
164
+ readonly id: string
165
+ readonly reference: string
166
+ readonly key: ComponentRecord["key"]
167
+ readonly parent?: string | undefined
168
+ readonly input: ComponentRecord["input"]
169
+ },
170
+ rendered: ComponentRender,
171
+ ): ComponentRecord => {
172
+ const record: ComponentRecord = {
173
+ instance: instance.id,
174
+ reference: instance.reference,
175
+ key: instance.key,
176
+ parent: instance.parent ?? null,
177
+ input: instance.input,
178
+ html: componentRangeHTML(rendered.html, assets),
179
+ }
180
+ if (rendered.payload !== undefined)
181
+ Object.assign(record, {
182
+ payload: componentValue(rendered.payload, COMPONENT_LIMITS.outputBytes),
183
+ })
184
+ if (rendered.islands !== undefined)
185
+ Object.assign(record, {
186
+ islands: rendered.islands.map((island) => ({
187
+ ...island,
188
+ props: componentValue(island.props),
189
+ })),
190
+ })
191
+ return record
132
192
  }
133
193
  return {
134
194
  owns: (routeId) => owners.has(routeId),
135
- capabilities: (routeId) => owners.get(routeId)?.owner.capabilities ?? [],
195
+ capabilities: (routeId) =>
196
+ owners.get(routeId)?.owner.capabilities ?? holes.get(routeId)?.capabilities ?? [],
197
+ hosts: (routeId) => holes.has(routeId),
198
+ async hole(routeId, component, key, input, context) {
199
+ const reference = isServerComponent(component) ? component.reference : undefined
200
+ const handle =
201
+ reference === undefined ? undefined : holes.get(routeId)?.components.get(reference)
202
+ if (
203
+ reference === undefined ||
204
+ handle === undefined ||
205
+ handle !== component ||
206
+ !componentKey(key)
207
+ )
208
+ throw new ServerComponentError("component_contract")
209
+ const signal = AbortSignal.any([
210
+ context.signal,
211
+ AbortSignal.timeout(COMPONENT_LIMITS.executionMs),
212
+ ])
213
+ const ctx: ServerComponentContext = Object.create(context, {
214
+ signal: { value: signal, enumerable: true },
215
+ })
216
+ const value = componentValue(input, COMPONENT_LIMITS.inputBytes)
217
+ const id = await serverComponentIdentity(routeId, handle, key, null)
218
+ return admission.run(signal, async () => {
219
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The route's hole allowlist identifies this handle; authorization validates the input before any callback runs.
220
+ const authorized = await authorizeServerComponent(handle as ServerComponent, value, ctx)
221
+ const prepared = await prepareAuthorizedServerComponent(authorized)
222
+ const rendered = await options.adapter.render(prepared.view, prepared.props, {
223
+ reference,
224
+ instance: id,
225
+ signal,
226
+ children: [],
227
+ })
228
+ if (signal.aborted) throw componentAbort(signal)
229
+ if (typeof rendered.html !== "string") throw new ServerComponentError("component_contract")
230
+ if ((rendered.islands?.length ?? 0) > COMPONENT_LIMITS.instances)
231
+ throw new ServerComponentError("component_limit", 413)
232
+ const record = recordOf({ id, reference, key, input: value }, rendered)
233
+ if (encoder.encode(JSON.stringify(record)).length > COMPONENT_LIMITS.outputBytes)
234
+ throw new ServerComponentError("component_limit", 413)
235
+ return record
236
+ })
237
+ },
136
238
  classify(request, routeId) {
137
239
  const selection = readComponentRequest(request)
138
240
  if (selection === undefined) return undefined
@@ -275,29 +377,10 @@ export function createServerComponents(options: ServerComponentsOptions): Server
275
377
  if (signal.aborted) throw componentAbort(signal)
276
378
  if (typeof rendered.html !== "string")
277
379
  throw new ServerComponentError("component_contract")
278
- const html = componentRangeHTML(rendered.html, assets)
380
+ const record = recordOf({ ...instance, id }, rendered)
279
381
  count += rendered.islands?.length ?? 0
280
382
  if (count > COMPONENT_LIMITS.instances)
281
383
  throw new ServerComponentError("component_limit", 413)
282
- const record: ComponentRecord = {
283
- instance: id,
284
- reference: instance.reference,
285
- key: instance.key,
286
- parent: instance.parent ?? null,
287
- input: instance.input,
288
- html,
289
- }
290
- if (rendered.payload !== undefined)
291
- Object.assign(record, {
292
- payload: componentValue(rendered.payload, COMPONENT_LIMITS.outputBytes),
293
- })
294
- if (rendered.islands !== undefined)
295
- Object.assign(record, {
296
- islands: rendered.islands.map((island) => ({
297
- ...island,
298
- props: componentValue(island.props),
299
- })),
300
- })
301
384
  const size = encoder.encode(JSON.stringify(record)).length
302
385
  bytes += size
303
386
  if (
@@ -50,6 +50,69 @@ function island(value: unknown): ComponentIsland {
50
50
  if (item.slots !== undefined) Object.assign(result, { slots: item.slots })
51
51
  return result
52
52
  }
53
+ /** One record, checked as each of a state's is; `ids` and `islandIds` collect what must be unique. */
54
+ function componentRecord(
55
+ value: unknown,
56
+ ids: Set<string>,
57
+ islandIds: Set<string>,
58
+ ): ComponentRecord {
59
+ const record = object(
60
+ value,
61
+ ["instance", "reference", "key", "parent", "input", "html"],
62
+ ["payload", "islands"],
63
+ )
64
+ if (
65
+ !componentToken(record.instance) ||
66
+ ids.has(record.instance) ||
67
+ !componentToken(record.reference) ||
68
+ !componentKey(record.key) ||
69
+ (record.parent !== null && !componentToken(record.parent)) ||
70
+ typeof record.html !== "string"
71
+ )
72
+ throw new ServerComponentError("component_protocol", 400)
73
+ ids.add(record.instance)
74
+ validateComponentHTML(record.html)
75
+ if (
76
+ record.islands !== undefined &&
77
+ (!Array.isArray(record.islands) || record.islands.length > COMPONENT_LIMITS.instances)
78
+ )
79
+ throw new ServerComponentError("component_protocol", 400)
80
+ const islands =
81
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Object shape is checked before field inspection; each typed field follows its closed vocabulary check.
82
+ record.islands === undefined ? undefined : (record.islands as unknown[]).map(island)
83
+ for (const island of islands ?? []) {
84
+ if (islandIds.has(island.renderId)) throw new ServerComponentError("component_protocol", 400)
85
+ islandIds.add(island.renderId)
86
+ }
87
+ const result: ComponentRecord = {
88
+ instance: record.instance,
89
+ reference: record.reference,
90
+ key: record.key,
91
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Object shape is checked before field inspection; each typed field follows its closed vocabulary check.
92
+ parent: record.parent as string | null,
93
+ html: record.html,
94
+ input: componentValue(record.input, COMPONENT_LIMITS.inputBytes),
95
+ }
96
+ if (record.payload !== undefined)
97
+ Object.assign(result, {
98
+ payload: componentValue(record.payload, COMPONENT_LIMITS.outputBytes),
99
+ })
100
+ if (islands !== undefined) Object.assign(result, { islands })
101
+ return result
102
+ }
103
+
104
+ /** A record that arrives alone, as a partially prerendered hole's does, checked as a state's are. */
105
+ export function parseComponentRecord(value: unknown): ComponentRecord {
106
+ const record = componentRecord(
107
+ componentValue(value, COMPONENT_LIMITS.documentBytes),
108
+ new Set(),
109
+ new Set(),
110
+ )
111
+ if ((record.islands?.length ?? 0) >= COMPONENT_LIMITS.instances)
112
+ throw new ServerComponentError("component_limit", 413)
113
+ return record
114
+ }
115
+
53
116
  export function parseComponentState(
54
117
  value: unknown,
55
118
  maxBytes: number = COMPONENT_LIMITS.documentBytes,
@@ -72,52 +135,11 @@ export function parseComponentState(
72
135
  const islandIds = new Set<string>()
73
136
  let instances = state.records.length
74
137
  const records: ComponentRecord[] = state.records.map((value: unknown) => {
75
- const record = object(
76
- value,
77
- ["instance", "reference", "key", "parent", "input", "html"],
78
- ["payload", "islands"],
79
- )
80
- if (
81
- !componentToken(record.instance) ||
82
- ids.has(record.instance) ||
83
- !componentToken(record.reference) ||
84
- !componentKey(record.key) ||
85
- (record.parent !== null && !componentToken(record.parent)) ||
86
- typeof record.html !== "string"
87
- )
88
- throw new ServerComponentError("component_protocol", 400)
89
- ids.add(record.instance)
90
- validateComponentHTML(record.html)
91
- if (
92
- record.islands !== undefined &&
93
- (!Array.isArray(record.islands) || record.islands.length > COMPONENT_LIMITS.instances)
94
- )
95
- throw new ServerComponentError("component_protocol", 400)
96
- const islands =
97
- // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Object shape is checked before field inspection; each typed field follows its closed vocabulary check.
98
- record.islands === undefined ? undefined : (record.islands as unknown[]).map(island)
99
- for (const island of islands ?? []) {
100
- if (islandIds.has(island.renderId)) throw new ServerComponentError("component_protocol", 400)
101
- islandIds.add(island.renderId)
102
- }
103
- instances += islands?.length ?? 0
138
+ const record = componentRecord(value, ids, islandIds)
139
+ instances += record.islands?.length ?? 0
104
140
  if (instances > COMPONENT_LIMITS.instances)
105
141
  throw new ServerComponentError("component_limit", 413)
106
- const result: ComponentRecord = {
107
- instance: record.instance,
108
- reference: record.reference,
109
- key: record.key,
110
- // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Object shape is checked before field inspection; each typed field follows its closed vocabulary check.
111
- parent: record.parent as string | null,
112
- html: record.html,
113
- input: componentValue(record.input, COMPONENT_LIMITS.inputBytes),
114
- }
115
- if (record.payload !== undefined)
116
- Object.assign(result, {
117
- payload: componentValue(record.payload, COMPONENT_LIMITS.outputBytes),
118
- })
119
- if (islands !== undefined) Object.assign(result, { islands })
120
- return result
142
+ return record
121
143
  })
122
144
  // Refreshes contain a single child and may name its already mounted parent. Documents carry the full forest.
123
145
  for (const record of records) {
@@ -98,6 +98,12 @@ export interface ComponentOwner {
98
98
  readonly scope: "anonymous" | ((ctx: ServerComponentContext) => string | Promise<string>)
99
99
  }
100
100
 
101
+ /** Experimental: the server components a partially prerendered route's holes may show. */
102
+ export interface ComponentHoles {
103
+ readonly components: readonly ServerComponentHandle[]
104
+ readonly capabilities?: readonly string[]
105
+ }
106
+
101
107
  export interface ServerComponentHandle {
102
108
  readonly reference?: string
103
109
  readonly input: StandardSchemaV1
@@ -135,4 +141,14 @@ export interface ServerComponentRuntime {
135
141
  ): Promise<ComponentState>
136
142
  response(state: ComponentState, selection: import("./protocol.ts").ComponentRequest): Response
137
143
  failure(error: unknown): Response
144
+ /** Whether the route's partially prerendered holes may show server components. */
145
+ hosts?(routeId: string): boolean
146
+ /** Render `component` as the route's hole `key`, with the hole loader's output as its input. */
147
+ hole?(
148
+ routeId: string,
149
+ component: object,
150
+ key: string,
151
+ input: unknown,
152
+ context: ServerComponentContext,
153
+ ): Promise<ComponentRecord>
138
154
  }
@@ -25,6 +25,17 @@ const reactRuntime = "\0nifra-component-react-runtime"
25
25
  // Vite uses forward slashes for module IDs even when the filesystem uses backslashes.
26
26
  const bare = (id: string): string => (id.split("?")[0] ?? id).replaceAll("\\", "/")
27
27
 
28
+ // Blanked with spaces rather than cut, so later offsets still point at the source.
29
+ function withoutUseClient(source: string, path: string): string {
30
+ for (const statement of sourceFile(source, path).statements) {
31
+ if (!ts.isExpressionStatement(statement) || !ts.isStringLiteral(statement.expression)) break
32
+ if (statement.expression.text !== "use client") continue
33
+ const start = statement.getStart()
34
+ return `${source.slice(0, start)}${" ".repeat(statement.end - start)}${source.slice(statement.end)}`
35
+ }
36
+ return source
37
+ }
38
+
28
39
  export interface ServerComponentsPluginOptions {
29
40
  readonly root: string
30
41
  readonly adapter: "react" | "vue" | "svelte" | "solid" | "preact"
@@ -224,23 +235,31 @@ export function serverComponentsPlugin(
224
235
  return null
225
236
  },
226
237
  transform(source, id) {
227
- if (id.includes(nativeQuery) || internal.has(bare(id))) return null
228
238
  const path = bare(id)
229
239
  let code = source
230
- if (componentFile.test(path)) {
231
- code = compileComponentModule(source, path, options.root, this.environment.name).code
232
- componentModules.add(path)
240
+ if (!id.includes(nativeQuery) && !internal.has(path)) {
241
+ if (componentFile.test(path)) {
242
+ code = compileComponentModule(source, path, options.root, this.environment.name).code
243
+ componentModules.add(path)
244
+ }
245
+ if (islandFile.test(path))
246
+ code = compileComponentModule(
247
+ source,
248
+ path,
249
+ options.root,
250
+ this.environment.name,
251
+ "island",
252
+ ).code
253
+ if (this.environment.name === "rsc" && options.adapter === "react")
254
+ code = guardCreateElementCalls(code, path)
233
255
  }
234
- if (islandFile.test(path))
235
- code = compileComponentModule(
236
- source,
237
- path,
238
- options.root,
239
- this.environment.name,
240
- "island",
241
- ).code
242
- if (this.environment.name === "rsc" && options.adapter === "react")
243
- code = guardCreateElementCalls(code, path)
256
+ // Only the rsc build acts on "use client"; the client and ssr bundles would warn about it.
257
+ if (
258
+ this.environment.name !== "rsc" &&
259
+ options.adapter === "react" &&
260
+ code.includes("use client")
261
+ )
262
+ code = withoutUseClient(code, path)
244
263
  return code === source ? null : { code, map: null }
245
264
  },
246
265
  }
package/src/vite.ts CHANGED
@@ -23,10 +23,21 @@ import { isHiddenPath } from "@nifrajs/core/static-file"
23
23
  import { CLIENT_BATCH_MAX_BYTES } from "./dev-client.ts"
24
24
  import { DEV_REQUEST_ID_HEADER, DEV_SERVER_FILES } from "./dev-feed.ts"
25
25
  import { listenOrExplain } from "./dev-port.ts"
26
- import { createDevSession, type DevAppHooks, isDevAgentPath } from "./dev-session.ts"
26
+ import {
27
+ createDevSession,
28
+ type DevAppHooks,
29
+ isDevAgentPath,
30
+ isLoopbackHost,
31
+ } from "./dev-session.ts"
27
32
  import { discoverRoutes } from "./fs.ts"
28
- import { DEFAULT_DEV_PORT, generateClientEntry, setSsrModuleLoader } from "./index.ts"
33
+ import {
34
+ DEFAULT_DEV_PORT,
35
+ generateClientEntry,
36
+ type LiveClientEntry,
37
+ setSsrModuleLoader,
38
+ } from "./index.ts"
29
39
  import { admitViteTags } from "./internal/dev-csp.ts"
40
+ import { type UpgradableApp, upgradeOnNode } from "./internal/dev-websocket.ts"
30
41
  import { viteDedupePackages } from "./internal/identity-policy.ts"
31
42
  import {
32
43
  assertIdentityParity,
@@ -49,7 +60,7 @@ import { viteServerFnStub } from "./plugins/vite-server-fn.ts"
49
60
  import { viteZoneGuard } from "./plugins/vite-zone-guard.ts"
50
61
 
51
62
  /** Minimal app surface - `createWebApp(...)` satisfies it. */
52
- interface FetchApp {
63
+ interface FetchApp extends UpgradableApp {
53
64
  fetch(request: Request): Response | Promise<Response>
54
65
  }
55
66
 
@@ -79,7 +90,10 @@ interface ViteLike {
79
90
  /** The module graph, used to invalidate a changed file AND its transitive importers before reload. */
80
91
  readonly moduleGraph?: ViteModuleGraph
81
92
  /** The resolved config: `publicDir` is absolute, or `""` when the public directory is off. */
82
- readonly config?: { readonly publicDir?: string }
93
+ readonly config?: {
94
+ readonly publicDir?: string
95
+ readonly server?: { readonly allowedHosts?: true | readonly string[] }
96
+ }
83
97
  readonly watcher: {
84
98
  on(event: "change" | "add" | "unlink", cb: (path: string) => void): void
85
99
  on(event: "ready", cb: () => void): void
@@ -163,6 +177,8 @@ export interface ViteDevServerOptions {
163
177
  * fix (default `true`). Off, the errors still reach the feed.
164
178
  */
165
179
  readonly indicator?: boolean
180
+ /** The app runs `createWebApp({ live })`: the same option as the build's (`BuildClientOptions.live`). */
181
+ readonly live?: LiveClientEntry | undefined
166
182
  }
167
183
 
168
184
  export interface ViteDevServer {
@@ -236,7 +252,7 @@ const readAgentBody = async (req: IncomingMessage, max: number): Promise<Buffer
236
252
  function toWebRequest(req: IncomingMessage, body: Buffer | undefined): Request {
237
253
  const headers = new Headers()
238
254
  for (const [key, value] of Object.entries(req.headers)) {
239
- if (typeof value === "string") headers.set(key, value)
255
+ if (typeof value === "string") appendJoined(headers, key, value)
240
256
  else if (Array.isArray(value)) for (const v of value) headers.append(key, v)
241
257
  }
242
258
  const url = `http://${req.headers.host ?? "localhost"}${req.url ?? "/"}`
@@ -252,6 +268,23 @@ function toWebRequest(req: IncomingMessage, body: Buffer | undefined): Request {
252
268
  return new Request(url, init as unknown as ConstructorParameters<typeof Request>[1])
253
269
  }
254
270
 
271
+ /**
272
+ * Node joins a repeated field into one string, and a trailing space there means the last member was
273
+ * empty (each field line is trimmed). `Headers` strips the whitespace around each value it is given,
274
+ * so the empty members are appended one by one to keep the "a, " production shows.
275
+ */
276
+ function appendJoined(headers: Headers, name: string, joined: string): void {
277
+ const delimiter = name === "cookie" ? "; " : ", "
278
+ let base = joined
279
+ let empty = 0
280
+ while (base.endsWith(delimiter)) {
281
+ base = base.slice(0, -delimiter.length)
282
+ empty++
283
+ }
284
+ headers.append(name, base)
285
+ for (; empty > 0; empty--) headers.append(name, "")
286
+ }
287
+
255
288
  /** The bits of a Node ServerResponse `pipeWebBodyToNode` touches - structural, to avoid a node:http dep here. */
256
289
  interface NodeResLike {
257
290
  /** Set once the socket is gone: `close` has fired, and a write returns `false` for good. */
@@ -395,7 +428,11 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
395
428
  const manifest = discoverRoutes(routesDir)
396
429
  writeFileSync(
397
430
  resolvePath(root, DEV_ENTRY),
398
- generateClientEntry(manifest, { clientModule: options.clientModule, resolve: toUrl }),
431
+ generateClientEntry(manifest, {
432
+ clientModule: options.clientModule,
433
+ resolve: toUrl,
434
+ live: options.live,
435
+ }),
399
436
  )
400
437
  }
401
438
  const usePolling = options.poll ?? process.env.CHOKIDAR_USEPOLLING === "1"
@@ -457,6 +494,38 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
457
494
  handleWithVite(req, res)
458
495
  })
459
496
 
497
+ // The app's own WebSocket routes. Vite's HMR socket rides this server with a listener of its own,
498
+ // so its handshakes, and any no app route takes, are left to the other listeners.
499
+ server.on("upgrade", (req, socket, head) => {
500
+ const protocol = req.headers["sec-websocket-protocol"] ?? ""
501
+ if (protocol.includes("vite-hmr") || protocol.includes("vite-ping")) return
502
+ if (!allowsHost(req.headers.host)) {
503
+ socket.on("error", () => {})
504
+ socket.end("HTTP/1.1 403 Forbidden\r\ncontent-length: 0\r\nconnection: close\r\n\r\n")
505
+ return
506
+ }
507
+ void upgradeOnNode(app, toWebRequest(req, undefined), req, socket, head)
508
+ })
509
+
510
+ /** Vite's own rule for a handshake's Host: a loopback name, or one `server.allowedHosts` lists. */
511
+ function allowsHost(host: string | undefined): boolean {
512
+ if (isLoopbackHost(host)) return true
513
+ const allowed = vite.config?.server?.allowedHosts
514
+ if (allowed === true) return true
515
+ if (allowed === undefined || host === undefined) return false
516
+ let name: string
517
+ try {
518
+ name = new URL(`http://${host}`).hostname
519
+ } catch {
520
+ return false
521
+ }
522
+ return allowed.some(
523
+ (entry) =>
524
+ entry === name ||
525
+ (entry.startsWith(".") && (name === entry.slice(1) || name.endsWith(entry))),
526
+ )
527
+ }
528
+
460
529
  // Vite's public middleware serves every file under the public directory, dotfiles included. A
461
530
  // build ships no hidden file and the Bun pipeline's `servePublicDir` refuses one, so a request for
462
531
  // `/.env` or `/.git/config` goes to the app here too, as it does in production.