@likec4/core 0.29.0 → 0.31.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 ADDED
@@ -0,0 +1,3 @@
1
+ # @likec4/core
2
+
3
+ Core likec4 types, errors, functions and utilities.
@@ -12,7 +12,7 @@ import { invariant } from '../errors';
12
12
  function transformToNodes(elementsIterator) {
13
13
  return Array.from(elementsIterator)
14
14
  .sort(compareByFqnHierarchically)
15
- .reduce((map, { id, kind, title, color, shape, description }) => {
15
+ .reduce((map, { id, color, shape, ...el }) => {
16
16
  let parent = parentFqn(id);
17
17
  while (parent) {
18
18
  if (map.has(parent)) {
@@ -26,14 +26,12 @@ function transformToNodes(elementsIterator) {
26
26
  parentNd.children.push(id);
27
27
  }
28
28
  const node = {
29
+ ...el,
29
30
  id,
30
- kind,
31
31
  parent,
32
- title,
33
32
  color: color ?? DefaultThemeColor,
34
33
  shape: shape ?? DefaultElementShape,
35
- children: [],
36
- ...(description ? { description } : {})
34
+ children: []
37
35
  };
38
36
  map.set(id, node);
39
37
  return map;
@@ -1,3 +1,4 @@
1
1
  export * from './_base';
2
2
  export * from './invariant';
3
+ export * from './model-index';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1,3 +1,4 @@
1
1
  export * from './_base';
2
2
  export * from './invariant';
3
+ export * from './model-index';
3
4
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,3 @@
1
+ export declare const InvalidModelError: import("modern-errors").ErrorSubclassCore<[], {}, import("modern-errors").CustomClass>;
2
+ export declare function ensureModel(condition: any, message?: string): asserts condition;
3
+ //# sourceMappingURL=model-index.d.ts.map
@@ -0,0 +1,14 @@
1
+ import { BaseError } from './_base';
2
+ export const InvalidModelError = BaseError.subclass('InvalidModelError');
3
+ export function ensureModel(
4
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
+ condition,
6
+ // Can provide a string, or a function that returns a string for cases where
7
+ // the message takes a fair amount of effort to compute
8
+ message) {
9
+ if (condition) {
10
+ return;
11
+ }
12
+ throw new InvalidModelError(message ?? 'ModelIndex Invariant failed');
13
+ }
14
+ //# sourceMappingURL=model-index.js.map
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export type * from './types';
2
+ export * from './types';
2
3
  export * from './compute-view';
3
4
  export * from './utils';
4
5
  export * from './model-index';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './types';
1
2
  export * from './compute-view';
2
3
  export * from './utils';
3
4
  export * from './model-index';
@@ -8,7 +8,7 @@ export default class ModelIndex {
8
8
  private _elements;
9
9
  private _relations;
10
10
  get relations(): Relation[];
11
- filterRelations: (predicate: (r: Relation) => boolean) => Relation[];
11
+ filterRelations(predicate: (r: Relation) => boolean): Relation[];
12
12
  static from({ elements, relations }: ModelInput): ModelIndex;
13
13
  addElement(el: Element): void;
14
14
  private locateTrie;
@@ -18,9 +18,10 @@ export default class ModelIndex {
18
18
  /**
19
19
  * Ancestors from closest parent to root
20
20
  */
21
- ancestors: (id: Fqn) => Element[];
21
+ ancestors(id: Fqn): Element[];
22
22
  rootElements(): Element[];
23
23
  get elements(): Element[];
24
+ hasElement(fqn: Fqn): boolean;
24
25
  addRelation(rel: Relation): void;
25
26
  }
26
27
  export {};
@@ -1,6 +1,6 @@
1
- import { values } from 'remeda';
2
- import { invariant } from '../errors';
3
- import { parentFqn } from '../utils/fqn';
1
+ import { pipe, sort, values } from 'remeda';
2
+ import { InvalidModelError, ensureModel } from '../errors';
3
+ import { compareByFqnHierarchically, parentFqn } from '../utils/fqn';
4
4
  function childrenOf(trie) {
5
5
  const children = [];
6
6
  for (const { el } of values(trie.children)) {
@@ -24,12 +24,13 @@ export default class ModelIndex {
24
24
  get relations() {
25
25
  return Array.from(this._relations.values());
26
26
  }
27
- filterRelations = (predicate) => {
27
+ filterRelations(predicate) {
28
28
  return this.relations.filter(predicate);
29
- };
29
+ }
30
30
  static from({ elements, relations }) {
31
31
  const index = new ModelIndex();
32
- for (const el of values(elements)) {
32
+ const sortedElements = pipe(values(elements), sort(compareByFqnHierarchically));
33
+ for (const el of sortedElements) {
33
34
  index.addElement(el);
34
35
  }
35
36
  for (const rel of values(relations)) {
@@ -38,8 +39,8 @@ export default class ModelIndex {
38
39
  return index;
39
40
  }
40
41
  addElement(el) {
41
- if (this._elements.has(el.id)) {
42
- throw new Error(`Element already exists with id ${el.id}`);
42
+ if (this.hasElement(el.id)) {
43
+ throw new InvalidModelError(`Element already exists with id ${el.id}`);
43
44
  }
44
45
  const path = asPath(el.id);
45
46
  let scope = this.root;
@@ -57,7 +58,7 @@ export default class ModelIndex {
57
58
  let scope = this.root;
58
59
  for (const name of asPath(id)) {
59
60
  const next = scope.children[name];
60
- invariant(next, `Invalid index, Element not found at path ${name} of ${id}`);
61
+ ensureModel(next, `Invalid index, Element not found at path ${name} of ${id}`);
61
62
  scope = next;
62
63
  }
63
64
  return scope;
@@ -65,7 +66,7 @@ export default class ModelIndex {
65
66
  find = (id) => {
66
67
  const el = this._elements.get(id);
67
68
  if (!el) {
68
- throw new Error(`Element not found ${id}`);
69
+ throw new InvalidModelError(`Element not found ${id}`);
69
70
  }
70
71
  return el;
71
72
  };
@@ -80,7 +81,7 @@ export default class ModelIndex {
80
81
  /**
81
82
  * Ancestors from closest parent to root
82
83
  */
83
- ancestors = (id) => {
84
+ ancestors(id) {
84
85
  const path = asPath(id);
85
86
  const ancestors = [];
86
87
  // The root
@@ -93,18 +94,14 @@ export default class ModelIndex {
93
94
  let trie = this.root;
94
95
  while (name) {
95
96
  const next = trie.children[name];
96
- if (!next) {
97
- throw new Error(`Invalid index, Element not found at path ${name} of ${id}`);
98
- }
97
+ ensureModel(next, `Invalid index, Element not found at path ${name} of ${id}`);
99
98
  trie = next;
100
- if (!trie.el) {
101
- throw new Error(`invalid index, no element ${name} found in ${id}`);
102
- }
99
+ ensureModel(trie.el, `invalid index, no element ${name} found in ${id}`);
103
100
  ancestors.unshift(trie.el);
104
101
  name = path.shift();
105
102
  }
106
103
  return ancestors;
107
- };
104
+ }
108
105
  // tagged = (tag?: Tag): TaggedResult => {
109
106
  // return tag ? {
110
107
  // elements: [...this._taggedElements.get(tag)?.values() ?? []],
@@ -120,16 +117,16 @@ export default class ModelIndex {
120
117
  get elements() {
121
118
  return Array.from(this._elements.values());
122
119
  }
123
- // hasElement(fqn: Fqn): boolean {
124
- // return fqn in this._elements
125
- // }
120
+ hasElement(fqn) {
121
+ return this._elements.has(fqn);
122
+ }
126
123
  addRelation(rel) {
127
124
  // Validate source and target
128
- if (!this._elements.has(rel.source)) {
129
- throw new Error(`Invalid index, source of relation not found ${rel.source}`);
125
+ if (!this.hasElement(rel.source)) {
126
+ throw new InvalidModelError(`Source of relation not found ${rel.source}`);
130
127
  }
131
- if (!this._elements.has(rel.target)) {
132
- throw new Error(`Invalid index, target of relation not found ${rel.target}`);
128
+ if (!this.hasElement(rel.target)) {
129
+ throw new InvalidModelError(`Target of relation not found ${rel.target}`);
133
130
  }
134
131
  this._relations.set(rel.id, rel);
135
132
  // for (const tag of rel.tags) {
@@ -0,0 +1,2 @@
1
+ export type NonEmptyArray<T> = [T, ...T[]];
2
+ //# 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 { NonEmptyArray } from './_common';
5
6
  export type NodeId = Fqn;
6
7
  export type EdgeId = Opaque<string, 'EdgeId'>;
7
8
  export interface ComputedNode {
@@ -9,9 +10,10 @@ export interface ComputedNode {
9
10
  kind: ElementKind;
10
11
  parent: NodeId | null;
11
12
  title: string;
12
- description?: string;
13
- technology?: string;
14
- tags?: Tag[];
13
+ description: string | null;
14
+ technology: string | null;
15
+ tags: NonEmptyArray<Tag> | null;
16
+ links: NonEmptyArray<string> | null;
15
17
  children: NodeId[];
16
18
  shape: ElementShape;
17
19
  color: ThemeColor;
@@ -1,7 +1,11 @@
1
+ import type { NonEmptyArray } from '.';
1
2
  import type { Opaque } from './opaque';
2
3
  export type Fqn = Opaque<string, 'Fqn'>;
3
- export declare function Fqn(name: string, parent?: Fqn | null): Fqn;
4
+ export declare function AsFqn(name: string, parent?: Fqn | null): Fqn;
4
5
  export type ElementKind = Opaque<string, 'ElementKind'>;
6
+ /**
7
+ * TailwindCSS based color palette
8
+ */
5
9
  export type ThemeColor = 'amber' | 'blue' | 'gray' | 'slate' | 'green' | 'indigo' | 'muted' | 'primary' | 'red' | 'secondary' | 'sky';
6
10
  export type ElementShape = 'rectangle' | 'person' | 'browser' | 'mobile' | 'cylinder' | 'storage' | 'queue';
7
11
  export declare const DefaultThemeColor: ThemeColor;
@@ -18,9 +22,10 @@ export interface Element {
18
22
  readonly id: Fqn;
19
23
  readonly kind: ElementKind;
20
24
  readonly title: string;
21
- readonly description?: string;
22
- readonly technology?: string;
23
- readonly tags?: Tag[];
25
+ readonly description: string | null;
26
+ readonly technology: string | null;
27
+ readonly tags: NonEmptyArray<Tag> | null;
28
+ readonly links: NonEmptyArray<string> | null;
24
29
  readonly shape?: ElementShape;
25
30
  readonly color?: ThemeColor;
26
31
  }
@@ -1,4 +1,4 @@
1
- export function Fqn(name, parent) {
1
+ export function AsFqn(name, parent) {
2
2
  return (parent ? parent + '.' + name : name);
3
3
  }
4
4
  export const DefaultThemeColor = 'primary';
@@ -1,5 +1,5 @@
1
1
  import type { ElementKind, Fqn, Tag } from './element';
2
- interface BaseExr {
2
+ interface BaseExpr {
3
3
  element?: never;
4
4
  elementKind?: never;
5
5
  elementTag?: never;
@@ -12,41 +12,41 @@ interface BaseExr {
12
12
  incoming?: never;
13
13
  outgoing?: never;
14
14
  }
15
- export interface ElementRefExpr extends Omit<BaseExr, 'element' | 'isDescedants'> {
15
+ export interface ElementRefExpr extends Omit<BaseExpr, 'element' | 'isDescedants'> {
16
16
  element: Fqn;
17
17
  isDescedants: boolean;
18
18
  }
19
19
  export declare function isElementRef(expr: Expression): expr is ElementRefExpr;
20
- export interface WildcardExpr extends Omit<BaseExr, 'wildcard'> {
20
+ export interface WildcardExpr extends Omit<BaseExpr, 'wildcard'> {
21
21
  wildcard: true;
22
22
  }
23
23
  export declare function isWildcard(expr: Expression): expr is WildcardExpr;
24
- export interface ElementKindExpr extends Omit<BaseExr, 'elementKind' | 'isEqual'> {
24
+ export interface ElementKindExpr extends Omit<BaseExpr, 'elementKind' | 'isEqual'> {
25
25
  elementKind: ElementKind;
26
26
  isEqual: boolean;
27
27
  }
28
28
  export declare function isElementKindExpr(expr: Expression): expr is ElementKindExpr;
29
- export interface ElementTagExpr extends Omit<BaseExr, 'elementTag' | 'isEqual'> {
29
+ export interface ElementTagExpr extends Omit<BaseExpr, 'elementTag' | 'isEqual'> {
30
30
  elementTag: Tag;
31
31
  isEqual: boolean;
32
32
  }
33
33
  export declare function isElementTagExpr(expr: Expression): expr is ElementTagExpr;
34
34
  export type ElementExpression = ElementRefExpr | WildcardExpr | ElementKindExpr | ElementTagExpr;
35
35
  export declare function isElement(expr: Expression): expr is ElementExpression;
36
- export interface RelationExpr extends Omit<BaseExr, 'source' | 'target'> {
36
+ export interface RelationExpr extends Omit<BaseExpr, 'source' | 'target'> {
37
37
  source: ElementExpression;
38
38
  target: ElementExpression;
39
39
  }
40
40
  export declare function isRelation(expr: Expression): expr is RelationExpr;
41
- export interface InOutExpr extends Omit<BaseExr, 'inout'> {
41
+ export interface InOutExpr extends Omit<BaseExpr, 'inout'> {
42
42
  inout: ElementExpression;
43
43
  }
44
44
  export declare function isInOut(expr: Expression): expr is InOutExpr;
45
- export interface IncomingExpr extends Omit<BaseExr, 'incoming'> {
45
+ export interface IncomingExpr extends Omit<BaseExpr, 'incoming'> {
46
46
  incoming: ElementExpression;
47
47
  }
48
48
  export declare function isIncoming(expr: Expression): expr is IncomingExpr;
49
- export interface OutgoingExpr extends Omit<BaseExr, 'outgoing'> {
49
+ export interface OutgoingExpr extends Omit<BaseExpr, 'outgoing'> {
50
50
  outgoing: ElementExpression;
51
51
  }
52
52
  export declare function isOutgoing(expr: Expression): expr is OutgoingExpr;
@@ -1,5 +1,7 @@
1
- export { Fqn, DefaultThemeColor, DefaultElementShape } from './element';
2
- export { isViewRuleExpression, isViewRuleStyle } from './view';
1
+ export { AsFqn, DefaultThemeColor, DefaultElementShape } from './element';
2
+ export { isViewRuleExpression, isViewRuleAutoLayout, isViewRuleStyle } from './view';
3
+ export * as Expr from './expression';
4
+ export type * from './_common';
3
5
  export type * from './opaque';
4
6
  export type * from './element';
5
7
  export type * from './relation';
@@ -1,3 +1,4 @@
1
- export { Fqn, DefaultThemeColor, DefaultElementShape } from './element';
2
- export { isViewRuleExpression, isViewRuleStyle } from './view';
1
+ export { AsFqn, DefaultThemeColor, DefaultElementShape } from './element';
2
+ export { isViewRuleExpression, isViewRuleAutoLayout, isViewRuleStyle } from './view';
3
+ export * as Expr from './expression';
3
4
  //# sourceMappingURL=index.js.map
@@ -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 { NonEmptyArray } from './_common';
4
5
  export type ViewID = Opaque<string, 'ViewID'>;
5
6
  export interface ViewRuleExpression {
6
7
  isInclude: boolean;
@@ -23,8 +24,10 @@ export type ViewRule = ViewRuleExpression | ViewRuleStyle | ViewRuleAutoLayout;
23
24
  export interface ElementView {
24
25
  readonly id: ViewID;
25
26
  readonly viewOf?: Fqn;
26
- readonly title?: string;
27
- readonly description?: string;
27
+ readonly title: string | null;
28
+ readonly description: string | null;
29
+ readonly tags: NonEmptyArray<Tag> | null;
30
+ readonly links: NonEmptyArray<string> | null;
28
31
  readonly rules: ViewRule[];
29
32
  }
30
33
  //# sourceMappingURL=view.d.ts.map
@@ -6,6 +6,16 @@ export declare function isDescendantOf(ancestors: Element[]): (e: Element) => bo
6
6
  export declare function notDescendantOf(ancestors: Element[]): (e: Element) => boolean;
7
7
  export declare function commonAncestor(first: Fqn, second: Fqn): Fqn | null;
8
8
  export declare function parentFqn(fqn: Fqn): Fqn | null;
9
+ /**
10
+ * Compares two fully qualified names (fqns) hierarchically based on their depth.
11
+ * From parent nodes to leaves
12
+ *
13
+ * @param {string} a - The first fqn to compare.
14
+ * @param {string} b - The second fqn to compare.
15
+ * @returns {number} - 0 if the fqns have the same depth.
16
+ * - Positive number if a is deeper than b.
17
+ * - Negative number if b is deeper than a.
18
+ */
9
19
  export declare const compareFqnHierarchically: (a: string, b: string) => number;
10
20
  export declare const compareByFqnHierarchically: <T extends {
11
21
  id: Fqn;
package/dist/utils/fqn.js CHANGED
@@ -56,6 +56,16 @@ export function parentFqn(fqn) {
56
56
  }
57
57
  return null;
58
58
  }
59
+ /**
60
+ * Compares two fully qualified names (fqns) hierarchically based on their depth.
61
+ * From parent nodes to leaves
62
+ *
63
+ * @param {string} a - The first fqn to compare.
64
+ * @param {string} b - The second fqn to compare.
65
+ * @returns {number} - 0 if the fqns have the same depth.
66
+ * - Positive number if a is deeper than b.
67
+ * - Negative number if b is deeper than a.
68
+ */
59
69
  export const compareFqnHierarchically = (a, b) => {
60
70
  const depthA = a.split('.').length;
61
71
  const depthB = b.split('.').length;
@@ -1,4 +1,6 @@
1
+ import type { NonEmptyArray } from "../types";
1
2
  export declare function isString(value: unknown): value is string;
2
3
  export declare function failExpectedNever(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
@@ -8,4 +8,7 @@ export function ignoreNeverInRuntime(arg) {
8
8
  console.warn(`Unexpected and ignored value: ${JSON.stringify(arg)}`);
9
9
  // throw new Error(`Unexpected value: ${arg}`);
10
10
  }
11
+ export function isNonEmptyArray(arr) {
12
+ return arr.length > 0;
13
+ }
11
14
  //# sourceMappingURL=guards.js.map
package/package.json CHANGED
@@ -1,21 +1,15 @@
1
1
  {
2
2
  "name": "@likec4/core",
3
- "version": "0.29.0",
3
+ "version": "0.31.0",
4
4
  "license": "MIT",
5
- "bugs": "https://github.com/likec4/likec4/issues",
6
5
  "homepage": "https://likec4.dev",
7
6
  "author": "Denis Davydkov <denis@davydkov.com>",
8
- "files": [
9
- "dist",
10
- "!dist/__test__",
11
- "!**/*.spec.*",
12
- "!**/*.map"
13
- ],
14
7
  "repository": {
15
8
  "type": "git",
16
9
  "url": "https://github.com/likec4/likec4.git",
17
10
  "directory": "packages/core"
18
11
  },
12
+ "bugs": "https://github.com/likec4/likec4/issues",
19
13
  "scripts": {
20
14
  "compile": "tsc --noEmit",
21
15
  "build": "tsc",
@@ -25,6 +19,12 @@
25
19
  "test:watch": "run -T vitest",
26
20
  "clean": "run -T rimraf dist"
27
21
  },
22
+ "files": [
23
+ "dist",
24
+ "!dist/__test__",
25
+ "!**/*.spec.*",
26
+ "!**/*.map"
27
+ ],
28
28
  "module": "./dist/index.js",
29
29
  "types": "./dist/index.d.ts",
30
30
  "type": "module",
@@ -93,7 +93,7 @@
93
93
  "@dagrejs/graphlib": "^2.1.13",
94
94
  "modern-errors": "^6.0.0",
95
95
  "rambdax": "^9.1.1",
96
- "remeda": "^1.23.0"
96
+ "remeda": "^1.24.0"
97
97
  },
98
98
  "devDependencies": {
99
99
  "typescript": "^5.1.6"