Files
suaveplan/packages/api-stack/api/api-sse-bun/src/limits/tenant-cap.ts
2026-05-27 23:29:53 +01:00

63 lines
2.0 KiB
TypeScript

import type { TenantId } from "@suaveplan/api-types/tenant";
import { SSE_BUN_CODES, SseBunError } from "../error-codes/error-codes.js";
/**
* Per-tenant connection counter the engine consults on every handshake.
*
* Implementations expose `tryAcquire(tenantId)` (returns `true` and
* increments on success, throws {@link SSE_BUN_CODES.TENANT_CAP_EXCEEDED}
* on cap breach) plus `release(tenantId)` called on graceful close.
*/
export interface TenantConnectionCap {
tryAcquire(tenantId: TenantId): true;
release(tenantId: TenantId): void;
count(tenantId: TenantId): number;
readonly total: number;
}
/**
* Build a tenant cap with the given per-tenant ceiling.
*/
export function createTenantConnectionCap(
maxConnectionsPerTenant: number
): TenantConnectionCap {
const counts = new Map<TenantId, number>();
let total = 0;
return {
tryAcquire(tenantId): true {
const current = counts.get(tenantId) ?? 0;
if (current >= maxConnectionsPerTenant) {
throw new SseBunError(
SSE_BUN_CODES.TENANT_CAP_EXCEEDED,
"error.sse_bun.tenant_cap_exceeded",
{
context: {
tenantId,
limit: maxConnectionsPerTenant,
current,
},
}
);
}
counts.set(tenantId, current + 1);
total += 1;
return true;
},
release(tenantId): void {
const current = counts.get(tenantId) ?? 0;
if (current <= 1) {
counts.delete(tenantId);
} else {
counts.set(tenantId, current - 1);
}
total = Math.max(0, total - 1);
},
count(tenantId): number {
return counts.get(tenantId) ?? 0;
},
get total() {
return total;
},
};
}