@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 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,14 +1,14 @@
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)
@@ -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
- failExpectedNever(target);
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
- filters.push(Relations.isInside(expr.element));
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 allInOut = ctx.index.filterRelations(isAnyInOut(root));
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
- const relationsWithImplicit = allInOut.filter(isAnyInOut(implicit.id));
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)), ...relations],
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
- const predicates = [];
173
- for (const [source, target] of anyPossibleRelations(elements)) {
174
- predicates.push(Relations.isAnyBetween(source.id, target.id));
175
- }
176
- const relations = predicates.length ? ctx.index.filterRelations(anyPass(predicates)) : [];
177
- 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
+ });
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(anyPass([isInside(root), isIncoming(root), isOutgoing(root)]));
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(anyPass(relations.map(r => hasRelation(r))))
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 = ctx.index.filterRelations(isIncoming(el.id));
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 = ctx.index.filterRelations(isOutgoing(el.id));
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 = ctx.index.filterRelations(isAnyInOut(target.id));
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
- ignoreNeverInRuntime(expr);
450
+ nonexhaustive(expr);
431
451
  }
432
452
  }
433
- 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();
434
457
  const edgeBuilder = new EdgeBuilder();
435
- const resolvedRelations = [...ctx.relations];
436
- for (const [source, target] of anyPossibleRelations([...elements, ...ctx.implicits])) {
437
- 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) {
438
464
  continue;
439
465
  }
440
- const findPredicate = Relations.isBetween(source.id, target.id);
441
- let idx = resolvedRelations.findIndex(findPredicate);
442
- while (idx >= 0) {
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
  }
@@ -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) => {
@@ -1,2 +1,4 @@
1
+ import type { Opaque } from './opaque';
1
2
  export type NonEmptyArray<T> = [T, ...T[]];
3
+ export type IconUrl = Opaque<string, 'IconUrl'>;
2
4
  //# sourceMappingURL=_common.d.ts.map
@@ -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 {
@@ -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
  }
@@ -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;
@@ -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,6 +1,6 @@
1
- import type { NonEmptyArray } from "../types";
1
+ import type { NonEmptyArray } from '../types';
2
2
  export declare function isString(value: unknown): value is string;
3
- export declare function failExpectedNever(arg: never): never;
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
@@ -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 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)}`);
@@ -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.31.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
  }