2.8 KiB
principal-expiry
(no submodules)
Purpose
Tracks the underlying token expiry for a frozen principal so the engine can drop the connection at the right moment. The contract requires the Connection.principal field to be immutable for the connection's lifetime; this module is the signal the engine uses to flip the connection state to closing once the original token would have lapsed.
The watcher is clock-injected so tests can drive expiry deterministically without sleeping. In production the engine polls check() from a regular interval (default: aligned to the heartbeat timer).
Features
- Single-fire —
onExpiredruns at most once per watcher instance. - Throws
WS_BUN.PRINCIPAL_EXPIREDafter firing so the engine sees the same shaped error as every other WS_BUN failure mode. hasFiredaccessor for test assertions and lifecycle bookkeeping.
Usage: Basic
import { PrincipalExpiryWatcher } from "@suaveplan/api-ws-bun";
const watcher = new PrincipalExpiryWatcher({
connectionId: "c1",
principalId: "u1",
expiresAt: Date.now() + 60_000,
clock: { now: () => Date.now() },
onExpired: ({ connectionId }) => emitBus(connectionId),
});
setInterval(() => {
try {
watcher.check();
} catch {
// engine closes the connection
}
}, 5_000);
Usage: Advanced
import { PrincipalExpiryWatcher, WS_BUN_CODES } from "@suaveplan/api-ws-bun";
let busEvent: { connectionId: string; principalId: string } | null = null;
const clock = { value: 0 };
const watcher = new PrincipalExpiryWatcher({
connectionId: "c1",
principalId: "u1",
expiresAt: 100,
clock: { now: () => clock.value },
onExpired: (event) => {
busEvent = event;
},
});
clock.value = 99;
watcher.check(); // no-op
clock.value = 100;
try {
watcher.check();
} catch (err) {
// err.code === WS_BUN_CODES.PRINCIPAL_EXPIRED
}
console.log(busEvent?.principalId); // "u1"
API Reference
PrincipalExpiryClock—{ now(): number }.PrincipalExpiryListener—(event) => void.PrincipalExpiryWatcherOptions—{ connectionId, principalId, expiresAt, clock, onExpired }.new PrincipalExpiryWatcher(options)— constructor.watcher.check(): void— run an expiry check.watcher.hasFired: boolean— has the expiry already triggered.
Implementation Notes
The watcher does NOT subscribe to an event bus on its own — the engine wires the onExpired callback into whatever bus the host owns. This keeps principal-expiry free of EventBus dependencies and lets the test suite assert on onExpired directly via a captured closure.
check() uses >= so the moment now === expiresAt, the principal is treated as expired. This matches the convention used elsewhere in the package (e.g. tenant-cap), keeping the "cliff" semantics uniform.