@likec4/core 1.8.1 → 1.9.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.
Files changed (89) hide show
  1. package/dist/index.cjs +4368 -0
  2. package/dist/index.d.cts +781 -0
  3. package/dist/index.d.mts +781 -0
  4. package/dist/index.mjs +4296 -0
  5. package/dist/shared/core.9l7eW3pn.d.cts +817 -0
  6. package/dist/shared/core.9l7eW3pn.d.mts +817 -0
  7. package/dist/shared/core.DcPLm41i.cjs +304 -0
  8. package/dist/shared/core.DfUTsojZ.mjs +251 -0
  9. package/dist/types/index.cjs +53 -0
  10. package/dist/types/index.d.cts +1 -0
  11. package/dist/types/index.d.mts +1 -0
  12. package/dist/types/index.mjs +1 -0
  13. package/package.json +41 -33
  14. package/src/colors/index.ts +27 -1
  15. package/src/errors/index.ts +3 -95
  16. package/src/index.ts +5 -6
  17. package/src/model/LikeC4Model.ts +480 -0
  18. package/src/model/LikeC4ViewModel.ts +318 -0
  19. package/src/model/__test__/LikeC4Model.spec.ts +79 -0
  20. package/src/model/__test__/fixture.ts +333 -0
  21. package/src/model/index.ts +2 -0
  22. package/src/model/types.ts +10 -0
  23. package/src/types/_common.ts +4 -2
  24. package/src/types/element.ts +8 -8
  25. package/src/types/expression.ts +3 -3
  26. package/src/types/index.ts +0 -1
  27. package/src/types/model.ts +16 -6
  28. package/src/types/relation.ts +15 -5
  29. package/src/types/theme.ts +28 -12
  30. package/src/types/view-notation.ts +3 -4
  31. package/src/types/view.ts +14 -10
  32. package/src/utils/fqn.ts +7 -4
  33. package/src/utils/relations.spec.ts +1 -3
  34. package/src/utils/relations.ts +7 -17
  35. package/dist/colors/element.d.ts +0 -69
  36. package/dist/colors/element.js +0 -75
  37. package/dist/colors/index.d.ts +0 -133
  38. package/dist/colors/index.js +0 -9
  39. package/dist/colors/relationships.d.ts +0 -58
  40. package/dist/colors/relationships.js +0 -49
  41. package/dist/errors/errors.spec.d.ts +0 -2
  42. package/dist/errors/errors.spec.js +0 -69
  43. package/dist/errors/index.d.ts +0 -39
  44. package/dist/errors/index.js +0 -104
  45. package/dist/index.d.ts +0 -7
  46. package/dist/index.js +0 -4
  47. package/dist/types/_common.d.ts +0 -9
  48. package/dist/types/_common.js +0 -1
  49. package/dist/types/element.d.ts +0 -55
  50. package/dist/types/element.js +0 -15
  51. package/dist/types/expression.d.ts +0 -119
  52. package/dist/types/expression.js +0 -67
  53. package/dist/types/index.d.ts +0 -14
  54. package/dist/types/index.js +0 -14
  55. package/dist/types/model.d.ts +0 -14
  56. package/dist/types/model.js +0 -1
  57. package/dist/types/opaque.d.ts +0 -103
  58. package/dist/types/opaque.js +0 -1
  59. package/dist/types/operators.d.ts +0 -44
  60. package/dist/types/operators.js +0 -59
  61. package/dist/types/overview-graph.d.ts +0 -41
  62. package/dist/types/overview-graph.js +0 -1
  63. package/dist/types/relation.d.ts +0 -30
  64. package/dist/types/relation.js +0 -3
  65. package/dist/types/theme.d.ts +0 -27
  66. package/dist/types/theme.js +0 -1
  67. package/dist/types/view-changes.d.ts +0 -26
  68. package/dist/types/view-changes.js +0 -1
  69. package/dist/types/view-notation.d.ts +0 -9
  70. package/dist/types/view-notation.js +0 -1
  71. package/dist/types/view.d.ts +0 -243
  72. package/dist/types/view.js +0 -50
  73. package/dist/utils/fqn.d.ts +0 -39
  74. package/dist/utils/fqn.js +0 -102
  75. package/dist/utils/fqn.spec.d.ts +0 -2
  76. package/dist/utils/fqn.spec.js +0 -82
  77. package/dist/utils/guards.d.ts +0 -5
  78. package/dist/utils/guards.js +0 -7
  79. package/dist/utils/index.d.ts +0 -5
  80. package/dist/utils/index.js +0 -4
  81. package/dist/utils/promises.d.ts +0 -2
  82. package/dist/utils/promises.js +0 -8
  83. package/dist/utils/relations.d.ts +0 -30
  84. package/dist/utils/relations.js +0 -79
  85. package/dist/utils/relations.spec.d.ts +0 -2
  86. package/dist/utils/relations.spec.js +0 -210
  87. package/src/errors/errors.spec.ts +0 -88
  88. package/src/reset.d.ts +0 -2
  89. package/src/types/opaque.ts +0 -108
@@ -1,41 +0,0 @@
1
- import type { NonEmptyArray, Point, XYPoint } from './_common';
2
- import type { BBox, ViewID } from './view';
3
- /**
4
- * OverviewGraph is a graph representation of all views in a model
5
- */
6
- export declare namespace OverviewGraph {
7
- type Node = {
8
- id: string;
9
- type: 'folder' | 'file';
10
- path: string;
11
- label: string;
12
- parentId: string | null;
13
- position: XYPoint;
14
- width: number;
15
- height: number;
16
- } | {
17
- id: string;
18
- type: 'view';
19
- viewId: ViewID;
20
- label: string;
21
- parentId: string | null;
22
- position: XYPoint;
23
- width: number;
24
- height: number;
25
- };
26
- /**
27
- * Edge represents a navigational link from one view to another
28
- */
29
- type Edge = {
30
- id: string;
31
- source: string;
32
- target: string;
33
- points: NonEmptyArray<Point>;
34
- };
35
- }
36
- export interface OverviewGraph {
37
- nodes: OverviewGraph.Node[];
38
- edges: OverviewGraph.Edge[];
39
- bounds: BBox;
40
- }
41
- //# sourceMappingURL=overview-graph.d.ts.map
@@ -1 +0,0 @@
1
- export {};
@@ -1,30 +0,0 @@
1
- import type { NonEmptyArray } from './_common';
2
- import type { Fqn, Link, Tag } from './element';
3
- import type { Opaque } from './opaque';
4
- import type { ThemeColor } from './theme';
5
- export type RelationID = Opaque<string, 'RelationID'>;
6
- export type RelationshipKind = Opaque<string, 'RelationshipKind'>;
7
- export type RelationshipLineType = 'dashed' | 'solid' | 'dotted';
8
- export type RelationshipArrowType = 'none' | 'normal' | 'onormal' | 'dot' | 'odot' | 'diamond' | 'odiamond' | 'crow' | 'open' | 'vee';
9
- export declare const DefaultLineStyle = "dashed";
10
- export declare const DefaultArrowType = "normal";
11
- export declare const DefaultRelationshipColor = "gray";
12
- export interface Relation {
13
- readonly id: RelationID;
14
- readonly source: Fqn;
15
- readonly target: Fqn;
16
- readonly title: string;
17
- readonly description?: string;
18
- readonly technology?: string;
19
- readonly tags?: NonEmptyArray<Tag>;
20
- readonly kind?: RelationshipKind;
21
- readonly color?: ThemeColor;
22
- readonly line?: RelationshipLineType;
23
- readonly head?: RelationshipArrowType;
24
- readonly tail?: RelationshipArrowType;
25
- readonly links?: NonEmptyArray<Link>;
26
- readonly metadata?: {
27
- [key: string]: string;
28
- };
29
- }
30
- //# sourceMappingURL=relation.d.ts.map
@@ -1,3 +0,0 @@
1
- export const DefaultLineStyle = 'dashed';
2
- export const DefaultArrowType = 'normal';
3
- export const DefaultRelationshipColor = 'gray';
@@ -1,27 +0,0 @@
1
- export type ThemeColor = 'amber' | 'blue' | 'gray' | 'slate' | 'green' | 'indigo' | 'muted' | 'primary' | 'red' | 'secondary' | 'sky';
2
- export type HexColorLiteral = `#${string}`;
3
- export type ColorLiteral = HexColorLiteral;
4
- export interface ElementThemeColorValues {
5
- fill: ColorLiteral;
6
- stroke: ColorLiteral;
7
- hiContrast: ColorLiteral;
8
- loContrast: ColorLiteral;
9
- }
10
- export type ElementThemeColors = {
11
- [key in ThemeColor]: ElementThemeColorValues;
12
- };
13
- export interface RelationshipThemeColorValues {
14
- lineColor: ColorLiteral;
15
- labelBgColor: ColorLiteral;
16
- labelColor: ColorLiteral;
17
- }
18
- export type RelationshipThemeColors = {
19
- [key in ThemeColor]: RelationshipThemeColorValues;
20
- };
21
- export interface LikeC4Theme {
22
- font: 'Arial';
23
- shadow: ColorLiteral;
24
- relationships: RelationshipThemeColors;
25
- elements: ElementThemeColors;
26
- }
27
- //# sourceMappingURL=theme.d.ts.map
@@ -1 +0,0 @@
1
- export {};
@@ -1,26 +0,0 @@
1
- import type { NonEmptyArray } from './_common';
2
- import type { BorderStyle, ElementShape, Fqn } from './element';
3
- import type { ThemeColor } from './theme';
4
- import type { AutoLayoutDirection, ViewManualLayout } from './view';
5
- export declare namespace ViewChange {
6
- interface ChangeElementStyle {
7
- op: 'change-element-style';
8
- style: {
9
- border?: BorderStyle;
10
- opacity?: number;
11
- shape?: ElementShape;
12
- color?: ThemeColor;
13
- };
14
- targets: NonEmptyArray<Fqn>;
15
- }
16
- interface SaveManualLayout {
17
- op: 'save-manual-layout';
18
- layout: ViewManualLayout;
19
- }
20
- interface ChangeAutoLayout {
21
- op: 'change-autolayout';
22
- layout: AutoLayoutDirection;
23
- }
24
- }
25
- export type ViewChange = ViewChange.ChangeElementStyle | ViewChange.SaveManualLayout | ViewChange.ChangeAutoLayout;
26
- //# sourceMappingURL=view-changes.d.ts.map
@@ -1 +0,0 @@
1
- export {};
@@ -1,9 +0,0 @@
1
- import type { ElementKind, ElementShape } from './element';
2
- import type { ThemeColor } from './theme';
3
- export type ElementNotation = {
4
- kinds: ElementKind[];
5
- shape: ElementShape;
6
- color: ThemeColor;
7
- title: string;
8
- };
9
- //# sourceMappingURL=view-notation.d.ts.map
@@ -1 +0,0 @@
1
- export {};
@@ -1,243 +0,0 @@
1
- import type { IconUrl, NonEmptyArray, Point, XYPoint } from './_common';
2
- import type { ElementKind, ElementShape, ElementStyle, Fqn, Link, Tag } from './element';
3
- import type { ElementExpression, ElementPredicateExpression, Expression } from './expression';
4
- import type { Opaque } from './opaque';
5
- import type { RelationID, RelationshipArrowType, RelationshipKind, RelationshipLineType } from './relation';
6
- import type { ThemeColor } from './theme';
7
- import type { ElementNotation } from './view-notation';
8
- export type ViewID = Opaque<string, 'ViewID'>;
9
- export type ViewRulePredicate = {
10
- include: Expression[];
11
- exclude?: never;
12
- } | {
13
- include?: never;
14
- exclude: Expression[];
15
- };
16
- export declare function isViewRulePredicate(rule: ViewRule): rule is ViewRulePredicate;
17
- export interface ViewRuleStyle {
18
- targets: ElementExpression[];
19
- notation?: string;
20
- style: ElementStyle & {
21
- color?: ThemeColor;
22
- shape?: ElementShape;
23
- icon?: IconUrl;
24
- };
25
- }
26
- export declare function isViewRuleStyle(rule: ViewRule): rule is ViewRuleStyle;
27
- export type AutoLayoutDirection = 'TB' | 'BT' | 'LR' | 'RL';
28
- export interface ViewRuleAutoLayout {
29
- autoLayout: AutoLayoutDirection;
30
- }
31
- export declare function isViewRuleAutoLayout(rule: ViewRule): rule is ViewRuleAutoLayout;
32
- export type ViewRule = ViewRulePredicate | ViewRuleStyle | ViewRuleAutoLayout;
33
- export interface BasicView<ViewType extends 'element' | 'dynamic'> {
34
- readonly __?: ViewType;
35
- readonly id: ViewID;
36
- readonly title: string | null;
37
- readonly description: string | null;
38
- readonly tags: NonEmptyArray<Tag> | null;
39
- readonly links: NonEmptyArray<Link> | null;
40
- /**
41
- * URI to the source file of this view.
42
- * Undefined if the view is auto-generated.
43
- */
44
- readonly docUri?: string;
45
- /**
46
- * For all views we find common ancestor path.
47
- * This is used to generate relative paths, i.e.:
48
- * - "/home/project/index.c4" becomes "index.c4"
49
- * - "/home/project/subdir/views.c4" becomes "subdir/views.c4"
50
- *
51
- * Undefined if the view is auto-generated.
52
- */
53
- readonly relativePath?: string;
54
- /**
55
- * If the view is changed manually this field contains the layout data.
56
- */
57
- readonly manualLayout?: ViewManualLayout | undefined;
58
- }
59
- export interface BasicElementView extends BasicView<'element'> {
60
- readonly viewOf?: Fqn;
61
- readonly rules: ViewRule[];
62
- }
63
- export interface ScopedElementView extends BasicElementView {
64
- readonly viewOf: Fqn;
65
- }
66
- export interface ExtendsElementView extends BasicElementView {
67
- readonly extends: ViewID;
68
- }
69
- export type ElementView = ScopedElementView | ExtendsElementView | BasicElementView;
70
- export interface DynamicViewStep {
71
- readonly source: Fqn;
72
- readonly target: Fqn;
73
- readonly title: string | null;
74
- readonly description?: string;
75
- readonly technology?: string;
76
- readonly notation?: string;
77
- readonly color?: ThemeColor;
78
- readonly line?: RelationshipLineType;
79
- readonly head?: RelationshipArrowType;
80
- readonly tail?: RelationshipArrowType;
81
- readonly isBackward?: boolean;
82
- }
83
- export type DynamicViewIncludeRule = {
84
- include: ElementPredicateExpression[];
85
- };
86
- export declare function isDynamicViewIncludeRule(rule: DynamicViewRule): rule is DynamicViewIncludeRule;
87
- export type DynamicViewRule = DynamicViewIncludeRule | ViewRuleStyle | ViewRuleAutoLayout;
88
- export interface DynamicView extends BasicView<'dynamic'> {
89
- readonly __: 'dynamic';
90
- readonly steps: DynamicViewStep[];
91
- readonly rules: DynamicViewRule[];
92
- }
93
- export type LikeC4View = ElementView | DynamicView;
94
- export declare function isDynamicView(view: LikeC4View): view is DynamicView;
95
- export declare function isElementView(view: LikeC4View): view is ElementView;
96
- export declare function isExtendsElementView(view: LikeC4View): view is ExtendsElementView;
97
- export declare function isScopedElementView(view: LikeC4View): view is ScopedElementView;
98
- export type NodeId = Fqn;
99
- export type EdgeId = Opaque<string, 'EdgeId'>;
100
- export type StepEdgeIdLiteral = `step-${number}`;
101
- export type StepEdgeId = Opaque<StepEdgeIdLiteral, 'EdgeId'>;
102
- export declare function StepEdgeId(step: number): StepEdgeId;
103
- export declare function isStepEdgeId(id: string): id is StepEdgeId;
104
- export declare function extractStep(id: EdgeId): number;
105
- export interface ComputedNode {
106
- id: NodeId;
107
- kind: ElementKind;
108
- parent: NodeId | null;
109
- title: string;
110
- description: string | null;
111
- technology: string | null;
112
- notation?: string;
113
- tags: NonEmptyArray<Tag> | null;
114
- links: NonEmptyArray<Link> | null;
115
- children: NodeId[];
116
- inEdges: EdgeId[];
117
- outEdges: EdgeId[];
118
- shape: ElementShape;
119
- /**
120
- * @deprecated Use `style` instead
121
- */
122
- color: ThemeColor;
123
- /**
124
- * @deprecated Use `style` instead
125
- */
126
- icon?: IconUrl;
127
- style: ElementStyle;
128
- navigateTo?: ViewID;
129
- level: number;
130
- depth?: number;
131
- /**
132
- * If this node was customized in the view
133
- */
134
- isCustomized?: boolean;
135
- }
136
- export interface ComputedEdge {
137
- id: EdgeId;
138
- parent: NodeId | null;
139
- source: NodeId;
140
- target: NodeId;
141
- label: string | null;
142
- description?: string;
143
- technology?: string;
144
- relations: RelationID[];
145
- kind?: RelationshipKind;
146
- color?: ThemeColor;
147
- line?: RelationshipLineType;
148
- head?: RelationshipArrowType;
149
- tail?: RelationshipArrowType;
150
- tags?: NonEmptyArray<Tag>;
151
- /**
152
- * If this edge is derived from custom relationship predicate
153
- */
154
- isCustomized?: boolean;
155
- /**
156
- * For layouting purposes
157
- * @default 'forward'
158
- */
159
- dir?: 'forward' | 'back';
160
- }
161
- export interface ViewWithHash {
162
- /**
163
- * Hash of the view object.
164
- * This is used to detect changes in layout
165
- */
166
- hash: string;
167
- }
168
- export interface ViewWithNotation {
169
- notation?: {
170
- elements: ElementNotation[];
171
- };
172
- }
173
- export interface ComputedElementView extends Omit<ElementView, 'rules' | 'docUri'>, ViewWithHash, ViewWithNotation {
174
- readonly extends?: ViewID;
175
- readonly autoLayout: ViewRuleAutoLayout['autoLayout'];
176
- readonly nodes: ComputedNode[];
177
- readonly edges: ComputedEdge[];
178
- rules?: never;
179
- docUri?: never;
180
- }
181
- export interface ComputedDynamicView extends Omit<DynamicView, 'rules' | 'steps' | 'docUri'>, ViewWithHash, ViewWithNotation {
182
- readonly autoLayout: ViewRuleAutoLayout['autoLayout'];
183
- readonly nodes: ComputedNode[];
184
- readonly edges: ComputedEdge[];
185
- steps?: never;
186
- rules?: never;
187
- docUri?: never;
188
- }
189
- export declare function isComputedDynamicView(view: ComputedView): view is ComputedDynamicView;
190
- export type ComputedView = ComputedElementView | ComputedDynamicView;
191
- export declare function isComputedElementView(view: ComputedView): view is ComputedElementView;
192
- export type BBox = {
193
- x: number;
194
- y: number;
195
- width: number;
196
- height: number;
197
- };
198
- export declare function getBBoxCenter({ x, y, width, height }: BBox): XYPoint;
199
- export interface DiagramNode extends ComputedNode {
200
- width: number;
201
- height: number;
202
- position: Point;
203
- labelBBox: BBox;
204
- }
205
- export interface DiagramEdge extends ComputedEdge {
206
- points: NonEmptyArray<Point>;
207
- controlPoints?: NonEmptyArray<XYPoint>;
208
- labelBBox?: BBox | null;
209
- dotpos?: string;
210
- }
211
- export interface DiagramView extends Omit<ComputedView, 'nodes' | 'edges' | 'manualLayout'> {
212
- readonly nodes: DiagramNode[];
213
- readonly edges: DiagramEdge[];
214
- readonly bounds: BBox;
215
- /**
216
- * If diagram has manual layout
217
- * But was changed and layout should be recalculated
218
- */
219
- hasLayoutDrift?: boolean;
220
- manualLayout?: never;
221
- }
222
- export type ViewManualLayout = {
223
- readonly hash: string;
224
- readonly x: number;
225
- readonly y: number;
226
- readonly width: number;
227
- readonly height: number;
228
- readonly autoLayout: AutoLayoutDirection;
229
- readonly nodes: Record<string, {
230
- isCompound: boolean;
231
- x: number;
232
- y: number;
233
- width: number;
234
- height: number;
235
- }>;
236
- readonly edges: Record<string, {
237
- dotpos?: string;
238
- points: NonEmptyArray<Point>;
239
- controlPoints?: NonEmptyArray<XYPoint>;
240
- labelBBox?: BBox;
241
- }>;
242
- };
243
- //# sourceMappingURL=view.d.ts.map
@@ -1,50 +0,0 @@
1
- import { isNullish } from 'remeda';
2
- export function isViewRulePredicate(rule) {
3
- return (('include' in rule && Array.isArray(rule.include))
4
- || ('exclude' in rule && Array.isArray(rule.exclude)));
5
- }
6
- export function isViewRuleStyle(rule) {
7
- return 'style' in rule && 'targets' in rule;
8
- }
9
- export function isViewRuleAutoLayout(rule) {
10
- return 'autoLayout' in rule;
11
- }
12
- export function isDynamicViewIncludeRule(rule) {
13
- return 'include' in rule && Array.isArray(rule.include);
14
- }
15
- export function isDynamicView(view) {
16
- return view.__ === 'dynamic';
17
- }
18
- export function isElementView(view) {
19
- return isNullish(view.__) || view.__ === 'element';
20
- }
21
- export function isExtendsElementView(view) {
22
- return isElementView(view) && 'extends' in view;
23
- }
24
- export function isScopedElementView(view) {
25
- return isElementView(view) && 'viewOf' in view;
26
- }
27
- export function StepEdgeId(step) {
28
- return `step-${String(step).padStart(3, '0')}`;
29
- }
30
- export function isStepEdgeId(id) {
31
- return id.startsWith('step-');
32
- }
33
- export function extractStep(id) {
34
- if (!isStepEdgeId(id)) {
35
- throw new Error(`Invalid step edge id: ${id}`);
36
- }
37
- return Number(id.slice('step-'.length));
38
- }
39
- export function isComputedDynamicView(view) {
40
- return view.__ === 'dynamic';
41
- }
42
- export function isComputedElementView(view) {
43
- return isNullish(view.__) || view.__ === 'element';
44
- }
45
- export function getBBoxCenter({ x, y, width, height }) {
46
- return {
47
- x: x + width / 2,
48
- y: y + height / 2
49
- };
50
- }
@@ -1,39 +0,0 @@
1
- import type { Element, Fqn } from '../types';
2
- type Predicate<T> = (x: T) => boolean;
3
- export declare function nameFromFqn(fqn: Fqn): string;
4
- export declare function isAncestor<E extends {
5
- id: Fqn;
6
- }>(...args: [ancestor: string, another: string] | [ancestor: E, another: E]): boolean;
7
- export declare function isSameHierarchy<E extends {
8
- id: Fqn;
9
- }>(one: E | Fqn, another: E | Fqn): boolean;
10
- export declare function isDescendantOf<E extends {
11
- id: Fqn;
12
- }>(ancestors: E[]): (e: E) => boolean;
13
- export declare function notDescendantOf(ancestors: Element[]): (e: Element) => boolean;
14
- export declare function commonAncestor(first: Fqn, second: Fqn): Fqn | null;
15
- export declare function parentFqn(fqn: Fqn): Fqn | null;
16
- export declare function parentFqnPredicate<T extends {
17
- parent: Fqn | null;
18
- }>(parent: Fqn): Predicate<T>;
19
- /**
20
- * Get all ancestor elements (i.e. parent, parent’s parent, etc.)
21
- * (from closest to root)
22
- */
23
- export declare function ancestorsFqn(fqn: Fqn): Fqn[];
24
- /**
25
- * Compares two fully qualified names (fqns) hierarchically based on their depth.
26
- * From parent nodes to leaves
27
- *
28
- * @param {string} a - The first fqn to compare.
29
- * @param {string} b - The second fqn to compare.
30
- * @returns {number} - 0 if the fqns have the same depth.
31
- * - Positive number if a is deeper than b.
32
- * - Negative number if b is deeper than a.
33
- */
34
- export declare function compareFqnHierarchically<T extends string = string>(a: T, b: T): number;
35
- export declare function compareByFqnHierarchically<T extends {
36
- id: string;
37
- }>(a: T, b: T): number;
38
- export {};
39
- //# sourceMappingURL=fqn.d.ts.map
package/dist/utils/fqn.js DELETED
@@ -1,102 +0,0 @@
1
- import { isString } from './guards';
2
- export function nameFromFqn(fqn) {
3
- const lastDot = fqn.lastIndexOf('.');
4
- if (lastDot > 0) {
5
- return fqn.slice(lastDot + 1);
6
- }
7
- else {
8
- return fqn;
9
- }
10
- }
11
- export function isAncestor(...args) {
12
- const ancestor = isString(args[0]) ? args[0] : args[0].id;
13
- const another = isString(args[1]) ? args[1] : args[1].id;
14
- return another.startsWith(ancestor + '.');
15
- }
16
- export function isSameHierarchy(one, another) {
17
- const first = isString(one) ? one : one.id;
18
- const second = isString(another) ? another : another.id;
19
- return first === second || second.startsWith(first + '.') || first.startsWith(second + '.');
20
- }
21
- export function isDescendantOf(ancestors) {
22
- const predicates = ancestors.flatMap(a => [(e) => e.id === a.id, (e) => isAncestor(a, e)]);
23
- return (e) => predicates.some(p => p(e));
24
- }
25
- export function notDescendantOf(ancestors) {
26
- const isDescendant = isDescendantOf(ancestors);
27
- return (e) => !isDescendant(e);
28
- }
29
- export function commonAncestor(first, second) {
30
- const parentA = parentFqn(first);
31
- const parentB = parentFqn(second);
32
- if (parentA === parentB) {
33
- return parentA;
34
- }
35
- if (!parentA || !parentB) {
36
- return null;
37
- }
38
- const a = first.split('.');
39
- const b = second.split('.');
40
- let ancestor = null;
41
- while (a.length > 1 && b.length > 1 && !!a[0] && a[0] === b[0]) {
42
- ancestor = (ancestor ? `${ancestor}.${a[0]}` : a[0]);
43
- a.shift();
44
- b.shift();
45
- }
46
- return ancestor;
47
- }
48
- export function parentFqn(fqn) {
49
- const lastDot = fqn.lastIndexOf('.');
50
- if (lastDot > 0) {
51
- return fqn.substring(0, lastDot);
52
- }
53
- return null;
54
- }
55
- export function parentFqnPredicate(parent) {
56
- const prefix = parent + '.';
57
- return (e) => !!e.parent && (e.parent === parent || e.parent.startsWith(prefix));
58
- }
59
- /**
60
- * Get all ancestor elements (i.e. parent, parent’s parent, etc.)
61
- * (from closest to root)
62
- */
63
- export function ancestorsFqn(fqn) {
64
- const path = fqn.split('.');
65
- path.pop();
66
- if (path.length === 0) {
67
- return [];
68
- }
69
- return path.reduce((acc, _, idx) => {
70
- const ancestor = path.slice(0, idx + 1).join('.');
71
- acc.unshift(ancestor);
72
- return acc;
73
- }, []);
74
- }
75
- /**
76
- * Compares two fully qualified names (fqns) hierarchically based on their depth.
77
- * From parent nodes to leaves
78
- *
79
- * @param {string} a - The first fqn to compare.
80
- * @param {string} b - The second fqn to compare.
81
- * @returns {number} - 0 if the fqns have the same depth.
82
- * - Positive number if a is deeper than b.
83
- * - Negative number if b is deeper than a.
84
- */
85
- export function compareFqnHierarchically(a, b) {
86
- const depthA = a.split('.').length;
87
- const depthB = b.split('.').length;
88
- switch (true) {
89
- case depthA > depthB: {
90
- return 1;
91
- }
92
- case depthA < depthB: {
93
- return -1;
94
- }
95
- default: {
96
- return 0;
97
- }
98
- }
99
- }
100
- export function compareByFqnHierarchically(a, b) {
101
- return compareFqnHierarchically(a.id, b.id);
102
- }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=fqn.spec.d.ts.map
@@ -1,82 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { ancestorsFqn, commonAncestor, compareFqnHierarchically, isAncestor, isDescendantOf, notDescendantOf, parentFqn } from './fqn';
3
- const el = (id) => ({ id });
4
- describe('parentFqn', () => {
5
- it('should return null if no parent', () => {
6
- expect(parentFqn('a')).toBeNull();
7
- });
8
- it('should return parent', () => {
9
- expect(parentFqn('a.b')).toBe('a');
10
- expect(parentFqn('a.b.c')).toBe('a.b');
11
- });
12
- });
13
- describe('ancestorsFqn', () => {
14
- it('should return empty array if no parent', () => {
15
- expect(ancestorsFqn('a')).toEqual([]);
16
- });
17
- it('should return ancestors', () => {
18
- expect(ancestorsFqn('a.b.c.d.e')).toEqual(['a.b.c.d', 'a.b.c', 'a.b', 'a']);
19
- });
20
- });
21
- describe('commonAncestor', () => {
22
- it('should return null if no common ancestor', () => {
23
- expect(commonAncestor('a', 'b')).toBeNull();
24
- expect(commonAncestor('a.b', 'c.d')).toBeNull();
25
- });
26
- it('should return common ancestor', () => {
27
- expect(commonAncestor('a.b', 'a.c')).toBe('a');
28
- expect(commonAncestor('a.b.c', 'a.b.e')).toBe('a.b');
29
- expect(commonAncestor('a.b.c.d.e', 'a.b.c.d')).toBe('a.b.c');
30
- });
31
- });
32
- describe('isAncestor', () => {
33
- it('should return true if ancestor', () => {
34
- expect(isAncestor('a', 'a.b')).toBe(true);
35
- expect(isAncestor('a.b', 'a.b.c')).toBe(true);
36
- });
37
- it('should return false if not ancestor', () => {
38
- expect(isAncestor('a', 'b')).toBe(false);
39
- expect(isAncestor('a.b', 'a')).toBe(false);
40
- expect(isAncestor('a.b', 'b.a')).toBe(false);
41
- });
42
- });
43
- describe('isDescendantOf', () => {
44
- const predicate = isDescendantOf(['a', 'b', 'a.b', 'a.b.c'].map(el));
45
- it('should return true if isDescendantOf', () => {
46
- expect(predicate(el('a'))).toBe(true);
47
- expect(predicate(el('b.c'))).toBe(true);
48
- expect(predicate(el('a.b.c.d.e'))).toBe(true);
49
- });
50
- it('should return false if not descendantOf', () => {
51
- expect(predicate(el('c'))).toBe(false);
52
- expect(predicate(el('ac'))).toBe(false);
53
- expect(predicate(el('d.a.c'))).toBe(false);
54
- });
55
- });
56
- describe('notDescendantOf', () => {
57
- const predicate = notDescendantOf(['a', 'b', 'a.b', 'a.b.c'].map(el));
58
- it('should return true if notDescendantOf', () => {
59
- expect(predicate(el('c'))).toBe(true);
60
- expect(predicate(el('ac'))).toBe(true);
61
- expect(predicate(el('d.a.c'))).toBe(true);
62
- });
63
- it('should return false if descendantOf', () => {
64
- expect(predicate(el('a'))).toBe(false);
65
- expect(predicate(el('b.c'))).toBe(false);
66
- expect(predicate(el('a.b.c.d.e'))).toBe(false);
67
- });
68
- });
69
- describe('compareFqnHierarchically', () => {
70
- it('should compare hierarchically', () => {
71
- expect(['a', 'b', 'a.b', 'a.b.c', 'a.c.c'].sort(compareFqnHierarchically)).toEqual([
72
- 'a',
73
- 'b',
74
- 'a.b',
75
- 'a.b.c',
76
- 'a.c.c'
77
- ]);
78
- });
79
- it('should preserve initial order', () => {
80
- expect(['aaa', 'aa', 'a', 'aaa.c', 'aa.b', 'a.c', 'a.b'].sort(compareFqnHierarchically)).toEqual(['aaa', 'aa', 'a', 'aaa.c', 'aa.b', 'a.c', 'a.b']);
81
- });
82
- });
@@ -1,5 +0,0 @@
1
- import type { NonEmptyArray } from '../types';
2
- export { hasAtLeast } from 'remeda';
3
- export declare function isString(value: unknown): value is string;
4
- export declare function isNonEmptyArray<A>(arr: ArrayLike<A> | undefined): arr is NonEmptyArray<A>;
5
- //# sourceMappingURL=guards.d.ts.map