@likec4/core 0.30.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 +101 -46
- 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 +4 -0
- package/dist/types/_common.js +2 -0
- package/dist/types/computed-view.d.ts +4 -1
- package/dist/types/element.d.ts +4 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/view.d.ts +7 -3
- package/dist/utils/fqn.d.ts +12 -6
- package/dist/utils/fqn.js +5 -8
- package/dist/utils/guards.d.ts +3 -1
- package/dist/utils/guards.js +6 -2
- package/dist/utils/relations.d.ts +11 -6
- package/dist/utils/relations.js +10 -4
- package/package.json +14 -3
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,18 +1,18 @@
|
|
|
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)
|
|
15
|
-
.reduce((map, { id, color, shape,
|
|
15
|
+
.reduce((map, { id, color, shape, ...el }) => {
|
|
16
16
|
let parent = parentFqn(id);
|
|
17
17
|
while (parent) {
|
|
18
18
|
if (map.has(parent)) {
|
|
@@ -31,8 +31,7 @@ function transformToNodes(elementsIterator) {
|
|
|
31
31
|
parent,
|
|
32
32
|
color: color ?? DefaultThemeColor,
|
|
33
33
|
shape: shape ?? DefaultElementShape,
|
|
34
|
-
children: []
|
|
35
|
-
tags: [...tags]
|
|
34
|
+
children: []
|
|
36
35
|
};
|
|
37
36
|
map.set(id, node);
|
|
38
37
|
return map;
|
|
@@ -65,11 +64,14 @@ function applyViewRuleStyles(rules, nodes) {
|
|
|
65
64
|
predicates.push(isDescedants ? n => n.id.startsWith(element + '.') : n => n.id === element);
|
|
66
65
|
continue;
|
|
67
66
|
}
|
|
68
|
-
|
|
67
|
+
failUnexpected(target);
|
|
69
68
|
}
|
|
70
69
|
filter(anyPass(predicates), nodes).forEach(n => {
|
|
71
70
|
n.shape = rule.style.shape ?? n.shape;
|
|
72
71
|
n.color = rule.style.color ?? n.color;
|
|
72
|
+
if (isDefined.strict(rule.style.icon)) {
|
|
73
|
+
n.icon = rule.style.icon;
|
|
74
|
+
}
|
|
73
75
|
});
|
|
74
76
|
}
|
|
75
77
|
return nodes;
|
|
@@ -80,7 +82,9 @@ const includeElementRef = (ctx, expr) => {
|
|
|
80
82
|
: [ctx.index.find(expr.element)];
|
|
81
83
|
const filters = [];
|
|
82
84
|
if (expr.isDescedants) {
|
|
83
|
-
|
|
85
|
+
elements.forEach(child => {
|
|
86
|
+
filters.push(both(Relations.isInside(expr.element), Relations.isAnyInOut(child.id)));
|
|
87
|
+
});
|
|
84
88
|
}
|
|
85
89
|
const ctxElements = uniq([...ctx.elements, ...ctx.implicits]);
|
|
86
90
|
const excludeImplicits = [];
|
|
@@ -148,44 +152,42 @@ const includeWildcardRef = (ctx, _expr) => {
|
|
|
148
152
|
...ctx.index.siblings(root),
|
|
149
153
|
...ctx.index.ancestors(root).flatMap(a => [...ctx.index.siblings(a.id)])
|
|
150
154
|
];
|
|
151
|
-
const
|
|
155
|
+
const inOut = ctx.index.filterRelations(isAnyInOut(root));
|
|
152
156
|
// Neighbors that have relations with root or its children
|
|
153
157
|
const implicits = [];
|
|
154
|
-
const relations = [];
|
|
155
158
|
for (const implicit of allImplicits) {
|
|
156
|
-
|
|
157
|
-
if (relationsWithImplicit.length) {
|
|
159
|
+
if (inOut.some(isAnyInOut(implicit.id))) {
|
|
158
160
|
implicits.push(implicit);
|
|
159
|
-
relations.push(...relationsWithImplicit);
|
|
160
161
|
}
|
|
161
162
|
}
|
|
162
163
|
return ctx.include({
|
|
163
164
|
elements,
|
|
164
|
-
relations: [...ctx.index.filterRelations(isInside(root)), ...
|
|
165
|
+
relations: [...ctx.index.filterRelations(isInside(root)), ...inOut],
|
|
165
166
|
implicits: implicits
|
|
166
167
|
});
|
|
167
168
|
}
|
|
168
169
|
else {
|
|
170
|
+
// Take root elements
|
|
169
171
|
const elements = ctx.index.rootElements();
|
|
170
172
|
if (elements.length <= 1) {
|
|
171
173
|
return new ComputeCtx(ctx.index, ctx.root, new Set(elements));
|
|
172
174
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
+
});
|
|
179
181
|
}
|
|
180
182
|
};
|
|
181
183
|
const excludeWildcardRef = (ctx, _expr) => {
|
|
182
184
|
const { root } = ctx;
|
|
183
185
|
if (root) {
|
|
184
|
-
const relations = [...ctx.relations].filter(
|
|
186
|
+
const relations = [...ctx.relations].filter(either(isInside(root), isAnyInOut(root)));
|
|
185
187
|
return ctx.exclude({
|
|
186
|
-
elements: [...ctx.elements].filter(e => isAncestor(root, e.id)),
|
|
188
|
+
elements: [...ctx.elements].filter(e => e.id === root || isAncestor(root, e.id)),
|
|
187
189
|
relations,
|
|
188
|
-
implicits: [...ctx.implicits].filter(
|
|
190
|
+
implicits: [...ctx.implicits].filter(e => relations.some(isAnyInOut(e.id)))
|
|
189
191
|
});
|
|
190
192
|
}
|
|
191
193
|
else {
|
|
@@ -233,10 +235,16 @@ const includeIncomingExpr = (ctx, expr) => {
|
|
|
233
235
|
if (elements.length === 0) {
|
|
234
236
|
return ctx;
|
|
235
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
|
+
}
|
|
236
244
|
const implicits = [];
|
|
237
245
|
const relations = [];
|
|
238
246
|
for (const el of elements) {
|
|
239
|
-
const elRelations =
|
|
247
|
+
const elRelations = allrelations.filter(isIncoming(el.id));
|
|
240
248
|
if (elRelations.length > 0) {
|
|
241
249
|
relations.push(...elRelations);
|
|
242
250
|
implicits.push(el);
|
|
@@ -272,10 +280,16 @@ const includeOutgoingExpr = (ctx, expr) => {
|
|
|
272
280
|
if (elements.length === 0) {
|
|
273
281
|
return ctx;
|
|
274
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
|
+
}
|
|
275
289
|
const implicits = [];
|
|
276
290
|
const relations = [];
|
|
277
291
|
for (const el of elements) {
|
|
278
|
-
const elRelations =
|
|
292
|
+
const elRelations = allrelations.filter(isOutgoing(el.id));
|
|
279
293
|
if (elRelations.length > 0) {
|
|
280
294
|
relations.push(...elRelations);
|
|
281
295
|
implicits.push(el);
|
|
@@ -311,10 +325,15 @@ const includeInOutExpr = (ctx, expr) => {
|
|
|
311
325
|
if (targets.length === 0) {
|
|
312
326
|
return ctx;
|
|
313
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
|
+
}
|
|
314
333
|
const implicits = [];
|
|
315
334
|
const relations = [];
|
|
316
335
|
for (const target of targets) {
|
|
317
|
-
const foundRelations =
|
|
336
|
+
const foundRelations = allrelations.filter(isAnyInOut(target.id));
|
|
318
337
|
if (foundRelations.length > 0) {
|
|
319
338
|
relations.push(...foundRelations);
|
|
320
339
|
implicits.push(target);
|
|
@@ -428,28 +447,64 @@ export function computeElementView(view, index) {
|
|
|
428
447
|
ctx = isInclude ? includeRelationExpr(ctx, expr) : excludeRelationExpr(ctx, expr);
|
|
429
448
|
continue;
|
|
430
449
|
}
|
|
431
|
-
|
|
450
|
+
nonexhaustive(expr);
|
|
432
451
|
}
|
|
433
452
|
}
|
|
434
|
-
|
|
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();
|
|
435
457
|
const edgeBuilder = new EdgeBuilder();
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
|
|
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) {
|
|
439
464
|
continue;
|
|
440
465
|
}
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const [rel] = resolvedRelations.splice(idx, 1);
|
|
445
|
-
if (rel) {
|
|
446
|
-
elements.add(source);
|
|
447
|
-
elements.add(target);
|
|
448
|
-
edgeBuilder.add(source.id, target.id, rel);
|
|
449
|
-
}
|
|
450
|
-
idx = resolvedRelations.findIndex(findPredicate);
|
|
466
|
+
const target = find(allElements, allPass([anscestorOf(rel.target), e => !isSameHierarchy(e, source)]));
|
|
467
|
+
if (!target) {
|
|
468
|
+
continue;
|
|
451
469
|
}
|
|
452
|
-
|
|
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
|
+
// }
|
|
453
508
|
const nodesreg = transformToNodes(elements);
|
|
454
509
|
const edges = edgeBuilder.build().map(edge => {
|
|
455
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) => {
|
|
@@ -2,6 +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 { IconUrl, NonEmptyArray } from './_common';
|
|
5
6
|
export type NodeId = Fqn;
|
|
6
7
|
export type EdgeId = Opaque<string, 'EdgeId'>;
|
|
7
8
|
export interface ComputedNode {
|
|
@@ -11,10 +12,12 @@ export interface ComputedNode {
|
|
|
11
12
|
title: string;
|
|
12
13
|
description: string | null;
|
|
13
14
|
technology: string | null;
|
|
14
|
-
tags: Tag
|
|
15
|
+
tags: NonEmptyArray<Tag> | null;
|
|
16
|
+
links: NonEmptyArray<string> | null;
|
|
15
17
|
children: NodeId[];
|
|
16
18
|
shape: ElementShape;
|
|
17
19
|
color: ThemeColor;
|
|
20
|
+
icon?: IconUrl;
|
|
18
21
|
navigateTo?: ViewID;
|
|
19
22
|
}
|
|
20
23
|
export interface ComputedEdge {
|
package/dist/types/element.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { IconUrl, NonEmptyArray } from './_common';
|
|
1
2
|
import type { Opaque } from './opaque';
|
|
2
3
|
export type Fqn = Opaque<string, 'Fqn'>;
|
|
3
4
|
export declare function AsFqn(name: string, parent?: Fqn | null): Fqn;
|
|
@@ -23,7 +24,9 @@ export interface Element {
|
|
|
23
24
|
readonly title: string;
|
|
24
25
|
readonly description: string | null;
|
|
25
26
|
readonly technology: string | null;
|
|
26
|
-
readonly tags:
|
|
27
|
+
readonly tags: NonEmptyArray<Tag> | null;
|
|
28
|
+
readonly links: NonEmptyArray<string> | null;
|
|
29
|
+
readonly icon?: IconUrl;
|
|
27
30
|
readonly shape?: ElementShape;
|
|
28
31
|
readonly color?: ThemeColor;
|
|
29
32
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { AsFqn, DefaultThemeColor, DefaultElementShape } from './element';
|
|
2
2
|
export { isViewRuleExpression, isViewRuleAutoLayout, isViewRuleStyle } from './view';
|
|
3
3
|
export * as Expr from './expression';
|
|
4
|
+
export type * from './_common';
|
|
4
5
|
export type * from './opaque';
|
|
5
6
|
export type * from './element';
|
|
6
7
|
export type * from './relation';
|
package/dist/types/view.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Opaque } from './opaque';
|
|
2
|
-
import type { ElementShape, Fqn, ThemeColor } from './element';
|
|
2
|
+
import type { ElementShape, Fqn, Tag, ThemeColor } from './element';
|
|
3
3
|
import type { ElementExpression, Expression } from './expression';
|
|
4
|
+
import type { IconUrl, NonEmptyArray } from './_common';
|
|
4
5
|
export type ViewID = Opaque<string, 'ViewID'>;
|
|
5
6
|
export interface ViewRuleExpression {
|
|
6
7
|
isInclude: boolean;
|
|
@@ -12,6 +13,7 @@ export interface ViewRuleStyle {
|
|
|
12
13
|
style: {
|
|
13
14
|
color?: ThemeColor;
|
|
14
15
|
shape?: ElementShape;
|
|
16
|
+
icon?: IconUrl;
|
|
15
17
|
};
|
|
16
18
|
}
|
|
17
19
|
export declare function isViewRuleStyle(rule: ViewRule): rule is ViewRuleStyle;
|
|
@@ -23,8 +25,10 @@ export type ViewRule = ViewRuleExpression | ViewRuleStyle | ViewRuleAutoLayout;
|
|
|
23
25
|
export interface ElementView {
|
|
24
26
|
readonly id: ViewID;
|
|
25
27
|
readonly viewOf?: Fqn;
|
|
26
|
-
readonly title
|
|
27
|
-
readonly description
|
|
28
|
+
readonly title: string | null;
|
|
29
|
+
readonly description: string | null;
|
|
30
|
+
readonly tags: NonEmptyArray<Tag> | null;
|
|
31
|
+
readonly links: NonEmptyArray<string> | null;
|
|
28
32
|
readonly rules: ViewRule[];
|
|
29
33
|
}
|
|
30
34
|
//# sourceMappingURL=view.d.ts.map
|
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,4 +1,6 @@
|
|
|
1
|
+
import type { NonEmptyArray } from '../types';
|
|
1
2
|
export declare function isString(value: unknown): value is string;
|
|
2
|
-
export declare function
|
|
3
|
+
export declare function failUnexpected(arg: never): never;
|
|
3
4
|
export declare function ignoreNeverInRuntime(arg: never): void;
|
|
5
|
+
export declare function isNonEmptyArray<A>(arr: ArrayLike<A>): arr is NonEmptyArray<A>;
|
|
4
6
|
//# sourceMappingURL=guards.d.ts.map
|
package/dist/utils/guards.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
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)}`);
|
|
9
10
|
// throw new Error(`Unexpected value: ${arg}`);
|
|
10
11
|
}
|
|
12
|
+
export function isNonEmptyArray(arr) {
|
|
13
|
+
return arr.length > 0;
|
|
14
|
+
}
|
|
11
15
|
//# sourceMappingURL=guards.js.map
|
|
@@ -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",
|
|
@@ -93,9 +103,10 @@
|
|
|
93
103
|
"@dagrejs/graphlib": "^2.1.13",
|
|
94
104
|
"modern-errors": "^6.0.0",
|
|
95
105
|
"rambdax": "^9.1.1",
|
|
96
|
-
"remeda": "^1.
|
|
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
|
}
|