/** * Durable record layer for out-of-band ("async") activity completion. * * Two record shapes share the `async-act:v1:` keyspace, discriminated by their * decode guards (never by key shape): * * - **Pending token records** ({@link KEYS.asyncActivity}): an activity that * deferred via `ctx.completeAsync()` and is awaiting an external completion. * - **Resolution records** ({@link KEYS.asyncActivityResolution}): an * acknowledged completion or failure whose resumed-workflow checkpoint has * not committed yet. Written atomically with the token-record delete so the * acknowledgement is durable before the caller learns it succeeded. * * The completion orchestration (park, consume, deliver, resume) lives in * `async-activity-completion.ts`; this module owns the persisted shapes, the * key derivations, and the in-memory resolution queue that recovery drains. */ import { type BatchOperation } from '../../storage/interface.ts'; import type { OperationOutcome, PendingAsyncActivityListOptions, PendingAsyncActivityPage } from '../types.ts'; import type { EngineInternals } from './internals.ts'; export declare const DEFAULT_PENDING_ASYNC_ACTIVITY_LIMIT = 50; export declare const MAX_PENDING_ASYNC_ACTIVITY_LIMIT = 200; export declare const MAX_PENDING_ASYNC_ACTIVITY_CURSOR_LENGTH = 8192; /** * Storage-key prefix for durable async-activity records. Matches the base of * {@link KEYS.asyncActivity}; the full key appends `:` (and * `:resolution` for resolution records). The trailing colon (absent from the * token prefix) scopes the global recovery scan to record keys only. */ export declare const ASYNC_ACTIVITY_KEY_PREFIX = "async-act:v1:"; /** * Per-workflow prefix for all async-activity storage keys — pending token * records AND resolution records. Used by cleanup and purge paths that need to * sweep every async-activity record for a workflow without enumerating * individual tokens. */ export declare function asyncActivityWorkflowPrefix(workflowId: string): string; /** * In-memory record of an activity that deferred to out-of-band completion and * is awaiting `completeAsyncActivity` / `failAsyncActivity`. */ export type PendingAsyncActivity = { readonly token: string; readonly workflowId: string; readonly activityName: string; readonly operationId: string; readonly step: number; readonly attempt: number; readonly createdAt: number; }; /** * In-memory form of an acknowledged outcome awaiting delivery into the workflow * generator. `originalReason` (the raw thrown value on the failure path) exists * only within the acknowledging process — it is not persisted, so a resolution * reloaded by recovery reconstructs the error from the recorded outcome. */ export type PendingAsyncActivityResolution = { readonly token: string; readonly outcome: OperationOutcome; readonly originalReason?: { value: unknown; }; readonly timelineStatus: 'completed' | 'failed'; readonly timelineOutput: unknown; }; export declare function isPendingAsyncActivityCursor(cursor: string): boolean; export declare function isPendingAsyncActivityCursorForWorkflow(cursor: string, workflowId: string): boolean; /** * Read a bounded page directly from the durable per-workflow async-activity * namespace. Pagination advances over raw storage keys, so corrupt or resolved * records cannot stall a caller or expand one request beyond `limit + 1` reads. */ export declare function listPendingAsyncActivities(internals: EngineInternals, workflowId: string, options?: PendingAsyncActivityListOptions): Promise; /** * Derive the durable, deterministic task token for an async activity. * * The token is anchored to the workflow id, the activity state key, and the * dispatch attempt — all of which are stable across replay — so a workflow that * crashes while parked on an async activity mints the identical token after * recovery. Plain `ctx.run()` uses the workflow step as the state key. * `operationId` is deliberately excluded because it is regenerated on every * yield and would change on replay. */ export declare function deriveAsyncActivityToken(workflowId: string, step: number | string, attempt: number): string; /** * Build the acknowledgement batch for a consumed token: delete the pending * token record and persist the resolution record carrying `outcome`, in one * batch, so the acknowledgement is durable before the caller learns it * succeeded. */ export declare function buildAsyncActivityAcknowledgementOperations(pending: PendingAsyncActivity, outcome: OperationOutcome): BatchOperation[]; /** * Register a deferred activity: record it in memory and durably, then announce * the token via {@link ActivityAsyncPendingEvent}. Idempotent on `token`: if the * token is already registered (e.g. because `recoverPendingAsyncActivities` loaded * it before the workflow replayed and re-deferred), the durable record is * refreshed but the event is NOT re-emitted, preventing duplicate side-effects * (e.g. re-sending a webhook notification) on replay. */ export declare function registerPendingAsyncActivity(internals: EngineInternals, pending: PendingAsyncActivity): Promise; /** * Reload async-activity records from storage into memory. Called by * `recoverAll()` so a token minted before a crash is resolvable again — even * before the recovered workflow has replayed far enough to re-register it — * and so an acknowledged-but-not-yet-checkpointed resolution is redelivered * when replay re-parks on the same deterministic token. * * Pass `workflowId` to reload just one workflow's records. ADR 0002's * reclaim-driven resume needs this: a resolution acknowledged by an expired * owner is discarded by that engine's `confirmWakeOwnership` check, leaving * only the durable record. The engine that takes the workflow over finished * its startup scan long ago, so without a per-workflow reload it never * discovers a resolution written after that scan — the workflow re-parks and * waits forever even though the completion caller was told it succeeded. The * scan is bounded to {@link asyncActivityWorkflowPrefix}, so a takeover pays * for one workflow rather than a store-wide sweep. */ export declare function recoverPendingAsyncActivities(internals: EngineInternals, workflowId?: string): Promise; /** * True when a resolution cannot be delivered yet because inline replay has not * adopted the workflow generator (the post-recovery window). */ export declare function shouldBufferPendingAsyncActivityResolution(internals: EngineInternals, workflowId: string): boolean; /** Queue a resolution for delivery when replay reaches its token again. */ export declare function queuePendingAsyncActivityResolution(internals: EngineInternals, workflowId: string, resolution: PendingAsyncActivityResolution): void; /** Take the queued resolution for `token`, if one is waiting. */ export declare function takePendingAsyncActivityResolution(internals: EngineInternals, workflowId: string, token: string): PendingAsyncActivityResolution | undefined;