Files
modules/indexes-search/hyper-p2p-graph-index/index.js
T
Raven ScottandCursor a974294e74 Expand storage, transport, routing, agents, and messaging event bus modules
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]>
2026-05-21 03:00:55 -04:00

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 }