@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.
- package/CHANGELOG.md +64 -0
- package/README.md +71 -37
- package/dist/agent-plugins/adapters/http.d.ts +7 -0
- package/dist/agent-plugins/adapters/http.js +15 -0
- package/dist/agent-plugins/adapters/memory.d.ts +8 -0
- package/dist/agent-plugins/adapters/memory.js +25 -0
- package/dist/agent-plugins/agent-plugins.module.d.ts +51 -0
- package/dist/agent-plugins/agent-plugins.module.js +6 -0
- package/dist/agent-plugins/conformance/agent-plugins-api.conformance.d.ts +12 -0
- package/dist/agent-plugins/conformance/agent-plugins-api.conformance.js +47 -0
- package/dist/agent-plugins/controllers/agent-plugin-files.controller.d.ts +20 -0
- package/dist/agent-plugins/controllers/agent-plugin-files.controller.js +43 -0
- package/dist/agent-plugins/controllers/agent-plugins.controller.d.ts +25 -0
- package/dist/agent-plugins/controllers/agent-plugins.controller.js +91 -0
- package/dist/agent-plugins/index.d.ts +9 -0
- package/dist/agent-plugins/index.js +9 -0
- package/dist/agent-plugins/messages.en.d.ts +23 -0
- package/dist/agent-plugins/messages.en.js +14 -0
- package/dist/agent-plugins/models.d.ts +57 -0
- package/dist/agent-plugins/models.js +2 -0
- package/dist/agent-plugins/package-file-tree.d.ts +111 -0
- package/dist/agent-plugins/package-file-tree.js +188 -0
- package/dist/agent-plugins/ports.d.ts +18 -0
- package/dist/agent-plugins/ports.js +3 -0
- package/dist/agent-plugins/react/components/AgentPluginFileContent.d.ts +5 -0
- package/dist/agent-plugins/react/components/AgentPluginFileContent.js +9 -0
- package/dist/agent-plugins/react/components/AgentPluginFileTree.d.ts +9 -0
- package/dist/agent-plugins/react/components/AgentPluginFileTree.js +7 -0
- package/dist/agent-plugins/react/components/AgentPluginInspector.d.ts +3 -0
- package/dist/agent-plugins/react/components/AgentPluginInspector.js +11 -0
- package/dist/agent-plugins/react/components/AgentPluginListPanel.d.ts +6 -0
- package/dist/agent-plugins/react/components/AgentPluginListPanel.js +10 -0
- package/dist/agent-plugins/react/components/AgentPluginRow.d.ts +5 -0
- package/dist/agent-plugins/react/components/AgentPluginRow.js +8 -0
- package/dist/agent-plugins/react/hooks/AgentPluginInspector.hooks.d.ts +90 -0
- package/dist/agent-plugins/react/hooks/AgentPluginInspector.hooks.js +74 -0
- package/dist/agent-plugins/react/hooks/AgentPluginPanel.hooks.d.ts +67 -0
- package/dist/agent-plugins/react/hooks/AgentPluginPanel.hooks.js +50 -0
- package/dist/agent-plugins/react/hooks/AgentPluginRow.hooks.d.ts +55 -0
- package/dist/agent-plugins/react/hooks/AgentPluginRow.hooks.js +6 -0
- package/dist/agent-plugins/react/hooks/AgentPluginsPage.hooks.d.ts +64 -0
- package/dist/agent-plugins/react/hooks/AgentPluginsPage.hooks.js +39 -0
- package/dist/agent-plugins/react/hooks/AgentPluginsPorts.hooks.d.ts +7 -0
- package/dist/agent-plugins/react/hooks/AgentPluginsPorts.hooks.js +10 -0
- package/dist/agent-plugins/react/hooks/MarketplaceTab.hooks.d.ts +6 -0
- package/dist/agent-plugins/react/hooks/MarketplaceTab.hooks.js +3 -0
- package/dist/agent-plugins/react/index.d.ts +245 -0
- package/dist/agent-plugins/react/index.js +13 -0
- package/dist/agent-plugins/react/pages/AgentPluginsPage.d.ts +4 -0
- package/dist/agent-plugins/react/pages/AgentPluginsPage.js +12 -0
- package/dist/agent-plugins/react/tabs/DownloadedTab.d.ts +4 -0
- package/dist/agent-plugins/react/tabs/DownloadedTab.js +5 -0
- package/dist/agent-plugins/react/tabs/InstalledTab.d.ts +4 -0
- package/dist/agent-plugins/react/tabs/InstalledTab.js +5 -0
- package/dist/agent-plugins/react/tabs/MarketplaceTab.d.ts +4 -0
- package/dist/agent-plugins/react/tabs/MarketplaceTab.js +10 -0
- package/dist/agent-plugins/rules.d.ts +33 -0
- package/dist/agent-plugins/rules.js +51 -0
- package/dist/browser/index.d.ts +1 -0
- package/dist/browser/index.js +1 -0
- package/dist/browser/navigation.d.ts +19 -4
- package/dist/browser/navigation.js +8 -8
- package/dist/browser/shell-navigation.d.ts +12 -0
- package/dist/browser/shell-navigation.js +16 -0
- package/dist/contracts/credential-management.d.ts +8 -0
- package/dist/contracts/credential-management.js +3 -0
- package/dist/contracts/media-picker.d.ts +17 -0
- package/dist/contracts/media-picker.js +5 -0
- package/dist/contracts/playground-render-target.d.ts +18 -0
- package/dist/contracts/playground-render-target.js +3 -0
- package/dist/core/entities/rules.d.ts +138 -0
- package/dist/core/entities/rules.js +344 -0
- package/dist/core/gated/types.d.ts +8 -4
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.js +1 -0
- package/dist/core/manifest/rules.d.ts +24 -6
- package/dist/core/manifest/rules.js +12 -11
- package/dist/core/module/controller-store.d.ts +22 -0
- package/dist/core/module/controller-store.js +32 -0
- package/dist/core/module/create-admin.d.ts +41 -0
- package/dist/core/module/create-admin.js +187 -0
- package/dist/core/module/define-admin-module.d.ts +4 -0
- package/dist/core/module/define-admin-module.js +5 -0
- package/dist/core/module/index.d.ts +7 -0
- package/dist/core/module/index.js +4 -0
- package/dist/core/module/token.d.ts +19 -0
- package/dist/core/module/token.js +4 -0
- package/dist/core/module/types.d.ts +79 -0
- package/dist/core/module/types.js +2 -0
- package/dist/core/permissions/rules.d.ts +4 -1
- package/dist/core/permissions/rules.js +1 -1
- package/dist/core/ports/analytics.d.ts +1 -1
- package/dist/core/ports/auth.d.ts +2 -2
- package/dist/core/ports/comments.d.ts +9 -6
- package/dist/core/ports/database.d.ts +13 -10
- package/dist/core/ports/entities.d.ts +302 -0
- package/dist/core/ports/entities.js +47 -0
- package/dist/core/ports/forms.d.ts +19 -7
- package/dist/core/ports/identity.d.ts +42 -17
- package/dist/core/ports/index.d.ts +2 -0
- package/dist/core/ports/integrations.d.ts +9 -4
- package/dist/core/ports/media.d.ts +16 -7
- package/dist/core/ports/members.d.ts +7 -3
- package/dist/core/ports/menus.d.ts +46 -0
- package/dist/core/ports/menus.js +43 -0
- package/dist/core/ports/plugins.d.ts +3 -2
- package/dist/core/ports/recovery.d.ts +10 -6
- package/dist/core/ports/redirects.d.ts +17 -7
- package/dist/core/ports/seo.d.ts +12 -6
- package/dist/core/ports/shell.d.ts +41 -0
- package/dist/core/ports/shell.js +2 -0
- package/dist/core/ports/workspace.d.ts +3 -3
- package/dist/core/routing/rules.d.ts +17 -4
- package/dist/core/routing/rules.js +4 -4
- package/dist/core/transport/errors.d.ts +13 -4
- package/dist/core/transport/errors.js +4 -4
- package/dist/core/transport/http.d.ts +26 -16
- package/dist/core/transport/http.js +59 -21
- package/dist/core/transport/types.d.ts +26 -14
- package/dist/core/transport/types.js +9 -7
- package/dist/media/adapters/http.d.ts +27 -0
- package/dist/media/adapters/http.js +44 -0
- package/dist/media/adapters/memory.d.ts +11 -0
- package/dist/media/adapters/memory.js +139 -0
- package/dist/media/conformance/media-api.conformance.d.ts +7 -0
- package/dist/media/conformance/media-api.conformance.js +69 -0
- package/dist/media/controllers/edit-media.controller.d.ts +24 -0
- package/dist/media/controllers/edit-media.controller.js +69 -0
- package/dist/media/controllers/library.controller.d.ts +47 -0
- package/dist/media/controllers/library.controller.js +173 -0
- package/dist/media/controllers/providers.controller.d.ts +30 -0
- package/dist/media/controllers/providers.controller.js +76 -0
- package/dist/media/html-attributes.d.ts +82 -0
- package/dist/media/html-attributes.js +204 -0
- package/dist/media/index.d.ts +12 -0
- package/dist/media/index.js +9 -0
- package/dist/media/media.module.d.ts +83 -0
- package/dist/media/media.module.js +43 -0
- package/dist/media/messages.en.d.ts +24 -0
- package/dist/media/messages.en.js +24 -0
- package/dist/media/models.d.ts +72 -0
- package/dist/media/models.js +2 -0
- package/dist/media/ports.d.ts +75 -0
- package/dist/media/ports.js +7 -0
- package/dist/media/react/components/EditMediaPanel.d.ts +3 -0
- package/dist/media/react/components/EditMediaPanel.js +10 -0
- package/dist/media/react/components/MediaCard.d.ts +5 -0
- package/dist/media/react/components/MediaCard.js +17 -0
- package/dist/media/react/components/MediaDialog.d.ts +3 -0
- package/dist/media/react/components/MediaDialog.js +8 -0
- package/dist/media/react/components/MediaGrid.d.ts +4 -0
- package/dist/media/react/components/MediaGrid.js +8 -0
- package/dist/media/react/components/MediaIcons.d.ts +14 -0
- package/dist/media/react/components/MediaIcons.js +39 -0
- package/dist/media/react/components/MediaLightbox.d.ts +5 -0
- package/dist/media/react/components/MediaLightbox.js +12 -0
- package/dist/media/react/components/MediaPicker.d.ts +3 -0
- package/dist/media/react/components/MediaPicker.js +10 -0
- package/dist/media/react/components/MediaPurgeDialog.d.ts +4 -0
- package/dist/media/react/components/MediaPurgeDialog.js +9 -0
- package/dist/media/react/components/UploadButton.d.ts +3 -0
- package/dist/media/react/components/UploadButton.js +8 -0
- package/dist/media/react/hooks/EditMediaPanel.hooks.d.ts +84 -0
- package/dist/media/react/hooks/EditMediaPanel.hooks.js +105 -0
- package/dist/media/react/hooks/LibraryTab.hooks.d.ts +103 -0
- package/dist/media/react/hooks/LibraryTab.hooks.js +164 -0
- package/dist/media/react/hooks/MediaCard.hooks.d.ts +83 -0
- package/dist/media/react/hooks/MediaCard.hooks.js +66 -0
- package/dist/media/react/hooks/MediaDialog.hooks.d.ts +10 -0
- package/dist/media/react/hooks/MediaDialog.hooks.js +20 -0
- package/dist/media/react/hooks/MediaGrid.hooks.d.ts +29 -0
- package/dist/media/react/hooks/MediaGrid.hooks.js +6 -0
- package/dist/media/react/hooks/MediaLightbox.hooks.d.ts +27 -0
- package/dist/media/react/hooks/MediaLightbox.hooks.js +64 -0
- package/dist/media/react/hooks/MediaPage.hooks.d.ts +68 -0
- package/dist/media/react/hooks/MediaPage.hooks.js +68 -0
- package/dist/media/react/hooks/MediaPicker.hooks.d.ts +26 -0
- package/dist/media/react/hooks/MediaPicker.hooks.js +25 -0
- package/dist/media/react/hooks/MediaPorts.hooks.d.ts +9 -0
- package/dist/media/react/hooks/MediaPorts.hooks.js +10 -0
- package/dist/media/react/hooks/MediaPurgeDialog.hooks.d.ts +22 -0
- package/dist/media/react/hooks/MediaPurgeDialog.hooks.js +6 -0
- package/dist/media/react/hooks/ProvidersTab.hooks.d.ts +55 -0
- package/dist/media/react/hooks/ProvidersTab.hooks.js +124 -0
- package/dist/media/react/hooks/UploadButton.hooks.d.ts +48 -0
- package/dist/media/react/hooks/UploadButton.hooks.js +118 -0
- package/dist/media/react/index.d.ts +373 -0
- package/dist/media/react/index.js +73 -0
- package/dist/media/react/pages/MediaPage.d.ts +4 -0
- package/dist/media/react/pages/MediaPage.js +9 -0
- package/dist/media/react/tabs/LibraryTab.d.ts +4 -0
- package/dist/media/react/tabs/LibraryTab.js +14 -0
- package/dist/media/react/tabs/ProvidersTab.d.ts +4 -0
- package/dist/media/react/tabs/ProvidersTab.js +10 -0
- package/dist/media/rules.d.ts +86 -0
- package/dist/media/rules.js +134 -0
- package/dist/media/styles.d.ts +4 -0
- package/dist/media/styles.js +5 -0
- package/dist/playground/adapters/http.d.ts +8 -0
- package/dist/playground/adapters/http.js +4 -0
- package/dist/playground/adapters/memory.d.ts +10 -0
- package/dist/playground/adapters/memory.js +30 -0
- package/dist/playground/conformance/playground-api.conformance.d.ts +6 -0
- package/dist/playground/conformance/playground-api.conformance.js +37 -0
- package/dist/playground/controllers/playground.controller.d.ts +16 -0
- package/dist/playground/controllers/playground.controller.js +21 -0
- package/dist/playground/index.d.ts +7 -0
- package/dist/playground/index.js +5 -0
- package/dist/playground/messages.en.d.ts +9 -0
- package/dist/playground/messages.en.js +2 -0
- package/dist/playground/models.d.ts +4 -0
- package/dist/playground/models.js +2 -0
- package/dist/playground/playground.module.d.ts +31 -0
- package/dist/playground/playground.module.js +5 -0
- package/dist/playground/ports.d.ts +5 -0
- package/dist/playground/ports.js +2 -0
- package/dist/playground/react/components/Canvas.d.ts +3 -0
- package/dist/playground/react/components/Canvas.js +9 -0
- package/dist/playground/react/hooks/Canvas.hooks.d.ts +7 -0
- package/dist/playground/react/hooks/Canvas.hooks.js +18 -0
- package/dist/playground/react/hooks/PlaygroundPage.hooks.d.ts +8 -0
- package/dist/playground/react/hooks/PlaygroundPage.hooks.js +2 -0
- package/dist/playground/react/hooks/PlaygroundPorts.hooks.d.ts +4 -0
- package/dist/playground/react/hooks/PlaygroundPorts.hooks.js +6 -0
- package/dist/playground/react/index.d.ts +136 -0
- package/dist/playground/react/index.js +10 -0
- package/dist/playground/react/pages/PlaygroundPage.d.ts +4 -0
- package/dist/playground/react/pages/PlaygroundPage.js +11 -0
- package/dist/playground/react/tabs/CanvasTab.d.ts +4 -0
- package/dist/playground/react/tabs/CanvasTab.js +5 -0
- package/dist/playground/rules.d.ts +4 -0
- package/dist/playground/rules.js +2 -0
- package/dist/react/OverlayHost.d.ts +3 -0
- package/dist/react/OverlayHost.hooks.d.ts +9 -0
- package/dist/react/OverlayHost.hooks.js +9 -0
- package/dist/react/OverlayHost.js +7 -0
- package/dist/react/bind-react.d.ts +46 -0
- package/dist/react/bind-react.js +28 -0
- package/dist/react/components/ConfirmButton/ConfirmButton.hooks.d.ts +7 -1
- package/dist/react/components/ConfirmButton/ConfirmButton.hooks.js +2 -2
- package/dist/react/components/ConfirmButton/ConfirmButton.js +1 -1
- package/dist/react/components/ConfirmDialog/ConfirmDialog.hooks.d.ts +17 -3
- package/dist/react/components/ConfirmDialog/ConfirmDialog.hooks.js +3 -3
- package/dist/react/components/ConfirmDialog/ConfirmDialog.js +5 -5
- package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.d.ts +6 -2
- package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.js +7 -8
- package/dist/react/components/RowMenu/RowMenu.d.ts +5 -1
- package/dist/react/components/RowMenu/RowMenu.hooks.d.ts +8 -2
- package/dist/react/components/RowMenu/RowMenu.hooks.js +4 -4
- package/dist/react/components/RowMenu/RowMenu.js +6 -6
- package/dist/react/components/Sidebar.d.ts +1 -1
- package/dist/react/components/Sidebar.js +103 -7
- package/dist/react/entities/EntityCell.d.ts +10 -0
- package/dist/react/entities/EntityCell.js +18 -0
- package/dist/react/entities/EntityDetail.d.ts +3 -0
- package/dist/react/entities/EntityDetail.js +21 -0
- package/dist/react/entities/EntityEdit.d.ts +13 -0
- package/dist/react/entities/EntityEdit.js +45 -0
- package/dist/react/entities/EntityField.d.ts +20 -0
- package/dist/react/entities/EntityField.js +31 -0
- package/dist/react/entities/EntityIndex.d.ts +3 -0
- package/dist/react/entities/EntityIndex.js +10 -0
- package/dist/react/entities/EntityList.d.ts +7 -0
- package/dist/react/entities/EntityList.js +52 -0
- package/dist/react/entities/contribution.d.ts +26 -0
- package/dist/react/entities/contribution.js +78 -0
- package/dist/react/entities/index.d.ts +10 -0
- package/dist/react/entities/index.js +7 -0
- package/dist/react/entities/rules.d.ts +179 -0
- package/dist/react/entities/rules.js +224 -0
- package/dist/react/entities/types.d.ts +45 -0
- package/dist/react/entities/types.js +5 -0
- package/dist/react/entities/use-entity-edit.d.ts +35 -0
- package/dist/react/entities/use-entity-edit.js +98 -0
- package/dist/react/entities/use-entity-read.d.ts +22 -0
- package/dist/react/entities/use-entity-read.js +27 -0
- package/dist/react/hooks/use-nav-sections.d.ts +12 -3
- package/dist/react/hooks/use-nav-sections.js +14 -14
- package/dist/react/hooks/use-sidebar-rail.d.ts +7 -1
- package/dist/react/hooks/use-sidebar-rail.js +11 -11
- package/dist/react/index.d.ts +3 -1
- package/dist/react/index.js +5 -2
- package/dist/react/overlays.d.ts +38 -0
- package/dist/react/overlays.js +46 -0
- package/dist/react/shell/AdminShell.d.ts +9 -0
- package/dist/react/shell/AdminShell.hooks.d.ts +15 -0
- package/dist/react/shell/AdminShell.hooks.js +27 -0
- package/dist/react/shell/AdminShell.js +46 -0
- package/dist/react/shell/index.d.ts +7 -0
- package/dist/react/shell/index.js +5 -0
- package/dist/react/shell/model.d.ts +25 -0
- package/dist/react/shell/model.js +29 -0
- package/dist/react/shell/theme-ports.d.ts +28 -0
- package/dist/react/shell/theme-ports.js +2 -0
- package/dist/react/shell/types.d.ts +99 -0
- package/dist/react/shell/types.js +2 -0
- package/dist/react/shell/use-admin-shell-session.d.ts +13 -0
- package/dist/react/shell/use-admin-shell-session.js +81 -0
- package/dist/react/shell/use-admin-theme.d.ts +16 -0
- package/dist/react/shell/use-admin-theme.js +57 -0
- package/dist/react/types.d.ts +6 -4
- package/dist/react/types.js +2 -2
- package/dist/react/use-controller.d.ts +16 -0
- package/dist/react/use-controller.js +18 -0
- package/dist/security/adapters/http.d.ts +16 -0
- package/dist/security/adapters/http.js +94 -0
- package/dist/security/adapters/memory.d.ts +15 -0
- package/dist/security/adapters/memory.js +60 -0
- package/dist/security/conformance/security-api.conformance.d.ts +10 -0
- package/dist/security/conformance/security-api.conformance.js +48 -0
- package/dist/security/controllers/access-tokens.controller.d.ts +44 -0
- package/dist/security/controllers/access-tokens.controller.js +112 -0
- package/dist/security/controllers/other-credentials.controller.d.ts +30 -0
- package/dist/security/controllers/other-credentials.controller.js +74 -0
- package/dist/security/controllers/root-key.controller.d.ts +35 -0
- package/dist/security/controllers/root-key.controller.js +90 -0
- package/dist/security/index.d.ts +9 -0
- package/dist/security/index.js +9 -0
- package/dist/security/messages.en.d.ts +12 -0
- package/dist/security/messages.en.js +2 -0
- package/dist/security/models.d.ts +112 -0
- package/dist/security/models.js +2 -0
- package/dist/security/ports.d.ts +58 -0
- package/dist/security/ports.js +5 -0
- package/dist/security/react/components/CredentialEditor.d.ts +3 -0
- package/dist/security/react/components/CredentialEditor.js +8 -0
- package/dist/security/react/hooks/AccessTokensTab.hooks.d.ts +94 -0
- package/dist/security/react/hooks/AccessTokensTab.hooks.js +36 -0
- package/dist/security/react/hooks/CredentialEditor.hooks.d.ts +49 -0
- package/dist/security/react/hooks/CredentialEditor.hooks.js +14 -0
- package/dist/security/react/hooks/RootKeyTab.hooks.d.ts +29 -0
- package/dist/security/react/hooks/RootKeyTab.hooks.js +19 -0
- package/dist/security/react/hooks/SecurityPage.hooks.d.ts +17 -0
- package/dist/security/react/hooks/SecurityPage.hooks.js +12 -0
- package/dist/security/react/hooks/SecurityPorts.hooks.d.ts +9 -0
- package/dist/security/react/hooks/SecurityPorts.hooks.js +6 -0
- package/dist/security/react/index.d.ts +215 -0
- package/dist/security/react/index.js +10 -0
- package/dist/security/react/pages/SecurityPage.d.ts +4 -0
- package/dist/security/react/pages/SecurityPage.js +10 -0
- package/dist/security/react/tabs/AccessTokensTab.d.ts +4 -0
- package/dist/security/react/tabs/AccessTokensTab.js +10 -0
- package/dist/security/react/tabs/RootKeyTab.d.ts +4 -0
- package/dist/security/react/tabs/RootKeyTab.js +9 -0
- package/dist/security/rules.d.ts +57 -0
- package/dist/security/rules.js +63 -0
- package/dist/security/security.module.d.ts +45 -0
- package/dist/security/security.module.js +5 -0
- package/dist/source-control/adapters/http.d.ts +6 -0
- package/dist/source-control/adapters/http.js +13 -0
- package/dist/source-control/adapters/memory.d.ts +9 -0
- package/dist/source-control/adapters/memory.js +35 -0
- package/dist/source-control/conformance/source-control-api.conformance.d.ts +10 -0
- package/dist/source-control/conformance/source-control-api.conformance.js +48 -0
- package/dist/source-control/controllers/source-control.controller.d.ts +26 -0
- package/dist/source-control/controllers/source-control.controller.js +70 -0
- package/dist/source-control/index.d.ts +8 -0
- package/dist/source-control/index.js +6 -0
- package/dist/source-control/messages.en.d.ts +11 -0
- package/dist/source-control/messages.en.js +2 -0
- package/dist/source-control/models.d.ts +70 -0
- package/dist/source-control/models.js +2 -0
- package/dist/source-control/ports.d.ts +23 -0
- package/dist/source-control/ports.js +3 -0
- package/dist/source-control/react/components/SourceControlCredentialFields.d.ts +5 -0
- package/dist/source-control/react/components/SourceControlCredentialFields.js +8 -0
- package/dist/source-control/react/components/SourceControlProviderRow.d.ts +5 -0
- package/dist/source-control/react/components/SourceControlProviderRow.js +7 -0
- package/dist/source-control/react/hooks/ProvidersTab.hooks.d.ts +45 -0
- package/dist/source-control/react/hooks/ProvidersTab.hooks.js +20 -0
- package/dist/source-control/react/hooks/SourceControlCredentialFields.hooks.d.ts +43 -0
- package/dist/source-control/react/hooks/SourceControlCredentialFields.hooks.js +9 -0
- package/dist/source-control/react/hooks/SourceControlPage.hooks.d.ts +17 -0
- package/dist/source-control/react/hooks/SourceControlPage.hooks.js +5 -0
- package/dist/source-control/react/hooks/SourceControlPorts.hooks.d.ts +9 -0
- package/dist/source-control/react/hooks/SourceControlPorts.hooks.js +6 -0
- package/dist/source-control/react/index.d.ts +156 -0
- package/dist/source-control/react/index.js +10 -0
- package/dist/source-control/react/pages/SourceControlPage.d.ts +4 -0
- package/dist/source-control/react/pages/SourceControlPage.js +11 -0
- package/dist/source-control/react/tabs/ProvidersTab.d.ts +4 -0
- package/dist/source-control/react/tabs/ProvidersTab.js +10 -0
- package/dist/source-control/rules.d.ts +32 -0
- package/dist/source-control/rules.js +35 -0
- package/dist/source-control/source-control.module.d.ts +36 -0
- package/dist/source-control/source-control.module.js +6 -0
- package/package.json +207 -4
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { AdminEntityDescriptor, AdminEntityRelationField, AdminEntityRowData } from '../../core/ports/entities.js';
|
|
2
|
+
import type { EntityRegistryPort } from './types.js';
|
|
3
|
+
export declare const RELATION_OPTION_LIMIT = 100;
|
|
4
|
+
export declare function resolveEntityPageSize({ raw }: {
|
|
5
|
+
readonly raw: string | null;
|
|
6
|
+
}): number;
|
|
7
|
+
/** Every `relation` field on a descriptor, narrowed so `target` is reachable. */
|
|
8
|
+
export declare function relationFieldsOf({ descriptor }: {
|
|
9
|
+
readonly descriptor: AdminEntityDescriptor;
|
|
10
|
+
}): readonly AdminEntityRelationField[];
|
|
11
|
+
/** One relation target's options: what a picker offers, and what a list cell resolves against. */
|
|
12
|
+
export interface RelationOptions {
|
|
13
|
+
/** Target rows as `{ id, title }`, in the target's own list order. */
|
|
14
|
+
readonly options: readonly {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
readonly title: string;
|
|
17
|
+
}[];
|
|
18
|
+
/** `id` -> title, for O(1) cell rendering. */
|
|
19
|
+
readonly titles: Readonly<Record<string, string>>;
|
|
20
|
+
/** True when the target has more rows than {@link RELATION_OPTION_LIMIT} loaded here. */
|
|
21
|
+
readonly truncated: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** Relation target name -> its loaded options. Keyed by TARGET, not by field, so two fields
|
|
24
|
+
* pointing at the same entity share one load. */
|
|
25
|
+
export type RelationIndex = Readonly<Record<string, RelationOptions>>;
|
|
26
|
+
/**
|
|
27
|
+
* Loads the option set for every distinct relation target on `descriptor`.
|
|
28
|
+
*
|
|
29
|
+
* @param descriptor The entity whose relations need resolving.
|
|
30
|
+
* @param registry Host registry resolving a target's existing entity port.
|
|
31
|
+
* @returns One entry per distinct target. A target that does not resolve is simply absent, and the
|
|
32
|
+
* screens then render the raw stored id marked as unresolved rather than a blank cell.
|
|
33
|
+
* @complexity One `list` call per distinct target, in parallel; O(targets × limit) rows held.
|
|
34
|
+
* @overallScore 100
|
|
35
|
+
*/
|
|
36
|
+
export declare function loadRelationIndex({ descriptor, registry }: {
|
|
37
|
+
readonly descriptor: AdminEntityDescriptor;
|
|
38
|
+
readonly registry: EntityRegistryPort;
|
|
39
|
+
}): Promise<RelationIndex>;
|
|
40
|
+
/**
|
|
41
|
+
* A row's human-readable title, or `null` when the descriptor's `titleField` does not resolve to a
|
|
42
|
+
* string on this row.
|
|
43
|
+
*
|
|
44
|
+
* Returns `null` rather than `String(value)` so a caller falls back to the id: a picker rendering
|
|
45
|
+
* the literal text `undefined` for every row is the exact symptom of a mistyped `titleField`, and
|
|
46
|
+
* `createAdminEntityRegistry` already rejects that at startup — this guards the remaining case,
|
|
47
|
+
* where the field is declared correctly and one row simply has no value for it.
|
|
48
|
+
*
|
|
49
|
+
* @complexity Time O(1).
|
|
50
|
+
* @overallScore 100
|
|
51
|
+
*/
|
|
52
|
+
export declare function readTitle({ row, titleField }: {
|
|
53
|
+
readonly row: AdminEntityRowData;
|
|
54
|
+
readonly titleField: string;
|
|
55
|
+
}): string | null;
|
|
56
|
+
/**
|
|
57
|
+
* The draft a "new" form starts from.
|
|
58
|
+
*
|
|
59
|
+
* Every declared field is present and absent-valued, so each control is controlled from the first
|
|
60
|
+
* render rather than switching from uncontrolled on the first keystroke.
|
|
61
|
+
*
|
|
62
|
+
* The one exception is a REQUIRED boolean, which starts `false`. A checkbox has no third state: an
|
|
63
|
+
* untouched one already means "no", so leaving it absent would block Save on a field the operator
|
|
64
|
+
* has, visibly, already answered — they would have to tick it and untick it to satisfy a
|
|
65
|
+
* requirement the unticked box was expressing all along. An OPTIONAL boolean stays absent, because
|
|
66
|
+
* there "unanswered" and "no" are genuinely different and only the descriptor's author knows which
|
|
67
|
+
* the row means.
|
|
68
|
+
*
|
|
69
|
+
* @complexity Time O(fields).
|
|
70
|
+
* @overallScore 100
|
|
71
|
+
*/
|
|
72
|
+
export declare function emptyDraft({ descriptor }: {
|
|
73
|
+
readonly descriptor: AdminEntityDescriptor;
|
|
74
|
+
}): Record<string, unknown>;
|
|
75
|
+
/**
|
|
76
|
+
* The draft an editor opens with: {@link emptyDraft}'s floor, with `row`'s declared values on top.
|
|
77
|
+
*
|
|
78
|
+
* Layered rather than `{ ...row }`, and that is the whole point. A row is not guaranteed to carry
|
|
79
|
+
* every declared field — an app that adds a `required` field to its descriptor has existing rows
|
|
80
|
+
* that predate it — and a draft built from the row alone inherits that gap. For a required BOOLEAN
|
|
81
|
+
* the consequence is {@link emptyDraft}'s trap arriving by the other door: the checkbox renders
|
|
82
|
+
* unticked, which already says "no", while Save stays blocked on "required and still empty" until
|
|
83
|
+
* the operator ticks and unticks it. The create path was fixed for this and the edit path was not,
|
|
84
|
+
* because they built their drafts in two different places.
|
|
85
|
+
*
|
|
86
|
+
* Only DECLARED fields are copied. A key the descriptor does not mention is invisible to every
|
|
87
|
+
* control on the screen, so carrying it into the draft would send a value back on save that nobody
|
|
88
|
+
* could see or edit — and `eraseEntityPort` reports it as an `undeclared-field` violation on the
|
|
89
|
+
* way in, so it is already known to be adapter drift rather than data.
|
|
90
|
+
*
|
|
91
|
+
* `id` is never copied: it is implicit on every row and `createAdminEntityRegistry` rejects any
|
|
92
|
+
* descriptor that declares it as a field.
|
|
93
|
+
*
|
|
94
|
+
* @param descriptor The entity being edited.
|
|
95
|
+
* @param row An existing row, or `null` when creating.
|
|
96
|
+
* @returns A draft with one entry per declared field.
|
|
97
|
+
* @complexity Time O(fields).
|
|
98
|
+
* @overallScore 100
|
|
99
|
+
*/
|
|
100
|
+
export declare function draftForRow({ descriptor, row }: {
|
|
101
|
+
readonly descriptor: AdminEntityDescriptor;
|
|
102
|
+
readonly row: AdminEntityRowData | null;
|
|
103
|
+
}): Record<string, unknown>;
|
|
104
|
+
/** Field names whose declared `required: true` is not satisfied by `draft`. */
|
|
105
|
+
export declare function missingRequiredFields({ descriptor, draft }: {
|
|
106
|
+
readonly descriptor: AdminEntityDescriptor;
|
|
107
|
+
readonly draft: Record<string, unknown>;
|
|
108
|
+
}): readonly string[];
|
|
109
|
+
/** One draft entry that holds a value its declared kind does not accept. */
|
|
110
|
+
export interface DraftFieldProblem {
|
|
111
|
+
readonly field: string;
|
|
112
|
+
/** Reads as "servings: is not an integer" — the port's own wording, not a second opinion. */
|
|
113
|
+
readonly detail: string;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Every draft entry whose value its declared kind would reject.
|
|
117
|
+
*
|
|
118
|
+
* Delegates to `describeAdminEntityFieldMismatch` — the SAME function `eraseEntityPort` uses to
|
|
119
|
+
* report adapter violations — rather than re-deciding here what an `integer` accepts. Two
|
|
120
|
+
* validators for one rule drift, and the drift is silent in the worst direction: the form saves a
|
|
121
|
+
* value the port then reports as a violation, so the write succeeds and the console blames the
|
|
122
|
+
* adapter for something the editor produced.
|
|
123
|
+
*
|
|
124
|
+
* The values this actually catches all come from `<input type="number">`, whose `step` constrains
|
|
125
|
+
* the spinner and not the parsed value: `1.5` typed into an `integer`, and `1e400` typed into a
|
|
126
|
+
* `real`, which becomes `Infinity` and then `null` the moment it meets `JSON.stringify`. Neither
|
|
127
|
+
* field kind appears in any descriptor the browser acceptance run renders, so neither has ever been
|
|
128
|
+
* seen to fail.
|
|
129
|
+
*
|
|
130
|
+
* Blank entries are skipped: an absent value is {@link missingRequiredFields}' business when the
|
|
131
|
+
* field is required, and legitimate when it is not.
|
|
132
|
+
*
|
|
133
|
+
* @complexity Time O(fields), plus the bounded JSON walk for each `json` field.
|
|
134
|
+
* @overallScore 100
|
|
135
|
+
*/
|
|
136
|
+
export declare function invalidDraftFields({ descriptor, draft }: {
|
|
137
|
+
readonly descriptor: AdminEntityDescriptor;
|
|
138
|
+
readonly draft: Record<string, unknown>;
|
|
139
|
+
}): readonly DraftFieldProblem[];
|
|
140
|
+
/**
|
|
141
|
+
* ISO-8601 -> the `YYYY-MM-DDTHH:mm:ss` an `<input type="datetime-local" step="1">` requires, read
|
|
142
|
+
* as UTC.
|
|
143
|
+
*
|
|
144
|
+
* Read and written as UTC on purpose, and labelled as such next to the control. Converting to the
|
|
145
|
+
* viewer's local zone would round-trip a stored instant through two conversions and shift it by the
|
|
146
|
+
* offset whenever the two disagree about DST — a bug that is invisible for anyone in UTC and
|
|
147
|
+
* corrupts timestamps for everyone else.
|
|
148
|
+
*
|
|
149
|
+
* Seconds are carried, not trimmed. The obvious `slice(0, 16)` matches the `datetime-local`
|
|
150
|
+
* default, and quietly rewrites `10:30:45Z` to `10:30:00Z` on the next Save of a row nobody meant
|
|
151
|
+
* to change the time of. Every timestamp in the dummy app is `00:00:00.000Z`, so the truncation is
|
|
152
|
+
* a no-op in the only data the acceptance run sees.
|
|
153
|
+
*
|
|
154
|
+
* @param value A stored field value, of any type.
|
|
155
|
+
* @returns The control's value, or `""` for anything not a parseable timestamp — which is what an
|
|
156
|
+
* empty `datetime-local` shows anyway.
|
|
157
|
+
* @complexity Time O(1).
|
|
158
|
+
* @overallScore 100
|
|
159
|
+
*/
|
|
160
|
+
export declare function toDatetimeLocalValue({ value }: {
|
|
161
|
+
readonly value: unknown;
|
|
162
|
+
}): string;
|
|
163
|
+
/**
|
|
164
|
+
* The inverse: a `datetime-local` control's value -> a stored ISO-8601 instant, read as UTC.
|
|
165
|
+
*
|
|
166
|
+
* The `Z` is what makes it UTC — without it the string is parsed in the browser's local zone, which
|
|
167
|
+
* is the half of the round trip that silently shifts an instant for everyone not in UTC.
|
|
168
|
+
*
|
|
169
|
+
* @param raw The control's `value`, `YYYY-MM-DDTHH:mm` or `YYYY-MM-DDTHH:mm:ss`.
|
|
170
|
+
* @returns `undefined` for an empty control; the ISO instant otherwise; and `raw` UNCHANGED when it
|
|
171
|
+
* will not parse — so `invalidDraftFields` reports it and Save is blocked, rather than this
|
|
172
|
+
* throwing a `RangeError` through a render or silently discarding what the operator typed.
|
|
173
|
+
* @complexity Time O(1).
|
|
174
|
+
* @overallScore 100
|
|
175
|
+
*/
|
|
176
|
+
export declare function fromDatetimeLocalValue({ raw }: {
|
|
177
|
+
readonly raw: string;
|
|
178
|
+
}): string | undefined;
|
|
179
|
+
//# sourceMappingURL=rules.d.ts.map
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { describeAdminEntityFieldMismatch } from '../../core/entities/rules.js';
|
|
2
|
+
export const RELATION_OPTION_LIMIT = 100;
|
|
3
|
+
export function resolveEntityPageSize({ raw }) {
|
|
4
|
+
const parsed = Number(raw);
|
|
5
|
+
return Number.isInteger(parsed) && parsed > 0 ? Math.min(parsed, 200) : 25;
|
|
6
|
+
}
|
|
7
|
+
/** Every `relation` field on a descriptor, narrowed so `target` is reachable. */
|
|
8
|
+
export function relationFieldsOf({ descriptor }) {
|
|
9
|
+
return descriptor.fields.filter((field) => field.kind === "relation");
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Loads the option set for every distinct relation target on `descriptor`.
|
|
13
|
+
*
|
|
14
|
+
* @param descriptor The entity whose relations need resolving.
|
|
15
|
+
* @param registry Host registry resolving a target's existing entity port.
|
|
16
|
+
* @returns One entry per distinct target. A target that does not resolve is simply absent, and the
|
|
17
|
+
* screens then render the raw stored id marked as unresolved rather than a blank cell.
|
|
18
|
+
* @complexity One `list` call per distinct target, in parallel; O(targets × limit) rows held.
|
|
19
|
+
* @overallScore 100
|
|
20
|
+
*/
|
|
21
|
+
export async function loadRelationIndex({ descriptor, registry }) {
|
|
22
|
+
const targets = [...new Set(relationFieldsOf({ descriptor }).map((field) => field.target))];
|
|
23
|
+
const loaded = await Promise.all(targets.map(async (target) => {
|
|
24
|
+
const port = registry.getEntity({ name: target });
|
|
25
|
+
if (port === null)
|
|
26
|
+
return null;
|
|
27
|
+
const page = await port.list({ limit: RELATION_OPTION_LIMIT });
|
|
28
|
+
const { titleField } = port.descriptor;
|
|
29
|
+
const options = page.items.map((row) => ({
|
|
30
|
+
id: row.id,
|
|
31
|
+
title: readTitle({ row, titleField }) ?? row.id,
|
|
32
|
+
}));
|
|
33
|
+
const titles = Object.create(null);
|
|
34
|
+
for (const option of options)
|
|
35
|
+
titles[option.id] = option.title;
|
|
36
|
+
// Truncation is read from `nextCursor`, not from `total`: `total` is optional on the port,
|
|
37
|
+
// so an adapter that omits it would make every over-long target silently look complete.
|
|
38
|
+
return [target, { options, titles, truncated: page.nextCursor !== null }];
|
|
39
|
+
}));
|
|
40
|
+
return Object.fromEntries(loaded.filter((entry) => entry !== null));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A row's human-readable title, or `null` when the descriptor's `titleField` does not resolve to a
|
|
44
|
+
* string on this row.
|
|
45
|
+
*
|
|
46
|
+
* Returns `null` rather than `String(value)` so a caller falls back to the id: a picker rendering
|
|
47
|
+
* the literal text `undefined` for every row is the exact symptom of a mistyped `titleField`, and
|
|
48
|
+
* `createAdminEntityRegistry` already rejects that at startup — this guards the remaining case,
|
|
49
|
+
* where the field is declared correctly and one row simply has no value for it.
|
|
50
|
+
*
|
|
51
|
+
* @complexity Time O(1).
|
|
52
|
+
* @overallScore 100
|
|
53
|
+
*/
|
|
54
|
+
export function readTitle({ row, titleField }) {
|
|
55
|
+
const value = row[titleField];
|
|
56
|
+
return typeof value === "string" && value !== "" ? value : null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The draft a "new" form starts from.
|
|
60
|
+
*
|
|
61
|
+
* Every declared field is present and absent-valued, so each control is controlled from the first
|
|
62
|
+
* render rather than switching from uncontrolled on the first keystroke.
|
|
63
|
+
*
|
|
64
|
+
* The one exception is a REQUIRED boolean, which starts `false`. A checkbox has no third state: an
|
|
65
|
+
* untouched one already means "no", so leaving it absent would block Save on a field the operator
|
|
66
|
+
* has, visibly, already answered — they would have to tick it and untick it to satisfy a
|
|
67
|
+
* requirement the unticked box was expressing all along. An OPTIONAL boolean stays absent, because
|
|
68
|
+
* there "unanswered" and "no" are genuinely different and only the descriptor's author knows which
|
|
69
|
+
* the row means.
|
|
70
|
+
*
|
|
71
|
+
* @complexity Time O(fields).
|
|
72
|
+
* @overallScore 100
|
|
73
|
+
*/
|
|
74
|
+
export function emptyDraft({ descriptor }) {
|
|
75
|
+
const draft = Object.create(null);
|
|
76
|
+
for (const field of descriptor.fields) {
|
|
77
|
+
draft[field.name] = field.kind === "boolean" && field.required === true ? false : undefined;
|
|
78
|
+
}
|
|
79
|
+
return draft;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The draft an editor opens with: {@link emptyDraft}'s floor, with `row`'s declared values on top.
|
|
83
|
+
*
|
|
84
|
+
* Layered rather than `{ ...row }`, and that is the whole point. A row is not guaranteed to carry
|
|
85
|
+
* every declared field — an app that adds a `required` field to its descriptor has existing rows
|
|
86
|
+
* that predate it — and a draft built from the row alone inherits that gap. For a required BOOLEAN
|
|
87
|
+
* the consequence is {@link emptyDraft}'s trap arriving by the other door: the checkbox renders
|
|
88
|
+
* unticked, which already says "no", while Save stays blocked on "required and still empty" until
|
|
89
|
+
* the operator ticks and unticks it. The create path was fixed for this and the edit path was not,
|
|
90
|
+
* because they built their drafts in two different places.
|
|
91
|
+
*
|
|
92
|
+
* Only DECLARED fields are copied. A key the descriptor does not mention is invisible to every
|
|
93
|
+
* control on the screen, so carrying it into the draft would send a value back on save that nobody
|
|
94
|
+
* could see or edit — and `eraseEntityPort` reports it as an `undeclared-field` violation on the
|
|
95
|
+
* way in, so it is already known to be adapter drift rather than data.
|
|
96
|
+
*
|
|
97
|
+
* `id` is never copied: it is implicit on every row and `createAdminEntityRegistry` rejects any
|
|
98
|
+
* descriptor that declares it as a field.
|
|
99
|
+
*
|
|
100
|
+
* @param descriptor The entity being edited.
|
|
101
|
+
* @param row An existing row, or `null` when creating.
|
|
102
|
+
* @returns A draft with one entry per declared field.
|
|
103
|
+
* @complexity Time O(fields).
|
|
104
|
+
* @overallScore 100
|
|
105
|
+
*/
|
|
106
|
+
export function draftForRow({ descriptor, row }) {
|
|
107
|
+
const draft = emptyDraft({ descriptor });
|
|
108
|
+
if (row === null)
|
|
109
|
+
return draft;
|
|
110
|
+
for (const field of descriptor.fields) {
|
|
111
|
+
// Skips `undefined` rather than spreading: an adapter that patches a row with an absent value
|
|
112
|
+
// leaves an OWN key holding `undefined`, and spreading that would overwrite the `false` floor a
|
|
113
|
+
// required boolean depends on.
|
|
114
|
+
const value = row[field.name];
|
|
115
|
+
if (value !== undefined)
|
|
116
|
+
draft[field.name] = value;
|
|
117
|
+
}
|
|
118
|
+
return draft;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Whether `value` reads as "the operator has not filled this in".
|
|
122
|
+
*
|
|
123
|
+
* `false` and `0` are deliberately NOT blank — they are answers, and a truthiness test that calls
|
|
124
|
+
* them empty is the single most reliable way to make a boolean or a numeric field unsaveable at its
|
|
125
|
+
* most ordinary value. `""` is blank for every kind whose control is a text box, but NOT for
|
|
126
|
+
* `json`, where the JSON string `""` is a legitimate stored value that is visibly on screen.
|
|
127
|
+
*
|
|
128
|
+
* @complexity Time O(1).
|
|
129
|
+
* @overallScore 100
|
|
130
|
+
*/
|
|
131
|
+
function isBlankDraftValue(field, value) {
|
|
132
|
+
if (value === undefined)
|
|
133
|
+
return true;
|
|
134
|
+
return field.kind !== "json" && value === "";
|
|
135
|
+
}
|
|
136
|
+
/** Field names whose declared `required: true` is not satisfied by `draft`. */
|
|
137
|
+
export function missingRequiredFields({ descriptor, draft }) {
|
|
138
|
+
return descriptor.fields
|
|
139
|
+
.filter((field) => field.required === true)
|
|
140
|
+
.filter((field) => isBlankDraftValue(field, draft[field.name]))
|
|
141
|
+
.map((field) => field.name);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Every draft entry whose value its declared kind would reject.
|
|
145
|
+
*
|
|
146
|
+
* Delegates to `describeAdminEntityFieldMismatch` — the SAME function `eraseEntityPort` uses to
|
|
147
|
+
* report adapter violations — rather than re-deciding here what an `integer` accepts. Two
|
|
148
|
+
* validators for one rule drift, and the drift is silent in the worst direction: the form saves a
|
|
149
|
+
* value the port then reports as a violation, so the write succeeds and the console blames the
|
|
150
|
+
* adapter for something the editor produced.
|
|
151
|
+
*
|
|
152
|
+
* The values this actually catches all come from `<input type="number">`, whose `step` constrains
|
|
153
|
+
* the spinner and not the parsed value: `1.5` typed into an `integer`, and `1e400` typed into a
|
|
154
|
+
* `real`, which becomes `Infinity` and then `null` the moment it meets `JSON.stringify`. Neither
|
|
155
|
+
* field kind appears in any descriptor the browser acceptance run renders, so neither has ever been
|
|
156
|
+
* seen to fail.
|
|
157
|
+
*
|
|
158
|
+
* Blank entries are skipped: an absent value is {@link missingRequiredFields}' business when the
|
|
159
|
+
* field is required, and legitimate when it is not.
|
|
160
|
+
*
|
|
161
|
+
* @complexity Time O(fields), plus the bounded JSON walk for each `json` field.
|
|
162
|
+
* @overallScore 100
|
|
163
|
+
*/
|
|
164
|
+
export function invalidDraftFields({ descriptor, draft }) {
|
|
165
|
+
const problems = [];
|
|
166
|
+
for (const field of descriptor.fields) {
|
|
167
|
+
const value = draft[field.name];
|
|
168
|
+
if (isBlankDraftValue(field, value))
|
|
169
|
+
continue;
|
|
170
|
+
const detail = describeAdminEntityFieldMismatch(field, value);
|
|
171
|
+
if (detail !== null)
|
|
172
|
+
problems.push({ field: field.name, detail });
|
|
173
|
+
}
|
|
174
|
+
return problems;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* ISO-8601 -> the `YYYY-MM-DDTHH:mm:ss` an `<input type="datetime-local" step="1">` requires, read
|
|
178
|
+
* as UTC.
|
|
179
|
+
*
|
|
180
|
+
* Read and written as UTC on purpose, and labelled as such next to the control. Converting to the
|
|
181
|
+
* viewer's local zone would round-trip a stored instant through two conversions and shift it by the
|
|
182
|
+
* offset whenever the two disagree about DST — a bug that is invisible for anyone in UTC and
|
|
183
|
+
* corrupts timestamps for everyone else.
|
|
184
|
+
*
|
|
185
|
+
* Seconds are carried, not trimmed. The obvious `slice(0, 16)` matches the `datetime-local`
|
|
186
|
+
* default, and quietly rewrites `10:30:45Z` to `10:30:00Z` on the next Save of a row nobody meant
|
|
187
|
+
* to change the time of. Every timestamp in the dummy app is `00:00:00.000Z`, so the truncation is
|
|
188
|
+
* a no-op in the only data the acceptance run sees.
|
|
189
|
+
*
|
|
190
|
+
* @param value A stored field value, of any type.
|
|
191
|
+
* @returns The control's value, or `""` for anything not a parseable timestamp — which is what an
|
|
192
|
+
* empty `datetime-local` shows anyway.
|
|
193
|
+
* @complexity Time O(1).
|
|
194
|
+
* @overallScore 100
|
|
195
|
+
*/
|
|
196
|
+
export function toDatetimeLocalValue({ value }) {
|
|
197
|
+
if (typeof value !== "string" || value === "")
|
|
198
|
+
return "";
|
|
199
|
+
const parsed = new Date(value);
|
|
200
|
+
if (Number.isNaN(parsed.getTime()))
|
|
201
|
+
return "";
|
|
202
|
+
const iso = parsed.toISOString();
|
|
203
|
+
return iso.endsWith('.000Z') ? iso.slice(0, 19) : iso.slice(0, 23);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* The inverse: a `datetime-local` control's value -> a stored ISO-8601 instant, read as UTC.
|
|
207
|
+
*
|
|
208
|
+
* The `Z` is what makes it UTC — without it the string is parsed in the browser's local zone, which
|
|
209
|
+
* is the half of the round trip that silently shifts an instant for everyone not in UTC.
|
|
210
|
+
*
|
|
211
|
+
* @param raw The control's `value`, `YYYY-MM-DDTHH:mm` or `YYYY-MM-DDTHH:mm:ss`.
|
|
212
|
+
* @returns `undefined` for an empty control; the ISO instant otherwise; and `raw` UNCHANGED when it
|
|
213
|
+
* will not parse — so `invalidDraftFields` reports it and Save is blocked, rather than this
|
|
214
|
+
* throwing a `RangeError` through a render or silently discarding what the operator typed.
|
|
215
|
+
* @complexity Time O(1).
|
|
216
|
+
* @overallScore 100
|
|
217
|
+
*/
|
|
218
|
+
export function fromDatetimeLocalValue({ raw }) {
|
|
219
|
+
if (raw === "")
|
|
220
|
+
return undefined;
|
|
221
|
+
const parsed = new Date(`${raw}Z`);
|
|
222
|
+
return Number.isNaN(parsed.getTime()) ? raw : parsed.toISOString();
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=rules.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { AdminErasedEntityPort } from '../../core/ports/entities.js';
|
|
2
|
+
/** Resolves existing entity ports; replace the registry object when registrations change. */
|
|
3
|
+
export interface EntityRegistryPort {
|
|
4
|
+
listEntities(args: Record<string, never>): readonly AdminErasedEntityPort[];
|
|
5
|
+
getEntity(args: {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
}): AdminErasedEntityPort | null;
|
|
8
|
+
}
|
|
9
|
+
export type EntityTranslate = (args: {
|
|
10
|
+
readonly key: string;
|
|
11
|
+
}) => string;
|
|
12
|
+
export interface EntityRouteTarget {
|
|
13
|
+
readonly entity?: string;
|
|
14
|
+
readonly id?: string;
|
|
15
|
+
readonly view?: 'list' | 'detail' | 'edit' | 'create';
|
|
16
|
+
}
|
|
17
|
+
export interface EntityRoutesPort {
|
|
18
|
+
href(args: EntityRouteTarget): string;
|
|
19
|
+
navigate(args: EntityRouteTarget): void;
|
|
20
|
+
}
|
|
21
|
+
export interface EntityScreenDependencies {
|
|
22
|
+
readonly registry: EntityRegistryPort;
|
|
23
|
+
readonly translate: EntityTranslate;
|
|
24
|
+
readonly routes: EntityRoutesPort;
|
|
25
|
+
}
|
|
26
|
+
export interface EntityListProps extends EntityScreenDependencies {
|
|
27
|
+
readonly entityName: string;
|
|
28
|
+
readonly limitParam: string | null;
|
|
29
|
+
}
|
|
30
|
+
export interface EntityDetailProps extends EntityScreenDependencies {
|
|
31
|
+
readonly entityName: string;
|
|
32
|
+
readonly id: string;
|
|
33
|
+
}
|
|
34
|
+
export interface EntityEditProps extends EntityScreenDependencies {
|
|
35
|
+
readonly entityName: string;
|
|
36
|
+
/** Null creates a row. All string ids, including `new`, refer to saved rows. */
|
|
37
|
+
readonly id: string | null;
|
|
38
|
+
}
|
|
39
|
+
/** Stable source templates let host dictionaries translate copy before values are inserted. */
|
|
40
|
+
export declare function entityText({ translate, key, values }: {
|
|
41
|
+
readonly translate: EntityTranslate;
|
|
42
|
+
readonly key: string;
|
|
43
|
+
readonly values: Readonly<Record<string, string | number>>;
|
|
44
|
+
}): string;
|
|
45
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Stable source templates let host dictionaries translate copy before values are inserted. */
|
|
2
|
+
export function entityText({ translate, key, values }) {
|
|
3
|
+
return translate({ key: key }).replace(/\{([\w]+)\}/g, (match, name) => Object.hasOwn(values, name) ? String(values[name]) : match);
|
|
4
|
+
}
|
|
5
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { AdminErasedEntityPort } from '../../core/ports/entities.js';
|
|
2
|
+
import type { EntityEditProps } from './types.js';
|
|
3
|
+
interface FormState {
|
|
4
|
+
readonly draft: Record<string, unknown>;
|
|
5
|
+
readonly jsonText: Readonly<Record<string, string>>;
|
|
6
|
+
readonly jsonErrors: Readonly<Record<string, boolean>>;
|
|
7
|
+
}
|
|
8
|
+
/** Called by the editor mounted for one entity/id pair. */
|
|
9
|
+
export declare function useEntityEdit({ port, ...props }: EntityEditProps & {
|
|
10
|
+
readonly port: AdminErasedEntityPort;
|
|
11
|
+
}): {
|
|
12
|
+
form: FormState | null;
|
|
13
|
+
read: {
|
|
14
|
+
readonly data: import("./use-entity-read.js").EntityLoadedRow | undefined;
|
|
15
|
+
readonly error: unknown;
|
|
16
|
+
readonly loading: boolean;
|
|
17
|
+
};
|
|
18
|
+
missing: readonly string[];
|
|
19
|
+
invalid: readonly import("./rules.js").DraftFieldProblem[];
|
|
20
|
+
canSave: boolean;
|
|
21
|
+
pending: "remove" | "save" | null;
|
|
22
|
+
failed: boolean;
|
|
23
|
+
setField: ({ name, value }: {
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly value: unknown;
|
|
26
|
+
}) => void;
|
|
27
|
+
setJsonText: ({ name, text }: {
|
|
28
|
+
readonly name: string;
|
|
29
|
+
readonly text: string;
|
|
30
|
+
}) => void;
|
|
31
|
+
save: () => Promise<void>;
|
|
32
|
+
remove: () => Promise<void>;
|
|
33
|
+
};
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=use-entity-edit.d.ts.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { draftForRow, invalidDraftFields, missingRequiredFields } from './rules.js';
|
|
3
|
+
import { loadEntityRow, useEntityRead } from './use-entity-read.js';
|
|
4
|
+
/** Called by the editor mounted for one entity/id pair. */
|
|
5
|
+
export function useEntityEdit({ port, ...props }) {
|
|
6
|
+
const { entityName, id, registry, routes } = props;
|
|
7
|
+
const scope = useMemo(() => ({ port, registry, entityName, id }), [port, registry, entityName, id]);
|
|
8
|
+
const currentScope = useRef(scope);
|
|
9
|
+
currentScope.current = scope;
|
|
10
|
+
const mounted = useRef(true);
|
|
11
|
+
useEffect(() => { mounted.current = true; return () => { mounted.current = false; }; }, []);
|
|
12
|
+
const load = useCallback(() => loadEntityRow({ registry, entityName, id }), [registry, entityName, id, port]);
|
|
13
|
+
const read = useEntityRead({ load, scope });
|
|
14
|
+
const [formState, setFormState] = useState(null);
|
|
15
|
+
const [mutation, setMutation] = useState({ scope, kind: null, failed: false });
|
|
16
|
+
const busy = useRef(false);
|
|
17
|
+
let form = formState?.scope === scope ? formState.form : null;
|
|
18
|
+
if (read.data && form === null && (id === null || read.data.row !== null)) {
|
|
19
|
+
const draft = draftForRow({ descriptor: port.descriptor, row: read.data.row });
|
|
20
|
+
const jsonText = Object.create(null);
|
|
21
|
+
for (const field of port.descriptor.fields) {
|
|
22
|
+
if (field.kind === 'json')
|
|
23
|
+
jsonText[field.name] = draft[field.name] === undefined ? '' : JSON.stringify(draft[field.name], null, 2);
|
|
24
|
+
}
|
|
25
|
+
form = { draft, jsonText, jsonErrors: {} };
|
|
26
|
+
setFormState({ scope, form });
|
|
27
|
+
}
|
|
28
|
+
const draft = form?.draft ?? {};
|
|
29
|
+
const missing = missingRequiredFields({ descriptor: port.descriptor, draft });
|
|
30
|
+
const invalid = invalidDraftFields({ descriptor: port.descriptor, draft });
|
|
31
|
+
const pending = mutation.scope === scope ? mutation.kind : null;
|
|
32
|
+
const canSave = form !== null && missing.length === 0 && invalid.length === 0 &&
|
|
33
|
+
!Object.values(form.jsonErrors).some(Boolean) && pending === null;
|
|
34
|
+
function setField({ name, value }) {
|
|
35
|
+
if (!port.descriptor.fields.some((field) => field.name === name) || busy.current)
|
|
36
|
+
return;
|
|
37
|
+
setFormState((state) => state?.scope === scope ? { scope, form: { ...state.form, draft: { ...state.form.draft, [name]: value } } } : state);
|
|
38
|
+
}
|
|
39
|
+
function setJsonText({ name, text }) {
|
|
40
|
+
if (!port.descriptor.fields.some((field) => field.name === name && field.kind === 'json') || busy.current)
|
|
41
|
+
return;
|
|
42
|
+
let value;
|
|
43
|
+
let failed = false;
|
|
44
|
+
try {
|
|
45
|
+
value = text.trim() === '' ? undefined : JSON.parse(text);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
failed = true;
|
|
49
|
+
}
|
|
50
|
+
setFormState((state) => state?.scope === scope ? {
|
|
51
|
+
scope,
|
|
52
|
+
form: {
|
|
53
|
+
draft: failed ? state.form.draft : { ...state.form.draft, [name]: value },
|
|
54
|
+
jsonText: { ...state.form.jsonText, [name]: text },
|
|
55
|
+
jsonErrors: { ...state.form.jsonErrors, [name]: failed },
|
|
56
|
+
},
|
|
57
|
+
} : state);
|
|
58
|
+
}
|
|
59
|
+
async function mutate({ kind }) {
|
|
60
|
+
if (busy.current || (kind === 'save' && !canSave) || (kind === 'remove' && (id === null || port.remove === undefined)))
|
|
61
|
+
return;
|
|
62
|
+
busy.current = true;
|
|
63
|
+
setMutation({ scope, kind, failed: false });
|
|
64
|
+
const active = () => mounted.current && currentScope.current === scope;
|
|
65
|
+
try {
|
|
66
|
+
if (kind === 'remove' && id !== null && port.remove) {
|
|
67
|
+
await port.remove(id);
|
|
68
|
+
if (active())
|
|
69
|
+
routes.navigate({ entity: entityName });
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// A create omits blanks; an update carries explicit undefined to clear optional fields.
|
|
73
|
+
const saved = id === null
|
|
74
|
+
? await port.create(Object.fromEntries(Object.entries(draft).filter(([, value]) => value !== undefined)))
|
|
75
|
+
: await port.update(id, draft);
|
|
76
|
+
if (active())
|
|
77
|
+
routes.navigate({ entity: entityName, id: saved.id });
|
|
78
|
+
}
|
|
79
|
+
if (active())
|
|
80
|
+
setMutation({ scope, kind: null, failed: false });
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
if (active())
|
|
84
|
+
setMutation({ scope, kind: null, failed: true });
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
busy.current = false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
form, read, missing, invalid, canSave, pending,
|
|
92
|
+
failed: mutation.scope === scope && mutation.failed,
|
|
93
|
+
setField, setJsonText,
|
|
94
|
+
save: () => mutate({ kind: 'save' }),
|
|
95
|
+
remove: () => mutate({ kind: 'remove' }),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=use-entity-edit.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AdminEntityRowData } from '../../core/ports/entities.js';
|
|
2
|
+
import type { EntityRegistryPort } from './types.js';
|
|
3
|
+
import { type RelationIndex } from './rules.js';
|
|
4
|
+
/** Per-mount async state; stale results are discarded and reads never overwrite another route. */
|
|
5
|
+
export declare function useEntityRead<T>({ load, scope }: {
|
|
6
|
+
readonly load: () => Promise<T>;
|
|
7
|
+
readonly scope: unknown;
|
|
8
|
+
}): {
|
|
9
|
+
readonly data: T | undefined;
|
|
10
|
+
readonly error: unknown;
|
|
11
|
+
readonly loading: boolean;
|
|
12
|
+
};
|
|
13
|
+
export interface EntityLoadedRow {
|
|
14
|
+
readonly row: AdminEntityRowData | null;
|
|
15
|
+
readonly relations: RelationIndex;
|
|
16
|
+
}
|
|
17
|
+
export declare function loadEntityRow({ registry, entityName, id }: {
|
|
18
|
+
readonly registry: EntityRegistryPort;
|
|
19
|
+
readonly entityName: string;
|
|
20
|
+
readonly id: string | null;
|
|
21
|
+
}): Promise<EntityLoadedRow>;
|
|
22
|
+
//# sourceMappingURL=use-entity-read.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { loadRelationIndex } from './rules.js';
|
|
3
|
+
/** Per-mount async state; stale results are discarded and reads never overwrite another route. */
|
|
4
|
+
export function useEntityRead({ load, scope }) {
|
|
5
|
+
const [state, setState] = useState({ scope, load });
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
let active = true;
|
|
8
|
+
setState({ scope, load });
|
|
9
|
+
Promise.resolve().then(load).then((data) => { if (active)
|
|
10
|
+
setState({ scope, load, data }); }, (error) => { if (active)
|
|
11
|
+
setState({ scope, load, error }); });
|
|
12
|
+
return () => { active = false; };
|
|
13
|
+
}, [load, scope]);
|
|
14
|
+
const current = state.scope === scope && state.load === load ? state : null;
|
|
15
|
+
return { data: current?.data, error: current?.error, loading: current === null || (!Object.hasOwn(current, 'data') && !Object.hasOwn(current, 'error')) };
|
|
16
|
+
}
|
|
17
|
+
export async function loadEntityRow({ registry, entityName, id }) {
|
|
18
|
+
const port = registry.getEntity({ name: entityName });
|
|
19
|
+
if (port === null)
|
|
20
|
+
return { row: null, relations: {} };
|
|
21
|
+
const [row, relations] = await Promise.all([
|
|
22
|
+
id === null ? Promise.resolve(null) : port.get(id),
|
|
23
|
+
loadRelationIndex({ descriptor: port.descriptor, registry }),
|
|
24
|
+
]);
|
|
25
|
+
return { row, relations };
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=use-entity-read.js.map
|
|
@@ -33,8 +33,12 @@ export declare const DEFAULT_NAV_SECTIONS_STORAGE_KEY = "jini-admin-nav-sections
|
|
|
33
33
|
export type NavSectionState = Readonly<Record<string, boolean>>;
|
|
34
34
|
export interface NavSections {
|
|
35
35
|
/** `true` when the section should render its items. Any label with no stored preference is open. */
|
|
36
|
-
isOpen: (
|
|
37
|
-
|
|
36
|
+
isOpen: (requiredArgs: {
|
|
37
|
+
readonly groupLabel: string;
|
|
38
|
+
}) => boolean;
|
|
39
|
+
toggle: (requiredArgs: {
|
|
40
|
+
readonly groupLabel: string;
|
|
41
|
+
}) => void;
|
|
38
42
|
}
|
|
39
43
|
/**
|
|
40
44
|
* @param storageKey `localStorage` key to persist under. Defaults to
|
|
@@ -46,5 +50,10 @@ export interface NavSections {
|
|
|
46
50
|
* handful, fixed by the host's panel manifest, never user-variable.
|
|
47
51
|
* @overallScore 100
|
|
48
52
|
*/
|
|
49
|
-
export declare function useNavSections(
|
|
53
|
+
export declare function useNavSections({ storage, events }: {
|
|
54
|
+
readonly storage: Pick<Storage, 'getItem' | 'setItem'>;
|
|
55
|
+
readonly events: Pick<Window, 'addEventListener' | 'removeEventListener'>;
|
|
56
|
+
}, { storageKey }?: {
|
|
57
|
+
readonly storageKey?: string;
|
|
58
|
+
}): NavSections;
|
|
50
59
|
//# sourceMappingURL=use-nav-sections.d.ts.map
|