/** * Live workflow-event streaming for {@link HttpClient}. * * The server broadcasts a workflow's lifecycle events over a per-workflow * WebSocket channel at `/v1/workflows/:id/watch`, with fetch-based SSE at * `/v1/workflows/:id/events/sse` for authenticated runtimes that cannot carry * headers through WebSocket construction. Each delivered frame becomes a JSON * {@link WorkflowEvent} (`{ type, timestamp, data }`) — the same shape * `getEvents()` returns. This module opens the selected channel and exposes the * events through two surfaces: a push callback (`onEvent`) used by * {@link HttpHandle.addEventListener} so listeners fire the moment an event * lands instead of on a 2-second poll, and an {@link AsyncIterable} used by * `client.tail(id)` / `handle.tail()`. * * **Catch-up + reconnect.** The WebSocket watch channel is live-only and a * dropped socket can miss events while disconnected. To close both gaps the * WebSocket subscription fetches the persisted event history (`getEvents`) on * every (re)connect, emits the events past a confirmed-contiguous history * watermark, then drops any live frame buffered during the fetch that the * replayed history already covered (the overlap window). SSE uses the server's * cursor-backed replay feed and reconnects with `Last-Event-ID`. Delivery is * at-least-once: a failed fetch or a shorter compaction-rebased history array * may re-deliver a frame once rather than lose it. The lone WebSocket exception * is a sequence-less-cursor edge under event-log compaction — a * compacted+regrown log of unchanged length — documented on `#historyWatermark`; * closing it needs a server-exposed event sequence. Reconnect attempts back off * and are capped; for WebSocket the cap is honored even for open-then-close * sockets, since the counter resets only after a catch-up proves the connection * healthy (`#catchUp`). * * **Clean close.** `close()` closes the socket and resolves the iterable. * Terminal workflow events (`completed`, `failed`, `cancelled`, `timed-out`) * auto-close the stream so `for await` consumers terminate when the workflow * finishes. * * @module client/event-stream */ import type { WorkflowEvent } from '../core/types.ts'; import type { WorkflowEventStreamOptions } from './event-stream-options.ts'; import { type StreamCloseReason } from './event-tail-lifecycle.ts'; export type { StreamCloseReason } from './event-tail-lifecycle.ts'; /** Fetches a workflow's persisted event history for connect/reconnect catch-up. */ export type EventHistoryFetcher = (workflowId: string) => Promise; /** * A live workflow-event subscription over the `/watch` WebSocket channel. * Delivers events to a push callback and to a single async iterator, catching * up from persisted history on every (re)connect and transparently reconnecting * when the socket drops. * * The iterator is single-consumer: it drains one shared buffer and parks one * waker, so a second concurrent `for await` over the same subscription would * steal events and clobber the waker. Open a fresh subscription per consumer * instead of iterating one twice. */ export declare class WorkflowEventSubscription implements AsyncIterable { #private; constructor(url: string, headers: Record, workflowId: string, fetchHistory: EventHistoryFetcher, onEvent: (event: WorkflowEvent) => void, options?: WorkflowEventStreamOptions); /** Why the stream terminated, or `null` while it is still open. */ get closeReason(): StreamCloseReason | null; /** * Resolves once the stream is live (socket open and first catch-up done), or * when it terminates — whichever comes first. Await this before driving a * workflow whose events you intend to observe, so no event is missed in the * window before the watch socket connects. */ whenConnected(): Promise; /** * Close the subscription cleanly: close the socket and resolve any active * async iteration. Idempotent. */ close(): void; [Symbol.asyncIterator](): AsyncIterator; }