import type { Event } from '@workflow/world'; /** * Delay before firing the deferred unconsumed-event check after the promise * queue has drained. Must be long enough for cross-VM microtask chains to * propagate (resolve in host → workflow code in VM → subscribe call back * in host). Any subscribe() arriving during this window cancels the check. */ export declare const DEFERRED_CHECK_DELAY_MS = 100; export declare enum EventConsumerResult { /** * Callback consumed the event, but should not be removed from the callbacks list */ Consumed = 0, /** * Callback did not consume the event, so it should be passed to the next callback */ NotConsumed = 1, /** * Callback consumed the event, and should be removed from the callbacks list */ Finished = 2 } type EventConsumerCallback = (event: Event | null) => EventConsumerResult; export interface EventsConsumerOptions { /** * Callback invoked after an event has been consumed. Consumers such as the * deterministic workflow clock must not observe events that are merely * inspected while waiting for user code to subscribe to the next operation. */ onConsumedEvent?: (event: Event) => void; /** * Callback invoked when a non-null event cannot be consumed by any registered * callback, indicating an orphaned or invalid event in the event log. The * check is deferred until after the promise queue has drained, ensuring that * any pending async work (e.g., deserialization/decryption) completes and * downstream subscribe() calls have a chance to cancel the check first. */ onUnconsumedEvent: (event: Event) => void; /** * Returns the current promise queue. The unconsumed event check is chained * onto this queue so it only fires after all pending async work (e.g., * deserialization) has completed. This prevents false positives when async * deserialization delays the resolve() that triggers the next subscribe(). */ getPromiseQueue: () => Promise; /** * Whether no data delivery is in flight (`isDeliveryIdle` in private.ts). * The unconsumed-event check waits for this before it fires: a delivery in * flight means the workflow VM is mid-reaction, and an event it has not * claimed yet is an event it has not reached yet. * * Defaults to always-idle so the tests that drive a consumer with no * orchestrator context keep the pre-existing timing. */ isDeliveryIdle?: () => boolean; } export declare class EventsConsumer { eventIndex: number; readonly events: Event[]; readonly callbacks: EventConsumerCallback[]; private onConsumedEvent?; private onUnconsumedEvent; private getPromiseQueue; private isDeliveryIdle; private pendingUnconsumedCheck; private pendingUnconsumedTimeout; private unconsumedCheckVersion; constructor(events: Event[], options: EventsConsumerOptions); /** * Registers a callback function to be called after an event has been consumed * by a different callback. The callback can return: * - `EventConsumerResult.Consumed` the event is considered consumed and will not be passed to any other callback, but the callback will remain in the callbacks list * - `EventConsumerResult.NotConsumed` the event is passed to the next callback * - `EventConsumerResult.Finished` the event is considered consumed and the callback is removed from the callbacks list * * @param fn - The callback function to register. */ subscribe(fn: EventConsumerCallback): void; private notifyConsumedEvent; private consume; /** * Offer `currentEvent` to each registered callback in turn. Returns true * when a callback consumed a real (non-null) event and the drain should * advance to the next event in the same synchronous pass; false otherwise * (nothing consumed it, or the consumed event was the end-of-events * sentinel). */ private consumeOne; private handleUnconsumed; /** * Run `fn` once no data delivery is in flight, polling the way * `scheduleWhenIdle` does: let the promise queue drain, re-check a timer * tick later, repeat. * * Without this the check is a bet that every delivery the walk is running * ahead of lands inside a fixed window. Consumption is synchronous while the * resolution it triggers is not: a step result hydrates in the host, resolves * from a detached continuation behind `awaitEarlierDeliveries`, and only then * does VM code run far enough to subscribe the next consumer. Replaying a * batch of N parallel step results leaves N-1 of them on that detached path * with the queue already drained, so the walk sits on the ordered event the * VM is about to draw and the window is the only thing standing between a * healthy run and `ReplayDivergenceError`. On a backend whose deliveries take * longer than the window, that bet loses. * * Termination is `hasParkedCommittedDelivery`'s: it counts only deliveries * that resolve on their own, so nothing here can gate its own retirement. A * genuinely orphaned event has no delivery to wait on and reaches `fn` on the * first poll. */ private whenDeliveryIdle; } export {}; //# sourceMappingURL=events-consumer.d.ts.map