/** * Session-watch registry — the server half of the session-level SSE subscription * (design: sema-internal `server/docs/SESSION-SSE-DESIGN.md`, 黑板 [ref]①/[ref]). * * One entry per watched session; N subscribers SHARE one probe loop (this is the whole point: * it converts web-side per-client polling into one per-replica probe). Two change channels: * - fast path: `notifyLocal(sessionId, leafId)` — an append that lands on THIS replica fans out * synchronously (zero latency on single-replica deployments, the common TOC/local shape); * - slow path: the probe loop reads leafId through the SAME storage face the /head probe uses. * * codex 复审(1.230 一轮)后的三条硬语义: * - **revision 守卫**:notifyLocal 每次推进 bump `rev`;探针起读前抓拍 rev,读回后 rev 变=结果作废 * (陈旧慢读不得把 B 倒退回 A——latest-wins 的机制保证,不靠时序运气)。 * - **replay-on-subscribe**:已有条目携带 `last` 时,新订阅者同步补发一帧(「join 与基线原子化」的 * registry 半场;连接层再配 lastSent 去重,乱序/重复帧对下游不可见)。 * - **冷启动 warm**:新条目 lastChangeAt=0(没观测到变化就不算热)——首拍一次 hotMs 快确认后即落 * warm 拍距,直到真观测到跳变才进热窗(codex:重连风暴下 256 会话×200ms=对存储的自造尖峰)。 * * Deliberately backend-agnostic (tidb|pg|local all go through getLeafId — the seamless-local-cloud * invariant): no PG LISTEN/NOTIFY specialization, no shared bus. Latest-wins semantics: subscribers * converge on the newest leafId; intermediate hops may be dropped (content still comes from tail faces). */ /** [ref] 二轮复审:SSE 资源旋钮的 env 解析——有界正整数,坏形(NaN/0/负/超界)一律回缺省并可观测 * (坏 `SESSION_EVENTS_MAX_PROBES=foo` 若直进 Number() 会把帽谓词变恒假=无帽,拍距 NaN 进 setTimeout * =0ms 紧循环打爆存储)。 */ export declare function posIntEnv(raw: string | undefined, fallback: number, max?: number): number; export interface SessionWatchOptions { /** Probe interval while a change was OBSERVED within `hotWindowMs` (default 200ms). */ hotMs?: number; /** Probe interval otherwise (default 2000ms). */ warmMs?: number; /** How long after the last observed change the probe stays hot (default 60s). */ hotWindowMs?: number; /** Cap on concurrently-probed sessions per replica (default 512). Exceeding = subscribe refused. */ maxWatchedSessions?: number; } export declare class SessionWatchRegistry { /** 五轮复审:owner 与 leaf **同拍原子读**——探针每拍先核 owner(被删=undefined/换主=≠条目 owner * ⇒ dropSession 断全部订阅者),匹配才应用 leaf。跨副本 reclaim 的披露窗从 25s 心跳收紧到 * ≤一个探针拍(hot 200ms/warm 2s),且旧流在新主推进**之前**先死(owner 检查先于 leaf 应用)。 */ private readonly getHead; private readonly entries; private readonly hotMs; private readonly warmMs; private readonly hotWindowMs; private readonly maxWatched; constructor( /** 五轮复审:owner 与 leaf **同拍原子读**——探针每拍先核 owner(被删=undefined/换主=≠条目 owner * ⇒ dropSession 断全部订阅者),匹配才应用 leaf。跨副本 reclaim 的披露窗从 25s 心跳收紧到 * ≤一个探针拍(hot 200ms/warm 2s),且旧流在新主推进**之前**先死(owner 检查先于 leaf 应用)。 */ getHead: (sessionId: string) => Promise<{ leafId: string | null | undefined; owner: string | null | undefined; }>, opts?: SessionWatchOptions); /** Number of sessions currently probed (observability + the cap predicate). */ watchedCount(): number; /** * Subscribe to head changes. Returns undefined when the per-replica probe cap is reached * (caller answers 503 + Retry-After — the client falls back to /head polling). * If the shared entry already knows a head (`last`), it is REPLAYED synchronously into * `onHead` before returning — a subscriber joining an advanced entry can never miss the * newest value (connection-level dedupe absorbs any overlap with the caller's own read). */ subscribe(sessionId: string, owner: string | null, onHead: (leafId: string | null) => void, onEnd?: () => void): (() => void) | undefined; /** [ref] 三轮复审(租户轮转围栏,同副本半场):session 删除即终结条目——通知全部订阅者(onEnd, * 路由据此断流)+ 停探针 + 弃缓存 head(新租户同键重订阅=全新世代,旧 replay 不可能渗入)。 */ dropSession(sessionId: string): void; /** S2 fast path: an append that landed on THIS replica. Safe to call for unwatched sessions (no-op). * `owner` 给了就 owner-限定(五轮复审):不符=条目属旧世代 ⇒ drop(断旧流),值不投——S2 接线时 * append 落点必然知道 session owner,应当传。 */ notifyLocal(sessionId: string, leafId: string | null, owner?: string | null, leafSeq?: number): void; /** 立即压缩下一拍到 0ms(owner 疑云的权威核验)。探针自身 probing 串行 + 世代守卫照旧;urgent 旗 * 保证「疑云到达时已有读在飞(其快照摄于 reclaim 前=陈旧)」也必有一次**疑云后开始**的权威读。 */ private probeNow; private arm; private probe; /** 对**给定世代**应用一次推进(fan-out 恒按 rev 单调序)。 */ private applyAdvance; } //# sourceMappingURL=session-watch.d.ts.map