Files
skills/docs/api-stack/api/api-ws-bun/src/principal/principal-expiry.md
2026-05-29 08:20:56 +01:00

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 — onExpired runs at most once per watcher instance.
  • Throws WS_BUN.PRINCIPAL_EXPIRED after firing so the engine sees the same shaped error as every other WS_BUN failure mode.
  • hasFired accessor 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.