first commit
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# `connection-registry` — multi-index lookup for active connections
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user