/** * Out-of-band ("async") activity completion. * * An activity can hand its work off to an external system — a webhook, a human * callback, a third-party async job — by calling `ctx.completeAsync()` on its * {@link ActivityContext}. That call throws an {@link AsyncActivityDeferral} * sentinel which the engine catches: instead of completing or failing the * activity operation (which would resume the workflow), the engine records a * pending entry keyed by a durable, deterministic task token and leaves the * workflow suspended at that step. * * Some external system later resolves the activity by token through * `engine.completeAsyncActivity(token, result)` or * `engine.failAsyncActivity(token, error)`. Completion resumes the workflow * generator with the supplied value; failure throws the supplied error into the * generator (the same path an inline activity failure takes, so the workflow's * retry/catch handling applies unchanged). * * Durability: the pending entry is persisted under * {@link KEYS.asyncActivity}. The token is derived from the workflow id, the * deterministic workflow step index, and the activity attempt, so it is stable * across replay. After an engine restart, `recoverAll()` replays the workflow * from its checkpoint; the deferred activity re-runs, re-defers, and produces * the *same* token. A callback that arrives after the crash therefore still * resolves the correct activity. If that callback races `recoverAll()` and * arrives after token recovery but before replay has adopted the workflow * generator, the engine buffers the completion or failure outcome and drains it * when replay reaches the same deterministic token. * * Acknowledgement durability: `completeAsyncActivity` / `failAsyncActivity` * resolve only after ONE fenced batch has durably (a) deleted the single-use * token record and (b) written a resolution record * ({@link KEYS.asyncActivityResolution}) carrying the supplied outcome. A crash * any time after the acknowledgement therefore cannot lose the outcome: * recovery reloads the resolution record, queues it, and redelivers it when * replay re-parks on the same deterministic token. The resolution record is * deleted through the atomic side-effect buffer, so in the normal case it rides * the very checkpoint that records the resumed result; a record whose delete * never commits is simply redelivered (idempotent for a deterministic token) or * swept by terminal cleanup/purge. If the acknowledgement batch itself fails, * the in-memory token claim is restored and the error propagates — the caller * learns the completion did NOT stick and can retry the still-live token. * * One caveat survives a crash: the failure path's raw thrown reason * (`originalReason`) is delivered as-is only within the acknowledging process. * A redelivery after recovery reconstructs the error from the persisted outcome * (message, name, failure category) — the same fidelity the worker resume path * has always had. * * The persisted record shapes, decode guards, key derivations, and the queued * resolution buffer live in `async-activity-records.ts`. */ import type { OperationOutcome } from '../types.ts'; import { WeftError } from '../weft-error.ts'; import { type PendingAsyncActivity } from './async-activity-records.ts'; import type { EngineInternals } from './internals.ts'; type AsyncActivityResolutionCallbacks = { feedOperationResult: (workflowId: string, outcome: OperationOutcome, originalReason?: { value: unknown; }) => void; finalizeTimeline: (workflowId: string, status: 'completed' | 'failed', output: unknown) => void; }; /** * Sentinel thrown by `ActivityContext.completeAsync()` to signal that the * activity is handing off to an out-of-band completion. The engine recognizes * this exact class (not a generic `Error`) and parks the activity rather than * treating it as a failure. The `token` is the durable task token an external * system uses to complete the activity later. */ export declare class AsyncActivityDeferral extends Error { readonly token: string; constructor(token: string); } /** * Thrown by {@link Engine.completeAsyncActivity} and * {@link Engine.failAsyncActivity} when no pending async activity matches the * supplied token. This covers unknown tokens, tokens for a different engine's * workflows, and tokens that were already completed or failed (each token is * single-use). * * @example * ```ts * import { AsyncActivityTokenNotFoundError } from '@lostgradient/weft'; * * function isStaleCallbackToken(error: unknown): boolean { * return error instanceof AsyncActivityTokenNotFoundError; * } * ``` */ export declare class AsyncActivityTokenNotFoundError extends WeftError<'AsyncActivityTokenNotFoundError'> { readonly token: string; constructor(token: string); } /** * Park an activity that threw {@link AsyncActivityDeferral}: register the * pending entry durably and return a promise that never settles, so the * surrounding `runOperationWithResult` leaves the workflow suspended until an * out-of-band completion resumes it. Keeps the operation-pipeline catch site a * one-liner. */ export declare function parkDeferredAsyncActivity(internals: EngineInternals, deferral: AsyncActivityDeferral, details: Omit, callbacks: AsyncActivityResolutionCallbacks): Promise; /** * Complete a deferred activity out-of-band with `result`, resuming the parked * workflow as though the activity had returned `result` inline. */ export declare function completeAsyncActivity(internals: EngineInternals, token: string, result: unknown, callbacks: AsyncActivityResolutionCallbacks): Promise; /** * Drive a generator that may yield promises — the workflow interceptor's * `activity` hook returns such a generator. Forwards rejections into the * generator so try/catch/finally blocks inside the interceptor run correctly * instead of being abandoned. * * Placed here to reduce the line count of operations-activity.ts (which was * approaching the 500-line lint ceiling). The function is used only from * `executeActivity` in operations-activity.ts and has no coupling to the async * activity completion logic — it is a general generator-driving utility. */ export declare function driveWorkflowInterceptorGenerator(generator: Generator): Promise; /** * Fail a deferred activity out-of-band with `error`. The error is thrown into * the workflow generator at the parked step — identical to an inline activity * that threw — so the workflow's own try/catch and any configured retry policy * apply unchanged. */ export declare function failAsyncActivity(internals: EngineInternals, token: string, error: unknown, callbacks: AsyncActivityResolutionCallbacks): Promise; export {};