@likec4/core 1.17.1 → 1.19.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 (184) hide show
  1. package/LICENSE +1 -1
  2. package/dist/builder/index.d.mts +502 -0
  3. package/dist/builder/index.d.ts +502 -0
  4. package/dist/builder/index.js +871 -0
  5. package/dist/compute-view/index.d.mts +22 -0
  6. package/dist/compute-view/index.d.ts +22 -0
  7. package/dist/compute-view/index.js +78 -0
  8. package/dist/index.d.mts +10 -919
  9. package/dist/index.d.ts +10 -919
  10. package/dist/{index.mjs → index.js} +7 -1688
  11. package/dist/model/index.d.mts +254 -0
  12. package/dist/model/index.d.ts +254 -0
  13. package/dist/model/index.js +1 -0
  14. package/dist/shared/chunk-RAAYCPUM.M-JWF7SS.js +21 -0
  15. package/dist/shared/core.BIfgabEw.d.ts +719 -0
  16. package/dist/shared/core.BZ9Msq7w.d.mts +719 -0
  17. package/dist/shared/{core.CoLQkcYL.d.cts → core.C05RDLhi.d.ts} +460 -362
  18. package/dist/shared/{core.CoLQkcYL.d.mts → core.CmYMqgha.d.mts} +460 -362
  19. package/dist/shared/core.D6-fWbM3.d.ts +127 -0
  20. package/dist/shared/core.D74xkKkG.d.mts +148 -0
  21. package/dist/shared/core.D74xkKkG.d.ts +148 -0
  22. package/dist/shared/core.sLaWHBjR.d.mts +127 -0
  23. package/dist/shared/index.-BdzdI2C.js +7443 -0
  24. package/dist/shared/object_hash.CE_oF3I3.js +1222 -0
  25. package/dist/shared/{core.BXTs-8n7.mjs → view.CyZrG8BJ.js} +108 -47
  26. package/dist/types/index.d.mts +71 -1
  27. package/dist/types/index.d.ts +71 -1
  28. package/dist/types/index.js +20 -0
  29. package/dist/utils/index.d.mts +404 -0
  30. package/dist/utils/index.d.ts +404 -0
  31. package/dist/utils/index.js +330 -0
  32. package/package.json +59 -47
  33. package/src/builder/Builder-style1.test-d.ts +190 -0
  34. package/src/builder/Builder-style2.test-d.ts +175 -0
  35. package/src/builder/Builder.deployment.ts +224 -0
  36. package/src/builder/Builder.deploymentModel.ts +385 -0
  37. package/src/builder/Builder.element.ts +19 -19
  38. package/src/builder/Builder.model.ts +67 -42
  39. package/src/builder/Builder.ts +533 -367
  40. package/src/builder/Builder.view-common.ts +234 -0
  41. package/src/builder/Builder.view-deployment.ts +135 -0
  42. package/src/builder/Builder.view-element.ts +189 -0
  43. package/src/builder/Builder.views.ts +109 -33
  44. package/src/builder/Builder.with.ts +475 -0
  45. package/src/builder/__snapshots__/Builder-style1.spec.ts.snap +374 -0
  46. package/src/builder/__snapshots__/Builder-style2.spec.ts.snap +183 -0
  47. package/src/builder/__snapshots__/Builder.spec.ts.snap +374 -0
  48. package/src/builder/_types.ts +113 -18
  49. package/src/builder/index.ts +10 -4
  50. package/src/colors/index.ts +2 -2
  51. package/src/compute-view/compute-view.ts +83 -0
  52. package/src/compute-view/deployment-view/__test__/TestHelper.ts +236 -0
  53. package/src/compute-view/deployment-view/__test__/fixture.ts +198 -0
  54. package/src/compute-view/deployment-view/_types.ts +37 -0
  55. package/src/compute-view/deployment-view/clean-connections.ts +238 -0
  56. package/src/compute-view/deployment-view/compute.ts +125 -0
  57. package/src/compute-view/deployment-view/memory/index.ts +9 -0
  58. package/src/compute-view/deployment-view/memory/memory.ts +97 -0
  59. package/src/compute-view/deployment-view/predicates/elements.ts +144 -0
  60. package/src/compute-view/deployment-view/predicates/relation-direct.ts +237 -0
  61. package/src/compute-view/deployment-view/predicates/relation-in-out.ts +61 -0
  62. package/src/compute-view/deployment-view/predicates/relation-incoming.ts +95 -0
  63. package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +88 -0
  64. package/src/compute-view/deployment-view/predicates/wildcard.ts +29 -0
  65. package/src/compute-view/deployment-view/stages/stage-exclude.ts +5 -0
  66. package/src/compute-view/deployment-view/stages/stage-final.ts +245 -0
  67. package/src/compute-view/deployment-view/stages/stage-include.ts +95 -0
  68. package/src/compute-view/deployment-view/utils.ts +276 -0
  69. package/src/compute-view/dynamic-view/__test__/fixture.ts +58 -0
  70. package/src/compute-view/dynamic-view/compute.ts +324 -0
  71. package/src/compute-view/element-view/__test__/TestHelper.ts +258 -0
  72. package/src/compute-view/element-view/__test__/__snapshots__/legacy.spec.ts.snap +458 -0
  73. package/src/compute-view/element-view/__test__/fixture.ts +675 -0
  74. package/src/compute-view/element-view/_types.ts +48 -0
  75. package/src/compute-view/element-view/clean-connections.ts +113 -0
  76. package/src/compute-view/element-view/compute.ts +315 -0
  77. package/src/compute-view/element-view/memory/NodeGroup.ts +31 -0
  78. package/src/compute-view/element-view/memory/index.ts +10 -0
  79. package/src/compute-view/element-view/memory/memory.ts +177 -0
  80. package/src/compute-view/element-view/memory/stage-exclude.ts +59 -0
  81. package/src/compute-view/element-view/memory/stage-final.ts +129 -0
  82. package/src/compute-view/element-view/memory/stage-include.ts +128 -0
  83. package/src/compute-view/element-view/predicates/_utils.ts +86 -0
  84. package/src/compute-view/element-view/predicates/element-expand.ts +33 -0
  85. package/src/compute-view/element-view/predicates/element-kind-tag.ts +26 -0
  86. package/src/compute-view/element-view/predicates/element-ref.ts +37 -0
  87. package/src/compute-view/element-view/predicates/relation-direct.ts +309 -0
  88. package/src/compute-view/element-view/predicates/relation-in-out.ts +66 -0
  89. package/src/compute-view/element-view/predicates/relation-in.ts +118 -0
  90. package/src/compute-view/element-view/predicates/relation-out.ts +116 -0
  91. package/src/compute-view/element-view/predicates/wildcard.ts +88 -0
  92. package/src/compute-view/element-view/utils.ts +59 -0
  93. package/src/compute-view/index.ts +4 -0
  94. package/src/compute-view/memory/AbstractMemory.ts +45 -0
  95. package/src/compute-view/memory/AbstractStageExclude.ts +216 -0
  96. package/src/compute-view/memory/AbstractStageInclude.ts +200 -0
  97. package/src/compute-view/memory/_types.ts +147 -0
  98. package/src/compute-view/memory/index.ts +5 -0
  99. package/src/compute-view/memory/ops.ts +43 -0
  100. package/src/compute-view/utils/__snapshots__/sortNodes.spec.ts.snap +113 -0
  101. package/src/compute-view/utils/ancestorsOfNode.ts +19 -0
  102. package/src/compute-view/utils/applyCustomElementProperties.ts +66 -0
  103. package/src/compute-view/utils/applyCustomRelationProperties.ts +41 -0
  104. package/src/compute-view/utils/applyViewRuleStyles.ts +53 -0
  105. package/src/compute-view/utils/buildComputedNodes.ts +148 -0
  106. package/src/compute-view/utils/buildElementNotations.ts +63 -0
  107. package/src/compute-view/utils/elementExpressionToPredicate.ts +50 -0
  108. package/src/compute-view/utils/link-nodes-with-edges.ts +54 -0
  109. package/src/compute-view/utils/merge-props-from-relationships.ts +128 -0
  110. package/src/compute-view/utils/relationExpressionToPredicates.ts +55 -0
  111. package/src/compute-view/utils/resolve-extended-views.ts +69 -0
  112. package/src/compute-view/utils/resolve-global-rules.ts +83 -0
  113. package/src/compute-view/utils/topological-sort.ts +256 -0
  114. package/src/compute-view/utils/uniqueTags.test.ts +42 -0
  115. package/src/compute-view/utils/uniqueTags.ts +21 -0
  116. package/src/compute-view/utils/view-hash.ts +27 -0
  117. package/src/compute-view/utils/with-readable-edges.ts +36 -0
  118. package/src/index.ts +22 -12
  119. package/src/model/DeploymentElementModel.ts +599 -0
  120. package/src/model/DeploymentModel.ts +370 -0
  121. package/src/model/ElementModel.ts +221 -0
  122. package/src/model/LikeC4Model.test-d.ts +152 -0
  123. package/src/model/LikeC4Model.ts +336 -457
  124. package/src/model/RelationModel.ts +85 -0
  125. package/src/model/__test__/fixture.ts +149 -338
  126. package/src/model/connection/Connection.ts +72 -0
  127. package/src/model/connection/deployment/DeploymentConnectionModel.ts +178 -0
  128. package/src/model/connection/deployment/find.ts +120 -0
  129. package/src/model/connection/deployment/index.ts +8 -0
  130. package/src/model/connection/index.ts +22 -0
  131. package/src/model/connection/model/ConnectionModel.ts +157 -0
  132. package/src/model/connection/model/find.ts +117 -0
  133. package/src/model/connection/model/index.ts +8 -0
  134. package/src/model/connection/ops.ts +274 -0
  135. package/src/model/guards.ts +9 -0
  136. package/src/model/index.ts +46 -2
  137. package/src/model/types.ts +112 -15
  138. package/src/model/view/EdgeModel.ts +103 -0
  139. package/src/model/view/LikeC4ViewModel.ts +226 -0
  140. package/src/model/view/NodeModel.ts +229 -0
  141. package/src/types/_common.ts +55 -1
  142. package/src/types/deployments.ts +169 -0
  143. package/src/types/element.ts +9 -5
  144. package/src/types/expression-v2.ts +172 -0
  145. package/src/types/expression.ts +17 -11
  146. package/src/types/global.ts +3 -3
  147. package/src/types/index.ts +220 -12
  148. package/src/types/model.ts +75 -12
  149. package/src/types/operators.ts +1 -1
  150. package/src/types/overview-graph.ts +2 -2
  151. package/src/types/relation.ts +18 -14
  152. package/src/types/theme.ts +0 -1
  153. package/src/types/view-notation.ts +2 -2
  154. package/src/types/view.ts +117 -41
  155. package/src/utils/{commonHead.ts → common-head.ts} +2 -0
  156. package/src/utils/const.ts +1 -0
  157. package/src/utils/fqn.ts +203 -56
  158. package/src/utils/getOrCreate.ts +42 -0
  159. package/src/utils/graphlib.ts +10 -0
  160. package/src/utils/index.ts +74 -6
  161. package/src/utils/iterable/_types.ts +5 -0
  162. package/src/utils/iterable/filter.ts +55 -0
  163. package/src/utils/iterable/flat.ts +17 -0
  164. package/src/utils/iterable/index.ts +12 -0
  165. package/src/utils/iterable/map.ts +40 -0
  166. package/src/utils/iterable/reduce.ts +24 -0
  167. package/src/utils/iterable/some.ts +36 -0
  168. package/src/utils/iterable/to.ts +19 -0
  169. package/src/utils/iterable/unique.ts +29 -0
  170. package/src/utils/object-hash.ts +3 -0
  171. package/src/utils/relations.ts +3 -64
  172. package/src/utils/set.ts +78 -0
  173. package/src/utils/string-hash.ts +16 -0
  174. package/dist/index.cjs +0 -5452
  175. package/dist/index.d.cts +0 -1053
  176. package/dist/shared/core.CoLQkcYL.d.ts +0 -734
  177. package/dist/shared/core.DIYZvpVg.cjs +0 -353
  178. package/dist/types/index.cjs +0 -70
  179. package/dist/types/index.d.cts +0 -2
  180. package/dist/types/index.mjs +0 -1
  181. package/src/builder/Builder.test-d.ts +0 -69
  182. package/src/builder/Builder.view.ts +0 -358
  183. package/src/model/LikeC4DiagramModel.ts +0 -338
  184. package/src/model/LikeC4ViewModel.ts +0 -320
package/src/utils/fqn.ts CHANGED
@@ -1,11 +1,23 @@
1
- import type { LiteralUnion } from 'type-fest'
2
- import type { Element, Fqn } from '../types'
1
+ import type { Fqn } from '../types'
3
2
  import { compareNatural } from './compare-natural'
4
3
  import { isString } from './guards'
5
4
 
6
- type Predicate<T> = (x: T) => boolean
5
+ export type Predicate<T> = (x: T) => boolean
7
6
 
8
- export function nameFromFqn(fqn: LiteralUnion<Fqn, string>) {
7
+ export function parentFqn<E extends string>(fqn: E): E | null {
8
+ const lastDot = fqn.lastIndexOf('.')
9
+ if (lastDot > 0) {
10
+ return fqn.slice(0, lastDot) as E
11
+ }
12
+ return null
13
+ }
14
+
15
+ export function parentFqnPredicate<T extends { parent: Fqn | null }>(parent: Fqn): Predicate<T> {
16
+ const prefix = parent + '.'
17
+ return (e: T) => !!e.parent && (e.parent === parent || e.parent.startsWith(prefix))
18
+ }
19
+
20
+ export function nameFromFqn<E extends string>(fqn: E): string {
9
21
  const lastDot = fqn.lastIndexOf('.')
10
22
  if (lastDot > 0) {
11
23
  return fqn.slice(lastDot + 1)
@@ -14,71 +26,123 @@ export function nameFromFqn(fqn: LiteralUnion<Fqn, string>) {
14
26
  }
15
27
  }
16
28
 
17
- export function isAncestor<E extends { id: Fqn }>(
18
- ...args: [ancestor: string, another: string] | [ancestor: E, another: E]
29
+ const asString: <E extends string | { id: string }>(e: E) => string = e => (isString(e) ? e : e.id)
30
+
31
+ /**
32
+ * Check if one element is an ancestor of another
33
+ * Composable version
34
+ * @signature
35
+ * isAncestor(another)(ancestor)
36
+ */
37
+ export function isAncestor<A extends string | { id: string }>(another: NoInfer<A>): (ancestor: A) => boolean
38
+ /**
39
+ * Check if one element is an ancestor of another
40
+ * @signature
41
+ * isAncestor(ancestor, another)
42
+ */
43
+ export function isAncestor<A extends string | { id: string }>(ancestor: A, another: A): boolean
44
+
45
+ export function isAncestor<T extends string>(
46
+ arg1: T | WithId<T>,
47
+ arg2?: NoInfer<T> | WithId<NoInfer<T>>,
19
48
  ) {
20
- const ancestor = isString(args[0]) ? args[0] : args[0].id
21
- const another = isString(args[1]) ? args[1] : args[1].id
22
- return another.startsWith(ancestor + '.')
49
+ const arg1Id = asString(arg1)
50
+ if (arg2) {
51
+ const arg2Id = asString(arg2)
52
+ return arg2Id.startsWith(arg1Id + '.')
53
+ }
54
+ return (ancestor: T | WithId<T>) => {
55
+ const ancestorId = asString(ancestor)
56
+ return arg1Id.startsWith(ancestorId + '.')
57
+ }
23
58
  }
24
59
 
25
- export function isSameHierarchy<E extends { id: Fqn }>(one: E | Fqn, another: E | Fqn) {
26
- const first = isString(one) ? one : one.id
27
- const second = isString(another) ? another : another.id
60
+ export function isSameHierarchy<T extends string>(
61
+ one: NoInfer<T> | WithId<NoInfer<T>>,
62
+ ): (another: T | WithId<T>) => boolean
63
+ export function isSameHierarchy<T extends string>(one: T | WithId<T>, another: T | WithId<T>): boolean
64
+ export function isSameHierarchy<T extends string>(one: T | WithId<T>, another?: T | WithId<T>) {
65
+ if (!another) {
66
+ return (b: T | WithId<T>) => isSameHierarchy(one, b)
67
+ }
68
+ const first = asString(one)
69
+ const second = asString(another)
28
70
  return first === second || second.startsWith(first + '.') || first.startsWith(second + '.')
29
71
  }
30
72
 
31
- export function isDescendantOf<E extends { id: Fqn }>(ancestors: E[]): (e: E) => boolean {
32
- const predicates = ancestors.flatMap(a => [(e: E) => e.id === a.id, (e: E) => isAncestor(a, e)])
33
- return (e: E) => predicates.some(p => p(e))
73
+ type WithId<T> = { id: T }
74
+
75
+ export function isDescendantOf<T extends string>(ancestor: WithId<T>): (descedant: WithId<T>) => boolean
76
+ export function isDescendantOf<T extends string>(descedant: WithId<T>, ancestor: WithId<T>): boolean
77
+ export function isDescendantOf<T extends string>(descedant: WithId<T>, ancestor?: WithId<T>) {
78
+ if (!ancestor) {
79
+ return (d: WithId<T>) => isAncestor(descedant, d)
80
+ }
81
+ return isAncestor(ancestor, descedant)
34
82
  }
35
83
 
36
- export function notDescendantOf(ancestors: Element[]): (e: Element) => boolean {
37
- const isDescendant = isDescendantOf(ancestors)
38
- return (e: Element) => !isDescendant(e)
84
+ /**
85
+ * How deep in the hierarchy the element is.
86
+ * Root element has depth 1
87
+ */
88
+ export function hierarchyLevel<E extends string | { id: Fqn }>(elementOfFqn: E): number {
89
+ const first = isString(elementOfFqn) ? elementOfFqn : elementOfFqn.id
90
+ return first.split('.').length
39
91
  }
40
92
 
41
- export function commonAncestor<IDs extends string>(first: Fqn<IDs>, second: Fqn<IDs>) {
42
- const parentA = parentFqn(first)
43
- const parentB = parentFqn(second)
44
- if (parentA === parentB) {
45
- return parentA
46
- }
47
- if (!parentA || !parentB) {
48
- return null
93
+ /**
94
+ * Calculate the distance as number of steps from one element to another, i.e.
95
+ * going up to the common ancestor, then going down to the other element.
96
+ * Sibling distance is always 1
97
+ *
98
+ * Can be used for hierarchical clustering
99
+ */
100
+ export function hierarchyDistance<E extends string | { id: Fqn }>(one: E, another: E): number {
101
+ const first = isString(one) ? one as Fqn : one.id
102
+ const second = isString(another) ? another as Fqn : another.id
103
+
104
+ if (first === second) {
105
+ return 0
49
106
  }
50
107
 
51
- const a = first.split('.')
52
- const b = second.split('.')
53
- let ancestor: Fqn<IDs> | null = null
108
+ const firstDepth = hierarchyLevel(first)
109
+ const secondDepth = hierarchyLevel(second)
54
110
 
55
- while (a.length > 1 && b.length > 1 && !!a[0] && a[0] === b[0]) {
56
- ancestor = (ancestor ? `${ancestor}.${a[0]}` : a[0]) as Fqn<IDs>
57
- a.shift()
58
- b.shift()
111
+ if (isSameHierarchy(first, second)) {
112
+ return Math.abs(firstDepth - secondDepth)
59
113
  }
60
- return ancestor
61
- }
62
114
 
63
- export function parentFqn<IDs extends string>(fqn: Fqn<IDs>): Fqn<IDs> | null {
64
- const lastDot = fqn.lastIndexOf('.')
65
- if (lastDot > 0) {
66
- return fqn.slice(0, lastDot) as Fqn<IDs>
67
- }
68
- return null
115
+ const ancestor = commonAncestor(first as Fqn, second as Fqn)
116
+ const ancestorDepth = ancestor ? hierarchyLevel(ancestor) : 0
117
+
118
+ return firstDepth + secondDepth - (2 * ancestorDepth + 1)
69
119
  }
70
120
 
71
- export function parentFqnPredicate<T extends { parent: Fqn | null }>(parent: Fqn): Predicate<T> {
72
- const prefix = parent + '.'
73
- return (e: T) => !!e.parent && (e.parent === parent || e.parent.startsWith(prefix))
121
+ export function commonAncestor<E extends string>(first: E, second: E): E | null {
122
+ const a = first.split('.')
123
+ if (a.length < 2) {
124
+ return null
125
+ }
126
+ const b = second.split('.')
127
+ if (b.length < 2) {
128
+ return null
129
+ }
130
+ let ancestor = [] as string[]
131
+ for (let i = 0; i < Math.min(a.length, b.length) - 1 && a[i] === b[i]; i++) {
132
+ ancestor.push(a[i]!)
133
+ }
134
+ if (ancestor.length === 0) {
135
+ return null
136
+ }
137
+ return ancestor.join('.') as E
74
138
  }
75
139
 
76
140
  /**
77
141
  * Get all ancestor elements (i.e. parent, parent’s parent, etc.)
78
- * (from closest to root)
142
+ * going up from parent to the root
79
143
  */
80
- export function ancestorsFqn<IDs extends string>(fqn: Fqn<IDs>): Fqn<IDs>[] {
81
- const path = fqn.split('.') as Fqn<IDs>[]
144
+ export function ancestorsFqn<Id extends string>(fqn: Id): Id[] {
145
+ const path = fqn.split('.') as Id[]
82
146
  path.pop()
83
147
  if (path.length === 0) {
84
148
  return []
@@ -88,9 +152,9 @@ export function ancestorsFqn<IDs extends string>(fqn: Fqn<IDs>): Fqn<IDs>[] {
88
152
  acc.push(part)
89
153
  return acc
90
154
  }
91
- acc.unshift(`${acc[0]}.${part}` as Fqn<IDs>)
155
+ acc.unshift(`${acc[0]}.${part}` as Id)
92
156
  return acc
93
- }, [] as Fqn<IDs>[])
157
+ }, [] as Id[])
94
158
  }
95
159
 
96
160
  /**
@@ -119,17 +183,42 @@ export function compareFqnHierarchically<T extends string = string>(a: T, b: T):
119
183
  }
120
184
  }
121
185
 
122
- export function compareByFqnHierarchically<T extends { id: string }>(a: T, b: T) {
186
+ export function compareByFqnHierarchically<T extends { id: string }>(a: T, b: T): -1 | 0 | 1 {
123
187
  return compareFqnHierarchically(a.id, b.id)
124
188
  }
125
189
 
126
- type IterableContainer<T = unknown> = ReadonlyArray<T> | readonly []
127
- type ReorderedArray<T extends IterableContainer> = {
190
+ export type IterableContainer<T = unknown> = ReadonlyArray<T> | readonly []
191
+ export type ReorderedArray<T extends IterableContainer> = {
128
192
  -readonly [P in keyof T]: T[number]
129
193
  }
130
194
 
195
+ /**
196
+ * Sorts an array of objects hierarchically based on their fully qualified names (FQN).
197
+ * Objects are sorted by the number of segments in their FQN (defined by dot-separated ID).
198
+ *
199
+ * @typeParam T - Object type that contains an 'id' string property
200
+ * @typeParam A - Type extending IterableContainer of T
201
+ *
202
+ * @param array - Array of objects to be sorted
203
+ * @returns A new array with items sorted by their FQN hierarchy depth (number of segments)
204
+ *
205
+ * @example
206
+ * ```ts
207
+ * const items = [
208
+ * { id: "a.b.c" },
209
+ * { id: "a" },
210
+ * { id: "a.b" }
211
+ * ];
212
+ * sortByFqnHierarchically(items);
213
+ * // Result: [
214
+ * // { id: "a" },
215
+ * // { id: "a.b" },
216
+ * // { id: "a.b.c" }
217
+ * // ]
218
+ * ```
219
+ */
131
220
  export function sortByFqnHierarchically<T extends { id: string }, A extends IterableContainer<T>>(
132
- array: A
221
+ array: A,
133
222
  ): ReorderedArray<A> {
134
223
  return array
135
224
  .map(item => ({ item, fqn: item.id.split('.') }))
@@ -139,14 +228,72 @@ export function sortByFqnHierarchically<T extends { id: string }, A extends Iter
139
228
  .map(({ item }) => item) as ReorderedArray<A>
140
229
  }
141
230
 
142
- export function sortNaturalByFqn<T extends { id: string }, A extends IterableContainer<T>>(
143
- array: A
231
+ function findTopAncestor<T extends { id: string }>(items: T[], item: T): T | null {
232
+ let parent = item
233
+ for (const e of items) {
234
+ if (isAncestor(e, parent)) {
235
+ parent = e
236
+ }
237
+ }
238
+ return parent !== item ? parent : null
239
+ }
240
+
241
+ /**
242
+ * Keeps initial order of the elements, but ensures that parents are before children
243
+ */
244
+ export function sortParentsFirst<T extends { id: string }, A extends IterableContainer<T>>(
245
+ array: A,
144
246
  ): ReorderedArray<A> {
247
+ const result = [] as T[]
248
+ const items = [...array]
249
+ let item
250
+ while ((item = items.shift())) {
251
+ let parent
252
+ while ((parent = findTopAncestor(items, item))) {
253
+ result.push(items.splice(items.indexOf(parent), 1)[0]!)
254
+ }
255
+ result.push(item)
256
+ }
257
+ return result as ReorderedArray<A>
258
+ }
259
+
260
+ /**
261
+ * Sorts an array of objects naturally by their fully qualified name (FQN) identifier.
262
+ *
263
+ * @template T - Type of objects containing an 'id' string property
264
+ * @template A - Type extending IterableContainer of T
265
+ *
266
+ * @param first - Optional. Either the array to sort or the sort direction ('asc'|'desc')
267
+ * @param sort - Optional. The sort direction ('asc'|'desc'). Defaults to 'asc' if not specified
268
+ *
269
+ * @example
270
+ * // As a function that returns a sorting function
271
+ * const sorted = sortNaturalByFqn('desc')(myArray);
272
+ *
273
+ * // Direct array sorting
274
+ * const sorted = sortNaturalByFqn(myArray, 'desc');
275
+ */
276
+ export function sortNaturalByFqn(
277
+ sort?: 'asc' | 'desc',
278
+ ): <T extends { id: string }, A extends IterableContainer<T>>(array: A) => ReorderedArray<A>
279
+ export function sortNaturalByFqn<T extends { id: string }, A extends IterableContainer<T>>(
280
+ array: A,
281
+ sort?: 'asc' | 'desc',
282
+ ): ReorderedArray<A>
283
+ export function sortNaturalByFqn<T extends { id: string }, A extends IterableContainer<T>>(
284
+ array?: A | 'asc' | 'desc',
285
+ sort?: 'asc' | 'desc',
286
+ ) {
287
+ if (!array || isString(array)) {
288
+ const dir = array ?? 'asc'
289
+ return (arr: A) => sortNaturalByFqn(arr, dir)
290
+ }
291
+ const dir = sort === 'desc' ? -1 : 1
145
292
  return array
146
293
  .map(item => ({ item, fqn: item.id.split('.') }))
147
294
  .sort((a, b) => {
148
295
  if (a.fqn.length !== b.fqn.length) {
149
- return a.fqn.length - b.fqn.length
296
+ return (a.fqn.length - b.fqn.length) * dir
150
297
  }
151
298
  for (let i = 0; i < a.fqn.length; i++) {
152
299
  const compare = compareNatural(a.fqn[i], b.fqn[i])
@@ -0,0 +1,42 @@
1
+ // export function getOrCreate<K, V>(map: Map<K, V>): Map<K, V> & {
2
+ // get(key: K, create: (key: K) => V): V
3
+ // get(key: K): V | undefined
4
+ // }
5
+ // export function getOrCreate<K, V>(map: Map<K, V>, key: K, create: (key: K) => V): V
6
+ // export function getOrCreate<K, V>(map: Map<K, V>, key?: K, create?: (key: K) => V) {
7
+ export function getOrCreate<K, V>(map: Map<K, V>, key: K, create: (key: K) => V): V {
8
+ let entry = map.get(key)
9
+ if (!entry) {
10
+ entry = create(key)
11
+ map.set(key, entry)
12
+ }
13
+ return entry
14
+ }
15
+
16
+ // export class BetterMap<K, V> extends Map<K, V> {
17
+ // constructor(iterable?: Iterable<readonly [K, V]> | null) {
18
+ // super(iterable)
19
+ // }
20
+
21
+ // /**
22
+ // * Returns the element associated with the specified key.
23
+ // * If no element is associated with the specified key, and a create function is provided, a new element will be created and stored.
24
+ // * @example
25
+ // * ```ts
26
+ // * const map = new BetterMap<Fqn, Element>()
27
+ // *
28
+ // * map.get(fqn, () => factory.createElement(fqn))
29
+ // *
30
+ // */
31
+ // override get(key: K, create: (key: K) => V): V
32
+ // override get(key: K): V | undefined
33
+ // override get(key: K, create?: (key: K) => V) {
34
+ // const entry = super.get(key)
35
+ // if (!entry && create) {
36
+ // const newEntry = create(key)
37
+ // this.set(key, newEntry)
38
+ // return newEntry
39
+ // }
40
+ // return entry
41
+ // }
42
+ // }
@@ -0,0 +1,10 @@
1
+ import dagre from '@dagrejs/dagre'
2
+
3
+ const Graph = dagre.graphlib.Graph
4
+
5
+ export { Graph }
6
+
7
+ export const postorder = dagre.graphlib.alg.postorder
8
+ export const preorder = dagre.graphlib.alg.preorder
9
+ export const findCycles = dagre.graphlib.alg.findCycles
10
+ export const isAcyclic = dagre.graphlib.alg.isAcyclic
@@ -1,6 +1,74 @@
1
- export * from './commonHead'
2
- export * from './compare-natural'
3
- export * from './fqn'
4
- export * from './guards'
5
- export * from './promises'
6
- export * from './relations'
1
+ import DefaultMap from 'mnemonist/default-map'
2
+ import LinkedList from 'mnemonist/linked-list'
3
+
4
+ export { DefaultMap, LinkedList }
5
+
6
+ export {
7
+ commonHead,
8
+ } from './common-head'
9
+
10
+ export {
11
+ compareNatural,
12
+ } from './compare-natural'
13
+ export {
14
+ ancestorsFqn,
15
+ commonAncestor,
16
+ compareByFqnHierarchically,
17
+ compareFqnHierarchically,
18
+ hierarchyDistance,
19
+ hierarchyLevel,
20
+ isAncestor,
21
+ isDescendantOf,
22
+ isSameHierarchy,
23
+ type IterableContainer,
24
+ nameFromFqn,
25
+ parentFqn,
26
+ type ReorderedArray,
27
+ sortByFqnHierarchically,
28
+ sortNaturalByFqn,
29
+ sortParentsFirst,
30
+ } from './fqn'
31
+
32
+ export {
33
+ getOrCreate,
34
+ } from './getOrCreate'
35
+
36
+ export {
37
+ hasAtLeast,
38
+ isNonEmptyArray,
39
+ isString,
40
+ } from './guards'
41
+ export {
42
+ ifilter,
43
+ iflat,
44
+ imap,
45
+ ireduce,
46
+ isIterable,
47
+ isome,
48
+ iunique,
49
+ toArray,
50
+ toSet,
51
+ } from './iterable'
52
+ export {
53
+ delay,
54
+ } from './promises'
55
+
56
+ export {
57
+ compareRelations,
58
+ } from './relations'
59
+
60
+ export {
61
+ difference,
62
+ equals as equalsSet,
63
+ intersection,
64
+ symmetricDifference,
65
+ union,
66
+ } from './set'
67
+
68
+ export {
69
+ stringHash,
70
+ } from './string-hash'
71
+
72
+ export {
73
+ objectHash,
74
+ } from './object-hash'
@@ -0,0 +1,5 @@
1
+ import { isNonNullish } from 'remeda'
2
+
3
+ export function isIterable<T, I extends Iterable<T>>(something: I | unknown): something is Iterable<T> {
4
+ return isNonNullish(something) && typeof something === 'object' && Symbol.iterator in something
5
+ }
@@ -0,0 +1,55 @@
1
+ import { isFunction } from 'remeda'
2
+ import { invariant } from '../../errors'
3
+ import type { IteratorLike } from '../../types'
4
+
5
+ /**
6
+ * Filters an iterable based on a predicate.
7
+ * Composabel first version of `ifilter`.
8
+ * @signature
9
+ * ifilter(predicate)(data)
10
+ */
11
+ export function ifilter<T, S extends T>(
12
+ predicate: (v: T) => v is S,
13
+ ): (iterable: Iterable<T>) => IteratorLike<S>
14
+ export function ifilter<T>(
15
+ predicate: (v: T) => boolean,
16
+ ): (iterable: Iterable<T>) => IteratorLike<T>
17
+
18
+ /**
19
+ * Filters an iterable based on a predicate.
20
+ * Data first version of `ifilter`.
21
+ * @signature
22
+ * ifilter(data, predicate)
23
+ * @example
24
+ * ifilter(new Set([1, 2, 3]), x => x % 2 === 1) // => Iterable<[1, 3]>
25
+ */
26
+ export function ifilter<T, S extends T>(
27
+ iterable: Iterable<T>,
28
+ predicate: (v: T) => v is S,
29
+ ): IteratorLike<S>
30
+ export function ifilter<T>(
31
+ iterable: Iterable<T>,
32
+ predicate: (v: T) => boolean,
33
+ ): IteratorLike<T>
34
+
35
+ export function ifilter(
36
+ arg1: unknown,
37
+ arg2?: unknown,
38
+ ): IteratorLike<unknown> | ((it: Iterable<unknown>) => IteratorLike<unknown>) {
39
+ const pred = (arg2 ?? arg1) as (v: any) => boolean
40
+ invariant(isFunction(pred))
41
+
42
+ function* _filter(iter: Iterable<unknown>): IteratorLike<unknown> {
43
+ for (const value of iter) {
44
+ if (pred(value)) {
45
+ yield value
46
+ }
47
+ }
48
+ return
49
+ }
50
+
51
+ if (!arg2) {
52
+ return _filter
53
+ }
54
+ return _filter(arg1 as Iterable<unknown>)
55
+ }
@@ -0,0 +1,17 @@
1
+ import type { IteratorLike } from '../../types'
2
+
3
+ export function iflat(): <T>(iterable: Iterable<IteratorLike<T>>) => IteratorLike<T>
4
+ export function iflat<T>(iterable: Iterable<IteratorLike<T>>): IteratorLike<T>
5
+
6
+ export function iflat<T>(
7
+ iterable?: Iterable<IteratorLike<T>>,
8
+ ): IteratorLike<T> | ((iterable: Iterable<IteratorLike<T>>) => IteratorLike<T>) {
9
+ return iterable ? _iflat(iterable) : _iflat
10
+ }
11
+
12
+ function* _iflat<T>(iterable: Iterable<IteratorLike<T>>): IteratorLike<T> {
13
+ for (const inner of iterable) {
14
+ yield* inner
15
+ }
16
+ return
17
+ }
@@ -0,0 +1,12 @@
1
+ export { isIterable } from './_types'
2
+
3
+ /**
4
+ * operators
5
+ */
6
+ export { ifilter } from './filter'
7
+ export { iflat } from './flat'
8
+ export { imap } from './map'
9
+ export { ireduce } from './reduce'
10
+ export { isome } from './some'
11
+ export { toArray, toSet } from './to'
12
+ export { iunique } from './unique'
@@ -0,0 +1,40 @@
1
+ import { isFunction } from 'remeda'
2
+ import { invariant } from '../../errors'
3
+ import type { IteratorLike } from '../../types'
4
+
5
+ type MapFunction<T, S> = (item: T) => S
6
+
7
+ /**
8
+ * Maps an iterable using a mapper function.
9
+ * Composable first version of `imap`.
10
+ * @signature
11
+ * imap(mapper)(data)
12
+ */
13
+ export function imap<T, S>(mapper: MapFunction<T, S>): (iterable: Iterable<T>) => IteratorLike<S>
14
+ /**
15
+ * Maps an iterable using a mapper function.
16
+ * Data first version of `imap`.
17
+ * @signature
18
+ * imap(data, mapper)
19
+ */
20
+ export function imap<T, S>(iterable: Iterable<T>, mapper: MapFunction<T, S>): IteratorLike<S>
21
+
22
+ export function imap(
23
+ arg1: unknown,
24
+ arg2?: unknown,
25
+ ): IteratorLike<unknown> | ((it: Iterable<unknown>) => IteratorLike<unknown>) {
26
+ const mapper = (arg2 ?? arg1) as MapFunction<any, any>
27
+ invariant(isFunction(mapper))
28
+
29
+ function* _map(iter: Iterable<unknown>): IteratorLike<unknown> {
30
+ for (const value of iter) {
31
+ yield mapper(value)
32
+ }
33
+ return
34
+ }
35
+
36
+ if (!arg2) {
37
+ return _map
38
+ }
39
+ return _map(arg1 as Iterable<unknown>)
40
+ }
@@ -0,0 +1,24 @@
1
+ import { isDefined, isFunction } from 'remeda'
2
+ import { invariant } from '../../errors'
3
+
4
+ export function ireduce<T, R>(reducer: (acc: R, item: T) => R, initialValue: R): (iterable: Iterable<T>) => R
5
+ export function ireduce<T, R>(iterable: Iterable<T>, reducer: (acc: R, item: T) => R, initialValue: R): R
6
+ export function ireduce<T, R>(
7
+ arg1: unknown,
8
+ arg2: unknown,
9
+ arg3?: unknown,
10
+ ): R | ((iterable: Iterable<T>) => R) {
11
+ const reducer = (isDefined(arg3) ? arg2 : arg1) as (acc: R, item: T) => R
12
+ const initialValue = arg3 ?? arg2
13
+ invariant(isFunction(reducer))
14
+
15
+ function _reduce(iter: Iterable<T>): R {
16
+ let acc = initialValue as R
17
+ for (const value of iter) {
18
+ acc = reducer(acc, value)
19
+ }
20
+ return acc
21
+ }
22
+
23
+ return isDefined(arg3) ? _reduce(arg1 as Iterable<T>) : _reduce
24
+ }
@@ -0,0 +1,36 @@
1
+ import { isFunction } from 'remeda'
2
+ import { invariant } from '../../errors'
3
+
4
+ /**
5
+ * Checks if at least one element in the iterable satisfies the predicate.
6
+ * Composable first version of `some`.
7
+ * @signature
8
+ * isome(predicate)(data)
9
+ */
10
+ export function isome<T>(predicate: (item: T) => boolean): (iterable: Iterable<T>) => boolean
11
+ /**
12
+ * Checks if at least one element in the iterable satisfies the predicate.
13
+ * Data first version of `some`.
14
+ * @signature
15
+ * isome(data, predicate)
16
+ */
17
+ export function isome<T>(iterable: Iterable<T>, predicate: (item: T) => boolean): boolean
18
+
19
+ export function isome<T>(arg1: unknown, arg2?: unknown): boolean | ((iterable: Iterable<T>) => boolean) {
20
+ const pred = (arg2 ?? arg1) as (v: any) => boolean
21
+ invariant(isFunction(pred))
22
+
23
+ function _some(iter: Iterable<unknown>): boolean {
24
+ for (const value of iter) {
25
+ if (pred(value)) {
26
+ return true
27
+ }
28
+ }
29
+ return false
30
+ }
31
+
32
+ if (!arg2) {
33
+ return _some
34
+ }
35
+ return _some(arg1 as Iterable<unknown>)
36
+ }
@@ -0,0 +1,19 @@
1
+ export function toArray(): <T>(iterable: Iterable<T> | ArrayLike<T>) => T[]
2
+ export function toArray<T>(iterable: Iterable<T> | ArrayLike<T>): T[]
3
+ export function toArray<T>(
4
+ iterable?: Iterable<T> | ArrayLike<T>
5
+ ): T[] | (<T>(iterable: Iterable<T> | ArrayLike<T>) => T[]) {
6
+ if (iterable) {
7
+ return Array.from(iterable)
8
+ }
9
+ return (it) => Array.from(it)
10
+ }
11
+
12
+ export function toSet(): <T>(iterable: Iterable<T>) => Set<T>
13
+ export function toSet<T>(iterable: Iterable<T>): Set<T>
14
+ export function toSet<T>(iterable?: Iterable<T>): Set<T> | ((iterable: Iterable<T>) => Set<T>) {
15
+ if (iterable) {
16
+ return new Set(iterable)
17
+ }
18
+ return (it) => new Set(it)
19
+ }