@eventcatalog/core 2.11.7 → 2.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @eventcatalog/core
2
2
 
3
+ ## 2.12.1
4
+
5
+ ### Patch Changes
6
+
7
+ - a57282e: fix(core): new property for AsyncAPI rendering
8
+ - d739d7b: fix(core): changelog buttons now render the correct changelog
9
+
10
+ ## 2.12.0
11
+
12
+ ### Minor Changes
13
+
14
+ - a136d50: feat(core): services can now send and receive the same messages in vis…
15
+
3
16
  ## 2.11.7
4
17
 
5
18
  ### Patch Changes
@@ -16,6 +16,9 @@ interface Config {
16
16
  src: string;
17
17
  text?: string;
18
18
  };
19
+ asyncAPI?: {
20
+ renderParsedSchemas?: boolean;
21
+ };
19
22
  mdxOptimize?: boolean;
20
23
  docs: {
21
24
  sidebar: {
@@ -16,6 +16,9 @@ interface Config {
16
16
  src: string;
17
17
  text?: string;
18
18
  };
19
+ asyncAPI?: {
20
+ renderParsedSchemas?: boolean;
21
+ };
19
22
  mdxOptimize?: boolean;
20
23
  docs: {
21
24
  sidebar: {
@@ -17,6 +17,9 @@ export interface Config {
17
17
  src: string;
18
18
  text?: string;
19
19
  };
20
+ asyncAPI?: {
21
+ renderParsedSchemas?: boolean;
22
+ };
20
23
  mdxOptimize?: boolean;
21
24
  docs: {
22
25
  sidebar: {
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "url": "https://github.com/event-catalog/eventcatalog.git"
7
7
  },
8
8
  "type": "module",
9
- "version": "2.11.7",
9
+ "version": "2.12.1",
10
10
  "publishConfig": {
11
11
  "access": "public"
12
12
  },
@@ -41,7 +41,7 @@
41
41
  "@astrojs/mdx": "^3.1.8",
42
42
  "@astrojs/react": "^3.6.2",
43
43
  "@astrojs/tailwind": "^5.1.2",
44
- "@asyncapi/react-component": "^2.2.2",
44
+ "@asyncapi/react-component": "^2.4.3",
45
45
  "@headlessui/react": "^2.0.3",
46
46
  "@heroicons/react": "^2.1.3",
47
47
  "@parcel/watcher": "^2.4.1",
@@ -81,6 +81,7 @@ const NodeGraphBuilder = ({
81
81
  setEdges((eds) =>
82
82
  eds.map((edge) => {
83
83
  edge.style = { ...edge.style, opacity: 1 };
84
+ edge.labelStyle = { ...edge.labelStyle, opacity: 1 };
84
85
  return { ...edge, animated: false };
85
86
  })
86
87
  );
@@ -107,9 +108,19 @@ const NodeGraphBuilder = ({
107
108
  if (edge.source === node.id || edge.target === node.id) {
108
109
  connectedNodeIds.add(edge.source);
109
110
  connectedNodeIds.add(edge.target);
110
- return { ...edge, style: { ...edge.style, opacity: 1 }, animated: true };
111
+ return {
112
+ ...edge,
113
+ style: { ...edge.style, opacity: 1 },
114
+ labelStyle: { ...edge.labelStyle, opacity: 1 },
115
+ animated: true,
116
+ };
111
117
  }
112
- return { ...edge, style: { ...edge.style, opacity: 0.1 }, animated: false };
118
+ return {
119
+ ...edge,
120
+ style: { ...edge.style, opacity: 0.1 },
121
+ labelStyle: { ...edge.labelStyle, opacity: 0.1 },
122
+ animated: false,
123
+ };
113
124
  });
114
125
 
115
126
  const updatedNodes = nodes.map((n) => {
@@ -18,13 +18,10 @@ function classNames(...classes: any) {
18
18
  }
19
19
 
20
20
  export default function CommandNode({ data, sourcePosition, targetPosition }: any) {
21
- const { mode, message, showSource = true, showTarget = true } = data as Data;
21
+ const { mode, message } = data as Data;
22
22
 
23
23
  const { name, version, summary, owners = [], producers = [], consumers = [] } = message.data;
24
24
 
25
- const renderTarget = showTarget || (targetPosition && producers.length > 0);
26
- const renderSource = showSource || (sourcePosition && consumers.length > 0);
27
-
28
25
  return (
29
26
  <div className={classNames('w-full rounded-md border flex justify-start bg-white text-black border-blue-400')}>
30
27
  <div
@@ -41,8 +38,8 @@ export default function CommandNode({ data, sourcePosition, targetPosition }: an
41
38
  )}
42
39
  </div>
43
40
  <div className="p-1 min-w-60 max-w-[min-content]">
44
- {renderTarget && <Handle type="target" position={targetPosition} />}
45
- {renderSource && <Handle type="source" position={sourcePosition} />}
41
+ {targetPosition && <Handle type="target" position={targetPosition} />}
42
+ {sourcePosition && <Handle type="source" position={sourcePosition} />}
46
43
  <div className={classNames(mode === 'full' ? `border-b border-gray-200` : '')}>
47
44
  <span className="text-xs font-bold block pb-0.5">{name}</span>
48
45
  <div className="flex justify-between">
@@ -18,13 +18,10 @@ function classNames(...classes: any) {
18
18
  }
19
19
 
20
20
  export default function EventNode({ data, sourcePosition, targetPosition }: any) {
21
- const { mode, message, showTarget = true, showSource = true } = data as Data;
21
+ const { mode, message } = data as Data;
22
22
 
23
23
  const { name, version, summary, owners = [], producers = [], consumers = [] } = message.data;
24
24
 
25
- const renderTarget = showTarget || (targetPosition && producers.length > 0);
26
- const renderSource = showSource || (sourcePosition && consumers.length > 0);
27
-
28
25
  return (
29
26
  <div className={classNames('w-full rounded-md border flex justify-start bg-white text-black border-orange-400')}>
30
27
  <div
@@ -41,8 +38,8 @@ export default function EventNode({ data, sourcePosition, targetPosition }: any)
41
38
  )}
42
39
  </div>
43
40
  <div className="p-1 min-w-60 max-w-[min-content]">
44
- {renderTarget && <Handle type="target" position={targetPosition} />}
45
- {renderSource && <Handle type="source" position={sourcePosition} />}
41
+ {targetPosition && <Handle type="target" position={targetPosition} />}
42
+ {sourcePosition && <Handle type="source" position={sourcePosition} />}
46
43
  <div className={classNames(mode === 'full' ? `border-b border-gray-200` : '')}>
47
44
  <span className="text-xs font-bold block pb-0.5">{name}</span>
48
45
  <div className="flex justify-between">
@@ -18,7 +18,7 @@ function classNames(...classes: any) {
18
18
  }
19
19
 
20
20
  export default function ExternalSystemNode({ data, sourcePosition, targetPosition }: any) {
21
- const { mode, step, showTarget = true, showSource = true } = data as Data;
21
+ const { mode, step } = data as Data;
22
22
  const { externalSystem } = step;
23
23
  const { name, summary, url } = externalSystem;
24
24
 
@@ -43,8 +43,8 @@ export default function ExternalSystemNode({ data, sourcePosition, targetPositio
43
43
  )}
44
44
  </div>
45
45
  <div className="p-1 min-w-60 max-w-[min-content]">
46
- {showTarget && <Handle type="target" position={targetPosition} />}
47
- {showSource && <Handle type="source" position={sourcePosition} />}
46
+ {targetPosition && <Handle type="target" position={targetPosition} />}
47
+ {sourcePosition && <Handle type="source" position={sourcePosition} />}
48
48
  <div className={classNames(mode === 'full' ? `border-b border-gray-200` : '')}>
49
49
  <div className="h-full ">
50
50
  <span className="text-sm font-bold pb-0.5 block w-full">{name}</span>
@@ -22,8 +22,8 @@ export default function QueryNode({ data, sourcePosition, targetPosition }: any)
22
22
 
23
23
  const { name, version, summary, owners = [], producers = [], consumers = [] } = message.data;
24
24
 
25
- const renderTarget = showTarget || (targetPosition && producers.length > 0);
26
- const renderSource = showSource || (sourcePosition && consumers.length > 0);
25
+ const renderTarget = true; //showTarget || (targetPosition && producers.length > 0);
26
+ const renderSource = true; // showSource || (sourcePosition && consumers.length > 0);
27
27
 
28
28
  return (
29
29
  <div className={classNames('w-full rounded-md border flex justify-start bg-white text-black border-green-400')}>
@@ -17,7 +17,7 @@ function classNames(...classes: any) {
17
17
  }
18
18
 
19
19
  export default function ServiceNode({ data, sourcePosition, targetPosition }: any) {
20
- const { label, bgColor = 'bg-blue-500', mode, service, showTarget = true, showSource = true } = data as Data;
20
+ const { label, bgColor = 'bg-blue-500', mode, service } = data as Data;
21
21
 
22
22
  const { version, owners = [], sends = [], receives = [], name } = service.data;
23
23
 
@@ -37,8 +37,8 @@ export default function ServiceNode({ data, sourcePosition, targetPosition }: an
37
37
  )}
38
38
  </div>
39
39
  <div className="p-1 min-w-60 max-w-[min-content]">
40
- {showTarget && <Handle type="target" position={targetPosition} />}
41
- {showSource && <Handle type="source" position={sourcePosition} />}
40
+ {targetPosition && <Handle type="target" position={targetPosition} />}
41
+ {sourcePosition && <Handle type="source" position={sourcePosition} />}
42
42
  <div className={classNames(mode === 'full' ? `border-b border-gray-200` : '')}>
43
43
  <span className="text-xs font-bold block pt-0.5 pb-0.5">{name}</span>
44
44
  <div className="flex justify-between">
@@ -16,13 +16,10 @@ function classNames(...classes: any) {
16
16
  }
17
17
 
18
18
  export default function StepNode({ data, sourcePosition, targetPosition }: any) {
19
- const { mode, step, showTarget = true, showSource = true } = data as Data;
19
+ const { mode, step } = data as Data;
20
20
 
21
21
  const { title, summary } = step;
22
22
 
23
- const renderTarget = showTarget || true;
24
- const renderSource = showSource || true;
25
-
26
23
  return (
27
24
  <div className={classNames('w-full rounded-md border flex justify-start bg-white text-black border-blue-400 min-h-[3em]')}>
28
25
  <div
@@ -38,8 +35,8 @@ export default function StepNode({ data, sourcePosition, targetPosition }: any)
38
35
  )}
39
36
  </div>
40
37
  <div className="p-1 min-w-60 max-w-[min-content]">
41
- {renderTarget && <Handle type="target" position={targetPosition} />}
42
- {renderSource && <Handle type="source" position={sourcePosition} />}
38
+ {targetPosition && <Handle type="target" position={targetPosition} />}
39
+ {sourcePosition && <Handle type="source" position={sourcePosition} />}
43
40
 
44
41
  {!summary && (
45
42
  <div className="h-full flex items-center">
@@ -21,9 +21,6 @@ export default function UserNode({ data, sourcePosition, targetPosition }: any)
21
21
 
22
22
  const { summary, actor: { name } = {} } = step;
23
23
 
24
- const renderTarget = showTarget || true;
25
- const renderSource = showSource || true;
26
-
27
24
  return (
28
25
  <div
29
26
  className={classNames(
@@ -45,8 +42,8 @@ export default function UserNode({ data, sourcePosition, targetPosition }: any)
45
42
  )}
46
43
  </div>
47
44
  <div className="p-1 min-w-60 max-w-[min-content]">
48
- {renderTarget && <Handle type="target" position={targetPosition} />}
49
- {renderSource && <Handle type="source" position={sourcePosition} />}
45
+ {targetPosition && <Handle type="target" position={targetPosition} />}
46
+ {sourcePosition && <Handle type="source" position={sourcePosition} />}
50
47
 
51
48
  {(!summary || mode !== 'full') && (
52
49
  <div className="h-full ">
@@ -66,7 +66,7 @@ const ownersList = owners.map((o) => ({
66
66
  <span class="block">View in visualiser</span>
67
67
  </a>
68
68
  <a
69
- href={buildUrl(`/docs/${domain.collection}/${domain.data.id}/${domain.data.version}/changelog`)}
69
+ href={buildUrl(`/docs/${domain.collection}/${domain.data.id}/${domain.data.latestVersion}/changelog`)}
70
70
  class="flex items-center space-x-2 justify-center text-center rounded-md w-full bg-white px-3.5 py-2.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-100/60 hover:text-primary"
71
71
  >
72
72
  <ScrollText strokeWidth={2} size={16} />
@@ -151,7 +151,7 @@ const schemaURL = path.join(publicPath, schemaFilePath || '');
151
151
  </a>
152
152
 
153
153
  <a
154
- href={buildUrl(`/docs/${message.collection}/${message.data.id}/${message.data.version}/changelog`)}
154
+ href={buildUrl(`/docs/${message.collection}/${message.data.id}/${message.data.latestVersion}/changelog`)}
155
155
  class="flex items-center space-x-2 justify-center text-center rounded-md w-full bg-white px-3.5 py-2.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-100/60 hover:text-primary"
156
156
  >
157
157
  <ScrollText strokeWidth={2} size={16} />
@@ -107,7 +107,7 @@ const schemaURL = join(publicPath, schemaFilePath || '');
107
107
  <span class="block">View in visualiser</span>
108
108
  </a>
109
109
  <a
110
- href={buildUrl(`/docs/${service.collection}/${service.data.id}/${service.data.version}/changelog`)}
110
+ href={buildUrl(`/docs/${service.collection}/${service.data.id}/${service.data.latestVersion}/changelog`)}
111
111
  class="flex items-center space-x-2 justify-center text-center rounded-md w-full bg-white px-3.5 py-2.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-100/60 hover:text-primary"
112
112
  >
113
113
  <ScrollText strokeWidth={2} size={16} />
@@ -40,10 +40,6 @@ export const Table = ({
40
40
  const [data, _setData] = useState(initialData);
41
41
  const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
42
42
 
43
- useEffect(() => {
44
- console.log(window.location.pathname);
45
- }, []);
46
-
47
43
  useEffect(() => {
48
44
  const urlParams = new URLSearchParams(window.location.search);
49
45
  const id = urlParams.get('id');
@@ -268,8 +268,6 @@ const showSideBarOnLoad = currentNavigationItem?.sidebar;
268
268
 
269
269
  const navItem = navigationItems.find((navItem) => navItem.id === id);
270
270
 
271
- console.log(navItem);
272
-
273
271
  if (!navItem.sidebar || !currentPath.includes(navItem.id)) {
274
272
  window.location.href = navItem.href;
275
273
  return;
@@ -7,14 +7,13 @@ import { Parser } from '@asyncapi/parser';
7
7
 
8
8
  import type { CollectionTypes, PageTypes } from '@types';
9
9
 
10
- import PlainPage from '@layouts/PlainPage.astro';
11
-
12
10
  import '@asyncapi/react-component/styles/default.min.css';
13
11
  import js from '@asyncapi/react-component/browser/standalone/without-parser.js?url';
14
12
  import { AsyncApiComponentWP, type ConfigInterface } from '@asyncapi/react-component';
15
13
  import { pageDataLoader } from '@utils/pages/pages';
16
14
  import type { CollectionEntry } from 'astro:content';
17
15
  import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
16
+ import Config from '@eventcatalog';
18
17
 
19
18
  export async function getStaticPaths() {
20
19
  const itemTypes: PageTypes[] = ['events', 'commands', 'queries', 'services', 'domains'];
@@ -45,7 +44,9 @@ const pathToSpec = path.join(catalog.publicPath, fileName);
45
44
  const pathOnDisk = path.join(process.cwd(), 'public', pathToSpec);
46
45
  const fileContent = readFileSync(pathOnDisk, 'utf-8');
47
46
 
48
- const parsed = await new Parser().parse(fileContent);
47
+ // AsyncAPI parser will parser schemas for users, they can turn this off.
48
+ const parseSchemas = Config?.asyncAPI?.renderParsedSchemas ?? true;
49
+ const parsed = await new Parser().parse(fileContent, { parseSchemas });
49
50
  const stringified = parsed.document?.json();
50
51
  const config: ConfigInterface = { show: { sidebar: true, errors: true } };
51
52
 
@@ -57,3 +57,15 @@ export const getItemsFromCollectionByIdAndSemverOrLatest = <T extends { data: {
57
57
  // latest version
58
58
  return sorted.length > 0 ? [sorted[sorted.length - 1]] : [];
59
59
  };
60
+
61
+ export const findMatchingNodes = (
62
+ nodesA: CollectionEntry<'events' | 'commands' | 'queries' | 'services'>[],
63
+ nodesB: CollectionEntry<'events' | 'commands' | 'queries' | 'services'>[]
64
+ ) => {
65
+ // Track messages that are both sent and received
66
+ return nodesA.filter((nodeA) => {
67
+ return nodesB.some((nodeB) => {
68
+ return nodeB.data.id === nodeA.data.id && nodeB.data.version === nodeA.data.version;
69
+ });
70
+ });
71
+ };
@@ -1,3 +1,4 @@
1
+ import { findMatchingNodes } from '@utils/collections/util';
1
2
  import { getCommands } from '@utils/commands';
2
3
  import { calculatedNodes, createDagreGraph, generateIdForNode, generatedIdForEdge } from '@utils/node-graph-utils/utils';
3
4
  import { type CollectionEntry } from 'astro:content';
@@ -33,6 +34,9 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
33
34
  const producers = (command.data.producers as CollectionEntry<'services'>[]) || [];
34
35
  const consumers = (command.data.consumers as CollectionEntry<'services'>[]) || [];
35
36
 
37
+ // Track nodes that are both sent and received
38
+ const bothSentAndReceived = findMatchingNodes(producers, consumers);
39
+
36
40
  if (producers && producers.length > 0) {
37
41
  producers.forEach((producer, index) => {
38
42
  nodes.push({
@@ -40,7 +44,7 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
40
44
  type: producer?.collection,
41
45
  sourcePosition: 'right',
42
46
  targetPosition: 'left',
43
- data: { mode, service: producer, showTarget: false },
47
+ data: { mode, service: producer },
44
48
  position: { x: 250, y: 0 },
45
49
  });
46
50
  edges.push({
@@ -67,7 +71,7 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
67
71
  id: generateIdForNode(command),
68
72
  sourcePosition: 'right',
69
73
  targetPosition: 'left',
70
- data: { mode, message: command, showTarget: producers.length > 0, showSource: consumers.length > 0 },
74
+ data: { mode, message: command },
71
75
  position: { x: 0, y: 0 },
72
76
  type: command.collection,
73
77
  });
@@ -78,7 +82,7 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
78
82
  id: generateIdForNode(consumer),
79
83
  sourcePosition: 'right',
80
84
  targetPosition: 'left',
81
- data: { title: consumer?.data.id, mode, service: consumer, showSource: false },
85
+ data: { title: consumer?.data.id, mode, service: consumer },
82
86
  position: { x: 0, y: 0 },
83
87
  type: consumer?.collection,
84
88
  });
@@ -100,6 +104,28 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
100
104
  });
101
105
  });
102
106
 
107
+ // Handle nodes that are both sent and received
108
+ bothSentAndReceived.forEach((message) => {
109
+ if (message) {
110
+ edges.push({
111
+ id: generatedIdForEdge(command, message) + '-both',
112
+ source: generateIdForNode(command),
113
+ target: generateIdForNode(message),
114
+ type: 'smoothstep',
115
+ label: `publishes and subscribes`,
116
+ animated: false,
117
+ markerEnd: {
118
+ type: MarkerType.ArrowClosed,
119
+ width: 40,
120
+ height: 40,
121
+ },
122
+ style: {
123
+ strokeWidth: 1,
124
+ },
125
+ });
126
+ }
127
+ });
128
+
103
129
  nodes.forEach((node: any) => {
104
130
  flow.setNode(node.id, { width: 150, height: 100 });
105
131
  });
@@ -4,6 +4,7 @@ import type { CollectionEntry } from 'astro:content';
4
4
  import dagre from 'dagre';
5
5
  import { calculatedNodes, createDagreGraph, generatedIdForEdge, generateIdForNode } from '../node-graph-utils/utils';
6
6
  import { MarkerType } from 'reactflow';
7
+ import { findMatchingNodes } from '@utils/collections/util';
7
8
 
8
9
  type DagreGraph = any;
9
10
 
@@ -34,6 +35,9 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
34
35
  const producers = (event.data.producers as CollectionEntry<'services'>[]) || [];
35
36
  const consumers = (event.data.consumers as CollectionEntry<'services'>[]) || [];
36
37
 
38
+ // Track nodes that are both sent and received
39
+ const bothSentAndReceived = findMatchingNodes(producers, consumers);
40
+
37
41
  if (producers && producers.length > 0) {
38
42
  producers.forEach((producer) => {
39
43
  nodes.push({
@@ -101,6 +105,28 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
101
105
  });
102
106
  });
103
107
 
108
+ // Handle messages that are both sent and received
109
+ bothSentAndReceived.forEach((message) => {
110
+ if (message) {
111
+ edges.push({
112
+ id: generatedIdForEdge(event, message) + '-both',
113
+ source: generateIdForNode(event),
114
+ target: generateIdForNode(message),
115
+ type: 'smoothstep',
116
+ label: `publishes and subscribes`,
117
+ animated: false,
118
+ markerEnd: {
119
+ type: MarkerType.ArrowClosed,
120
+ width: 40,
121
+ height: 40,
122
+ },
123
+ style: {
124
+ strokeWidth: 1,
125
+ },
126
+ });
127
+ }
128
+ });
129
+
104
130
  nodes.forEach((node: any) => {
105
131
  flow.setNode(node.id, { width: 150, height: 100 });
106
132
  });
@@ -3,6 +3,7 @@ import type { CollectionEntry } from 'astro:content';
3
3
  import dagre from 'dagre';
4
4
  import { calculatedNodes, createDagreGraph, generatedIdForEdge, generateIdForNode } from '../node-graph-utils/utils';
5
5
  import { MarkerType } from 'reactflow';
6
+ import { findMatchingNodes } from '@utils/collections/util';
6
7
 
7
8
  type DagreGraph = any;
8
9
 
@@ -33,6 +34,9 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
33
34
  const producers = (query.data.producers as CollectionEntry<'services'>[]) || [];
34
35
  const consumers = (query.data.consumers as CollectionEntry<'services'>[]) || [];
35
36
 
37
+ // Track nodes that are both sent and received
38
+ const bothSentAndReceived = findMatchingNodes(producers, consumers);
39
+
36
40
  if (producers && producers.length > 0) {
37
41
  producers.forEach((producer) => {
38
42
  nodes.push({
@@ -40,7 +44,7 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
40
44
  type: producer?.collection,
41
45
  sourcePosition: 'right',
42
46
  targetPosition: 'left',
43
- data: { mode, service: producer, showTarget: false },
47
+ data: { mode, service: producer },
44
48
  position: { x: 250, y: 0 },
45
49
  });
46
50
  edges.push({
@@ -67,7 +71,7 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
67
71
  id: generateIdForNode(query),
68
72
  sourcePosition: 'right',
69
73
  targetPosition: 'left',
70
- data: { mode, message: query, showTarget: producers.length > 0, showSource: consumers.length > 0 },
74
+ data: { mode, message: query },
71
75
  position: { x: 0, y: 0 },
72
76
  type: query.collection,
73
77
  });
@@ -78,7 +82,7 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
78
82
  id: generateIdForNode(consumer),
79
83
  sourcePosition: 'right',
80
84
  targetPosition: 'left',
81
- data: { title: consumer?.data.id, mode, service: consumer, showSource: false },
85
+ data: { title: consumer?.data.id, mode, service: consumer },
82
86
  position: { x: 0, y: 0 },
83
87
  type: consumer?.collection,
84
88
  });
@@ -100,6 +104,28 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
100
104
  });
101
105
  });
102
106
 
107
+ // Handle nodes that are both sent and received
108
+ bothSentAndReceived.forEach((message) => {
109
+ if (message) {
110
+ edges.push({
111
+ id: generatedIdForEdge(query, message) + '-both',
112
+ source: generateIdForNode(query),
113
+ target: generateIdForNode(message),
114
+ type: 'smoothstep',
115
+ label: `publishes and subscribes`,
116
+ animated: false,
117
+ markerEnd: {
118
+ type: MarkerType.ArrowClosed,
119
+ width: 40,
120
+ height: 40,
121
+ },
122
+ style: {
123
+ strokeWidth: 1,
124
+ },
125
+ });
126
+ }
127
+ });
128
+
103
129
  nodes.forEach((node: any) => {
104
130
  flow.setNode(node.id, { width: 150, height: 100 });
105
131
  });
@@ -1,7 +1,7 @@
1
1
  import { getCollection, type CollectionEntry } from 'astro:content';
2
2
  import dagre from 'dagre';
3
3
  import { createDagreGraph, generateIdForNode, generatedIdForEdge, calculatedNodes } from '@utils/node-graph-utils/utils';
4
- import { getItemsFromCollectionByIdAndSemverOrLatest } from '@utils/collections/util';
4
+ import { findMatchingNodes, getItemsFromCollectionByIdAndSemverOrLatest } from '@utils/collections/util';
5
5
  import { MarkerType } from 'reactflow';
6
6
 
7
7
  type DagreGraph = any;
@@ -78,10 +78,12 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
78
78
  const receives = (receivesHydrated as CollectionEntry<'events' | 'commands' | 'queries'>[]) || [];
79
79
  const sends = (sendsHydrated as CollectionEntry<'events' | 'commands' | 'queries'>[]) || [];
80
80
 
81
- // Get all the data from them
81
+ // Track messages that are both sent and received
82
+ const bothSentAndReceived = findMatchingNodes(receives, sends);
82
83
 
84
+ // Get all the data from them
83
85
  if (receives && receives.length > 0) {
84
- //All the messages the service receives
86
+ // All the messages the service receives
85
87
  receives.forEach((receive, index) => {
86
88
  nodes.push({
87
89
  id: generateIdForNode(receive),
@@ -89,7 +91,7 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
89
91
  sourcePosition: 'right',
90
92
  targetPosition: 'left',
91
93
  data: { mode, message: receive, showTarget: renderAllEdges },
92
- position: { x: 250, y: 0 },
94
+ position: { x: 250, y: index * 100 },
93
95
  });
94
96
  edges.push({
95
97
  id: generatedIdForEdge(receive, service),
@@ -121,13 +123,13 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
121
123
  });
122
124
 
123
125
  // The messages the service sends
124
- sends.forEach((send) => {
126
+ sends.forEach((send, index) => {
125
127
  nodes.push({
126
128
  id: generateIdForNode(send),
127
129
  sourcePosition: 'right',
128
130
  targetPosition: 'left',
129
131
  data: { mode, message: send, showSource: renderAllEdges },
130
- position: { x: 0, y: 0 },
132
+ position: { x: 500, y: index * 100 },
131
133
  type: send?.collection,
132
134
  });
133
135
  edges.push({
@@ -148,6 +150,28 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
148
150
  });
149
151
  });
150
152
 
153
+ // Handle messages that are both sent and received
154
+ bothSentAndReceived.forEach((message) => {
155
+ if (message) {
156
+ edges.push({
157
+ id: generatedIdForEdge(service, message) + '-both',
158
+ source: generateIdForNode(service),
159
+ target: generateIdForNode(message),
160
+ type: 'smoothstep',
161
+ label: `${getSendsMessageByMessageType(message?.collection)} & ${getReceivesMessageByMessageType(message?.collection)}`,
162
+ animated: false,
163
+ markerEnd: {
164
+ type: MarkerType.ArrowClosed,
165
+ width: 40,
166
+ height: 40,
167
+ },
168
+ style: {
169
+ strokeWidth: 1,
170
+ },
171
+ });
172
+ }
173
+ });
174
+
151
175
  nodes.forEach((node: any) => {
152
176
  flow.setNode(node.id, { width: 150, height: 100 });
153
177
  });
@@ -156,7 +180,7 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
156
180
  flow.setEdge(edge.source, edge.target);
157
181
  });
158
182
 
159
- // Render the diagram in memory getting hte X and Y
183
+ // Render the diagram in memory getting the X and Y
160
184
  dagre.layout(flow);
161
185
 
162
186
  return {