/** * Synchronous update request/response coordination. * * Manages the lifecycle of workflow updates with idempotency support, * timeout-based waiting, and automatic cleanup of expired responses. * * @module updates */ import type { BatchOperation, Storage } from '../storage/interface'; import type { WorkflowStatus } from './types'; import { WeftError } from './weft-error.ts'; export interface UpdateRequest { updateId: string; workflowId: string; name: string; payload: unknown; idempotencyKey?: string | undefined; createdAt: number; } export interface UpdateResponse { updateId: string; result?: unknown; error?: string | undefined; createdAt: number; } export interface UpdateRequestOptions { idempotencyKey?: string; timeout?: number; } /** * Thrown by the engine when an update sent via `engine.update` or * `handle.update` does not receive a response within the configured timeout. * Read `updateId` to identify the stalled update. * * @example * ```ts * import { workflow, Engine, UpdateTimeoutError, update } from '@lostgradient/weft'; * * const engine = new Engine(); * engine.register( * workflow({ name: 'paused' }).execute(async function* () { * await new Promise(() => {}); // workflow never resolves on its own * }), * ); * const handle = await engine.start('paused', null); * const proceed = update('proceed'); * try { * await handle.update(proceed, undefined, { timeout: 100 }); * } catch (err) { * if (err instanceof UpdateTimeoutError) { * console.error('update timed out:', err.updateId); * } * } * ``` */ export declare class UpdateTimeoutError extends WeftError<'UpdateTimeoutError'> { readonly updateId: string; constructor(updateId: string, timeout: number); } /** * Thrown when an update is rejected by its pre-acceptance validator — before * the payload is durably written or the workflow observes it. Inspect * `updateName` to see which update was rejected and `issues` for the * structured validation messages. * * @example * ```ts * import { workflow, Engine, UpdateValidationError, update } from '@lostgradient/weft'; * * const setAge = update<{ age: number }, void>('setAge'); * const engine = new Engine(); * engine.register( * workflow({ name: 'guarded' }).execute(async function* (ctx) { * ctx.onUpdate(setAge, () => undefined, { * validator: (v): unknown => { * const age = (v as Record)['age']; * if (typeof age !== 'number' || age < 0) { * return { issues: [{ message: 'age must be a non-negative number' }] }; * } * return undefined; * }, * }); * await new Promise(() => {}); // park forever * }), * ); * const handle = await engine.start('guarded', null); * try { * await handle.update(setAge, { age: -1 }); * } catch (err) { * if (err instanceof UpdateValidationError) { * console.error(err.issues[0]?.message); // 'age must be a non-negative number' * } * } * ``` */ export declare class UpdateValidationError extends WeftError<'UpdateValidationError'> { readonly updateName: string; /** * Structured validation issues from the pre-acceptance validator. Each issue * carries a human-readable `message` and an optional RFC 6901 JSON Pointer * `path` indicating which field in the update payload failed validation. */ readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: string; }>; constructor(updateName: string, issues: ReadonlyArray<{ readonly message: string; readonly path?: string; }>); } /** * Thrown when an update is sent to a workflow that is already in a terminal * state (completed, failed, cancelled, or timed-out). Check `workflowId` and * `status` to understand which workflow rejected the update. * * @example * ```ts * import { workflow, Engine, WorkflowTerminalError, update } from '@lostgradient/weft'; * * const engine = new Engine(); * engine.register(workflow({ name: 'quick' }).execute(async function* () { return 'done'; })); * * const handle = await engine.start('quick', null); * await handle.result(); * const anything = update('anything'); * try { * await handle.update(anything); * } catch (err) { * if (err instanceof WorkflowTerminalError) { * console.error('workflow', err.workflowId, 'is', err.status); * } * } * ``` */ export declare class WorkflowTerminalError extends WeftError<'WorkflowTerminalError'> { readonly workflowId: string; readonly status: WorkflowStatus; constructor(workflowId: string, status: WorkflowStatus); } /** * Manages the lifecycle of synchronous workflow updates: persisting requests, * checking idempotency, building response batch operations, and polling for * results. Used internally by the {@link Engine}; callers interact through * `engine.update` or `handle.update` rather than the coordinator directly. * * @example * ```ts * import { UpdateCoordinator } from '@lostgradient/weft'; * import { MemoryStorage } from '@lostgradient/weft/storage/memory'; * * const storage = new MemoryStorage(); * const coordinator = new UpdateCoordinator(storage); * const updateId = await coordinator.createRequest('wf-1', 'increment', { by: 1 }); * console.log(updateId); // UUID string * ``` */ export declare class UpdateCoordinator { #private; constructor(storage: Storage); /** Create and persist an update request. Returns the update ID. */ createRequest(workflowId: string, name: string, payload: unknown, options?: UpdateRequestOptions): Promise; /** Check idempotency: if this key was already processed, return the existing response. */ checkIdempotency(workflowId: string, idempotencyKey: string): Promise; /** Get pending update requests for a workflow, sorted FIFO by creation time. */ getPendingUpdates(workflowId: string): Promise; /** Build batch operations for persisting an update response (to be included in checkpoint batch). */ buildResponseOperations(updateId: string, workflowId: string, result: unknown, error?: string, idempotencyKey?: string): BatchOperation[]; /** Delete a pending update request that will never be observed. */ deleteRequest(workflowId: string, updateId: string): Promise; /** * Atomically check whether a response exists and conditionally delete the * request. Returns the response if the workflow already consumed this update, * or null if the request was deleted before a consumer won the race. */ deleteRequestIfUnconsumed(workflowId: string, updateId: string): Promise; /** Retrieve a stored response by update ID. */ getResponse(updateId: string): Promise; /** Wait for an update response with timeout. Uses polling. */ waitForResponse(updateId: string, timeout: number): Promise; /** Clean up expired responses and their orphaned idempotency mappings. */ cleanupExpiredResponses(ttlMs?: number): Promise; }