@parcel/graph 2.0.2-nightly.2536 → 2.0.2-nightly.2549
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/lib/AdjacencyList.js +1165 -0
- package/lib/ContentGraph.js +1 -0
- package/lib/Graph.js +40 -213
- package/lib/index.js +3 -13
- package/package.json +2 -2
- package/src/AdjacencyList.js +1211 -0
- package/src/ContentGraph.js +9 -4
- package/src/Graph.js +77 -197
- package/src/index.js +4 -2
- package/test/AdjacencyList.test.js +280 -0
- package/test/Graph.test.js +9 -6
- package/test/integration/adjacency-list-shared-array.js +20 -0
@@ -0,0 +1,280 @@
|
|
1
|
+
// @flow strict-local
|
2
|
+
|
3
|
+
import assert from 'assert';
|
4
|
+
import path from 'path';
|
5
|
+
import {Worker} from 'worker_threads';
|
6
|
+
|
7
|
+
import AdjacencyList, {NodeTypeMap, EdgeTypeMap} from '../src/AdjacencyList';
|
8
|
+
import {toNodeId} from '../src/types';
|
9
|
+
|
10
|
+
describe('AdjacencyList', () => {
|
11
|
+
it('constructor should initialize an empty graph', () => {
|
12
|
+
let stats = new AdjacencyList().stats;
|
13
|
+
assert(stats.nodes === 0);
|
14
|
+
assert(stats.edges === 0);
|
15
|
+
});
|
16
|
+
|
17
|
+
it('addNode should add a node to the graph', () => {
|
18
|
+
let graph = new AdjacencyList();
|
19
|
+
let id = graph.addNode();
|
20
|
+
assert.equal(id, 0);
|
21
|
+
assert.equal(graph.stats.nodes, 1);
|
22
|
+
});
|
23
|
+
|
24
|
+
it('addNode should resize nodes array when necessary', () => {
|
25
|
+
let graph = new AdjacencyList();
|
26
|
+
let size = graph.serialize().nodes.byteLength;
|
27
|
+
let a = graph.addNode();
|
28
|
+
let b = graph.addNode();
|
29
|
+
assert(size < (size = graph.serialize().nodes.byteLength));
|
30
|
+
graph.addEdge(a, b, 1);
|
31
|
+
graph.addEdge(a, b, 2);
|
32
|
+
graph.addEdge(a, b, 3);
|
33
|
+
graph.addEdge(a, b, 4);
|
34
|
+
assert(size < graph.serialize().nodes.byteLength);
|
35
|
+
});
|
36
|
+
|
37
|
+
it('removeEdge should remove an edge from the graph', () => {
|
38
|
+
let graph = new AdjacencyList();
|
39
|
+
let node0 = graph.addNode();
|
40
|
+
let node1 = graph.addNode();
|
41
|
+
let node2 = graph.addNode();
|
42
|
+
let node3 = graph.addNode();
|
43
|
+
let node4 = graph.addNode();
|
44
|
+
let node5 = graph.addNode();
|
45
|
+
let node6 = graph.addNode();
|
46
|
+
graph.addEdge(node0, node1);
|
47
|
+
graph.addEdge(node2, node1);
|
48
|
+
// this will get removed
|
49
|
+
graph.addEdge(node3, node1);
|
50
|
+
graph.addEdge(node4, node1);
|
51
|
+
graph.addEdge(node5, node1);
|
52
|
+
graph.addEdge(node6, node1);
|
53
|
+
|
54
|
+
assert.deepEqual(graph.getNodeIdsConnectedTo(node1), [0, 2, 3, 4, 5, 6]);
|
55
|
+
|
56
|
+
graph.removeEdge(node3, node1);
|
57
|
+
assert.deepEqual(graph.getNodeIdsConnectedTo(node1), [0, 2, 4, 5, 6]);
|
58
|
+
});
|
59
|
+
|
60
|
+
it('removeEdge should remove an edge of a specific type from the graph', () => {
|
61
|
+
let graph = new AdjacencyList();
|
62
|
+
let a = graph.addNode();
|
63
|
+
let b = graph.addNode();
|
64
|
+
let c = graph.addNode();
|
65
|
+
let d = graph.addNode();
|
66
|
+
graph.addEdge(a, b);
|
67
|
+
graph.addEdge(a, b, 2);
|
68
|
+
graph.addEdge(a, b, 3);
|
69
|
+
graph.addEdge(a, c);
|
70
|
+
graph.addEdge(a, d, 3);
|
71
|
+
assert.equal(graph.stats.edges, 5);
|
72
|
+
assert.ok(graph.hasEdge(a, b));
|
73
|
+
assert.ok(graph.hasEdge(a, b, 2));
|
74
|
+
assert.ok(graph.hasEdge(a, b, 3));
|
75
|
+
assert.ok(graph.hasEdge(a, c));
|
76
|
+
assert.ok(graph.hasEdge(a, d, 3));
|
77
|
+
assert.deepEqual(Array.from(graph.getAllEdges()), [
|
78
|
+
{from: a, to: b, type: 1},
|
79
|
+
{from: a, to: b, type: 2},
|
80
|
+
{from: a, to: b, type: 3},
|
81
|
+
{from: a, to: c, type: 1},
|
82
|
+
{from: a, to: d, type: 3},
|
83
|
+
]);
|
84
|
+
|
85
|
+
graph.removeEdge(a, b, 2);
|
86
|
+
assert.equal(graph.stats.edges, 4);
|
87
|
+
assert.ok(graph.hasEdge(a, b));
|
88
|
+
assert.equal(graph.hasEdge(a, b, 2), false);
|
89
|
+
assert.ok(graph.hasEdge(a, b, 3));
|
90
|
+
assert.ok(graph.hasEdge(a, c));
|
91
|
+
assert.ok(graph.hasEdge(a, d, 3));
|
92
|
+
assert.deepEqual(Array.from(graph.getAllEdges()), [
|
93
|
+
{from: a, to: b, type: 1},
|
94
|
+
{from: a, to: b, type: 3},
|
95
|
+
{from: a, to: c, type: 1},
|
96
|
+
{from: a, to: d, type: 3},
|
97
|
+
]);
|
98
|
+
});
|
99
|
+
|
100
|
+
it('addEdge should add an edge to the graph', () => {
|
101
|
+
let graph = new AdjacencyList();
|
102
|
+
let a = graph.addNode();
|
103
|
+
let b = graph.addNode();
|
104
|
+
graph.addEdge(a, b);
|
105
|
+
assert.equal(graph.stats.nodes, 2);
|
106
|
+
assert.equal(graph.stats.edges, 1);
|
107
|
+
assert.ok(graph.hasEdge(a, b));
|
108
|
+
});
|
109
|
+
|
110
|
+
it('addEdge should add multiple edges from a node in order', () => {
|
111
|
+
let graph = new AdjacencyList();
|
112
|
+
let a = graph.addNode();
|
113
|
+
let b = graph.addNode();
|
114
|
+
let c = graph.addNode();
|
115
|
+
let d = graph.addNode();
|
116
|
+
graph.addEdge(a, b);
|
117
|
+
graph.addEdge(a, d);
|
118
|
+
graph.addEdge(a, c);
|
119
|
+
assert.deepEqual(graph.getNodeIdsConnectedFrom(a), [b, d, c]);
|
120
|
+
});
|
121
|
+
|
122
|
+
it('addEdge should add multiple edges to a node in order', () => {
|
123
|
+
let graph = new AdjacencyList();
|
124
|
+
let a = graph.addNode();
|
125
|
+
let b = graph.addNode();
|
126
|
+
let c = graph.addNode();
|
127
|
+
let d = graph.addNode();
|
128
|
+
graph.addEdge(a, b);
|
129
|
+
graph.addEdge(d, b);
|
130
|
+
graph.addEdge(a, d);
|
131
|
+
graph.addEdge(c, b);
|
132
|
+
assert.deepEqual(graph.getNodeIdsConnectedTo(b), [a, d, c]);
|
133
|
+
});
|
134
|
+
|
135
|
+
it('addEdge should add multiple edges of different types in order', () => {
|
136
|
+
let graph = new AdjacencyList();
|
137
|
+
let a = graph.addNode();
|
138
|
+
let b = graph.addNode();
|
139
|
+
graph.addEdge(a, b);
|
140
|
+
graph.addEdge(a, b, 1);
|
141
|
+
graph.addEdge(a, b, 4);
|
142
|
+
graph.addEdge(a, b, 3);
|
143
|
+
assert.deepEqual(graph.getNodeIdsConnectedFrom(a), [b]);
|
144
|
+
assert.deepEqual(Array.from(graph.getAllEdges()), [
|
145
|
+
{from: a, to: b, type: 1},
|
146
|
+
{from: a, to: b, type: 4},
|
147
|
+
{from: a, to: b, type: 3},
|
148
|
+
]);
|
149
|
+
});
|
150
|
+
|
151
|
+
it('addEdge should return false if an edge is already added', () => {
|
152
|
+
let graph = new AdjacencyList();
|
153
|
+
let a = graph.addNode();
|
154
|
+
let b = graph.addNode();
|
155
|
+
assert.equal(graph.addEdge(a, b), true);
|
156
|
+
assert.equal(graph.addEdge(a, b), false);
|
157
|
+
});
|
158
|
+
|
159
|
+
it('addEdge should resize edges array when necessary', () => {
|
160
|
+
let graph = new AdjacencyList();
|
161
|
+
let size = graph.serialize().edges.byteLength;
|
162
|
+
let a = graph.addNode();
|
163
|
+
let b = graph.addNode();
|
164
|
+
graph.addEdge(a, b, 1);
|
165
|
+
graph.addEdge(a, b, 2);
|
166
|
+
graph.addEdge(a, b, 3);
|
167
|
+
assert(size < graph.serialize().edges.byteLength);
|
168
|
+
});
|
169
|
+
|
170
|
+
it('addEdge should error when a node has not been added to the graph', () => {
|
171
|
+
let graph = new AdjacencyList();
|
172
|
+
assert.throws(() => graph.addEdge(toNodeId(0), toNodeId(1)));
|
173
|
+
graph.addNode();
|
174
|
+
assert.throws(() => graph.addEdge(toNodeId(0), toNodeId(1)));
|
175
|
+
graph.addNode();
|
176
|
+
assert.doesNotThrow(() => graph.addEdge(toNodeId(0), toNodeId(1)));
|
177
|
+
assert.throws(() => graph.addEdge(toNodeId(0), toNodeId(2)));
|
178
|
+
});
|
179
|
+
|
180
|
+
it('addEdge should error when an unsupported edge type is provided', () => {
|
181
|
+
let graph = new AdjacencyList();
|
182
|
+
let a = graph.addNode();
|
183
|
+
let b = graph.addNode();
|
184
|
+
assert.throws(() => graph.addEdge(a, b, 0));
|
185
|
+
assert.throws(() => graph.addEdge(a, b, -1));
|
186
|
+
assert.doesNotThrow(() => graph.addEdge(a, b, 1));
|
187
|
+
});
|
188
|
+
|
189
|
+
it('addEdge should not replace a deleted edge if the edge was already added', () => {
|
190
|
+
// Mock hash fn to generate collisions
|
191
|
+
// $FlowFixMe[prop-missing]
|
192
|
+
let originalHash = AdjacencyList.prototype.hash;
|
193
|
+
// $FlowFixMe[prop-missing]
|
194
|
+
AdjacencyList.prototype.hash = () => 1;
|
195
|
+
|
196
|
+
let graph = new AdjacencyList();
|
197
|
+
let n0 = graph.addNode();
|
198
|
+
let n1 = graph.addNode();
|
199
|
+
let n2 = graph.addNode();
|
200
|
+
graph.addEdge(n0, n1, 1);
|
201
|
+
graph.addEdge(n1, n2, 1);
|
202
|
+
graph.removeEdge(n1, n2, 1);
|
203
|
+
assert(graph.addEdge(n0, n1, 1) === false);
|
204
|
+
assert(graph.stats.edges === 1);
|
205
|
+
|
206
|
+
// $FlowFixMe[prop-missing]
|
207
|
+
AdjacencyList.prototype.hash = originalHash;
|
208
|
+
});
|
209
|
+
|
210
|
+
it('addEdge should replace a deleted edge', () => {
|
211
|
+
// Mock hash fn to generate collisions
|
212
|
+
// $FlowFixMe[prop-missing]
|
213
|
+
let originalHash = AdjacencyList.prototype.hash;
|
214
|
+
// $FlowFixMe[prop-missing]
|
215
|
+
AdjacencyList.prototype.hash = () => 1;
|
216
|
+
|
217
|
+
let graph = new AdjacencyList();
|
218
|
+
let n0 = graph.addNode();
|
219
|
+
let n1 = graph.addNode();
|
220
|
+
graph.addEdge(n0, n1, 2);
|
221
|
+
graph.removeEdge(n0, n1, 2);
|
222
|
+
assert(graph.addEdge(n0, n1, 2));
|
223
|
+
assert(graph.stats.edges === 1);
|
224
|
+
assert(graph.stats.deleted === 1);
|
225
|
+
// Resize to reclaim deleted edge space.
|
226
|
+
graph.resizeEdges(4);
|
227
|
+
assert(graph.stats.edges === 1);
|
228
|
+
assert(graph.stats.deleted === 0);
|
229
|
+
|
230
|
+
// $FlowFixMe[prop-missing]
|
231
|
+
AdjacencyList.prototype.hash = originalHash;
|
232
|
+
});
|
233
|
+
|
234
|
+
describe('deserialize', function () {
|
235
|
+
this.timeout(10000);
|
236
|
+
|
237
|
+
it('should share the underlying data across worker threads', async () => {
|
238
|
+
let graph = new AdjacencyList();
|
239
|
+
let n0 = graph.addNode();
|
240
|
+
let n1 = graph.addNode();
|
241
|
+
graph.addEdge(n0, n1, 1);
|
242
|
+
graph.addEdge(n0, n1, 2);
|
243
|
+
|
244
|
+
let worker = new Worker(
|
245
|
+
path.join(__dirname, 'integration/adjacency-list-shared-array.js'),
|
246
|
+
);
|
247
|
+
|
248
|
+
let originalSerialized = graph.serialize();
|
249
|
+
let originalNodes = [...originalSerialized.nodes];
|
250
|
+
let originalEdges = [...originalSerialized.edges];
|
251
|
+
let work = new Promise(resolve => worker.on('message', resolve));
|
252
|
+
worker.postMessage(originalSerialized);
|
253
|
+
let received = AdjacencyList.deserialize(await work);
|
254
|
+
await worker.terminate();
|
255
|
+
|
256
|
+
assert.deepEqual(received.serialize().nodes, graph.serialize().nodes);
|
257
|
+
assert.deepEqual(received.serialize().edges, graph.serialize().edges);
|
258
|
+
|
259
|
+
originalNodes.forEach((v, i) => {
|
260
|
+
if (i < NodeTypeMap.HEADER_SIZE) {
|
261
|
+
assert.equal(v, received.serialize().nodes[i]);
|
262
|
+
assert.equal(v, graph.serialize().nodes[i]);
|
263
|
+
} else {
|
264
|
+
assert.equal(v * 2, received.serialize().nodes[i]);
|
265
|
+
assert.equal(v * 2, graph.serialize().nodes[i]);
|
266
|
+
}
|
267
|
+
});
|
268
|
+
|
269
|
+
originalEdges.forEach((v, i) => {
|
270
|
+
if (i < EdgeTypeMap.HEADER_SIZE) {
|
271
|
+
assert.equal(v, received.serialize().edges[i]);
|
272
|
+
assert.equal(v, graph.serialize().edges[i]);
|
273
|
+
} else {
|
274
|
+
assert.equal(v * 2, received.serialize().edges[i]);
|
275
|
+
assert.equal(v * 2, graph.serialize().edges[i]);
|
276
|
+
}
|
277
|
+
});
|
278
|
+
});
|
279
|
+
});
|
280
|
+
});
|
package/test/Graph.test.js
CHANGED
@@ -10,7 +10,7 @@ describe('Graph', () => {
|
|
10
10
|
it('constructor should initialize an empty graph', () => {
|
11
11
|
let graph = new Graph();
|
12
12
|
assert.deepEqual(graph.nodes, new Map());
|
13
|
-
assert.deepEqual(graph.getAllEdges(), []);
|
13
|
+
assert.deepEqual([...graph.getAllEdges()], []);
|
14
14
|
});
|
15
15
|
|
16
16
|
it('addNode should add a node to the graph', () => {
|
@@ -114,7 +114,10 @@ describe('Graph', () => {
|
|
114
114
|
assert(graph.nodes.has(nodeD));
|
115
115
|
assert(!graph.nodes.has(nodeB));
|
116
116
|
assert(!graph.nodes.has(nodeC));
|
117
|
-
assert.deepEqual(
|
117
|
+
assert.deepEqual(
|
118
|
+
[...graph.getAllEdges()],
|
119
|
+
[{from: nodeA, to: nodeD, type: 1}],
|
120
|
+
);
|
118
121
|
});
|
119
122
|
|
120
123
|
it('removing a node recursively deletes orphaned nodes', () => {
|
@@ -154,7 +157,7 @@ describe('Graph', () => {
|
|
154
157
|
graph.removeNode(nodeB);
|
155
158
|
|
156
159
|
assert.deepEqual([...graph.nodes.keys()], [nodeA, nodeC, nodeF]);
|
157
|
-
assert.deepEqual(graph.getAllEdges(), [
|
160
|
+
assert.deepEqual(Array.from(graph.getAllEdges()), [
|
158
161
|
{from: nodeA, to: nodeC, type: 1},
|
159
162
|
{from: nodeC, to: nodeF, type: 1},
|
160
163
|
]);
|
@@ -199,7 +202,7 @@ describe('Graph', () => {
|
|
199
202
|
graph.removeNode(nodeB);
|
200
203
|
|
201
204
|
assert.deepEqual([...graph.nodes.keys()], [nodeA, nodeC, nodeF]);
|
202
|
-
assert.deepEqual(graph.getAllEdges(), [
|
205
|
+
assert.deepEqual(Array.from(graph.getAllEdges()), [
|
203
206
|
{from: nodeA, to: nodeC, type: 1},
|
204
207
|
{from: nodeC, to: nodeF, type: 1},
|
205
208
|
]);
|
@@ -234,7 +237,7 @@ describe('Graph', () => {
|
|
234
237
|
graph.removeEdge(nodeC, nodeE);
|
235
238
|
|
236
239
|
assert.deepEqual(nodesBefore, getNodeIds());
|
237
|
-
assert.deepEqual(graph.getAllEdges(), [
|
240
|
+
assert.deepEqual(Array.from(graph.getAllEdges()), [
|
238
241
|
{from: nodeA, to: nodeB, type: 1},
|
239
242
|
{from: nodeB, to: nodeC, type: 1},
|
240
243
|
{from: nodeB, to: nodeD, type: 1},
|
@@ -277,7 +280,7 @@ describe('Graph', () => {
|
|
277
280
|
assert(graph.hasNode(nodeB));
|
278
281
|
assert(!graph.hasNode(nodeC));
|
279
282
|
assert(graph.hasNode(nodeD));
|
280
|
-
assert.deepEqual(graph.getAllEdges(), [
|
283
|
+
assert.deepEqual(Array.from(graph.getAllEdges()), [
|
281
284
|
{from: nodeA, to: nodeB, type: 1},
|
282
285
|
{from: nodeA, to: nodeD, type: 1},
|
283
286
|
]);
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require('@parcel/babel-register');
|
2
|
+
const {parentPort} = require('worker_threads');
|
3
|
+
const {
|
4
|
+
default: AdjacencyList,
|
5
|
+
NodeTypeMap,
|
6
|
+
EdgeTypeMap,
|
7
|
+
} = require('../../src/AdjacencyList');
|
8
|
+
|
9
|
+
parentPort.once('message', (serialized) => {
|
10
|
+
let graph = AdjacencyList.deserialize(serialized);
|
11
|
+
serialized.nodes.forEach((v, i) => {
|
12
|
+
if (i < NodeTypeMap.HEADER_SIZE) return;
|
13
|
+
serialized.nodes[i] = v * 2;
|
14
|
+
});
|
15
|
+
serialized.edges.forEach((v, i) => {
|
16
|
+
if (i < EdgeTypeMap.HEADER_SIZE) return;
|
17
|
+
serialized.edges[i] = v * 2;
|
18
|
+
});
|
19
|
+
parentPort.postMessage(graph.serialize());
|
20
|
+
});
|