|
|
|
"use strict";
|
|
|
|
|
|
|
|
const util = require("util");
|
|
|
|
|
|
|
|
const CycleError = require("./cycle-error");
|
|
|
|
|
|
|
|
// TODO: Publish this as a stand-alone module
|
|
|
|
|
|
|
|
module.exports = function kahn(nodes) {
|
|
|
|
let queue = [];
|
|
|
|
let list = [];
|
|
|
|
|
|
|
|
// Start with all the zero-dependency nodes
|
|
|
|
for (let node of nodes) {
|
|
|
|
if (node.parents.length === 0) {
|
|
|
|
queue.push(node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
while (queue.length > 0) {
|
|
|
|
let current = queue.shift();
|
|
|
|
|
|
|
|
list.push(current);
|
|
|
|
|
|
|
|
// For each dependent/parent of this item
|
|
|
|
for (let child of current.children) {
|
|
|
|
child.parents.splice(child.parents.indexOf(current), 1);
|
|
|
|
|
|
|
|
// If all its dependencies have been fully processed and removed
|
|
|
|
if (child.parents.length === 0) {
|
|
|
|
queue.push(child);
|
|
|
|
} else {
|
|
|
|
// TODO: Add a proper loop checker!
|
|
|
|
if (child.children.some((item) => item === child) || child.parents.some((item) => item === child)) {
|
|
|
|
throw new Error(`Dependency on self encountered: ${util.inspect(child)}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (list.length !== nodes.length) {
|
|
|
|
let processedNodes = new Set(list);
|
|
|
|
let affectedNodes = nodes.filter((node) => !processedNodes.has(node));
|
|
|
|
|
|
|
|
throw new CycleError(`One or more cycles were detected, involving ${affectedNodes.length} nodes`, {
|
|
|
|
affectedNodes: affectedNodes
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return list;
|
|
|
|
};
|