# @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)_ - [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)_- [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 ```typescript 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 ```typescript import { createEventBus } from "@suaveplan/events"; import { HybridDiscoveryManager, type HybridDiscoveryEventMap, } from "@suaveplan/network-discovery-hybrid"; const bus = createEventBus(); 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` 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.