Files
2026-05-27 23:47:53 +01:00
..
2026-05-27 23:47:53 +01:00
2026-05-27 23:47:53 +01:00
2026-05-27 23:47:53 +01:00
2026-05-27 23:47:53 +01:00
2026-05-27 23:47:53 +01:00
2026-05-27 23:47:53 +01:00
2026-05-27 23:47:53 +01:00
2026-05-27 23:47:53 +01:00

@suaveplan/network-discovery-hybrid - Browser-tier multi-strategy DiscoveryStrategy orchestrator: composes any subset of the five Wave-2 sibling strategies (peerjs / crdt / trystero / webtorrent / gundb) into a single HybridDiscoveryManager with priority-based peer selection, per-strategy health tracking, automatic fallback, de-duplicated peer set with PeerSource provenance, and unified lifecycle. Implements the network-bootstrap DiscoveryStrategy contract so it can itself be chained.

Summary

Browser-tier multi-strategy DiscoveryStrategy orchestrator. Composes any subset of the five Wave-2 sibling discovery strategies behind a single HybridDiscoveryManager with priority-based peer selection, per-strategy health tracking, automatic fallback, and a de-duplicated peer set with PeerSource provenance.

Documentation tree

- [hybrid-discovery-manager](./src/core/hybrid-discovery-manager/hybrid-discovery-manager.md) — _(no submodules)_- [lifecycle](./src/core/lifecycle/lifecycle.md) — _(no submodules)_- [lifecycle](./src/core/lifecycle/lifecycle.md) — _(no submodules)_- [peer-tracker](./src/core/peer-tracker/peer-tracker.md) — _(no submodules)_- [peer-tracker](./src/core/peer-tracker/peer-tracker.md) — _(no submodules)_- [strategy-loader](./src/core/strategy-loader/strategy-loader.md) — _(no submodules)_- [strategy-loader](./src/core/strategy-loader/strategy-loader.md) — _(no submodules)_- [strategy-manager](./src/core/strategy-manager/strategy-manager.md) — _(no submodules)_- [strategy-manager](./src/core/strategy-manager/strategy-manager.md) — _(no submodules)_- [errors](./src/errors/errors.md) — _(no submodules)_- [errors](./src/errors/errors.md) — _(no submodules)_- [otel](./src/otel/otel.md) — _(no submodules)_- [otel](./src/otel/otel.md) — _(no submodules)_- [types](./src/types/types.md) — _(no submodules)_- [types](./src/types/types.md) — _(no submodules)_- [hybrid-discovery-manager](./src/core/hybrid-discovery-manager/hybrid-discovery-manager.md) — _(no submodules)_- [lifecycle](./src/core/lifecycle/lifecycle.md) — _(no submodules)_- [lifecycle](./src/core/lifecycle/lifecycle.md) — _(no submodules)_- [peer-tracker](./src/core/peer-tracker/peer-tracker.md) — _(no submodules)_- [peer-tracker](./src/core/peer-tracker/peer-tracker.md) — _(no submodules)_- [strategy-loader](./src/core/strategy-loader/strategy-loader.md) — _(no submodules)_- [strategy-loader](./src/core/strategy-loader/strategy-loader.md) — _(no submodules)_- [strategy-manager](./src/core/strategy-manager/strategy-manager.md) — _(no submodules)_- [strategy-manager](./src/core/strategy-manager/strategy-manager.md) — _(no submodules)_- [errors](./src/errors/errors.md) — _(no submodules)_- [errors](./src/errors/errors.md) — _(no submodules)_- [otel](./src/otel/otel.md) — _(no submodules)_- [otel](./src/otel/otel.md) — _(no submodules)_- [types](./src/types/types.md) — _(no submodules)_- [types](./src/types/types.md) — _(no submodules)_- [hybrid-discovery-manager](./src/core/hybrid-discovery-manager/hybrid-discovery-manager.md) — _(no submodules)_- [lifecycle](./src/core/lifecycle/lifecycle.md) — _(no submodules)_- [lifecycle](./src/core/lifecycle/lifecycle.md) — _(no submodules)_- [peer-tracker](./src/core/peer-tracker/peer-tracker.md) — _(no submodules)_- [peer-tracker](./src/core/peer-tracker/peer-tracker.md) — _(no submodules)_- [strategy-loader](./src/core/strategy-loader/strategy-loader.md) — _(no submodules)_- [strategy-loader](./src/core/strategy-loader/strategy-loader.md) — _(no submodules)_- [strategy-manager](./src/core/strategy-manager/strategy-manager.md) — _(no submodules)_- [strategy-manager](./src/core/strategy-manager/strategy-manager.md) — _(no submodules)_- [errors](./src/errors/errors.md) — _(no submodules)_- [errors](./src/errors/errors.md) — _(no submodules)_- [otel](./src/otel/otel.md) — _(no submodules)_- [otel](./src/otel/otel.md) — _(no submodules)_- [types](./src/types/types.md) — _(no submodules)_- [types](./src/types/types.md) — _(no submodules)_- [hybrid-discovery-manager](./src/core/hybrid-discovery-manager/hybrid-discovery-manager.md) — _(no submodules)_- [lifecycle](./src/core/lifecycle/lifecycle.md) — _(no submodules)_ - [lifecycle](./src/core/lifecycle/lifecycle.md) — _(no submodules)_- [peer-tracker](./src/core/peer-tracker/peer-tracker.md) — _(no submodules)_ - [peer-tracker](./src/core/peer-tracker/peer-tracker.md) — _(no submodules)_- [strategy-loader](./src/core/strategy-loader/strategy-loader.md) — _(no submodules)_ - [strategy-loader](./src/core/strategy-loader/strategy-loader.md) — _(no submodules)_- [strategy-manager](./src/core/strategy-manager/strategy-manager.md) — _(no submodules)_ - [strategy-manager](./src/core/strategy-manager/strategy-manager.md) — _(no submodules)_- [errors](./src/errors/errors.md) — _(no submodules)_ - [errors](./src/errors/errors.md) — _(no submodules)_- [otel](./src/otel/otel.md) — _(no submodules)_ - [otel](./src/otel/otel.md) — _(no submodules)_- [types](./src/types/types.md) — _(no submodules)_ - [types](./src/types/types.md) — _(no submodules)_

Overview

HybridDiscoveryManager registers any subset of the following sibling strategies (and arbitrary "custom" strategies) and presents them as a single DiscoveryStrategy from @suaveplan/network-bootstrap:

  • @suaveplan/network-discovery-peerjs
  • @suaveplan/network-discovery-crdt
  • @suaveplan/network-discovery-trystero
  • @suaveplan/network-discovery-webtorrent
  • @suaveplan/network-discovery-gundb

Each sibling is declared as an optional peer-dependency so consumers only pay for the strategies they actually register. Missing peer-deps surface as STRATEGY_LOAD_FAILED health records, not crashes.

Because the orchestrator itself implements DiscoveryStrategy, it can be chained into a higher-level DiscoveryChain from @suaveplan/network-bootstrap — the hybrid is a recursive composer.

Basic Example

import { HybridDiscoveryManager } from "@suaveplan/network-discovery-hybrid";

const manager = new HybridDiscoveryManager({
    strategy: "hybrid",
    strategies: [
        { id: "peerjs", priority: "primary", strategy: peerJsStrategy },
        { id: "trystero", priority: "fallback", strategy: trysteroStrategy },
    ],
});

await manager.start();
const peers = await manager.discover(5_000);
await manager.stop();

Advanced Example

import { createEventBus } from "@suaveplan/events";
import {
    HybridDiscoveryManager,
    type HybridDiscoveryEventMap,
} from "@suaveplan/network-discovery-hybrid";

const bus = createEventBus<HybridDiscoveryEventMap>();
bus.on("hybrid:fallback", ({ from, to }) =>
    console.log(`Fallback ${from} → ${to}`)
);

const manager = new HybridDiscoveryManager(
    {
        strategy: "hybrid",
        loadBalancing: "fastest",
        autoFallback: true,
        deduplication: true,
        discoveryTimeoutMs: 8_000,
        strategies: [
            { id: "peerjs", priority: "primary", weight: 70 },
            {
                id: "trystero",
                priority: "fallback",
                weight: 30,
                factory: async () => {
                    const mod = await import(
                        "@suaveplan/network-discovery-trystero"
                    );
                    return new mod.TrysteroDiscovery({ /* ... */ });
                },
            },
        ],
    },
    { bus }
);

const controller = new AbortController();
await manager.start(controller.signal);
try {
    const peers = await manager.discover(5_000, controller.signal);
    console.log(manager.getStatus());
} finally {
    await manager.dispose();
}

Features

  • Three-bucket strategy priority — primary, fallback, backup.
  • Three load-balancing modes — priority, weighted, fastest.
  • Per-strategy StrategyHealth with rolling success-rate EMA, consecutive-failure counter, and last-error.
  • Automatic fallback — degraded primaries trigger dormant fallbacks to come online.
  • De-duplicated peer set with append-only PeerSource.discoveredBy provenance.
  • Unified lifecycle (start / stop / dispose) — idempotent throughout.
  • @suaveplan/timing Scheduler driving periodic health-checks + stale-peer sweep.
  • Optional @suaveplan/events EventBusLike<HybridDiscoveryEventMap> integration.
  • Full OTel surface (counters, histograms, gauge, spans).
  • AbortSignal threaded through every async path.
  • Zod boundary validation on the public config.
  • 100% test coverage including fast-check property tests.

Error Codes

Code Description
NET_DISC_HYBRID.NO_STRATEGIES Configuration contained an empty strategies array.
NET_DISC_HYBRID.ALL_STRATEGIES_DEGRADED Every registered strategy reported unavailable.
NET_DISC_HYBRID.STRATEGY_LOAD_FAILED Dynamic load of an optional peer-dep failed; recorded into health, not bubbled.
NET_DISC_HYBRID.DUPLICATE_STRATEGY Two StrategyConfig entries shared the same id.
NET_DISC_HYBRID.INVALID_OPTIONS Caller-supplied configuration failed Zod validation.
NET_DISC_HYBRID.DISPOSED Operation attempted after dispose().

Testing

The package contains a real in-package TestStrategy fixture under tests/setup/test-strategy.ts that fully implements the DiscoveryStrategy contract deterministically. There are NO mocks anywhere — no vi.mock, no __mocks__/, no module-level stubs. The integration suite in tests/integration.test.ts exercises the orchestrator against multiple real TestStrategy instances tagged with the five Wave-2 sibling ids and additionally verifies the real sibling packages are importable as optional peer-deps.