@eventcatalog/language-server 0.1.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.
Files changed (88) hide show
  1. package/README.md +71 -0
  2. package/SPEC.md +1939 -0
  3. package/dist/ast-utils.d.ts +96 -0
  4. package/dist/ast-utils.d.ts.map +1 -0
  5. package/dist/ast-utils.js +241 -0
  6. package/dist/ast-utils.js.map +1 -0
  7. package/dist/compiler.d.ts +7 -0
  8. package/dist/compiler.d.ts.map +1 -0
  9. package/dist/compiler.js +654 -0
  10. package/dist/compiler.js.map +1 -0
  11. package/dist/ec-completion-provider.d.ts +15 -0
  12. package/dist/ec-completion-provider.d.ts.map +1 -0
  13. package/dist/ec-completion-provider.js +202 -0
  14. package/dist/ec-completion-provider.js.map +1 -0
  15. package/dist/ec-module.d.ts +18 -0
  16. package/dist/ec-module.d.ts.map +1 -0
  17. package/dist/ec-module.js +27 -0
  18. package/dist/ec-module.js.map +1 -0
  19. package/dist/ec-scope-provider.d.ts +2 -0
  20. package/dist/ec-scope-provider.d.ts.map +1 -0
  21. package/dist/ec-scope-provider.js +2 -0
  22. package/dist/ec-scope-provider.js.map +1 -0
  23. package/dist/ec-scope.d.ts +10 -0
  24. package/dist/ec-scope.d.ts.map +1 -0
  25. package/dist/ec-scope.js +18 -0
  26. package/dist/ec-scope.js.map +1 -0
  27. package/dist/ec-validator.d.ts +9 -0
  28. package/dist/ec-validator.d.ts.map +1 -0
  29. package/dist/ec-validator.js +238 -0
  30. package/dist/ec-validator.js.map +1 -0
  31. package/dist/formatter.d.ts +6 -0
  32. package/dist/formatter.d.ts.map +1 -0
  33. package/dist/formatter.js +88 -0
  34. package/dist/formatter.js.map +1 -0
  35. package/dist/generated/ast.d.ts +970 -0
  36. package/dist/generated/ast.d.ts.map +1 -0
  37. package/dist/generated/ast.js +1537 -0
  38. package/dist/generated/ast.js.map +1 -0
  39. package/dist/generated/grammar.d.ts +7 -0
  40. package/dist/generated/grammar.d.ts.map +1 -0
  41. package/dist/generated/grammar.js +6062 -0
  42. package/dist/generated/grammar.js.map +1 -0
  43. package/dist/generated/module.d.ts +14 -0
  44. package/dist/generated/module.d.ts.map +1 -0
  45. package/dist/generated/module.js +21 -0
  46. package/dist/generated/module.js.map +1 -0
  47. package/dist/graph-types.d.ts +32 -0
  48. package/dist/graph-types.d.ts.map +1 -0
  49. package/dist/graph-types.js +2 -0
  50. package/dist/graph-types.js.map +1 -0
  51. package/dist/graph.d.ts +4 -0
  52. package/dist/graph.d.ts.map +1 -0
  53. package/dist/graph.js +931 -0
  54. package/dist/graph.js.map +1 -0
  55. package/dist/index.d.ts +12 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +10 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/main-browser.d.ts +2 -0
  60. package/dist/main-browser.d.ts.map +1 -0
  61. package/dist/main-browser.js +16 -0
  62. package/dist/main-browser.js.map +1 -0
  63. package/dist/main.d.ts +2 -0
  64. package/dist/main.d.ts.map +1 -0
  65. package/dist/main.js +15 -0
  66. package/dist/main.js.map +1 -0
  67. package/package.json +55 -0
  68. package/specification/00-overview.md +99 -0
  69. package/specification/01-domain.md +80 -0
  70. package/specification/02-service.md +50 -0
  71. package/specification/03-event.md +28 -0
  72. package/specification/04-command.md +25 -0
  73. package/specification/05-query.md +25 -0
  74. package/specification/06-channel.md +131 -0
  75. package/specification/08-container.md +54 -0
  76. package/specification/09-data-product.md +39 -0
  77. package/specification/10-flow.md +163 -0
  78. package/specification/11-diagram.md +3 -0
  79. package/specification/12-user.md +54 -0
  80. package/specification/13-team.md +62 -0
  81. package/specification/14-relationships.md +89 -0
  82. package/specification/15-versioning.md +41 -0
  83. package/specification/16-annotations.md +100 -0
  84. package/specification/17-examples.md +373 -0
  85. package/specification/18-grammar.md +242 -0
  86. package/specification/19-visualizer.md +197 -0
  87. package/specification/build-spec.sh +49 -0
  88. package/syntaxes/ec.tmLanguage.json +61 -0
package/dist/graph.js ADDED
@@ -0,0 +1,931 @@
1
+ import { isDomainDef, isServiceDef, isEventDef, isCommandDef, isQueryDef, isChannelDef, isContainerDef, isDataProductDef, isFlowDef, isDiagramDef, isUserDef, isTeamDef, isVisualizerDef, isToClause, isFromClause, isServiceRefStmt, isDomainRefStmt, isChannelRefStmt, isDataProductRefStmt, isFlowRefStmt, isContainerRefStmt, isActorDef, isExternalSystemDef, isPositionalAnnotationArg, isNamedAnnotationArg, isStringAnnotationValue, isIdAnnotationValue, } from "./generated/ast.js";
2
+ import { stripQuotes, getVersion, getName, getSummary, getOwners, getServices, getSubdomains, getContainers, getServiceRefs, getFlowRefs, getDataProductRefs, getSends, getReceives, getWritesToRefs, getReadsFromRefs, getChannelRefs, getAddress, getProtocols, getContainerType, getInputs, getOutputs, getAnnotations, getToClause, getFromClause, getLegend, getSearch, getToolbar, getFocusMode, getAnimated, getStyle, getDeprecated, getDraft, getSchema, getRoutes, getFlowEntryChains, getFlowWhenBlocks, } from "./ast-utils.js";
3
+ function extractNotes(body) {
4
+ const annotations = getAnnotations(body);
5
+ const notes = [];
6
+ for (const ann of annotations) {
7
+ if (ann.name !== "note")
8
+ continue;
9
+ const note = {
10
+ content: "",
11
+ };
12
+ const contentArg = ann.args.find((a) => isPositionalAnnotationArg(a));
13
+ if (contentArg) {
14
+ const v = isPositionalAnnotationArg(contentArg)
15
+ ? contentArg.value
16
+ : undefined;
17
+ if (v && isStringAnnotationValue(v))
18
+ note.content = stripQuotes(v.value);
19
+ else if (v && isIdAnnotationValue(v))
20
+ note.content = v.value;
21
+ }
22
+ for (const arg of ann.args) {
23
+ if (isNamedAnnotationArg(arg)) {
24
+ const val = isStringAnnotationValue(arg.value)
25
+ ? stripQuotes(arg.value.value)
26
+ : isIdAnnotationValue(arg.value)
27
+ ? arg.value.value
28
+ : undefined;
29
+ if (val && arg.key === "author")
30
+ note.author = val;
31
+ if (val && arg.key === "priority")
32
+ note.priority = val;
33
+ }
34
+ }
35
+ if (note.content)
36
+ notes.push(note);
37
+ }
38
+ return notes;
39
+ }
40
+ export function astToGraph(program, visualizerName) {
41
+ // Find all visualizer definitions
42
+ const visualizerDefs = program.definitions.filter(isVisualizerDef);
43
+ const visualizerNames = visualizerDefs.map((v) => v.name);
44
+ // If no visualizers exist, return empty graph
45
+ if (visualizerDefs.length === 0) {
46
+ return { nodes: [], edges: [], visualizers: [], empty: true };
47
+ }
48
+ // Select the active visualizer
49
+ const activeViz = visualizerName
50
+ ? visualizerDefs.find((v) => v.name === visualizerName) || visualizerDefs[0]
51
+ : visualizerDefs[0];
52
+ // Extract title and display options from visualizer body
53
+ const vizBody = activeViz.body;
54
+ const vizTitle = getName(vizBody) || activeViz.name;
55
+ const options = {
56
+ legend: getLegend(vizBody),
57
+ search: getSearch(vizBody),
58
+ toolbar: getToolbar(vizBody),
59
+ focusMode: getFocusMode(vizBody),
60
+ animated: getAnimated(vizBody),
61
+ style: getStyle(vizBody),
62
+ };
63
+ // Build a lookup map of all definitions (including nested ones) for resolving refs
64
+ const topLevelDefs = new Map();
65
+ function addToDefMap(def) {
66
+ if ("name" in def) {
67
+ topLevelDefs.set(def.name, def);
68
+ }
69
+ // Also add nested definitions (services, containers, etc. inside domains)
70
+ if (isDomainDef(def) && def.body) {
71
+ const body = def.body;
72
+ for (const svc of getServices(body)) {
73
+ addToDefMap(svc);
74
+ }
75
+ for (const container of getContainers(body)) {
76
+ addToDefMap(container);
77
+ }
78
+ for (const ch of body.filter(isChannelDef)) {
79
+ addToDefMap(ch);
80
+ }
81
+ // Subdomains are handled separately (they have their own services/containers)
82
+ for (const subdomain of getSubdomains(body)) {
83
+ const subBody = subdomain.body;
84
+ for (const svc of getServices(subBody)) {
85
+ addToDefMap(svc);
86
+ }
87
+ for (const container of getContainers(subBody)) {
88
+ addToDefMap(container);
89
+ }
90
+ for (const ch of subBody.filter(isChannelDef)) {
91
+ addToDefMap(ch);
92
+ }
93
+ }
94
+ }
95
+ }
96
+ for (const def of program.definitions) {
97
+ if (!isVisualizerDef(def)) {
98
+ addToDefMap(def);
99
+ }
100
+ }
101
+ const nodes = [];
102
+ const edges = [];
103
+ // Build a versioned node ID: type:name@version or type:name (unversioned)
104
+ function nid(name, type, version) {
105
+ if (version)
106
+ return `${type}:${name}@${version}`;
107
+ return `${type}:${name}`;
108
+ }
109
+ // Infer the graph node type for a flow ref by looking up top-level definitions
110
+ function inferTypeFromCatalog(name) {
111
+ const defTypeMap = {};
112
+ for (const def of program.definitions) {
113
+ if ("name" in def && def.name === name) {
114
+ if (isServiceDef(def))
115
+ return "service";
116
+ if (isEventDef(def))
117
+ return "event";
118
+ if (isCommandDef(def))
119
+ return "command";
120
+ if (isQueryDef(def))
121
+ return "query";
122
+ if (isChannelDef(def))
123
+ return "channel";
124
+ if (isContainerDef(def))
125
+ return "container";
126
+ if (isDataProductDef(def))
127
+ return "data-product";
128
+ if (isFlowDef(def))
129
+ return "flow";
130
+ if (isActorDef(def))
131
+ return "actor";
132
+ if (isExternalSystemDef(def))
133
+ return "external-system";
134
+ }
135
+ }
136
+ // Also check if it already exists as a node (from earlier processing)
137
+ for (const n of nodes) {
138
+ if (n.id.startsWith(`${name}`) || n.id.includes(`:${name}`)) {
139
+ return n.type;
140
+ }
141
+ }
142
+ // Default: treat as step (unresolved flow refs are process steps)
143
+ return "step";
144
+ }
145
+ // Find the best matching node for a type+name, optionally with a specific version.
146
+ // If version is given, look for that exact versioned node first.
147
+ // If no version, look for any existing node of that type+name (prefer the first one found).
148
+ function resolveNodeId(name, type, version) {
149
+ if (version) {
150
+ // When a specific version is requested, only return an exact match
151
+ const exact = nodes.find((n) => n.id === nid(name, type, version));
152
+ return exact?.id;
153
+ }
154
+ // No version: find any node matching type:name (with or without version suffix)
155
+ const prefix = `${type}:${name}`;
156
+ const match = nodes.find((n) => n.id === prefix || n.id.startsWith(prefix + "@"));
157
+ return match?.id;
158
+ }
159
+ function addNode(name, type, label, parentId, metadata = {}) {
160
+ const version = metadata.version;
161
+ const id = nid(name, type, version);
162
+ const existing = nodes.find((n) => n.id === id);
163
+ if (existing) {
164
+ if (parentId && !existing.parentId) {
165
+ existing.parentId = parentId;
166
+ }
167
+ // Merge metadata: fill in values the existing node is missing
168
+ for (const [key, value] of Object.entries(metadata)) {
169
+ if (value != null && existing.metadata[key] == null) {
170
+ existing.metadata[key] = value;
171
+ }
172
+ }
173
+ if (label !== name && existing.label === name) {
174
+ existing.label = label;
175
+ }
176
+ return id;
177
+ }
178
+ // For unversioned references, check if a versioned node already exists
179
+ if (!version) {
180
+ const resolved = resolveNodeId(name, type);
181
+ if (resolved) {
182
+ const existingNode = nodes.find((n) => n.id === resolved);
183
+ if (parentId && !existingNode.parentId) {
184
+ existingNode.parentId = parentId;
185
+ }
186
+ return resolved;
187
+ }
188
+ }
189
+ // For versioned definitions, check if an unversioned stub already exists and upgrade it
190
+ if (version) {
191
+ const unversionedId = nid(name, type);
192
+ const stub = nodes.find((n) => n.id === unversionedId);
193
+ if (stub) {
194
+ // Upgrade: change the stub's ID to include the version
195
+ const oldId = stub.id;
196
+ stub.id = id;
197
+ stub.metadata.version = version;
198
+ for (const [key, value] of Object.entries(metadata)) {
199
+ if (value != null && stub.metadata[key] == null) {
200
+ stub.metadata[key] = value;
201
+ }
202
+ }
203
+ if (label !== name && stub.label === name) {
204
+ stub.label = label;
205
+ }
206
+ if (parentId && !stub.parentId) {
207
+ stub.parentId = parentId;
208
+ }
209
+ // Update all edges that referenced the old unversioned ID
210
+ for (const edge of edges) {
211
+ if (edge.source === oldId) {
212
+ edge.source = id;
213
+ edge.id = `${edge.source}-${edge.type}-${edge.target}`;
214
+ }
215
+ if (edge.target === oldId) {
216
+ edge.target = id;
217
+ edge.id = `${edge.source}-${edge.type}-${edge.target}`;
218
+ }
219
+ }
220
+ return id;
221
+ }
222
+ }
223
+ nodes.push({ id, type, label, parentId, metadata });
224
+ return id;
225
+ }
226
+ function addEdge(sourceId, targetId, type, label) {
227
+ const id = `${sourceId}-${type}-${targetId}`;
228
+ if (edges.some((e) => e.id === id))
229
+ return;
230
+ edges.push({ id, source: sourceId, target: targetId, type, label });
231
+ }
232
+ // Enrich a node with metadata from matching top-level definitions (e.g. channel defined outside visualizer)
233
+ function enrichFromTopLevel(nodeId, name, type) {
234
+ const node = nodes.find((n) => n.id === nodeId);
235
+ if (!node)
236
+ return;
237
+ // Find all top-level defs with this name (there can be multiple versions)
238
+ const matchingDefs = program.definitions.filter((d) => !isVisualizerDef(d) && "name" in d && d.name === name);
239
+ for (const def of matchingDefs) {
240
+ const body = ("body" in def ? def.body : []);
241
+ const defVersion = getVersion(body);
242
+ // If the node has a version, only enrich from the matching version def
243
+ if (node.metadata.version &&
244
+ defVersion &&
245
+ node.metadata.version !== defVersion) {
246
+ continue;
247
+ }
248
+ const enrichOwners = getOwners(body);
249
+ const enrichNotes = extractNotes(body);
250
+ const meta = {
251
+ version: defVersion,
252
+ summary: getSummary(body),
253
+ deprecated: getDeprecated(body),
254
+ draft: getDraft(body),
255
+ schema: getSchema(body),
256
+ ...(enrichOwners.length > 0 ? { owners: enrichOwners } : {}),
257
+ ...(enrichNotes.length > 0 ? { notes: enrichNotes } : {}),
258
+ };
259
+ if (isChannelDef(def)) {
260
+ meta.address = getAddress(body);
261
+ meta.protocols = getProtocols(body);
262
+ }
263
+ for (const [key, value] of Object.entries(meta)) {
264
+ if (value != null && node.metadata[key] == null) {
265
+ node.metadata[key] = value;
266
+ }
267
+ }
268
+ const label = getName(body);
269
+ if (label && node.label === name) {
270
+ node.label = label;
271
+ }
272
+ // Process route statements for channels discovered via sends/receives
273
+ if (isChannelDef(def)) {
274
+ for (const route of getRoutes(body)) {
275
+ const targetChId = resolveOrCreateMsg(route.ref.name, "channel", route.ref.version);
276
+ addEdge(nodeId, targetChId, "routes-to");
277
+ }
278
+ }
279
+ }
280
+ }
281
+ // For sends/receives references: resolve to versioned node if it exists, otherwise create
282
+ function resolveOrCreateMsg(name, type, version, parentId) {
283
+ // If a specific version is requested, try to find that exact node
284
+ if (version) {
285
+ const existingId = resolveNodeId(name, type, version);
286
+ if (existingId) {
287
+ const node = nodes.find((n) => n.id === existingId);
288
+ if (parentId && !node.parentId)
289
+ node.parentId = parentId;
290
+ enrichFromTopLevel(existingId, name, type);
291
+ return existingId;
292
+ }
293
+ const id = addNode(name, type, name, parentId, { version });
294
+ enrichFromTopLevel(id, name, type);
295
+ return id;
296
+ }
297
+ // No version: try to find any existing node for this type+name
298
+ const existingId = resolveNodeId(name, type);
299
+ if (existingId) {
300
+ const node = nodes.find((n) => n.id === existingId);
301
+ if (parentId && !node.parentId)
302
+ node.parentId = parentId;
303
+ enrichFromTopLevel(existingId, name, type);
304
+ return existingId;
305
+ }
306
+ const id = addNode(name, type, name, parentId, {});
307
+ enrichFromTopLevel(id, name, type);
308
+ return id;
309
+ }
310
+ function processSends(serviceId, sendsList, domainId) {
311
+ for (const s of sendsList) {
312
+ const msgType = s.messageType;
313
+ // Determine version: from inline body, or from the statement itself
314
+ let version = s.version;
315
+ if (!version && s.body.length > 0) {
316
+ version = getVersion(s.body);
317
+ }
318
+ const msgNodeId = resolveOrCreateMsg(s.messageName, msgType, version, domainId);
319
+ if (domainId) {
320
+ addEdge(domainId, msgNodeId, "contains");
321
+ }
322
+ addEdge(serviceId, msgNodeId, "sends");
323
+ // Inline body summary + schema + notes
324
+ if (s.body.length > 0) {
325
+ const existing = nodes.find((n) => n.id === msgNodeId);
326
+ if (existing) {
327
+ const summary = getSummary(s.body);
328
+ if (summary && !existing.metadata.summary)
329
+ existing.metadata.summary = summary;
330
+ const schema = getSchema(s.body);
331
+ if (schema && !existing.metadata.schema)
332
+ existing.metadata.schema = schema;
333
+ const inlineNotes = extractNotes(s.body);
334
+ if (inlineNotes.length > 0 && !existing.metadata.notes)
335
+ existing.metadata.notes = inlineNotes;
336
+ }
337
+ }
338
+ // Channel clause (on the sends statement itself)
339
+ if (s.channelClause) {
340
+ const clause = s.channelClause;
341
+ if (isToClause(clause) || isFromClause(clause)) {
342
+ for (const ch of clause.channels) {
343
+ const chId = resolveOrCreateMsg(ch.channelName, "channel", ch.channelVersion, domainId);
344
+ if (domainId)
345
+ addEdge(domainId, chId, "contains");
346
+ addEdge(msgNodeId, chId, "routes-to");
347
+ }
348
+ }
349
+ }
350
+ // Channel clauses inside body
351
+ const bodyTo = getToClause(s.body);
352
+ if (bodyTo) {
353
+ for (const ch of bodyTo.channels) {
354
+ const chId = resolveOrCreateMsg(ch.channelName, "channel", ch.channelVersion, domainId);
355
+ if (domainId)
356
+ addEdge(domainId, chId, "contains");
357
+ addEdge(msgNodeId, chId, "routes-to");
358
+ }
359
+ }
360
+ const bodyFrom = getFromClause(s.body);
361
+ if (bodyFrom) {
362
+ for (const ch of bodyFrom.channels) {
363
+ const chId = resolveOrCreateMsg(ch.channelName, "channel", ch.channelVersion, domainId);
364
+ if (domainId)
365
+ addEdge(domainId, chId, "contains");
366
+ addEdge(msgNodeId, chId, "routes-to");
367
+ }
368
+ }
369
+ }
370
+ }
371
+ function processReceives(serviceId, receivesList, domainId) {
372
+ for (const r of receivesList) {
373
+ const msgType = r.messageType;
374
+ let version = r.version;
375
+ if (!version && r.body.length > 0) {
376
+ version = getVersion(r.body);
377
+ }
378
+ const msgNodeId = resolveOrCreateMsg(r.messageName, msgType, version, domainId);
379
+ if (domainId) {
380
+ addEdge(domainId, msgNodeId, "contains");
381
+ }
382
+ addEdge(msgNodeId, serviceId, "receives");
383
+ if (r.body.length > 0) {
384
+ const existing = nodes.find((n) => n.id === msgNodeId);
385
+ if (existing) {
386
+ const summary = getSummary(r.body);
387
+ if (summary && !existing.metadata.summary)
388
+ existing.metadata.summary = summary;
389
+ const schema = getSchema(r.body);
390
+ if (schema && !existing.metadata.schema)
391
+ existing.metadata.schema = schema;
392
+ const inlineNotes = extractNotes(r.body);
393
+ if (inlineNotes.length > 0 && !existing.metadata.notes)
394
+ existing.metadata.notes = inlineNotes;
395
+ }
396
+ }
397
+ // Channel clause: "from ChannelX" means the service receives via that channel.
398
+ // Only create Channel → Service edge. The Message → Channel edge is handled
399
+ // by the sends side or by channel route statements.
400
+ const channelRefs = [];
401
+ if (r.channelClause) {
402
+ const clause = r.channelClause;
403
+ if (isToClause(clause) || isFromClause(clause)) {
404
+ for (const ch of clause.channels) {
405
+ channelRefs.push({
406
+ name: ch.channelName,
407
+ version: ch.channelVersion,
408
+ });
409
+ }
410
+ }
411
+ }
412
+ const bodyTo = getToClause(r.body);
413
+ if (bodyTo) {
414
+ for (const ch of bodyTo.channels) {
415
+ channelRefs.push({
416
+ name: ch.channelName,
417
+ version: ch.channelVersion,
418
+ });
419
+ }
420
+ }
421
+ const bodyFrom = getFromClause(r.body);
422
+ if (bodyFrom) {
423
+ for (const ch of bodyFrom.channels) {
424
+ channelRefs.push({
425
+ name: ch.channelName,
426
+ version: ch.channelVersion,
427
+ });
428
+ }
429
+ }
430
+ if (channelRefs.length > 0) {
431
+ // Remove the direct Message → Service edge since the channel mediates
432
+ const directEdgeIdx = edges.findIndex((e) => e.source === msgNodeId &&
433
+ e.target === serviceId &&
434
+ e.type === "receives");
435
+ if (directEdgeIdx !== -1)
436
+ edges.splice(directEdgeIdx, 1);
437
+ for (const ch of channelRefs) {
438
+ const chId = resolveOrCreateMsg(ch.name, "channel", ch.version, domainId);
439
+ if (domainId)
440
+ addEdge(domainId, chId, "contains");
441
+ // Only add Message → Channel edge if no route path already exists
442
+ // from the message to this channel (via sends + channel routes).
443
+ // Check if the message already has a routes-to edge to ANY channel;
444
+ // if a route chain exists, the sends side + route statements handle connectivity.
445
+ const msgHasChannelEdge = edges.some((e) => e.source === msgNodeId && e.type === "routes-to");
446
+ if (!msgHasChannelEdge) {
447
+ addEdge(msgNodeId, chId, "routes-to");
448
+ }
449
+ addEdge(chId, serviceId, "receives");
450
+ }
451
+ }
452
+ }
453
+ }
454
+ function processService(svc, parentId) {
455
+ const body = svc.body;
456
+ const svcName = getName(body) || svc.name;
457
+ const notes = extractNotes(body);
458
+ const svcOwners = getOwners(body);
459
+ const svcId = addNode(svc.name, "service", svcName, parentId, {
460
+ version: getVersion(body),
461
+ summary: getSummary(body),
462
+ deprecated: getDeprecated(body),
463
+ draft: getDraft(body),
464
+ ...(svcOwners.length > 0 ? { owners: svcOwners } : {}),
465
+ ...(notes.length > 0 ? { notes } : {}),
466
+ });
467
+ if (parentId) {
468
+ addEdge(parentId, svcId, "contains");
469
+ }
470
+ // Messages sent/received by a service inside a domain should be parented to the same domain
471
+ processSends(svcId, getSends(body), parentId);
472
+ processReceives(svcId, getReceives(body), parentId);
473
+ for (const ref of getWritesToRefs(body)) {
474
+ const containerId = resolveOrCreateMsg(ref.ref.name, "container", ref.ref.version);
475
+ addEdge(svcId, containerId, "writes-to");
476
+ }
477
+ for (const ref of getReadsFromRefs(body)) {
478
+ const containerId = resolveOrCreateMsg(ref.ref.name, "container", ref.ref.version);
479
+ addEdge(containerId, svcId, "reads-from");
480
+ }
481
+ }
482
+ function processSubdomain(sub, parentId) {
483
+ const body = sub.body;
484
+ const subName = getName(body) || sub.name;
485
+ const subOwners = getOwners(body);
486
+ const subId = addNode(sub.name, "domain", subName, parentId, {
487
+ version: getVersion(body),
488
+ summary: getSummary(body),
489
+ deprecated: getDeprecated(body),
490
+ draft: getDraft(body),
491
+ ...(subOwners.length > 0 ? { owners: subOwners } : {}),
492
+ });
493
+ addEdge(parentId, subId, "contains");
494
+ for (const svc of getServices(body)) {
495
+ processService(svc, subId);
496
+ }
497
+ for (const nested of getSubdomains(body)) {
498
+ processSubdomain(nested, subId);
499
+ }
500
+ }
501
+ function processDomain(domain) {
502
+ const body = domain.body;
503
+ const domainName = getName(body) || domain.name;
504
+ const domOwners = getOwners(body);
505
+ const domId = addNode(domain.name, "domain", domainName, undefined, {
506
+ version: getVersion(body),
507
+ summary: getSummary(body),
508
+ deprecated: getDeprecated(body),
509
+ draft: getDraft(body),
510
+ ...(domOwners.length > 0 ? { owners: domOwners } : {}),
511
+ });
512
+ for (const svc of getServices(body)) {
513
+ processService(svc, domId);
514
+ }
515
+ for (const ref of getServiceRefs(body)) {
516
+ const svcId = resolveOrCreateMsg(ref.ref.name, "service", ref.ref.version, domId);
517
+ addEdge(domId, svcId, "contains");
518
+ }
519
+ for (const container of getContainers(body)) {
520
+ const containerBody = container.body;
521
+ const containerId = addNode(container.name, "container", getName(containerBody) || container.name, domId, {
522
+ version: getVersion(containerBody),
523
+ summary: getSummary(containerBody),
524
+ deprecated: getDeprecated(containerBody),
525
+ draft: getDraft(containerBody),
526
+ containerType: getContainerType(containerBody),
527
+ });
528
+ addEdge(domId, containerId, "contains");
529
+ }
530
+ for (const item of body.filter(isChannelDef)) {
531
+ const chBody = item.body;
532
+ const chBodyNotes = extractNotes(chBody);
533
+ const chId = addNode(item.name, "channel", getName(chBody) || item.name, domId, {
534
+ version: getVersion(chBody),
535
+ summary: getSummary(chBody),
536
+ deprecated: getDeprecated(chBody),
537
+ draft: getDraft(chBody),
538
+ address: getAddress(chBody),
539
+ protocols: getProtocols(chBody),
540
+ ...(chBodyNotes.length > 0 ? { notes: chBodyNotes } : {}),
541
+ });
542
+ addEdge(domId, chId, "contains");
543
+ for (const route of getRoutes(chBody)) {
544
+ const targetChId = resolveOrCreateMsg(route.ref.name, "channel", route.ref.version);
545
+ addEdge(chId, targetChId, "routes-to");
546
+ }
547
+ }
548
+ for (const sub of getSubdomains(body)) {
549
+ processSubdomain(sub, domId);
550
+ }
551
+ for (const ref of getFlowRefs(body)) {
552
+ const flowId = resolveOrCreateMsg(ref.ref.name, "flow", ref.ref.version, domId);
553
+ addEdge(domId, flowId, "contains");
554
+ }
555
+ for (const ref of getDataProductRefs(body)) {
556
+ const dpId = resolveOrCreateMsg(ref.ref.name, "data-product", ref.ref.version, domId);
557
+ addEdge(domId, dpId, "contains");
558
+ }
559
+ processSends(domId, getSends(body));
560
+ processReceives(domId, getReceives(body));
561
+ }
562
+ function processMessage(def) {
563
+ const nodeType = isEventDef(def)
564
+ ? "event"
565
+ : isCommandDef(def)
566
+ ? "command"
567
+ : "query";
568
+ const body = def.body;
569
+ const msgName = getName(body) || def.name;
570
+ const notes = extractNotes(body);
571
+ const msgOwners = getOwners(body);
572
+ addNode(def.name, nodeType, msgName, undefined, {
573
+ version: getVersion(body),
574
+ summary: getSummary(body),
575
+ deprecated: getDeprecated(body),
576
+ draft: getDraft(body),
577
+ schema: getSchema(body),
578
+ ...(msgOwners.length > 0 ? { owners: msgOwners } : {}),
579
+ ...(notes.length > 0 ? { notes } : {}),
580
+ });
581
+ for (const ref of getChannelRefs(body)) {
582
+ resolveOrCreateMsg(ref.ref.name, "channel", ref.ref.version);
583
+ }
584
+ }
585
+ function processDefinition(def) {
586
+ if (isDomainDef(def)) {
587
+ processDomain(def);
588
+ }
589
+ else if (isServiceDef(def)) {
590
+ processService(def);
591
+ }
592
+ else if (isEventDef(def) || isCommandDef(def) || isQueryDef(def)) {
593
+ processMessage(def);
594
+ }
595
+ else if (isChannelDef(def)) {
596
+ const body = def.body;
597
+ const chNotes = extractNotes(body);
598
+ const chOwners = getOwners(body);
599
+ const chId = addNode(def.name, "channel", getName(body) || def.name, undefined, {
600
+ version: getVersion(body),
601
+ summary: getSummary(body),
602
+ deprecated: getDeprecated(body),
603
+ draft: getDraft(body),
604
+ address: getAddress(body),
605
+ protocols: getProtocols(body),
606
+ ...(chOwners.length > 0 ? { owners: chOwners } : {}),
607
+ ...(chNotes.length > 0 ? { notes: chNotes } : {}),
608
+ });
609
+ // Process route statements: channel → channel edges
610
+ for (const route of getRoutes(body)) {
611
+ const targetChId = resolveOrCreateMsg(route.ref.name, "channel", route.ref.version);
612
+ addEdge(chId, targetChId, "routes-to");
613
+ }
614
+ }
615
+ else if (isContainerDef(def)) {
616
+ const body = def.body;
617
+ const contOwners = getOwners(body);
618
+ addNode(def.name, "container", getName(body) || def.name, undefined, {
619
+ version: getVersion(body),
620
+ summary: getSummary(body),
621
+ deprecated: getDeprecated(body),
622
+ draft: getDraft(body),
623
+ containerType: getContainerType(body),
624
+ ...(contOwners.length > 0 ? { owners: contOwners } : {}),
625
+ });
626
+ }
627
+ else if (isDataProductDef(def)) {
628
+ const body = def.body;
629
+ const dpOwners = getOwners(body);
630
+ const dpId = addNode(def.name, "data-product", getName(body) || def.name, undefined, {
631
+ version: getVersion(body),
632
+ summary: getSummary(body),
633
+ deprecated: getDeprecated(body),
634
+ draft: getDraft(body),
635
+ ...(dpOwners.length > 0 ? { owners: dpOwners } : {}),
636
+ });
637
+ for (const inp of getInputs(body)) {
638
+ const existing = resolveNodeId(inp.ref.name, "event", inp.ref.version) ||
639
+ resolveNodeId(inp.ref.name, "command", inp.ref.version) ||
640
+ resolveNodeId(inp.ref.name, "query", inp.ref.version);
641
+ const inpId = existing ||
642
+ addNode(inp.ref.name, "event", inp.ref.name, undefined, {
643
+ version: inp.ref.version,
644
+ });
645
+ addEdge(inpId, dpId, "sends");
646
+ }
647
+ for (const out of getOutputs(body)) {
648
+ const existing = resolveNodeId(out.name, "event") ||
649
+ resolveNodeId(out.name, "command") ||
650
+ resolveNodeId(out.name, "query");
651
+ const outId = existing || addNode(out.name, "event", out.name);
652
+ addEdge(dpId, outId, "sends");
653
+ }
654
+ }
655
+ else if (isFlowDef(def)) {
656
+ const body = def.body;
657
+ const entryChains = getFlowEntryChains(body);
658
+ const whenBlocks = getFlowWhenBlocks(body);
659
+ if (entryChains.length > 0 || whenBlocks.length > 0) {
660
+ // Resolve a FlowRef to a graph node, inferring type from catalog
661
+ function resolveFlowRef(ref) {
662
+ return resolveOrCreateMsg(ref.name, inferTypeFromCatalog(ref.name));
663
+ }
664
+ // Get the edge label from a FlowRef (the quoted string after the name)
665
+ function flowRefLabel(ref) {
666
+ return ref.label ? stripQuotes(ref.label) : undefined;
667
+ }
668
+ // Process entry chains
669
+ for (const chain of entryChains) {
670
+ for (const ref of [...chain.sources, ...chain.targets]) {
671
+ resolveFlowRef(ref);
672
+ }
673
+ const firstTarget = chain.targets[0];
674
+ if (firstTarget) {
675
+ const firstTargetId = resolveFlowRef(firstTarget);
676
+ for (const src of chain.sources) {
677
+ // Label on source ref becomes the edge label to first target
678
+ addEdge(resolveFlowRef(src), firstTargetId, "flow-step", flowRefLabel(src));
679
+ }
680
+ }
681
+ for (let i = 0; i < chain.targets.length - 1; i++) {
682
+ addEdge(resolveFlowRef(chain.targets[i]), resolveFlowRef(chain.targets[i + 1]), "flow-step", flowRefLabel(chain.targets[i]));
683
+ }
684
+ }
685
+ // Process when blocks
686
+ for (const block of whenBlocks) {
687
+ for (const trigger of block.triggers) {
688
+ resolveFlowRef(trigger);
689
+ }
690
+ for (const action of block.actions) {
691
+ const actionId = resolveFlowRef(action.ref);
692
+ // Triggers connect to each action; action ref label goes on the edge
693
+ for (const trigger of block.triggers) {
694
+ addEdge(resolveFlowRef(trigger), actionId, "flow-step", flowRefLabel(action.ref));
695
+ }
696
+ // Action outputs
697
+ for (const output of action.outputs) {
698
+ const targetId = resolveFlowRef(output.target);
699
+ addEdge(actionId, targetId, "flow-step", output.label ? stripQuotes(output.label) : undefined);
700
+ }
701
+ }
702
+ }
703
+ }
704
+ else {
705
+ // No flow body — just show as a single node
706
+ addNode(def.name, "flow", getName(body) || def.name, undefined, {
707
+ version: getVersion(body),
708
+ summary: getSummary(body),
709
+ deprecated: getDeprecated(body),
710
+ draft: getDraft(body),
711
+ });
712
+ }
713
+ }
714
+ else if (isDiagramDef(def)) {
715
+ const body = def.body;
716
+ addNode(def.name, "diagram", getName(body) || def.name, undefined, {
717
+ version: getVersion(body),
718
+ summary: getSummary(body),
719
+ deprecated: getDeprecated(body),
720
+ draft: getDraft(body),
721
+ });
722
+ }
723
+ else if (isUserDef(def)) {
724
+ const props = def.props;
725
+ const userName = getName(props) || def.name;
726
+ let role;
727
+ let email;
728
+ let team;
729
+ const owns = [];
730
+ for (const p of def.props) {
731
+ if ("value" in p && p.$type === "UserRoleProp")
732
+ role = stripQuotes(p.value);
733
+ if ("value" in p && p.$type === "UserEmailProp")
734
+ email = stripQuotes(p.value);
735
+ if (p.$type === "UserTeamProp")
736
+ team = p.teamRef;
737
+ if (p.$type === "UserOwnsProp") {
738
+ owns.push({
739
+ resourceType: p.owns.resourceType,
740
+ id: p.owns.resourceName,
741
+ });
742
+ }
743
+ }
744
+ const userId = addNode(def.name, "user", userName, undefined, {
745
+ role,
746
+ email,
747
+ });
748
+ for (const o of owns) {
749
+ const targetId = resolveNodeId(o.id, o.resourceType) || nid(o.id, o.resourceType);
750
+ addEdge(userId, targetId, "owns");
751
+ }
752
+ if (team) {
753
+ const teamId = resolveNodeId(team, "team") || nid(team, "team");
754
+ addEdge(userId, teamId, "member-of");
755
+ }
756
+ }
757
+ else if (isTeamDef(def)) {
758
+ const props = def.props;
759
+ const teamName = getName(props) || def.name;
760
+ let summary;
761
+ let email;
762
+ const members = [];
763
+ const owns = [];
764
+ for (const p of def.props) {
765
+ if (p.$type === "TeamSummaryProp")
766
+ summary = stripQuotes(p.value);
767
+ if (p.$type === "TeamEmailProp")
768
+ email = stripQuotes(p.value);
769
+ if (p.$type === "TeamMemberProp")
770
+ members.push(p.memberRef);
771
+ if (p.$type === "TeamOwnsProp") {
772
+ owns.push({
773
+ resourceType: p.owns.resourceType,
774
+ id: p.owns.resourceName,
775
+ });
776
+ }
777
+ }
778
+ const teamId = addNode(def.name, "team", teamName, undefined, {
779
+ summary,
780
+ email,
781
+ });
782
+ for (const m of members) {
783
+ const memberId = resolveNodeId(m, "user") || nid(m, "user");
784
+ addEdge(memberId, teamId, "member-of");
785
+ }
786
+ for (const o of owns) {
787
+ const targetId = resolveNodeId(o.id, o.resourceType) || nid(o.id, o.resourceType);
788
+ addEdge(teamId, targetId, "owns");
789
+ }
790
+ }
791
+ else if (isActorDef(def)) {
792
+ const body = (def.body || []);
793
+ addNode(def.name, "actor", getName(body) || def.name, undefined, {
794
+ summary: getSummary(body),
795
+ });
796
+ }
797
+ else if (isExternalSystemDef(def)) {
798
+ const body = (def.body || []);
799
+ addNode(def.name, "external-system", getName(body) || def.name, undefined, {
800
+ summary: getSummary(body),
801
+ });
802
+ }
803
+ }
804
+ function processVisualizerItem(item) {
805
+ // Ref statements → look up top-level def and process it
806
+ if (isServiceRefStmt(item)) {
807
+ const def = topLevelDefs.get(item.ref.name);
808
+ if (def && isServiceDef(def)) {
809
+ processService(def);
810
+ }
811
+ else {
812
+ addNode(item.ref.name, "service", item.ref.name, undefined, {
813
+ version: item.ref.version,
814
+ });
815
+ }
816
+ return;
817
+ }
818
+ if (isDomainRefStmt(item)) {
819
+ const def = topLevelDefs.get(item.ref.name);
820
+ if (def && isDomainDef(def)) {
821
+ processDomain(def);
822
+ }
823
+ else {
824
+ addNode(item.ref.name, "domain", item.ref.name, undefined, {
825
+ version: item.ref.version,
826
+ });
827
+ }
828
+ return;
829
+ }
830
+ if (isChannelRefStmt(item)) {
831
+ const def = topLevelDefs.get(item.ref.name);
832
+ if (def && isChannelDef(def)) {
833
+ processDefinition(def);
834
+ }
835
+ else {
836
+ addNode(item.ref.name, "channel", item.ref.name, undefined, {
837
+ version: item.ref.version,
838
+ });
839
+ }
840
+ return;
841
+ }
842
+ if (isDataProductRefStmt(item)) {
843
+ const def = topLevelDefs.get(item.ref.name);
844
+ if (def && isDataProductDef(def)) {
845
+ processDefinition(def);
846
+ }
847
+ else {
848
+ addNode(item.ref.name, "data-product", item.ref.name, undefined, {
849
+ version: item.ref.version,
850
+ });
851
+ }
852
+ return;
853
+ }
854
+ if (isFlowRefStmt(item)) {
855
+ const def = topLevelDefs.get(item.ref.name);
856
+ if (def && isFlowDef(def)) {
857
+ processDefinition(def);
858
+ }
859
+ else {
860
+ addNode(item.ref.name, "flow", item.ref.name, undefined, {
861
+ version: item.ref.version,
862
+ });
863
+ }
864
+ return;
865
+ }
866
+ if (isContainerRefStmt(item)) {
867
+ const def = topLevelDefs.get(item.ref.name);
868
+ if (def && isContainerDef(def)) {
869
+ processDefinition(def);
870
+ }
871
+ else {
872
+ addNode(item.ref.name, "container", item.ref.name, undefined, {
873
+ version: item.ref.version,
874
+ });
875
+ }
876
+ return;
877
+ }
878
+ // EventDef/CommandDef/QueryDef with empty body → treat as reference
879
+ if ((isEventDef(item) || isCommandDef(item) || isQueryDef(item)) &&
880
+ (!item.body || item.body.length === 0)) {
881
+ const nodeType = isEventDef(item)
882
+ ? "event"
883
+ : isCommandDef(item)
884
+ ? "command"
885
+ : "query";
886
+ const def = topLevelDefs.get(item.name);
887
+ if (def && (isEventDef(def) || isCommandDef(def) || isQueryDef(def))) {
888
+ processMessage(def);
889
+ }
890
+ else {
891
+ addNode(item.name, nodeType, item.name);
892
+ }
893
+ return;
894
+ }
895
+ // Inline definitions → use existing process functions
896
+ if (isDomainDef(item)) {
897
+ processDomain(item);
898
+ return;
899
+ }
900
+ if (isServiceDef(item)) {
901
+ processService(item);
902
+ return;
903
+ }
904
+ if (isEventDef(item) || isCommandDef(item) || isQueryDef(item)) {
905
+ processMessage(item);
906
+ return;
907
+ }
908
+ if (isChannelDef(item) ||
909
+ isContainerDef(item) ||
910
+ isDataProductDef(item) ||
911
+ isFlowDef(item) ||
912
+ isActorDef(item) ||
913
+ isExternalSystemDef(item)) {
914
+ processDefinition(item);
915
+ return;
916
+ }
917
+ }
918
+ // Process only items inside the active visualizer body
919
+ for (const item of activeViz.body) {
920
+ processVisualizerItem(item);
921
+ }
922
+ return {
923
+ nodes,
924
+ edges,
925
+ visualizers: visualizerNames,
926
+ activeVisualizer: activeViz.name,
927
+ title: vizTitle,
928
+ options,
929
+ };
930
+ }
931
+ //# sourceMappingURL=graph.js.map