@dudousxd/nestjs-durable-core 0.40.1 → 0.42.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
@@ -683,13 +683,17 @@ interface HistoryEvent {
683
683
  }
684
684
  /** A decision the workflow function produced at a `seq` that was not yet in history. */
685
685
  type WorkflowCommand =
686
- /** `ctx.call(remoteStep, input)` — dispatch a remote step and await it. */
686
+ /** `ctx.call(remoteStep, input)` — dispatch a remote step and await it. A worker's
687
+ * `ctx.gather_calls([...])` fan-out tags every dispatched call with the same `parallelGroup` so the
688
+ * dashboard renders the remote steps as one parallel fan (parity with the gathered `recordStep` /
689
+ * `startChild` tags). Undefined for a lone sequential `ctx.call`. */
687
690
  {
688
691
  kind: 'call';
689
692
  seq: number;
690
693
  name: string;
691
694
  group: string;
692
695
  input: unknown;
696
+ parallelGroup?: string;
693
697
  }
694
698
  /** `ctx.step(name, body)` — a LOCAL step the worker already ran this turn; the engine persists its
695
699
  * result so replay returns it instead of re-running (durability for side-effectful work).
@@ -833,6 +837,58 @@ interface Transport {
833
837
  * still surfaces. Pairs with {@link groupHealth}. */
834
838
  listWorkerGroups?(): Promise<string[]>;
835
839
  }
840
+ /** How a worker decides its concurrency, carried in {@link WorkerStatus} so a dashboard can tell a
841
+ * fixed knob from a self-regulating one (and, for the latter, where the controller currently sits). */
842
+ interface WorkerConcurrencyStatus {
843
+ /** `fixed` = the configured `concurrency` number, never moves. `adaptive` = the controller tunes it. */
844
+ mode: 'fixed' | 'adaptive';
845
+ /** The concurrency ceiling in effect right now (fixed: the configured value; adaptive: the live limit). */
846
+ limit: number;
847
+ /** Adaptive only — the floor the controller won't go below. */
848
+ min?: number;
849
+ /** Adaptive only — the ceiling the controller won't exceed. */
850
+ max?: number;
851
+ }
852
+ /** The adaptive controller's most recent limit change — the "why did it move" a dashboard shows so an
853
+ * auto-tuner isn't a black box. Absent until the controller first adjusts (or for a fixed worker). */
854
+ interface WorkerAdjust {
855
+ /** Epoch ms of the adjustment. */
856
+ at: number;
857
+ /** Limit before this change. */
858
+ from: number;
859
+ /** Limit after this change. */
860
+ to: number;
861
+ /** Why it moved: `ram_ceiling` (hard brake), `backpressure` (errors/stall), `grow` (had headroom),
862
+ * `shrink` (latency gradient showed queuing), `cpu_ceiling`. */
863
+ reason: 'ram_ceiling' | 'cpu_ceiling' | 'backpressure' | 'grow' | 'shrink';
864
+ }
865
+ /** A live snapshot of a worker's execution state, carried in the worker-liveness heartbeat (so it
866
+ * rides the existing TTL'd key — no extra round-trip) and surfaced per worker in the dashboard. Every
867
+ * field beyond {@link concurrency}/{@link inFlight} is best-effort: a field a runtime can't measure is
868
+ * simply omitted. The shape is shared by the TS and Python SDKs so a mixed-language group reports
869
+ * uniformly. */
870
+ interface WorkerStatus {
871
+ /** Which SDK is running this worker — `node` (TS) or `python`. */
872
+ runtime?: 'node' | 'python';
873
+ /** The worker's concurrency knob and its live ceiling. */
874
+ concurrency: WorkerConcurrencyStatus;
875
+ /** Tasks executing right now (0..limit). With `limit`, this is the saturation a dashboard charts. */
876
+ inFlight: number;
877
+ /** Resident set size in bytes, if the runtime can read it. */
878
+ rssBytes?: number;
879
+ /** The process memory ceiling in bytes (cgroup `memory.max`, else host total), if known. */
880
+ rssLimitBytes?: number;
881
+ /** RSS as a percent of {@link rssLimitBytes} (0..100) — what the adaptive RAM brake watches. */
882
+ rssPct?: number;
883
+ /** Process CPU utilisation percent over the last control tick (0..100×cores), if measured. */
884
+ cpuPct?: number;
885
+ /** Completed tasks per minute over the recent window — the controller's throughput signal. */
886
+ throughputPerMin?: number;
887
+ /** p95 of recent task durations in ms — the latency a dashboard charts against the limit. */
888
+ p95Ms?: number;
889
+ /** The adaptive controller's last limit change (absent for a fixed worker or before the first move). */
890
+ lastAdjust?: WorkerAdjust;
891
+ }
836
892
  /** One worker's liveness record — a TTL'd heartbeat a worker refreshes while it's consuming. Its
837
893
  * ABSENCE (the key expired) is the signal: a worker that died or stalled stops refreshing. */
838
894
  interface WorkerHeartbeat {
@@ -842,6 +898,9 @@ interface WorkerHeartbeat {
842
898
  instanceId: string;
843
899
  /** Epoch ms of the worker's most recent heartbeat. */
844
900
  lastBeatAt: number;
901
+ /** The worker's live execution snapshot, when its heartbeat carries one (newer SDKs stamp a JSON
902
+ * value; an older SDK's bare-timestamp heartbeat leaves this undefined). */
903
+ status?: WorkerStatus;
845
904
  }
846
905
  /** Per-group worker-health snapshot: how much work is queued vs. how many workers are alive to do it.
847
906
  * The actionable alert state is `depth > 0 && liveWorkers.length === 0` — work piling up with no
@@ -2492,4 +2551,4 @@ declare class InMemoryTransport implements Transport, ControlPlane {
2492
2551
  onControl(handler: (msg: ControlMessage) => void): void;
2493
2552
  }
2494
2553
 
2495
- 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 RetentionPolicy, 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, TERMINAL_RUN_STATUSES, 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 };
2554
+ 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 RetentionPolicy, 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, TERMINAL_RUN_STATUSES, TRANSPORT, TRANSPORT_CANONICAL, type Transport, type UpdateResult, type UpdateValidator, WORKFLOW_NAME_KEY, type WorkerAdjust, type WorkerConcurrencyStatus, type WorkerHeartbeat, type WorkerStatus, 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
@@ -683,13 +683,17 @@ interface HistoryEvent {
683
683
  }
684
684
  /** A decision the workflow function produced at a `seq` that was not yet in history. */
685
685
  type WorkflowCommand =
686
- /** `ctx.call(remoteStep, input)` — dispatch a remote step and await it. */
686
+ /** `ctx.call(remoteStep, input)` — dispatch a remote step and await it. A worker's
687
+ * `ctx.gather_calls([...])` fan-out tags every dispatched call with the same `parallelGroup` so the
688
+ * dashboard renders the remote steps as one parallel fan (parity with the gathered `recordStep` /
689
+ * `startChild` tags). Undefined for a lone sequential `ctx.call`. */
687
690
  {
688
691
  kind: 'call';
689
692
  seq: number;
690
693
  name: string;
691
694
  group: string;
692
695
  input: unknown;
696
+ parallelGroup?: string;
693
697
  }
694
698
  /** `ctx.step(name, body)` — a LOCAL step the worker already ran this turn; the engine persists its
695
699
  * result so replay returns it instead of re-running (durability for side-effectful work).
@@ -833,6 +837,58 @@ interface Transport {
833
837
  * still surfaces. Pairs with {@link groupHealth}. */
834
838
  listWorkerGroups?(): Promise<string[]>;
835
839
  }
840
+ /** How a worker decides its concurrency, carried in {@link WorkerStatus} so a dashboard can tell a
841
+ * fixed knob from a self-regulating one (and, for the latter, where the controller currently sits). */
842
+ interface WorkerConcurrencyStatus {
843
+ /** `fixed` = the configured `concurrency` number, never moves. `adaptive` = the controller tunes it. */
844
+ mode: 'fixed' | 'adaptive';
845
+ /** The concurrency ceiling in effect right now (fixed: the configured value; adaptive: the live limit). */
846
+ limit: number;
847
+ /** Adaptive only — the floor the controller won't go below. */
848
+ min?: number;
849
+ /** Adaptive only — the ceiling the controller won't exceed. */
850
+ max?: number;
851
+ }
852
+ /** The adaptive controller's most recent limit change — the "why did it move" a dashboard shows so an
853
+ * auto-tuner isn't a black box. Absent until the controller first adjusts (or for a fixed worker). */
854
+ interface WorkerAdjust {
855
+ /** Epoch ms of the adjustment. */
856
+ at: number;
857
+ /** Limit before this change. */
858
+ from: number;
859
+ /** Limit after this change. */
860
+ to: number;
861
+ /** Why it moved: `ram_ceiling` (hard brake), `backpressure` (errors/stall), `grow` (had headroom),
862
+ * `shrink` (latency gradient showed queuing), `cpu_ceiling`. */
863
+ reason: 'ram_ceiling' | 'cpu_ceiling' | 'backpressure' | 'grow' | 'shrink';
864
+ }
865
+ /** A live snapshot of a worker's execution state, carried in the worker-liveness heartbeat (so it
866
+ * rides the existing TTL'd key — no extra round-trip) and surfaced per worker in the dashboard. Every
867
+ * field beyond {@link concurrency}/{@link inFlight} is best-effort: a field a runtime can't measure is
868
+ * simply omitted. The shape is shared by the TS and Python SDKs so a mixed-language group reports
869
+ * uniformly. */
870
+ interface WorkerStatus {
871
+ /** Which SDK is running this worker — `node` (TS) or `python`. */
872
+ runtime?: 'node' | 'python';
873
+ /** The worker's concurrency knob and its live ceiling. */
874
+ concurrency: WorkerConcurrencyStatus;
875
+ /** Tasks executing right now (0..limit). With `limit`, this is the saturation a dashboard charts. */
876
+ inFlight: number;
877
+ /** Resident set size in bytes, if the runtime can read it. */
878
+ rssBytes?: number;
879
+ /** The process memory ceiling in bytes (cgroup `memory.max`, else host total), if known. */
880
+ rssLimitBytes?: number;
881
+ /** RSS as a percent of {@link rssLimitBytes} (0..100) — what the adaptive RAM brake watches. */
882
+ rssPct?: number;
883
+ /** Process CPU utilisation percent over the last control tick (0..100×cores), if measured. */
884
+ cpuPct?: number;
885
+ /** Completed tasks per minute over the recent window — the controller's throughput signal. */
886
+ throughputPerMin?: number;
887
+ /** p95 of recent task durations in ms — the latency a dashboard charts against the limit. */
888
+ p95Ms?: number;
889
+ /** The adaptive controller's last limit change (absent for a fixed worker or before the first move). */
890
+ lastAdjust?: WorkerAdjust;
891
+ }
836
892
  /** One worker's liveness record — a TTL'd heartbeat a worker refreshes while it's consuming. Its
837
893
  * ABSENCE (the key expired) is the signal: a worker that died or stalled stops refreshing. */
838
894
  interface WorkerHeartbeat {
@@ -842,6 +898,9 @@ interface WorkerHeartbeat {
842
898
  instanceId: string;
843
899
  /** Epoch ms of the worker's most recent heartbeat. */
844
900
  lastBeatAt: number;
901
+ /** The worker's live execution snapshot, when its heartbeat carries one (newer SDKs stamp a JSON
902
+ * value; an older SDK's bare-timestamp heartbeat leaves this undefined). */
903
+ status?: WorkerStatus;
845
904
  }
846
905
  /** Per-group worker-health snapshot: how much work is queued vs. how many workers are alive to do it.
847
906
  * The actionable alert state is `depth > 0 && liveWorkers.length === 0` — work piling up with no
@@ -2492,4 +2551,4 @@ declare class InMemoryTransport implements Transport, ControlPlane {
2492
2551
  onControl(handler: (msg: ControlMessage) => void): void;
2493
2552
  }
2494
2553
 
2495
- 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 RetentionPolicy, 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, TERMINAL_RUN_STATUSES, 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 };
2554
+ 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 RetentionPolicy, 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, TERMINAL_RUN_STATUSES, TRANSPORT, TRANSPORT_CANONICAL, type Transport, type UpdateResult, type UpdateValidator, WORKFLOW_NAME_KEY, type WorkerAdjust, type WorkerConcurrencyStatus, type WorkerHeartbeat, type WorkerStatus, 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.js CHANGED
@@ -2990,6 +2990,7 @@ var WorkflowEngine = class {
2990
2990
  error: cmd.error
2991
2991
  });
2992
2992
  } else if (cmd.kind === "call") {
2993
+ if (await this.store.getCheckpoint(run.id, cmd.seq)) continue;
2993
2994
  await this.store.saveCheckpoint(stepCheckpoint({
2994
2995
  runId: run.id,
2995
2996
  seq: cmd.seq,
@@ -3001,7 +3002,11 @@ var WorkflowEngine = class {
3001
3002
  workerGroup: cmd.group,
3002
3003
  enqueuedAt: at,
3003
3004
  startedAt: at,
3004
- finishedAt: at
3005
+ finishedAt: at,
3006
+ // A `ctx.gather_calls([...])` fan-out stamps every dispatched `call` in the fan with the
3007
+ // same group, so the dashboard renders the remote steps as one parallel fan (parity with
3008
+ // the gathered `recordStep`/`startChild` tags). Undefined for a lone sequential `ctx.call`.
3009
+ parallelGroup: cmd.parallelGroup
3005
3010
  }));
3006
3011
  await this.pool.dispatch({
3007
3012
  runId: run.id,