-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.js
121 lines (110 loc) · 3.57 KB
/
index.js
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
var through = require('through');
var _ = require('lodash');
var Promise = require('es6-promise').Promise;
var denodeify = require('es6-denodeify')(Promise);
/**
nodeResolver maps Objects read from a stream to Node Objects
Node has properties
id: Any (required)
deps: Array[id] (optional)
*/
var topsort = function (nodeResolver) {
var nodes = new Nodes(nodeResolver);
var pending = {};
var nodeCount = 0;
var stream = through (
function (data) {
var count = nodeCount;
nodeCount = nodeCount + 1;
pending[count] = nodes.handleNode(data)
.then(function (resolved) {
delete pending[count];
resolved.forEach(stream.queue, stream);
}).catch(function (reason) {
stream.emit('topsort-error:resolving-node', reason);
});
},
function() {
Promise.all(_.values(pending)).then(function() {
nodes.end(stream);
stream.queue(null);
});
}
);
return stream;
};
function Nodes (nodeResolver) {
this.nodes = {};
if (nodeResolver.length > 1) {
this.nodeResolver = denodeify(nodeResolver);
} else {
this.nodeResolver = function (data) {
return new Promise(function (resolve) {
return resolve(nodeResolver(data));
});
};
}
}
Nodes.prototype = {
handleNode: function (data) {
var scope = this;
return this.nodeResolver(data)
.then(function (nodeInfo) {
nodeInfo = nodeInfo || {}
if (_.isUndefined(nodeInfo.id)) {
nodeInfo.id = data;
}
if (_.isUndefined(nodeInfo.deps)) {
nodeInfo.deps = [];
}
var node = scope.registerNode(nodeInfo, data);
return scope.getResolvedNodes(node).map(function (node) {
return node.data;
});
});
},
registerNode: function (nodeInfo, data) {
var node = this.getNode(nodeInfo.id);
node.data = data;
node.dependencies = nodeInfo.deps.map(this.getNode, this) || [];
node.dependencies.forEach(function (dep) {
if (!dep.resolved) {
dep.dependants.push(node);
}
});
return node;
},
getResolvedNodes: function (node) {
var dependencies = node.dependencies;
var resolvedNodes = [];
var canResolve = _(node.dependencies).every(function (dep) {
return dep.resolved;
});
if (canResolve) {
node.resolved = true;
resolvedNodes.push(node);
var dependants = node.dependants;
this.deregisterNode(node);
dependants.forEach(function (dependant) {
resolvedNodes.push.apply(resolvedNodes, this.getResolvedNodes(dependant));
}, this);
}
return resolvedNodes;
},
getNode: function (id) {
return (this.nodes[id] = this.nodes[id] || { id: id, dependants: [] });
},
deregisterNode: function (node) {
delete node.dependencies;
delete node.dependants;
},
end: function (stream) {
var nodesNotResolved = _.filter(this.nodes, function (node) {
return !node.resolved;
});
if (nodesNotResolved.length > 0) {
stream.emit('topsort-error:unresolved-nodes', _.pluck(nodesNotResolved, 'id'));
}
}
};
module.exports = topsort;