@eventcatalog/core 3.7.1 → 3.8.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/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/{chunk-44EDP7IH.js → chunk-4EJDLNIX.js} +1 -1
- package/dist/{chunk-MIHGWXCX.js → chunk-EG36OTR7.js} +1 -1
- package/dist/{chunk-BVJJ3COQ.js → chunk-GITARDPK.js} +1 -1
- package/dist/{chunk-PV5ER42D.js → chunk-IEEU454Z.js} +1 -1
- package/dist/{chunk-B6NBNZGS.js → chunk-ZIG6J4R2.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.js +5 -5
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +13 -1
- package/eventcatalog/src/components/Grids/DomainGrid.tsx +109 -6
- package/eventcatalog/src/components/Grids/utils.tsx +10 -1
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +2 -0
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.tsx +4 -0
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/DataProduct.tsx +132 -0
- package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +29 -2
- package/eventcatalog/src/components/SchemaExplorer/types.ts +5 -1
- package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +3 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/utils.ts +1 -0
- package/eventcatalog/src/components/Tables/Discover/DiscoverTable.tsx +23 -1
- package/eventcatalog/src/components/Tables/Discover/columns.tsx +62 -0
- package/eventcatalog/src/content.config.ts +34 -0
- package/eventcatalog/src/enterprise/ai/chat-api.ts +26 -0
- package/eventcatalog/src/enterprise/custom-documentation/utils/custom-docs.ts +1 -1
- package/eventcatalog/src/enterprise/tools/catalog-tools.ts +169 -2
- package/eventcatalog/src/pages/discover/[type]/_index.data.ts +5 -1
- package/eventcatalog/src/pages/discover/[type]/index.astro +57 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/_index.data.ts +1 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +5 -1
- package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/_index.data.ts +27 -3
- package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/index.astro +74 -25
- package/eventcatalog/src/pages/schemas/explorer/_index.data.ts +107 -1
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +10 -1
- package/eventcatalog/src/stores/sidebar-store/builders/container.ts +23 -16
- package/eventcatalog/src/stores/sidebar-store/builders/data-product.ts +130 -0
- package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +51 -0
- package/eventcatalog/src/stores/sidebar-store/state.ts +68 -13
- package/eventcatalog/src/styles/theme.css +4 -0
- package/eventcatalog/src/styles/themes/forest.css +4 -0
- package/eventcatalog/src/styles/themes/ocean.css +4 -0
- package/eventcatalog/src/styles/themes/sapphire.css +4 -0
- package/eventcatalog/src/styles/themes/sunset.css +4 -0
- package/eventcatalog/src/types/index.ts +4 -2
- package/eventcatalog/src/utils/collections/commands.ts +11 -29
- package/eventcatalog/src/utils/collections/containers.ts +25 -1
- package/eventcatalog/src/utils/collections/data-products.ts +85 -0
- package/eventcatalog/src/utils/collections/domains.ts +33 -11
- package/eventcatalog/src/utils/collections/events.ts +11 -29
- package/eventcatalog/src/utils/collections/icons.ts +5 -0
- package/eventcatalog/src/utils/collections/messages.ts +68 -0
- package/eventcatalog/src/utils/collections/queries.ts +11 -29
- package/eventcatalog/src/utils/collections/services.ts +2 -26
- package/eventcatalog/src/utils/collections/util.ts +75 -2
- package/eventcatalog/src/utils/node-graphs/container-node-graph.ts +91 -3
- package/eventcatalog/src/utils/node-graphs/data-products-node-graph.ts +225 -0
- package/eventcatalog/src/utils/node-graphs/domains-node-graph.ts +28 -2
- package/eventcatalog/src/utils/node-graphs/message-node-graph.ts +74 -20
- package/eventcatalog/src/utils/page-loaders/page-data-loader.ts +2 -0
- package/package.json +2 -2
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { getCollection } from 'astro:content';
|
|
2
2
|
import type { CollectionEntry } from 'astro:content';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { createVersionedMap
|
|
4
|
+
import { createVersionedMap } from './util';
|
|
5
|
+
import { hydrateProducersAndConsumers } from './messages';
|
|
5
6
|
import utils from '@eventcatalog/sdk';
|
|
6
7
|
|
|
7
8
|
const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
|
|
@@ -35,10 +36,11 @@ export const getEvents = async ({ getAllVersions = true, hydrateServices = true
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
// 1. Fetch collections in parallel
|
|
38
|
-
const [allEvents, allServices, allChannels] = await Promise.all([
|
|
39
|
+
const [allEvents, allServices, allChannels, allDataProducts] = await Promise.all([
|
|
39
40
|
getCollection('events'),
|
|
40
41
|
getCollection('services'),
|
|
41
42
|
getCollection('channels'),
|
|
43
|
+
getCollection('data-products'),
|
|
42
44
|
]);
|
|
43
45
|
|
|
44
46
|
// 2. Build optimized maps
|
|
@@ -64,33 +66,13 @@ export const getEvents = async ({ getAllVersions = true, hydrateServices = true
|
|
|
64
66
|
const latestVersion = eventVersions[0]?.data.version || event.data.version;
|
|
65
67
|
const versions = eventVersions.map((e) => e.data.version);
|
|
66
68
|
|
|
67
|
-
// Find
|
|
68
|
-
const producers =
|
|
69
|
-
.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
})
|
|
75
|
-
)
|
|
76
|
-
.map((service) => {
|
|
77
|
-
if (!hydrateServices) return { id: service.data.id, version: service.data.version };
|
|
78
|
-
return service;
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
// Find Consumers (Services that receive this event)
|
|
82
|
-
const consumers = allServices
|
|
83
|
-
.filter((service) =>
|
|
84
|
-
service.data.receives?.some((item) => {
|
|
85
|
-
if (item.id !== event.data.id) return false;
|
|
86
|
-
if (item.version === 'latest' || item.version === undefined) return event.data.version === latestVersion;
|
|
87
|
-
return satisfies(event.data.version, item.version);
|
|
88
|
-
})
|
|
89
|
-
)
|
|
90
|
-
.map((service) => {
|
|
91
|
-
if (!hydrateServices) return { id: service.data.id, version: service.data.version };
|
|
92
|
-
return service;
|
|
93
|
-
});
|
|
69
|
+
// Find producers and consumers (services + data products)
|
|
70
|
+
const { producers, consumers } = hydrateProducersAndConsumers({
|
|
71
|
+
message: { data: { ...event.data, latestVersion } },
|
|
72
|
+
services: allServices,
|
|
73
|
+
dataProducts: allDataProducts,
|
|
74
|
+
hydrate: hydrateServices,
|
|
75
|
+
});
|
|
94
76
|
|
|
95
77
|
// Find Channels
|
|
96
78
|
const messageChannels = event.data.channels || [];
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
ArrowsRightLeftIcon,
|
|
11
11
|
VariableIcon,
|
|
12
12
|
MapIcon,
|
|
13
|
+
CubeIcon,
|
|
13
14
|
} from '@heroicons/react/24/outline';
|
|
14
15
|
import { BookText, Box, DatabaseIcon } from 'lucide-react';
|
|
15
16
|
|
|
@@ -43,6 +44,8 @@ export const getIconForCollection = (collection: string) => {
|
|
|
43
44
|
return Box;
|
|
44
45
|
case 'containers':
|
|
45
46
|
return DatabaseIcon;
|
|
47
|
+
case 'data-products':
|
|
48
|
+
return CubeIcon;
|
|
46
49
|
default:
|
|
47
50
|
return ServerIcon;
|
|
48
51
|
}
|
|
@@ -74,6 +77,8 @@ export const getColorAndIconForCollection = (collection: string) => {
|
|
|
74
77
|
return { color: 'yellow', Icon: icon };
|
|
75
78
|
case 'services':
|
|
76
79
|
return { color: 'pink', Icon: icon };
|
|
80
|
+
case 'data-products':
|
|
81
|
+
return { color: 'cyan', Icon: icon };
|
|
77
82
|
default:
|
|
78
83
|
return { color: 'gray', Icon: icon };
|
|
79
84
|
}
|
|
@@ -3,6 +3,7 @@ import { getCommands } from '@utils/collections/commands';
|
|
|
3
3
|
import { getEvents } from '@utils/collections/events';
|
|
4
4
|
import { getQueries } from './queries';
|
|
5
5
|
import type { CollectionEntry } from 'astro:content';
|
|
6
|
+
import { satisfies } from './util';
|
|
6
7
|
export { getCommands } from '@utils/collections/commands';
|
|
7
8
|
export { getEvents } from '@utils/collections/events';
|
|
8
9
|
|
|
@@ -11,6 +12,69 @@ interface Props {
|
|
|
11
12
|
hydrateServices?: boolean;
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
interface HydrateProducersAndConsumersProps {
|
|
16
|
+
message: {
|
|
17
|
+
data: {
|
|
18
|
+
id: string;
|
|
19
|
+
version: string;
|
|
20
|
+
latestVersion?: string;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
services: CollectionEntry<'services'>[];
|
|
24
|
+
dataProducts: CollectionEntry<'data-products'>[];
|
|
25
|
+
hydrate?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Hydrates producers and consumers for a message (event, command, or query).
|
|
30
|
+
* Finds services and data products that produce or consume the given message.
|
|
31
|
+
*/
|
|
32
|
+
export const hydrateProducersAndConsumers = ({
|
|
33
|
+
message,
|
|
34
|
+
services = [],
|
|
35
|
+
dataProducts = [],
|
|
36
|
+
hydrate = true,
|
|
37
|
+
}: HydrateProducersAndConsumersProps) => {
|
|
38
|
+
const { id: messageId, version: messageVersion, latestVersion = messageVersion } = message.data;
|
|
39
|
+
|
|
40
|
+
const matchesVersion = (pointerVersion: string | undefined) => {
|
|
41
|
+
if (pointerVersion === 'latest' || pointerVersion === undefined) {
|
|
42
|
+
return messageVersion === latestVersion;
|
|
43
|
+
}
|
|
44
|
+
return satisfies(messageVersion, pointerVersion);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const toResult = <T extends CollectionEntry<'services'> | CollectionEntry<'data-products'>>(resource: T) => {
|
|
48
|
+
if (!hydrate) return { id: resource.data.id, version: resource.data.version };
|
|
49
|
+
return resource;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Services that send this message (producers)
|
|
53
|
+
const serviceProducers = services
|
|
54
|
+
.filter((s) => s.data.sends?.some((p) => p.id === messageId && matchesVersion(p.version)))
|
|
55
|
+
.map(toResult);
|
|
56
|
+
|
|
57
|
+
// Services that receive this message (consumers)
|
|
58
|
+
const serviceConsumers = services
|
|
59
|
+
.filter((s) => s.data.receives?.some((p) => p.id === messageId && matchesVersion(p.version)))
|
|
60
|
+
.map(toResult);
|
|
61
|
+
|
|
62
|
+
// Data products that output this message (producers)
|
|
63
|
+
const dataProductProducers = dataProducts
|
|
64
|
+
.filter((dp) => dp.data.outputs?.some((p) => p.id === messageId && matchesVersion(p.version)))
|
|
65
|
+
.map(toResult);
|
|
66
|
+
|
|
67
|
+
// Data products that input this message (consumers)
|
|
68
|
+
const dataProductConsumers = dataProducts
|
|
69
|
+
.filter((dp) => dp.data.inputs?.some((p) => p.id === messageId && matchesVersion(p.version)))
|
|
70
|
+
.map(toResult);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
producers: [...serviceProducers, ...dataProductProducers],
|
|
74
|
+
consumers: [...serviceConsumers, ...dataProductConsumers],
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
|
|
14
78
|
type Messages = {
|
|
15
79
|
commands: CollectionEntry<'commands'>[];
|
|
16
80
|
events: CollectionEntry<'events'>[];
|
|
@@ -40,3 +104,7 @@ export const getMessages = async ({ getAllVersions = true, hydrateServices = tru
|
|
|
40
104
|
queries,
|
|
41
105
|
};
|
|
42
106
|
};
|
|
107
|
+
|
|
108
|
+
export const isCollectionAMessage = (collection: string): boolean => {
|
|
109
|
+
return ['events', 'commands', 'queries'].includes(collection);
|
|
110
|
+
};
|
|
@@ -2,7 +2,8 @@ import { getCollection } from 'astro:content';
|
|
|
2
2
|
import type { CollectionEntry } from 'astro:content';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import utils from '@eventcatalog/sdk';
|
|
5
|
-
import { createVersionedMap
|
|
5
|
+
import { createVersionedMap } from './util';
|
|
6
|
+
import { hydrateProducersAndConsumers } from './messages';
|
|
6
7
|
|
|
7
8
|
const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
|
|
8
9
|
const CACHE_ENABLED = process.env.DISABLE_EVENTCATALOG_CACHE !== 'true';
|
|
@@ -34,10 +35,11 @@ export const getQueries = async ({ getAllVersions = true, hydrateServices = true
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
// 1. Fetch collections in parallel
|
|
37
|
-
const [allQueries, allServices, allChannels] = await Promise.all([
|
|
38
|
+
const [allQueries, allServices, allChannels, allDataProducts] = await Promise.all([
|
|
38
39
|
getCollection('queries'),
|
|
39
40
|
getCollection('services'),
|
|
40
41
|
getCollection('channels'),
|
|
42
|
+
getCollection('data-products'),
|
|
41
43
|
]);
|
|
42
44
|
|
|
43
45
|
// 2. Build optimized maps
|
|
@@ -60,33 +62,13 @@ export const getQueries = async ({ getAllVersions = true, hydrateServices = true
|
|
|
60
62
|
const latestVersion = queryVersions[0]?.data.version || query.data.version;
|
|
61
63
|
const versions = queryVersions.map((e) => e.data.version);
|
|
62
64
|
|
|
63
|
-
// Find
|
|
64
|
-
const producers =
|
|
65
|
-
.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
})
|
|
71
|
-
)
|
|
72
|
-
.map((service) => {
|
|
73
|
-
if (!hydrateServices) return { id: service.data.id, version: service.data.version };
|
|
74
|
-
return service;
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// Find Consumers (Services that receive this query)
|
|
78
|
-
const consumers = allServices
|
|
79
|
-
.filter((service) =>
|
|
80
|
-
service.data.receives?.some((item) => {
|
|
81
|
-
if (item.id !== query.data.id) return false;
|
|
82
|
-
if (item.version === 'latest' || item.version === undefined) return query.data.version === latestVersion;
|
|
83
|
-
return satisfies(query.data.version, item.version);
|
|
84
|
-
})
|
|
85
|
-
)
|
|
86
|
-
.map((service) => {
|
|
87
|
-
if (!hydrateServices) return { id: service.data.id, version: service.data.version };
|
|
88
|
-
return service;
|
|
89
|
-
});
|
|
65
|
+
// Find producers and consumers (services + data products)
|
|
66
|
+
const { producers, consumers } = hydrateProducersAndConsumers({
|
|
67
|
+
message: { data: { ...query.data, latestVersion } },
|
|
68
|
+
services: allServices,
|
|
69
|
+
dataProducts: allDataProducts,
|
|
70
|
+
hydrate: hydrateServices,
|
|
71
|
+
});
|
|
90
72
|
|
|
91
73
|
// Find Channels
|
|
92
74
|
const messageChannels = query.data.channels || [];
|
|
@@ -6,7 +6,7 @@ import type { CollectionMessageTypes, CollectionTypes } from '@types';
|
|
|
6
6
|
const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
|
|
7
7
|
import utils, { type Domain } from '@eventcatalog/sdk';
|
|
8
8
|
import { getDomains, getDomainsForService } from './domains';
|
|
9
|
-
import { createVersionedMap, findInMap } from '@utils/collections/util';
|
|
9
|
+
import { createVersionedMap, findInMap, processSpecifications } from '@utils/collections/util';
|
|
10
10
|
|
|
11
11
|
export type Service = CollectionEntry<'services'>;
|
|
12
12
|
|
|
@@ -174,31 +174,7 @@ export const getConsumersOfMessage = (services: Service[], message: CollectionEn
|
|
|
174
174
|
};
|
|
175
175
|
|
|
176
176
|
export const getSpecificationsForService = (service: CollectionEntry<CollectionTypes>) => {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (service.data.specifications && !Array.isArray(service.data.specifications)) {
|
|
180
|
-
if (service.data.specifications.asyncapiPath) {
|
|
181
|
-
specifications.push({
|
|
182
|
-
type: 'asyncapi',
|
|
183
|
-
path: service.data.specifications.asyncapiPath,
|
|
184
|
-
name: 'AsyncAPI',
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
if (service.data.specifications.openapiPath) {
|
|
188
|
-
specifications.push({
|
|
189
|
-
type: 'openapi',
|
|
190
|
-
path: service.data.specifications.openapiPath,
|
|
191
|
-
name: 'OpenAPI',
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return specifications.map((spec) => ({
|
|
197
|
-
...spec,
|
|
198
|
-
name: spec.name || (spec.type === 'asyncapi' ? 'AsyncAPI' : 'OpenAPI'),
|
|
199
|
-
filename: path.basename(spec.path),
|
|
200
|
-
filenameWithoutExtension: path.basename(spec.path, path.extname(spec.path)),
|
|
201
|
-
}));
|
|
177
|
+
return processSpecifications(service.data.specifications as any);
|
|
202
178
|
};
|
|
203
179
|
// Get services for channel
|
|
204
180
|
export const getProducersAndConsumersForChannel = async (channel: CollectionEntry<'channels'>) => {
|
|
@@ -1,6 +1,70 @@
|
|
|
1
1
|
import type { CollectionTypes } from '@types';
|
|
2
2
|
import type { CollectionEntry } from 'astro:content';
|
|
3
3
|
import semver, { coerce, compare, eq, satisfies as satisfiesRange } from 'semver';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
// --- SPECIFICATION HELPERS ---
|
|
7
|
+
|
|
8
|
+
export type SpecificationType = 'asyncapi' | 'openapi' | 'graphql';
|
|
9
|
+
|
|
10
|
+
export interface SpecificationInput {
|
|
11
|
+
type: SpecificationType;
|
|
12
|
+
path: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ProcessedSpecification {
|
|
17
|
+
type: SpecificationType;
|
|
18
|
+
path: string;
|
|
19
|
+
name: string;
|
|
20
|
+
filename: string;
|
|
21
|
+
filenameWithoutExtension: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const getDefaultSpecificationName = (type: string): string => {
|
|
25
|
+
switch (type) {
|
|
26
|
+
case 'asyncapi':
|
|
27
|
+
return 'AsyncAPI';
|
|
28
|
+
case 'openapi':
|
|
29
|
+
return 'OpenAPI';
|
|
30
|
+
case 'graphql':
|
|
31
|
+
return 'GraphQL';
|
|
32
|
+
default:
|
|
33
|
+
return 'Specification';
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
interface LegacySpecificationFormat {
|
|
38
|
+
asyncapiPath?: string;
|
|
39
|
+
openapiPath?: string;
|
|
40
|
+
graphqlPath?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const processSpecifications = (
|
|
44
|
+
specifications: SpecificationInput[] | LegacySpecificationFormat | undefined
|
|
45
|
+
): ProcessedSpecification[] => {
|
|
46
|
+
const specs: SpecificationInput[] = Array.isArray(specifications) ? [...specifications] : [];
|
|
47
|
+
|
|
48
|
+
// Handle legacy object format
|
|
49
|
+
if (specifications && !Array.isArray(specifications)) {
|
|
50
|
+
if (specifications.asyncapiPath) {
|
|
51
|
+
specs.push({ type: 'asyncapi', path: specifications.asyncapiPath, name: 'AsyncAPI' });
|
|
52
|
+
}
|
|
53
|
+
if (specifications.openapiPath) {
|
|
54
|
+
specs.push({ type: 'openapi', path: specifications.openapiPath, name: 'OpenAPI' });
|
|
55
|
+
}
|
|
56
|
+
if (specifications.graphqlPath) {
|
|
57
|
+
specs.push({ type: 'graphql', path: specifications.graphqlPath, name: 'GraphQL' });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return specs.map((spec) => ({
|
|
62
|
+
...spec,
|
|
63
|
+
name: spec.name || getDefaultSpecificationName(spec.type),
|
|
64
|
+
filename: path.basename(spec.path),
|
|
65
|
+
filenameWithoutExtension: path.basename(spec.path, path.extname(spec.path)),
|
|
66
|
+
}));
|
|
67
|
+
};
|
|
4
68
|
|
|
5
69
|
export const getPreviousVersion = (version: string, versions: string[]) => {
|
|
6
70
|
const index = versions.indexOf(version);
|
|
@@ -94,8 +158,8 @@ export const getItemsFromCollectionByIdAndSemverOrLatest = <T extends { data: {
|
|
|
94
158
|
};
|
|
95
159
|
|
|
96
160
|
export const findMatchingNodes = (
|
|
97
|
-
nodesA: CollectionEntry<'events' | 'commands' | 'queries' | 'services' | 'containers'>[],
|
|
98
|
-
nodesB: CollectionEntry<'events' | 'commands' | 'queries' | 'services' | 'containers'>[]
|
|
161
|
+
nodesA: CollectionEntry<'events' | 'commands' | 'queries' | 'services' | 'containers' | 'data-products'>[],
|
|
162
|
+
nodesB: CollectionEntry<'events' | 'commands' | 'queries' | 'services' | 'containers' | 'data-products'>[]
|
|
99
163
|
) => {
|
|
100
164
|
// Track messages that are both sent and received
|
|
101
165
|
return nodesA.filter((nodeA) => {
|
|
@@ -118,6 +182,7 @@ export const resourceToCollectionMap = {
|
|
|
118
182
|
container: 'containers',
|
|
119
183
|
entity: 'entities',
|
|
120
184
|
diagram: 'diagrams',
|
|
185
|
+
'data-product': 'data-products',
|
|
121
186
|
} as const;
|
|
122
187
|
|
|
123
188
|
export const collectionToResourceMap = {
|
|
@@ -133,6 +198,7 @@ export const collectionToResourceMap = {
|
|
|
133
198
|
containers: 'container',
|
|
134
199
|
entities: 'entity',
|
|
135
200
|
diagrams: 'diagram',
|
|
201
|
+
'data-products': 'data-product',
|
|
136
202
|
} as const;
|
|
137
203
|
|
|
138
204
|
export const getDeprecatedDetails = (item: CollectionEntry<CollectionTypes>) => {
|
|
@@ -197,6 +263,13 @@ export const createVersionedMap = <T extends { data: { id: string; version?: str
|
|
|
197
263
|
return map;
|
|
198
264
|
};
|
|
199
265
|
|
|
266
|
+
// Merge as many given maps as you want
|
|
267
|
+
export const mergeMaps = <T>(...maps: Map<string, T[]>[]): Map<string, T[]> => {
|
|
268
|
+
return maps.reduce((acc, map) => {
|
|
269
|
+
return new Map([...acc, ...map]);
|
|
270
|
+
}, new Map<string, T[]>());
|
|
271
|
+
};
|
|
272
|
+
|
|
200
273
|
/**
|
|
201
274
|
* Fast lookup helper.
|
|
202
275
|
* If version is provided, find it. If not, return the first (latest) item.
|
|
@@ -37,9 +37,14 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
|
|
|
37
37
|
|
|
38
38
|
const servicesThatWriteToContainer = (container.data.servicesThatWriteToContainer as CollectionEntry<'services'>[]) || [];
|
|
39
39
|
const servicesThatReadFromContainer = (container.data.servicesThatReadFromContainer as CollectionEntry<'services'>[]) || [];
|
|
40
|
+
const dataProductsThatWriteToContainer =
|
|
41
|
+
(container.data.dataProductsThatWriteToContainer as CollectionEntry<'data-products'>[]) || [];
|
|
42
|
+
const dataProductsThatReadFromContainer =
|
|
43
|
+
(container.data.dataProductsThatReadFromContainer as CollectionEntry<'data-products'>[]) || [];
|
|
40
44
|
|
|
41
|
-
// Track nodes that are
|
|
45
|
+
// Track nodes that are both sent and received
|
|
42
46
|
const bothSentAndReceived = findMatchingNodes(servicesThatWriteToContainer, servicesThatReadFromContainer);
|
|
47
|
+
const dataProductsBothSentAndReceived = findMatchingNodes(dataProductsThatWriteToContainer, dataProductsThatReadFromContainer);
|
|
43
48
|
|
|
44
49
|
servicesThatWriteToContainer.forEach((service) => {
|
|
45
50
|
nodes.push({
|
|
@@ -67,7 +72,34 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
|
|
|
67
72
|
}
|
|
68
73
|
});
|
|
69
74
|
|
|
70
|
-
//
|
|
75
|
+
// Data products that write to the container
|
|
76
|
+
dataProductsThatWriteToContainer.forEach((dataProduct) => {
|
|
77
|
+
nodes.push({
|
|
78
|
+
id: generateIdForNode(dataProduct),
|
|
79
|
+
type: 'data-products',
|
|
80
|
+
sourcePosition: 'right',
|
|
81
|
+
targetPosition: 'left',
|
|
82
|
+
data: { mode, dataProduct: { ...dataProduct.data } },
|
|
83
|
+
position: { x: 250, y: 0 },
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (!dataProductsBothSentAndReceived.includes(dataProduct)) {
|
|
87
|
+
edges.push({
|
|
88
|
+
id: generatedIdForEdge(dataProduct, container),
|
|
89
|
+
source: generateIdForNode(dataProduct),
|
|
90
|
+
target: generateIdForNode(container),
|
|
91
|
+
label: 'writes to',
|
|
92
|
+
data: { dataProduct },
|
|
93
|
+
animated: false,
|
|
94
|
+
type: 'default',
|
|
95
|
+
style: {
|
|
96
|
+
strokeWidth: 1,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// The container itself
|
|
71
103
|
nodes.push({
|
|
72
104
|
id: generateIdForNode(container),
|
|
73
105
|
sourcePosition: 'right',
|
|
@@ -114,7 +146,38 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
|
|
|
114
146
|
}
|
|
115
147
|
});
|
|
116
148
|
|
|
117
|
-
//
|
|
149
|
+
// Data products that read from the container
|
|
150
|
+
dataProductsThatReadFromContainer.forEach((dataProduct) => {
|
|
151
|
+
nodes.push({
|
|
152
|
+
id: generateIdForNode(dataProduct),
|
|
153
|
+
sourcePosition: 'left',
|
|
154
|
+
targetPosition: 'right',
|
|
155
|
+
data: { title: dataProduct?.data.id, mode, dataProduct: { ...dataProduct.data } },
|
|
156
|
+
position: { x: 0, y: 0 },
|
|
157
|
+
type: 'data-products',
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (!dataProductsBothSentAndReceived.includes(dataProduct)) {
|
|
161
|
+
edges.push(
|
|
162
|
+
createEdge({
|
|
163
|
+
id: generatedIdForEdge(dataProduct, container),
|
|
164
|
+
source: generateIdForNode(container),
|
|
165
|
+
target: generateIdForNode(dataProduct),
|
|
166
|
+
label: `reads from \n (${container.data.technology})`,
|
|
167
|
+
data: { dataProduct },
|
|
168
|
+
type: 'multiline',
|
|
169
|
+
markerStart: {
|
|
170
|
+
type: MarkerType.ArrowClosed,
|
|
171
|
+
width: 40,
|
|
172
|
+
height: 40,
|
|
173
|
+
},
|
|
174
|
+
markerEnd: undefined,
|
|
175
|
+
})
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Handle services that are both sent and received
|
|
118
181
|
bothSentAndReceived.forEach((_service) => {
|
|
119
182
|
if (container) {
|
|
120
183
|
edges.push(
|
|
@@ -139,6 +202,31 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
|
|
|
139
202
|
}
|
|
140
203
|
});
|
|
141
204
|
|
|
205
|
+
// Handle data products that both read and write
|
|
206
|
+
dataProductsBothSentAndReceived.forEach((_dataProduct) => {
|
|
207
|
+
if (container) {
|
|
208
|
+
edges.push(
|
|
209
|
+
createEdge({
|
|
210
|
+
id: generatedIdForEdge(container, _dataProduct) + '-both',
|
|
211
|
+
source: generateIdForNode(_dataProduct),
|
|
212
|
+
target: generateIdForNode(container),
|
|
213
|
+
label: `read and writes to \n (${container.data.technology})`,
|
|
214
|
+
type: 'multiline',
|
|
215
|
+
markerStart: {
|
|
216
|
+
type: MarkerType.ArrowClosed,
|
|
217
|
+
width: 40,
|
|
218
|
+
height: 40,
|
|
219
|
+
},
|
|
220
|
+
markerEnd: {
|
|
221
|
+
type: MarkerType.ArrowClosed,
|
|
222
|
+
width: 40,
|
|
223
|
+
height: 40,
|
|
224
|
+
},
|
|
225
|
+
})
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
142
230
|
nodes.forEach((node: any) => {
|
|
143
231
|
flow.setNode(node.id, { width: 150, height: 100 });
|
|
144
232
|
});
|