2.5 KiB
limits
(no submodules)
Purpose
Provides the per-tenant connection-count enforcement function that the handshake pipeline calls before admitting a new connection. The cap defends every host process against a single tenant exhausting socket capacity — a noisy-neighbour mitigation explicitly called out in design §11.4 (default maxConnectionsPerTenant=1000).
Enforcement is a function rather than a class because the only state needed is the registry itself; threading another stateful module through the handshake path would obscure the actual policy decision.
Features
- O(1) cap check — leans on
registry.countByTenantwhich is index-backed. - Throws
WS_BUN.TENANT_CAP_EXCEEDEDwith the offending tenant id + configured cap in the message for debuggable telemetry. - Read-only — does not mutate the registry, so the handshake path can run additional checks after the cap test without rollback.
Usage: Basic
import { enforceTenantCap, BunConnectionRegistry } from "@suaveplan/api-ws-bun";
const registry = new BunConnectionRegistry();
enforceTenantCap(registry, tenantId, 1000); // throws if >= 1000.
Usage: Advanced
import {
enforceTenantCap,
BunConnectionRegistry,
WS_BUN_CODES,
} from "@suaveplan/api-ws-bun";
function tryAccept(reg: BunConnectionRegistry, tenant, cap) {
try {
enforceTenantCap(reg, tenant, cap);
return { accepted: true as const };
} catch (err) {
if ((err as { code?: string }).code === WS_BUN_CODES.TENANT_CAP_EXCEEDED) {
return { accepted: false as const, httpStatus: 429 };
}
throw err;
}
}
API Reference
enforceTenantCap(registry, tenant, cap): void— throws on cap breach; otherwise no-op.
Implementation Notes
The check uses >= rather than > so a cap of 1000 admits exactly 1000 concurrent connections; the 1001st gets the 429. This matches the spec scenario verbatim and avoids the off-by-one trap of "999 admitted, 1000 rejected".
The implementation does not reserve a registry slot before the cap test. The window between the check and registry.add(conn) is bounded by a synchronous function call in the engine, so racing two handshakes through that microsecond gap would only be possible if Bun.serve fired two upgrade callbacks on the same JavaScript turn — which it does not. If a future engine extends the gap, this function should be replaced with an atomic reserveSlot / releaseSlot pair.