@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.
- package/README.md +71 -0
- package/SPEC.md +1939 -0
- package/dist/ast-utils.d.ts +96 -0
- package/dist/ast-utils.d.ts.map +1 -0
- package/dist/ast-utils.js +241 -0
- package/dist/ast-utils.js.map +1 -0
- package/dist/compiler.d.ts +7 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +654 -0
- package/dist/compiler.js.map +1 -0
- package/dist/ec-completion-provider.d.ts +15 -0
- package/dist/ec-completion-provider.d.ts.map +1 -0
- package/dist/ec-completion-provider.js +202 -0
- package/dist/ec-completion-provider.js.map +1 -0
- package/dist/ec-module.d.ts +18 -0
- package/dist/ec-module.d.ts.map +1 -0
- package/dist/ec-module.js +27 -0
- package/dist/ec-module.js.map +1 -0
- package/dist/ec-scope-provider.d.ts +2 -0
- package/dist/ec-scope-provider.d.ts.map +1 -0
- package/dist/ec-scope-provider.js +2 -0
- package/dist/ec-scope-provider.js.map +1 -0
- package/dist/ec-scope.d.ts +10 -0
- package/dist/ec-scope.d.ts.map +1 -0
- package/dist/ec-scope.js +18 -0
- package/dist/ec-scope.js.map +1 -0
- package/dist/ec-validator.d.ts +9 -0
- package/dist/ec-validator.d.ts.map +1 -0
- package/dist/ec-validator.js +238 -0
- package/dist/ec-validator.js.map +1 -0
- package/dist/formatter.d.ts +6 -0
- package/dist/formatter.d.ts.map +1 -0
- package/dist/formatter.js +88 -0
- package/dist/formatter.js.map +1 -0
- package/dist/generated/ast.d.ts +970 -0
- package/dist/generated/ast.d.ts.map +1 -0
- package/dist/generated/ast.js +1537 -0
- package/dist/generated/ast.js.map +1 -0
- package/dist/generated/grammar.d.ts +7 -0
- package/dist/generated/grammar.d.ts.map +1 -0
- package/dist/generated/grammar.js +6062 -0
- package/dist/generated/grammar.js.map +1 -0
- package/dist/generated/module.d.ts +14 -0
- package/dist/generated/module.d.ts.map +1 -0
- package/dist/generated/module.js +21 -0
- package/dist/generated/module.js.map +1 -0
- package/dist/graph-types.d.ts +32 -0
- package/dist/graph-types.d.ts.map +1 -0
- package/dist/graph-types.js +2 -0
- package/dist/graph-types.js.map +1 -0
- package/dist/graph.d.ts +4 -0
- package/dist/graph.d.ts.map +1 -0
- package/dist/graph.js +931 -0
- package/dist/graph.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/main-browser.d.ts +2 -0
- package/dist/main-browser.d.ts.map +1 -0
- package/dist/main-browser.js +16 -0
- package/dist/main-browser.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +15 -0
- package/dist/main.js.map +1 -0
- package/package.json +55 -0
- package/specification/00-overview.md +99 -0
- package/specification/01-domain.md +80 -0
- package/specification/02-service.md +50 -0
- package/specification/03-event.md +28 -0
- package/specification/04-command.md +25 -0
- package/specification/05-query.md +25 -0
- package/specification/06-channel.md +131 -0
- package/specification/08-container.md +54 -0
- package/specification/09-data-product.md +39 -0
- package/specification/10-flow.md +163 -0
- package/specification/11-diagram.md +3 -0
- package/specification/12-user.md +54 -0
- package/specification/13-team.md +62 -0
- package/specification/14-relationships.md +89 -0
- package/specification/15-versioning.md +41 -0
- package/specification/16-annotations.md +100 -0
- package/specification/17-examples.md +373 -0
- package/specification/18-grammar.md +242 -0
- package/specification/19-visualizer.md +197 -0
- package/specification/build-spec.sh +49 -0
- 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
|