@nifrajs/web 4.0.2 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (384) hide show
  1. package/README.md +11 -0
  2. package/dist/build-vite.d.ts +19 -0
  3. package/dist/build-vite.d.ts.map +1 -1
  4. package/dist/build-vite.js +31 -10
  5. package/dist/build-vite.js.map +1 -1
  6. package/dist/build.d.ts +8 -10
  7. package/dist/build.d.ts.map +1 -1
  8. package/dist/build.js +90 -31
  9. package/dist/build.js.map +1 -1
  10. package/dist/cdn.d.ts +8 -0
  11. package/dist/cdn.d.ts.map +1 -1
  12. package/dist/cdn.js +14 -15
  13. package/dist/cdn.js.map +1 -1
  14. package/dist/client.d.ts +1 -1
  15. package/dist/client.d.ts.map +1 -1
  16. package/dist/client.js +25 -7
  17. package/dist/client.js.map +1 -1
  18. package/dist/deferred.d.ts +2 -2
  19. package/dist/deferred.d.ts.map +1 -1
  20. package/dist/deferred.js +28 -7
  21. package/dist/deferred.js.map +1 -1
  22. package/dist/dev-feed.d.ts.map +1 -1
  23. package/dist/dev-feed.js +24 -5
  24. package/dist/dev-feed.js.map +1 -1
  25. package/dist/dev-session.d.ts.map +1 -1
  26. package/dist/dev-session.js +4 -14
  27. package/dist/dev-session.js.map +1 -1
  28. package/dist/dev.d.ts +7 -17
  29. package/dist/dev.d.ts.map +1 -1
  30. package/dist/dev.js +13 -1
  31. package/dist/dev.js.map +1 -1
  32. package/dist/draft.d.ts +8 -5
  33. package/dist/draft.d.ts.map +1 -1
  34. package/dist/draft.js +30 -13
  35. package/dist/draft.js.map +1 -1
  36. package/dist/fonts-google.d.ts.map +1 -1
  37. package/dist/fonts-google.js +7 -0
  38. package/dist/fonts-google.js.map +1 -1
  39. package/dist/index.d.ts +2 -2
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +2 -2
  42. package/dist/index.js.map +1 -1
  43. package/dist/internal/codegen.d.ts +1 -0
  44. package/dist/internal/codegen.d.ts.map +1 -1
  45. package/dist/internal/codegen.js +56 -29
  46. package/dist/internal/codegen.js.map +1 -1
  47. package/dist/internal/disposable-dir.d.ts +8 -0
  48. package/dist/internal/disposable-dir.d.ts.map +1 -0
  49. package/dist/internal/disposable-dir.js +25 -0
  50. package/dist/internal/disposable-dir.js.map +1 -0
  51. package/dist/internal/entry-chunks.d.ts +9 -0
  52. package/dist/internal/entry-chunks.d.ts.map +1 -0
  53. package/dist/internal/entry-chunks.js +15 -0
  54. package/dist/internal/entry-chunks.js.map +1 -0
  55. package/dist/internal/evidence-traversal.d.ts +12 -0
  56. package/dist/internal/evidence-traversal.d.ts.map +1 -0
  57. package/dist/internal/evidence-traversal.js +48 -0
  58. package/dist/internal/evidence-traversal.js.map +1 -0
  59. package/dist/internal/head-merge.d.ts +6 -0
  60. package/dist/internal/head-merge.d.ts.map +1 -1
  61. package/dist/internal/head-merge.js +9 -0
  62. package/dist/internal/head-merge.js.map +1 -1
  63. package/dist/internal/js-string.d.ts +2 -0
  64. package/dist/internal/js-string.d.ts.map +1 -1
  65. package/dist/internal/js-string.js +5 -1
  66. package/dist/internal/js-string.js.map +1 -1
  67. package/dist/internal/mount-shadow.js +1 -1
  68. package/dist/internal/mount-shadow.js.map +1 -1
  69. package/dist/internal/output-guard.d.ts +7 -0
  70. package/dist/internal/output-guard.d.ts.map +1 -1
  71. package/dist/internal/output-guard.js +80 -8
  72. package/dist/internal/output-guard.js.map +1 -1
  73. package/dist/internal/page-execution.d.ts +6 -0
  74. package/dist/internal/page-execution.d.ts.map +1 -1
  75. package/dist/internal/page-execution.js +377 -108
  76. package/dist/internal/page-execution.js.map +1 -1
  77. package/dist/internal/progressive-cookies.d.ts +14 -0
  78. package/dist/internal/progressive-cookies.d.ts.map +1 -0
  79. package/dist/internal/progressive-cookies.js +43 -0
  80. package/dist/internal/progressive-cookies.js.map +1 -0
  81. package/dist/internal/public-env.d.ts +2 -0
  82. package/dist/internal/public-env.d.ts.map +1 -0
  83. package/dist/internal/public-env.js +13 -0
  84. package/dist/internal/public-env.js.map +1 -0
  85. package/dist/internal/render-document.d.ts +1 -0
  86. package/dist/internal/render-document.d.ts.map +1 -1
  87. package/dist/internal/render-document.js +28 -5
  88. package/dist/internal/render-document.js.map +1 -1
  89. package/dist/internal/response-controls.d.ts +2 -0
  90. package/dist/internal/response-controls.d.ts.map +1 -1
  91. package/dist/internal/response-controls.js +26 -16
  92. package/dist/internal/response-controls.js.map +1 -1
  93. package/dist/internal/revive-dates.d.ts +16 -0
  94. package/dist/internal/revive-dates.d.ts.map +1 -0
  95. package/dist/internal/revive-dates.js +32 -0
  96. package/dist/internal/revive-dates.js.map +1 -0
  97. package/dist/internal/route-path.d.ts +8 -0
  98. package/dist/internal/route-path.d.ts.map +1 -0
  99. package/dist/internal/route-path.js +8 -0
  100. package/dist/internal/route-path.js.map +1 -0
  101. package/dist/internal/secret-scan.d.ts.map +1 -1
  102. package/dist/internal/secret-scan.js +88 -20
  103. package/dist/internal/secret-scan.js.map +1 -1
  104. package/dist/internal/server-boundary.d.ts.map +1 -1
  105. package/dist/internal/server-boundary.js +33 -5
  106. package/dist/internal/server-boundary.js.map +1 -1
  107. package/dist/internal/web-app.d.ts +21 -16
  108. package/dist/internal/web-app.d.ts.map +1 -1
  109. package/dist/internal/web-app.js +73 -21
  110. package/dist/internal/web-app.js.map +1 -1
  111. package/dist/internal/wire-date.d.ts +3 -0
  112. package/dist/internal/wire-date.d.ts.map +1 -0
  113. package/dist/internal/wire-date.js +14 -0
  114. package/dist/internal/wire-date.js.map +1 -0
  115. package/dist/internal/zone-graph.d.ts +4 -0
  116. package/dist/internal/zone-graph.d.ts.map +1 -1
  117. package/dist/internal/zone-graph.js +54 -14
  118. package/dist/internal/zone-graph.js.map +1 -1
  119. package/dist/isr.d.ts +58 -2
  120. package/dist/isr.d.ts.map +1 -1
  121. package/dist/isr.js +296 -39
  122. package/dist/isr.js.map +1 -1
  123. package/dist/llms-txt.d.ts +2 -0
  124. package/dist/llms-txt.d.ts.map +1 -1
  125. package/dist/llms-txt.js +29 -3
  126. package/dist/llms-txt.js.map +1 -1
  127. package/dist/manifest.d.ts +28 -4
  128. package/dist/manifest.d.ts.map +1 -1
  129. package/dist/manifest.js +3 -0
  130. package/dist/manifest.js.map +1 -1
  131. package/dist/open-resolved.d.ts +18 -0
  132. package/dist/open-resolved.d.ts.map +1 -0
  133. package/dist/open-resolved.js +62 -0
  134. package/dist/open-resolved.js.map +1 -0
  135. package/dist/plugins/kit.d.ts +1 -0
  136. package/dist/plugins/kit.d.ts.map +1 -1
  137. package/dist/plugins/kit.js +26 -11
  138. package/dist/plugins/kit.js.map +1 -1
  139. package/dist/plugins/vite-leak-guard.d.ts +3 -1
  140. package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
  141. package/dist/plugins/vite-leak-guard.js +4 -0
  142. package/dist/plugins/vite-leak-guard.js.map +1 -1
  143. package/dist/public-dir.d.ts +8 -1
  144. package/dist/public-dir.d.ts.map +1 -1
  145. package/dist/public-dir.js +17 -6
  146. package/dist/public-dir.js.map +1 -1
  147. package/dist/query.d.ts +13 -4
  148. package/dist/query.d.ts.map +1 -1
  149. package/dist/query.js +89 -26
  150. package/dist/query.js.map +1 -1
  151. package/dist/render-seam.d.ts +2 -0
  152. package/dist/render-seam.d.ts.map +1 -1
  153. package/dist/render-seam.js +1 -0
  154. package/dist/render-seam.js.map +1 -1
  155. package/dist/route-types.d.ts.map +1 -1
  156. package/dist/route-types.js +20 -9
  157. package/dist/route-types.js.map +1 -1
  158. package/dist/router.d.ts +9 -2
  159. package/dist/router.d.ts.map +1 -1
  160. package/dist/router.js +39 -46
  161. package/dist/router.js.map +1 -1
  162. package/dist/server-components/build-info.d.ts +3 -0
  163. package/dist/server-components/build-info.d.ts.map +1 -0
  164. package/dist/server-components/build-info.js +7 -0
  165. package/dist/server-components/build-info.js.map +1 -0
  166. package/dist/server-components/build-target.d.ts +12 -0
  167. package/dist/server-components/build-target.d.ts.map +1 -0
  168. package/dist/server-components/build-target.js +153 -0
  169. package/dist/server-components/build-target.js.map +1 -0
  170. package/dist/server-components/build.d.ts +24 -0
  171. package/dist/server-components/build.d.ts.map +1 -0
  172. package/dist/server-components/build.js +366 -0
  173. package/dist/server-components/build.js.map +1 -0
  174. package/dist/server-components/bundle-proof.d.ts +3 -0
  175. package/dist/server-components/bundle-proof.d.ts.map +1 -0
  176. package/dist/server-components/bundle-proof.js +23 -0
  177. package/dist/server-components/bundle-proof.js.map +1 -0
  178. package/dist/server-components/client.d.ts +46 -0
  179. package/dist/server-components/client.d.ts.map +1 -0
  180. package/dist/server-components/client.js +594 -0
  181. package/dist/server-components/client.js.map +1 -0
  182. package/dist/server-components/definition.d.ts +11 -0
  183. package/dist/server-components/definition.d.ts.map +1 -0
  184. package/dist/server-components/definition.js +94 -0
  185. package/dist/server-components/definition.js.map +1 -0
  186. package/dist/server-components/dev-http.d.ts +5 -0
  187. package/dist/server-components/dev-http.d.ts.map +1 -0
  188. package/dist/server-components/dev-http.js +71 -0
  189. package/dist/server-components/dev-http.js.map +1 -0
  190. package/dist/server-components/error.d.ts +9 -0
  191. package/dist/server-components/error.d.ts.map +1 -0
  192. package/dist/server-components/error.js +24 -0
  193. package/dist/server-components/error.js.map +1 -0
  194. package/dist/server-components/frames.d.ts +7 -0
  195. package/dist/server-components/frames.d.ts.map +1 -0
  196. package/dist/server-components/frames.js +169 -0
  197. package/dist/server-components/frames.js.map +1 -0
  198. package/dist/server-components/html-policy.d.ts +6 -0
  199. package/dist/server-components/html-policy.d.ts.map +1 -0
  200. package/dist/server-components/html-policy.js +75 -0
  201. package/dist/server-components/html-policy.js.map +1 -0
  202. package/dist/server-components/html.d.ts +4 -0
  203. package/dist/server-components/html.d.ts.map +1 -0
  204. package/dist/server-components/html.js +67 -0
  205. package/dist/server-components/html.js.map +1 -0
  206. package/dist/server-components/islands.d.ts +44 -0
  207. package/dist/server-components/islands.d.ts.map +1 -0
  208. package/dist/server-components/islands.js +42 -0
  209. package/dist/server-components/islands.js.map +1 -0
  210. package/dist/server-components/native.d.ts +4 -0
  211. package/dist/server-components/native.d.ts.map +1 -0
  212. package/dist/server-components/native.js +81 -0
  213. package/dist/server-components/native.js.map +1 -0
  214. package/dist/server-components/portal.d.ts +6 -0
  215. package/dist/server-components/portal.d.ts.map +1 -0
  216. package/dist/server-components/portal.js +35 -0
  217. package/dist/server-components/portal.js.map +1 -0
  218. package/dist/server-components/props.d.ts +3 -0
  219. package/dist/server-components/props.d.ts.map +1 -0
  220. package/dist/server-components/props.js +14 -0
  221. package/dist/server-components/props.js.map +1 -0
  222. package/dist/server-components/protocol.d.ts +44 -0
  223. package/dist/server-components/protocol.d.ts.map +1 -0
  224. package/dist/server-components/protocol.js +139 -0
  225. package/dist/server-components/protocol.js.map +1 -0
  226. package/dist/server-components/range.d.ts +24 -0
  227. package/dist/server-components/range.d.ts.map +1 -0
  228. package/dist/server-components/range.js +263 -0
  229. package/dist/server-components/range.js.map +1 -0
  230. package/dist/server-components/registry.d.ts +6 -0
  231. package/dist/server-components/registry.d.ts.map +1 -0
  232. package/dist/server-components/registry.js +30 -0
  233. package/dist/server-components/registry.js.map +1 -0
  234. package/dist/server-components/response.d.ts +4 -0
  235. package/dist/server-components/response.d.ts.map +1 -0
  236. package/dist/server-components/response.js +32 -0
  237. package/dist/server-components/response.js.map +1 -0
  238. package/dist/server-components/router-integration.d.ts +9 -0
  239. package/dist/server-components/router-integration.d.ts.map +1 -0
  240. package/dist/server-components/router-integration.js +29 -0
  241. package/dist/server-components/router-integration.js.map +1 -0
  242. package/dist/server-components/runtime.d.ts +14 -0
  243. package/dist/server-components/runtime.d.ts.map +1 -0
  244. package/dist/server-components/runtime.js +276 -0
  245. package/dist/server-components/runtime.js.map +1 -0
  246. package/dist/server-components/scheduler.d.ts +14 -0
  247. package/dist/server-components/scheduler.d.ts.map +1 -0
  248. package/dist/server-components/scheduler.js +74 -0
  249. package/dist/server-components/scheduler.js.map +1 -0
  250. package/dist/server-components/schema.d.ts +4 -0
  251. package/dist/server-components/schema.d.ts.map +1 -0
  252. package/dist/server-components/schema.js +77 -0
  253. package/dist/server-components/schema.js.map +1 -0
  254. package/dist/server-components/server-entry.d.ts +12 -0
  255. package/dist/server-components/server-entry.d.ts.map +1 -0
  256. package/dist/server-components/server-entry.js +90 -0
  257. package/dist/server-components/server-entry.js.map +1 -0
  258. package/dist/server-components/source.d.ts +15 -0
  259. package/dist/server-components/source.d.ts.map +1 -0
  260. package/dist/server-components/source.js +269 -0
  261. package/dist/server-components/source.js.map +1 -0
  262. package/dist/server-components/state.d.ts +4 -0
  263. package/dist/server-components/state.d.ts.map +1 -0
  264. package/dist/server-components/state.js +123 -0
  265. package/dist/server-components/state.js.map +1 -0
  266. package/dist/server-components/stream.d.ts +6 -0
  267. package/dist/server-components/stream.d.ts.map +1 -0
  268. package/dist/server-components/stream.js +70 -0
  269. package/dist/server-components/stream.js.map +1 -0
  270. package/dist/server-components/trusted-html.d.ts +3 -0
  271. package/dist/server-components/trusted-html.d.ts.map +1 -0
  272. package/dist/server-components/trusted-html.js +14 -0
  273. package/dist/server-components/trusted-html.js.map +1 -0
  274. package/dist/server-components/types.d.ts +107 -0
  275. package/dist/server-components/types.d.ts.map +1 -0
  276. package/dist/server-components/types.js +2 -0
  277. package/dist/server-components/types.js.map +1 -0
  278. package/dist/server-components/typescript.d.ts +11 -0
  279. package/dist/server-components/typescript.d.ts.map +1 -0
  280. package/dist/server-components/typescript.js +28 -0
  281. package/dist/server-components/typescript.js.map +1 -0
  282. package/dist/server-components/value.d.ts +5 -0
  283. package/dist/server-components/value.d.ts.map +1 -0
  284. package/dist/server-components/value.js +77 -0
  285. package/dist/server-components/value.js.map +1 -0
  286. package/dist/server-components/vite.d.ts +12 -0
  287. package/dist/server-components/vite.d.ts.map +1 -0
  288. package/dist/server-components/vite.js +197 -0
  289. package/dist/server-components/vite.js.map +1 -0
  290. package/dist/server-components.d.ts +5 -0
  291. package/dist/server-components.d.ts.map +1 -0
  292. package/dist/server-components.js +4 -0
  293. package/dist/server-components.js.map +1 -0
  294. package/dist/service-worker.d.ts +8 -1
  295. package/dist/service-worker.d.ts.map +1 -1
  296. package/dist/service-worker.js +20 -6
  297. package/dist/service-worker.js.map +1 -1
  298. package/dist/vite.d.ts +12 -0
  299. package/dist/vite.d.ts.map +1 -1
  300. package/dist/vite.js +7 -1
  301. package/dist/vite.js.map +1 -1
  302. package/dist/zones.d.ts +40 -0
  303. package/dist/zones.d.ts.map +1 -1
  304. package/dist/zones.js +170 -6
  305. package/dist/zones.js.map +1 -1
  306. package/package.json +97 -4
  307. package/src/build-vite.ts +31 -9
  308. package/src/build.ts +109 -31
  309. package/src/cdn.ts +18 -12
  310. package/src/client.ts +25 -6
  311. package/src/deferred.ts +27 -9
  312. package/src/dev-feed.ts +24 -5
  313. package/src/dev-session.ts +4 -13
  314. package/src/dev.ts +29 -1
  315. package/src/draft.ts +35 -14
  316. package/src/fonts-google.ts +10 -0
  317. package/src/index.ts +3 -2
  318. package/src/internal/codegen.ts +60 -29
  319. package/src/internal/disposable-dir.ts +24 -0
  320. package/src/internal/entry-chunks.ts +18 -0
  321. package/src/internal/evidence-traversal.ts +57 -0
  322. package/src/internal/head-merge.ts +16 -0
  323. package/src/internal/js-string.ts +6 -1
  324. package/src/internal/mount-shadow.ts +1 -1
  325. package/src/internal/output-guard.ts +88 -7
  326. package/src/internal/page-execution.ts +506 -108
  327. package/src/internal/progressive-cookies.ts +60 -0
  328. package/src/internal/public-env.ts +14 -0
  329. package/src/internal/render-document.ts +26 -11
  330. package/src/internal/response-controls.ts +31 -14
  331. package/src/internal/revive-dates.ts +35 -0
  332. package/src/internal/route-path.ts +8 -0
  333. package/src/internal/secret-scan.ts +90 -18
  334. package/src/internal/server-boundary.ts +29 -5
  335. package/src/internal/web-app.ts +116 -37
  336. package/src/internal/wire-date.ts +16 -0
  337. package/src/internal/zone-graph.ts +59 -19
  338. package/src/isr.ts +351 -43
  339. package/src/llms-txt.ts +32 -3
  340. package/src/manifest.ts +28 -4
  341. package/src/open-resolved.ts +64 -0
  342. package/src/plugins/kit.ts +31 -11
  343. package/src/plugins/vite-leak-guard.ts +5 -1
  344. package/src/public-dir.ts +27 -8
  345. package/src/query.ts +107 -26
  346. package/src/render-seam.ts +2 -0
  347. package/src/route-types.ts +34 -7
  348. package/src/router.ts +58 -55
  349. package/src/server-components/build-info.ts +11 -0
  350. package/src/server-components/build-target.ts +194 -0
  351. package/src/server-components/build.ts +419 -0
  352. package/src/server-components/bundle-proof.ts +33 -0
  353. package/src/server-components/client.ts +663 -0
  354. package/src/server-components/definition.ts +125 -0
  355. package/src/server-components/dev-http.ts +75 -0
  356. package/src/server-components/error.ts +24 -0
  357. package/src/server-components/frames.ts +181 -0
  358. package/src/server-components/html-policy.ts +94 -0
  359. package/src/server-components/html.ts +81 -0
  360. package/src/server-components/islands.ts +111 -0
  361. package/src/server-components/native.ts +101 -0
  362. package/src/server-components/portal.ts +42 -0
  363. package/src/server-components/props.ts +19 -0
  364. package/src/server-components/protocol.ts +167 -0
  365. package/src/server-components/range.ts +313 -0
  366. package/src/server-components/registry.ts +34 -0
  367. package/src/server-components/response.ts +44 -0
  368. package/src/server-components/router-integration.ts +40 -0
  369. package/src/server-components/runtime.ts +376 -0
  370. package/src/server-components/scheduler.ts +84 -0
  371. package/src/server-components/schema.ts +69 -0
  372. package/src/server-components/server-entry.ts +98 -0
  373. package/src/server-components/source.ts +324 -0
  374. package/src/server-components/state.ts +143 -0
  375. package/src/server-components/stream.ts +71 -0
  376. package/src/server-components/trusted-html.ts +21 -0
  377. package/src/server-components/types.ts +138 -0
  378. package/src/server-components/typescript.ts +39 -0
  379. package/src/server-components/value.ts +82 -0
  380. package/src/server-components/vite.ts +242 -0
  381. package/src/server-components.ts +32 -0
  382. package/src/service-worker.ts +24 -7
  383. package/src/vite.ts +8 -1
  384. package/src/zones.ts +208 -7
@@ -0,0 +1,125 @@
1
+ import { narrowCapabilityContext } from "@nifrajs/core/capabilities"
2
+ import { validateStandard } from "@nifrajs/core/schema"
3
+ import { outputGuard, runGuard } from "../internal/output-guard.ts"
4
+ import { isControlFlow, isStatusSignal } from "../internal/render-document.ts"
5
+ import { ServerComponentError } from "./error.ts"
6
+ import { assertComponentSchema, componentValue } from "./schema.ts"
7
+ import type {
8
+ PreparedServerComponent,
9
+ ServerComponent,
10
+ ServerComponentContext,
11
+ ServerComponentDefinition,
12
+ } from "./types.ts"
13
+
14
+ interface RuntimeDefinition {
15
+ readonly authorize: (ctx: ServerComponentContext, input: never) => void | Promise<void>
16
+ readonly load: (ctx: ServerComponentContext, input: never) => unknown | Promise<unknown>
17
+ readonly guard: ReturnType<typeof outputGuard>
18
+ readonly capabilities: readonly string[]
19
+ }
20
+ const definitions = new WeakMap<object, RuntimeDefinition>()
21
+ const authorizations = new WeakMap<
22
+ object,
23
+ {
24
+ component: ServerComponent
25
+ definition: RuntimeDefinition
26
+ input: unknown
27
+ scope: ServerComponentContext
28
+ }
29
+ >()
30
+
31
+ export function defineServerComponent<Input, Props, View>(
32
+ definition: ServerComponentDefinition<Input, Props, View>,
33
+ reference?: string,
34
+ ): ServerComponent<Input, Props, View> {
35
+ if (typeof definition.authorize !== "function" || typeof definition.load !== "function")
36
+ throw new ServerComponentError("component_contract")
37
+ assertComponentSchema(definition.input)
38
+ assertComponentSchema(definition.props)
39
+ const component: ServerComponent<Input, Props, View> = {
40
+ view: definition.view,
41
+ input: definition.input,
42
+ props: definition.props,
43
+ }
44
+ if (reference !== undefined) Object.assign(component, { reference })
45
+ Object.freeze(component)
46
+ definitions.set(component, {
47
+ authorize: definition.authorize,
48
+ load: definition.load,
49
+ guard: outputGuard(definition.props, "server component", "props"),
50
+ capabilities: Object.freeze([...(definition.capabilities ?? [])]),
51
+ })
52
+ return component
53
+ }
54
+
55
+ export function isServerComponent(value: unknown): value is ServerComponent {
56
+ return typeof value === "object" && value !== null && definitions.has(value)
57
+ }
58
+
59
+ export function serverComponentCapabilities(component: object): readonly string[] {
60
+ const definition = definitions.get(component)
61
+ if (definition === undefined) throw new ServerComponentError("component_contract")
62
+ return definition.capabilities
63
+ }
64
+
65
+ export function inheritOwnerAuthorization(): void {}
66
+
67
+ /** Used for whole-owner preflight so an invalid sibling cannot follow successful work. */
68
+ export async function validateServerComponentInput(
69
+ component: ServerComponent,
70
+ input: unknown,
71
+ ): Promise<void> {
72
+ if (!definitions.has(component)) throw new ServerComponentError("component_unavailable", 404)
73
+ componentValue(input, 1024)
74
+ const parsed = await validateStandard(component.input, input)
75
+ if (!parsed.ok) throw new ServerComponentError("component_input", 422)
76
+ componentValue(parsed.value, 1024)
77
+ }
78
+
79
+ export async function prepareServerComponent<Input, Props, View>(
80
+ component: ServerComponent<Input, Props, View>,
81
+ input: unknown,
82
+ ctx: ServerComponentContext,
83
+ ): Promise<PreparedServerComponent<Props, View>> {
84
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The private definition registry binds each schema to its callbacks; validation and authorization precede this erased generic invocation.
85
+ return (await prepareAuthorizedServerComponent(
86
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The private definition registry binds each schema to its callbacks; validation and authorization precede this erased generic invocation.
87
+ await authorizeServerComponent(component as ServerComponent, input, ctx),
88
+ )) as PreparedServerComponent<Props, View>
89
+ }
90
+ export async function authorizeServerComponent(
91
+ component: ServerComponent,
92
+ input: unknown,
93
+ ctx: ServerComponentContext,
94
+ ): Promise<object> {
95
+ const definition = definitions.get(component)
96
+ if (definition === undefined) throw new ServerComponentError("component_unavailable", 404)
97
+ if (ctx.signal.aborted) throw new ServerComponentError("component_cancelled", 499)
98
+ componentValue(input, 1024)
99
+ const parsed = await validateStandard(component.input, input)
100
+ if (!parsed.ok) throw new ServerComponentError("component_input", 422)
101
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The private definition registry binds each schema to its callbacks; validation and authorization precede this erased generic invocation.
102
+ const scope = narrowCapabilityContext(ctx, definition.capabilities) as ServerComponentContext
103
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The private definition registry binds each schema to its callbacks; validation and authorization precede this erased generic invocation.
104
+ await definition.authorize(scope, parsed.value as never)
105
+ if (ctx.signal.aborted) throw new ServerComponentError("component_cancelled", 499)
106
+ const token = Object.freeze({})
107
+ authorizations.set(token, { component, definition, input: parsed.value, scope })
108
+ return token
109
+ }
110
+ export async function prepareAuthorizedServerComponent(
111
+ token: object,
112
+ ): Promise<PreparedServerComponent> {
113
+ const authorized = authorizations.get(token)
114
+ if (authorized === undefined) throw new ServerComponentError("component_contract")
115
+ authorizations.delete(token)
116
+ const { component, definition, scope, input } = authorized
117
+ if (scope.signal.aborted) throw new ServerComponentError("component_cancelled", 499)
118
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The private definition registry binds each schema to its callbacks; validation and authorization precede this erased generic invocation.
119
+ const data = await definition.load(scope, input as never)
120
+ if (isControlFlow(data) || isStatusSignal(data)) throw data
121
+ if (scope.signal.aborted) throw new ServerComponentError("component_cancelled", 499)
122
+ const props = await runGuard(definition.guard, data, "server component props")
123
+ const publicProps = componentValue(props)
124
+ return { props: publicProps, view: component.view }
125
+ }
@@ -0,0 +1,75 @@
1
+ import { Readable } from "node:stream"
2
+ import { pipeline } from "node:stream/promises"
3
+ import type { ViteDevServer } from "vite"
4
+ import type { ComponentPipelineOptions } from "./build.ts"
5
+
6
+ /** The native dev path serves the same factory and Fetch handler as production. */
7
+ export function installComponentDevHandler(
8
+ server: ViteDevServer,
9
+ options: ComponentPipelineOptions,
10
+ ): void {
11
+ if (options.adapter === "react") return // plugin-rsc owns the SSR request entry in React dev.
12
+ server.middlewares.use(async (req, res, next) => {
13
+ const path = req.url ?? "/"
14
+ if (
15
+ path.startsWith("/@") ||
16
+ path.startsWith("/node_modules/") ||
17
+ /\.(?:[cm]?[jt]sx?|vue|svelte|css)(?:\?|$)/.test(path)
18
+ ) {
19
+ next()
20
+ return
21
+ }
22
+ const abort = new AbortController()
23
+ const close = (): void => {
24
+ if (!res.writableEnded) abort.abort()
25
+ }
26
+ res.on("close", close)
27
+ try {
28
+ const module = await server.ssrLoadModule(options.entries.ssr)
29
+ if (typeof module.createApp !== "function")
30
+ throw new Error(
31
+ "[nifra/components] The SSR entry must export createApp(clientEntry, assets)",
32
+ )
33
+ const app = await module.createApp(`/${options.entries.client.replace(/^\.\//, "")}`, {
34
+ styles: [],
35
+ })
36
+ if (typeof app?.fetch !== "function") throw new Error("component_contract")
37
+ const headers = new Headers()
38
+ for (let i = 0; i < req.rawHeaders.length; i += 2)
39
+ headers.append(req.rawHeaders[i]!, req.rawHeaders[i + 1]!)
40
+ const method = req.method ?? "GET"
41
+ const init: RequestInit & { duplex?: "half" } = { method, headers, signal: abort.signal }
42
+ if (!["GET", "HEAD"].includes(method)) {
43
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion lint/plugin/noChainedTypeAssertions: Node and Bun name the same WHATWG stream API differently; this zero-copy bridge preserves that ABI.
44
+ init.body = Readable.toWeb(req) as unknown as ReadableStream<Uint8Array>
45
+ init.duplex = "half"
46
+ }
47
+ const request = new Request(new URL(path, `http://${req.headers.host ?? "localhost"}`), init)
48
+ const response: Response = await app.fetch(request)
49
+ if (abort.signal.aborted || res.destroyed) {
50
+ await response.body?.cancel()
51
+ return
52
+ }
53
+ res.statusCode = response.status
54
+ for (const [name, value] of response.headers)
55
+ if (name !== "set-cookie") res.setHeader(name, value)
56
+ const cookies = response.headers.getSetCookie()
57
+ if (cookies.length > 0) res.setHeader("set-cookie", cookies)
58
+ // The runtime stream implements Node's Web Streams API despite the distinct TS declarations.
59
+ if (response.body !== null && method !== "HEAD")
60
+ await pipeline(
61
+ Readable.fromWeb(
62
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion lint/plugin/noChainedTypeAssertions: Node and Bun name the same WHATWG stream API differently; this bridge preserves the stream without copying or decoding.
63
+ response.body as unknown as import("node:stream/web").ReadableStream<Uint8Array>,
64
+ ),
65
+ res,
66
+ )
67
+ else res.end()
68
+ } catch (error) {
69
+ // A disconnected read has no response destination and must not broadcast a Vite error overlay.
70
+ if (!abort.signal.aborted && !res.destroyed) next(error)
71
+ } finally {
72
+ res.off("close", close)
73
+ }
74
+ })
75
+ }
@@ -0,0 +1,24 @@
1
+ export const COMPONENT_ERROR_CODES = [
2
+ "component_contract",
3
+ "component_input",
4
+ "component_protocol",
5
+ "component_limit",
6
+ "component_denied",
7
+ "component_unavailable",
8
+ "component_version",
9
+ "component_busy",
10
+ "component_timeout",
11
+ "component_failed",
12
+ "component_cancelled",
13
+ ] as const
14
+ export type ComponentErrorCode = (typeof COMPONENT_ERROR_CODES)[number]
15
+
16
+ export class ServerComponentError extends Error {
17
+ override readonly name = "ServerComponentError"
18
+ constructor(
19
+ readonly code: ComponentErrorCode,
20
+ readonly status: number = 500,
21
+ ) {
22
+ super(`[nifra/web] ${code}`)
23
+ }
24
+ }
@@ -0,0 +1,181 @@
1
+ import { ServerComponentError } from "./error.ts"
2
+ import { COMPONENT_LIMITS } from "./protocol.ts"
3
+
4
+ const magic = new Uint8Array([78, 83, 67, 49])
5
+ const encoder = new TextEncoder()
6
+ const decoder = new TextDecoder("utf-8", { fatal: true })
7
+
8
+ function frame(type: number, data: Uint8Array): Uint8Array {
9
+ const bytes = new Uint8Array(data.length + 5)
10
+ bytes[0] = type
11
+ new DataView(bytes.buffer).setUint32(1, data.length)
12
+ bytes.set(data, 5)
13
+ return bytes
14
+ }
15
+
16
+ export function componentFrames(
17
+ metadata: unknown,
18
+ value: unknown,
19
+ maxBytes = COMPONENT_LIMITS.outputBytes,
20
+ ): ReadableStream<Uint8Array> {
21
+ const meta = encoder.encode(JSON.stringify(metadata))
22
+ const body = encoder.encode(JSON.stringify(value))
23
+ if (meta.length > COMPONENT_LIMITS.frameBytes || body.length + meta.length > maxBytes)
24
+ throw new ServerComponentError("component_limit", 413)
25
+ let position = -2
26
+ return new ReadableStream({
27
+ pull(controller) {
28
+ if (position === -2) {
29
+ controller.enqueue(magic)
30
+ position++
31
+ return
32
+ }
33
+ if (position === -1) {
34
+ controller.enqueue(frame(1, meta))
35
+ position++
36
+ return
37
+ }
38
+ if (position < body.length) {
39
+ const end = Math.min(body.length, position + 65536)
40
+ controller.enqueue(frame(2, body.subarray(position, end)))
41
+ position = end
42
+ return
43
+ }
44
+ controller.enqueue(frame(3, new Uint8Array()))
45
+ controller.close()
46
+ },
47
+ })
48
+ }
49
+
50
+ export interface DecodedComponentFrames {
51
+ readonly metadata: unknown
52
+ readonly value: unknown
53
+ }
54
+
55
+ export async function readComponentFrames(
56
+ stream: ReadableStream<Uint8Array>,
57
+ signal: AbortSignal,
58
+ maxBytes = COMPONENT_LIMITS.outputBytes,
59
+ ): Promise<DecodedComponentFrames> {
60
+ const reader = stream.getReader()
61
+ const header = new Uint8Array(5)
62
+ let magicOffset = 0
63
+ let headerOffset = 0
64
+ let payload: Uint8Array | undefined
65
+ let payloadOffset = 0
66
+ let type = 0
67
+ let metadata: unknown
68
+ let gotMetadata = false
69
+ let terminal = false
70
+ let bytes = 0
71
+ let bodyBytes = 0
72
+ let metadataBytes = 0
73
+ const body: Uint8Array[] = []
74
+ const deadline = AbortSignal.any([signal, AbortSignal.timeout(COMPONENT_LIMITS.operationMs)])
75
+ const cancel = (): void => {
76
+ void reader.cancel().catch(() => {
77
+ /* The original read failure is authoritative. */
78
+ })
79
+ }
80
+ deadline.addEventListener("abort", cancel, { once: true })
81
+ const consume = (data: Uint8Array): void => {
82
+ if (type === 1) {
83
+ if (gotMetadata || bodyBytes !== 0) throw new ServerComponentError("component_protocol", 400)
84
+ metadataBytes = data.length
85
+ metadata = JSON.parse(decoder.decode(data))
86
+ gotMetadata = true
87
+ } else if (type === 2) {
88
+ if (!gotMetadata || data.length === 0)
89
+ throw new ServerComponentError("component_protocol", 400)
90
+ bodyBytes += data.length
91
+ if (bodyBytes + metadataBytes > maxBytes)
92
+ throw new ServerComponentError("component_limit", 413)
93
+ body.push(data)
94
+ } else {
95
+ if (!gotMetadata || bodyBytes === 0 || data.length !== 0)
96
+ throw new ServerComponentError("component_protocol", 400)
97
+ terminal = true
98
+ }
99
+ }
100
+ try {
101
+ while (true) {
102
+ if (deadline.aborted)
103
+ throw new ServerComponentError(
104
+ signal.aborted ? "component_cancelled" : "component_timeout",
105
+ 504,
106
+ )
107
+ let timeout: ReturnType<typeof setTimeout> | undefined
108
+ const next = await Promise.race([
109
+ reader.read(),
110
+ new Promise<never>((_, reject) => {
111
+ timeout = setTimeout(() => {
112
+ reject(new ServerComponentError("component_timeout", 504))
113
+ // Publish the timeout before cancellation can resolve reader.read() as ordinary EOF.
114
+ cancel()
115
+ }, COMPONENT_LIMITS.idleMs)
116
+ }),
117
+ ]).finally(() => clearTimeout(timeout))
118
+ if (next.done) break
119
+ if (terminal || next.value.length === 0)
120
+ throw new ServerComponentError("component_protocol", 400)
121
+ bytes += next.value.length
122
+ if (bytes > maxBytes + 4096) throw new ServerComponentError("component_limit", 413)
123
+ let offset = 0
124
+ while (offset < next.value.length) {
125
+ if (terminal) throw new ServerComponentError("component_protocol", 400)
126
+ if (magicOffset < magic.length) {
127
+ if (next.value[offset++] !== magic[magicOffset++])
128
+ throw new ServerComponentError("component_protocol", 400)
129
+ continue
130
+ }
131
+ if (headerOffset < header.length) {
132
+ const size = Math.min(header.length - headerOffset, next.value.length - offset)
133
+ header.set(next.value.subarray(offset, offset + size), headerOffset)
134
+ offset += size
135
+ headerOffset += size
136
+ if (headerOffset < header.length) continue
137
+ type = header[0]!
138
+ const length = new DataView(header.buffer).getUint32(1)
139
+ if (length > COMPONENT_LIMITS.frameBytes || ![1, 2, 3].includes(type))
140
+ throw new ServerComponentError("component_protocol", 400)
141
+ payload = new Uint8Array(length)
142
+ payloadOffset = 0
143
+ }
144
+ // A frame allocates once. Arbitrarily split transport chunks are copied exactly once.
145
+ const data = payload!
146
+ const size = Math.min(data.length - payloadOffset, next.value.length - offset)
147
+ data.set(next.value.subarray(offset, offset + size), payloadOffset)
148
+ offset += size
149
+ payloadOffset += size
150
+ if (payloadOffset === data.length) {
151
+ consume(data)
152
+ headerOffset = 0
153
+ payload = undefined
154
+ }
155
+ }
156
+ }
157
+ if (deadline.aborted)
158
+ throw new ServerComponentError(
159
+ signal.aborted ? "component_cancelled" : "component_timeout",
160
+ 504,
161
+ )
162
+ if (!terminal || headerOffset !== 0 || payload !== undefined)
163
+ throw new ServerComponentError("component_protocol", 400)
164
+ const merged = new Uint8Array(bodyBytes)
165
+ let offset = 0
166
+ for (const chunk of body) {
167
+ merged.set(chunk, offset)
168
+ offset += chunk.length
169
+ }
170
+ return { metadata, value: JSON.parse(decoder.decode(merged)) }
171
+ } catch (error) {
172
+ await reader.cancel().catch(() => {
173
+ /* Preserve the first protocol/read failure. */
174
+ })
175
+ if (error instanceof ServerComponentError) throw error
176
+ throw new ServerComponentError("component_protocol", 400)
177
+ } finally {
178
+ deadline.removeEventListener("abort", cancel)
179
+ reader.releaseLock()
180
+ }
181
+ }
@@ -0,0 +1,94 @@
1
+ import { ServerComponentError } from "./error.ts"
2
+
3
+ const forbidden = new Set([
4
+ "script",
5
+ "iframe",
6
+ "object",
7
+ "embed",
8
+ "applet",
9
+ "base",
10
+ "meta",
11
+ "link",
12
+ "style",
13
+ "template",
14
+ "noscript",
15
+ "xmp",
16
+ "noembed",
17
+ "noframes",
18
+ "plaintext",
19
+ "frame",
20
+ "frameset",
21
+ "animate",
22
+ "animatemotion",
23
+ "animatetransform",
24
+ "set",
25
+ ])
26
+ const urls = new Set([
27
+ "href",
28
+ "src",
29
+ "xlink:href",
30
+ "action",
31
+ "formaction",
32
+ "poster",
33
+ "background",
34
+ "cite",
35
+ "data",
36
+ "codebase",
37
+ ])
38
+ const fail = (): never => {
39
+ throw new ServerComponentError("component_protocol", 400)
40
+ }
41
+
42
+ function css(value: string): string {
43
+ return (
44
+ value
45
+ .replace(/\/\*[\s\S]*?\*\//g, "")
46
+ .replace(
47
+ /\\([0-9a-f]{1,6})\s?|\\([^\r\n])/gi,
48
+ (_all, hex: string | undefined, char: string | undefined) =>
49
+ hex === undefined
50
+ ? (char ?? "")
51
+ : String.fromCodePoint(Math.min(parseInt(hex, 16), 0x10ffff)),
52
+ )
53
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: URL/CSS schemes must be checked after stripping ASCII controls.
54
+ .replace(/[\u0000-\u0020\u007f]/g, "")
55
+ .toLowerCase()
56
+ )
57
+ }
58
+
59
+ /** The same decoded-attribute policy applies before SSR and before DOM insertion. */
60
+ export function validateComponentTag(
61
+ tag: string,
62
+ attributes: readonly { name: string; value: string }[],
63
+ assets?: ReadonlySet<string>,
64
+ ): void {
65
+ if (tag.toLowerCase() === "link" && assets !== undefined) {
66
+ const attrs = new Map(
67
+ attributes.map((attribute) => [attribute.name.toLowerCase(), attribute.value]),
68
+ )
69
+ if (
70
+ !["preload", "modulepreload"].includes(attrs.get("rel") ?? "") ||
71
+ !assets.has(attrs.get("href") ?? "") ||
72
+ [...attrs.keys()].some(
73
+ (name) => !["rel", "href", "as", "crossorigin", "fetchpriority"].includes(name),
74
+ )
75
+ )
76
+ fail()
77
+ return
78
+ }
79
+ if (forbidden.has(tag.toLowerCase())) fail()
80
+ for (const attribute of attributes) {
81
+ const name = attribute.name.toLowerCase()
82
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Browsers discard these controls when interpreting URL schemes.
83
+ const value = attribute.value.replace(/[\u0000-\u0020\u007f]/g, "").toLowerCase()
84
+ if (name.startsWith("on") || ["srcdoc", "is", "srcset", "ping"].includes(name)) fail()
85
+ if (urls.has(name) && /^(?:javascript|vbscript|data):/.test(value)) fail()
86
+ if (
87
+ name === "style" &&
88
+ /expression\(|@import|(?:javascript|vbscript|data):|-moz-binding|behavior:/.test(
89
+ css(attribute.value),
90
+ )
91
+ )
92
+ fail()
93
+ }
94
+ }
@@ -0,0 +1,81 @@
1
+ import { Tokenizer } from "parse5"
2
+ import { ServerComponentError } from "./error.ts"
3
+ import { validateComponentTag } from "./html-policy.ts"
4
+ import { COMPONENT_LIMITS } from "./protocol.ts"
5
+
6
+ /**
7
+ * A browser reads `title` and `textarea` content as RCDATA, where `<!--` and tags are text, but
8
+ * only in an HTML context; a bare tokenizer never switches. Content holding no `<` before its own
9
+ * end tag tokenizes the same either way, so requiring that keeps this check exact wherever the
10
+ * HTML is inserted. Every other text-mode element is forbidden outright.
11
+ */
12
+ function closesAsText(html: string, tag: string, from: number): boolean {
13
+ const next = html.indexOf("<", from)
14
+ const after = next + 2 + tag.length
15
+ return (
16
+ next !== -1 &&
17
+ html.slice(next, after).toLowerCase() === `</${tag}` &&
18
+ /^[\t\n\f />]$/.test(html[after] ?? "")
19
+ )
20
+ }
21
+
22
+ /** Tokenization validates attributes even in SVG/table contexts a document parser may discard. */
23
+ function tokenize(
24
+ html: string,
25
+ assets?: ReadonlySet<string>,
26
+ hints?: Array<{ start: number; end: number }>,
27
+ ): void {
28
+ if (new TextEncoder().encode(html).length > COMPONENT_LIMITS.outputBytes)
29
+ throw new ServerComponentError("component_limit", 413)
30
+ const tokenizer = new Tokenizer(
31
+ { sourceCodeLocationInfo: true },
32
+ {
33
+ onStartTag(token) {
34
+ validateComponentTag(token.tagName, token.attrs, assets)
35
+ if (
36
+ (token.tagName === "title" || token.tagName === "textarea") &&
37
+ (token.location === null || !closesAsText(html, token.tagName, token.location.endOffset))
38
+ )
39
+ throw new ServerComponentError("component_protocol", 400)
40
+ if (token.tagName === "link" && token.location !== null)
41
+ hints?.push({ start: token.location.startOffset, end: token.location.endOffset })
42
+ },
43
+ onEndTag() {},
44
+ onComment() {},
45
+ onEof() {},
46
+ onCharacter() {},
47
+ onWhitespaceCharacter() {},
48
+ onNullCharacter() {
49
+ throw new ServerComponentError("component_protocol", 400)
50
+ },
51
+ onDoctype() {
52
+ throw new ServerComponentError("component_protocol", 400)
53
+ },
54
+ onParseError(error) {
55
+ // `<!>` (Solid's template markers, `<!$>` and `<!/>` too) is a comment to the tokenizer and to
56
+ // every browser alike; any other parse error is refused.
57
+ if (error.code !== "incorrectly-opened-comment")
58
+ throw new ServerComponentError("component_protocol", 400)
59
+ },
60
+ },
61
+ )
62
+ tokenizer.write(html, true)
63
+ }
64
+
65
+ export function validateComponentHTML(html: string, assets?: ReadonlySet<string>): void {
66
+ tokenize(html, assets)
67
+ }
68
+
69
+ /** React may produce resource hints while decoding Flight. They are not owned range nodes. */
70
+ export function componentRangeHTML(html: string, assets: ReadonlySet<string>): string {
71
+ const hints: Array<{ start: number; end: number }> = []
72
+ tokenize(html, assets, hints)
73
+ const parts: string[] = []
74
+ let offset = 0
75
+ for (const hint of hints) {
76
+ parts.push(html.slice(offset, hint.start))
77
+ offset = hint.end
78
+ }
79
+ parts.push(html.slice(offset))
80
+ return parts.join("")
81
+ }