@nifrajs/web 3.3.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 (407) 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 +35 -6
  10. package/dist/build-plan.d.ts.map +1 -1
  11. package/dist/build-plan.js +89 -35
  12. package/dist/build-plan.js.map +1 -1
  13. package/dist/build-vite.d.ts +18 -0
  14. package/dist/build-vite.d.ts.map +1 -1
  15. package/dist/build-vite.js +97 -17
  16. package/dist/build-vite.js.map +1 -1
  17. package/dist/build.d.ts +72 -23
  18. package/dist/build.d.ts.map +1 -1
  19. package/dist/build.js +411 -98
  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 +21 -1
  28. package/dist/client.d.ts.map +1 -1
  29. package/dist/client.js +226 -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/css-contract.d.ts +21 -0
  40. package/dist/css-contract.d.ts.map +1 -0
  41. package/dist/css-contract.js +28 -0
  42. package/dist/css-contract.js.map +1 -0
  43. package/dist/dev-client.d.ts +76 -0
  44. package/dist/dev-client.d.ts.map +1 -0
  45. package/dist/dev-client.js +280 -0
  46. package/dist/dev-client.js.map +1 -0
  47. package/dist/dev-diagnostics.d.ts +26 -2
  48. package/dist/dev-diagnostics.d.ts.map +1 -1
  49. package/dist/dev-diagnostics.js +38 -7
  50. package/dist/dev-diagnostics.js.map +1 -1
  51. package/dist/dev-error.d.ts +4 -13
  52. package/dist/dev-error.d.ts.map +1 -1
  53. package/dist/dev-error.js +46 -4
  54. package/dist/dev-error.js.map +1 -1
  55. package/dist/dev-feed.d.ts +298 -0
  56. package/dist/dev-feed.d.ts.map +1 -0
  57. package/dist/dev-feed.js +797 -0
  58. package/dist/dev-feed.js.map +1 -0
  59. package/dist/dev-indicator.d.ts +38 -0
  60. package/dist/dev-indicator.d.ts.map +1 -0
  61. package/dist/dev-indicator.js +93 -0
  62. package/dist/dev-indicator.js.map +1 -0
  63. package/dist/dev-session.d.ts +77 -0
  64. package/dist/dev-session.d.ts.map +1 -0
  65. package/dist/dev-session.js +580 -0
  66. package/dist/dev-session.js.map +1 -0
  67. package/dist/dev-sourcemap.d.ts +27 -0
  68. package/dist/dev-sourcemap.d.ts.map +1 -0
  69. package/dist/dev-sourcemap.js +235 -0
  70. package/dist/dev-sourcemap.js.map +1 -0
  71. package/dist/dev-ssr-graph.d.ts.map +1 -1
  72. package/dist/dev-ssr-graph.js +13 -5
  73. package/dist/dev-ssr-graph.js.map +1 -1
  74. package/dist/dev.d.ts +15 -2
  75. package/dist/dev.d.ts.map +1 -1
  76. package/dist/dev.js +69 -44
  77. package/dist/dev.js.map +1 -1
  78. package/dist/diagnostic-catalog.d.ts +35 -0
  79. package/dist/diagnostic-catalog.d.ts.map +1 -0
  80. package/dist/diagnostic-catalog.js +203 -0
  81. package/dist/diagnostic-catalog.js.map +1 -0
  82. package/dist/diagnostic-prompt.d.ts +49 -0
  83. package/dist/diagnostic-prompt.d.ts.map +1 -0
  84. package/dist/diagnostic-prompt.js +303 -0
  85. package/dist/diagnostic-prompt.js.map +1 -0
  86. package/dist/diagnostic.d.ts +17 -23
  87. package/dist/diagnostic.d.ts.map +1 -1
  88. package/dist/diagnostic.js +31 -39
  89. package/dist/diagnostic.js.map +1 -1
  90. package/dist/draft.d.ts.map +1 -1
  91. package/dist/draft.js +4 -1
  92. package/dist/draft.js.map +1 -1
  93. package/dist/fn.d.ts +5 -0
  94. package/dist/fn.d.ts.map +1 -1
  95. package/dist/fn.js +13 -3
  96. package/dist/fn.js.map +1 -1
  97. package/dist/fs.d.ts.map +1 -1
  98. package/dist/fs.js +4 -2
  99. package/dist/fs.js.map +1 -1
  100. package/dist/index.d.ts +15 -15
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/index.js +13 -12
  103. package/dist/index.js.map +1 -1
  104. package/dist/internal/cdn/cloudflare.d.ts +48 -0
  105. package/dist/internal/cdn/cloudflare.d.ts.map +1 -0
  106. package/dist/internal/cdn/cloudflare.js +94 -0
  107. package/dist/internal/cdn/cloudflare.js.map +1 -0
  108. package/dist/internal/cdn/core.d.ts +91 -0
  109. package/dist/internal/cdn/core.d.ts.map +1 -0
  110. package/dist/internal/cdn/core.js +220 -0
  111. package/dist/internal/cdn/core.js.map +1 -0
  112. package/dist/internal/cdn/fastly.d.ts +17 -0
  113. package/dist/internal/cdn/fastly.d.ts.map +1 -0
  114. package/dist/internal/cdn/fastly.js +43 -0
  115. package/dist/internal/cdn/fastly.js.map +1 -0
  116. package/dist/internal/cdn/vercel.d.ts +31 -0
  117. package/dist/internal/cdn/vercel.d.ts.map +1 -0
  118. package/dist/internal/cdn/vercel.js +56 -0
  119. package/dist/internal/cdn/vercel.js.map +1 -0
  120. package/dist/internal/code-unit-order.d.ts +4 -0
  121. package/dist/internal/code-unit-order.d.ts.map +1 -0
  122. package/dist/internal/code-unit-order.js +6 -0
  123. package/dist/internal/code-unit-order.js.map +1 -0
  124. package/dist/internal/codegen.d.ts +3 -0
  125. package/dist/internal/codegen.d.ts.map +1 -1
  126. package/dist/internal/codegen.js +151 -30
  127. package/dist/internal/codegen.js.map +1 -1
  128. package/dist/internal/dev-csp.d.ts +24 -0
  129. package/dist/internal/dev-csp.d.ts.map +1 -0
  130. package/dist/internal/dev-csp.js +160 -0
  131. package/dist/internal/dev-csp.js.map +1 -0
  132. package/dist/internal/dev-reserved.d.ts +25 -0
  133. package/dist/internal/dev-reserved.d.ts.map +1 -0
  134. package/dist/internal/dev-reserved.js +39 -0
  135. package/dist/internal/dev-reserved.js.map +1 -0
  136. package/dist/internal/html-spans.d.ts +10 -0
  137. package/dist/internal/html-spans.d.ts.map +1 -0
  138. package/dist/internal/html-spans.js +46 -0
  139. package/dist/internal/html-spans.js.map +1 -0
  140. package/dist/internal/loading-runtime.d.ts +44 -0
  141. package/dist/internal/loading-runtime.d.ts.map +1 -0
  142. package/dist/internal/loading-runtime.js +206 -0
  143. package/dist/internal/loading-runtime.js.map +1 -0
  144. package/dist/internal/matches-runtime.d.ts +8 -0
  145. package/dist/internal/matches-runtime.d.ts.map +1 -0
  146. package/dist/internal/matches-runtime.js +61 -0
  147. package/dist/internal/matches-runtime.js.map +1 -0
  148. package/dist/internal/mount-shadow.d.ts +36 -0
  149. package/dist/internal/mount-shadow.d.ts.map +1 -0
  150. package/dist/internal/mount-shadow.js +65 -0
  151. package/dist/internal/mount-shadow.js.map +1 -0
  152. package/dist/internal/node-builtins.d.ts +3 -0
  153. package/dist/internal/node-builtins.d.ts.map +1 -0
  154. package/dist/internal/node-builtins.js +79 -0
  155. package/dist/internal/node-builtins.js.map +1 -0
  156. package/dist/internal/output-guard.d.ts +65 -0
  157. package/dist/internal/output-guard.d.ts.map +1 -0
  158. package/dist/internal/output-guard.js +630 -0
  159. package/dist/internal/output-guard.js.map +1 -0
  160. package/dist/internal/page-execution.d.ts +69 -0
  161. package/dist/internal/page-execution.d.ts.map +1 -0
  162. package/dist/internal/page-execution.js +1021 -0
  163. package/dist/internal/page-execution.js.map +1 -0
  164. package/dist/internal/parity.d.ts +21 -0
  165. package/dist/internal/parity.d.ts.map +1 -1
  166. package/dist/internal/parity.js +104 -22
  167. package/dist/internal/parity.js.map +1 -1
  168. package/dist/internal/private-env.d.ts +12 -0
  169. package/dist/internal/private-env.d.ts.map +1 -0
  170. package/dist/internal/private-env.js +335 -0
  171. package/dist/internal/private-env.js.map +1 -0
  172. package/dist/internal/public-url.d.ts +3 -0
  173. package/dist/internal/public-url.d.ts.map +1 -0
  174. package/dist/internal/public-url.js +18 -0
  175. package/dist/internal/public-url.js.map +1 -0
  176. package/dist/internal/render-document.d.ts +37 -21
  177. package/dist/internal/render-document.d.ts.map +1 -1
  178. package/dist/internal/render-document.js +104 -65
  179. package/dist/internal/render-document.js.map +1 -1
  180. package/dist/internal/request-url.d.ts +3 -0
  181. package/dist/internal/request-url.d.ts.map +1 -0
  182. package/dist/internal/request-url.js +14 -0
  183. package/dist/internal/request-url.js.map +1 -0
  184. package/dist/internal/response-controls.d.ts +75 -0
  185. package/dist/internal/response-controls.d.ts.map +1 -0
  186. package/dist/internal/response-controls.js +256 -0
  187. package/dist/internal/response-controls.js.map +1 -0
  188. package/dist/internal/runtime-contract.d.ts +11 -16
  189. package/dist/internal/runtime-contract.d.ts.map +1 -1
  190. package/dist/internal/runtime-contract.js.map +1 -1
  191. package/dist/internal/secret-scan.d.ts +104 -0
  192. package/dist/internal/secret-scan.d.ts.map +1 -0
  193. package/dist/internal/secret-scan.js +485 -0
  194. package/dist/internal/secret-scan.js.map +1 -0
  195. package/dist/internal/server-boundary.d.ts +6 -29
  196. package/dist/internal/server-boundary.d.ts.map +1 -1
  197. package/dist/internal/server-boundary.js +6 -66
  198. package/dist/internal/server-boundary.js.map +1 -1
  199. package/dist/internal/source-map.d.ts +47 -0
  200. package/dist/internal/source-map.d.ts.map +1 -0
  201. package/dist/internal/source-map.js +225 -0
  202. package/dist/internal/source-map.js.map +1 -0
  203. package/dist/internal/target-compat.d.ts +27 -0
  204. package/dist/internal/target-compat.d.ts.map +1 -0
  205. package/dist/internal/target-compat.js +77 -0
  206. package/dist/internal/target-compat.js.map +1 -0
  207. package/dist/internal/timing-safe-equal.d.ts +6 -0
  208. package/dist/internal/timing-safe-equal.d.ts.map +1 -1
  209. package/dist/internal/timing-safe-equal.js +10 -0
  210. package/dist/internal/timing-safe-equal.js.map +1 -1
  211. package/dist/internal/vitals-runtime.d.ts +18 -0
  212. package/dist/internal/vitals-runtime.d.ts.map +1 -0
  213. package/dist/internal/vitals-runtime.js +38 -0
  214. package/dist/internal/vitals-runtime.js.map +1 -0
  215. package/dist/internal/web-app.d.ts +56 -8
  216. package/dist/internal/web-app.d.ts.map +1 -1
  217. package/dist/internal/web-app.js +215 -772
  218. package/dist/internal/web-app.js.map +1 -1
  219. package/dist/internal/zone-graph.d.ts +89 -0
  220. package/dist/internal/zone-graph.d.ts.map +1 -0
  221. package/dist/internal/zone-graph.js +492 -0
  222. package/dist/internal/zone-graph.js.map +1 -0
  223. package/dist/isr.d.ts +83 -6
  224. package/dist/isr.d.ts.map +1 -1
  225. package/dist/isr.js +301 -39
  226. package/dist/isr.js.map +1 -1
  227. package/dist/llms-txt.d.ts +3 -0
  228. package/dist/llms-txt.d.ts.map +1 -1
  229. package/dist/llms-txt.js +37 -9
  230. package/dist/llms-txt.js.map +1 -1
  231. package/dist/manifest.d.ts +221 -14
  232. package/dist/manifest.d.ts.map +1 -1
  233. package/dist/manifest.js +386 -9
  234. package/dist/manifest.js.map +1 -1
  235. package/dist/module-graph.d.ts +26 -5
  236. package/dist/module-graph.d.ts.map +1 -1
  237. package/dist/module-graph.js +17 -7
  238. package/dist/module-graph.js.map +1 -1
  239. package/dist/navigation.d.ts +17 -3
  240. package/dist/navigation.d.ts.map +1 -1
  241. package/dist/navigation.js +11 -5
  242. package/dist/navigation.js.map +1 -1
  243. package/dist/nonce.d.ts +47 -0
  244. package/dist/nonce.d.ts.map +1 -0
  245. package/dist/nonce.js +63 -0
  246. package/dist/nonce.js.map +1 -0
  247. package/dist/plugins/css-modules.d.ts.map +1 -1
  248. package/dist/plugins/css-modules.js +4 -3
  249. package/dist/plugins/css-modules.js.map +1 -1
  250. package/dist/plugins/kit.d.ts +9 -0
  251. package/dist/plugins/kit.d.ts.map +1 -1
  252. package/dist/plugins/kit.js +18 -0
  253. package/dist/plugins/kit.js.map +1 -1
  254. package/dist/plugins/svg.d.ts +22 -3
  255. package/dist/plugins/svg.d.ts.map +1 -1
  256. package/dist/plugins/svg.js +112 -1
  257. package/dist/plugins/svg.js.map +1 -1
  258. package/dist/plugins/vite-leak-guard.d.ts +107 -3
  259. package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
  260. package/dist/plugins/vite-leak-guard.js +378 -17
  261. package/dist/plugins/vite-leak-guard.js.map +1 -1
  262. package/dist/plugins/vite-zone-guard.d.ts +61 -0
  263. package/dist/plugins/vite-zone-guard.d.ts.map +1 -0
  264. package/dist/plugins/vite-zone-guard.js +208 -0
  265. package/dist/plugins/vite-zone-guard.js.map +1 -0
  266. package/dist/plugins/zone-guard.d.ts +27 -0
  267. package/dist/plugins/zone-guard.d.ts.map +1 -0
  268. package/dist/plugins/zone-guard.js +72 -0
  269. package/dist/plugins/zone-guard.js.map +1 -0
  270. package/dist/prerender.d.ts +10 -0
  271. package/dist/prerender.d.ts.map +1 -1
  272. package/dist/prerender.js +35 -3
  273. package/dist/prerender.js.map +1 -1
  274. package/dist/public-dir.d.ts +9 -0
  275. package/dist/public-dir.d.ts.map +1 -1
  276. package/dist/public-dir.js +131 -48
  277. package/dist/public-dir.js.map +1 -1
  278. package/dist/pwa-manifest.d.ts +68 -0
  279. package/dist/pwa-manifest.d.ts.map +1 -0
  280. package/dist/pwa-manifest.js +178 -0
  281. package/dist/pwa-manifest.js.map +1 -0
  282. package/dist/query.js +4 -4
  283. package/dist/query.js.map +1 -1
  284. package/dist/render-seam.d.ts +42 -2
  285. package/dist/render-seam.d.ts.map +1 -1
  286. package/dist/render-seam.js +6 -0
  287. package/dist/render-seam.js.map +1 -1
  288. package/dist/route-manifest.d.ts +3 -0
  289. package/dist/route-manifest.d.ts.map +1 -1
  290. package/dist/route-manifest.js +26 -3
  291. package/dist/route-manifest.js.map +1 -1
  292. package/dist/route-types.d.ts +24 -0
  293. package/dist/route-types.d.ts.map +1 -0
  294. package/dist/route-types.js +195 -0
  295. package/dist/route-types.js.map +1 -0
  296. package/dist/router.d.ts +28 -6
  297. package/dist/router.d.ts.map +1 -1
  298. package/dist/router.js +129 -40
  299. package/dist/router.js.map +1 -1
  300. package/dist/trusted-html.d.ts +34 -0
  301. package/dist/trusted-html.d.ts.map +1 -0
  302. package/dist/trusted-html.js +35 -0
  303. package/dist/trusted-html.js.map +1 -0
  304. package/dist/vitals.d.ts +33 -0
  305. package/dist/vitals.d.ts.map +1 -0
  306. package/dist/vitals.js +16 -0
  307. package/dist/vitals.js.map +1 -0
  308. package/dist/vite.d.ts +16 -1
  309. package/dist/vite.d.ts.map +1 -1
  310. package/dist/vite.js +187 -36
  311. package/dist/vite.js.map +1 -1
  312. package/dist/zones.d.ts +70 -0
  313. package/dist/zones.d.ts.map +1 -0
  314. package/dist/zones.js +365 -0
  315. package/dist/zones.js.map +1 -0
  316. package/package.json +67 -14
  317. package/src/backend-only.ts +16 -0
  318. package/src/boundary.ts +18 -5
  319. package/src/build-plan.ts +113 -36
  320. package/src/build-vite.ts +124 -15
  321. package/src/build.ts +535 -102
  322. package/src/bun-dev-entry.ts +1 -1
  323. package/src/cdn.ts +216 -0
  324. package/src/client.ts +251 -18
  325. package/src/conformance.ts +16 -3
  326. package/src/csp.ts +194 -0
  327. package/src/css-contract.ts +44 -0
  328. package/src/dev-client.ts +349 -0
  329. package/src/dev-diagnostics.ts +60 -7
  330. package/src/dev-error.ts +49 -5
  331. package/src/dev-feed.ts +1140 -0
  332. package/src/dev-indicator.ts +113 -0
  333. package/src/dev-session.ts +729 -0
  334. package/src/dev-sourcemap.ts +281 -0
  335. package/src/dev-ssr-graph.ts +16 -5
  336. package/src/dev.ts +86 -43
  337. package/src/diagnostic-catalog.ts +243 -0
  338. package/src/diagnostic-prompt.ts +355 -0
  339. package/src/diagnostic.ts +63 -66
  340. package/src/draft.ts +4 -1
  341. package/src/fn.ts +19 -3
  342. package/src/fs.ts +13 -2
  343. package/src/index.ts +57 -10
  344. package/src/internal/cdn/cloudflare.ts +146 -0
  345. package/src/internal/cdn/core.ts +316 -0
  346. package/src/internal/cdn/fastly.ts +62 -0
  347. package/src/internal/cdn/vercel.ts +95 -0
  348. package/src/internal/code-unit-order.ts +5 -0
  349. package/src/internal/codegen.ts +180 -29
  350. package/src/internal/dev-csp.ts +176 -0
  351. package/src/internal/dev-reserved.ts +52 -0
  352. package/src/internal/html-spans.ts +50 -0
  353. package/src/internal/loading-runtime.ts +244 -0
  354. package/src/internal/matches-runtime.ts +62 -0
  355. package/src/internal/mount-shadow.ts +88 -0
  356. package/src/internal/node-builtins.ts +79 -0
  357. package/src/internal/output-guard.ts +701 -0
  358. package/src/internal/page-execution.ts +1371 -0
  359. package/src/internal/parity.ts +150 -36
  360. package/src/internal/private-env.ts +334 -0
  361. package/src/internal/public-url.ts +22 -0
  362. package/src/internal/render-document.ts +153 -73
  363. package/src/internal/request-url.ts +14 -0
  364. package/src/internal/response-controls.ts +284 -0
  365. package/src/internal/runtime-contract.ts +10 -15
  366. package/src/internal/secret-scan.ts +623 -0
  367. package/src/internal/server-boundary.ts +6 -69
  368. package/src/internal/source-map.ts +256 -0
  369. package/src/internal/target-compat.ts +92 -0
  370. package/src/internal/timing-safe-equal.ts +13 -0
  371. package/src/internal/vitals-runtime.ts +53 -0
  372. package/src/internal/web-app.ts +290 -879
  373. package/src/internal/zone-graph.ts +593 -0
  374. package/src/isr.ts +366 -39
  375. package/src/llms-txt.ts +44 -8
  376. package/src/manifest.ts +671 -17
  377. package/src/module-graph.ts +53 -9
  378. package/src/navigation.ts +29 -6
  379. package/src/nonce.ts +104 -0
  380. package/src/plugins/css-modules.ts +5 -3
  381. package/src/plugins/kit.ts +24 -0
  382. package/src/plugins/svg.ts +135 -4
  383. package/src/plugins/vite-leak-guard.ts +529 -16
  384. package/src/plugins/vite-zone-guard.ts +263 -0
  385. package/src/plugins/zone-guard.ts +110 -0
  386. package/src/prerender.ts +49 -3
  387. package/src/public-dir.ts +135 -49
  388. package/src/pwa-manifest.ts +226 -0
  389. package/src/query.ts +4 -4
  390. package/src/render-seam.ts +46 -1
  391. package/src/route-manifest.ts +15 -3
  392. package/src/route-types.ts +264 -0
  393. package/src/router.ts +163 -63
  394. package/src/trusted-html.ts +44 -0
  395. package/src/vitals.ts +44 -0
  396. package/src/vite.ts +207 -36
  397. package/src/zones.ts +430 -0
  398. package/dist/plugins/vite-server-only.d.ts +0 -49
  399. package/dist/plugins/vite-server-only.d.ts.map +0 -1
  400. package/dist/plugins/vite-server-only.js +0 -57
  401. package/dist/plugins/vite-server-only.js.map +0 -1
  402. package/dist/server-only.d.ts +0 -21
  403. package/dist/server-only.d.ts.map +0 -1
  404. package/dist/server-only.js +0 -25
  405. package/dist/server-only.js.map +0 -1
  406. package/src/plugins/vite-server-only.ts +0 -75
  407. package/src/server-only.ts +0 -25
@@ -0,0 +1,284 @@
1
+ import {
2
+ type CookieOptions,
3
+ type ResponseResult,
4
+ status as statusResult,
5
+ } from "@nifrajs/core/server"
6
+ import type { LoaderResponseControls } from "../manifest.ts"
7
+ import { DATA_HEADER } from "../router.ts"
8
+ import { isResponseResult } from "./render-document.ts"
9
+
10
+ /** The serving app's response controls - the structural subset of a route handler's `c.set` used here. */
11
+ export interface CoreResponseControls {
12
+ readonly headers: Record<string, string>
13
+ cookie(name: string, value: string, options?: CookieOptions): void
14
+ deleteCookie(name: string, options?: Pick<CookieOptions, "path" | "domain">): void
15
+ }
16
+
17
+ export const PRIVATE_NO_STORE = "private, no-store"
18
+
19
+ /**
20
+ * Headers of a navigation data response. It shares its URL with the document and differs only by a
21
+ * request header, so it must never be stored by a cache keyed on the URL alone: a stored data payload
22
+ * would be served in place of the page.
23
+ */
24
+ export const DATA_RESPONSE_HEADERS: Readonly<Record<string, string>> = Object.freeze({
25
+ "cache-control": PRIVATE_NO_STORE,
26
+ vary: DATA_HEADER,
27
+ })
28
+
29
+ /**
30
+ * Merge order of the header writers: middleware root to leaf (`MIDDLEWARE_SCOPE + i`), layouts root
31
+ * to leaf (`i`), then the page loader, then the action.
32
+ */
33
+ export const PAGE_SCOPE = 1e9
34
+ export const ACTION_SCOPE = PAGE_SCOPE + 1
35
+ export const MIDDLEWARE_SCOPE = -PAGE_SCOPE
36
+
37
+ // RFC 9110 `token` and `field-value` (visible ASCII, space, tab, obs-text). A value outside this set
38
+ // could split the header block or be rejected by the transport after the loader already ran.
39
+ const TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/
40
+ const FIELD_VALUE = /^[\t\x20-\x7e\x80-\xff]*$/
41
+
42
+ const FRAMEWORK_PREFIX = "x-nifra-"
43
+
44
+ const TRANSPORT_OWNED = new Set([
45
+ "connection",
46
+ "content-encoding",
47
+ "content-length",
48
+ "keep-alive",
49
+ "te",
50
+ "trailer",
51
+ "transfer-encoding",
52
+ "upgrade",
53
+ ])
54
+
55
+ const reservedReason = (name: string): string | undefined => {
56
+ if (name === "set-cookie") return "use ctx.set.cookie(), which applies the secure cookie defaults"
57
+ if (name === "location") return "return redirect() instead"
58
+ if (name === "content-type") return "the framework sets it for the representation it renders"
59
+ if (TRANSPORT_OWNED.has(name)) return "the transport owns it"
60
+ if (name.startsWith(FRAMEWORK_PREFIX)) return "the x-nifra- prefix is reserved for the framework"
61
+ return undefined
62
+ }
63
+
64
+ /** `existing` with the data-request header added, so a cache keeps the document and its data apart. */
65
+ export function varyOnDataHeader(existing: string | null | undefined): string {
66
+ if (existing === null || existing === undefined) return DATA_HEADER
67
+ let empty = true
68
+ for (const part of existing.split(",")) {
69
+ const name = part.trim().toLowerCase()
70
+ if (name === "") continue
71
+ if (name === "*" || name === DATA_HEADER) return existing
72
+ empty = false
73
+ }
74
+ return empty ? DATA_HEADER : `${existing}, ${DATA_HEADER}`
75
+ }
76
+
77
+ const CACHE_CONTROL = "cache-control"
78
+
79
+ /** Headers of a rendered page that carries a cookie queued through `ctx.set`. */
80
+ export const PRIVATE_PAGE_HEADERS: Readonly<Record<string, string>> = Object.freeze({
81
+ [CACHE_CONTROL]: PRIVATE_NO_STORE,
82
+ })
83
+
84
+ /**
85
+ * `outcome` kept out of shared caches, for a request that queued a cookie through `ctx.set`.
86
+ *
87
+ * A rendered page is already private, and a plain render (`redirect()`, `status()`) inherits the
88
+ * serving app's header record. That leaves a hand-built `Response`, which inherits nothing, and a
89
+ * plain render that names its own `cache-control`, which would win over the inherited one.
90
+ */
91
+ export function privateOutcome(outcome: unknown): unknown {
92
+ if (outcome instanceof Response) return privateResponse(outcome)
93
+ if (!isResponseResult(outcome)) return outcome
94
+ const plain = outcome.plain
95
+ const own = plain?.headers
96
+ if (plain === undefined || own === undefined) return outcome
97
+ let headers: Record<string, string> | undefined
98
+ for (const name of Object.keys(own)) {
99
+ if (name.toLowerCase() !== CACHE_CONTROL) continue
100
+ headers ??= { ...own }
101
+ delete headers[name]
102
+ }
103
+ if (headers === undefined) return outcome
104
+ headers[CACHE_CONTROL] = PRIVATE_NO_STORE
105
+ return statusResult(plain.status, plain.body, { headers }) satisfies ResponseResult
106
+ }
107
+
108
+ function privateResponse(response: Response): Response {
109
+ if (response.headers.get(CACHE_CONTROL) === PRIVATE_NO_STORE) return response
110
+ try {
111
+ response.headers.set(CACHE_CONTROL, PRIVATE_NO_STORE)
112
+ return response
113
+ } catch {
114
+ // Immutable headers (`Response.redirect()`, a `fetch()` result): answer with a copy.
115
+ const headers = new Headers(response.headers)
116
+ headers.set(CACHE_CONTROL, PRIVATE_NO_STORE)
117
+ return new Response(response.body, {
118
+ status: response.status,
119
+ statusText: response.statusText,
120
+ headers,
121
+ })
122
+ }
123
+ }
124
+
125
+ interface WrittenHeaders {
126
+ readonly order: number
127
+ readonly headers: Record<string, string>
128
+ }
129
+
130
+ /**
131
+ * One page request's `ctx.set`: the response headers and cookies its loaders and action write.
132
+ *
133
+ * Every loader gets its own header record ({@link scope}), merged in a fixed order when the response
134
+ * is committed, so layout loaders that run in parallel with the page loader cannot race on a name.
135
+ * Cookies go straight to the serving app's own controls, which serialize them with the secure defaults
136
+ * and append them to whatever the request answers with.
137
+ */
138
+ export class PageResponseControls {
139
+ readonly #c: { readonly set: CoreResponseControls }
140
+ #written: WrittenHeaders[] | undefined
141
+ #sealed = false
142
+ #personalized = false
143
+
144
+ constructor(c: { readonly set: CoreResponseControls }) {
145
+ this.#c = c
146
+ }
147
+
148
+ /** True once a cookie was queued through `ctx.set`: the response is specific to this visitor. */
149
+ get personalized(): boolean {
150
+ return this.#personalized
151
+ }
152
+
153
+ /** The `ctx.set` of one loader or action. `order` fixes where its headers land in the merge. */
154
+ scope(order: number): LoaderResponseControls {
155
+ return new ScopedResponseControls(this, order)
156
+ }
157
+
158
+ /** A scope's header record, created on first use. */
159
+ track(order: number): Record<string, string> {
160
+ const headers = Object.create(null) as Record<string, string>
161
+ this.#written ??= []
162
+ this.#written.push({ order, headers })
163
+ return headers
164
+ }
165
+
166
+ /** The serving app's controls, marked as carrying per-visitor state. Throws once sealed. */
167
+ cookies(member: string): CoreResponseControls {
168
+ this.assertOpen(member)
169
+ this.personalize()
170
+ return this.#c.set
171
+ }
172
+
173
+ /** Mark the response as one visitor's - a queued cookie, or a draft render - so no cache stores it. */
174
+ personalize(): void {
175
+ if (this.#personalized) return
176
+ this.#personalized = true
177
+ // A redirect or status render merges the serving app's header record, so this keeps those
178
+ // outcomes out of shared caches too (a 301 is cacheable by default).
179
+ this.#c.set.headers["cache-control"] = PRIVATE_NO_STORE
180
+ }
181
+
182
+ assertOpen(member: string): void {
183
+ if (!this.#sealed) return
184
+ throw new Error(
185
+ `[nifra/web] ctx.set.${member} was used after the response was committed. Write response headers and cookies before the loader or action returns - a deferred promise settles too late.`,
186
+ )
187
+ }
188
+
189
+ /**
190
+ * Close the controls: the response is decided. A later write - a deferred promise that settles after
191
+ * the loader returned - throws instead of being dropped without a trace.
192
+ */
193
+ seal(): void {
194
+ if (this.#sealed) return
195
+ this.#sealed = true
196
+ if (this.#written === undefined) return
197
+ for (const entry of this.#written) Object.freeze(entry.headers)
198
+ }
199
+
200
+ /**
201
+ * Seal, validate, and merge every written header. Returns `undefined` when none was written.
202
+ * Throws on a name or value that cannot be sent, and on a name the framework or transport owns.
203
+ */
204
+ commit(): Record<string, string> | undefined {
205
+ this.seal()
206
+ const written = this.#written
207
+ if (written === undefined) return undefined
208
+ if (written.length > 1) written.sort((a, b) => a.order - b.order)
209
+ let merged: Record<string, string> | undefined
210
+ for (const entry of written) {
211
+ for (const rawName of Object.keys(entry.headers)) {
212
+ const name = validHeaderName(rawName)
213
+ merged ??= Object.create(null) as Record<string, string>
214
+ merged[name] = validHeaderValue(name, entry.headers[rawName])
215
+ }
216
+ }
217
+ return merged
218
+ }
219
+
220
+ /**
221
+ * The committed headers for the document. Written headers make the document vary on the
222
+ * data-request header; a queued cookie makes it private, whatever a loader asked for.
223
+ */
224
+ documentHeaders(): Record<string, string> | undefined {
225
+ const written = this.commit()
226
+ if (written !== undefined) written.vary = varyOnDataHeader(written.vary)
227
+ if (!this.#personalized) return written
228
+ const headers = written ?? (Object.create(null) as Record<string, string>)
229
+ headers["cache-control"] = PRIVATE_NO_STORE
230
+ return headers
231
+ }
232
+ }
233
+
234
+ class ScopedResponseControls implements LoaderResponseControls {
235
+ readonly #owner: PageResponseControls
236
+ readonly #order: number
237
+ #headers: Record<string, string> | undefined
238
+
239
+ constructor(owner: PageResponseControls, order: number) {
240
+ this.#owner = owner
241
+ this.#order = order
242
+ }
243
+
244
+ get headers(): Record<string, string> {
245
+ this.#owner.assertOpen("headers")
246
+ this.#headers ??= this.#owner.track(this.#order)
247
+ return this.#headers
248
+ }
249
+
250
+ cookie(name: string, value: string, options?: CookieOptions): void {
251
+ this.#owner.cookies("cookie()").cookie(name, value, options)
252
+ }
253
+
254
+ deleteCookie(name: string, options?: Pick<CookieOptions, "path" | "domain">): void {
255
+ this.#owner.cookies("deleteCookie()").deleteCookie(name, options)
256
+ }
257
+ }
258
+
259
+ function validHeaderName(rawName: string): string {
260
+ if (!TOKEN.test(rawName)) {
261
+ throw new Error(
262
+ `[nifra/web] ctx.set.headers: ${JSON.stringify(rawName)} is not a valid header name`,
263
+ )
264
+ }
265
+ const name = rawName.toLowerCase()
266
+ const reason = reservedReason(name)
267
+ if (reason !== undefined) {
268
+ throw new Error(`[nifra/web] ctx.set.headers: "${name}" is reserved - ${reason}`)
269
+ }
270
+ return name
271
+ }
272
+
273
+ // The message names the header only: a value can carry a token or a visitor's data.
274
+ function validHeaderValue(name: string, value: unknown): string {
275
+ if (typeof value !== "string") {
276
+ throw new Error(`[nifra/web] ctx.set.headers: the value of "${name}" must be a string`)
277
+ }
278
+ if (!FIELD_VALUE.test(value)) {
279
+ throw new Error(
280
+ `[nifra/web] ctx.set.headers: the value of "${name}" contains a character a header cannot carry (a line break, a control character, or one outside Latin-1)`,
281
+ )
282
+ }
283
+ return value
284
+ }
@@ -1,28 +1,23 @@
1
- /** Phantom brand key for {@link ServerOnly}. */
2
- declare const SERVER_ONLY_BRAND: unique symbol
1
+ /** Phantom brand key for {@link BackendOnly}. */
2
+ declare const BACKEND_ONLY_BRAND: unique symbol
3
3
 
4
4
  /**
5
5
  * Type-level intent marker for a value that must only exist on the server - a secret, a DB handle, a
6
- * server-only client. `ServerOnly<T>` is structurally `T` (the brand is an optional phantom field, so
6
+ * server-only client. `BackendOnly<T>` is structurally `T` (the brand is an optional phantom field, so
7
7
  * existing code keeps type-checking), but it advertises to readers + the compiler that the value is
8
8
  * not meant to cross to the browser. It is **purely type-level** and erases at build - it does NOT,
9
9
  * by itself, keep the value out of the client bundle.
10
10
  *
11
- * The enforcement is two runtime conventions:
12
- * - add the side-effect import `import "@nifrajs/web/server-only"` at the top of the module - the
13
- * client build ({@link buildClient}) fails loud, with the import chain, if it reaches a browser
14
- * chunk (the poison-import marker);
15
- * - or name the file `*.server.ts` - the `.server` convention empties it in the client build.
16
- *
17
- * Use this brand to express the intent in the types; pair it with one of those runtime markers so a
18
- * leak is caught at build time rather than shipping a secret to the client.
11
+ * The enforcement is the zone rules: code under `backend/` or in a `*.backend.ts` file never reaches a
12
+ * browser build, and a module that imports `@nifrajs/web/backend-only` fails every browser build that
13
+ * reaches it, wherever it lives.
19
14
  *
20
15
  * @example
21
- * import "@nifrajs/web/server-only"
22
- * import type { ServerOnly } from "@nifrajs/web"
23
- * export const apiKey: ServerOnly<string> = process.env.SECRET_API_KEY!
16
+ * import "@nifrajs/web/backend-only"
17
+ * import type { BackendOnly } from "@nifrajs/web"
18
+ * export const apiKey: BackendOnly<string> = process.env.SECRET_API_KEY!
24
19
  */
25
- export type ServerOnly<T> = T & { readonly [SERVER_ONLY_BRAND]?: never }
20
+ export type BackendOnly<T> = T & { readonly [BACKEND_ONLY_BRAND]?: never }
26
21
 
27
22
  /**
28
23
  * Pre-hydration form guard - a tiny inline script flushed in `<head>` (it runs in the window between