import type { EngineInternals } from '../internals.ts'; import { type TerminationCallbacks } from './cleanup.ts'; /** * Suspend a running workflow without terminating it: a non-terminal cousin of * {@link terminateWorkflow}. The workflow's status transitions `running → * suspended`, its durable checkpoint is preserved, and it becomes resumable via * `engine.resume(id)` / `handle.resume()`. Suspension is client-driven * preemption, so — unlike a fault — a suspended workflow is NOT auto-recovered * by `engine.recoverAll()`. * * Contrast with cancel/timeout, which this deliberately does NOT do: * - does NOT abort the workflow's `AbortController` — suspend is a pause, not a * cancellation, so user code observing `ctx.signal.aborted` or registered * abort listeners must not fire. The live inline run is *parked* * (`parkWorkflow`: evict execution state without aborting), the same primitive * the engine uses for signal-parking — but with the default eviction, not the * `retainContext` form signal-parking uses, so suspend leaves no queryable * Context behind, * - does NOT run cancel handlers, * - does NOT settle the result promise (`handle.result()` stays pending until a * later `resume()` drives the run to completion, or a `cancel()` terminates it), * - does NOT clean up durable output artifacts or in-memory services (the * `services` value is preserved so an in-process `resume()` can reuse it), * - does NOT schedule terminal cleanup. * * The CAS status flip and the in-memory teardown both run inside one serialized * per-workflow write section with `allowedStatuses: ['running']`. If the * workflow already left `running` (it completed, failed, or a concurrent cancel * won the race), the flip is skipped and suspend is a no-op — and because the * teardown is gated on the flip succeeding, a workflow that lost the race keeps * its execution state intact. * * The teardown evicts every piece of in-memory execution state that could let a * post-suspend operation drive the parked run: the inline context/generator (via * `parkWorkflow`), the in-memory checkpoint, the parked-inline marker, and the * in-flight operation waiters (signal/update/sleep/review — deleted, NOT * resolved, so a signal arriving after suspend buffers durably and is replayed * on resume instead of waking a dormant operation loop against the gone * generator). The durable checkpoint, durable buffered signals, durable sleep * timers, and `workflowServices` are all left intact for resume. * * A signal that races the in-lock teardown is benign: `continueWorkflow` no-ops * for an evicted generator, and `persistCheckpoint` no-ops when the context and * in-memory checkpoint are gone — so no step can commit past the suspend point. * * `'suspended'` is neither `'running'` nor `'pending'`, and both local-ownership * predicates (`isInlineWorkflowLocallyOwned`, `hasLocalCheckpointOwnership`) are * gated on those two statuses. So once the status flips, the workflow stops * registering as locally owned — which is exactly what makes `recoverAll()` skip * it AND what lets `engine.resume()` re-drive it from storage instead of taking * its local-ownership early return. * * The execution deadline is absolute wall-clock time: suspension does NOT extend * it. The pending `deadline:` timer is deleted durably IN THE SAME COMMIT BATCH * as the status flip, and re-armed at the same absolute fire time on resume (or * fires immediately if already past). It is a durable delete rather than a * `scheduler.cancel()` call because the scheduler is durable-scan-based and * resume's re-arm is likewise durable-only (`buildTimerBatchOperations`); folding * the delete into the commit makes it atomic with the flip and ordered before any * concurrent resume, so an immediate resume cannot have its freshly re-armed * deadline deleted by a late fire-and-forget cancel. * * Worker execution mode is not supported: a worker run cannot be parked without * sending it a cancellation. To keep the contract state-dependent (suspend on a * non-running workflow is always a no-op), the mode check runs only AFTER the * status load confirms the workflow is `running`; a `running` worker workflow * throws {@link WorkflowSuspendNotSupportedError}, while a completed or unknown * one is a no-op regardless of execution mode. */ export declare function suspendWorkflow(internals: EngineInternals, workflowId: string, callbacks: TerminationCallbacks): Promise;