3.1 KiB
3.1 KiB
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
ConnectionRegistryinterface —add,remove,lookupByTenant,lookupByTopic,countByTenant.InMemoryConnectionRegistry— O(1)add/remove/ lookups via three internal maps; idempotentadd/remove; topic bindings dropped automatically when a connection is removed.recordSubscription(conn, topic, tenant)anddropSubscription(...)hooks the subscription bus uses to keep the(topic, tenant)index in sync.
Usage: Basic
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
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 bySubscriptionBus.subscribe.dropSubscription(connection, topic, tenant)— wired byunsubscribe.
Implementation Notes
- The three indices (by id, by tenant, by
(topic, tenant)) stay in sync through theadd/remove/recordSubscription/dropSubscriptionquartet.removewalks the topic index because in practice a connection's full subscription set isn't carried on theWsConnectioncontract — engines may keep that state themselves. recordSubscriptionsilently 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.