@likec4/core 0.31.0 → 0.32.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 +13 -2
- package/dist/colors.d.ts +6 -6
- package/dist/colors.js +13 -7
- package/dist/compute-view/compute-element-view.d.ts +1 -1
- package/dist/compute-view/compute-element-view.js +99 -43
- package/dist/compute-view/utils/sortNodes.js +4 -2
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.js +1 -0
- package/dist/errors/invariant.d.ts +1 -0
- package/dist/errors/invariant.js +5 -0
- package/dist/errors/nonexhaustive.d.ts +3 -0
- package/dist/errors/nonexhaustive.js +6 -0
- package/dist/model-index/ModelIndex.js +1 -3
- package/dist/types/_common.d.ts +2 -0
- package/dist/types/computed-view.d.ts +2 -1
- package/dist/types/element.d.ts +2 -1
- package/dist/types/view.d.ts +2 -1
- package/dist/utils/fqn.d.ts +12 -6
- package/dist/utils/fqn.js +5 -8
- package/dist/utils/guards.d.ts +2 -2
- package/dist/utils/guards.js +3 -2
- package/dist/utils/relations.d.ts +11 -6
- package/dist/utils/relations.js +10 -4
- package/package.json +13 -2
package/README.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
-
#
|
|
1
|
+
# LikeC4 Core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[docs](https://likec4.dev/docs/) | [example](https://likec4.dev/examples/bigbank/likec4/)
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @likec4/core
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Package contains:
|
|
10
|
+
|
|
11
|
+
- types
|
|
12
|
+
- view computation
|
|
13
|
+
- utilities and guards
|
|
14
|
+
- errors
|
package/dist/colors.d.ts
CHANGED
|
@@ -55,20 +55,20 @@ export declare const Colors: {
|
|
|
55
55
|
readonly red: {
|
|
56
56
|
readonly fill: "#ef4444";
|
|
57
57
|
readonly stroke: "#dc2626";
|
|
58
|
-
readonly hiContrast: "#
|
|
59
|
-
readonly loContrast: "#
|
|
58
|
+
readonly hiContrast: "#111827";
|
|
59
|
+
readonly loContrast: "#1f2937";
|
|
60
60
|
};
|
|
61
61
|
readonly green: {
|
|
62
62
|
readonly fill: "#16a34a";
|
|
63
63
|
readonly stroke: "#15803d";
|
|
64
|
-
readonly hiContrast: "#
|
|
65
|
-
readonly loContrast: "#
|
|
64
|
+
readonly hiContrast: "#111827";
|
|
65
|
+
readonly loContrast: "#1f2937";
|
|
66
66
|
};
|
|
67
67
|
readonly amber: {
|
|
68
68
|
readonly fill: "#d97706";
|
|
69
69
|
readonly stroke: "#b45309";
|
|
70
|
-
readonly hiContrast: "#
|
|
71
|
-
readonly loContrast: "#
|
|
70
|
+
readonly hiContrast: "#111827";
|
|
71
|
+
readonly loContrast: "#1f2937";
|
|
72
72
|
};
|
|
73
73
|
readonly indigo: {
|
|
74
74
|
readonly fill: "#6366f1";
|
package/dist/colors.js
CHANGED
|
@@ -32,7 +32,7 @@ export const RelationColors = {
|
|
|
32
32
|
// lineColor: colors.neutral[400],
|
|
33
33
|
// labelColor: colors.neutral[300],
|
|
34
34
|
lineColor: '#a3a3a3',
|
|
35
|
-
labelColor: '#d4d4d4'
|
|
35
|
+
labelColor: '#d4d4d4'
|
|
36
36
|
};
|
|
37
37
|
export const Colors = {
|
|
38
38
|
primary: blue,
|
|
@@ -58,8 +58,10 @@ export const Colors = {
|
|
|
58
58
|
// loContrast: colors.red[200],
|
|
59
59
|
fill: '#ef4444',
|
|
60
60
|
stroke: '#dc2626',
|
|
61
|
-
hiContrast: '#fef2f2',
|
|
62
|
-
loContrast: '#fecaca'
|
|
61
|
+
// hiContrast: '#fef2f2',
|
|
62
|
+
// loContrast: '#fecaca',
|
|
63
|
+
hiContrast: '#111827',
|
|
64
|
+
loContrast: '#1f2937' // colors.gray[800],
|
|
63
65
|
},
|
|
64
66
|
green: {
|
|
65
67
|
// fill: colors.green[600],
|
|
@@ -68,8 +70,10 @@ export const Colors = {
|
|
|
68
70
|
// loContrast: colors.green[200],
|
|
69
71
|
fill: '#16a34a',
|
|
70
72
|
stroke: '#15803d',
|
|
71
|
-
hiContrast: '#
|
|
72
|
-
|
|
73
|
+
hiContrast: '#111827',
|
|
74
|
+
// hiContrast: '#f0fdf4',
|
|
75
|
+
loContrast: '#1f2937' // colors.gray[800],
|
|
76
|
+
// loContrast: '#bbf7d0'
|
|
73
77
|
},
|
|
74
78
|
amber: {
|
|
75
79
|
// fill: colors.amber[600],
|
|
@@ -78,8 +82,10 @@ export const Colors = {
|
|
|
78
82
|
// loContrast: colors.amber[200],
|
|
79
83
|
fill: '#d97706',
|
|
80
84
|
stroke: '#b45309',
|
|
81
|
-
hiContrast: '#fffbeb',
|
|
82
|
-
loContrast: '#fde68a'
|
|
85
|
+
// hiContrast: '#fffbeb',
|
|
86
|
+
// loContrast: '#fde68a',
|
|
87
|
+
hiContrast: '#111827',
|
|
88
|
+
loContrast: '#1f2937' // colors.gray[800],
|
|
83
89
|
},
|
|
84
90
|
indigo: {
|
|
85
91
|
// fill: colors.indigo[500],
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { ModelIndex } from '../model-index';
|
|
2
|
-
import { type
|
|
2
|
+
import { type ComputeResult, type ElementView } from '../types';
|
|
3
3
|
export declare function computeElementView<V extends ElementView>(view: V, index: ModelIndex): ComputeResult<V>;
|
|
4
4
|
//# sourceMappingURL=compute-element-view.d.ts.map
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { anyPass,
|
|
2
|
-
import {
|
|
1
|
+
import { anyPass, both, either, filter, isNil, uniq } from 'rambdax';
|
|
2
|
+
import { allPass, find, isDefined } from 'remeda';
|
|
3
|
+
import { invariant, nonexhaustive } from '../errors';
|
|
4
|
+
import { DefaultElementShape, DefaultThemeColor } from '../types';
|
|
3
5
|
import * as Expr from '../types/expression';
|
|
4
6
|
import { isViewRuleAutoLayout, isViewRuleExpression, isViewRuleStyle } from '../types/view';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
+
import { Relations, commonAncestor, compareByFqnHierarchically, failUnexpected, isAncestor, isSameHierarchy, parentFqn } from '../utils';
|
|
8
|
+
import { compareRelations, isAnyInOut, isBetween, isIncoming, isInside, isOutgoing } from '../utils/relations';
|
|
7
9
|
import { EdgeBuilder } from './EdgeBuilder';
|
|
8
|
-
import { sortNodes } from './utils/sortNodes';
|
|
9
10
|
import { ComputeCtx } from './compute-ctx';
|
|
10
|
-
import {
|
|
11
|
-
import { invariant } from '../errors';
|
|
11
|
+
import { sortNodes } from './utils/sortNodes';
|
|
12
12
|
function transformToNodes(elementsIterator) {
|
|
13
13
|
return Array.from(elementsIterator)
|
|
14
14
|
.sort(compareByFqnHierarchically)
|
|
@@ -64,11 +64,14 @@ function applyViewRuleStyles(rules, nodes) {
|
|
|
64
64
|
predicates.push(isDescedants ? n => n.id.startsWith(element + '.') : n => n.id === element);
|
|
65
65
|
continue;
|
|
66
66
|
}
|
|
67
|
-
|
|
67
|
+
failUnexpected(target);
|
|
68
68
|
}
|
|
69
69
|
filter(anyPass(predicates), nodes).forEach(n => {
|
|
70
70
|
n.shape = rule.style.shape ?? n.shape;
|
|
71
71
|
n.color = rule.style.color ?? n.color;
|
|
72
|
+
if (isDefined.strict(rule.style.icon)) {
|
|
73
|
+
n.icon = rule.style.icon;
|
|
74
|
+
}
|
|
72
75
|
});
|
|
73
76
|
}
|
|
74
77
|
return nodes;
|
|
@@ -79,7 +82,9 @@ const includeElementRef = (ctx, expr) => {
|
|
|
79
82
|
: [ctx.index.find(expr.element)];
|
|
80
83
|
const filters = [];
|
|
81
84
|
if (expr.isDescedants) {
|
|
82
|
-
|
|
85
|
+
elements.forEach(child => {
|
|
86
|
+
filters.push(both(Relations.isInside(expr.element), Relations.isAnyInOut(child.id)));
|
|
87
|
+
});
|
|
83
88
|
}
|
|
84
89
|
const ctxElements = uniq([...ctx.elements, ...ctx.implicits]);
|
|
85
90
|
const excludeImplicits = [];
|
|
@@ -147,44 +152,42 @@ const includeWildcardRef = (ctx, _expr) => {
|
|
|
147
152
|
...ctx.index.siblings(root),
|
|
148
153
|
...ctx.index.ancestors(root).flatMap(a => [...ctx.index.siblings(a.id)])
|
|
149
154
|
];
|
|
150
|
-
const
|
|
155
|
+
const inOut = ctx.index.filterRelations(isAnyInOut(root));
|
|
151
156
|
// Neighbors that have relations with root or its children
|
|
152
157
|
const implicits = [];
|
|
153
|
-
const relations = [];
|
|
154
158
|
for (const implicit of allImplicits) {
|
|
155
|
-
|
|
156
|
-
if (relationsWithImplicit.length) {
|
|
159
|
+
if (inOut.some(isAnyInOut(implicit.id))) {
|
|
157
160
|
implicits.push(implicit);
|
|
158
|
-
relations.push(...relationsWithImplicit);
|
|
159
161
|
}
|
|
160
162
|
}
|
|
161
163
|
return ctx.include({
|
|
162
164
|
elements,
|
|
163
|
-
relations: [...ctx.index.filterRelations(isInside(root)), ...
|
|
165
|
+
relations: [...ctx.index.filterRelations(isInside(root)), ...inOut],
|
|
164
166
|
implicits: implicits
|
|
165
167
|
});
|
|
166
168
|
}
|
|
167
169
|
else {
|
|
170
|
+
// Take root elements
|
|
168
171
|
const elements = ctx.index.rootElements();
|
|
169
172
|
if (elements.length <= 1) {
|
|
170
173
|
return new ComputeCtx(ctx.index, ctx.root, new Set(elements));
|
|
171
174
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
// Only relations without common ancenstors, i.e. between hierarchies
|
|
176
|
+
const relations = ctx.index.filterRelations(rel => isNil(commonAncestor(rel.source, rel.target)));
|
|
177
|
+
return ctx.include({
|
|
178
|
+
elements,
|
|
179
|
+
relations
|
|
180
|
+
});
|
|
178
181
|
}
|
|
179
182
|
};
|
|
180
183
|
const excludeWildcardRef = (ctx, _expr) => {
|
|
181
184
|
const { root } = ctx;
|
|
182
185
|
if (root) {
|
|
183
|
-
const relations = [...ctx.relations].filter(
|
|
186
|
+
const relations = [...ctx.relations].filter(either(isInside(root), isAnyInOut(root)));
|
|
184
187
|
return ctx.exclude({
|
|
185
|
-
elements: [...ctx.elements].filter(e => isAncestor(root, e.id)),
|
|
188
|
+
elements: [...ctx.elements].filter(e => e.id === root || isAncestor(root, e.id)),
|
|
186
189
|
relations,
|
|
187
|
-
implicits: [...ctx.implicits].filter(
|
|
190
|
+
implicits: [...ctx.implicits].filter(e => relations.some(isAnyInOut(e.id)))
|
|
188
191
|
});
|
|
189
192
|
}
|
|
190
193
|
else {
|
|
@@ -232,10 +235,16 @@ const includeIncomingExpr = (ctx, expr) => {
|
|
|
232
235
|
if (elements.length === 0) {
|
|
233
236
|
return ctx;
|
|
234
237
|
}
|
|
238
|
+
// '-> element.*' should include only "outside" relations.
|
|
239
|
+
// From the outside to the element descendants,
|
|
240
|
+
let allrelations = ctx.index.relations;
|
|
241
|
+
if (Expr.isElementRef(expr.incoming)) {
|
|
242
|
+
allrelations = allrelations.filter(isIncoming(expr.incoming.element));
|
|
243
|
+
}
|
|
235
244
|
const implicits = [];
|
|
236
245
|
const relations = [];
|
|
237
246
|
for (const el of elements) {
|
|
238
|
-
const elRelations =
|
|
247
|
+
const elRelations = allrelations.filter(isIncoming(el.id));
|
|
239
248
|
if (elRelations.length > 0) {
|
|
240
249
|
relations.push(...elRelations);
|
|
241
250
|
implicits.push(el);
|
|
@@ -271,10 +280,16 @@ const includeOutgoingExpr = (ctx, expr) => {
|
|
|
271
280
|
if (elements.length === 0) {
|
|
272
281
|
return ctx;
|
|
273
282
|
}
|
|
283
|
+
// 'element.* -> ' should include only "outside" relations.
|
|
284
|
+
// From element descendants to outside of 'element.*'
|
|
285
|
+
let allrelations = ctx.index.relations;
|
|
286
|
+
if (Expr.isElementRef(expr.outgoing)) {
|
|
287
|
+
allrelations = allrelations.filter(isOutgoing(expr.outgoing.element));
|
|
288
|
+
}
|
|
274
289
|
const implicits = [];
|
|
275
290
|
const relations = [];
|
|
276
291
|
for (const el of elements) {
|
|
277
|
-
const elRelations =
|
|
292
|
+
const elRelations = allrelations.filter(isOutgoing(el.id));
|
|
278
293
|
if (elRelations.length > 0) {
|
|
279
294
|
relations.push(...elRelations);
|
|
280
295
|
implicits.push(el);
|
|
@@ -310,10 +325,15 @@ const includeInOutExpr = (ctx, expr) => {
|
|
|
310
325
|
if (targets.length === 0) {
|
|
311
326
|
return ctx;
|
|
312
327
|
}
|
|
328
|
+
// '-> element.* -> ' should include only "outside" relations.
|
|
329
|
+
let allrelations = ctx.index.relations;
|
|
330
|
+
if (Expr.isElementRef(expr.inout)) {
|
|
331
|
+
allrelations = allrelations.filter(isAnyInOut(expr.inout.element));
|
|
332
|
+
}
|
|
313
333
|
const implicits = [];
|
|
314
334
|
const relations = [];
|
|
315
335
|
for (const target of targets) {
|
|
316
|
-
const foundRelations =
|
|
336
|
+
const foundRelations = allrelations.filter(isAnyInOut(target.id));
|
|
317
337
|
if (foundRelations.length > 0) {
|
|
318
338
|
relations.push(...foundRelations);
|
|
319
339
|
implicits.push(target);
|
|
@@ -427,28 +447,64 @@ export function computeElementView(view, index) {
|
|
|
427
447
|
ctx = isInclude ? includeRelationExpr(ctx, expr) : excludeRelationExpr(ctx, expr);
|
|
428
448
|
continue;
|
|
429
449
|
}
|
|
430
|
-
|
|
450
|
+
nonexhaustive(expr);
|
|
431
451
|
}
|
|
432
452
|
}
|
|
433
|
-
|
|
453
|
+
// All "predicated" elements (including implicit ones)
|
|
454
|
+
const allElements = [...ctx.elements, ...ctx.implicits].sort(compareByFqnHierarchically).reverse();
|
|
455
|
+
// Filtered "allElements", only with relations
|
|
456
|
+
const elementsWithRelations = new Set();
|
|
434
457
|
const edgeBuilder = new EdgeBuilder();
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
458
|
+
const anscestorOf = (id) => (e) => e.id === id || isAncestor(e.id, id);
|
|
459
|
+
// Step 1: Add explicit relations
|
|
460
|
+
const sortedRelations = [...ctx.relations].sort(compareRelations);
|
|
461
|
+
for (const rel of sortedRelations) {
|
|
462
|
+
const source = find(allElements, anscestorOf(rel.source));
|
|
463
|
+
if (!source) {
|
|
438
464
|
continue;
|
|
439
465
|
}
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const [rel] = resolvedRelations.splice(idx, 1);
|
|
444
|
-
if (rel) {
|
|
445
|
-
elements.add(source);
|
|
446
|
-
elements.add(target);
|
|
447
|
-
edgeBuilder.add(source.id, target.id, rel);
|
|
448
|
-
}
|
|
449
|
-
idx = resolvedRelations.findIndex(findPredicate);
|
|
466
|
+
const target = find(allElements, allPass([anscestorOf(rel.target), e => !isSameHierarchy(e, source)]));
|
|
467
|
+
if (!target) {
|
|
468
|
+
continue;
|
|
450
469
|
}
|
|
451
|
-
|
|
470
|
+
elementsWithRelations.add(source);
|
|
471
|
+
elementsWithRelations.add(target);
|
|
472
|
+
edgeBuilder.add(source.id, target.id, rel);
|
|
473
|
+
}
|
|
474
|
+
const elements = new Set([...elementsWithRelations, ...ctx.elements]);
|
|
475
|
+
// Step 2: Add implicit relations
|
|
476
|
+
// if (relationsWithImplicits.length > 0) {
|
|
477
|
+
// elmnts = [...ctx.elements, ...ctx.implicits].sort(compareByFqnHierarchically).reverse()
|
|
478
|
+
// for (const rel of relationsWithImplicits) {
|
|
479
|
+
// const source = find(elmnts, anscestorOf(rel.source))
|
|
480
|
+
// if (!source) {
|
|
481
|
+
// continue
|
|
482
|
+
// }
|
|
483
|
+
// const target = find(elmnts, anscestorOf(rel.target))
|
|
484
|
+
// if (!target || isSameHierarchy(source, target)) {
|
|
485
|
+
// continue
|
|
486
|
+
// }
|
|
487
|
+
// elements.add(source)
|
|
488
|
+
// elements.add(target)
|
|
489
|
+
// edgeBuilder.add(source.id, target.id, rel)
|
|
490
|
+
// }
|
|
491
|
+
// }
|
|
492
|
+
// for (const [source, target] of anyPossibleRelations([...elements, ...ctx.implicits])) {
|
|
493
|
+
// if (!elements.has(source) && !elements.has(target)) {
|
|
494
|
+
// continue
|
|
495
|
+
// }
|
|
496
|
+
// const findPredicate = Relations.isBetween(source.id, target.id)
|
|
497
|
+
// let idx = resolvedRelations.findIndex(findPredicate)
|
|
498
|
+
// while (idx >= 0) {
|
|
499
|
+
// const [rel] = resolvedRelations.splice(idx, 1)
|
|
500
|
+
// if (rel) {
|
|
501
|
+
// elements.add(source)
|
|
502
|
+
// elements.add(target)
|
|
503
|
+
// edgeBuilder.add(source.id, target.id, rel)
|
|
504
|
+
// }
|
|
505
|
+
// idx = resolvedRelations.findIndex(findPredicate)
|
|
506
|
+
// }
|
|
507
|
+
// }
|
|
452
508
|
const nodesreg = transformToNodes(elements);
|
|
453
509
|
const edges = edgeBuilder.build().map(edge => {
|
|
454
510
|
while (edge.parent) {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { isSameHierarchy } from '../../utils';
|
|
1
2
|
import { Graph, alg } from '@dagrejs/graphlib';
|
|
3
|
+
import { nonNullable } from '../../errors/invariant';
|
|
2
4
|
export function sortNodes(_nodes, edges) {
|
|
3
5
|
const g = new Graph({
|
|
4
6
|
compound: true,
|
|
@@ -17,7 +19,7 @@ export function sortNodes(_nodes, edges) {
|
|
|
17
19
|
const source = _nodes.get(edge.source);
|
|
18
20
|
let target = _nodes.get(edge.target);
|
|
19
21
|
while (source && target) {
|
|
20
|
-
if (!g.hasEdge(source.id, target.id)) {
|
|
22
|
+
if (!isSameHierarchy(source, target) && !g.hasEdge(source.id, target.id)) {
|
|
21
23
|
g.setEdge(source.id, target.id);
|
|
22
24
|
// console.log(`${source.id} -> ${target.id}`)
|
|
23
25
|
if (!alg.isAcyclic(g)) {
|
|
@@ -34,7 +36,7 @@ export function sortNodes(_nodes, edges) {
|
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
const sorted = alg.topsort(g);
|
|
37
|
-
const nodes = sorted.map(id => _nodes.get(id));
|
|
39
|
+
const nodes = sorted.map(id => nonNullable(_nodes.get(id)));
|
|
38
40
|
for (const node of nodes) {
|
|
39
41
|
node.children = nodes.flatMap(n => (n.parent === node.id ? n.id : []));
|
|
40
42
|
}
|
package/dist/errors/index.d.ts
CHANGED
package/dist/errors/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export declare const InvariantError: import("modern-errors").ErrorSubclassCore<[], {}, import("modern-errors").CustomClass>;
|
|
2
2
|
export declare function invariant(condition: any, message?: string): asserts condition;
|
|
3
|
+
export declare function nonNullable<T>(value: T): NonNullable<T>;
|
|
3
4
|
//# sourceMappingURL=invariant.d.ts.map
|
package/dist/errors/invariant.js
CHANGED
|
@@ -13,4 +13,9 @@ message) {
|
|
|
13
13
|
}
|
|
14
14
|
throw new InvariantError(message ?? 'Invariant failed');
|
|
15
15
|
}
|
|
16
|
+
// Throws an error if the value is null or undefined
|
|
17
|
+
export function nonNullable(value) {
|
|
18
|
+
invariant(value != null, 'Expected value to exist');
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
16
21
|
//# sourceMappingURL=invariant.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { BaseError } from './_base';
|
|
2
|
+
export const NonExhaustiveError = BaseError.subclass('NonExhaustiveError');
|
|
3
|
+
export function nonexhaustive(arg) {
|
|
4
|
+
throw new NonExhaustiveError(`NonExhaustive value: ${JSON.stringify(arg)}`);
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=nonexhaustive.js.map
|
|
@@ -65,9 +65,7 @@ export default class ModelIndex {
|
|
|
65
65
|
};
|
|
66
66
|
find = (id) => {
|
|
67
67
|
const el = this._elements.get(id);
|
|
68
|
-
|
|
69
|
-
throw new InvalidModelError(`Element not found ${id}`);
|
|
70
|
-
}
|
|
68
|
+
ensureModel(el, `Element not found with id ${id}`);
|
|
71
69
|
return el;
|
|
72
70
|
};
|
|
73
71
|
children = (id) => {
|
package/dist/types/_common.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Opaque } from './opaque';
|
|
|
2
2
|
import type { ElementKind, ElementShape, Fqn, Tag, ThemeColor } from './element';
|
|
3
3
|
import type { RelationID } from './relation';
|
|
4
4
|
import type { ElementView, ViewID, ViewRuleAutoLayout } from './view';
|
|
5
|
-
import type { NonEmptyArray } from './_common';
|
|
5
|
+
import type { IconUrl, NonEmptyArray } from './_common';
|
|
6
6
|
export type NodeId = Fqn;
|
|
7
7
|
export type EdgeId = Opaque<string, 'EdgeId'>;
|
|
8
8
|
export interface ComputedNode {
|
|
@@ -17,6 +17,7 @@ export interface ComputedNode {
|
|
|
17
17
|
children: NodeId[];
|
|
18
18
|
shape: ElementShape;
|
|
19
19
|
color: ThemeColor;
|
|
20
|
+
icon?: IconUrl;
|
|
20
21
|
navigateTo?: ViewID;
|
|
21
22
|
}
|
|
22
23
|
export interface ComputedEdge {
|
package/dist/types/element.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NonEmptyArray } from '
|
|
1
|
+
import type { IconUrl, NonEmptyArray } from './_common';
|
|
2
2
|
import type { Opaque } from './opaque';
|
|
3
3
|
export type Fqn = Opaque<string, 'Fqn'>;
|
|
4
4
|
export declare function AsFqn(name: string, parent?: Fqn | null): Fqn;
|
|
@@ -26,6 +26,7 @@ export interface Element {
|
|
|
26
26
|
readonly technology: string | null;
|
|
27
27
|
readonly tags: NonEmptyArray<Tag> | null;
|
|
28
28
|
readonly links: NonEmptyArray<string> | null;
|
|
29
|
+
readonly icon?: IconUrl;
|
|
29
30
|
readonly shape?: ElementShape;
|
|
30
31
|
readonly color?: ThemeColor;
|
|
31
32
|
}
|
package/dist/types/view.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Opaque } from './opaque';
|
|
2
2
|
import type { ElementShape, Fqn, Tag, ThemeColor } from './element';
|
|
3
3
|
import type { ElementExpression, Expression } from './expression';
|
|
4
|
-
import type { NonEmptyArray } from './_common';
|
|
4
|
+
import type { IconUrl, NonEmptyArray } from './_common';
|
|
5
5
|
export type ViewID = Opaque<string, 'ViewID'>;
|
|
6
6
|
export interface ViewRuleExpression {
|
|
7
7
|
isInclude: boolean;
|
|
@@ -13,6 +13,7 @@ export interface ViewRuleStyle {
|
|
|
13
13
|
style: {
|
|
14
14
|
color?: ThemeColor;
|
|
15
15
|
shape?: ElementShape;
|
|
16
|
+
icon?: IconUrl;
|
|
16
17
|
};
|
|
17
18
|
}
|
|
18
19
|
export declare function isViewRuleStyle(rule: ViewRule): rule is ViewRuleStyle;
|
package/dist/utils/fqn.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import type { Element, Fqn } from '../types';
|
|
2
2
|
export declare function nameFromFqn(fqn: Fqn): string;
|
|
3
|
-
export declare function isAncestor
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
export declare function isAncestor<E extends {
|
|
4
|
+
id: Fqn;
|
|
5
|
+
}>(...args: [ancestor: Fqn, another: Fqn] | [ancestor: E, another: E]): boolean;
|
|
6
|
+
export declare function isSameHierarchy<E extends {
|
|
7
|
+
id: Fqn;
|
|
8
|
+
}>(...args: [one: Fqn, another: Fqn] | [one: E, another: E]): boolean;
|
|
9
|
+
export declare function isDescendantOf<E extends {
|
|
10
|
+
id: Fqn;
|
|
11
|
+
}>(ancestors: E[]): (e: E) => boolean;
|
|
6
12
|
export declare function notDescendantOf(ancestors: Element[]): (e: Element) => boolean;
|
|
7
13
|
export declare function commonAncestor(first: Fqn, second: Fqn): Fqn | null;
|
|
8
14
|
export declare function parentFqn(fqn: Fqn): Fqn | null;
|
|
@@ -16,8 +22,8 @@ export declare function parentFqn(fqn: Fqn): Fqn | null;
|
|
|
16
22
|
* - Positive number if a is deeper than b.
|
|
17
23
|
* - Negative number if b is deeper than a.
|
|
18
24
|
*/
|
|
19
|
-
export declare
|
|
20
|
-
export declare
|
|
25
|
+
export declare function compareFqnHierarchically(a: string, b: string): number;
|
|
26
|
+
export declare function compareByFqnHierarchically<T extends {
|
|
21
27
|
id: Fqn;
|
|
22
|
-
}>(a: T, b: T)
|
|
28
|
+
}>(a: T, b: T): number;
|
|
23
29
|
//# sourceMappingURL=fqn.d.ts.map
|
package/dist/utils/fqn.js
CHANGED
|
@@ -20,10 +20,7 @@ export function isSameHierarchy(...args) {
|
|
|
20
20
|
return one === another || another.startsWith(one + '.') || one.startsWith(another + '.');
|
|
21
21
|
}
|
|
22
22
|
export function isDescendantOf(ancestors) {
|
|
23
|
-
const predicates = ancestors.flatMap(a => [
|
|
24
|
-
(e) => e.id === a.id,
|
|
25
|
-
(e) => isAncestor(a, e)
|
|
26
|
-
]);
|
|
23
|
+
const predicates = ancestors.flatMap(a => [(e) => e.id === a.id, (e) => isAncestor(a, e)]);
|
|
27
24
|
return anyPass(predicates);
|
|
28
25
|
}
|
|
29
26
|
export function notDescendantOf(ancestors) {
|
|
@@ -66,7 +63,7 @@ export function parentFqn(fqn) {
|
|
|
66
63
|
* - Positive number if a is deeper than b.
|
|
67
64
|
* - Negative number if b is deeper than a.
|
|
68
65
|
*/
|
|
69
|
-
export
|
|
66
|
+
export function compareFqnHierarchically(a, b) {
|
|
70
67
|
const depthA = a.split('.').length;
|
|
71
68
|
const depthB = b.split('.').length;
|
|
72
69
|
if (depthA === depthB) {
|
|
@@ -76,8 +73,8 @@ export const compareFqnHierarchically = (a, b) => {
|
|
|
76
73
|
else {
|
|
77
74
|
return depthA - depthB;
|
|
78
75
|
}
|
|
79
|
-
}
|
|
80
|
-
export
|
|
76
|
+
}
|
|
77
|
+
export function compareByFqnHierarchically(a, b) {
|
|
81
78
|
return compareFqnHierarchically(a.id, b.id);
|
|
82
|
-
}
|
|
79
|
+
}
|
|
83
80
|
//# sourceMappingURL=fqn.js.map
|
package/dist/utils/guards.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { NonEmptyArray } from
|
|
1
|
+
import type { NonEmptyArray } from '../types';
|
|
2
2
|
export declare function isString(value: unknown): value is string;
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function failUnexpected(arg: never): never;
|
|
4
4
|
export declare function ignoreNeverInRuntime(arg: never): void;
|
|
5
5
|
export declare function isNonEmptyArray<A>(arr: ArrayLike<A>): arr is NonEmptyArray<A>;
|
|
6
6
|
//# sourceMappingURL=guards.d.ts.map
|
package/dist/utils/guards.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { NonExhaustiveError } from '../errors/nonexhaustive';
|
|
1
2
|
export function isString(value) {
|
|
2
3
|
return typeof value === 'string';
|
|
3
4
|
}
|
|
4
|
-
export function
|
|
5
|
-
throw new
|
|
5
|
+
export function failUnexpected(arg) {
|
|
6
|
+
throw new NonExhaustiveError(`Unexpected value: ${JSON.stringify(arg)}`);
|
|
6
7
|
}
|
|
7
8
|
export function ignoreNeverInRuntime(arg) {
|
|
8
9
|
console.warn(`Unexpected and ignored value: ${JSON.stringify(arg)}`);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type { Fqn
|
|
1
|
+
import type { Fqn } from '../types';
|
|
2
2
|
type Relation = {
|
|
3
|
-
source:
|
|
4
|
-
target:
|
|
3
|
+
source: string;
|
|
4
|
+
target: string;
|
|
5
5
|
};
|
|
6
6
|
type RelationPredicate = (rel: Relation) => boolean;
|
|
7
7
|
export declare const compareRelations: <T extends {
|
|
8
|
-
source:
|
|
9
|
-
target:
|
|
8
|
+
source: string;
|
|
9
|
+
target: string;
|
|
10
10
|
}>(a: T, b: T) => number;
|
|
11
11
|
export declare const isInside: (parent: Fqn) => RelationPredicate;
|
|
12
12
|
export declare const isBetween: (source: Fqn, target: Fqn) => RelationPredicate;
|
|
@@ -14,6 +14,11 @@ export declare const isAnyBetween: (source: Fqn, target: Fqn) => RelationPredica
|
|
|
14
14
|
export declare const isIncoming: (target: Fqn) => RelationPredicate;
|
|
15
15
|
export declare const isOutgoing: (source: Fqn) => RelationPredicate;
|
|
16
16
|
export declare const isAnyInOut: (source: Fqn) => RelationPredicate;
|
|
17
|
-
export declare const hasRelation:
|
|
17
|
+
export declare const hasRelation: <R extends {
|
|
18
|
+
source: Fqn;
|
|
19
|
+
target: Fqn;
|
|
20
|
+
}>(rel: R) => <E extends {
|
|
21
|
+
id: Fqn;
|
|
22
|
+
}>(element: E) => boolean;
|
|
18
23
|
export {};
|
|
19
24
|
//# sourceMappingURL=relations.d.ts.map
|
package/dist/utils/relations.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { anyPass } from 'remeda';
|
|
2
2
|
import { compareFqnHierarchically, isAncestor } from './fqn';
|
|
3
|
+
import { either } from 'rambdax';
|
|
3
4
|
export const compareRelations = (a, b) => {
|
|
4
5
|
return (compareFqnHierarchically(a.source, b.source) || compareFqnHierarchically(a.target, b.target));
|
|
5
6
|
};
|
|
@@ -23,21 +24,26 @@ export const isAnyBetween = (source, target) => {
|
|
|
23
24
|
export const isIncoming = (target) => {
|
|
24
25
|
const targetPrefix = target + '.';
|
|
25
26
|
return (rel) => {
|
|
26
|
-
return (!(rel.source + '.').startsWith(targetPrefix) &&
|
|
27
|
+
return (!(rel.source + '.').startsWith(targetPrefix) &&
|
|
28
|
+
(rel.target === target || (rel.target + '.').startsWith(targetPrefix)));
|
|
27
29
|
};
|
|
28
30
|
};
|
|
29
31
|
export const isOutgoing = (source) => {
|
|
30
32
|
const sourcePrefix = source + '.';
|
|
31
33
|
return (rel) => {
|
|
32
|
-
return ((rel.source === source || (rel.source + '.').startsWith(sourcePrefix)) &&
|
|
34
|
+
return ((rel.source === source || (rel.source + '.').startsWith(sourcePrefix)) &&
|
|
35
|
+
!(rel.target + '.').startsWith(sourcePrefix));
|
|
33
36
|
};
|
|
34
37
|
};
|
|
35
38
|
export const isAnyInOut = (source) => {
|
|
36
|
-
return
|
|
39
|
+
return either(isIncoming(source), isOutgoing(source));
|
|
37
40
|
};
|
|
38
41
|
export const hasRelation = (rel) => {
|
|
39
42
|
return (element) => {
|
|
40
|
-
return
|
|
43
|
+
return (rel.source === element.id ||
|
|
44
|
+
rel.target === element.id ||
|
|
45
|
+
isAncestor(element.id, rel.source) ||
|
|
46
|
+
isAncestor(element.id, rel.target));
|
|
41
47
|
};
|
|
42
48
|
};
|
|
43
49
|
//# sourceMappingURL=relations.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@likec4/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"homepage": "https://likec4.dev",
|
|
6
6
|
"author": "Denis Davydkov <denis@davydkov.com>",
|
|
@@ -44,6 +44,11 @@
|
|
|
44
44
|
"import": "./dist/colors.js",
|
|
45
45
|
"default": "./dist/colors.js"
|
|
46
46
|
},
|
|
47
|
+
"./errors": {
|
|
48
|
+
"types": "./dist/errors/index.d.ts",
|
|
49
|
+
"import": "./dist/errors/index.js",
|
|
50
|
+
"default": "./dist/errors/index.js"
|
|
51
|
+
},
|
|
47
52
|
"./utils": {
|
|
48
53
|
"types": "./dist/utils/index.d.ts",
|
|
49
54
|
"import": "./dist/utils/index.js",
|
|
@@ -77,6 +82,11 @@
|
|
|
77
82
|
"import": "./dist/colors.js",
|
|
78
83
|
"default": "./dist/colors.js"
|
|
79
84
|
},
|
|
85
|
+
"./errors": {
|
|
86
|
+
"types": "./dist/errors/index.d.ts",
|
|
87
|
+
"import": "./dist/errors/index.js",
|
|
88
|
+
"default": "./dist/errors/index.js"
|
|
89
|
+
},
|
|
80
90
|
"./utils": {
|
|
81
91
|
"types": "./dist/utils/index.d.ts",
|
|
82
92
|
"import": "./dist/utils/index.js",
|
|
@@ -96,6 +106,7 @@
|
|
|
96
106
|
"remeda": "^1.24.0"
|
|
97
107
|
},
|
|
98
108
|
"devDependencies": {
|
|
99
|
-
"typescript": "^5.1.6"
|
|
109
|
+
"typescript": "^5.1.6",
|
|
110
|
+
"vitest": "^0.34.1"
|
|
100
111
|
}
|
|
101
112
|
}
|