import type { WeftEventMap } from '../core/events.ts'; import type { BatchOperation, ConditionalBatchCondition } from '../storage/interface.ts'; /** Discriminator string carried on every feed envelope. */ export type FeedEventKind = keyof WeftEventMap | 'workflow:checkpoint' | 'stream:chunk' | (string & {}); /** * Opaque cursor into a workflow or fleet event feed. Only `encodeCursor` / * `decodeCursor` know the format — treat it as an opaque string, pass it back * as `fromCursor` to resume a feed, and never parse it. * * @example * ```ts * import type { Cursor, EventEnvelope } from '@lostgradient/weft/server/handler'; * * declare const envelope: EventEnvelope; * const lastCursor: Cursor = envelope.cursor; * void lastCursor; * ``` */ export type Cursor = string; /** Encode a non-negative integer sequence as an opaque cursor. */ export declare function encodeCursor(sequence: number): Cursor; /** * Decode an opaque cursor back to its sequence. `-1` is the initial * sentinel for "before the first event"; malformed input returns null. */ export declare function decodeCursor(cursor: Cursor): number | null; export type EventSelector = 'events' | 'tokens'; /** * A single committed record from a workflow's event feed — the `events` * (durable log entries, e.g. `workflow:checkpoint`) or `tokens` (streamed * output chunks) selector, distinguished by `selector`. Returned by * `WorkflowEventFeed.replay()` / `WorkflowEventFeed.subscribe()`, and * consumed directly by the `/v1/workflows/:id/events/sse` REST route. * * @example * ```ts * import type { EventEnvelope } from '@lostgradient/weft/server/handler'; * * declare const envelope: EventEnvelope; * console.log(envelope.selector); // 'events' | 'tokens' * console.log(envelope.kind); // e.g. 'workflow:checkpoint' * ``` */ export type EventEnvelope = { readonly kind: FeedEventKind; readonly workflowId: string; readonly selector: EventSelector; readonly sequence: number; readonly cursor: Cursor; readonly emittedAtMs: number; readonly payload: unknown; }; /** * The durable source a `WorkflowEventFeed` replays and subscribes against. * Most callers never implement this directly — `createEngineEventFeedBackend()` * builds the production, `Engine`-backed implementation. Implement it * yourself only to back a feed with a non-`Engine` source (e.g. a test * double). * * @example * ```ts * import { Engine, MemoryStorage } from '@lostgradient/weft'; * import { * createEngineEventFeedBackend, * type WorkflowEventFeedBackend, * } from '@lostgradient/weft/server/handler'; * * const engine = new Engine({ storage: new MemoryStorage() }); * const backend: WorkflowEventFeedBackend = createEngineEventFeedBackend(engine); * void backend; * ``` */ export type WorkflowEventFeedBackend = { replay(options: { workflowId: string; selector: EventSelector; afterSequence: number; }): AsyncIterable; snapshotTailSequence(workflowId: string, selector: EventSelector): Promise; subscribeLive(workflowId: string, selector: EventSelector, listener: (envelope: EventEnvelope) => void): () => void; }; /** * A per-workflow event feed: replay committed history from a cursor, then * subscribe for live delivery. This is the shape of * `HandlerOptions.workflowEventFeed` — build a real one with * `createWorkflowEventFeed()` to drive `/v1/workflows/:id/events/sse` * through `handleRequest()` without `serve()`. * * @example * ```ts * import { Engine, MemoryStorage } from '@lostgradient/weft'; * import { * createEngineEventFeedBackend, * createWorkflowEventFeed, * type WorkflowEventFeed, * } from '@lostgradient/weft/server/handler'; * * const engine = new Engine({ storage: new MemoryStorage() }); * const workflowEventFeed: WorkflowEventFeed = createWorkflowEventFeed( * createEngineEventFeedBackend(engine), * ); * void workflowEventFeed; * ``` */ export type WorkflowEventFeed = { replay(options: { workflowId: string; selector: EventSelector; fromCursor?: Cursor; limit?: number; }): AsyncIterable; subscribe(options: { workflowId: string; selector: EventSelector; } & ReplayLiveSubscribeOptions): AsyncIterable; dispose(): void; }; export type WorkflowEventFeedOptions = { /** Max envelopes the live buffer holds before overflow terminates the subscription. */ liveBufferSize?: number; }; /** * A committed fleet-wide event, optionally scoped to one workflow. * @example * ```ts * import type { FleetEventEnvelope } from '@lostgradient/weft/server/handler'; * declare const event: FleetEventEnvelope; * console.log(event.cursor); * ``` */ export type FleetEventEnvelope = { readonly kind: FeedEventKind; readonly workflowId?: string | undefined; readonly sequence: number; readonly cursor: Cursor; readonly emittedAtMs: number; readonly payload: unknown; }; /** * The caller-supplied fields for a new fleet event. * @example * ```ts * import type { FleetEventInput } from '@lostgradient/weft/server/handler'; * const event: FleetEventInput = { kind: 'worker:connected', emittedAtMs: 1, payload: {} }; * ``` */ export type FleetEventInput = { readonly kind: FeedEventKind; readonly workflowId?: string | undefined; readonly emittedAtMs: number; readonly payload: unknown; }; /** * A fleet event input guaranteed to identify its workflow. * @example * ```ts * import type { FleetWorkflowEventInput } from '@lostgradient/weft/server/handler'; * declare const event: FleetWorkflowEventInput; * console.log(event.workflowId); * ``` */ export type FleetWorkflowEventInput = FleetEventInput & { readonly workflowId: string; }; /** * Caller-owned state operations committed atomically with an event. * @example * ```ts * import type { FleetEventAppendOptions } from '@lostgradient/weft/server/handler'; * const options: FleetEventAppendOptions = { operations: [{ type: 'delete', key: 'app:pending' }] }; * ``` */ export type FleetEventAppendOptions = { readonly conditions?: readonly ConditionalBatchCondition[]; readonly operations?: readonly BatchOperation[]; }; /** * The compaction boundary returned when a cursor predates retained history. * @example * ```ts * import type { FleetEventGapEnvelope } from '@lostgradient/weft/server/handler'; * declare const gap: FleetEventGapEnvelope; * console.log(gap.payload.firstRetainedSequence); * ``` */ export type FleetEventGapEnvelope = { readonly kind: 'fleet:gap'; readonly sequence: number; readonly cursor: Cursor; readonly emittedAtMs: number; readonly payload: { readonly requestedCursor: Cursor; readonly firstRetainedSequence: number; }; }; /** * Durable fleet-feed polling and replay handoff options. * @example * ```ts * import type { FleetEventFeedOptions } from '@lostgradient/weft/server/handler'; * const options: FleetEventFeedOptions = { livePollIntervalMs: 100 }; * ``` */ export type FleetEventFeedOptions = { /** How often a subscriber checks durable storage for commits from another process. */ readonly livePollIntervalMs?: number; }; export type SequencedEventEnvelope = { readonly sequence: number; readonly cursor: Cursor; }; export type ReplayLiveFeedBackend = { replay(options: { afterSequence: number; requestedCursor?: Cursor; }): AsyncIterable; snapshotTailSequence(): Promise; subscribeLive(listener: (envelope: TEnvelope) => void): () => void; }; export type ReplayLiveFeed = { replay(options?: { fromCursor?: Cursor; limit?: number; }): AsyncIterable; subscribe(options?: ReplayLiveSubscribeOptions): AsyncIterable; dispose(): void; }; export type ReplayLiveSubscribeOptions = { fromCursor?: Cursor; signal?: AbortSignal; replayLimit?: number; filterEnvelope?: (envelope: TEnvelope) => boolean; countReplayEnvelope?: (envelope: TEnvelope) => boolean; createReplayLimitError?: (count: number, limit: number) => unknown; onReplayComplete?: () => void; }; export declare function createReplayLiveFeed(backend: ReplayLiveFeedBackend, options?: WorkflowEventFeedOptions): ReplayLiveFeed; /** * Build a `WorkflowEventFeed` over the given backend. Pass the result as * `HandlerOptions.workflowEventFeed` to drive `/v1/workflows/:id/events/sse` * through `handleRequest()` directly, without `serve()`. Call once and share * the returned feed across every workflow and transport that needs it — * `createWorkflowEventFeed()` itself holds no per-workflow state. * * @example * ```ts * import { Engine, MemoryStorage } from '@lostgradient/weft'; * import { * createEngineEventFeedBackend, * createWorkflowEventFeed, * handleRequest, * type HandlerOptions, * } from '@lostgradient/weft/server/handler'; * * const engine = new Engine({ storage: new MemoryStorage() }); * const workflowEventFeed = createWorkflowEventFeed(createEngineEventFeedBackend(engine)); * const options: HandlerOptions = { workflowEventFeed }; * * async function handleWorkflowEventsSse(request: Request): Promise { * return handleRequest(request, engine, options); * } * void handleWorkflowEventsSse; * ``` */ export declare function createWorkflowEventFeed(backend: WorkflowEventFeedBackend, options?: WorkflowEventFeedOptions): WorkflowEventFeed;