@dudousxd/nestjs-durable-core 0.62.1 → 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
@@ -627,6 +627,24 @@ interface WorkflowRun {
627
627
  * store persists it as `'default'`. Read paths (dashboard, `getRun`) are NOT namespace-scoped.
628
628
  */
629
629
  namespace?: string | undefined;
630
+ /**
631
+ * The npm package that DECLARED this run's workflow — e.g. `@dudousxd/nestjs-catalog-pipeline` for
632
+ * a workflow shipped by a library, or the host app's own `package.json` `name` for one written in
633
+ * the app. This is a CODE-PROVENANCE axis ("which lib owns this body"), NOT a tenancy axis — for
634
+ * that, see {@link namespace}.
635
+ *
636
+ * Stamped at creation from the workflow's registration, exactly as {@link namespace} is stamped
637
+ * from the creating engine. It is DERIVED, never caller input: `StartOptions` has no `origin`, so
638
+ * nothing a caller passes to `start` can make a run claim another lib's name.
639
+ *
640
+ * `undefined` means UNKNOWN — never "the app". It is what you get for a run created before this
641
+ * field existed, for a registration path that carries no origin (`registerRemote`, convention
642
+ * routing, a synthesized remote child, or a bare `engine.register` with no `origin` option), and
643
+ * for a workflow whose declaring package could not be resolved with confidence. A wrong origin is
644
+ * worse than an absent one: a filter that silently drops runs looks exactly like runs that never
645
+ * existed, so callers must render unknown as unknown and never fold it into a real package.
646
+ */
647
+ origin?: string | undefined;
630
648
  /**
631
649
  * Dispatch priority for a REMOTE run (one advanced by a {@link WorkflowExecutor} over a broker):
632
650
  * carried onto each {@link WorkflowTask} so an urgent child workflow can jump ahead of enqueued
@@ -1045,6 +1063,15 @@ interface RunQuery {
1045
1063
  attributes?: AttributeFilter[] | undefined;
1046
1064
  /** Restrict to runs in this namespace (exact match), ANDed with the other predicates. */
1047
1065
  namespace?: string | undefined;
1066
+ /**
1067
+ * Restrict to runs whose workflow was declared by this package (exact match against
1068
+ * {@link WorkflowRun.origin}), ANDed with the other predicates.
1069
+ *
1070
+ * A run with NO origin (unknown — see the field) matches no `origin` value at all, so a UI facet
1071
+ * built on this must keep an "all origins" option: filtering by any single package silently hides
1072
+ * every unattributed run, which reads to an operator as "those runs do not exist".
1073
+ */
1074
+ origin?: string | undefined;
1048
1075
  limit?: number | undefined;
1049
1076
  offset?: number | undefined;
1050
1077
  }
@@ -2262,7 +2289,14 @@ declare class EventAccumulators {
2262
2289
  }
2263
2290
 
2264
2291
  type WorkflowFn = (ctx: InternalWorkflowCtx, input: unknown) => Promise<unknown>;
2265
- /** Options for {@link WorkflowEngine.start}. */
2292
+ /**
2293
+ * Options for {@link WorkflowEngine.start}.
2294
+ *
2295
+ * Note the absence of `origin`: {@link WorkflowRun.origin} says which PACKAGE declared the workflow,
2296
+ * which is a property of the registered code, not of whoever started it. It is taken from the
2297
+ * registration and nothing here can override it — otherwise one caller could label its runs as
2298
+ * another lib's, and an origin filter would quietly lie.
2299
+ */
2266
2300
  interface StartOptions {
2267
2301
  /** Run-scoped tags, merged with the workflow's static `@Workflow({ tags })` onto the run. */
2268
2302
  tags?: string[] | undefined;
@@ -2596,6 +2630,12 @@ declare class WorkflowEngine {
2596
2630
  /** Capabilities a live worker must advertise to run this workflow's turns (handshake §7.5).
2597
2631
  * Only meaningful with `group`/`executor` (a remote/group-served workflow). Absent = runs anywhere. */
2598
2632
  requires?: string[] | undefined;
2633
+ /** The package that DECLARED this workflow, stamped onto every run started from it (see
2634
+ * {@link WorkflowRun.origin}). Supplied by the registering integration — `@dudousxd/nestjs-durable`
2635
+ * derives it from the `@Workflow` class's declaring file — never by whoever calls `start`. Omit
2636
+ * when it cannot be derived with confidence: absent means UNKNOWN, and an absent origin is far
2637
+ * cheaper to live with than a wrong one. */
2638
+ origin?: string | undefined;
2599
2639
  }): void;
2600
2640
  /**
2601
2641
  * The retained in-process body for `name@version`, for an IN-APP worker that consumes this engine's
@@ -3137,8 +3177,22 @@ declare class WorkflowEngine {
3137
3177
  * cancel flag stays too, re-derived from the status on any later re-drive regardless.
3138
3178
  */
3139
3179
  private compensateAndCancel;
3140
- /** 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
+ */
3141
3194
  private runExecution;
3195
+ private runExecutionTurn;
3142
3196
  /**
3143
3197
  * Undo every registered saga compensation in strict reverse order — the shared unwind both the
3144
3198
  * run-failure path (`runExecution`'s catch) and the compensating-cancel path
@@ -3630,6 +3684,70 @@ type RoutingResolution = RoutableResolution | BlockedResolution;
3630
3684
  */
3631
3685
  declare function resolveRouting(handler: CapabilityRequirement, workers: readonly (RawWorkerDescriptor | WorkerDescriptor)[]): RoutingResolution;
3632
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
+
3633
3751
  /** Canonical step id — the stable identity of a step within a run, used for dedupe and
3634
3752
  * correlation. The format is part of the cross-language wire contract (Python builds the same). */
3635
3753
  declare function stepId(runId: string, seq: number): string;
@@ -4305,4 +4423,4 @@ declare class InMemoryTransport implements Transport, ControlPlane {
4305
4423
  onControl(handler: (msg: ControlMessage) => void): void;
4306
4424
  }
4307
4425
 
4308
- 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
@@ -627,6 +627,24 @@ interface WorkflowRun {
627
627
  * store persists it as `'default'`. Read paths (dashboard, `getRun`) are NOT namespace-scoped.
628
628
  */
629
629
  namespace?: string | undefined;
630
+ /**
631
+ * The npm package that DECLARED this run's workflow — e.g. `@dudousxd/nestjs-catalog-pipeline` for
632
+ * a workflow shipped by a library, or the host app's own `package.json` `name` for one written in
633
+ * the app. This is a CODE-PROVENANCE axis ("which lib owns this body"), NOT a tenancy axis — for
634
+ * that, see {@link namespace}.
635
+ *
636
+ * Stamped at creation from the workflow's registration, exactly as {@link namespace} is stamped
637
+ * from the creating engine. It is DERIVED, never caller input: `StartOptions` has no `origin`, so
638
+ * nothing a caller passes to `start` can make a run claim another lib's name.
639
+ *
640
+ * `undefined` means UNKNOWN — never "the app". It is what you get for a run created before this
641
+ * field existed, for a registration path that carries no origin (`registerRemote`, convention
642
+ * routing, a synthesized remote child, or a bare `engine.register` with no `origin` option), and
643
+ * for a workflow whose declaring package could not be resolved with confidence. A wrong origin is
644
+ * worse than an absent one: a filter that silently drops runs looks exactly like runs that never
645
+ * existed, so callers must render unknown as unknown and never fold it into a real package.
646
+ */
647
+ origin?: string | undefined;
630
648
  /**
631
649
  * Dispatch priority for a REMOTE run (one advanced by a {@link WorkflowExecutor} over a broker):
632
650
  * carried onto each {@link WorkflowTask} so an urgent child workflow can jump ahead of enqueued
@@ -1045,6 +1063,15 @@ interface RunQuery {
1045
1063
  attributes?: AttributeFilter[] | undefined;
1046
1064
  /** Restrict to runs in this namespace (exact match), ANDed with the other predicates. */
1047
1065
  namespace?: string | undefined;
1066
+ /**
1067
+ * Restrict to runs whose workflow was declared by this package (exact match against
1068
+ * {@link WorkflowRun.origin}), ANDed with the other predicates.
1069
+ *
1070
+ * A run with NO origin (unknown — see the field) matches no `origin` value at all, so a UI facet
1071
+ * built on this must keep an "all origins" option: filtering by any single package silently hides
1072
+ * every unattributed run, which reads to an operator as "those runs do not exist".
1073
+ */
1074
+ origin?: string | undefined;
1048
1075
  limit?: number | undefined;
1049
1076
  offset?: number | undefined;
1050
1077
  }
@@ -2262,7 +2289,14 @@ declare class EventAccumulators {
2262
2289
  }
2263
2290
 
2264
2291
  type WorkflowFn = (ctx: InternalWorkflowCtx, input: unknown) => Promise<unknown>;
2265
- /** Options for {@link WorkflowEngine.start}. */
2292
+ /**
2293
+ * Options for {@link WorkflowEngine.start}.
2294
+ *
2295
+ * Note the absence of `origin`: {@link WorkflowRun.origin} says which PACKAGE declared the workflow,
2296
+ * which is a property of the registered code, not of whoever started it. It is taken from the
2297
+ * registration and nothing here can override it — otherwise one caller could label its runs as
2298
+ * another lib's, and an origin filter would quietly lie.
2299
+ */
2266
2300
  interface StartOptions {
2267
2301
  /** Run-scoped tags, merged with the workflow's static `@Workflow({ tags })` onto the run. */
2268
2302
  tags?: string[] | undefined;
@@ -2596,6 +2630,12 @@ declare class WorkflowEngine {
2596
2630
  /** Capabilities a live worker must advertise to run this workflow's turns (handshake §7.5).
2597
2631
  * Only meaningful with `group`/`executor` (a remote/group-served workflow). Absent = runs anywhere. */
2598
2632
  requires?: string[] | undefined;
2633
+ /** The package that DECLARED this workflow, stamped onto every run started from it (see
2634
+ * {@link WorkflowRun.origin}). Supplied by the registering integration — `@dudousxd/nestjs-durable`
2635
+ * derives it from the `@Workflow` class's declaring file — never by whoever calls `start`. Omit
2636
+ * when it cannot be derived with confidence: absent means UNKNOWN, and an absent origin is far
2637
+ * cheaper to live with than a wrong one. */
2638
+ origin?: string | undefined;
2599
2639
  }): void;
2600
2640
  /**
2601
2641
  * The retained in-process body for `name@version`, for an IN-APP worker that consumes this engine's
@@ -3137,8 +3177,22 @@ declare class WorkflowEngine {
3137
3177
  * cancel flag stays too, re-derived from the status on any later re-drive regardless.
3138
3178
  */
3139
3179
  private compensateAndCancel;
3140
- /** 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
+ */
3141
3194
  private runExecution;
3195
+ private runExecutionTurn;
3142
3196
  /**
3143
3197
  * Undo every registered saga compensation in strict reverse order — the shared unwind both the
3144
3198
  * run-failure path (`runExecution`'s catch) and the compensating-cancel path
@@ -3630,6 +3684,70 @@ type RoutingResolution = RoutableResolution | BlockedResolution;
3630
3684
  */
3631
3685
  declare function resolveRouting(handler: CapabilityRequirement, workers: readonly (RawWorkerDescriptor | WorkerDescriptor)[]): RoutingResolution;
3632
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
+
3633
3751
  /** Canonical step id — the stable identity of a step within a run, used for dedupe and
3634
3752
  * correlation. The format is part of the cross-language wire contract (Python builds the same). */
3635
3753
  declare function stepId(runId: string, seq: number): string;
@@ -4305,4 +4423,4 @@ declare class InMemoryTransport implements Transport, ControlPlane {
4305
4423
  onControl(handler: (msg: ControlMessage) => void): void;
4306
4424
  }
4307
4425
 
4308
- 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",
@@ -2558,6 +2616,7 @@ var WorkflowEngine = class {
2558
2616
  onEvent: opts?.onEvent,
2559
2617
  eventBatch: opts?.eventBatch,
2560
2618
  requires: opts?.requires,
2619
+ origin: opts?.origin,
2561
2620
  // A real, retained in-process body — so `workflowBody` can hand it to an in-app worker, and so
2562
2621
  // it is NOT confused with the throwing stub `registerRemote` installs for a body-less remote.
2563
2622
  hasBody: true
@@ -2784,6 +2843,11 @@ var WorkflowEngine = class {
2784
2843
  workflowVersion: registered.version,
2785
2844
  status: "pending",
2786
2845
  namespace: opts?.namespace ?? this.namespace,
2846
+ // DERIVED from the registration, exactly like `namespace` is derived from this engine — there is
2847
+ // deliberately no `opts.origin`, so a caller cannot label its run as another package's work.
2848
+ // `undefined` when the registration carried none (remote/convention/synthesized child, or a
2849
+ // registrar that could not resolve the declaring package): unknown, not "the app".
2850
+ origin: registered.origin,
2787
2851
  input,
2788
2852
  tags,
2789
2853
  searchAttributes: opts?.searchAttributes,
@@ -4518,8 +4582,31 @@ var WorkflowEngine = class {
4518
4582
  error: error2
4519
4583
  };
4520
4584
  }
4521
- /** The run body, lease held + renewed by {@link execute}. */
4522
- 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) {
4523
4610
  if (run.status === "cancelling") this.cancelRequested.add(run.id);
4524
4611
  if (run.status === "pending") {
4525
4612
  await this.store.updateRun(run.id, {
@@ -6129,6 +6216,7 @@ var InMemoryStateStore = class {
6129
6216
  if (query.statuses) runs = runs.filter((r) => query.statuses?.includes(r.status));
6130
6217
  if (query.tag) runs = runs.filter((r) => r.tags?.includes(query.tag));
6131
6218
  if (query.namespace !== void 0) runs = runs.filter((r) => r.namespace === query.namespace);
6219
+ if (query.origin !== void 0) runs = runs.filter((r) => r.origin === query.origin);
6132
6220
  if (query.attributes?.length) {
6133
6221
  let candidates = null;
6134
6222
  for (const f of query.attributes) {
@@ -6277,6 +6365,7 @@ export {
6277
6365
  canRoute,
6278
6366
  capableWorkers,
6279
6367
  classifyWaiterToken,
6368
+ clearDurableExecutionWrappers,
6280
6369
  collectMetrics,
6281
6370
  controlPlaneDescriptor,
6282
6371
  createStepLogger,
@@ -6290,6 +6379,7 @@ export {
6290
6379
  eventNameOf,
6291
6380
  eventPrefix,
6292
6381
  eventToken,
6382
+ hasDurableExecutionWrappers,
6293
6383
  heartbeatStatus,
6294
6384
  indexWaitersByRun,
6295
6385
  info,
@@ -6309,6 +6399,7 @@ export {
6309
6399
  requiredCapabilities,
6310
6400
  resolveRouting,
6311
6401
  resolveRunWaiting,
6402
+ runDurableExecution,
6312
6403
  runInStepLogger,
6313
6404
  runInWorkflowCtx,
6314
6405
  runSchedules,
@@ -6323,6 +6414,7 @@ export {
6323
6414
  subEvent,
6324
6415
  subProcess,
6325
6416
  tenantGroup,
6417
+ useDurableExecution,
6326
6418
  warn,
6327
6419
  workflowName
6328
6420
  };