@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 CHANGED
@@ -1,3 +1,14 @@
1
- # @likec4/core
1
+ # LikeC4 Core
2
2
 
3
- Core likec4 types, errors, functions and utilities.
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: "#fef2f2";
59
- readonly loContrast: "#fecaca";
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: "#f0fdf4";
65
- readonly loContrast: "#bbf7d0";
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: "#fffbeb";
71
- readonly loContrast: "#fde68a";
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: '#f0fdf4',
72
- loContrast: '#bbf7d0'
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 ElementView, type ComputeResult } from '../types';
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, filter, uniq, isNil } from 'rambdax';
2
- import { DefaultThemeColor, DefaultElementShape } from '../types';
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 { compareByFqnHierarchically, failExpectedNever, ignoreNeverInRuntime, isAncestor, isSameHierarchy, parentFqn, Relations } from '../utils';
6
- import { hasRelation, isAnyInOut, isBetween, isIncoming, isInside, isOutgoing } from '../utils/relations';
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 { anyPossibleRelations } from './utils/anyPossibleRelations';
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, tags, ...el }) => {
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
- failExpectedNever(target);
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
- filters.push(Relations.isInside(expr.element));
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 allInOut = ctx.index.filterRelations(isAnyInOut(root));
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
- const relationsWithImplicit = allInOut.filter(isAnyInOut(implicit.id));
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)), ...relations],
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
- const predicates = [];
174
- for (const [source, target] of anyPossibleRelations(elements)) {
175
- predicates.push(Relations.isAnyBetween(source.id, target.id));
176
- }
177
- const relations = predicates.length ? ctx.index.filterRelations(anyPass(predicates)) : [];
178
- return new ComputeCtx(ctx.index, ctx.root, new Set(elements), new Set(relations));
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(anyPass([isInside(root), isIncoming(root), isOutgoing(root)]));
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(anyPass(relations.map(r => hasRelation(r))))
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 = ctx.index.filterRelations(isIncoming(el.id));
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 = ctx.index.filterRelations(isOutgoing(el.id));
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 = ctx.index.filterRelations(isAnyInOut(target.id));
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
- ignoreNeverInRuntime(expr);
450
+ nonexhaustive(expr);
432
451
  }
433
452
  }
434
- const elements = new Set([...ctx.elements]);
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 resolvedRelations = [...ctx.relations];
437
- for (const [source, target] of anyPossibleRelations([...elements, ...ctx.implicits])) {
438
- if (!elements.has(source) && !elements.has(target)) {
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 findPredicate = Relations.isBetween(source.id, target.id);
442
- let idx = resolvedRelations.findIndex(findPredicate);
443
- while (idx >= 0) {
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
  }
@@ -1,4 +1,5 @@
1
1
  export * from './_base';
2
2
  export * from './invariant';
3
+ export * from './nonexhaustive';
3
4
  export * from './model-index';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1,4 +1,5 @@
1
1
  export * from './_base';
2
2
  export * from './invariant';
3
+ export * from './nonexhaustive';
3
4
  export * from './model-index';
4
5
  //# sourceMappingURL=index.js.map
@@ -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
@@ -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,3 @@
1
+ export declare const NonExhaustiveError: import("modern-errors").ErrorSubclassCore<[], {}, import("modern-errors").CustomClass>;
2
+ export declare function nonexhaustive(arg: never): never;
3
+ //# sourceMappingURL=nonexhaustive.d.ts.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
- if (!el) {
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) => {
@@ -0,0 +1,4 @@
1
+ import type { Opaque } from './opaque';
2
+ export type NonEmptyArray<T> = [T, ...T[]];
3
+ export type IconUrl = Opaque<string, 'IconUrl'>;
4
+ //# sourceMappingURL=_common.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=_common.js.map
@@ -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 {
@@ -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: ReadonlyArray<Tag>;
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
  }
@@ -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';
@@ -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?: string;
27
- readonly description?: string;
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
@@ -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(...args: [ancestor: Fqn, another: Fqn] | [ancestor: Element, another: Element]): boolean;
4
- export declare function isSameHierarchy(...args: [one: Fqn, another: Fqn] | [one: Element, another: Element]): boolean;
5
- export declare function isDescendantOf(ancestors: Element[]): (e: Element) => boolean;
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 const compareFqnHierarchically: (a: string, b: string) => number;
20
- export declare const compareByFqnHierarchically: <T extends {
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) => number;
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 const compareFqnHierarchically = (a, b) => {
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 const compareByFqnHierarchically = (a, b) => {
76
+ }
77
+ export function compareByFqnHierarchically(a, b) {
81
78
  return compareFqnHierarchically(a.id, b.id);
82
- };
79
+ }
83
80
  //# sourceMappingURL=fqn.js.map
@@ -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 failExpectedNever(arg: never): never;
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
@@ -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 failExpectedNever(arg) {
5
- throw new Error(`Unexpected value: ${JSON.stringify(arg)}`);
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, Element } from '../types';
1
+ import type { Fqn } from '../types';
2
2
  type Relation = {
3
- source: Fqn;
4
- target: Fqn;
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: Fqn;
9
- target: Fqn;
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: (rel: Relation) => (element: Element) => boolean;
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
@@ -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) && (rel.target === target || (rel.target + '.').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)) && !(rel.target + '.').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 anyPass([isIncoming(source), isOutgoing(source)]);
39
+ return either(isIncoming(source), isOutgoing(source));
37
40
  };
38
41
  export const hasRelation = (rel) => {
39
42
  return (element) => {
40
- return isAncestor(rel.source, element.id) || isAncestor(rel.target, element.id);
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.30.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.23.0"
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
  }