@nifrajs/web 4.0.0 → 5.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 (388) hide show
  1. package/README.md +11 -0
  2. package/dist/build-vite.d.ts +19 -0
  3. package/dist/build-vite.d.ts.map +1 -1
  4. package/dist/build-vite.js +31 -10
  5. package/dist/build-vite.js.map +1 -1
  6. package/dist/build.d.ts +8 -10
  7. package/dist/build.d.ts.map +1 -1
  8. package/dist/build.js +90 -31
  9. package/dist/build.js.map +1 -1
  10. package/dist/cdn.d.ts +8 -0
  11. package/dist/cdn.d.ts.map +1 -1
  12. package/dist/cdn.js +14 -15
  13. package/dist/cdn.js.map +1 -1
  14. package/dist/client.d.ts +1 -1
  15. package/dist/client.d.ts.map +1 -1
  16. package/dist/client.js +49 -26
  17. package/dist/client.js.map +1 -1
  18. package/dist/deferred.d.ts +2 -2
  19. package/dist/deferred.d.ts.map +1 -1
  20. package/dist/deferred.js +28 -7
  21. package/dist/deferred.js.map +1 -1
  22. package/dist/dev-feed.d.ts.map +1 -1
  23. package/dist/dev-feed.js +24 -5
  24. package/dist/dev-feed.js.map +1 -1
  25. package/dist/dev-session.d.ts.map +1 -1
  26. package/dist/dev-session.js +4 -14
  27. package/dist/dev-session.js.map +1 -1
  28. package/dist/dev.d.ts +7 -17
  29. package/dist/dev.d.ts.map +1 -1
  30. package/dist/dev.js +13 -1
  31. package/dist/dev.js.map +1 -1
  32. package/dist/draft.d.ts +8 -5
  33. package/dist/draft.d.ts.map +1 -1
  34. package/dist/draft.js +30 -13
  35. package/dist/draft.js.map +1 -1
  36. package/dist/fonts-google.d.ts.map +1 -1
  37. package/dist/fonts-google.js +7 -0
  38. package/dist/fonts-google.js.map +1 -1
  39. package/dist/index.d.ts +2 -2
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +2 -2
  42. package/dist/index.js.map +1 -1
  43. package/dist/internal/codegen.d.ts +1 -0
  44. package/dist/internal/codegen.d.ts.map +1 -1
  45. package/dist/internal/codegen.js +56 -29
  46. package/dist/internal/codegen.js.map +1 -1
  47. package/dist/internal/disposable-dir.d.ts +8 -0
  48. package/dist/internal/disposable-dir.d.ts.map +1 -0
  49. package/dist/internal/disposable-dir.js +25 -0
  50. package/dist/internal/disposable-dir.js.map +1 -0
  51. package/dist/internal/entry-chunks.d.ts +9 -0
  52. package/dist/internal/entry-chunks.d.ts.map +1 -0
  53. package/dist/internal/entry-chunks.js +15 -0
  54. package/dist/internal/entry-chunks.js.map +1 -0
  55. package/dist/internal/evidence-traversal.d.ts +12 -0
  56. package/dist/internal/evidence-traversal.d.ts.map +1 -0
  57. package/dist/internal/evidence-traversal.js +48 -0
  58. package/dist/internal/evidence-traversal.js.map +1 -0
  59. package/dist/internal/head-merge.d.ts +6 -0
  60. package/dist/internal/head-merge.d.ts.map +1 -1
  61. package/dist/internal/head-merge.js +9 -0
  62. package/dist/internal/head-merge.js.map +1 -1
  63. package/dist/internal/js-string.d.ts +2 -0
  64. package/dist/internal/js-string.d.ts.map +1 -1
  65. package/dist/internal/js-string.js +5 -1
  66. package/dist/internal/js-string.js.map +1 -1
  67. package/dist/internal/mount-shadow.js +1 -1
  68. package/dist/internal/mount-shadow.js.map +1 -1
  69. package/dist/internal/output-guard.d.ts +7 -0
  70. package/dist/internal/output-guard.d.ts.map +1 -1
  71. package/dist/internal/output-guard.js +80 -8
  72. package/dist/internal/output-guard.js.map +1 -1
  73. package/dist/internal/page-execution.d.ts +6 -0
  74. package/dist/internal/page-execution.d.ts.map +1 -1
  75. package/dist/internal/page-execution.js +377 -108
  76. package/dist/internal/page-execution.js.map +1 -1
  77. package/dist/internal/progressive-cookies.d.ts +14 -0
  78. package/dist/internal/progressive-cookies.d.ts.map +1 -0
  79. package/dist/internal/progressive-cookies.js +43 -0
  80. package/dist/internal/progressive-cookies.js.map +1 -0
  81. package/dist/internal/public-env.d.ts +2 -0
  82. package/dist/internal/public-env.d.ts.map +1 -0
  83. package/dist/internal/public-env.js +13 -0
  84. package/dist/internal/public-env.js.map +1 -0
  85. package/dist/internal/render-document.d.ts +1 -0
  86. package/dist/internal/render-document.d.ts.map +1 -1
  87. package/dist/internal/render-document.js +28 -5
  88. package/dist/internal/render-document.js.map +1 -1
  89. package/dist/internal/response-controls.d.ts +2 -0
  90. package/dist/internal/response-controls.d.ts.map +1 -1
  91. package/dist/internal/response-controls.js +26 -16
  92. package/dist/internal/response-controls.js.map +1 -1
  93. package/dist/internal/revive-dates.d.ts +16 -0
  94. package/dist/internal/revive-dates.d.ts.map +1 -0
  95. package/dist/internal/revive-dates.js +32 -0
  96. package/dist/internal/revive-dates.js.map +1 -0
  97. package/dist/internal/route-path.d.ts +8 -0
  98. package/dist/internal/route-path.d.ts.map +1 -0
  99. package/dist/internal/route-path.js +8 -0
  100. package/dist/internal/route-path.js.map +1 -0
  101. package/dist/internal/secret-scan.d.ts.map +1 -1
  102. package/dist/internal/secret-scan.js +105 -19
  103. package/dist/internal/secret-scan.js.map +1 -1
  104. package/dist/internal/server-boundary.d.ts.map +1 -1
  105. package/dist/internal/server-boundary.js +33 -5
  106. package/dist/internal/server-boundary.js.map +1 -1
  107. package/dist/internal/web-app.d.ts +21 -16
  108. package/dist/internal/web-app.d.ts.map +1 -1
  109. package/dist/internal/web-app.js +73 -21
  110. package/dist/internal/web-app.js.map +1 -1
  111. package/dist/internal/wire-date.d.ts +3 -0
  112. package/dist/internal/wire-date.d.ts.map +1 -0
  113. package/dist/internal/wire-date.js +14 -0
  114. package/dist/internal/wire-date.js.map +1 -0
  115. package/dist/internal/zone-graph.d.ts +4 -0
  116. package/dist/internal/zone-graph.d.ts.map +1 -1
  117. package/dist/internal/zone-graph.js +54 -14
  118. package/dist/internal/zone-graph.js.map +1 -1
  119. package/dist/isr.d.ts +58 -2
  120. package/dist/isr.d.ts.map +1 -1
  121. package/dist/isr.js +296 -39
  122. package/dist/isr.js.map +1 -1
  123. package/dist/llms-txt.d.ts +2 -0
  124. package/dist/llms-txt.d.ts.map +1 -1
  125. package/dist/llms-txt.js +29 -3
  126. package/dist/llms-txt.js.map +1 -1
  127. package/dist/manifest.d.ts +28 -4
  128. package/dist/manifest.d.ts.map +1 -1
  129. package/dist/manifest.js +14 -3
  130. package/dist/manifest.js.map +1 -1
  131. package/dist/open-resolved.d.ts +18 -0
  132. package/dist/open-resolved.d.ts.map +1 -0
  133. package/dist/open-resolved.js +62 -0
  134. package/dist/open-resolved.js.map +1 -0
  135. package/dist/plugins/kit.d.ts +1 -0
  136. package/dist/plugins/kit.d.ts.map +1 -1
  137. package/dist/plugins/kit.js +26 -11
  138. package/dist/plugins/kit.js.map +1 -1
  139. package/dist/plugins/vite-leak-guard.d.ts +3 -1
  140. package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
  141. package/dist/plugins/vite-leak-guard.js +4 -0
  142. package/dist/plugins/vite-leak-guard.js.map +1 -1
  143. package/dist/plugins/zone-guard.d.ts.map +1 -1
  144. package/dist/plugins/zone-guard.js +10 -2
  145. package/dist/plugins/zone-guard.js.map +1 -1
  146. package/dist/public-dir.d.ts +8 -1
  147. package/dist/public-dir.d.ts.map +1 -1
  148. package/dist/public-dir.js +17 -6
  149. package/dist/public-dir.js.map +1 -1
  150. package/dist/query.d.ts +13 -4
  151. package/dist/query.d.ts.map +1 -1
  152. package/dist/query.js +89 -26
  153. package/dist/query.js.map +1 -1
  154. package/dist/render-seam.d.ts +2 -0
  155. package/dist/render-seam.d.ts.map +1 -1
  156. package/dist/render-seam.js +1 -0
  157. package/dist/render-seam.js.map +1 -1
  158. package/dist/route-types.d.ts.map +1 -1
  159. package/dist/route-types.js +20 -9
  160. package/dist/route-types.js.map +1 -1
  161. package/dist/router.d.ts +9 -2
  162. package/dist/router.d.ts.map +1 -1
  163. package/dist/router.js +39 -46
  164. package/dist/router.js.map +1 -1
  165. package/dist/server-components/build-info.d.ts +3 -0
  166. package/dist/server-components/build-info.d.ts.map +1 -0
  167. package/dist/server-components/build-info.js +7 -0
  168. package/dist/server-components/build-info.js.map +1 -0
  169. package/dist/server-components/build-target.d.ts +12 -0
  170. package/dist/server-components/build-target.d.ts.map +1 -0
  171. package/dist/server-components/build-target.js +153 -0
  172. package/dist/server-components/build-target.js.map +1 -0
  173. package/dist/server-components/build.d.ts +24 -0
  174. package/dist/server-components/build.d.ts.map +1 -0
  175. package/dist/server-components/build.js +366 -0
  176. package/dist/server-components/build.js.map +1 -0
  177. package/dist/server-components/bundle-proof.d.ts +3 -0
  178. package/dist/server-components/bundle-proof.d.ts.map +1 -0
  179. package/dist/server-components/bundle-proof.js +23 -0
  180. package/dist/server-components/bundle-proof.js.map +1 -0
  181. package/dist/server-components/client.d.ts +46 -0
  182. package/dist/server-components/client.d.ts.map +1 -0
  183. package/dist/server-components/client.js +594 -0
  184. package/dist/server-components/client.js.map +1 -0
  185. package/dist/server-components/definition.d.ts +11 -0
  186. package/dist/server-components/definition.d.ts.map +1 -0
  187. package/dist/server-components/definition.js +94 -0
  188. package/dist/server-components/definition.js.map +1 -0
  189. package/dist/server-components/dev-http.d.ts +5 -0
  190. package/dist/server-components/dev-http.d.ts.map +1 -0
  191. package/dist/server-components/dev-http.js +71 -0
  192. package/dist/server-components/dev-http.js.map +1 -0
  193. package/dist/server-components/error.d.ts +9 -0
  194. package/dist/server-components/error.d.ts.map +1 -0
  195. package/dist/server-components/error.js +24 -0
  196. package/dist/server-components/error.js.map +1 -0
  197. package/dist/server-components/frames.d.ts +7 -0
  198. package/dist/server-components/frames.d.ts.map +1 -0
  199. package/dist/server-components/frames.js +169 -0
  200. package/dist/server-components/frames.js.map +1 -0
  201. package/dist/server-components/html-policy.d.ts +6 -0
  202. package/dist/server-components/html-policy.d.ts.map +1 -0
  203. package/dist/server-components/html-policy.js +75 -0
  204. package/dist/server-components/html-policy.js.map +1 -0
  205. package/dist/server-components/html.d.ts +4 -0
  206. package/dist/server-components/html.d.ts.map +1 -0
  207. package/dist/server-components/html.js +67 -0
  208. package/dist/server-components/html.js.map +1 -0
  209. package/dist/server-components/islands.d.ts +44 -0
  210. package/dist/server-components/islands.d.ts.map +1 -0
  211. package/dist/server-components/islands.js +42 -0
  212. package/dist/server-components/islands.js.map +1 -0
  213. package/dist/server-components/native.d.ts +4 -0
  214. package/dist/server-components/native.d.ts.map +1 -0
  215. package/dist/server-components/native.js +81 -0
  216. package/dist/server-components/native.js.map +1 -0
  217. package/dist/server-components/portal.d.ts +6 -0
  218. package/dist/server-components/portal.d.ts.map +1 -0
  219. package/dist/server-components/portal.js +35 -0
  220. package/dist/server-components/portal.js.map +1 -0
  221. package/dist/server-components/props.d.ts +3 -0
  222. package/dist/server-components/props.d.ts.map +1 -0
  223. package/dist/server-components/props.js +14 -0
  224. package/dist/server-components/props.js.map +1 -0
  225. package/dist/server-components/protocol.d.ts +44 -0
  226. package/dist/server-components/protocol.d.ts.map +1 -0
  227. package/dist/server-components/protocol.js +139 -0
  228. package/dist/server-components/protocol.js.map +1 -0
  229. package/dist/server-components/range.d.ts +24 -0
  230. package/dist/server-components/range.d.ts.map +1 -0
  231. package/dist/server-components/range.js +263 -0
  232. package/dist/server-components/range.js.map +1 -0
  233. package/dist/server-components/registry.d.ts +6 -0
  234. package/dist/server-components/registry.d.ts.map +1 -0
  235. package/dist/server-components/registry.js +30 -0
  236. package/dist/server-components/registry.js.map +1 -0
  237. package/dist/server-components/response.d.ts +4 -0
  238. package/dist/server-components/response.d.ts.map +1 -0
  239. package/dist/server-components/response.js +32 -0
  240. package/dist/server-components/response.js.map +1 -0
  241. package/dist/server-components/router-integration.d.ts +9 -0
  242. package/dist/server-components/router-integration.d.ts.map +1 -0
  243. package/dist/server-components/router-integration.js +29 -0
  244. package/dist/server-components/router-integration.js.map +1 -0
  245. package/dist/server-components/runtime.d.ts +14 -0
  246. package/dist/server-components/runtime.d.ts.map +1 -0
  247. package/dist/server-components/runtime.js +276 -0
  248. package/dist/server-components/runtime.js.map +1 -0
  249. package/dist/server-components/scheduler.d.ts +14 -0
  250. package/dist/server-components/scheduler.d.ts.map +1 -0
  251. package/dist/server-components/scheduler.js +74 -0
  252. package/dist/server-components/scheduler.js.map +1 -0
  253. package/dist/server-components/schema.d.ts +4 -0
  254. package/dist/server-components/schema.d.ts.map +1 -0
  255. package/dist/server-components/schema.js +77 -0
  256. package/dist/server-components/schema.js.map +1 -0
  257. package/dist/server-components/server-entry.d.ts +12 -0
  258. package/dist/server-components/server-entry.d.ts.map +1 -0
  259. package/dist/server-components/server-entry.js +90 -0
  260. package/dist/server-components/server-entry.js.map +1 -0
  261. package/dist/server-components/source.d.ts +15 -0
  262. package/dist/server-components/source.d.ts.map +1 -0
  263. package/dist/server-components/source.js +269 -0
  264. package/dist/server-components/source.js.map +1 -0
  265. package/dist/server-components/state.d.ts +4 -0
  266. package/dist/server-components/state.d.ts.map +1 -0
  267. package/dist/server-components/state.js +123 -0
  268. package/dist/server-components/state.js.map +1 -0
  269. package/dist/server-components/stream.d.ts +6 -0
  270. package/dist/server-components/stream.d.ts.map +1 -0
  271. package/dist/server-components/stream.js +70 -0
  272. package/dist/server-components/stream.js.map +1 -0
  273. package/dist/server-components/trusted-html.d.ts +3 -0
  274. package/dist/server-components/trusted-html.d.ts.map +1 -0
  275. package/dist/server-components/trusted-html.js +14 -0
  276. package/dist/server-components/trusted-html.js.map +1 -0
  277. package/dist/server-components/types.d.ts +107 -0
  278. package/dist/server-components/types.d.ts.map +1 -0
  279. package/dist/server-components/types.js +2 -0
  280. package/dist/server-components/types.js.map +1 -0
  281. package/dist/server-components/typescript.d.ts +11 -0
  282. package/dist/server-components/typescript.d.ts.map +1 -0
  283. package/dist/server-components/typescript.js +28 -0
  284. package/dist/server-components/typescript.js.map +1 -0
  285. package/dist/server-components/value.d.ts +5 -0
  286. package/dist/server-components/value.d.ts.map +1 -0
  287. package/dist/server-components/value.js +77 -0
  288. package/dist/server-components/value.js.map +1 -0
  289. package/dist/server-components/vite.d.ts +12 -0
  290. package/dist/server-components/vite.d.ts.map +1 -0
  291. package/dist/server-components/vite.js +197 -0
  292. package/dist/server-components/vite.js.map +1 -0
  293. package/dist/server-components.d.ts +5 -0
  294. package/dist/server-components.d.ts.map +1 -0
  295. package/dist/server-components.js +4 -0
  296. package/dist/server-components.js.map +1 -0
  297. package/dist/service-worker.d.ts +8 -1
  298. package/dist/service-worker.d.ts.map +1 -1
  299. package/dist/service-worker.js +20 -6
  300. package/dist/service-worker.js.map +1 -1
  301. package/dist/vite.d.ts +12 -0
  302. package/dist/vite.d.ts.map +1 -1
  303. package/dist/vite.js +77 -17
  304. package/dist/vite.js.map +1 -1
  305. package/dist/zones.d.ts +40 -0
  306. package/dist/zones.d.ts.map +1 -1
  307. package/dist/zones.js +170 -6
  308. package/dist/zones.js.map +1 -1
  309. package/package.json +97 -4
  310. package/src/build-vite.ts +31 -9
  311. package/src/build.ts +109 -31
  312. package/src/cdn.ts +18 -12
  313. package/src/client.ts +48 -23
  314. package/src/deferred.ts +27 -9
  315. package/src/dev-feed.ts +24 -5
  316. package/src/dev-session.ts +4 -13
  317. package/src/dev.ts +29 -1
  318. package/src/draft.ts +35 -14
  319. package/src/fonts-google.ts +10 -0
  320. package/src/index.ts +3 -2
  321. package/src/internal/codegen.ts +60 -29
  322. package/src/internal/disposable-dir.ts +24 -0
  323. package/src/internal/entry-chunks.ts +18 -0
  324. package/src/internal/evidence-traversal.ts +57 -0
  325. package/src/internal/head-merge.ts +16 -0
  326. package/src/internal/js-string.ts +6 -1
  327. package/src/internal/mount-shadow.ts +1 -1
  328. package/src/internal/output-guard.ts +88 -7
  329. package/src/internal/page-execution.ts +506 -108
  330. package/src/internal/progressive-cookies.ts +60 -0
  331. package/src/internal/public-env.ts +14 -0
  332. package/src/internal/render-document.ts +26 -11
  333. package/src/internal/response-controls.ts +31 -14
  334. package/src/internal/revive-dates.ts +35 -0
  335. package/src/internal/route-path.ts +8 -0
  336. package/src/internal/secret-scan.ts +104 -17
  337. package/src/internal/server-boundary.ts +29 -5
  338. package/src/internal/web-app.ts +116 -37
  339. package/src/internal/wire-date.ts +16 -0
  340. package/src/internal/zone-graph.ts +59 -19
  341. package/src/isr.ts +351 -43
  342. package/src/llms-txt.ts +32 -3
  343. package/src/manifest.ts +43 -7
  344. package/src/open-resolved.ts +64 -0
  345. package/src/plugins/kit.ts +31 -11
  346. package/src/plugins/vite-leak-guard.ts +5 -1
  347. package/src/plugins/zone-guard.ts +11 -2
  348. package/src/public-dir.ts +27 -8
  349. package/src/query.ts +107 -26
  350. package/src/render-seam.ts +2 -0
  351. package/src/route-types.ts +34 -7
  352. package/src/router.ts +58 -55
  353. package/src/server-components/build-info.ts +11 -0
  354. package/src/server-components/build-target.ts +194 -0
  355. package/src/server-components/build.ts +419 -0
  356. package/src/server-components/bundle-proof.ts +33 -0
  357. package/src/server-components/client.ts +663 -0
  358. package/src/server-components/definition.ts +125 -0
  359. package/src/server-components/dev-http.ts +75 -0
  360. package/src/server-components/error.ts +24 -0
  361. package/src/server-components/frames.ts +181 -0
  362. package/src/server-components/html-policy.ts +94 -0
  363. package/src/server-components/html.ts +81 -0
  364. package/src/server-components/islands.ts +111 -0
  365. package/src/server-components/native.ts +101 -0
  366. package/src/server-components/portal.ts +42 -0
  367. package/src/server-components/props.ts +19 -0
  368. package/src/server-components/protocol.ts +167 -0
  369. package/src/server-components/range.ts +313 -0
  370. package/src/server-components/registry.ts +34 -0
  371. package/src/server-components/response.ts +44 -0
  372. package/src/server-components/router-integration.ts +40 -0
  373. package/src/server-components/runtime.ts +376 -0
  374. package/src/server-components/scheduler.ts +84 -0
  375. package/src/server-components/schema.ts +69 -0
  376. package/src/server-components/server-entry.ts +98 -0
  377. package/src/server-components/source.ts +324 -0
  378. package/src/server-components/state.ts +143 -0
  379. package/src/server-components/stream.ts +71 -0
  380. package/src/server-components/trusted-html.ts +21 -0
  381. package/src/server-components/types.ts +138 -0
  382. package/src/server-components/typescript.ts +39 -0
  383. package/src/server-components/value.ts +82 -0
  384. package/src/server-components/vite.ts +242 -0
  385. package/src/server-components.ts +32 -0
  386. package/src/service-worker.ts +24 -7
  387. package/src/vite.ts +68 -17
  388. package/src/zones.ts +208 -7
@@ -1,3 +1,5 @@
1
+ import { emitRequestErrorLog } from "@nifrajs/core/edge-kit"
2
+ import { jsonLogger } from "@nifrajs/core/logger"
1
3
  import {
2
4
  type BackendEvidenceProvider,
3
5
  type BackendMount,
@@ -16,12 +18,15 @@ import { CACHE_CHANNEL } from "../isr.ts"
16
18
  import { generateLlmsTxt } from "../llms-txt.ts"
17
19
  import type { Manifest } from "../manifest.ts"
18
20
  import type { RenderAdapter } from "../render-seam.ts"
19
- import { PRERENDERED_LIST_PATH } from "../router.ts"
21
+ import { createMatcher, PRERENDERED_LIST_PATH } from "../router.ts"
22
+ import type { EvidenceAncestry } from "./evidence-traversal.ts"
20
23
  import { formatShadowedPages, shadowedPages } from "./mount-shadow.ts"
21
24
  import { createPageRequestExecutor, type NonceResolver } from "./page-execution.ts"
25
+ import { requestPattern } from "./route-path.ts"
22
26
 
23
27
  export type { NonceResolver } from "./page-execution.ts"
24
28
  export interface CreateWebAppOptions<Env = unknown> {
29
+ readonly serverComponents?: import("../server-components/types.ts").ServerComponentRuntime
25
30
  readonly adapter: RenderAdapter
26
31
  readonly manifest: Manifest
27
32
  /** URL of the built client entry (module script) injected into every page. */
@@ -78,9 +83,10 @@ export interface CreateWebAppOptions<Env = unknown> {
78
83
  *
79
84
  * **Why the timing matters.** `beforeHandle`, `afterHandle`, `around`, `derive`/`decorate`, and
80
85
  * `onError` are snapshotted into each route AS IT IS DECLARED, and `createWebApp` declares every
81
- * page (plus the `/*` catch-all) before it returns - so a caller's `app.use(…)` afterwards binds
82
- * them to nothing, silently. `requestId()` is exactly that case: it is a `derive`, so applied late
83
- * it leaves every page without `c.requestId`. Route assurance evidence is order-scoped the same
86
+ * page (plus the `/*` catch-all) before it returns - so a caller's `app.use(…)` afterwards would
87
+ * bind them to no page, and outside production it throws (`HOOK_AFTER_PAGES`). `requestId()` is
88
+ * exactly that case: it is a `derive`, so applied late it would leave every page without
89
+ * `c.requestId`. A `group()` is its own scope and stays allowed. Route assurance evidence is order-scoped the same
84
90
  * way, so a late `securityHeaders()` leaves the pages unable to PROVE the header to `nifra assure`
85
91
  * even though it does still set it.
86
92
  *
@@ -101,10 +107,11 @@ export interface CreateWebAppOptions<Env = unknown> {
101
107
  * per-route via `@nifrajs/client`'s `createRoutes`). Opaque to the core.
102
108
  *
103
109
  * **Auto-mount.** Every `inProcessClient(backend)` exposes the symbol-keyed platform-aware backend
104
- * mount interface from `@nifrajs/core/mount`; `createWebApp` also serves that backend over HTTP at
105
- * {@link apiPrefix} (default `/api`): a request whose pathname starts with the prefix is dispatched
106
- * before page routing with the same `env`/`waitUntil` platform context, and the backend's `Response`
107
- * is returned untouched. The mount runs in `nifra dev` too. Pass `apiPrefix: ""` to disable it.
110
+ * mount interface from `@nifrajs/core/mount`; given an {@link apiPrefix}, `createWebApp` also serves
111
+ * that backend over HTTP there: a request whose pathname starts with the prefix is dispatched before
112
+ * page routing with the same `env`/`waitUntil` platform context, and the backend's `Response` is
113
+ * returned untouched. The mount runs in `nifra dev` too. Without `apiPrefix` the backend is
114
+ * loader-only.
108
115
  *
109
116
  * **Request-scoped loader calls.** While a page renders, its loaders, actions and boundaries get a
110
117
  * `ctx.api` bound to that request's platform identity: a backend handler reached through it sees the
@@ -114,12 +121,12 @@ export interface CreateWebAppOptions<Env = unknown> {
114
121
  * does not lose it. Headers do NOT travel: the page request's `cookie` and `authorization` are never
115
122
  * copied, so a loader call is anonymous unless the loader passes headers on the call itself. */
116
123
  readonly api?: unknown
117
- /** HTTP path prefix the {@link api} backend is auto-mounted at (default `"/api"`). A request whose
124
+ /** HTTP path prefix the {@link api} backend is mounted at, such as `"/api"`. A request whose
118
125
  * pathname is exactly the prefix or starts with `prefix + "/"` is dispatched to the backend before
119
126
  * page routing; the backend therefore defines its routes at the **full** path (`server().post("/api/
120
- * sync", …)`), matching the in-process `inProcessClient` call sites. Set to `""` to disable the
121
- * auto-mount entirely (the app serves pages only and `api` stays a loader-only `ctx.api`). Mounting
122
- * is also a no-op when `api` does not expose the symbol mount.
127
+ * sync", …)`), matching the in-process `inProcessClient` call sites. Unset or `""`, nothing is
128
+ * mounted: the app serves pages only and `api` stays a loader-only `ctx.api`. Mounting is also a
129
+ * no-op when `api` does not expose the symbol mount.
123
130
  *
124
131
  * A page file under the prefix (`routes/api/report.tsx`) could never render - the backend answers
125
132
  * first and its 404 is final - so `createWebApp` throws, naming the file. The same holds for every
@@ -146,7 +153,8 @@ export interface CreateWebAppOptions<Env = unknown> {
146
153
  * Tried longest-path-first and BEFORE the `api` mount, so a mount at `/api/auth` wins over a backend
147
154
  * at `/api` no matter which was declared first. `stripPrefix` is the per-mount form of {@link apiStrip}:
148
155
  * leave it off to pass the full path through. `opaque` states why a mount whose routes nifra cannot
149
- * read (better-auth's handler) is outside assurance; it is then listed as a known gap.
156
+ * read (better-auth's handler) is outside assurance; it is then listed as a known gap, with any
157
+ * routes it declares reported as declared.
150
158
  */
151
159
  readonly mounts?: ReadonlyArray<{
152
160
  readonly path: string
@@ -154,7 +162,7 @@ export interface CreateWebAppOptions<Env = unknown> {
154
162
  readonly stripPrefix?: boolean
155
163
  readonly priority?: number
156
164
  readonly fallbackOn?: 404
157
- readonly opaque?: string
165
+ readonly opaque?: MountOptions<Env>["opaque"]
158
166
  }>
159
167
  /** Secret for **draft / preview mode** (see `enableDraft`). When set, a request carrying a valid
160
168
  * signed `__nifra_draft` cookie gets `ctx.draft === true` in loaders/actions (else always `false`).
@@ -204,8 +212,9 @@ export interface CreateWebAppOptions<Env = unknown> {
204
212
  readonly staticBoundaryCache?: import("../boundary.ts").StaticBoundaryCache
205
213
  /** Observe every loader/action failure - for error-reporting plugins (Sentry-style). Called for
206
214
  * real throws (not control-flow `Response`s like `redirect`), **before** the nearest `_error`
207
- * boundary renders / a soft-nav 500 / a rethrow - so it sees errors that the boundary would
208
- * otherwise hide. Observation only; its own throws are swallowed so a faulty reporter can't break
215
+ * boundary renders / a soft-nav 500 / a rethrow. A failure a boundary answers is also written to the
216
+ * server's `logger` (under its `errorLogDetail`) whether or not this is set, exactly as an unhandled
217
+ * error is. Observation only; its own throws are swallowed so a faulty reporter can't break
209
218
  * rendering. (`beforeLoader` is intentionally omitted - the core HTTP hooks already cover
210
219
  * pre-request work.) */
211
220
  readonly onLoaderError?: (
@@ -258,10 +267,14 @@ function backendMountOf<Env>(api: unknown): MountableApp<Env> | undefined {
258
267
  return mount
259
268
  }
260
269
 
261
- function evidenceProviderOf(value: unknown): BackendEvidenceProvider | undefined {
270
+ /** A provider a web app composes: it also takes the chain of web providers above it, which a
271
+ * provider that composes nothing ignores. */
272
+ type ComposedEvidenceProvider = (ancestry?: EvidenceAncestry) => ReturnType<BackendEvidenceProvider>
273
+
274
+ function evidenceProviderOf(value: unknown): ComposedEvidenceProvider | undefined {
262
275
  if ((typeof value !== "object" && typeof value !== "function") || value === null) return undefined
263
276
  const provider = (value as { [NIFRA_BACKEND_EVIDENCE]?: unknown })[NIFRA_BACKEND_EVIDENCE]
264
- return typeof provider === "function" ? (provider as BackendEvidenceProvider) : undefined
277
+ return typeof provider === "function" ? (provider as ComposedEvidenceProvider) : undefined
265
278
  }
266
279
 
267
280
  function mountPathPrefix(path: string): string {
@@ -274,6 +287,15 @@ function mountPathPrefix(path: string): string {
274
287
  return withoutWildcard.slice(0, end)
275
288
  }
276
289
 
290
+ /** The server's own error log, for page failures an `_error` boundary answers before it sees them. */
291
+ function boundaryErrorLog(
292
+ options: ServerOptions | undefined,
293
+ ): (error: unknown, request: Request) => void {
294
+ const logger = options?.logger ?? jsonLogger()
295
+ const detail = options?.errorLogDetail ?? "full"
296
+ return (error, request) => emitRequestErrorLog(logger, detail, error, { req: request })
297
+ }
298
+
277
299
  /**
278
300
  * Build a nifra app from a route manifest: every route SSRs its layout chain via `renderPage`,
279
301
  * and a wildcard catch-all renders `_404` (or a plain 404). Reuses @nifrajs/core's router +
@@ -306,6 +328,38 @@ export function createWebApp<Env = unknown>(
306
328
  // `beforeHandle`, assurance evidence) actually bind to them - see the `use` option. A caller
307
329
  // cannot do this after the fact: by the time this function returns, the routes are declared.
308
330
  options.use?.(app)
331
+ if (options.serverComponents !== undefined) {
332
+ const runtime = options.serverComponents
333
+ const match = createMatcher(
334
+ manifest.routes
335
+ .filter((route) => runtime.owns(route.id))
336
+ .map((route) => ({ routeId: route.id, pattern: route.pattern })),
337
+ )
338
+ const privateRoute = (request: Request): boolean =>
339
+ request.headers.has("x-nifra-component") || match(new URL(request.url).pathname) !== undefined
340
+ Object.defineProperty(app, Symbol.for("nifra.web.private-route"), { value: privateRoute })
341
+ app.onResponse((response, request) => {
342
+ if (!privateRoute(request)) return response
343
+ const headers = new Headers(response.headers)
344
+ headers.set("cache-control", "private, no-store")
345
+ const vary = new Set(
346
+ (headers.get("vary") ?? "")
347
+ .split(",")
348
+ .map((name) => name.trim().toLowerCase())
349
+ .filter(Boolean),
350
+ )
351
+ vary.add("x-nifra-component")
352
+ vary.add("x-nifra-data")
353
+ headers.set("vary", [...vary].join(", "))
354
+ headers.delete("x-nifra-isr-revalidate")
355
+ headers.delete("x-nifra-isr-tags")
356
+ return new Response(response.body, {
357
+ status: response.status,
358
+ statusText: response.statusText,
359
+ headers,
360
+ })
361
+ })
362
+ }
309
363
  // A resolver created by `createNonceResolver({ header })` carries a paired response hook. Register it
310
364
  // after caller middleware so its request-specific CSP value is the last default applied to a page.
311
365
  const nonceResponse = options.nonce?.onResponse
@@ -322,15 +376,15 @@ export function createWebApp<Env = unknown>(
322
376
  cacheChannel.enabled = true
323
377
  },
324
378
  })
325
- // Auto-mount the in-process backend over HTTP at `apiPrefix` (default `/api`), BEFORE page routing.
379
+ // Mount the in-process backend over HTTP at `apiPrefix`, when one is given, BEFORE page routing.
326
380
  // Core's pre-route mount seam runs before the page wildcard `/*`; parent request hooks run first,
327
381
  // child request hooks run inside the mount, and parent response hooks still wrap the result.
328
382
  // The mount handles every HTTP method and preserves backend 404 responses instead of letting the
329
383
  // page catch-all render them. We dispatch the same `Request` object (its body unread), so
330
384
  // streamed/large bodies pass through untouched. The backend defines full prefixed paths
331
- // (`server().post("/api/sync", …)`) unless `apiStrip` is enabled. `apiPrefix: ""` opts out;
332
- // a non-mountable `api` remains loader-only and pages-only apps keep the no-mount fast path.
333
- const apiPrefix = options.apiPrefix ?? "/api"
385
+ // (`server().post("/api/sync", …)`) unless `apiStrip` is enabled. Without a prefix, or with a
386
+ // non-mountable `api`, the backend is loader-only and pages-only apps keep the no-mount fast path.
387
+ const apiPrefix = options.apiPrefix ?? ""
334
388
  const apiStrip = options.apiStrip === true
335
389
  const mountedApi = backendMountOf<Env>(api)
336
390
  // Longest path first, so a more specific mount (`/api/auth`) is tried before a broader one (`/api`)
@@ -361,6 +415,22 @@ export function createWebApp<Env = unknown>(
361
415
  ? undefined
362
416
  : servedPrerenderedList(options.prerenderedPaths)
363
417
  const pageExecutor = createPageRequestExecutor<Env>({
418
+ ...(options.serverComponents === undefined
419
+ ? {}
420
+ : { serverComponents: options.serverComponents }),
421
+ ...(options.serverComponents === undefined
422
+ ? {}
423
+ : {
424
+ rerenderOwnerGET: async (
425
+ request: Request,
426
+ context: import("./page-execution.ts").PageRouteContext<Env>,
427
+ ) =>
428
+ app.fetch(request, {
429
+ env: context.env,
430
+ ...(context.waitUntil === undefined ? {} : { waitUntil: context.waitUntil }),
431
+ ...(context.clientIp === undefined ? {} : { clientIp: context.clientIp }),
432
+ }),
433
+ }),
364
434
  adapter,
365
435
  manifest,
366
436
  clientEntry,
@@ -384,18 +454,30 @@ export function createWebApp<Env = unknown>(
384
454
  ? {}
385
455
  : { nonce: cspNonceResolver(options.csp) as NonceResolver<Env>, csp: options.csp }),
386
456
  ...(options.onLoaderError === undefined ? {} : { onLoaderError: options.onLoaderError }),
457
+ logBoundaryError: boundaryErrorLog(options.server),
387
458
  cacheChannel,
388
459
  })
389
460
 
390
461
  for (const route of manifest.routes) {
391
- app.register("GET", route.pattern, undefined, pageExecutor.get(route))
392
- app.register("POST", route.pattern, undefined, pageExecutor.post(route))
462
+ const pattern = requestPattern(route.pattern)
463
+ app.register(
464
+ "GET",
465
+ pattern,
466
+ options.serverComponents?.owns(route.id)
467
+ ? { capabilities: options.serverComponents.capabilities(route.id) }
468
+ : undefined,
469
+ pageExecutor.get(route),
470
+ )
471
+ app.register("POST", pattern, undefined, pageExecutor.post(route))
393
472
  }
394
473
  // llms.txt & llms-full.txt describe what a visitor can reach: the pages, and the backend only when it
395
474
  // is served over HTTP - a backend the loaders call in-process has no public surface to describe. An
396
475
  // app route at either path takes precedence, and each text is built once per app.
397
476
  if (options.llmsTxt !== false) {
398
- const llmsOptions = { includeLocalGuidelines: options.publishLocalGuidelines === true }
477
+ const llmsOptions = {
478
+ includeLocalGuidelines: options.publishLocalGuidelines === true,
479
+ apiPathPrefix: apiStrip ? mountPathPrefix(apiPrefix) : "",
480
+ }
399
481
  const publicApi = mountedApi !== undefined && apiPrefix !== "" ? api : undefined
400
482
  for (const [path, full] of [
401
483
  ["/llms.txt", false],
@@ -439,13 +521,9 @@ export function createWebApp<Env = unknown>(
439
521
  // absent.
440
522
  app.register("GET", "/*", undefined, pageExecutor.fallback)
441
523
 
442
- let evidenceComposing = false
443
- const evidenceProvider: BackendEvidenceProvider = async () => {
444
- if (evidenceComposing) {
445
- throw new Error("[nifra/web] cyclic composed assurance evidence mount")
446
- }
447
- evidenceComposing = true
448
- try {
524
+ const evidenceProvider: ComposedEvidenceProvider = async (passed) => {
525
+ const { traverseEvidence } = await import("./evidence-traversal.ts")
526
+ return traverseEvidence(evidenceProvider, passed, async (ancestry) => {
449
527
  const { composeProjectEvidence, snapshotProjectEvidence } = await import(
450
528
  "@nifrajs/core/evidence"
451
529
  )
@@ -464,7 +542,7 @@ export function createWebApp<Env = unknown>(
464
542
  )
465
543
  }
466
544
  parts.push({
467
- evidence: await provider.call(mount.app),
545
+ evidence: await provider.call(mount.app, ancestry),
468
546
  ...(mount.stripPrefix === true ? { pathPrefix: mountPathPrefix(mount.path) } : {}),
469
547
  })
470
548
  }
@@ -476,14 +554,13 @@ export function createWebApp<Env = unknown>(
476
554
  )
477
555
  }
478
556
  parts.push({
479
- evidence: await provider.call(api),
557
+ evidence: await provider.call(api, ancestry),
558
+ // biome-ignore lint/plugin/noConditionalEmptyObjectSpread: Evidence paths acquire a prefix only when the mounted backend strips it.
480
559
  ...(apiStrip ? { pathPrefix: mountPathPrefix(apiPrefix) } : {}),
481
560
  })
482
561
  }
483
562
  return composeProjectEvidence(parts)
484
- } finally {
485
- evidenceComposing = false
486
- }
563
+ })
487
564
  }
488
565
  Object.defineProperty(app, NIFRA_BACKEND_EVIDENCE, {
489
566
  configurable: false,
@@ -491,6 +568,8 @@ export function createWebApp<Env = unknown>(
491
568
  value: evidenceProvider,
492
569
  writable: false,
493
570
  })
571
+ // Core's dev-time hook audit reads this: an order-scoped hook added from here on skips every page.
572
+ Object.defineProperty(app, Symbol.for("nifra.web.pagesDeclared"), { value: true })
494
573
 
495
574
  return app
496
575
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * `t.date()` across the wire, server half. JSON has no dates, so the output guard hands the page a
3
+ * `wireDate()`: a real `Date` for the server render that serializes as `{ "__nifra_date": iso }`. The
4
+ * browser half (`revive-dates.ts`) rebuilds those tags, so both renders see the same value.
5
+ */
6
+ import { DATE_TAG } from "./revive-dates.ts"
7
+
8
+ const WIRE_JSON: PropertyDescriptor = {
9
+ value(this: Date): Readonly<Record<string, string>> {
10
+ return { [DATE_TAG]: this.toISOString() }
11
+ },
12
+ }
13
+
14
+ /** A `Date` that crosses JSON tagged, so the browser can tell it from a string that looks like one. */
15
+ export const wireDate = (time: number): Date =>
16
+ Object.defineProperty(new Date(time), "toJSON", WIRE_JSON)
@@ -60,6 +60,38 @@ export interface ZoneLeak {
60
60
  readonly reason: string
61
61
  /** Shortest import path from an entry: `[entry, ...as-written specifiers]`. */
62
62
  readonly chain: readonly string[]
63
+ /** The undeclared package the refusal is about; its files share one line in the report. */
64
+ readonly package?: string
65
+ }
66
+
67
+ const leakLine = (leak: ZoneLeak, module = leak.module): string =>
68
+ ` - ${module}: ${leak.reason}${leak.chain.length > 1 ? `\n via ${leak.chain.join(" → ")}` : ""}`
69
+
70
+ /** One line per leak, except that the files of one undeclared package share a single line. */
71
+ function leakLines(leaks: readonly ZoneLeak[]): string[] {
72
+ const byPackage = new Map<string, ZoneLeak[]>()
73
+ for (const leak of leaks) {
74
+ if (leak.package === undefined) continue
75
+ const group = byPackage.get(leak.package)
76
+ if (group === undefined) byPackage.set(leak.package, [leak])
77
+ else group.push(leak)
78
+ }
79
+ const lines: string[] = []
80
+ for (const leak of leaks) {
81
+ const group = leak.package === undefined ? undefined : byPackage.get(leak.package)
82
+ if (group === undefined || group.length < 2) {
83
+ lines.push(leakLine(leak))
84
+ continue
85
+ }
86
+ if (group[0] !== leak) continue
87
+ const shown = group
88
+ .slice(0, 3)
89
+ .map((item) => item.module)
90
+ .join(", ")
91
+ const more = group.length > 3 ? ` and ${group.length - 3} more` : ""
92
+ lines.push(leakLine(leak, `${group.length} files (${shown}${more})`))
93
+ }
94
+ return lines
63
95
  }
64
96
 
65
97
  export interface ClientGraphVerdict {
@@ -71,6 +103,8 @@ export interface ClientGraphVerdict {
71
103
  }
72
104
 
73
105
  export interface VerifyClientGraphOptions {
106
+ /** Exact modules certified by the component compiler in this generation. */
107
+ readonly componentModules?: ReadonlySet<string>
74
108
  readonly classifier: ZoneClassifier
75
109
  readonly sourceOf: (id: string) => ModuleSource
76
110
  /** The private-env denial for a browser-code file (see `privateEnvCheck`); unchecked when absent. */
@@ -192,6 +226,7 @@ export function verifyClientGraph(
192
226
  }
193
227
 
194
228
  const denied = new Map<string, string>()
229
+ const deniedPackage = new Map<string, string>()
195
230
  for (const id of [...Object.keys(graph.modules), ...dropped]) {
196
231
  const source = sourceOf(id)
197
232
  const file = source.kind === "builtin" ? undefined : source.file
@@ -202,6 +237,8 @@ export function verifyClientGraph(
202
237
  if (reason === undefined && BROWSER_CODE.has(classification.zone))
203
238
  reason = options.privateEnv?.(file)
204
239
  if (reason !== undefined) denied.set(id, reason)
240
+ if (classification.zone === "error" && classification.package !== undefined)
241
+ deniedPackage.set(id, classification.package)
205
242
  }
206
243
 
207
244
  // Chains start at a route file where they can: a lazily split module is an entry of its own chunk,
@@ -225,11 +262,15 @@ export function verifyClientGraph(
225
262
  : entries
226
263
  const chains = denied.size === 0 ? new Map<string, string[]>() : shortestChains(roots, edges)
227
264
  const leaks = [...denied]
228
- .map(([id, reason]) => ({
229
- module: labelOf(id),
230
- reason,
231
- chain: (chains.get(id) ?? [id]).map((label, i) => (i === 0 ? labelOf(label) : label)),
232
- }))
265
+ .map(([id, reason]): ZoneLeak => {
266
+ const leak = {
267
+ module: labelOf(id),
268
+ reason,
269
+ chain: (chains.get(id) ?? [id]).map((label, i) => (i === 0 ? labelOf(label) : label)),
270
+ }
271
+ const pkg = deniedPackage.get(id)
272
+ return pkg === undefined ? leak : { ...leak, package: pkg }
273
+ })
233
274
  .sort((a, b) => codeUnitOrder(a.module, b.module))
234
275
 
235
276
  const misplacedExports: string[] = []
@@ -333,10 +374,14 @@ export function verifyServerGraph(
333
374
  readonly module: string
334
375
  readonly reason: string
335
376
  readonly tail?: string
377
+ readonly package?: string
336
378
  }> = []
337
379
  for (const [id, module] of Object.entries(graph.modules)) {
338
380
  const from = classOf(id)
339
- if (from?.zone === "error") found.push({ at: id, module: labelOf(id), reason: from.reason })
381
+ if (from?.zone === "error") {
382
+ const at = { at: id, module: labelOf(id), reason: from.reason }
383
+ found.push(from.package === undefined ? at : { ...at, package: from.package })
384
+ }
340
385
  const file = fileOf(id)
341
386
  const env =
342
387
  from !== undefined && file !== undefined && BROWSER_CODE.has(from.zone)
@@ -364,7 +409,9 @@ export function verifyServerGraph(
364
409
  const to = classOf(target)
365
410
  if (from === undefined || to === undefined || from.zone === "error" || to.zone === "error")
366
411
  continue
367
- if (!importAllowed(from.zone, to.zone)) {
412
+ const componentBridge =
413
+ to.zone === "component" && options.componentModules?.has(fileOf(target) ?? "") === true
414
+ if (!componentBridge && !importAllowed(from.zone, to.zone)) {
368
415
  found.push({
369
416
  at: id,
370
417
  module: labelOf(target),
@@ -386,9 +433,10 @@ export function verifyServerGraph(
386
433
  ]
387
434
  const chains = shortestChains(entries, edges)
388
435
  const violations = found
389
- .map(({ at, module, reason, tail }) => {
436
+ .map(({ at, module, reason, tail, package: pkg }): ZoneLeak => {
390
437
  const chain = (chains.get(at) ?? [at]).map((label, i) => (i === 0 ? labelOf(label) : label))
391
- return { module, reason, chain: tail === undefined ? chain : [...chain, tail] }
438
+ const violation = { module, reason, chain: tail === undefined ? chain : [...chain, tail] }
439
+ return pkg === undefined ? violation : { ...violation, package: pkg }
392
440
  })
393
441
  .sort((a, b) => codeUnitOrder(a.module, b.module) || codeUnitOrder(a.reason, b.reason))
394
442
  return { violations }
@@ -397,11 +445,7 @@ export function verifyServerGraph(
397
445
  /** The build-failing message for a server graph that breaks the zone rules. */
398
446
  export function formatServerGraphVerdict(verdict: ServerGraphVerdict): string | undefined {
399
447
  if (verdict.violations.length === 0) return undefined
400
- const lines = verdict.violations.map(
401
- (v) =>
402
- ` - ${v.module}: ${v.reason}${v.chain.length > 1 ? `\n via ${v.chain.join(" → ")}` : ""}`,
403
- )
404
- return `[nifra/web] the server build breaks the zone rules:\n${lines.join("\n")}`
448
+ return `[nifra/web] the server build breaks the zone rules:\n${leakLines(verdict.violations).join("\n")}`
405
449
  }
406
450
 
407
451
  /** One emitted file, as the accounting sees it. */
@@ -561,12 +605,8 @@ const indent = (lines: readonly string[]): string => lines.map((line) => ` - ${
561
605
  export function formatClientGraphVerdict(verdict: ClientGraphVerdict): string | undefined {
562
606
  const sections: string[] = []
563
607
  if (verdict.leaks.length > 0) {
564
- const lines = verdict.leaks.map(
565
- (leak) =>
566
- ` - ${leak.module}: ${leak.reason}${leak.chain.length > 1 ? `\n via ${leak.chain.join(" → ")}` : ""}`,
567
- )
568
608
  sections.push(
569
- `[nifra/web] the browser build reached code that may not ship to a browser:\n${lines.join("\n")}\n` +
609
+ `[nifra/web] the browser build reached code that may not ship to a browser:\n${leakLines(verdict.leaks).join("\n")}\n` +
570
610
  "Reach backend code through a route's backend half (x.backend.ts) or a *.fn.ts server function; " +
571
611
  "code both sides need belongs in shared/.",
572
612
  )