import type { SessionGraph } from './session-graph.js'; import type { SessionGraphIdentity } from './session-graph-factory.js'; /** Minimal seam over SessionGraphFactory so the registry is unit-testable. */ export interface SessionGraphSource { build(identity: SessionGraphIdentity): Promise; } export interface SessionRegistryOptions { /** Idle time before an unpinned graph is evicted. */ idleTtlMs: number; /** Max live sessions before LRU eviction of unpinned graphs (drain if pinned). */ maxSessions: number; factory: SessionGraphSource; } export declare class SessionRegistry { private readonly opts; private readonly graphs; /** * Graphs detached from `graphs` because they were pinned at the moment * `invalidateAll()` was called. We cannot dispose mid-run, so we move them * here to drain — once their last in-flight `release()` lands, dispose * fires. They are NOT served to new `acquire()` calls, which forces those * to mint a fresh graph using the just-applied config. Keyed by sessionId * so `release()` can find them after `graphs.get(sessionId)` misses. */ private readonly draining; /** Single-flight guard: in-flight builds keyed by sessionId (review HIGH #2). */ private readonly pendingBuilds; private readonly pending; /** * Set by `disposeAll` BEFORE any await. While closed the registry refuses new * `acquire` calls so a post-shutdown caller cannot orphan a fresh graph past * disposal. Defense-in-depth — the server's `close()` drains HTTP first, so * in practice no acquire arrives after disposeAll. */ private _closed; /** * Monotonic counter bumped by `invalidateAll()` (Fix #19). Each acquire * captures the value BEFORE awaiting the build; if the counter has moved * by the time the build resolves, the in-flight build is treated as * orphaned — its `.then()` disposes the resolved graph instead of * publishing it, and the awaiting acquire rejects. Closes the race where * a build started before invalidate would otherwise publish a graph built * with the OLD config after the invalidate completed. */ private _generation; constructor(opts: SessionRegistryOptions); get size(): number; /** * Lazy-build + pin. Async because factory.build is async. SINGLE-FLIGHT: two * concurrent acquires for the SAME new sessionId await the SAME build promise * and receive the identical graph (never two graphs for one session). Each * in-flight request increments the refcount (spec A.4). */ acquire(sessionId: string): Promise; /** * Release one in-flight request. Non-creating lookup: an unknown/removed * sessionId is a no-op (never resurrect). Disposes a marked graph once idle. */ release(sessionId: string, graph?: SessionGraph): void; /** * Evict (dispose) a specific session by ID, regardless of idle TTL. * If the graph is pinned (an in-flight request is holding it), it is marked * for disposal so `release()` disposes it when the request completes. * No-op if the session is not live. */ evictOne(sessionId: string): Promise; /** Evict every unpinned graph idle longer than idleTtlMs. */ evictIdle(): Promise; /** Resolve all in-flight dispose() calls (test + shutdown helper). */ flushEvictions(): Promise; /** * Dispose every graph (server shutdown). Awaits in-flight builds FIRST so * graphs whose build resolves after disposeAll's initial sweep are not * orphaned. `allSettled` is intentional: a failed build must not reject * disposeAll (the failing build cleared its pendingBuilds slot in its own * catch handler, so there is nothing to dispose for it). */ disposeAll(): Promise; /** * Soft reset — dispose every graph but keep the registry OPEN for new * acquires. Used by config-reload (PUT /v1/config + hot-reload) to force * the next request to mint a fresh per-session graph that picks up the * just-applied config. Unpinned graphs are disposed immediately; pinned * graphs are marked for disposal (drained when their last in-flight * request releases) — same drain semantics as `enforceCap`. * * Unlike `disposeAll`, this does NOT set `_closed`, so callers can keep * serving traffic after a config change. */ invalidateAll(): Promise; private enforceCap; /** Sessions that still count toward the cap (not yet draining). */ private liveCount; private evictNow; } //# sourceMappingURL=session-registry.d.ts.map