@nifrajs/web 4.0.2 → 5.1.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 (426) hide show
  1. package/README.md +19 -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 +6 -6
  15. package/dist/client.d.ts.map +1 -1
  16. package/dist/client.js +46 -18
  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-port.d.ts +3 -2
  26. package/dist/dev-port.d.ts.map +1 -1
  27. package/dist/dev-port.js +2 -6
  28. package/dist/dev-port.js.map +1 -1
  29. package/dist/dev-session.d.ts.map +1 -1
  30. package/dist/dev-session.js +4 -14
  31. package/dist/dev-session.js.map +1 -1
  32. package/dist/dev.d.ts +7 -17
  33. package/dist/dev.d.ts.map +1 -1
  34. package/dist/dev.js +13 -1
  35. package/dist/dev.js.map +1 -1
  36. package/dist/draft.d.ts +8 -5
  37. package/dist/draft.d.ts.map +1 -1
  38. package/dist/draft.js +30 -13
  39. package/dist/draft.js.map +1 -1
  40. package/dist/fonts-google.d.ts.map +1 -1
  41. package/dist/fonts-google.js +7 -0
  42. package/dist/fonts-google.js.map +1 -1
  43. package/dist/index.d.ts +5 -4
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +4 -3
  46. package/dist/index.js.map +1 -1
  47. package/dist/internal/codegen.d.ts +1 -0
  48. package/dist/internal/codegen.d.ts.map +1 -1
  49. package/dist/internal/codegen.js +56 -29
  50. package/dist/internal/codegen.js.map +1 -1
  51. package/dist/internal/disposable-dir.d.ts +8 -0
  52. package/dist/internal/disposable-dir.d.ts.map +1 -0
  53. package/dist/internal/disposable-dir.js +25 -0
  54. package/dist/internal/disposable-dir.js.map +1 -0
  55. package/dist/internal/entry-chunks.d.ts +9 -0
  56. package/dist/internal/entry-chunks.d.ts.map +1 -0
  57. package/dist/internal/entry-chunks.js +15 -0
  58. package/dist/internal/entry-chunks.js.map +1 -0
  59. package/dist/internal/evidence-traversal.d.ts +12 -0
  60. package/dist/internal/evidence-traversal.d.ts.map +1 -0
  61. package/dist/internal/evidence-traversal.js +48 -0
  62. package/dist/internal/evidence-traversal.js.map +1 -0
  63. package/dist/internal/head-merge.d.ts +6 -0
  64. package/dist/internal/head-merge.d.ts.map +1 -1
  65. package/dist/internal/head-merge.js +9 -0
  66. package/dist/internal/head-merge.js.map +1 -1
  67. package/dist/internal/js-string.d.ts +2 -0
  68. package/dist/internal/js-string.d.ts.map +1 -1
  69. package/dist/internal/js-string.js +5 -1
  70. package/dist/internal/js-string.js.map +1 -1
  71. package/dist/internal/mount-shadow.js +1 -1
  72. package/dist/internal/mount-shadow.js.map +1 -1
  73. package/dist/internal/output-guard.d.ts +7 -0
  74. package/dist/internal/output-guard.d.ts.map +1 -1
  75. package/dist/internal/output-guard.js +80 -8
  76. package/dist/internal/output-guard.js.map +1 -1
  77. package/dist/internal/package-entries.d.ts +34 -0
  78. package/dist/internal/package-entries.d.ts.map +1 -0
  79. package/dist/internal/package-entries.js +324 -0
  80. package/dist/internal/package-entries.js.map +1 -0
  81. package/dist/internal/page-execution.d.ts +6 -0
  82. package/dist/internal/page-execution.d.ts.map +1 -1
  83. package/dist/internal/page-execution.js +437 -114
  84. package/dist/internal/page-execution.js.map +1 -1
  85. package/dist/internal/parity.d.ts.map +1 -1
  86. package/dist/internal/parity.js +15 -2
  87. package/dist/internal/parity.js.map +1 -1
  88. package/dist/internal/private-env.d.ts +8 -0
  89. package/dist/internal/private-env.d.ts.map +1 -1
  90. package/dist/internal/private-env.js +25 -10
  91. package/dist/internal/private-env.js.map +1 -1
  92. package/dist/internal/progressive-cookies.d.ts +14 -0
  93. package/dist/internal/progressive-cookies.d.ts.map +1 -0
  94. package/dist/internal/progressive-cookies.js +43 -0
  95. package/dist/internal/progressive-cookies.js.map +1 -0
  96. package/dist/internal/public-env.d.ts +2 -0
  97. package/dist/internal/public-env.d.ts.map +1 -0
  98. package/dist/internal/public-env.js +13 -0
  99. package/dist/internal/public-env.js.map +1 -0
  100. package/dist/internal/render-document.d.ts +5 -44
  101. package/dist/internal/render-document.d.ts.map +1 -1
  102. package/dist/internal/render-document.js +51 -63
  103. package/dist/internal/render-document.js.map +1 -1
  104. package/dist/internal/response-controls.d.ts +2 -0
  105. package/dist/internal/response-controls.d.ts.map +1 -1
  106. package/dist/internal/response-controls.js +26 -16
  107. package/dist/internal/response-controls.js.map +1 -1
  108. package/dist/internal/revive-dates.d.ts +16 -0
  109. package/dist/internal/revive-dates.d.ts.map +1 -0
  110. package/dist/internal/revive-dates.js +32 -0
  111. package/dist/internal/revive-dates.js.map +1 -0
  112. package/dist/internal/route-path.d.ts +14 -0
  113. package/dist/internal/route-path.d.ts.map +1 -0
  114. package/dist/internal/route-path.js +19 -0
  115. package/dist/internal/route-path.js.map +1 -0
  116. package/dist/internal/secret-scan.d.ts.map +1 -1
  117. package/dist/internal/secret-scan.js +190 -20
  118. package/dist/internal/secret-scan.js.map +1 -1
  119. package/dist/internal/server-boundary.d.ts.map +1 -1
  120. package/dist/internal/server-boundary.js +33 -5
  121. package/dist/internal/server-boundary.js.map +1 -1
  122. package/dist/internal/web-app.d.ts +25 -18
  123. package/dist/internal/web-app.d.ts.map +1 -1
  124. package/dist/internal/web-app.js +81 -24
  125. package/dist/internal/web-app.js.map +1 -1
  126. package/dist/internal/wire-date.d.ts +3 -0
  127. package/dist/internal/wire-date.d.ts.map +1 -0
  128. package/dist/internal/wire-date.js +14 -0
  129. package/dist/internal/wire-date.js.map +1 -0
  130. package/dist/internal/without-query.d.ts +4 -0
  131. package/dist/internal/without-query.d.ts.map +1 -0
  132. package/dist/internal/without-query.js +9 -0
  133. package/dist/internal/without-query.js.map +1 -0
  134. package/dist/internal/zone-graph.d.ts +4 -0
  135. package/dist/internal/zone-graph.d.ts.map +1 -1
  136. package/dist/internal/zone-graph.js +71 -20
  137. package/dist/internal/zone-graph.js.map +1 -1
  138. package/dist/isr.d.ts +59 -3
  139. package/dist/isr.d.ts.map +1 -1
  140. package/dist/isr.js +311 -41
  141. package/dist/isr.js.map +1 -1
  142. package/dist/llms-txt.d.ts +2 -0
  143. package/dist/llms-txt.d.ts.map +1 -1
  144. package/dist/llms-txt.js +29 -3
  145. package/dist/llms-txt.js.map +1 -1
  146. package/dist/manifest.d.ts +37 -4
  147. package/dist/manifest.d.ts.map +1 -1
  148. package/dist/manifest.js +4 -0
  149. package/dist/manifest.js.map +1 -1
  150. package/dist/open-resolved.d.ts +18 -0
  151. package/dist/open-resolved.d.ts.map +1 -0
  152. package/dist/open-resolved.js +62 -0
  153. package/dist/open-resolved.js.map +1 -0
  154. package/dist/plugins/kit.d.ts +1 -0
  155. package/dist/plugins/kit.d.ts.map +1 -1
  156. package/dist/plugins/kit.js +26 -11
  157. package/dist/plugins/kit.js.map +1 -1
  158. package/dist/plugins/vite-leak-guard.d.ts +9 -1
  159. package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
  160. package/dist/plugins/vite-leak-guard.js +84 -7
  161. package/dist/plugins/vite-leak-guard.js.map +1 -1
  162. package/dist/plugins/vite-zone-guard.d.ts.map +1 -1
  163. package/dist/plugins/vite-zone-guard.js +17 -21
  164. package/dist/plugins/vite-zone-guard.js.map +1 -1
  165. package/dist/plugins/zone-guard.d.ts.map +1 -1
  166. package/dist/plugins/zone-guard.js +44 -3
  167. package/dist/plugins/zone-guard.js.map +1 -1
  168. package/dist/prerender.d.ts +5 -1
  169. package/dist/prerender.d.ts.map +1 -1
  170. package/dist/prerender.js +41 -19
  171. package/dist/prerender.js.map +1 -1
  172. package/dist/public-dir.d.ts +8 -1
  173. package/dist/public-dir.d.ts.map +1 -1
  174. package/dist/public-dir.js +17 -6
  175. package/dist/public-dir.js.map +1 -1
  176. package/dist/query.d.ts +13 -4
  177. package/dist/query.d.ts.map +1 -1
  178. package/dist/query.js +89 -26
  179. package/dist/query.js.map +1 -1
  180. package/dist/render-seam.d.ts +25 -2
  181. package/dist/render-seam.d.ts.map +1 -1
  182. package/dist/render-seam.js +1 -0
  183. package/dist/render-seam.js.map +1 -1
  184. package/dist/route-types.d.ts.map +1 -1
  185. package/dist/route-types.js +20 -9
  186. package/dist/route-types.js.map +1 -1
  187. package/dist/router.d.ts +9 -2
  188. package/dist/router.d.ts.map +1 -1
  189. package/dist/router.js +47 -50
  190. package/dist/router.js.map +1 -1
  191. package/dist/seo.d.ts +49 -0
  192. package/dist/seo.d.ts.map +1 -0
  193. package/dist/seo.js +48 -0
  194. package/dist/seo.js.map +1 -0
  195. package/dist/server-components/build-info.d.ts +3 -0
  196. package/dist/server-components/build-info.d.ts.map +1 -0
  197. package/dist/server-components/build-info.js +7 -0
  198. package/dist/server-components/build-info.js.map +1 -0
  199. package/dist/server-components/build-target.d.ts +12 -0
  200. package/dist/server-components/build-target.d.ts.map +1 -0
  201. package/dist/server-components/build-target.js +153 -0
  202. package/dist/server-components/build-target.js.map +1 -0
  203. package/dist/server-components/build.d.ts +24 -0
  204. package/dist/server-components/build.d.ts.map +1 -0
  205. package/dist/server-components/build.js +366 -0
  206. package/dist/server-components/build.js.map +1 -0
  207. package/dist/server-components/bundle-proof.d.ts +3 -0
  208. package/dist/server-components/bundle-proof.d.ts.map +1 -0
  209. package/dist/server-components/bundle-proof.js +23 -0
  210. package/dist/server-components/bundle-proof.js.map +1 -0
  211. package/dist/server-components/client.d.ts +46 -0
  212. package/dist/server-components/client.d.ts.map +1 -0
  213. package/dist/server-components/client.js +594 -0
  214. package/dist/server-components/client.js.map +1 -0
  215. package/dist/server-components/definition.d.ts +11 -0
  216. package/dist/server-components/definition.d.ts.map +1 -0
  217. package/dist/server-components/definition.js +94 -0
  218. package/dist/server-components/definition.js.map +1 -0
  219. package/dist/server-components/dev-http.d.ts +5 -0
  220. package/dist/server-components/dev-http.d.ts.map +1 -0
  221. package/dist/server-components/dev-http.js +71 -0
  222. package/dist/server-components/dev-http.js.map +1 -0
  223. package/dist/server-components/error.d.ts +9 -0
  224. package/dist/server-components/error.d.ts.map +1 -0
  225. package/dist/server-components/error.js +24 -0
  226. package/dist/server-components/error.js.map +1 -0
  227. package/dist/server-components/frames.d.ts +7 -0
  228. package/dist/server-components/frames.d.ts.map +1 -0
  229. package/dist/server-components/frames.js +169 -0
  230. package/dist/server-components/frames.js.map +1 -0
  231. package/dist/server-components/html-policy.d.ts +6 -0
  232. package/dist/server-components/html-policy.d.ts.map +1 -0
  233. package/dist/server-components/html-policy.js +75 -0
  234. package/dist/server-components/html-policy.js.map +1 -0
  235. package/dist/server-components/html.d.ts +4 -0
  236. package/dist/server-components/html.d.ts.map +1 -0
  237. package/dist/server-components/html.js +67 -0
  238. package/dist/server-components/html.js.map +1 -0
  239. package/dist/server-components/islands.d.ts +44 -0
  240. package/dist/server-components/islands.d.ts.map +1 -0
  241. package/dist/server-components/islands.js +42 -0
  242. package/dist/server-components/islands.js.map +1 -0
  243. package/dist/server-components/native.d.ts +4 -0
  244. package/dist/server-components/native.d.ts.map +1 -0
  245. package/dist/server-components/native.js +81 -0
  246. package/dist/server-components/native.js.map +1 -0
  247. package/dist/server-components/portal.d.ts +6 -0
  248. package/dist/server-components/portal.d.ts.map +1 -0
  249. package/dist/server-components/portal.js +35 -0
  250. package/dist/server-components/portal.js.map +1 -0
  251. package/dist/server-components/props.d.ts +3 -0
  252. package/dist/server-components/props.d.ts.map +1 -0
  253. package/dist/server-components/props.js +14 -0
  254. package/dist/server-components/props.js.map +1 -0
  255. package/dist/server-components/protocol.d.ts +44 -0
  256. package/dist/server-components/protocol.d.ts.map +1 -0
  257. package/dist/server-components/protocol.js +139 -0
  258. package/dist/server-components/protocol.js.map +1 -0
  259. package/dist/server-components/range.d.ts +24 -0
  260. package/dist/server-components/range.d.ts.map +1 -0
  261. package/dist/server-components/range.js +263 -0
  262. package/dist/server-components/range.js.map +1 -0
  263. package/dist/server-components/registry.d.ts +6 -0
  264. package/dist/server-components/registry.d.ts.map +1 -0
  265. package/dist/server-components/registry.js +30 -0
  266. package/dist/server-components/registry.js.map +1 -0
  267. package/dist/server-components/response.d.ts +4 -0
  268. package/dist/server-components/response.d.ts.map +1 -0
  269. package/dist/server-components/response.js +32 -0
  270. package/dist/server-components/response.js.map +1 -0
  271. package/dist/server-components/router-integration.d.ts +9 -0
  272. package/dist/server-components/router-integration.d.ts.map +1 -0
  273. package/dist/server-components/router-integration.js +29 -0
  274. package/dist/server-components/router-integration.js.map +1 -0
  275. package/dist/server-components/runtime.d.ts +14 -0
  276. package/dist/server-components/runtime.d.ts.map +1 -0
  277. package/dist/server-components/runtime.js +276 -0
  278. package/dist/server-components/runtime.js.map +1 -0
  279. package/dist/server-components/scheduler.d.ts +14 -0
  280. package/dist/server-components/scheduler.d.ts.map +1 -0
  281. package/dist/server-components/scheduler.js +74 -0
  282. package/dist/server-components/scheduler.js.map +1 -0
  283. package/dist/server-components/schema.d.ts +4 -0
  284. package/dist/server-components/schema.d.ts.map +1 -0
  285. package/dist/server-components/schema.js +77 -0
  286. package/dist/server-components/schema.js.map +1 -0
  287. package/dist/server-components/server-entry.d.ts +12 -0
  288. package/dist/server-components/server-entry.d.ts.map +1 -0
  289. package/dist/server-components/server-entry.js +90 -0
  290. package/dist/server-components/server-entry.js.map +1 -0
  291. package/dist/server-components/source.d.ts +15 -0
  292. package/dist/server-components/source.d.ts.map +1 -0
  293. package/dist/server-components/source.js +269 -0
  294. package/dist/server-components/source.js.map +1 -0
  295. package/dist/server-components/state.d.ts +4 -0
  296. package/dist/server-components/state.d.ts.map +1 -0
  297. package/dist/server-components/state.js +123 -0
  298. package/dist/server-components/state.js.map +1 -0
  299. package/dist/server-components/stream.d.ts +6 -0
  300. package/dist/server-components/stream.d.ts.map +1 -0
  301. package/dist/server-components/stream.js +70 -0
  302. package/dist/server-components/stream.js.map +1 -0
  303. package/dist/server-components/trusted-html.d.ts +3 -0
  304. package/dist/server-components/trusted-html.d.ts.map +1 -0
  305. package/dist/server-components/trusted-html.js +14 -0
  306. package/dist/server-components/trusted-html.js.map +1 -0
  307. package/dist/server-components/types.d.ts +107 -0
  308. package/dist/server-components/types.d.ts.map +1 -0
  309. package/dist/server-components/types.js +2 -0
  310. package/dist/server-components/types.js.map +1 -0
  311. package/dist/server-components/typescript.d.ts +11 -0
  312. package/dist/server-components/typescript.d.ts.map +1 -0
  313. package/dist/server-components/typescript.js +28 -0
  314. package/dist/server-components/typescript.js.map +1 -0
  315. package/dist/server-components/value.d.ts +5 -0
  316. package/dist/server-components/value.d.ts.map +1 -0
  317. package/dist/server-components/value.js +77 -0
  318. package/dist/server-components/value.js.map +1 -0
  319. package/dist/server-components/vite.d.ts +12 -0
  320. package/dist/server-components/vite.d.ts.map +1 -0
  321. package/dist/server-components/vite.js +197 -0
  322. package/dist/server-components/vite.js.map +1 -0
  323. package/dist/server-components.d.ts +5 -0
  324. package/dist/server-components.d.ts.map +1 -0
  325. package/dist/server-components.js +4 -0
  326. package/dist/server-components.js.map +1 -0
  327. package/dist/service-worker.d.ts +8 -1
  328. package/dist/service-worker.d.ts.map +1 -1
  329. package/dist/service-worker.js +20 -6
  330. package/dist/service-worker.js.map +1 -1
  331. package/dist/vite.d.ts +12 -0
  332. package/dist/vite.d.ts.map +1 -1
  333. package/dist/vite.js +7 -1
  334. package/dist/vite.js.map +1 -1
  335. package/dist/zones.d.ts +64 -2
  336. package/dist/zones.d.ts.map +1 -1
  337. package/dist/zones.js +331 -28
  338. package/dist/zones.js.map +1 -1
  339. package/package.json +102 -4
  340. package/src/build-vite.ts +31 -9
  341. package/src/build.ts +109 -31
  342. package/src/cdn.ts +18 -12
  343. package/src/client.ts +48 -17
  344. package/src/deferred.ts +27 -9
  345. package/src/dev-feed.ts +24 -5
  346. package/src/dev-port.ts +4 -10
  347. package/src/dev-session.ts +4 -13
  348. package/src/dev.ts +29 -1
  349. package/src/draft.ts +35 -14
  350. package/src/fonts-google.ts +10 -0
  351. package/src/index.ts +5 -6
  352. package/src/internal/codegen.ts +60 -29
  353. package/src/internal/disposable-dir.ts +24 -0
  354. package/src/internal/entry-chunks.ts +18 -0
  355. package/src/internal/evidence-traversal.ts +57 -0
  356. package/src/internal/head-merge.ts +16 -0
  357. package/src/internal/js-string.ts +6 -1
  358. package/src/internal/mount-shadow.ts +1 -1
  359. package/src/internal/output-guard.ts +88 -7
  360. package/src/internal/package-entries.ts +331 -0
  361. package/src/internal/page-execution.ts +573 -114
  362. package/src/internal/parity.ts +15 -2
  363. package/src/internal/private-env.ts +27 -13
  364. package/src/internal/progressive-cookies.ts +60 -0
  365. package/src/internal/public-env.ts +14 -0
  366. package/src/internal/render-document.ts +66 -89
  367. package/src/internal/response-controls.ts +31 -14
  368. package/src/internal/revive-dates.ts +35 -0
  369. package/src/internal/route-path.ts +24 -0
  370. package/src/internal/secret-scan.ts +190 -18
  371. package/src/internal/server-boundary.ts +29 -5
  372. package/src/internal/web-app.ts +126 -42
  373. package/src/internal/wire-date.ts +16 -0
  374. package/src/internal/without-query.ts +8 -0
  375. package/src/internal/zone-graph.ts +78 -25
  376. package/src/isr.ts +364 -45
  377. package/src/llms-txt.ts +32 -3
  378. package/src/manifest.ts +38 -4
  379. package/src/open-resolved.ts +64 -0
  380. package/src/plugins/kit.ts +31 -11
  381. package/src/plugins/vite-leak-guard.ts +104 -8
  382. package/src/plugins/vite-zone-guard.ts +21 -19
  383. package/src/plugins/zone-guard.ts +41 -2
  384. package/src/prerender.ts +39 -18
  385. package/src/public-dir.ts +27 -8
  386. package/src/query.ts +107 -26
  387. package/src/render-seam.ts +30 -2
  388. package/src/route-types.ts +34 -7
  389. package/src/router.ts +66 -59
  390. package/src/seo.ts +68 -0
  391. package/src/server-components/build-info.ts +11 -0
  392. package/src/server-components/build-target.ts +194 -0
  393. package/src/server-components/build.ts +419 -0
  394. package/src/server-components/bundle-proof.ts +33 -0
  395. package/src/server-components/client.ts +663 -0
  396. package/src/server-components/definition.ts +125 -0
  397. package/src/server-components/dev-http.ts +75 -0
  398. package/src/server-components/error.ts +24 -0
  399. package/src/server-components/frames.ts +181 -0
  400. package/src/server-components/html-policy.ts +94 -0
  401. package/src/server-components/html.ts +81 -0
  402. package/src/server-components/islands.ts +111 -0
  403. package/src/server-components/native.ts +101 -0
  404. package/src/server-components/portal.ts +42 -0
  405. package/src/server-components/props.ts +19 -0
  406. package/src/server-components/protocol.ts +167 -0
  407. package/src/server-components/range.ts +313 -0
  408. package/src/server-components/registry.ts +34 -0
  409. package/src/server-components/response.ts +44 -0
  410. package/src/server-components/router-integration.ts +40 -0
  411. package/src/server-components/runtime.ts +376 -0
  412. package/src/server-components/scheduler.ts +84 -0
  413. package/src/server-components/schema.ts +69 -0
  414. package/src/server-components/server-entry.ts +98 -0
  415. package/src/server-components/source.ts +324 -0
  416. package/src/server-components/state.ts +143 -0
  417. package/src/server-components/stream.ts +71 -0
  418. package/src/server-components/trusted-html.ts +21 -0
  419. package/src/server-components/types.ts +138 -0
  420. package/src/server-components/typescript.ts +39 -0
  421. package/src/server-components/value.ts +82 -0
  422. package/src/server-components/vite.ts +242 -0
  423. package/src/server-components.ts +32 -0
  424. package/src/service-worker.ts +24 -7
  425. package/src/vite.ts +8 -1
  426. package/src/zones.ts +418 -32
@@ -586,6 +586,17 @@ const linkedRepoBoundary = async (packageRoot: string): Promise<string> => {
586
586
  return packageRoot
587
587
  }
588
588
 
589
+ /** Where a real package folder lives when only its files are links: Bun installs a `file:` folder
590
+ * dependency as a directory of per-file symlinks, so its imports resolve beside the linked files. */
591
+ const fileLinkedDir = async (dir: string): Promise<string | undefined> => {
592
+ const manifest = join(dir, "package.json")
593
+ try {
594
+ return (await lstat(manifest)).isSymbolicLink() ? dirname(await realpath(manifest)) : undefined
595
+ } catch {
596
+ return undefined
597
+ }
598
+ }
599
+
589
600
  const linkedPackageRoots = async (
590
601
  importerRoots: readonly string[],
591
602
  manifests: readonly Record<string, unknown>[],
@@ -605,8 +616,10 @@ const linkedPackageRoots = async (
605
616
  }
606
617
  const candidate = join(importerRoot, "node_modules", ...name.split("/"))
607
618
  const link = await lstat(candidate).catch(() => undefined)
608
- if (link === undefined || !link.isSymbolicLink()) continue
609
- const resolved = await realpath(candidate).catch(() => undefined)
619
+ if (link === undefined) continue
620
+ const resolved = link.isSymbolicLink()
621
+ ? await realpath(candidate).catch(() => undefined)
622
+ : await fileLinkedDir(candidate)
610
623
  if (resolved === undefined || pathInside(realScanRoot, resolved)) continue
611
624
  found.set(resolved, true)
612
625
  }
@@ -32,12 +32,12 @@ const BEFORE_EXPRESSION = new Set([
32
32
  "instanceof",
33
33
  ])
34
34
 
35
- type Token =
35
+ export type Token =
36
36
  | { readonly kind: "id" | "punct" | "string"; readonly value: string }
37
37
  | { readonly kind: "other" }
38
38
 
39
39
  /** JavaScript tokens, with strings, template text, comments and regular expressions skipped. */
40
- function tokenize(code: string): Token[] {
40
+ export function tokenize(code: string): Token[] {
41
41
  const tokens: Token[] = []
42
42
  // One entry per open `${`: how many `{` are open inside it.
43
43
  const templates: number[] = []
@@ -152,24 +152,37 @@ function envReads(tokens: readonly Token[]): EnvRead[] {
152
152
  const token = tokens[at]
153
153
  return token?.kind === "id" ? token.value : undefined
154
154
  }
155
- /** A property read after an env object that ends at `at`: `.X` or `["X"]`. */
155
+ // The tokenizer reads `?.` as `.`, so `?.[` and `?.(` arrive as `.` before the bracket.
156
+ const open = (at: number, bracket: "[" | "("): number =>
157
+ is(at, "punct", ".") && is(at + 1, "punct", bracket) ? at + 1 : at
158
+ /** The string key of `["X"]` (or `?.["X"]`) at `at`, and the index after it. */
159
+ const keyAt = (at: number): { readonly key: string; readonly next: number } | undefined => {
160
+ const bracket = open(at, "[")
161
+ const key =
162
+ is(bracket, "punct", "[") && is(bracket + 2, "punct", "]") ? stringAt(bracket + 1) : undefined
163
+ return key === undefined ? undefined : { key, next: bracket + 3 }
164
+ }
165
+ /** A property read after an env object that ends at `at`: `.X`, `["X"]` or `?.["X"]`. */
156
166
  const member = (base: string, at: number, importMeta: boolean): EnvRead => {
157
167
  const property = is(at, "punct", ".") ? idAt(at + 1) : undefined
158
168
  if (property !== undefined)
159
169
  return { expression: `${base}.${property}`, name: property, importMeta }
160
- const key = is(at, "punct", "[") && is(at + 2, "punct", "]") ? stringAt(at + 1) : undefined
170
+ const key = keyAt(at)?.key
161
171
  if (key !== undefined)
162
172
  return { expression: `${base}[${JSON.stringify(key)}]`, name: key, importMeta }
163
173
  return { expression: base, importMeta }
164
174
  }
175
+ /** The index after `.env`, `["env"]` or `?.["env"]` at `at`, or `-1`. */
176
+ const envAt = (at: number): number => {
177
+ if (is(at, "punct", ".") && is(at + 1, "id", "env")) return at + 2
178
+ const bracket = keyAt(at)
179
+ return bracket?.key === "env" ? bracket.next : -1
180
+ }
165
181
  for (let i = 0; i < tokens.length; i++) {
166
182
  const head = idAt(i)
167
- if (
168
- (head === "process" || head === "Bun") &&
169
- is(i + 1, "punct", ".") &&
170
- is(i + 2, "id", "env")
171
- ) {
172
- reads.push(member(`${head}.env`, i + 3, false))
183
+ const env = head === "process" || head === "Bun" ? envAt(i + 1) : -1
184
+ if (env !== -1) {
185
+ reads.push(member(`${head}.env`, env, false))
173
186
  } else if (
174
187
  head === "import" &&
175
188
  is(i + 1, "punct", ".") &&
@@ -180,12 +193,13 @@ function envReads(tokens: readonly Token[]): EnvRead[] {
180
193
  reads.push(member("import.meta.env", i + 5, true))
181
194
  } else if (head === "Deno" && is(i + 1, "punct", ".") && is(i + 2, "id", "env")) {
182
195
  const method = is(i + 3, "punct", ".") ? idAt(i + 4) : undefined
196
+ const call = open(i + 5, "(")
183
197
  const name =
184
- (method === "get" || method === "has") && is(i + 5, "punct", "(")
185
- ? stringAt(i + 6)
198
+ (method === "get" || method === "has") && is(call, "punct", "(")
199
+ ? stringAt(call + 1)
186
200
  : undefined
187
201
  reads.push(
188
- name !== undefined && is(i + 7, "punct", ")")
202
+ name !== undefined && is(call + 2, "punct", ")")
189
203
  ? { expression: `Deno.env.${method}(${JSON.stringify(name)})`, name, importMeta: false }
190
204
  : {
191
205
  expression: method === undefined ? "Deno.env" : `Deno.env.${method}()`,
@@ -0,0 +1,60 @@
1
+ import type { CookieOptions } from "@nifrajs/core/server"
2
+
3
+ export interface ProgressiveCookie {
4
+ readonly name: string
5
+ readonly value: string
6
+ readonly options?: CookieOptions | undefined
7
+ readonly deleted?: boolean
8
+ }
9
+
10
+ /** Parent-domain acceptance belongs to the browser's cookie jar and public-suffix policy. A Secure
11
+ * cookie on a request seen as `http:` does too: behind a TLS terminator only the browser knows its
12
+ * scheme, so it decides whether the cookie is kept. */
13
+ export function progressiveCookieRedirect(
14
+ request: Request,
15
+ changes: readonly ProgressiveCookie[],
16
+ ): boolean {
17
+ const url = new URL(request.url)
18
+ const host = url.hostname.toLowerCase()
19
+ return changes.some((change) => {
20
+ const domain = change.options?.domain?.replace(/^\./, "").toLowerCase()
21
+ if (domain !== undefined && domain !== host && host.endsWith("." + domain)) return true
22
+ return secureOnPlainHttp(url, change)
23
+ })
24
+ }
25
+
26
+ const secureOnPlainHttp = (url: URL, change: ProgressiveCookie): boolean =>
27
+ (change.options?.secure ?? !change.deleted) && url.protocol !== "https:"
28
+
29
+ /** Reauthorize the internal GET with action cookies that are valid for this exact owner URL. */
30
+ export function progressiveCookieHeader(
31
+ request: Request,
32
+ changes: readonly ProgressiveCookie[],
33
+ ): string {
34
+ const url = new URL(request.url)
35
+ let pairs = (request.headers.get("cookie") ?? "")
36
+ .split(";")
37
+ .map((part) => part.trim())
38
+ .filter(Boolean)
39
+ for (const change of changes) {
40
+ const options = change.options
41
+ const domain = options?.domain?.replace(/^\./, "").toLowerCase()
42
+ // Parent-domain cookies need a browser cookie jar/public-suffix decision; never synthesize them.
43
+ if (domain !== undefined && domain !== url.hostname.toLowerCase()) continue
44
+ if (secureOnPlainHttp(url, change)) continue
45
+ const path = options?.path ?? "/"
46
+ if (
47
+ url.pathname !== path &&
48
+ !(url.pathname.startsWith(path) && (path.endsWith("/") || url.pathname[path.length] === "/"))
49
+ )
50
+ continue
51
+ pairs = pairs.filter((pair) => pair.slice(0, pair.indexOf("=")).trim() !== change.name)
52
+ const expired =
53
+ options?.maxAge !== undefined
54
+ ? options.maxAge <= 0
55
+ : options?.expires !== undefined && options.expires.getTime() <= Date.now()
56
+ if (!change.deleted && !expired)
57
+ pairs.push(change.name + "=" + encodeURIComponent(change.value))
58
+ }
59
+ return pairs.join("; ")
60
+ }
@@ -0,0 +1,14 @@
1
+ export function publicEnvDefines(
2
+ prefix: string,
3
+ env: Readonly<Record<string, string | undefined>>,
4
+ ): Record<string, string> {
5
+ const defines: Record<string, string> = {}
6
+ if (prefix === "") return defines // opt-out: bake in nothing
7
+ for (const [name, value] of Object.entries(env)) {
8
+ // Skip `undefined` (a deleted/unset key can still enumerate) so we never bake in `"undefined"`.
9
+ if (name.startsWith(prefix) && value !== undefined) {
10
+ defines[`process.env.${name}`] = JSON.stringify(value)
11
+ }
12
+ }
13
+ return defines
14
+ }
@@ -6,15 +6,12 @@ import { DEFERRED_ERROR_CODE, DEFERRED_RUNTIME, prepareDeferred } from "../defer
6
6
  import { ISR_REVALIDATE_HEADER, ISR_REVALIDATE_TAGS_HEADER, serializeISRTags } from "../isr.ts"
7
7
  import type {
8
8
  Loader as LayoutLoader,
9
- LinkDescriptor,
10
9
  Meta,
11
- MetaDescriptor,
12
10
  MetaInput,
13
11
  RouteModule,
14
- ScriptDescriptor,
15
12
  UnsafeScriptDescriptor,
16
13
  } from "../manifest.ts"
17
- import type { MatchChain, RenderAdapter, RenderProps } from "../render-seam.ts"
14
+ import type { DocumentParts, MatchChain, RenderAdapter, RenderProps } from "../render-seam.ts"
18
15
  import {
19
16
  ACTION_GLOBAL,
20
17
  BOUNDARY_GLOBAL,
@@ -76,6 +73,7 @@ export interface RenderedPage {
76
73
  }
77
74
 
78
75
  export interface RenderPageOptions {
76
+ readonly serverComponents?: import("../server-components/types.ts").ComponentState
79
77
  readonly adapter: RenderAdapter
80
78
  /** The layout chain to render - outermost layout → page (opaque; the adapter renders it). */
81
79
  readonly chain: readonly unknown[]
@@ -200,8 +198,10 @@ export interface RenderPageOptions {
200
198
  export interface RenderAssemblyCache {
201
199
  /** Shell up to (not including) the deferred-runtime insertion point. */
202
200
  shellPre?: string
203
- /** Shell from after the deferred-runtime insertion point through the open `#root` container. */
201
+ /** Shell from after the deferred-runtime insertion point through `<body>`. */
204
202
  shellPost?: string
203
+ /** The open `#root` container. */
204
+ rootOpen?: string
205
205
  /** Tail opener: the handover script open tag + the data member's key. */
206
206
  tailPre?: string
207
207
  /** Tail from after the per-request handover members (the route id, prerendered paths, the handover
@@ -343,6 +343,10 @@ export function renderPageResult(options: RenderPageInput): MaybePromise<Rendere
343
343
  boundarySplit === undefined
344
344
  ? ""
345
345
  : `,"${BOUNDARY_GLOBAL}":${serializeData(boundarySplit.forClient)}`
346
+ const componentTail =
347
+ options.serverComponents === undefined
348
+ ? ""
349
+ : `,"__NIFRA_COMPONENTS__":${serializeData(options.serverComponents)}`
346
350
  const deferredRuntime =
347
351
  allDeferred.length > 0 ? `<script${nonceAttr}>${DEFERRED_RUNTIME}</script>` : ""
348
352
  // The regex only injects the CSP nonce into the (constant) hydration head; with no nonce it's a
@@ -418,7 +422,8 @@ export function renderPageResult(options: RenderPageInput): MaybePromise<Rendere
418
422
  ? `<link rel="modulepreload" href="${escapeAttr(clientEntry ?? "")}">${preloadLinks}`
419
423
  : ""
420
424
  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}`
421
- slot.shellPost = `${islandPreloads}${hydrationHead}</head><body><div id="${escapeAttr(rootId)}"${rootMarker}>`
425
+ slot.shellPost = `${islandPreloads}${hydrationHead}</head><body>`
426
+ slot.rootOpen = `<div id="${escapeAttr(rootId)}"${rootMarker}>`
422
427
  // Island bundles load regardless of `hydrate` - a static page (hydrate:false) ships no framework
423
428
  // client but can still mount no-framework islands (`@nifrajs/web/islands`).
424
429
  let islandTags = ""
@@ -436,14 +441,20 @@ export function renderPageResult(options: RenderPageInput): MaybePromise<Rendere
436
441
  // The deferred runtime rides only on a hydrating page (a static page has no client takeover), and
437
442
  // only when something actually deferred - matching the pre-cache emission exactly.
438
443
  const runtimeSeam = hydrate && allDeferred.length > 0 ? deferredRuntime : ""
439
- const shellHtml = `${slot.shellPre}${runtimeSeam}${slot.shellPost}`
444
+ const shellHtml = `${slot.shellPre}${runtimeSeam}${slot.shellPost}${slot.rootOpen}`
445
+ const parts: DocumentParts | undefined =
446
+ adapter.documentParts === true ? { head: "", body: "" } : undefined
447
+ const shellOf = (): string =>
448
+ parts === undefined || (parts.head === "" && parts.body === "")
449
+ ? shellHtml
450
+ : `${slot.shellPre}${runtimeSeam}${parts.head}${slot.shellPost}${parts.body}${slot.rootOpen}`
440
451
  // Closes the hydration container; deferred resolve scripts go AFTER it (outside `#root`) so they
441
452
  // aren't part of the adapter's hydrated tree (an inline script inside it breaks hydration).
442
453
  const closeRootHtml = "</div>"
443
454
  // Tail - the page-state handover + the client module. Module scripts defer (run after parse), so
444
455
  // the handover + every streamed deferred resolution are in the document before the entry hydrates.
445
456
  const tailHtml = hydrate
446
- ? `${slot.tailPre}${serializeData(forClient)}${action}${layoutTail}${boundaryTail}${slot.tailPost}`
457
+ ? `${slot.tailPre}${serializeData(forClient)}${action}${layoutTail}${boundaryTail}${componentTail}${slot.tailPost}`
447
458
  : (slot.tailPost as string)
448
459
  const headers: Record<string, string> = { "content-type": "text/html; charset=utf-8" }
449
460
  // Caller-supplied headers (a terminal status page's `cache-control`, say). Applied before the
@@ -469,6 +480,9 @@ export function renderPageResult(options: RenderPageInput): MaybePromise<Rendere
469
480
  if (policy !== undefined) headers["content-security-policy"] = policy
470
481
  }
471
482
  const renderProps: RenderProps = {
483
+ ...(options.serverComponents === undefined
484
+ ? {}
485
+ : { serverComponents: options.serverComponents }),
472
486
  data: forComponent,
473
487
  actionData: actionSplit?.forComponent,
474
488
  // `params`/`path` thread the matched route + URL to the adapter's `useParams`/`useLocation` so they
@@ -496,13 +510,22 @@ export function renderPageResult(options: RenderPageInput): MaybePromise<Rendere
496
510
  // gets an automatic Content-Length. A render throw surfaces here exactly as the streaming path's
497
511
  // shell-readiness `await` does, so the `_error` boundary still maps it to a status.
498
512
  if (allDeferred.length === 0 && adapter.renderToString !== undefined) {
499
- const out = adapter.renderToString(chain, renderProps)
513
+ const out =
514
+ parts !== undefined
515
+ ? adapter.renderToString(
516
+ chain,
517
+ renderProps,
518
+ nonce === undefined ? { parts } : { nonce, parts },
519
+ )
520
+ : nonce === undefined
521
+ ? adapter.renderToString(chain, renderProps)
522
+ : adapter.renderToString(chain, renderProps, { nonce })
500
523
  return typeof out === "string"
501
- ? new BufferedRenderedPage(shellHtml + out + closeRootHtml + tailHtml, status, headers)
524
+ ? new BufferedRenderedPage(shellOf() + out + closeRootHtml + tailHtml, status, headers)
502
525
  : out.then(
503
526
  (bodyHtml) =>
504
527
  new BufferedRenderedPage(
505
- shellHtml + bodyHtml + closeRootHtml + tailHtml,
528
+ shellOf() + bodyHtml + closeRootHtml + tailHtml,
506
529
  status,
507
530
  headers,
508
531
  ),
@@ -513,7 +536,8 @@ export function renderPageResult(options: RenderPageInput): MaybePromise<Rendere
513
536
  adapter,
514
537
  chain,
515
538
  renderProps,
516
- shellHtml,
539
+ shellOf,
540
+ parts,
517
541
  closeRootHtml,
518
542
  allDeferred,
519
543
  tailHtml,
@@ -528,7 +552,8 @@ async function renderStreamedPage(
528
552
  adapter: RenderAdapter,
529
553
  chain: readonly unknown[],
530
554
  renderProps: RenderProps,
531
- shellHtml: string,
555
+ shellOf: () => string,
556
+ parts: DocumentParts | undefined,
532
557
  closeRootHtml: string,
533
558
  allDeferred: ReadonlyArray<{ readonly id: number; readonly promise: Promise<unknown> }>,
534
559
  tailHtml: string,
@@ -542,13 +567,20 @@ async function renderStreamedPage(
542
567
  // (React: on-shell-ready; Solid: synchronously), so a shell-render throw surfaces before any byte is
543
568
  // sent. A *mid*-stream failure errors the body instead.
544
569
  const enc = TEXT_ENCODER
545
- const shell = enc.encode(shellHtml)
546
570
  const closeRoot = enc.encode(closeRootHtml)
547
571
  const tail = enc.encode(tailHtml)
548
572
  const appStream =
549
- nonce === undefined
550
- ? await adapter.renderToStream(chain, renderProps)
551
- : await adapter.renderToStream(chain, renderProps, { nonce })
573
+ parts !== undefined
574
+ ? await adapter.renderToStream(
575
+ chain,
576
+ renderProps,
577
+ nonce === undefined ? { parts } : { nonce, parts },
578
+ )
579
+ : nonce === undefined
580
+ ? await adapter.renderToStream(chain, renderProps)
581
+ : await adapter.renderToStream(chain, renderProps, { nonce })
582
+ // After the render: an adapter fills its document parts before handing back the stream.
583
+ const shell = enc.encode(shellOf())
552
584
  const body = streamDocument(shell, appStream, closeRoot, allDeferred, tail, enc, nonceAttr)
553
585
  return new Response(body, { status, headers })
554
586
  }
@@ -616,13 +648,17 @@ function streamDocument(
616
648
  enc: TextEncoder,
617
649
  nonceAttr: string,
618
650
  ): ReadableStream<Uint8Array> {
651
+ const reader = stream.getReader()
652
+ // A client that goes away cancels the body: the renderer stops, and nothing more is written.
653
+ let cancelled = false
654
+ let released = false
619
655
  return new ReadableStream({
620
656
  async start(controller) {
621
- const reader = stream.getReader()
622
657
  try {
623
658
  controller.enqueue(shell)
624
659
  for (;;) {
625
660
  const { done, value } = await reader.read()
661
+ if (cancelled) return
626
662
  if (done) break
627
663
  controller.enqueue(value)
628
664
  }
@@ -631,34 +667,37 @@ function streamDocument(
631
667
  // defer() must not block a faster one; each script self-addresses by id, so order is irrelevant.
632
668
  await Promise.all(
633
669
  deferred.map(async (d) => {
670
+ let script: string
634
671
  try {
635
672
  const value = serializeData(await d.promise)
636
673
  // `serializeData` JSON-serializes the value and escapes `<`, `>`, and Unicode line
637
674
  // separators before this framework-owned script is emitted.
638
- controller.enqueue(
639
- enc.encode(`<script${nonceAttr}>window.__nifraResolve(${d.id},${value})</script>`), // nosemgrep: javascript.lang.security.audit.unknown-value-with-script-tag.unknown-value-with-script-tag
640
- )
675
+ script = `<script${nonceAttr}>window.__nifraResolve(${d.id},${value})</script>` // nosemgrep: javascript.lang.security.audit.unknown-value-with-script-tag.unknown-value-with-script-tag
641
676
  } catch (err) {
642
677
  // A rejected deferred streams __nifraReject (the client `<Await>` surfaces it) - it must
643
678
  // not break the whole body. Redact: stream a stable opaque code, never the raw error
644
679
  // text; log the real reason server-side.
645
680
  console.error("[nifra/web] deferred value rejected:", err)
646
- controller.enqueue(
647
- enc.encode(
648
- `<script${nonceAttr}>window.__nifraReject(${d.id},${serializeData(DEFERRED_ERROR_CODE)})</script>`,
649
- ),
650
- )
681
+ script = `<script${nonceAttr}>window.__nifraReject(${d.id},${serializeData(DEFERRED_ERROR_CODE)})</script>`
651
682
  }
683
+ if (!cancelled) controller.enqueue(enc.encode(script))
652
684
  }),
653
685
  )
686
+ if (cancelled) return
654
687
  controller.enqueue(tail)
655
688
  controller.close()
656
689
  } catch (err) {
657
- controller.error(err)
690
+ if (!cancelled) controller.error(err)
658
691
  } finally {
692
+ released = true
659
693
  reader.releaseLock()
660
694
  }
661
695
  },
696
+ cancel(reason) {
697
+ cancelled = true
698
+ // Once the renderer is drained the reader is released, and there is nothing left to stop.
699
+ return released ? undefined : reader.cancel(reason)
700
+ },
662
701
  })
663
702
  }
664
703
 
@@ -1108,68 +1147,6 @@ function headTags(head: Meta | undefined, documentNonce?: string): string {
1108
1147
  return out
1109
1148
  }
1110
1149
 
1111
- /**
1112
- * A `<link rel="canonical">` descriptor for a route's `meta.link`. The canonical URL tells search
1113
- * engines which URL is authoritative for a page (deduping query-string / tracking variants).
1114
- *
1115
- * ```ts
1116
- * export const meta = (a) => ({ link: [canonical(`https://site.com/posts/${a.params.slug}`)] })
1117
- * ```
1118
- */
1119
- export function canonical(href: string): LinkDescriptor {
1120
- return { rel: "canonical", href }
1121
- }
1122
-
1123
- /** Inputs for {@link openGraph} - the common Open Graph properties. All optional; only the provided
1124
- * ones become tags. `type` defaults to `"website"`. */
1125
- export interface OpenGraphInput {
1126
- readonly title?: string
1127
- readonly description?: string
1128
- /** Absolute URL of the share image (`og:image`). */
1129
- readonly image?: string
1130
- /** Canonical URL of the page (`og:url`). */
1131
- readonly url?: string
1132
- /** Object type (`og:type`) - `"website"`, `"article"`, … Default `"website"`. */
1133
- readonly type?: string
1134
- }
1135
-
1136
- /**
1137
- * Build the Open Graph `<meta property="og:*">` entries for a route's `meta.meta`. Returns only the
1138
- * properties you supplied (plus `og:type`, defaulting to `"website"`), so it composes with other meta.
1139
- *
1140
- * ```ts
1141
- * export const meta = { meta: [...openGraph({ title: "Nifra", image: "https://site.com/og.png" })] }
1142
- * ```
1143
- */
1144
- export function openGraph(input: OpenGraphInput): MetaDescriptor[] {
1145
- const tags: MetaDescriptor[] = []
1146
- const add = (property: string, content: string | undefined): void => {
1147
- if (content !== undefined) tags.push({ property, content })
1148
- }
1149
- add("og:title", input.title)
1150
- add("og:description", input.description)
1151
- add("og:image", input.image)
1152
- add("og:url", input.url)
1153
- // `og:type` always present (the spec's required property) - default "website" when unset.
1154
- tags.push({ property: "og:type", content: input.type ?? "website" })
1155
- return tags
1156
- }
1157
-
1158
- /**
1159
- * Build a JSON-LD `<script type="application/ld+json">` entry for a route's `meta.script` from a plain
1160
- * object. `JSON.stringify` produces the body; the head renderer breakout-escapes it (see
1161
- * `escapeScriptContent`), so a string field containing `</script>` is embedded safely.
1162
- *
1163
- * ```ts
1164
- * export const meta = {
1165
- * script: [jsonLd({ "@context": "https://schema.org", "@type": "Article", headline: "Hi" })],
1166
- * }
1167
- * ```
1168
- */
1169
- export function jsonLd(data: Record<string, unknown>): ScriptDescriptor {
1170
- return { type: "application/ld+json", content: JSON.stringify(data) }
1171
- }
1172
-
1173
1150
  /**
1174
1151
  * Deliberately unsafe escape hatch for executable inline code. The required nonce keeps the result
1175
1152
  * compatible with a strict CSP and makes the security-sensitive choice visible at the call site.
@@ -2,9 +2,15 @@ import {
2
2
  type CookieOptions,
3
3
  type ResponseResult,
4
4
  status as statusResult,
5
+ writableResponse,
5
6
  } from "@nifrajs/core/server"
6
7
  import type { LoaderResponseControls } from "../manifest.ts"
7
8
  import { DATA_HEADER } from "../router.ts"
9
+ import {
10
+ type ProgressiveCookie,
11
+ progressiveCookieHeader,
12
+ progressiveCookieRedirect,
13
+ } from "./progressive-cookies.ts"
8
14
  import { isResponseResult } from "./render-document.ts"
9
15
 
10
16
  /** The serving app's response controls - the structural subset of a route handler's `c.set` used here. */
@@ -107,19 +113,9 @@ export function privateOutcome(outcome: unknown): unknown {
107
113
 
108
114
  function privateResponse(response: Response): Response {
109
115
  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
- }
116
+ const target = writableResponse(response)
117
+ target.headers.set(CACHE_CONTROL, PRIVATE_NO_STORE)
118
+ return target
123
119
  }
124
120
 
125
121
  interface WrittenHeaders {
@@ -140,6 +136,7 @@ export class PageResponseControls {
140
136
  #written: WrittenHeaders[] | undefined
141
137
  #sealed = false
142
138
  #personalized = false
139
+ #cookieChanges: ProgressiveCookie[] | undefined
143
140
 
144
141
  constructor(c: { readonly set: CoreResponseControls }) {
145
142
  this.#c = c
@@ -167,7 +164,27 @@ export class PageResponseControls {
167
164
  cookies(member: string): CoreResponseControls {
168
165
  this.assertOpen(member)
169
166
  this.personalize()
170
- return this.#c.set
167
+ const controls = this.#c.set
168
+ return {
169
+ headers: controls.headers,
170
+ cookie: (name, value, options) => {
171
+ controls.cookie(name, value, options)
172
+ this.#cookieChanges ??= []
173
+ this.#cookieChanges.push({ name, value, options })
174
+ },
175
+ deleteCookie: (name, options) => {
176
+ controls.deleteCookie(name, options)
177
+ this.#cookieChanges ??= []
178
+ this.#cookieChanges.push({ name, value: "", options, deleted: true })
179
+ },
180
+ }
181
+ }
182
+
183
+ progressiveCookies(request: Request): string {
184
+ return progressiveCookieHeader(request, this.#cookieChanges ?? [])
185
+ }
186
+ progressiveRedirect(request: Request): boolean {
187
+ return progressiveCookieRedirect(request, this.#cookieChanges ?? [])
171
188
  }
172
189
 
173
190
  /** Mark the response as one visitor's - a queued cookie, or a draft render - so no cache stores it. */
@@ -0,0 +1,35 @@
1
+ /**
2
+ * `t.date()` across the wire, browser half: every place the browser parses page data rebuilds the
3
+ * `{ "__nifra_date": iso }` tags the server's `wireDate()` wrote into `Date`s. Kept apart from
4
+ * `wire-date.ts` so client bundles never carry the server class.
5
+ */
6
+
7
+ export const DATE_TAG = "__nifra_date"
8
+ const TAGGED = `"${DATE_TAG}"`
9
+
10
+ // A JSON.parse reviver runs leaves first and defines each result as an own property, so a key named
11
+ // `__proto__` never becomes the prototype.
12
+ const revive = (_key: string, value: unknown): unknown => {
13
+ if (typeof value !== "object" || value === null) return value
14
+ const tagged: unknown = Reflect.get(value, DATE_TAG)
15
+ return typeof tagged === "string" && Object.keys(value).length === 1 ? new Date(tagged) : value
16
+ }
17
+
18
+ /** `JSON.parse` that rebuilds every `{ "__nifra_date": iso }` into a `Date`, when the text has one. */
19
+ export const parsePageData = (text: string): unknown =>
20
+ text.includes(TAGGED) ? JSON.parse(text, revive) : JSON.parse(text)
21
+
22
+ /**
23
+ * {@link parsePageData}'s reviver as source declaring `__nifraDates`, for client code nifra generates rather than
24
+ * bundles (server-function stubs; `DEFERRED_RUNTIME` carries the same text). A literal with nothing
25
+ * interpolated: Bun keeps a template that interpolates an import, and with it every string built from
26
+ * it, in client bundles that never use them.
27
+ */
28
+ export const REVIVE_DATES_SOURCE = `const __nifraDates = (d) => {
29
+ if (!d || typeof d !== "object") return d
30
+ if (Array.isArray(d)) { for (let i = 0; i < d.length; i++) d[i] = __nifraDates(d[i]); return d }
31
+ const k = Object.keys(d)
32
+ if (k.length === 1 && k[0] === "__nifra_date" && typeof d.__nifra_date === "string") return new Date(d.__nifra_date)
33
+ for (const x of k) { const v = __nifraDates(d[x]); if (v !== d[x]) Object.defineProperty(d, x, { value: v, enumerable: true, writable: true, configurable: true }) }
34
+ return d
35
+ }`
@@ -0,0 +1,24 @@
1
+ /**
2
+ * A route pattern's literal text as a request path spells it. A route file named `café.tsx` is
3
+ * requested as `/caf%C3%A9`, so the routers, which compare the raw request path, are given the pattern
4
+ * that way. Only controls, space and non-ASCII are encoded (what a browser encodes in a path), so ASCII
5
+ * punctuation such as a param constraint keeps its meaning. Runs once per pattern, never per request.
6
+ */
7
+ export const requestPattern = (pattern: string): string =>
8
+ pattern.normalize("NFC").replace(/[^!-~]/gu, encodeURIComponent)
9
+
10
+ // This module also compiles without the DOM lib; at runtime the name is the browser's own global.
11
+ declare const location: { readonly origin: string } | undefined
12
+
13
+ /**
14
+ * An app path as a URL `fetch`, `history` and `location` resolve on this document's own origin. A path
15
+ * starting `//` or `/\` is the path of a page here (`https://shop.example//evil.example/x`), but resolved
16
+ * as written it is a protocol-relative URL to another host, so it is anchored to this origin.
17
+ */
18
+ export const appUrl = (path: string): string =>
19
+ path.charCodeAt(0) === 47 /* / */ &&
20
+ (path.charCodeAt(1) === 47 || path.charCodeAt(1) === 92) /* / or \ */ &&
21
+ typeof location !== "undefined" &&
22
+ location.origin !== "null"
23
+ ? location.origin + path
24
+ : path