@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.cjs +242 -143
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +243 -114
- package/dist/index.d.ts +243 -114
- package/dist/index.js +241 -143
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
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>,
|
|
777
|
-
child<TOutput>(workflow: string, input: unknown,
|
|
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>,
|
|
786
|
-
startChild(workflow: string, input: unknown,
|
|
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
|
|
1265
|
-
private readonly
|
|
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 };
|