@likec4/language-server 1.8.1 → 1.10.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/contrib/likec4.tmLanguage.json +1 -1
- package/dist/browser.cjs +21 -0
- package/dist/browser.d.cts +22 -0
- package/dist/browser.d.mts +22 -0
- package/dist/browser.d.ts +22 -0
- package/dist/browser.mjs +19 -0
- package/dist/index.cjs +10 -0
- package/dist/index.d.cts +18 -0
- package/dist/index.d.mts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.mjs +1 -0
- package/dist/likec4lib.cjs +961 -0
- package/dist/likec4lib.d.cts +6 -0
- package/dist/likec4lib.d.mts +6 -0
- package/dist/likec4lib.d.ts +6 -0
- package/dist/likec4lib.mjs +957 -0
- package/dist/model-graph/index.cjs +10 -0
- package/dist/model-graph/index.d.cts +79 -0
- package/dist/model-graph/index.d.mts +79 -0
- package/dist/model-graph/index.d.ts +79 -0
- package/dist/model-graph/index.mjs +1 -0
- package/dist/node.cjs +18 -0
- package/dist/node.d.cts +20 -0
- package/dist/node.d.mts +20 -0
- package/dist/node.d.ts +20 -0
- package/dist/node.mjs +16 -0
- package/dist/protocol.cjs +25 -0
- package/dist/protocol.d.cts +43 -0
- package/dist/protocol.d.mts +43 -0
- package/dist/protocol.d.ts +43 -0
- package/dist/protocol.mjs +17 -0
- package/dist/shared/language-server.CjFzaJwI.d.cts +1223 -0
- package/dist/shared/language-server.CtKHXJDD.d.ts +1223 -0
- package/dist/shared/language-server.D-84I33F.d.mts +1223 -0
- package/dist/shared/language-server.DBJJUUgF.mjs +5737 -0
- package/dist/shared/language-server.DtBRb9os.mjs +1656 -0
- package/dist/shared/language-server.DwyCJvXm.cjs +1669 -0
- package/dist/shared/language-server.JWkqVjGv.cjs +5748 -0
- package/package.json +36 -20
- package/src/ast.ts +48 -36
- package/src/browser.ts +0 -3
- package/src/elementRef.ts +1 -1
- package/src/formatting/LikeC4Formatter.ts +388 -0
- package/src/formatting/utils.ts +26 -0
- package/src/generated/ast.ts +170 -12
- package/src/generated/grammar.ts +1 -1
- package/src/generated-lib/icons.ts +1 -1
- package/src/like-c4.langium +49 -8
- package/src/likec4lib.ts +2 -3
- package/src/logger.ts +9 -1
- package/src/lsp/DocumentLinkProvider.ts +27 -15
- package/src/lsp/RenameProvider.ts +8 -0
- package/src/lsp/SemanticTokenProvider.ts +20 -2
- package/src/lsp/index.ts +1 -0
- package/src/model/fqn-computation.ts +33 -23
- package/src/model/fqn-index.ts +5 -21
- package/src/model/model-builder.ts +180 -112
- package/src/model/model-locator.ts +1 -1
- package/src/model/model-parser-where.ts +3 -2
- package/src/model/model-parser.ts +99 -39
- package/src/model-graph/LikeC4ModelGraph.ts +42 -21
- package/src/model-graph/compute-view/__test__/fixture.ts +16 -14
- package/src/model-graph/compute-view/compute.ts +110 -81
- package/src/model-graph/compute-view/predicates.ts +6 -8
- package/src/model-graph/dynamic-view/__test__/fixture.ts +1 -0
- package/src/model-graph/dynamic-view/compute.ts +98 -61
- package/src/model-graph/utils/buildElementNotations.ts +1 -1
- package/src/model-graph/utils/elementExpressionToPredicate.ts +1 -1
- package/src/model-graph/utils/sortNodes.ts +2 -6
- package/src/module.ts +21 -4
- package/src/protocol.ts +4 -5
- package/src/references/scope-computation.ts +10 -1
- package/src/references/scope-provider.ts +2 -1
- package/src/shared/NodeKindProvider.ts +73 -34
- package/src/test/setup.ts +3 -8
- package/src/test/testServices.ts +27 -7
- package/src/utils/graphlib.ts +11 -0
- package/src/validation/index.ts +2 -1
- package/src/validation/property-checks.ts +13 -1
- package/src/validation/specification.ts +3 -3
- package/src/view-utils/manual-layout.ts +1 -1
- package/src/view-utils/resolve-extended-views.ts +19 -10
- package/src/view-utils/resolve-relative-paths.ts +19 -24
- package/src/view-utils/view-hash.ts +1 -1
- package/src/reset.d.ts +0 -2
|
@@ -0,0 +1,1669 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const core = require('@likec4/core');
|
|
4
|
+
const remeda = require('remeda');
|
|
5
|
+
const graphlib = require('@dagrejs/graphlib');
|
|
6
|
+
const objectHash = require('object-hash');
|
|
7
|
+
|
|
8
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
9
|
+
|
|
10
|
+
const graphlib__default = /*#__PURE__*/_interopDefaultCompat(graphlib);
|
|
11
|
+
const objectHash__default = /*#__PURE__*/_interopDefaultCompat(objectHash);
|
|
12
|
+
|
|
13
|
+
function calcViewLayoutHash(view) {
|
|
14
|
+
const tohash = {
|
|
15
|
+
id: view.id,
|
|
16
|
+
__: view.__ ?? "element",
|
|
17
|
+
autoLayout: view.autoLayout,
|
|
18
|
+
nodes: remeda.pipe(
|
|
19
|
+
view.nodes,
|
|
20
|
+
remeda.map(remeda.pick(["id", "title", "description", "technology", "shape", "icon", "children"])),
|
|
21
|
+
remeda.mapToObj(({ id, icon, ...node }) => [id, { ...node, icon: remeda.isTruthy(icon) ? "Y" : "N" }])
|
|
22
|
+
),
|
|
23
|
+
edges: remeda.pipe(
|
|
24
|
+
view.edges,
|
|
25
|
+
remeda.map(remeda.pick(["source", "target", "label", "description", "technology", "dir", "head", "tail", "line"])),
|
|
26
|
+
remeda.mapToObj(({ source, target, ...edge }) => [`${source}:${target}`, edge])
|
|
27
|
+
)
|
|
28
|
+
};
|
|
29
|
+
view.hash = objectHash__default(tohash, {
|
|
30
|
+
ignoreUnknown: true,
|
|
31
|
+
respectType: false
|
|
32
|
+
});
|
|
33
|
+
return view;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function elementExprToPredicate(target) {
|
|
37
|
+
if (core.Expr.isElementWhere(target)) {
|
|
38
|
+
const predicate = elementExprToPredicate(target.where.expr);
|
|
39
|
+
const where = core.whereOperatorAsPredicate(target.where.condition);
|
|
40
|
+
return (n) => predicate(n) && where(n);
|
|
41
|
+
}
|
|
42
|
+
if (core.Expr.isWildcard(target)) {
|
|
43
|
+
return () => true;
|
|
44
|
+
}
|
|
45
|
+
if (core.Expr.isElementKindExpr(target)) {
|
|
46
|
+
return target.isEqual ? (n) => n.kind === target.elementKind : (n) => n.kind !== target.elementKind;
|
|
47
|
+
}
|
|
48
|
+
if (core.Expr.isElementTagExpr(target)) {
|
|
49
|
+
return target.isEqual ? ({ tags }) => !!tags && tags.includes(target.elementTag) : ({ tags }) => remeda.isNullish(tags) || !tags.includes(target.elementTag);
|
|
50
|
+
}
|
|
51
|
+
if (core.Expr.isExpandedElementExpr(target)) {
|
|
52
|
+
return (n) => n.id === target.expanded || core.parentFqn(n.id) === target.expanded;
|
|
53
|
+
}
|
|
54
|
+
if (core.Expr.isElementRef(target)) {
|
|
55
|
+
const { element, isDescedants } = target;
|
|
56
|
+
return isDescedants ? (n) => n.id.startsWith(element + ".") : (n) => n.id === element;
|
|
57
|
+
}
|
|
58
|
+
if (core.Expr.isCustomElement(target)) {
|
|
59
|
+
return elementExprToPredicate(target.custom.expr);
|
|
60
|
+
}
|
|
61
|
+
core.nonexhaustive(target);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function applyCustomElementProperties(_rules, _nodes) {
|
|
65
|
+
const rules = _rules.flatMap((r) => "include" in r ? r.include.filter(core.Expr.isCustomElement) : []);
|
|
66
|
+
if (rules.length === 0) {
|
|
67
|
+
return _nodes;
|
|
68
|
+
}
|
|
69
|
+
const nodes = [..._nodes];
|
|
70
|
+
for (const {
|
|
71
|
+
custom: { expr, ...props }
|
|
72
|
+
} of rules) {
|
|
73
|
+
const { border, opacity, ...rest } = remeda.omitBy(props, remeda.isNullish);
|
|
74
|
+
const notEmpty = !remeda.isEmpty(rest);
|
|
75
|
+
const satisfies = elementExprToPredicate(expr);
|
|
76
|
+
nodes.forEach((node, i) => {
|
|
77
|
+
if (!satisfies(node)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (notEmpty) {
|
|
81
|
+
node = {
|
|
82
|
+
...node,
|
|
83
|
+
isCustomized: true,
|
|
84
|
+
...rest
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
let styleOverride;
|
|
88
|
+
if (border !== void 0) {
|
|
89
|
+
styleOverride = { border };
|
|
90
|
+
}
|
|
91
|
+
if (opacity !== void 0) {
|
|
92
|
+
styleOverride = { ...styleOverride, opacity };
|
|
93
|
+
}
|
|
94
|
+
if (styleOverride) {
|
|
95
|
+
node = {
|
|
96
|
+
...node,
|
|
97
|
+
isCustomized: true,
|
|
98
|
+
style: {
|
|
99
|
+
...node.style,
|
|
100
|
+
...styleOverride
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
nodes[i] = node;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return nodes;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function relationExpressionToPredicates(expr) {
|
|
111
|
+
switch (true) {
|
|
112
|
+
case core.Expr.isRelationWhere(expr):
|
|
113
|
+
return relationExpressionToPredicates(expr.where.expr);
|
|
114
|
+
case core.Expr.isRelation(expr): {
|
|
115
|
+
const isSource = elementExprToPredicate(expr.source);
|
|
116
|
+
const isTarget = elementExprToPredicate(expr.target);
|
|
117
|
+
return (edge) => {
|
|
118
|
+
return isSource(edge.source) && isTarget(edge.target) || !!expr.isBidirectional && isSource(edge.target) && isTarget(edge.source);
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
case core.Expr.isInOut(expr): {
|
|
122
|
+
const isInOut = elementExprToPredicate(expr.inout);
|
|
123
|
+
return (edge) => isInOut(edge.source) || isInOut(edge.target);
|
|
124
|
+
}
|
|
125
|
+
case core.Expr.isIncoming(expr): {
|
|
126
|
+
const isTarget = elementExprToPredicate(expr.incoming);
|
|
127
|
+
return (edge) => isTarget(edge.target);
|
|
128
|
+
}
|
|
129
|
+
case core.Expr.isOutgoing(expr): {
|
|
130
|
+
const isSource = elementExprToPredicate(expr.outgoing);
|
|
131
|
+
return (edge) => isSource(edge.source);
|
|
132
|
+
}
|
|
133
|
+
default:
|
|
134
|
+
core.nonexhaustive(expr);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function applyCustomRelationProperties(_rules, nodes, _edges) {
|
|
138
|
+
const rules = _rules.flatMap((r) => "include" in r ? r.include.filter(core.Expr.isCustomRelationExpr) : []);
|
|
139
|
+
const edges = Array.from(_edges);
|
|
140
|
+
if (rules.length === 0 || edges.length === 0) {
|
|
141
|
+
return edges;
|
|
142
|
+
}
|
|
143
|
+
for (const {
|
|
144
|
+
customRelation: { relation, title, ...customprops }
|
|
145
|
+
} of rules) {
|
|
146
|
+
const props = remeda.omitBy(customprops, remeda.isNullish);
|
|
147
|
+
const satisfies = relationExpressionToPredicates(relation);
|
|
148
|
+
edges.forEach((edge, i) => {
|
|
149
|
+
const source = nodes.find((n) => n.id === edge.source);
|
|
150
|
+
const target = nodes.find((n) => n.id === edge.target);
|
|
151
|
+
if (!source || !target) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (satisfies({ source, target })) {
|
|
155
|
+
edges[i] = {
|
|
156
|
+
...edge,
|
|
157
|
+
label: title ?? edge.label,
|
|
158
|
+
isCustomized: true,
|
|
159
|
+
...props
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return edges;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function applyViewRuleStyles(_rules, nodes) {
|
|
168
|
+
const rules = _rules.filter(core.isViewRuleStyle);
|
|
169
|
+
if (rules.length === 0) {
|
|
170
|
+
return nodes;
|
|
171
|
+
}
|
|
172
|
+
for (const rule of rules) {
|
|
173
|
+
const predicates = [];
|
|
174
|
+
for (const target of rule.targets) {
|
|
175
|
+
if (core.Expr.isWildcard(target)) {
|
|
176
|
+
predicates.push(() => true);
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
predicates.push(elementExprToPredicate(target));
|
|
180
|
+
}
|
|
181
|
+
remeda.filter(nodes, remeda.anyPass(predicates)).forEach((n) => {
|
|
182
|
+
n.shape = rule.style.shape ?? n.shape;
|
|
183
|
+
n.color = rule.style.color ?? n.color;
|
|
184
|
+
if (remeda.isDefined(rule.style.icon)) {
|
|
185
|
+
n.icon = rule.style.icon;
|
|
186
|
+
}
|
|
187
|
+
if (remeda.isDefined(rule.notation)) {
|
|
188
|
+
n.notation = rule.notation;
|
|
189
|
+
}
|
|
190
|
+
let styleOverride;
|
|
191
|
+
if (remeda.isDefined(rule.style.border)) {
|
|
192
|
+
styleOverride = { border: rule.style.border };
|
|
193
|
+
}
|
|
194
|
+
if (remeda.isDefined(rule.style.opacity)) {
|
|
195
|
+
styleOverride = { ...styleOverride, opacity: rule.style.opacity };
|
|
196
|
+
}
|
|
197
|
+
if (styleOverride) {
|
|
198
|
+
n.style = {
|
|
199
|
+
...n.style,
|
|
200
|
+
...styleOverride
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return nodes;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function updateDepthOfAncestors(node, nodes) {
|
|
209
|
+
let parentNd;
|
|
210
|
+
while (!!node.parent && (parentNd = nodes.get(node.parent))) {
|
|
211
|
+
const depth = parentNd.depth ?? 1;
|
|
212
|
+
parentNd.depth = Math.max(depth, (node.depth ?? 0) + 1);
|
|
213
|
+
if (parentNd.depth === depth) {
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
node = parentNd;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function buildComputeNodes(elements) {
|
|
220
|
+
return Array.from(elements).sort(core.compareByFqnHierarchically).reduce((map, { id, color, shape, style, ...el }) => {
|
|
221
|
+
let parent = core.parentFqn(id);
|
|
222
|
+
let level = 0;
|
|
223
|
+
while (parent) {
|
|
224
|
+
const parentNd = map.get(parent);
|
|
225
|
+
if (parentNd) {
|
|
226
|
+
if (parentNd.children.length == 0) {
|
|
227
|
+
parentNd.depth = 1;
|
|
228
|
+
updateDepthOfAncestors(parentNd, map);
|
|
229
|
+
}
|
|
230
|
+
parentNd.children.push(id);
|
|
231
|
+
level = parentNd.level + 1;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
parent = core.parentFqn(parent);
|
|
235
|
+
}
|
|
236
|
+
const node = {
|
|
237
|
+
...el,
|
|
238
|
+
id,
|
|
239
|
+
parent,
|
|
240
|
+
level,
|
|
241
|
+
color: color ?? core.DefaultThemeColor,
|
|
242
|
+
shape: shape ?? core.DefaultElementShape,
|
|
243
|
+
children: [],
|
|
244
|
+
inEdges: [],
|
|
245
|
+
outEdges: [],
|
|
246
|
+
style: {
|
|
247
|
+
...style
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
map.set(id, node);
|
|
251
|
+
return map;
|
|
252
|
+
}, /* @__PURE__ */ new Map());
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function buildElementNotations(nodes) {
|
|
256
|
+
return remeda.pipe(
|
|
257
|
+
nodes,
|
|
258
|
+
remeda.groupBy(remeda.prop("notation")),
|
|
259
|
+
remeda.mapValues(
|
|
260
|
+
remeda.piped(
|
|
261
|
+
remeda.groupBy(remeda.prop("shape")),
|
|
262
|
+
remeda.mapValues(
|
|
263
|
+
remeda.piped(
|
|
264
|
+
remeda.groupBy(remeda.prop("color")),
|
|
265
|
+
remeda.mapValues(
|
|
266
|
+
remeda.piped(
|
|
267
|
+
remeda.map(remeda.prop("kind")),
|
|
268
|
+
remeda.unique()
|
|
269
|
+
)
|
|
270
|
+
),
|
|
271
|
+
remeda.entries(),
|
|
272
|
+
remeda.map(([color, kinds]) => ({
|
|
273
|
+
kinds,
|
|
274
|
+
color
|
|
275
|
+
}))
|
|
276
|
+
)
|
|
277
|
+
),
|
|
278
|
+
remeda.entries(),
|
|
279
|
+
remeda.flatMap(
|
|
280
|
+
([shape, colors]) => colors.map(({ color, kinds }) => ({
|
|
281
|
+
shape,
|
|
282
|
+
color,
|
|
283
|
+
kinds
|
|
284
|
+
}))
|
|
285
|
+
)
|
|
286
|
+
)
|
|
287
|
+
),
|
|
288
|
+
remeda.entries(),
|
|
289
|
+
remeda.flatMap(
|
|
290
|
+
([title, shapes]) => shapes.map(({ shape, color, kinds }) => ({
|
|
291
|
+
title,
|
|
292
|
+
shape,
|
|
293
|
+
color,
|
|
294
|
+
kinds
|
|
295
|
+
}))
|
|
296
|
+
),
|
|
297
|
+
remeda.sortBy(
|
|
298
|
+
remeda.prop("shape"),
|
|
299
|
+
remeda.prop("title"),
|
|
300
|
+
[
|
|
301
|
+
(n) => n.kinds.length,
|
|
302
|
+
"desc"
|
|
303
|
+
]
|
|
304
|
+
)
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const postorder = graphlib__default.alg.postorder;
|
|
309
|
+
const findCycles = graphlib__default.alg.findCycles;
|
|
310
|
+
const isAcyclic = graphlib__default.alg.isAcyclic;
|
|
311
|
+
|
|
312
|
+
function sortChildren(nodes) {
|
|
313
|
+
nodes.forEach((parent) => {
|
|
314
|
+
if (parent.children.length > 0) {
|
|
315
|
+
parent.children = nodes.flatMap((n) => n.parent === parent.id ? n.id : []);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
function sortNodes({
|
|
320
|
+
nodes,
|
|
321
|
+
edges
|
|
322
|
+
}) {
|
|
323
|
+
if (edges.length === 0) {
|
|
324
|
+
return nodes;
|
|
325
|
+
}
|
|
326
|
+
const g = new graphlib.Graph({
|
|
327
|
+
compound: false,
|
|
328
|
+
directed: true,
|
|
329
|
+
multigraph: false
|
|
330
|
+
});
|
|
331
|
+
const getNode = (id) => core.nonNullable(
|
|
332
|
+
nodes.find((n) => n.id === id),
|
|
333
|
+
"Edge not found"
|
|
334
|
+
);
|
|
335
|
+
const getEdge = (id) => core.nonNullable(
|
|
336
|
+
edges.find((edge) => edge.id === id),
|
|
337
|
+
"Edge not found"
|
|
338
|
+
);
|
|
339
|
+
for (const e of [...edges].sort(core.compareRelations)) {
|
|
340
|
+
g.setEdge(e.source, e.target);
|
|
341
|
+
}
|
|
342
|
+
for (const n of nodes) {
|
|
343
|
+
g.setNode(n.id);
|
|
344
|
+
if (n.children.length > 0) {
|
|
345
|
+
n.inEdges.forEach((e) => {
|
|
346
|
+
const edge = getEdge(e);
|
|
347
|
+
if (edge.target !== n.id && getNode(edge.source).children.length === 0) {
|
|
348
|
+
g.setEdge(edge.source, n.id);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
if (n.parent) {
|
|
353
|
+
g.setEdge(n.parent, n.id);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
let sources = g.sources();
|
|
357
|
+
if (sources.length === 0) {
|
|
358
|
+
sources = remeda.pipe(
|
|
359
|
+
nodes,
|
|
360
|
+
remeda.sort(core.compareByFqnHierarchically),
|
|
361
|
+
remeda.filter((n) => n.inEdges.length === 0 || n.parent === null),
|
|
362
|
+
remeda.map((n) => n.id)
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
const orderedIds = postorder(g, sources).reverse();
|
|
366
|
+
const sorted = orderedIds.map(getNode);
|
|
367
|
+
if (sorted.length < nodes.length) {
|
|
368
|
+
const unsorted = remeda.difference(nodes, sorted);
|
|
369
|
+
sorted.push(...unsorted);
|
|
370
|
+
}
|
|
371
|
+
core.invariant(sorted.length === nodes.length, "Not all nodes were processed by graphlib");
|
|
372
|
+
sortChildren(sorted);
|
|
373
|
+
return sorted;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const NoFilter = () => true;
|
|
377
|
+
const Identity = (x) => x;
|
|
378
|
+
const filterBy = (pred) => pred === NoFilter ? Identity : remeda.filter(pred);
|
|
379
|
+
const filterOne = (pred) => pred === NoFilter ? Identity : (x) => pred(x) ? x : null;
|
|
380
|
+
function includeElementRef(expr, where = NoFilter) {
|
|
381
|
+
const currentElements = [...this.resolvedElements];
|
|
382
|
+
const filter = filterBy(where);
|
|
383
|
+
const elements = filter(
|
|
384
|
+
expr.isDescedants === true ? this.graph.childrenOrElement(expr.element) : [this.graph.element(expr.element)]
|
|
385
|
+
);
|
|
386
|
+
if (elements.length === 0) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
this.addElement(...elements);
|
|
390
|
+
if (elements.length > 1) {
|
|
391
|
+
this.addEdges(this.graph.edgesWithin(elements));
|
|
392
|
+
}
|
|
393
|
+
if (currentElements.length > 0 && elements.length > 0) {
|
|
394
|
+
for (const el of elements) {
|
|
395
|
+
this.addEdges(this.graph.anyEdgesBetween(el, currentElements));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
function excludeElementRef(expr, where = NoFilter) {
|
|
400
|
+
const elements = [...this.resolvedElements].filter(remeda.allPass([
|
|
401
|
+
elementExprToPredicate(expr),
|
|
402
|
+
where
|
|
403
|
+
]));
|
|
404
|
+
this.excludeElement(...elements);
|
|
405
|
+
}
|
|
406
|
+
function includeWildcardRef(_expr, where = NoFilter) {
|
|
407
|
+
const root = this.root;
|
|
408
|
+
if (!root) {
|
|
409
|
+
const elements = this.graph.rootElements.filter(where);
|
|
410
|
+
if (elements.length <= 0) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const currentElements2 = [...this.resolvedElements];
|
|
414
|
+
this.addElement(...elements);
|
|
415
|
+
this.addEdges(this.graph.edgesWithin(elements));
|
|
416
|
+
if (currentElements2.length > 0) {
|
|
417
|
+
for (const el of elements) {
|
|
418
|
+
this.addEdges(this.graph.anyEdgesBetween(el, currentElements2));
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
const currentElements = [...this.resolvedElements];
|
|
424
|
+
const _elRoot = filterOne(where)(this.graph.element(root));
|
|
425
|
+
if (_elRoot) {
|
|
426
|
+
this.addElement(_elRoot);
|
|
427
|
+
}
|
|
428
|
+
const filter = filterBy(where);
|
|
429
|
+
const children = filter(this.graph.children(root));
|
|
430
|
+
const hasChildren = children.length > 0;
|
|
431
|
+
if (hasChildren) {
|
|
432
|
+
this.addElement(...children);
|
|
433
|
+
this.addEdges(this.graph.edgesWithin(children));
|
|
434
|
+
} else if (_elRoot) {
|
|
435
|
+
children.push(_elRoot);
|
|
436
|
+
}
|
|
437
|
+
const neighbours = [
|
|
438
|
+
...currentElements,
|
|
439
|
+
...filter([
|
|
440
|
+
...this.graph.siblings(root),
|
|
441
|
+
...this.graph.ancestors(root).flatMap((a) => this.graph.siblings(a.id))
|
|
442
|
+
])
|
|
443
|
+
];
|
|
444
|
+
for (const el of children) {
|
|
445
|
+
this.addEdges(this.graph.anyEdgesBetween(el, neighbours));
|
|
446
|
+
}
|
|
447
|
+
if (!hasChildren && _elRoot) {
|
|
448
|
+
const edgesWithSiblings = this.graph.anyEdgesBetween(_elRoot, this.graph.siblings(root));
|
|
449
|
+
if (edgesWithSiblings.length === 0) {
|
|
450
|
+
const _parentId = core.parentFqn(root);
|
|
451
|
+
const parent = _parentId && this.graph.element(_parentId);
|
|
452
|
+
if (parent && where(parent)) {
|
|
453
|
+
this.addElement(parent);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
function excludeWildcardRef(_expr, where = NoFilter) {
|
|
459
|
+
if (where !== NoFilter) {
|
|
460
|
+
const elements = [...this.resolvedElements].filter(where);
|
|
461
|
+
this.excludeElement(...elements);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const root = this.root;
|
|
465
|
+
if (root) {
|
|
466
|
+
this.excludeElement(
|
|
467
|
+
this.graph.element(root),
|
|
468
|
+
...this.graph.children(root)
|
|
469
|
+
);
|
|
470
|
+
this.excludeRelation(
|
|
471
|
+
...this.graph.connectedRelations(root)
|
|
472
|
+
);
|
|
473
|
+
} else {
|
|
474
|
+
this.reset();
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
function includeExpandedElementExpr(expr, where = NoFilter) {
|
|
478
|
+
const filter = filterBy(where);
|
|
479
|
+
const currentElements = [...this.resolvedElements];
|
|
480
|
+
const parent = this.graph.element(expr.expanded);
|
|
481
|
+
if (where(parent)) {
|
|
482
|
+
this.addElement(parent);
|
|
483
|
+
const anyEdgesBetween = this.graph.anyEdgesBetween(parent, currentElements);
|
|
484
|
+
this.addEdges(anyEdgesBetween);
|
|
485
|
+
}
|
|
486
|
+
const expanded = [];
|
|
487
|
+
for (const el of filter(this.graph.children(expr.expanded))) {
|
|
488
|
+
this.addImplicit(el);
|
|
489
|
+
const edges = this.graph.anyEdgesBetween(el, currentElements);
|
|
490
|
+
if (edges.length > 0) {
|
|
491
|
+
this.addEdges(edges);
|
|
492
|
+
expanded.push(el);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (expanded.length > 1) {
|
|
496
|
+
this.addEdges(this.graph.edgesWithin(expanded));
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
function excludeExpandedElementExpr(expr, where = NoFilter) {
|
|
500
|
+
const elements = [...this.resolvedElements].filter(remeda.allPass([
|
|
501
|
+
elementExprToPredicate(expr),
|
|
502
|
+
where
|
|
503
|
+
]));
|
|
504
|
+
this.excludeElement(...elements);
|
|
505
|
+
}
|
|
506
|
+
const asElementPredicate = (expr) => {
|
|
507
|
+
if (expr.isEqual) {
|
|
508
|
+
if (core.Expr.isElementKindExpr(expr)) {
|
|
509
|
+
return (e) => e.kind === expr.elementKind;
|
|
510
|
+
} else {
|
|
511
|
+
return ({ tags }) => !!tags && tags.includes(expr.elementTag);
|
|
512
|
+
}
|
|
513
|
+
} else {
|
|
514
|
+
if (core.Expr.isElementKindExpr(expr)) {
|
|
515
|
+
return (e) => e.kind !== expr.elementKind;
|
|
516
|
+
} else {
|
|
517
|
+
return ({ tags }) => remeda.isNullish(tags) || tags.length === 0 || !tags.includes(expr.elementTag);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
function includeElementKindOrTag(expr, where = NoFilter) {
|
|
522
|
+
const elements = this.graph.elements.filter(asElementPredicate(expr)).filter(where);
|
|
523
|
+
if (elements.length > 0) {
|
|
524
|
+
const currentElements = [...this.resolvedElements];
|
|
525
|
+
this.addElement(...elements);
|
|
526
|
+
this.addEdges(this.graph.edgesWithin(elements));
|
|
527
|
+
for (const el of elements) {
|
|
528
|
+
this.addEdges(this.graph.anyEdgesBetween(el, currentElements));
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function excludeElementKindOrTag(expr, where = NoFilter) {
|
|
533
|
+
const elements = [...this.resolvedElements].filter(asElementPredicate(expr)).filter(where);
|
|
534
|
+
if (elements.length > 0) {
|
|
535
|
+
this.excludeElement(...elements);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
function resolveNeighbours(expr) {
|
|
539
|
+
if (core.Expr.isElementRef(expr)) {
|
|
540
|
+
return this.graph.ascendingSiblings(expr.element);
|
|
541
|
+
}
|
|
542
|
+
return this.root ? this.graph.ascendingSiblings(this.root) : this.graph.rootElements;
|
|
543
|
+
}
|
|
544
|
+
function resolveElements(expr) {
|
|
545
|
+
if (core.Expr.isWildcard(expr)) {
|
|
546
|
+
if (this.root) {
|
|
547
|
+
return [...this.graph.children(this.root), this.graph.element(this.root)];
|
|
548
|
+
} else {
|
|
549
|
+
return this.graph.rootElements;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
if (core.Expr.isElementKindExpr(expr)) {
|
|
553
|
+
return this.graph.elements.filter((el) => {
|
|
554
|
+
if (expr.isEqual) {
|
|
555
|
+
return el.kind === expr.elementKind;
|
|
556
|
+
}
|
|
557
|
+
return el.kind !== expr.elementKind;
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
if (core.Expr.isElementTagExpr(expr)) {
|
|
561
|
+
return this.graph.elements.filter((el) => {
|
|
562
|
+
const tags = el.tags;
|
|
563
|
+
if (expr.isEqual) {
|
|
564
|
+
return !!tags && tags.includes(expr.elementTag);
|
|
565
|
+
}
|
|
566
|
+
return remeda.isNullish(tags) || tags.length === 0 || !tags.includes(expr.elementTag);
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
if (core.Expr.isExpandedElementExpr(expr)) {
|
|
570
|
+
return [this.graph.element(expr.expanded)];
|
|
571
|
+
}
|
|
572
|
+
if (!core.Expr.isElementRef(expr)) {
|
|
573
|
+
return core.nonexhaustive(expr);
|
|
574
|
+
}
|
|
575
|
+
if (this.root === expr.element && expr.isDescedants !== true) {
|
|
576
|
+
return [...this.graph.children(this.root), this.graph.element(this.root)];
|
|
577
|
+
}
|
|
578
|
+
if (expr.isDescedants) {
|
|
579
|
+
return this.graph.childrenOrElement(expr.element);
|
|
580
|
+
} else {
|
|
581
|
+
return [this.graph.element(expr.element)];
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
function edgesIncomingExpr(expr) {
|
|
585
|
+
if (core.Expr.isWildcard(expr)) {
|
|
586
|
+
if (!this.root) {
|
|
587
|
+
return [];
|
|
588
|
+
}
|
|
589
|
+
const sources2 = this.graph.ascendingSiblings(this.root);
|
|
590
|
+
const targets2 = [...this.graph.children(this.root), this.graph.element(this.root)];
|
|
591
|
+
return this.graph.edgesBetween(sources2, targets2);
|
|
592
|
+
}
|
|
593
|
+
const targets = resolveElements.call(this, expr);
|
|
594
|
+
if (targets.length === 0) {
|
|
595
|
+
return [];
|
|
596
|
+
}
|
|
597
|
+
let sources = [...this.resolvedElements];
|
|
598
|
+
if (core.Expr.isElementRef(expr) || core.Expr.isExpandedElementExpr(expr)) {
|
|
599
|
+
const exprElement = expr.element ?? expr.expanded;
|
|
600
|
+
const isDescedants = expr.isDescedants ?? false;
|
|
601
|
+
sources = sources.filter(
|
|
602
|
+
(el) => (
|
|
603
|
+
// allow elements, that are not nested or are direct children
|
|
604
|
+
!core.isAncestor(exprElement, el.id) || isDescedants && core.parentFqn(el.id) === exprElement
|
|
605
|
+
)
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
if (sources.length === 0) {
|
|
609
|
+
sources = resolveNeighbours.call(this, expr);
|
|
610
|
+
}
|
|
611
|
+
return this.graph.edgesBetween(sources, targets);
|
|
612
|
+
}
|
|
613
|
+
const filterEdges = (edges, where) => {
|
|
614
|
+
if (!where) {
|
|
615
|
+
return edges;
|
|
616
|
+
}
|
|
617
|
+
return remeda.pipe(
|
|
618
|
+
edges,
|
|
619
|
+
remeda.map((e) => ({ ...e, relations: e.relations.filter(where) })),
|
|
620
|
+
remeda.filter((e) => e.relations.length > 0)
|
|
621
|
+
);
|
|
622
|
+
};
|
|
623
|
+
const filterRelations = (edges, where) => {
|
|
624
|
+
return remeda.pipe(
|
|
625
|
+
edges,
|
|
626
|
+
remeda.flatMap((e) => e.relations),
|
|
627
|
+
where ? remeda.filter(where) : Identity,
|
|
628
|
+
remeda.unique()
|
|
629
|
+
);
|
|
630
|
+
};
|
|
631
|
+
function includeIncomingExpr(expr, where) {
|
|
632
|
+
const edges = filterEdges(edgesIncomingExpr.call(this, expr.incoming), where);
|
|
633
|
+
if (edges.length === 0) {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
this.addEdges(edges);
|
|
637
|
+
this.addImplicit(...edges.map((e) => e.target));
|
|
638
|
+
}
|
|
639
|
+
function excludeIncomingExpr(expr, where) {
|
|
640
|
+
let relations = filterRelations(edgesIncomingExpr.call(this, expr.incoming), where);
|
|
641
|
+
this.excludeRelation(...relations);
|
|
642
|
+
}
|
|
643
|
+
function edgesOutgoingExpr(expr) {
|
|
644
|
+
if (core.Expr.isWildcard(expr)) {
|
|
645
|
+
if (!this.root) {
|
|
646
|
+
return [];
|
|
647
|
+
}
|
|
648
|
+
const targets2 = this.graph.ascendingSiblings(this.root);
|
|
649
|
+
const sources2 = [...this.graph.children(this.root), this.graph.element(this.root)];
|
|
650
|
+
return this.graph.edgesBetween(sources2, targets2);
|
|
651
|
+
}
|
|
652
|
+
const sources = resolveElements.call(this, expr);
|
|
653
|
+
if (sources.length === 0) {
|
|
654
|
+
return [];
|
|
655
|
+
}
|
|
656
|
+
let targets = [...this.resolvedElements];
|
|
657
|
+
if (core.Expr.isElementRef(expr) || core.Expr.isExpandedElementExpr(expr)) {
|
|
658
|
+
const sourceElement = expr.element ?? expr.expanded;
|
|
659
|
+
const isDescedants = expr.isDescedants ?? false;
|
|
660
|
+
targets = targets.filter(
|
|
661
|
+
(el) => (
|
|
662
|
+
// allow elements, that are not nested or are direct children
|
|
663
|
+
!core.isAncestor(sourceElement, el.id) || isDescedants && core.parentFqn(el.id) === sourceElement
|
|
664
|
+
)
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
if (targets.length === 0) {
|
|
668
|
+
targets = resolveNeighbours.call(this, expr);
|
|
669
|
+
}
|
|
670
|
+
return this.graph.edgesBetween(sources, targets);
|
|
671
|
+
}
|
|
672
|
+
function includeOutgoingExpr(expr, where) {
|
|
673
|
+
const edges = filterEdges(edgesOutgoingExpr.call(this, expr.outgoing), where);
|
|
674
|
+
if (edges.length === 0) {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
this.addEdges(edges);
|
|
678
|
+
this.addImplicit(...edges.map((e) => e.source));
|
|
679
|
+
}
|
|
680
|
+
function excludeOutgoingExpr(expr, where) {
|
|
681
|
+
const relations = filterRelations(edgesOutgoingExpr.call(this, expr.outgoing), where);
|
|
682
|
+
this.excludeRelation(...relations);
|
|
683
|
+
}
|
|
684
|
+
var EdgePredicateResult;
|
|
685
|
+
((EdgePredicateResult2) => {
|
|
686
|
+
EdgePredicateResult2.Empty = {
|
|
687
|
+
implicits: [],
|
|
688
|
+
edges: []
|
|
689
|
+
};
|
|
690
|
+
})(EdgePredicateResult || (EdgePredicateResult = {}));
|
|
691
|
+
function edgesInOutExpr({ inout }, where) {
|
|
692
|
+
if (core.Expr.isWildcard(inout)) {
|
|
693
|
+
if (!this.root) {
|
|
694
|
+
return EdgePredicateResult.Empty;
|
|
695
|
+
}
|
|
696
|
+
const neighbours = this.graph.ascendingSiblings(this.root);
|
|
697
|
+
return {
|
|
698
|
+
edges: filterEdges(this.graph.anyEdgesBetween(this.graph.element(this.root), neighbours), where),
|
|
699
|
+
implicits: []
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
const elements = resolveElements.call(this, inout);
|
|
703
|
+
if (elements.length === 0) {
|
|
704
|
+
return EdgePredicateResult.Empty;
|
|
705
|
+
}
|
|
706
|
+
let currentElements = [...this.resolvedElements];
|
|
707
|
+
if (core.Expr.isElementRef(inout) || core.Expr.isExpandedElementExpr(inout)) {
|
|
708
|
+
const exprElement = inout.element ?? inout.expanded;
|
|
709
|
+
const isDescedants = inout.isDescedants ?? false;
|
|
710
|
+
currentElements = currentElements.filter(
|
|
711
|
+
(el) => (
|
|
712
|
+
// allow elements, that are not nested or are direct children
|
|
713
|
+
!core.isAncestor(exprElement, el.id) || isDescedants && core.parentFqn(el.id) === exprElement
|
|
714
|
+
)
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
if (currentElements.length === 0) {
|
|
718
|
+
currentElements = resolveNeighbours.call(this, inout);
|
|
719
|
+
}
|
|
720
|
+
return elements.reduce((acc, el) => {
|
|
721
|
+
const edges = filterEdges(this.graph.anyEdgesBetween(el, currentElements), where);
|
|
722
|
+
if (edges.length > 0) {
|
|
723
|
+
acc.implicits.push(el);
|
|
724
|
+
acc.edges.push(...edges);
|
|
725
|
+
}
|
|
726
|
+
return acc;
|
|
727
|
+
}, { implicits: [], edges: [] });
|
|
728
|
+
}
|
|
729
|
+
function includeInOutExpr(expr, where) {
|
|
730
|
+
const { implicits, edges } = edgesInOutExpr.call(this, expr, where);
|
|
731
|
+
this.addEdges(edges);
|
|
732
|
+
this.addImplicit(...implicits);
|
|
733
|
+
}
|
|
734
|
+
function excludeInOutExpr(expr, where) {
|
|
735
|
+
const { edges } = edgesInOutExpr.call(this, expr, where);
|
|
736
|
+
this.excludeRelation(...edges.flatMap((e) => e.relations));
|
|
737
|
+
}
|
|
738
|
+
function resolveRelationExprElements(expr) {
|
|
739
|
+
if (core.Expr.isElementRef(expr) && this.root === expr.element && expr.isDescedants !== true) {
|
|
740
|
+
return [...this.graph.children(expr.element), this.graph.element(expr.element)];
|
|
741
|
+
}
|
|
742
|
+
if (core.Expr.isExpandedElementExpr(expr) && this.root === expr.expanded) {
|
|
743
|
+
return [...this.graph.children(expr.expanded), this.graph.element(expr.expanded)];
|
|
744
|
+
}
|
|
745
|
+
return resolveElements.call(this, expr);
|
|
746
|
+
}
|
|
747
|
+
function includeRelationExpr(expr, where) {
|
|
748
|
+
let sources, targets;
|
|
749
|
+
if (core.Expr.isWildcard(expr.source) && !core.Expr.isWildcard(expr.target)) {
|
|
750
|
+
sources = resolveNeighbours.call(this, expr.target);
|
|
751
|
+
targets = resolveRelationExprElements.call(this, expr.target);
|
|
752
|
+
} else if (!core.Expr.isWildcard(expr.source) && core.Expr.isWildcard(expr.target)) {
|
|
753
|
+
sources = resolveRelationExprElements.call(this, expr.source);
|
|
754
|
+
targets = resolveNeighbours.call(this, expr.source);
|
|
755
|
+
} else {
|
|
756
|
+
sources = resolveRelationExprElements.call(this, expr.source);
|
|
757
|
+
targets = resolveRelationExprElements.call(this, expr.target);
|
|
758
|
+
}
|
|
759
|
+
const edges = this.graph.edgesBetween(sources, targets);
|
|
760
|
+
if (expr.isBidirectional === true) {
|
|
761
|
+
edges.push(...this.graph.edgesBetween(targets, sources));
|
|
762
|
+
}
|
|
763
|
+
this.addEdges(filterEdges(edges, where));
|
|
764
|
+
}
|
|
765
|
+
function excludeRelationExpr(expr, where) {
|
|
766
|
+
const isSource = elementExprToPredicate(expr.source);
|
|
767
|
+
const isTarget = elementExprToPredicate(expr.target);
|
|
768
|
+
const satisfies = (edge) => {
|
|
769
|
+
let result = isSource(edge.source) && isTarget(edge.target);
|
|
770
|
+
if (!result && expr.isBidirectional) {
|
|
771
|
+
result = isSource(edge.target) && isTarget(edge.source);
|
|
772
|
+
}
|
|
773
|
+
return result;
|
|
774
|
+
};
|
|
775
|
+
const edges = this.edges.filter(satisfies);
|
|
776
|
+
const relations = filterRelations(edges, where);
|
|
777
|
+
this.excludeRelation(...relations);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function compareEdges(a, b) {
|
|
781
|
+
return core.compareRelations(
|
|
782
|
+
{ source: a.source.id, target: a.target.id },
|
|
783
|
+
{ source: b.source.id, target: b.target.id }
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
class ComputeCtx {
|
|
787
|
+
constructor(view, graph) {
|
|
788
|
+
this.view = view;
|
|
789
|
+
this.graph = graph;
|
|
790
|
+
}
|
|
791
|
+
// Intermediate state
|
|
792
|
+
explicits = /* @__PURE__ */ new Set();
|
|
793
|
+
implicits = /* @__PURE__ */ new Set();
|
|
794
|
+
ctxEdges = [];
|
|
795
|
+
static elementView(view, graph) {
|
|
796
|
+
return new ComputeCtx(view, graph).compute();
|
|
797
|
+
}
|
|
798
|
+
compute() {
|
|
799
|
+
this.reset();
|
|
800
|
+
const {
|
|
801
|
+
docUri: _docUri,
|
|
802
|
+
// exclude docUri
|
|
803
|
+
rules,
|
|
804
|
+
...view
|
|
805
|
+
} = this.view;
|
|
806
|
+
const viewPredicates = rules.filter(core.isViewRulePredicate);
|
|
807
|
+
if (this.root && viewPredicates.length == 0) {
|
|
808
|
+
this.addElement(this.graph.element(this.root));
|
|
809
|
+
}
|
|
810
|
+
this.processPredicates(viewPredicates);
|
|
811
|
+
this.removeRedundantImplicitEdges();
|
|
812
|
+
const elements = [...this.includedElements];
|
|
813
|
+
const nodesMap = buildComputeNodes(elements);
|
|
814
|
+
const edgesMap = /* @__PURE__ */ new Map();
|
|
815
|
+
const edges = this.computeEdges();
|
|
816
|
+
for (const edge of edges) {
|
|
817
|
+
edgesMap.set(edge.id, edge);
|
|
818
|
+
const source = nodesMap.get(edge.source);
|
|
819
|
+
const target = nodesMap.get(edge.target);
|
|
820
|
+
core.invariant(source, `Source node ${edge.source} not found`);
|
|
821
|
+
core.invariant(target, `Target node ${edge.target} not found`);
|
|
822
|
+
while (edge.parent && !nodesMap.has(edge.parent)) {
|
|
823
|
+
edge.parent = core.parentFqn(edge.parent);
|
|
824
|
+
}
|
|
825
|
+
source.outEdges.push(edge.id);
|
|
826
|
+
target.inEdges.push(edge.id);
|
|
827
|
+
for (const sourceAncestor of core.ancestorsFqn(edge.source)) {
|
|
828
|
+
if (sourceAncestor === edge.parent) {
|
|
829
|
+
break;
|
|
830
|
+
}
|
|
831
|
+
nodesMap.get(sourceAncestor)?.outEdges.push(edge.id);
|
|
832
|
+
}
|
|
833
|
+
for (const targetAncestor of core.ancestorsFqn(edge.target)) {
|
|
834
|
+
if (targetAncestor === edge.parent) {
|
|
835
|
+
break;
|
|
836
|
+
}
|
|
837
|
+
nodesMap.get(targetAncestor)?.inEdges.push(edge.id);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
const initialSort = elements.flatMap((e) => nodesMap.get(e.id) ?? []);
|
|
841
|
+
const nodes = applyCustomElementProperties(
|
|
842
|
+
rules,
|
|
843
|
+
applyViewRuleStyles(
|
|
844
|
+
rules,
|
|
845
|
+
// Build graph and apply postorder sort
|
|
846
|
+
sortNodes({
|
|
847
|
+
nodes: initialSort,
|
|
848
|
+
edges
|
|
849
|
+
})
|
|
850
|
+
)
|
|
851
|
+
);
|
|
852
|
+
const sortedEdges = /* @__PURE__ */ new Set([
|
|
853
|
+
...nodes.flatMap((n) => n.children.length === 0 ? n.outEdges.flatMap((id) => edgesMap.get(id) ?? []) : []),
|
|
854
|
+
...edges
|
|
855
|
+
]);
|
|
856
|
+
const autoLayoutRule = this.view.rules.findLast(core.isViewRuleAutoLayout);
|
|
857
|
+
const elementNotations = buildElementNotations(nodes);
|
|
858
|
+
return calcViewLayoutHash({
|
|
859
|
+
...view,
|
|
860
|
+
autoLayout: autoLayoutRule?.autoLayout ?? "TB",
|
|
861
|
+
nodes: remeda.map(nodes, remeda.omit(["notation"])),
|
|
862
|
+
edges: applyCustomRelationProperties(rules, nodes, sortedEdges),
|
|
863
|
+
...elementNotations.length > 0 && {
|
|
864
|
+
notation: {
|
|
865
|
+
elements: elementNotations
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
get root() {
|
|
871
|
+
return core.isScopedElementView(this.view) ? this.view.viewOf : null;
|
|
872
|
+
}
|
|
873
|
+
computeEdges() {
|
|
874
|
+
return this.ctxEdges.map((e) => {
|
|
875
|
+
core.invariant(remeda.hasAtLeast(e.relations, 1), "Edge must have at least one relation");
|
|
876
|
+
const relations = remeda.sort(e.relations, core.compareRelations);
|
|
877
|
+
const source = e.source.id;
|
|
878
|
+
const target = e.target.id;
|
|
879
|
+
const edge = {
|
|
880
|
+
id: `${source}:${target}`,
|
|
881
|
+
parent: core.commonAncestor(source, target),
|
|
882
|
+
source,
|
|
883
|
+
target,
|
|
884
|
+
label: null,
|
|
885
|
+
relations: relations.map((r) => r.id)
|
|
886
|
+
};
|
|
887
|
+
let relation;
|
|
888
|
+
relation = relations.length === 1 ? relations[0] : relations.find((r) => r.source === source && r.target === target);
|
|
889
|
+
if (!relation) {
|
|
890
|
+
const allprops = remeda.pipe(
|
|
891
|
+
relations,
|
|
892
|
+
remeda.reduce((acc, r) => {
|
|
893
|
+
if (remeda.isTruthy(r.title) && !acc.title.includes(r.title)) {
|
|
894
|
+
acc.title.push(r.title);
|
|
895
|
+
}
|
|
896
|
+
if (remeda.isTruthy(r.description) && !acc.description.includes(r.description)) {
|
|
897
|
+
acc.description.push(r.description);
|
|
898
|
+
}
|
|
899
|
+
if (remeda.isTruthy(r.technology) && !acc.technology.includes(r.technology)) {
|
|
900
|
+
acc.technology.push(r.technology);
|
|
901
|
+
}
|
|
902
|
+
if (remeda.isTruthy(r.kind) && !acc.kind.includes(r.kind)) {
|
|
903
|
+
acc.kind.push(r.kind);
|
|
904
|
+
}
|
|
905
|
+
if (remeda.isTruthy(r.color) && !acc.color.includes(r.color)) {
|
|
906
|
+
acc.color.push(r.color);
|
|
907
|
+
}
|
|
908
|
+
if (remeda.isTruthy(r.line) && !acc.line.includes(r.line)) {
|
|
909
|
+
acc.line.push(r.line);
|
|
910
|
+
}
|
|
911
|
+
if (remeda.isTruthy(r.head) && !acc.head.includes(r.head)) {
|
|
912
|
+
acc.head.push(r.head);
|
|
913
|
+
}
|
|
914
|
+
if (remeda.isTruthy(r.tail) && !acc.tail.includes(r.tail)) {
|
|
915
|
+
acc.tail.push(r.tail);
|
|
916
|
+
}
|
|
917
|
+
if (remeda.isTruthy(r.navigateTo) && !acc.navigateTo.includes(r.navigateTo)) {
|
|
918
|
+
acc.navigateTo.push(r.navigateTo);
|
|
919
|
+
}
|
|
920
|
+
return acc;
|
|
921
|
+
}, {
|
|
922
|
+
title: [],
|
|
923
|
+
description: [],
|
|
924
|
+
technology: [],
|
|
925
|
+
kind: [],
|
|
926
|
+
head: [],
|
|
927
|
+
tail: [],
|
|
928
|
+
color: [],
|
|
929
|
+
line: [],
|
|
930
|
+
navigateTo: []
|
|
931
|
+
})
|
|
932
|
+
);
|
|
933
|
+
relation = {
|
|
934
|
+
title: remeda.only(allprops.title) ?? "[...]",
|
|
935
|
+
description: remeda.only(allprops.description),
|
|
936
|
+
technology: remeda.only(allprops.technology),
|
|
937
|
+
kind: remeda.only(allprops.kind),
|
|
938
|
+
head: remeda.only(allprops.head),
|
|
939
|
+
tail: remeda.only(allprops.tail),
|
|
940
|
+
color: remeda.only(allprops.color),
|
|
941
|
+
line: remeda.only(allprops.line),
|
|
942
|
+
navigateTo: remeda.only(allprops.navigateTo)
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
const tags = remeda.unique(remeda.flatMap(relations, (r) => r.tags ?? []));
|
|
946
|
+
return Object.assign(
|
|
947
|
+
edge,
|
|
948
|
+
this.getEdgeLabel(relation),
|
|
949
|
+
remeda.isTruthy(relation.description) && { description: relation.description },
|
|
950
|
+
remeda.isTruthy(relation.technology) && { technology: relation.technology },
|
|
951
|
+
remeda.isTruthy(relation.kind) && { kind: relation.kind },
|
|
952
|
+
relation.color && { color: relation.color },
|
|
953
|
+
relation.line && { line: relation.line },
|
|
954
|
+
relation.head && { head: relation.head },
|
|
955
|
+
relation.tail && { tail: relation.tail },
|
|
956
|
+
relation.navigateTo && { navigateTo: relation.navigateTo },
|
|
957
|
+
remeda.hasAtLeast(tags, 1) && { tags }
|
|
958
|
+
);
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
get includedElements() {
|
|
962
|
+
return /* @__PURE__ */ new Set([
|
|
963
|
+
...this.explicits,
|
|
964
|
+
...this.ctxEdges.flatMap((e) => [e.source, e.target])
|
|
965
|
+
]);
|
|
966
|
+
}
|
|
967
|
+
get resolvedElements() {
|
|
968
|
+
return /* @__PURE__ */ new Set([
|
|
969
|
+
...this.explicits,
|
|
970
|
+
...this.implicits,
|
|
971
|
+
...this.ctxEdges.flatMap((e) => [e.source, e.target])
|
|
972
|
+
]);
|
|
973
|
+
}
|
|
974
|
+
get edges() {
|
|
975
|
+
return this.ctxEdges;
|
|
976
|
+
}
|
|
977
|
+
addEdges(edges) {
|
|
978
|
+
for (const e of edges) {
|
|
979
|
+
if (!remeda.hasAtLeast(e.relations, 1)) {
|
|
980
|
+
continue;
|
|
981
|
+
}
|
|
982
|
+
const existing = this.ctxEdges.find(
|
|
983
|
+
(_e) => _e.source.id === e.source.id && _e.target.id === e.target.id
|
|
984
|
+
);
|
|
985
|
+
if (existing) {
|
|
986
|
+
existing.relations = remeda.unique([...existing.relations, ...e.relations]);
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
this.ctxEdges.push(e);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Add element explicitly
|
|
994
|
+
* Included even without relationships
|
|
995
|
+
*/
|
|
996
|
+
addElement(...el) {
|
|
997
|
+
for (const r of el) {
|
|
998
|
+
this.explicits.add(r);
|
|
999
|
+
this.implicits.add(r);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Add element implicitly
|
|
1004
|
+
* Included if only has relationships
|
|
1005
|
+
*/
|
|
1006
|
+
addImplicit(...el) {
|
|
1007
|
+
for (const r of el) {
|
|
1008
|
+
this.implicits.add(r);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
excludeElement(...excludes) {
|
|
1012
|
+
for (const el of excludes) {
|
|
1013
|
+
this.ctxEdges = this.ctxEdges.filter((e) => e.source.id !== el.id && e.target.id !== el.id);
|
|
1014
|
+
this.explicits.delete(el);
|
|
1015
|
+
this.implicits.delete(el);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
// protected excludeImplicit(...excludes: Element[]) {
|
|
1019
|
+
// for (const el of excludes) {
|
|
1020
|
+
// this.implicits.delete(el)
|
|
1021
|
+
// }
|
|
1022
|
+
// }
|
|
1023
|
+
excludeRelation(...relations) {
|
|
1024
|
+
if (relations.length === 0) {
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
const excludedImplicits = /* @__PURE__ */ new Set();
|
|
1028
|
+
const ctxEdges = remeda.pipe(
|
|
1029
|
+
this.ctxEdges,
|
|
1030
|
+
remeda.map((edge) => {
|
|
1031
|
+
const edgerelations = edge.relations.filter((r) => !relations.includes(r));
|
|
1032
|
+
if (edgerelations.length === 0) {
|
|
1033
|
+
excludedImplicits.add(edge.source);
|
|
1034
|
+
excludedImplicits.add(edge.target);
|
|
1035
|
+
return null;
|
|
1036
|
+
}
|
|
1037
|
+
if (edgerelations.length !== edge.relations.length) {
|
|
1038
|
+
return {
|
|
1039
|
+
...edge,
|
|
1040
|
+
relations: edgerelations
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
return edge;
|
|
1044
|
+
}),
|
|
1045
|
+
remeda.filter(remeda.isNonNull)
|
|
1046
|
+
);
|
|
1047
|
+
if (excludedImplicits.size === 0) {
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
this.ctxEdges = ctxEdges;
|
|
1051
|
+
const remaining = this.includedElements;
|
|
1052
|
+
if (remaining.size === 0) {
|
|
1053
|
+
this.implicits.clear();
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
for (const el of excludedImplicits) {
|
|
1057
|
+
if (!remaining.has(el)) {
|
|
1058
|
+
this.implicits.delete(el);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
reset() {
|
|
1063
|
+
this.explicits.clear();
|
|
1064
|
+
this.implicits.clear();
|
|
1065
|
+
this.ctxEdges = [];
|
|
1066
|
+
}
|
|
1067
|
+
// Filter out edges if there are edges between descendants
|
|
1068
|
+
// i.e. remove implicit edges, derived from childs
|
|
1069
|
+
removeRedundantImplicitEdges() {
|
|
1070
|
+
const processedRelations = /* @__PURE__ */ new Set();
|
|
1071
|
+
const excludeProcessed = (relations) => relations.reduce((acc, rel) => {
|
|
1072
|
+
if (!processedRelations.has(rel)) {
|
|
1073
|
+
acc.push(rel);
|
|
1074
|
+
processedRelations.add(rel);
|
|
1075
|
+
}
|
|
1076
|
+
return acc;
|
|
1077
|
+
}, []);
|
|
1078
|
+
const isNestedEdgeOf = (parent) => {
|
|
1079
|
+
const { source, target } = parent;
|
|
1080
|
+
return (edge) => {
|
|
1081
|
+
const isSameSource = source.id === edge.source.id;
|
|
1082
|
+
const isSameTarget = target.id === edge.target.id;
|
|
1083
|
+
if (isSameSource && isSameTarget) {
|
|
1084
|
+
return true;
|
|
1085
|
+
}
|
|
1086
|
+
const isSourceNested = core.isAncestor(source.id, edge.source.id);
|
|
1087
|
+
const isTargetNested = core.isAncestor(target.id, edge.target.id);
|
|
1088
|
+
return isSourceNested && isTargetNested || isSameSource && isTargetNested || isSameTarget && isSourceNested;
|
|
1089
|
+
};
|
|
1090
|
+
};
|
|
1091
|
+
const edges = [...this.ctxEdges].sort(compareEdges).reverse();
|
|
1092
|
+
this.ctxEdges = edges.reduce((acc, e) => {
|
|
1093
|
+
const relations = excludeProcessed(e.relations);
|
|
1094
|
+
if (relations.length === 0) {
|
|
1095
|
+
return acc;
|
|
1096
|
+
}
|
|
1097
|
+
if (acc.length > 0 && acc.some(isNestedEdgeOf(e))) {
|
|
1098
|
+
return acc;
|
|
1099
|
+
}
|
|
1100
|
+
acc.push({
|
|
1101
|
+
source: e.source,
|
|
1102
|
+
target: e.target,
|
|
1103
|
+
relations
|
|
1104
|
+
});
|
|
1105
|
+
return acc;
|
|
1106
|
+
}, []);
|
|
1107
|
+
}
|
|
1108
|
+
processPredicates(viewRules) {
|
|
1109
|
+
for (const rule of viewRules) {
|
|
1110
|
+
const isInclude = "include" in rule;
|
|
1111
|
+
const exprs = rule.include ?? rule.exclude;
|
|
1112
|
+
for (const expr of exprs) {
|
|
1113
|
+
if (core.Expr.isElementPredicateExpr(expr)) {
|
|
1114
|
+
this.processElementPredicate(expr, isInclude);
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
if (core.Expr.isRelationPredicateExpr(expr)) {
|
|
1118
|
+
this.processRelationPredicate(expr, isInclude);
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
core.nonexhaustive(expr);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
return this;
|
|
1125
|
+
}
|
|
1126
|
+
processElementPredicate(expr, isInclude, where) {
|
|
1127
|
+
if (core.Expr.isCustomElement(expr)) {
|
|
1128
|
+
if (isInclude) {
|
|
1129
|
+
this.processElementPredicate(expr.custom.expr, isInclude);
|
|
1130
|
+
}
|
|
1131
|
+
return this;
|
|
1132
|
+
}
|
|
1133
|
+
if (core.Expr.isElementWhere(expr)) {
|
|
1134
|
+
const where2 = core.whereOperatorAsPredicate(expr.where.condition);
|
|
1135
|
+
this.processElementPredicate(expr.where.expr, isInclude, where2);
|
|
1136
|
+
return this;
|
|
1137
|
+
}
|
|
1138
|
+
if (core.Expr.isExpandedElementExpr(expr)) {
|
|
1139
|
+
isInclude ? includeExpandedElementExpr.call(this, expr, where) : excludeExpandedElementExpr.call(this, expr, where);
|
|
1140
|
+
return this;
|
|
1141
|
+
}
|
|
1142
|
+
if (core.Expr.isElementKindExpr(expr) || core.Expr.isElementTagExpr(expr)) {
|
|
1143
|
+
isInclude ? includeElementKindOrTag.call(this, expr, where) : excludeElementKindOrTag.call(this, expr, where);
|
|
1144
|
+
return this;
|
|
1145
|
+
}
|
|
1146
|
+
if (core.Expr.isElementRef(expr)) {
|
|
1147
|
+
isInclude ? includeElementRef.call(this, expr, where) : excludeElementRef.call(this, expr, where);
|
|
1148
|
+
return this;
|
|
1149
|
+
}
|
|
1150
|
+
if (core.Expr.isWildcard(expr)) {
|
|
1151
|
+
isInclude ? includeWildcardRef.call(this, expr, where) : excludeWildcardRef.call(this, expr, where);
|
|
1152
|
+
return this;
|
|
1153
|
+
}
|
|
1154
|
+
core.nonexhaustive(expr);
|
|
1155
|
+
}
|
|
1156
|
+
processRelationPredicate(expr, isInclude, where) {
|
|
1157
|
+
if (core.Expr.isCustomRelationExpr(expr)) {
|
|
1158
|
+
if (isInclude) {
|
|
1159
|
+
this.processRelationPredicate(expr.customRelation.relation, isInclude);
|
|
1160
|
+
}
|
|
1161
|
+
return this;
|
|
1162
|
+
}
|
|
1163
|
+
if (core.Expr.isRelationWhere(expr)) {
|
|
1164
|
+
const where2 = core.whereOperatorAsPredicate(expr.where.condition);
|
|
1165
|
+
this.processRelationPredicate(expr.where.expr, isInclude, where2);
|
|
1166
|
+
return this;
|
|
1167
|
+
}
|
|
1168
|
+
if (core.Expr.isIncoming(expr)) {
|
|
1169
|
+
isInclude ? includeIncomingExpr.call(this, expr, where) : excludeIncomingExpr.call(this, expr, where);
|
|
1170
|
+
return this;
|
|
1171
|
+
}
|
|
1172
|
+
if (core.Expr.isOutgoing(expr)) {
|
|
1173
|
+
isInclude ? includeOutgoingExpr.call(this, expr, where) : excludeOutgoingExpr.call(this, expr, where);
|
|
1174
|
+
return this;
|
|
1175
|
+
}
|
|
1176
|
+
if (core.Expr.isInOut(expr)) {
|
|
1177
|
+
isInclude ? includeInOutExpr.call(this, expr, where) : excludeInOutExpr.call(this, expr, where);
|
|
1178
|
+
return this;
|
|
1179
|
+
}
|
|
1180
|
+
if (core.Expr.isRelation(expr)) {
|
|
1181
|
+
isInclude ? includeRelationExpr.call(this, expr, where) : excludeRelationExpr.call(this, expr, where);
|
|
1182
|
+
return this;
|
|
1183
|
+
}
|
|
1184
|
+
core.nonexhaustive(expr);
|
|
1185
|
+
}
|
|
1186
|
+
getEdgeLabel(relation) {
|
|
1187
|
+
const labelParts = [];
|
|
1188
|
+
if (remeda.isTruthy(relation.title)) {
|
|
1189
|
+
labelParts.push(relation.title);
|
|
1190
|
+
}
|
|
1191
|
+
if (remeda.isTruthy(relation.technology)) {
|
|
1192
|
+
labelParts.push(`[${relation.technology}]`);
|
|
1193
|
+
}
|
|
1194
|
+
return labelParts.length > 0 ? { label: labelParts.join("\n") } : {};
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
function computeElementView(view, graph) {
|
|
1199
|
+
return ComputeCtx.elementView(view, graph);
|
|
1200
|
+
}
|
|
1201
|
+
function computeView(view, graph) {
|
|
1202
|
+
try {
|
|
1203
|
+
return {
|
|
1204
|
+
isSuccess: true,
|
|
1205
|
+
view: computeElementView(view, graph)
|
|
1206
|
+
};
|
|
1207
|
+
} catch (e) {
|
|
1208
|
+
return {
|
|
1209
|
+
isSuccess: false,
|
|
1210
|
+
error: e instanceof Error ? e : new Error(`Unknown error: ${e}`),
|
|
1211
|
+
view: void 0
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
class DynamicViewComputeCtx {
|
|
1217
|
+
constructor(view, graph) {
|
|
1218
|
+
this.view = view;
|
|
1219
|
+
this.graph = graph;
|
|
1220
|
+
}
|
|
1221
|
+
// Intermediate state
|
|
1222
|
+
explicits = /* @__PURE__ */ new Set();
|
|
1223
|
+
steps = [];
|
|
1224
|
+
static compute(view, graph) {
|
|
1225
|
+
return new DynamicViewComputeCtx(view, graph).compute();
|
|
1226
|
+
}
|
|
1227
|
+
addStep({
|
|
1228
|
+
source: stepSource,
|
|
1229
|
+
target: stepTarget,
|
|
1230
|
+
title: stepTitle,
|
|
1231
|
+
isBackward,
|
|
1232
|
+
navigateTo: stepNavigateTo,
|
|
1233
|
+
...step
|
|
1234
|
+
}, index, parent) {
|
|
1235
|
+
const id = parent ? core.StepEdgeId(parent, index) : core.StepEdgeId(index);
|
|
1236
|
+
const source = this.graph.element(stepSource);
|
|
1237
|
+
const target = this.graph.element(stepTarget);
|
|
1238
|
+
this.explicits.add(source);
|
|
1239
|
+
this.explicits.add(target);
|
|
1240
|
+
const {
|
|
1241
|
+
title,
|
|
1242
|
+
relations,
|
|
1243
|
+
tags,
|
|
1244
|
+
navigateTo: derivedNavigateTo
|
|
1245
|
+
} = this.findRelations(source, target);
|
|
1246
|
+
const navigateTo = remeda.isTruthy(stepNavigateTo) && stepNavigateTo !== this.view.id ? stepNavigateTo : derivedNavigateTo;
|
|
1247
|
+
this.steps.push({
|
|
1248
|
+
id,
|
|
1249
|
+
...step,
|
|
1250
|
+
source,
|
|
1251
|
+
target,
|
|
1252
|
+
title: stepTitle ?? title,
|
|
1253
|
+
relations: relations ?? [],
|
|
1254
|
+
isBackward: isBackward ?? false,
|
|
1255
|
+
...navigateTo ? { navigateTo } : {},
|
|
1256
|
+
...tags ? { tags } : {}
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
compute() {
|
|
1260
|
+
const {
|
|
1261
|
+
docUri: _docUri,
|
|
1262
|
+
// exclude docUri
|
|
1263
|
+
rules,
|
|
1264
|
+
steps: viewSteps,
|
|
1265
|
+
...view
|
|
1266
|
+
} = this.view;
|
|
1267
|
+
let stepNum = 1;
|
|
1268
|
+
for (const step of viewSteps) {
|
|
1269
|
+
if (core.isDynamicViewParallelSteps(step)) {
|
|
1270
|
+
if (step.__parallel.length === 0) {
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1273
|
+
if (step.__parallel.length === 1) {
|
|
1274
|
+
this.addStep(step.__parallel[0], stepNum);
|
|
1275
|
+
} else {
|
|
1276
|
+
step.__parallel.forEach((s, i) => this.addStep(s, i + 1, stepNum));
|
|
1277
|
+
}
|
|
1278
|
+
} else {
|
|
1279
|
+
this.addStep(step, stepNum);
|
|
1280
|
+
}
|
|
1281
|
+
stepNum++;
|
|
1282
|
+
}
|
|
1283
|
+
for (const rule of rules) {
|
|
1284
|
+
if (core.isDynamicViewIncludeRule(rule)) {
|
|
1285
|
+
for (const expr of rule.include) {
|
|
1286
|
+
const predicate = elementExprToPredicate(expr);
|
|
1287
|
+
this.graph.elements.filter(predicate).forEach((e) => this.explicits.add(e));
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
const elements = [...this.explicits];
|
|
1292
|
+
const nodesMap = buildComputeNodes(elements);
|
|
1293
|
+
const edges = this.steps.map(({ source, target, relations, title, isBackward, ...step }) => {
|
|
1294
|
+
const sourceNode = core.nonNullable(nodesMap.get(source.id), `Source node ${source.id} not found`);
|
|
1295
|
+
const targetNode = core.nonNullable(nodesMap.get(target.id), `Target node ${target.id} not found`);
|
|
1296
|
+
const edge = {
|
|
1297
|
+
parent: core.commonAncestor(source.id, target.id),
|
|
1298
|
+
source: source.id,
|
|
1299
|
+
target: target.id,
|
|
1300
|
+
label: title,
|
|
1301
|
+
relations,
|
|
1302
|
+
color: core.DefaultRelationshipColor,
|
|
1303
|
+
line: core.DefaultLineStyle,
|
|
1304
|
+
head: core.DefaultArrowType,
|
|
1305
|
+
...step
|
|
1306
|
+
};
|
|
1307
|
+
if (isBackward) {
|
|
1308
|
+
edge.dir = "back";
|
|
1309
|
+
}
|
|
1310
|
+
while (edge.parent && !nodesMap.has(edge.parent)) {
|
|
1311
|
+
edge.parent = core.parentFqn(edge.parent);
|
|
1312
|
+
}
|
|
1313
|
+
sourceNode.outEdges.push(edge.id);
|
|
1314
|
+
targetNode.inEdges.push(edge.id);
|
|
1315
|
+
for (const sourceAncestor of core.ancestorsFqn(edge.source)) {
|
|
1316
|
+
if (sourceAncestor === edge.parent) {
|
|
1317
|
+
break;
|
|
1318
|
+
}
|
|
1319
|
+
nodesMap.get(sourceAncestor)?.outEdges.push(edge.id);
|
|
1320
|
+
}
|
|
1321
|
+
for (const targetAncestor of core.ancestorsFqn(edge.target)) {
|
|
1322
|
+
if (targetAncestor === edge.parent) {
|
|
1323
|
+
break;
|
|
1324
|
+
}
|
|
1325
|
+
nodesMap.get(targetAncestor)?.inEdges.push(edge.id);
|
|
1326
|
+
}
|
|
1327
|
+
return edge;
|
|
1328
|
+
});
|
|
1329
|
+
const nodes = applyCustomElementProperties(
|
|
1330
|
+
rules,
|
|
1331
|
+
applyViewRuleStyles(
|
|
1332
|
+
rules,
|
|
1333
|
+
// Keep order of elements
|
|
1334
|
+
elements.map((e) => core.nonNullable(nodesMap.get(e.id)))
|
|
1335
|
+
)
|
|
1336
|
+
);
|
|
1337
|
+
const autoLayoutRule = rules.findLast(core.isViewRuleAutoLayout);
|
|
1338
|
+
const elementNotations = buildElementNotations(nodes);
|
|
1339
|
+
return calcViewLayoutHash({
|
|
1340
|
+
...view,
|
|
1341
|
+
autoLayout: autoLayoutRule?.autoLayout ?? "LR",
|
|
1342
|
+
nodes: remeda.map(nodes, remeda.omit(["notation"])),
|
|
1343
|
+
edges,
|
|
1344
|
+
...elementNotations.length > 0 && {
|
|
1345
|
+
notation: {
|
|
1346
|
+
elements: elementNotations
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
findRelations(source, target) {
|
|
1352
|
+
const relationships = remeda.unique(this.graph.edgesBetween(source, target).flatMap((e) => e.relations));
|
|
1353
|
+
if (relationships.length === 0) {
|
|
1354
|
+
return {
|
|
1355
|
+
title: null,
|
|
1356
|
+
tags: null,
|
|
1357
|
+
relations: null,
|
|
1358
|
+
navigateTo: null
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
const alltags = remeda.pipe(
|
|
1362
|
+
relationships,
|
|
1363
|
+
remeda.flatMap((r) => r.tags),
|
|
1364
|
+
remeda.filter(remeda.isTruthy),
|
|
1365
|
+
remeda.unique()
|
|
1366
|
+
);
|
|
1367
|
+
const tags = remeda.hasAtLeast(alltags, 1) ? alltags : null;
|
|
1368
|
+
const relations = remeda.hasAtLeast(relationships, 1) ? remeda.map(relationships, (r) => r.id) : null;
|
|
1369
|
+
const relation = remeda.only(relationships) || relationships.find((r) => r.source === source.id && r.target === target.id);
|
|
1370
|
+
const title = remeda.isTruthy(relation?.title) ? relation.title : remeda.pipe(
|
|
1371
|
+
relationships,
|
|
1372
|
+
remeda.map((r) => r.title),
|
|
1373
|
+
remeda.filter(remeda.isTruthy),
|
|
1374
|
+
remeda.unique(),
|
|
1375
|
+
remeda.only()
|
|
1376
|
+
);
|
|
1377
|
+
const navigateTo = !!relation?.navigateTo && relation.navigateTo !== this.view.id ? relation.navigateTo : remeda.pipe(
|
|
1378
|
+
relationships,
|
|
1379
|
+
remeda.map((r) => r.navigateTo),
|
|
1380
|
+
remeda.filter(remeda.isTruthy),
|
|
1381
|
+
remeda.filter((v) => v !== this.view.id),
|
|
1382
|
+
remeda.unique(),
|
|
1383
|
+
remeda.only()
|
|
1384
|
+
);
|
|
1385
|
+
return {
|
|
1386
|
+
title: title ?? null,
|
|
1387
|
+
tags,
|
|
1388
|
+
relations,
|
|
1389
|
+
navigateTo: navigateTo ?? null
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
function computeDynamicView(view, graph) {
|
|
1395
|
+
try {
|
|
1396
|
+
return {
|
|
1397
|
+
isSuccess: true,
|
|
1398
|
+
view: DynamicViewComputeCtx.compute(view, graph)
|
|
1399
|
+
};
|
|
1400
|
+
} catch (e) {
|
|
1401
|
+
return {
|
|
1402
|
+
isSuccess: false,
|
|
1403
|
+
error: e instanceof Error ? e : new Error(`Unknown error: ${e}`),
|
|
1404
|
+
view: void 0
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
const RelationsSet = Set;
|
|
1410
|
+
const MapRelations = Map;
|
|
1411
|
+
function intersection(a, b) {
|
|
1412
|
+
if (a.size === 0 || b.size === 0) {
|
|
1413
|
+
return /* @__PURE__ */ new Set();
|
|
1414
|
+
}
|
|
1415
|
+
return new Set([...a].filter((value) => b.has(value)));
|
|
1416
|
+
}
|
|
1417
|
+
class LikeC4ModelGraph {
|
|
1418
|
+
#elements = /* @__PURE__ */ new Map();
|
|
1419
|
+
// Parent element for given FQN
|
|
1420
|
+
#parents = /* @__PURE__ */ new Map();
|
|
1421
|
+
// Children elements for given FQN
|
|
1422
|
+
#children = /* @__PURE__ */ new Map();
|
|
1423
|
+
#rootElements = /* @__PURE__ */ new Set();
|
|
1424
|
+
#relations = /* @__PURE__ */ new Map();
|
|
1425
|
+
// Incoming to an element or its descendants
|
|
1426
|
+
#incoming = new MapRelations();
|
|
1427
|
+
// Outgoing from an element or its descendants
|
|
1428
|
+
#outgoing = new MapRelations();
|
|
1429
|
+
// Relationships inside the element, among descendants
|
|
1430
|
+
#internal = new MapRelations();
|
|
1431
|
+
#cacheAscendingSiblings = /* @__PURE__ */ new Map();
|
|
1432
|
+
constructor({ elements, relations }) {
|
|
1433
|
+
for (const el of Object.values(elements)) {
|
|
1434
|
+
this.addElement(el);
|
|
1435
|
+
}
|
|
1436
|
+
for (const rel of Object.values(relations)) {
|
|
1437
|
+
this.addRelation(rel);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
get rootElements() {
|
|
1441
|
+
return [...this.#rootElements];
|
|
1442
|
+
}
|
|
1443
|
+
get elements() {
|
|
1444
|
+
return [...this.#elements.values()];
|
|
1445
|
+
}
|
|
1446
|
+
element(id) {
|
|
1447
|
+
const el = this.#elements.get(id);
|
|
1448
|
+
core.invariant(el, `Element ${id} not found`);
|
|
1449
|
+
return el;
|
|
1450
|
+
}
|
|
1451
|
+
connectedRelations(id) {
|
|
1452
|
+
return [...this._incomingTo(id), ...this._outgoingFrom(id), ...this._internalOf(id)];
|
|
1453
|
+
}
|
|
1454
|
+
children(id) {
|
|
1455
|
+
return this._childrenOf(id).slice();
|
|
1456
|
+
}
|
|
1457
|
+
// Get children or element itself if no children
|
|
1458
|
+
childrenOrElement(id) {
|
|
1459
|
+
const children = this.children(id);
|
|
1460
|
+
return children.length > 0 ? children : [this.element(id)];
|
|
1461
|
+
}
|
|
1462
|
+
// Get all sibling (i.e. same parent)
|
|
1463
|
+
siblings(element) {
|
|
1464
|
+
const id = remeda.isString(element) ? element : element.id;
|
|
1465
|
+
const parent = core.parentFqn(id);
|
|
1466
|
+
const siblings = parent ? this._childrenOf(parent) : this.rootElements;
|
|
1467
|
+
return siblings.filter((e) => e.id !== id);
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Get all ancestor elements (i.e. parent, parent’s parent, etc.)
|
|
1471
|
+
* (from closest to root)
|
|
1472
|
+
*/
|
|
1473
|
+
ancestors(element) {
|
|
1474
|
+
let id = remeda.isString(element) ? element : element.id;
|
|
1475
|
+
const result = [];
|
|
1476
|
+
let parent;
|
|
1477
|
+
while (parent = this.#parents.get(id)) {
|
|
1478
|
+
result.push(parent);
|
|
1479
|
+
id = parent.id;
|
|
1480
|
+
}
|
|
1481
|
+
return result;
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Resolve siblings of the element and its ancestors
|
|
1485
|
+
* (from closest to root)
|
|
1486
|
+
*/
|
|
1487
|
+
ascendingSiblings(element) {
|
|
1488
|
+
const id = remeda.isString(element) ? element : element.id;
|
|
1489
|
+
let siblings = this.#cacheAscendingSiblings.get(id);
|
|
1490
|
+
if (!siblings) {
|
|
1491
|
+
siblings = [
|
|
1492
|
+
...this.siblings(id),
|
|
1493
|
+
...this.ancestors(id).flatMap((a) => this.siblings(a.id))
|
|
1494
|
+
];
|
|
1495
|
+
this.#cacheAscendingSiblings.set(id, siblings);
|
|
1496
|
+
}
|
|
1497
|
+
return siblings.slice();
|
|
1498
|
+
}
|
|
1499
|
+
/**
|
|
1500
|
+
* Resolve all RelationEdges between element and others (any direction)
|
|
1501
|
+
*/
|
|
1502
|
+
anyEdgesBetween(_element, others) {
|
|
1503
|
+
if (others.length === 0) {
|
|
1504
|
+
return [];
|
|
1505
|
+
}
|
|
1506
|
+
const element = remeda.isString(_element) ? this.element(_element) : _element;
|
|
1507
|
+
const in_element = this._incomingTo(element.id);
|
|
1508
|
+
const element_out = this._outgoingFrom(element.id);
|
|
1509
|
+
if (in_element.size === 0 && element_out.size === 0) {
|
|
1510
|
+
return [];
|
|
1511
|
+
}
|
|
1512
|
+
const result = [];
|
|
1513
|
+
for (const _other of others) {
|
|
1514
|
+
const other = remeda.isString(_other) ? this.element(_other) : _other;
|
|
1515
|
+
if (core.isSameHierarchy(element, other)) {
|
|
1516
|
+
continue;
|
|
1517
|
+
}
|
|
1518
|
+
if (element_out.size > 0) {
|
|
1519
|
+
const outcoming = intersection(this._incomingTo(other.id), element_out);
|
|
1520
|
+
if (outcoming.size > 0) {
|
|
1521
|
+
result.push({
|
|
1522
|
+
source: element,
|
|
1523
|
+
target: other,
|
|
1524
|
+
relations: [...outcoming]
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
if (in_element.size > 0) {
|
|
1529
|
+
const incoming = intersection(this._outgoingFrom(other.id), in_element);
|
|
1530
|
+
if (incoming.size > 0) {
|
|
1531
|
+
result.push({
|
|
1532
|
+
source: other,
|
|
1533
|
+
target: element,
|
|
1534
|
+
relations: [...incoming]
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
return result;
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* Resolve all RelationEdges between elements (any direction)
|
|
1543
|
+
*/
|
|
1544
|
+
edgesWithin(elements) {
|
|
1545
|
+
if (elements.length < 2) {
|
|
1546
|
+
return [];
|
|
1547
|
+
}
|
|
1548
|
+
return elements.reduce((acc, el, index, array) => {
|
|
1549
|
+
if (index === array.length - 1) {
|
|
1550
|
+
return acc;
|
|
1551
|
+
}
|
|
1552
|
+
acc.push(...this.anyEdgesBetween(el, array.slice(index + 1)));
|
|
1553
|
+
return acc;
|
|
1554
|
+
}, []);
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Get directed RelationEdge between source and target if exists
|
|
1558
|
+
*/
|
|
1559
|
+
edgesBetween(_sources, _targets) {
|
|
1560
|
+
const sources = remeda.isArray(_sources) ? _sources : [_sources];
|
|
1561
|
+
const targets = remeda.isArray(_targets) ? _targets : [_targets];
|
|
1562
|
+
if (sources.length === 0 || targets.length === 0) {
|
|
1563
|
+
return [];
|
|
1564
|
+
}
|
|
1565
|
+
const result = [];
|
|
1566
|
+
for (const _source of sources) {
|
|
1567
|
+
const source = remeda.isString(_source) ? this.element(_source) : _source;
|
|
1568
|
+
const outcoming = this._outgoingFrom(source.id);
|
|
1569
|
+
if (outcoming.size === 0) {
|
|
1570
|
+
continue;
|
|
1571
|
+
}
|
|
1572
|
+
for (const _target of targets) {
|
|
1573
|
+
const target = remeda.isString(_target) ? this.element(_target) : _target;
|
|
1574
|
+
if (core.isSameHierarchy(source, target)) {
|
|
1575
|
+
continue;
|
|
1576
|
+
}
|
|
1577
|
+
const incoming = this._incomingTo(target.id);
|
|
1578
|
+
const relations = intersection(outcoming, incoming);
|
|
1579
|
+
if (relations.size > 0) {
|
|
1580
|
+
result.push({
|
|
1581
|
+
source,
|
|
1582
|
+
target,
|
|
1583
|
+
relations: [...relations]
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
return result;
|
|
1589
|
+
}
|
|
1590
|
+
addElement(el) {
|
|
1591
|
+
if (this.#elements.has(el.id)) {
|
|
1592
|
+
throw new Error(`Element ${el.id} already exists`);
|
|
1593
|
+
}
|
|
1594
|
+
this.#elements.set(el.id, el);
|
|
1595
|
+
const parentId = core.parentFqn(el.id);
|
|
1596
|
+
if (parentId) {
|
|
1597
|
+
this.#parents.set(el.id, this.element(parentId));
|
|
1598
|
+
this._childrenOf(parentId).push(el);
|
|
1599
|
+
} else {
|
|
1600
|
+
this.#rootElements.add(el);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
addRelation(rel) {
|
|
1604
|
+
if (this.#relations.has(rel.id)) {
|
|
1605
|
+
throw new Error(`Relation ${rel.id} already exists`);
|
|
1606
|
+
}
|
|
1607
|
+
this.#relations.set(rel.id, rel);
|
|
1608
|
+
this._incomingTo(rel.target).add(rel);
|
|
1609
|
+
this._outgoingFrom(rel.source).add(rel);
|
|
1610
|
+
const relParent = core.commonAncestor(rel.source, rel.target);
|
|
1611
|
+
if (relParent) {
|
|
1612
|
+
for (const ancestor of [relParent, ...core.ancestorsFqn(relParent)]) {
|
|
1613
|
+
this._internalOf(ancestor).add(rel);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
for (const sourceAncestor of core.ancestorsFqn(rel.source)) {
|
|
1617
|
+
if (sourceAncestor === relParent) {
|
|
1618
|
+
break;
|
|
1619
|
+
}
|
|
1620
|
+
this._outgoingFrom(sourceAncestor).add(rel);
|
|
1621
|
+
}
|
|
1622
|
+
for (const targetAncestor of core.ancestorsFqn(rel.target)) {
|
|
1623
|
+
if (targetAncestor === relParent) {
|
|
1624
|
+
break;
|
|
1625
|
+
}
|
|
1626
|
+
this._incomingTo(targetAncestor).add(rel);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
_childrenOf(id) {
|
|
1630
|
+
let children = this.#children.get(id);
|
|
1631
|
+
if (!children) {
|
|
1632
|
+
children = [];
|
|
1633
|
+
this.#children.set(id, children);
|
|
1634
|
+
}
|
|
1635
|
+
return children;
|
|
1636
|
+
}
|
|
1637
|
+
_incomingTo(id) {
|
|
1638
|
+
let incoming = this.#incoming.get(id);
|
|
1639
|
+
if (!incoming) {
|
|
1640
|
+
incoming = new RelationsSet();
|
|
1641
|
+
this.#incoming.set(id, incoming);
|
|
1642
|
+
}
|
|
1643
|
+
return incoming;
|
|
1644
|
+
}
|
|
1645
|
+
_outgoingFrom(id) {
|
|
1646
|
+
let outgoing = this.#outgoing.get(id);
|
|
1647
|
+
if (!outgoing) {
|
|
1648
|
+
outgoing = new RelationsSet();
|
|
1649
|
+
this.#outgoing.set(id, outgoing);
|
|
1650
|
+
}
|
|
1651
|
+
return outgoing;
|
|
1652
|
+
}
|
|
1653
|
+
_internalOf(id) {
|
|
1654
|
+
let internal = this.#internal.get(id);
|
|
1655
|
+
if (!internal) {
|
|
1656
|
+
internal = new RelationsSet();
|
|
1657
|
+
this.#internal.set(id, internal);
|
|
1658
|
+
}
|
|
1659
|
+
return internal;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
exports.LikeC4ModelGraph = LikeC4ModelGraph;
|
|
1664
|
+
exports.computeDynamicView = computeDynamicView;
|
|
1665
|
+
exports.computeElementView = computeElementView;
|
|
1666
|
+
exports.computeView = computeView;
|
|
1667
|
+
exports.findCycles = findCycles;
|
|
1668
|
+
exports.isAcyclic = isAcyclic;
|
|
1669
|
+
exports.postorder = postorder;
|