@eventcatalog/core 4.12.3 → 5.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +226 -95
- package/NOTICE +30 -0
- package/README.md +3 -2
- package/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +43 -16
- package/dist/analytics/log-build.d.cts +6 -5
- package/dist/analytics/log-build.d.ts +6 -5
- package/dist/analytics/log-build.js +4 -3
- package/dist/{chunk-U4YJSTGV.js → chunk-AYNZXUB5.js} +2 -10
- package/dist/{chunk-TV3YAWKQ.js → chunk-CR6AK5LR.js} +1 -1
- package/dist/{chunk-M577VUOX.js → chunk-LR7AGRHN.js} +13 -16
- package/dist/chunk-TQLRG35T.js +203 -0
- package/dist/{chunk-HPC6KUDQ.js → chunk-TWN43V4T.js} +1 -1
- package/dist/{chunk-ZYTCBNB3.js → chunk-WAIADKQE.js} +1 -1
- package/dist/{chunk-5BPN6QEC.js → chunk-ZHKEARLJ.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +407 -291
- package/dist/eventcatalog.config.d.cts +2 -3
- package/dist/eventcatalog.config.d.ts +2 -3
- package/dist/eventcatalog.js +35 -98
- package/dist/federation/content-cache.d.cts +0 -5
- package/dist/federation/content-cache.d.ts +0 -5
- package/dist/federation/diagnostics.d.cts +0 -5
- package/dist/federation/diagnostics.d.ts +0 -5
- package/dist/federation/federate.cjs +0 -9
- package/dist/federation/federate.d.cts +0 -5
- package/dist/federation/federate.d.ts +0 -5
- package/dist/federation/federate.js +4 -5
- package/dist/federation/filesystem-source-provider.d.cts +0 -5
- package/dist/federation/filesystem-source-provider.d.ts +0 -5
- package/dist/federation/github-source-provider.d.cts +0 -5
- package/dist/federation/github-source-provider.d.ts +0 -5
- package/dist/federation/output-transaction.d.cts +0 -4
- package/dist/federation/output-transaction.d.ts +0 -4
- package/dist/federation/public-assets.d.cts +0 -5
- package/dist/federation/public-assets.d.ts +0 -5
- package/dist/federation/source-provider.d.cts +0 -5
- package/dist/federation/source-provider.d.ts +0 -5
- package/dist/federation/types.d.cts +0 -5
- package/dist/federation/types.d.ts +0 -5
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/license-C1y-56bb.d.cts +23 -0
- package/dist/license-C1y-56bb.d.ts +23 -0
- package/dist/runtime-dependencies/@eventcatalog__visualiser__layout.mjs +3 -0
- package/dist/runtime-dependencies/@modelcontextprotocol__ext-apps.mjs +3 -0
- package/dist/runtime-dependencies/@modelcontextprotocol__ext-apps__react.mjs +3 -0
- package/dist/runtime-dependencies/@modelcontextprotocol__ext-apps__server.mjs +3 -0
- package/dist/runtime-dependencies/@modelcontextprotocol__sdk__types.js.mjs +3 -0
- package/dist/runtime-dependencies/manifest.json +12 -1
- package/dist/runtime-dependencies/react-syntax-highlighter__dist__esm__languages__prism__graphql.mjs +3 -0
- package/dist/runtime-dependencies/react-syntax-highlighter__dist__esm__languages__prism__json.mjs +3 -0
- package/dist/runtime-dependencies/react-syntax-highlighter__dist__esm__languages__prism__markup.mjs +3 -0
- package/dist/runtime-dependencies/react-syntax-highlighter__dist__esm__languages__prism__protobuf.mjs +3 -0
- package/dist/runtime-dependencies/react-syntax-highlighter__dist__esm__languages__prism__yaml.mjs +3 -0
- package/dist/runtime-dependencies/react-syntax-highlighter__dist__esm__styles__prism__one-dark.mjs +3 -0
- package/dist/runtime-dependencies/react-syntax-highlighter__dist__esm__styles__prism__one-light.mjs +3 -0
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/dist/utils/license-status.cjs +240 -0
- package/dist/utils/license-status.d.cts +28 -0
- package/dist/utils/license-status.d.ts +28 -0
- package/dist/utils/license-status.js +12 -0
- package/eventcatalog/astro.config.mjs +1 -1
- package/eventcatalog/integrations/eventcatalog-runtime.mjs +1 -1
- package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +29 -19
- package/eventcatalog/src/components/ChatPanel/OfflineReply.tsx +74 -0
- package/eventcatalog/src/components/FieldsExplorer/FieldFilters.tsx +24 -28
- package/eventcatalog/src/components/FieldsExplorer/FieldNodeGraph.tsx +16 -11
- package/eventcatalog/src/components/FieldsExplorer/FieldsExplorer.tsx +4 -218
- package/eventcatalog/src/components/FieldsExplorer/FieldsTable.tsx +27 -36
- package/eventcatalog/src/components/Header.astro +43 -58
- package/eventcatalog/src/components/Lists/PillListFlat.styles.css +17 -17
- package/eventcatalog/src/components/MDX/EntityMap/EntityMap.astro +1 -1
- package/eventcatalog/src/components/MDX/Flow/Flow.astro +4 -0
- package/eventcatalog/src/components/MDX/NodeGraph/AstroNodeGraph.tsx +14 -0
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +49 -18
- package/eventcatalog/src/components/MDX/RemoteFile.astro +2 -24
- package/eventcatalog/src/components/SchemaExplorer/ApiAccessSection.tsx +59 -93
- package/eventcatalog/src/components/SchemaExplorer/ApiContentViewer.tsx +1 -32
- package/eventcatalog/src/components/SchemaExplorer/DiffViewer.tsx +20 -67
- package/eventcatalog/src/components/SchemaExplorer/SchemaContentViewer.tsx +2 -11
- package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +11 -60
- package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +1 -3
- package/eventcatalog/src/components/SchemaExplorer/SchemaPageViewer.tsx +0 -3
- package/eventcatalog/src/components/SchemaExplorer/VersionHistoryModal.tsx +2 -9
- package/eventcatalog/src/components/SchemaExplorer/parse-schema.ts +46 -0
- package/eventcatalog/src/components/SchemaExplorer/utils.ts +11 -0
- package/eventcatalog/src/components/Settings/AssistantSettingsForm.tsx +2 -9
- package/eventcatalog/src/components/Settings/LicenseSettings.tsx +288 -0
- package/eventcatalog/src/components/Settings/LlmAccessSettingsForm.tsx +3 -19
- package/eventcatalog/src/components/Settings/McpSettingsForm.tsx +85 -30
- package/eventcatalog/src/components/Settings/SettingsShared.tsx +7 -75
- package/eventcatalog/src/components/SideNav/CustomDocsNav.astro +2 -2
- package/eventcatalog/src/components/TrialBadge.astro +82 -0
- package/eventcatalog/src/content.config.ts +9 -96
- package/eventcatalog/src/{enterprise → features}/ai/chat-api.ts +22 -34
- package/eventcatalog/src/{enterprise → features}/analytics/adapters/ga4.ts +0 -5
- package/eventcatalog/src/{enterprise → features}/analytics/adapters/gtm.ts +0 -5
- package/eventcatalog/src/{enterprise → features}/analytics/adapters/posthog.ts +0 -5
- package/eventcatalog/src/{enterprise → features}/analytics/components/AnalyticsHead.astro +2 -4
- package/eventcatalog/src/{enterprise → features}/analytics/components/AnalyticsTracker.astro +2 -4
- package/eventcatalog/src/{enterprise → features}/analytics/tracker.ts +0 -5
- package/eventcatalog/src/{enterprise → features}/api/catalog.ts +0 -5
- package/eventcatalog/src/{enterprise → features}/auth/[...auth].ts +0 -5
- package/eventcatalog/src/{enterprise → features}/auth/middleware/middleware-auth.ts +0 -5
- package/eventcatalog/src/{enterprise → features}/auth/middleware/middleware.ts +0 -5
- package/eventcatalog/src/{enterprise → features}/collections/custom-pages.ts +0 -5
- package/eventcatalog/src/{enterprise → features}/collections/index.ts +0 -5
- package/eventcatalog/src/{enterprise → features}/collections/resource-docs.ts +0 -5
- package/eventcatalog/src/{enterprise → features}/custom-documentation/collection.ts +0 -5
- package/eventcatalog/src/features/custom-documentation/components/CustomDocsNav/CustomDocsNav.astro +9 -0
- package/eventcatalog/src/{enterprise → features}/custom-documentation/components/CustomDocsNav/CustomDocsNavWrapper.tsx +0 -5
- package/eventcatalog/src/{enterprise → features}/custom-documentation/components/CustomDocsNav/components/NestedItem.tsx +1 -6
- package/eventcatalog/src/{enterprise → features}/custom-documentation/components/CustomDocsNav/components/NoResultsFound.tsx +0 -5
- package/eventcatalog/src/{enterprise → features}/custom-documentation/components/CustomDocsNav/index.tsx +1 -6
- package/eventcatalog/src/{enterprise → features}/custom-documentation/components/CustomDocsNav/types.ts +0 -5
- package/eventcatalog/src/{enterprise/custom-documentation/pages/docs/custom/index.astro → features/custom-documentation/components/CustomDocumentationPage.astro} +5 -5
- package/eventcatalog/src/{enterprise → features}/custom-documentation/utils/badge-styles.ts +0 -5
- package/eventcatalog/src/{enterprise → features}/custom-documentation/utils/custom-docs.ts +1 -6
- package/eventcatalog/src/{enterprise → features}/custom-documentation/utils/docs-search.ts +0 -5
- package/eventcatalog/src/{enterprise → features}/custom-pages/routes.ts +0 -5
- package/eventcatalog/src/{enterprise → features}/directory/user-team-directory.ts +0 -5
- package/eventcatalog/src/{enterprise → features}/fields/field-extractor.ts +0 -5
- package/eventcatalog/src/{enterprise → features}/fields/field-indexer.ts +0 -5
- package/eventcatalog/src/{enterprise → features}/fields/fields-db.ts +0 -5
- package/eventcatalog/src/{enterprise → features}/fields/pages/api/fields.ts +1 -6
- package/eventcatalog/src/{enterprise → features}/fields/pages/fields.astro +1 -2
- package/eventcatalog/src/{enterprise → features}/integrations/eventcatalog-features.ts +17 -75
- package/eventcatalog/src/features/mcp/apps/architecture-diagram/links.ts +59 -0
- package/eventcatalog/src/features/mcp/apps/architecture-diagram/node-chat.tsx +299 -0
- package/eventcatalog/src/features/mcp/apps/architecture-diagram/shared.ts +14 -0
- package/eventcatalog/src/features/mcp/apps/architecture-diagram/view.tsx +454 -0
- package/eventcatalog/src/features/mcp/apps/generated/viewer.html +432 -0
- package/eventcatalog/src/features/mcp/apps/mermaid-stub.ts +10 -0
- package/eventcatalog/src/features/mcp/apps/schema-viewer/schema-code.tsx +65 -0
- package/eventcatalog/src/features/mcp/apps/schema-viewer/shared.ts +23 -0
- package/eventcatalog/src/features/mcp/apps/schema-viewer/view.tsx +170 -0
- package/eventcatalog/src/features/mcp/apps/shared/app-view.tsx +98 -0
- package/eventcatalog/src/features/mcp/apps/shared/resource-types.ts +15 -0
- package/eventcatalog/src/features/mcp/apps/viewer/shared.ts +24 -0
- package/eventcatalog/src/features/mcp/apps/viewer/styles.css +25 -0
- package/eventcatalog/src/features/mcp/apps/viewer/view.tsx +59 -0
- package/eventcatalog/src/features/mcp/mcp-app-icons.ts +63 -0
- package/eventcatalog/src/features/mcp/mcp-apps.ts +160 -0
- package/eventcatalog/src/{enterprise → features}/mcp/mcp-auth.ts +0 -5
- package/eventcatalog/src/{enterprise → features}/mcp/mcp-scope.ts +7 -7
- package/eventcatalog/src/{enterprise → features}/mcp/mcp-scoped-tools.ts +47 -40
- package/eventcatalog/src/{enterprise → features}/mcp/mcp-server.ts +347 -34
- package/eventcatalog/src/{enterprise → features}/mcp/oauth-protected-resource.ts +0 -5
- package/eventcatalog/src/features/mcp/schema-viewer.ts +24 -0
- package/eventcatalog/src/{enterprise → features}/tools/catalog-tools.ts +133 -102
- package/eventcatalog/src/features/tools/index.ts +5 -0
- package/eventcatalog/src/{enterprise → features}/visualizer-layout/reset.ts +0 -5
- package/eventcatalog/src/{enterprise → features}/visualizer-layout/save.ts +0 -5
- package/eventcatalog/src/layouts/BaseLayout.astro +2 -2
- package/eventcatalog/src/layouts/Footer.astro +31 -39
- package/eventcatalog/src/layouts/SettingsLayout.astro +6 -6
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +7 -37
- package/eventcatalog/src/{enterprise → pages}/api/schemas/[collection]/[id]/[version]/index.ts +0 -22
- package/eventcatalog/src/{enterprise → pages}/api/schemas/services/[id]/[version]/[specification]/index.ts +0 -19
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/_index.data.ts +2 -6
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/index.astro +1 -5
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/_index.data.ts +2 -6
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/index.astro +1 -5
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId].md.ts +2 -6
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId].mdx.ts +2 -6
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +9 -5
- package/eventcatalog/src/pages/docs/custom/[...path]/index.astro +12 -0
- package/eventcatalog/src/{enterprise/custom-documentation/pages/docs/custom/root-index.astro → pages/docs/custom/index.astro} +1 -23
- package/eventcatalog/src/pages/docs/llm/llms-full.txt.ts +4 -9
- package/eventcatalog/src/pages/docs/llm/llms.txt.ts +4 -4
- package/eventcatalog/src/pages/docs/llm/schemas.txt.ts +0 -11
- package/eventcatalog/src/pages/docs/services/[id]/[docType]/[docId]/index.astro +2 -2
- package/eventcatalog/src/pages/index.astro +2 -2
- package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/index.astro +0 -3
- package/eventcatalog/src/pages/schemas/explorer/index.astro +1 -4
- package/eventcatalog/src/pages/settings/assistant.astro +2 -4
- package/eventcatalog/src/pages/settings/license.astro +18 -0
- package/eventcatalog/src/pages/settings/llm-access.astro +1 -4
- package/eventcatalog/src/pages/settings/mcp.astro +33 -4
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +1 -2
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/data/index.astro +2 -45
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/entity-map/index.astro +1 -1
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version].mermaid.ts +7 -109
- package/eventcatalog/src/pages/visualiser/_mermaid-route.ts +78 -0
- package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/_index.data.ts +44 -0
- package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/entity-map/index.astro +1 -1
- package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/index.astro +56 -0
- package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/systems-context/index.astro +3 -40
- package/eventcatalog/src/pages/visualiser/domains/[id]/[version].mermaid.ts +20 -0
- package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/_index.data.ts +29 -0
- package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/context/index.astro +3 -38
- package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/index.astro +56 -0
- package/eventcatalog/src/pages/visualiser/systems/[id]/[version].mermaid.ts +20 -0
- package/eventcatalog/src/stores/sidebar-store/builders/agent.ts +1 -1
- package/eventcatalog/src/stores/sidebar-store/builders/container.ts +1 -1
- package/eventcatalog/src/stores/sidebar-store/builders/data-product.ts +1 -1
- package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +7 -32
- package/eventcatalog/src/stores/sidebar-store/builders/entity.ts +1 -1
- package/eventcatalog/src/stores/sidebar-store/builders/message.ts +1 -1
- package/eventcatalog/src/stores/sidebar-store/builders/service.ts +2 -8
- package/eventcatalog/src/stores/sidebar-store/builders/system.ts +2 -12
- package/eventcatalog/src/stores/sidebar-store/custom-sidebar.ts +0 -10
- package/eventcatalog/src/stores/sidebar-store/state.ts +4 -6
- package/eventcatalog/src/styles/theme.css +22 -18
- package/eventcatalog/src/styles/themes/forest.css +8 -8
- package/eventcatalog/src/styles/themes/ocean.css +8 -8
- package/eventcatalog/src/styles/themes/sapphire.css +8 -8
- package/eventcatalog/src/styles/themes/sunset.css +8 -8
- package/eventcatalog/src/utils/clipboard.ts +4 -4
- package/eventcatalog/src/utils/collections/flow-step-schema.ts +117 -0
- package/eventcatalog/src/utils/collections/pointer-schema.ts +7 -0
- package/eventcatalog/src/utils/collections/resource-docs.ts +677 -14
- package/eventcatalog/src/utils/collections/schema-loader.ts +0 -5
- package/eventcatalog/src/utils/feature.ts +14 -23
- package/eventcatalog/src/utils/license.ts +114 -0
- package/eventcatalog/src/utils/node-graphs/architecture-diagram-types.ts +32 -0
- package/eventcatalog/src/utils/node-graphs/architecture-diagram.ts +84 -0
- package/eventcatalog/src/utils/node-graphs/channel-node-graph.ts +4 -25
- package/eventcatalog/src/utils/node-graphs/container-node-graph.ts +12 -39
- package/eventcatalog/src/utils/node-graphs/data-products-node-graph.ts +11 -27
- package/eventcatalog/src/utils/node-graphs/domain-levels-node-graph.ts +679 -0
- package/eventcatalog/src/utils/node-graphs/domains-node-graph.ts +3 -20
- package/eventcatalog/src/utils/node-graphs/export-mermaid.ts +51 -4
- package/eventcatalog/src/utils/node-graphs/field-node-graph.ts +8 -23
- package/eventcatalog/src/utils/node-graphs/flow-lanes.ts +119 -0
- package/eventcatalog/src/utils/node-graphs/flows-node-graph.ts +133 -47
- package/eventcatalog/src/utils/node-graphs/layout-node-graph.ts +28 -0
- package/eventcatalog/src/utils/node-graphs/layout-persistence.ts +3 -3
- package/eventcatalog/src/utils/node-graphs/message-node-graph.ts +16 -34
- package/eventcatalog/src/utils/node-graphs/services-node-graph.ts +11 -25
- package/eventcatalog/src/utils/node-graphs/system-context-node-graph.ts +18 -54
- package/eventcatalog/src/utils/node-graphs/system-levels-node-graph.ts +59 -0
- package/eventcatalog/src/utils/node-graphs/systems-node-graph.ts +20 -80
- package/eventcatalog/src/utils/node-graphs/utils/utils.ts +1 -78
- package/eventcatalog/src/utils/trial.ts +36 -0
- package/eventcatalog/tsconfig.json +1 -1
- package/package.json +11 -11
- package/dist/chunk-FV56YFM4.js +0 -7
- package/dist/federation/entitlement.cjs +0 -31
- package/dist/federation/entitlement.d.cts +0 -12
- package/dist/federation/entitlement.d.ts +0 -12
- package/dist/federation/entitlement.js +0 -6
- package/dist/runtime-dependencies/dagre.mjs +0 -3
- package/eventcatalog/src/components/Settings/BillingSettingsForm.tsx +0 -265
- package/eventcatalog/src/enterprise/LICENSE +0 -57
- package/eventcatalog/src/enterprise/README.md +0 -5
- package/eventcatalog/src/enterprise/collections/resource-docs-utils.ts +0 -691
- package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/CustomDocsNav.astro +0 -9
- package/eventcatalog/src/enterprise/custom-documentation/pages/docs/custom/[...path]/index.astro +0 -18
- package/eventcatalog/src/enterprise/custom-documentation/pages/docs/custom/feature.astro +0 -171
- package/eventcatalog/src/enterprise/feature.ts +0 -75
- package/eventcatalog/src/enterprise/tools/index.ts +0 -10
- package/eventcatalog/src/hooks/eventcatalog-visualizer.ts +0 -126
- package/eventcatalog/src/pages/settings/billing.astro +0 -17
- package/eventcatalog/src/pages/visualiser/domain-integrations/index.astro +0 -26
- package/eventcatalog/src/types/mcp-modules.d.ts +0 -67
- package/eventcatalog/src/utils/node-graphs/domains-canvas.ts +0 -261
- package/eventcatalog/src/utils/node-graphs/export-node-graph.ts +0 -91
- /package/eventcatalog/src/{enterprise → features}/auth/error.astro +0 -0
- /package/eventcatalog/src/{enterprise → features}/auth/login.astro +0 -0
- /package/eventcatalog/src/{enterprise → features}/auth/unauthorized.astro +0 -0
- /package/eventcatalog/src/{enterprise/custom-documentation/pages → pages}/docs/custom/[...path]/_index.data.ts +0 -0
- /package/eventcatalog/src/{enterprise/custom-documentation/pages → pages}/docs/custom/[...path].mdx.ts +0 -0
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
import { getDomains } from '@utils/collections/domains';
|
|
2
|
+
import { getNodesAndEdges as getSystemNodesAndEdges } from './systems-node-graph';
|
|
3
|
+
import { getNodesAndEdges as getServiceNodesAndEdges } from './services-node-graph';
|
|
4
|
+
import { getNodesAndEdgesForDomainSystems as getDomainContextNodesAndEdges } from './system-context-node-graph';
|
|
5
|
+
import { createEdge, generateIdForNode } from '@utils/node-graphs/utils/utils';
|
|
6
|
+
import { layoutLevels } from '@utils/node-graphs/layout-node-graph';
|
|
7
|
+
import { getMessagesLabel, hideMessageNodes, isCarrier } from '@eventcatalog/visualiser/layout';
|
|
8
|
+
|
|
9
|
+
interface NodesAndEdgesProps {
|
|
10
|
+
id: string;
|
|
11
|
+
version: string;
|
|
12
|
+
mode?: 'simple' | 'full';
|
|
13
|
+
channelRenderMode?: 'single' | 'flat';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const CACHE_ENABLED = process.env.DISABLE_EVENTCATALOG_CACHE !== 'true';
|
|
17
|
+
|
|
18
|
+
type Graph = { nodes: any[]; edges: any[] };
|
|
19
|
+
type Tree = { domain: any; groupId: string; parentId?: string }[];
|
|
20
|
+
type Owners = Map<string, { domain: any; system?: any }>;
|
|
21
|
+
|
|
22
|
+
const MESSAGE_TYPES = new Set(['events', 'commands', 'queries']);
|
|
23
|
+
const SERVICE_TYPES = new Set(['services', 'agents']);
|
|
24
|
+
|
|
25
|
+
const systemGroupId = (system: any) => `system-group-${system.data.id}-${system.data.version}`;
|
|
26
|
+
const CONTEXT_EDGE_PREFIX = 'context-';
|
|
27
|
+
const domainGroupId = (domain: any) => `domain-group-${domain.data.id}-${domain.data.version}`;
|
|
28
|
+
const domainNodeId = (domain: any) => `domain-${domain.data.id}-${domain.data.version}`;
|
|
29
|
+
|
|
30
|
+
/** A system shown as a single node (not expanded into what's inside it) */
|
|
31
|
+
const createSystemNode = (system: any, parentId?: string) => ({
|
|
32
|
+
id: generateIdForNode(system),
|
|
33
|
+
type: 'systems',
|
|
34
|
+
parentId,
|
|
35
|
+
position: { x: 0, y: 0 },
|
|
36
|
+
data: {
|
|
37
|
+
mode: 'full',
|
|
38
|
+
system: {
|
|
39
|
+
id: system.data.id,
|
|
40
|
+
version: system.data.version,
|
|
41
|
+
name: system.data.name,
|
|
42
|
+
summary: system.data.summary,
|
|
43
|
+
scope: system.data.scope,
|
|
44
|
+
},
|
|
45
|
+
servicesCount: (system.data.services || []).length,
|
|
46
|
+
containersCount: (system.data.containers || []).length,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/** A domain shown as a single card, with counts of what's in it */
|
|
51
|
+
const createDomainCard = (domain: any, { parentId, subdomain = false }: { parentId?: string; subdomain?: boolean } = {}) => {
|
|
52
|
+
const systems = (domain.data.systems || []) as any[];
|
|
53
|
+
return {
|
|
54
|
+
id: domainNodeId(domain),
|
|
55
|
+
type: 'context-domain',
|
|
56
|
+
...(parentId ? { parentId } : {}),
|
|
57
|
+
position: { x: 0, y: 0 },
|
|
58
|
+
data: {
|
|
59
|
+
mode: 'full',
|
|
60
|
+
domain: {
|
|
61
|
+
id: domain.data.id,
|
|
62
|
+
version: domain.data.version,
|
|
63
|
+
name: domain.data.name,
|
|
64
|
+
summary: domain.data.summary,
|
|
65
|
+
},
|
|
66
|
+
subdomain,
|
|
67
|
+
systemsCount: systems.length,
|
|
68
|
+
servicesCount:
|
|
69
|
+
systems.reduce((count, system) => count + (system.data.services || []).length, 0) + (domain.data.services || []).length,
|
|
70
|
+
entitiesCount: (domain.data.entities || []).length,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Level 1: the domain with its subdomains and systems, its context (related
|
|
77
|
+
* systems and actors) and the other domains its systems send messages to or
|
|
78
|
+
* receive them from. Everything inside a system collapses into the system, and
|
|
79
|
+
* anything else in a (sub)domain (e.g. a service not in a system) into the
|
|
80
|
+
* domain, so edges connect domains, systems and actors. A subdomain without
|
|
81
|
+
* systems is shown as a card.
|
|
82
|
+
*/
|
|
83
|
+
export const collapseSystems = (
|
|
84
|
+
graph: Graph,
|
|
85
|
+
systems: any[],
|
|
86
|
+
contextNodes: Map<string, any>,
|
|
87
|
+
subdomains: Map<string, any>
|
|
88
|
+
): Graph => {
|
|
89
|
+
const nodesById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
90
|
+
const systemsByGroupId = new Map<string, any>(systems.map((system: any) => [systemGroupId(system), system]));
|
|
91
|
+
const ancestors = (nodeId: string) => {
|
|
92
|
+
const ids: string[] = [];
|
|
93
|
+
for (let node = nodesById.get(nodeId); node?.parentId; node = nodesById.get(node.parentId)) ids.push(node.parentId);
|
|
94
|
+
return ids;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Subdomains without systems (in them or their subdomains) become cards,
|
|
98
|
+
// along with everything in them
|
|
99
|
+
const withSystems = new Set([...systemsByGroupId.keys()].flatMap((groupId) => ancestors(groupId)));
|
|
100
|
+
const cards = new Map<string, any>();
|
|
101
|
+
subdomains.forEach((subdomain, groupId) => {
|
|
102
|
+
if (withSystems.has(groupId) || ancestors(groupId).some((id) => subdomains.has(id) && !withSystems.has(id))) return;
|
|
103
|
+
cards.set(groupId, createDomainCard(subdomain, { parentId: nodesById.get(groupId)?.parentId, subdomain: true }));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// The node something is shown as: its system, the card of the subdomain it's
|
|
107
|
+
// in, the (sub)domain it's in, or itself
|
|
108
|
+
const representative = (nodeId: string): string => {
|
|
109
|
+
for (let node = nodesById.get(nodeId); node; node = node.parentId ? nodesById.get(node.parentId) : undefined) {
|
|
110
|
+
const system: any = systemsByGroupId.get(node.id);
|
|
111
|
+
if (system) return generateIdForNode(system);
|
|
112
|
+
const card = cards.get(node.id);
|
|
113
|
+
if (card) return card.id;
|
|
114
|
+
if (node.type === 'domain-group' || node.type === 'context-domain') return node.id;
|
|
115
|
+
}
|
|
116
|
+
return nodeId;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const systemNodes = [...systemsByGroupId].map(([groupId, system]) => {
|
|
120
|
+
const parentId = nodesById.get(groupId)?.parentId;
|
|
121
|
+
// Prefer the node from the context diagram, it has the system's details
|
|
122
|
+
const contextNode = contextNodes.get(generateIdForNode(system));
|
|
123
|
+
return contextNode ? { ...contextNode, parentId } : createSystemNode(system, parentId);
|
|
124
|
+
});
|
|
125
|
+
const otherNodes = graph.nodes.filter(
|
|
126
|
+
(node) => representative(node.id) === node.id && !systemsByGroupId.has(node.id) && node.type !== 'data'
|
|
127
|
+
);
|
|
128
|
+
const shownNodes = new Map([...otherNodes, ...systemNodes, ...cards.values()].map((node) => [node.id, node]));
|
|
129
|
+
// Whether one shown node is inside another (e.g. a system in its domain)
|
|
130
|
+
const isInside = (nodeId: string, groupId: string) => {
|
|
131
|
+
for (let node = shownNodes.get(nodeId); node?.parentId; node = shownNodes.get(node.parentId)) {
|
|
132
|
+
if (node.parentId === groupId) return true;
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Edges between systems / actors / domains: context relationships as they
|
|
138
|
+
// are, edges to nodes kept as their first label, the rest labelled with the
|
|
139
|
+
// messages they carry
|
|
140
|
+
const connections = new Map<
|
|
141
|
+
string,
|
|
142
|
+
{ source: string; target: string; messages: Map<string, string>; relationship?: any; label?: string; crossDomain?: boolean }
|
|
143
|
+
>();
|
|
144
|
+
graph.edges.forEach((edge) => {
|
|
145
|
+
const source = representative(edge.source);
|
|
146
|
+
const target = representative(edge.target);
|
|
147
|
+
if (
|
|
148
|
+
source === target ||
|
|
149
|
+
!shownNodes.has(source) ||
|
|
150
|
+
!shownNodes.has(target) ||
|
|
151
|
+
isInside(source, target) ||
|
|
152
|
+
isInside(target, source)
|
|
153
|
+
)
|
|
154
|
+
return;
|
|
155
|
+
const key = `${source}->${target}`;
|
|
156
|
+
const connection = connections.get(key) ?? { source, target, messages: new Map<string, string>() };
|
|
157
|
+
if (edge.id.startsWith(CONTEXT_EDGE_PREFIX)) connection.relationship = edge;
|
|
158
|
+
if (edge.data?.crossDomain) connection.crossDomain = true;
|
|
159
|
+
[edge.source, edge.target].forEach((nodeId) => {
|
|
160
|
+
const node = nodesById.get(nodeId);
|
|
161
|
+
if (MESSAGE_TYPES.has(node?.type)) connection.messages.set(node.data?.message?.name ?? nodeId, node.type);
|
|
162
|
+
});
|
|
163
|
+
connections.set(key, connection);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// A relationship already connects the pair (either way), so skip messages
|
|
167
|
+
const related = new Set(
|
|
168
|
+
[...connections.values()]
|
|
169
|
+
.filter((c) => c.relationship)
|
|
170
|
+
.flatMap((c) => [`${c.source}->${c.target}`, `${c.target}->${c.source}`])
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
nodes: [...shownNodes.values()],
|
|
175
|
+
edges: [...connections.values()]
|
|
176
|
+
.filter((c) => c.relationship || !related.has(`${c.source}->${c.target}`))
|
|
177
|
+
.map(({ source, target, messages, relationship, label, crossDomain }) =>
|
|
178
|
+
relationship
|
|
179
|
+
? { ...relationship, source, target }
|
|
180
|
+
: createEdge({
|
|
181
|
+
id: `${source}->${target}`,
|
|
182
|
+
source,
|
|
183
|
+
target,
|
|
184
|
+
label: label ?? getMessagesLabel(messages),
|
|
185
|
+
...(crossDomain ? { data: { crossDomain } } : {}),
|
|
186
|
+
})
|
|
187
|
+
),
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The domain and its subdomains (and theirs), each drawn as a group inside the
|
|
193
|
+
* group of the domain it's a subdomain of.
|
|
194
|
+
*/
|
|
195
|
+
export const buildDomainTree = (domain: any, findDomain: (id: string, version?: string) => any): Tree => {
|
|
196
|
+
const tree: Tree = [];
|
|
197
|
+
const addToTree = (d: any, parentId?: string) => {
|
|
198
|
+
if (tree.some((entry) => entry.domain.data.id === d.data.id)) return;
|
|
199
|
+
tree.push({ domain: d, groupId: domainGroupId(d), parentId });
|
|
200
|
+
(d.data.domains || []).forEach((subdomain: any) => {
|
|
201
|
+
const found = findDomain(subdomain.data.id, subdomain.data.version);
|
|
202
|
+
if (found) addToTree(found, domainGroupId(d));
|
|
203
|
+
});
|
|
204
|
+
};
|
|
205
|
+
addToTree(domain);
|
|
206
|
+
return tree;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/** Which domain (and system) each service belongs to, and the domain each subdomain is in */
|
|
210
|
+
export const collectOwners = (domains: any[]) => {
|
|
211
|
+
const owners: Owners = new Map();
|
|
212
|
+
const parentDomainOf = new Map<string, any>();
|
|
213
|
+
domains.forEach((d: any) => {
|
|
214
|
+
(d.data.systems || []).forEach((system: any) =>
|
|
215
|
+
(system.data.services || []).forEach((service: any) => owners.set(generateIdForNode(service), { domain: d, system }))
|
|
216
|
+
);
|
|
217
|
+
(d.data.domains || []).forEach((subdomain: any) => {
|
|
218
|
+
if (!parentDomainOf.has(subdomain.data.id)) parentDomainOf.set(subdomain.data.id, d);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
// Services outside systems, unless a system already claims them
|
|
222
|
+
domains.forEach((d: any) =>
|
|
223
|
+
(d.data.services || []).forEach((service: any) => {
|
|
224
|
+
const serviceId = generateIdForNode(service);
|
|
225
|
+
if (!owners.has(serviceId)) owners.set(serviceId, { domain: d });
|
|
226
|
+
})
|
|
227
|
+
);
|
|
228
|
+
return { owners, parentDomainOf };
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* The domain's graph from the graphs of its (and its subdomains') systems and
|
|
233
|
+
* services outside systems: each node placed in its (sub)domain or system, and
|
|
234
|
+
* services in other domains replaced by those domains, as a card (or a box
|
|
235
|
+
* around their subdomains' cards). Returns the nodes, the edges (pointed at the
|
|
236
|
+
* replacements) and the nodes standing for other domains.
|
|
237
|
+
*/
|
|
238
|
+
export const buildDomainGraph = ({
|
|
239
|
+
tree,
|
|
240
|
+
owners,
|
|
241
|
+
parentDomainOf,
|
|
242
|
+
systems,
|
|
243
|
+
systemGraphs,
|
|
244
|
+
looseServices,
|
|
245
|
+
serviceGraphs,
|
|
246
|
+
mode,
|
|
247
|
+
}: {
|
|
248
|
+
tree: Tree;
|
|
249
|
+
owners: Owners;
|
|
250
|
+
parentDomainOf: Map<string, any>;
|
|
251
|
+
systems: { system: any; groupId: string }[];
|
|
252
|
+
systemGraphs: Graph[];
|
|
253
|
+
looseServices: { service: any; groupId: string }[];
|
|
254
|
+
serviceGraphs: Graph[];
|
|
255
|
+
mode: string;
|
|
256
|
+
}) => {
|
|
257
|
+
const groupOfDomain = new Map(tree.map((entry) => [entry.domain.data.id, entry.groupId]));
|
|
258
|
+
const groupOfSystem = new Map(systems.map(({ system, groupId }) => [systemGroupId(system), groupId]));
|
|
259
|
+
const nodes = new Map<string, any>(
|
|
260
|
+
tree.map(({ domain: d, groupId, parentId }) => [
|
|
261
|
+
groupId,
|
|
262
|
+
{
|
|
263
|
+
id: groupId,
|
|
264
|
+
type: 'domain-group',
|
|
265
|
+
position: { x: 0, y: 0 },
|
|
266
|
+
...(parentId ? { parentId } : {}),
|
|
267
|
+
draggable: false,
|
|
268
|
+
selectable: false,
|
|
269
|
+
data: {
|
|
270
|
+
domain: { name: d.data.name, version: d.data.version },
|
|
271
|
+
// The domain viewed may itself be a subdomain of another
|
|
272
|
+
subdomain: !!parentId || parentDomainOf.has(d.data.id),
|
|
273
|
+
isFocused: !parentId,
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
])
|
|
277
|
+
);
|
|
278
|
+
const edges: any[] = [];
|
|
279
|
+
// Nodes replaced by another (services in other domains become the domain)
|
|
280
|
+
const replacedBy = new Map<string, string>();
|
|
281
|
+
|
|
282
|
+
// Other domains are shown as a single card, with counts of what's in them. A
|
|
283
|
+
// subdomain of another domain is shown inside a box for that domain (and
|
|
284
|
+
// that inside its domain's, if it's a subdomain too)
|
|
285
|
+
const inParentDomain = (other: any) => {
|
|
286
|
+
const parent = parentDomainOf.get(other.data.id);
|
|
287
|
+
return parent && (groupOfDomain.get(parent.data.id) ?? addOtherDomainBox(parent));
|
|
288
|
+
};
|
|
289
|
+
// Cards replaced by a box, when a domain's subdomains are shown in it
|
|
290
|
+
const cardBoxes = new Map<string, string>();
|
|
291
|
+
const addOtherDomain = (other: any): string => {
|
|
292
|
+
const boxId = domainGroupId(other);
|
|
293
|
+
if (nodes.has(boxId)) return boxId;
|
|
294
|
+
const nodeId = domainNodeId(other);
|
|
295
|
+
if (!nodes.has(nodeId)) {
|
|
296
|
+
const parentId = inParentDomain(other);
|
|
297
|
+
const card = createDomainCard(other, { parentId, subdomain: !!parentId });
|
|
298
|
+
nodes.set(nodeId, { ...card, data: { ...card.data, otherDomain: true } });
|
|
299
|
+
}
|
|
300
|
+
return nodeId;
|
|
301
|
+
};
|
|
302
|
+
const addOtherDomainBox = (other: any): string => {
|
|
303
|
+
const boxId = domainGroupId(other);
|
|
304
|
+
if (!nodes.has(boxId)) {
|
|
305
|
+
const parentId = inParentDomain(other);
|
|
306
|
+
nodes.set(boxId, {
|
|
307
|
+
id: boxId,
|
|
308
|
+
type: 'domain-group',
|
|
309
|
+
position: { x: 0, y: 0 },
|
|
310
|
+
...(parentId ? { parentId } : {}),
|
|
311
|
+
data: {
|
|
312
|
+
domain: { name: other.data.name, version: other.data.version },
|
|
313
|
+
subdomain: !!parentId,
|
|
314
|
+
isFocused: false,
|
|
315
|
+
otherDomain: true,
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
// Shown as the box from now on, rather than a card as well
|
|
319
|
+
const cardId = domainNodeId(other);
|
|
320
|
+
if (nodes.has(cardId)) {
|
|
321
|
+
nodes.delete(cardId);
|
|
322
|
+
cardBoxes.set(cardId, boxId);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return boxId;
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
// Adds a graph's nodes: a system's (its nodes are in its group), or a service's
|
|
329
|
+
// outside any system, whose messages, channels and data stores go `inside`
|
|
330
|
+
// the (sub)domain it's in
|
|
331
|
+
const addNodes = (graphNodes: any[], inside?: string) =>
|
|
332
|
+
graphNodes.forEach((node: any) => {
|
|
333
|
+
if (node.type === 'system-group') {
|
|
334
|
+
nodes.set(node.id, {
|
|
335
|
+
...node,
|
|
336
|
+
parentId: groupOfSystem.get(node.id) ?? inside,
|
|
337
|
+
data: { ...node.data, isFocused: false },
|
|
338
|
+
});
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const owner = SERVICE_TYPES.has(node.type) ? owners.get(node.id) : undefined;
|
|
343
|
+
if (owner && !groupOfDomain.has(owner.domain.data.id)) {
|
|
344
|
+
replacedBy.set(node.id, addOtherDomain(owner.domain));
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (owner && owner.system) {
|
|
348
|
+
// Only placed by the system that owns it
|
|
349
|
+
if (systemGroupId(owner.system) === node.parentId) nodes.set(node.id, node);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (owner) {
|
|
353
|
+
// A service in the domain (or a subdomain), but not in any of its
|
|
354
|
+
// systems (not marked as the one being viewed, as it is in its own diagram)
|
|
355
|
+
nodes.set(node.id, {
|
|
356
|
+
...node,
|
|
357
|
+
parentId: groupOfDomain.get(owner.domain.data.id),
|
|
358
|
+
extent: undefined,
|
|
359
|
+
data: { ...node.data, isFocused: false },
|
|
360
|
+
});
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
// Messages, channels, data stores: kept in the first graph they appear in
|
|
364
|
+
if (nodes.has(node.id)) return;
|
|
365
|
+
nodes.set(node.id, node.parentId || !inside || SERVICE_TYPES.has(node.type) ? node : { ...node, parentId: inside });
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
systems.forEach(({ system, groupId: domainGroup }, index) => {
|
|
369
|
+
const graph = systemGraphs[index];
|
|
370
|
+
const groupId = systemGroupId(system);
|
|
371
|
+
// A system with nothing in it still shows as an (empty) system
|
|
372
|
+
if (!graph.nodes.some((node: any) => node.id === groupId)) {
|
|
373
|
+
nodes.set(groupId, {
|
|
374
|
+
id: groupId,
|
|
375
|
+
type: 'system-group',
|
|
376
|
+
parentId: domainGroup,
|
|
377
|
+
position: { x: 0, y: 0 },
|
|
378
|
+
data: { system: { name: system.data.name, version: system.data.version } },
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
addNodes(graph.nodes);
|
|
382
|
+
edges.push(...graph.edges);
|
|
383
|
+
});
|
|
384
|
+
serviceGraphs.forEach((graph, index) => {
|
|
385
|
+
addNodes(graph.nodes, looseServices[index].groupId);
|
|
386
|
+
edges.push(...graph.edges);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// Services outside systems that weren't in any graph
|
|
390
|
+
looseServices.forEach(({ service, groupId }) => {
|
|
391
|
+
const serviceId = generateIdForNode(service);
|
|
392
|
+
if (nodes.has(serviceId)) return;
|
|
393
|
+
nodes.set(serviceId, {
|
|
394
|
+
id: serviceId,
|
|
395
|
+
type: service.collection,
|
|
396
|
+
parentId: groupId,
|
|
397
|
+
position: { x: 0, y: 0 },
|
|
398
|
+
data: { mode, service: { ...service.data } },
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// Other domains shown as a box (around their subdomains) rather than a card
|
|
403
|
+
replacedBy.forEach((nodeId, replaced) => {
|
|
404
|
+
const boxId = cardBoxes.get(nodeId);
|
|
405
|
+
if (boxId) replacedBy.set(replaced, boxId);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
const graphEdges = connectEdges(edges, replacedBy, nodes);
|
|
409
|
+
placeMessagesWithPublishers(nodes, graphEdges);
|
|
410
|
+
return { nodes, edges: graphEdges, otherDomainIds: new Set(replacedBy.values()) };
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Messages are placed in the first graph they appear in, which can be a
|
|
415
|
+
* system consuming them. Moves each message into the system (or domain) of the
|
|
416
|
+
* services publishing it, when they're all in the same one.
|
|
417
|
+
*/
|
|
418
|
+
export const placeMessagesWithPublishers = (nodes: Map<string, any>, edges: any[]) => {
|
|
419
|
+
const publisherGroups = new Map<string, Set<string | undefined>>();
|
|
420
|
+
edges.forEach((edge) => {
|
|
421
|
+
const publisher = nodes.get(edge.source);
|
|
422
|
+
if (!MESSAGE_TYPES.has(nodes.get(edge.target)?.type) || !SERVICE_TYPES.has(publisher?.type)) return;
|
|
423
|
+
const groups = publisherGroups.get(edge.target) || new Set();
|
|
424
|
+
groups.add(publisher.parentId);
|
|
425
|
+
publisherGroups.set(edge.target, groups);
|
|
426
|
+
});
|
|
427
|
+
publisherGroups.forEach((groups, messageId) => {
|
|
428
|
+
const [parentId] = [...groups];
|
|
429
|
+
const message = nodes.get(messageId);
|
|
430
|
+
if (groups.size === 1 && parentId && message.parentId !== parentId) {
|
|
431
|
+
nodes.set(messageId, { ...message, parentId, extent: undefined });
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Edges pointed at the nodes replacing their ends, dropping duplicates, ones to
|
|
438
|
+
* nowhere and ones between a domain and a subdomain in it.
|
|
439
|
+
*/
|
|
440
|
+
export const connectEdges = (edges: any[], replacedBy: Map<string, string>, nodes: Map<string, any>) => {
|
|
441
|
+
const isInside = (nodeId: string, groupId: string) => {
|
|
442
|
+
for (let node = nodes.get(nodeId); node?.parentId; node = nodes.get(node.parentId)) {
|
|
443
|
+
if (node.parentId === groupId) return true;
|
|
444
|
+
}
|
|
445
|
+
return false;
|
|
446
|
+
};
|
|
447
|
+
const seen = new Set<string>();
|
|
448
|
+
return edges
|
|
449
|
+
.map((edge) => ({
|
|
450
|
+
...edge,
|
|
451
|
+
source: replacedBy.get(edge.source) ?? edge.source,
|
|
452
|
+
target: replacedBy.get(edge.target) ?? edge.target,
|
|
453
|
+
}))
|
|
454
|
+
.filter((edge) => {
|
|
455
|
+
const key = `${edge.source}->${edge.target}`;
|
|
456
|
+
if (
|
|
457
|
+
edge.source === edge.target ||
|
|
458
|
+
!nodes.has(edge.source) ||
|
|
459
|
+
!nodes.has(edge.target) ||
|
|
460
|
+
isInside(edge.source, edge.target) ||
|
|
461
|
+
isInside(edge.target, edge.source) ||
|
|
462
|
+
seen.has(key)
|
|
463
|
+
)
|
|
464
|
+
return false;
|
|
465
|
+
seen.add(key);
|
|
466
|
+
return true;
|
|
467
|
+
});
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* The context diagrams of the domain and its subdomains: relationships between
|
|
472
|
+
* their systems, and to related systems and actors outside them. Their systems
|
|
473
|
+
* are shown expanded, so relationships connect to the expanded systems.
|
|
474
|
+
*/
|
|
475
|
+
export const addContext = (contexts: Graph[], systems: { system: any }[], nodes: Map<string, any>) => {
|
|
476
|
+
const contextNodesById = new Map(contexts.flatMap((context) => context.nodes.map((node: any) => [node.id, node])));
|
|
477
|
+
const contextEdgesById = new Map(contexts.flatMap((context) => context.edges.map((edge: any) => [edge.id, edge])));
|
|
478
|
+
const domainSystemIds = new Set(systems.map(({ system }) => generateIdForNode(system)));
|
|
479
|
+
const toSystemGroup = (nodeId: string) => (domainSystemIds.has(nodeId) ? `system-group-${nodeId}` : nodeId);
|
|
480
|
+
return {
|
|
481
|
+
contextNodesById,
|
|
482
|
+
nodes: [...contextNodesById.values()].filter((node: any) => !domainSystemIds.has(node.id) && !nodes.has(node.id)),
|
|
483
|
+
edges: [...contextEdgesById.values()].map((edge: any) => ({
|
|
484
|
+
...edge,
|
|
485
|
+
id: `${CONTEXT_EDGE_PREFIX}${edge.id}`,
|
|
486
|
+
source: toSystemGroup(edge.source),
|
|
487
|
+
target: toSystemGroup(edge.target),
|
|
488
|
+
})),
|
|
489
|
+
};
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Marks cross-domain communication. Messages and channels only other domains
|
|
494
|
+
* send to (e.g. a message this domain consumes, and the topic it comes
|
|
495
|
+
* through) are another domain's: they sit outside this domain, between the
|
|
496
|
+
* domain sending them and their consumers, rather than in the system that
|
|
497
|
+
* consumes them. They, and this domain's messages and channels that reach
|
|
498
|
+
* other domains, are on a cross-domain path (`data.crossDomain`), as are the
|
|
499
|
+
* edges to and from them and other domains.
|
|
500
|
+
*/
|
|
501
|
+
export const markCrossDomain = (graph: Graph, otherDomainIds: Set<string>): Graph => {
|
|
502
|
+
const sendersOf = new Map<string, string[]>();
|
|
503
|
+
const receiversOf = new Map<string, string[]>();
|
|
504
|
+
graph.edges.forEach((edge) => {
|
|
505
|
+
const senders = sendersOf.get(edge.target) || [];
|
|
506
|
+
senders.push(edge.source);
|
|
507
|
+
sendersOf.set(edge.target, senders);
|
|
508
|
+
const receivers = receiversOf.get(edge.source) || [];
|
|
509
|
+
receivers.push(edge.target);
|
|
510
|
+
receiversOf.set(edge.source, receivers);
|
|
511
|
+
});
|
|
512
|
+
const carriers = graph.nodes.filter(isCarrier);
|
|
513
|
+
|
|
514
|
+
// Along chains (message -> channel -> channel), until nothing more is found
|
|
515
|
+
const spread = (reached: Set<string>, isReached: (nodeId: string, reached: Set<string>) => boolean) => {
|
|
516
|
+
for (let found = true; found; ) {
|
|
517
|
+
found = false;
|
|
518
|
+
carriers.forEach((node) => {
|
|
519
|
+
if (!reached.has(node.id) && isReached(node.id, reached)) {
|
|
520
|
+
reached.add(node.id);
|
|
521
|
+
found = true;
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
return reached;
|
|
526
|
+
};
|
|
527
|
+
// Sent only by other domains (or their messages and channels)
|
|
528
|
+
const external = spread(new Set(otherDomainIds), (nodeId, reached) => {
|
|
529
|
+
const senders = sendersOf.get(nodeId) || [];
|
|
530
|
+
return senders.length > 0 && senders.every((sender) => reached.has(sender));
|
|
531
|
+
});
|
|
532
|
+
// Reaching other domains (directly, or through messages and channels that do)
|
|
533
|
+
const outgoing = spread(new Set(external), (nodeId, reached) =>
|
|
534
|
+
(receiversOf.get(nodeId) || []).some((receiver) => reached.has(receiver))
|
|
535
|
+
);
|
|
536
|
+
|
|
537
|
+
const isExternalCarrier = (node: any) => external.has(node.id) && !otherDomainIds.has(node.id);
|
|
538
|
+
const onCrossDomainPath = (nodeId: string) => outgoing.has(nodeId);
|
|
539
|
+
return {
|
|
540
|
+
nodes: graph.nodes.map((node) => {
|
|
541
|
+
if (isExternalCarrier(node)) {
|
|
542
|
+
return {
|
|
543
|
+
...node,
|
|
544
|
+
parentId: undefined,
|
|
545
|
+
extent: undefined,
|
|
546
|
+
data: { ...node.data, otherDomain: true, crossDomain: true },
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
return isCarrier(node) && onCrossDomainPath(node.id) ? { ...node, data: { ...node.data, crossDomain: true } } : node;
|
|
550
|
+
}),
|
|
551
|
+
edges: graph.edges.map((edge) =>
|
|
552
|
+
onCrossDomainPath(edge.source) || onCrossDomainPath(edge.target)
|
|
553
|
+
? { ...edge, data: { ...edge.data, crossDomain: true } }
|
|
554
|
+
: edge
|
|
555
|
+
),
|
|
556
|
+
};
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Level 3: the domain with its subdomains (as domains inside it) and systems
|
|
561
|
+
* expanded into their services, data stores, messages and channels. Services in
|
|
562
|
+
* other domains are shown as those domains. Returns level 1 (`overview`, when
|
|
563
|
+
* the domain has systems or subdomains) and level 2 (`hiddenMessages`) too,
|
|
564
|
+
* built from the same graph.
|
|
565
|
+
*/
|
|
566
|
+
type DomainLevels = Graph & { overview?: Graph; hiddenMessages: Graph };
|
|
567
|
+
|
|
568
|
+
// Built once for each domain version and options: the Diagram page and every
|
|
569
|
+
// docs page embedding the domain share it
|
|
570
|
+
const levelsCache = new Map<string, Promise<DomainLevels>>();
|
|
571
|
+
|
|
572
|
+
export const getNodesAndEdges = (props: NodesAndEdgesProps): Promise<DomainLevels> => {
|
|
573
|
+
if (!CACHE_ENABLED) return buildLevels(props);
|
|
574
|
+
const { id, version, mode = 'full', channelRenderMode } = props;
|
|
575
|
+
const key = [id, version, mode, channelRenderMode].join('/');
|
|
576
|
+
if (!levelsCache.has(key)) levelsCache.set(key, buildLevels(props));
|
|
577
|
+
return levelsCache.get(key)!;
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
const buildLevels = async ({ id, version, mode = 'full', channelRenderMode }: NodesAndEdgesProps): Promise<DomainLevels> => {
|
|
581
|
+
// Each domain with only its own services (not its subdomains'): the version
|
|
582
|
+
// shown (which may be an older one), and the latest of every domain for its
|
|
583
|
+
// subdomains and the other domains around it
|
|
584
|
+
const [allVersions, domains] = await Promise.all([
|
|
585
|
+
getDomains({ getAllVersions: true, includeServicesInSubdomains: false }),
|
|
586
|
+
getDomains({ getAllVersions: false, includeServicesInSubdomains: false }),
|
|
587
|
+
]);
|
|
588
|
+
const domain = allVersions.find((d) => d.data.id === id && d.data.version === version);
|
|
589
|
+
if (!domain) return { nodes: [], edges: [], hiddenMessages: { nodes: [], edges: [] } };
|
|
590
|
+
// Subdomains' versions in frontmatter can be loose, so fall back to the latest
|
|
591
|
+
const findDomain = (domainId: string, domainVersion?: string) =>
|
|
592
|
+
domains.find((d) => d.data.id === domainId && d.data.version === domainVersion) ??
|
|
593
|
+
domains.find((d) => d.data.id === domainId);
|
|
594
|
+
|
|
595
|
+
const tree = buildDomainTree(domain, findDomain);
|
|
596
|
+
// The tree's domains last, so the versions shown own their services
|
|
597
|
+
const { owners, parentDomainOf } = collectOwners([...domains, ...tree.map((entry) => entry.domain)]);
|
|
598
|
+
// A service the domain (or a subdomain) lists itself is shown as its own,
|
|
599
|
+
// even when a system in another domain has it too
|
|
600
|
+
const treeDomainIds = new Set(tree.map((entry) => entry.domain.data.id));
|
|
601
|
+
tree.forEach(({ domain: d }) =>
|
|
602
|
+
(d.data.services || []).forEach((service: any) => {
|
|
603
|
+
const serviceId = generateIdForNode(service);
|
|
604
|
+
if (!treeDomainIds.has(owners.get(serviceId)?.domain.data.id)) owners.set(serviceId, { domain: d });
|
|
605
|
+
})
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
// Every system in the domain and its subdomains, and the group each is in,
|
|
609
|
+
// and the services in them that aren't in any system
|
|
610
|
+
const systems = tree.flatMap(({ domain: d, groupId }) =>
|
|
611
|
+
((d.data.systems || []) as any[]).map((system) => ({ system, groupId }))
|
|
612
|
+
);
|
|
613
|
+
const looseServices = tree.flatMap(({ domain: d, groupId }) =>
|
|
614
|
+
((d.data.services || []) as any[])
|
|
615
|
+
.filter((service) => !owners.get(generateIdForNode(service))?.system)
|
|
616
|
+
.map((service) => ({ service, groupId }))
|
|
617
|
+
);
|
|
618
|
+
const [systemGraphs, serviceGraphs, contexts] = await Promise.all([
|
|
619
|
+
Promise.all(
|
|
620
|
+
systems.map(({ system }) =>
|
|
621
|
+
getSystemNodesAndEdges({
|
|
622
|
+
id: system.data.id,
|
|
623
|
+
version: system.data.version,
|
|
624
|
+
mode,
|
|
625
|
+
channelRenderMode,
|
|
626
|
+
wrapInSystemGroup: true,
|
|
627
|
+
layout: false,
|
|
628
|
+
})
|
|
629
|
+
)
|
|
630
|
+
),
|
|
631
|
+
Promise.all(
|
|
632
|
+
looseServices.map(({ service }) =>
|
|
633
|
+
getServiceNodesAndEdges({
|
|
634
|
+
id: service.data.id,
|
|
635
|
+
version: service.data.version,
|
|
636
|
+
mode,
|
|
637
|
+
renderAllEdges: true,
|
|
638
|
+
channelRenderMode,
|
|
639
|
+
collection: service.collection === 'agents' ? 'agents' : 'services',
|
|
640
|
+
layout: false,
|
|
641
|
+
})
|
|
642
|
+
)
|
|
643
|
+
),
|
|
644
|
+
Promise.all(
|
|
645
|
+
tree.map(({ domain: d }) => getDomainContextNodesAndEdges({ id: d.data.id, version: d.data.version, mode, layout: false }))
|
|
646
|
+
),
|
|
647
|
+
]);
|
|
648
|
+
|
|
649
|
+
const { nodes, edges, otherDomainIds } = buildDomainGraph({
|
|
650
|
+
tree,
|
|
651
|
+
owners,
|
|
652
|
+
parentDomainOf,
|
|
653
|
+
systems,
|
|
654
|
+
systemGraphs,
|
|
655
|
+
looseServices,
|
|
656
|
+
serviceGraphs,
|
|
657
|
+
mode,
|
|
658
|
+
});
|
|
659
|
+
const context = addContext(contexts, systems, nodes);
|
|
660
|
+
const graph = { nodes: [...nodes.values(), ...context.nodes], edges: [...edges, ...context.edges] };
|
|
661
|
+
const detailedGraph = markCrossDomain(graph, otherDomainIds);
|
|
662
|
+
|
|
663
|
+
// Level 1 (domains, systems and their relationships) collapses each system,
|
|
664
|
+
// including the messages its services consume. Messages outside systems (sent
|
|
665
|
+
// by services not in one) are hidden too, with the nodes either side connected
|
|
666
|
+
// directly. A domain without systems or subdomains has no level 1.
|
|
667
|
+
if (systems.length === 0 && tree.length === 1) return layoutLevels(detailedGraph);
|
|
668
|
+
// Built from the graph before messages only other domains send were moved
|
|
669
|
+
// out of the systems consuming them, so they collapse into those systems
|
|
670
|
+
const collapsed = collapseSystems(
|
|
671
|
+
{ ...graph, edges: detailedGraph.edges },
|
|
672
|
+
systems.map(({ system }) => system),
|
|
673
|
+
context.contextNodesById,
|
|
674
|
+
new Map(tree.filter((entry) => entry.parentId).map((entry) => [entry.groupId, entry.domain]))
|
|
675
|
+
);
|
|
676
|
+
const overview = hideMessageNodes(collapsed.nodes, collapsed.edges);
|
|
677
|
+
|
|
678
|
+
return layoutLevels(detailedGraph, overview);
|
|
679
|
+
};
|