first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -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.