@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,313 @@
1
+ import type { ComponentClientAdapter, ComponentClientRoot, CurrentComponent } from "./client.ts"
2
+ import { ServerComponentError } from "./error.ts"
3
+ import { validateComponentTag } from "./html-policy.ts"
4
+ import { type ClientIsland, isClientIsland } from "./islands.ts"
5
+ import { guardComponentProps } from "./props.ts"
6
+ import type { ComponentIsland, ComponentState } from "./types.ts"
7
+
8
+ export interface NativeIslandRoot {
9
+ readonly ready?: Promise<void>
10
+ update(props: Record<string, unknown>, current: CurrentComponent): void | Promise<void>
11
+ dispose(): void | Promise<void>
12
+ }
13
+ export interface NativeIslandRuntime {
14
+ hydrate(
15
+ host: Element,
16
+ contract: ClientIsland,
17
+ record: ComponentIsland,
18
+ current: CurrentComponent,
19
+ ): NativeIslandRoot | Promise<NativeIslandRoot>
20
+ }
21
+ export interface NativeRangeOptions {
22
+ readonly adapter: Exclude<ComponentState["adapter"], "react">
23
+ readonly islands: readonly ClientIsland[]
24
+ readonly runtime: NativeIslandRuntime
25
+ /** A narrowly scoped Trusted Types policy's createHTML, when the application requires TT. */
26
+ readonly trustedHTML?: (html: string) => string | { toString(): string }
27
+ }
28
+ interface Prepared {
29
+ fragment: DocumentFragment
30
+ islands: readonly ComponentIsland[]
31
+ contracts: ReadonlyMap<string, ClientIsland>
32
+ nested: ReadonlyMap<string, Element>
33
+ }
34
+ interface MountedIsland {
35
+ readonly host: Element
36
+ readonly root: NativeIslandRoot
37
+ readonly record: ComponentIsland
38
+ }
39
+ function failure(): ServerComponentError {
40
+ return new ServerComponentError("component_protocol", 400)
41
+ }
42
+
43
+ /** Parse inertly and reject executable content before it can reach a live document. */
44
+ export function parseComponentHTML(
45
+ html: string,
46
+ context: Element,
47
+ trustedHTML?: NativeRangeOptions["trustedHTML"],
48
+ ): DocumentFragment {
49
+ const range = document.createRange()
50
+ range.selectNodeContents(context)
51
+ // A TT policy only authorizes inert parsing here. Validation still precedes insertion.
52
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Preparation validates the complete record and owned DOM topology; only this adapter creates the prepared plan and native range.
53
+ const source = (trustedHTML === undefined ? html : trustedHTML(html)) as string
54
+ const inert = new DOMParser().parseFromString(source, "text/html")
55
+ validateHTML(inert)
56
+ const fragment = range.createContextualFragment(source)
57
+ validateHTML(fragment)
58
+ return fragment
59
+ }
60
+ function validateHTML(root: ParentNode): void {
61
+ for (const element of root.querySelectorAll("*")) {
62
+ validateComponentTag(element.localName, [...element.attributes])
63
+ }
64
+ }
65
+ function hosts(fragment: ParentNode, islands: readonly ComponentIsland[]): Map<string, Element> {
66
+ const map = new Map<string, Element>()
67
+ for (const host of fragment.querySelectorAll("[data-nifra-island]")) {
68
+ let parent = host.parentElement
69
+ let nested = false
70
+ while (parent !== null && parent !== fragment) {
71
+ if (parent.hasAttribute("data-nifra-component")) {
72
+ nested = true
73
+ break
74
+ }
75
+ parent = parent.parentElement
76
+ }
77
+ if (nested) continue
78
+ const id = host.getAttribute("data-nifra-island")
79
+ if (
80
+ id === null ||
81
+ map.has(id) ||
82
+ host.localName !== "nifra-island" ||
83
+ (host.parentElement?.closest("nifra-island") !== null &&
84
+ host.parentElement?.closest("nifra-island") !== undefined)
85
+ )
86
+ throw failure()
87
+ map.set(id, host)
88
+ }
89
+ if (map.size !== islands.length || islands.some((record) => !map.has(record.renderId)))
90
+ throw failure()
91
+ return map
92
+ }
93
+ class NativeRange implements ComponentClientRoot {
94
+ readonly #host: Element
95
+ readonly #options: NativeRangeOptions
96
+ #mounted = new Map<string, MountedIsland>()
97
+ #disposed = false
98
+ #pending = Promise.resolve()
99
+ constructor(host: Element, options: NativeRangeOptions) {
100
+ this.#host = host
101
+ this.#options = options
102
+ }
103
+ async initialize(prepared: Prepared, current: CurrentComponent): Promise<void> {
104
+ const actual = hosts(this.#host, prepared.islands)
105
+ try {
106
+ for (const record of prepared.islands) {
107
+ if (!current()) throw failure()
108
+ const host = actual.get(record.renderId)!
109
+ const contract = prepared.contracts.get(record.reference)!
110
+ const root = await this.#options.runtime.hydrate(host, contract, record, current)
111
+ await root.ready
112
+ if (!current()) {
113
+ await root.dispose()
114
+ throw failure()
115
+ }
116
+ this.#mounted.set(record.renderId, { host, root, record })
117
+ }
118
+ } catch (error) {
119
+ await this.dispose()
120
+ throw error
121
+ }
122
+ }
123
+ commit(prepared: Prepared, current: CurrentComponent): Promise<void> {
124
+ const pending = this.#pending.then(() => this.#commit(prepared, current))
125
+ this.#pending = pending.catch(() => {
126
+ /* The calling refresh owns the failure; later generations can proceed. */
127
+ })
128
+ return pending
129
+ }
130
+ async #commit(prepared: Prepared, current: CurrentComponent): Promise<void> {
131
+ if (!current() || this.#disposed) return
132
+ const nextHosts = hosts(prepared.fragment, prepared.islands)
133
+ const retainedChildren = new Map<string, Element>()
134
+ for (const host of this.#host.querySelectorAll("[data-nifra-component]")) {
135
+ const id = host.getAttribute("data-nifra-component")
136
+ if (id !== null && prepared.nested.has(id)) retainedChildren.set(id, host)
137
+ }
138
+ const next = new Map<string, MountedIsland>()
139
+ const active = document.activeElement
140
+ const selection =
141
+ active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement
142
+ ? ([active.selectionStart, active.selectionEnd, active.selectionDirection] as const)
143
+ : undefined
144
+ const acquired: NativeIslandRoot[] = []
145
+ const slots: Array<{ host: Element; fragment: DocumentFragment }> = []
146
+ try {
147
+ for (const record of prepared.islands) {
148
+ const previous = this.#mounted.get(record.renderId)
149
+ const placeholder = nextHosts.get(record.renderId)!
150
+ if (
151
+ previous !== undefined &&
152
+ previous.record.reference === record.reference &&
153
+ previous.record.key === record.key
154
+ ) {
155
+ for (const name of new Set([
156
+ ...Object.keys(previous.record.slots ?? {}),
157
+ ...Object.keys(record.slots ?? {}),
158
+ ])) {
159
+ const slot = [...previous.host.querySelectorAll("nifra-slot[data-nifra-slot]")].find(
160
+ (element) => element.getAttribute("data-nifra-slot") === name,
161
+ )
162
+ if (slot === undefined) throw failure()
163
+ slots.push({
164
+ host: slot,
165
+ fragment: parseComponentHTML(
166
+ record.slots?.[name] ?? "",
167
+ slot,
168
+ this.#options.trustedHTML,
169
+ ),
170
+ })
171
+ }
172
+ next.set(record.renderId, { ...previous, record })
173
+ } else {
174
+ const root = await this.#options.runtime.hydrate(
175
+ placeholder,
176
+ prepared.contracts.get(record.reference)!,
177
+ record,
178
+ current,
179
+ )
180
+ acquired.push(root)
181
+ await root.ready
182
+ if (!current() || this.#disposed) {
183
+ await Promise.all(acquired.map((root) => root.dispose()))
184
+ return
185
+ }
186
+ next.set(record.renderId, { host: placeholder, root, record })
187
+ }
188
+ }
189
+ if (!current() || this.#disposed) {
190
+ await Promise.all(acquired.map((root) => root.dispose()))
191
+ return
192
+ }
193
+ const removed = [...this.#mounted.values()].filter(
194
+ (previous) => next.get(previous.record.renderId)?.root !== previous.root,
195
+ )
196
+ // No await inside this transaction: cancellation cannot detach a live retained root.
197
+ for (const [id, previous] of this.#mounted)
198
+ if (next.get(id)?.root === previous.root) nextHosts.get(id)!.replaceWith(previous.host)
199
+ for (const [id, child] of retainedChildren) prepared.nested.get(id)!.replaceWith(child)
200
+ for (const slot of slots) slot.host.replaceChildren(slot.fragment)
201
+ this.#host.replaceChildren(prepared.fragment)
202
+ this.#mounted = next
203
+ const updates: Array<void | Promise<void>> = []
204
+ for (const record of prepared.islands) {
205
+ if (!current() || this.#disposed) return
206
+ const previous = next.get(record.renderId)
207
+ if (previous !== undefined && !acquired.includes(previous.root))
208
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Preparation validates the complete record and owned DOM topology; only this adapter creates the prepared plan and native range.
209
+ updates.push(previous.root.update(record.props as Record<string, unknown>, current))
210
+ }
211
+ await Promise.all(updates)
212
+ if (active instanceof HTMLElement && active.isConnected && current()) {
213
+ active.focus({ preventScroll: true })
214
+ if (
215
+ selection !== undefined &&
216
+ (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement) &&
217
+ selection[0] !== null &&
218
+ selection[1] !== null
219
+ )
220
+ active.setSelectionRange(selection[0], selection[1], selection[2] ?? undefined)
221
+ }
222
+ await Promise.all(removed.map((island) => island.root.dispose()))
223
+ } catch (error) {
224
+ await Promise.all(
225
+ acquired
226
+ .filter((root) => ![...this.#mounted.values()].some((island) => island.root === root))
227
+ .map((root) => root.dispose()),
228
+ )
229
+ if (current()) await this.dispose()
230
+ throw error
231
+ }
232
+ }
233
+ update(): never {
234
+ throw new ServerComponentError("component_contract")
235
+ }
236
+ async dispose(): Promise<void> {
237
+ if (this.#disposed) return
238
+ this.#disposed = true
239
+ try {
240
+ await Promise.all([...this.#mounted.values()].map((island) => island.root.dispose()))
241
+ } finally {
242
+ this.#mounted.clear()
243
+ this.#host.replaceChildren()
244
+ }
245
+ }
246
+ }
247
+ export function nativeComponentClient(options: NativeRangeOptions): ComponentClientAdapter {
248
+ const contracts = new Map<string, ClientIsland>()
249
+ for (const contract of options.islands) {
250
+ if (
251
+ !isClientIsland(contract) ||
252
+ contract.reference === undefined ||
253
+ contracts.has(contract.reference)
254
+ )
255
+ throw new ServerComponentError("component_contract")
256
+ contracts.set(contract.reference, contract)
257
+ }
258
+ return {
259
+ name: options.adapter,
260
+ async prepare(record, current, host, context) {
261
+ const islands: ComponentIsland[] = []
262
+ for (const island of record.islands ?? []) {
263
+ const contract = contracts.get(island.reference)
264
+ if (
265
+ contract === undefined ||
266
+ Object.keys(island.slots ?? {}).some((name) => !contract.slots.includes(name))
267
+ )
268
+ throw failure()
269
+ const props = await guardComponentProps(contract.props, island.props)
270
+ if (!current()) throw failure()
271
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Preparation validates the complete record and owned DOM topology; only this adapter creates the prepared plan and native range.
272
+ islands.push({ ...island, props: props as ComponentIsland["props"] })
273
+ }
274
+ const parseContext = host ?? document.createElement("nifra-component")
275
+ const fragment = parseComponentHTML(record.html, parseContext, options.trustedHTML)
276
+ hosts(fragment, islands)
277
+ const nested = new Map<string, Element>()
278
+ for (const child of fragment.querySelectorAll("[data-nifra-component]")) {
279
+ if (child.parentElement?.closest("[data-nifra-component]") != null) continue
280
+ const id = child.getAttribute("data-nifra-component")
281
+ if (
282
+ id === null ||
283
+ nested.has(id) ||
284
+ !(context?.records ?? []).some(
285
+ (item) => item.instance === id && item.parent === record.instance,
286
+ )
287
+ )
288
+ throw failure()
289
+ nested.set(id, child)
290
+ }
291
+ return { fragment, islands, contracts, nested } satisfies Prepared
292
+ },
293
+ async hydrate(host, _record, prepared, current) {
294
+ const range = new NativeRange(host, options)
295
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Preparation validates the complete record and owned DOM topology; only this adapter creates the prepared plan and native range.
296
+ await range.initialize(prepared as Prepared, current)
297
+ return range
298
+ },
299
+ async create(host, _record, prepared, current) {
300
+ if (!current()) throw failure()
301
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Preparation validates the complete record and owned DOM topology; only this adapter creates the prepared plan and native range.
302
+ host.replaceChildren((prepared as Prepared).fragment)
303
+ const range = new NativeRange(host, options)
304
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Preparation validates the complete record and owned DOM topology; only this adapter creates the prepared plan and native range.
305
+ await range.initialize(prepared as Prepared, current)
306
+ return range
307
+ },
308
+ update(root, _record, prepared, current) {
309
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Preparation validates the complete record and owned DOM topology; only this adapter creates the prepared plan and native range.
310
+ return (root as NativeRange).commit(prepared as Prepared, current)
311
+ },
312
+ }
313
+ }
@@ -0,0 +1,34 @@
1
+ import type { ComponentController } from "./client.ts"
2
+
3
+ export const componentControllers = new Set<ComponentController>()
4
+ let channel: BroadcastChannel | undefined
5
+ function connectInvalidation(): void {
6
+ if (
7
+ typeof window === "undefined" ||
8
+ typeof BroadcastChannel === "undefined" ||
9
+ channel !== undefined
10
+ )
11
+ return
12
+ channel = new BroadcastChannel("nifra-component-scope-v1")
13
+ channel.onmessage = (event: MessageEvent<unknown>) => {
14
+ if (event.data === 1)
15
+ for (const controller of componentControllers)
16
+ void controller.invalidateScope().catch(() => controller.reportError("component_failed"))
17
+ }
18
+ }
19
+ export async function invalidateComponentScope(): Promise<void> {
20
+ await Promise.all([...componentControllers].map((controller) => controller.invalidateScope()))
21
+ channel?.postMessage(1)
22
+ }
23
+
24
+ export function registerComponentController(controller: ComponentController): void {
25
+ componentControllers.add(controller)
26
+ connectInvalidation()
27
+ }
28
+ export function unregisterComponentController(controller: ComponentController): void {
29
+ componentControllers.delete(controller)
30
+ if (componentControllers.size === 0) {
31
+ channel?.close()
32
+ channel = undefined
33
+ }
34
+ }
@@ -0,0 +1,44 @@
1
+ import { COMPONENT_ERROR_CODES, type ComponentErrorCode, ServerComponentError } from "./error.ts"
2
+ import { COMPONENT_LIMITS } from "./protocol.ts"
3
+ import { readComponentBytes } from "./stream.ts"
4
+ import { componentValue } from "./value.ts"
5
+
6
+ /** Read a small terminal envelope; an ordinary owner error keeps its HTTP status and safe code. */
7
+ export async function componentResponseFailure(
8
+ response: Response,
9
+ signal: AbortSignal,
10
+ ): Promise<ServerComponentError> {
11
+ if (
12
+ response.body === null ||
13
+ response.headers.get("content-type")?.split(";")[0]?.trim() !== "application/json"
14
+ )
15
+ return new ServerComponentError("component_failed", response.status)
16
+ let value: unknown
17
+ try {
18
+ value = componentValue(
19
+ JSON.parse(
20
+ new TextDecoder("utf-8", { fatal: true }).decode(
21
+ await readComponentBytes(response.body, signal, COMPONENT_LIMITS.headerBytes),
22
+ ),
23
+ ),
24
+ COMPONENT_LIMITS.headerBytes,
25
+ )
26
+ } catch (error) {
27
+ if (error instanceof ServerComponentError) throw error
28
+ return new ServerComponentError("component_failed", response.status)
29
+ }
30
+ if (typeof value !== "object" || value === null || Array.isArray(value))
31
+ return new ServerComponentError("component_failed", response.status)
32
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The bounded error envelope and exact error-code vocabulary are checked before constructing the typed failure.
33
+ const envelope = value as Record<string, unknown>
34
+ if (
35
+ Object.keys(envelope).length !== 2 ||
36
+ envelope.v !== 1 ||
37
+ typeof envelope.error !== "string" ||
38
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The bounded error envelope and exact error-code vocabulary are checked before constructing the typed failure.
39
+ !(COMPONENT_ERROR_CODES as readonly string[]).includes(envelope.error)
40
+ )
41
+ return new ServerComponentError("component_failed", response.status)
42
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The bounded error envelope and exact error-code vocabulary are checked before constructing the typed failure.
43
+ return new ServerComponentError(envelope.error as ComponentErrorCode, response.status)
44
+ }
@@ -0,0 +1,40 @@
1
+ import type { ClientRouter } from "../router.ts"
2
+ import type { ComponentController } from "./client.ts"
3
+ import { componentControllers } from "./registry.ts"
4
+
5
+ /** Optional router integration; imported only by opted-in application entries. */
6
+ export function bindComponentRouter(
7
+ router: Pick<ClientRouter, "snapshot" | "subscribe">,
8
+ ): () => void {
9
+ let previous = router.snapshot()
10
+ return router.subscribe(() => {
11
+ const next = router.snapshot()
12
+ if (next.pending && next.pendingPath !== previous.pendingPath)
13
+ for (const controller of componentControllers) controller.cancelPending()
14
+ if (
15
+ !next.pending &&
16
+ (next.serverComponents !== previous.serverComponents || next.path !== previous.path)
17
+ ) {
18
+ for (const controller of componentControllers) {
19
+ const task =
20
+ next.serverComponents === undefined
21
+ ? controller.navigate()
22
+ : controller.adopt(next.serverComponents, next.path)
23
+ void task.catch(() => controller.reportError("component_failed"))
24
+ }
25
+ }
26
+ previous = next
27
+ })
28
+ }
29
+
30
+ /** Refresh only after the existing mutation transport reports success. */
31
+ export async function refreshAfterComponentMutation<T extends { readonly ok: boolean }>(
32
+ mutation: Promise<T>,
33
+ controller: ComponentController,
34
+ instances: readonly string[],
35
+ ): Promise<T> {
36
+ const result = await mutation
37
+ if (result.ok)
38
+ await Promise.all([...new Set(instances)].map((instance) => controller.refresh(instance)))
39
+ return result
40
+ }