@eventcatalog/core 2.16.2 → 2.16.4

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.
Files changed (61) hide show
  1. package/README.md +4 -1
  2. package/dist/analytics/analytics.cjs +1 -1
  3. package/dist/analytics/analytics.js +2 -2
  4. package/dist/analytics/log-build.cjs +1 -1
  5. package/dist/analytics/log-build.js +3 -3
  6. package/dist/{chunk-D5BKFMHD.js → chunk-CV37NPK3.js} +1 -1
  7. package/dist/{chunk-5K2SMJL6.js → chunk-X7R2UJHK.js} +1 -1
  8. package/dist/{chunk-7V6DXP63.js → chunk-YHWKWHZA.js} +1 -1
  9. package/dist/constants.cjs +1 -1
  10. package/dist/constants.js +1 -1
  11. package/dist/eventcatalog.cjs +1 -1
  12. package/dist/eventcatalog.js +3 -3
  13. package/eventcatalog/package.json +69 -0
  14. package/eventcatalog/src/components/MDX/Accordion/Accordion.tsx +2 -2
  15. package/eventcatalog/src/components/MDX/AsyncAPI/AsyncAPI.astro +2 -2
  16. package/eventcatalog/src/components/MDX/ChannelInformation/ChannelInformation.tsx +2 -2
  17. package/eventcatalog/src/components/MDX/Flow/Flow.astro +2 -2
  18. package/eventcatalog/src/components/MDX/NodeGraph/DownloadButton.tsx +2 -2
  19. package/eventcatalog/src/components/MDX/NodeGraph/Edges/AnimatedMessageEdge.tsx +2 -1
  20. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +12 -1
  21. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.tsx +6 -5
  22. package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Channel.tsx +1 -1
  23. package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Command.tsx +1 -1
  24. package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Event.tsx +1 -1
  25. package/eventcatalog/src/components/MDX/NodeGraph/Nodes/ExternalSystem.tsx +1 -1
  26. package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Query.tsx +1 -1
  27. package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Service.tsx +1 -1
  28. package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Step.tsx +3 -3
  29. package/eventcatalog/src/components/MDX/NodeGraph/Nodes/User.tsx +3 -3
  30. package/eventcatalog/src/components/MDX/OpenAPI/OpenAPI.astro +2 -2
  31. package/eventcatalog/src/components/MDX/SchemaViewer/SchemaViewer.astro +2 -2
  32. package/eventcatalog/src/components/SideBars/CatalogResourcesSideBar/index.tsx +10 -1
  33. package/eventcatalog/src/components/Tables/Table.tsx +15 -5
  34. package/eventcatalog/src/components/Tables/columns/DomainTableColumns.tsx +4 -10
  35. package/eventcatalog/src/components/Tables/columns/FlowTableColumns.tsx +3 -3
  36. package/eventcatalog/src/components/Tables/columns/MessageTableColumns.tsx +5 -2
  37. package/eventcatalog/src/components/Tables/columns/ServiceTableColumns.tsx +2 -8
  38. package/eventcatalog/src/components/Tables/columns/SharedColumns.tsx +44 -0
  39. package/eventcatalog/src/components/Tables/filters/custom-filters.ts +5 -0
  40. package/eventcatalog/src/hooks/eventcatalog-visualizer.ts +12 -7
  41. package/eventcatalog/src/layouts/DiscoverLayout.astro +1 -1
  42. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +10 -1
  43. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +10 -6
  44. package/eventcatalog/src/pages/docs/[type]/[id]/language.astro +6 -6
  45. package/eventcatalog/src/pages/visualiser/context-map/index.astro +30 -0
  46. package/eventcatalog/src/utils/collections/icons.ts +3 -0
  47. package/eventcatalog/src/utils/collections/services.ts +35 -0
  48. package/eventcatalog/src/utils/node-graphs/domains-node-graph.ts +206 -3
  49. package/eventcatalog/src/utils/node-graphs/flows-node-graph.ts +2 -2
  50. package/eventcatalog/src/utils/node-graphs/message-node-graph.ts +1 -1
  51. package/eventcatalog/src/utils/node-graphs/services-node-graph.ts +1 -1
  52. package/eventcatalog/src/utils/node-graphs/utils/utils.ts +1 -1
  53. package/package.json +2 -2
  54. package/bin/dist/eventcatalog.cjs +0 -558
  55. package/bin/dist/eventcatalog.config.cjs +0 -18
  56. package/bin/dist/eventcatalog.config.d.cts +0 -35
  57. package/bin/dist/eventcatalog.config.d.ts +0 -35
  58. package/bin/dist/eventcatalog.config.js +0 -0
  59. package/bin/dist/eventcatalog.d.cts +0 -1
  60. package/bin/dist/eventcatalog.d.ts +0 -1
  61. package/bin/dist/eventcatalog.js +0 -531
@@ -3,8 +3,9 @@ import { createColumnHelper } from '@tanstack/react-table';
3
3
  import type { CollectionMessageTypes } from '@types';
4
4
  import type { CollectionEntry } from 'astro:content';
5
5
  import { useMemo } from 'react';
6
- import { filterByName, filterCollectionByName } from '../filters/custom-filters';
6
+ import { filterByName, filterCollectionByName, filterByBadge } from '../filters/custom-filters';
7
7
  import { buildUrl } from '@utils/url-builder';
8
+ import { createBadgesColumn } from './SharedColumns';
8
9
 
9
10
  const columnHelper = createColumnHelper<CollectionEntry<CollectionMessageTypes>>();
10
11
 
@@ -55,6 +56,7 @@ export const columns = () => [
55
56
  },
56
57
  filterFn: filterByName,
57
58
  }),
59
+
58
60
  columnHelper.accessor('data.summary', {
59
61
  id: 'summary',
60
62
  header: () => 'Summary',
@@ -62,7 +64,7 @@ export const columns = () => [
62
64
  footer: (info) => info.column.id,
63
65
  meta: {
64
66
  showFilter: false,
65
- className: 'max-w-md',
67
+ className: 'max-w-[200px]',
66
68
  },
67
69
  }),
68
70
 
@@ -145,6 +147,7 @@ export const columns = () => [
145
147
  footer: (info) => info.column.id,
146
148
  filterFn: filterCollectionByName('consumers'),
147
149
  }),
150
+ createBadgesColumn(columnHelper),
148
151
  columnHelper.accessor('data.name', {
149
152
  header: () => <span />,
150
153
  cell: (info) => {
@@ -5,6 +5,7 @@ import { useMemo, useState } from 'react';
5
5
  import { filterByName, filterCollectionByName } from '../filters/custom-filters';
6
6
  import { buildUrl } from '@utils/url-builder';
7
7
  import { getColorAndIconForMessageType } from './MessageTableColumns';
8
+ import { createBadgesColumn } from './SharedColumns';
8
9
 
9
10
  const columnHelper = createColumnHelper<CollectionEntry<'services'>>();
10
11
 
@@ -40,14 +41,6 @@ export const columns = () => [
40
41
  },
41
42
  filterFn: filterByName,
42
43
  }),
43
- // columnHelper.accessor('data.version', {
44
- // header: () => <span>Version</span>,
45
- // cell: (info) => {
46
- // const service = info.row.original;
47
- // return <div className="text-left">{`v${info.getValue()} ${service.data.latestVersion === service.data.version ? '(latest)': ''}`}</div>
48
- // },
49
- // footer: (info) => info.column.id,
50
- // }),
51
44
  columnHelper.accessor('data.summary', {
52
45
  id: 'summary',
53
46
  header: () => 'Summary',
@@ -175,6 +168,7 @@ export const columns = () => [
175
168
  },
176
169
  filterFn: filterCollectionByName('sends'),
177
170
  }),
171
+ createBadgesColumn(columnHelper),
178
172
  columnHelper.accessor('data.name', {
179
173
  header: () => <span />,
180
174
  cell: (info) => {
@@ -0,0 +1,44 @@
1
+ import { createColumnHelper } from '@tanstack/react-table';
2
+ import { Tag } from 'lucide-react';
3
+ import { filterByBadge } from '../filters/custom-filters';
4
+ export const createBadgesColumn = <T extends { data: { badges?: any[] } }>(
5
+ columnHelper: ReturnType<typeof createColumnHelper<T>>
6
+ ) => {
7
+ return columnHelper.accessor((row) => row.data.badges, {
8
+ id: 'badges',
9
+ header: () => <span>Badges</span>,
10
+ cell: (info) => {
11
+ const item = info.row.original;
12
+ const badges = item.data.badges || [];
13
+
14
+ if (badges?.length === 0 || !badges)
15
+ return <div className="font-light text-sm text-gray-400/60 text-left italic">No badges documented</div>;
16
+
17
+ return (
18
+ <ul>
19
+ {badges.map((badge: any, index: number) => {
20
+ return (
21
+ <li key={`${badge.id}-${index}`} className="py-1 group font-light ">
22
+ <div className="group-hover:text-primary flex space-x-1 items-center ">
23
+ <div className="flex items-center border border-gray-300 shadow-sm rounded-md">
24
+ <span className="flex items-center">
25
+ <span className={`bg-${badge.backgroundColor}-500 h-full rounded-tl rounded-bl p-1`}>
26
+ {badge.icon && <badge.icon className="h-4 w-4 text-white" />}
27
+ {!badge.icon && <Tag className="h-4 w-4 text-white" />}
28
+ </span>
29
+ <span className="leading-none px-2 group-hover:underline ">{badge.content}</span>
30
+ </span>
31
+ </div>
32
+ </div>
33
+ </li>
34
+ );
35
+ })}
36
+ </ul>
37
+ );
38
+ },
39
+ meta: {
40
+ filterVariant: 'badges',
41
+ },
42
+ filterFn: filterByBadge,
43
+ });
44
+ };
@@ -12,3 +12,8 @@ export const filterByName = (row: any, key: string, searchValue: string) => {
12
12
  const label = `${row?.original?.data.name} (v${row?.original?.data.version})` || '';
13
13
  return label.toLowerCase().includes(searchValue.toLowerCase());
14
14
  };
15
+
16
+ export const filterByBadge = (row: any, key: string, searchValue: string) => {
17
+ const badges = row?.original?.data?.badges || [];
18
+ return badges.some((badge: any) => badge.content.toLowerCase().includes(searchValue.toLowerCase()));
19
+ };
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useMemo, useState, useEffect } from 'react';
2
- import { type Edge, type Node } from 'reactflow';
2
+ import { type Edge, type Node } from '@xyflow/react';
3
3
  import {
4
4
  createEdge,
5
5
  generatedIdForEdge,
@@ -8,6 +8,8 @@ import {
8
8
  getEdgeLabelForServiceAsTarget,
9
9
  getNodesAndEdgesFromDagre,
10
10
  } from '@utils/node-graphs/utils/utils';
11
+ import type { CollectionEntry } from 'astro:content';
12
+ import type { CollectionMessageTypes, CollectionTypes } from '@types';
11
13
 
12
14
  interface EventCatalogVisualizerProps {
13
15
  nodes: Node[];
@@ -50,18 +52,21 @@ export const useEventCatalogVisualiser = ({ nodes, edges, setNodes, setEdges }:
50
52
  return [...acc, edge];
51
53
  }
52
54
 
55
+ const dataTarget = data?.target as CollectionEntry<CollectionTypes>;
56
+ const dataSource = data?.source as CollectionEntry<CollectionTypes>;
57
+
53
58
  if (sourceIsChannel) {
54
59
  const edgeLabel =
55
- data.target.collection === 'services'
56
- ? getEdgeLabelForMessageAsSource(data.source)
57
- : getEdgeLabelForServiceAsTarget(data.target);
60
+ dataTarget?.collection === 'services'
61
+ ? getEdgeLabelForMessageAsSource(dataSource as CollectionEntry<CollectionMessageTypes>)
62
+ : getEdgeLabelForServiceAsTarget(dataTarget as CollectionEntry<CollectionMessageTypes>);
58
63
 
59
64
  return [
60
65
  ...acc,
61
66
  createEdge({
62
- id: generatedIdForEdge(data.source, data.target),
63
- source: generateIdForNode(data.source),
64
- target: generateIdForNode(data.target),
67
+ id: generatedIdForEdge(dataSource, dataTarget),
68
+ source: generateIdForNode(dataSource),
69
+ target: generateIdForNode(dataTarget),
65
70
  label: edgeLabel,
66
71
  }),
67
72
  ];
@@ -100,7 +100,7 @@ const tabs = [
100
100
  </div>
101
101
 
102
102
  <!-- Table -->
103
- <div class="pb-20 ml-6">
103
+ <div class="pb-20 ml-6 md:pr-10">
104
104
  <div>
105
105
  <div class="sm:flex sm:items-center py-4 pb-4" id="discover-title">
106
106
  <div class="sm:flex-auto space-y-2">
@@ -95,7 +95,7 @@ const navigationItems = [
95
95
  },
96
96
  ];
97
97
 
98
- const sideNav = allData.reduce((acc, item) => {
98
+ const allDataAsSideNav = allData.reduce((acc, item) => {
99
99
  const title = item.collection;
100
100
  const group = acc[title] || [];
101
101
  const currentPath = Astro.url.pathname;
@@ -129,6 +129,15 @@ const sideNav = allData.reduce((acc, item) => {
129
129
  };
130
130
  }, {} as any);
131
131
 
132
+ const sideNav = {
133
+ ...(currentPath.includes('visualiser')
134
+ ? {
135
+ 'bounded context map': [{ label: 'Domain map', href: '/visualiser/context-map', collection: 'bounded-context-map' }],
136
+ }
137
+ : {}),
138
+ ...allDataAsSideNav,
139
+ };
140
+
132
141
  const currentNavigationItem = navigationItems.find((item) => item.current);
133
142
  const { title, description, sidebar: showSideBarOverride } = Astro.props;
134
143
 
@@ -236,10 +236,6 @@ const badges = [getBadge(), ...contentBadges, ...getSpecificationBadges()];
236
236
  .mermaid svg {
237
237
  margin: 1em auto 2em;
238
238
  }
239
-
240
- .mermaid tspan {
241
- font-size: 8em;
242
- }
243
239
  </style>
244
240
 
245
241
  <script define:vars={{ props }}>
@@ -251,13 +247,21 @@ const badges = [getBadge(), ...contentBadges, ...getSpecificationBadges()];
251
247
  </script>
252
248
 
253
249
  <script>
250
+ // Listen for Astro transititions
251
+ document.addEventListener('astro:page-load', () => {
252
+ const graphs = document.getElementsByClassName('mermaid');
253
+ if (document.getElementsByClassName('mermaid').length > 0) {
254
+ renderDiagrams(graphs);
255
+ }
256
+ });
257
+
254
258
  /**
255
259
  * @params {HTMLCollectionOf<HTMLElement>} graphs
256
260
  */
257
261
  async function renderDiagrams(graphs: any) {
258
262
  const { default: mermaid } = await import('mermaid');
259
263
  mermaid.initialize({
260
- fontSize: 2,
264
+ // fontSize: 2,
261
265
  flowchart: {
262
266
  curve: 'linear',
263
267
  rankSpacing: 0,
@@ -266,7 +270,7 @@ const badges = [getBadge(), ...contentBadges, ...getSpecificationBadges()];
266
270
  startOnLoad: false,
267
271
  fontFamily: 'var(--sans-font)',
268
272
  // @ts-ignore This works, but TS expects a enum for some reason
269
- theme: window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'default',
273
+ theme: 'light',
270
274
  });
271
275
 
272
276
  for (const graph of graphs) {
@@ -81,7 +81,7 @@ const ubiquitousLanguage = ubiquitousLanguages[0];
81
81
  <div class="py-4 w-full min-h-[calc(100vh-10em)]">
82
82
  {
83
83
  ubiquitousLanguage && (
84
- <>
84
+ <div>
85
85
  <div class="mb-6">
86
86
  <input
87
87
  type="text"
@@ -102,13 +102,13 @@ const ubiquitousLanguage = ubiquitousLanguages[0];
102
102
  <div class="term-card block bg-white border border-gray-200 rounded-lg p-6 transition-all duration-300 ease-in-out hover:shadow-md hover:border-primary focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-primary focus:ring-white min-h-[12em] cursor-pointer">
103
103
  <div class="flex flex-col h-full space-y-8">
104
104
  {term.icon && (
105
- <>
105
+ <div>
106
106
  {(() => {
107
107
  const Icon = LucideIcons[term.icon as keyof typeof LucideIcons];
108
108
  //@ts-ignore
109
109
  return Icon ? <Icon className="w-6 h-6 text-primary" /> : null;
110
110
  })()}
111
- </>
111
+ </div>
112
112
  )}
113
113
  <div>
114
114
  <h3 class="text-gray-800 text-lg font-semibold transition-colors duration-300 ease-in-out group-hover:text-gray-300">
@@ -119,12 +119,12 @@ const ubiquitousLanguage = ubiquitousLanguages[0];
119
119
  {term.summary}
120
120
  </p>
121
121
  {term.description && (
122
- <>
122
+ <div>
123
123
  <p class="description-text hidden text-gray-600 transition-colors duration-300 ease-in-out group-hover:text-gray-200 m-0 font-light text-sm whitespace-pre-line">
124
124
  {term.description}
125
125
  </p>
126
126
  <span class="show-more-text text-sm text-primary font-medium">Show more</span>
127
- </>
127
+ </div>
128
128
  )}
129
129
  </div>
130
130
  </div>
@@ -138,7 +138,7 @@ const ubiquitousLanguage = ubiquitousLanguages[0];
138
138
  <h3 class="text-lg font-medium text-gray-900">No matching terms</h3>
139
139
  <p class="mt-2 text-sm text-gray-500">Try adjusting your search terms.</p>
140
140
  </div>
141
- </>
141
+ </div>
142
142
  )
143
143
  }
144
144
  </div>
@@ -0,0 +1,30 @@
1
+ ---
2
+ import NodeGraph from '@components/MDX/NodeGraph/NodeGraph.astro';
3
+ import { ViewTransitions } from 'astro:transitions';
4
+ import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
5
+ ---
6
+
7
+ <VerticalSideBarLayout title={`Visualiser | Bounded context map`}>
8
+ <div class="bg-gray-100/50 m-4">
9
+ <div
10
+ class="h-[calc(100vh-110px)] w-full relative border border-gray-200"
11
+ id={`domain-context-map-portal`}
12
+ transition:animate="fade"
13
+ >
14
+ </div>
15
+ <NodeGraph
16
+ id="domain-context-map"
17
+ collection="domain-context-map"
18
+ title="Bounded context map"
19
+ mode="full"
20
+ linkTo="visualiser"
21
+ version="1.0.0"
22
+ linksToVisualiser={false}
23
+ href={{
24
+ label: ``,
25
+ url: '/',
26
+ }}
27
+ />
28
+ </div>
29
+ <ViewTransitions />
30
+ </VerticalSideBarLayout>
@@ -9,6 +9,7 @@ import {
9
9
  UserIcon,
10
10
  ArrowsRightLeftIcon,
11
11
  VariableIcon,
12
+ MapIcon,
12
13
  } from '@heroicons/react/24/outline';
13
14
  import { BookText } from 'lucide-react';
14
15
 
@@ -36,6 +37,8 @@ export const getIconForCollection = (collection: string) => {
36
37
  return VariableIcon;
37
38
  case 'ubiquitousLanguages':
38
39
  return BookText;
40
+ case 'bounded-context-map':
41
+ return MapIcon;
39
42
  default:
40
43
  return ServerIcon;
41
44
  }
@@ -2,6 +2,7 @@ import { getItemsFromCollectionByIdAndSemverOrLatest, getVersionForCollectionIte
2
2
  import { getCollection } from 'astro:content';
3
3
  import type { CollectionEntry } from 'astro:content';
4
4
  import path from 'path';
5
+ import semver from 'semver';
5
6
 
6
7
  const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
7
8
 
@@ -65,3 +66,37 @@ export const getServices = async ({ getAllVersions = true }: Props = {}): Promis
65
66
  };
66
67
  });
67
68
  };
69
+
70
+ export const getProducersOfMessage = (services: Service[], message: CollectionEntry<'events' | 'commands' | 'queries'>) => {
71
+ return services.filter((service) => {
72
+ return service.data.sends?.some((send) => {
73
+ const idMatch = send.id === message.data.id;
74
+
75
+ // If no version specified in send, treat as 'latest'
76
+ if (!send.version) return idMatch;
77
+
78
+ // If version is 'latest', match any version
79
+ if (send.version === 'latest') return idMatch;
80
+
81
+ // Use semver to compare versions
82
+ return idMatch && semver.satisfies(message.data.version, send.version);
83
+ });
84
+ });
85
+ };
86
+
87
+ export const getConsumersOfMessage = (services: Service[], message: CollectionEntry<'events' | 'commands' | 'queries'>) => {
88
+ return services.filter((service) => {
89
+ return service.data.receives?.some((receive) => {
90
+ const idMatch = receive.id === message.data.id;
91
+
92
+ // If no version specified in send, treat as 'latest'
93
+ if (!receive.version) return idMatch;
94
+
95
+ // If version is 'latest', match any version
96
+ if (receive.version === 'latest') return idMatch;
97
+
98
+ // Use semver to compare versions
99
+ return idMatch && semver.satisfies(message.data.version, receive.version);
100
+ });
101
+ });
102
+ };
@@ -1,19 +1,222 @@
1
1
  import { getCollection } from 'astro:content';
2
- import { createDagreGraph, calculatedNodes } from '@utils/node-graphs/utils/utils';
2
+ import {
3
+ createDagreGraph,
4
+ calculatedNodes,
5
+ generateIdForNode,
6
+ createNode,
7
+ getEdgeLabelForServiceAsTarget,
8
+ generatedIdForEdge,
9
+ createEdge,
10
+ } from '@utils/node-graphs/utils/utils';
3
11
  import { getNodesAndEdges as getServicesNodeAndEdges } from './services-node-graph';
4
12
  import merge from 'lodash.merge';
5
13
  import { getItemsFromCollectionByIdAndSemverOrLatest } from '@utils/collections/util';
14
+ import type { Node } from '@xyflow/react';
15
+ import { getProducersOfMessage } from '@utils/collections/services';
6
16
 
7
17
  type DagreGraph = any;
8
18
 
9
19
  interface Props {
20
+ defaultFlow?: DagreGraph;
21
+ }
22
+
23
+ export const getNodesAndEdgesForDomainContextMap = async ({ defaultFlow = null }: Props) => {
24
+ const flow = defaultFlow ?? createDagreGraph({ ranksep: 360, nodesep: 50, edgesep: 50 });
25
+ let nodes = [] as any,
26
+ edges = [] as any;
27
+
28
+ const allDomains = await getCollection('domains');
29
+ const domains = allDomains.filter((domain) => !domain.id.includes('/versioned'));
30
+ const services = await getCollection('services');
31
+
32
+ const events = await getCollection('events');
33
+ const commands = await getCollection('commands');
34
+ const queries = await getCollection('queries');
35
+
36
+ const messages = [...events, ...commands, ...queries];
37
+
38
+ domains.forEach((domain, index) => {
39
+ const nodeId = generateIdForNode(domain);
40
+ const rawServices = domain.data.services ?? [];
41
+ const domainServices = rawServices
42
+ .map((service) => getItemsFromCollectionByIdAndSemverOrLatest(services, service.id, service.version))
43
+ .flat()
44
+ .filter((e) => e !== undefined);
45
+
46
+ // Calculate domain node size based on services
47
+ const servicesCount = domainServices.length;
48
+ const SERVICES_PER_ROW = 1;
49
+ const SERVICE_WIDTH = 330;
50
+ const SERVICE_HEIGHT = 100;
51
+ const PADDING = 40;
52
+ const TITLE_HEIGHT = 20;
53
+
54
+ const rows = Math.ceil(servicesCount / SERVICES_PER_ROW);
55
+ const domainWidth = SERVICE_WIDTH * SERVICES_PER_ROW;
56
+ const domainHeight = SERVICE_HEIGHT * rows + PADDING * 4;
57
+
58
+ // Position domains in a grid layout
59
+ const DOMAINS_PER_ROW = 2;
60
+ const rowIndex = Math.floor(index / DOMAINS_PER_ROW);
61
+ const colIndex = index % DOMAINS_PER_ROW;
62
+
63
+ const test = servicesCount * SERVICE_HEIGHT + PADDING * 2;
64
+
65
+ nodes.push({
66
+ id: nodeId,
67
+ type: 'group',
68
+ position: {
69
+ x: colIndex * (domainWidth + 400), // Increased from 100 to 400px gap between domains
70
+ y: rowIndex * (domainHeight + 300), // Increased from 100 to 300px gap between rows
71
+ },
72
+ style: {
73
+ width: domainWidth,
74
+ height: domainHeight,
75
+ backgroundColor: 'transparent',
76
+ borderRadius: '8px',
77
+ border: '1px solid #ddd',
78
+ 'box-shadow': '0 0 10px 0 rgba(0, 0, 0, 0.1)',
79
+ },
80
+ data: {
81
+ label: domain.data.name,
82
+ domain,
83
+ },
84
+ });
85
+
86
+ nodes.push({
87
+ id: `domain-context-map-title-${domain.data.name}`,
88
+ data: { label: `Bounded Context: ${domain.data.name}` },
89
+ position: { x: 0, y: 0 },
90
+ style: {
91
+ height: 40,
92
+ backgroundColor: 'transparent',
93
+ border: 'none',
94
+ color: 'black',
95
+ width: domainWidth,
96
+ },
97
+ extent: 'parent',
98
+ parentId: nodeId,
99
+ connectable: false,
100
+ sourcePosition: 'left',
101
+ targetPosition: 'right',
102
+ draggable: false,
103
+ } as Node);
104
+
105
+ // Position services in a grid within the domain
106
+ if (domainServices) {
107
+ domainServices.forEach((service, serviceIndex) => {
108
+ const row = Math.floor(serviceIndex / SERVICES_PER_ROW);
109
+ const col = serviceIndex % SERVICES_PER_ROW;
110
+ const serviceNodeId = `service-${domain.id}-${service.id}`;
111
+
112
+ // Add spacing between services
113
+ const SERVICE_MARGIN = 25;
114
+ const xPosition = PADDING + col * (SERVICE_WIDTH + SERVICE_MARGIN) + 20;
115
+ const yPosition = PADDING + row * (SERVICE_HEIGHT + SERVICE_MARGIN) + TITLE_HEIGHT;
116
+
117
+ nodes.push({
118
+ id: generateIdForNode(service),
119
+ sourcePosition: 'right',
120
+ targetPosition: 'left',
121
+ type: 'services',
122
+ position: {
123
+ x: xPosition,
124
+ y: yPosition,
125
+ },
126
+ parentId: nodeId,
127
+ extent: 'parent',
128
+ draggable: false,
129
+ data: {
130
+ mode: 'full',
131
+ service,
132
+ },
133
+ });
134
+
135
+ // Edges
136
+ const rawReceives = service.data.receives ?? [];
137
+ const rawSends = service.data.sends ?? [];
138
+
139
+ const receives = rawReceives
140
+ .map((receive) => getItemsFromCollectionByIdAndSemverOrLatest(messages, receive.id, receive.version))
141
+ .flat();
142
+ const sends = rawSends.map((send) => getItemsFromCollectionByIdAndSemverOrLatest(messages, send.id, send.version)).flat();
143
+
144
+ for (const receive of receives) {
145
+ const producers = getProducersOfMessage(services, receive);
146
+
147
+ for (const producer of producers) {
148
+ const isSameDomain = domainServices.some((domainService) => domainService.data.id === producer.data.id);
149
+
150
+ if (!isSameDomain) {
151
+ // WIP... adding messages?
152
+ // edges.push(createEdge({
153
+ // id: generatedIdForEdge(receive, service),
154
+ // source: generateIdForNode(receive),
155
+ // target: generateIdForNode(service),
156
+ // label: getEdgeLabelForServiceAsTarget(receive),
157
+ // zIndex: 1000,
158
+ // }));
159
+
160
+ // Find the producer and consumer nodes to get their positions
161
+ // const producerNode = nodes.find(n => n.id === generateIdForNode(producer));
162
+ // const consumerNode = nodes.find(n => n.id === generateIdForNode(service));
163
+
164
+ edges.push(
165
+ createEdge({
166
+ id: generatedIdForEdge(producer, service),
167
+ source: generateIdForNode(producer),
168
+ target: generateIdForNode(service),
169
+ label: getEdgeLabelForServiceAsTarget(receive),
170
+ zIndex: 1000,
171
+ })
172
+ );
173
+
174
+ // // Calculate middle position between producer and consumer
175
+ // const messageX = (producerNode?.position?.x ?? 0) +
176
+ // ((consumerNode?.position?.x ?? 0) - (producerNode?.position?.x ?? 0)) / 2;
177
+ // const messageY = (producerNode?.position?.y ?? 0) +
178
+ // ((consumerNode?.position?.y ?? 0) - (producerNode?.position?.y ?? 0)) / 2;
179
+
180
+ // nodes.push({
181
+ // id: generateIdForNode(receive),
182
+ // type: receive.collection,
183
+ // sourcePosition: 'right',
184
+ // targetPosition: 'left',
185
+ // data: {
186
+ // message: receive,
187
+ // mode: 'full',
188
+ // },
189
+ // position: { x: messageX, y: messageY },
190
+ // });
191
+
192
+ // edges.push(createEdge({
193
+ // id: generatedIdForEdge(producer, receive),
194
+ // source: generateIdForNode(producer),
195
+ // target: generateIdForNode(receive),
196
+ // label: getEdgeLabelForServiceAsTarget(receive),
197
+ // zIndex: 1000,
198
+ // }));
199
+ }
200
+ }
201
+ }
202
+ });
203
+ }
204
+ });
205
+
206
+ return {
207
+ nodes,
208
+ edges,
209
+ };
210
+ };
211
+
212
+ interface NodesAndEdgesProps {
10
213
  id: string;
11
214
  version: string;
12
215
  defaultFlow?: DagreGraph;
13
- mode?: 'simple' | 'full';
216
+ mode: 'simple' | 'full';
14
217
  }
15
218
 
16
- export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simple' }: Props) => {
219
+ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simple' }: NodesAndEdgesProps) => {
17
220
  const flow = defaultFlow || createDagreGraph({ ranksep: 360, nodesep: 50, edgesep: 50 });
18
221
  let nodes = new Map(),
19
222
  edges = new Map();
@@ -1,8 +1,8 @@
1
1
  import { getCollection, type CollectionEntry } from 'astro:content';
2
2
  import dagre from 'dagre';
3
3
  import { createDagreGraph, calculatedNodes } from '@utils/node-graphs/utils/utils';
4
- import { MarkerType } from 'reactflow';
5
- import type { Node as NodeType } from 'reactflow';
4
+ import { MarkerType } from '@xyflow/react';
5
+ import type { Node as NodeType } from '@xyflow/react';
6
6
  import { getItemsFromCollectionByIdAndSemverOrLatest } from '@utils/collections/util';
7
7
 
8
8
  type DagreGraph = any;
@@ -12,7 +12,7 @@ import {
12
12
  getEdgeLabelForMessageAsSource,
13
13
  getEdgeLabelForServiceAsTarget,
14
14
  } from './utils/utils';
15
- import { MarkerType } from 'reactflow';
15
+ import { MarkerType } from '@xyflow/react';
16
16
  import { findMatchingNodes } from '@utils/collections/util';
17
17
  import type { CollectionMessageTypes } from '@types';
18
18
  import { getCommands } from '@utils/commands';
@@ -9,7 +9,7 @@ import {
9
9
  getChannelNodesAndEdges,
10
10
  } from '@utils/node-graphs/utils/utils';
11
11
  import { findMatchingNodes, getItemsFromCollectionByIdAndSemverOrLatest } from '@utils/collections/util';
12
- import { MarkerType } from 'reactflow';
12
+ import { MarkerType } from '@xyflow/react';
13
13
  import type { CollectionMessageTypes } from '@types';
14
14
 
15
15
  type DagreGraph = any;
@@ -1,5 +1,5 @@
1
1
  import { getCollection, type CollectionEntry } from 'astro:content';
2
- import { MarkerType, Position, type Edge, type Node } from 'reactflow';
2
+ import { MarkerType, Position, type Edge, type Node } from '@xyflow/react';
3
3
  import dagre from 'dagre';
4
4
  import { getItemsFromCollectionByIdAndSemverOrLatest } from '@utils/collections/util';
5
5
  import type { CollectionMessageTypes, CollectionTypes } from '@types';