@jini-ai/admin 0.3.10 → 0.4.1

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 (387) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +71 -37
  3. package/dist/agent-plugins/adapters/http.d.ts +7 -0
  4. package/dist/agent-plugins/adapters/http.js +15 -0
  5. package/dist/agent-plugins/adapters/memory.d.ts +8 -0
  6. package/dist/agent-plugins/adapters/memory.js +25 -0
  7. package/dist/agent-plugins/agent-plugins.module.d.ts +51 -0
  8. package/dist/agent-plugins/agent-plugins.module.js +6 -0
  9. package/dist/agent-plugins/conformance/agent-plugins-api.conformance.d.ts +12 -0
  10. package/dist/agent-plugins/conformance/agent-plugins-api.conformance.js +47 -0
  11. package/dist/agent-plugins/controllers/agent-plugin-files.controller.d.ts +20 -0
  12. package/dist/agent-plugins/controllers/agent-plugin-files.controller.js +43 -0
  13. package/dist/agent-plugins/controllers/agent-plugins.controller.d.ts +25 -0
  14. package/dist/agent-plugins/controllers/agent-plugins.controller.js +91 -0
  15. package/dist/agent-plugins/index.d.ts +9 -0
  16. package/dist/agent-plugins/index.js +9 -0
  17. package/dist/agent-plugins/messages.en.d.ts +23 -0
  18. package/dist/agent-plugins/messages.en.js +14 -0
  19. package/dist/agent-plugins/models.d.ts +57 -0
  20. package/dist/agent-plugins/models.js +2 -0
  21. package/dist/agent-plugins/package-file-tree.d.ts +111 -0
  22. package/dist/agent-plugins/package-file-tree.js +188 -0
  23. package/dist/agent-plugins/ports.d.ts +18 -0
  24. package/dist/agent-plugins/ports.js +3 -0
  25. package/dist/agent-plugins/react/components/AgentPluginFileContent.d.ts +5 -0
  26. package/dist/agent-plugins/react/components/AgentPluginFileContent.js +9 -0
  27. package/dist/agent-plugins/react/components/AgentPluginFileTree.d.ts +9 -0
  28. package/dist/agent-plugins/react/components/AgentPluginFileTree.js +7 -0
  29. package/dist/agent-plugins/react/components/AgentPluginInspector.d.ts +3 -0
  30. package/dist/agent-plugins/react/components/AgentPluginInspector.js +11 -0
  31. package/dist/agent-plugins/react/components/AgentPluginListPanel.d.ts +6 -0
  32. package/dist/agent-plugins/react/components/AgentPluginListPanel.js +10 -0
  33. package/dist/agent-plugins/react/components/AgentPluginRow.d.ts +5 -0
  34. package/dist/agent-plugins/react/components/AgentPluginRow.js +8 -0
  35. package/dist/agent-plugins/react/hooks/AgentPluginInspector.hooks.d.ts +90 -0
  36. package/dist/agent-plugins/react/hooks/AgentPluginInspector.hooks.js +74 -0
  37. package/dist/agent-plugins/react/hooks/AgentPluginPanel.hooks.d.ts +67 -0
  38. package/dist/agent-plugins/react/hooks/AgentPluginPanel.hooks.js +50 -0
  39. package/dist/agent-plugins/react/hooks/AgentPluginRow.hooks.d.ts +55 -0
  40. package/dist/agent-plugins/react/hooks/AgentPluginRow.hooks.js +6 -0
  41. package/dist/agent-plugins/react/hooks/AgentPluginsPage.hooks.d.ts +64 -0
  42. package/dist/agent-plugins/react/hooks/AgentPluginsPage.hooks.js +39 -0
  43. package/dist/agent-plugins/react/hooks/AgentPluginsPorts.hooks.d.ts +7 -0
  44. package/dist/agent-plugins/react/hooks/AgentPluginsPorts.hooks.js +10 -0
  45. package/dist/agent-plugins/react/hooks/MarketplaceTab.hooks.d.ts +6 -0
  46. package/dist/agent-plugins/react/hooks/MarketplaceTab.hooks.js +3 -0
  47. package/dist/agent-plugins/react/index.d.ts +245 -0
  48. package/dist/agent-plugins/react/index.js +13 -0
  49. package/dist/agent-plugins/react/pages/AgentPluginsPage.d.ts +4 -0
  50. package/dist/agent-plugins/react/pages/AgentPluginsPage.js +12 -0
  51. package/dist/agent-plugins/react/tabs/DownloadedTab.d.ts +4 -0
  52. package/dist/agent-plugins/react/tabs/DownloadedTab.js +5 -0
  53. package/dist/agent-plugins/react/tabs/InstalledTab.d.ts +4 -0
  54. package/dist/agent-plugins/react/tabs/InstalledTab.js +5 -0
  55. package/dist/agent-plugins/react/tabs/MarketplaceTab.d.ts +4 -0
  56. package/dist/agent-plugins/react/tabs/MarketplaceTab.js +10 -0
  57. package/dist/agent-plugins/rules.d.ts +33 -0
  58. package/dist/agent-plugins/rules.js +51 -0
  59. package/dist/browser/index.d.ts +1 -0
  60. package/dist/browser/index.js +1 -0
  61. package/dist/browser/navigation.d.ts +19 -4
  62. package/dist/browser/navigation.js +8 -8
  63. package/dist/browser/shell-navigation.d.ts +12 -0
  64. package/dist/browser/shell-navigation.js +16 -0
  65. package/dist/contracts/credential-management.d.ts +8 -0
  66. package/dist/contracts/credential-management.js +3 -0
  67. package/dist/contracts/media-picker.d.ts +17 -0
  68. package/dist/contracts/media-picker.js +5 -0
  69. package/dist/contracts/playground-render-target.d.ts +18 -0
  70. package/dist/contracts/playground-render-target.js +3 -0
  71. package/dist/core/entities/rules.d.ts +138 -0
  72. package/dist/core/entities/rules.js +344 -0
  73. package/dist/core/gated/types.d.ts +8 -4
  74. package/dist/core/index.d.ts +4 -0
  75. package/dist/core/index.js +1 -0
  76. package/dist/core/manifest/rules.d.ts +24 -6
  77. package/dist/core/manifest/rules.js +12 -11
  78. package/dist/core/module/controller-store.d.ts +22 -0
  79. package/dist/core/module/controller-store.js +32 -0
  80. package/dist/core/module/create-admin.d.ts +41 -0
  81. package/dist/core/module/create-admin.js +187 -0
  82. package/dist/core/module/define-admin-module.d.ts +4 -0
  83. package/dist/core/module/define-admin-module.js +5 -0
  84. package/dist/core/module/index.d.ts +7 -0
  85. package/dist/core/module/index.js +4 -0
  86. package/dist/core/module/token.d.ts +19 -0
  87. package/dist/core/module/token.js +4 -0
  88. package/dist/core/module/types.d.ts +79 -0
  89. package/dist/core/module/types.js +2 -0
  90. package/dist/core/permissions/rules.d.ts +4 -1
  91. package/dist/core/permissions/rules.js +1 -1
  92. package/dist/core/ports/analytics.d.ts +1 -1
  93. package/dist/core/ports/auth.d.ts +2 -2
  94. package/dist/core/ports/comments.d.ts +9 -6
  95. package/dist/core/ports/database.d.ts +13 -10
  96. package/dist/core/ports/entities.d.ts +302 -0
  97. package/dist/core/ports/entities.js +47 -0
  98. package/dist/core/ports/forms.d.ts +19 -7
  99. package/dist/core/ports/identity.d.ts +42 -17
  100. package/dist/core/ports/index.d.ts +2 -0
  101. package/dist/core/ports/integrations.d.ts +9 -4
  102. package/dist/core/ports/media.d.ts +16 -7
  103. package/dist/core/ports/members.d.ts +7 -3
  104. package/dist/core/ports/menus.d.ts +46 -0
  105. package/dist/core/ports/menus.js +43 -0
  106. package/dist/core/ports/plugins.d.ts +3 -2
  107. package/dist/core/ports/recovery.d.ts +10 -6
  108. package/dist/core/ports/redirects.d.ts +17 -7
  109. package/dist/core/ports/seo.d.ts +12 -6
  110. package/dist/core/ports/shell.d.ts +41 -0
  111. package/dist/core/ports/shell.js +2 -0
  112. package/dist/core/ports/workspace.d.ts +3 -3
  113. package/dist/core/routing/rules.d.ts +17 -4
  114. package/dist/core/routing/rules.js +4 -4
  115. package/dist/core/transport/errors.d.ts +13 -4
  116. package/dist/core/transport/errors.js +4 -4
  117. package/dist/core/transport/http.d.ts +26 -16
  118. package/dist/core/transport/http.js +59 -21
  119. package/dist/core/transport/types.d.ts +26 -14
  120. package/dist/core/transport/types.js +9 -7
  121. package/dist/media/adapters/http.d.ts +27 -0
  122. package/dist/media/adapters/http.js +44 -0
  123. package/dist/media/adapters/memory.d.ts +11 -0
  124. package/dist/media/adapters/memory.js +139 -0
  125. package/dist/media/conformance/media-api.conformance.d.ts +7 -0
  126. package/dist/media/conformance/media-api.conformance.js +69 -0
  127. package/dist/media/controllers/edit-media.controller.d.ts +24 -0
  128. package/dist/media/controllers/edit-media.controller.js +69 -0
  129. package/dist/media/controllers/library.controller.d.ts +47 -0
  130. package/dist/media/controllers/library.controller.js +173 -0
  131. package/dist/media/controllers/providers.controller.d.ts +30 -0
  132. package/dist/media/controllers/providers.controller.js +76 -0
  133. package/dist/media/html-attributes.d.ts +82 -0
  134. package/dist/media/html-attributes.js +204 -0
  135. package/dist/media/index.d.ts +12 -0
  136. package/dist/media/index.js +9 -0
  137. package/dist/media/media.module.d.ts +83 -0
  138. package/dist/media/media.module.js +43 -0
  139. package/dist/media/messages.en.d.ts +24 -0
  140. package/dist/media/messages.en.js +24 -0
  141. package/dist/media/models.d.ts +72 -0
  142. package/dist/media/models.js +2 -0
  143. package/dist/media/ports.d.ts +75 -0
  144. package/dist/media/ports.js +7 -0
  145. package/dist/media/react/components/EditMediaPanel.d.ts +3 -0
  146. package/dist/media/react/components/EditMediaPanel.js +10 -0
  147. package/dist/media/react/components/MediaCard.d.ts +5 -0
  148. package/dist/media/react/components/MediaCard.js +17 -0
  149. package/dist/media/react/components/MediaDialog.d.ts +3 -0
  150. package/dist/media/react/components/MediaDialog.js +8 -0
  151. package/dist/media/react/components/MediaGrid.d.ts +4 -0
  152. package/dist/media/react/components/MediaGrid.js +8 -0
  153. package/dist/media/react/components/MediaIcons.d.ts +14 -0
  154. package/dist/media/react/components/MediaIcons.js +39 -0
  155. package/dist/media/react/components/MediaLightbox.d.ts +5 -0
  156. package/dist/media/react/components/MediaLightbox.js +12 -0
  157. package/dist/media/react/components/MediaPicker.d.ts +3 -0
  158. package/dist/media/react/components/MediaPicker.js +10 -0
  159. package/dist/media/react/components/MediaPurgeDialog.d.ts +4 -0
  160. package/dist/media/react/components/MediaPurgeDialog.js +9 -0
  161. package/dist/media/react/components/UploadButton.d.ts +3 -0
  162. package/dist/media/react/components/UploadButton.js +8 -0
  163. package/dist/media/react/hooks/EditMediaPanel.hooks.d.ts +84 -0
  164. package/dist/media/react/hooks/EditMediaPanel.hooks.js +105 -0
  165. package/dist/media/react/hooks/LibraryTab.hooks.d.ts +103 -0
  166. package/dist/media/react/hooks/LibraryTab.hooks.js +164 -0
  167. package/dist/media/react/hooks/MediaCard.hooks.d.ts +83 -0
  168. package/dist/media/react/hooks/MediaCard.hooks.js +66 -0
  169. package/dist/media/react/hooks/MediaDialog.hooks.d.ts +10 -0
  170. package/dist/media/react/hooks/MediaDialog.hooks.js +20 -0
  171. package/dist/media/react/hooks/MediaGrid.hooks.d.ts +29 -0
  172. package/dist/media/react/hooks/MediaGrid.hooks.js +6 -0
  173. package/dist/media/react/hooks/MediaLightbox.hooks.d.ts +27 -0
  174. package/dist/media/react/hooks/MediaLightbox.hooks.js +64 -0
  175. package/dist/media/react/hooks/MediaPage.hooks.d.ts +68 -0
  176. package/dist/media/react/hooks/MediaPage.hooks.js +68 -0
  177. package/dist/media/react/hooks/MediaPicker.hooks.d.ts +26 -0
  178. package/dist/media/react/hooks/MediaPicker.hooks.js +25 -0
  179. package/dist/media/react/hooks/MediaPorts.hooks.d.ts +9 -0
  180. package/dist/media/react/hooks/MediaPorts.hooks.js +10 -0
  181. package/dist/media/react/hooks/MediaPurgeDialog.hooks.d.ts +22 -0
  182. package/dist/media/react/hooks/MediaPurgeDialog.hooks.js +6 -0
  183. package/dist/media/react/hooks/ProvidersTab.hooks.d.ts +55 -0
  184. package/dist/media/react/hooks/ProvidersTab.hooks.js +124 -0
  185. package/dist/media/react/hooks/UploadButton.hooks.d.ts +48 -0
  186. package/dist/media/react/hooks/UploadButton.hooks.js +118 -0
  187. package/dist/media/react/index.d.ts +373 -0
  188. package/dist/media/react/index.js +73 -0
  189. package/dist/media/react/pages/MediaPage.d.ts +4 -0
  190. package/dist/media/react/pages/MediaPage.js +9 -0
  191. package/dist/media/react/tabs/LibraryTab.d.ts +4 -0
  192. package/dist/media/react/tabs/LibraryTab.js +14 -0
  193. package/dist/media/react/tabs/ProvidersTab.d.ts +4 -0
  194. package/dist/media/react/tabs/ProvidersTab.js +10 -0
  195. package/dist/media/rules.d.ts +86 -0
  196. package/dist/media/rules.js +134 -0
  197. package/dist/media/styles.d.ts +4 -0
  198. package/dist/media/styles.js +5 -0
  199. package/dist/playground/adapters/http.d.ts +8 -0
  200. package/dist/playground/adapters/http.js +4 -0
  201. package/dist/playground/adapters/memory.d.ts +10 -0
  202. package/dist/playground/adapters/memory.js +30 -0
  203. package/dist/playground/conformance/playground-api.conformance.d.ts +6 -0
  204. package/dist/playground/conformance/playground-api.conformance.js +37 -0
  205. package/dist/playground/controllers/playground.controller.d.ts +16 -0
  206. package/dist/playground/controllers/playground.controller.js +21 -0
  207. package/dist/playground/index.d.ts +7 -0
  208. package/dist/playground/index.js +5 -0
  209. package/dist/playground/messages.en.d.ts +9 -0
  210. package/dist/playground/messages.en.js +2 -0
  211. package/dist/playground/models.d.ts +4 -0
  212. package/dist/playground/models.js +2 -0
  213. package/dist/playground/playground.module.d.ts +31 -0
  214. package/dist/playground/playground.module.js +5 -0
  215. package/dist/playground/ports.d.ts +5 -0
  216. package/dist/playground/ports.js +2 -0
  217. package/dist/playground/react/components/Canvas.d.ts +3 -0
  218. package/dist/playground/react/components/Canvas.js +9 -0
  219. package/dist/playground/react/hooks/Canvas.hooks.d.ts +7 -0
  220. package/dist/playground/react/hooks/Canvas.hooks.js +18 -0
  221. package/dist/playground/react/hooks/PlaygroundPage.hooks.d.ts +8 -0
  222. package/dist/playground/react/hooks/PlaygroundPage.hooks.js +2 -0
  223. package/dist/playground/react/hooks/PlaygroundPorts.hooks.d.ts +4 -0
  224. package/dist/playground/react/hooks/PlaygroundPorts.hooks.js +6 -0
  225. package/dist/playground/react/index.d.ts +136 -0
  226. package/dist/playground/react/index.js +10 -0
  227. package/dist/playground/react/pages/PlaygroundPage.d.ts +4 -0
  228. package/dist/playground/react/pages/PlaygroundPage.js +11 -0
  229. package/dist/playground/react/tabs/CanvasTab.d.ts +4 -0
  230. package/dist/playground/react/tabs/CanvasTab.js +5 -0
  231. package/dist/playground/rules.d.ts +4 -0
  232. package/dist/playground/rules.js +2 -0
  233. package/dist/react/OverlayHost.d.ts +3 -0
  234. package/dist/react/OverlayHost.hooks.d.ts +9 -0
  235. package/dist/react/OverlayHost.hooks.js +9 -0
  236. package/dist/react/OverlayHost.js +7 -0
  237. package/dist/react/bind-react.d.ts +46 -0
  238. package/dist/react/bind-react.js +28 -0
  239. package/dist/react/components/ConfirmButton/ConfirmButton.hooks.d.ts +7 -1
  240. package/dist/react/components/ConfirmButton/ConfirmButton.hooks.js +2 -2
  241. package/dist/react/components/ConfirmButton/ConfirmButton.js +1 -1
  242. package/dist/react/components/ConfirmDialog/ConfirmDialog.hooks.d.ts +17 -3
  243. package/dist/react/components/ConfirmDialog/ConfirmDialog.hooks.js +3 -3
  244. package/dist/react/components/ConfirmDialog/ConfirmDialog.js +5 -5
  245. package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.d.ts +6 -2
  246. package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.js +7 -8
  247. package/dist/react/components/RowMenu/RowMenu.d.ts +5 -1
  248. package/dist/react/components/RowMenu/RowMenu.hooks.d.ts +8 -2
  249. package/dist/react/components/RowMenu/RowMenu.hooks.js +4 -4
  250. package/dist/react/components/RowMenu/RowMenu.js +6 -6
  251. package/dist/react/components/Sidebar.d.ts +1 -1
  252. package/dist/react/components/Sidebar.js +103 -7
  253. package/dist/react/entities/EntityCell.d.ts +10 -0
  254. package/dist/react/entities/EntityCell.js +18 -0
  255. package/dist/react/entities/EntityDetail.d.ts +3 -0
  256. package/dist/react/entities/EntityDetail.js +21 -0
  257. package/dist/react/entities/EntityEdit.d.ts +13 -0
  258. package/dist/react/entities/EntityEdit.js +45 -0
  259. package/dist/react/entities/EntityField.d.ts +20 -0
  260. package/dist/react/entities/EntityField.js +31 -0
  261. package/dist/react/entities/EntityIndex.d.ts +3 -0
  262. package/dist/react/entities/EntityIndex.js +10 -0
  263. package/dist/react/entities/EntityList.d.ts +7 -0
  264. package/dist/react/entities/EntityList.js +52 -0
  265. package/dist/react/entities/contribution.d.ts +26 -0
  266. package/dist/react/entities/contribution.js +78 -0
  267. package/dist/react/entities/index.d.ts +10 -0
  268. package/dist/react/entities/index.js +7 -0
  269. package/dist/react/entities/rules.d.ts +179 -0
  270. package/dist/react/entities/rules.js +224 -0
  271. package/dist/react/entities/types.d.ts +45 -0
  272. package/dist/react/entities/types.js +5 -0
  273. package/dist/react/entities/use-entity-edit.d.ts +35 -0
  274. package/dist/react/entities/use-entity-edit.js +98 -0
  275. package/dist/react/entities/use-entity-read.d.ts +22 -0
  276. package/dist/react/entities/use-entity-read.js +27 -0
  277. package/dist/react/hooks/use-nav-sections.d.ts +12 -3
  278. package/dist/react/hooks/use-nav-sections.js +14 -14
  279. package/dist/react/hooks/use-sidebar-rail.d.ts +7 -1
  280. package/dist/react/hooks/use-sidebar-rail.js +11 -11
  281. package/dist/react/index.d.ts +3 -1
  282. package/dist/react/index.js +5 -2
  283. package/dist/react/overlays.d.ts +38 -0
  284. package/dist/react/overlays.js +46 -0
  285. package/dist/react/shell/AdminShell.d.ts +9 -0
  286. package/dist/react/shell/AdminShell.hooks.d.ts +15 -0
  287. package/dist/react/shell/AdminShell.hooks.js +27 -0
  288. package/dist/react/shell/AdminShell.js +46 -0
  289. package/dist/react/shell/index.d.ts +7 -0
  290. package/dist/react/shell/index.js +5 -0
  291. package/dist/react/shell/model.d.ts +25 -0
  292. package/dist/react/shell/model.js +29 -0
  293. package/dist/react/shell/theme-ports.d.ts +28 -0
  294. package/dist/react/shell/theme-ports.js +2 -0
  295. package/dist/react/shell/types.d.ts +99 -0
  296. package/dist/react/shell/types.js +2 -0
  297. package/dist/react/shell/use-admin-shell-session.d.ts +13 -0
  298. package/dist/react/shell/use-admin-shell-session.js +81 -0
  299. package/dist/react/shell/use-admin-theme.d.ts +16 -0
  300. package/dist/react/shell/use-admin-theme.js +57 -0
  301. package/dist/react/types.d.ts +6 -4
  302. package/dist/react/types.js +2 -2
  303. package/dist/react/use-controller.d.ts +16 -0
  304. package/dist/react/use-controller.js +18 -0
  305. package/dist/security/adapters/http.d.ts +16 -0
  306. package/dist/security/adapters/http.js +94 -0
  307. package/dist/security/adapters/memory.d.ts +15 -0
  308. package/dist/security/adapters/memory.js +60 -0
  309. package/dist/security/conformance/security-api.conformance.d.ts +10 -0
  310. package/dist/security/conformance/security-api.conformance.js +48 -0
  311. package/dist/security/controllers/access-tokens.controller.d.ts +44 -0
  312. package/dist/security/controllers/access-tokens.controller.js +112 -0
  313. package/dist/security/controllers/other-credentials.controller.d.ts +30 -0
  314. package/dist/security/controllers/other-credentials.controller.js +74 -0
  315. package/dist/security/controllers/root-key.controller.d.ts +35 -0
  316. package/dist/security/controllers/root-key.controller.js +90 -0
  317. package/dist/security/index.d.ts +9 -0
  318. package/dist/security/index.js +9 -0
  319. package/dist/security/messages.en.d.ts +12 -0
  320. package/dist/security/messages.en.js +2 -0
  321. package/dist/security/models.d.ts +112 -0
  322. package/dist/security/models.js +2 -0
  323. package/dist/security/ports.d.ts +58 -0
  324. package/dist/security/ports.js +5 -0
  325. package/dist/security/react/components/CredentialEditor.d.ts +3 -0
  326. package/dist/security/react/components/CredentialEditor.js +8 -0
  327. package/dist/security/react/hooks/AccessTokensTab.hooks.d.ts +94 -0
  328. package/dist/security/react/hooks/AccessTokensTab.hooks.js +36 -0
  329. package/dist/security/react/hooks/CredentialEditor.hooks.d.ts +49 -0
  330. package/dist/security/react/hooks/CredentialEditor.hooks.js +14 -0
  331. package/dist/security/react/hooks/RootKeyTab.hooks.d.ts +29 -0
  332. package/dist/security/react/hooks/RootKeyTab.hooks.js +19 -0
  333. package/dist/security/react/hooks/SecurityPage.hooks.d.ts +17 -0
  334. package/dist/security/react/hooks/SecurityPage.hooks.js +12 -0
  335. package/dist/security/react/hooks/SecurityPorts.hooks.d.ts +9 -0
  336. package/dist/security/react/hooks/SecurityPorts.hooks.js +6 -0
  337. package/dist/security/react/index.d.ts +215 -0
  338. package/dist/security/react/index.js +10 -0
  339. package/dist/security/react/pages/SecurityPage.d.ts +4 -0
  340. package/dist/security/react/pages/SecurityPage.js +10 -0
  341. package/dist/security/react/tabs/AccessTokensTab.d.ts +4 -0
  342. package/dist/security/react/tabs/AccessTokensTab.js +10 -0
  343. package/dist/security/react/tabs/RootKeyTab.d.ts +4 -0
  344. package/dist/security/react/tabs/RootKeyTab.js +9 -0
  345. package/dist/security/rules.d.ts +57 -0
  346. package/dist/security/rules.js +63 -0
  347. package/dist/security/security.module.d.ts +45 -0
  348. package/dist/security/security.module.js +5 -0
  349. package/dist/source-control/adapters/http.d.ts +6 -0
  350. package/dist/source-control/adapters/http.js +13 -0
  351. package/dist/source-control/adapters/memory.d.ts +9 -0
  352. package/dist/source-control/adapters/memory.js +35 -0
  353. package/dist/source-control/conformance/source-control-api.conformance.d.ts +10 -0
  354. package/dist/source-control/conformance/source-control-api.conformance.js +48 -0
  355. package/dist/source-control/controllers/source-control.controller.d.ts +26 -0
  356. package/dist/source-control/controllers/source-control.controller.js +70 -0
  357. package/dist/source-control/index.d.ts +8 -0
  358. package/dist/source-control/index.js +6 -0
  359. package/dist/source-control/messages.en.d.ts +11 -0
  360. package/dist/source-control/messages.en.js +2 -0
  361. package/dist/source-control/models.d.ts +70 -0
  362. package/dist/source-control/models.js +2 -0
  363. package/dist/source-control/ports.d.ts +23 -0
  364. package/dist/source-control/ports.js +3 -0
  365. package/dist/source-control/react/components/SourceControlCredentialFields.d.ts +5 -0
  366. package/dist/source-control/react/components/SourceControlCredentialFields.js +8 -0
  367. package/dist/source-control/react/components/SourceControlProviderRow.d.ts +5 -0
  368. package/dist/source-control/react/components/SourceControlProviderRow.js +7 -0
  369. package/dist/source-control/react/hooks/ProvidersTab.hooks.d.ts +45 -0
  370. package/dist/source-control/react/hooks/ProvidersTab.hooks.js +20 -0
  371. package/dist/source-control/react/hooks/SourceControlCredentialFields.hooks.d.ts +43 -0
  372. package/dist/source-control/react/hooks/SourceControlCredentialFields.hooks.js +9 -0
  373. package/dist/source-control/react/hooks/SourceControlPage.hooks.d.ts +17 -0
  374. package/dist/source-control/react/hooks/SourceControlPage.hooks.js +5 -0
  375. package/dist/source-control/react/hooks/SourceControlPorts.hooks.d.ts +9 -0
  376. package/dist/source-control/react/hooks/SourceControlPorts.hooks.js +6 -0
  377. package/dist/source-control/react/index.d.ts +156 -0
  378. package/dist/source-control/react/index.js +10 -0
  379. package/dist/source-control/react/pages/SourceControlPage.d.ts +4 -0
  380. package/dist/source-control/react/pages/SourceControlPage.js +11 -0
  381. package/dist/source-control/react/tabs/ProvidersTab.d.ts +4 -0
  382. package/dist/source-control/react/tabs/ProvidersTab.js +10 -0
  383. package/dist/source-control/rules.d.ts +32 -0
  384. package/dist/source-control/rules.js +35 -0
  385. package/dist/source-control/source-control.module.d.ts +36 -0
  386. package/dist/source-control/source-control.module.js +6 -0
  387. package/package.json +207 -4
@@ -0,0 +1,47 @@
1
+ import { countMediaTabs } from '../rules.js';
2
+ import type { MediaApiPort } from '../ports.js';
3
+ import type { MediaAsset, MediaQuery, UploadInput } from '../models.js';
4
+ export interface LibraryState {
5
+ readonly items: readonly MediaAsset[] | null;
6
+ readonly query: MediaQuery;
7
+ readonly loading: boolean;
8
+ readonly busy: boolean;
9
+ readonly pendingIds: readonly string[];
10
+ readonly error: string | null;
11
+ readonly pendingPurge: MediaAsset | null;
12
+ readonly hasUntyped: boolean;
13
+ readonly counts: ReturnType<typeof countMediaTabs> | null;
14
+ }
15
+ export declare function createLibraryController({ api }: {
16
+ api: MediaApiPort;
17
+ }, { query }?: {
18
+ query?: MediaQuery;
19
+ }): {
20
+ getSnapshot: () => Readonly<LibraryState>;
21
+ subscribe: ({ listener }: {
22
+ listener: () => void;
23
+ }) => () => void;
24
+ load: (_required?: Record<string, never>, _optional?: Record<string, never>) => Promise<void>;
25
+ setQuery({ query }: {
26
+ query: MediaQuery;
27
+ }, _optional?: Record<string, never>): Promise<void>;
28
+ upload({ input, alt }: {
29
+ input: UploadInput;
30
+ alt?: string;
31
+ }, _optional?: Record<string, never>): Promise<boolean>;
32
+ trash({ id }: {
33
+ id: string;
34
+ }, _optional?: Record<string, never>): Promise<boolean>;
35
+ restore({ id }: {
36
+ id: string;
37
+ }, _optional?: Record<string, never>): Promise<boolean>;
38
+ requestPurge({ item }: {
39
+ item: MediaAsset;
40
+ }, _optional?: Record<string, never>): void;
41
+ cancelPurge(_required?: Record<string, never>, _optional?: Record<string, never>): void;
42
+ confirmPurge({ confirmed }: {
43
+ confirmed: boolean;
44
+ }, _optional?: Record<string, never>): Promise<boolean>;
45
+ dispose(_required?: Record<string, never>, _optional?: Record<string, never>): void;
46
+ };
47
+ //# sourceMappingURL=library.controller.d.ts.map
@@ -0,0 +1,173 @@
1
+ import { createControllerStore } from '../../core/module/controller-store.js';
2
+ import { queryMedia, countMediaTabs, canRestoreMedia, isRetryableMediaError } from '../rules.js';
3
+ export function createLibraryController({ api }, { query = {} } = {}) {
4
+ const store = createControllerStore({
5
+ initial: {
6
+ items: null,
7
+ query,
8
+ loading: false,
9
+ busy: false,
10
+ pendingIds: [],
11
+ error: null,
12
+ pendingPurge: null,
13
+ hasUntyped: false,
14
+ counts: null,
15
+ },
16
+ });
17
+ let library = null;
18
+ let readFailed = false, retryAttempt = 0;
19
+ let retryTimer = null;
20
+ function cancelRetry() {
21
+ if (retryTimer !== null)
22
+ clearTimeout(retryTimer);
23
+ retryTimer = null;
24
+ }
25
+ let readAbort = null, generation = 0;
26
+ async function load(_required = {}, _optional = {}) {
27
+ if (store.signal.aborted)
28
+ return;
29
+ cancelRetry();
30
+ readAbort?.abort();
31
+ const abort = new AbortController();
32
+ readAbort = abort;
33
+ const ticket = ++generation;
34
+ store.set({ patch: { loading: true, error: null } });
35
+ try {
36
+ const all = await api.list({}, { signal: abort.signal });
37
+ const items = queryMedia({ media: all, query: store.getSnapshot().query });
38
+ if (ticket === generation) {
39
+ readFailed = false;
40
+ retryAttempt = 0;
41
+ library = all;
42
+ store.set({
43
+ patch: {
44
+ items: Object.freeze(items.map((item) => Object.freeze({ ...item }))),
45
+ counts: countMediaTabs({ media: all }),
46
+ hasUntyped: all.some((item) => item.contentType === null),
47
+ error: null,
48
+ },
49
+ });
50
+ }
51
+ }
52
+ catch (error) {
53
+ // A later background failure must not blank an already-rendered screen.
54
+ if (!abort.signal.aborted && ticket === generation) {
55
+ readFailed = true;
56
+ store.set({
57
+ patch: { loading: false, error: error instanceof Error ? error.message : 'Failed to load media' },
58
+ });
59
+ // Retry reads only, never destructive writes. Keep trying at a capped delay so
60
+ // recovery needs no manual repair, while an offline service isn't hammered.
61
+ if (isRetryableMediaError({ error })) {
62
+ const delay = Math.min(1000 * 2 ** Math.min(retryAttempt++, 5), 30000);
63
+ retryTimer = setTimeout(() => { retryTimer = null; void load(); }, delay);
64
+ }
65
+ }
66
+ }
67
+ finally {
68
+ if (ticket === generation)
69
+ store.set({ patch: { loading: false } });
70
+ }
71
+ }
72
+ let writes = Promise.resolve();
73
+ let exclusiveWrite = false;
74
+ function write(run, id) {
75
+ if (store.signal.aborted || exclusiveWrite || (id === undefined ? store.getSnapshot().busy : store.getSnapshot().pendingIds.includes(id)))
76
+ return Promise.resolve(false);
77
+ if (id === undefined)
78
+ exclusiveWrite = true;
79
+ // Serialize accepted writes rather than dropping another card's click. Reserve
80
+ // each id immediately so a double click on the same asset cannot enqueue twice.
81
+ store.set({ patch: { busy: true, pendingIds: id === undefined ? store.getSnapshot().pendingIds : [...store.getSnapshot().pendingIds, id] } });
82
+ const operation = writes.then(async () => {
83
+ if (store.signal.aborted)
84
+ return false;
85
+ // Starting a write clears other writes' failures, so a stale failure from one must
86
+ // not survive past the start of an unrelated one.
87
+ generation++;
88
+ readAbort?.abort();
89
+ cancelRetry();
90
+ store.set({ patch: { loading: false, error: null } });
91
+ try {
92
+ await run();
93
+ if (store.signal.aborted)
94
+ return false;
95
+ await load();
96
+ return true;
97
+ }
98
+ catch (error) {
99
+ if (!store.signal.aborted)
100
+ store.set({ patch: { loading: false, error: error instanceof Error ? error.message : 'Media write failed' } });
101
+ return false;
102
+ }
103
+ finally {
104
+ if (id === undefined)
105
+ exclusiveWrite = false;
106
+ const pendingIds = store.getSnapshot().pendingIds.filter(value => value !== id);
107
+ store.set({ patch: { busy: pendingIds.length > 0, pendingIds } });
108
+ }
109
+ });
110
+ writes = operation.catch(() => undefined);
111
+ return operation;
112
+ }
113
+ return {
114
+ getSnapshot: store.getSnapshot,
115
+ subscribe: store.subscribe,
116
+ load,
117
+ async setQuery({ query }, _optional = {}) {
118
+ // Already-fetched full library owns display order/filtering. Tab/search/order changes
119
+ // never spend another request or flash a loading zero badge.
120
+ store.set({ patch: { query, ...(library ? { items: queryMedia({ media: library, query }) } : {}) } });
121
+ if (!library || readFailed)
122
+ await load();
123
+ },
124
+ async upload({ input, alt = '' }, _optional = {}) {
125
+ return write(() => api.upload(input, { alt, signal: store.signal }));
126
+ },
127
+ // Trashing (the reversible first rung of the ladder) has no confirm step — only
128
+ // the irreversible purge step gates on the dialog, matching the original asymmetry.
129
+ async trash({ id }, _optional = {}) {
130
+ return write(() => api.trash({ id }, { signal: store.signal }), id);
131
+ },
132
+ async restore({ id }, _optional = {}) {
133
+ if (!canRestoreMedia({ api })) {
134
+ store.set({ patch: { error: 'Restoring media is unavailable' } });
135
+ return false;
136
+ }
137
+ return write(() => api.restore({ id }, { signal: store.signal }), id);
138
+ },
139
+ requestPurge({ item }, _optional = {}) {
140
+ if (!store.getSnapshot().busy && item.status === 'trashed')
141
+ store.set({ patch: { pendingPurge: item } });
142
+ },
143
+ cancelPurge(_required = {}, _optional = {}) {
144
+ if (!store.getSnapshot().busy)
145
+ store.set({ patch: { pendingPurge: null } });
146
+ },
147
+ async confirmPurge({ confirmed }, _optional = {}) {
148
+ const item = store.getSnapshot().pendingPurge;
149
+ if (!confirmed || !item)
150
+ return false;
151
+ const saved = await write(async () => {
152
+ const result = await api.delete({ id: item.id }, { signal: store.signal });
153
+ if (!result.purged)
154
+ throw new Error('The file was not permanently deleted');
155
+ // A failed follow-up read must not leave a permanently deleted card focusable.
156
+ if (library) {
157
+ library = library.filter(row => row.id !== item.id);
158
+ store.set({ patch: { items: queryMedia({ media: library, query: store.getSnapshot().query }), counts: countMediaTabs({ media: library }) } });
159
+ }
160
+ });
161
+ if (saved)
162
+ store.set({ patch: { pendingPurge: null } });
163
+ return saved;
164
+ },
165
+ dispose(_required = {}, _optional = {}) {
166
+ generation++;
167
+ cancelRetry();
168
+ readAbort?.abort();
169
+ store.dispose();
170
+ },
171
+ };
172
+ }
173
+ //# sourceMappingURL=library.controller.js.map
@@ -0,0 +1,30 @@
1
+ import type { MediaProvidersPort } from '../ports.js';
2
+ import type { MediaProvider } from '../models.js';
3
+ export declare function createProvidersController({ api }: {
4
+ api: MediaProvidersPort;
5
+ }, _optional?: Record<string, never>): {
6
+ getSnapshot: () => Readonly<{
7
+ items: readonly MediaProvider[];
8
+ loading: boolean;
9
+ saving: boolean;
10
+ error: string | null;
11
+ }>;
12
+ subscribe: ({ listener }: {
13
+ listener: () => void;
14
+ }) => () => void;
15
+ load: (_required?: Record<string, never>, _optional?: Record<string, never>) => Promise<void>;
16
+ save({ id, credential }: {
17
+ id: string;
18
+ credential: string;
19
+ }, _optional?: Record<string, never>): Promise<boolean>;
20
+ saveSettings({ id, baseUrl, model }: {
21
+ id: string;
22
+ baseUrl: string;
23
+ model: string;
24
+ }, _optional?: Record<string, never>): Promise<boolean>;
25
+ remove({ id }: {
26
+ id: string;
27
+ }, _optional?: Record<string, never>): Promise<boolean>;
28
+ dispose(_required?: Record<string, never>, _optional?: Record<string, never>): void;
29
+ };
30
+ //# sourceMappingURL=providers.controller.d.ts.map
@@ -0,0 +1,76 @@
1
+ import { createControllerStore } from '../../core/module/controller-store.js';
2
+ export function createProvidersController({ api }, _optional = {}) {
3
+ const store = createControllerStore({
4
+ initial: {
5
+ items: [],
6
+ loading: false,
7
+ saving: false,
8
+ error: null,
9
+ },
10
+ });
11
+ let generation = 0;
12
+ async function load(_required = {}, _optional = {}) {
13
+ if (store.signal.aborted)
14
+ return;
15
+ const ticket = ++generation;
16
+ store.set({ patch: { loading: true } });
17
+ try {
18
+ const items = await api.list({}, { signal: store.signal });
19
+ // null leaves local edits untouched; [] is an authoritative empty answer.
20
+ if (ticket === generation)
21
+ store.set({
22
+ patch: items === null
23
+ ? { error: 'Provider service unavailable' }
24
+ : { items: Object.freeze([...items]), error: null },
25
+ });
26
+ }
27
+ catch {
28
+ if (ticket === generation)
29
+ store.set({ patch: { error: 'Provider service unavailable' } });
30
+ }
31
+ finally {
32
+ if (ticket === generation)
33
+ store.set({ patch: { loading: false } });
34
+ }
35
+ }
36
+ async function write(run) {
37
+ if (store.signal.aborted || store.getSnapshot().saving)
38
+ return false;
39
+ store.set({ patch: { saving: true, error: null } });
40
+ try {
41
+ await run();
42
+ if (store.signal.aborted)
43
+ return false;
44
+ await load();
45
+ return true;
46
+ }
47
+ catch {
48
+ store.set({ patch: { error: 'Failed to save provider credentials' } });
49
+ return false;
50
+ }
51
+ finally {
52
+ store.set({ patch: { saving: false } });
53
+ }
54
+ }
55
+ return {
56
+ getSnapshot: store.getSnapshot,
57
+ subscribe: store.subscribe,
58
+ load,
59
+ save({ id, credential }, _optional = {}) {
60
+ return write(() => api.saveCredential({ id, credential }, { signal: store.signal }));
61
+ },
62
+ saveSettings({ id, baseUrl, model }, _optional = {}) {
63
+ if (!api.saveSettings)
64
+ return Promise.resolve(false);
65
+ return write(() => api.saveSettings({ id, baseUrl, model }, { signal: store.signal }));
66
+ },
67
+ remove({ id }, _optional = {}) {
68
+ return write(() => api.removeCredential({ id }, { signal: store.signal }));
69
+ },
70
+ dispose(_required = {}, _optional = {}) {
71
+ generation++;
72
+ store.dispose();
73
+ },
74
+ };
75
+ }
76
+ //# sourceMappingURL=providers.controller.js.map
@@ -0,0 +1,82 @@
1
+ /** Browser-only live metadata hint, separate from server validation across the browser/Node
2
+ * boundary. The server must enforce the same policy on writes and rendering independently.
3
+ * Hints never gate Save: an invalid attribute once silently blocked saving unrelated title/alt/
4
+ * caption/credit/class changes. Rejections must instead reach the authoritative atomic write.
5
+ * One flat allowlist includes global and image/video extras: per-element name filtering is
6
+ * unnecessary because an unused loading attribute on a wrapper is harmless. Extend the list,
7
+ * not the tokenizer, when adding an allowed name. data-/aria- families remain open-ended. */
8
+ export declare const MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES: readonly ["class", "id", "style", "title", "role", "tabindex", "lang", "dir", "hidden", "translate", "draggable", "loading", "decoding", "playsinline", "muted", "loop", "autoplay", "poster"];
9
+ /** Whether `name` is on the allowlist — a well-formed attribute name
10
+ * ({@link MEDIA_HTML_ATTRIBUTE_NAME_PATTERN}) that is either an exact match against
11
+ * {@link MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES} or carries a `data-`/`aria-` prefix (both open-ended
12
+ * families with no fixed suffix list). Case-insensitive: HTML attribute names are themselves
13
+ * case-insensitive, and an operator typing `DATA-FOO` should not slip past a lowercase-only check.
14
+ *
15
+ * @complexity O(n) in the name's length (one anchored regex test), then O(1) — one prefix check,
16
+ * one fixed-length array lookup.
17
+ */
18
+ export declare function isAllowedMediaHtmlAttributeName({ name }: {
19
+ name: string;
20
+ }, _optional?: Record<string, never>): boolean;
21
+ /** Why one parsed attribute token was rejected — see {@link parseMediaHtmlAttributes}'s own doc for
22
+ * why `event-handler`/`unsafe-url` are checked, and reported, ahead of plain allowlist membership.
23
+ * `unsafe-url` replaces the old `javascript-url` name (2026-09-23: it now also covers
24
+ * `vbscript:`/`data:` schemes, not just `javascript:`) — the i18n message key for it is unchanged.
25
+ * `unsafe-style` is new: a `style` value carrying a known CSS injection/escape vector. */
26
+ export type MediaHtmlAttributeRejectionReason = "disallowed-name" | "event-handler" | "unsafe-url" | "unsafe-style" | "malformed";
27
+ export interface MediaHtmlAttributeError {
28
+ reason: MediaHtmlAttributeRejectionReason;
29
+ /** The exact attribute name (or, for `malformed`, the unparsable fragment) the caller's error
30
+ * message must name — never a generic "invalid input" (owner requirement: a visible, specific
31
+ * error naming the rejected attribute). */
32
+ attribute: string;
33
+ }
34
+ export interface ParsedMediaHtmlAttributes {
35
+ /** Lowercased attribute name -> value, for every token that WAS accepted. Never all-or-nothing
36
+ * except on `malformed` (see {@link parseMediaHtmlAttributes}'s own header): a rejected token
37
+ * (2026-09-23) is simply absent here while every other accepted token in the same string still
38
+ * lands. A boolean attribute (`muted`, written with no `="..."`) maps to `""`. */
39
+ attributes: Record<string, string>;
40
+ /** Every rejection found, in the order encountered — zero or more (2026-09-23). Unlike
41
+ * `attributes`, this is never reset to empty except that a `malformed` entry is always the last
42
+ * one recorded (parsing stops there). */
43
+ errors: MediaHtmlAttributeError[];
44
+ /** The FIRST rejection in {@link errors}, or `null` when every token was accepted — kept as its
45
+ * own field for the existing single-reason hint consumers (`MediaEditDialog.hooks.tsx`,
46
+ * `use-edit-media-panel.hooks.ts`) that only ever show one reason at a time. */
47
+ error: MediaHtmlAttributeError | null;
48
+ }
49
+ /**
50
+ * Parses the `HTML attributes` field's free text (`name="value" name2="value2"`, or a bare
51
+ * boolean `name`) into a validated attribute map, per-token (ported 2026-09-23 from the shared
52
+ * server parser, `html-attributes.ts`'s `parseEmbedHtmlAttributes`): an accepted token lands in
53
+ * `attributes`, a rejected one lands in `errors` and is simply omitted — this string never fails
54
+ * closed as a whole EXCEPT when the tokenizer itself loses sync (a stray quote or other unparseable
55
+ * fragment), in which case `attributes` is `{}` and the sole entry in `errors` has
56
+ * `reason: "malformed"` (token boundaries downstream of a sync loss cannot be trusted — value text
57
+ * could re-parse as a name — so that one case still discards everything).
58
+ *
59
+ * This is a SECURITY boundary, not a syntax convenience: media metadata is authored in this admin
60
+ * but rendered on the public site, so a free-text HTML-attribute passthrough is a stored-XSS vector
61
+ * (`onerror`, `onclick`, `href="javascript:"`, any `on*` handler, an unsafe `style` value) the
62
+ * moment it reaches a public page. The allowlist in {@link isAllowedMediaHtmlAttributeName} is
63
+ * therefore the ONLY path to acceptance — nothing here tries to sanitize or escape an otherwise-
64
+ * disallowed name or value into something safe; it is rejected outright, and the caller must show
65
+ * the reason (not silently drop it) so an operator can tell a typo from a hard "no". NOTE: this
66
+ * validator runs in the admin only — see this repo's media-admin-ui report for where the write and
67
+ * render paths that would actually persist and emit this attribute still need the SAME allowlist
68
+ * enforced server-side (a client-only check is not a control, since the API accepts whatever a
69
+ * caller sends).
70
+ *
71
+ * @complexity Time O(n) in `text`'s length (one regex pass over it), space O(k) for k accepted
72
+ * attributes plus O(e) for e rejected tokens.
73
+ */
74
+ export declare function parseMediaHtmlAttributes({ text }: {
75
+ text: string;
76
+ }, _optional?: Record<string, never>): ParsedMediaHtmlAttributes;
77
+ /** The hint names the rejected token so operators can distinguish a typo from a hard no.
78
+ * This is client feedback only; the server must independently enforce write/render policy. */
79
+ export declare function describeMediaHtmlAttributeError({ error }: {
80
+ error: MediaHtmlAttributeError;
81
+ }, _optional?: Record<string, never>): string;
82
+ //# sourceMappingURL=html-attributes.d.ts.map
@@ -0,0 +1,204 @@
1
+ /** Browser-only live metadata hint, separate from server validation across the browser/Node
2
+ * boundary. The server must enforce the same policy on writes and rendering independently.
3
+ * Hints never gate Save: an invalid attribute once silently blocked saving unrelated title/alt/
4
+ * caption/credit/class changes. Rejections must instead reach the authoritative atomic write.
5
+ * One flat allowlist includes global and image/video extras: per-element name filtering is
6
+ * unnecessary because an unused loading attribute on a wrapper is harmless. Extend the list,
7
+ * not the tokenizer, when adding an allowed name. data-/aria- families remain open-ended. */
8
+ export const MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES = [
9
+ // Global attributes (2026-09-23).
10
+ "class",
11
+ "id",
12
+ "style",
13
+ "title",
14
+ "role",
15
+ "tabindex",
16
+ "lang",
17
+ "dir",
18
+ "hidden",
19
+ "translate",
20
+ "draggable",
21
+ // Pre-existing <img>/<video> extras, kept everywhere (one list, not one per element).
22
+ "loading",
23
+ "decoding",
24
+ "playsinline",
25
+ "muted",
26
+ "loop",
27
+ "autoplay",
28
+ "poster",
29
+ ];
30
+ /**
31
+ * The shape EVERY accepted attribute name must have, checked before allowlist membership
32
+ * (2026-09-07 stored-XSS fix; keep identical in `@jini-ai/cms/media`'s `html-attributes.ts` — see
33
+ * that file's copy of this comment for the full incident).
34
+ *
35
+ * The `data-`/`aria-` families below are open-ended by design — no fixed suffix list — so the
36
+ * prefix check alone accepted whatever characters {@link HTML_ATTRIBUTE_TOKEN}'s name class
37
+ * (`[^\s="']+`, which excludes only whitespace, `=` and quotes) let through. `<`, `>` and `/` are
38
+ * all legal in that class, and the server renderer templating a name into ` name="value"` escapes
39
+ * only the VALUE — so a stored `data-x><svg/onload=alert(1)` closed the `<img>` and opened a live
40
+ * `<svg onload>` on the public page. Constraining the NAME to the characters a real HTML attribute
41
+ * name can contain is what makes "the value is escaped" sufficient.
42
+ */
43
+ const MEDIA_HTML_ATTRIBUTE_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
44
+ /** Whether `name` is on the allowlist — a well-formed attribute name
45
+ * ({@link MEDIA_HTML_ATTRIBUTE_NAME_PATTERN}) that is either an exact match against
46
+ * {@link MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES} or carries a `data-`/`aria-` prefix (both open-ended
47
+ * families with no fixed suffix list). Case-insensitive: HTML attribute names are themselves
48
+ * case-insensitive, and an operator typing `DATA-FOO` should not slip past a lowercase-only check.
49
+ *
50
+ * @complexity O(n) in the name's length (one anchored regex test), then O(1) — one prefix check,
51
+ * one fixed-length array lookup.
52
+ */
53
+ export function isAllowedMediaHtmlAttributeName({ name }, _optional = {}) {
54
+ const lower = name.toLowerCase();
55
+ if (!MEDIA_HTML_ATTRIBUTE_NAME_PATTERN.test(lower))
56
+ return false;
57
+ if (lower.startsWith("data-") || lower.startsWith("aria-"))
58
+ return true;
59
+ return MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES.includes(lower);
60
+ }
61
+ /** Matches one `name`, or one `name="value"`/`name='value'`/`name=value` pair — the same loose
62
+ * shape real HTML attribute syntax allows, since that is the syntax an operator typing this field
63
+ * would naturally reach for. */
64
+ const HTML_ATTRIBUTE_TOKEN = /([^\s="']+)(?:=(?:"([^"]*)"|'([^']*)'|([^\s"']+)))?/g;
65
+ /** The highest ASCII code point {@link isUnsafeMediaUrlValue} strips: every C0 control character
66
+ * plus the ordinary space, i.e. code points `0`-`32` inclusive. Named rather than written as an
67
+ * inline regex control-character range so the linter never has to reason about a literal control
68
+ * character inside a pattern. Ported from the shared server parser, `html-attributes.ts`. */
69
+ const ASCII_WHITESPACE_OR_CONTROL_MAX_CODE_POINT = 0x20;
70
+ /**
71
+ * Whether `value`'s scheme, once every ASCII whitespace/control character (code points `0`-`32`) is
72
+ * stripped and the result lowercased, starts with `javascript:`, `vbscript:` or `data:` — the three
73
+ * schemes a browser will still execute or navigate to even when the URL carries interior whitespace
74
+ * a naive end-trim (the old `.trim()`-only check) would miss, e.g. `java\tscript:x`: browsers strip
75
+ * tabs and newlines from inside a URL before parsing its scheme, so this check must too. Ported
76
+ * 2026-09-23 from the shared server parser's `isUnsafeUrlValue` (`html-attributes.ts`).
77
+ *
78
+ * @complexity O(n) in the value's length (one filter pass, one prefix test).
79
+ */
80
+ function isUnsafeMediaUrlValue(value) {
81
+ let stripped = "";
82
+ for (const char of value) {
83
+ if ((char.codePointAt(0) ?? 0) > ASCII_WHITESPACE_OR_CONTROL_MAX_CODE_POINT)
84
+ stripped += char;
85
+ }
86
+ stripped = stripped.toLowerCase();
87
+ return stripped.startsWith("javascript:") || stripped.startsWith("vbscript:") || stripped.startsWith("data:");
88
+ }
89
+ /**
90
+ * Whether a `style` value carries a known CSS injection or CSS-escape-bypass vector:
91
+ * `expression(...)` (legacy IE script-in-CSS), a `javascript:`/`vbscript:` value inside a CSS
92
+ * `url(...)`, `-moz-binding`/`behavior:` (XBL/HTC script bindings), `@import` (pulls in a second,
93
+ * unvetted stylesheet), or ANY backslash — a CSS escape sequence (`\65` is `e`) can respell any of
94
+ * the above past a plain substring check, so a backslash anywhere in the value is rejected outright.
95
+ * Plain `url(...)` values (background images) are otherwise allowed. Ported 2026-09-23 from the
96
+ * shared server parser's `isUnsafeStyleValue` (`html-attributes.ts`).
97
+ *
98
+ * @complexity O(n) in the value's length (one regex test).
99
+ */
100
+ const UNSAFE_MEDIA_STYLE_PATTERN = /expression\(|javascript:|vbscript:|-moz-binding|behavior:|@import|\\/i;
101
+ function isUnsafeMediaStyleValue(value) {
102
+ return UNSAFE_MEDIA_STYLE_PATTERN.test(value);
103
+ }
104
+ /** Classifies one already-tokenized `name`/`value` pair. Split out of {@link parseMediaHtmlAttributes}
105
+ * so each rejection reason is its own directly testable branch, and so the loop below reads as
106
+ * "tokenize, then classify" rather than one function doing both.
107
+ *
108
+ * Order matters (ported 2026-09-23 from the shared server parser's `classifyEmbedHtmlAttributeToken`):
109
+ * `on*` is checked FIRST (so an `onerror` always reports as `event-handler`, the more actionable
110
+ * reason, never the generic `disallowed-name`), then the URL-scheme check runs against EVERY value
111
+ * regardless of name (matching the pre-existing `javascript:` check this is ported from), then
112
+ * allowlist membership, then — only for an already-allowed `style` — the CSS-specific unsafe-value
113
+ * check.
114
+ *
115
+ * @complexity O(1) — four fixed checks against one already-extracted token; each check's own O(n) in
116
+ * the token's length is linear, not nested.
117
+ */
118
+ function classifyMediaHtmlAttributeToken(name, value) {
119
+ if (name.startsWith("on"))
120
+ return { reason: "event-handler", attribute: name };
121
+ if (isUnsafeMediaUrlValue(value))
122
+ return { reason: "unsafe-url", attribute: name };
123
+ if (!isAllowedMediaHtmlAttributeName({ name }))
124
+ return { reason: "disallowed-name", attribute: name };
125
+ if (name === "style" && isUnsafeMediaStyleValue(value))
126
+ return { reason: "unsafe-style", attribute: name };
127
+ return null;
128
+ }
129
+ /**
130
+ * Parses the `HTML attributes` field's free text (`name="value" name2="value2"`, or a bare
131
+ * boolean `name`) into a validated attribute map, per-token (ported 2026-09-23 from the shared
132
+ * server parser, `html-attributes.ts`'s `parseEmbedHtmlAttributes`): an accepted token lands in
133
+ * `attributes`, a rejected one lands in `errors` and is simply omitted — this string never fails
134
+ * closed as a whole EXCEPT when the tokenizer itself loses sync (a stray quote or other unparseable
135
+ * fragment), in which case `attributes` is `{}` and the sole entry in `errors` has
136
+ * `reason: "malformed"` (token boundaries downstream of a sync loss cannot be trusted — value text
137
+ * could re-parse as a name — so that one case still discards everything).
138
+ *
139
+ * This is a SECURITY boundary, not a syntax convenience: media metadata is authored in this admin
140
+ * but rendered on the public site, so a free-text HTML-attribute passthrough is a stored-XSS vector
141
+ * (`onerror`, `onclick`, `href="javascript:"`, any `on*` handler, an unsafe `style` value) the
142
+ * moment it reaches a public page. The allowlist in {@link isAllowedMediaHtmlAttributeName} is
143
+ * therefore the ONLY path to acceptance — nothing here tries to sanitize or escape an otherwise-
144
+ * disallowed name or value into something safe; it is rejected outright, and the caller must show
145
+ * the reason (not silently drop it) so an operator can tell a typo from a hard "no". NOTE: this
146
+ * validator runs in the admin only — see this repo's media-admin-ui report for where the write and
147
+ * render paths that would actually persist and emit this attribute still need the SAME allowlist
148
+ * enforced server-side (a client-only check is not a control, since the API accepts whatever a
149
+ * caller sends).
150
+ *
151
+ * @complexity Time O(n) in `text`'s length (one regex pass over it), space O(k) for k accepted
152
+ * attributes plus O(e) for e rejected tokens.
153
+ */
154
+ export function parseMediaHtmlAttributes({ text }, _optional = {}) {
155
+ const trimmed = text.trim();
156
+ if (trimmed === "")
157
+ return { attributes: {}, errors: [], error: null };
158
+ const attributes = {};
159
+ const errors = [];
160
+ HTML_ATTRIBUTE_TOKEN.lastIndex = 0;
161
+ let consumed = 0;
162
+ // A `for` loop (rather than `while ((match = …exec(trimmed)) !== null)`) keeps every assignment
163
+ // to `match` in statement position, never inside the loop's own test expression.
164
+ for (let match = HTML_ATTRIBUTE_TOKEN.exec(trimmed); match !== null; match = HTML_ATTRIBUTE_TOKEN.exec(trimmed)) {
165
+ // Non-whitespace text between the previous match and this one is a fragment the token pattern
166
+ // could not parse as a name (e.g. a stray quote) — the tokenizer has lost sync, so everything
167
+ // parsed so far is discarded rather than trusted.
168
+ const skipped = trimmed.slice(consumed, match.index);
169
+ if (skipped.trim() !== "") {
170
+ const malformed = { reason: "malformed", attribute: skipped.trim() };
171
+ errors.push(malformed);
172
+ return { attributes: {}, errors, error: malformed };
173
+ }
174
+ consumed = match.index + match[0].length;
175
+ const name = match[1].toLowerCase();
176
+ const value = match[2] ?? match[3] ?? match[4] ?? "";
177
+ const rejection = classifyMediaHtmlAttributeToken(name, value);
178
+ if (rejection) {
179
+ errors.push(rejection);
180
+ continue;
181
+ }
182
+ attributes[name] = value;
183
+ }
184
+ const trailing = trimmed.slice(consumed);
185
+ if (trailing.trim() !== "") {
186
+ const malformed = { reason: "malformed", attribute: trailing.trim() };
187
+ errors.push(malformed);
188
+ return { attributes: {}, errors, error: malformed };
189
+ }
190
+ return { attributes, errors, error: errors[0] ?? null };
191
+ }
192
+ /** The hint names the rejected token so operators can distinguish a typo from a hard no.
193
+ * This is client feedback only; the server must independently enforce write/render policy. */
194
+ export function describeMediaHtmlAttributeError({ error }, _optional = {}) {
195
+ const name = error.attribute;
196
+ switch (error.reason) {
197
+ case 'event-handler': return `Event handler attributes like '${name}' are not allowed.`;
198
+ case 'unsafe-url': return `'${name}' cannot use a javascript: value.`;
199
+ case 'unsafe-style': return `'${name}' cannot use an unsafe style value.`;
200
+ case 'malformed': return `Could not parse HTML attributes near '${name}'.`;
201
+ default: return `'${name}' is not an allowed HTML attribute.`;
202
+ }
203
+ }
204
+ //# sourceMappingURL=html-attributes.js.map
@@ -0,0 +1,12 @@
1
+ export { mediaModule } from './media.module.js';
2
+ export { mediaApiToken, mediaProvidersToken } from './ports.js';
3
+ export type { MediaApiPort, MediaProvidersPort, MediaEventsPort } from './ports.js';
4
+ export type { MediaAsset, MediaQuery, UploadInput, MediaMetadataPatch, MediaRequestOptions, MediaProvider, MediaProviderOption, MediaProviderConfiguration, MediaProviderConfigurationMap, MediaTabId, MediaContentTabId, MediaOrderBy, } from './models.js';
5
+ export { mediaMessagesEn } from './messages.en.js';
6
+ export { filterMediaByTab, resolveActiveTab, queryMedia, diffMediaMetadata, acceptsMedia, safeMediaUrl, canReplaceMedia, canRestoreMedia, isRetryableMediaError, parseOptionalPixelSize, formatByteSize, formatUploadDate, countMediaTabs, mediaEmbedSnippet, mediaRowHandles, } from './rules.js';
7
+ export { createLibraryController } from './controllers/library.controller.js';
8
+ export type { LibraryState } from './controllers/library.controller.js';
9
+ export { createEditMediaController } from './controllers/edit-media.controller.js';
10
+ export { createProvidersController } from './controllers/providers.controller.js';
11
+ export { parseMediaHtmlAttributes, describeMediaHtmlAttributeError, isAllowedMediaHtmlAttributeName, MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES } from './html-attributes.js';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,9 @@
1
+ export { mediaModule } from './media.module.js';
2
+ export { mediaApiToken, mediaProvidersToken } from './ports.js';
3
+ export { mediaMessagesEn } from './messages.en.js';
4
+ export { filterMediaByTab, resolveActiveTab, queryMedia, diffMediaMetadata, acceptsMedia, safeMediaUrl, canReplaceMedia, canRestoreMedia, isRetryableMediaError, parseOptionalPixelSize, formatByteSize, formatUploadDate, countMediaTabs, mediaEmbedSnippet, mediaRowHandles, } from './rules.js';
5
+ export { createLibraryController } from './controllers/library.controller.js';
6
+ export { createEditMediaController } from './controllers/edit-media.controller.js';
7
+ export { createProvidersController } from './controllers/providers.controller.js';
8
+ export { parseMediaHtmlAttributes, describeMediaHtmlAttributeError, isAllowedMediaHtmlAttributeName, MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES } from './html-attributes.js';
9
+ //# sourceMappingURL=index.js.map