@dudousxd/nestjs-durable-core 0.29.1 → 0.31.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
@@ -1,5 +1,166 @@
1
1
  import { z } from 'zod';
2
2
 
3
+ /**
4
+ * Flow control for remote steps. A queue caps how much work `ctx.call(step, input, { queue })`
5
+ * admits at once — a concurrency limit and/or a fixed-window rate limit. When a call can't be
6
+ * admitted it doesn't dispatch: the run re-suspends with a near-future wake time, and the timer
7
+ * poller re-tries admission later — so flow control is durable (survives crashes) without holding
8
+ * the run in memory.
9
+ *
10
+ * Accounting is per engine instance (the DBOS `workerConcurrency` tier): correct for the common
11
+ * single-orchestrator deployment. Global, cross-instance limits would need a durable counter in the
12
+ * store — a deliberate follow-up, not built here.
13
+ *
14
+ * Beyond raw caps, the controller can admit contended slots by PRIORITY and by per-KEY FAIRNESS
15
+ * (round-robin), so a single noisy key/tenant can't monopolize the concurrency budget. This is the
16
+ * DISPATCH/admission layer — it never touches workflow replay (engine positional logic). Default
17
+ * behaviour is unchanged FIFO when neither priority nor fairness is configured/passed.
18
+ *
19
+ * Design (vs Temporal/Inngest/DBOS): Temporal task queues are FIFO with separate priority levels;
20
+ * Inngest concurrency keys + throttle give per-key fairness; DBOS queues are FIFO with a
21
+ * `workerConcurrency` cap. We fold both knobs into the same in-process admission gate: an optional
22
+ * per-call `priority` (higher first) and an optional queue-level `fairness: 'key'` (round-robin by
23
+ * the per-call `key`). Because blocked calls re-suspend durably and retry independently (there is no
24
+ * resident FIFO list to reorder), the controller remembers the set of WAITERS that asked-and-were-
25
+ * blocked, and on a later retry only admits the one that is rightful-next under the policy.
26
+ */
27
+ interface QueueConfig {
28
+ /** Queue name, referenced by `ctx.call(step, input, { queue: name })`. */
29
+ name: string;
30
+ /** Max steps in flight at once for this queue (this instance). Omit for unlimited. */
31
+ concurrency?: number;
32
+ /** Fixed-window rate limit: at most `limit` admissions per `periodMs`. Omit for unlimited. */
33
+ rateLimit?: {
34
+ limit: number;
35
+ periodMs: number;
36
+ };
37
+ /** Delay (ms) before a concurrency-blocked call re-checks for a free slot. Default 1000. */
38
+ retryMs?: number;
39
+ /**
40
+ * Fairness policy for a contended concurrency slot. `'key'` round-robins across distinct per-call
41
+ * keys (the least-recently-served key wins the next slot), so one key can't monopolize the budget.
42
+ * Omit (or `'fifo'`) for plain first-come ordering. Priority always wins over fairness; fairness
43
+ * only breaks ties within the same priority.
44
+ */
45
+ fairness?: 'fifo' | 'key';
46
+ /**
47
+ * Arrival-order tiebreak among otherwise-equal waiters (same priority, same fairness rank).
48
+ * `'fifo'` (default) admits the earliest arrival; `'lifo'` admits the most recent (a stack). Orthogonal
49
+ * to `priority` and `fairness` — those still win first; this only breaks their ties.
50
+ */
51
+ order?: 'fifo' | 'lifo';
52
+ }
53
+ /**
54
+ * Per-call admission hints. All optional — omitting them keeps the legacy FIFO behaviour.
55
+ * - `priority`: higher is admitted first (default 0).
56
+ * - `key`: the fairness bucket (e.g. a tenant id) for `fairness: 'key'`.
57
+ * - `waiterId`: a stable id for THIS call across its retries (the engine passes the stepId), so the
58
+ * controller can track it as one waiter instead of counting each retry as a new contender.
59
+ */
60
+ interface AdmissionItem {
61
+ priority?: number | undefined;
62
+ key?: string | undefined;
63
+ waiterId?: string | undefined;
64
+ }
65
+ /** Admission decision: either admitted, or blocked until `retryAt` (epoch ms). */
66
+ type Admission = {
67
+ ok: true;
68
+ } | {
69
+ ok: false;
70
+ retryAt: number;
71
+ };
72
+ /** In-process admission controller for one {@link QueueConfig}. */
73
+ declare class QueueController {
74
+ readonly config: QueueConfig;
75
+ private readonly clock;
76
+ private inFlight;
77
+ private windowStart;
78
+ private windowCount;
79
+ /** Registered waiters by id (only used when priority/fairness ordering is in play). */
80
+ private readonly waiters;
81
+ /** Monotonic counter stamping each newly-seen waiter for a stable FIFO tiebreak. */
82
+ private waiterSeq;
83
+ /** Per-key "last served" order for round-robin fairness — higher = more recently served. */
84
+ private readonly keyServed;
85
+ private servedTick;
86
+ constructor(config: QueueConfig, clock: () => number);
87
+ /** Whether this controller does any priority/fairness ordering (else it's a plain FIFO gate). */
88
+ private get ordered();
89
+ /**
90
+ * Try to admit one unit of work. On `ok`, the caller has taken a concurrency slot and must call
91
+ * {@link release} when the step settles. On block, `retryAt` is when admission may next succeed.
92
+ *
93
+ * `item` carries optional priority/fairness hints; omitting it is the legacy FIFO path. When
94
+ * ordering is configured (or a priority is given), a blocked call REGISTERS as a waiter and is only
95
+ * admitted on a later retry once it is the rightful next under (priority desc, fairness, FIFO).
96
+ */
97
+ tryAdmit(item?: AdmissionItem): Admission;
98
+ /** Release a concurrency slot taken by a successful {@link tryAdmit}. */
99
+ release(): void;
100
+ /** Track (or refresh) a blocked call as a waiter; its FIFO seq is stamped only on first sight. */
101
+ private register;
102
+ /**
103
+ * Is the calling item the best candidate among all registered waiters? Ordering, best first:
104
+ * 1. higher `priority`;
105
+ * 2. fairness: with `fairness: 'key'`, the least-recently-served key wins (round-robin);
106
+ * 3. earliest registration (FIFO) — stable tiebreak.
107
+ * A bare item (no waiterId) is compared on its hints directly.
108
+ */
109
+ private isNext;
110
+ /** Strict "a should be admitted before b" comparison under the active policy. */
111
+ private better;
112
+ /** Last-served tick for a key (0 = never served, so an unseen key is admitted before a busy one). */
113
+ private servedOf;
114
+ }
115
+
116
+ /**
117
+ * Pluggable backend for the remote-step flow-control gate (`ctx.call(step, input, { queue })`). The
118
+ * engine asks it whether a queued call may be admitted now (concurrency / rate / ordering) and tells
119
+ * it when an admitted slot is released.
120
+ *
121
+ * The default {@link InMemoryAdmissionBackend} keeps per-instance counts — correct for a single
122
+ * orchestrator. Swap in a store/Redis-backed backend to make the caps and ordering GLOBAL across
123
+ * engine replicas (so `concurrency: 5` means 5 in-flight across the whole fleet, not 5 per pod).
124
+ *
125
+ * `tryAdmit`/`release` are async so a global backend can do its atomic round-trip; the in-memory one
126
+ * resolves synchronously. An UNREGISTERED queue name is treated as ungated (admits immediately) — the
127
+ * engine only tracks/releases a slot for a registered queue.
128
+ */
129
+ interface AdmissionBackend {
130
+ /** Register (or replace) a queue's config. Called from `engine.registerQueue`. */
131
+ register(config: QueueConfig): void;
132
+ /** Whether `queue` is registered (gated). The engine only tracks/releases a slot for a gated queue. */
133
+ handles(queue: string): boolean;
134
+ /** May a call on `queue` take a slot now? On `ok`, the caller holds a slot until {@link release}. */
135
+ tryAdmit(queue: string, item: AdmissionItem): Promise<Admission>;
136
+ /**
137
+ * Release the slot granted by {@link tryAdmit} for `queue`. `slotId` is the admitted item's
138
+ * `waiterId` (the engine's stepId), so a lease-based backend can drop the exact slot; an in-process
139
+ * counter ignores it.
140
+ */
141
+ release(queue: string, slotId: string): Promise<void>;
142
+ /**
143
+ * Optional: subscribe to "a slot just freed on `queue`" events (e.g. a fleet-wide pub/sub). The
144
+ * engine uses it to wake its admission-blocked runs early instead of waiting for their retry tick.
145
+ * A backend without it falls back to retry-tick polling. Fired best-effort across the fleet.
146
+ */
147
+ onFreed?(handler: (queue: string) => void): void;
148
+ }
149
+ /**
150
+ * Default admission backend: one in-process {@link QueueController} per registered queue. Caps and
151
+ * waiter ordering are per engine instance (see {@link QueueConfig}). Preserves the exact pre-backend
152
+ * behaviour — the engine used to hold these controllers directly.
153
+ */
154
+ declare class InMemoryAdmissionBackend implements AdmissionBackend {
155
+ private readonly clock;
156
+ private readonly controllers;
157
+ constructor(clock: () => number);
158
+ register(config: QueueConfig): void;
159
+ handles(queue: string): boolean;
160
+ tryAdmit(queue: string, item: AdmissionItem): Promise<Admission>;
161
+ release(queue: string, _slotId: string): Promise<void>;
162
+ }
163
+
3
164
  /**
4
165
  * Parse a human duration to milliseconds. Accepts a number (already ms) or a string like
5
166
  * `'500ms'`, `'30s'`, `'15m'`, `'2h'`, `'7d'`, `'7 days'`.
@@ -77,6 +238,13 @@ interface WorkflowRun {
77
238
  tags?: string[] | undefined;
78
239
  /** Typed, queryable run data (e.g. `{ amount: 200, tier: 'pro' }`) — see {@link RunQuery.attributes}. */
79
240
  searchAttributes?: SearchAttributes | undefined;
241
+ /**
242
+ * Dispatch priority for a REMOTE run (one advanced by a {@link WorkflowExecutor} over a broker):
243
+ * carried onto each {@link WorkflowTask} so an urgent child workflow can jump ahead of enqueued
244
+ * lower-priority ones at the worker. Higher wins; absent = unprioritised. Best-effort ordering, not
245
+ * durable state required for correctness — a transport without priority support ignores it.
246
+ */
247
+ priority?: number | undefined;
80
248
  createdAt: Date;
81
249
  updatedAt: Date;
82
250
  }
@@ -226,6 +394,14 @@ interface StateStore {
226
394
  createRun(run: WorkflowRun): Promise<void>;
227
395
  updateRun(runId: string, patch: Partial<WorkflowRun>): Promise<void>;
228
396
  getRun(runId: string): Promise<WorkflowRun | null>;
397
+ /**
398
+ * Hard-delete a run and all of its rows — the run plus its checkpoints, signal waiters, and
399
+ * normalized search-attribute rows. Unlike a cancelled run (which stays as terminal history), a
400
+ * deleted run is GONE: it no longer appears in {@link getRun} or {@link listRuns}. This removes
401
+ * exactly the one run — the engine's {@link WorkflowEngine.deleteRun} handles the child cascade.
402
+ * No-op if the run doesn't exist. (Token-keyed buffered signals are transient and left alone.)
403
+ */
404
+ deleteRun(runId: string): Promise<void>;
229
405
  getCheckpoint(runId: string, seq: number): Promise<StepCheckpoint | null>;
230
406
  /**
231
407
  * Persist a checkpoint and advance the run atomically. Durable semantics depend on this
@@ -370,6 +546,13 @@ interface RemoteTask {
370
546
  * so failover is symmetric without the worker choosing a transport. Absent for a single transport.
371
547
  */
372
548
  transport?: string | undefined;
549
+ /**
550
+ * Admission priority carried through to the broker (BullMQ job `priority`) so a transport that
551
+ * supports priority ordering lets an urgent task jump ahead of already-enqueued lower-priority
552
+ * ones at the worker. Mirrors the per-call `priority` from `ctx.call(..., { priority })`. Higher
553
+ * wins; absent means default/unprioritised. Transports without priority support ignore it.
554
+ */
555
+ priority?: number | undefined;
373
556
  attempt: number;
374
557
  }
375
558
  interface StepResult {
@@ -411,6 +594,12 @@ interface WorkflowTask {
411
594
  /** Id of the transport this task was dispatched on (pool failover) — see {@link NamedTransport}. */
412
595
  transport?: string;
413
596
  traceparent?: string;
597
+ /**
598
+ * Admission priority carried to the broker (BullMQ job `priority`) so an urgent child workflow can
599
+ * jump ahead of already-enqueued lower-priority ones at the worker. Higher wins; absent means
600
+ * default/unprioritised. Transports without priority support ignore it.
601
+ */
602
+ priority?: number | undefined;
414
603
  attempt: number;
415
604
  }
416
605
  /** One resolved durable op in a run's history — a superset of a completed {@link StepCheckpoint}. */
@@ -616,6 +805,21 @@ type ControlMessage = {
616
805
  runId: string;
617
806
  });
618
807
  type BackoffStrategy = 'fixed' | 'exp';
808
+ /**
809
+ * Options for `ctx.child` / `ctx.startChild`. A bare string passed instead is shorthand for
810
+ * `{ childId }`, so the existing `ctx.child(ref, input, 'my-id')` form keeps working.
811
+ */
812
+ interface ChildCallOptions {
813
+ /** Deterministic child run id; defaults to one derived from the parent run id + call position. */
814
+ childId?: string | undefined;
815
+ /**
816
+ * Dispatch priority for a REMOTE child workflow — stamped on the child run and carried onto every
817
+ * {@link WorkflowTask} dispatched to advance it, so an urgent child can jump ahead of enqueued
818
+ * lower-priority ones at the worker. Higher wins; absent = unprioritised. Ignored for an in-process
819
+ * (TS class) child, which runs in the engine and never hits a broker queue.
820
+ */
821
+ priority?: number | undefined;
822
+ }
619
823
  interface StepOptions {
620
824
  /** Max attempts before the step (and run) fails. */
621
825
  retries?: number;
@@ -773,8 +977,8 @@ interface WorkflowCtx {
773
977
  * Pass the child's **class** (`ctx.child(ShippingWorkflow, input)`) for a typed input + result; pass
774
978
  * a **string** name for a cross-runtime child (e.g. a Python workflow) where there's no class.
775
979
  */
776
- child<C extends WorkflowClass>(workflow: C, input: WorkflowInputOf<C>, childId?: string): Promise<WorkflowOutputOf<C>>;
777
- child<TOutput>(workflow: string, input: unknown, childId?: string): Promise<TOutput>;
980
+ child<C extends WorkflowClass>(workflow: C, input: WorkflowInputOf<C>, options?: string | ChildCallOptions): Promise<WorkflowOutputOf<C>>;
981
+ child<TOutput>(workflow: string, input: unknown, options?: string | ChildCallOptions): Promise<TOutput>;
778
982
  /**
779
983
  * Start a child workflow **fire-and-forget**: dispatches it once (checkpointed, replay-safe) and
780
984
  * returns its run id immediately — the parent keeps running instead of suspending. Use it to kick
@@ -782,8 +986,8 @@ interface WorkflowCtx {
782
986
  * the ids, then later `await ctx.child(...)` each with the same id to join (the start is idempotent
783
987
  * by id, so the child runs exactly once). Class or string ref, like {@link child}.
784
988
  */
785
- startChild<C extends WorkflowClass>(workflow: C, input: WorkflowInputOf<C>, childId?: string): Promise<string>;
786
- startChild(workflow: string, input: unknown, childId?: string): Promise<string>;
989
+ startChild<C extends WorkflowClass>(workflow: C, input: WorkflowInputOf<C>, options?: string | ChildCallOptions): Promise<string>;
990
+ startChild(workflow: string, input: unknown, options?: string | ChildCallOptions): Promise<string>;
787
991
  /**
788
992
  * Pause the run at this point until a human resumes it from the dashboard (or
789
993
  * `engine.continue(runId)`). Records a visible `pending` checkpoint so the breakpoint shows up
@@ -987,113 +1191,6 @@ declare class EventAccumulators {
987
1191
  private batch;
988
1192
  }
989
1193
 
990
- /**
991
- * Flow control for remote steps. A queue caps how much work `ctx.call(step, input, { queue })`
992
- * admits at once — a concurrency limit and/or a fixed-window rate limit. When a call can't be
993
- * admitted it doesn't dispatch: the run re-suspends with a near-future wake time, and the timer
994
- * poller re-tries admission later — so flow control is durable (survives crashes) without holding
995
- * the run in memory.
996
- *
997
- * Accounting is per engine instance (the DBOS `workerConcurrency` tier): correct for the common
998
- * single-orchestrator deployment. Global, cross-instance limits would need a durable counter in the
999
- * store — a deliberate follow-up, not built here.
1000
- *
1001
- * Beyond raw caps, the controller can admit contended slots by PRIORITY and by per-KEY FAIRNESS
1002
- * (round-robin), so a single noisy key/tenant can't monopolize the concurrency budget. This is the
1003
- * DISPATCH/admission layer — it never touches workflow replay (engine positional logic). Default
1004
- * behaviour is unchanged FIFO when neither priority nor fairness is configured/passed.
1005
- *
1006
- * Design (vs Temporal/Inngest/DBOS): Temporal task queues are FIFO with separate priority levels;
1007
- * Inngest concurrency keys + throttle give per-key fairness; DBOS queues are FIFO with a
1008
- * `workerConcurrency` cap. We fold both knobs into the same in-process admission gate: an optional
1009
- * per-call `priority` (higher first) and an optional queue-level `fairness: 'key'` (round-robin by
1010
- * the per-call `key`). Because blocked calls re-suspend durably and retry independently (there is no
1011
- * resident FIFO list to reorder), the controller remembers the set of WAITERS that asked-and-were-
1012
- * blocked, and on a later retry only admits the one that is rightful-next under the policy.
1013
- */
1014
- interface QueueConfig {
1015
- /** Queue name, referenced by `ctx.call(step, input, { queue: name })`. */
1016
- name: string;
1017
- /** Max steps in flight at once for this queue (this instance). Omit for unlimited. */
1018
- concurrency?: number;
1019
- /** Fixed-window rate limit: at most `limit` admissions per `periodMs`. Omit for unlimited. */
1020
- rateLimit?: {
1021
- limit: number;
1022
- periodMs: number;
1023
- };
1024
- /** Delay (ms) before a concurrency-blocked call re-checks for a free slot. Default 1000. */
1025
- retryMs?: number;
1026
- /**
1027
- * Fairness policy for a contended concurrency slot. `'key'` round-robins across distinct per-call
1028
- * keys (the least-recently-served key wins the next slot), so one key can't monopolize the budget.
1029
- * Omit (or `'fifo'`) for plain first-come ordering. Priority always wins over fairness; fairness
1030
- * only breaks ties within the same priority.
1031
- */
1032
- fairness?: 'fifo' | 'key';
1033
- }
1034
- /**
1035
- * Per-call admission hints. All optional — omitting them keeps the legacy FIFO behaviour.
1036
- * - `priority`: higher is admitted first (default 0).
1037
- * - `key`: the fairness bucket (e.g. a tenant id) for `fairness: 'key'`.
1038
- * - `waiterId`: a stable id for THIS call across its retries (the engine passes the stepId), so the
1039
- * controller can track it as one waiter instead of counting each retry as a new contender.
1040
- */
1041
- interface AdmissionItem {
1042
- priority?: number | undefined;
1043
- key?: string | undefined;
1044
- waiterId?: string | undefined;
1045
- }
1046
- /** Admission decision: either admitted, or blocked until `retryAt` (epoch ms). */
1047
- type Admission = {
1048
- ok: true;
1049
- } | {
1050
- ok: false;
1051
- retryAt: number;
1052
- };
1053
- /** In-process admission controller for one {@link QueueConfig}. */
1054
- declare class QueueController {
1055
- readonly config: QueueConfig;
1056
- private readonly clock;
1057
- private inFlight;
1058
- private windowStart;
1059
- private windowCount;
1060
- /** Registered waiters by id (only used when priority/fairness ordering is in play). */
1061
- private readonly waiters;
1062
- /** Monotonic counter stamping each newly-seen waiter for a stable FIFO tiebreak. */
1063
- private waiterSeq;
1064
- /** Per-key "last served" order for round-robin fairness — higher = more recently served. */
1065
- private readonly keyServed;
1066
- private servedTick;
1067
- constructor(config: QueueConfig, clock: () => number);
1068
- /** Whether this controller does any priority/fairness ordering (else it's a plain FIFO gate). */
1069
- private get ordered();
1070
- /**
1071
- * Try to admit one unit of work. On `ok`, the caller has taken a concurrency slot and must call
1072
- * {@link release} when the step settles. On block, `retryAt` is when admission may next succeed.
1073
- *
1074
- * `item` carries optional priority/fairness hints; omitting it is the legacy FIFO path. When
1075
- * ordering is configured (or a priority is given), a blocked call REGISTERS as a waiter and is only
1076
- * admitted on a later retry once it is the rightful next under (priority desc, fairness, FIFO).
1077
- */
1078
- tryAdmit(item?: AdmissionItem): Admission;
1079
- /** Release a concurrency slot taken by a successful {@link tryAdmit}. */
1080
- release(): void;
1081
- /** Track (or refresh) a blocked call as a waiter; its FIFO seq is stamped only on first sight. */
1082
- private register;
1083
- /**
1084
- * Is the calling item the best candidate among all registered waiters? Ordering, best first:
1085
- * 1. higher `priority`;
1086
- * 2. fairness: with `fairness: 'key'`, the least-recently-served key wins (round-robin);
1087
- * 3. earliest registration (FIFO) — stable tiebreak.
1088
- * A bare item (no waiterId) is compared on its hints directly.
1089
- */
1090
- private isNext;
1091
- /** Strict "a should be admitted before b" comparison under the active policy. */
1092
- private better;
1093
- /** Last-served tick for a key (0 = never served, so an unseen key is admitted before a busy one). */
1094
- private servedOf;
1095
- }
1096
-
1097
1194
  type WorkflowFn = (ctx: WorkflowCtx, input: unknown) => Promise<unknown>;
1098
1195
  /** Options for {@link WorkflowEngine.start}. */
1099
1196
  interface StartOptions {
@@ -1101,6 +1198,11 @@ interface StartOptions {
1101
1198
  tags?: string[] | undefined;
1102
1199
  /** Typed, queryable run data stamped on the run (e.g. `{ amount: 200, tier: 'pro' }`). */
1103
1200
  searchAttributes?: SearchAttributes | undefined;
1201
+ /**
1202
+ * Dispatch priority for a remote run — stamped on the run and carried onto every {@link WorkflowTask}
1203
+ * the engine dispatches to advance it. Higher wins; absent = unprioritised. See {@link WorkflowRun.priority}.
1204
+ */
1205
+ priority?: number | undefined;
1104
1206
  }
1105
1207
  /**
1106
1208
  * Serialize runs of a workflow that share a key — a durable, FIFO mutex (e.g. one pipeline per base).
@@ -1139,6 +1241,12 @@ interface WorkflowEngineDeps {
1139
1241
  controlPlane?: ControlPlane | undefined;
1140
1242
  /** Epoch-ms clock; injectable for tests. Defaults to `Date.now`. */
1141
1243
  clock?: (() => number) | undefined;
1244
+ /**
1245
+ * Flow-control admission backend for `ctx.call(step, input, { queue })`. Defaults to an in-process
1246
+ * {@link InMemoryAdmissionBackend} (per-instance caps). Inject a store/Redis-backed backend to make
1247
+ * concurrency / rate-limit / ordering GLOBAL across engine replicas.
1248
+ */
1249
+ admission?: AdmissionBackend | undefined;
1142
1250
  /** Unique id for this engine instance, used for recovery leases. Defaults to a random id. */
1143
1251
  instanceId?: string | undefined;
1144
1252
  /** Recovery lease duration in ms — how long this instance owns a run it picked up. Default 30s. */
@@ -1261,8 +1369,10 @@ declare class WorkflowEngine {
1261
1369
  private readonly updateValidators;
1262
1370
  /** Runs being cancelled WITH saga compensation — see `cancel({ compensate: true })`. */
1263
1371
  private readonly cancelRequested;
1264
- /** Flow-control queues for remote steps, keyed by name (see {@link registerQueue}). */
1265
- private readonly queues;
1372
+ /** Flow-control admission backend for remote steps (see {@link registerQueue}). */
1373
+ private readonly admission;
1374
+ /** Runs on THIS instance blocked on admission, by queue — woken early on a freed-slot signal. */
1375
+ private readonly queueWaiters;
1266
1376
  /** Which queue a dispatched step took a slot from, by stepId — so the result can release it. */
1267
1377
  private readonly stepQueue;
1268
1378
  /** Executions currently in flight, so a graceful shutdown can wait for them to settle. */
@@ -1501,6 +1611,17 @@ declare class WorkflowEngine {
1501
1611
  cancelWhere(filter: Omit<RunQuery, 'limit' | 'offset'>, opts?: {
1502
1612
  compensate?: boolean;
1503
1613
  }): Promise<RunResult[]>;
1614
+ /**
1615
+ * Hard-delete a run and its entire subtree. Unlike {@link cancel} (which marks a run `cancelled`
1616
+ * but keeps it as history), delete REMOVES the run and all its rows (checkpoints, signal waiters,
1617
+ * search-attribute rows) — it vanishes from {@link getRun} and {@link listRuns}. Use it to purge a
1618
+ * finished run whose data is being deleted (e.g. a pipeline run whose result rows are cleared).
1619
+ *
1620
+ * Cascades depth-first via {@link getRunChildren} so children are gone before the parent (no
1621
+ * orphaned child runs). Prefer {@link cancel} first for a live run — deleting one mid-flight orphans
1622
+ * its worker (its next checkpoint write fails). Returns the number of runs deleted (0 if absent).
1623
+ */
1624
+ deleteRun(runId: string): Promise<number>;
1504
1625
  /**
1505
1626
  * Cascade cancellation to a run's children — both awaited (`ctx.child`, found via its live
1506
1627
  * `child:<id>` waiter) and fire-and-forget (`ctx.startChild`, found via its `spawn:<id>`
@@ -1619,6 +1740,12 @@ declare class WorkflowEngine {
1619
1740
  * A no-op if the checkpoint isn't `pending` (a duplicate or late delivery).
1620
1741
  */
1621
1742
  private completeRemoteResult;
1743
+ /**
1744
+ * A slot freed on `queue` (a fleet-wide signal): resume this instance's runs blocked on it so they
1745
+ * re-contend now instead of at their retry tick. Snapshot-and-clear — a run still blocked after the
1746
+ * retry re-registers itself, and one that's gone (cancelled/admitted) is dropped. Best-effort.
1747
+ */
1748
+ private wakeQueueWaiters;
1622
1749
  /** Release the flow-control slot a dispatched step held (if any), by its stepId. */
1623
1750
  private releaseQueueSlot;
1624
1751
  /** In-memory await path for a remote step with a liveness `timeoutMs` (re-dispatch on timeout). */
@@ -1789,6 +1916,7 @@ declare class CodecStateStore implements StateStore {
1789
1916
  createRun(run: WorkflowRun): Promise<void>;
1790
1917
  updateRun(runId: string, patch: Partial<WorkflowRun>): Promise<void>;
1791
1918
  getRun(runId: string): Promise<WorkflowRun | null>;
1919
+ deleteRun(runId: string): Promise<void>;
1792
1920
  getCheckpoint(runId: string, seq: number): Promise<StepCheckpoint | null>;
1793
1921
  saveCheckpoint(checkpoint: StepCheckpoint): Promise<void>;
1794
1922
  transaction<T>(work: (tx: {
@@ -2025,6 +2153,7 @@ declare class InMemoryStateStore implements StateStore {
2025
2153
  createRun(run: WorkflowRun): Promise<void>;
2026
2154
  updateRun(runId: string, patch: Partial<WorkflowRun>): Promise<void>;
2027
2155
  getRun(runId: string): Promise<WorkflowRun | null>;
2156
+ deleteRun(runId: string): Promise<void>;
2028
2157
  getCheckpoint(runId: string, seq: number): Promise<StepCheckpoint | null>;
2029
2158
  saveCheckpoint(checkpoint: StepCheckpoint): Promise<void>;
2030
2159
  listIncompleteRuns(): Promise<WorkflowRun[]>;
@@ -2080,4 +2209,4 @@ declare class InMemoryTransport implements Transport, ControlPlane {
2080
2209
  onControl(handler: (msg: ControlMessage) => void): void;
2081
2210
  }
2082
2211
 
2083
- export { type AccumulatorHost, type Admission, type AdmissionItem, type AttributeFilter, type AttributeOp, type BackoffStrategy, CodecStateStore, ContinueAsNew, type ControlMessage, type ControlPlane, DURABLE_OPTIONS, 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, 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, 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, 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 };
2212
+ 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, 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, 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, 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 };