@eventcatalog/core 2.9.1 → 2.10.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.
Files changed (34) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +4 -3
  3. package/scripts/catalog-to-astro-content-directory.js +1 -3
  4. package/scripts/default-files-for-collections/queries.md +8 -0
  5. package/scripts/eventcatalog-config-file-utils.js +3 -14
  6. package/scripts/map-catalog-to-astro.js +12 -1
  7. package/src/components/DiscoverInsight.astro +1 -1
  8. package/src/components/DocsNavigation.astro +3 -1
  9. package/src/components/MDX/NodeGraph/NodeGraph.astro +12 -0
  10. package/src/components/MDX/NodeGraph/NodeGraph.tsx +12 -6
  11. package/src/components/MDX/NodeGraph/Nodes/Query.tsx +74 -0
  12. package/src/components/SideBars/MessageSideBar.astro +34 -3
  13. package/src/components/Tables/columns/MessageTableColumns.tsx +16 -3
  14. package/src/components/Tables/columns/ServiceTableColumns.tsx +2 -3
  15. package/src/components/Tables/columns/index.tsx +1 -0
  16. package/src/content/config.ts +11 -0
  17. package/src/layouts/DiscoverLayout.astro +11 -0
  18. package/src/layouts/VisualiserLayout.astro +45 -39
  19. package/src/pages/discover/[type]/index.astro +1 -1
  20. package/src/pages/docs/[type]/[id]/[version]/asyncapi/index.astro +1 -1
  21. package/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +5 -2
  22. package/src/pages/docs/[type]/[id]/[version]/index.astro +6 -2
  23. package/src/pages/docs/[type]/[id]/[version]/spec/index.astro +1 -1
  24. package/src/pages/index.astro +12 -3
  25. package/src/pages/visualiser/[type]/[id]/[version]/index.astro +1 -1
  26. package/src/types/index.ts +3 -3
  27. package/src/utils/flows/node-graph.ts +5 -4
  28. package/src/utils/messages.ts +3 -1
  29. package/src/utils/node-graph-utils/utils.ts +3 -3
  30. package/src/utils/pages/pages.ts +2 -0
  31. package/src/utils/queries/node-graph.ts +118 -0
  32. package/src/utils/queries.ts +65 -0
  33. package/src/utils/services/node-graph.ts +31 -5
  34. package/src/utils/services/services.ts +6 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @eventcatalog/core
2
2
 
3
+ ## 2.10.1
4
+
5
+ ### Patch Changes
6
+
7
+ - e2c99e7: fix(core): fixed issue adding additional properties to users frontmatter on build
8
+
9
+ ## 2.10.0
10
+
11
+ ### Minor Changes
12
+
13
+ - aa2090b: feat(core): added support for query message types/resources
14
+
3
15
  ## 2.9.1
4
16
 
5
17
  ### Patch Changes
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.9.1",
9
+ "version": "2.10.1",
10
10
  "publishConfig": {
11
11
  "access": "public"
12
12
  },
@@ -66,6 +66,7 @@
66
66
  "diff": "^7.0.0",
67
67
  "diff2html": "^3.4.48",
68
68
  "glob": "^10.4.1",
69
+ "gray-matter": "^4.0.3",
69
70
  "html-to-image": "^1.11.11",
70
71
  "lodash.merge": "4.6.2",
71
72
  "mermaid": "^10.9.1",
@@ -75,12 +76,12 @@
75
76
  "reactflow": "^11.11.4",
76
77
  "rehype-slug": "^6.0.0",
77
78
  "remark-gfm": "^3.0.1",
79
+ "rimraf": "^5.0.7",
78
80
  "semver": "7.6.3",
79
81
  "tailwindcss": "^3.4.3",
80
82
  "typescript": "^5.4.5",
81
83
  "unist-util-visit": "^5.0.0",
82
- "uuid": "^10.0.0",
83
- "rimraf": "^5.0.7"
84
+ "uuid": "^10.0.0"
84
85
  },
85
86
  "devDependencies": {
86
87
  "@changesets/cli": "^2.27.5",
@@ -41,7 +41,7 @@ const copyFiles = async (source, target) => {
41
41
 
42
42
  const ensureAstroCollectionNotEmpty = async (astroDir) => {
43
43
  // TODO: maybe import collections from `src/content/config.ts`...
44
- const COLLECTIONS = ['events', 'commands', 'services', 'users', 'teams', 'domains', 'flows', 'pages', 'changelogs'];
44
+ const COLLECTIONS = ['events', 'commands', 'services', 'users', 'teams', 'domains', 'flows', 'pages', 'changelogs', 'queries'];
45
45
 
46
46
  // Check empty collections
47
47
  const emptyCollections = [];
@@ -69,8 +69,6 @@ const ensureAstroCollectionNotEmpty = async (astroDir) => {
69
69
  export const catalogToAstro = async (source, astroDir) => {
70
70
  const astroContentDir = path.join(astroDir, 'src/content/');
71
71
 
72
- console.log(path.join(astroContentDir, 'config.ts'));
73
-
74
72
  // Config file
75
73
  const astroConfigFile = fs.readFileSync(path.join(astroContentDir, 'config.ts'));
76
74
 
@@ -0,0 +1,8 @@
1
+ ---
2
+ id: empty
3
+ name: empty
4
+ version: 0.0.1
5
+ hidden: true
6
+ ---
7
+
8
+ <!-- Do not delete this file, required for EC, you an ignore this file -->
@@ -4,6 +4,7 @@ import { copyFile } from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
  import { v4 as uuidV4 } from 'uuid';
6
6
  import { pathToFileURL } from 'url';
7
+ import matter from 'gray-matter';
7
8
 
8
9
  export async function cleanup(projectDirectory) {
9
10
  const filePath = path.join(projectDirectory, 'eventcatalog.config.mjs');
@@ -82,19 +83,7 @@ export const verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirector
82
83
  };
83
84
 
84
85
  export function addPropertyToFrontMatter(input, newProperty, newValue) {
85
- // Split the input into front matter and content
86
- const [_, frontMatter, content] = input.split('---');
86
+ const file = matter(input);
87
87
 
88
- // Parse the front matter
89
- const frontMatterLines = frontMatter.trim().split('\n');
90
- const updatedFrontMatterLines = [...frontMatterLines];
91
-
92
- // Add the new property
93
- updatedFrontMatterLines.push(`${newProperty}: ${newValue}`);
94
-
95
- // Reconstruct the updated input
96
- const updatedFrontMatter = updatedFrontMatterLines.join('\n');
97
- const updatedInput = `---\n${updatedFrontMatter}\n---\n${content.trim()}`;
98
-
99
- return updatedInput;
88
+ return matter.stringify(file.content, { ...file.data, [newProperty]: newValue });
100
89
  }
@@ -1,6 +1,17 @@
1
1
  import path from 'node:path';
2
2
 
3
- const COLLECTION_KEYS = ['events', 'commands', 'services', 'users', 'teams', 'domains', 'flows', 'pages', 'changelogs'];
3
+ const COLLECTION_KEYS = [
4
+ 'events',
5
+ 'commands',
6
+ 'services',
7
+ 'users',
8
+ 'teams',
9
+ 'domains',
10
+ 'flows',
11
+ 'pages',
12
+ 'changelogs',
13
+ 'queries',
14
+ ];
4
15
 
5
16
  /**
6
17
  * @typedef {Object} MapCatalogToAstroParams
@@ -6,7 +6,7 @@ interface Props {
6
6
  color: string;
7
7
  dataTarget: number;
8
8
  icon: React.ElementType;
9
- label: 'domains' | 'services' | 'commands' | 'events' | 'flows';
9
+ label: 'domains' | 'services' | 'commands' | 'queries' | 'events' | 'flows';
10
10
  }
11
11
 
12
12
  const { color, dataTarget, icon: Icon, label } = Astro.props;
@@ -8,16 +8,18 @@ import { getTeams } from '@utils/teams';
8
8
  import { getUsers } from '@utils/users';
9
9
  import config, { type CatalogConfig } from '@eventcatalog';
10
10
  import { buildUrl } from '@utils/url-builder';
11
+ import { getQueries } from '@utils/queries';
11
12
 
12
13
  const events = await getEvents({ getAllVersions: false });
13
14
  const commands = await getCommands({ getAllVersions: false });
15
+ const queries = await getQueries({ getAllVersions: false });
14
16
  const services = await getServices({ getAllVersions: false });
15
17
  const domains = await getDomains({ getAllVersions: false });
16
18
  const flows = await getFlows({ getAllVersions: false });
17
19
  const teams = await getTeams();
18
20
  const users = await getUsers();
19
21
 
20
- const messages = [...events, ...commands];
22
+ const messages = [...events, ...commands, ...queries];
21
23
 
22
24
  // @ts-ignore for large catalogs https://github.com/event-catalog/eventcatalog/issues/552
23
25
  const allData = [...domains, ...services, ...messages, ...flows, ...teams, ...users];
@@ -3,6 +3,7 @@ import NodeGraphNew from './NodeGraph';
3
3
  import { getNodesAndEdges as getNodesAndEdgesForService } from '@utils/services/node-graph';
4
4
  import { getNodesAndEdges as getNodesAndEdgesForEvent } from '@utils/events/node-graph';
5
5
  import { getNodesAndEdges as getNodesAndEdgesForCommand } from '@utils/commands/node-graph';
6
+ import { getNodesAndEdges as getNodesAndEdgesForQueries } from '@utils/queries/node-graph';
6
7
  import { getNodesAndEdges as getNodesAndEdgesForDomain } from '@utils/domains/node-graph';
7
8
  import { getNodesAndEdges as getNodesAndEdgesForFlows } from '@utils/flows/node-graph';
8
9
 
@@ -57,6 +58,17 @@ if (collection === 'commands') {
57
58
  edges = eventEdges;
58
59
  }
59
60
 
61
+ if (collection === 'queries') {
62
+ const { nodes: eventNodes, edges: eventEdges } = await getNodesAndEdgesForQueries({
63
+ id: id,
64
+ version,
65
+ mode,
66
+ });
67
+
68
+ nodes = eventNodes;
69
+ edges = eventEdges;
70
+ }
71
+
60
72
  if (collection === 'domains') {
61
73
  const { nodes: eventNodes, edges: eventEdges } = await getNodesAndEdgesForDomain({
62
74
  id: id,
@@ -15,6 +15,7 @@ import ReactFlow, {
15
15
  import 'reactflow/dist/style.css';
16
16
  import ServiceNode from './Nodes/Service';
17
17
  import EventNode from './Nodes/Event';
18
+ import QueryNode from './Nodes/Query';
18
19
  import UserNode from './Nodes/User';
19
20
  import StepNode from './Nodes/Step';
20
21
  import CommandNode from './Nodes/Command';
@@ -57,6 +58,7 @@ const NodeGraphBuilder = ({
57
58
  () => ({
58
59
  services: ServiceNode,
59
60
  events: EventNode,
61
+ queries: QueryNode,
60
62
  commands: CommandNode,
61
63
  step: StepNode,
62
64
  user: UserNode,
@@ -160,21 +162,25 @@ const NodeGraphBuilder = ({
160
162
  {includeBackground && <Controls />}
161
163
  {includeKey && (
162
164
  <Panel position="bottom-right">
163
- <div className=" bg-white font-light px-4 text-[14px] shadow-md py-1 rounded-md">
164
- <span className="font-bold">Key</span>
165
- <ul>
166
- <li className="flex space-x-2 items-center text-[12px]">
165
+ <div className=" bg-white font-light px-4 text-[12px] shadow-md py-1 rounded-md">
166
+ {/* <span className="font-bold">Key</span> */}
167
+ <ul className="m-0 p-0">
168
+ <li className="flex space-x-2 items-center text-[10px]">
167
169
  <span className="w-2 h-2 bg-orange-500 block" />
168
170
  <span className="block">Event</span>
169
171
  </li>
170
- <li className="flex space-x-2 items-center text-[12px]">
172
+ <li className="flex space-x-2 items-center text-[10px]">
171
173
  <span className="w-2 h-2 bg-pink-500 block" />
172
174
  <span className="block">Service</span>
173
175
  </li>
174
- <li className="flex space-x-2 items-center text-[12px]">
176
+ <li className="flex space-x-2 items-center text-[10px]">
175
177
  <span className="w-2 h-2 bg-blue-500 block" />
176
178
  <span className="block">Command</span>
177
179
  </li>
180
+ <li className="flex space-x-2 items-center text-[10px]">
181
+ <span className="w-2 h-2 bg-green-500 block" />
182
+ <span className="block">Query</span>
183
+ </li>
178
184
  </ul>
179
185
  </div>
180
186
  </Panel>
@@ -0,0 +1,74 @@
1
+ import { MagnifyingGlassIcon } from '@heroicons/react/16/solid';
2
+ import type { CollectionEntry } from 'astro:content';
3
+ import { Handle } from 'reactflow';
4
+
5
+ interface Data {
6
+ title: string;
7
+ label: string;
8
+ bgColor: string;
9
+ color: string;
10
+ mode: 'simple' | 'full';
11
+ message: CollectionEntry<'queries'>;
12
+ showTarget?: boolean;
13
+ showSource?: boolean;
14
+ }
15
+
16
+ function classNames(...classes: any) {
17
+ return classes.filter(Boolean).join(' ');
18
+ }
19
+
20
+ export default function QueryNode({ data, sourcePosition, targetPosition }: any) {
21
+ const { mode, message, showTarget = true, showSource = true } = data as Data;
22
+
23
+ const { name, version, summary, owners = [], producers = [], consumers = [] } = message.data;
24
+
25
+ const renderTarget = showTarget || (targetPosition && producers.length > 0);
26
+ const renderSource = showSource || (sourcePosition && consumers.length > 0);
27
+
28
+ return (
29
+ <div className={classNames('w-full rounded-md border flex justify-start bg-white text-black border-green-400')}>
30
+ <div
31
+ className={classNames(
32
+ 'bg-gradient-to-b from-green-500 to-green-700 relative flex items-center w-5 justify-center rounded-l-sm text-green-100-500',
33
+ `border-r-[1px] border-green-500`
34
+ )}
35
+ >
36
+ <MagnifyingGlassIcon className="w-4 h-4 opacity-90 text-white absolute top-1 " />
37
+ {mode === 'full' && (
38
+ <span className="rotate -rotate-90 w-1/2 text-center absolute bottom-1 text-[9px] text-white font-bold uppercase tracking-[3px] ">
39
+ Query
40
+ </span>
41
+ )}
42
+ </div>
43
+ <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} />}
46
+ <div className={classNames(mode === 'full' ? `border-b border-gray-200` : '')}>
47
+ <span className="text-xs font-bold block pb-0.5">{name}</span>
48
+ <div className="flex justify-between">
49
+ <span className="text-[10px] font-light block pt-0.5 pb-0.5 ">v{version}</span>
50
+ {mode === 'simple' && <span className="text-[10px] text-gray-500 font-light block pt-0.5 pb-0.5 ">Query</span>}
51
+ </div>
52
+ </div>
53
+ {mode === 'full' && (
54
+ <div className="divide-y divide-gray-200 ">
55
+ <div className="leading-3 py-1">
56
+ <span className="text-[8px] font-light">{summary}</span>
57
+ </div>
58
+ <div className="grid grid-cols-2 gap-x-4 py-1">
59
+ <span className="text-xs" style={{ fontSize: '0.2em' }}>
60
+ Producers: {producers.length}
61
+ </span>
62
+ <span className="text-xs" style={{ fontSize: '0.2em' }}>
63
+ Consumers: {consumers.length}
64
+ </span>
65
+ <span className="text-xs" style={{ fontSize: '0.2em' }}>
66
+ Owners: {owners.length}
67
+ </span>
68
+ </div>
69
+ </div>
70
+ )}
71
+ </div>
72
+ </div>
73
+ );
74
+ }
@@ -38,7 +38,38 @@ const ownersList = owners.map((o) => ({
38
38
  href: buildUrl(`/docs/${o.collection}/${o.data.id}`),
39
39
  }));
40
40
 
41
- const type = message.collection.slice(0, -1);
41
+ const getType = (type: string) => {
42
+ switch (type) {
43
+ case 'queries':
44
+ return 'Query';
45
+ default:
46
+ return message.collection.slice(0, -1);
47
+ }
48
+ };
49
+
50
+ const getProducerEmptyMessage = (type: string) => {
51
+ const value = type.toLowerCase();
52
+ switch (value) {
53
+ case 'query':
54
+ case 'command':
55
+ return `This ${value} does not get invoked by any services.`;
56
+ default:
57
+ return `This ${value} does not get produced by any services.`;
58
+ }
59
+ };
60
+
61
+ const getConsumerEmptyMessage = (type: string) => {
62
+ const value = type.toLowerCase();
63
+ switch (value) {
64
+ case 'query':
65
+ case 'command':
66
+ return `This ${value} does not invoke any service.`;
67
+ default:
68
+ return `This ${value} does not get consumed by any services.`;
69
+ }
70
+ };
71
+
72
+ const type = getType(message.collection);
42
73
 
43
74
  // @ts-ignore
44
75
  const publicPath = message?.catalog?.publicPath;
@@ -52,14 +83,14 @@ const schemaURL = path.join(publicPath, schemaFilePath || '');
52
83
  color="pink"
53
84
  title={`${type} Producers (${producerList.length})`}
54
85
  pills={producerList}
55
- emptyMessage={`This ${type} does not get produced by any services.`}
86
+ emptyMessage={getProducerEmptyMessage(type)}
56
87
  client:load
57
88
  />
58
89
  <PillList
59
90
  color="pink"
60
91
  title={`${type} Consumers (${consumerList.length})`}
61
92
  pills={consumerList}
62
- emptyMessage={`This ${type} does not get consumed by any services.`}
93
+ emptyMessage={getConsumerEmptyMessage(type)}
63
94
  client:load
64
95
  />
65
96
  <OwnersList
@@ -1,4 +1,4 @@
1
- import { ServerIcon, BoltIcon, ChatBubbleLeftIcon } from '@heroicons/react/24/solid';
1
+ import { ServerIcon, BoltIcon, ChatBubbleLeftIcon, MagnifyingGlassIcon } from '@heroicons/react/24/solid';
2
2
  import { createColumnHelper } from '@tanstack/react-table';
3
3
  import type { CollectionMessageTypes } from '@types';
4
4
  import type { CollectionEntry } from 'astro:content';
@@ -8,6 +8,20 @@ import { buildUrl } from '@utils/url-builder';
8
8
 
9
9
  const columnHelper = createColumnHelper<CollectionEntry<CollectionMessageTypes>>();
10
10
 
11
+ export const getColorAndIconForMessageType = (type: string) => {
12
+ switch (type) {
13
+ case 'event':
14
+ return { color: 'orange', Icon: BoltIcon };
15
+ case 'command':
16
+ return { color: 'blue', Icon: ChatBubbleLeftIcon };
17
+ case 'querie':
18
+ case 'query':
19
+ return { color: 'green', Icon: MagnifyingGlassIcon };
20
+ default:
21
+ return { color: 'gray', Icon: ChatBubbleLeftIcon };
22
+ }
23
+ };
24
+
11
25
  export const columns = () => [
12
26
  columnHelper.accessor('data.name', {
13
27
  id: 'name',
@@ -15,8 +29,7 @@ export const columns = () => [
15
29
  cell: (info) => {
16
30
  const messageRaw = info.row.original;
17
31
  const type = useMemo(() => messageRaw.collection.slice(0, -1), [messageRaw.collection]);
18
- const color = type === 'event' ? 'orange' : 'blue';
19
- const Icon = type === 'event' ? BoltIcon : ChatBubbleLeftIcon;
32
+ const { color, Icon } = getColorAndIconForMessageType(type);
20
33
  return (
21
34
  <div className=" group ">
22
35
  <a
@@ -4,6 +4,7 @@ import type { CollectionEntry } from 'astro:content';
4
4
  import { useMemo } from 'react';
5
5
  import { filterByName, filterCollectionByName } from '../filters/custom-filters';
6
6
  import { buildUrl } from '@utils/url-builder';
7
+ import { getColorAndIconForMessageType } from './MessageTableColumns';
7
8
 
8
9
  const columnHelper = createColumnHelper<CollectionEntry<'services'>>();
9
10
 
@@ -13,7 +14,6 @@ export const columns = () => [
13
14
  header: () => <span>Service</span>,
14
15
  cell: (info) => {
15
16
  const messageRaw = info.row.original;
16
- const type = useMemo(() => messageRaw.collection.slice(0, -1), [messageRaw.collection]);
17
17
  const color = 'pink';
18
18
  return (
19
19
  <div className="group font-light">
@@ -73,8 +73,7 @@ export const columns = () => [
73
73
  <ul>
74
74
  {receives.map((consumer: any) => {
75
75
  const type = consumer.collection.slice(0, -1);
76
- const color = type === 'event' ? 'orange' : 'blue';
77
- const Icon = type === 'event' ? BoltIcon : ChatBubbleLeftIcon;
76
+ const { color, Icon } = useMemo(() => getColorAndIconForMessageType(type), [type]);
78
77
  return (
79
78
  <li key={consumer.data.id} className="py-1 group font-light ">
80
79
  <a
@@ -7,6 +7,7 @@ export const getColumnsByCollection = (collection: string): any => {
7
7
  switch (collection) {
8
8
  case 'events':
9
9
  case 'commands':
10
+ case 'queries':
10
11
  return MessageTableColumns();
11
12
  case 'services':
12
13
  return ServiceTableColumns();
@@ -150,6 +150,16 @@ const commands = defineCollection({
150
150
  .merge(baseSchema),
151
151
  });
152
152
 
153
+ const queries = defineCollection({
154
+ type: 'content',
155
+ schema: z
156
+ .object({
157
+ producers: z.array(reference('services')).optional(),
158
+ consumers: z.array(reference('services')).optional(),
159
+ })
160
+ .merge(baseSchema),
161
+ });
162
+
153
163
  const services = defineCollection({
154
164
  type: 'content',
155
165
  schema: z
@@ -207,6 +217,7 @@ const teams = defineCollection({
207
217
  export const collections = {
208
218
  events,
209
219
  commands,
220
+ queries,
210
221
  services,
211
222
  users,
212
223
  teams,
@@ -9,8 +9,11 @@ import { getFlows } from '@utils/flows/flows';
9
9
  import { getEvents } from '@utils/events';
10
10
  import { getServices } from '@utils/services/services';
11
11
  import { buildUrl } from '@utils/url-builder';
12
+ import { getQueries } from '@utils/queries';
13
+ import { MagnifyingGlassIcon } from '@heroicons/react/20/solid';
12
14
 
13
15
  const events = await getEvents();
16
+ const queries = await getQueries();
14
17
  const commands = await getCommands();
15
18
  const services = await getServices();
16
19
  const domains = await getDomains();
@@ -36,6 +39,14 @@ const tabs = [
36
39
  activeColor: 'blue',
37
40
  enabled: commands.length > 0,
38
41
  },
42
+ {
43
+ label: `Queries (${queries.length})`,
44
+ href: buildUrl('/discover/queries'),
45
+ isActive: currentPath === '/discover/queries',
46
+ icon: MagnifyingGlassIcon,
47
+ activeColor: 'green',
48
+ enabled: queries.length > 0,
49
+ },
39
50
  {
40
51
  label: `Services (${services.length})`,
41
52
  href: buildUrl('/discover/services'),
@@ -5,20 +5,22 @@ import { getCommands } from '@utils/commands';
5
5
  import { getDomains } from '@utils/domains/domains';
6
6
  import { getEvents } from '@utils/events';
7
7
  import { getFlows } from '@utils/flows/flows';
8
+ import { getQueries } from '@utils/queries';
8
9
  import { getServices } from '@utils/services/services';
9
10
  import { buildUrl } from '@utils/url-builder';
10
11
  import { ViewTransitions } from 'astro:transitions';
11
12
 
12
- const [services, events, commands, domains, flows] = await Promise.all([
13
+ const [services, events, commands, queries, domains, flows] = await Promise.all([
13
14
  getServices(),
14
15
  getEvents(),
15
16
  getCommands(),
17
+ getQueries(),
16
18
  getDomains(),
17
19
  getFlows(),
18
20
  ]);
19
21
 
20
22
  // @ts-ignore for large catalogs https://github.com/event-catalog/eventcatalog/issues/552
21
- const navItems = [...domains, ...services, ...events, ...commands, ...flows];
23
+ const navItems = [...domains, ...services, ...events, ...commands, ...queries, ...flows];
22
24
 
23
25
  const navigation = navItems
24
26
  .filter((item) => item.data.version === item.data.latestVersion)
@@ -41,6 +43,8 @@ const getColor = (collection: string) => {
41
43
  return 'green';
42
44
  case 'events':
43
45
  return 'red';
46
+ case 'queries':
47
+ return 'green';
44
48
  case 'commands':
45
49
  return 'yellow';
46
50
  case 'domains':
@@ -52,45 +56,47 @@ const getColor = (collection: string) => {
52
56
  ---
53
57
 
54
58
  <PlainPage title={title} description={description}>
55
- <div class="flex min-h-full flex-row md:flex-col">
56
- <div class="mx-auto flex flex-col-reverse md:flex-row w-full items-start">
57
- <div>
58
- <aside
59
- class="md:sticky mt-8 md:mt-0 top-0 w-full md:w-60 xl:w-[19em] shrink-0 lg:block font-light pr-10"
60
- id="visualiser-navigation"
61
- >
62
- {
63
- Object.keys(navigation).map((key: any) => {
64
- const items = navigation[key]
65
- .map((item: any) => {
66
- const isCurrent = currentPath.includes(`${item.collection}/${item.data.id}/${item.data.version}`);
67
- const isLatest = item.data.version === item.data.latestVersion;
68
- if (!isLatest) return null;
69
- return {
70
- label: item.data.name,
71
- version: item.data.version,
72
- color: getColor(item.collection),
73
- href: buildUrl(`/visualiser/${item.collection}/${item.data.id}/${item.data.version}`),
74
- active: isCurrent,
75
- };
76
- })
77
- .filter((n: any) => n !== null);
78
- return (
79
- <BasicList
80
- title={`${key} (${navigation[key].length})`}
81
- items={items}
82
- emptyMessage="Nothing to show"
83
- color="gray"
84
- client:load
85
- />
86
- );
87
- })
88
- }
59
+ <div class="">
60
+ <div class="">
61
+ <div class="hidden md:block">
62
+ <aside class="sm:fixed grow w-56 xl:w-[19em] overflow-y-auto h-full z-10 pb-20" id="visualiser-navigation">
63
+ <div class="w-full">
64
+ <div class="sticky top-0 z-10 h-8 pointer-events-none -mt-7">
65
+ <div class="h-full bg-gradient-to-b from-white to-transparent"></div>
66
+ </div>
67
+ {
68
+ Object.keys(navigation).map((key: any) => {
69
+ const items = navigation[key]
70
+ .map((item: any) => {
71
+ const isCurrent = currentPath.includes(`${item.collection}/${item.data.id}/${item.data.version}`);
72
+ const isLatest = item.data.version === item.data.latestVersion;
73
+ if (!isLatest) return null;
74
+ return {
75
+ label: item.data.name,
76
+ version: item.data.version,
77
+ color: getColor(item.collection),
78
+ href: buildUrl(`/visualiser/${item.collection}/${item.data.id}/${item.data.version}`),
79
+ active: isCurrent,
80
+ };
81
+ })
82
+ .filter((n: any) => n !== null);
83
+ return (
84
+ <BasicList
85
+ title={`${key} (${navigation[key].length})`}
86
+ items={items}
87
+ emptyMessage="Nothing to show"
88
+ color="gray"
89
+ client:load
90
+ />
91
+ );
92
+ })
93
+ }
94
+ </div>
89
95
  </aside>
90
96
  </div>
91
97
 
92
- <main class="flex-1 h-full w-full relative">
93
- <button
98
+ <main class="flex-1 h-full w-full relative sm:pl-60 xl:pl-80">
99
+ <!-- <button
94
100
  id="fullscreen-toggle"
95
101
  class="absolute top-[1em] z-40 left-5 bg-white hover:bg-gray-100/50 border border-gray-200 hover:text-primary text-gray-800 font-semibold py-2 px-4 rounded-lg transition duration-300 ease-in-out flex items-center space-x-2 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75"
96
102
  >
@@ -102,7 +108,7 @@ const getColor = (collection: string) => {
102
108
  d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path>
103
109
  </svg>
104
110
  <span>Toggle Fullscreen</span>
105
- </button>
111
+ </button> -->
106
112
  <slot />
107
113
  </main>
108
114
  </div>
@@ -12,7 +12,7 @@ export async function getStaticPaths() {
12
12
  flows: getFlows,
13
13
  };
14
14
 
15
- const itemTypes: PageTypesWithFlows[] = ['events', 'commands', 'services', 'domains', 'flows'];
15
+ const itemTypes: PageTypesWithFlows[] = ['events', 'commands', 'queries', 'services', 'domains', 'flows'];
16
16
  const allItems = await Promise.all(itemTypes.map((type) => loaders[type]()));
17
17
 
18
18
  return allItems.flatMap((items, index) => ({
@@ -16,7 +16,7 @@ import { pageDataLoader } from '@utils/pages/pages';
16
16
  import type { CollectionEntry } from 'astro:content';
17
17
 
18
18
  export async function getStaticPaths() {
19
- const itemTypes: PageTypes[] = ['events', 'commands', 'services', 'domains'];
19
+ const itemTypes: PageTypes[] = ['events', 'commands', 'queries', 'services', 'domains'];
20
20
  const allItems = await Promise.all(itemTypes.map((type) => pageDataLoader[type]()));
21
21
 
22
22
  const hasAsyncAPISpec = (item: CollectionEntry<CollectionTypes>) => item.data.specifications?.asyncapiPath !== undefined;
@@ -4,7 +4,7 @@ import Footer from '@layouts/Footer.astro';
4
4
 
5
5
  import type { PageTypes } from '@types';
6
6
  import { getChangeLogs } from '@utils/changelogs/changelogs';
7
- import { RectangleGroupIcon, ServerIcon, BoltIcon, ChatBubbleLeftIcon } from '@heroicons/react/24/outline';
7
+ import { RectangleGroupIcon, ServerIcon, BoltIcon, ChatBubbleLeftIcon, MagnifyingGlassIcon } from '@heroicons/react/24/outline';
8
8
  import { pageDataLoader } from '@utils/pages/pages';
9
9
  import 'diff2html/bundles/css/diff2html.min.css';
10
10
 
@@ -13,7 +13,7 @@ import { getVersions, getPreviousVersion } from '@utils/collections/util';
13
13
  import { getDiffsForCurrentAndPreviousVersion } from '@utils/collections/file-diffs';
14
14
 
15
15
  export async function getStaticPaths() {
16
- const itemTypes: PageTypes[] = ['events', 'commands', 'services', 'domains'];
16
+ const itemTypes: PageTypes[] = ['events', 'commands', 'queries', 'services', 'domains'];
17
17
  const allItems = await Promise.all(itemTypes.map((type) => pageDataLoader[type]()));
18
18
 
19
19
  return allItems.flatMap((items, index) =>
@@ -85,6 +85,9 @@ const getBadge = () => {
85
85
  if (props.collection === 'commands') {
86
86
  return { backgroundColor: 'blue', textColor: 'blue', content: 'Command', icon: ChatBubbleLeftIcon, class: 'text-blue-400' };
87
87
  }
88
+ if (props.collection === 'queries') {
89
+ return { backgroundColor: 'green', textColor: 'green', content: 'Query', icon: MagnifyingGlassIcon, class: 'text-blue-400' };
90
+ }
88
91
  if (props.collection === 'domains') {
89
92
  return {
90
93
  backgroundColor: 'yellow',
@@ -26,7 +26,7 @@ export async function getStaticPaths() {
26
26
  flows: getFlows,
27
27
  };
28
28
 
29
- const itemTypes: PageTypesWithFlows[] = ['events', 'commands', 'services', 'domains', 'flows'];
29
+ const itemTypes: PageTypesWithFlows[] = ['events', 'commands', 'queries', 'services', 'domains', 'flows'];
30
30
  const allItems = await Promise.all(itemTypes.map((type) => loaders[type]()));
31
31
 
32
32
  return allItems.flatMap((items, index) =>
@@ -206,7 +206,11 @@ const badges = [getBadge(), ...contentBadges, ...getSpecificationBadges()];
206
206
  </main>
207
207
  <aside class="hidden lg:block sticky top-20 h-[calc(100vh-theme(spacing.16))] w-56 overflow-y-auto">
208
208
  <!-- @ts-ignore -->
209
- {(props?.collection === 'events' || props.collection === 'commands') && <MessageSideBar message={props} />}
209
+ {
210
+ (props?.collection === 'events' || props.collection === 'commands' || props.collection === 'queries') && (
211
+ <MessageSideBar message={props} />
212
+ )
213
+ }
210
214
  {props?.collection === 'services' && <ServiceSideBar service={props} />}
211
215
  {props?.collection === 'domains' && <DomainSideBar domain={props} />}
212
216
  </aside>
@@ -10,7 +10,7 @@ import { buildUrl } from '@utils/url-builder';
10
10
  import { pageDataLoader } from '@utils/pages/pages';
11
11
 
12
12
  export async function getStaticPaths() {
13
- const itemTypes: PageTypes[] = ['events', 'commands', 'services', 'domains'];
13
+ const itemTypes: PageTypes[] = ['events', 'commands', 'queries', 'services', 'domains'];
14
14
 
15
15
  const allItems = await Promise.all(itemTypes.map((type) => pageDataLoader[type]()));
16
16
 
@@ -2,7 +2,14 @@
2
2
  import Footer from '@layouts/Footer.astro';
3
3
  import PlainPage from '@layouts/PlainPage.astro';
4
4
  import { buildUrl } from '@utils/url-builder';
5
- import { BoltIcon, ChatBubbleLeftIcon, QueueListIcon, RectangleGroupIcon, ServerIcon } from '@heroicons/react/24/outline';
5
+ import {
6
+ BoltIcon,
7
+ ChatBubbleLeftIcon,
8
+ QueueListIcon,
9
+ RectangleGroupIcon,
10
+ ServerIcon,
11
+ MagnifyingGlassIcon,
12
+ } from '@heroicons/react/24/outline';
6
13
 
7
14
  import { getMessages } from '@utils/messages';
8
15
  import { getDomains } from '@utils/domains/domains';
@@ -10,7 +17,7 @@ import { getServices } from '@utils/services/services';
10
17
  import { getFlows } from '@utils/flows/flows';
11
18
  import DiscoverInsight from '@components/DiscoverInsight.astro';
12
19
 
13
- const { commands = [], events = [] } = await getMessages();
20
+ const { commands = [], events = [], queries = [] } = await getMessages();
14
21
  const domains = await getDomains();
15
22
  const services = await getServices();
16
23
  const flows = await getFlows();
@@ -30,13 +37,15 @@ const flows = await getFlows();
30
37
  <div class="hidden md:block">
31
38
  <h2 class="text-center text-xl font-bold">Architecture insights</h2>
32
39
  <section class="mb-16 bg-white rounded-xl shadow-lg p-6">
33
- <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4">
40
+ <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
34
41
  <DiscoverInsight color="text-yellow-600" dataTarget={domains.length} icon={RectangleGroupIcon} label="domains" />
35
42
 
36
43
  <DiscoverInsight color="text-pink-500" dataTarget={services.length} icon={ServerIcon} label="services" />
37
44
 
38
45
  <DiscoverInsight color="text-blue-500" dataTarget={commands.length} icon={ChatBubbleLeftIcon} label="commands" />
39
46
 
47
+ <DiscoverInsight color="text-green-500" dataTarget={queries.length} icon={MagnifyingGlassIcon} label="queries" />
48
+
40
49
  <DiscoverInsight color="text-orange-400" dataTarget={events.length} icon={BoltIcon} label="events" />
41
50
 
42
51
  <DiscoverInsight color="text-green-800" dataTarget={flows.length} icon={QueueListIcon} label="flows" />
@@ -14,7 +14,7 @@ export async function getStaticPaths() {
14
14
  flows: getFlows,
15
15
  };
16
16
 
17
- const itemTypes: PageTypesWithFlows[] = ['events', 'commands', 'services', 'domains', 'flows'];
17
+ const itemTypes: PageTypesWithFlows[] = ['events', 'commands', 'queries', 'services', 'domains', 'flows'];
18
18
  const allItems = await Promise.all(itemTypes.map((type) => loaders[type]()));
19
19
 
20
20
  return allItems.flatMap((items, index) =>
@@ -1,4 +1,4 @@
1
- export type CollectionTypes = 'commands' | 'events' | 'domains' | 'services' | 'flows';
2
- export type CollectionMessageTypes = 'commands' | 'events';
1
+ export type CollectionTypes = 'commands' | 'events' | 'queries' | 'domains' | 'services' | 'flows';
2
+ export type CollectionMessageTypes = 'commands' | 'events' | 'queries';
3
3
 
4
- export type PageTypes = 'events' | 'commands' | 'services' | 'domains';
4
+ export type PageTypes = 'events' | 'commands' | 'queries' | 'services' | 'domains';
@@ -25,7 +25,7 @@ const getServiceNode = (step: any, services: CollectionEntry<'services'>[]) => {
25
25
  };
26
26
  };
27
27
 
28
- const getMessageNode = (step: any, messages: CollectionEntry<'events' | 'commands'>[]) => {
28
+ const getMessageNode = (step: any, messages: CollectionEntry<'events' | 'commands' | 'queries'>[]) => {
29
29
  const messagesForVersion = getItemsFromCollectionByIdAndSemverOrLatest(messages, step.message.id, step.message.version);
30
30
  const message = messagesForVersion[0];
31
31
  return {
@@ -53,9 +53,10 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
53
53
 
54
54
  const events = await getCollection('events');
55
55
  const commands = await getCollection('commands');
56
+ const queries = await getCollection('queries');
56
57
  const services = await getCollection('services');
57
58
 
58
- const messages = [...events, ...commands];
59
+ const messages = [...events, ...commands, ...queries];
59
60
 
60
61
  const steps = flow?.data.steps || [];
61
62
 
@@ -69,7 +70,7 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
69
70
  });
70
71
 
71
72
  // Create nodes
72
- hydratedSteps.forEach((step, index: number) => {
73
+ hydratedSteps.forEach((step: any, index: number) => {
73
74
  const node = {
74
75
  id: `step-${step.id}`,
75
76
  sourcePosition: 'right',
@@ -93,7 +94,7 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
93
94
  });
94
95
 
95
96
  // Create Edges
96
- hydratedSteps.forEach((step, index: number) => {
97
+ hydratedSteps.forEach((step: any, index: number) => {
97
98
  let paths = step.next_steps || [];
98
99
 
99
100
  if (step.next_step) {
@@ -1,6 +1,7 @@
1
1
  // Exporting getCommands and getEvents directly
2
2
  import { getCommands } from '@utils/commands';
3
3
  import { getEvents } from '@utils/events';
4
+ import { getQueries } from './queries';
4
5
  export { getCommands } from '@utils/commands';
5
6
  export { getEvents } from '@utils/events';
6
7
 
@@ -8,6 +9,7 @@ export { getEvents } from '@utils/events';
8
9
  export const getMessages = async () => {
9
10
  const commands = await getCommands();
10
11
  const events = await getEvents();
12
+ const queries = await getQueries();
11
13
 
12
- return { commands, events };
14
+ return { commands, events, queries };
13
15
  };
@@ -2,13 +2,13 @@ import type { CollectionEntry } from 'astro:content';
2
2
  import type { Node } from 'reactflow';
3
3
  import dagre from 'dagre';
4
4
 
5
- export const generateIdForNode = (node: CollectionEntry<'events' | 'services' | 'commands'>) => {
5
+ export const generateIdForNode = (node: CollectionEntry<'events' | 'services' | 'commands' | 'queries'>) => {
6
6
  return `${node.data.id}-${node.data.version}`;
7
7
  };
8
8
 
9
9
  export const generatedIdForEdge = (
10
- source: CollectionEntry<'events' | 'services' | 'commands'>,
11
- target: CollectionEntry<'events' | 'services' | 'commands'>
10
+ source: CollectionEntry<'events' | 'services' | 'commands' | 'queries'>,
11
+ target: CollectionEntry<'events' | 'services' | 'commands' | 'queries'>
12
12
  ) => {
13
13
  return `${source.data.id}-${source.data.version}-${target.data.id}-${target.data.version}`;
14
14
  };
@@ -1,12 +1,14 @@
1
1
  import type { CollectionTypes, PageTypes } from '@types';
2
2
  import { getDomains } from '@utils/domains/domains';
3
3
  import { getCommands, getEvents } from '@utils/messages';
4
+ import { getQueries } from '@utils/queries';
4
5
  import { getServices } from '@utils/services/services';
5
6
  import type { CollectionEntry } from 'astro:content';
6
7
 
7
8
  export const pageDataLoader: Record<PageTypes, () => Promise<CollectionEntry<CollectionTypes>[]>> = {
8
9
  events: getEvents,
9
10
  commands: getCommands,
11
+ queries: getQueries,
10
12
  services: getServices,
11
13
  domains: getDomains,
12
14
  };
@@ -0,0 +1,118 @@
1
+ import { getQueries } from '@utils/queries';
2
+ import type { CollectionEntry } from 'astro:content';
3
+ import dagre from 'dagre';
4
+ import { calculatedNodes, createDagreGraph, generatedIdForEdge, generateIdForNode } from '../node-graph-utils/utils';
5
+ import { MarkerType } from 'reactflow';
6
+
7
+ type DagreGraph = any;
8
+
9
+ interface Props {
10
+ id: string;
11
+ version: string;
12
+ defaultFlow?: DagreGraph;
13
+ mode?: 'simple' | 'full';
14
+ }
15
+
16
+ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simple' }: Props) => {
17
+ const flow = defaultFlow || createDagreGraph({ ranksep: 300, nodesep: 50 });
18
+ const nodes = [] as any,
19
+ edges = [] as any;
20
+
21
+ const queries = await getQueries();
22
+
23
+ const query = queries.find((query) => query.data.id === id && query.data.version === version);
24
+
25
+ // Nothing found...
26
+ if (!query) {
27
+ return {
28
+ nodes: [],
29
+ edges: [],
30
+ };
31
+ }
32
+
33
+ const producers = (query.data.producers as CollectionEntry<'services'>[]) || [];
34
+ const consumers = (query.data.consumers as CollectionEntry<'services'>[]) || [];
35
+
36
+ if (producers && producers.length > 0) {
37
+ producers.forEach((producer) => {
38
+ nodes.push({
39
+ id: generateIdForNode(producer),
40
+ type: producer?.collection,
41
+ sourcePosition: 'right',
42
+ targetPosition: 'left',
43
+ data: { mode, service: producer, showTarget: false },
44
+ position: { x: 250, y: 0 },
45
+ });
46
+ edges.push({
47
+ id: generatedIdForEdge(producer, query),
48
+ source: generateIdForNode(producer),
49
+ target: generateIdForNode(query),
50
+ type: 'smoothstep',
51
+ label: 'requests',
52
+ animated: false,
53
+ markerEnd: {
54
+ type: MarkerType.ArrowClosed,
55
+ width: 40,
56
+ height: 40,
57
+ },
58
+ style: {
59
+ strokeWidth: 1,
60
+ },
61
+ });
62
+ });
63
+ }
64
+
65
+ // The query itself
66
+ nodes.push({
67
+ id: generateIdForNode(query),
68
+ sourcePosition: 'right',
69
+ targetPosition: 'left',
70
+ data: { mode, message: query, showTarget: producers.length > 0, showSource: consumers.length > 0 },
71
+ position: { x: 0, y: 0 },
72
+ type: query.collection,
73
+ });
74
+
75
+ // The messages the service sends
76
+ consumers.forEach((consumer) => {
77
+ nodes.push({
78
+ id: generateIdForNode(consumer),
79
+ sourcePosition: 'right',
80
+ targetPosition: 'left',
81
+ data: { title: consumer?.data.id, mode, service: consumer, showSource: false },
82
+ position: { x: 0, y: 0 },
83
+ type: consumer?.collection,
84
+ });
85
+ edges.push({
86
+ id: generatedIdForEdge(query, consumer),
87
+ source: generateIdForNode(query),
88
+ target: generateIdForNode(consumer),
89
+ type: 'smoothstep',
90
+ label: 'accepts',
91
+ animated: false,
92
+ markerEnd: {
93
+ type: MarkerType.ArrowClosed,
94
+ width: 40,
95
+ height: 40,
96
+ },
97
+ style: {
98
+ strokeWidth: 1,
99
+ },
100
+ });
101
+ });
102
+
103
+ nodes.forEach((node: any) => {
104
+ flow.setNode(node.id, { width: 150, height: 100 });
105
+ });
106
+
107
+ edges.forEach((edge: any) => {
108
+ flow.setEdge(edge.source, edge.target);
109
+ });
110
+
111
+ // Render the diagram in memory getting hte X and Y
112
+ dagre.layout(flow);
113
+
114
+ return {
115
+ nodes: calculatedNodes(flow, nodes),
116
+ edges: edges,
117
+ };
118
+ };
@@ -0,0 +1,65 @@
1
+ import { getCollection } from 'astro:content';
2
+ import type { CollectionEntry } from 'astro:content';
3
+ import path from 'path';
4
+ import { getVersionForCollectionItem, satisfies } from './collections/util';
5
+
6
+ const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
7
+
8
+ type Query = CollectionEntry<'queries'> & {
9
+ catalog: {
10
+ path: string;
11
+ filePath: string;
12
+ type: string;
13
+ };
14
+ };
15
+
16
+ interface Props {
17
+ getAllVersions?: boolean;
18
+ }
19
+
20
+ export const getQueries = async ({ getAllVersions = true }: Props = {}): Promise<Query[]> => {
21
+ const queries = await getCollection('queries', (query) => {
22
+ return (getAllVersions || !query.slug.includes('versioned')) && query.data.hidden !== true;
23
+ });
24
+
25
+ const services = await getCollection('services');
26
+
27
+ return queries.map((query) => {
28
+ const { latestVersion, versions } = getVersionForCollectionItem(query, queries);
29
+
30
+ const producers = services.filter((service) =>
31
+ service.data.sends?.some((item) => {
32
+ if (item.id != query.data.id) return false;
33
+ if (item.version == 'latest' || item.version == undefined) return query.data.version == latestVersion;
34
+ return satisfies(query.data.version, item.version);
35
+ })
36
+ );
37
+
38
+ const consumers = services.filter((service) =>
39
+ service.data.receives?.some((item) => {
40
+ if (item.id != query.data.id) return false;
41
+ if (item.version == 'latest' || item.version == undefined) return query.data.version == latestVersion;
42
+ return satisfies(query.data.version, item.version);
43
+ })
44
+ );
45
+
46
+ return {
47
+ ...query,
48
+ data: {
49
+ ...query.data,
50
+ producers,
51
+ consumers,
52
+ versions,
53
+ latestVersion,
54
+ },
55
+ catalog: {
56
+ path: path.join(query.collection, query.id.replace('/index.mdx', '')),
57
+ absoluteFilePath: path.join(PROJECT_DIR, query.collection, query.id.replace('/index.mdx', '/index.md')),
58
+ astroContentFilePath: path.join(process.cwd(), 'src', 'content', query.collection, query.id),
59
+ filePath: path.join(process.cwd(), 'src', 'catalog-files', query.collection, query.id.replace('/index.mdx', '')),
60
+ publicPath: path.join('/generated', query.collection, query.id.replace('/index.mdx', '')),
61
+ type: 'event',
62
+ },
63
+ };
64
+ });
65
+ };
@@ -14,6 +14,31 @@ interface Props {
14
14
  renderAllEdges?: boolean;
15
15
  }
16
16
 
17
+ const getSendsMessageByMessageType = (messageType: string) => {
18
+ switch (messageType) {
19
+ case 'events':
20
+ return 'publishes event';
21
+ case 'commands':
22
+ return 'invokes command';
23
+ case 'queries':
24
+ return 'requests';
25
+ default:
26
+ return 'invokes message';
27
+ }
28
+ };
29
+
30
+ const getReceivesMessageByMessageType = (messageType: string) => {
31
+ switch (messageType) {
32
+ case 'events':
33
+ return 'receives event';
34
+ case 'commands':
35
+ case 'queries':
36
+ return 'accepts';
37
+ default:
38
+ return 'accepts message';
39
+ }
40
+ };
41
+
17
42
  export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simple', renderAllEdges = false }: Props) => {
18
43
  const flow = defaultFlow || createDagreGraph({ ranksep: 300, nodesep: 50 });
19
44
  const nodes = [] as any,
@@ -36,8 +61,9 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
36
61
 
37
62
  const events = await getCollection('events');
38
63
  const commands = await getCollection('commands');
64
+ const queries = await getCollection('queries');
39
65
 
40
- const messages = [...events, ...commands];
66
+ const messages = [...events, ...commands, ...queries];
41
67
 
42
68
  const receivesHydrated = receivesRaw
43
69
  .map((message) => getItemsFromCollectionByIdAndSemverOrLatest(messages, message.id, message.version))
@@ -49,8 +75,8 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
49
75
  .flat()
50
76
  .filter((e) => e !== undefined);
51
77
 
52
- const receives = (receivesHydrated as CollectionEntry<'events' | 'commands'>[]) || [];
53
- const sends = (sendsHydrated as CollectionEntry<'events' | 'commands'>[]) || [];
78
+ const receives = (receivesHydrated as CollectionEntry<'events' | 'commands' | 'queries'>[]) || [];
79
+ const sends = (sendsHydrated as CollectionEntry<'events' | 'commands' | 'queries'>[]) || [];
54
80
 
55
81
  // Get all the data from them
56
82
 
@@ -70,7 +96,7 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
70
96
  source: generateIdForNode(receive),
71
97
  target: generateIdForNode(service),
72
98
  type: 'smoothstep',
73
- label: receive?.collection === 'events' ? 'receives event' : 'accepts',
99
+ label: getReceivesMessageByMessageType(receive?.collection),
74
100
  animated: false,
75
101
  markerEnd: {
76
102
  type: MarkerType.ArrowClosed,
@@ -109,7 +135,7 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
109
135
  source: generateIdForNode(service),
110
136
  target: generateIdForNode(send),
111
137
  type: 'smoothstep',
112
- label: send?.collection === 'events' ? 'publishes event' : 'invokes command',
138
+ label: getSendsMessageByMessageType(send?.collection),
113
139
  animated: false,
114
140
  markerEnd: {
115
141
  type: MarkerType.ArrowClosed,
@@ -18,8 +18,9 @@ export const getServices = async ({ getAllVersions = true }: Props = {}): Promis
18
18
  });
19
19
  const events = await getCollection('events');
20
20
  const commands = await getCollection('commands');
21
+ const queries = await getCollection('queries');
21
22
 
22
- const allMessages = [...events, ...commands];
23
+ const allMessages = [...events, ...commands, ...queries];
23
24
 
24
25
  // @ts-ignore // TODO: Fix this type
25
26
  return services.map((service) => {
@@ -29,14 +30,14 @@ export const getServices = async ({ getAllVersions = true }: Props = {}): Promis
29
30
  const receivesMessages = service.data.receives || [];
30
31
 
31
32
  const sends = sendsMessages
32
- .map((message) => getItemsFromCollectionByIdAndSemverOrLatest(allMessages, message.id, message.version))
33
+ .map((message: any) => getItemsFromCollectionByIdAndSemverOrLatest(allMessages, message.id, message.version))
33
34
  .flat()
34
- .filter((e) => e !== undefined);
35
+ .filter((e: any) => e !== undefined);
35
36
 
36
37
  const receives = receivesMessages
37
- .map((message) => getItemsFromCollectionByIdAndSemverOrLatest(allMessages, message.id, message.version))
38
+ .map((message: any) => getItemsFromCollectionByIdAndSemverOrLatest(allMessages, message.id, message.version))
38
39
  .flat()
39
- .filter((e) => e !== undefined);
40
+ .filter((e: any) => e !== undefined);
40
41
 
41
42
  return {
42
43
  ...service,