@npmcli/arborist 2.6.3 → 2.8.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.
@@ -0,0 +1,405 @@
1
+ // Internal methods used by buildIdealTree.
2
+ // Answer the question: "can I put this dep here?"
3
+ //
4
+ // IMPORTANT: *nothing* in this class should *ever* modify or mutate the tree
5
+ // at all. The contract here is strictly limited to read operations. We call
6
+ // this in the process of walking through the ideal tree checking many
7
+ // different potential placement targets for a given node. If a change is made
8
+ // to the tree along the way, that can cause serious problems!
9
+ //
10
+ // In order to enforce this restriction, in debug mode, canPlaceDep() will
11
+ // snapshot the tree at the start of the process, and then at the end, will
12
+ // verify that it still matches the snapshot, and throw an error if any changes
13
+ // occurred.
14
+ //
15
+ // The algorithm is roughly like this:
16
+ // - check the node itself:
17
+ // - if there is no version present, and no conflicting edges from target,
18
+ // OK, provided all peers can be placed at or above the target.
19
+ // - if the current version matches, KEEP
20
+ // - if there is an older version present, which can be replaced, then
21
+ // - if satisfying and preferDedupe? KEEP
22
+ // - else: REPLACE
23
+ // - if there is a newer version present, and preferDedupe, REPLACE
24
+ // - if the version present satisfies the edge, KEEP
25
+ // - else: CONFLICT
26
+ // - if the node is not in conflict, check each of its peers:
27
+ // - if the peer can be placed in the target, continue
28
+ // - else if the peer can be placed in a parent, and there is no other
29
+ // conflicting version shadowing it, continue
30
+ // - else CONFLICT
31
+ // - If the peers are not in conflict, return the original node's value
32
+ //
33
+ // An exception to this logic is that if the target is the deepest location
34
+ // that a node can be placed, and the conflicting node can be placed deeper,
35
+ // then we will return REPLACE rather than CONFLICT, and Arborist will queue
36
+ // the replaced node for resolution elsewhere.
37
+
38
+ const semver = require('semver')
39
+ const debug = require('./debug.js')
40
+ const peerEntrySets = require('./peer-entry-sets.js')
41
+ const deepestNestingTarget = require('./deepest-nesting-target.js')
42
+
43
+ const CONFLICT = Symbol('CONFLICT')
44
+ const OK = Symbol('OK')
45
+ const REPLACE = Symbol('REPLACE')
46
+ const KEEP = Symbol('KEEP')
47
+
48
+ class CanPlaceDep {
49
+ // dep is a dep that we're trying to place. it should already live in
50
+ // a virtual tree where its peer set is loaded as children of the root.
51
+ // target is the actual place where we're trying to place this dep
52
+ // in a node_modules folder.
53
+ // edge is the edge that we're trying to satisfy with this placement.
54
+ // parent is the CanPlaceDep object of the entry node when placing a peer.
55
+ constructor (options) {
56
+ const {
57
+ dep,
58
+ target,
59
+ edge,
60
+ preferDedupe,
61
+ parent = null,
62
+ peerPath = [],
63
+ explicitRequest = false,
64
+ } = options
65
+
66
+ debug(() => {
67
+ if (!dep)
68
+ throw new Error('no dep provided to CanPlaceDep')
69
+
70
+ if (!target)
71
+ throw new Error('no target provided to CanPlaceDep')
72
+
73
+ if (!edge)
74
+ throw new Error('no edge provided to CanPlaceDep')
75
+
76
+ this._nodeSnapshot = JSON.stringify(dep)
77
+ this._treeSnapshot = JSON.stringify(target.root)
78
+ })
79
+
80
+ // the result of whether we can place it or not
81
+ this.canPlace = null
82
+ // if peers conflict, but this one doesn't, then that is useful info
83
+ this.canPlaceSelf = null
84
+
85
+ this.dep = dep
86
+ this.target = target
87
+ this.edge = edge
88
+ this.explicitRequest = explicitRequest
89
+
90
+ // preventing cycles when we check peer sets
91
+ this.peerPath = peerPath
92
+ // we always prefer to dedupe peers, because they are trying
93
+ // a bit harder to be singletons.
94
+ this.preferDedupe = !!preferDedupe || edge.peer
95
+ this.parent = parent
96
+ this.children = []
97
+
98
+ this.isSource = target === this.peerSetSource
99
+ this.name = edge.name
100
+ this.current = target.children.get(this.name)
101
+ this.targetEdge = target.edgesOut.get(this.name)
102
+ this.conflicts = new Map()
103
+
104
+ // check if this dep was already subject to a peerDep override while
105
+ // building the peerSet.
106
+ this.edgeOverride = !dep.satisfies(edge)
107
+
108
+ this.canPlace = this.checkCanPlace()
109
+ if (!this.canPlaceSelf)
110
+ this.canPlaceSelf = this.canPlace
111
+
112
+ debug(() => {
113
+ const nodeSnapshot = JSON.stringify(dep)
114
+ const treeSnapshot = JSON.stringify(target.root)
115
+ /* istanbul ignore if */
116
+ if (this._nodeSnapshot !== nodeSnapshot) {
117
+ throw Object.assign(new Error('dep changed in CanPlaceDep'), {
118
+ expect: this._nodeSnapshot,
119
+ actual: nodeSnapshot,
120
+ })
121
+ }
122
+ /* istanbul ignore if */
123
+ if (this._treeSnapshot !== treeSnapshot) {
124
+ throw Object.assign(new Error('tree changed in CanPlaceDep'), {
125
+ expect: this._treeSnapshot,
126
+ actual: treeSnapshot,
127
+ })
128
+ }
129
+ })
130
+ }
131
+
132
+ checkCanPlace () {
133
+ const { target, targetEdge, current, dep } = this
134
+
135
+ // if the dep failed to load, we're going to fail the build or
136
+ // prune it out anyway, so just move forward placing/replacing it.
137
+ if (dep.errors.length)
138
+ return current ? REPLACE : OK
139
+
140
+ // cannot place peers inside their dependents, except for tops
141
+ if (targetEdge && targetEdge.peer && !target.isTop)
142
+ return CONFLICT
143
+
144
+ if (targetEdge && !dep.satisfies(targetEdge) && targetEdge !== this.edge)
145
+ return CONFLICT
146
+
147
+ return current ? this.checkCanPlaceCurrent() : this.checkCanPlaceNoCurrent()
148
+ }
149
+
150
+ // we know that the target has a dep by this name in its node_modules
151
+ // already. Can return KEEP, REPLACE, or CONFLICT.
152
+ checkCanPlaceCurrent () {
153
+ const { preferDedupe, explicitRequest, current, target, edge, dep } = this
154
+
155
+ if (dep.matches(current)) {
156
+ if (current.satisfies(edge) || this.edgeOverride)
157
+ return explicitRequest ? REPLACE : KEEP
158
+ }
159
+
160
+ const { version: curVer } = current
161
+ const { version: newVer } = dep
162
+ const tryReplace = curVer && newVer && semver.gte(newVer, curVer)
163
+ if (tryReplace && dep.canReplace(current)) {
164
+ /* XXX-istanbul ignore else - It's extremely rare that a replaceable
165
+ * node would be a conflict, if the current one wasn't a conflict,
166
+ * but it is theoretically possible if peer deps are pinned. In
167
+ * that case we treat it like any other conflict, and keep trying */
168
+ const cpp = this.canPlacePeers(REPLACE)
169
+ if (cpp !== CONFLICT)
170
+ return cpp
171
+ }
172
+
173
+ // ok, can't replace the current with new one, but maybe current is ok?
174
+ if (current.satisfies(edge) && (!explicitRequest || preferDedupe))
175
+ return KEEP
176
+
177
+ // if we prefer deduping, then try replacing newer with older
178
+ if (preferDedupe && !tryReplace && dep.canReplace(current)) {
179
+ const cpp = this.canPlacePeers(REPLACE)
180
+ if (cpp !== CONFLICT)
181
+ return cpp
182
+ }
183
+
184
+ // Check for interesting cases!
185
+ // First, is this the deepest place that this thing can go, and NOT the
186
+ // deepest place where the conflicting dep can go? If so, replace it,
187
+ // and let it re-resolve deeper in the tree.
188
+ const myDeepest = this.deepestNestingTarget
189
+
190
+ // ok, i COULD be placed deeper, so leave the current one alone.
191
+ if (target !== myDeepest)
192
+ return CONFLICT
193
+
194
+ // if we are not checking a peerDep, then we MUST place it here, in the
195
+ // target that has a non-peer dep on it.
196
+ if (!edge.peer && target === edge.from)
197
+ return this.canPlacePeers(REPLACE)
198
+
199
+ // if we aren't placing a peer in a set, then we're done here.
200
+ // This is ignored because it SHOULD be redundant, as far as I can tell,
201
+ // with the deepest target and target===edge.from tests. But until we
202
+ // can prove that isn't possible, this condition is here for safety.
203
+ /* istanbul ignore if - allegedly impossible */
204
+ if (!this.parent && !edge.peer)
205
+ return CONFLICT
206
+
207
+ // check the deps in the peer group for each edge into that peer group
208
+ // if ALL of them can be pushed deeper, or if it's ok to replace its
209
+ // members with the contents of the new peer group, then we're good.
210
+ let canReplace = true
211
+ for (const [entryEdge, currentPeers] of peerEntrySets(current)) {
212
+ if (entryEdge === this.edge || entryEdge === this.peerEntryEdge)
213
+ continue
214
+
215
+ // First, see if it's ok to just replace the peerSet entirely.
216
+ // we do this by walking out from the entryEdge, because in a case like
217
+ // this:
218
+ //
219
+ // v -> PEER(a@1||2)
220
+ // a@1 -> PEER(b@1)
221
+ // a@2 -> PEER(b@2)
222
+ // b@1 -> PEER(a@1)
223
+ // b@2 -> PEER(a@2)
224
+ //
225
+ // root
226
+ // +-- v
227
+ // +-- a@2
228
+ // +-- b@2
229
+ //
230
+ // Trying to place a peer group of (a@1, b@1) would fail to note that
231
+ // they can be replaced, if we did it by looping 1 by 1. If we are
232
+ // replacing something, we don't have to check its peer deps, because
233
+ // the peerDeps in the placed peerSet will presumably satisfy.
234
+ const entryNode = entryEdge.to
235
+ const entryRep = dep.parent.children.get(entryNode.name)
236
+ if (entryRep) {
237
+ if (entryRep.canReplace(entryNode, dep.parent.children.keys()))
238
+ continue
239
+ }
240
+
241
+ let canClobber = !entryRep
242
+ if (!entryRep) {
243
+ const peerReplacementWalk = new Set([entryNode])
244
+ OUTER: for (const currentPeer of peerReplacementWalk) {
245
+ for (const edge of currentPeer.edgesOut.values()) {
246
+ if (!edge.peer || !edge.valid)
247
+ continue
248
+ const rep = dep.parent.children.get(edge.name)
249
+ if (!rep) {
250
+ if (edge.to)
251
+ peerReplacementWalk.add(edge.to)
252
+ continue
253
+ }
254
+ if (!rep.satisfies(edge)) {
255
+ canClobber = false
256
+ break OUTER
257
+ }
258
+ }
259
+ }
260
+ }
261
+ if (canClobber)
262
+ continue
263
+
264
+ // ok, we can't replace, but maybe we can nest the current set deeper?
265
+ let canNestCurrent = true
266
+ for (const currentPeer of currentPeers) {
267
+ if (!canNestCurrent)
268
+ break
269
+
270
+ // still possible to nest this peerSet
271
+ const curDeep = deepestNestingTarget(entryEdge.from, currentPeer.name)
272
+ if (curDeep === target || target.isDescendantOf(curDeep)) {
273
+ canNestCurrent = false
274
+ canReplace = false
275
+ }
276
+ if (canNestCurrent)
277
+ continue
278
+ }
279
+ }
280
+
281
+ // if we can nest or replace all the current peer groups, we can replace.
282
+ if (canReplace)
283
+ return this.canPlacePeers(REPLACE)
284
+
285
+ return CONFLICT
286
+ }
287
+
288
+ checkCanPlaceNoCurrent () {
289
+ const { target, peerEntryEdge, dep, name } = this
290
+
291
+ // check to see what that name resolves to here, and who may depend on
292
+ // being able to reach it by crawling up past the parent. we know
293
+ // that it's not the target's direct child node, and if it was a direct
294
+ // dep of the target, we would have conflicted earlier.
295
+ const current = target !== peerEntryEdge.from && target.resolve(name)
296
+ if (current) {
297
+ for (const edge of current.edgesIn.values()) {
298
+ if (edge.from.isDescendantOf(target) && edge.valid) {
299
+ if (!dep.satisfies(edge))
300
+ return CONFLICT
301
+ }
302
+ }
303
+ }
304
+
305
+ // no objections, so this is fine as long as peers are ok here.
306
+ return this.canPlacePeers(OK)
307
+ }
308
+
309
+ get deepestNestingTarget () {
310
+ const start = this.parent ? this.parent.deepestNestingTarget
311
+ : this.edge.from
312
+ return deepestNestingTarget(start, this.name)
313
+ }
314
+
315
+ get conflictChildren () {
316
+ return this.allChildren.filter(c => c.canPlace === CONFLICT)
317
+ }
318
+
319
+ get allChildren () {
320
+ const set = new Set(this.children)
321
+ for (const child of set) {
322
+ for (const grandchild of child.children)
323
+ set.add(grandchild)
324
+ }
325
+ return [...set]
326
+ }
327
+
328
+ get top () {
329
+ return this.parent ? this.parent.top : this
330
+ }
331
+
332
+ // check if peers can go here. returns state or CONFLICT
333
+ canPlacePeers (state) {
334
+ this.canPlaceSelf = state
335
+ if (this._canPlacePeers)
336
+ return this._canPlacePeers
337
+
338
+ // TODO: represent peerPath in ERESOLVE error somehow?
339
+ const peerPath = [...this.peerPath, this.dep]
340
+ let sawConflict = false
341
+ for (const peerEdge of this.dep.edgesOut.values()) {
342
+ if (!peerEdge.peer || !peerEdge.to || peerPath.includes(peerEdge.to))
343
+ continue
344
+ const peer = peerEdge.to
345
+ // it may be the case that the *initial* dep can be nested, but a peer
346
+ // of that dep needs to be placed shallower, because the target has
347
+ // a peer dep on the peer as well.
348
+ const target = deepestNestingTarget(this.target, peer.name)
349
+ const cpp = new CanPlaceDep({
350
+ dep: peer,
351
+ target,
352
+ parent: this,
353
+ edge: peerEdge,
354
+ peerPath,
355
+ // always place peers in preferDedupe mode
356
+ preferDedupe: true,
357
+ })
358
+ /* istanbul ignore next */
359
+ debug(() => {
360
+ if (this.children.some(c => c.dep === cpp.dep))
361
+ throw new Error('checking same dep repeatedly')
362
+ })
363
+ this.children.push(cpp)
364
+
365
+ if (cpp.canPlace === CONFLICT)
366
+ sawConflict = true
367
+ }
368
+
369
+ this._canPlacePeers = sawConflict ? CONFLICT : state
370
+ return this._canPlacePeers
371
+ }
372
+
373
+ // what is the node that is causing this peerSet to be placed?
374
+ get peerSetSource () {
375
+ return this.parent ? this.parent.peerSetSource : this.edge.from
376
+ }
377
+
378
+ get peerEntryEdge () {
379
+ return this.top.edge
380
+ }
381
+
382
+ static get CONFLICT () {
383
+ return CONFLICT
384
+ }
385
+
386
+ static get OK () {
387
+ return OK
388
+ }
389
+
390
+ static get REPLACE () {
391
+ return REPLACE
392
+ }
393
+
394
+ static get KEEP () {
395
+ return KEEP
396
+ }
397
+
398
+ get description () {
399
+ const { canPlace } = this
400
+ return canPlace && canPlace.description ||
401
+ /* istanbul ignore next - old node affordance */ canPlace
402
+ }
403
+ }
404
+
405
+ module.exports = CanPlaceDep
@@ -0,0 +1,16 @@
1
+ // given a starting node, what is the *deepest* target where name could go?
2
+ // This is not on the Node class for the simple reason that we sometimes
3
+ // need to check the deepest *potential* target for a Node that is not yet
4
+ // added to the tree where we are checking.
5
+ const deepestNestingTarget = (start, name) => {
6
+ for (const target of start.ancestry()) {
7
+ // note: this will skip past the first target if edge is peer
8
+ if (target.isProjectRoot || !target.resolveParent)
9
+ return target
10
+ const targetEdge = target.edgesOut.get(name)
11
+ if (!targetEdge || !targetEdge.peer)
12
+ return target
13
+ }
14
+ }
15
+
16
+ module.exports = deepestNestingTarget
package/lib/diff.js CHANGED
@@ -45,8 +45,7 @@ class Diff {
45
45
  const { root } = filterNode
46
46
  if (root !== ideal && root !== actual)
47
47
  throw new Error('invalid filterNode: outside idealTree/actualTree')
48
- const { target } = root
49
- const rootTarget = target || root
48
+ const rootTarget = root.target
50
49
  const edge = [...rootTarget.edgesOut.values()].filter(e => {
51
50
  return e.to && (e.to === filterNode || e.to.target === filterNode)
52
51
  })[0]
@@ -56,8 +55,7 @@ class Diff {
56
55
  filterSet.add(actual)
57
56
  if (edge && edge.to) {
58
57
  filterSet.add(edge.to)
59
- if (edge.to.target)
60
- filterSet.add(edge.to.target)
58
+ filterSet.add(edge.to.target)
61
59
  }
62
60
  filterSet.add(filterNode)
63
61
 
@@ -65,7 +63,7 @@ class Diff {
65
63
  tree: filterNode,
66
64
  visit: node => filterSet.add(node),
67
65
  getChildren: node => {
68
- node = node.target || node
66
+ node = node.target
69
67
  const loc = node.location
70
68
  const idealNode = ideal.inventory.get(loc)
71
69
  const ideals = !idealNode ? []
@@ -145,9 +143,9 @@ const allChildren = node => {
145
143
  if (!node)
146
144
  return new Map()
147
145
 
148
- // if the node is a global root, and also a link, then what we really
146
+ // if the node is root, and also a link, then what we really
149
147
  // want is to traverse the target's children
150
- if (node.global && node.isRoot && node.isLink)
148
+ if (node.isRoot && node.isLink)
151
149
  return allChildren(node.target)
152
150
 
153
151
  const kids = new Map()
package/lib/edge.js CHANGED
@@ -37,6 +37,7 @@ const printableEdge = (edge) => {
37
37
  ...(edgeFrom != null ? { from: edgeFrom } : {}),
38
38
  ...(edgeTo ? { to: edgeTo } : {}),
39
39
  ...(edge.error ? { error: edge.error } : {}),
40
+ ...(edge.overridden ? { overridden: true } : {}),
40
41
  })
41
42
  }
42
43
 
@@ -72,6 +73,7 @@ class Edge {
72
73
  throw new TypeError('must provide "from" node')
73
74
  this[_setFrom](from)
74
75
  this[_error] = this[_loadError]()
76
+ this.overridden = false
75
77
  }
76
78
 
77
79
  satisfiedBy (node) {
package/lib/node.js CHANGED
@@ -409,7 +409,7 @@ class Node {
409
409
  }
410
410
 
411
411
  isDescendantOf (node) {
412
- for (let p = this; p; p = p.parent) {
412
+ for (let p = this; p; p = p.resolveParent) {
413
413
  if (p === node)
414
414
  return true
415
415
  }
@@ -481,6 +481,11 @@ class Node {
481
481
  return this === this.root || this === this.root.target
482
482
  }
483
483
 
484
+ * ancestry () {
485
+ for (let anc = this; anc; anc = anc.resolveParent)
486
+ yield anc
487
+ }
488
+
484
489
  set root (root) {
485
490
  // setting to null means this is the new root
486
491
  // should only ever be one step
@@ -649,7 +654,7 @@ class Node {
649
654
  })
650
655
 
651
656
  if (this.isLink) {
652
- const target = node.target || node
657
+ const target = node.target
653
658
  this[_target] = target
654
659
  this[_package] = target.package
655
660
  target.linksIn.add(this)
@@ -878,16 +883,31 @@ class Node {
878
883
  // root dependency brings peer deps along with it. In that case, we
879
884
  // will go ahead and create the invalid state, and then try to resolve
880
885
  // it with more tree construction, because it's a user request.
881
- canReplaceWith (node) {
886
+ canReplaceWith (node, ignorePeers = []) {
882
887
  if (node.name !== this.name)
883
888
  return false
884
889
 
890
+ if (node.packageName !== this.packageName)
891
+ return false
892
+
893
+ ignorePeers = new Set(ignorePeers)
894
+
885
895
  // gather up all the deps of this node and that are only depended
886
896
  // upon by deps of this node. those ones don't count, since
887
897
  // they'll be replaced if this node is replaced anyway.
888
898
  const depSet = gatherDepSet([this], e => e.to !== this && e.valid)
889
899
 
890
900
  for (const edge of this.edgesIn) {
901
+ // when replacing peer sets, we need to be able to replace the entire
902
+ // peer group, which means we ignore incoming edges from other peers
903
+ // within the replacement set.
904
+ const ignored = !this.isTop &&
905
+ edge.from.parent === this.parent &&
906
+ edge.peer &&
907
+ ignorePeers.has(edge.from.name)
908
+ if (ignored)
909
+ continue
910
+
891
911
  // only care about edges that don't originate from this node
892
912
  if (!depSet.has(edge.from) && !edge.satisfiedBy(node))
893
913
  return false
@@ -896,8 +916,8 @@ class Node {
896
916
  return true
897
917
  }
898
918
 
899
- canReplace (node) {
900
- return node.canReplaceWith(this)
919
+ canReplace (node, ignorePeers) {
920
+ return node.canReplaceWith(this, ignorePeers)
901
921
  }
902
922
 
903
923
  // return true if it's safe to remove this node, because anything that
@@ -1174,7 +1194,7 @@ class Node {
1174
1194
  }
1175
1195
 
1176
1196
  get target () {
1177
- return null
1197
+ return this
1178
1198
  }
1179
1199
 
1180
1200
  set target (n) {
@@ -1197,11 +1217,25 @@ class Node {
1197
1217
  return this.isTop ? this : this.parent.top
1198
1218
  }
1199
1219
 
1220
+ get isFsTop () {
1221
+ return !this.fsParent
1222
+ }
1223
+
1224
+ get fsTop () {
1225
+ return this.isFsTop ? this : this.fsParent.fsTop
1226
+ }
1227
+
1200
1228
  get resolveParent () {
1201
1229
  return this.parent || this.fsParent
1202
1230
  }
1203
1231
 
1204
1232
  resolve (name) {
1233
+ /* istanbul ignore next - should be impossible,
1234
+ * but I keep doing this mistake in tests */
1235
+ debug(() => {
1236
+ if (typeof name !== 'string' || !name)
1237
+ throw new Error('non-string passed to Node.resolve')
1238
+ })
1205
1239
  const mine = this.children.get(name)
1206
1240
  if (mine)
1207
1241
  return mine
@@ -0,0 +1,72 @@
1
+ // Given a node in a tree, return all of the peer dependency sets that
2
+ // it is a part of, with the entry (top or non-peer) edges into the sets
3
+ // identified.
4
+ //
5
+ // With this information, we can determine whether it is appropriate to
6
+ // replace the entire peer set with another (and remove the old one),
7
+ // push the set deeper into the tree, and so on.
8
+ //
9
+ // Returns a Map of { edge => Set(peerNodes) },
10
+
11
+ const peerEntrySets = node => {
12
+ // this is the union of all peer groups that the node is a part of
13
+ // later, we identify all of the entry edges, and create a set of
14
+ // 1 or more overlapping sets that this node is a part of.
15
+ const unionSet = new Set([node])
16
+ for (const node of unionSet) {
17
+ for (const edge of node.edgesOut.values()) {
18
+ if (edge.valid && edge.peer && edge.to)
19
+ unionSet.add(edge.to)
20
+ }
21
+ for (const edge of node.edgesIn) {
22
+ if (edge.valid && edge.peer)
23
+ unionSet.add(edge.from)
24
+ }
25
+ }
26
+ const entrySets = new Map()
27
+ for (const peer of unionSet) {
28
+ for (const edge of peer.edgesIn) {
29
+ // if not valid, it doesn't matter anyway. either it's been previously
30
+ // overridden, or it's the thing we're interested in replacing.
31
+ if (!edge.valid)
32
+ continue
33
+ // this is the entry point into the peer set
34
+ if (!edge.peer || edge.from.isTop) {
35
+ // get the subset of peer brought in by this peer entry edge
36
+ const sub = new Set([peer])
37
+ for (const peer of sub) {
38
+ for (const edge of peer.edgesOut.values()) {
39
+ if (edge.valid && edge.peer && edge.to)
40
+ sub.add(edge.to)
41
+ }
42
+ }
43
+ // if this subset does not include the node we are focused on,
44
+ // then it is not relevant for our purposes. Example:
45
+ //
46
+ // a -> (b, c, d)
47
+ // b -> PEER(d) b -> d -> e -> f <-> g
48
+ // c -> PEER(f, h) c -> (f <-> g, h -> g)
49
+ // d -> PEER(e) d -> e -> f <-> g
50
+ // e -> PEER(f)
51
+ // f -> PEER(g)
52
+ // g -> PEER(f)
53
+ // h -> PEER(g)
54
+ //
55
+ // The unionSet(e) will include c, but we don't actually care about
56
+ // it. We only expanded to the edge of the peer nodes in order to
57
+ // find the entry edges that caused the inclusion of peer sets
58
+ // including (e), so we want:
59
+ // Map{
60
+ // Edge(a->b) => Set(b, d, e, f, g)
61
+ // Edge(a->d) => Set(d, e, f, g)
62
+ // }
63
+ if (sub.has(node))
64
+ entrySets.set(edge, sub)
65
+ }
66
+ }
67
+ }
68
+
69
+ return entrySets
70
+ }
71
+
72
+ module.exports = peerEntrySets