@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
@@ -3,40 +3,78 @@
3
3
  *
4
4
  * Ported from the reference implementation's `lib/api.ts` `request()`, with the two
5
5
  * hard-coded values it closed over — the base URL and the same-origin cookie policy — lifted into
6
- * options. Nothing else about the request/response handling changed; the error-body parsing in
7
- * particular is deliberately identical, including the `.catch(() => ({}))` on a body that is not
8
- * JSON.
6
+ * options. Request deadlines, typed failures and successful response metadata preserve the
7
+ * original contracts with neutral operator copy. Error-body parsing deliberately keeps the
8
+ * `.catch(() => ({}))` outcome on a body that is not JSON, while distinguishing it from parsed null.
9
9
  */
10
10
  import { AdminApiError } from './errors.js';
11
+ // Browser connection pools can leave requests queued forever without a fetch rejection.
12
+ // 60 seconds bounds that hang while allowing large uploads on slow connections to complete.
13
+ const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
14
+ /** Unparseable proxy/crash responses must remain distinct from valid JSON null or {}. */
15
+ const UNPARSEABLE_BODY = Symbol('unparseable-json-body');
16
+ function unreachableApiMessage({ status } = {}) {
17
+ const detail = status === undefined ? '' : ` (HTTP ${status})`;
18
+ return `cannot reach the API${detail} — is the server running?`;
19
+ }
20
+ /** Timeout DOMExceptions can come from another realm; instanceof Error alone is unreliable. */
21
+ function throwTranslatedFetchFailure({ cause, timeoutMs }, { isPageUnloading } = {}) {
22
+ const name = cause && typeof cause === 'object' && 'name' in cause ? cause.name : undefined;
23
+ if (name === 'AbortError')
24
+ throw cause;
25
+ if (name === 'TimeoutError') {
26
+ const message = `the API did not respond within ${timeoutMs / 1000}s — the browser may be out of free connections for this origin (try closing other admin tabs) or the server is unresponsive`;
27
+ throw new AdminApiError({ message, status: 0 }, {
28
+ code: 'REQUEST_TIMEOUT', body: { cause: cause instanceof Error ? cause.message : String(cause) },
29
+ });
30
+ }
31
+ // Only fetch's TypeError proves a network failure. Adapter bugs retain their original type.
32
+ if (!(cause instanceof TypeError))
33
+ throw cause;
34
+ if (isPageUnloading?.())
35
+ throw new DOMException('request cancelled: the page is unloading', 'AbortError');
36
+ throw new AdminApiError({ message: unreachableApiMessage(), status: 0 }, {
37
+ code: 'API_UNREACHABLE', body: { cause: cause.message },
38
+ });
39
+ }
11
40
  /**
12
41
  * The standard `fetch`-backed transport.
13
42
  *
14
43
  * Success bodies are parsed as JSON. A non-2xx response throws `AdminApiError` carrying the
15
44
  * server's `error` string, HTTP status, canonical `code` when present, and the raw parsed body.
16
45
  */
17
- export function createHttpTransport(options) {
18
- const { baseUrl, headers: baseHeaders, credentials = 'same-origin' } = options;
19
- const doFetch = options.fetch ?? globalThis.fetch;
46
+ export function createHttpTransport({ baseUrl, fetch: doFetch }, { headers: baseHeaders, credentials = 'same-origin', timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS, isPageUnloading } = {}) {
20
47
  return {
21
- async request(path, init = {}) {
22
- const res = await doFetch(`${baseUrl}${path}`, {
48
+ async request({ path }, options = {}) {
49
+ const { onOk, ...init } = options;
50
+ // Headers normalizes names and accepts every HeadersInit form; object spreads lose
51
+ // Headers instances and treat tuple arrays as numeric keys instead of header entries.
52
+ const headers = new Headers({ 'Content-Type': 'application/json' });
53
+ new Headers(baseHeaders).forEach((value, name) => headers.set(name, value));
54
+ new Headers(init.headers).forEach((value, name) => headers.set(name, value));
55
+ // An explicit signal owns cancellation and its deadline; never replace it with ours.
56
+ const signal = init.signal ?? AbortSignal.timeout(timeoutMs);
57
+ const res = await doFetch({ url: `${baseUrl}${path}` }, {
23
58
  credentials,
24
59
  ...init,
25
- // Spread last so a per-call header overrides both the JSON default and the transport's
60
+ signal,
61
+ // Assign last so a per-call header overrides both the JSON default and the transport's
26
62
  // own. The reference implementation's original spread `...init` last, which meant a call passing `headers` lost
27
63
  // the `Content-Type` default rather than merging with it.
28
- headers: {
29
- 'Content-Type': 'application/json',
30
- ...baseHeaders,
31
- ...init.headers,
32
- },
33
- });
64
+ headers,
65
+ }).catch((cause) => throwTranslatedFetchFailure({ cause, timeoutMs }, isPageUnloading ? { isPageUnloading } : {}));
34
66
  // A 204 or an empty body is not a JSON parse failure worth surfacing — it is a successful
35
67
  // mutation with nothing to return. Same `.catch(() => ({}))` as the original.
36
- const body = (await res.json().catch(() => ({})));
68
+ const parsed = await res.json().catch(() => UNPARSEABLE_BODY);
69
+ const wasUnparseable = parsed === UNPARSEABLE_BODY;
70
+ const body = (wasUnparseable ? {} : parsed);
37
71
  if (!res.ok) {
38
- throw new AdminApiError(String(body?.error ?? `request failed (${res.status})`), res.status, typeof body?.code === 'string' ? body.code : undefined, body);
72
+ // Valid JSON proves the application answered. Only unparseable 5xx responses indicate
73
+ // an unreachable upstream or a server crash outside the application's JSON envelope.
74
+ const noAppEnvelope = wasUnparseable && res.status >= 500;
75
+ throw new AdminApiError({ message: String(body?.error ?? (noAppEnvelope ? unreachableApiMessage({ status: res.status }) : `request failed (${res.status})`)), status: res.status }, { code: noAppEnvelope ? 'API_UNREACHABLE' : typeof body?.code === 'string' ? body.code : undefined, body });
39
76
  }
77
+ onOk?.({ response: res });
40
78
  return body;
41
79
  },
42
80
  };
@@ -44,10 +82,10 @@ export function createHttpTransport(options) {
44
82
  /**
45
83
  * Assembles route groups into one client.
46
84
  *
47
- * Jini-shipped and host-owned groups are indistinguishable here on purpose — both are just
48
- * `(transport) => port`. See `types.ts`'s header for the worked example.
85
+ * Shared and host-owned groups are indistinguishable here on purpose — both are just
86
+ * `({ transport }) => port`. See `types.ts`'s header for the worked example.
49
87
  */
50
- export function createAdminClient(transport, groups) {
88
+ export function createAdminClient({ transport, groups }) {
51
89
  const built = { transport };
52
90
  for (const [name, factory] of Object.entries(groups)) {
53
91
  // `transport` is a reserved key: shadowing it would silently replace the escape hatch every
@@ -56,7 +94,7 @@ export function createAdminClient(transport, groups) {
56
94
  if (name === 'transport') {
57
95
  throw new Error('createAdminClient: "transport" is a reserved route-group name');
58
96
  }
59
- built[name] = factory(transport);
97
+ built[name] = factory({ transport });
60
98
  }
61
99
  return built;
62
100
  }
@@ -13,23 +13,25 @@
13
13
  * factory over it — makes both work:
14
14
  *
15
15
  * ```ts
16
- * const transport = createHttpTransport({ baseUrl: '/api/admin/v1' });
16
+ * const transport = createHttpTransport({ baseUrl: '/api/admin/v1', fetch: ({ url }, init) => fetch(url, init) });
17
17
  *
18
- * const client = createAdminClient(transport, {
19
- * identity: createIdentityRoutes, // shipped by @jini-ai/admin
20
- * media: createMediaRoutes, // shipped by @jini-ai/admin
18
+ * const client = createAdminClient({ transport, groups: {
19
+ * identity: createHostIdentityRoutes, // host implementation of AdminIdentityPort
20
+ * media: createHostMediaRoutes, // host implementation of AdminMediaPort
21
21
  * posts: createHostPostRoutes, // the host's own, same signature
22
22
  * widgets: createHostWidgetRoutes, // the host's own
23
- * });
23
+ * } });
24
24
  *
25
- * await client.identity.listUsers(); // Jini's
26
- * await client.posts.list(); // the host's
25
+ * await client.identity.listUsers({}); // host adapter over Jini's port contract
26
+ * await client.posts.list({}); // the host's own port
27
27
  * ```
28
28
  *
29
29
  * Both halves share one auth policy, one error class, one place to add retries or tracing. A host
30
30
  * adding a route is a factory plus one line in the map — it never edits a Jini file, and it never
31
31
  * grows a second `fetch` wrapper with its own subtly different error handling, which is exactly
32
32
  * how `api.ts` reached 1,548 lines.
33
+ * This package ships port contracts and the assembler, not backend route factories;
34
+ * the host supplies each factory and may share it with other hosts without changing this seam.
33
35
  *
34
36
  * ## Why `AdminTransport` is an interface and not just a function
35
37
  *
@@ -51,25 +53,35 @@ export interface AdminTransport {
51
53
  * @param path Route-group-relative path, always starting with `/` (`/users`, `/media/abc`).
52
54
  * The base is the transport's concern — a route group that embeds a base is not portable to
53
55
  * a host that mounts the admin API somewhere else.
54
- * @param init Standard `RequestInit`. A transport that is not HTTP-backed may ignore fields it
55
- * cannot honour, but must respect `method` and `body`.
56
+ * @param init Standard `RequestInit` plus an optional success observer. A transport that is not
57
+ * HTTP-backed may ignore fields it cannot honour, but must respect `method` and `body`.
56
58
  * @throws {AdminApiError} on any non-success response.
57
59
  */
58
- request<T>(path: string, init?: RequestInit): Promise<T>;
60
+ request<T>(requiredArgs: {
61
+ readonly path: string;
62
+ }, optionalArgs?: RequestInit & {
63
+ /** Called only on success with the response metadata, after parsing and before resolving.
64
+ * Reading ETag here lets route adapters send If-Match without duplicating transport logic. */
65
+ readonly onOk?: (args: {
66
+ readonly response: Response;
67
+ }) => void;
68
+ }): Promise<T>;
59
69
  }
60
70
  /**
61
71
  * A route group: turns a transport into a typed set of operations.
62
72
  *
63
- * Every port implementation in this package is one of these, and a host's own route groups use
64
- * the identical signature — that symmetry is what lets Jini-shipped and host-owned groups sit in
73
+ * Host implementations of this package's ports and a host's own route groups use
74
+ * the identical signature — that symmetry is what lets shared and host-owned groups sit in
65
75
  * the same client with no adapter between them.
66
76
  */
67
- export type AdminRouteGroupFactory<TPort> = (transport: AdminTransport) => TPort;
77
+ export type AdminRouteGroupFactory<TPort> = (requiredArgs: {
78
+ readonly transport: AdminTransport;
79
+ }) => TPort;
68
80
  /**
69
81
  * The assembled client: one object carrying every wired route group.
70
82
  *
71
83
  * `TGroups` is inferred from the factory map handed to `createAdminClient`, so a host's own
72
- * groups are as type-safe as the shipped ones with no declaration merging or module augmentation.
84
+ * groups are as type-safe as shared ones with no declaration merging or module augmentation.
73
85
  */
74
86
  export type AdminClient<TGroups extends Record<string, AdminRouteGroupFactory<unknown>>> = {
75
87
  readonly [K in keyof TGroups]: ReturnType<TGroups[K]>;
@@ -13,23 +13,25 @@
13
13
  * factory over it — makes both work:
14
14
  *
15
15
  * ```ts
16
- * const transport = createHttpTransport({ baseUrl: '/api/admin/v1' });
16
+ * const transport = createHttpTransport({ baseUrl: '/api/admin/v1', fetch: ({ url }, init) => fetch(url, init) });
17
17
  *
18
- * const client = createAdminClient(transport, {
19
- * identity: createIdentityRoutes, // shipped by @jini-ai/admin
20
- * media: createMediaRoutes, // shipped by @jini-ai/admin
18
+ * const client = createAdminClient({ transport, groups: {
19
+ * identity: createHostIdentityRoutes, // host implementation of AdminIdentityPort
20
+ * media: createHostMediaRoutes, // host implementation of AdminMediaPort
21
21
  * posts: createHostPostRoutes, // the host's own, same signature
22
22
  * widgets: createHostWidgetRoutes, // the host's own
23
- * });
23
+ * } });
24
24
  *
25
- * await client.identity.listUsers(); // Jini's
26
- * await client.posts.list(); // the host's
25
+ * await client.identity.listUsers({}); // host adapter over Jini's port contract
26
+ * await client.posts.list({}); // the host's own port
27
27
  * ```
28
28
  *
29
29
  * Both halves share one auth policy, one error class, one place to add retries or tracing. A host
30
30
  * adding a route is a factory plus one line in the map — it never edits a Jini file, and it never
31
31
  * grows a second `fetch` wrapper with its own subtly different error handling, which is exactly
32
32
  * how `api.ts` reached 1,548 lines.
33
+ * This package ships port contracts and the assembler, not backend route factories;
34
+ * the host supplies each factory and may share it with other hosts without changing this seam.
33
35
  *
34
36
  * ## Why `AdminTransport` is an interface and not just a function
35
37
  *
@@ -0,0 +1,27 @@
1
+ import type { MediaApiPort } from '../ports.js';
2
+ import type { MediaRequestOptions } from '../models.js';
3
+ /** Authentication, workspace addressing, transport retries and error decoding belong to the host.
4
+ * Retryable errors remain intact so the library can recover automatically after an outage.
5
+ * The adapter never reaches for global fetch, tokens or a hardcoded workspace. */
6
+ export interface MediaTransportPort {
7
+ request<T>(required: {
8
+ path: string;
9
+ method: 'GET' | 'POST' | 'PATCH' | 'DELETE';
10
+ body?: unknown;
11
+ }, optional?: MediaRequestOptions): Promise<T>;
12
+ url(required: {
13
+ path: string;
14
+ }, optional?: Record<string, never>): string;
15
+ }
16
+ export declare function createHttpMediaApi({ transport, basePath }: {
17
+ transport: MediaTransportPort;
18
+ basePath: string;
19
+ }, { replacePath, restorePath, }?: {
20
+ replacePath?: (required: {
21
+ id: string;
22
+ }, optional?: Record<string, never>) => string;
23
+ restorePath?: (required: {
24
+ id: string;
25
+ }, optional?: Record<string, never>) => string;
26
+ }): MediaApiPort;
27
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1,44 @@
1
+ import { queryMedia } from '../rules.js';
2
+ export function createHttpMediaApi({ transport, basePath }, { replacePath, restorePath, } = {}) {
3
+ const base = basePath.replace(/\/$/, '');
4
+ const path = (id) => `${base}/${encodeURIComponent(id)}`;
5
+ async function asset(method, target, body, options) {
6
+ options.signal?.throwIfAborted();
7
+ const response = await transport.request({ path: target, method, ...(body === undefined ? {} : { body }) }, options);
8
+ return response.media;
9
+ }
10
+ return {
11
+ replaceSupported: !!replacePath,
12
+ restoreSupported: !!restorePath,
13
+ async list(query, options = {}) {
14
+ options.signal?.throwIfAborted();
15
+ const response = await transport.request({ path: base, method: 'GET' }, options);
16
+ return queryMedia({ media: response.media, query });
17
+ },
18
+ upload(input, { signal, ...metadata } = {}) {
19
+ return asset('POST', base, { ...input, ...metadata }, signal ? { signal } : {});
20
+ },
21
+ update({ id, patch }, options = {}) {
22
+ return asset('PATCH', path(id), patch, options);
23
+ },
24
+ ...(replacePath ? { replace({ id, upload }, options = {}) {
25
+ return asset('POST', replacePath({ id }), upload, options);
26
+ } } : {}),
27
+ trash({ id }, options = {}) {
28
+ return asset('POST', `${path(id)}/trash`, undefined, options);
29
+ },
30
+ ...(restorePath ? { restore({ id }, options = {}) {
31
+ return asset('POST', restorePath({ id }), undefined, options);
32
+ } } : {}),
33
+ delete({ id }, options = {}) {
34
+ options.signal?.throwIfAborted();
35
+ return transport.request({ path: path(id), method: 'DELETE' }, options);
36
+ },
37
+ originalUrl({ id, version }, _optional = {}) {
38
+ // Replacement preserves the id; the refreshed version must identify its new bytes.
39
+ const revision = version === undefined ? '' : `?v=${encodeURIComponent(version)}`;
40
+ return transport.url({ path: `${path(id)}/original${revision}` });
41
+ },
42
+ };
43
+ }
44
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1,11 @@
1
+ import type { MediaApiPort, MediaProvidersPort } from '../ports.js';
2
+ import type { MediaAsset, MediaProvider } from '../models.js';
3
+ export declare function createMemoryMediaApi({ assets }: {
4
+ assets?: readonly MediaAsset[];
5
+ }, { now }?: {
6
+ now?: () => string;
7
+ }): MediaApiPort;
8
+ export declare function createMemoryMediaProviders({ providers }: {
9
+ providers?: readonly MediaProvider[];
10
+ }, _optional?: Record<string, never>): MediaProvidersPort;
11
+ //# sourceMappingURL=memory.d.ts.map
@@ -0,0 +1,139 @@
1
+ import { queryMedia } from '../rules.js';
2
+ export function createMemoryMediaApi({ assets = [] }, { now = () => new Date().toISOString() } = {}) {
3
+ const items = new Map(assets.map((item) => [item.id, Object.freeze({ ...item })]));
4
+ let sequence = 0;
5
+ function check(options) {
6
+ options.signal?.throwIfAborted();
7
+ }
8
+ function get(id) {
9
+ const item = items.get(id);
10
+ if (!item)
11
+ throw new Error(`Media not found: ${id}`);
12
+ return item;
13
+ }
14
+ function put(item) {
15
+ const frozen = Object.freeze(item);
16
+ items.set(item.id, frozen);
17
+ return frozen;
18
+ }
19
+ function content(input) {
20
+ // The REAL server ignores the declared string and stores sniffContentType(bytes) instead.
21
+ // This fake has no bytes to sniff — its input is an opaque base64 string a test made up —
22
+ // so it echoes what the caller declared. Tests needing disagreement must seed assets directly.
23
+ return { byteSize: Math.max(0, Math.floor(input.dataBase64.length * 3 / 4) - (input.dataBase64.match(/=+$/)?.[0].length ?? 0)), contentType: input.contentType, sha256: `memory-content-${++sequence}` };
24
+ }
25
+ return {
26
+ replaceSupported: true,
27
+ restoreSupported: true,
28
+ async list(query, options = {}) {
29
+ check(options);
30
+ return Object.freeze(queryMedia({ media: [...items.values()], query }));
31
+ },
32
+ async upload(input, options = {}) {
33
+ check(options);
34
+ let id;
35
+ do {
36
+ id = `memory-${++sequence}`;
37
+ } while (items.has(id));
38
+ const timestamp = now();
39
+ return put({
40
+ id,
41
+ title: options.title ?? input.filename,
42
+ alt: options.alt ?? '',
43
+ caption: options.caption ?? '',
44
+ credit: options.credit ?? '',
45
+ slug: options.slug ?? id,
46
+ width: options.width ?? null,
47
+ height: options.height ?? null,
48
+ cssClass: options.cssClass ?? null,
49
+ htmlAttributes: options.htmlAttributes ?? null,
50
+ status: 'active',
51
+ publicUrl: null,
52
+ createdAt: timestamp,
53
+ updatedAt: timestamp,
54
+ version: 1,
55
+ ...content(input),
56
+ });
57
+ },
58
+ async update({ id, patch }, options = {}) {
59
+ check(options);
60
+ const previous = get(id);
61
+ const metadata = Object.fromEntries(Object.entries(patch).filter(([key, value]) => ['title', 'slug', 'alt', 'caption', 'credit', 'width', 'height', 'cssClass', 'htmlAttributes'].includes(key) && value !== undefined));
62
+ return put({ ...previous, ...metadata, version: previous.version + 1, updatedAt: now() });
63
+ },
64
+ async replace({ id, upload }, options = {}) {
65
+ check(options);
66
+ const previous = get(id);
67
+ if (previous.status !== 'active')
68
+ throw new Error('Cannot replace trashed media');
69
+ return put({
70
+ ...previous,
71
+ ...content(upload),
72
+ version: previous.version + 1,
73
+ updatedAt: now(),
74
+ });
75
+ },
76
+ async trash({ id }, options = {}) {
77
+ check(options);
78
+ const previous = get(id);
79
+ return put({
80
+ ...previous,
81
+ status: 'trashed',
82
+ publicUrl: null,
83
+ version: previous.version + 1,
84
+ updatedAt: now(),
85
+ });
86
+ },
87
+ async restore({ id }, options = {}) {
88
+ check(options);
89
+ const previous = get(id);
90
+ return put({ ...previous, status: 'active', version: previous.version + 1, updatedAt: now() });
91
+ },
92
+ async delete({ id }, options = {}) {
93
+ check(options);
94
+ if (get(id).status !== 'trashed')
95
+ throw new Error('Trash media before permanent deletion');
96
+ items.delete(id);
97
+ return { purged: true };
98
+ },
99
+ // Distinct memory:// scheme so a URL assertion fails if the consumer calls a real client.
100
+ originalUrl: ({ id }) => `memory://media-original/${encodeURIComponent(id)}`,
101
+ };
102
+ }
103
+ export function createMemoryMediaProviders({ providers = [] }, _optional = {}) {
104
+ const items = new Map(providers.map((item) => [item.id, Object.freeze({ ...item })]));
105
+ function write(id, configured) {
106
+ const previous = items.get(id);
107
+ if (!previous)
108
+ throw new Error('Unknown provider');
109
+ const next = Object.freeze({ ...previous, configured });
110
+ items.set(id, next);
111
+ return next;
112
+ }
113
+ return {
114
+ async list(_required, options = {}) {
115
+ options.signal?.throwIfAborted();
116
+ return Object.freeze([...items.values()]);
117
+ },
118
+ async saveSettings({ id, baseUrl, model }, options = {}) {
119
+ options.signal?.throwIfAborted();
120
+ const previous = items.get(id);
121
+ if (!previous)
122
+ throw new Error('Unknown provider');
123
+ const next = Object.freeze({ ...previous, baseUrl, model });
124
+ items.set(id, next);
125
+ return next;
126
+ },
127
+ async saveCredential({ id, credential }, options = {}) {
128
+ options.signal?.throwIfAborted();
129
+ if (!credential.trim())
130
+ throw new Error('Credential is required');
131
+ return write(id, true);
132
+ },
133
+ async removeCredential({ id }, options = {}) {
134
+ options.signal?.throwIfAborted();
135
+ return write(id, false);
136
+ },
137
+ };
138
+ }
139
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1,7 @@
1
+ import type { MediaApiPort } from '../ports.js';
2
+ /** Destructive checklist: run only against an isolated backend with no production data.
3
+ * Returns named checks and throws at the first failed contract. No test-framework dependency. */
4
+ export declare function runMediaApiConformance({ api }: {
5
+ api: MediaApiPort;
6
+ }, _optional?: Record<string, never>): Promise<readonly string[]>;
7
+ //# sourceMappingURL=media-api.conformance.d.ts.map
@@ -0,0 +1,69 @@
1
+ import { canReplaceMedia } from '../rules.js';
2
+ /** Destructive checklist: run only against an isolated backend with no production data.
3
+ * Returns named checks and throws at the first failed contract. No test-framework dependency. */
4
+ export async function runMediaApiConformance({ api }, _optional = {}) {
5
+ const checks = [];
6
+ function assert(condition, name) {
7
+ if (!condition)
8
+ throw new Error(`Media API conformance: ${name}`);
9
+ checks.push(name);
10
+ }
11
+ async function rejects(run, name) {
12
+ let rejected = false;
13
+ try {
14
+ await run();
15
+ }
16
+ catch {
17
+ rejected = true;
18
+ }
19
+ assert(rejected, name);
20
+ }
21
+ const input = {
22
+ filename: 'conformance.png',
23
+ contentType: 'image/png',
24
+ dataBase64: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aT1sAAAAASUVORK5CYII=',
25
+ };
26
+ const created = await api.upload(input, { alt: 'initial alt' });
27
+ assert(created.id.length > 0 && created.status === 'active', 'upload creates an active asset');
28
+ try {
29
+ const before = await api.list({});
30
+ assert(before.some((item) => item.id === created.id), 'list contains upload');
31
+ const updated = await api.update({ id: created.id, patch: { title: 'renamed' } });
32
+ assert(updated.title === 'renamed' && updated.alt === 'initial alt', 'partial metadata patch preserves untouched fields');
33
+ assert(created.title === 'conformance.png', 'prior snapshots do not mutate');
34
+ const images = await api.list({ filter: 'images' });
35
+ assert(images.every((item) => item.contentType?.startsWith('image/')), 'image filter');
36
+ assert(typeof api.originalUrl({ id: created.id }) === 'string', 'original URL is synchronous');
37
+ await rejects(() => api.delete({ id: created.id }), 'active asset cannot be purged');
38
+ if (canReplaceMedia({ api })) {
39
+ const replaced = await api.replace({
40
+ id: created.id,
41
+ upload: { ...input, filename: 'replacement.png' },
42
+ });
43
+ assert(replaced.id === created.id &&
44
+ replaced.alt === updated.alt &&
45
+ replaced.title === updated.title, 'replace retains identity and metadata');
46
+ }
47
+ else {
48
+ assert(api.replace === undefined || api.replaceSupported === false, 'replacement is optional and unavailable');
49
+ }
50
+ const abort = new AbortController();
51
+ abort.abort();
52
+ await rejects(() => api.list({}, { signal: abort.signal }), 'aborted list rejects');
53
+ await rejects(() => api.update({ id: created.id, patch: { title: 'must not save' } }, { signal: abort.signal }), 'aborted write rejects');
54
+ assert((await api.list({})).find((item) => item.id === created.id)?.title === 'renamed', 'aborted write has no effects');
55
+ const trashed = await api.trash({ id: created.id });
56
+ assert(trashed.status === 'trashed', 'trash is a distinct state');
57
+ assert((await api.delete({ id: created.id })).purged, 'trashed asset can be purged');
58
+ assert(!(await api.list({})).some((item) => item.id === created.id), 'purge removes asset');
59
+ await rejects(() => api.update({ id: created.id, patch: {} }), 'missing asset rejects');
60
+ return Object.freeze(checks);
61
+ }
62
+ finally {
63
+ if ((await api.list({})).some((item) => item.id === created.id)) {
64
+ await api.trash({ id: created.id });
65
+ await api.delete({ id: created.id });
66
+ }
67
+ }
68
+ }
69
+ //# sourceMappingURL=media-api.conformance.js.map
@@ -0,0 +1,24 @@
1
+ import type { MediaApiPort } from '../ports.js';
2
+ import type { MediaAsset, MediaMetadataPatch, UploadInput } from '../models.js';
3
+ export declare function createEditMediaController({ api, item }: {
4
+ api: MediaApiPort;
5
+ item: MediaAsset;
6
+ }, _optional?: Record<string, never>): {
7
+ getSnapshot: () => Readonly<{
8
+ draft: MediaMetadataPatch;
9
+ saving: boolean;
10
+ error: string | null;
11
+ }>;
12
+ subscribe: ({ listener }: {
13
+ listener: () => void;
14
+ }) => () => void;
15
+ dispose: () => void;
16
+ setDraft({ patch }: {
17
+ patch: MediaMetadataPatch;
18
+ }, _optional?: Record<string, never>): void;
19
+ save(_required?: Record<string, never>, _optional?: Record<string, never>): Promise<boolean>;
20
+ replace({ upload }: {
21
+ upload: UploadInput;
22
+ }, _optional?: Record<string, never>): Promise<boolean>;
23
+ };
24
+ //# sourceMappingURL=edit-media.controller.d.ts.map
@@ -0,0 +1,69 @@
1
+ import { createControllerStore } from '../../core/module/controller-store.js';
2
+ import { diffMediaMetadata, canReplaceMedia } from '../rules.js';
3
+ export function createEditMediaController({ api, item }, _optional = {}) {
4
+ // Frozen at mount, exactly like draft's initializer — stays the value draft was seeded
5
+ // from even as the live list advances underneath it. Never diff against a drifting item.
6
+ let baseline = Object.freeze({ ...item });
7
+ const store = createControllerStore({
8
+ initial: {
9
+ draft: metadataDraft({ item }),
10
+ saving: false,
11
+ error: null,
12
+ },
13
+ });
14
+ async function perform(run) {
15
+ if (store.signal.aborted || store.getSnapshot().saving)
16
+ return false;
17
+ store.set({ patch: { saving: true, error: null } });
18
+ try {
19
+ const saved = await run();
20
+ if (store.signal.aborted)
21
+ return false;
22
+ baseline = saved;
23
+ store.set({
24
+ patch: {
25
+ draft: metadataDraft({ item: baseline }),
26
+ },
27
+ });
28
+ return true;
29
+ }
30
+ catch (error) {
31
+ store.set({
32
+ patch: { error: error instanceof Error ? error.message : 'Failed to save media metadata' },
33
+ });
34
+ return false;
35
+ }
36
+ finally {
37
+ store.set({ patch: { saving: false } });
38
+ }
39
+ }
40
+ return {
41
+ getSnapshot: store.getSnapshot,
42
+ subscribe: store.subscribe,
43
+ dispose: store.dispose,
44
+ setDraft({ patch }, _optional = {}) {
45
+ if (!store.getSnapshot().saving)
46
+ store.set({ patch: { draft: { ...store.getSnapshot().draft, ...patch } } });
47
+ },
48
+ async save(_required = {}, _optional = {}) {
49
+ const patch = diffMediaMetadata({ item: baseline, draft: store.getSnapshot().draft });
50
+ if (Object.keys(patch).length === 0)
51
+ return true;
52
+ return perform(() => api.update({ id: item.id, patch }, { signal: store.signal }));
53
+ },
54
+ async replace({ upload }, _optional = {}) {
55
+ if (!canReplaceMedia({ api })) {
56
+ store.set({ patch: { error: 'File replacement is unavailable' } });
57
+ return false;
58
+ }
59
+ return perform(() => api.replace({ id: item.id, upload }, { signal: store.signal }));
60
+ },
61
+ };
62
+ }
63
+ /** Do not invent CMS fields for hosts which omit them: undefined is an untouched field. */
64
+ function metadataDraft({ item }) {
65
+ return Object.fromEntries(Object.entries({ title: item.title, slug: item.slug, alt: item.alt,
66
+ caption: item.caption, credit: item.credit, width: item.width, height: item.height,
67
+ cssClass: item.cssClass, htmlAttributes: item.htmlAttributes }).filter(([, value]) => value !== undefined));
68
+ }
69
+ //# sourceMappingURL=edit-media.controller.js.map