import type { AlertStateSnapshot } from '../../alerting/types.ts'; import { type Storage as WeftStorage } from '../../storage/interface.ts'; import { type ActivityMetadata, type ActivityRegistrationOptions, type RegisteredActivityFunction } from '../activity-registry.ts'; import type { WorkflowRevisionRecord } from '../catalog/index.ts'; import type { StoredStreamChunk } from '../context.ts'; import type { TypedEventTarget, WeftEventMap } from '../events.ts'; import type { Interceptor } from '../interceptor.ts'; import { type ReviewRequest } from '../review/index.ts'; import { Scheduler } from '../scheduler.ts'; import type { WorkflowSourceHandle } from '../source/index.ts'; import { type AnyActivityDefinition, type AnyWorkflowDefinition, type AttributeFilterKey, type BulkCancelResult, type BulkDeleteResult, type BulkOperationCommitOptions, type BulkOperationDryRunOptions, type BulkOperationDryRunResult, type BulkRetryFailedResult, type BulkSignalAllCommitOptions, type BulkSignalAllDryRunOptions, type BulkSignalResult, type BulkTagResult, type CheckpointState, type CheckpointSummary, type CoordinatedUpdateResult, type DefaultActivityTypes, type DefaultWorkflowRegistry, type ForkOptions, type InferActivityEntries, type InferActivityEntry, type InferWorkflowEntries, type InferWorkflowEntry, type ListFilter, type ListOptions, type PaginatedResult, type PendingAsyncActivityListOptions, type PendingAsyncActivityPage, type PurgeResult, type QueryDefinition, type RegisteredWorkflowDefinition, type RetentionOverview, type ReviewListEntry, type ReviewListFilter, type ScheduleDefinition, type ScheduleFilter, type ScheduleOptions, type ScheduleSpec, type ScheduleSummary, type ScheduleUpdateOptions, type SearchAttributeValue, type SignalDefinition, type SignalDeliveryOptions, type StartOrSignalOptions, type StartOrSignalSignal, type StartWorkflowOptions, type SubmitReviewOptions, type TypedListFilter, type UpdateDefinition, type WorkflowEvent, type WorkflowFinalizerStatus, type WorkflowInput, type WorkflowOutput, type WorkflowReplay, type WorkflowScheduleProvenance, type WorkflowServices, type WorkflowServicesUnion, type WorkflowState, type WorkflowSummary, type WorkflowTimelineEntry } from '../types.ts'; import type { TimerEntry } from '../types/checkpoint.ts'; import type { WorkflowAlreadyRegistered } from '../types/workflow-builder.ts'; import { type AggregateOptions, type AggregateResult } from './aggregate.ts'; import { type EmptyActivityDefinitions, type EmptyWorkflowDefinitions, type KnownWorkflowNames } from './construction.ts'; import { type ActivityDefinitionName, type EngineCreateOptions, type EngineCreateWorkflowRegistry, type RegisteredActivityDefinitionExecute, type UnknownWorkflowNameWhenDefaultRegistryIsEmpty } from './engine-create-types.ts'; import type { EngineConstructorOptions } from './engine-internal-types.ts'; import type { EngineStateNamespace } from './engine-state-namespace.ts'; import { type EngineWorkflowsNamespace } from './engine-workflows-namespace.ts'; import { HANDLE_RESULT_PROMISE, WorkflowHandle } from './handles.ts'; import { ENGINE_LEASE_LOST_WARNING_NAME } from './lease-deposition.ts'; import type { EngineLeaseHealth } from './lease-health.ts'; import { type RecoverAllOptions, type StartOrSignalResult } from './lifecycle.ts'; import { assertCompatiblePersistedDataVersion } from './persisted-data-version.ts'; import { ScheduleHandle } from './schedule-handle.ts'; import { type ResolveWorkflowSourceOptions } from './source-resolution.ts'; import { type WorkflowFeedListener, type WorkflowFeedRecord, type WorkflowFeedSelector } from './workflow-feed.ts'; export type { WorkflowRevisionReferenceCounts } from '../catalog/index.ts'; export { ActivityReconciliationCapabilityError, ActivityReconciliationConflictError, ActivityReconciliationIndeterminateError, } from './activity-reconciliation.ts'; export { AsyncActivityTokenNotFoundError } from './async-activity-completion.ts'; export type { PendingAsyncActivity } from './async-activity-records.ts'; export { getWorkflowRevisionDiagnostics, removeWorkflowRevision, type WorkflowCatalogRemovalResult, type WorkflowRevisionDiagnostics, } from './catalog-removal.ts'; export { DynamicWorkflowSourceUnavailableError, WorkflowSourceNotRegisteredError, } from './dynamic-source-errors.ts'; export type { PendingTimelineEntry, RegistrationEntry, ResolvedOptions, TrackedWaiterKeys, WorkflowResultWaiter, } from './engine-internal-types.ts'; export { ActivityResolutionError, BulkDeleteRequiresTerminalWorkflowsError, BulkOperationConfirmationError, EngineCreateNameMismatchError, EngineDisposalError, EngineDisposedError, ForkSourceReplacedError, IdempotencyKeyPurgedError, PersistedDataCorruptError, PersistedDataIncompatibleError, StartOrSignalConflictError, WorkflowAlreadyExistsError, WorkflowConcurrencyLimitExceededError, WorkflowNotFoundError, WorkflowNotRegisteredError, WorkflowSuspendNotSupportedError, WorkflowTeardownPendingError, WorkflowTypeNotRegisteredForRecoveryError, } from './errors.ts'; export { HANDLE_RESULT_PROMISE, WorkflowHandle } from './handles.ts'; export { getWorkflowCatalog } from './internals.ts'; export { WeftWorkflowClaimLostWarning, WeftWorkflowWakeDiscardedWarning, WORKFLOW_CLAIM_LOST_WARNING_NAME, WORKFLOW_WAKE_DISCARDED_WARNING_NAME, } from './lease-deposition.ts'; export type { WorkflowWakeKind } from './lease-deposition.ts'; export { EngineLeaseAcquisitionTimeoutError, EngineLeaseCorruptedError, EngineLeaseNotHeldError, OwnershipModeMismatchError, WorkflowClaimUnavailableError, } from './lease-errors.ts'; export type { EngineLeaseHealth, LeaseLostReason } from './lease-health.ts'; export type { RecoverAllOptions, RecoveredWorkflowInfo } from './lifecycle.ts'; export { WorkflowRevisionUnavailableError } from './revision-errors.ts'; export { ScheduleHandle } from './schedule-handle.ts'; export type { ResolveWorkflowSourceOptions } from './source-resolution.ts'; export type { WorkflowFeedListener, WorkflowFeedRecord, WorkflowFeedSelector, } from './workflow-feed.ts'; export type { EngineCreateOptions } from './engine-create-types.ts'; export { clearEngineLeakWarningTokenForTesting, getEngineLeakCollectionCountForTesting, hasEngineLeakWarningTokenForTesting, setEngineLeakWarningOverrideForTesting, setNextEngineLeakWarningTokenForTesting, shouldEmitEngineLeakWarningForTesting, } from './engine-leak-warnings.ts'; export type { EngineStateNamespace } from './engine-state-namespace.ts'; export type { ActivateWorkflowRevisionOptions, EngineWorkflowsNamespace, } from './engine-workflows-namespace.ts'; export { assertCompatiblePersistedDataVersion }; export declare const ENGINE_PARKED_WORKFLOW_COUNT_FOR_TESTING: unique symbol; export declare const ENGINE_SIGNAL_WAITER_COUNT_FOR_TESTING: unique symbol; export declare const ENGINE_SLEEP_RESOLVER_COUNT_FOR_TESTING: unique symbol; export declare const ENGINE_WAIT_FOR_SLEEP_RESOLVER_FOR_TESTING: unique symbol; export declare const ENGINE_SET_WORKER_TURN_TIMEOUT_RESOLVER_FOR_TESTING: unique symbol; /** * Bound for `ENGINE_WAIT_FOR_SLEEP_RESOLVER_FOR_TESTING`. * * The only production-adjacent consumer awaits that hook inside the * `weft:test:periodic-sync` Service Worker message handler in * service-worker-browser.test.ts, and that file bounds each message round trip * at 5s (`sendWorkerMessage`), each phase at 15s or whatever remains of the * test budget, and each test at 30s. This bound has to be strictly tighter than * the innermost of those — at 5s it would tie with the message bound and the * generic "Service Worker message timed out" could win the race, hiding the * diagnostic this bound exists to produce. 3s expires first, and the handler * posts the rejection back over the port so the workflow-naming error is what * reaches CI. * * The ordering holds whenever the phase bound is the 15s ceiling. It cannot * hold once a test has burned all but a few seconds of its 30s budget, because * no inner window survives an outer bound shorter than itself. That case is * reported rather than papered over: the phase error says the bound was * shortened by the remaining budget and that a nested timeout may have been * preempted, which points at the earlier phases that actually consumed it. */ export declare const SLEEP_RESOLVER_READY_WAIT_TIMEOUT_MS_FOR_TESTING = 3000; /** * The `name` of the `process` warning emitted when a lease-owning engine is * disposed through the synchronous `[Symbol.dispose]()` path. Sync disposal can * only fire the lease release in the background; use `await engine.shutdown()`, * `await using`, or `await engine[Symbol.asyncDispose]()` when prompt rolling * deploy handoff matters. * * @example * ```ts * import { ENGINE_LEASE_SYNCHRONOUS_DISPOSE_WARNING_NAME } from '@lostgradient/weft'; * * process.on('warning', (warning) => { * if (warning.name === ENGINE_LEASE_SYNCHRONOUS_DISPOSE_WARNING_NAME) { * // Alert on shutdown paths that can make lease handoff wait for leaseTtl. * } * }); * ``` */ export declare const ENGINE_LEASE_SYNCHRONOUS_DISPOSE_WARNING_NAME = "WeftEngineLeaseSynchronousDisposeWarning"; export { ENGINE_LEASE_LOST_WARNING_NAME }; /** * Durable execution engine. * * Register workflow and activity definitions with {@link Engine.register}, * start workflows with {@link Engine.start}, * and observe or cancel them via the returned {@link WorkflowHandle}. Each * workflow is a generator that yields to a {@link Context}; the engine * persists a checkpoint at every yield so the workflow survives crashes, * restarts, and worker reassignment without losing progress. * * The default type parameters preserve the module-augmentation registry * model. Use `new Engine<{}, {}>()` when you want an engine-local registry * that only accepts definitions added through `register`. * * @example Run a workflow with an activity * ```ts * import { workflow, activity, Engine, type Context, type WorkflowContext } from '@lostgradient/weft'; * const fetchUser = activity({ * name: 'fetchUser', * execute: async (input: unknown) => ({ name: 'Alice' }), * }); * const engine = new Engine(); * engine.register( * workflow({ name: 'greet' }).execute(async function* (ctx: WorkflowContext, input: unknown) { * const user = yield* ctx.run(fetchUser, input); * return `Hello, ${user.name}`; * }), * ); * const handle = await engine.start('greet', 'user-1'); * void handle; * ``` * * @example With a SQLite backend * ```ts * import { Engine } from '@lostgradient/weft'; * import { BunSQLiteStorage } from '@lostgradient/weft/storage/sqlite/bun'; * await using storage = new BunSQLiteStorage('./weft.db'); * await using engine = new Engine({ storage }); * await engine.recoverAll(); * void engine; * ``` */ export declare class Engine extends EventTarget implements Disposable, AsyncDisposable, TypedEventTarget { #private; /** * Construct and register an engine in one step. Activities are registered * before workflows. Recovery runs by default after all definitions are * installed; pass `recover: false` to opt out. * * @example * ```ts * import { activity, Engine, workflow } from '@lostgradient/weft'; * * const greet = activity({ name: 'greet', execute: async (name: string) => `Hi ${name}` }); * const welcome = workflow({ name: 'welcome' }).execute(async function* (ctx, input: string) { * return yield* ctx.run(greet, input); * }); * * const engine = await Engine.create({ * activities: { greet }, * workflows: { welcome }, * }); * void engine; * ``` */ static create(options: EngineCreateOptions & { activities?: undefined; workflows?: undefined; }): Promise; static create>(options: EngineCreateOptions & { activities?: undefined; workflows: TWorkflowDefinitions; }): Promise>>; static create>(options: EngineCreateOptions & { activities: TActivityDefinitions; workflows?: undefined; }): Promise>>; static create, TActivityDefinitions extends Record>(options: EngineCreateOptions & { activities: TActivityDefinitions; workflows: TWorkflowDefinitions; }): Promise, InferActivityEntries>>; constructor(options?: EngineConstructorOptions>); addEventListener>(type: K, listener: (event: WeftEventMap[K]) => void, options?: boolean | AddEventListenerOptions): void; addEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void; removeEventListener>(type: K, listener: (event: WeftEventMap[K]) => void, options?: boolean | EventListenerOptions): void; removeEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | EventListenerOptions): void; get state(): EngineStateNamespace; get workflows(): EngineWorkflowsNamespace; /** Return detached snapshots of the alert rules that are currently firing. */ getActiveAlerts(): readonly AlertStateSnapshot[]; /** * Register a workflow by name or definition, or register an activity * definition. Definition overloads return this same engine with the * definition added to its phantom type registry. This is additive over the * module-augmented default registry; construct `new Engine<{}, {}>()` for a * strict local registry. * * @example * ```ts * import { Engine, workflow, type WorkflowContext } from '@lostgradient/weft'; * * const engine = new Engine(); * engine.register( * workflow({ name: 'hello' }).execute(async function* (_ctx: WorkflowContext, name: string) { * return `Hello, ${name}`; * }), * ); * ``` */ /** * Builder-workflow registration with a parameter-position name-conflict * guard. New names widen the engine's typed workflow registry; re-registering * a name already present intersects the parameter type with * {@link WorkflowAlreadyRegistered} — a branded marker no real * `WorkflowDefinition` satisfies — so the call line itself fails to compile. * * Runtime is more lenient: registering the same `WorkflowDefinition` object * reference again is idempotent (no-op); same-name-different-object throws. * TypeScript cannot distinguish the two at the type level. Callers needing * the runtime-idempotent path from TypeScript must use a documented escape * hatch (e.g. `engine.register(welcome as never)`). */ register(workflow: TDefinition & (TDefinition['name'] extends keyof TWorkflows ? WorkflowAlreadyRegistered : unknown)): Engine, TActivities>; register & ActivityDefinitionName>(definition: TDefinition & { readonly name: TName; readonly execute: RegisteredActivityDefinitionExecute; }): Engine>; register(definition: ActivityDefinitionName extends Extract ? never : TDefinition): Engine>; /** * Record `source` as a lazily-resolvable dynamic workflow source * (WFT-13/14): synchronous, and it never invokes `source.load` — it only * records a catalog candidate keyed `(source.descriptor.name, * source.descriptor.revision)`. Call `resolveWorkflowSource()` to * actually load, validate, and install it. A workflow name may not be * both eagerly registered (`engine.register()`) and a dynamic source; * registering the identical `source` reference twice for the same * `(name, revision)` is idempotent. * * @example * ```ts * import { Engine, workflowSource } from '@lostgradient/weft'; * * declare const loadCheckout: () => Promise<{ * checkout: import('@lostgradient/weft').WorkflowDefinition; * }>; * const engine = new Engine(); * engine.registerSource( * workflowSource( * { name: 'checkout', location: './checkout.ts', exportName: 'checkout', revision: 'r1' }, * loadCheckout, * ), * ); * ``` */ registerSource(source: WorkflowSourceHandle): void; /** * Load, validate, and install one dynamic workflow source revision * previously recorded via {@link Engine.registerSource}. Single-flight * per `(name, revision)`: concurrent callers for the same key share one * loader invocation. Does not wire `engine.start()` or recovery to await * resolution — that is a later batch's job (WFT-15). * * @example * ```ts * import { Engine } from '@lostgradient/weft'; * * declare const engine: Engine; * const record = await engine.resolveWorkflowSource('checkout', 'r1'); * console.log(record.manifest.revision); * ``` */ resolveWorkflowSource(name: string, revision: string, options?: ResolveWorkflowSourceOptions): Promise; /** * Register every workflow from an object map at once and return a typed * engine view that exposes the newly added workflow names. * * Mirrors `Engine.create({ workflows })` for post-construction use. The map * key is canonical: if a value's runtime `name` disagrees with its key, the * call throws {@link EngineCreateNameMismatchError} before any partial * registration completes (insertion order — earlier entries persist). * * @example * ```ts * import { Engine, workflow } from '@lostgradient/weft'; * * const welcome = workflow({ name: 'welcome' }) * .execute(async function* (_ctx, name: string) { * return `Hello, ${name}`; * }); * * const engine = new Engine(); * const typedEngine = engine.registerWorkflows({ welcome }); * await typedEngine.start('welcome', 'Ada'); * ``` */ registerWorkflows>(workflows: TWorkflowDefinitions): Engine, TActivities>; addInterceptor(interceptor: Interceptor): void; protected resolveRegisteredActivity(name: string): RegisteredActivityFunction | undefined; protected registerActivityFunction(name: string, fn: Function, options?: ActivityRegistrationOptions): void; protected unregisterRegisteredActivity(name: string): void; getWorkflowDefinition(type: string): RegisteredWorkflowDefinition | undefined; listWorkflowDefinitions(): RegisteredWorkflowDefinition[]; getActivityDefinition(name: string): ActivityMetadata | undefined; listActivityDefinitions(): ActivityMetadata[]; /** * Resolve one activity's catalog metadata against the EAGER per-workflow * `.activities({...})` registry only, falling back to the global registry * — never a `registerSource()`-registered type's dynamic-source revision, * resolved or not (WFT-19: `internals.activityRegistriesByWorkflow` is * eager-only as of this batch; a dynamic-source workflow's own activity * dispatch resolves via the running instance's exact `(type, revision)` * pin instead, in `activity-resolution.ts`, which this synchronous, * type-only accessor has no instance to pin against). Build tooling * (`buildWorkerManifestFromRegistry`) uses this so a workflow-scoped * activity's schema, not a same-named global activity's, feeds its * `contractHash` when both are registered. */ getWorkflowActivityDefinition(workflowType: string, activityName: string): ActivityMetadata | undefined; /** * List every activity definition registered on this workflow's own * per-workflow `.activities({...})` registry — name, schema, description, * tags, retry/timeout metadata, never a handler function. Returns an * empty array for a workflow with no `.activities({...})` step (including * an unknown `workflowType`), never `undefined`, so a caller can fold the * result straight into a contract without an existence check first. * * Companion to {@link getWorkflowActivityDefinition}: that resolves one * activity by name with the per-workflow-first, global-fallback dispatch * order; this enumerates only the names the EAGER per-workflow registry * itself declares — activities a workflow reaches solely through the * global registry are deliberately excluded, and so (as of WFT-19) is * every `registerSource()`-registered type regardless of whether a * revision has been resolved (see {@link getWorkflowActivityDefinition}'s * doc). Build tooling that needs a workflow's full scoped-activity * contract set (`buildRegistrySnapshot`, * `worker/manifest/registry-contract-builder.ts`) uses this instead of * reaching into engine internals directly. */ listWorkflowActivityDefinitions(workflowType: string): ActivityMetadata[]; start>(type: TName, input: WorkflowInput, options?: StartWorkflowOptions>): Promise>>; start(type: UnknownWorkflowNameWhenDefaultRegistryIsEmpty, input: unknown, options?: StartWorkflowOptions): Promise; /** * Atomically start a workflow or signal it if it already exists * (signal-with-start). With an absent target, the workflow record and the * first signal commit in one batch and the freshly-launched run consumes the * signal on its first drive. A non-terminal target (running, pending, or * suspended) is signalled through a workflow-serialized signal path. If * terminal completion wins that handoff, the target is treated as terminal: * the default path throws {@link StartOrSignalConflictError}, while * `options.onTerminalConflict: 'start-new'` transfers the signal to the * replacement run. A terminal target throws {@link StartOrSignalConflictError} * unless `options.onTerminalConflict: 'start-new'` is supplied with an explicit * workflow id and deterministic `signal.signalId`. * * Concurrent callers converge on one workflow and one delivered signal. Pass * `options.idempotencyKey` to dedup independent callers (e.g. retried * webhooks); the signal id derives from the key when `signal.signalId` is * omitted, so callers that share only the key still converge. `signal.signalId` * and `options.idempotencyKey` are mutually exclusive (provide exactly one), as * are `options.id` and `options.idempotencyKey`. Requires a storage backend * with `conditionalBatch`. */ startOrSignal>(type: TName, input: WorkflowInput, signal: StartOrSignalSignal, options?: StartOrSignalOptions>): Promise>>; startOrSignal(type: UnknownWorkflowNameWhenDefaultRegistryIsEmpty, input: unknown, signal: StartOrSignalSignal, options?: StartOrSignalOptions): Promise; getHandle(workflowId: string): WorkflowHandle; list(filter?: TypedListFilter, options?: ListOptions): Promise>; aggregate(filter: ListFilter | undefined, options: AggregateOptions): Promise; getRetentionOverview(): RetentionOverview; /** * Run one host-driven maintenance cycle. This runs the `ownership: * 'workflow-lease'` construction gates (idempotent — a no-op once already * bootstrapped) and one per-workflow claim-renewal pass, fires due durable * timers, deletes expired update responses, applies configured retention, * and re-evaluates alert rules without relying on process-local intervals. * * Use with `backgroundTasks: 'manual'` from a serverless alarm, Cron trigger, * or another externally scheduled wake-up. Concurrent calls are safe, but a * host should await each cycle before scheduling another. Under `ownership: * 'workflow-lease'`, this is also the ONLY thing that renews this engine's * per-workflow claims in manual mode — no interval is ever started for * `backgroundTasks: 'manual'`, so a host that calls this less often than * `workflowClaimRenewInterval` will see its own live claims lapse. */ runMaintenance(now?: number): Promise; purge(filter?: ListFilter): Promise; cancelAll(filter: ListFilter, options: BulkOperationDryRunOptions): Promise; cancelAll(filter: ListFilter, options?: BulkOperationCommitOptions): Promise; retryFailedAll(filter: ListFilter, options: BulkOperationDryRunOptions): Promise; retryFailedAll(filter: ListFilter, options?: BulkOperationCommitOptions): Promise; signalAll(filter: ListFilter, name: string, payload: unknown, options: BulkSignalAllDryRunOptions): Promise; signalAll(filter: ListFilter, name: string, payload: unknown, options: BulkSignalAllCommitOptions): Promise; signalAll(filter: ListFilter, name: string, payload?: unknown, options?: BulkOperationCommitOptions): Promise; deleteAll(filter: ListFilter, options: BulkOperationDryRunOptions): Promise; deleteAll(filter: ListFilter, options?: BulkOperationCommitOptions): Promise; tagAll(filter: ListFilter, tags: string[], options: BulkOperationDryRunOptions): Promise; tagAll(filter: ListFilter, tags: string[], options?: BulkOperationCommitOptions): Promise; untagAll(filter: ListFilter, tags: string[], options: BulkOperationDryRunOptions): Promise; untagAll(filter: ListFilter, tags: string[], options?: BulkOperationCommitOptions): Promise; /** * Register a recurring schedule that starts a workflow on a cron expression or * fixed interval. Returns a {@link ScheduleHandle} for pausing, resuming, * updating, or cancelling the schedule. * * Two call forms: * - A {@link ScheduleDefinition} object: `engine.schedule({ workflow, cron, input })`. * Carries the workflow (definition or type name), the `cron`/`every` spec, * optional `input`, `id`, `overlapPolicy`, `backfill`, and `jitter`. * - Positional: `engine.schedule(type, input, spec, options?)` where `spec` is * a cron string or a {@link ScheduleSpec} (`{ cron }` or `{ every }`). * * The {@link ScheduleOptions.overlap} policy governs what happens when a tick * fires while the previous run is still in flight. * * @example * ```ts * import { workflow, Engine } from '@lostgradient/weft'; * * const engine = new Engine(); * engine.register(workflow({ name: 'sweep' }).execute(async function* () { return 'ok'; })); * * // Definition form: every day at 09:00, skip a tick if the prior run is still running. * const handle = await engine.schedule({ * workflow: 'sweep', * cron: '0 9 * * *', * overlapPolicy: 'skip', * }); * await handle.pause(); * ``` */ schedule(definition: ScheduleDefinition): Promise; schedule(type: string, input: unknown, spec: string | ScheduleSpec, options?: ScheduleOptions): Promise; getSchedule(scheduleId: string): Promise; listSchedules(filter?: ScheduleFilter): Promise>; pauseSchedule(scheduleId: string): Promise; resumeSchedule(scheduleId: string): Promise; cancelSchedule(scheduleId: string): Promise; updateSchedule(scheduleId: string, newSpec: string | ScheduleSpec, options?: ScheduleUpdateOptions): Promise; [HANDLE_RESULT_PROMISE](workflowId: string): Promise; [ENGINE_PARKED_WORKFLOW_COUNT_FOR_TESTING](): number; [ENGINE_SIGNAL_WAITER_COUNT_FOR_TESTING](): number; [ENGINE_SLEEP_RESOLVER_COUNT_FOR_TESTING](): number; [ENGINE_WAIT_FOR_SLEEP_RESOLVER_FOR_TESTING](workflowId: string): Promise; [ENGINE_SET_WORKER_TURN_TIMEOUT_RESOLVER_FOR_TESTING](resolver: (turn: { workflowId: string; kind: 'run' | 'resume'; }) => number): void; signal(workflowId: string, name: SignalDefinition): Promise; signal(workflowId: string, name: SignalDefinition, payload: TInput, options?: SignalDeliveryOptions): Promise; signal(workflowId: string, name: string, payload?: unknown, options?: SignalDeliveryOptions): Promise; update(workflowId: string, name: UpdateDefinition, payload?: void, options?: { timeout?: number; }): Promise; update(workflowId: string, name: UpdateDefinition, payload: TInput, options?: { timeout?: number; }): Promise; update(workflowId: string, name: string, payload?: unknown, options?: { timeout?: number; }): Promise; query(workflowId: string, name: QueryDefinition): Promise; query(workflowId: string, name: QueryDefinition, input: TInput): Promise; query(workflowId: string, name: string, input?: unknown): Promise; getStreamChunks(workflowId: string, key: string, options?: { after?: number; }): Promise; /** * Read a value a workflow offloaded with `ctx.offload(key, ...)` back out of * storage by `workflowId` + `key`. * * This is the external, post-completion reader for offloaded artifacts — the * missing sibling of {@link getStreamChunks} and {@link getEvents}. Offloaded * values survive normal completion (`completeWorkflow`/`failWorkflow` preserve * them) so a consumer can read a finished workflow's offloaded output after * `handle.result()` resolves. They are swept only when a workflow is * terminated, cancelled, or times out. * * @returns The decoded offload value, or `null` when no value is stored under * that key (key was never written, workflow ID unknown, or artifact swept). * * @example * ```ts * import { Engine } from '@lostgradient/weft'; * * async function readReport(engine: Engine, workflowId: string): Promise { * // `null` when the workflow offloaded nothing under this key, or after a * // terminated workflow swept its output artifacts. * return engine.getOffload(workflowId, 'report'); * } * ``` */ getOffload(workflowId: string, key: string): Promise; fork(sourceWorkflowId: string, options?: ForkOptions): Promise; /** * Re-drive a workflow from its persisted checkpoint and return a live handle. * Accepts a workflow left `'running'` (e.g. recovered after a process restart) * or one explicitly `'suspended'` via {@link Engine.suspend} — a suspended * workflow is durably flipped back to `'running'` as part of resuming. Throws * if the workflow is in any other status (terminal, pending) or not found. */ resume(workflowId: string): Promise; /** * Recover every running workflow found in storage. By default, recovery * fails before doing any resume work if a stored running workflow has no * registered workflow type on this engine. * * `acknowledgeUnknownWorkflowTypes` is a dangerous escape hatch for rolling * deploys or explicit operator storage repair. * When set, unknown workflow types are skipped and reported through * {@link WorkflowRecoverySkippedEvent}. * * A recovered workflow whose persisted version metadata no longer matches * its registered `WorkflowDefinition.version` is isolated per * {@link RecoverAllOptions.versionMismatchPolicy} (default `'fail-run'`): * only that workflow fails, and `recoverAll()` continues recovering its * siblings in the same call. */ recoverAll(options?: RecoverAllOptions): Promise; /** * Complete a deferred activity out-of-band with `result`. The activity must * have parked itself via `ActivityContext.completeAsync()`; pass the durable * task token announced through the `activity:async-pending` event (or * persisted by your callback dispatcher). The parked workflow resumes as * though the activity had returned `result` inline. During recovery, a * completion that arrives after token recovery but before replay adopts the * workflow generator is buffered and delivered when replay reaches the same * async-activity token. Await `recoverAll()` before accepting callback traffic * when your application needs startup ordering to be fully deterministic. * * @throws {AsyncActivityTokenNotFoundError} when no pending activity matches * the token (unknown, or already completed/failed — tokens are single-use). */ completeAsyncActivity(token: string, result: unknown): Promise; /** * List a bounded page of durable async activities awaiting out-of-band * completion for one workflow. This reads storage directly, so callers can * recover tokens after a process restart before `recoverAll()` repopulates * the in-memory completion map. */ listPendingAsyncActivities(workflowId: string, options?: PendingAsyncActivityListOptions): 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. During recovery, a failure that arrives after token * recovery but before replay adopts the workflow generator is buffered and * delivered when replay reaches the same async-activity token. Await * `recoverAll()` before accepting callback traffic when your application needs * startup ordering to be fully deterministic. * * @throws {AsyncActivityTokenNotFoundError} when no pending activity matches * the token (unknown, or already completed/failed — tokens are single-use). */ failAsyncActivity(token: string, error: unknown): Promise; cancel(workflowId: string): Promise; /** * Suspend a running workflow without terminating it. The workflow transitions * to the non-terminal `'suspended'` status, keeps its durable checkpoint, and * is later resumable via {@link Engine.resume} (or `handle.resume()`). Unlike * {@link Engine.cancel}, this does not run cancel handlers and does not settle * the result promise — `handle.result()` stays pending until a later `resume()` * drives the run to completion. * * Suspension is client-driven preemption, so a suspended workflow is NOT * auto-recovered by {@link Engine.recoverAll}; resume it explicitly. Calling * `suspend` on a workflow that is not running (already terminal, or never * started) is a no-op. */ suspend(workflowId: string): Promise; timeout(workflowId: string): Promise; get(workflowId: string): Promise; /** Return the schedule occurrence that launched a workflow, when applicable. */ getScheduleProvenance(workflowId: string): Promise; /** Return durable post-terminal finalizer progress or outcome, when applicable. */ getFinalizerStatus(workflowId: string): Promise; /** * Return this process's last-known ownership-lease health. * * This is a synchronous diagnostic snapshot: it never performs a storage read * and therefore remains available to operator transports after a confirmed * deposition has detached the lease manager. Holder identifiers and timestamps * are present only while the manager still has its own last-written holder * record; a detached deposed engine reports the confirmed loss without * inventing successor details. */ getLeaseHealth(): EngineLeaseHealth; getCurrentCheckpointStep(workflowId: string): Promise; getAttributes(workflowId: string): Promise | null>; setAttributes(workflowId: string, attributes: Record): Promise; addTags(workflowId: string, ...tags: string[]): Promise; removeTags(workflowId: string, ...tags: string[]): Promise; getEvents(workflowId: string): Promise; replayWorkflowFeed(workflowId: string, selector: WorkflowFeedSelector, afterSequence: number): AsyncIterable; snapshotWorkflowFeedTail(workflowId: string, selector: WorkflowFeedSelector): Promise; subscribeWorkflowFeedCommits(workflowId: string, selector: WorkflowFeedSelector, listener: WorkflowFeedListener): () => void; listCheckpoints(workflowId: string): Promise; getCheckpointAt(workflowId: string, step: number): Promise; getTimeline(workflowId: string): Promise; replayTo(workflowId: string, step: number): Promise; listReviews(filter?: ReviewListFilter): Promise; getReview(workflowId: string, reviewId: string): Promise; submitReview(reviewId: string, options: SubmitReviewOptions): Promise; getUpdateResult(updateId: string): Promise; submitCoordinatedUpdate(workflowId: string, name: string, payload?: unknown, options?: { timeout?: number; idempotencyKey?: string; }): Promise; /** * Awaited engine shutdown. Equivalent to * `await engine[Symbol.asyncDispose]()` and useful in explicit signal handlers * where `await using` syntax cannot own the process lifetime directly. * * Under `ownership: 'lease'`, this is the explicit prompt-handoff primitive: * it drains queued inline starts, tears down in-memory write paths, and awaits * lease release before resolving. The returned boolean is `true` when no lease * needed release or the holder delete committed, and `false` when the delete * did not commit. Synchronous disposal remains immediate and can make the next * engine wait for `leaseTtl` if the process exits before its background release * completes. */ shutdown(): Promise; /** * Synchronous teardown (`using engine = ...`). Pending inline launches that * have not yet run are **discarded**, not executed. When you need queued * starts to complete before teardown — or want a clean event loop with no * dangling deferred-launch macrotask — prefer {@link Engine[Symbol.asyncDispose]} * via `await using`. */ [Symbol.dispose](): void; [Symbol.asyncDispose](): Promise; get storage(): WeftStorage; get scheduler(): Scheduler; /** * Fire a single timer entry directly. Intended for external schedulers * (Service Worker, custom transports) that own timer dispatch but want * the engine to actually resume the workflow associated with the entry. * * Most users do not call this directly. The internal `Scheduler` invokes * the same code path automatically when its tick observes a due entry. * * @example * ```ts * import { Engine } from '@lostgradient/weft'; * declare const externalEntry: import('@lostgradient/weft').TimerEntry; * const engine = new Engine(); * await engine.fireTimer(externalEntry); * ``` */ fireTimer(entry: TimerEntry): Promise; } /** * `Engine` with its two chained-builder registration methods removed — * `register` and `registerWorkflows`, whose return type is itself * `Engine` (see `register()`'s JSDoc: registering returns * "this same engine with the definition added to its phantom type * registry"). That self-reference is what makes `Engine` invariant in its * registry generics: a concretely narrowed `Engine` (e.g. from * `Engine.create({ workflows })`) is not structurally assignable to the * plain default `Engine`, even though every other * member — including `start` and `startOrSignal`, whose *parameter* types * (not return types) reference `TWorkflows` but never produce another * `Engine` — is not part of that recursive comparison. * * `start` and `startOrSignal` are deliberately KEPT (not omitted): hosted * transports genuinely call them at runtime (REST/JSON-RPC workflow starts, * MCP tool invocations) via `runtimeWorkflowEngine()`'s registry-erased * dynamic-name overload, so a value satisfying this type must still provide * them — a duck-typed engine substitute lacking `start` correctly fails to * satisfy `RegistryAgnosticEngine` (see this type's `.test-d.ts` coverage). * * Host-facing options that accept an already-constructed `Engine` without * needing the two chained-builder registration methods — `serve({ engine })`, * the Service Worker helpers, the MCP session/HTTP/stdio surfaces — use this * type instead of the bare default `Engine`, so both `new Engine({ storage })` * and `Engine.create({ workflows })` are accepted without a call-site cast. * See #708. * * (`Engine` looks like the obvious fix — the widest legal * instantiation of the registry generics — but TypeScript's structural check * on `register()`'s self-referential return type does not reliably resolve * that relationship: it can pass or fail for the identical `Engine` / * `Engine` pair depending on unrelated compilation context, such as other * files in the same program. Removing the registry-typed members entirely, * rather than widening their generic arguments, avoids the recursive * comparison altogether.) * * @example Retain an engine with a concrete workflow registry behind a host-facing type * ```ts * import { * Engine, * MemoryStorage, * workflow, * type RegistryAgnosticEngine, * } from '@lostgradient/weft'; * * const greeting = workflow({ name: 'greeting' }).execute(async function* () { * return 'hello'; * }); * * const engine: RegistryAgnosticEngine = await Engine.create({ * storage: new MemoryStorage(), * workflows: { greeting }, * }); * * void engine.start; * void engine.startOrSignal; * ``` */ export type RegistryAgnosticEngine = Omit;