Add session bridge pair management, core replicator policy clear, drive mount unmount-all, relay tunnel bulk close, compact codec resetStats, peer bootstrap and noise session listing, graph and disk stripe clear, memetic clear, retry policy route management, bitfield drain, task orchestrator cancelAllPending, and distributed event bus topic/unsubscribe helpers. Co-authored-by: Cursor <[email protected]>
166 lines
3.7 KiB
JavaScript
166 lines
3.7 KiB
JavaScript
require('bare-process/global')
|
|
const EventEmitter = require('bare-events')
|
|
const { assertNonEmpty } = require('../../_shared/lib/errors.js')
|
|
|
|
const PROTOCOL = 'graph-index/v1'
|
|
|
|
class HyperP2PGraphIndex extends EventEmitter {
|
|
constructor (opts = {}) {
|
|
super()
|
|
this._adj = new Map()
|
|
this._stats = { edges: 0, queries: 0 }
|
|
}
|
|
|
|
addEdge (from, to, opts = {}) {
|
|
assertNonEmpty(from, 'from')
|
|
assertNonEmpty(to, 'to')
|
|
const directed = opts.directed !== false
|
|
const add = (a, b) => {
|
|
const list = this._adj.get(a) || new Set()
|
|
list.add(b)
|
|
this._adj.set(a, list)
|
|
}
|
|
add(from, to)
|
|
if (!directed) add(to, from)
|
|
this._stats.edges++
|
|
this.emit('edge', { from, to, directed })
|
|
return { from, to, directed }
|
|
}
|
|
|
|
neighbors (node) {
|
|
assertNonEmpty(node, 'node')
|
|
this._stats.queries++
|
|
const set = this._adj.get(node)
|
|
return set ? [...set].sort() : []
|
|
}
|
|
|
|
bfs (start, depth = 1) {
|
|
assertNonEmpty(start, 'start')
|
|
const maxDepth = Math.max(0, depth | 0)
|
|
const visited = new Set([start])
|
|
const layers = [{ node: start, depth: 0 }]
|
|
const order = [start]
|
|
let frontier = [start]
|
|
|
|
for (let d = 0; d < maxDepth && frontier.length; d++) {
|
|
const next = []
|
|
for (const node of frontier) {
|
|
for (const nbr of this.neighbors(node)) {
|
|
if (visited.has(nbr)) continue
|
|
visited.add(nbr)
|
|
order.push(nbr)
|
|
layers.push({ node: nbr, depth: d + 1 })
|
|
next.push(nbr)
|
|
}
|
|
}
|
|
frontier = next
|
|
}
|
|
|
|
return { start, depth: maxDepth, order, layers }
|
|
}
|
|
|
|
hasEdge (from, to) {
|
|
const set = this._adj.get(from)
|
|
return !!(set && set.has(to))
|
|
}
|
|
|
|
removeEdge (from, to, opts = {}) {
|
|
assertNonEmpty(from, 'from')
|
|
assertNonEmpty(to, 'to')
|
|
const directed = opts.directed !== false
|
|
const del = (a, b) => {
|
|
const set = this._adj.get(a)
|
|
if (set) set.delete(b)
|
|
}
|
|
del(from, to)
|
|
if (!directed) del(to, from)
|
|
return true
|
|
}
|
|
|
|
outDegree (node) {
|
|
return this.neighbors(node).length
|
|
}
|
|
|
|
edgeCount () {
|
|
let n = 0
|
|
for (const list of this._adj.values()) n += list.size
|
|
return n
|
|
}
|
|
|
|
nodes () {
|
|
const all = new Set()
|
|
for (const [n, list] of this._adj) {
|
|
all.add(n)
|
|
for (const v of list) all.add(v)
|
|
}
|
|
return [...all].sort()
|
|
}
|
|
|
|
removeNode (node) {
|
|
assertNonEmpty(node, 'node')
|
|
this._adj.delete(node)
|
|
for (const [n, list] of this._adj) list.delete(node)
|
|
return true
|
|
}
|
|
|
|
shortestPath (from, to, maxHops = 32) {
|
|
assertNonEmpty(from, 'from')
|
|
assertNonEmpty(to, 'to')
|
|
if (from === to) return [from]
|
|
const queue = [[from]]
|
|
const visited = new Set([from])
|
|
while (queue.length) {
|
|
const path = queue.shift()
|
|
if (path.length > maxHops) break
|
|
const node = path[path.length - 1]
|
|
for (const nbr of this.neighbors(node)) {
|
|
if (visited.has(nbr)) continue
|
|
const next = [...path, nbr]
|
|
if (nbr === to) return next
|
|
visited.add(nbr)
|
|
queue.push(next)
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
reachableCount (start, depth = 2) {
|
|
return this.bfs(start, depth).order.length
|
|
}
|
|
|
|
hasPath (from, to) {
|
|
return this.shortestPath(from, to) != null
|
|
}
|
|
|
|
toJSON () {
|
|
const edges = []
|
|
for (const [from, list] of this._adj) {
|
|
for (const to of list) edges.push({ from, to })
|
|
}
|
|
return { edges }
|
|
}
|
|
|
|
clearAll () {
|
|
const n = this.nodes().length
|
|
this._adj.clear()
|
|
return n
|
|
}
|
|
|
|
getStats () {
|
|
return {
|
|
...this._stats,
|
|
nodes: this.nodes().length,
|
|
protocol: PROTOCOL
|
|
}
|
|
}
|
|
|
|
async ready () { return this }
|
|
|
|
async close () {
|
|
this._adj.clear()
|
|
this.emit('closed')
|
|
}
|
|
}
|
|
|
|
module.exports = { HyperP2PGraphIndex, PROTOCOL }
|