@atlaspack/graph 3.5.19 → 3.5.21-dev-ts-project-refs-d30e9754f.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.
- package/CHANGELOG.md +10 -0
- package/LICENSE +201 -0
- package/dist/AdjacencyList.js +1348 -0
- package/dist/BitSet.js +108 -0
- package/dist/ContentGraph.js +70 -0
- package/dist/Graph.js +498 -0
- package/dist/index.js +17 -0
- package/dist/shared-buffer.js +24 -0
- package/dist/types.js +10 -0
- package/package.json +7 -7
- package/tsconfig.json +16 -2
- package/tsconfig.tsbuildinfo +1 -0
package/dist/BitSet.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BitSet = void 0;
|
|
4
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32#implementing_count_leading_ones_and_beyond
|
|
5
|
+
function ctz32(n) {
|
|
6
|
+
if (n === 0) {
|
|
7
|
+
return 32;
|
|
8
|
+
}
|
|
9
|
+
let reversed = n & -n;
|
|
10
|
+
return 31 - Math.clz32(reversed);
|
|
11
|
+
}
|
|
12
|
+
class BitSet {
|
|
13
|
+
constructor(maxBits) {
|
|
14
|
+
this.bits = new Uint32Array(Math.ceil(maxBits / 32));
|
|
15
|
+
}
|
|
16
|
+
clone() {
|
|
17
|
+
let res = new BitSet(this.capacity);
|
|
18
|
+
res.bits.set(this.bits);
|
|
19
|
+
return res;
|
|
20
|
+
}
|
|
21
|
+
static union(a, b) {
|
|
22
|
+
let res = a.clone();
|
|
23
|
+
res.union(b);
|
|
24
|
+
return res;
|
|
25
|
+
}
|
|
26
|
+
static intersect(a, b) {
|
|
27
|
+
let res = a.clone();
|
|
28
|
+
res.intersect(b);
|
|
29
|
+
return res;
|
|
30
|
+
}
|
|
31
|
+
get capacity() {
|
|
32
|
+
return this.bits.length * 32;
|
|
33
|
+
}
|
|
34
|
+
add(bit) {
|
|
35
|
+
let i = bit >>> 5;
|
|
36
|
+
let b = bit & 31;
|
|
37
|
+
this.bits[i] |= 1 << b;
|
|
38
|
+
}
|
|
39
|
+
delete(bit) {
|
|
40
|
+
let i = bit >>> 5;
|
|
41
|
+
let b = bit & 31;
|
|
42
|
+
this.bits[i] &= ~(1 << b);
|
|
43
|
+
}
|
|
44
|
+
has(bit) {
|
|
45
|
+
let i = bit >>> 5;
|
|
46
|
+
let b = bit & 31;
|
|
47
|
+
return Boolean(this.bits[i] & (1 << b));
|
|
48
|
+
}
|
|
49
|
+
empty() {
|
|
50
|
+
for (let k = 0; k < this.bits.length; k++) {
|
|
51
|
+
if (this.bits[k] !== 0) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
clear() {
|
|
58
|
+
this.bits.fill(0);
|
|
59
|
+
}
|
|
60
|
+
intersect(other) {
|
|
61
|
+
for (let i = 0; i < this.bits.length; i++) {
|
|
62
|
+
this.bits[i] &= other.bits[i];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
union(other) {
|
|
66
|
+
for (let i = 0; i < this.bits.length; i++) {
|
|
67
|
+
this.bits[i] |= other.bits[i];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
remove(other) {
|
|
71
|
+
for (let i = 0; i < this.bits.length; i++) {
|
|
72
|
+
this.bits[i] &= ~other.bits[i];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
size() {
|
|
76
|
+
let bits = this.bits;
|
|
77
|
+
let setBitsCount = 0;
|
|
78
|
+
for (let k = 0; k < bits.length; k++) {
|
|
79
|
+
let chunk = bits[k];
|
|
80
|
+
while (chunk !== 0) {
|
|
81
|
+
chunk &= chunk - 1; // Clear the least significant bit set
|
|
82
|
+
setBitsCount++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return setBitsCount;
|
|
86
|
+
}
|
|
87
|
+
equals(other) {
|
|
88
|
+
for (let i = 0; i < this.bits.length; i++) {
|
|
89
|
+
if (this.bits[i] !== other.bits[i]) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
forEach(fn) {
|
|
96
|
+
// https://lemire.me/blog/2018/02/21/iterating-over-set-bits-quickly/
|
|
97
|
+
let bits = this.bits;
|
|
98
|
+
for (let k = 0; k < bits.length; k++) {
|
|
99
|
+
let v = bits[k];
|
|
100
|
+
while (v !== 0) {
|
|
101
|
+
let t = (v & -v) >>> 0;
|
|
102
|
+
fn((k << 5) + ctz32(v));
|
|
103
|
+
v ^= t;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
exports.BitSet = BitSet;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const Graph_1 = __importDefault(require("./Graph"));
|
|
7
|
+
const nullthrows_1 = __importDefault(require("nullthrows"));
|
|
8
|
+
class ContentGraph extends Graph_1.default {
|
|
9
|
+
constructor(opts) {
|
|
10
|
+
if (opts) {
|
|
11
|
+
let { _contentKeyToNodeId, _nodeIdToContentKey, ...rest } = opts;
|
|
12
|
+
super(rest);
|
|
13
|
+
this._contentKeyToNodeId = _contentKeyToNodeId;
|
|
14
|
+
this._nodeIdToContentKey = _nodeIdToContentKey;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
super();
|
|
18
|
+
this._contentKeyToNodeId = new Map();
|
|
19
|
+
this._nodeIdToContentKey = new Map();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
static deserialize(opts) {
|
|
23
|
+
return new ContentGraph(opts);
|
|
24
|
+
}
|
|
25
|
+
serialize() {
|
|
26
|
+
return {
|
|
27
|
+
...super.serialize(),
|
|
28
|
+
_contentKeyToNodeId: this._contentKeyToNodeId,
|
|
29
|
+
// @ts-expect-error TS2353
|
|
30
|
+
_nodeIdToContentKey: this._nodeIdToContentKey,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
addNodeByContentKey(contentKey, node) {
|
|
34
|
+
if (this.hasContentKey(contentKey)) {
|
|
35
|
+
throw new Error('Graph already has content key ' + contentKey);
|
|
36
|
+
}
|
|
37
|
+
let nodeId = super.addNode(node);
|
|
38
|
+
this._contentKeyToNodeId.set(contentKey, nodeId);
|
|
39
|
+
this._nodeIdToContentKey.set(nodeId, contentKey);
|
|
40
|
+
return nodeId;
|
|
41
|
+
}
|
|
42
|
+
addNodeByContentKeyIfNeeded(contentKey, node) {
|
|
43
|
+
return this.hasContentKey(contentKey)
|
|
44
|
+
? this.getNodeIdByContentKey(contentKey)
|
|
45
|
+
: this.addNodeByContentKey(contentKey, node);
|
|
46
|
+
}
|
|
47
|
+
getNodeByContentKey(contentKey) {
|
|
48
|
+
let nodeId = this._contentKeyToNodeId.get(contentKey);
|
|
49
|
+
if (nodeId != null) {
|
|
50
|
+
return super.getNode(nodeId);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
getContentKeyByNodeId(nodeId) {
|
|
54
|
+
return (0, nullthrows_1.default)(this._nodeIdToContentKey.get(nodeId), `Expected node id ${nodeId} to exist`);
|
|
55
|
+
}
|
|
56
|
+
getNodeIdByContentKey(contentKey) {
|
|
57
|
+
return (0, nullthrows_1.default)(this._contentKeyToNodeId.get(contentKey), `Expected content key ${contentKey} to exist`);
|
|
58
|
+
}
|
|
59
|
+
hasContentKey(contentKey) {
|
|
60
|
+
return this._contentKeyToNodeId.has(contentKey);
|
|
61
|
+
}
|
|
62
|
+
removeNode(nodeId, removeOrphans = true) {
|
|
63
|
+
this._assertHasNodeId(nodeId);
|
|
64
|
+
let contentKey = (0, nullthrows_1.default)(this._nodeIdToContentKey.get(nodeId));
|
|
65
|
+
this._contentKeyToNodeId.delete(contentKey);
|
|
66
|
+
this._nodeIdToContentKey.delete(nodeId);
|
|
67
|
+
super.removeNode(nodeId, removeOrphans);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.default = ContentGraph;
|
package/dist/Graph.js
ADDED
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ALL_EDGE_TYPES = exports.NULL_EDGE_TYPE = void 0;
|
|
7
|
+
exports.mapVisitor = mapVisitor;
|
|
8
|
+
const types_1 = require("./types");
|
|
9
|
+
const AdjacencyList_1 = __importDefault(require("./AdjacencyList"));
|
|
10
|
+
const BitSet_1 = require("./BitSet");
|
|
11
|
+
const nullthrows_1 = __importDefault(require("nullthrows"));
|
|
12
|
+
exports.NULL_EDGE_TYPE = 1;
|
|
13
|
+
exports.ALL_EDGE_TYPES = -1;
|
|
14
|
+
class Graph {
|
|
15
|
+
constructor(opts) {
|
|
16
|
+
this.nodes = opts?.nodes || [];
|
|
17
|
+
this.setRootNodeId(opts?.rootNodeId);
|
|
18
|
+
let adjacencyList = opts?.adjacencyList;
|
|
19
|
+
let initialCapacity = opts?.initialCapacity;
|
|
20
|
+
this.adjacencyList = adjacencyList
|
|
21
|
+
? AdjacencyList_1.default.deserialize(adjacencyList)
|
|
22
|
+
: new AdjacencyList_1.default(typeof initialCapacity === 'number' ? { initialCapacity } : undefined);
|
|
23
|
+
}
|
|
24
|
+
setRootNodeId(id) {
|
|
25
|
+
this.rootNodeId = id;
|
|
26
|
+
}
|
|
27
|
+
static deserialize(opts) {
|
|
28
|
+
return new this({
|
|
29
|
+
nodes: opts.nodes,
|
|
30
|
+
adjacencyList: opts.adjacencyList,
|
|
31
|
+
rootNodeId: opts.rootNodeId,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
serialize() {
|
|
35
|
+
return {
|
|
36
|
+
nodes: this.nodes,
|
|
37
|
+
adjacencyList: this.adjacencyList.serialize(),
|
|
38
|
+
rootNodeId: this.rootNodeId,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// Returns an iterator of all edges in the graph. This can be large, so iterating
|
|
42
|
+
// the complete list can be costly in large graphs. Used when merging graphs.
|
|
43
|
+
getAllEdges() {
|
|
44
|
+
return this.adjacencyList.getAllEdges();
|
|
45
|
+
}
|
|
46
|
+
addNode(node) {
|
|
47
|
+
let id = this.adjacencyList.addNode();
|
|
48
|
+
this.nodes.push(node);
|
|
49
|
+
return id;
|
|
50
|
+
}
|
|
51
|
+
hasNode(id) {
|
|
52
|
+
return this.nodes[id] != null;
|
|
53
|
+
}
|
|
54
|
+
getNode(id) {
|
|
55
|
+
return this.nodes[id];
|
|
56
|
+
}
|
|
57
|
+
addEdge(from, to, type = exports.NULL_EDGE_TYPE) {
|
|
58
|
+
if (Number(type) === 0) {
|
|
59
|
+
throw new Error(`Edge type "${type}" not allowed`);
|
|
60
|
+
}
|
|
61
|
+
if (this.getNode(from) == null) {
|
|
62
|
+
throw new Error(`"from" node '${(0, types_1.fromNodeId)(from)}' not found`);
|
|
63
|
+
}
|
|
64
|
+
if (this.getNode(to) == null) {
|
|
65
|
+
throw new Error(`"to" node '${(0, types_1.fromNodeId)(to)}' not found`);
|
|
66
|
+
}
|
|
67
|
+
return this.adjacencyList.addEdge(from, to, type);
|
|
68
|
+
}
|
|
69
|
+
hasEdge(from, to, type = exports.NULL_EDGE_TYPE) {
|
|
70
|
+
return this.adjacencyList.hasEdge(from, to, type);
|
|
71
|
+
}
|
|
72
|
+
forEachNodeIdConnectedTo(to, fn, type = exports.NULL_EDGE_TYPE) {
|
|
73
|
+
this._assertHasNodeId(to);
|
|
74
|
+
this.adjacencyList.forEachNodeIdConnectedTo(to, fn, type);
|
|
75
|
+
}
|
|
76
|
+
forEachNodeIdConnectedFrom(from, fn, type = exports.NULL_EDGE_TYPE) {
|
|
77
|
+
this._assertHasNodeId(from);
|
|
78
|
+
this.adjacencyList.forEachNodeIdConnectedFromReverse(from, (id) => {
|
|
79
|
+
fn(id);
|
|
80
|
+
return false;
|
|
81
|
+
}, type);
|
|
82
|
+
}
|
|
83
|
+
getNodeIdsConnectedTo(nodeId, type = exports.NULL_EDGE_TYPE) {
|
|
84
|
+
this._assertHasNodeId(nodeId);
|
|
85
|
+
return this.adjacencyList.getNodeIdsConnectedTo(nodeId, type);
|
|
86
|
+
}
|
|
87
|
+
getNodeIdsConnectedFrom(nodeId, type = exports.NULL_EDGE_TYPE) {
|
|
88
|
+
this._assertHasNodeId(nodeId);
|
|
89
|
+
return this.adjacencyList.getNodeIdsConnectedFrom(nodeId, type);
|
|
90
|
+
}
|
|
91
|
+
// Removes node and any edges coming from or to that node
|
|
92
|
+
removeNode(nodeId, removeOrphans = true) {
|
|
93
|
+
if (!this.hasNode(nodeId)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
for (let { type, from } of this.adjacencyList.getInboundEdgesByType(nodeId)) {
|
|
97
|
+
this._removeEdge(from, nodeId, type,
|
|
98
|
+
// Do not allow orphans to be removed as this node could be one
|
|
99
|
+
// and is already being removed.
|
|
100
|
+
false);
|
|
101
|
+
}
|
|
102
|
+
for (let { type, to } of this.adjacencyList.getOutboundEdgesByType(nodeId)) {
|
|
103
|
+
this._removeEdge(nodeId, to, type, removeOrphans);
|
|
104
|
+
}
|
|
105
|
+
this.nodes[nodeId] = null;
|
|
106
|
+
}
|
|
107
|
+
removeEdges(nodeId, type = exports.NULL_EDGE_TYPE) {
|
|
108
|
+
if (!this.hasNode(nodeId)) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
for (let to of this.getNodeIdsConnectedFrom(nodeId, type)) {
|
|
112
|
+
this._removeEdge(nodeId, to, type);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
removeEdge(from, to, type = exports.NULL_EDGE_TYPE, removeOrphans = true) {
|
|
116
|
+
if (!this.adjacencyList.hasEdge(from, to, type)) {
|
|
117
|
+
throw new Error(`Edge from ${(0, types_1.fromNodeId)(from)} to ${(0, types_1.fromNodeId)(to)} not found!`);
|
|
118
|
+
}
|
|
119
|
+
this._removeEdge(from, to, type, removeOrphans);
|
|
120
|
+
}
|
|
121
|
+
// Removes edge and node the edge is to if the node is orphaned
|
|
122
|
+
_removeEdge(from, to, type = exports.NULL_EDGE_TYPE, removeOrphans = true) {
|
|
123
|
+
if (!this.adjacencyList.hasEdge(from, to, type)) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
this.adjacencyList.removeEdge(from, to, type);
|
|
127
|
+
if (removeOrphans && this.isOrphanedNode(to)) {
|
|
128
|
+
this.removeNode(to);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
isOrphanedNode(nodeId) {
|
|
132
|
+
if (!this.hasNode(nodeId)) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
if (this.rootNodeId == null) {
|
|
136
|
+
// If the graph does not have a root, and there are inbound edges,
|
|
137
|
+
// this node should not be considered orphaned.
|
|
138
|
+
return !this.adjacencyList.hasInboundEdges(nodeId);
|
|
139
|
+
}
|
|
140
|
+
// Otherwise, attempt to traverse backwards to the root. If there is a path,
|
|
141
|
+
// then this is not an orphaned node.
|
|
142
|
+
let hasPathToRoot = false;
|
|
143
|
+
// go back to traverseAncestors
|
|
144
|
+
this.traverseAncestors(nodeId, (ancestorId, _, actions) => {
|
|
145
|
+
if (ancestorId === this.rootNodeId) {
|
|
146
|
+
hasPathToRoot = true;
|
|
147
|
+
actions.stop();
|
|
148
|
+
}
|
|
149
|
+
}, exports.ALL_EDGE_TYPES);
|
|
150
|
+
if (hasPathToRoot) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
updateNode(nodeId, node) {
|
|
156
|
+
this._assertHasNodeId(nodeId);
|
|
157
|
+
this.nodes[nodeId] = node;
|
|
158
|
+
}
|
|
159
|
+
// Update a node's downstream nodes making sure to prune any orphaned branches
|
|
160
|
+
replaceNodeIdsConnectedTo(fromNodeId, toNodeIds, replaceFilter, type = exports.NULL_EDGE_TYPE, removeOrphans = true) {
|
|
161
|
+
this._assertHasNodeId(fromNodeId);
|
|
162
|
+
let outboundEdges = this.getNodeIdsConnectedFrom(fromNodeId, type);
|
|
163
|
+
let childrenToRemove = new Set(replaceFilter
|
|
164
|
+
? outboundEdges.filter((toNodeId) => replaceFilter(toNodeId))
|
|
165
|
+
: outboundEdges);
|
|
166
|
+
for (let toNodeId of toNodeIds) {
|
|
167
|
+
childrenToRemove.delete(toNodeId);
|
|
168
|
+
if (!this.hasEdge(fromNodeId, toNodeId, type)) {
|
|
169
|
+
this.addEdge(fromNodeId, toNodeId, type);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
for (let child of childrenToRemove) {
|
|
173
|
+
this._removeEdge(fromNodeId, child, type, removeOrphans);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
traverse(visit, startNodeId, type = exports.NULL_EDGE_TYPE) {
|
|
177
|
+
let enter = typeof visit === 'function' ? visit : visit.enter;
|
|
178
|
+
if (type === exports.ALL_EDGE_TYPES &&
|
|
179
|
+
enter &&
|
|
180
|
+
(typeof visit === 'function' || !visit.exit)) {
|
|
181
|
+
return this.dfsFast(enter, startNodeId);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
return this.dfs({
|
|
185
|
+
visit,
|
|
186
|
+
startNodeId,
|
|
187
|
+
getChildren: (nodeId) => this.getNodeIdsConnectedFrom(nodeId, type),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
filteredTraverse(filter, visit, startNodeId, type) {
|
|
192
|
+
return this.traverse(mapVisitor(filter, visit), startNodeId, type);
|
|
193
|
+
}
|
|
194
|
+
traverseAncestors(startNodeId, visit, type = exports.NULL_EDGE_TYPE) {
|
|
195
|
+
return this.dfs({
|
|
196
|
+
visit,
|
|
197
|
+
startNodeId,
|
|
198
|
+
getChildren: (nodeId) => this.getNodeIdsConnectedTo(nodeId, type),
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
dfsFast(visit, startNodeId) {
|
|
202
|
+
let traversalStartNode = (0, nullthrows_1.default)(startNodeId ?? this.rootNodeId, 'A start node is required to traverse');
|
|
203
|
+
this._assertHasNodeId(traversalStartNode);
|
|
204
|
+
let visited;
|
|
205
|
+
if (!this._visited || this._visited.capacity < this.nodes.length) {
|
|
206
|
+
this._visited = new BitSet_1.BitSet(this.nodes.length);
|
|
207
|
+
visited = this._visited;
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
visited = this._visited;
|
|
211
|
+
visited.clear();
|
|
212
|
+
}
|
|
213
|
+
// Take shared instance to avoid re-entrancy issues.
|
|
214
|
+
this._visited = null;
|
|
215
|
+
let stopped = false;
|
|
216
|
+
let skipped = false;
|
|
217
|
+
let actions = {
|
|
218
|
+
skipChildren() {
|
|
219
|
+
skipped = true;
|
|
220
|
+
},
|
|
221
|
+
stop() {
|
|
222
|
+
stopped = true;
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
let queue = [{ nodeId: traversalStartNode, context: null }];
|
|
226
|
+
while (queue.length !== 0) {
|
|
227
|
+
// @ts-expect-error TS2339
|
|
228
|
+
let { nodeId, context } = queue.pop();
|
|
229
|
+
if (!this.hasNode(nodeId) || visited.has(nodeId))
|
|
230
|
+
continue;
|
|
231
|
+
visited.add(nodeId);
|
|
232
|
+
skipped = false;
|
|
233
|
+
let newContext = visit(nodeId, context, actions);
|
|
234
|
+
if (typeof newContext !== 'undefined') {
|
|
235
|
+
context = newContext;
|
|
236
|
+
}
|
|
237
|
+
if (skipped) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (stopped) {
|
|
241
|
+
this._visited = visited;
|
|
242
|
+
return context;
|
|
243
|
+
}
|
|
244
|
+
this.adjacencyList.forEachNodeIdConnectedFromReverse(nodeId, (child) => {
|
|
245
|
+
if (!visited.has(child)) {
|
|
246
|
+
queue.push({ nodeId: child, context });
|
|
247
|
+
}
|
|
248
|
+
return false;
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
this._visited = visited;
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
// A post-order implementation of dfsFast
|
|
255
|
+
postOrderDfsFast(visit, startNodeId) {
|
|
256
|
+
let traversalStartNode = (0, nullthrows_1.default)(startNodeId ?? this.rootNodeId, 'A start node is required to traverse');
|
|
257
|
+
this._assertHasNodeId(traversalStartNode);
|
|
258
|
+
let visited;
|
|
259
|
+
if (!this._visited || this._visited.capacity < this.nodes.length) {
|
|
260
|
+
this._visited = new BitSet_1.BitSet(this.nodes.length);
|
|
261
|
+
visited = this._visited;
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
visited = this._visited;
|
|
265
|
+
visited.clear();
|
|
266
|
+
}
|
|
267
|
+
this._visited = null;
|
|
268
|
+
let stopped = false;
|
|
269
|
+
let actions = {
|
|
270
|
+
stop() {
|
|
271
|
+
stopped = true;
|
|
272
|
+
},
|
|
273
|
+
skipChildren() {
|
|
274
|
+
throw new Error('Calling skipChildren inside a post-order traversal is not allowed');
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
let queue = [traversalStartNode];
|
|
278
|
+
while (queue.length !== 0) {
|
|
279
|
+
let nodeId = queue[queue.length - 1];
|
|
280
|
+
if (!visited.has(nodeId)) {
|
|
281
|
+
visited.add(nodeId);
|
|
282
|
+
this.adjacencyList.forEachNodeIdConnectedFromReverse(nodeId, (child) => {
|
|
283
|
+
if (!visited.has(child)) {
|
|
284
|
+
queue.push(child);
|
|
285
|
+
}
|
|
286
|
+
return false;
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
queue.pop();
|
|
291
|
+
visit(nodeId, null, actions);
|
|
292
|
+
if (stopped) {
|
|
293
|
+
this._visited = visited;
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
this._visited = visited;
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Iterative implementation of DFS that supports all use-cases.
|
|
303
|
+
*
|
|
304
|
+
* This replaces `dfs` and will replace `dfsFast`.
|
|
305
|
+
*/
|
|
306
|
+
dfs({ visit, startNodeId, getChildren, }) {
|
|
307
|
+
let traversalStartNode = (0, nullthrows_1.default)(startNodeId ?? this.rootNodeId, 'A start node is required to traverse');
|
|
308
|
+
this._assertHasNodeId(traversalStartNode);
|
|
309
|
+
let visited;
|
|
310
|
+
if (!this._visited || this._visited.capacity < this.nodes.length) {
|
|
311
|
+
this._visited = new BitSet_1.BitSet(this.nodes.length);
|
|
312
|
+
visited = this._visited;
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
visited = this._visited;
|
|
316
|
+
visited.clear();
|
|
317
|
+
}
|
|
318
|
+
// Take shared instance to avoid re-entrancy issues.
|
|
319
|
+
this._visited = null;
|
|
320
|
+
let stopped = false;
|
|
321
|
+
let skipped = false;
|
|
322
|
+
let actions = {
|
|
323
|
+
skipChildren() {
|
|
324
|
+
skipped = true;
|
|
325
|
+
},
|
|
326
|
+
stop() {
|
|
327
|
+
stopped = true;
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
const queue = [
|
|
331
|
+
{ nodeId: traversalStartNode, context: null },
|
|
332
|
+
];
|
|
333
|
+
const enter = typeof visit === 'function' ? visit : visit.enter;
|
|
334
|
+
while (queue.length !== 0) {
|
|
335
|
+
const command = queue.pop();
|
|
336
|
+
// @ts-expect-error TS18048
|
|
337
|
+
if (command.exit != null) {
|
|
338
|
+
// @ts-expect-error TS2339
|
|
339
|
+
let { nodeId, context, exit } = command;
|
|
340
|
+
// @ts-expect-error TS18048
|
|
341
|
+
let newContext = exit(nodeId, command.context, actions);
|
|
342
|
+
if (typeof newContext !== 'undefined') {
|
|
343
|
+
context = newContext;
|
|
344
|
+
}
|
|
345
|
+
if (skipped) {
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (stopped) {
|
|
349
|
+
this._visited = visited;
|
|
350
|
+
return context;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
// @ts-expect-error TS2339
|
|
355
|
+
let { nodeId, context } = command;
|
|
356
|
+
if (!this.hasNode(nodeId) || visited.has(nodeId))
|
|
357
|
+
continue;
|
|
358
|
+
visited.add(nodeId);
|
|
359
|
+
skipped = false;
|
|
360
|
+
if (enter) {
|
|
361
|
+
let newContext = enter(nodeId, context, actions);
|
|
362
|
+
if (typeof newContext !== 'undefined') {
|
|
363
|
+
context = newContext;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (skipped) {
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (stopped) {
|
|
370
|
+
this._visited = visited;
|
|
371
|
+
return context;
|
|
372
|
+
}
|
|
373
|
+
if (typeof visit !== 'function' && visit.exit) {
|
|
374
|
+
queue.push({
|
|
375
|
+
nodeId,
|
|
376
|
+
exit: visit.exit,
|
|
377
|
+
context,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
// TODO turn into generator function
|
|
381
|
+
const children = getChildren(nodeId);
|
|
382
|
+
for (let i = children.length - 1; i > -1; i -= 1) {
|
|
383
|
+
const child = children[i];
|
|
384
|
+
if (visited.has(child)) {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
queue.push({ nodeId: child, context });
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
this._visited = visited;
|
|
392
|
+
}
|
|
393
|
+
bfs(visit) {
|
|
394
|
+
let rootNodeId = (0, nullthrows_1.default)(this.rootNodeId, 'A root node is required to traverse');
|
|
395
|
+
let queue = [rootNodeId];
|
|
396
|
+
let visited = new Set([rootNodeId]);
|
|
397
|
+
while (queue.length > 0) {
|
|
398
|
+
let node = queue.shift();
|
|
399
|
+
let stop = visit(rootNodeId);
|
|
400
|
+
if (stop === true) {
|
|
401
|
+
return node;
|
|
402
|
+
}
|
|
403
|
+
// @ts-expect-error TS2345
|
|
404
|
+
for (let child of this.getNodeIdsConnectedFrom(node)) {
|
|
405
|
+
if (!visited.has(child)) {
|
|
406
|
+
visited.add(child);
|
|
407
|
+
queue.push(child);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
topoSort(type) {
|
|
414
|
+
let sorted = [];
|
|
415
|
+
this.traverse({
|
|
416
|
+
exit: (nodeId) => {
|
|
417
|
+
sorted.push(nodeId);
|
|
418
|
+
},
|
|
419
|
+
}, null, type);
|
|
420
|
+
return sorted.reverse();
|
|
421
|
+
}
|
|
422
|
+
findAncestor(nodeId, fn) {
|
|
423
|
+
let res = null;
|
|
424
|
+
this.traverseAncestors(nodeId, (nodeId, ctx, traversal) => {
|
|
425
|
+
if (fn(nodeId)) {
|
|
426
|
+
res = nodeId;
|
|
427
|
+
traversal.stop();
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
return res;
|
|
431
|
+
}
|
|
432
|
+
findAncestors(nodeId, fn) {
|
|
433
|
+
let res = [];
|
|
434
|
+
this.traverseAncestors(nodeId, (nodeId, ctx, traversal) => {
|
|
435
|
+
if (fn(nodeId)) {
|
|
436
|
+
res.push(nodeId);
|
|
437
|
+
traversal.skipChildren();
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
return res;
|
|
441
|
+
}
|
|
442
|
+
findDescendant(nodeId, fn) {
|
|
443
|
+
let res = null;
|
|
444
|
+
this.traverse((nodeId, ctx, traversal) => {
|
|
445
|
+
if (fn(nodeId)) {
|
|
446
|
+
res = nodeId;
|
|
447
|
+
traversal.stop();
|
|
448
|
+
}
|
|
449
|
+
}, nodeId);
|
|
450
|
+
return res;
|
|
451
|
+
}
|
|
452
|
+
findDescendants(nodeId, fn) {
|
|
453
|
+
let res = [];
|
|
454
|
+
this.traverse((nodeId, ctx, traversal) => {
|
|
455
|
+
if (fn(nodeId)) {
|
|
456
|
+
res.push(nodeId);
|
|
457
|
+
traversal.skipChildren();
|
|
458
|
+
}
|
|
459
|
+
}, nodeId);
|
|
460
|
+
return res;
|
|
461
|
+
}
|
|
462
|
+
_assertHasNodeId(nodeId) {
|
|
463
|
+
if (!this.hasNode(nodeId)) {
|
|
464
|
+
throw new Error('Does not have node ' + (0, types_1.fromNodeId)(nodeId));
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
exports.default = Graph;
|
|
469
|
+
function mapVisitor(filter, visit) {
|
|
470
|
+
function makeEnter(visit) {
|
|
471
|
+
return function mappedEnter(nodeId, context, actions) {
|
|
472
|
+
let value = filter(nodeId, actions);
|
|
473
|
+
if (value != null) {
|
|
474
|
+
return visit(value, context, actions);
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
if (typeof visit === 'function') {
|
|
479
|
+
return makeEnter(visit);
|
|
480
|
+
}
|
|
481
|
+
let mapped = {};
|
|
482
|
+
if (visit.enter != null) {
|
|
483
|
+
mapped.enter = makeEnter(visit.enter);
|
|
484
|
+
}
|
|
485
|
+
if (visit.exit != null) {
|
|
486
|
+
mapped.exit = function mappedExit(nodeId, context, actions) {
|
|
487
|
+
let exit = visit.exit;
|
|
488
|
+
if (!exit) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
let value = filter(nodeId, actions);
|
|
492
|
+
if (value != null) {
|
|
493
|
+
return exit(value, context, actions);
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
return mapped;
|
|
498
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.BitSet = exports.ContentGraph = exports.mapVisitor = exports.ALL_EDGE_TYPES = exports.Graph = exports.fromNodeId = exports.toNodeId = void 0;
|
|
7
|
+
var types_1 = require("./types");
|
|
8
|
+
Object.defineProperty(exports, "toNodeId", { enumerable: true, get: function () { return types_1.toNodeId; } });
|
|
9
|
+
Object.defineProperty(exports, "fromNodeId", { enumerable: true, get: function () { return types_1.fromNodeId; } });
|
|
10
|
+
var Graph_1 = require("./Graph");
|
|
11
|
+
Object.defineProperty(exports, "Graph", { enumerable: true, get: function () { return __importDefault(Graph_1).default; } });
|
|
12
|
+
Object.defineProperty(exports, "ALL_EDGE_TYPES", { enumerable: true, get: function () { return Graph_1.ALL_EDGE_TYPES; } });
|
|
13
|
+
Object.defineProperty(exports, "mapVisitor", { enumerable: true, get: function () { return Graph_1.mapVisitor; } });
|
|
14
|
+
var ContentGraph_1 = require("./ContentGraph");
|
|
15
|
+
Object.defineProperty(exports, "ContentGraph", { enumerable: true, get: function () { return __importDefault(ContentGraph_1).default; } });
|
|
16
|
+
var BitSet_1 = require("./BitSet");
|
|
17
|
+
Object.defineProperty(exports, "BitSet", { enumerable: true, get: function () { return BitSet_1.BitSet; } });
|