import type { SSEPayload } from './sse-connection-manager.js'; /** * A relayed SSE frame. `origin` is the id of the SSEManager instance that * published it, so the publishing pod can ignore its own echo (it already wrote * the frame to its local clients before publishing). `kind` distinguishes a * per-subject `send` (carries `requestId`) from a `broadcast` (no subject — * every pod re-emits to ALL of its local clients). */ export interface SSERelayMessage { origin: string; kind: 'send' | 'broadcast'; /** Present for `send`; the stream subject the payload targets. */ requestId?: string; payload: SSEPayload; } /** * Cross-pod fan-out bus for SSE frames. * * Under multiple replicas the producer of a log frame (e.g. a plugin build * worker on pod A) and the consumer (a browser EventSource on pod B) are on * DIFFERENT pods. The in-process client map only reaches the producer's own * pod, so B's stream would silently miss frames. The relay closes that: every * pod SUBSCRIBES once on startup and re-emits received frames to its LOCAL * clients, while `send()`/`broadcast()` PUBLISH to the bus (in addition to * writing locally). The origin tag prevents a pod from double-delivering its own * frame when it receives its own publish back. * * All methods are best-effort / fail-safe: a bus outage must degrade to * local-only delivery (today's single-replica behavior), never crash the stream. */ export interface SSERelay { /** Fire-and-forget publish of a frame to every subscribed pod. Never throws. */ publish(msg: SSERelayMessage): void; /** Subscribe this pod's re-emit handler. Called once on manager construction. */ subscribe(handler: (msg: SSERelayMessage) => void): void; /** Tear down the underlying connection(s). */ close(): Promise; } /** * Minimal ioredis pub/sub surface the Redis relay needs. `subscribe` requires a * DEDICATED connection (a subscribed ioredis client can't run other commands), * so the relay `duplicate()`s the publisher for the subscriber side. */ export interface RedisPubSubClient { publish(channel: string, message: string): Promise; subscribe(...channels: string[]): Promise; on(event: 'message', cb: (channel: string, message: string) => void): void; on(event: 'error', cb: (err: unknown) => void): void; duplicate(): RedisPubSubClient; quit(): Promise; } /** * The relay channel for one service. Every replica of a service shares it, and * no other service does: an SSE subject (an org id, a build requestId) only has * meaning inside the service whose clients subscribed to it, so a shared channel * made every service's pods parse — and re-emit to same-named subjects — every * other service's frames. `requestId` rides in the message body rather than the * channel name so each pod SUBSCRIBES exactly once on startup — no per-subject * subscribe/unsubscribe churn as clients come and go. */ export declare function sseRelayChannel(serviceName: string): string; /** * Redis-backed relay. Uses the given client for PUBLISH and a `duplicate()` for * SUBSCRIBE (ioredis forbids mixing subscribe with normal commands on one * connection). Publish is fire-and-forget with a swallowed rejection; a subscribe * handler that throws is isolated so one bad frame can't kill the subscriber. */ export declare function createRedisSSERelay(publisher: RedisPubSubClient, channel: string): SSERelay; /** * Build a Redis-backed SSE relay from the shared env Redis, on this service's * own channel (`SERVICE_NAME`). Returns null when Redis isn't configured so the * caller keeps local-only delivery. */ export declare function createEnvRedisSSERelay(serviceName?: string): SSERelay | null;