import type { DurableOperationHandle, OperationEnqueueOptions, OperationExecutor, OperationRecord, OperationRunnerConfig, OperationStoreFilter, OperationSubmitOptions } from '../types/operations.js'; /** * Runs operations against a durable store. * * The runner owns the whole lifecycle: it persists a record before doing any work, claims a lease, * heartbeats while the executor runs, retries with backoff, dead-letters what never succeeds, and * emits signed webhooks. A crashed worker leaves a record whose lease simply lapses, which * `recover()` then picks up — that is the entire mechanism behind surviving a restart. * * With the default `MemoryOperationStore` this is a process-local runner with full lifecycle * semantics and no external dependency; swapping in `RedisOperationStore` makes it durable without * any other change. */ export declare class OperationRunner { private readonly config; private readonly store; private readonly owner; private readonly leaseMs; private readonly heartbeatMs; private readonly now; private operationCounter; /** Handles of operations this runner is executing, so `release()` can reach them. */ private readonly active; /** Operations being handed back to the queue, which the execution loop settles as a release. */ private readonly releasing; constructor(config?: OperationRunnerConfig); /** Identifier this runner writes into leases. */ get workerId(): string; /** * Accepts an operation and starts running it. * * Returns before the executor has done anything, so the caller can stream events or persist the * id. An `idempotencyKey` that matches an existing record replays that operation instead of * starting a second one, which is what stops an ambiguous timeout from double-charging. */ submit(executor: OperationExecutor, options?: OperationSubmitOptions): Promise>; /** * Accepts an operation without running it, for a worker to claim. * * The record is stored as queued, and `claimQueued()` on any runner that shares the store runs it. * That is how an API replica accepts work that a separate pool of workers executes. Resolves to * the stored record, or to the one an `idempotencyKey` already names; read it again, or follow it * with `submit()` under the same key, to see how it ends. */ enqueue(options?: OperationEnqueueOptions): Promise>; /** * Claims up to `limit` queued operations that no worker holds, oldest first, and runs them here. * * Each claim is a compare-and-set on the record, so several workers polling one store never run * the same operation twice. Returns the handles of what this runner claimed; an empty list means the * queue had nothing for it. */ claimQueued(executor: OperationExecutor, limit?: number, filter?: OperationStoreFilter): Promise>>; /** * Hands an operation this runner is executing back to the queue, for another worker to continue. * * The executor's signal is aborted with an `OperationReleasedError`, and the record returns to * `queued` as a new attempt that does not count against `maxAttempts`. A draining worker gives its * work away this way rather than failing it. Returns false when this runner is not executing the * operation. */ release(id: string): boolean; /** Ids of the operations this runner is executing now, including any waiting to retry. */ get executing(): string[]; /** Stores a new record, or finds the one an idempotency key already names. */ private createRecord; /** Reads the persisted record, without starting anything. */ read(id: string): Promise | undefined>; /** * Cancels a persisted operation, including one owned by another worker. * * The running worker notices through its heartbeat, which is why cancellation is observed rather * than immediate across processes. */ cancel(id: string, reason?: string): Promise; /** * Re-runs operations whose lease lapsed, typically after a worker crashed. * * A record past its `expiresAt` is expired rather than retried, and one that has used every * attempt is dead-lettered, so a permanently failing operation cannot be recovered forever. */ recover(executor: OperationExecutor, limit?: number): Promise>>; private startHandle; private execute; /** Moves a claimable record to `running` and stamps a lease. */ private claim; private startHeartbeat; private buildContext; private persistSuccess; private persistCancelled; /** * Puts a released operation back in the queue and settles the local handle. * * A running record becomes a new attempt, with one more allowed, so the worker that claims it * knows to continue from where this one stopped and the hand-off costs nothing from the retry * budget. */ private persistReleased; private settleDeadLetter; private settleExpired; /** Applies a checked transition and bumps the concurrency token. */ private advance; /** * Rebuilds a handle for an operation this process did not start. * * Used for an idempotency replay: the caller gets a handle that resolves from the stored record * rather than a second execution. */ private attachToRecord; private followRecord; private decorate; private emitWebhook; private createOperationId; }