@eventcatalog/core 5.7.4 → 5.7.5
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-PXYVJK54.js → chunk-3I4MO4G6.js} +1 -1
- package/dist/{chunk-RUG7AIT5.js → chunk-NNR5RP47.js} +1 -1
- package/dist/{chunk-GOIDVIMH.js → chunk-Q6ZCTZM3.js} +1 -1
- package/dist/{chunk-PLTHZFTW.js → chunk-SAB3WVWU.js} +1 -1
- package/dist/{chunk-L2PFSX4L.js → chunk-SR6NZ4QL.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/docs/development/guides/domains/06-visualize-domains/01-domain-diagram.md +2 -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/features/mcp/apps/generated/viewer.html +68 -68
- package/eventcatalog/src/utils/node-graphs/channel-node-graph.ts +2 -2
- package/eventcatalog/src/utils/node-graphs/domain-levels-node-graph.ts +150 -57
- package/eventcatalog/src/utils/node-graphs/message-node-graph.ts +0 -87
- package/eventcatalog/src/utils/node-graphs/systems-node-graph.ts +10 -24
- package/package.json +4 -4
|
@@ -85,13 +85,13 @@ export const getNodesAndEdgesForChannelChain = ({
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
// Connect the last channel to the target
|
|
88
|
+
// Connect the last channel to the target, which accepts a command or query (it handles them) and consumes an event
|
|
89
89
|
edges.push(
|
|
90
90
|
createEdge({
|
|
91
91
|
id: generatedIdForEdge(channelChain[channelChain.length - 1], target),
|
|
92
92
|
source: generateIdForNode(channelChain[channelChain.length - 1]),
|
|
93
93
|
target: generateIdForNode(target),
|
|
94
|
-
label: 'consumes',
|
|
94
|
+
label: ['commands', 'queries'].includes(source.collection) ? 'accepts' : 'consumes',
|
|
95
95
|
data: { customColor: getColorFromString(source.data.id) },
|
|
96
96
|
})
|
|
97
97
|
);
|
|
@@ -29,6 +29,8 @@ type Tree = { domain: any; groupId: string; parentId?: string }[];
|
|
|
29
29
|
type Owners = Map<string, { domain: any; system?: any }>;
|
|
30
30
|
|
|
31
31
|
const MESSAGE_TYPES = new Set(['events', 'commands', 'queries']);
|
|
32
|
+
// Commands and queries belong to what receives them (it handles them), as events do to what sends them
|
|
33
|
+
const RECEIVER_OWNED_TYPES = new Set(['commands', 'queries']);
|
|
32
34
|
const SERVICE_TYPES = new Set(['services', 'agents']);
|
|
33
35
|
|
|
34
36
|
const systemGroupId = (system: any) => `system-group-${system.data.id}-${system.data.version}`;
|
|
@@ -44,6 +46,45 @@ const isInside = (nodes: Map<string, any>, nodeId: string, groupId: string) => {
|
|
|
44
46
|
return false;
|
|
45
47
|
};
|
|
46
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Whether a node (or the services another domain stands for, `receivedBy`) says
|
|
51
|
+
* it receives a message: its id, and its version (or range, or latest). Undefined
|
|
52
|
+
* when it doesn't say what it receives
|
|
53
|
+
*/
|
|
54
|
+
const saysItReceives = (node: any, message: any, receivedBy?: any[]): boolean | undefined => {
|
|
55
|
+
const receives = (node?.data?.service ?? node?.data?.agent)?.receives ?? receivedBy;
|
|
56
|
+
if (!receives) return undefined;
|
|
57
|
+
const { id, version } = message?.data?.message ?? {};
|
|
58
|
+
return receives.some((pointer: any) => {
|
|
59
|
+
const received = pointer.data ?? pointer;
|
|
60
|
+
return received.id === id && versionMatches(version ?? '', received.version ?? 'latest');
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* What a message reaches, through any channels (not the channels themselves).
|
|
66
|
+
* Past a channel, only what doesn't say it receives something else: a channel
|
|
67
|
+
* can carry other messages, to consumers of their own
|
|
68
|
+
*/
|
|
69
|
+
const receiversThroughChannels = (messageId: string, nodes: Map<string, any>, receiversOf: Map<string, string[]>) => {
|
|
70
|
+
const message = nodes.get(messageId);
|
|
71
|
+
const receivers: string[] = [];
|
|
72
|
+
const seen = new Set([messageId]);
|
|
73
|
+
for (let current = [messageId]; current.length > 0; ) {
|
|
74
|
+
const throughChannel = current[0] !== messageId;
|
|
75
|
+
const next = current.flatMap((nodeId) => receiversOf.get(nodeId) ?? []).filter((nodeId) => !seen.has(nodeId));
|
|
76
|
+
next.forEach((nodeId) => seen.add(nodeId));
|
|
77
|
+
receivers.push(
|
|
78
|
+
...next.filter(
|
|
79
|
+
(nodeId) =>
|
|
80
|
+
nodes.get(nodeId)?.type !== 'channels' && (!throughChannel || saysItReceives(nodes.get(nodeId), message) !== false)
|
|
81
|
+
)
|
|
82
|
+
);
|
|
83
|
+
current = next.filter((nodeId) => nodes.get(nodeId)?.type === 'channels');
|
|
84
|
+
}
|
|
85
|
+
return receivers;
|
|
86
|
+
};
|
|
87
|
+
|
|
47
88
|
/** A system shown as a single node (not expanded into what's inside it) */
|
|
48
89
|
const createSystemNode = (system: any, parentId?: string) => ({
|
|
49
90
|
id: generateIdForNode(system),
|
|
@@ -181,16 +222,8 @@ export const collapseSystems = (
|
|
|
181
222
|
isMessage(sender) ? [sender] : isChannel(sender) ? messagesThrough(sender, seen) : []
|
|
182
223
|
);
|
|
183
224
|
};
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const receives = (nodeId: string, messageId: string) => {
|
|
187
|
-
const data = nodesById.get(nodeId)?.data;
|
|
188
|
-
const message = nodesById.get(messageId)?.data?.message;
|
|
189
|
-
return ((data?.service ?? data?.agent)?.receives ?? receivedBy.get(nodeId) ?? []).some((pointer: any) => {
|
|
190
|
-
const { id, version } = pointer.data ?? pointer;
|
|
191
|
-
return id === message?.id && versionMatches(message?.version ?? '', version ?? 'latest');
|
|
192
|
-
});
|
|
193
|
-
};
|
|
225
|
+
const receives = (nodeId: string, messageId: string) =>
|
|
226
|
+
saysItReceives(nodesById.get(nodeId), nodesById.get(messageId), receivedBy.get(nodeId)) ?? false;
|
|
194
227
|
|
|
195
228
|
// Where an edge is from and the messages it carries. A channel is one node for
|
|
196
229
|
// all its messages, so an edge out of one is from each message sent through it
|
|
@@ -279,7 +312,7 @@ export const buildDomainTree = (domain: any, findDomain: (id: string, version?:
|
|
|
279
312
|
return tree;
|
|
280
313
|
};
|
|
281
314
|
|
|
282
|
-
/** Which domain (and system) each service belongs to, and the domain each subdomain is in */
|
|
315
|
+
/** Which domain (and system) each service or agent belongs to, and the domain each subdomain is in */
|
|
283
316
|
export const collectOwners = (domains: any[]) => {
|
|
284
317
|
const owners: Owners = new Map();
|
|
285
318
|
const parentDomainOf = new Map<string, any>();
|
|
@@ -291,9 +324,9 @@ export const collectOwners = (domains: any[]) => {
|
|
|
291
324
|
if (!parentDomainOf.has(subdomain.data.id)) parentDomainOf.set(subdomain.data.id, d);
|
|
292
325
|
});
|
|
293
326
|
});
|
|
294
|
-
// Services outside systems, unless a system already claims them
|
|
327
|
+
// Services outside systems, unless a system already claims them, and agents (systems don't have them)
|
|
295
328
|
domains.forEach((d: any) =>
|
|
296
|
-
(d.data.services || []).forEach((service: any) => {
|
|
329
|
+
[...(d.data.services || []), ...(d.data.agents || [])].forEach((service: any) => {
|
|
297
330
|
const serviceId = generateIdForNode(service);
|
|
298
331
|
if (!owners.has(serviceId)) owners.set(serviceId, { domain: d });
|
|
299
332
|
})
|
|
@@ -444,7 +477,12 @@ export const buildDomainGraph = ({
|
|
|
444
477
|
}
|
|
445
478
|
// Messages, channels, data stores: kept in the first graph they appear in
|
|
446
479
|
if (nodes.has(node.id)) return;
|
|
447
|
-
|
|
480
|
+
// A service or agent in no domain isn't in the system whose graph it came from
|
|
481
|
+
if (SERVICE_TYPES.has(node.type)) {
|
|
482
|
+
nodes.set(node.id, { ...node, parentId: undefined, extent: undefined });
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
nodes.set(node.id, node.parentId || !inside ? node : { ...node, parentId: inside });
|
|
448
486
|
});
|
|
449
487
|
|
|
450
488
|
systems.forEach(({ system, groupId: domainGroup }, index) => {
|
|
@@ -498,8 +536,8 @@ export const buildDomainGraph = ({
|
|
|
498
536
|
if (boxId) replacedBy.set(replaced, boxId);
|
|
499
537
|
});
|
|
500
538
|
|
|
501
|
-
const graphEdges = connectEdges(edges, new Map([...cardBoxes, ...replacedBy]), nodes);
|
|
502
|
-
|
|
539
|
+
const graphEdges = dropReceivesThroughChannels(connectEdges(edges, new Map([...cardBoxes, ...replacedBy]), nodes), nodes);
|
|
540
|
+
placeMessagesWithOwners(nodes, graphEdges, new Set(domainMessages.keys()));
|
|
503
541
|
// What the services each other domain stands for receive, by the node shown for the domain
|
|
504
542
|
const receivedBy = new Map<string, any[]>();
|
|
505
543
|
replacedBy.forEach((nodeId, replaced) =>
|
|
@@ -546,14 +584,6 @@ const addDomainMessages = ({
|
|
|
546
584
|
const treeDomainIds = new Set(tree.map((entry) => entry.domain.data.id));
|
|
547
585
|
const otherDomainIds = new Set<string>();
|
|
548
586
|
const domainMessages = new Map<string, Set<string>>();
|
|
549
|
-
// The domain each agent is in (owners only has services): the tree's versions first
|
|
550
|
-
const agentOwners = new Map<string, any>();
|
|
551
|
-
[...tree.map((entry) => entry.domain), ...domains].forEach((domain) =>
|
|
552
|
-
(domain.data.agents || []).forEach((agent: any) => {
|
|
553
|
-
const agentId = generateIdForNode(agent);
|
|
554
|
-
if (!agentOwners.has(agentId)) agentOwners.set(agentId, domain);
|
|
555
|
-
})
|
|
556
|
-
);
|
|
557
587
|
// Whether a domain or service sends (or receives) the message (their messages are already looked up)
|
|
558
588
|
const lists = (resource: any, key: 'sends' | 'receives', messageId: string) =>
|
|
559
589
|
(resource.data[key] || []).some((message: any) => message.data && generateIdForNode(message) === messageId);
|
|
@@ -579,19 +609,19 @@ const addDomainMessages = ({
|
|
|
579
609
|
return false;
|
|
580
610
|
};
|
|
581
611
|
// Whether a service in a (sub)domain's box says it sends (or receives) the message: e.g. one it sends in a
|
|
582
|
-
// message group, which its graph shows as the group rather than the message. Services only:
|
|
583
|
-
//
|
|
612
|
+
// message group, which its graph shows as the group rather than the message. Services only: a domain's
|
|
613
|
+
// agents are only drawn when another graph brings them in
|
|
584
614
|
const groupOfTreeDomain = new Map(tree.map((entry) => [entry.domain.data.id, entry.groupId]));
|
|
585
615
|
const declaredIn = (groupId: string, key: 'sends' | 'receives', messageId: string) =>
|
|
586
616
|
catalogServices.some((service) => {
|
|
587
|
-
if (!lists(service, key, messageId)) return false;
|
|
617
|
+
if (service.collection === 'agents' || !lists(service, key, messageId)) return false;
|
|
588
618
|
const ownerGroup = groupOfTreeDomain.get(owners.get(generateIdForNode(service))?.domain.data.id);
|
|
589
619
|
return !!ownerGroup && (ownerGroup === groupId || isInside(nodes, ownerGroup, groupId));
|
|
590
620
|
});
|
|
591
621
|
// The node standing for a service (or agent) on the other side, or none when it's in the domain
|
|
592
622
|
const otherSide = (service: any) => {
|
|
593
623
|
const serviceId = generateIdForNode(service);
|
|
594
|
-
const ownerDomain = owners.get(serviceId)?.domain
|
|
624
|
+
const ownerDomain = owners.get(serviceId)?.domain;
|
|
595
625
|
if (ownerDomain) return treeDomainIds.has(ownerDomain.data.id) ? undefined : addOther(ownerDomain);
|
|
596
626
|
if (!nodes.has(serviceId)) {
|
|
597
627
|
nodes.set(serviceId, {
|
|
@@ -675,25 +705,36 @@ const addDomainMessages = ({
|
|
|
675
705
|
/**
|
|
676
706
|
* Messages are placed in the first graph they appear in, which can be a
|
|
677
707
|
* system consuming them. Moves each message into the system (or domain) of the
|
|
678
|
-
* services
|
|
679
|
-
*
|
|
708
|
+
* services it belongs to, when they're all in the same one: an event's
|
|
709
|
+
* publishers, a command's or query's receivers (through any channels). Each
|
|
710
|
+
* channel goes with the messages sent through it. Messages a domain
|
|
680
711
|
* sends or receives itself (`kept`) stay outside its box, where its edge to them
|
|
681
712
|
* is drawn.
|
|
682
713
|
*/
|
|
683
|
-
export const
|
|
684
|
-
const
|
|
714
|
+
export const placeMessagesWithOwners = (nodes: Map<string, any>, edges: any[], kept: Set<string> = new Set()) => {
|
|
715
|
+
const ownerGroups = new Map<string, Set<string | undefined>>();
|
|
716
|
+
const addOwner = (messageId: string, owner: any) =>
|
|
717
|
+
ownerGroups.set(messageId, (ownerGroups.get(messageId) ?? new Set()).add(owner.parentId));
|
|
718
|
+
const receiversOf = new Map<string, string[]>();
|
|
719
|
+
edges.forEach((edge) => receiversOf.set(edge.source, [...(receiversOf.get(edge.source) ?? []), edge.target]));
|
|
685
720
|
edges.forEach((edge) => {
|
|
686
721
|
const publisher = nodes.get(edge.source);
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
722
|
+
const type = nodes.get(edge.target)?.type;
|
|
723
|
+
if (!MESSAGE_TYPES.has(type) || RECEIVER_OWNED_TYPES.has(type) || !SERVICE_TYPES.has(publisher?.type)) return;
|
|
724
|
+
addOwner(edge.target, publisher);
|
|
725
|
+
});
|
|
726
|
+
nodes.forEach((message, messageId) => {
|
|
727
|
+
if (!RECEIVER_OWNED_TYPES.has(message.type)) return;
|
|
728
|
+
receiversThroughChannels(messageId, nodes, receiversOf)
|
|
729
|
+
.filter((nodeId) => SERVICE_TYPES.has(nodes.get(nodeId)?.type))
|
|
730
|
+
.forEach((nodeId) => addOwner(messageId, nodes.get(nodeId)));
|
|
691
731
|
});
|
|
692
|
-
|
|
732
|
+
ownerGroups.forEach((groups, messageId) => {
|
|
693
733
|
if (kept.has(messageId)) return;
|
|
694
734
|
const [parentId] = [...groups];
|
|
695
735
|
const message = nodes.get(messageId);
|
|
696
|
-
|
|
736
|
+
// Owners are drawn nodes, so no parent means outside every box (e.g. a service or agent in no domain)
|
|
737
|
+
if (groups.size === 1 && message.parentId !== parentId) {
|
|
697
738
|
nodes.set(messageId, { ...message, parentId, extent: undefined });
|
|
698
739
|
}
|
|
699
740
|
});
|
|
@@ -705,9 +746,10 @@ export const placeMessagesWithPublishers = (nodes: Map<string, any>, edges: any[
|
|
|
705
746
|
moved = false;
|
|
706
747
|
nodes.forEach((channel, channelId) => {
|
|
707
748
|
if (channel.type !== 'channels' || kept.has(channelId)) return;
|
|
708
|
-
const
|
|
749
|
+
const senders = (sendersOf.get(channelId) ?? []).filter((sender) => nodes.has(sender));
|
|
750
|
+
const groups = new Set(senders.map((sender) => nodes.get(sender).parentId));
|
|
709
751
|
const [parentId] = [...groups];
|
|
710
|
-
if (groups.size === 1 &&
|
|
752
|
+
if (groups.size === 1 && channel.parentId !== parentId) {
|
|
711
753
|
nodes.set(channelId, { ...channel, parentId, extent: undefined });
|
|
712
754
|
moved = true;
|
|
713
755
|
}
|
|
@@ -715,6 +757,27 @@ export const placeMessagesWithPublishers = (nodes: Map<string, any>, edges: any[
|
|
|
715
757
|
}
|
|
716
758
|
};
|
|
717
759
|
|
|
760
|
+
/**
|
|
761
|
+
* Drops the edges drawn for domains receiving a message themselves (`domain-receives-`)
|
|
762
|
+
* when the message already reaches them through its channels, e.g. another
|
|
763
|
+
* domain listing a command in its `receives` whose service receives it from a
|
|
764
|
+
* queue: the domain is drawn receiving it from the queue, not from the command too.
|
|
765
|
+
*/
|
|
766
|
+
const dropReceivesThroughChannels = (edges: any[], nodes: Map<string, any>) => {
|
|
767
|
+
const isChannel = (nodeId: string) => nodes.get(nodeId)?.type === 'channels';
|
|
768
|
+
const reachesThroughChannels = (messageId: string, targetId: string) => {
|
|
769
|
+
const seen = new Set<string>();
|
|
770
|
+
for (let current = [messageId]; current.length > 0; ) {
|
|
771
|
+
const next = edges.filter((edge) => current.includes(edge.source) && isChannel(edge.target) && !seen.has(edge.target));
|
|
772
|
+
if (next.some((edge) => edges.some((out) => out.source === edge.target && out.target === targetId))) return true;
|
|
773
|
+
next.forEach((edge) => seen.add(edge.target));
|
|
774
|
+
current = next.map((edge) => edge.target);
|
|
775
|
+
}
|
|
776
|
+
return false;
|
|
777
|
+
};
|
|
778
|
+
return edges.filter((edge) => !edge.id.startsWith('domain-receives-') || !reachesThroughChannels(edge.source, edge.target));
|
|
779
|
+
};
|
|
780
|
+
|
|
718
781
|
/**
|
|
719
782
|
* Edges pointed at the nodes replacing their ends, dropping duplicates, ones to
|
|
720
783
|
* nowhere and ones between a domain and a subdomain in it.
|
|
@@ -766,15 +829,19 @@ export const addContext = (contexts: Graph[], systems: { system: any }[], nodes:
|
|
|
766
829
|
};
|
|
767
830
|
|
|
768
831
|
/**
|
|
769
|
-
* Marks cross-domain communication.
|
|
770
|
-
* send to (e.g.
|
|
832
|
+
* Marks cross-domain communication. Events and channels only other domains
|
|
833
|
+
* send to (e.g. an event this domain consumes, and the topic it comes
|
|
771
834
|
* through) are another domain's: they sit outside this domain, between the
|
|
772
835
|
* domain sending them and their consumers, rather than in the system that
|
|
773
|
-
* consumes them.
|
|
836
|
+
* consumes them. Commands and queries belong to what receives them, so those
|
|
837
|
+
* (and the channels only they're sent through) only other domains receive are
|
|
838
|
+
* another domain's, and those this domain receives are its own, whoever sends
|
|
839
|
+
* them. Messages and channels other domains send, and this domain's that reach
|
|
774
840
|
* other domains, are on a cross-domain path (`data.crossDomain`), as are the
|
|
775
841
|
* edges to and from them and other domains.
|
|
776
842
|
*/
|
|
777
843
|
export const markCrossDomain = (graph: Graph, otherDomainIds: Set<string>): Graph => {
|
|
844
|
+
const nodesById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
778
845
|
const sendersOf = new Map<string, string[]>();
|
|
779
846
|
const receiversOf = new Map<string, string[]>();
|
|
780
847
|
graph.edges.forEach((edge) => {
|
|
@@ -801,16 +868,45 @@ export const markCrossDomain = (graph: Graph, otherDomainIds: Set<string>): Grap
|
|
|
801
868
|
return reached;
|
|
802
869
|
};
|
|
803
870
|
// Sent only by other domains (or their messages and channels)
|
|
804
|
-
const
|
|
871
|
+
const sentByOthers = spread(new Set(otherDomainIds), (nodeId, reached) => {
|
|
805
872
|
const senders = sendersOf.get(nodeId) || [];
|
|
806
873
|
return senders.length > 0 && senders.every((sender) => reached.has(sender));
|
|
807
874
|
});
|
|
875
|
+
// Received only by other domains (or their messages and channels)
|
|
876
|
+
const receivedByOthers = spread(new Set(otherDomainIds), (nodeId, reached) => {
|
|
877
|
+
const receivers = receiversOf.get(nodeId) || [];
|
|
878
|
+
return receivers.length > 0 && receivers.every((receiver) => reached.has(receiver));
|
|
879
|
+
});
|
|
808
880
|
// Reaching other domains (directly, or through messages and channels that do)
|
|
809
|
-
const outgoing = spread(new Set(
|
|
881
|
+
const outgoing = spread(new Set(sentByOthers), (nodeId, reached) =>
|
|
810
882
|
(receiversOf.get(nodeId) || []).some((receiver) => reached.has(receiver))
|
|
811
883
|
);
|
|
812
884
|
|
|
813
|
-
|
|
885
|
+
// A command or query, or a channel only they're sent through (by channel, worked out once: routes can split and
|
|
886
|
+
// join again)
|
|
887
|
+
const receiverOwnedChannels = new Map<string, boolean>();
|
|
888
|
+
const isReceiverOwned = (nodeId: string): boolean => {
|
|
889
|
+
const node = nodesById.get(nodeId);
|
|
890
|
+
if (MESSAGE_TYPES.has(node?.type)) return RECEIVER_OWNED_TYPES.has(node.type);
|
|
891
|
+
if (node?.type !== 'channels') return false;
|
|
892
|
+
if (!receiverOwnedChannels.has(nodeId)) {
|
|
893
|
+
// Not the receiver's while it's being worked out, so a loop of channels ends
|
|
894
|
+
receiverOwnedChannels.set(nodeId, false);
|
|
895
|
+
const carried = (sendersOf.get(nodeId) || []).filter((sender) => nodesById.has(sender) && isCarrier(nodesById.get(sender)));
|
|
896
|
+
receiverOwnedChannels.set(nodeId, carried.length > 0 && carried.every(isReceiverOwned));
|
|
897
|
+
}
|
|
898
|
+
return receiverOwnedChannels.get(nodeId)!;
|
|
899
|
+
};
|
|
900
|
+
// A command or query only other domains receive (not counting consumers of other messages on its channels)
|
|
901
|
+
const receivedOnlyByOthers = (messageId: string) => {
|
|
902
|
+
const receivers = receiversThroughChannels(messageId, nodesById, receiversOf);
|
|
903
|
+
return receivers.length > 0 && receivers.every((receiver) => otherDomainIds.has(receiver));
|
|
904
|
+
};
|
|
905
|
+
const isExternalCarrier = (node: any) => {
|
|
906
|
+
if (!isCarrier(node) || otherDomainIds.has(node.id)) return false;
|
|
907
|
+
if (RECEIVER_OWNED_TYPES.has(node.type)) return receivedOnlyByOthers(node.id);
|
|
908
|
+
return (isReceiverOwned(node.id) ? receivedByOthers : sentByOthers).has(node.id);
|
|
909
|
+
};
|
|
814
910
|
const onCrossDomainPath = (nodeId: string) => outgoing.has(nodeId);
|
|
815
911
|
return {
|
|
816
912
|
nodes: graph.nodes.map((node) => {
|
|
@@ -858,11 +954,8 @@ export const getNodesAndEdges = (props: NodesAndEdgesProps): Promise<DomainLevel
|
|
|
858
954
|
* or its latest when no domain has it: who sends or receives a domain's own
|
|
859
955
|
* messages is matched against these.
|
|
860
956
|
*/
|
|
861
|
-
const inVersionsUsed = (resources: any[],
|
|
862
|
-
const used = new Set(
|
|
863
|
-
...owners.keys(),
|
|
864
|
-
...domains.flatMap((domain) => (domain.data.agents || []).map((agent: any) => generateIdForNode(agent))),
|
|
865
|
-
]);
|
|
957
|
+
const inVersionsUsed = (resources: any[], owners: Owners) => {
|
|
958
|
+
const used = new Set(owners.keys());
|
|
866
959
|
const key = (resource: any) => `${resource.collection}:${resource.data.id}`;
|
|
867
960
|
const usedKeys = new Set(resources.filter((resource) => used.has(generateIdForNode(resource))).map(key));
|
|
868
961
|
return resources.filter((resource) =>
|
|
@@ -892,11 +985,11 @@ const buildLevels = async ({ id, version, mode = 'full', channelRenderMode }: No
|
|
|
892
985
|
const tree = buildDomainTree(domain, findDomain);
|
|
893
986
|
// The tree's domains last, so the versions shown own their services
|
|
894
987
|
const { owners, parentDomainOf } = collectOwners([...domains, ...tree.map((entry) => entry.domain)]);
|
|
895
|
-
// A service the domain
|
|
896
|
-
// even when a system in another domain has it too
|
|
988
|
+
// A service (or agent) the domain or a subdomain lists itself is shown as its
|
|
989
|
+
// own, even when a system in another domain has it too
|
|
897
990
|
const treeDomainIds = new Set(tree.map((entry) => entry.domain.data.id));
|
|
898
991
|
tree.forEach(({ domain: d }) =>
|
|
899
|
-
(d.data.services || []).forEach((service: any) => {
|
|
992
|
+
[...(d.data.services || []), ...(d.data.agents || [])].forEach((service: any) => {
|
|
900
993
|
const serviceId = generateIdForNode(service);
|
|
901
994
|
if (!treeDomainIds.has(owners.get(serviceId)?.domain.data.id)) owners.set(serviceId, { domain: d });
|
|
902
995
|
})
|
|
@@ -953,7 +1046,7 @@ const buildLevels = async ({ id, version, mode = 'full', channelRenderMode }: No
|
|
|
953
1046
|
serviceGraphs,
|
|
954
1047
|
mode,
|
|
955
1048
|
domains,
|
|
956
|
-
catalogServices: inVersionsUsed([...services, ...agents],
|
|
1049
|
+
catalogServices: inVersionsUsed([...services, ...agents], owners),
|
|
957
1050
|
});
|
|
958
1051
|
const context = addContext(contexts, systems, nodes);
|
|
959
1052
|
const graph = { nodes: [...nodes.values(), ...context.nodes], edges: [...edges, ...context.edges] };
|
|
@@ -964,8 +1057,8 @@ const buildLevels = async ({ id, version, mode = 'full', channelRenderMode }: No
|
|
|
964
1057
|
// by services not in one) are hidden too, with the nodes either side connected
|
|
965
1058
|
// directly. A domain without systems or subdomains has no level 1.
|
|
966
1059
|
if (systems.length === 0 && tree.length === 1) return layoutLevels(detailedGraph);
|
|
967
|
-
// Built from the graph before
|
|
968
|
-
//
|
|
1060
|
+
// Built from the graph before other domains' messages were moved out of the
|
|
1061
|
+
// systems they were drawn in, so they collapse into those systems
|
|
969
1062
|
const collapsed = collapseSystems(
|
|
970
1063
|
{ ...graph, edges: detailedGraph.edges },
|
|
971
1064
|
systems.map(({ system }) => system),
|
|
@@ -82,79 +82,6 @@ const getRoutableNodeData = (resource: RoutableResource, mode: 'simple' | 'full'
|
|
|
82
82
|
contextMenu: getRoutableContextMenu(resource),
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
const sanitizeToolId = (name: string) =>
|
|
86
|
-
name
|
|
87
|
-
.replace(/[^a-zA-Z0-9]+/g, '-')
|
|
88
|
-
.replace(/(^-|-$)/g, '')
|
|
89
|
-
.toLowerCase();
|
|
90
|
-
|
|
91
|
-
const generateIdForAgentToolNode = (agent: RoutableResource, tool: { name: string; type: string }) =>
|
|
92
|
-
`${generateIdForNode(agent)}-tool-${sanitizeToolId(`${tool.name}-${tool.type}`)}`;
|
|
93
|
-
|
|
94
|
-
const appendAgentToolNodesAndEdges = ({
|
|
95
|
-
agent,
|
|
96
|
-
nodes,
|
|
97
|
-
edges,
|
|
98
|
-
mode,
|
|
99
|
-
}: {
|
|
100
|
-
agent: RoutableResource;
|
|
101
|
-
nodes: Node[];
|
|
102
|
-
edges: Edge[];
|
|
103
|
-
mode: 'simple' | 'full';
|
|
104
|
-
}) => {
|
|
105
|
-
if (!isAgent(agent)) return;
|
|
106
|
-
|
|
107
|
-
(agent.data.tools || []).forEach((tool) => {
|
|
108
|
-
const toolNodeId = generateIdForAgentToolNode(agent, tool);
|
|
109
|
-
|
|
110
|
-
nodes.push(
|
|
111
|
-
createNode({
|
|
112
|
-
id: toolNodeId,
|
|
113
|
-
type: 'agentTool',
|
|
114
|
-
data: {
|
|
115
|
-
mode,
|
|
116
|
-
agentTool: {
|
|
117
|
-
id: sanitizeToolId(`${tool.name}-${tool.type}`),
|
|
118
|
-
name: tool.name,
|
|
119
|
-
type: tool.type,
|
|
120
|
-
icon: tool.icon,
|
|
121
|
-
url: tool.url,
|
|
122
|
-
description: tool.description,
|
|
123
|
-
},
|
|
124
|
-
contextMenu: tool.url
|
|
125
|
-
? [
|
|
126
|
-
{
|
|
127
|
-
label: 'Open tool endpoint',
|
|
128
|
-
href: tool.url,
|
|
129
|
-
external: true,
|
|
130
|
-
},
|
|
131
|
-
]
|
|
132
|
-
: undefined,
|
|
133
|
-
},
|
|
134
|
-
position: { x: 0, y: 0 },
|
|
135
|
-
})
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
edges.push(
|
|
139
|
-
createEdge({
|
|
140
|
-
id: `${generateIdForNode(agent)}-${toolNodeId}`,
|
|
141
|
-
source: generateIdForNode(agent),
|
|
142
|
-
target: toolNodeId,
|
|
143
|
-
label: 'calls tool',
|
|
144
|
-
type: 'step',
|
|
145
|
-
animated: false,
|
|
146
|
-
data: { animated: false },
|
|
147
|
-
markerEnd: {
|
|
148
|
-
type: MarkerType.ArrowClosed,
|
|
149
|
-
color: '#666',
|
|
150
|
-
width: 20,
|
|
151
|
-
height: 20,
|
|
152
|
-
},
|
|
153
|
-
})
|
|
154
|
-
);
|
|
155
|
-
});
|
|
156
|
-
};
|
|
157
|
-
|
|
158
85
|
const getDataProductNodeData = (resource: CollectionEntry<'data-products'>, mode: 'simple' | 'full') => ({
|
|
159
86
|
title: resource.data.id,
|
|
160
87
|
mode,
|
|
@@ -293,9 +220,6 @@ const getNodesAndEdges = async ({
|
|
|
293
220
|
data: isDataProduct(producer) ? getDataProductNodeData(producer, mode) : getRoutableNodeData(producer, mode),
|
|
294
221
|
position: { x: 0, y: 0 },
|
|
295
222
|
});
|
|
296
|
-
if (!isDataProduct(producer)) {
|
|
297
|
-
appendAgentToolNodesAndEdges({ agent: producer, nodes, edges, mode });
|
|
298
|
-
}
|
|
299
223
|
|
|
300
224
|
// Data products don't have channel configuration, so connect directly to the message
|
|
301
225
|
if (isDataProduct(producer)) {
|
|
@@ -424,9 +348,6 @@ const getNodesAndEdges = async ({
|
|
|
424
348
|
position: { x: 0, y: 0 },
|
|
425
349
|
type: isDataProduct(consumer) ? 'data-products' : consumer.collection,
|
|
426
350
|
});
|
|
427
|
-
if (!isDataProduct(consumer)) {
|
|
428
|
-
appendAgentToolNodesAndEdges({ agent: consumer, nodes, edges, mode });
|
|
429
|
-
}
|
|
430
351
|
|
|
431
352
|
// Data products don't have channel configuration, so connect directly from the message
|
|
432
353
|
if (isDataProduct(consumer)) {
|
|
@@ -656,10 +577,6 @@ const getNodesAndEdges = async ({
|
|
|
656
577
|
position: { x: 0, y: 0 },
|
|
657
578
|
})
|
|
658
579
|
);
|
|
659
|
-
|
|
660
|
-
if (receiver.collection !== 'domains') {
|
|
661
|
-
appendAgentToolNodesAndEdges({ agent: receiver, nodes, edges, mode });
|
|
662
|
-
}
|
|
663
580
|
};
|
|
664
581
|
|
|
665
582
|
// Complete both sides of a trigger chain around the focused message:
|
|
@@ -779,7 +696,6 @@ export const getNodesAndEdgesForConsumedMessage = ({
|
|
|
779
696
|
position: { x: 0, y: 0 },
|
|
780
697
|
})
|
|
781
698
|
);
|
|
782
|
-
appendAgentToolNodesAndEdges({ agent: target, nodes, edges, mode });
|
|
783
699
|
|
|
784
700
|
const targetMessageConfiguration = target.data.receives?.find(
|
|
785
701
|
(receive) => receive.id === message.data.id && versionMatches(receive.version, message.data.version)
|
|
@@ -889,7 +805,6 @@ export const getNodesAndEdgesForConsumedMessage = ({
|
|
|
889
805
|
position: { x: 0, y: 0 },
|
|
890
806
|
})
|
|
891
807
|
);
|
|
892
|
-
appendAgentToolNodesAndEdges({ agent: producer, nodes, edges, mode });
|
|
893
808
|
|
|
894
809
|
// The message is always connected directly to the producer
|
|
895
810
|
edges.push(
|
|
@@ -1115,7 +1030,6 @@ export const getNodesAndEdgesForProducedMessage = ({
|
|
|
1115
1030
|
position: { x: 0, y: 0 },
|
|
1116
1031
|
})
|
|
1117
1032
|
);
|
|
1118
|
-
appendAgentToolNodesAndEdges({ agent: source, nodes, edges, mode });
|
|
1119
1033
|
|
|
1120
1034
|
// Render the edge from the producer to the message
|
|
1121
1035
|
edges.push(
|
|
@@ -1214,7 +1128,6 @@ export const getNodesAndEdgesForProducedMessage = ({
|
|
|
1214
1128
|
position: { x: 0, y: 0 },
|
|
1215
1129
|
})
|
|
1216
1130
|
);
|
|
1217
|
-
appendAgentToolNodesAndEdges({ agent: consumer, nodes, edges, mode });
|
|
1218
1131
|
|
|
1219
1132
|
// Check if the consumer is consuming the message from a channel
|
|
1220
1133
|
const consumerConfigurationForMessage = consumer.data.receives?.find(
|
|
@@ -126,21 +126,17 @@ export const getNodesAndEdges = async ({
|
|
|
126
126
|
|
|
127
127
|
// Wrap the graph in one parent "system group" boundary node. Messages,
|
|
128
128
|
// channels and data stores stay in it. With confineToMembers, only this
|
|
129
|
-
// system's own services are children; other services
|
|
130
|
-
// are left outside.
|
|
131
|
-
const graphNodes = [...nodes.values()];
|
|
132
|
-
const graphEdges = [...edges.values()];
|
|
129
|
+
// system's own services are children; other services are left outside.
|
|
133
130
|
const graph = {
|
|
134
131
|
nodes:
|
|
135
132
|
wrapInSystemGroup && nodes.size > 0
|
|
136
133
|
? wrapNodesInSystemGroup(
|
|
137
|
-
|
|
138
|
-
graphEdges,
|
|
134
|
+
[...nodes.values()],
|
|
139
135
|
system,
|
|
140
136
|
confineToMembers ? memberServiceNodeIds(system, serviceMap) : undefined
|
|
141
137
|
)
|
|
142
|
-
:
|
|
143
|
-
edges:
|
|
138
|
+
: [...nodes.values()],
|
|
139
|
+
edges: [...edges.values()],
|
|
144
140
|
};
|
|
145
141
|
|
|
146
142
|
return layout ? layoutNodeGraph(graph) : graph;
|
|
@@ -171,22 +167,11 @@ const withoutGroupParent = (node: any) => {
|
|
|
171
167
|
* Wraps a system's nodes in one parent `system-group` node (sized to fit them
|
|
172
168
|
* when the graph is laid out), so they visibly belong to the system.
|
|
173
169
|
* When `memberIds` is set, service and agent nodes that aren't members are
|
|
174
|
-
* left outside the group
|
|
170
|
+
* left outside the group.
|
|
175
171
|
*/
|
|
176
|
-
const wrapNodesInSystemGroup = (systemNodes: any[],
|
|
172
|
+
const wrapNodesInSystemGroup = (systemNodes: any[], system: any, memberIds?: Set<string>) => {
|
|
177
173
|
const groupId = `system-group-${system.data.id}-${system.data.version}`;
|
|
178
174
|
|
|
179
|
-
const outside = new Set<string>(
|
|
180
|
-
memberIds
|
|
181
|
-
? systemNodes.filter((node) => SERVICE_NODE_TYPES.has(node.type) && !memberIds.has(node.id)).map((node) => node.id)
|
|
182
|
-
: []
|
|
183
|
-
);
|
|
184
|
-
// An agent's tools sit wherever the agent does
|
|
185
|
-
const toolIds = new Set(systemNodes.filter((node) => node.type === 'agentTool').map((node) => node.id));
|
|
186
|
-
for (const edge of systemEdges) {
|
|
187
|
-
if (outside.has(edge.source) && toolIds.has(edge.target)) outside.add(edge.target);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
175
|
const groupNode = {
|
|
191
176
|
id: groupId,
|
|
192
177
|
type: 'system-group',
|
|
@@ -203,8 +188,9 @@ const wrapNodesInSystemGroup = (systemNodes: any[], systemEdges: any[], system:
|
|
|
203
188
|
// Parent must come before its children in the array for React Flow
|
|
204
189
|
return [
|
|
205
190
|
groupNode,
|
|
206
|
-
...systemNodes.map((node) =>
|
|
207
|
-
|
|
208
|
-
|
|
191
|
+
...systemNodes.map((node) => {
|
|
192
|
+
if (memberIds && SERVICE_NODE_TYPES.has(node.type) && !memberIds.has(node.id)) return withoutGroupParent(node);
|
|
193
|
+
return { ...node, parentId: groupId, extent: 'parent' };
|
|
194
|
+
}),
|
|
209
195
|
];
|
|
210
196
|
};
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"license": "BUSL-1.1",
|
|
9
9
|
"type": "module",
|
|
10
|
-
"version": "5.7.
|
|
10
|
+
"version": "5.7.5",
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
@@ -141,11 +141,11 @@
|
|
|
141
141
|
"y-protocols": "^1.0.7",
|
|
142
142
|
"yjs": "^13.6.33",
|
|
143
143
|
"zod": "^4.6.6",
|
|
144
|
-
"@eventcatalog/exporters": "0.1.4",
|
|
145
144
|
"@eventcatalog/fitness": "0.1.1",
|
|
145
|
+
"@eventcatalog/exporters": "0.1.4",
|
|
146
146
|
"@eventcatalog/linter": "1.3.1",
|
|
147
|
-
"@eventcatalog/
|
|
148
|
-
"@eventcatalog/
|
|
147
|
+
"@eventcatalog/sdk": "2.32.0",
|
|
148
|
+
"@eventcatalog/visualiser": "^5.2.3"
|
|
149
149
|
},
|
|
150
150
|
"devDependencies": {
|
|
151
151
|
"@astrojs/check": "^0.9.10",
|