@nifrajs/web 3.5.0 → 4.0.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 (390) hide show
  1. package/dist/backend-only.d.ts +15 -0
  2. package/dist/backend-only.d.ts.map +1 -0
  3. package/dist/backend-only.js +16 -0
  4. package/dist/backend-only.js.map +1 -0
  5. package/dist/boundary.d.ts +5 -0
  6. package/dist/boundary.d.ts.map +1 -1
  7. package/dist/boundary.js +11 -2
  8. package/dist/boundary.js.map +1 -1
  9. package/dist/build-plan.d.ts +18 -6
  10. package/dist/build-plan.d.ts.map +1 -1
  11. package/dist/build-plan.js +82 -34
  12. package/dist/build-plan.js.map +1 -1
  13. package/dist/build-vite.d.ts +4 -0
  14. package/dist/build-vite.d.ts.map +1 -1
  15. package/dist/build-vite.js +68 -13
  16. package/dist/build-vite.js.map +1 -1
  17. package/dist/build.d.ts +57 -23
  18. package/dist/build.d.ts.map +1 -1
  19. package/dist/build.js +356 -85
  20. package/dist/build.js.map +1 -1
  21. package/dist/bun-dev-entry.d.ts +1 -1
  22. package/dist/bun-dev-entry.js +1 -1
  23. package/dist/cdn.d.ts +74 -0
  24. package/dist/cdn.d.ts.map +1 -0
  25. package/dist/cdn.js +132 -0
  26. package/dist/cdn.js.map +1 -0
  27. package/dist/client.d.ts +3 -1
  28. package/dist/client.d.ts.map +1 -1
  29. package/dist/client.js +111 -15
  30. package/dist/client.js.map +1 -1
  31. package/dist/conformance.d.ts +4 -3
  32. package/dist/conformance.d.ts.map +1 -1
  33. package/dist/conformance.js +12 -3
  34. package/dist/conformance.js.map +1 -1
  35. package/dist/csp.d.ts +93 -0
  36. package/dist/csp.d.ts.map +1 -0
  37. package/dist/csp.js +122 -0
  38. package/dist/csp.js.map +1 -0
  39. package/dist/dev-client.d.ts +76 -0
  40. package/dist/dev-client.d.ts.map +1 -0
  41. package/dist/dev-client.js +280 -0
  42. package/dist/dev-client.js.map +1 -0
  43. package/dist/dev-diagnostics.d.ts +26 -2
  44. package/dist/dev-diagnostics.d.ts.map +1 -1
  45. package/dist/dev-diagnostics.js +38 -7
  46. package/dist/dev-diagnostics.js.map +1 -1
  47. package/dist/dev-error.d.ts +4 -13
  48. package/dist/dev-error.d.ts.map +1 -1
  49. package/dist/dev-error.js +46 -4
  50. package/dist/dev-error.js.map +1 -1
  51. package/dist/dev-feed.d.ts +298 -0
  52. package/dist/dev-feed.d.ts.map +1 -0
  53. package/dist/dev-feed.js +797 -0
  54. package/dist/dev-feed.js.map +1 -0
  55. package/dist/dev-indicator.d.ts +38 -0
  56. package/dist/dev-indicator.d.ts.map +1 -0
  57. package/dist/dev-indicator.js +93 -0
  58. package/dist/dev-indicator.js.map +1 -0
  59. package/dist/dev-session.d.ts +77 -0
  60. package/dist/dev-session.d.ts.map +1 -0
  61. package/dist/dev-session.js +580 -0
  62. package/dist/dev-session.js.map +1 -0
  63. package/dist/dev-sourcemap.d.ts +27 -0
  64. package/dist/dev-sourcemap.d.ts.map +1 -0
  65. package/dist/dev-sourcemap.js +235 -0
  66. package/dist/dev-sourcemap.js.map +1 -0
  67. package/dist/dev-ssr-graph.d.ts.map +1 -1
  68. package/dist/dev-ssr-graph.js +13 -5
  69. package/dist/dev-ssr-graph.js.map +1 -1
  70. package/dist/dev.d.ts +15 -2
  71. package/dist/dev.d.ts.map +1 -1
  72. package/dist/dev.js +69 -44
  73. package/dist/dev.js.map +1 -1
  74. package/dist/diagnostic-catalog.d.ts +35 -0
  75. package/dist/diagnostic-catalog.d.ts.map +1 -0
  76. package/dist/diagnostic-catalog.js +203 -0
  77. package/dist/diagnostic-catalog.js.map +1 -0
  78. package/dist/diagnostic-prompt.d.ts +49 -0
  79. package/dist/diagnostic-prompt.d.ts.map +1 -0
  80. package/dist/diagnostic-prompt.js +303 -0
  81. package/dist/diagnostic-prompt.js.map +1 -0
  82. package/dist/diagnostic.d.ts +17 -23
  83. package/dist/diagnostic.d.ts.map +1 -1
  84. package/dist/diagnostic.js +31 -39
  85. package/dist/diagnostic.js.map +1 -1
  86. package/dist/draft.d.ts.map +1 -1
  87. package/dist/draft.js +4 -1
  88. package/dist/draft.js.map +1 -1
  89. package/dist/fn.d.ts +5 -0
  90. package/dist/fn.d.ts.map +1 -1
  91. package/dist/fn.js +13 -3
  92. package/dist/fn.js.map +1 -1
  93. package/dist/fs.d.ts.map +1 -1
  94. package/dist/fs.js +4 -2
  95. package/dist/fs.js.map +1 -1
  96. package/dist/index.d.ts +13 -15
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +11 -12
  99. package/dist/index.js.map +1 -1
  100. package/dist/internal/cdn/cloudflare.d.ts +48 -0
  101. package/dist/internal/cdn/cloudflare.d.ts.map +1 -0
  102. package/dist/internal/cdn/cloudflare.js +94 -0
  103. package/dist/internal/cdn/cloudflare.js.map +1 -0
  104. package/dist/internal/cdn/core.d.ts +91 -0
  105. package/dist/internal/cdn/core.d.ts.map +1 -0
  106. package/dist/internal/cdn/core.js +220 -0
  107. package/dist/internal/cdn/core.js.map +1 -0
  108. package/dist/internal/cdn/fastly.d.ts +17 -0
  109. package/dist/internal/cdn/fastly.d.ts.map +1 -0
  110. package/dist/internal/cdn/fastly.js +43 -0
  111. package/dist/internal/cdn/fastly.js.map +1 -0
  112. package/dist/internal/cdn/vercel.d.ts +31 -0
  113. package/dist/internal/cdn/vercel.d.ts.map +1 -0
  114. package/dist/internal/cdn/vercel.js +56 -0
  115. package/dist/internal/cdn/vercel.js.map +1 -0
  116. package/dist/internal/code-unit-order.d.ts +4 -0
  117. package/dist/internal/code-unit-order.d.ts.map +1 -0
  118. package/dist/internal/code-unit-order.js +6 -0
  119. package/dist/internal/code-unit-order.js.map +1 -0
  120. package/dist/internal/codegen.d.ts.map +1 -1
  121. package/dist/internal/codegen.js +141 -25
  122. package/dist/internal/codegen.js.map +1 -1
  123. package/dist/internal/dev-csp.d.ts +24 -0
  124. package/dist/internal/dev-csp.d.ts.map +1 -0
  125. package/dist/internal/dev-csp.js +160 -0
  126. package/dist/internal/dev-csp.js.map +1 -0
  127. package/dist/internal/dev-reserved.d.ts +25 -0
  128. package/dist/internal/dev-reserved.d.ts.map +1 -0
  129. package/dist/internal/dev-reserved.js +39 -0
  130. package/dist/internal/dev-reserved.js.map +1 -0
  131. package/dist/internal/html-spans.d.ts +10 -0
  132. package/dist/internal/html-spans.d.ts.map +1 -0
  133. package/dist/internal/html-spans.js +46 -0
  134. package/dist/internal/html-spans.js.map +1 -0
  135. package/dist/internal/loading-runtime.d.ts +44 -0
  136. package/dist/internal/loading-runtime.d.ts.map +1 -0
  137. package/dist/internal/loading-runtime.js +206 -0
  138. package/dist/internal/loading-runtime.js.map +1 -0
  139. package/dist/internal/matches-runtime.d.ts +8 -0
  140. package/dist/internal/matches-runtime.d.ts.map +1 -0
  141. package/dist/internal/matches-runtime.js +61 -0
  142. package/dist/internal/matches-runtime.js.map +1 -0
  143. package/dist/internal/mount-shadow.d.ts +36 -0
  144. package/dist/internal/mount-shadow.d.ts.map +1 -0
  145. package/dist/internal/mount-shadow.js +65 -0
  146. package/dist/internal/mount-shadow.js.map +1 -0
  147. package/dist/internal/node-builtins.d.ts +3 -0
  148. package/dist/internal/node-builtins.d.ts.map +1 -0
  149. package/dist/internal/node-builtins.js +79 -0
  150. package/dist/internal/node-builtins.js.map +1 -0
  151. package/dist/internal/output-guard.d.ts +65 -0
  152. package/dist/internal/output-guard.d.ts.map +1 -0
  153. package/dist/internal/output-guard.js +630 -0
  154. package/dist/internal/output-guard.js.map +1 -0
  155. package/dist/internal/page-execution.d.ts +20 -8
  156. package/dist/internal/page-execution.d.ts.map +1 -1
  157. package/dist/internal/page-execution.js +571 -105
  158. package/dist/internal/page-execution.js.map +1 -1
  159. package/dist/internal/parity.d.ts +21 -0
  160. package/dist/internal/parity.d.ts.map +1 -1
  161. package/dist/internal/parity.js +104 -22
  162. package/dist/internal/parity.js.map +1 -1
  163. package/dist/internal/private-env.d.ts +12 -0
  164. package/dist/internal/private-env.d.ts.map +1 -0
  165. package/dist/internal/private-env.js +335 -0
  166. package/dist/internal/private-env.js.map +1 -0
  167. package/dist/internal/public-url.d.ts +3 -0
  168. package/dist/internal/public-url.d.ts.map +1 -0
  169. package/dist/internal/public-url.js +18 -0
  170. package/dist/internal/public-url.js.map +1 -0
  171. package/dist/internal/render-document.d.ts +25 -19
  172. package/dist/internal/render-document.d.ts.map +1 -1
  173. package/dist/internal/render-document.js +77 -61
  174. package/dist/internal/render-document.js.map +1 -1
  175. package/dist/internal/response-controls.d.ts +75 -0
  176. package/dist/internal/response-controls.d.ts.map +1 -0
  177. package/dist/internal/response-controls.js +256 -0
  178. package/dist/internal/response-controls.js.map +1 -0
  179. package/dist/internal/runtime-contract.d.ts +11 -16
  180. package/dist/internal/runtime-contract.d.ts.map +1 -1
  181. package/dist/internal/runtime-contract.js.map +1 -1
  182. package/dist/internal/secret-scan.d.ts +104 -0
  183. package/dist/internal/secret-scan.d.ts.map +1 -0
  184. package/dist/internal/secret-scan.js +485 -0
  185. package/dist/internal/secret-scan.js.map +1 -0
  186. package/dist/internal/server-boundary.d.ts +6 -29
  187. package/dist/internal/server-boundary.d.ts.map +1 -1
  188. package/dist/internal/server-boundary.js +6 -66
  189. package/dist/internal/server-boundary.js.map +1 -1
  190. package/dist/internal/source-map.d.ts +47 -0
  191. package/dist/internal/source-map.d.ts.map +1 -0
  192. package/dist/internal/source-map.js +225 -0
  193. package/dist/internal/source-map.js.map +1 -0
  194. package/dist/internal/target-compat.d.ts +27 -0
  195. package/dist/internal/target-compat.d.ts.map +1 -0
  196. package/dist/internal/target-compat.js +77 -0
  197. package/dist/internal/target-compat.js.map +1 -0
  198. package/dist/internal/timing-safe-equal.d.ts +6 -0
  199. package/dist/internal/timing-safe-equal.d.ts.map +1 -1
  200. package/dist/internal/timing-safe-equal.js +10 -0
  201. package/dist/internal/timing-safe-equal.js.map +1 -1
  202. package/dist/internal/vitals-runtime.d.ts +18 -0
  203. package/dist/internal/vitals-runtime.d.ts.map +1 -0
  204. package/dist/internal/vitals-runtime.js +38 -0
  205. package/dist/internal/vitals-runtime.js.map +1 -0
  206. package/dist/internal/web-app.d.ts +38 -9
  207. package/dist/internal/web-app.d.ts.map +1 -1
  208. package/dist/internal/web-app.js +199 -59
  209. package/dist/internal/web-app.js.map +1 -1
  210. package/dist/internal/zone-graph.d.ts +89 -0
  211. package/dist/internal/zone-graph.d.ts.map +1 -0
  212. package/dist/internal/zone-graph.js +492 -0
  213. package/dist/internal/zone-graph.js.map +1 -0
  214. package/dist/isr.d.ts +83 -6
  215. package/dist/isr.d.ts.map +1 -1
  216. package/dist/isr.js +263 -36
  217. package/dist/isr.js.map +1 -1
  218. package/dist/llms-txt.d.ts.map +1 -1
  219. package/dist/llms-txt.js +31 -6
  220. package/dist/llms-txt.js.map +1 -1
  221. package/dist/manifest.d.ts +220 -13
  222. package/dist/manifest.d.ts.map +1 -1
  223. package/dist/manifest.js +386 -9
  224. package/dist/manifest.js.map +1 -1
  225. package/dist/module-graph.d.ts +26 -5
  226. package/dist/module-graph.d.ts.map +1 -1
  227. package/dist/module-graph.js +17 -7
  228. package/dist/module-graph.js.map +1 -1
  229. package/dist/navigation.d.ts +17 -3
  230. package/dist/navigation.d.ts.map +1 -1
  231. package/dist/navigation.js +11 -5
  232. package/dist/navigation.js.map +1 -1
  233. package/dist/nonce.d.ts +47 -0
  234. package/dist/nonce.d.ts.map +1 -0
  235. package/dist/nonce.js +63 -0
  236. package/dist/nonce.js.map +1 -0
  237. package/dist/plugins/css-modules.d.ts.map +1 -1
  238. package/dist/plugins/css-modules.js +4 -3
  239. package/dist/plugins/css-modules.js.map +1 -1
  240. package/dist/plugins/kit.d.ts +9 -0
  241. package/dist/plugins/kit.d.ts.map +1 -1
  242. package/dist/plugins/kit.js +18 -0
  243. package/dist/plugins/kit.js.map +1 -1
  244. package/dist/plugins/svg.d.ts +22 -3
  245. package/dist/plugins/svg.d.ts.map +1 -1
  246. package/dist/plugins/svg.js +112 -1
  247. package/dist/plugins/svg.js.map +1 -1
  248. package/dist/plugins/vite-leak-guard.d.ts +107 -3
  249. package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
  250. package/dist/plugins/vite-leak-guard.js +378 -17
  251. package/dist/plugins/vite-leak-guard.js.map +1 -1
  252. package/dist/plugins/vite-zone-guard.d.ts +61 -0
  253. package/dist/plugins/vite-zone-guard.d.ts.map +1 -0
  254. package/dist/plugins/vite-zone-guard.js +208 -0
  255. package/dist/plugins/vite-zone-guard.js.map +1 -0
  256. package/dist/plugins/zone-guard.d.ts +27 -0
  257. package/dist/plugins/zone-guard.d.ts.map +1 -0
  258. package/dist/plugins/zone-guard.js +72 -0
  259. package/dist/plugins/zone-guard.js.map +1 -0
  260. package/dist/prerender.d.ts +10 -0
  261. package/dist/prerender.d.ts.map +1 -1
  262. package/dist/prerender.js +35 -3
  263. package/dist/prerender.js.map +1 -1
  264. package/dist/public-dir.d.ts +9 -0
  265. package/dist/public-dir.d.ts.map +1 -1
  266. package/dist/public-dir.js +67 -9
  267. package/dist/public-dir.js.map +1 -1
  268. package/dist/pwa-manifest.d.ts +68 -0
  269. package/dist/pwa-manifest.d.ts.map +1 -0
  270. package/dist/pwa-manifest.js +178 -0
  271. package/dist/pwa-manifest.js.map +1 -0
  272. package/dist/query.js +4 -4
  273. package/dist/query.js.map +1 -1
  274. package/dist/render-seam.d.ts +42 -2
  275. package/dist/render-seam.d.ts.map +1 -1
  276. package/dist/render-seam.js +6 -0
  277. package/dist/render-seam.js.map +1 -1
  278. package/dist/route-manifest.d.ts +3 -0
  279. package/dist/route-manifest.d.ts.map +1 -1
  280. package/dist/route-manifest.js +26 -3
  281. package/dist/route-manifest.js.map +1 -1
  282. package/dist/route-types.d.ts +24 -0
  283. package/dist/route-types.d.ts.map +1 -0
  284. package/dist/route-types.js +195 -0
  285. package/dist/route-types.js.map +1 -0
  286. package/dist/router.d.ts +28 -6
  287. package/dist/router.d.ts.map +1 -1
  288. package/dist/router.js +129 -40
  289. package/dist/router.js.map +1 -1
  290. package/dist/vitals.d.ts +33 -0
  291. package/dist/vitals.d.ts.map +1 -0
  292. package/dist/vitals.js +16 -0
  293. package/dist/vitals.js.map +1 -0
  294. package/dist/vite.d.ts +16 -1
  295. package/dist/vite.d.ts.map +1 -1
  296. package/dist/vite.js +187 -36
  297. package/dist/vite.js.map +1 -1
  298. package/dist/zones.d.ts +70 -0
  299. package/dist/zones.d.ts.map +1 -0
  300. package/dist/zones.js +365 -0
  301. package/dist/zones.js.map +1 -0
  302. package/package.json +67 -14
  303. package/src/backend-only.ts +16 -0
  304. package/src/boundary.ts +18 -5
  305. package/src/build-plan.ts +90 -35
  306. package/src/build-vite.ts +77 -11
  307. package/src/build.ts +466 -96
  308. package/src/bun-dev-entry.ts +1 -1
  309. package/src/cdn.ts +216 -0
  310. package/src/client.ts +111 -18
  311. package/src/conformance.ts +16 -3
  312. package/src/csp.ts +194 -0
  313. package/src/dev-client.ts +349 -0
  314. package/src/dev-diagnostics.ts +60 -7
  315. package/src/dev-error.ts +49 -5
  316. package/src/dev-feed.ts +1140 -0
  317. package/src/dev-indicator.ts +113 -0
  318. package/src/dev-session.ts +729 -0
  319. package/src/dev-sourcemap.ts +281 -0
  320. package/src/dev-ssr-graph.ts +16 -5
  321. package/src/dev.ts +86 -43
  322. package/src/diagnostic-catalog.ts +243 -0
  323. package/src/diagnostic-prompt.ts +355 -0
  324. package/src/diagnostic.ts +63 -66
  325. package/src/draft.ts +4 -1
  326. package/src/fn.ts +19 -3
  327. package/src/fs.ts +13 -2
  328. package/src/index.ts +42 -10
  329. package/src/internal/cdn/cloudflare.ts +146 -0
  330. package/src/internal/cdn/core.ts +316 -0
  331. package/src/internal/cdn/fastly.ts +62 -0
  332. package/src/internal/cdn/vercel.ts +95 -0
  333. package/src/internal/code-unit-order.ts +5 -0
  334. package/src/internal/codegen.ts +161 -24
  335. package/src/internal/dev-csp.ts +176 -0
  336. package/src/internal/dev-reserved.ts +52 -0
  337. package/src/internal/html-spans.ts +50 -0
  338. package/src/internal/loading-runtime.ts +244 -0
  339. package/src/internal/matches-runtime.ts +62 -0
  340. package/src/internal/mount-shadow.ts +88 -0
  341. package/src/internal/node-builtins.ts +79 -0
  342. package/src/internal/output-guard.ts +701 -0
  343. package/src/internal/page-execution.ts +795 -185
  344. package/src/internal/parity.ts +150 -36
  345. package/src/internal/private-env.ts +334 -0
  346. package/src/internal/public-url.ts +22 -0
  347. package/src/internal/render-document.ts +114 -67
  348. package/src/internal/response-controls.ts +284 -0
  349. package/src/internal/runtime-contract.ts +10 -15
  350. package/src/internal/secret-scan.ts +623 -0
  351. package/src/internal/server-boundary.ts +6 -69
  352. package/src/internal/source-map.ts +256 -0
  353. package/src/internal/target-compat.ts +92 -0
  354. package/src/internal/timing-safe-equal.ts +13 -0
  355. package/src/internal/vitals-runtime.ts +53 -0
  356. package/src/internal/web-app.ts +262 -84
  357. package/src/internal/zone-graph.ts +593 -0
  358. package/src/isr.ts +325 -37
  359. package/src/llms-txt.ts +28 -4
  360. package/src/manifest.ts +670 -16
  361. package/src/module-graph.ts +53 -9
  362. package/src/navigation.ts +29 -6
  363. package/src/nonce.ts +104 -0
  364. package/src/plugins/css-modules.ts +5 -3
  365. package/src/plugins/kit.ts +24 -0
  366. package/src/plugins/svg.ts +135 -4
  367. package/src/plugins/vite-leak-guard.ts +529 -16
  368. package/src/plugins/vite-zone-guard.ts +263 -0
  369. package/src/plugins/zone-guard.ts +110 -0
  370. package/src/prerender.ts +49 -3
  371. package/src/public-dir.ts +69 -8
  372. package/src/pwa-manifest.ts +226 -0
  373. package/src/query.ts +4 -4
  374. package/src/render-seam.ts +46 -1
  375. package/src/route-manifest.ts +15 -3
  376. package/src/route-types.ts +264 -0
  377. package/src/router.ts +163 -63
  378. package/src/vitals.ts +44 -0
  379. package/src/vite.ts +207 -36
  380. package/src/zones.ts +430 -0
  381. package/dist/plugins/vite-server-only.d.ts +0 -49
  382. package/dist/plugins/vite-server-only.d.ts.map +0 -1
  383. package/dist/plugins/vite-server-only.js +0 -57
  384. package/dist/plugins/vite-server-only.js.map +0 -1
  385. package/dist/server-only.d.ts +0 -21
  386. package/dist/server-only.d.ts.map +0 -1
  387. package/dist/server-only.js +0 -25
  388. package/dist/server-only.js.map +0 -1
  389. package/src/plugins/vite-server-only.ts +0 -75
  390. package/src/server-only.ts +0 -25
package/src/vite.ts CHANGED
@@ -16,11 +16,14 @@ import {
16
16
  type IncomingMessage,
17
17
  type ServerResponse,
18
18
  } from "node:http"
19
- import { isAbsolute, join, relative, resolve as resolvePath, sep } from "node:path"
20
- import { createDevDiagnostics } from "./dev-diagnostics.ts"
19
+ import { dirname, isAbsolute, join, relative, resolve as resolvePath, sep } from "node:path"
20
+ import { CLIENT_BATCH_MAX_BYTES } from "./dev-client.ts"
21
+ import { DEV_REQUEST_ID_HEADER, DEV_SERVER_FILES } from "./dev-feed.ts"
21
22
  import { listenOrExplain } from "./dev-port.ts"
23
+ import { createDevSession, type DevAppHooks, isDevAgentPath } from "./dev-session.ts"
22
24
  import { discoverRoutes } from "./fs.ts"
23
25
  import { DEFAULT_DEV_PORT, generateClientEntry, setSsrModuleLoader } from "./index.ts"
26
+ import { admitViteTags } from "./internal/dev-csp.ts"
24
27
  import { viteDedupePackages } from "./internal/identity-policy.ts"
25
28
  import {
26
29
  assertIdentityParity,
@@ -39,7 +42,7 @@ import {
39
42
  reproduciblePath,
40
43
  } from "./plugins/kit.ts"
41
44
  import { viteServerFnStub } from "./plugins/vite-server-fn.ts"
42
- import { viteServerOnlyEmpty } from "./plugins/vite-server-only.ts"
45
+ import { viteZoneGuard } from "./plugins/vite-zone-guard.ts"
43
46
 
44
47
  /** Minimal app surface - `createWebApp(...)` satisfies it. */
45
48
  interface FetchApp {
@@ -62,13 +65,20 @@ interface ViteModuleGraph {
62
65
  interface ViteLike {
63
66
  /** Load a module through VITE's graph - the seam that makes the Vite pipeline own SSR too. */
64
67
  ssrLoadModule(url: string): Promise<Record<string, unknown>>
65
- readonly middlewares: (req: IncomingMessage, res: ServerResponse, next: () => void) => void
68
+ readonly middlewares: (
69
+ req: IncomingMessage,
70
+ res: ServerResponse,
71
+ next: (error?: unknown) => void,
72
+ ) => void
66
73
  transformIndexHtml(url: string, html: string): Promise<string>
67
74
  ssrFixStacktrace(err: Error): void
68
75
  /** The module graph, used to invalidate a changed file AND its transitive importers before reload. */
69
76
  readonly moduleGraph?: ViteModuleGraph
70
77
  readonly watcher: {
71
78
  on(event: "change" | "add" | "unlink", cb: (path: string) => void): void
79
+ on(event: "ready", cb: () => void): void
80
+ /** Chokidar's ready state, used to handle a ready event emitted before this adapter attaches. */
81
+ readonly _readyEmitted?: boolean
72
82
  }
73
83
  close(): Promise<void>
74
84
  }
@@ -94,10 +104,14 @@ export interface ViteDevServerOptions {
94
104
  * Without it SSR resolves through Bun while the client resolves through Vite - two resolvers, one
95
105
  * process - which is what makes `resolve.dedupe` fail to reach SSR and produces the dual-React
96
106
  * crash. Vite re-evaluates on change, so no `importQuery` cache-buster is needed alongside it.
107
+ *
108
+ * Pass `dev.onLoaderError` to `createWebApp` so failures an `_error` boundary renders still reach the
109
+ * dev feed (`nifra errors`).
97
110
  */
98
111
  readonly createApp: (
99
112
  clientEntry: string,
100
113
  load: (absolutePath: string) => Promise<unknown>,
114
+ dev: DevAppHooks,
101
115
  ) => FetchApp | Promise<FetchApp>
102
116
  /** Vite plugins - inject your framework's official plugin, e.g. `[react()]`. */
103
117
  readonly plugins?: readonly unknown[]
@@ -133,6 +147,16 @@ export interface ViteDevServerOptions {
133
147
  * resolve it; `nifra build` never honors it. Wired to `nifra dev --allow-duplicate-identity`.
134
148
  */
135
149
  readonly allowDuplicateIdentity?: boolean
150
+ /**
151
+ * Write `.nifra/dev-server.json` (how `nifra errors`/`nifra logs` and the MCP tools find this server)
152
+ * and the persisted dev log beside it (default `true`).
153
+ */
154
+ readonly record?: boolean
155
+ /**
156
+ * Show the browser errors a dev page reports in a badge on that page, with a Copy prompt button per
157
+ * fix (default `true`). Off, the errors still reach the feed.
158
+ */
159
+ readonly indicator?: boolean
136
160
  }
137
161
 
138
162
  export interface ViteDevServer {
@@ -140,10 +164,12 @@ export interface ViteDevServer {
140
164
  stop(): Promise<void>
141
165
  }
142
166
 
167
+ export type { DevAppHooks } from "./dev-session.ts"
143
168
  export { LAST_ERROR_PATH } from "./diagnostic.ts"
144
169
 
145
170
  // The codegen'd client entry is written here (at the Vite root) so Vite serves + HMRs it.
146
171
  const DEV_ENTRY = ".nifra-vite-entry.tsx"
172
+ const VITE_CLOSE_BOUND_MS = 2000
147
173
 
148
174
  /** Candidate spellings for Vite's module-graph map. Vite has used native and slash-normalized
149
175
  * absolute Windows paths across versions, while watcher events can additionally arrive as a file URL. */
@@ -165,6 +191,23 @@ const readNodeBody = async (req: IncomingMessage): Promise<Buffer | undefined> =
165
191
  return Buffer.concat(chunks)
166
192
  }
167
193
 
194
+ /**
195
+ * An agent request's body, never buffered past `max` bytes. A larger body is drained, not kept, so the
196
+ * client still gets an answer: the session refuses the truncated batch.
197
+ */
198
+ const readAgentBody = async (req: IncomingMessage, max: number): Promise<Buffer | undefined> => {
199
+ if (req.method === "GET" || req.method === "HEAD") return undefined
200
+ const chunks: Buffer[] = []
201
+ let total = 0
202
+ for await (const chunk of req) {
203
+ if (!Buffer.isBuffer(chunk)) continue
204
+ total += chunk.length
205
+ if (total <= max) chunks.push(chunk)
206
+ }
207
+ // One byte past the cap is enough for the session's own size check to refuse it.
208
+ return total > max ? Buffer.alloc(max + 1) : Buffer.concat(chunks)
209
+ }
210
+
168
211
  /** Build a Web `Request` from a Node `IncomingMessage` (+ already-read body) for nifra's `app.fetch`. */
169
212
  function toWebRequest(req: IncomingMessage, body: Buffer | undefined): Request {
170
213
  const headers = new Headers()
@@ -382,27 +425,101 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
382
425
  // `vite`, which is assigned just below before the server starts listening.
383
426
  let vite!: ViteLike
384
427
  let app: FetchApp
385
- // The most recent SSR failure, served as JSON at LAST_ERROR_PATH. Shared with the Bun adapter so the
386
- // endpoint and its headers can't drift between the two dev servers.
387
- const devDiagnostics = createDevDiagnostics(root)
428
+ // Failures, console output, request traces and the discovery record for the agent driving this
429
+ // server, plus the overlay and LAST_ERROR_PATH. Shared with the Bun adapter so they cannot drift.
430
+ const session = createDevSession({
431
+ root,
432
+ pipeline: "vite",
433
+ publicEnvPrefix: options.publicEnvPrefix,
434
+ record: options.record,
435
+ indicator: options.indicator,
436
+ })
437
+ const devHooks: DevAppHooks = { onLoaderError: session.onLoaderError }
438
+
439
+ // Refuses backend code before Vite serves or transforms it; held here so a refused module's request
440
+ // is answered with the refusal rather than falling through to the app.
441
+ const zoneGuard = viteZoneGuard({
442
+ appRoot: dirname(routesDir),
443
+ routesDir,
444
+ generatedFiles: [resolvePath(root, DEV_ENTRY)],
445
+ ...(options.publicEnvPrefix !== undefined ? { publicEnvPrefix: options.publicEnvPrefix } : {}),
446
+ })
388
447
 
389
448
  const server: NodeHttpServer = createHttpServer((req, res) => {
390
- // Keep this before Vite's middleware: it is an agent endpoint owned by nifra, not a file or app route.
391
- if (devDiagnostics.isLastErrorPath((req.url ?? "/").split("?", 1)[0] ?? "/")) {
392
- const { body, headers } = devDiagnostics.lastError()
393
- res.statusCode = 200
394
- for (const [key, value] of Object.entries(headers)) res.setHeader(key, value)
395
- res.end(body)
449
+ // Keep this before Vite's middleware: the agent endpoints are owned by nifra, not files or routes.
450
+ if (isDevAgentPath((req.url ?? "/").split("?", 1)[0] ?? "/")) {
451
+ void (async () => {
452
+ try {
453
+ const body = await readAgentBody(req, CLIENT_BATCH_MAX_BYTES)
454
+ const agent = await session.handle(toWebRequest(req, body))
455
+ if (agent === undefined) {
456
+ handleWithVite(req, res)
457
+ return
458
+ }
459
+ res.statusCode = agent.status
460
+ applyResponseHeaders(agent.headers, res)
461
+ res.end(await agent.text())
462
+ } catch (err) {
463
+ // A client that aborts mid-body rejects the read; unhandled, that rejection ends the process.
464
+ if (!req.destroyed) console.error("[nifra/web/vite] dev endpoint failed:", err)
465
+ if (!res.headersSent) res.statusCode = 500
466
+ res.end()
467
+ }
468
+ })()
396
469
  return
397
470
  }
398
- vite.middlewares(req, res, () => {
471
+ handleWithVite(req, res)
472
+ })
473
+
474
+ function handleWithVite(req: IncomingMessage, res: ServerResponse): void {
475
+ vite.middlewares(req, res, (error) => {
476
+ // In middleware mode Vite reports a module's transform error to the overlay and passes the
477
+ // request on. A request naming a source file was for a module, never a page: it gets the error,
478
+ // not the app's HTML.
479
+ const url = req.url ?? "/"
480
+ const file = /\.[\w]+(?:[?#]|$)/.test(url) ? zoneGuard.fileForUrl(url) : undefined
481
+ if (error !== undefined || file !== undefined) {
482
+ res.statusCode = 500
483
+ res.setHeader("content-type", "text/plain; charset=utf-8")
484
+ res.setHeader("cache-control", "no-store")
485
+ const reason =
486
+ zoneGuard.refusalFor(url) ??
487
+ (error instanceof Error
488
+ ? error.message
489
+ : `[nifra] ${url} failed to transform; the dev server log has the error`)
490
+ session.buildFailed(error ?? new Error(reason), `${url} failed to transform`)
491
+ res.end(`${reason}\n`)
492
+ return
493
+ }
399
494
  // Not a Vite asset → nifra SSR. (`next` runs after Vite declines, so the body is still readable.)
400
495
  void (async () => {
401
496
  try {
402
497
  const body = await readNodeBody(req)
403
- const nifraRes = await app.fetch(toWebRequest(req, body))
498
+ const webRequest = toWebRequest(req, body)
499
+ let overlay = false
500
+ const nifraRes = await session.track(webRequest, async () => {
501
+ try {
502
+ return await app.fetch(webRequest)
503
+ } catch (err) {
504
+ // Source-map the stack first (Vite maps the bundled frames back to your `.ts`), then render
505
+ // the readable dev overlay instead of a bare text dump. Dev-only - production maps to `_error`.
506
+ if (err instanceof Error) vite.ssrFixStacktrace(err)
507
+ overlay = true
508
+ return new Response(
509
+ session.failure(err, { method: req.method ?? "GET", url: req.url ?? "/" }),
510
+ { status: 500, headers: { "content-type": "text/html; charset=utf-8" } },
511
+ )
512
+ }
513
+ })
404
514
  const contentType = nifraRes.headers.get("content-type") ?? ""
405
515
  res.statusCode = nifraRes.status
516
+ const requestId = nifraRes.headers.get(DEV_REQUEST_ID_HEADER)
517
+ if (requestId !== null) res.setHeader(DEV_REQUEST_ID_HEADER, requestId)
518
+ if (overlay || session.isOverlay(nifraRes)) {
519
+ res.setHeader("content-type", "text/html; charset=utf-8")
520
+ res.end(await nifraRes.text())
521
+ return
522
+ }
406
523
  if (!contentType.includes("text/html")) {
407
524
  // Data / redirect / asset response - pass through untouched, streamed (SSE-safe). Set-Cookie is
408
525
  // emitted per-header so multi-cookie responses (e.g. better-auth sessions) aren't collapsed.
@@ -410,25 +527,26 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
410
527
  await pipeWebBodyToNode(nifraRes.body, res)
411
528
  return
412
529
  }
413
- // Inject Vite's HMR client + the framework's refresh preamble into the SSR'd HTML.
414
- const html = await vite.transformIndexHtml(req.url ?? "/", await nifraRes.text())
530
+ // Inject Vite's HMR client + the framework's refresh preamble into the SSR'd HTML, keeping the
531
+ // app's headers (cookies, CSP, cache-control) as the Bun pipeline does.
532
+ const headers = new Headers(nifraRes.headers)
533
+ headers.delete("content-length") // the body grows with Vite's tags
534
+ const page = session.decorateHtml(webRequest, await nifraRes.text(), headers, requestId)
535
+ const html = admitViteTags(await vite.transformIndexHtml(req.url ?? "/", page), headers)
536
+ applyResponseHeaders(headers, res)
415
537
  res.setHeader("content-type", "text/html; charset=utf-8")
416
538
  res.end(html)
417
539
  } catch (err) {
418
- // Source-map the stack first (Vite maps the bundled frames back to your `.ts`), then render
419
- // the readable dev overlay instead of a bare text dump. Dev-only - production maps to `_error`.
540
+ // A failure past the app (reading the body, Vite's HTML transform): same overlay, same feed.
420
541
  if (err instanceof Error) vite.ssrFixStacktrace(err)
421
- const html = devDiagnostics.capture(err, {
422
- method: req.method ?? "GET",
423
- url: req.url ?? "/",
424
- })
542
+ const html = session.failure(err, { method: req.method ?? "GET", url: req.url ?? "/" })
425
543
  res.statusCode = 500
426
544
  res.setHeader("content-type", "text/html; charset=utf-8")
427
545
  res.end(html)
428
546
  }
429
547
  })()
430
548
  })
431
- })
549
+ }
432
550
 
433
551
  // `conditions: ["bun"]` makes Vite resolve nifra's workspace packages (`@nifrajs/web-react/client`, …) to
434
552
  // their TS **source** - so the dev server needs no prior `dist` build of the adapter packages.
@@ -461,6 +579,16 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
461
579
  const adapterPackage = packageNameOf(options.clientModule)
462
580
  const ssrExternal = [...(adapterPackage !== undefined ? [adapterPackage] : []), "@nifrajs/web"]
463
581
  const usePolling = options.poll ?? process.env.CHOKIDAR_USEPOLLING === "1"
582
+ // The feed writes its record and log under `.nifra/`: watching them would turn every log flush into
583
+ // a "file change" that re-creates the app and marks every entry stale.
584
+ const watch: { ignored: RegExp[]; usePolling?: boolean; interval?: number } = {
585
+ ignored: [DEV_SERVER_FILES],
586
+ }
587
+ // Poll when native fs events aren't delivered (containers/sandboxes).
588
+ if (usePolling) {
589
+ watch.usePolling = true
590
+ watch.interval = 80
591
+ }
464
592
  // Never `import("vite")` directly here: the guard in `importVite` has to run first, and the dev
465
593
  // server importing vite unguarded is precisely what poisoned the module for the whole process.
466
594
  const viteModule = await importVite<ViteModule>()
@@ -480,12 +608,11 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
480
608
  server: {
481
609
  middlewareMode: true,
482
610
  hmr: { server },
483
- // Explicit watch config; poll when native fs events aren't delivered (containers/sandboxes).
484
- watch: usePolling ? { usePolling: true, interval: 80 } : {},
611
+ watch,
485
612
  },
486
- // Ahead of the user's plugins: a `*.fn` module must be replaced before anything else
487
- // reads it, and the dev server is a client bundler like any other.
488
- plugins: [viteServerFnStub(), viteServerOnlyEmpty(), ...plugins],
613
+ // Ahead of the user's plugins: the zone guard refuses backend code before anything serves or
614
+ // transforms it, and a `*.fn` module must be replaced before anything else reads it.
615
+ plugins: [zoneGuard, viteServerFnStub(), ...plugins],
489
616
  resolve: {
490
617
  conditions: resolveConditions,
491
618
  // Dedupe each framework runtime to ONE copy. In a multi-root workspace a shared package can pull
@@ -523,6 +650,22 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
523
650
  },
524
651
  ...(options.define ? { define: options.define } : {}),
525
652
  })
653
+ // Vite resolves before chokidar necessarily finishes its initial scan. Do not expose the
654
+ // dev server until that scan is complete: a route created immediately after startup must be
655
+ // observed as a change, not folded into the ignored initial snapshot on slower filesystems.
656
+ const watcherReady = new Promise<void>((resolve) => {
657
+ let settled = false
658
+ const markReady = (): void => {
659
+ if (settled) return
660
+ settled = true
661
+ resolve()
662
+ }
663
+ vite.watcher.on("ready", markReady)
664
+ // createServer() can return after chokidar has emitted `ready`, especially on fast local
665
+ // filesystems. Chokidar exposes this state in its structural type so we can cover that case
666
+ // without a startup timeout or a false-positive partial directory snapshot.
667
+ if (vite.watcher._readyEmitted === true) markReady()
668
+ })
526
669
 
527
670
  const ssrLoad = (absolutePath: string): Promise<unknown> => vite.ssrLoadModule(absolutePath)
528
671
  // Published so an ADAPTER can reach this graph too. Route modules already load through it; an
@@ -530,7 +673,7 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
530
673
  // `Chain.svelte`) would otherwise take it from the runtime, which has no compiler for it and no
531
674
  // reason to agree with Vite about which copy of the framework runtime it renders through.
532
675
  setSsrModuleLoader(ssrLoad)
533
- app = await options.createApp(entryUrl, ssrLoad)
676
+ app = await options.createApp(entryUrl, ssrLoad, devHooks)
534
677
 
535
678
  // Evict a changed file from the SSR graph together with every module that (transitively) imports it,
536
679
  // BEFORE re-creating the app. Vite re-evaluates a directly-changed module on its own, but a parent
@@ -566,20 +709,35 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
566
709
  const refreshApp = (path?: string): void => {
567
710
  if (path !== undefined) invalidateImporterClosure(path)
568
711
  const version = ++refreshVersion
569
- Promise.resolve(options.createApp(entryUrl, ssrLoad))
712
+ Promise.resolve(options.createApp(entryUrl, ssrLoad, devHooks))
570
713
  .then((next) => {
571
714
  if (version === refreshVersion) app = next
572
715
  })
573
- .catch((err) => console.error("[nifra/web/vite] app re-create failed:", err))
716
+ .catch((err) => {
717
+ console.error("[nifra/web/vite] app re-create failed:", err)
718
+ session.buildFailed(err, "app re-create failed")
719
+ })
574
720
  }
575
- vite.watcher.on("change", (path) => refreshApp(normalizeFilePath(path)))
721
+ vite.watcher.on("change", (path) => {
722
+ session.markChange()
723
+ refreshApp(normalizeFilePath(path))
724
+ })
576
725
  for (const event of ["add", "unlink"] as const) {
577
726
  vite.watcher.on(event, (path) => {
727
+ session.markChange()
578
728
  const filePath = normalizeFilePath(path)
579
- if (pathInside(routesDir, filePath)) writeClientEntry()
729
+ if (pathInside(routesDir, filePath)) {
730
+ writeClientEntry()
731
+ // A newly added route is not in Vite's module graph yet, so invalidating only the
732
+ // route path cannot evict the cached generated entry that imports the route. Evict the
733
+ // generated entry after rewriting it before recreating the SSR app; this also prevents
734
+ // an unlink refresh from briefly loading an entry that still references the deleted file.
735
+ invalidateImporterClosure(resolvePath(root, DEV_ENTRY))
736
+ }
580
737
  refreshApp(filePath)
581
738
  })
582
739
  }
740
+ await watcherReady
583
741
 
584
742
  try {
585
743
  await listenOrExplain(server, port, "127.0.0.1")
@@ -589,21 +747,34 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
589
747
  // then HANGS on it, which is worse than the raw crash this guard replaced: the user sees a dev server
590
748
  // that appears to be starting. Tear Vite down so the failure is terminal.
591
749
  await vite.close().catch(() => {})
750
+ session.stop()
592
751
  throw err
593
752
  }
594
753
  // Report the port actually BOUND, not the one requested. They differ for `port: 0`, which is how you
595
754
  // ask the OS for a free one - the correct thing to do in a test or when running several apps at once.
596
755
  // Echoing the request back would return a literal 0, which connects to nothing.
597
756
  const address = server.address()
757
+ const boundPort = typeof address === "object" && address !== null ? address.port : port
758
+ session.listening(boundPort)
598
759
  return {
599
- port: typeof address === "object" && address !== null ? address.port : port,
760
+ port: boundPort,
600
761
  stop: async () => {
762
+ session.stop()
601
763
  // Cleared with the server that owns it: the slot is process-global, so a stopped server leaving
602
764
  // its loader behind would hand the next one's adapter a graph that is closed (tests start and
603
765
  // stop several dev servers in one process).
604
766
  setSsrModuleLoader(undefined)
605
767
  server.close()
606
- await vite.close()
768
+ // Vite (8.2) never settles a first dependency optimization that close() cuts short, and close()
769
+ // waits on it for good. Everything else closes in parallel, so only that wait is bounded.
770
+ let bound: ReturnType<typeof setTimeout> | undefined
771
+ await Promise.race([
772
+ vite.close(),
773
+ new Promise<void>((resolve) => {
774
+ bound = setTimeout(resolve, VITE_CLOSE_BOUND_MS)
775
+ }),
776
+ ])
777
+ clearTimeout(bound)
607
778
  },
608
779
  }
609
780
  }