import { type SessionEvent } from "@opengeni/contracts"; import { type Database } from "@opengeni/db"; import { type EventBus } from "@opengeni/events"; import type { Observability } from "@opengeni/observability"; export declare const SSE_QUEUED_FRAME_MAX_COUNT = 1; export declare const SSE_WRITE_STALL_TIMEOUT_MS = 30000; export declare const SSE_HEARTBEAT_INTERVAL_MS = 15000; export declare const HTTP1_BROWSER_SSE_BATCH_MAX_BYTES: number; export declare const HTTP1_BROWSER_SSE_BATCH_CONTENT_TYPE = "application/vnd.opengeni.sse-batch"; export type SseDeliveryBoundObservation = { reason: "desired_size_non_positive" | "stall_timeout" | "frame_too_large"; desiredSize: number | null; queuedFrames: number; queuedBytes: number; }; export type ByteBoundedSseStreamOptions = { connectionLifetimeMs?: number | undefined; maxQueuedBytes?: number; stallTimeoutMs?: number; onStop?: () => void; onObservation?: (observation: SseDeliveryBoundObservation) => void; }; export type ByteBoundedSseStream = { stream: ReadableStream; write: (frame: string) => Promise; close: () => void; fail: (error: unknown) => void; stopped: () => boolean; }; /** * A byte-counted SSE body. `ReadableStreamDefaultController.enqueue()` does not * itself wait for a slow HTTP consumer, so replaying bounded frames without * checking `desiredSize` can still accumulate an unbounded server-side queue. * * One writer is expected per stream. The Web Streams queue holds at most one * complete frame. One oversized frame is delivered intact rather than lost; * the byte budget is a batching target, not a content limit. A second write * waits for consumer pull only for a bounded interval; cancellation or a stalled * reader wakes it and terminates upstream delivery before another durable page is * read. One frame is deliberate: queued memory is bounded by one event, not * by the size of the session, without imposing a lossy per-message limit. */ export declare function createByteBoundedSseStream(options?: ByteBoundedSseStreamOptions): ByteBoundedSseStream; export type LatestWinsDelivery = { publish: (events: readonly T[]) => void; stop: () => void; whenIdle: () => Promise; pendingSequence: () => number | null; }; /** * Keep at most one live notification while an earlier notification is being * delivered. The notification is only a cursor target: `send` gap-fills every * missing durable event from Postgres, so replacing N intermediate notices with * their newest sequence loses no event and prevents backpressure from migrating * into the NATS subscription queue. */ export declare function createLatestWinsDelivery(send: (event: T) => Promise, onError: (error: unknown) => void): LatestWinsDelivery; export declare function sseSessionStream(db: Database, bus: EventBus, workspaceId: string, sessionId: string, after: number, signal: AbortSignal, options?: SessionSseDeliveryOptions): Promise; export declare function replaySessionEvents(loadPage: (after: number, limit: number) => Promise, send: (event: SessionEvent) => Promise, after: number, pageSize?: number): Promise; export declare function sseWorkspaceControlStream(db: Database, bus: EventBus, workspaceId: string, after: number, signal: AbortSignal, options?: SseDeliveryOptions): Promise; export type WorkspaceInteractionSseOptions = SseDeliveryOptions & { pollIntervalMs?: number | undefined; }; /** * One HTTP connection for the two workspace-wide invalidation domains used by * every visible OpenGeni surface. Keeping these as separate HTTP/1 streams * consumes all six per-origin browser connections with only two windows and * starves ordinary mutations/terminal grants. The durable cursors remain * independent; this function only multiplexes their already-bounded SSE frames. */ export declare function sseWorkspaceLiveStream(db: Database, bus: EventBus, accountId: string, workspaceId: string, controlAfter: number, interactionAfter: number, signal: AbortSignal, options?: WorkspaceInteractionSseOptions): Promise; /** * Latest-wins interaction invalidation stream. The durable truth is one * monotonic workspace row, not an ever-growing event log. Each poll reads only * that row; reconnect immediately projects the newest revision after `after`. */ export declare function sseWorkspaceInteractionRevisionStream(db: Database, accountId: string, workspaceId: string, after: number, signal: AbortSignal, options?: WorkspaceInteractionSseOptions): Promise; export type SseDeliveryOptions = { connectionLifetimeMs?: number | undefined; /** Return a known-length batch instead of a chunked response. HTTP/1 only. */ finiteResponseMaxBytes?: number | undefined; /** Browser transport classification for a finite response. */ finiteResponseMediaType?: "event-stream" | "http1-browser-batch" | undefined; maxQueuedBytes?: number; stallTimeoutMs?: number; heartbeatIntervalMs?: number; observability?: Observability | undefined; onObservation?: ((observation: SseDeliveryBoundObservation) => void) | undefined; /** Current ACL re-check, run even while the event stream is idle. */ reauthorize?: (() => Promise) | undefined; reauthorizeAfterMs?: number | undefined; /** Exact selected actor emitted on the stream response for cross-tab fencing. */ actorEpoch?: string | undefined; }; export declare function browserSseDeliveryOptions(transport: string | undefined): Pick; export type SessionSseDeliveryOptions = SseDeliveryOptions;