# `connection-registry` — multi-index lookup for active connections _(no submodules)_ ## Purpose Every plugin endpoint that opens a WebSocket connection registers it with a host-owned `ConnectionRegistry`. The registry is the lookup surface every other piece of the engine consumes: the subscription bus iterates connections by `(topic, tenant)`, the per-tenant cap counts by `tenant`, the heartbeat loop walks the registry to find stale connections. Keeping the contract here lets future broker-backed registries (Redis, SurrealDB) swap in without touching plugin code. This module ships the abstract `ConnectionRegistry` interface AND a single-host in-memory implementation. The in-memory registry doubles as the default for the conformance harness and is what every test in the Stage-1 contract depends on. ## Features - `ConnectionRegistry` interface — `add`, `remove`, `lookupByTenant`, `lookupByTopic`, `countByTenant`. - `InMemoryConnectionRegistry` — O(1) `add` / `remove` / lookups via three internal maps; idempotent `add` / `remove`; topic bindings dropped automatically when a connection is removed. - `recordSubscription(conn, topic, tenant)` and `dropSubscription(...)` hooks the subscription bus uses to keep the `(topic, tenant)` index in sync. ## Usage: Basic ```ts import { InMemoryConnectionRegistry } from "@suaveplan/api-ws/connection-registry"; const registry = new InMemoryConnectionRegistry(); registry.add(connection); const peersForTenant = registry.lookupByTenant(connection.tenantId); registry.remove(connection); ``` ## Usage: Advanced ```ts import { InMemoryConnectionRegistry, type ConnectionRegistry, } from "@suaveplan/api-ws/connection-registry"; class TenantCapGate { constructor( private readonly registry: ConnectionRegistry, private readonly maxPerTenant: number, ) {} accept(conn: { tenantId: TenantId }): boolean { return this.registry.countByTenant(conn.tenantId) < this.maxPerTenant; } } const registry: ConnectionRegistry = new InMemoryConnectionRegistry(); const gate = new TenantCapGate(registry, 1000); ``` ## API Reference - `interface ConnectionRegistry { add, remove, lookupByTenant, lookupByTopic, countByTenant }`. - `class InMemoryConnectionRegistry implements ConnectionRegistry`. - `recordSubscription(connection, topic, tenant)` — wired by `SubscriptionBus.subscribe`. - `dropSubscription(connection, topic, tenant)` — wired by `unsubscribe`. ## Implementation Notes - The three indices (by id, by tenant, by `(topic, tenant)`) stay in sync through the `add` / `remove` / `recordSubscription` / `dropSubscription` quartet. `remove` walks the topic index because in practice a connection's full subscription set isn't carried on the `WsConnection` contract — engines may keep that state themselves. - `recordSubscription` silently ignores unknown connections so the subscription bus can call it without re-checking membership. - The `(topic, tenant)` key uses `::` as a delimiter; tenant ids that contain `::` are still distinguishable because the encoding is prefix-based, not lossy.