Expand media, network-stack, pear-platform, and scheduling module APIs

Add batch helpers, clear/count utilities, and management methods across content protection, FEC, flow shaper, circuit loom, trust gate, peer scheduler, and related modules.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Raven Scott
2026-05-21 03:17:48 -04:00
co-authored by Cursor
parent c3b112f435
commit c2282bd2b9
20 changed files with 220 additions and 0 deletions
@@ -94,6 +94,16 @@ class HyperP2PMessageEnvelope extends EventEmitter {
return { ...envelope, payload, at: envelope.at || Date.now() } return { ...envelope, payload, at: envelope.at || Date.now() }
} }
wrapBatch (payloads, opts = {}) {
if (!Array.isArray(payloads)) throw new Error('payloads must be an array')
return payloads.map((p) => this.wrap(p, opts))
}
unwrapBatch (envelopes) {
if (!Array.isArray(envelopes)) throw new Error('envelopes must be an array')
return envelopes.map((e) => this.unwrap(e))
}
resetStats () { resetStats () {
this._stats = { wrapped: 0, unwrapped: 0, encoded: 0, decoded: 0, failed: 0 } this._stats = { wrapped: 0, unwrapped: 0, encoded: 0, decoded: 0, failed: 0 }
return this._stats return this._stats
@@ -124,6 +124,15 @@ class HyperP2PSlaBudget extends EventEmitter {
return true return true
} }
serviceCount () {
return this._services.size
}
allocateBatch (allocations) {
if (!Array.isArray(allocations)) throw new Error('allocations must be an array')
return allocations.map((a) => this.allocate(a.service, a.amount))
}
getStats () { getStats () {
return { return {
...this._stats, ...this._stats,
@@ -81,6 +81,19 @@ class HyperP2PContentProtection extends EventEmitter {
return payloads.map((p) => this.encryptSegment(p, keyId)) return payloads.map((p) => this.encryptSegment(p, keyId))
} }
decryptBatch (segments, keyId = null) {
if (!Array.isArray(segments)) throw new Error('segments array required')
return segments.map((s) => this.decryptSegment(s.data || s, keyId))
}
hasKey (keyId) {
return this._keys.has(String(keyId))
}
keyCount () {
return this._keys.size
}
clearKeys () { clearKeys () {
const n = this._keys.size const n = this._keys.size
this._keys.clear() this._keys.clear()
@@ -93,6 +93,16 @@ class HyperP2PFecVideo extends EventEmitter {
return entries.map((e) => this.encodeGroup(e.groupId, e.shards)) return entries.map((e) => this.encodeGroup(e.groupId, e.shards))
} }
clearAll () {
const n = this._groups.size
this._groups.clear()
return n
}
groupCount () {
return this._groups.size
}
getStats () { getStats () {
return mediaStats(this._stats, PROTOCOL, { groups: this._groups.size }) return mediaStats(this._stats, PROTOCOL, { groups: this._groups.size })
} }
@@ -87,6 +87,15 @@ class HyperP2PLatencyOptimizer extends EventEmitter {
return n return n
} }
pathCount () {
return this._paths.size
}
measureBatch (entries) {
if (!Array.isArray(entries)) throw new Error('entries must be an array')
return entries.map((e) => this.measurePath(e.pathId, e.hops || []))
}
getStats () { getStats () {
return mediaStats(this._stats, PROTOCOL, { paths: this._paths.size }) return mediaStats(this._stats, PROTOCOL, { paths: this._paths.size })
} }
@@ -90,6 +90,16 @@ class HyperP2POriginHybridBridge extends EventEmitter {
return this._stats.originBytes + this._stats.p2pBytes return this._stats.originBytes + this._stats.p2pBytes
} }
clearStream (streamId) {
return this._origins.delete(assertStreamId(streamId))
}
clearAll () {
const n = this._origins.size
this._origins.clear()
return n
}
getStats () { getStats () {
return mediaStats(this._stats, PROTOCOL, { return mediaStats(this._stats, PROTOCOL, {
offloadRatio: this.p2pOffloadRatio(), offloadRatio: this.p2pOffloadRatio(),
@@ -94,6 +94,17 @@ class HyperP2PQosTopic extends EventEmitter {
return n return n
} }
handlerCount () {
return this._handlers.size
}
peekHighest () {
for (let q = MAX_QOS; q >= 0; q--) {
if (this._queues[q].length) return { ...this._queues[q][0] }
}
return null
}
_onGossip (data) { _onGossip (data) {
if (!data || data.type !== 'qos-publish') return if (!data || data.type !== 'qos-publish') return
this._stats.gossipIn++ this._stats.gossipIn++
@@ -87,6 +87,15 @@ class HyperP2PStreamChunker extends EventEmitter {
return { tail, data: out } return { tail, data: out }
} }
pendingBytes () {
return this._pending.length
}
writeBatch (chunks) {
if (!Array.isArray(chunks)) throw new Error('chunks must be an array')
return chunks.map((c) => this.write(c))
}
getStats () { getStats () {
return { return {
...this._stats, ...this._stats,
@@ -98,6 +98,15 @@ class HyperP2PCircuitLoom extends EventEmitter {
return n return n
} }
circuitCount () {
return this._circuits.size
}
hopCount (circuitId) {
const c = this._circuits.get(circuitId)
return c ? c.hops.length : 0
}
getStats () { getStats () {
return { ...this._stats, open: this.openCount(), total: this._circuits.size, protocol: PROTOCOL } return { ...this._stats, open: this.openCount(), total: this._circuits.size, protocol: PROTOCOL }
} }
@@ -79,6 +79,23 @@ class HyperP2PFlowShaper extends EventEmitter {
for (const p of PRIOS) this._tokens[p] = Math.min(this._rates[p], this._tokens[p] + Math.floor(this._rates[p] / 10)) for (const p of PRIOS) this._tokens[p] = Math.min(this._rates[p], this._tokens[p] + Math.floor(this._rates[p] / 10))
} }
clearQueues () {
let n = 0
for (const p of PRIOS) {
n += this._queues[p].length
this._queues[p] = []
}
return n
}
setRate (priority, rate) {
if (!PRIOS.includes(priority)) throw new Error('priority must be control|data|bulk')
if (rate < 0) throw new Error('rate must be non-negative')
this._rates[priority] = rate
this._tokens[priority] = rate
return rate
}
getStats () { getStats () {
return { return {
...this._stats, ...this._stats,
@@ -82,6 +82,16 @@ class HyperP2PSecretStreamPair {
this._stats = { pairs: 0 } this._stats = { pairs: 0 }
} }
publicKeyHex () {
return require('b4a').toString(this.keyPair.publicKey, 'hex')
}
rotateKeyPair () {
this.destroyActive()
this.keyPair = hypercoreCrypto.keyPair()
return this.keyPair
}
getStats () { getStats () {
return { ...this._stats, protocol: PROTOCOL } return { ...this._stats, protocol: PROTOCOL }
} }
@@ -67,6 +67,20 @@ class HyperP2PHealthProbe extends EventEmitter {
return n return n
} }
reportBatch (entries) {
if (!Array.isArray(entries)) throw new Error('entries must be an array')
return entries.map((e) => this.report(e.peerId, e.status || e))
}
isHealthy (peerId) {
const r = this.get(peerId)
return !!(r && r.ok)
}
peerCount () {
return this._reports.size
}
_onGossip (data) { _onGossip (data) {
if (!data || data.type !== 'health' || !data.entry) return if (!data || data.type !== 'health' || !data.entry) return
this._stats.gossipIn++ this._stats.gossipIn++
@@ -85,6 +85,16 @@ class HyperBareAddonResolve extends EventEmitter {
return n return n
} }
registerBatch (entries) {
if (!Array.isArray(entries)) throw new Error('entries must be an array')
return entries.map((e) => this.register(e.name, e.spec || e))
}
resolveBatch (names) {
if (!Array.isArray(names)) throw new Error('names must be an array')
return names.map((n) => this.resolve(n))
}
getStats () { getStats () {
return platformStats(this._stats, PROTOCOL, { return platformStats(this._stats, PROTOCOL, {
addons: this._addons.size, addons: this._addons.size,
@@ -68,6 +68,15 @@ class HyperBareBundleBridge extends EventEmitter {
return n return n
} }
resolveBatch (ids) {
if (!Array.isArray(ids)) throw new Error('ids must be an array')
return ids.map((id) => this.resolve(id))
}
bundleCount () {
return this._bundles.size
}
_gossip (data) { _gossip (data) {
if (!this._peerMsgs) return if (!this._peerMsgs) return
gossipSend(this, data) gossipSend(this, data)
@@ -92,6 +92,16 @@ class HyperBareTargetMatrix extends EventEmitter {
return this._targets.some((t) => t.id === id) return this._targets.some((t) => t.id === id)
} }
resetToDefaults () {
this._targets = [...DEFAULT_TARGETS]
this.host = this._targets[0]?.id || 'darwin-arm64'
return this.list()
}
targetCount () {
return this._targets.length
}
getStats () { getStats () {
return platformStats(this._stats, PROTOCOL, { return platformStats(this._stats, PROTOCOL, {
host: this.host, host: this.host,
@@ -122,6 +122,21 @@ class HyperPearPreflightSync extends EventEmitter {
return [...this._jobs.keys()] return [...this._jobs.keys()]
} }
failTimedOut () {
let n = 0
for (const job of this.listPending()) {
if (this.timedOut(job.id)) {
this.fail(job.id, 'warmup timeout')
n++
}
}
return n
}
jobCount () {
return this._jobs.size
}
getStats () { getStats () {
return platformStats(this._stats, PROTOCOL, { return platformStats(this._stats, PROTOCOL, {
jobs: this._jobs.size, jobs: this._jobs.size,
@@ -94,6 +94,16 @@ class HyperPearTrustGate extends EventEmitter {
return n return n
} }
checkBatch (keys, autoTrust = false) {
if (!Array.isArray(keys)) throw new Error('keys must be an array')
return keys.map((k) => this.check(k, autoTrust))
}
trustBatch (keys) {
if (!Array.isArray(keys)) throw new Error('keys must be an array')
return keys.map((k) => this.trust(k))
}
getStats () { getStats () {
return platformStats(this._stats, PROTOCOL, { trusted: this._trusted.size }) return platformStats(this._stats, PROTOCOL, { trusted: this._trusted.size })
} }
@@ -123,6 +123,24 @@ class HyperP2PCircuitBreaker extends EventEmitter {
return n return n
} }
failuresFor (id) {
const c = this._circuits.get(id)
return c ? c.failures : 0
}
ensure (id) {
if (!this._circuits.has(id)) {
this._circuits.set(id, {
state: 'closed',
failures: 0,
successes: 0,
openedAt: 0,
halfOpenTrials: 0
})
}
return this.getState(id)
}
getStats () { getStats () {
return { return {
...this._stats, ...this._stats,
@@ -70,6 +70,15 @@ class HyperP2PPeerScheduler extends EventEmitter {
return ids.length return ids.length
} }
jobIds () {
return [...this._jobs.keys()]
}
scheduleBatch (entries) {
if (!Array.isArray(entries)) throw new Error('entries must be an array')
return entries.map((e) => this.schedule(e.expr, e.id, { shard: e.shard }))
}
msUntil (jobId, now = Date.now()) { msUntil (jobId, now = Date.now()) {
const job = this._jobs.get(jobId) const job = this._jobs.get(jobId)
if (!job) return null if (!job) return null
@@ -87,6 +87,14 @@ class HyperP2PKeyRotation extends EventEmitter {
return list.reduce((a, b) => (a.activateAt < b.activateAt ? a : b)) return list.reduce((a, b) => (a.activateAt < b.activateAt ? a : b))
} }
getActive (keyId) {
return this._active.get(String(keyId)) || null
}
pendingKeyIds () {
return [...this._pending.keys()]
}
_maybeActivate (keyId, now = Date.now()) { _maybeActivate (keyId, now = Date.now()) {
const list = this._pending.get(keyId) || [] const list = this._pending.get(keyId) || []
for (const e of list) { for (const e of list) {