@eventcatalog/core 3.26.16 → 3.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37,7 +37,7 @@ var import_axios = __toESM(require("axios"), 1);
37
37
  var import_os = __toESM(require("os"), 1);
38
38
 
39
39
  // package.json
40
- var version = "3.26.16";
40
+ var version = "3.27.0";
41
41
 
42
42
  // src/constants.ts
43
43
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "../chunk-3CPDUB6E.js";
4
- import "../chunk-LKJCBJX3.js";
3
+ } from "../chunk-KNR65XBF.js";
4
+ import "../chunk-SWTABLK7.js";
5
5
  export {
6
6
  raiseEvent
7
7
  };
@@ -111,7 +111,7 @@ var import_axios = __toESM(require("axios"), 1);
111
111
  var import_os = __toESM(require("os"), 1);
112
112
 
113
113
  // package.json
114
- var version = "3.26.16";
114
+ var version = "3.27.0";
115
115
 
116
116
  // src/constants.ts
117
117
  var VERSION = version;
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  log_build_default
3
- } from "../chunk-E27MVTFI.js";
4
- import "../chunk-3CPDUB6E.js";
3
+ } from "../chunk-VOS4MRLD.js";
4
+ import "../chunk-KNR65XBF.js";
5
5
  import "../chunk-4UVFXLPI.js";
6
- import "../chunk-LKJCBJX3.js";
6
+ import "../chunk-SWTABLK7.js";
7
7
  import "../chunk-5T63CXKU.js";
8
8
  export {
9
9
  log_build_default as default
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  logger
3
- } from "./chunk-232PH27E.js";
3
+ } from "./chunk-R7ILHVCQ.js";
4
4
  import {
5
5
  cleanup,
6
6
  getEventCatalogConfigFile
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-LKJCBJX3.js";
3
+ } from "./chunk-SWTABLK7.js";
4
4
 
5
5
  // src/analytics/analytics.js
6
6
  import axios from "axios";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-LKJCBJX3.js";
3
+ } from "./chunk-SWTABLK7.js";
4
4
 
5
5
  // src/utils/cli-logger.ts
6
6
  import pc from "picocolors";
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "3.26.16";
2
+ var version = "3.27.0";
3
3
 
4
4
  // src/constants.ts
5
5
  var VERSION = version;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "./chunk-3CPDUB6E.js";
3
+ } from "./chunk-KNR65XBF.js";
4
4
  import {
5
5
  countResources,
6
6
  serializeCounts
@@ -25,7 +25,7 @@ __export(constants_exports, {
25
25
  module.exports = __toCommonJS(constants_exports);
26
26
 
27
27
  // package.json
28
- var version = "3.26.16";
28
+ var version = "3.27.0";
29
29
 
30
30
  // src/constants.ts
31
31
  var VERSION = version;
package/dist/constants.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-LKJCBJX3.js";
3
+ } from "./chunk-SWTABLK7.js";
4
4
  export {
5
5
  VERSION
6
6
  };
@@ -114,7 +114,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
114
114
  var import_picocolors = __toESM(require("picocolors"), 1);
115
115
 
116
116
  // package.json
117
- var version = "3.26.16";
117
+ var version = "3.27.0";
118
118
 
119
119
  // src/constants.ts
120
120
  var VERSION = version;
@@ -6,8 +6,8 @@ import {
6
6
  } from "./chunk-PLNJC7NZ.js";
7
7
  import {
8
8
  log_build_default
9
- } from "./chunk-E27MVTFI.js";
10
- import "./chunk-3CPDUB6E.js";
9
+ } from "./chunk-VOS4MRLD.js";
10
+ import "./chunk-KNR65XBF.js";
11
11
  import "./chunk-4UVFXLPI.js";
12
12
  import {
13
13
  runMigrations
@@ -22,13 +22,13 @@ import {
22
22
  } from "./chunk-3KXCGYET.js";
23
23
  import {
24
24
  generate
25
- } from "./chunk-IYV3DBOW.js";
25
+ } from "./chunk-6KGRDHJX.js";
26
26
  import {
27
27
  logger
28
- } from "./chunk-232PH27E.js";
28
+ } from "./chunk-R7ILHVCQ.js";
29
29
  import {
30
30
  VERSION
31
- } from "./chunk-LKJCBJX3.js";
31
+ } from "./chunk-SWTABLK7.js";
32
32
  import {
33
33
  getEventCatalogConfigFile,
34
34
  verifyRequiredFieldsAreInCatalogConfigFile
package/dist/generate.cjs CHANGED
@@ -78,7 +78,7 @@ var getEventCatalogConfigFile = async (projectDirectory) => {
78
78
  var import_picocolors = __toESM(require("picocolors"), 1);
79
79
 
80
80
  // package.json
81
- var version = "3.26.16";
81
+ var version = "3.27.0";
82
82
 
83
83
  // src/constants.ts
84
84
  var VERSION = version;
package/dist/generate.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  generate
3
- } from "./chunk-IYV3DBOW.js";
4
- import "./chunk-232PH27E.js";
5
- import "./chunk-LKJCBJX3.js";
3
+ } from "./chunk-6KGRDHJX.js";
4
+ import "./chunk-R7ILHVCQ.js";
5
+ import "./chunk-SWTABLK7.js";
6
6
  import "./chunk-5T63CXKU.js";
7
7
  export {
8
8
  generate
@@ -36,7 +36,7 @@ module.exports = __toCommonJS(cli_logger_exports);
36
36
  var import_picocolors = __toESM(require("picocolors"), 1);
37
37
 
38
38
  // package.json
39
- var version = "3.26.16";
39
+ var version = "3.27.0";
40
40
 
41
41
  // src/constants.ts
42
42
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  logger
3
- } from "../chunk-232PH27E.js";
4
- import "../chunk-LKJCBJX3.js";
3
+ } from "../chunk-R7ILHVCQ.js";
4
+ import "../chunk-SWTABLK7.js";
5
5
  export {
6
6
  logger
7
7
  };
@@ -68,6 +68,7 @@ const sendsPointer = z.object({
68
68
  id: z.string(),
69
69
  version: z.string().optional().default('latest'),
70
70
  fields: z.array(z.string()).optional(),
71
+ group: z.string().optional(),
71
72
  to: z
72
73
  .array(
73
74
  z.object({
@@ -82,6 +83,7 @@ const receivesPointer = z.object({
82
83
  id: z.string(),
83
84
  version: z.string().optional().default('latest'),
84
85
  fields: z.array(z.string()).optional(),
86
+ group: z.string().optional(),
85
87
  from: z
86
88
  .array(
87
89
  z.object({
@@ -11,13 +11,89 @@ import {
11
11
  versionMatches,
12
12
  DEFAULT_NODE_WIDTH,
13
13
  DEFAULT_NODE_HEIGHT,
14
+ partitionMessagesByGroup,
15
+ getOperationFields,
14
16
  } from '@utils/node-graphs/utils/utils';
15
17
 
18
+ const sanitizeGroupId = (name: string) => name.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase();
19
+
16
20
  import { findMatchingNodes, findInMap, createVersionedMap } from '@utils/collections/util';
17
- import { MarkerType } from '@xyflow/react';
21
+ import { MarkerType, type Node, type Edge } from '@xyflow/react';
18
22
  import type { CollectionMessageTypes } from '@types';
19
23
  import { getNodesAndEdgesForConsumedMessage, getNodesAndEdgesForProducedMessage } from './message-node-graph';
20
24
 
25
+ /**
26
+ * Pre-compute the downstream nodes/edges that should appear when a message group
27
+ * is expanded. Calls the standard message node-graph function for each grouped
28
+ * message, then strips out the service and message nodes/edges that the client-side
29
+ * expand logic handles separately.
30
+ */
31
+ const precomputeGroupExpansion = (
32
+ messages: any[],
33
+ channelPointers: any[],
34
+ direction: 'sends' | 'receives',
35
+ opts: {
36
+ serviceNodeId: string;
37
+ services: any;
38
+ service: any;
39
+ mode: string;
40
+ channels: any;
41
+ channelMap: any;
42
+ existingNodes: any[];
43
+ existingEdges: any[];
44
+ }
45
+ ) => {
46
+ const expandedNodes: Node[] = [];
47
+ const expandedEdges: Edge[] = [];
48
+
49
+ for (let i = 0; i < messages.length; i++) {
50
+ const msg = messages[i];
51
+ const channelRefs = channelPointers[i] || [];
52
+
53
+ // Call the same function used by the ungrouped path
54
+ const args =
55
+ direction === 'receives'
56
+ ? {
57
+ message: msg as any,
58
+ targetChannels: channelRefs,
59
+ services: opts.services,
60
+ currentNodes: [...opts.existingNodes, ...expandedNodes],
61
+ target: opts.service,
62
+ mode: opts.mode,
63
+ channels: opts.channels,
64
+ channelMap: opts.channelMap,
65
+ }
66
+ : {
67
+ message: msg as any,
68
+ sourceChannels: channelRefs,
69
+ services: opts.services,
70
+ currentNodes: [...opts.existingNodes, ...expandedNodes],
71
+ source: opts.service,
72
+ currentEdges: [...opts.existingEdges, ...expandedEdges],
73
+ mode: opts.mode,
74
+ channels: opts.channels,
75
+ channelMap: opts.channelMap,
76
+ };
77
+
78
+ const { nodes: msgNodes, edges: msgEdges } =
79
+ direction === 'receives'
80
+ ? getNodesAndEdgesForConsumedMessage(args as any)
81
+ : getNodesAndEdgesForProducedMessage(args as any);
82
+
83
+ // Strip the service node and message node — the client expand logic creates these.
84
+ // Also strip the direct service↔message edge for the same reason.
85
+ const msgId = `${msg.data.id}-${msg.data.version}`;
86
+ const isServiceOrMessage = (id: string) => id === opts.serviceNodeId || id === msgId;
87
+ const isDirectEdge = (e: any) =>
88
+ (e.source === opts.serviceNodeId && e.target === msgId) || (e.source === msgId && e.target === opts.serviceNodeId);
89
+
90
+ expandedNodes.push(...msgNodes.filter((n: any) => !isServiceOrMessage(n.id)));
91
+ expandedEdges.push(...msgEdges.filter((e: any) => !isDirectEdge(e)));
92
+ }
93
+
94
+ return { expandedNodes, expandedEdges };
95
+ };
96
+
21
97
  type DagreGraph = any;
22
98
 
23
99
  interface Props {
@@ -124,15 +200,89 @@ export const getNodesAndEdges = async ({
124
200
  const bothSentAndReceived = findMatchingNodes(receives, sends);
125
201
  const bothReadsAndWrites = findMatchingNodes(readsFrom, writesTo);
126
202
 
203
+ // Partition messages by group
204
+ const receivesPartition = partitionMessagesByGroup(receivesRaw, receives);
205
+ const sendsPartition = partitionMessagesByGroup(sendsRaw, sends);
206
+
207
+ // Collect grouped message IDs to exclude from bothSentAndReceived
208
+ const groupedMessageIds = new Set<string>();
209
+ for (const [, groupData] of receivesPartition.grouped) {
210
+ groupData.messages.forEach((m: any) => groupedMessageIds.add(`${m.data.id}-${m.data.version}`));
211
+ }
212
+ for (const [, groupData] of sendsPartition.grouped) {
213
+ groupData.messages.forEach((m: any) => groupedMessageIds.add(`${m.data.id}-${m.data.version}`));
214
+ }
215
+
216
+ const filteredBothSentAndReceived = bothSentAndReceived.filter(
217
+ (m: any) => m && !groupedMessageIds.has(`${m.data.id}-${m.data.version}`)
218
+ );
219
+
220
+ const serviceNodeId = `${service.data.id}-${service.data.version}`;
221
+
127
222
  if (renderMessages) {
128
- // All the messages the service receives
129
- receives.forEach((receive) => {
130
- const targetChannels = receivesRaw.find(
131
- (receiveRaw) => receiveRaw.id === receive.data.id && versionMatches(receiveRaw.version, receive.data.version)
132
- )?.from;
223
+ // Emit group nodes for grouped receives
224
+ for (const [groupName, groupData] of receivesPartition.grouped) {
225
+ const groupNodeId = `message-group-${service.data.id}-${service.data.version}-${sanitizeGroupId(groupName)}-receives`;
226
+
227
+ // Match each message to its pointer by id/version (not index) to avoid
228
+ // misalignment when a pointer fails hydration and is missing from messages.
229
+ const findPointerForMessage = (msg: any) =>
230
+ groupData.pointers.find((p: any) => p.id === msg.data.id && versionMatches(p.version, msg.data.version));
231
+
232
+ const { expandedNodes, expandedEdges } = precomputeGroupExpansion(
233
+ groupData.messages,
234
+ groupData.messages.map((msg: any) => findPointerForMessage(msg)?.from || []),
235
+ 'receives',
236
+ { serviceNodeId, services, service, mode, channels, channelMap, existingNodes: nodes, existingEdges: edges }
237
+ );
238
+
239
+ nodes.push({
240
+ id: groupNodeId,
241
+ sourcePosition: 'right',
242
+ targetPosition: 'left',
243
+ type: 'messageGroup',
244
+ data: {
245
+ mode,
246
+ groupName,
247
+ direction: 'receives' as const,
248
+ messageCount: groupData.messages.length,
249
+ messageTypes: [...new Set(groupData.messages.map((m: any) => m.collection))],
250
+ messages: groupData.messages.map((msg: any) => ({
251
+ message: { ...msg, data: { ...msg.data, ...getOperationFields(msg.data) } },
252
+ channels: findPointerForMessage(msg)?.from || [],
253
+ })),
254
+ service: { id: service.data.id, version: service.data.version },
255
+ expandedNodes,
256
+ expandedEdges,
257
+ },
258
+ });
259
+
260
+ edges.push(
261
+ createEdge({
262
+ id: `${groupNodeId}-to-${serviceNodeId}`,
263
+ source: groupNodeId,
264
+ target: serviceNodeId,
265
+ label: `consumes ${groupData.messages.length}\nmessages`,
266
+ type: 'multiline',
267
+ markerEnd: {
268
+ type: MarkerType.ArrowClosed,
269
+ color: '#666',
270
+ width: 20,
271
+ height: 20,
272
+ },
273
+ })
274
+ );
275
+ }
276
+
277
+ // Ungrouped receives go through existing path
278
+ receivesPartition.ungrouped.messages.forEach((receive) => {
279
+ const receiveRaw = receivesRaw.find(
280
+ (r) => r.id === (receive as any).data.id && versionMatches(r.version, (receive as any).data.version)
281
+ );
282
+ const targetChannels = receiveRaw?.from;
133
283
 
134
284
  const { nodes: consumedMessageNodes, edges: consumedMessageEdges } = getNodesAndEdgesForConsumedMessage({
135
- message: receive,
285
+ message: receive as any,
136
286
  targetChannels: targetChannels,
137
287
  services,
138
288
  currentNodes: nodes,
@@ -239,13 +389,68 @@ export const getNodesAndEdges = async ({
239
389
  });
240
390
 
241
391
  if (renderMessages) {
242
- sends.forEach((send) => {
243
- const sourceChannels = sendsRaw.find(
244
- (sendRaw) => sendRaw.id === send.data.id && versionMatches(sendRaw.version, send.data.version)
245
- )?.to;
392
+ // Emit group nodes for grouped sends
393
+ for (const [groupName, groupData] of sendsPartition.grouped) {
394
+ const groupNodeId = `message-group-${service.data.id}-${service.data.version}-${sanitizeGroupId(groupName)}-sends`;
395
+
396
+ // Match each message to its pointer by id/version (not index) — same reason as receives above
397
+ const findPointerForMessage = (msg: any) =>
398
+ groupData.pointers.find((p: any) => p.id === msg.data.id && versionMatches(p.version, msg.data.version));
399
+
400
+ const { expandedNodes, expandedEdges } = precomputeGroupExpansion(
401
+ groupData.messages,
402
+ groupData.messages.map((msg: any) => findPointerForMessage(msg)?.to || []),
403
+ 'sends',
404
+ { serviceNodeId, services, service, mode, channels, channelMap, existingNodes: nodes, existingEdges: edges }
405
+ );
406
+
407
+ nodes.push({
408
+ id: groupNodeId,
409
+ sourcePosition: 'right',
410
+ targetPosition: 'left',
411
+ type: 'messageGroup',
412
+ data: {
413
+ mode,
414
+ groupName,
415
+ direction: 'sends' as const,
416
+ messageCount: groupData.messages.length,
417
+ messageTypes: [...new Set(groupData.messages.map((m: any) => m.collection))],
418
+ messages: groupData.messages.map((msg: any) => ({
419
+ message: { ...msg, data: { ...msg.data, ...getOperationFields(msg.data) } },
420
+ channels: findPointerForMessage(msg)?.to || [],
421
+ })),
422
+ service: { id: service.data.id, version: service.data.version },
423
+ expandedNodes,
424
+ expandedEdges,
425
+ },
426
+ });
427
+
428
+ edges.push(
429
+ createEdge({
430
+ id: `${serviceNodeId}-to-${groupNodeId}`,
431
+ source: serviceNodeId,
432
+ target: groupNodeId,
433
+ label: `publishes ${groupData.messages.length}\nmessages`,
434
+ type: 'multiline',
435
+ markerEnd: {
436
+ type: MarkerType.ArrowClosed,
437
+ color: '#666',
438
+ width: 20,
439
+ height: 20,
440
+ },
441
+ })
442
+ );
443
+ }
444
+
445
+ // Ungrouped sends through existing path
446
+ sendsPartition.ungrouped.messages.forEach((send) => {
447
+ const sendRaw = sendsRaw.find(
448
+ (s) => s.id === (send as any).data.id && versionMatches(s.version, (send as any).data.version)
449
+ );
450
+ const sourceChannels = sendRaw?.to;
246
451
 
247
452
  const { nodes: producedMessageNodes, edges: producedMessageEdges } = getNodesAndEdgesForProducedMessage({
248
- message: send,
453
+ message: send as any,
249
454
  sourceChannels: sourceChannels,
250
455
  services,
251
456
  currentNodes: nodes,
@@ -260,8 +465,8 @@ export const getNodesAndEdges = async ({
260
465
  edges.push(...producedMessageEdges);
261
466
  });
262
467
 
263
- // Handle messages that are both sent and received
264
- bothSentAndReceived.forEach((message) => {
468
+ // Handle messages that are both sent and received (filtered to exclude grouped)
469
+ filteredBothSentAndReceived.forEach((message) => {
265
470
  if (message) {
266
471
  edges.push({
267
472
  id: generatedIdForEdge(service, message) + '-both',
@@ -310,6 +310,55 @@ export const getOperationFields = (data: Record<string, any>) => {
310
310
  };
311
311
  };
312
312
 
313
+ interface PointerWithGroup {
314
+ id: string;
315
+ version?: string;
316
+ group?: string;
317
+ [key: string]: any;
318
+ }
319
+
320
+ interface PartitionResult {
321
+ grouped: Map<string, { messages: CollectionItem[]; pointers: PointerWithGroup[] }>;
322
+ ungrouped: { messages: CollectionItem[]; pointers: PointerWithGroup[] };
323
+ }
324
+
325
+ /**
326
+ * Partition raw message pointers and their hydrated messages by group.
327
+ * Matches each pointer to its hydrated message by id + version.
328
+ */
329
+ export const partitionMessagesByGroup = (
330
+ rawPointers: PointerWithGroup[],
331
+ hydratedMessages: CollectionItem[]
332
+ ): PartitionResult => {
333
+ const grouped = new Map<string, { messages: CollectionItem[]; pointers: PointerWithGroup[] }>();
334
+ const ungrouped: { messages: CollectionItem[]; pointers: PointerWithGroup[] } = {
335
+ messages: [],
336
+ pointers: [],
337
+ };
338
+
339
+ rawPointers.forEach((pointer) => {
340
+ const message = hydratedMessages.find((m) => m.data.id === pointer.id && versionMatches(pointer.version, m.data.version));
341
+
342
+ if (pointer.group) {
343
+ if (!grouped.has(pointer.group)) {
344
+ grouped.set(pointer.group, { messages: [], pointers: [] });
345
+ }
346
+ const group = grouped.get(pointer.group)!;
347
+ group.pointers.push(pointer);
348
+ if (message) {
349
+ group.messages.push(message);
350
+ }
351
+ } else {
352
+ ungrouped.pointers.push(pointer);
353
+ if (message) {
354
+ ungrouped.messages.push(message);
355
+ }
356
+ }
357
+ });
358
+
359
+ return { grouped, ungrouped };
360
+ };
361
+
313
362
  export const getNodesAndEdgesFromDagre = ({
314
363
  nodes,
315
364
  edges,
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "license": "SEE LICENSE IN LICENSE",
9
9
  "type": "module",
10
- "version": "3.26.16",
10
+ "version": "3.27.0",
11
11
  "publishConfig": {
12
12
  "access": "public"
13
13
  },
@@ -104,8 +104,8 @@
104
104
  "uuid": "^10.0.0",
105
105
  "zod": "^4.3.6",
106
106
  "@eventcatalog/linter": "1.0.19",
107
- "@eventcatalog/sdk": "2.18.4",
108
- "@eventcatalog/visualiser": "^3.16.1"
107
+ "@eventcatalog/visualiser": "^3.17.0",
108
+ "@eventcatalog/sdk": "2.18.4"
109
109
  },
110
110
  "devDependencies": {
111
111
  "@astrojs/check": "^0.9.8",