@dudousxd/nestjs-durable-core 0.63.0 → 0.64.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -3177,8 +3177,22 @@ declare class WorkflowEngine {
3177
3177
  * cancel flag stays too, re-derived from the status on any later re-drive regardless.
3178
3178
  */
3179
3179
  private compensateAndCancel;
3180
- /** The run body, lease held + renewed by {@link execute}. */
3180
+ /**
3181
+ * The run body, lease held + renewed by {@link execute}, wrapped in the registered
3182
+ * {@link runDurableExecution} wrappers so an observability scope covers ONE WHOLE TURN.
3183
+ *
3184
+ * The wrap goes here — around the entire turn — rather than tightly around the `fn(ctx, input)`
3185
+ * call, because the store writes that bracket the body (the `pending` → `running` transition, the
3186
+ * step checkpoints, the terminal `settleRun` update) are exactly the queries an operator wants
3187
+ * correlated to the turn that issued them. Narrowing the wrap to the body would leave them
3188
+ * uncorrelated for the sake of a purity that buys nothing.
3189
+ *
3190
+ * Only the TS path is wrapped. {@link runRemoteExecution} does not execute a body at all — it
3191
+ * dispatches a workflow task and awaits the worker's decision — so a scope opened there would
3192
+ * describe a dispatch, not an execution, and would mislead anyone reading the trace.
3193
+ */
3181
3194
  private runExecution;
3195
+ private runExecutionTurn;
3182
3196
  /**
3183
3197
  * Undo every registered saga compensation in strict reverse order — the shared unwind both the
3184
3198
  * run-failure path (`runExecution`'s catch) and the compensating-cancel path
@@ -3670,6 +3684,70 @@ type RoutingResolution = RoutableResolution | BlockedResolution;
3670
3684
  */
3671
3685
  declare function resolveRouting(handler: CapabilityRequirement, workers: readonly (RawWorkerDescriptor | WorkerDescriptor)[]): RoutingResolution;
3672
3686
 
3687
+ /**
3688
+ * Wrappers around the **real execution** of a unit of durable work — a workflow turn, or a step
3689
+ * handler body. Distinct from {@link StepInterceptor} (`engine.use`), which only sees the in-process
3690
+ * `localStep` primitives (`ctx.now`, `ctx.sideEffect`, `ctx.task`'s dispatch step, saga
3691
+ * compensations): a user's `ctx.step` is ALWAYS dispatched, so its handler runs inside a transport
3692
+ * callback or a worker process where no engine is in scope at all.
3693
+ *
3694
+ * That is exactly why this registry is process-level rather than per-engine. `runStepHandler` is a
3695
+ * free function every transport shares, and a thin worker (`DurableModule` with a `connection` and
3696
+ * no `store`) holds no `WorkflowEngine` to hang a hook off. A per-engine registry could not reach
3697
+ * either, so the seam that covers every deployment shape has to sit above the engine.
3698
+ *
3699
+ * The intended consumer is an observability bridge (Telescope, OTel, a logger) that needs to
3700
+ * establish an ambient scope — an AsyncLocalStorage batch, a span — that the executing code records
3701
+ * into. It is deliberately NOT a transform point: see {@link runDurableExecution}.
3702
+ */
3703
+ /** The unit of durable work a wrapper is being invoked around. */
3704
+ interface DurableExecution {
3705
+ /** `'workflow'` = one turn of a run's body; `'step'` = one step handler body. */
3706
+ readonly unit: 'workflow' | 'step';
3707
+ readonly runId: string;
3708
+ /** The workflow name. Present on a turn; a dispatched step task does not carry it on the wire. */
3709
+ readonly workflow: string | undefined;
3710
+ /** The step name, for `unit: 'step'`. Undefined for a turn. */
3711
+ readonly name: string | undefined;
3712
+ /** The step's logical position within the run, for `unit: 'step'`. */
3713
+ readonly seq: number | undefined;
3714
+ /** 1-based dispatch attempt, when the task carries one. */
3715
+ readonly attempt: number | undefined;
3716
+ }
3717
+ /**
3718
+ * Wraps one unit of durable work. Call `next()` to run the body (or the next wrapper). Whatever the
3719
+ * wrapper returns or throws is discarded — see {@link runDurableExecution} for why.
3720
+ */
3721
+ type DurableExecutionWrapper = (execution: DurableExecution, next: () => Promise<unknown>) => Promise<unknown>;
3722
+ /**
3723
+ * Register a {@link DurableExecutionWrapper}. Returns an unregister function.
3724
+ *
3725
+ * Registering is the ONLY cost an unused hook imposes: with an empty registry
3726
+ * {@link runDurableExecution} returns the body's own promise without allocating a chain, so a host
3727
+ * that wires no observability pays nothing at all on the execution path.
3728
+ */
3729
+ declare function useDurableExecution(wrapper: DurableExecutionWrapper): () => void;
3730
+ /** Drop every registered wrapper. For tests that must not leak a wrapper into the next file. */
3731
+ declare function clearDurableExecutionWrappers(): void;
3732
+ /** Whether any wrapper is registered — lets a hot path skip building an execution descriptor. */
3733
+ declare function hasDurableExecutionWrappers(): boolean;
3734
+ /**
3735
+ * Run `body` inside every registered wrapper and settle with **the body's own outcome**.
3736
+ *
3737
+ * The wrappers' return values and throws are deliberately ignored. An observer that misbehaves —
3738
+ * returns early without calling `next()` is the one case we cannot paper over, but one that throws
3739
+ * *after* the body settled, or swallows the body's error and returns normally, is common enough in
3740
+ * telemetry code — must not be able to change what the workflow saw. A wrapper that turned a failed
3741
+ * step into a completed one would corrupt the run's history and its checkpoints, which is a far
3742
+ * worse failure than a missing trace. This is the same reasoning the bullmq/schedule Telescope
3743
+ * watchers apply when they re-throw the host's error rather than swallowing it, pushed down into
3744
+ * the seam so every wrapper gets it for free instead of having to remember.
3745
+ *
3746
+ * The one unrecoverable case is a wrapper that never calls `next()`: the body did not run, so there
3747
+ * is no outcome to return and we throw rather than invent one.
3748
+ */
3749
+ declare function runDurableExecution<T>(execution: DurableExecution, body: () => Promise<T>): Promise<T>;
3750
+
3673
3751
  /** Canonical step id — the stable identity of a step within a run, used for dedupe and
3674
3752
  * correlation. The format is part of the cross-language wire contract (Python builds the same). */
3675
3753
  declare function stepId(runId: string, seq: number): string;
@@ -4345,4 +4423,4 @@ declare class InMemoryTransport implements Transport, ControlPlane {
4345
4423
  onControl(handler: (msg: ControlMessage) => void): void;
4346
4424
  }
4347
4425
 
4348
- export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AttributeFilter, type AttributeOp, type BackoffStrategy, type BlockedDispatch, type BlockedResolution, CONTROL_FLOW_SIGNAL, CURRENT_PROTOCOL_VERSION, type CapabilityRequirement, type ChildCallOptions, CodecStateStore, ContinueAsNew, type ControlMessage, type ControlPlane, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, DURABLE_STEP_CONFIG, DURABLE_STEP_NAME, type DispatchDiagnostics, type DispatchPlan, type DurableTopology, type DurableWebhook, DurableWorkflow, EVENT_BUFFER_SCAN_LIMIT, EVENT_TOKEN_PREFIX, type EngineEvent, type EngineEventType, type EngineListener, Entities, type EntityConfig, type EntityHandler, type EntityHost, EventAccumulators, type EventBatchConfig, FatalError, GatherError, type GroupHealth, type Heartbeat, type HeartbeatStatus, type HistoryEvent, InMemoryAdmissionBackend, InMemoryStateStore, InMemoryTransport, type InferSearchAttributes, LEGACY_V1_CAPABILITIES, LEGACY_V1_PROTOCOL, type MetricsCollector, type MetricsSnapshot, type NamedTransport, type NegotiateOptions, type NegotiationOutcome, type NegotiationReason, type NegotiationResult, NonDeterminismError, type PayloadCodec, type QueueConfig, QueueController, type RawWorkerDescriptor, RemoteStepError, RemoteStepTimeout, type RemoteTask, RemoteWorkflowExecutor, RemoteWorkflowTimeout, type RetentionPolicy, type RoutableDispatch, type RoutableResolution, type RoutingResolution, type RunAttributeRow, type RunDetail, type RunDispatcher, RunGateway, type RunListItem, type RunQuery, type RunReply, type RunReplyResult, type RunRequest, type RunRequestKind, type RunResult, type RunSchedulesOptions, type RunStatus, type RunWaiting, STATE_STORE, STATE_STORE_CANONICAL, type ScheduledWorkflow, type SearchAttributes, type SearchAttributesSchema, SignalTimeoutError, type SignalWaiter, type SingletonConfig, SingletonQueueFullError, StandardSchemaV1, type StartOptions, type StartRunMessage, type StateStore, type StepCheckpoint, type StepConfig, type StepDef, type StepDispatchOpts, type StepError, type StepEvent, type StepHandler, type StepInterceptor, type StepInvocation, type StepKind, type StepLogger, type StepOptions, type StepRef, type StepResult, type StepUndo, type StoreTransaction, type SubProcessHandle, TERMINAL_RUN_STATUSES, TRANSPORT, TRANSPORT_CANONICAL, type TenantEvent, type Transport, type UndoOf, type UpdateResult, type UpdateValidator, WORKFLOW_NAME_KEY, type WireDates, type WorkerAdjust, type WorkerConcurrencyStatus, type WorkerDescriptor, type WorkerHeartbeat, type WorkerLifecycle, type WorkerStatus, WorkflowBlocked, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowHandler, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRun, type WorkflowStartClient, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, applyAttributeQuery, attributeColumnFor, attributeOperand, bindWorkflowClass, breakpointToken, canRoute, capableWorkers, classifyWaiterToken, collectMetrics, controlPlaneDescriptor, createStepLogger, currentStep, currentWorkflowCtx, debug, descriptorHash, error, eventMatchOf, eventMatches, eventNameOf, eventPrefix, eventToken, heartbeatStatus, indexWaitersByRun, info, isEventToken, isLegacyDescriptor, isWorkflowControlFlowSignal, log, matchesAttributes, missingCapabilities, negotiate, normalizeAttributeRows, normalizeDescriptor, parseDuration, planDispatch, prevCronFireMs, readSearchAttributes, requiredCapabilities, resolveRouting, resolveRunWaiting, runInStepLogger, runInWorkflowCtx, runSchedules, runStepHandler, sanitizeQueueToken, scheduledRunId, sqlComparator, stepConfigOf, stepId, stepNameOf, sub, subEvent, subProcess, tenantGroup, warn, workflowName };
4426
+ export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AttributeFilter, type AttributeOp, type BackoffStrategy, type BlockedDispatch, type BlockedResolution, CONTROL_FLOW_SIGNAL, CURRENT_PROTOCOL_VERSION, type CapabilityRequirement, type ChildCallOptions, CodecStateStore, ContinueAsNew, type ControlMessage, type ControlPlane, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, DURABLE_STEP_CONFIG, DURABLE_STEP_NAME, type DispatchDiagnostics, type DispatchPlan, type DurableExecution, type DurableExecutionWrapper, type DurableTopology, type DurableWebhook, DurableWorkflow, EVENT_BUFFER_SCAN_LIMIT, EVENT_TOKEN_PREFIX, type EngineEvent, type EngineEventType, type EngineListener, Entities, type EntityConfig, type EntityHandler, type EntityHost, EventAccumulators, type EventBatchConfig, FatalError, GatherError, type GroupHealth, type Heartbeat, type HeartbeatStatus, type HistoryEvent, InMemoryAdmissionBackend, InMemoryStateStore, InMemoryTransport, type InferSearchAttributes, LEGACY_V1_CAPABILITIES, LEGACY_V1_PROTOCOL, type MetricsCollector, type MetricsSnapshot, type NamedTransport, type NegotiateOptions, type NegotiationOutcome, type NegotiationReason, type NegotiationResult, NonDeterminismError, type PayloadCodec, type QueueConfig, QueueController, type RawWorkerDescriptor, RemoteStepError, RemoteStepTimeout, type RemoteTask, RemoteWorkflowExecutor, RemoteWorkflowTimeout, type RetentionPolicy, type RoutableDispatch, type RoutableResolution, type RoutingResolution, type RunAttributeRow, type RunDetail, type RunDispatcher, RunGateway, type RunListItem, type RunQuery, type RunReply, type RunReplyResult, type RunRequest, type RunRequestKind, type RunResult, type RunSchedulesOptions, type RunStatus, type RunWaiting, STATE_STORE, STATE_STORE_CANONICAL, type ScheduledWorkflow, type SearchAttributes, type SearchAttributesSchema, SignalTimeoutError, type SignalWaiter, type SingletonConfig, SingletonQueueFullError, StandardSchemaV1, type StartOptions, type StartRunMessage, type StateStore, type StepCheckpoint, type StepConfig, type StepDef, type StepDispatchOpts, type StepError, type StepEvent, type StepHandler, type StepInterceptor, type StepInvocation, type StepKind, type StepLogger, type StepOptions, type StepRef, type StepResult, type StepUndo, type StoreTransaction, type SubProcessHandle, TERMINAL_RUN_STATUSES, TRANSPORT, TRANSPORT_CANONICAL, type TenantEvent, type Transport, type UndoOf, type UpdateResult, type UpdateValidator, WORKFLOW_NAME_KEY, type WireDates, type WorkerAdjust, type WorkerConcurrencyStatus, type WorkerDescriptor, type WorkerHeartbeat, type WorkerLifecycle, type WorkerStatus, WorkflowBlocked, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowHandler, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRun, type WorkflowStartClient, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, applyAttributeQuery, attributeColumnFor, attributeOperand, bindWorkflowClass, breakpointToken, canRoute, capableWorkers, classifyWaiterToken, clearDurableExecutionWrappers, collectMetrics, controlPlaneDescriptor, createStepLogger, currentStep, currentWorkflowCtx, debug, descriptorHash, error, eventMatchOf, eventMatches, eventNameOf, eventPrefix, eventToken, hasDurableExecutionWrappers, heartbeatStatus, indexWaitersByRun, info, isEventToken, isLegacyDescriptor, isWorkflowControlFlowSignal, log, matchesAttributes, missingCapabilities, negotiate, normalizeAttributeRows, normalizeDescriptor, parseDuration, planDispatch, prevCronFireMs, readSearchAttributes, requiredCapabilities, resolveRouting, resolveRunWaiting, runDurableExecution, runInStepLogger, runInWorkflowCtx, runSchedules, runStepHandler, sanitizeQueueToken, scheduledRunId, sqlComparator, stepConfigOf, stepId, stepNameOf, sub, subEvent, subProcess, tenantGroup, useDurableExecution, warn, workflowName };
package/dist/index.d.ts CHANGED
@@ -3177,8 +3177,22 @@ declare class WorkflowEngine {
3177
3177
  * cancel flag stays too, re-derived from the status on any later re-drive regardless.
3178
3178
  */
3179
3179
  private compensateAndCancel;
3180
- /** The run body, lease held + renewed by {@link execute}. */
3180
+ /**
3181
+ * The run body, lease held + renewed by {@link execute}, wrapped in the registered
3182
+ * {@link runDurableExecution} wrappers so an observability scope covers ONE WHOLE TURN.
3183
+ *
3184
+ * The wrap goes here — around the entire turn — rather than tightly around the `fn(ctx, input)`
3185
+ * call, because the store writes that bracket the body (the `pending` → `running` transition, the
3186
+ * step checkpoints, the terminal `settleRun` update) are exactly the queries an operator wants
3187
+ * correlated to the turn that issued them. Narrowing the wrap to the body would leave them
3188
+ * uncorrelated for the sake of a purity that buys nothing.
3189
+ *
3190
+ * Only the TS path is wrapped. {@link runRemoteExecution} does not execute a body at all — it
3191
+ * dispatches a workflow task and awaits the worker's decision — so a scope opened there would
3192
+ * describe a dispatch, not an execution, and would mislead anyone reading the trace.
3193
+ */
3181
3194
  private runExecution;
3195
+ private runExecutionTurn;
3182
3196
  /**
3183
3197
  * Undo every registered saga compensation in strict reverse order — the shared unwind both the
3184
3198
  * run-failure path (`runExecution`'s catch) and the compensating-cancel path
@@ -3670,6 +3684,70 @@ type RoutingResolution = RoutableResolution | BlockedResolution;
3670
3684
  */
3671
3685
  declare function resolveRouting(handler: CapabilityRequirement, workers: readonly (RawWorkerDescriptor | WorkerDescriptor)[]): RoutingResolution;
3672
3686
 
3687
+ /**
3688
+ * Wrappers around the **real execution** of a unit of durable work — a workflow turn, or a step
3689
+ * handler body. Distinct from {@link StepInterceptor} (`engine.use`), which only sees the in-process
3690
+ * `localStep` primitives (`ctx.now`, `ctx.sideEffect`, `ctx.task`'s dispatch step, saga
3691
+ * compensations): a user's `ctx.step` is ALWAYS dispatched, so its handler runs inside a transport
3692
+ * callback or a worker process where no engine is in scope at all.
3693
+ *
3694
+ * That is exactly why this registry is process-level rather than per-engine. `runStepHandler` is a
3695
+ * free function every transport shares, and a thin worker (`DurableModule` with a `connection` and
3696
+ * no `store`) holds no `WorkflowEngine` to hang a hook off. A per-engine registry could not reach
3697
+ * either, so the seam that covers every deployment shape has to sit above the engine.
3698
+ *
3699
+ * The intended consumer is an observability bridge (Telescope, OTel, a logger) that needs to
3700
+ * establish an ambient scope — an AsyncLocalStorage batch, a span — that the executing code records
3701
+ * into. It is deliberately NOT a transform point: see {@link runDurableExecution}.
3702
+ */
3703
+ /** The unit of durable work a wrapper is being invoked around. */
3704
+ interface DurableExecution {
3705
+ /** `'workflow'` = one turn of a run's body; `'step'` = one step handler body. */
3706
+ readonly unit: 'workflow' | 'step';
3707
+ readonly runId: string;
3708
+ /** The workflow name. Present on a turn; a dispatched step task does not carry it on the wire. */
3709
+ readonly workflow: string | undefined;
3710
+ /** The step name, for `unit: 'step'`. Undefined for a turn. */
3711
+ readonly name: string | undefined;
3712
+ /** The step's logical position within the run, for `unit: 'step'`. */
3713
+ readonly seq: number | undefined;
3714
+ /** 1-based dispatch attempt, when the task carries one. */
3715
+ readonly attempt: number | undefined;
3716
+ }
3717
+ /**
3718
+ * Wraps one unit of durable work. Call `next()` to run the body (or the next wrapper). Whatever the
3719
+ * wrapper returns or throws is discarded — see {@link runDurableExecution} for why.
3720
+ */
3721
+ type DurableExecutionWrapper = (execution: DurableExecution, next: () => Promise<unknown>) => Promise<unknown>;
3722
+ /**
3723
+ * Register a {@link DurableExecutionWrapper}. Returns an unregister function.
3724
+ *
3725
+ * Registering is the ONLY cost an unused hook imposes: with an empty registry
3726
+ * {@link runDurableExecution} returns the body's own promise without allocating a chain, so a host
3727
+ * that wires no observability pays nothing at all on the execution path.
3728
+ */
3729
+ declare function useDurableExecution(wrapper: DurableExecutionWrapper): () => void;
3730
+ /** Drop every registered wrapper. For tests that must not leak a wrapper into the next file. */
3731
+ declare function clearDurableExecutionWrappers(): void;
3732
+ /** Whether any wrapper is registered — lets a hot path skip building an execution descriptor. */
3733
+ declare function hasDurableExecutionWrappers(): boolean;
3734
+ /**
3735
+ * Run `body` inside every registered wrapper and settle with **the body's own outcome**.
3736
+ *
3737
+ * The wrappers' return values and throws are deliberately ignored. An observer that misbehaves —
3738
+ * returns early without calling `next()` is the one case we cannot paper over, but one that throws
3739
+ * *after* the body settled, or swallows the body's error and returns normally, is common enough in
3740
+ * telemetry code — must not be able to change what the workflow saw. A wrapper that turned a failed
3741
+ * step into a completed one would corrupt the run's history and its checkpoints, which is a far
3742
+ * worse failure than a missing trace. This is the same reasoning the bullmq/schedule Telescope
3743
+ * watchers apply when they re-throw the host's error rather than swallowing it, pushed down into
3744
+ * the seam so every wrapper gets it for free instead of having to remember.
3745
+ *
3746
+ * The one unrecoverable case is a wrapper that never calls `next()`: the body did not run, so there
3747
+ * is no outcome to return and we throw rather than invent one.
3748
+ */
3749
+ declare function runDurableExecution<T>(execution: DurableExecution, body: () => Promise<T>): Promise<T>;
3750
+
3673
3751
  /** Canonical step id — the stable identity of a step within a run, used for dedupe and
3674
3752
  * correlation. The format is part of the cross-language wire contract (Python builds the same). */
3675
3753
  declare function stepId(runId: string, seq: number): string;
@@ -4345,4 +4423,4 @@ declare class InMemoryTransport implements Transport, ControlPlane {
4345
4423
  onControl(handler: (msg: ControlMessage) => void): void;
4346
4424
  }
4347
4425
 
4348
- export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AttributeFilter, type AttributeOp, type BackoffStrategy, type BlockedDispatch, type BlockedResolution, CONTROL_FLOW_SIGNAL, CURRENT_PROTOCOL_VERSION, type CapabilityRequirement, type ChildCallOptions, CodecStateStore, ContinueAsNew, type ControlMessage, type ControlPlane, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, DURABLE_STEP_CONFIG, DURABLE_STEP_NAME, type DispatchDiagnostics, type DispatchPlan, type DurableTopology, type DurableWebhook, DurableWorkflow, EVENT_BUFFER_SCAN_LIMIT, EVENT_TOKEN_PREFIX, type EngineEvent, type EngineEventType, type EngineListener, Entities, type EntityConfig, type EntityHandler, type EntityHost, EventAccumulators, type EventBatchConfig, FatalError, GatherError, type GroupHealth, type Heartbeat, type HeartbeatStatus, type HistoryEvent, InMemoryAdmissionBackend, InMemoryStateStore, InMemoryTransport, type InferSearchAttributes, LEGACY_V1_CAPABILITIES, LEGACY_V1_PROTOCOL, type MetricsCollector, type MetricsSnapshot, type NamedTransport, type NegotiateOptions, type NegotiationOutcome, type NegotiationReason, type NegotiationResult, NonDeterminismError, type PayloadCodec, type QueueConfig, QueueController, type RawWorkerDescriptor, RemoteStepError, RemoteStepTimeout, type RemoteTask, RemoteWorkflowExecutor, RemoteWorkflowTimeout, type RetentionPolicy, type RoutableDispatch, type RoutableResolution, type RoutingResolution, type RunAttributeRow, type RunDetail, type RunDispatcher, RunGateway, type RunListItem, type RunQuery, type RunReply, type RunReplyResult, type RunRequest, type RunRequestKind, type RunResult, type RunSchedulesOptions, type RunStatus, type RunWaiting, STATE_STORE, STATE_STORE_CANONICAL, type ScheduledWorkflow, type SearchAttributes, type SearchAttributesSchema, SignalTimeoutError, type SignalWaiter, type SingletonConfig, SingletonQueueFullError, StandardSchemaV1, type StartOptions, type StartRunMessage, type StateStore, type StepCheckpoint, type StepConfig, type StepDef, type StepDispatchOpts, type StepError, type StepEvent, type StepHandler, type StepInterceptor, type StepInvocation, type StepKind, type StepLogger, type StepOptions, type StepRef, type StepResult, type StepUndo, type StoreTransaction, type SubProcessHandle, TERMINAL_RUN_STATUSES, TRANSPORT, TRANSPORT_CANONICAL, type TenantEvent, type Transport, type UndoOf, type UpdateResult, type UpdateValidator, WORKFLOW_NAME_KEY, type WireDates, type WorkerAdjust, type WorkerConcurrencyStatus, type WorkerDescriptor, type WorkerHeartbeat, type WorkerLifecycle, type WorkerStatus, WorkflowBlocked, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowHandler, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRun, type WorkflowStartClient, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, applyAttributeQuery, attributeColumnFor, attributeOperand, bindWorkflowClass, breakpointToken, canRoute, capableWorkers, classifyWaiterToken, collectMetrics, controlPlaneDescriptor, createStepLogger, currentStep, currentWorkflowCtx, debug, descriptorHash, error, eventMatchOf, eventMatches, eventNameOf, eventPrefix, eventToken, heartbeatStatus, indexWaitersByRun, info, isEventToken, isLegacyDescriptor, isWorkflowControlFlowSignal, log, matchesAttributes, missingCapabilities, negotiate, normalizeAttributeRows, normalizeDescriptor, parseDuration, planDispatch, prevCronFireMs, readSearchAttributes, requiredCapabilities, resolveRouting, resolveRunWaiting, runInStepLogger, runInWorkflowCtx, runSchedules, runStepHandler, sanitizeQueueToken, scheduledRunId, sqlComparator, stepConfigOf, stepId, stepNameOf, sub, subEvent, subProcess, tenantGroup, warn, workflowName };
4426
+ export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AttributeFilter, type AttributeOp, type BackoffStrategy, type BlockedDispatch, type BlockedResolution, CONTROL_FLOW_SIGNAL, CURRENT_PROTOCOL_VERSION, type CapabilityRequirement, type ChildCallOptions, CodecStateStore, ContinueAsNew, type ControlMessage, type ControlPlane, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, DURABLE_STEP_CONFIG, DURABLE_STEP_NAME, type DispatchDiagnostics, type DispatchPlan, type DurableExecution, type DurableExecutionWrapper, type DurableTopology, type DurableWebhook, DurableWorkflow, EVENT_BUFFER_SCAN_LIMIT, EVENT_TOKEN_PREFIX, type EngineEvent, type EngineEventType, type EngineListener, Entities, type EntityConfig, type EntityHandler, type EntityHost, EventAccumulators, type EventBatchConfig, FatalError, GatherError, type GroupHealth, type Heartbeat, type HeartbeatStatus, type HistoryEvent, InMemoryAdmissionBackend, InMemoryStateStore, InMemoryTransport, type InferSearchAttributes, LEGACY_V1_CAPABILITIES, LEGACY_V1_PROTOCOL, type MetricsCollector, type MetricsSnapshot, type NamedTransport, type NegotiateOptions, type NegotiationOutcome, type NegotiationReason, type NegotiationResult, NonDeterminismError, type PayloadCodec, type QueueConfig, QueueController, type RawWorkerDescriptor, RemoteStepError, RemoteStepTimeout, type RemoteTask, RemoteWorkflowExecutor, RemoteWorkflowTimeout, type RetentionPolicy, type RoutableDispatch, type RoutableResolution, type RoutingResolution, type RunAttributeRow, type RunDetail, type RunDispatcher, RunGateway, type RunListItem, type RunQuery, type RunReply, type RunReplyResult, type RunRequest, type RunRequestKind, type RunResult, type RunSchedulesOptions, type RunStatus, type RunWaiting, STATE_STORE, STATE_STORE_CANONICAL, type ScheduledWorkflow, type SearchAttributes, type SearchAttributesSchema, SignalTimeoutError, type SignalWaiter, type SingletonConfig, SingletonQueueFullError, StandardSchemaV1, type StartOptions, type StartRunMessage, type StateStore, type StepCheckpoint, type StepConfig, type StepDef, type StepDispatchOpts, type StepError, type StepEvent, type StepHandler, type StepInterceptor, type StepInvocation, type StepKind, type StepLogger, type StepOptions, type StepRef, type StepResult, type StepUndo, type StoreTransaction, type SubProcessHandle, TERMINAL_RUN_STATUSES, TRANSPORT, TRANSPORT_CANONICAL, type TenantEvent, type Transport, type UndoOf, type UpdateResult, type UpdateValidator, WORKFLOW_NAME_KEY, type WireDates, type WorkerAdjust, type WorkerConcurrencyStatus, type WorkerDescriptor, type WorkerHeartbeat, type WorkerLifecycle, type WorkerStatus, WorkflowBlocked, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowHandler, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRun, type WorkflowStartClient, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, applyAttributeQuery, attributeColumnFor, attributeOperand, bindWorkflowClass, breakpointToken, canRoute, capableWorkers, classifyWaiterToken, clearDurableExecutionWrappers, collectMetrics, controlPlaneDescriptor, createStepLogger, currentStep, currentWorkflowCtx, debug, descriptorHash, error, eventMatchOf, eventMatches, eventNameOf, eventPrefix, eventToken, hasDurableExecutionWrappers, heartbeatStatus, indexWaitersByRun, info, isEventToken, isLegacyDescriptor, isWorkflowControlFlowSignal, log, matchesAttributes, missingCapabilities, negotiate, normalizeAttributeRows, normalizeDescriptor, parseDuration, planDispatch, prevCronFireMs, readSearchAttributes, requiredCapabilities, resolveRouting, resolveRunWaiting, runDurableExecution, runInStepLogger, runInWorkflowCtx, runSchedules, runStepHandler, sanitizeQueueToken, scheduledRunId, sqlComparator, stepConfigOf, stepId, stepNameOf, sub, subEvent, subProcess, tenantGroup, useDurableExecution, warn, workflowName };
package/dist/index.js CHANGED
@@ -481,6 +481,57 @@ function backoffDelay(attempt, options) {
481
481
  }
482
482
  __name(backoffDelay, "backoffDelay");
483
483
 
484
+ // src/execution-hooks.ts
485
+ var wrappers = [];
486
+ function useDurableExecution(wrapper) {
487
+ wrappers.push(wrapper);
488
+ return () => {
489
+ const i = wrappers.indexOf(wrapper);
490
+ if (i >= 0) wrappers.splice(i, 1);
491
+ };
492
+ }
493
+ __name(useDurableExecution, "useDurableExecution");
494
+ function clearDurableExecutionWrappers() {
495
+ wrappers.length = 0;
496
+ }
497
+ __name(clearDurableExecutionWrappers, "clearDurableExecutionWrappers");
498
+ function hasDurableExecutionWrappers() {
499
+ return wrappers.length > 0;
500
+ }
501
+ __name(hasDurableExecutionWrappers, "hasDurableExecutionWrappers");
502
+ async function runDurableExecution(execution, body) {
503
+ if (wrappers.length === 0) return body();
504
+ let outcome;
505
+ const inner = /* @__PURE__ */ __name(async () => {
506
+ try {
507
+ const value = await body();
508
+ outcome = {
509
+ ok: true,
510
+ value
511
+ };
512
+ return value;
513
+ } catch (error2) {
514
+ outcome = {
515
+ ok: false,
516
+ error: error2
517
+ };
518
+ throw error2;
519
+ }
520
+ }, "inner");
521
+ const chain = wrappers.reduceRight((next, wrapper) => () => wrapper(execution, next), inner);
522
+ try {
523
+ await chain();
524
+ } catch (error2) {
525
+ if (outcome === void 0) throw error2;
526
+ }
527
+ if (outcome === void 0) {
528
+ throw new Error("a durable execution wrapper returned without calling next() \u2014 the unit of work never ran");
529
+ }
530
+ if (outcome.ok) return outcome.value;
531
+ throw outcome.error;
532
+ }
533
+ __name(runDurableExecution, "runDurableExecution");
534
+
484
535
  // src/step-logger.ts
485
536
  function withDuration(start, now, data) {
486
537
  if (data && typeof data === "object" && "durationMs" in data) {
@@ -622,7 +673,14 @@ async function runStepHandler(task, handler) {
622
673
  } : result, "withEvents");
623
674
  try {
624
675
  const logger = createStepLogger(events, Date.now);
625
- const output = await runInStepLogger(logger, () => handler(task.input, logger));
676
+ const output = await runDurableExecution({
677
+ unit: "step",
678
+ runId: task.runId,
679
+ workflow: void 0,
680
+ name: task.name,
681
+ seq: task.seq,
682
+ attempt: task.attempt
683
+ }, async () => runInStepLogger(logger, () => handler(task.input, logger)));
626
684
  return withEvents({
627
685
  ...base,
628
686
  status: "completed",
@@ -4524,8 +4582,31 @@ var WorkflowEngine = class {
4524
4582
  error: error2
4525
4583
  };
4526
4584
  }
4527
- /** The run body, lease held + renewed by {@link execute}. */
4528
- async runExecution(run, fn, registered) {
4585
+ /**
4586
+ * The run body, lease held + renewed by {@link execute}, wrapped in the registered
4587
+ * {@link runDurableExecution} wrappers so an observability scope covers ONE WHOLE TURN.
4588
+ *
4589
+ * The wrap goes here — around the entire turn — rather than tightly around the `fn(ctx, input)`
4590
+ * call, because the store writes that bracket the body (the `pending` → `running` transition, the
4591
+ * step checkpoints, the terminal `settleRun` update) are exactly the queries an operator wants
4592
+ * correlated to the turn that issued them. Narrowing the wrap to the body would leave them
4593
+ * uncorrelated for the sake of a purity that buys nothing.
4594
+ *
4595
+ * Only the TS path is wrapped. {@link runRemoteExecution} does not execute a body at all — it
4596
+ * dispatches a workflow task and awaits the worker's decision — so a scope opened there would
4597
+ * describe a dispatch, not an execution, and would mislead anyone reading the trace.
4598
+ */
4599
+ runExecution(run, fn, registered) {
4600
+ return runDurableExecution({
4601
+ unit: "workflow",
4602
+ runId: run.id,
4603
+ workflow: run.workflow,
4604
+ name: void 0,
4605
+ seq: void 0,
4606
+ attempt: void 0
4607
+ }, () => this.runExecutionTurn(run, fn, registered));
4608
+ }
4609
+ async runExecutionTurn(run, fn, registered) {
4529
4610
  if (run.status === "cancelling") this.cancelRequested.add(run.id);
4530
4611
  if (run.status === "pending") {
4531
4612
  await this.store.updateRun(run.id, {
@@ -6284,6 +6365,7 @@ export {
6284
6365
  canRoute,
6285
6366
  capableWorkers,
6286
6367
  classifyWaiterToken,
6368
+ clearDurableExecutionWrappers,
6287
6369
  collectMetrics,
6288
6370
  controlPlaneDescriptor,
6289
6371
  createStepLogger,
@@ -6297,6 +6379,7 @@ export {
6297
6379
  eventNameOf,
6298
6380
  eventPrefix,
6299
6381
  eventToken,
6382
+ hasDurableExecutionWrappers,
6300
6383
  heartbeatStatus,
6301
6384
  indexWaitersByRun,
6302
6385
  info,
@@ -6316,6 +6399,7 @@ export {
6316
6399
  requiredCapabilities,
6317
6400
  resolveRouting,
6318
6401
  resolveRunWaiting,
6402
+ runDurableExecution,
6319
6403
  runInStepLogger,
6320
6404
  runInWorkflowCtx,
6321
6405
  runSchedules,
@@ -6330,6 +6414,7 @@ export {
6330
6414
  subEvent,
6331
6415
  subProcess,
6332
6416
  tenantGroup,
6417
+ useDurableExecution,
6333
6418
  warn,
6334
6419
  workflowName
6335
6420
  };