-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdependency-graph-resolver.ts
More file actions
86 lines (70 loc) · 2.42 KB
/
Copy pathdependency-graph-resolver.ts
File metadata and controls
86 lines (70 loc) · 2.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
class Node<T> {
id: string;
val: T;
indegree: number;
dependencies: Node<T>[];
constructor(id: string, val: T) {
this.id = id;
this.val = val;
this.indegree = 0;
this.dependencies = [];
}
}
export class DependencyGraphResolver {
/**
* @return a dependency graph in the form of an adjacency list
*/
static calculateDependencyList<T>(vals: T[], getId: (t: T) => string, getDependencyIds: (t: T) => string[]): string[] {
if (vals.length === 0) {
return [];
}
const nodes = vals.map((r) => new Node(getId(r), r));
const nodeMap = new Map(nodes.map((n) => [n.id, n] as const));
this.populateNodeDependencies(nodeMap, getDependencyIds);
this.populateNodeIndegrees(nodeMap);
const zeroIndegressNodes = nodes.filter((n) => n.indegree === 0);
if (zeroIndegressNodes.length === 0) {
throw new Error('Cyclic dependency found in configs. No resources is found that is not referenced');
}
const queue: Node<T>[] = [];
const result: Node<T>[] = [];
queue.push(...zeroIndegressNodes);
while (queue.length > 0) {
const node = queue.shift();
if (!node) {
throw new Error('Internal error: Undefined node found while resolving dependency graph');
}
node.dependencies.forEach((parentNode) => {
if (--parentNode.indegree === 0) {
queue.push(parentNode);
}
});
result.unshift(node);
}
if (result.length !== vals.length) {
const cyclicItems = vals.filter((n) => !result.some((r) => r.id === getId(n)));
throw new Error(`Cyclic dependency found in configs. Ids: [${cyclicItems.map((i) => getId(i)).join(', ')}]`);
}
return result.map((n) => n.id);
}
private static populateNodeDependencies<T>(nodeMap: Map<string, Node<T>>, getDependencyIds: (t: T) => string[]) {
[...nodeMap.values()].forEach((n) => {
const dependencies = getDependencyIds(n.val)
.map((id) => {
const node = nodeMap.get(id);
if (!node) {
throw new Error(`Internal error: Node of id ${id} does not exist when resolving dependency graph`);
}
return node;
});
n.dependencies.push(...dependencies);
})
}
private static populateNodeIndegrees<T>(nodeMap: Map<string, Node<T>>) {
for (const node of nodeMap.values()) {
for (const dependentNode of node.dependencies) {
dependentNode.indegree++;
}
}
}
}