@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
@@ -1,5 +1,6 @@
1
1
  import { isSameOriginPath, type ResponseResult, status as statusResult } from "@nifrajs/core/server"
2
2
  import type { BoundaryRegistration, BoundaryStates } from "../boundary.ts"
3
+ import { type CspPolicy, cspDocumentNonce, cspHeaderValue, prepareCspPolicy } from "../csp.ts"
3
4
  import { type CssLoadingMode, DEFAULT_CSS_LOADING, normalizeCssLoading } from "../css-contract.ts"
4
5
  import { DEFERRED_ERROR_CODE, DEFERRED_RUNTIME, prepareDeferred } from "../deferred.ts"
5
6
  import { ISR_REVALIDATE_HEADER, ISR_REVALIDATE_TAGS_HEADER, serializeISRTags } from "../isr.ts"
@@ -13,16 +14,17 @@ import type {
13
14
  ScriptDescriptor,
14
15
  UnsafeScriptDescriptor,
15
16
  } from "../manifest.ts"
16
- import type { RenderAdapter, RenderProps } from "../render-seam.ts"
17
+ import type { MatchChain, RenderAdapter, RenderProps } from "../render-seam.ts"
17
18
  import {
18
19
  ACTION_GLOBAL,
19
20
  BOUNDARY_GLOBAL,
20
21
  DATA_GLOBAL,
22
+ HANDOVER_ID,
21
23
  LAYOUT_DATA_GLOBAL,
22
24
  ROOT_ATTRIBUTE,
23
25
  ROUTE_GLOBAL,
24
26
  } from "../render-seam.ts"
25
- import { PRERENDERED_GLOBAL, REDIRECT_HEADER } from "../router.ts"
27
+ import { PRERENDERED_GLOBAL } from "../router.ts"
26
28
  import { trustedHeadAttributes } from "./head-attributes.ts"
27
29
  import { isStaticMeta, mergeHeads } from "./head-merge.ts"
28
30
  import { PRE_HYDRATION_GUARD } from "./runtime-contract.ts"
@@ -53,7 +55,7 @@ const RESPONSE_RESULT = Symbol.for("nifra.response.result")
53
55
  * `status(...)` returns. Recognized here rather than imported: core keeps its own predicate internal,
54
56
  * and the registry symbol is the contract between them (two copies of core must still agree).
55
57
  */
56
- const isResponseResult = (value: unknown): value is ResponseResult =>
58
+ export const isResponseResult = (value: unknown): value is ResponseResult =>
57
59
  typeof value === "object" &&
58
60
  value !== null &&
59
61
  (value as { readonly [RESPONSE_RESULT]?: unknown })[RESPONSE_RESULT] === true &&
@@ -108,6 +110,9 @@ export interface RenderPageOptions {
108
110
  /** SSG: the prerendered-path set, serialized to `window.__NIFRA_PRERENDERED__` so the client fetches
109
111
  * a static `_data.json` on soft-nav into a prerendered route. Empty/omitted ⇒ not injected. */
110
112
  readonly prerenderedPaths?: readonly string[]
113
+ /** SSG: the URL of a JSON array of the prerendered paths, handed over in place of
114
+ * {@link prerenderedPaths} so a large set is fetched once instead of riding in every page. */
115
+ readonly prerenderedListUrl?: string
111
116
  /** ISR: route freshness in seconds, emitted as the `x-nifra-isr-revalidate` header for a `withISR`
112
117
  * wrapper to read. Omit ⇒ no header (the wrapper's default TTL applies). */
113
118
  readonly revalidate?: number
@@ -132,6 +137,8 @@ export interface RenderPageOptions {
132
137
  readonly layoutData?: readonly unknown[]
133
138
  /** Dynamic-boundary states, forwarded to the adapter and serialized for hydration when present. */
134
139
  readonly boundaries?: BoundaryStates
140
+ /** The chain's ids and `handle` exports, forwarded as `RenderProps.matchChain` for `useMatches`. */
141
+ readonly matchChain?: MatchChain
135
142
  /** HTTP status for the response (default 200; e.g. 404 for a not-found page). */
136
143
  readonly status?: number
137
144
  /** Extra response headers - e.g. the `cache-control` a terminal status page wants. `content-type`
@@ -168,6 +175,12 @@ export interface RenderPageOptions {
168
175
  /** CSP nonce applied to every framework-owned executable script in this document. Nonce-bearing
169
176
  * documents are marked `private, no-store` so a request-specific nonce is never replayed. */
170
177
  readonly nonce?: string
178
+ /**
179
+ * A hash-based Content-Security-Policy from `createCspPolicy`. The document then carries `nonce`
180
+ * only when it needs one (it defers a value, or a head tag names the nonce); otherwise it renders
181
+ * nonce-free and cacheable. The policy's header is set on the response either way.
182
+ */
183
+ readonly csp?: CspPolicy
171
184
  /**
172
185
  * Advanced: a per-route slot the renderer fills with the request-invariant document pieces (shell
173
186
  * prefix/suffix, tail statics) on the first render and reuses afterwards, skipping their re-assembly.
@@ -189,13 +202,10 @@ export interface RenderAssemblyCache {
189
202
  shellPre?: string
190
203
  /** Shell from after the deferred-runtime insertion point through the open `#root` container. */
191
204
  shellPost?: string
192
- /** Tail opener: the inline script open tag + the route-id global (before the action global). */
205
+ /** Tail opener: the handover script open tag + the data member's key. */
193
206
  tailPre?: string
194
- /** Tail mid: the prerendered-paths global (between the action and layout-data globals). */
195
- tailMid?: string
196
- /** Tail data prefix: the `window.<data global>=` assignment head. */
197
- tailData?: string
198
- /** Tail from after the serialized data through `</html>`. */
207
+ /** Tail from after the per-request handover members (the route id, prerendered paths, the handover
208
+ * close) through `</html>`. */
199
209
  tailPost?: string
200
210
  }
201
211
 
@@ -221,6 +231,12 @@ export function renderPage(options: RenderPageInput): MaybePromise<Response> {
221
231
  }
222
232
 
223
233
  export function renderPageResult(options: RenderPageInput): MaybePromise<RenderedPage> {
234
+ const { csp } = options
235
+ if (csp !== undefined) {
236
+ // The policy hashes the adapter's constant scripts once; only the first document waits for it.
237
+ const ready = prepareCspPolicy(csp, options.adapter)
238
+ if (ready !== undefined) return ready.then(() => renderPageResult(options))
239
+ }
224
240
  const {
225
241
  adapter,
226
242
  chain,
@@ -231,6 +247,7 @@ export function renderPageResult(options: RenderPageInput): MaybePromise<Rendere
231
247
  styles = [],
232
248
  cssLoading: requestedCssLoading,
233
249
  prerenderedPaths = [],
250
+ prerenderedListUrl,
234
251
  revalidate,
235
252
  revalidateTags = [],
236
253
  routeId,
@@ -240,22 +257,25 @@ export function renderPageResult(options: RenderPageInput): MaybePromise<Rendere
240
257
  rootId = "root",
241
258
  hydrate = true,
242
259
  islandScripts = [],
243
- nonce,
260
+ nonce: requestedNonce,
244
261
  boundaries,
245
262
  headers: extraHeaders,
246
263
  } = options
247
264
  const cssLoading = normalizeCssLoading(requestedCssLoading ?? DEFAULT_CSS_LOADING)
248
- if (nonce !== undefined && nonce.trim() === "") {
265
+ if (requestedNonce !== undefined && requestedNonce.trim() === "") {
249
266
  throw new TypeError("[nifra/web] renderPage nonce must be non-empty when provided")
250
267
  }
251
- const nonceAttr = nonce === undefined ? "" : ` nonce="${escapeAttr(nonce)}"`
252
- const route = routeId === undefined ? "" : `window.${ROUTE_GLOBAL}=${serializeData(routeId)};`
268
+ // Each handover entry after the data is `,"<global>":<json>`; the data opens the object, so it always
269
+ // has a first member and every other entry is a plain suffix.
270
+ const route = routeId === undefined ? "" : `,"${ROUTE_GLOBAL}":${serializeData(routeId)}`
253
271
  // The SSG prerendered-path set (when an app declares it) - the client reads it to fetch a static
254
272
  // `_data.json` on soft-nav into a prerendered route instead of hitting the worker. Empty ⇒ omitted.
255
273
  const prerendered =
256
- prerenderedPaths.length === 0
257
- ? ""
258
- : `window.${PRERENDERED_GLOBAL}=${serializeData(prerenderedPaths)};`
274
+ prerenderedListUrl !== undefined
275
+ ? `,"${PRERENDERED_GLOBAL}":${serializeData(prerenderedListUrl)}`
276
+ : prerenderedPaths.length === 0
277
+ ? ""
278
+ : `,"${PRERENDERED_GLOBAL}":${serializeData(prerenderedPaths)}`
259
279
  // Split deferred values: the component sees markers (id + promise) to `<Await>`; the serialized
260
280
  // data carries `{__nifra_deferred: id}` placeholders (promises don't serialize). `actionData` may
261
281
  // also `defer()` - split it too, continuing the id space so a single registry settles both. The
@@ -295,21 +315,34 @@ export function renderPageResult(options: RenderPageInput): MaybePromise<Rendere
295
315
  ...(actionSplit ? actionSplit.deferred : []),
296
316
  ...(boundarySplit ? boundarySplit.deferred : []),
297
317
  ]
318
+ // Under a CSP policy a document carries a nonce only when it has a script specific to this request:
319
+ // each settled deferred value streams one, and an executable head tag names the nonce. Every other
320
+ // document is nonce-free - its inline scripts are the constant ones the policy lists by hash - so its
321
+ // CSP header is the same on every request and it stays cacheable.
322
+ const nonce =
323
+ csp === undefined
324
+ ? requestedNonce
325
+ : cspDocumentNonce(
326
+ csp,
327
+ adapter,
328
+ hydrate,
329
+ allDeferred.length > 0 || headNamesNonce(head),
330
+ requestedNonce,
331
+ )
332
+ const nonceAttr = nonce === undefined ? "" : ` nonce="${escapeAttr(nonce)}"`
298
333
  // Omitted entirely when no layout has a loader - a page-only app emits exactly what it did before.
299
334
  const layoutTail =
300
335
  options.layoutData === undefined
301
336
  ? ""
302
- : `window.${LAYOUT_DATA_GLOBAL}=${serializeData(layoutSplits.map((split) => split.forClient))};`
337
+ : `,"${LAYOUT_DATA_GLOBAL}":${serializeData(layoutSplits.map((split) => split.forClient))}`
303
338
 
304
339
  // Only emit the action global when an action actually ran, so plain GET output is unchanged.
305
340
  const action =
306
- actionSplit === undefined
307
- ? ""
308
- : `window.${ACTION_GLOBAL}=${serializeData(actionSplit.forClient)};`
341
+ actionSplit === undefined ? "" : `,"${ACTION_GLOBAL}":${serializeData(actionSplit.forClient)}`
309
342
  const boundaryTail =
310
343
  boundarySplit === undefined
311
344
  ? ""
312
- : `window.${BOUNDARY_GLOBAL}=${serializeData(boundarySplit.forClient)};`
345
+ : `,"${BOUNDARY_GLOBAL}":${serializeData(boundarySplit.forClient)}`
313
346
  const deferredRuntime =
314
347
  allDeferred.length > 0 ? `<script${nonceAttr}>${DEFERRED_RUNTIME}</script>` : ""
315
348
  // The regex only injects the CSP nonce into the (constant) hydration head; with no nonce it's a
@@ -321,6 +354,14 @@ export function renderPageResult(options: RenderPageInput): MaybePromise<Rendere
321
354
  const slot: RenderAssemblyCache =
322
355
  nonce === undefined && options.assemblyCache !== undefined ? options.assemblyCache : {}
323
356
  if (slot.shellPre === undefined) {
357
+ // An empty entry is not "no client": `<script type="module" src="">` resolves to the page URL, so
358
+ // the browser would load the document itself as a module. Checked here, where the shell is built,
359
+ // so a cached route pays it once.
360
+ if (hydrate && (clientEntry === undefined || clientEntry.trim() === "")) {
361
+ throw new TypeError(
362
+ "[nifra/web] a hydrating page needs a non-empty clientEntry (the built client entry URL); pass hydrate: false for a page with no client",
363
+ )
364
+ }
324
365
  // Matched-route chunk preloads, concatenated directly. De-duped against the entry, which is
325
366
  // preloaded separately below.
326
367
  let preloadLinks = ""
@@ -376,7 +417,7 @@ export function renderPageResult(options: RenderPageInput): MaybePromise<Rendere
376
417
  const entryPreloads = hydrate
377
418
  ? `<link rel="modulepreload" href="${escapeAttr(clientEntry ?? "")}">${preloadLinks}`
378
419
  : ""
379
- slot.shellPre = `<!doctype html><html${htmlAttrs}><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">${hydrationGuard}<title>${escapeHtml(head?.title ?? title)}</title>${headTags(head)}${styleLinks}${entryPreloads}`
420
+ slot.shellPre = `<!doctype html><html${htmlAttrs}><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">${hydrationGuard}<title>${escapeHtml(head?.title ?? title)}</title>${headTags(head, nonce)}${styleLinks}${entryPreloads}`
380
421
  slot.shellPost = `${islandPreloads}${hydrationHead}</head><body><div id="${escapeAttr(rootId)}"${rootMarker}>`
381
422
  // Island bundles load regardless of `hydrate` - a static page (hydrate:false) ships no framework
382
423
  // client but can still mount no-framework islands (`@nifrajs/web/islands`).
@@ -384,14 +425,11 @@ export function renderPageResult(options: RenderPageInput): MaybePromise<Rendere
384
425
  for (const src of islandScripts)
385
426
  islandTags += `<script type="module" src="${escapeAttr(src)}"${nonceAttr}></script>`
386
427
  if (hydrate) {
387
- slot.tailPre = `<script${nonceAttr}>${route}`
388
- slot.tailMid = prerendered
389
- slot.tailData = `window.${DATA_GLOBAL}=`
390
- slot.tailPost = `</script><script type="module" src="${escapeAttr(clientEntry ?? "")}"${nonceAttr}></script>${islandTags}</body></html>`
428
+ // Inert JSON, never executed: the page state needs no nonce and no hash under any CSP.
429
+ slot.tailPre = `<script type="application/json" id="${HANDOVER_ID}">{"${DATA_GLOBAL}":`
430
+ slot.tailPost = `${route}${prerendered}}</script><script type="module" src="${escapeAttr(clientEntry ?? "")}"${nonceAttr}></script>${islandTags}</body></html>`
391
431
  } else {
392
432
  slot.tailPre = ""
393
- slot.tailMid = ""
394
- slot.tailData = ""
395
433
  slot.tailPost = `${islandTags}</body></html>`
396
434
  }
397
435
  }
@@ -402,10 +440,10 @@ export function renderPageResult(options: RenderPageInput): MaybePromise<Rendere
402
440
  // Closes the hydration container; deferred resolve scripts go AFTER it (outside `#root`) so they
403
441
  // aren't part of the adapter's hydrated tree (an inline script inside it breaks hydration).
404
442
  const closeRootHtml = "</div>"
405
- // Tail - the loader-data globals + the client module. Module scripts defer (run after parse), so
406
- // the data global + every streamed deferred resolution are set before the entry hydrates.
443
+ // Tail - the page-state handover + the client module. Module scripts defer (run after parse), so
444
+ // the handover + every streamed deferred resolution are in the document before the entry hydrates.
407
445
  const tailHtml = hydrate
408
- ? `${slot.tailPre}${action}${slot.tailMid}${layoutTail}${boundaryTail}${slot.tailData}${serializeData(forClient)}${slot.tailPost}`
446
+ ? `${slot.tailPre}${serializeData(forClient)}${action}${layoutTail}${boundaryTail}${slot.tailPost}`
409
447
  : (slot.tailPost as string)
410
448
  const headers: Record<string, string> = { "content-type": "text/html; charset=utf-8" }
411
449
  // Caller-supplied headers (a terminal status page's `cache-control`, say). Applied before the
@@ -426,6 +464,10 @@ export function renderPageResult(options: RenderPageInput): MaybePromise<Rendere
426
464
  // also keeps `withISR` from storing a document whose executable scripts carry request-specific
427
465
  // authorization to run under the caller's CSP.
428
466
  if (nonce !== undefined) headers["cache-control"] = "private, no-store"
467
+ if (csp !== undefined) {
468
+ const policy = cspHeaderValue(csp, adapter, nonce)
469
+ if (policy !== undefined) headers["content-security-policy"] = policy
470
+ }
429
471
  const renderProps: RenderProps = {
430
472
  data: forComponent,
431
473
  actionData: actionSplit?.forComponent,
@@ -444,6 +486,7 @@ export function renderPageResult(options: RenderPageInput): MaybePromise<Rendere
444
486
  ...(boundarySplit !== undefined
445
487
  ? { boundaries: boundarySplit.forComponent as BoundaryStates }
446
488
  : {}),
489
+ ...(options.matchChain !== undefined ? { matchChain: options.matchChain } : {}),
447
490
  }
448
491
 
449
492
  // Fast path: nothing `defer()`s and the adapter can render synchronously to a string → buffer the
@@ -476,6 +519,7 @@ export function renderPageResult(options: RenderPageInput): MaybePromise<Rendere
476
519
  tailHtml,
477
520
  status,
478
521
  headers,
522
+ nonce,
479
523
  nonceAttr,
480
524
  ).then((response) => new ResponseRenderedPage(response))
481
525
  }
@@ -490,6 +534,7 @@ async function renderStreamedPage(
490
534
  tailHtml: string,
491
535
  status: number,
492
536
  headers: Record<string, string>,
537
+ nonce: string | undefined,
493
538
  nonceAttr: string,
494
539
  ): Promise<Response> {
495
540
  // Streaming path - required for `defer()` (progressive `<Await>` resolution) and used by any adapter
@@ -500,7 +545,10 @@ async function renderStreamedPage(
500
545
  const shell = enc.encode(shellHtml)
501
546
  const closeRoot = enc.encode(closeRootHtml)
502
547
  const tail = enc.encode(tailHtml)
503
- const appStream = await adapter.renderToStream(chain, renderProps)
548
+ const appStream =
549
+ nonce === undefined
550
+ ? await adapter.renderToStream(chain, renderProps)
551
+ : await adapter.renderToStream(chain, renderProps, { nonce })
504
552
  const body = streamDocument(shell, appStream, closeRoot, allDeferred, tail, enc, nonceAttr)
505
553
  return new Response(body, { status, headers })
506
554
  }
@@ -623,7 +671,7 @@ export interface RedirectOptions {
623
671
  * attacker-controlled input straight through. Set `true` for a deliberate external redirect. */
624
672
  readonly external?: boolean
625
673
  /** Extra response headers. A redirect is no longer a `Response`, so there is no `.headers` to
626
- * mutate after the fact - name them here. Cookies still ride `c.set`, as on any other response. */
674
+ * mutate after the fact - name them here. Cookies queued with `ctx.set.cookie()` still ride it. */
627
675
  readonly headers?: Readonly<Record<string, string>>
628
676
  }
629
677
 
@@ -727,37 +775,6 @@ export function withDuplicateInstanceHint(err: unknown): unknown {
727
775
  return augmented
728
776
  }
729
777
 
730
- /**
731
- * An action's control-flow value passes straight through - except a redirect on a client-submit data
732
- * request: fetch would follow the 3xx into HTML the client can't use, so the redirect rides the
733
- * X-Nifra-Redirect header on a 204 and the client navigates. One conversion shared by the returned-
734
- * and thrown- paths, so `return redirect()` and `throw redirect()` agree.
735
- *
736
- * Takes either shape: `redirect()` is a plain render, while a hand-rolled `new Response(...)` from an
737
- * action still arrives as a `Response`. The rewrite stays on the lane its input was on - a plain
738
- * redirect converts to a plain 204, and never materializes the `Response` it is replacing.
739
- */
740
- export function actionResponse(
741
- result: Response | ResponseResult,
742
- isDataRequest: boolean,
743
- ): Response | ResponseResult {
744
- if (!isDataRequest) return result
745
- if (isResponseResult(result)) {
746
- const plain = result.plain
747
- // No `plain` means a carrier that only knows how to build a `Response` (not one of ours) - fall
748
- // back rather than guess at its status.
749
- if (plain === undefined) return actionResponse(result.toResponse(), isDataRequest)
750
- if (plain.status < 300 || plain.status >= 400) return result
751
- const location = plain.headers?.location ?? "/"
752
- return statusResult(204, undefined, { headers: { [REDIRECT_HEADER]: location } })
753
- }
754
- if (result.status >= 300 && result.status < 400) {
755
- const location = result.headers.get("location") ?? "/"
756
- return statusResult(204, undefined, { headers: { [REDIRECT_HEADER]: location } })
757
- }
758
- return result
759
- }
760
-
761
778
  /** A loaded layout module. `loader`/`gate` are the layout-loader surface; `meta` predates it. */
762
779
  export type LoadedLayoutModules = ReadonlyArray<{
763
780
  default: unknown
@@ -765,6 +782,10 @@ export type LoadedLayoutModules = ReadonlyArray<{
765
782
  loader?: LayoutLoader
766
783
  action?: unknown
767
784
  gate?: boolean
785
+ // Read only when it is a function - see `ShouldRevalidate` in manifest.ts.
786
+ shouldRevalidate?: unknown
787
+ // Reported by `useMatches` - see `RouteModule.handle`.
788
+ handle?: unknown
768
789
  // A layout may declare its own `searchSchema`; the route's effective search merges the layout chain's
769
790
  // schemas with the page's (page-wins). Present on the raw module already - typed here so it is readable.
770
791
  searchSchema?: RouteModule["searchSchema"]
@@ -998,6 +1019,19 @@ function tagAttrs(tag: "meta" | "link", attrs: Readonly<object>): string | null
998
1019
  // so a route module that's GC'd takes its entry with it.
999
1020
  const headTagsCache = new WeakMap<Meta, string>()
1000
1021
 
1022
+ // An executable script runs as written, so it is checked rather than escaped: `\u003c` is valid only
1023
+ // inside a JS string, and escaping turned `if (a < b)` into a syntax error. What the HTML tokenizer
1024
+ // reads as the element's end, or as the start of an escaped section, is refused instead.
1025
+ const SCRIPT_BREAKOUT = /<\/script|<!--/i
1026
+
1027
+ function assertExecutableScriptContent(content: string): void {
1028
+ if (SCRIPT_BREAKOUT.test(content)) {
1029
+ throw new TypeError(
1030
+ '[nifra/web] an executable inline script cannot contain "</script" or "<!--"; write "<\\/script" or split the string',
1031
+ )
1032
+ }
1033
+ }
1034
+
1001
1035
  function assertExecutableScriptType(type: string): void {
1002
1036
  if (!EXECUTABLE_SCRIPT_TYPES.has(type)) {
1003
1037
  throw new TypeError(
@@ -1013,9 +1047,15 @@ function assertExecutableScriptType(type: string): void {
1013
1047
  * or close the tag early. String concatenation (no intermediate `.map()` arrays + spread) - parity with
1014
1048
  * the already concat-based preloadLinks/styleLinks/islandPreloads loops; byte-identical output. Result
1015
1049
  * is memoized only for objects observed as static meta exports (serialized once per route). */
1016
- function headTags(head: Meta | undefined): string {
1050
+ /** Whether a document head names the CSP nonce: an executable script or a `<link nonce>`. */
1051
+ const headNamesNonce = (head: Meta | undefined): boolean =>
1052
+ (head?.unsafeScript?.length ?? 0) > 0 || (head?.link?.some((l) => l.nonce !== undefined) ?? false)
1053
+
1054
+ function headTags(head: Meta | undefined, documentNonce?: string): string {
1017
1055
  if (head === undefined) return ""
1018
- const cacheable = isStaticMeta(head)
1056
+ // Executable head metadata carries a caller-chosen nonce, so never cache it by object identity. A
1057
+ // static route object may otherwise retain one request's nonce and replay it on another document.
1058
+ const cacheable = isStaticMeta(head) && (head.unsafeScript?.length ?? 0) === 0
1019
1059
  if (cacheable) {
1020
1060
  const cached = headTagsCache.get(head)
1021
1061
  if (cached !== undefined) return cached
@@ -1056,7 +1096,13 @@ function headTags(head: Meta | undefined): string {
1056
1096
  // attribute and inject markup. Measured: `type: '"><img src=x onerror=alert(1)>'` reached the
1057
1097
  // document. An injection in the slot whose whole purpose is to make script emission trustworthy.
1058
1098
  assertExecutableScriptType(s.type)
1059
- out += `<script type="${s.type}" nonce="${escapeAttr(s.nonce)}" data-nifra>${escapeScriptContent(s.content)}</script>`
1099
+ if (documentNonce !== undefined && s.nonce !== documentNonce) {
1100
+ throw new TypeError(
1101
+ "[nifra/web] executable head scripts must use the same CSP nonce as the document",
1102
+ )
1103
+ }
1104
+ assertExecutableScriptContent(s.content)
1105
+ out += `<script type="${s.type}" nonce="${escapeAttr(s.nonce)}" data-nifra>${s.content}</script>`
1060
1106
  }
1061
1107
  if (cacheable) headTagsCache.set(head, out)
1062
1108
  return out
@@ -1140,6 +1186,7 @@ export function unsafeInlineScript(
1140
1186
  // place a caller is told what it may pass. Failing at the call site names the argument; failing at
1141
1187
  // render names a document.
1142
1188
  assertExecutableScriptType(type)
1189
+ assertExecutableScriptContent(content)
1143
1190
  return {
1144
1191
  unsafe: true,
1145
1192
  type,