@dudousxd/nestjs-durable-core 0.32.1 → 0.34.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
@@ -283,6 +283,13 @@ interface StepCheckpoint {
283
283
  events?: StepEvent[] | undefined;
284
284
  /** For sleep steps: epoch ms the sleep elapses at. */
285
285
  wakeAt?: number | undefined;
286
+ /**
287
+ * Set on the running placeholder checkpoints of the children dispatched by one `ctx.all` call —
288
+ * a shared tag (`all:<firstSeq>`) grouping the N siblings so the dashboard can render them as one
289
+ * parallel fan-out. Optional and additive: absent on every non-`all` checkpoint. Mirrors the
290
+ * Python SDK's `parallelGroup`.
291
+ */
292
+ parallelGroup?: string | undefined;
286
293
  /**
287
294
  * When the step entered the system: for a remote step, when the engine dispatched it to the
288
295
  * transport; for a local step, when it began. Queue-wait time = `startedAt − enqueuedAt`.
@@ -570,7 +577,13 @@ interface StepResult {
570
577
  interface Heartbeat {
571
578
  runId: string;
572
579
  seq: number;
573
- stepId: string;
580
+ /**
581
+ * The in-flight step this beat keeps alive. ABSENT for a RUN-scoped (workflow-turn) heartbeat: a
582
+ * worker replaying a long workflow turn beats for its `runId` so the engine rearms the run's
583
+ * `advance` deadline (see `WorkflowEngineDeps.remoteAdvanceSilenceMs`). The engine then keys the
584
+ * liveness reset by `runId` instead of `stepId`.
585
+ */
586
+ stepId?: string | undefined;
574
587
  group: string;
575
588
  }
576
589
  /** engine → workflow worker: advance this run one turn by replaying the function against `history`. */
@@ -636,6 +649,9 @@ type WorkflowCommand =
636
649
  startedAt?: number;
637
650
  finishedAt?: number;
638
651
  events?: StepEvent[];
652
+ /** A worker's `ctx.gather([...])` tags every step in the parallel fan with the same group, so the
653
+ * engine carries it onto the checkpoint and the dashboard renders the fan as one group. */
654
+ parallelGroup?: string;
639
655
  }
640
656
  /** `ctx.sleep(ms)` — a durable timer of `ms` duration. The engine computes the absolute deadline
641
657
  * (now + ms) when it applies the command, so the worker never reads the clock (determinism). */
@@ -650,12 +666,15 @@ type WorkflowCommand =
650
666
  seq: number;
651
667
  signal: string;
652
668
  }
653
- /** `ctx.start_child(workflow, input)` — start a child run with its own lifecycle. */
669
+ /** `ctx.start_child(workflow, input)` — start a child run with its own lifecycle. A worker's
670
+ * `ctx.all([...])` fan-out tags every dispatched child with the same `parallelGroup` so the
671
+ * dashboard can render the fan as one group (parity with the gathered `recordStep` tag). */
654
672
  | {
655
673
  kind: 'startChild';
656
674
  seq: number;
657
675
  workflow: string;
658
676
  input: unknown;
677
+ parallelGroup?: string;
659
678
  };
660
679
  /** workflow worker → engine: the result of replaying one turn of a remote workflow. */
661
680
  interface WorkflowDecision {
@@ -693,6 +712,9 @@ interface WorkflowStepEvent {
693
712
  error?: StepError;
694
713
  /** Sub-process + log trail the step emitted so far (the handler's p-processes). */
695
714
  events?: StepEvent[];
715
+ /** A worker's `ctx.gather([...])` fan tags every step's lifecycle with the same group so the
716
+ * dashboard renders the live fan-out as one group (parity with the `recordStep` tag). */
717
+ parallelGroup?: string | undefined;
696
718
  }
697
719
  /**
698
720
  * Advances a workflow run one turn. The engine has one per workflow: the default {@link InProcess}
@@ -988,6 +1010,24 @@ interface WorkflowCtx {
988
1010
  */
989
1011
  startChild<C extends WorkflowClass>(workflow: C, input: WorkflowInputOf<C>, options?: string | ChildCallOptions): Promise<string>;
990
1012
  startChild(workflow: string, input: unknown, options?: string | ChildCallOptions): Promise<string>;
1013
+ /**
1014
+ * Run N children of the SAME workflow **in parallel** and wait for ALL of them: dispatches one
1015
+ * child per entry in `inputs` (concurrently, each with its own durable lifecycle), suspends — zero
1016
+ * compute — until every child reaches a terminal state, then resumes with their outputs in **input
1017
+ * order**. Child ids are group-scoped and stable (`<runId>.all.<firstSeq>.<i>`), and the running
1018
+ * placeholders share a `parallelGroup` tag so the dashboard renders the fan-out as one group.
1019
+ *
1020
+ * `mode` (default `waitAll`): `waitAll` waits for all then throws an aggregate {@link GatherError}
1021
+ * if any failed; `failFast` throws as soon as a failed child is seen (siblings are not cancelled in
1022
+ * v1 — their eventual results are ignored). Empty `inputs` returns `[]` with no side effects. The
1023
+ * wait-all / fan-out counterpart to {@link child}; parity with the Python SDK's `gather_children`.
1024
+ */
1025
+ all<C extends WorkflowClass>(workflow: C, inputs: WorkflowInputOf<C>[], opts?: {
1026
+ mode?: 'waitAll' | 'failFast';
1027
+ }): Promise<WorkflowOutputOf<C>[]>;
1028
+ all<TOutput = unknown>(workflow: string, inputs: unknown[], opts?: {
1029
+ mode?: 'waitAll' | 'failFast';
1030
+ }): Promise<TOutput[]>;
991
1031
  /**
992
1032
  * Pause the run at this point until a human resumes it from the dashboard (or
993
1033
  * `engine.continue(runId)`). Records a visible `pending` checkpoint so the breakpoint shows up
@@ -1257,6 +1297,18 @@ interface WorkflowEngineDeps {
1257
1297
  * Omit for unlimited (the default — recovery always retries).
1258
1298
  */
1259
1299
  maxRecoveryAttempts?: number | undefined;
1300
+ /**
1301
+ * Opt-in liveness deadline (ms) for a remote workflow `advance`. If the worker neither returns a
1302
+ * decision NOR sends a run-scoped {@link Heartbeat} within this window, the engine presumes it dead:
1303
+ * it rejects the advance with {@link RemoteWorkflowTimeout}, releases the lease, and lets recovery
1304
+ * re-drive (replaying completed steps from history). Each beat REARMS the window, so a worker still
1305
+ * legitimately replaying a long turn (it keeps beating) is never re-driven — closing the duplicate-
1306
+ * side-effect hazard a fixed {@link RemoteWorkflowExecutor} `timeoutMs` cannot avoid. Omit (default)
1307
+ * for the prior unbounded await — byte-identical behavior when unset. If a `RemoteWorkflowExecutor` is
1308
+ * ALSO constructed with its own `timeoutMs`, the shorter of the two deadlines wins; prefer setting only
1309
+ * one (this rearmed window for heartbeat-aware workers, or the fixed `timeoutMs` otherwise).
1310
+ */
1311
+ remoteAdvanceSilenceMs?: number | undefined;
1260
1312
  /**
1261
1313
  * Build the public callback URL for a `ctx.webhook()` token (e.g.
1262
1314
  * ``(t) => `https://api.example.com/durable/webhooks/${t}` ``). Populates
@@ -1330,6 +1382,7 @@ declare class WorkflowEngine {
1330
1382
  private readonly instanceId;
1331
1383
  private readonly leaseMs;
1332
1384
  private readonly maxRecoveryAttempts?;
1385
+ private readonly remoteAdvanceSilenceMs?;
1333
1386
  private readonly webhookUrl?;
1334
1387
  private readonly traceparent?;
1335
1388
  private readonly context?;
@@ -1354,7 +1407,9 @@ declare class WorkflowEngine {
1354
1407
  private readonly accumulators;
1355
1408
  /** In-flight remote steps awaiting a worker result, keyed by stepId. */
1356
1409
  private readonly pending;
1357
- /** Per-step "reset the liveness timer" callbacks, called when a heartbeat arrives. */
1410
+ /** "Reset the liveness timer" callbacks keyed by the beat's target — a stepId (in-flight remote step)
1411
+ * or a runId (in-flight remote workflow turn) — invoked when a matching heartbeat arrives. The two id
1412
+ * shapes don't collide in practice: a stepId is `${runId}:${seq}`, a run key is the bare runId. */
1358
1413
  private readonly heartbeatResets;
1359
1414
  private readonly listeners;
1360
1415
  /** Step interceptors (onion middleware around real local-step execution), first = outermost. */
@@ -1751,10 +1806,15 @@ declare class WorkflowEngine {
1751
1806
  /** In-memory await path for a remote step with a liveness `timeoutMs` (re-dispatch on timeout). */
1752
1807
  private callRemoteInMemory;
1753
1808
  /**
1754
- * Await a remote result, but reject with `RemoteStepTimeout` if neither the result nor a heartbeat
1755
- * arrives within `timeoutMs`. Each heartbeat (delivered via `transport.onHeartbeat`) rearms the
1756
- * window, so a worker that keeps beating stays alive past `timeoutMs`.
1809
+ * Await `resultPromise`, but reject with `onTimeout()` if neither it settles nor a heartbeat for `id`
1810
+ * arrives within `timeoutMs`. Each heartbeat (delivered via `transport.onHeartbeat`, looked up in
1811
+ * `heartbeatResets` by `id`) REARMS the window, so a worker that keeps beating stays alive past
1812
+ * `timeoutMs`. Shared by the remote-STEP liveness (keyed by stepId) and the remote-WORKFLOW advance
1813
+ * liveness (keyed by runId); the caller supplies the timeout error and any extra cleanup via `onTimeout`.
1757
1814
  */
1815
+ private awaitWithLivenessDeadline;
1816
+ /** Remote-STEP liveness: await a worker result, timing out (with the step's pending-map cleanup) to
1817
+ * `RemoteStepTimeout` if it neither lands nor beats within `timeoutMs`. See {@link awaitWithLivenessDeadline}. */
1758
1818
  private awaitWithHeartbeat;
1759
1819
  }
1760
1820
  /** Raised inside the workflow when a remote worker reports a step failure. */
@@ -1803,6 +1863,29 @@ declare class RemoteStepTimeout extends Error {
1803
1863
  readonly timeoutMs: number;
1804
1864
  constructor(stepId: string, timeoutMs: number);
1805
1865
  }
1866
+ /**
1867
+ * Thrown by a remote {@link WorkflowExecutor} (e.g. {@link RemoteWorkflowExecutor}) when an `advance`
1868
+ * does not produce its decision within the configured `timeoutMs` — i.e. the worker is presumed gone
1869
+ * (the decision was dropped by a stall/redelivery or an instance restart spanning the in-memory waiter
1870
+ * map). Crucially this is NOT a run failure: the work may have actually completed and only the decision
1871
+ * was lost. The engine treats it as RECOVERABLE — it releases the run lease and leaves the run
1872
+ * `running` so `recoverIncomplete` re-drives it deterministically (replaying completed steps from
1873
+ * history). Distinct from a real executor error, which still fails the run.
1874
+ *
1875
+ * OPT-IN: only thrown when the executor was constructed with a `timeoutMs`. Absent a timeout, the
1876
+ * engine awaits the decision with its prior (unbounded) behavior — so existing users see no change.
1877
+ *
1878
+ * Known hazard: a timeout that fires while a worker is LEGITIMATELY still executing a not-yet-
1879
+ * checkpointed step will re-drive and re-run that in-flight step → DUPLICATE side effects. Therefore
1880
+ * the timeout is only safe when set GENEROUSLY (longer than the longest legitimate single turn). The
1881
+ * robust fix — a liveness/heartbeat-rearmed deadline so only a genuinely-dead worker re-drives — is the
1882
+ * documented follow-up (see the Track A diagnosis doc, "Part B").
1883
+ */
1884
+ declare class RemoteWorkflowTimeout extends Error {
1885
+ readonly taskId: string;
1886
+ readonly timeoutMs: number;
1887
+ constructor(taskId: string, timeoutMs: number);
1888
+ }
1806
1889
  /**
1807
1890
  * Thrown on resume when the workflow code no longer matches the recorded history: the step at a
1808
1891
  * logical position has a different name/kind than the checkpoint saved there. This means the
@@ -1816,6 +1899,24 @@ declare class NonDeterminismError extends Error {
1816
1899
  readonly seq: number;
1817
1900
  constructor(runId: string, seq: number, expected: string, recorded: string);
1818
1901
  }
1902
+ /**
1903
+ * Thrown by `ctx.all` when one or more parallel child workflows fail. Carries the per-item failures
1904
+ * (input index, child run id, error message) and presents an aggregate message summarizing the count
1905
+ * and the failing ids — the wait-all/fail-fast counterpart to a single child's FatalError. Mirrors
1906
+ * the Python SDK's `GatherFailed`.
1907
+ */
1908
+ declare class GatherError extends Error {
1909
+ readonly failures: {
1910
+ index: number;
1911
+ id: string;
1912
+ error: string;
1913
+ }[];
1914
+ constructor(failures: {
1915
+ index: number;
1916
+ id: string;
1917
+ error: string;
1918
+ }[], total?: number);
1919
+ }
1819
1920
  declare class WorkflowSuspended extends Error {
1820
1921
  /** Epoch ms to auto-resume (durable sleep), or undefined when waiting on an external signal. */
1821
1922
  readonly wakeAt?: number | undefined;
@@ -2059,6 +2160,14 @@ declare function prevCronFireMs(expr: string, nowMs: number, timezone?: string):
2059
2160
  */
2060
2161
  declare function runSchedules(engine: Pick<WorkflowEngine, 'start' | 'getRun'>, schedules: readonly ScheduledWorkflow[], nowMs: number, opts?: RunSchedulesOptions): Promise<string[]>;
2061
2162
 
2163
+ /**
2164
+ * A {@link StepLogger} that appends to `events`, stamping each line with `now()`. Shared by the
2165
+ * local-step path (`ctx.step`) and the remote-worker path (`runStepHandler`) so a step records
2166
+ * the same {@link StepEvent} shape wherever it runs — the TypeScript twin of the Python SDK's
2167
+ * `StepContext`.
2168
+ */
2169
+ declare function createStepLogger(events: StepEvent[], now: () => number): StepLogger;
2170
+
2062
2171
  /**
2063
2172
  * Does a run's search attributes satisfy EVERY filter (AND)? A missing key never matches (so `ne`
2064
2173
  * on an absent key is false too — the attribute simply isn't there to compare). Empty/undefined
@@ -2218,13 +2327,16 @@ declare class InMemoryStateStore implements StateStore {
2218
2327
  declare class InMemoryTransport implements Transport, ControlPlane {
2219
2328
  private readonly handlers;
2220
2329
  private resultHandler?;
2330
+ private heartbeatHandler?;
2221
2331
  private stepEventHandler?;
2222
2332
  private readonly controlHandlers;
2223
2333
  /** Register a fake worker handler for a step name. */
2224
2334
  handle(name: string, fn: StepHandler): void;
2225
2335
  dispatch(task: RemoteTask): Promise<void>;
2226
2336
  onResult(handler: (result: StepResult) => Promise<void>): void;
2227
- onHeartbeat(_handler: (beat: Heartbeat) => Promise<void>): void;
2337
+ onHeartbeat(handler: (beat: Heartbeat) => Promise<void>): void;
2338
+ /** Test hook: deliver a liveness heartbeat to the engine, as a real broker would on its channel. */
2339
+ emitHeartbeat(beat: Heartbeat): Promise<void>;
2228
2340
  /** Workflow step lifecycle: deliver to the engine's handler (async, mirroring a real broker). */
2229
2341
  dispatchStepEvent(event: WorkflowStepEvent): Promise<void>;
2230
2342
  onStepEvent(handler: (event: WorkflowStepEvent) => Promise<void>): void;
@@ -2232,4 +2344,4 @@ declare class InMemoryTransport implements Transport, ControlPlane {
2232
2344
  onControl(handler: (msg: ControlMessage) => void): void;
2233
2345
  }
2234
2346
 
2235
- export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AttributeFilter, type AttributeOp, type BackoffStrategy, type ChildCallOptions, CodecStateStore, ContinueAsNew, type ControlMessage, type ControlPlane, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, type DurableWebhook, type EngineEvent, type EngineEventType, type EngineListener, Entities, type EntityConfig, type EntityHandler, type EntityHost, EventAccumulators, type EventBatchConfig, FatalError, type GroupHealth, type Heartbeat, type HistoryEvent, InMemoryAdmissionBackend, InMemoryStateStore, InMemoryTransport, type MetricsCollector, type MetricsSnapshot, type NamedTransport, NonDeterminismError, type PayloadCodec, type QueueConfig, QueueController, type RemoteStepConfig, type RemoteStepDef, RemoteStepError, RemoteStepTimeout, type RemoteTask, RemoteWorkflowExecutor, type RunAttributeRow, type RunDispatcher, type RunQuery, type RunResult, type RunSchedulesOptions, type RunStatus, STATE_STORE, STATE_STORE_CANONICAL, type ScheduledWorkflow, type SearchAttributes, SignalTimeoutError, type SignalWaiter, type SingletonConfig, SingletonQueueFullError, type StartOptions, type StateStore, type StepCheckpoint, type StepError, type StepEvent, type StepHandler, type StepInterceptor, type StepInvocation, type StepKind, type StepLogger, type StepOptions, type StepResult, type StoreTransaction, type SubProcessHandle, TRANSPORT, TRANSPORT_CANONICAL, type Transport, type UpdateResult, type UpdateValidator, WORKFLOW_NAME_KEY, type WorkerHeartbeat, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRun, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, applyAttributeQuery, attributeColumnFor, attributeOperand, breakpointToken, collectMetrics, eventMatchOf, eventMatches, eventPrefix, eventToken, matchesAttributes, normalizeAttributeRows, parseDuration, prevCronFireMs, remoteStep, runSchedules, runStepHandler, scheduledRunId, sqlComparator, stepId, workflowName };
2347
+ export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AttributeFilter, type AttributeOp, type BackoffStrategy, type ChildCallOptions, CodecStateStore, ContinueAsNew, type ControlMessage, type ControlPlane, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, type DurableWebhook, type EngineEvent, type EngineEventType, type EngineListener, Entities, type EntityConfig, type EntityHandler, type EntityHost, EventAccumulators, type EventBatchConfig, FatalError, GatherError, type GroupHealth, type Heartbeat, type HistoryEvent, InMemoryAdmissionBackend, InMemoryStateStore, InMemoryTransport, type MetricsCollector, type MetricsSnapshot, type NamedTransport, NonDeterminismError, type PayloadCodec, type QueueConfig, QueueController, type RemoteStepConfig, type RemoteStepDef, RemoteStepError, RemoteStepTimeout, type RemoteTask, RemoteWorkflowExecutor, RemoteWorkflowTimeout, type RunAttributeRow, type RunDispatcher, type RunQuery, type RunResult, type RunSchedulesOptions, type RunStatus, STATE_STORE, STATE_STORE_CANONICAL, type ScheduledWorkflow, type SearchAttributes, SignalTimeoutError, type SignalWaiter, type SingletonConfig, SingletonQueueFullError, type StartOptions, type StateStore, type StepCheckpoint, type StepError, type StepEvent, type StepHandler, type StepInterceptor, type StepInvocation, type StepKind, type StepLogger, type StepOptions, type StepResult, type StoreTransaction, type SubProcessHandle, TRANSPORT, TRANSPORT_CANONICAL, type Transport, type UpdateResult, type UpdateValidator, WORKFLOW_NAME_KEY, type WorkerHeartbeat, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRun, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, applyAttributeQuery, attributeColumnFor, attributeOperand, breakpointToken, collectMetrics, createStepLogger, eventMatchOf, eventMatches, eventPrefix, eventToken, matchesAttributes, normalizeAttributeRows, parseDuration, prevCronFireMs, remoteStep, runSchedules, runStepHandler, scheduledRunId, sqlComparator, stepId, workflowName };
package/dist/index.d.ts CHANGED
@@ -283,6 +283,13 @@ interface StepCheckpoint {
283
283
  events?: StepEvent[] | undefined;
284
284
  /** For sleep steps: epoch ms the sleep elapses at. */
285
285
  wakeAt?: number | undefined;
286
+ /**
287
+ * Set on the running placeholder checkpoints of the children dispatched by one `ctx.all` call —
288
+ * a shared tag (`all:<firstSeq>`) grouping the N siblings so the dashboard can render them as one
289
+ * parallel fan-out. Optional and additive: absent on every non-`all` checkpoint. Mirrors the
290
+ * Python SDK's `parallelGroup`.
291
+ */
292
+ parallelGroup?: string | undefined;
286
293
  /**
287
294
  * When the step entered the system: for a remote step, when the engine dispatched it to the
288
295
  * transport; for a local step, when it began. Queue-wait time = `startedAt − enqueuedAt`.
@@ -570,7 +577,13 @@ interface StepResult {
570
577
  interface Heartbeat {
571
578
  runId: string;
572
579
  seq: number;
573
- stepId: string;
580
+ /**
581
+ * The in-flight step this beat keeps alive. ABSENT for a RUN-scoped (workflow-turn) heartbeat: a
582
+ * worker replaying a long workflow turn beats for its `runId` so the engine rearms the run's
583
+ * `advance` deadline (see `WorkflowEngineDeps.remoteAdvanceSilenceMs`). The engine then keys the
584
+ * liveness reset by `runId` instead of `stepId`.
585
+ */
586
+ stepId?: string | undefined;
574
587
  group: string;
575
588
  }
576
589
  /** engine → workflow worker: advance this run one turn by replaying the function against `history`. */
@@ -636,6 +649,9 @@ type WorkflowCommand =
636
649
  startedAt?: number;
637
650
  finishedAt?: number;
638
651
  events?: StepEvent[];
652
+ /** A worker's `ctx.gather([...])` tags every step in the parallel fan with the same group, so the
653
+ * engine carries it onto the checkpoint and the dashboard renders the fan as one group. */
654
+ parallelGroup?: string;
639
655
  }
640
656
  /** `ctx.sleep(ms)` — a durable timer of `ms` duration. The engine computes the absolute deadline
641
657
  * (now + ms) when it applies the command, so the worker never reads the clock (determinism). */
@@ -650,12 +666,15 @@ type WorkflowCommand =
650
666
  seq: number;
651
667
  signal: string;
652
668
  }
653
- /** `ctx.start_child(workflow, input)` — start a child run with its own lifecycle. */
669
+ /** `ctx.start_child(workflow, input)` — start a child run with its own lifecycle. A worker's
670
+ * `ctx.all([...])` fan-out tags every dispatched child with the same `parallelGroup` so the
671
+ * dashboard can render the fan as one group (parity with the gathered `recordStep` tag). */
654
672
  | {
655
673
  kind: 'startChild';
656
674
  seq: number;
657
675
  workflow: string;
658
676
  input: unknown;
677
+ parallelGroup?: string;
659
678
  };
660
679
  /** workflow worker → engine: the result of replaying one turn of a remote workflow. */
661
680
  interface WorkflowDecision {
@@ -693,6 +712,9 @@ interface WorkflowStepEvent {
693
712
  error?: StepError;
694
713
  /** Sub-process + log trail the step emitted so far (the handler's p-processes). */
695
714
  events?: StepEvent[];
715
+ /** A worker's `ctx.gather([...])` fan tags every step's lifecycle with the same group so the
716
+ * dashboard renders the live fan-out as one group (parity with the `recordStep` tag). */
717
+ parallelGroup?: string | undefined;
696
718
  }
697
719
  /**
698
720
  * Advances a workflow run one turn. The engine has one per workflow: the default {@link InProcess}
@@ -988,6 +1010,24 @@ interface WorkflowCtx {
988
1010
  */
989
1011
  startChild<C extends WorkflowClass>(workflow: C, input: WorkflowInputOf<C>, options?: string | ChildCallOptions): Promise<string>;
990
1012
  startChild(workflow: string, input: unknown, options?: string | ChildCallOptions): Promise<string>;
1013
+ /**
1014
+ * Run N children of the SAME workflow **in parallel** and wait for ALL of them: dispatches one
1015
+ * child per entry in `inputs` (concurrently, each with its own durable lifecycle), suspends — zero
1016
+ * compute — until every child reaches a terminal state, then resumes with their outputs in **input
1017
+ * order**. Child ids are group-scoped and stable (`<runId>.all.<firstSeq>.<i>`), and the running
1018
+ * placeholders share a `parallelGroup` tag so the dashboard renders the fan-out as one group.
1019
+ *
1020
+ * `mode` (default `waitAll`): `waitAll` waits for all then throws an aggregate {@link GatherError}
1021
+ * if any failed; `failFast` throws as soon as a failed child is seen (siblings are not cancelled in
1022
+ * v1 — their eventual results are ignored). Empty `inputs` returns `[]` with no side effects. The
1023
+ * wait-all / fan-out counterpart to {@link child}; parity with the Python SDK's `gather_children`.
1024
+ */
1025
+ all<C extends WorkflowClass>(workflow: C, inputs: WorkflowInputOf<C>[], opts?: {
1026
+ mode?: 'waitAll' | 'failFast';
1027
+ }): Promise<WorkflowOutputOf<C>[]>;
1028
+ all<TOutput = unknown>(workflow: string, inputs: unknown[], opts?: {
1029
+ mode?: 'waitAll' | 'failFast';
1030
+ }): Promise<TOutput[]>;
991
1031
  /**
992
1032
  * Pause the run at this point until a human resumes it from the dashboard (or
993
1033
  * `engine.continue(runId)`). Records a visible `pending` checkpoint so the breakpoint shows up
@@ -1257,6 +1297,18 @@ interface WorkflowEngineDeps {
1257
1297
  * Omit for unlimited (the default — recovery always retries).
1258
1298
  */
1259
1299
  maxRecoveryAttempts?: number | undefined;
1300
+ /**
1301
+ * Opt-in liveness deadline (ms) for a remote workflow `advance`. If the worker neither returns a
1302
+ * decision NOR sends a run-scoped {@link Heartbeat} within this window, the engine presumes it dead:
1303
+ * it rejects the advance with {@link RemoteWorkflowTimeout}, releases the lease, and lets recovery
1304
+ * re-drive (replaying completed steps from history). Each beat REARMS the window, so a worker still
1305
+ * legitimately replaying a long turn (it keeps beating) is never re-driven — closing the duplicate-
1306
+ * side-effect hazard a fixed {@link RemoteWorkflowExecutor} `timeoutMs` cannot avoid. Omit (default)
1307
+ * for the prior unbounded await — byte-identical behavior when unset. If a `RemoteWorkflowExecutor` is
1308
+ * ALSO constructed with its own `timeoutMs`, the shorter of the two deadlines wins; prefer setting only
1309
+ * one (this rearmed window for heartbeat-aware workers, or the fixed `timeoutMs` otherwise).
1310
+ */
1311
+ remoteAdvanceSilenceMs?: number | undefined;
1260
1312
  /**
1261
1313
  * Build the public callback URL for a `ctx.webhook()` token (e.g.
1262
1314
  * ``(t) => `https://api.example.com/durable/webhooks/${t}` ``). Populates
@@ -1330,6 +1382,7 @@ declare class WorkflowEngine {
1330
1382
  private readonly instanceId;
1331
1383
  private readonly leaseMs;
1332
1384
  private readonly maxRecoveryAttempts?;
1385
+ private readonly remoteAdvanceSilenceMs?;
1333
1386
  private readonly webhookUrl?;
1334
1387
  private readonly traceparent?;
1335
1388
  private readonly context?;
@@ -1354,7 +1407,9 @@ declare class WorkflowEngine {
1354
1407
  private readonly accumulators;
1355
1408
  /** In-flight remote steps awaiting a worker result, keyed by stepId. */
1356
1409
  private readonly pending;
1357
- /** Per-step "reset the liveness timer" callbacks, called when a heartbeat arrives. */
1410
+ /** "Reset the liveness timer" callbacks keyed by the beat's target — a stepId (in-flight remote step)
1411
+ * or a runId (in-flight remote workflow turn) — invoked when a matching heartbeat arrives. The two id
1412
+ * shapes don't collide in practice: a stepId is `${runId}:${seq}`, a run key is the bare runId. */
1358
1413
  private readonly heartbeatResets;
1359
1414
  private readonly listeners;
1360
1415
  /** Step interceptors (onion middleware around real local-step execution), first = outermost. */
@@ -1751,10 +1806,15 @@ declare class WorkflowEngine {
1751
1806
  /** In-memory await path for a remote step with a liveness `timeoutMs` (re-dispatch on timeout). */
1752
1807
  private callRemoteInMemory;
1753
1808
  /**
1754
- * Await a remote result, but reject with `RemoteStepTimeout` if neither the result nor a heartbeat
1755
- * arrives within `timeoutMs`. Each heartbeat (delivered via `transport.onHeartbeat`) rearms the
1756
- * window, so a worker that keeps beating stays alive past `timeoutMs`.
1809
+ * Await `resultPromise`, but reject with `onTimeout()` if neither it settles nor a heartbeat for `id`
1810
+ * arrives within `timeoutMs`. Each heartbeat (delivered via `transport.onHeartbeat`, looked up in
1811
+ * `heartbeatResets` by `id`) REARMS the window, so a worker that keeps beating stays alive past
1812
+ * `timeoutMs`. Shared by the remote-STEP liveness (keyed by stepId) and the remote-WORKFLOW advance
1813
+ * liveness (keyed by runId); the caller supplies the timeout error and any extra cleanup via `onTimeout`.
1757
1814
  */
1815
+ private awaitWithLivenessDeadline;
1816
+ /** Remote-STEP liveness: await a worker result, timing out (with the step's pending-map cleanup) to
1817
+ * `RemoteStepTimeout` if it neither lands nor beats within `timeoutMs`. See {@link awaitWithLivenessDeadline}. */
1758
1818
  private awaitWithHeartbeat;
1759
1819
  }
1760
1820
  /** Raised inside the workflow when a remote worker reports a step failure. */
@@ -1803,6 +1863,29 @@ declare class RemoteStepTimeout extends Error {
1803
1863
  readonly timeoutMs: number;
1804
1864
  constructor(stepId: string, timeoutMs: number);
1805
1865
  }
1866
+ /**
1867
+ * Thrown by a remote {@link WorkflowExecutor} (e.g. {@link RemoteWorkflowExecutor}) when an `advance`
1868
+ * does not produce its decision within the configured `timeoutMs` — i.e. the worker is presumed gone
1869
+ * (the decision was dropped by a stall/redelivery or an instance restart spanning the in-memory waiter
1870
+ * map). Crucially this is NOT a run failure: the work may have actually completed and only the decision
1871
+ * was lost. The engine treats it as RECOVERABLE — it releases the run lease and leaves the run
1872
+ * `running` so `recoverIncomplete` re-drives it deterministically (replaying completed steps from
1873
+ * history). Distinct from a real executor error, which still fails the run.
1874
+ *
1875
+ * OPT-IN: only thrown when the executor was constructed with a `timeoutMs`. Absent a timeout, the
1876
+ * engine awaits the decision with its prior (unbounded) behavior — so existing users see no change.
1877
+ *
1878
+ * Known hazard: a timeout that fires while a worker is LEGITIMATELY still executing a not-yet-
1879
+ * checkpointed step will re-drive and re-run that in-flight step → DUPLICATE side effects. Therefore
1880
+ * the timeout is only safe when set GENEROUSLY (longer than the longest legitimate single turn). The
1881
+ * robust fix — a liveness/heartbeat-rearmed deadline so only a genuinely-dead worker re-drives — is the
1882
+ * documented follow-up (see the Track A diagnosis doc, "Part B").
1883
+ */
1884
+ declare class RemoteWorkflowTimeout extends Error {
1885
+ readonly taskId: string;
1886
+ readonly timeoutMs: number;
1887
+ constructor(taskId: string, timeoutMs: number);
1888
+ }
1806
1889
  /**
1807
1890
  * Thrown on resume when the workflow code no longer matches the recorded history: the step at a
1808
1891
  * logical position has a different name/kind than the checkpoint saved there. This means the
@@ -1816,6 +1899,24 @@ declare class NonDeterminismError extends Error {
1816
1899
  readonly seq: number;
1817
1900
  constructor(runId: string, seq: number, expected: string, recorded: string);
1818
1901
  }
1902
+ /**
1903
+ * Thrown by `ctx.all` when one or more parallel child workflows fail. Carries the per-item failures
1904
+ * (input index, child run id, error message) and presents an aggregate message summarizing the count
1905
+ * and the failing ids — the wait-all/fail-fast counterpart to a single child's FatalError. Mirrors
1906
+ * the Python SDK's `GatherFailed`.
1907
+ */
1908
+ declare class GatherError extends Error {
1909
+ readonly failures: {
1910
+ index: number;
1911
+ id: string;
1912
+ error: string;
1913
+ }[];
1914
+ constructor(failures: {
1915
+ index: number;
1916
+ id: string;
1917
+ error: string;
1918
+ }[], total?: number);
1919
+ }
1819
1920
  declare class WorkflowSuspended extends Error {
1820
1921
  /** Epoch ms to auto-resume (durable sleep), or undefined when waiting on an external signal. */
1821
1922
  readonly wakeAt?: number | undefined;
@@ -2059,6 +2160,14 @@ declare function prevCronFireMs(expr: string, nowMs: number, timezone?: string):
2059
2160
  */
2060
2161
  declare function runSchedules(engine: Pick<WorkflowEngine, 'start' | 'getRun'>, schedules: readonly ScheduledWorkflow[], nowMs: number, opts?: RunSchedulesOptions): Promise<string[]>;
2061
2162
 
2163
+ /**
2164
+ * A {@link StepLogger} that appends to `events`, stamping each line with `now()`. Shared by the
2165
+ * local-step path (`ctx.step`) and the remote-worker path (`runStepHandler`) so a step records
2166
+ * the same {@link StepEvent} shape wherever it runs — the TypeScript twin of the Python SDK's
2167
+ * `StepContext`.
2168
+ */
2169
+ declare function createStepLogger(events: StepEvent[], now: () => number): StepLogger;
2170
+
2062
2171
  /**
2063
2172
  * Does a run's search attributes satisfy EVERY filter (AND)? A missing key never matches (so `ne`
2064
2173
  * on an absent key is false too — the attribute simply isn't there to compare). Empty/undefined
@@ -2218,13 +2327,16 @@ declare class InMemoryStateStore implements StateStore {
2218
2327
  declare class InMemoryTransport implements Transport, ControlPlane {
2219
2328
  private readonly handlers;
2220
2329
  private resultHandler?;
2330
+ private heartbeatHandler?;
2221
2331
  private stepEventHandler?;
2222
2332
  private readonly controlHandlers;
2223
2333
  /** Register a fake worker handler for a step name. */
2224
2334
  handle(name: string, fn: StepHandler): void;
2225
2335
  dispatch(task: RemoteTask): Promise<void>;
2226
2336
  onResult(handler: (result: StepResult) => Promise<void>): void;
2227
- onHeartbeat(_handler: (beat: Heartbeat) => Promise<void>): void;
2337
+ onHeartbeat(handler: (beat: Heartbeat) => Promise<void>): void;
2338
+ /** Test hook: deliver a liveness heartbeat to the engine, as a real broker would on its channel. */
2339
+ emitHeartbeat(beat: Heartbeat): Promise<void>;
2228
2340
  /** Workflow step lifecycle: deliver to the engine's handler (async, mirroring a real broker). */
2229
2341
  dispatchStepEvent(event: WorkflowStepEvent): Promise<void>;
2230
2342
  onStepEvent(handler: (event: WorkflowStepEvent) => Promise<void>): void;
@@ -2232,4 +2344,4 @@ declare class InMemoryTransport implements Transport, ControlPlane {
2232
2344
  onControl(handler: (msg: ControlMessage) => void): void;
2233
2345
  }
2234
2346
 
2235
- export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AttributeFilter, type AttributeOp, type BackoffStrategy, type ChildCallOptions, CodecStateStore, ContinueAsNew, type ControlMessage, type ControlPlane, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, type DurableWebhook, type EngineEvent, type EngineEventType, type EngineListener, Entities, type EntityConfig, type EntityHandler, type EntityHost, EventAccumulators, type EventBatchConfig, FatalError, type GroupHealth, type Heartbeat, type HistoryEvent, InMemoryAdmissionBackend, InMemoryStateStore, InMemoryTransport, type MetricsCollector, type MetricsSnapshot, type NamedTransport, NonDeterminismError, type PayloadCodec, type QueueConfig, QueueController, type RemoteStepConfig, type RemoteStepDef, RemoteStepError, RemoteStepTimeout, type RemoteTask, RemoteWorkflowExecutor, type RunAttributeRow, type RunDispatcher, type RunQuery, type RunResult, type RunSchedulesOptions, type RunStatus, STATE_STORE, STATE_STORE_CANONICAL, type ScheduledWorkflow, type SearchAttributes, SignalTimeoutError, type SignalWaiter, type SingletonConfig, SingletonQueueFullError, type StartOptions, type StateStore, type StepCheckpoint, type StepError, type StepEvent, type StepHandler, type StepInterceptor, type StepInvocation, type StepKind, type StepLogger, type StepOptions, type StepResult, type StoreTransaction, type SubProcessHandle, TRANSPORT, TRANSPORT_CANONICAL, type Transport, type UpdateResult, type UpdateValidator, WORKFLOW_NAME_KEY, type WorkerHeartbeat, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRun, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, applyAttributeQuery, attributeColumnFor, attributeOperand, breakpointToken, collectMetrics, eventMatchOf, eventMatches, eventPrefix, eventToken, matchesAttributes, normalizeAttributeRows, parseDuration, prevCronFireMs, remoteStep, runSchedules, runStepHandler, scheduledRunId, sqlComparator, stepId, workflowName };
2347
+ export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AttributeFilter, type AttributeOp, type BackoffStrategy, type ChildCallOptions, CodecStateStore, ContinueAsNew, type ControlMessage, type ControlPlane, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, type DurableWebhook, type EngineEvent, type EngineEventType, type EngineListener, Entities, type EntityConfig, type EntityHandler, type EntityHost, EventAccumulators, type EventBatchConfig, FatalError, GatherError, type GroupHealth, type Heartbeat, type HistoryEvent, InMemoryAdmissionBackend, InMemoryStateStore, InMemoryTransport, type MetricsCollector, type MetricsSnapshot, type NamedTransport, NonDeterminismError, type PayloadCodec, type QueueConfig, QueueController, type RemoteStepConfig, type RemoteStepDef, RemoteStepError, RemoteStepTimeout, type RemoteTask, RemoteWorkflowExecutor, RemoteWorkflowTimeout, type RunAttributeRow, type RunDispatcher, type RunQuery, type RunResult, type RunSchedulesOptions, type RunStatus, STATE_STORE, STATE_STORE_CANONICAL, type ScheduledWorkflow, type SearchAttributes, SignalTimeoutError, type SignalWaiter, type SingletonConfig, SingletonQueueFullError, type StartOptions, type StateStore, type StepCheckpoint, type StepError, type StepEvent, type StepHandler, type StepInterceptor, type StepInvocation, type StepKind, type StepLogger, type StepOptions, type StepResult, type StoreTransaction, type SubProcessHandle, TRANSPORT, TRANSPORT_CANONICAL, type Transport, type UpdateResult, type UpdateValidator, WORKFLOW_NAME_KEY, type WorkerHeartbeat, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRun, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, applyAttributeQuery, attributeColumnFor, attributeOperand, breakpointToken, collectMetrics, createStepLogger, eventMatchOf, eventMatches, eventPrefix, eventToken, matchesAttributes, normalizeAttributeRows, parseDuration, prevCronFireMs, remoteStep, runSchedules, runStepHandler, scheduledRunId, sqlComparator, stepId, workflowName };