@dudousxd/nestjs-durable-core 0.14.0 → 0.17.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/codec-state-store.d.ts +8 -0
- package/dist/codec-state-store.d.ts.map +1 -1
- package/dist/codec-state-store.js +14 -0
- package/dist/codec-state-store.js.map +1 -1
- package/dist/engine.d.ts +84 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +240 -3
- package/dist/engine.js.map +1 -1
- package/dist/interfaces.d.ts +52 -1
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/testing/in-memory-state-store.d.ts +9 -0
- package/dist/testing/in-memory-state-store.d.ts.map +1 -1
- package/dist/testing/in-memory-state-store.js +20 -0
- package/dist/testing/in-memory-state-store.js.map +1 -1
- package/dist/workflow-ctx.d.ts +2 -0
- package/dist/workflow-ctx.d.ts.map +1 -1
- package/dist/workflow-ctx.js +67 -0
- package/dist/workflow-ctx.js.map +1 -1
- package/package.json +1 -1
|
@@ -34,6 +34,10 @@ export declare class CodecStateStore implements StateStore {
|
|
|
34
34
|
getRun(runId: string): Promise<WorkflowRun | null>;
|
|
35
35
|
getCheckpoint(runId: string, seq: number): Promise<StepCheckpoint | null>;
|
|
36
36
|
saveCheckpoint(checkpoint: StepCheckpoint): Promise<void>;
|
|
37
|
+
transaction<T>(work: (tx: {
|
|
38
|
+
raw: unknown;
|
|
39
|
+
saveCheckpoint: (cp: StepCheckpoint) => Promise<void>;
|
|
40
|
+
}) => Promise<T>): Promise<T>;
|
|
37
41
|
listIncompleteRuns(): Promise<WorkflowRun[]>;
|
|
38
42
|
listPendingRuns(limit: number): Promise<WorkflowRun[]>;
|
|
39
43
|
listDueTimers(nowMs: number): Promise<WorkflowRun[]>;
|
|
@@ -43,6 +47,10 @@ export declare class CodecStateStore implements StateStore {
|
|
|
43
47
|
putSignalWaiter(waiter: SignalWaiter): Promise<void>;
|
|
44
48
|
takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
|
|
45
49
|
listSignalWaiters(prefix: string): Promise<SignalWaiter[]>;
|
|
50
|
+
bufferSignal(token: string, payload: unknown): Promise<void>;
|
|
51
|
+
takeBufferedSignal(token: string): Promise<{
|
|
52
|
+
payload: unknown;
|
|
53
|
+
} | null>;
|
|
46
54
|
listRuns(query: RunQuery): Promise<WorkflowRun[]>;
|
|
47
55
|
listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
|
|
48
56
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codec-state-store.d.ts","sourceRoot":"","sources":["../src/codec-state-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEpG;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IAChC,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;CACjC;AAED;;;;;;;;;GASG;AACH,qBAAa,eAAgB,YAAW,UAAU;IAE9C,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK;gBADL,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,YAAY;IAGtC,OAAO,CAAC,GAAG;IAGX,OAAO,CAAC,GAAG;IAIX,OAAO,CAAC,MAAM;IAGd,OAAO,CAAC,MAAM;IAGd,OAAO,CAAC,KAAK;IAGb,OAAO,CAAC,KAAK;IAIb,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B,SAAS,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAG1C,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAM9D,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAGlD,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAG/E,cAAc,CAAC,UAAU,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"codec-state-store.d.ts","sourceRoot":"","sources":["../src/codec-state-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEpG;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IAChC,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;CACjC;AAED;;;;;;;;;GASG;AACH,qBAAa,eAAgB,YAAW,UAAU;IAE9C,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK;gBADL,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,YAAY;IAGtC,OAAO,CAAC,GAAG;IAGX,OAAO,CAAC,GAAG;IAIX,OAAO,CAAC,MAAM;IAGd,OAAO,CAAC,MAAM;IAGd,OAAO,CAAC,KAAK;IAGb,OAAO,CAAC,KAAK;IAIb,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B,SAAS,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAG1C,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAM9D,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAGlD,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAG/E,cAAc,CAAC,UAAU,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAGzD,WAAW,CAAC,CAAC,EACX,IAAI,EAAE,CAAC,EAAE,EAAE;QACT,GAAG,EAAE,OAAO,CAAC;QACb,cAAc,EAAE,CAAC,EAAE,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACvD,KAAK,OAAO,CAAC,CAAC,CAAC,GACf,OAAO,CAAC,CAAC,CAAC;IASP,kBAAkB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAG5C,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAGtD,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAG1D,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAG/F,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAG5C,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAGlF,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAGpD,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAG7D,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAG1D,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAGtD,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAIvE,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAGjD,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;CAGhE"}
|
|
@@ -59,6 +59,13 @@ class CodecStateStore {
|
|
|
59
59
|
saveCheckpoint(checkpoint) {
|
|
60
60
|
return this.inner.saveCheckpoint(this.encCp(checkpoint));
|
|
61
61
|
}
|
|
62
|
+
transaction(work) {
|
|
63
|
+
if (!this.inner.transaction) {
|
|
64
|
+
throw new Error('the wrapped store does not support transactions');
|
|
65
|
+
}
|
|
66
|
+
// Encode the checkpoint inside the tx, so payloads stay encoded at rest like the normal path.
|
|
67
|
+
return this.inner.transaction((tx) => work({ raw: tx.raw, saveCheckpoint: (cp) => tx.saveCheckpoint(this.encCp(cp)) }));
|
|
68
|
+
}
|
|
62
69
|
async listIncompleteRuns() {
|
|
63
70
|
return (await this.inner.listIncompleteRuns()).map((r) => this.decRun(r));
|
|
64
71
|
}
|
|
@@ -86,6 +93,13 @@ class CodecStateStore {
|
|
|
86
93
|
listSignalWaiters(prefix) {
|
|
87
94
|
return this.inner.listSignalWaiters(prefix);
|
|
88
95
|
}
|
|
96
|
+
bufferSignal(token, payload) {
|
|
97
|
+
return this.inner.bufferSignal(token, this.enc(payload));
|
|
98
|
+
}
|
|
99
|
+
async takeBufferedSignal(token) {
|
|
100
|
+
const buffered = await this.inner.takeBufferedSignal(token);
|
|
101
|
+
return buffered && { payload: this.dec(buffered.payload) };
|
|
102
|
+
}
|
|
89
103
|
async listRuns(query) {
|
|
90
104
|
return (await this.inner.listRuns(query)).map((r) => this.decRun(r));
|
|
91
105
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codec-state-store.js","sourceRoot":"","sources":["../src/codec-state-store.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"codec-state-store.js","sourceRoot":"","sources":["../src/codec-state-store.ts"],"names":[],"mappings":"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"}
|
package/dist/engine.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ControlPlane, EngineListener, NamedTransport, RunDispatcher, RunResult, SearchAttributes, StateStore, StepError, StepInterceptor, Transport, UpdateResult, UpdateValidator, WorkflowCtx, WorkflowRun } from './interfaces';
|
|
2
2
|
import { type QueueConfig } from './queue';
|
|
3
|
-
import { type WorkflowClass, type WorkflowInputOf } from './workflow-ref';
|
|
3
|
+
import { type WorkflowClass, type WorkflowInputOf, type WorkflowRef } from './workflow-ref';
|
|
4
4
|
type WorkflowFn = (ctx: WorkflowCtx, input: unknown) => Promise<unknown>;
|
|
5
5
|
/** Options for {@link WorkflowEngine.start}. */
|
|
6
6
|
export interface StartOptions {
|
|
@@ -21,6 +21,24 @@ export interface SingletonConfig {
|
|
|
21
21
|
/** Max concurrent runs sharing the key. Default 1 (a mutex). */
|
|
22
22
|
limit?: number;
|
|
23
23
|
}
|
|
24
|
+
/** How `onEvent` triggers are coalesced into fewer runs (see `@Workflow({ debounce | batch })`). */
|
|
25
|
+
export type EventBatchConfig = {
|
|
26
|
+
mode: 'debounce';
|
|
27
|
+
windowMs: number;
|
|
28
|
+
} | {
|
|
29
|
+
mode: 'batch';
|
|
30
|
+
maxSize: number;
|
|
31
|
+
windowMs: number;
|
|
32
|
+
};
|
|
33
|
+
/** One operation on a durable entity: mutate `state` in place and/or return a result. */
|
|
34
|
+
export type EntityHandler<S = unknown> = (state: S, arg: unknown) => unknown | Promise<unknown>;
|
|
35
|
+
/** A durable keyed entity (a virtual object): per-key serialized handlers over durable state. */
|
|
36
|
+
export interface EntityConfig<S = unknown> {
|
|
37
|
+
/** Build the initial state for a fresh key. */
|
|
38
|
+
initialState: () => S;
|
|
39
|
+
/** Operation handlers, keyed by op name. Each runs serially per key, exactly once. */
|
|
40
|
+
handlers: Record<string, EntityHandler<S>>;
|
|
41
|
+
}
|
|
24
42
|
export interface WorkflowEngineDeps {
|
|
25
43
|
store: StateStore;
|
|
26
44
|
/** A single task transport. Shorthand for a one-entry `transports` pool (id `default`). */
|
|
@@ -100,6 +118,8 @@ export declare class WorkflowEngine {
|
|
|
100
118
|
private readonly latest;
|
|
101
119
|
/** Event name → workflow names started when that event is published (see `onEvent`). */
|
|
102
120
|
private readonly eventTriggers;
|
|
121
|
+
/** Registered durable entities, keyed by entity name (see `registerEntity`). */
|
|
122
|
+
private readonly entities;
|
|
103
123
|
/** In-flight remote steps awaiting a worker result, keyed by stepId. */
|
|
104
124
|
private readonly pending;
|
|
105
125
|
/** Per-step "reset the liveness timer" callbacks, called when a heartbeat arrives. */
|
|
@@ -109,6 +129,8 @@ export declare class WorkflowEngine {
|
|
|
109
129
|
private readonly interceptors;
|
|
110
130
|
/** Callbacks notified (on any instance) when a run is cancelled — for cooperative cancellation. */
|
|
111
131
|
private readonly cancelListeners;
|
|
132
|
+
/** Callbacks notified when a run is enqueued elsewhere — for low-latency cross-pod dispatch. */
|
|
133
|
+
private readonly enqueuedListeners;
|
|
112
134
|
/** Notified when a run is dead-lettered (moved to `dead`) — a hook for a DLQ handler. */
|
|
113
135
|
private readonly deadListeners;
|
|
114
136
|
/** Validators gating `engine.update`, keyed by `<workflow>:<updateName>`. */
|
|
@@ -123,6 +145,37 @@ export declare class WorkflowEngine {
|
|
|
123
145
|
private readonly inflight;
|
|
124
146
|
private draining;
|
|
125
147
|
constructor(deps: WorkflowEngineDeps);
|
|
148
|
+
/**
|
|
149
|
+
* Built-in workflows that coalesce `onEvent` triggers (see `@Workflow({ debounce | batch })`). Each
|
|
150
|
+
* is a long-lived per-target accumulator fed by `signalWithStart`; it collects payloads then starts
|
|
151
|
+
* the target once, and `continueAsNew`s to re-arm for the next burst. Signal buffering keeps the
|
|
152
|
+
* re-arm race-free (an event arriving between firing and re-parking is buffered, not lost).
|
|
153
|
+
*/
|
|
154
|
+
private registerEventAccumulators;
|
|
155
|
+
/**
|
|
156
|
+
* Register a **durable entity** (a virtual object): a keyed actor whose `handlers` run **serialized
|
|
157
|
+
* per key** over **durable state**, exactly once. Drive it with `signalEntity` (fire) /
|
|
158
|
+
* `ctx.callEntity` (call + await result) and read its state with `getEntityState`. Each key is one
|
|
159
|
+
* long-lived run (`entity:<name>:<key>`) that processes ops in order.
|
|
160
|
+
*/
|
|
161
|
+
registerEntity<S>(name: string, config: EntityConfig<S>): void;
|
|
162
|
+
/** Send an operation to an entity (fire-and-forget). Ordered + exactly-once per key. */
|
|
163
|
+
signalEntity(name: string, key: string, op: string, arg?: unknown): Promise<void>;
|
|
164
|
+
/** Read an entity's current durable state (published after each op), or undefined if it has none yet. */
|
|
165
|
+
getEntityState<S = unknown>(name: string, key: string): Promise<S | undefined>;
|
|
166
|
+
/** Deliver an op to an entity's runner, starting it on first contact. `reply` (a signal token) gets
|
|
167
|
+
* the handler's result, for `ctx.callEntity`. */
|
|
168
|
+
private dispatchEntityOp;
|
|
169
|
+
/** The built-in entity runner: one long-lived run per key that processes ops serially over state. */
|
|
170
|
+
private registerEntityRunner;
|
|
171
|
+
/** Route an `onEvent` payload into its target's debounce/batch accumulator. */
|
|
172
|
+
private routeBatchedEvent;
|
|
173
|
+
/**
|
|
174
|
+
* Be notified when a run is enqueued on ANOTHER instance (via the control plane), so a worker can
|
|
175
|
+
* pick it up at once — e.g. `engine.onEnqueued((runId) => engine.runOne(runId))`. Returns an
|
|
176
|
+
* unsubscribe function. Only wire this on instances that should execute runs (workers).
|
|
177
|
+
*/
|
|
178
|
+
onEnqueued(listener: (runId: string) => void): () => void;
|
|
126
179
|
/** Fire cooperative-cancellation listeners for `runId` (a worker bridge aborts in-flight work). */
|
|
127
180
|
private notifyCancelled;
|
|
128
181
|
/**
|
|
@@ -136,6 +189,7 @@ export declare class WorkflowEngine {
|
|
|
136
189
|
executionTimeout?: string | number;
|
|
137
190
|
validateInput?: (input: unknown) => void | Promise<void>;
|
|
138
191
|
onEvent?: string[];
|
|
192
|
+
eventBatch?: EventBatchConfig;
|
|
139
193
|
}): void;
|
|
140
194
|
/**
|
|
141
195
|
* Register a flow-control queue referenced by `ctx.call(step, input, { queue })`. Caps concurrent
|
|
@@ -225,6 +279,14 @@ export declare class WorkflowEngine {
|
|
|
225
279
|
* the HTTP request on workflow execution.
|
|
226
280
|
*/
|
|
227
281
|
requeue(runId: string): Promise<RunResult | null>;
|
|
282
|
+
/**
|
|
283
|
+
* **Fix-and-replay**: re-run a run (typically a `dead`/`failed` one) with a corrected `input`, as a
|
|
284
|
+
* fresh run with clean history. It's a NEW run — `newRunId` defaults to `<runId>~retry~<uuid>` — so
|
|
285
|
+
* the original stays inspectable. Returns the new run's id, or null if `runId` is unknown.
|
|
286
|
+
*/
|
|
287
|
+
retryWithInput(runId: string, input: unknown, newRunId?: string): Promise<{
|
|
288
|
+
runId: string;
|
|
289
|
+
} | null>;
|
|
228
290
|
/**
|
|
229
291
|
* Pick up and execute every `pending` run — the poll-based side of dispatch for a worker pod with
|
|
230
292
|
* no broker. Runs enqueued by other pods (or by a caller using a no-op dispatcher) sit `pending`
|
|
@@ -261,6 +323,20 @@ export declare class WorkflowEngine {
|
|
|
261
323
|
id?: string;
|
|
262
324
|
}): Promise<number>;
|
|
263
325
|
signal(token: string, payload: unknown): Promise<RunResult | null>;
|
|
326
|
+
/**
|
|
327
|
+
* Ensure a run exists for `runId`, then deliver a signal to it — atomically race-free thanks to
|
|
328
|
+
* signal buffering: if the run is new (or busy / not yet waiting), the signal is buffered and
|
|
329
|
+
* consumed when it reaches `waitForSignal(token)`. The canonical **durable-entity / accumulator**
|
|
330
|
+
* pattern: one long-lived run per key (the `runId`) that loops on `waitForSignal`, fed events by
|
|
331
|
+
* many `signalWithStart` calls. `start` is idempotent by `runId`, so concurrent callers converge on
|
|
332
|
+
* one run. (Use a per-run `token`, e.g. derived from `runId`, so the signal targets this entity.)
|
|
333
|
+
*/
|
|
334
|
+
signalWithStart(workflow: WorkflowRef, input: unknown, runId: string, signal: {
|
|
335
|
+
token: string;
|
|
336
|
+
payload?: unknown;
|
|
337
|
+
}, opts?: StartOptions): Promise<{
|
|
338
|
+
runId: string;
|
|
339
|
+
}>;
|
|
264
340
|
/**
|
|
265
341
|
* Report the result of a `ctx.task(name, …)` back to its run (async completion). The external
|
|
266
342
|
* worker that the task dispatched to calls this when done; the run resumes with `result`. Returns
|
|
@@ -284,6 +360,13 @@ export declare class WorkflowEngine {
|
|
|
284
360
|
cancel(runId: string, opts?: {
|
|
285
361
|
compensate?: boolean;
|
|
286
362
|
}): Promise<RunResult | null>;
|
|
363
|
+
/**
|
|
364
|
+
* Cascade cancellation to a run's children — both awaited (`ctx.child`, found via its live
|
|
365
|
+
* `child:<id>` waiter) and fire-and-forget (`ctx.startChild`, found via its `spawn:<id>`
|
|
366
|
+
* checkpoint). Recursive, so a whole subtree is cancelled; the terminal guard in `cancel` stops it
|
|
367
|
+
* at finished / already-cancelled runs (no loops, no re-cancel).
|
|
368
|
+
*/
|
|
369
|
+
private cancelChildren;
|
|
287
370
|
/** Checkpoint a finished step and announce it — the two things that must always happen together. */
|
|
288
371
|
private completeStep;
|
|
289
372
|
/** Checkpoint a step that failed terminally, so the failure point is visible (not just the run). */
|
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|
package/dist/engine.js
CHANGED
|
@@ -65,6 +65,8 @@ class WorkflowEngine {
|
|
|
65
65
|
latest = new Map();
|
|
66
66
|
/** Event name → workflow names started when that event is published (see `onEvent`). */
|
|
67
67
|
eventTriggers = new Map();
|
|
68
|
+
/** Registered durable entities, keyed by entity name (see `registerEntity`). */
|
|
69
|
+
entities = new Map();
|
|
68
70
|
/** In-flight remote steps awaiting a worker result, keyed by stepId. */
|
|
69
71
|
pending = new Map();
|
|
70
72
|
/** Per-step "reset the liveness timer" callbacks, called when a heartbeat arrives. */
|
|
@@ -74,6 +76,8 @@ class WorkflowEngine {
|
|
|
74
76
|
interceptors = [];
|
|
75
77
|
/** Callbacks notified (on any instance) when a run is cancelled — for cooperative cancellation. */
|
|
76
78
|
cancelListeners = new Set();
|
|
79
|
+
/** Callbacks notified when a run is enqueued elsewhere — for low-latency cross-pod dispatch. */
|
|
80
|
+
enqueuedListeners = new Set();
|
|
77
81
|
/** Notified when a run is dead-lettered (moved to `dead`) — a hook for a DLQ handler. */
|
|
78
82
|
deadListeners = new Set();
|
|
79
83
|
/** Validators gating `engine.update`, keyed by `<workflow>:<updateName>`. */
|
|
@@ -142,8 +146,142 @@ class WorkflowEngine {
|
|
|
142
146
|
else if (msg.kind === 'cancel') {
|
|
143
147
|
this.notifyCancelled(msg.runId);
|
|
144
148
|
}
|
|
149
|
+
else if (msg.kind === 'enqueued') {
|
|
150
|
+
// A run was enqueued on another instance — let worker subscribers pick it up immediately
|
|
151
|
+
// instead of waiting for the next poll. (Self-broadcasts are already filtered above.)
|
|
152
|
+
for (const fn of this.enqueuedListeners) {
|
|
153
|
+
try {
|
|
154
|
+
fn(msg.runId);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
/* an enqueue listener must not break the engine */
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
this.registerEventAccumulators();
|
|
163
|
+
this.registerEntityRunner();
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Built-in workflows that coalesce `onEvent` triggers (see `@Workflow({ debounce | batch })`). Each
|
|
167
|
+
* is a long-lived per-target accumulator fed by `signalWithStart`; it collects payloads then starts
|
|
168
|
+
* the target once, and `continueAsNew`s to re-arm for the next burst. Signal buffering keeps the
|
|
169
|
+
* re-arm race-free (an event arriving between firing and re-parking is buffered, not lost).
|
|
170
|
+
*/
|
|
171
|
+
registerEventAccumulators() {
|
|
172
|
+
const tokenFor = (target) => `__evtacc__:${target}`;
|
|
173
|
+
this.register('__evt_debounce', '1', async (ctx, input) => {
|
|
174
|
+
const { target, windowMs } = input;
|
|
175
|
+
const token = tokenFor(target);
|
|
176
|
+
let last = await ctx.waitForSignal(token); // park for the first event of a burst
|
|
177
|
+
for (;;) {
|
|
178
|
+
try {
|
|
179
|
+
last = await ctx.waitForSignal(token, { timeoutMs: windowMs }); // extend on each event
|
|
180
|
+
}
|
|
181
|
+
catch (e) {
|
|
182
|
+
if (e instanceof errors_1.SignalTimeoutError)
|
|
183
|
+
break; // quiet for `windowMs` → fire
|
|
184
|
+
throw e; // WorkflowSuspended / ContinueAsNew etc. must propagate
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
await ctx.startChild(target, last);
|
|
188
|
+
return ctx.continueAsNew(input); // re-arm
|
|
189
|
+
});
|
|
190
|
+
this.register('__evt_batch', '1', async (ctx, input) => {
|
|
191
|
+
const { target, maxSize, windowMs } = input;
|
|
192
|
+
const token = tokenFor(target);
|
|
193
|
+
const events = [await ctx.waitForSignal(token)]; // park for the first
|
|
194
|
+
const deadline = (await ctx.now()) + windowMs;
|
|
195
|
+
while (events.length < maxSize) {
|
|
196
|
+
const remaining = deadline - (await ctx.now());
|
|
197
|
+
if (remaining <= 0)
|
|
198
|
+
break;
|
|
199
|
+
try {
|
|
200
|
+
events.push(await ctx.waitForSignal(token, { timeoutMs: remaining }));
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
if (e instanceof errors_1.SignalTimeoutError)
|
|
204
|
+
break; // window elapsed → fire what we have
|
|
205
|
+
throw e; // WorkflowSuspended / ContinueAsNew etc. must propagate
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
await ctx.startChild(target, { events });
|
|
209
|
+
return ctx.continueAsNew(input); // re-arm
|
|
145
210
|
});
|
|
146
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Register a **durable entity** (a virtual object): a keyed actor whose `handlers` run **serialized
|
|
214
|
+
* per key** over **durable state**, exactly once. Drive it with `signalEntity` (fire) /
|
|
215
|
+
* `ctx.callEntity` (call + await result) and read its state with `getEntityState`. Each key is one
|
|
216
|
+
* long-lived run (`entity:<name>:<key>`) that processes ops in order.
|
|
217
|
+
*/
|
|
218
|
+
registerEntity(name, config) {
|
|
219
|
+
this.entities.set(name, config);
|
|
220
|
+
}
|
|
221
|
+
/** Send an operation to an entity (fire-and-forget). Ordered + exactly-once per key. */
|
|
222
|
+
async signalEntity(name, key, op, arg) {
|
|
223
|
+
await this.dispatchEntityOp(name, key, op, arg);
|
|
224
|
+
}
|
|
225
|
+
/** Read an entity's current durable state (published after each op), or undefined if it has none yet. */
|
|
226
|
+
getEntityState(name, key) {
|
|
227
|
+
return this.getEvent(`entity:${name}:${key}`, 'state');
|
|
228
|
+
}
|
|
229
|
+
/** Deliver an op to an entity's runner, starting it on first contact. `reply` (a signal token) gets
|
|
230
|
+
* the handler's result, for `ctx.callEntity`. */
|
|
231
|
+
dispatchEntityOp(name, key, op, arg, reply) {
|
|
232
|
+
const runId = `entity:${name}:${key}`;
|
|
233
|
+
return this.signalWithStart('__entity', { name, key }, runId, {
|
|
234
|
+
token: runId,
|
|
235
|
+
payload: { op, arg, reply },
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/** The built-in entity runner: one long-lived run per key that processes ops serially over state. */
|
|
239
|
+
registerEntityRunner() {
|
|
240
|
+
this.register('__entity', '1', async (ctx, input) => {
|
|
241
|
+
const { name, key } = input;
|
|
242
|
+
const config = this.entities.get(name);
|
|
243
|
+
if (!config)
|
|
244
|
+
throw new errors_1.FatalError(`entity "${name}" is not registered`);
|
|
245
|
+
const token = `entity:${name}:${key}`;
|
|
246
|
+
let state = config.initialState();
|
|
247
|
+
for (let i = 0;; i += 1) {
|
|
248
|
+
const msg = (await ctx.waitForSignal(token));
|
|
249
|
+
// Run the handler and snapshot the (possibly mutated) state in ONE checkpoint, so replay
|
|
250
|
+
// restores the state from the checkpoint instead of re-running the handler.
|
|
251
|
+
const out = await ctx.step(`op:${i}`, async () => {
|
|
252
|
+
const handler = config.handlers[msg.op];
|
|
253
|
+
if (!handler)
|
|
254
|
+
throw new errors_1.FatalError(`entity "${name}" has no handler for op "${msg.op}"`);
|
|
255
|
+
const result = await handler(state, msg.arg);
|
|
256
|
+
return { result, state };
|
|
257
|
+
});
|
|
258
|
+
state = out.state; // carry state forward (and restore it from the checkpoint on replay)
|
|
259
|
+
await ctx.setEvent('state', state); // publish for getEntityState
|
|
260
|
+
if (msg.reply) {
|
|
261
|
+
await ctx.step(`reply:${i}`, async () => {
|
|
262
|
+
await this.signal(msg.reply, out.result);
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
/** Route an `onEvent` payload into its target's debounce/batch accumulator. */
|
|
269
|
+
routeBatchedEvent(target, batch, payload) {
|
|
270
|
+
const accId = `__evtacc__:${target}`;
|
|
271
|
+
const [accWorkflow, input] = batch.mode === 'debounce'
|
|
272
|
+
? ['__evt_debounce', { target, windowMs: batch.windowMs }]
|
|
273
|
+
: ['__evt_batch', { target, maxSize: batch.maxSize, windowMs: batch.windowMs }];
|
|
274
|
+
return this.signalWithStart(accWorkflow, input, accId, { token: accId, payload });
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Be notified when a run is enqueued on ANOTHER instance (via the control plane), so a worker can
|
|
278
|
+
* pick it up at once — e.g. `engine.onEnqueued((runId) => engine.runOne(runId))`. Returns an
|
|
279
|
+
* unsubscribe function. Only wire this on instances that should execute runs (workers).
|
|
280
|
+
*/
|
|
281
|
+
onEnqueued(listener) {
|
|
282
|
+
this.enqueuedListeners.add(listener);
|
|
283
|
+
return () => this.enqueuedListeners.delete(listener);
|
|
284
|
+
}
|
|
147
285
|
/** Fire cooperative-cancellation listeners for `runId` (a worker bridge aborts in-flight work). */
|
|
148
286
|
notifyCancelled(runId) {
|
|
149
287
|
for (const fn of this.cancelListeners) {
|
|
@@ -170,6 +308,7 @@ class WorkflowEngine {
|
|
|
170
308
|
executionTimeoutMs: opts?.executionTimeout != null ? (0, duration_1.parseDuration)(opts.executionTimeout) : undefined,
|
|
171
309
|
validateInput: opts?.validateInput,
|
|
172
310
|
onEvent: opts?.onEvent,
|
|
311
|
+
eventBatch: opts?.eventBatch,
|
|
173
312
|
};
|
|
174
313
|
this.workflows.set(versionKey(name, version), registered);
|
|
175
314
|
const current = this.latest.get(name);
|
|
@@ -304,6 +443,13 @@ class WorkflowEngine {
|
|
|
304
443
|
// The run is durably enqueued; a dispatcher (in-process by default) executes it — `start` does
|
|
305
444
|
// NOT run the body inline. Await the terminal/suspended state with `waitForRun(runId)` if needed.
|
|
306
445
|
await this.runDispatcher.dispatch(runId);
|
|
446
|
+
// Nudge worker instances to pick it up now instead of on their next poll (no-op without a control
|
|
447
|
+
// plane; self-receipt is filtered, so it only helps OTHER pods — e.g. an API pod's enqueue).
|
|
448
|
+
if (this.controlPlane) {
|
|
449
|
+
void this.controlPlane
|
|
450
|
+
.publishControl({ kind: 'enqueued', runId, from: this.instanceId })
|
|
451
|
+
.catch(() => undefined);
|
|
452
|
+
}
|
|
307
453
|
return { runId, status: 'pending' };
|
|
308
454
|
}
|
|
309
455
|
/** Read a run's current persisted state (or null if unknown). A thin pass-through to the store. */
|
|
@@ -377,7 +523,29 @@ class WorkflowEngine {
|
|
|
377
523
|
* replay from their checkpoints, so only the work that had not finished runs again.
|
|
378
524
|
*/
|
|
379
525
|
async recoverIncomplete(nowMs = this.clock()) {
|
|
380
|
-
|
|
526
|
+
if (this.draining)
|
|
527
|
+
return [];
|
|
528
|
+
const results = [];
|
|
529
|
+
for (const run of await this.store.listIncompleteRuns()) {
|
|
530
|
+
// A live worker renews its lease, so an acquirable lease means the run is genuinely orphaned
|
|
531
|
+
// (its worker crashed). Skip the ones still owned.
|
|
532
|
+
const acquired = await this.store.tryLockRun(run.id, this.instanceId, nowMs + this.leaseMs, nowMs);
|
|
533
|
+
if (!acquired)
|
|
534
|
+
continue;
|
|
535
|
+
// Count the attempt / dead-letter a poison pill past maxRecoveryAttempts.
|
|
536
|
+
const settled = await this.countRecovery(run);
|
|
537
|
+
if (settled) {
|
|
538
|
+
results.push(settled);
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
// Re-enqueue rather than resume inline: recovery must NOT block (boot, or a poll tick) on a
|
|
542
|
+
// long workflow step. A dispatcher/worker re-runs it, replaying its checkpoints.
|
|
543
|
+
await this.store.releaseRunLock(run.id);
|
|
544
|
+
await this.store.updateRun(run.id, { status: 'pending', updatedAt: new Date() });
|
|
545
|
+
await this.runDispatcher.dispatch(run.id);
|
|
546
|
+
results.push({ runId: run.id, status: 'pending' });
|
|
547
|
+
}
|
|
548
|
+
return results;
|
|
381
549
|
}
|
|
382
550
|
/**
|
|
383
551
|
* Per-recovery bookkeeping (called once the lease is held): count the attempt, or — past
|
|
@@ -439,6 +607,19 @@ class WorkflowEngine {
|
|
|
439
607
|
await this.runDispatcher.dispatch(runId);
|
|
440
608
|
return { runId, status: 'pending' };
|
|
441
609
|
}
|
|
610
|
+
/**
|
|
611
|
+
* **Fix-and-replay**: re-run a run (typically a `dead`/`failed` one) with a corrected `input`, as a
|
|
612
|
+
* fresh run with clean history. It's a NEW run — `newRunId` defaults to `<runId>~retry~<uuid>` — so
|
|
613
|
+
* the original stays inspectable. Returns the new run's id, or null if `runId` is unknown.
|
|
614
|
+
*/
|
|
615
|
+
async retryWithInput(runId, input, newRunId) {
|
|
616
|
+
const run = await this.store.getRun(runId);
|
|
617
|
+
if (!run)
|
|
618
|
+
return null;
|
|
619
|
+
const id = newRunId ?? `${runId}~retry~${globalThis.crypto.randomUUID().slice(0, 8)}`;
|
|
620
|
+
await this.start(run.workflow, input, id, { tags: run.tags });
|
|
621
|
+
return { runId: id };
|
|
622
|
+
}
|
|
442
623
|
/**
|
|
443
624
|
* Pick up and execute every `pending` run — the poll-based side of dispatch for a worker pod with
|
|
444
625
|
* no broker. Runs enqueued by other pods (or by a caller using a no-op dispatcher) sit `pending`
|
|
@@ -547,7 +728,15 @@ class WorkflowEngine {
|
|
|
547
728
|
// A subscriber that rejects the payload (validateInput) must not block the others or the
|
|
548
729
|
// waiters — its run simply never starts, mirroring fire-and-forget dead-letter routing.
|
|
549
730
|
try {
|
|
550
|
-
|
|
731
|
+
const batch = this.latest.get(workflow)?.eventBatch;
|
|
732
|
+
if (batch) {
|
|
733
|
+
// Coalesce: route the event into a per-workflow accumulator (one long-lived run that
|
|
734
|
+
// debounces/batches and then starts the target with the collected payload(s)).
|
|
735
|
+
await this.routeBatchedEvent(workflow, batch, payload);
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
await this.start(workflow, payload, `evt:${eventId}:${workflow}`);
|
|
739
|
+
}
|
|
551
740
|
touched += 1;
|
|
552
741
|
}
|
|
553
742
|
catch {
|
|
@@ -559,8 +748,12 @@ class WorkflowEngine {
|
|
|
559
748
|
}
|
|
560
749
|
async signal(token, payload) {
|
|
561
750
|
const waiter = await this.store.takeSignalWaiter(token);
|
|
562
|
-
if (!waiter)
|
|
751
|
+
if (!waiter) {
|
|
752
|
+
// No one is waiting yet — buffer it so the next `waitForSignal(token)` consumes it instead of
|
|
753
|
+
// dropping it (reliable signals; the basis of `signalWithStart`).
|
|
754
|
+
await this.store.bufferSignal(token, payload);
|
|
563
755
|
return null;
|
|
756
|
+
}
|
|
564
757
|
await this.store.saveCheckpoint((0, checkpoints_1.instantCheckpoint)({
|
|
565
758
|
runId: waiter.runId,
|
|
566
759
|
seq: waiter.seq,
|
|
@@ -570,6 +763,21 @@ class WorkflowEngine {
|
|
|
570
763
|
}));
|
|
571
764
|
return this.resume(waiter.runId);
|
|
572
765
|
}
|
|
766
|
+
/**
|
|
767
|
+
* Ensure a run exists for `runId`, then deliver a signal to it — atomically race-free thanks to
|
|
768
|
+
* signal buffering: if the run is new (or busy / not yet waiting), the signal is buffered and
|
|
769
|
+
* consumed when it reaches `waitForSignal(token)`. The canonical **durable-entity / accumulator**
|
|
770
|
+
* pattern: one long-lived run per key (the `runId`) that loops on `waitForSignal`, fed events by
|
|
771
|
+
* many `signalWithStart` calls. `start` is idempotent by `runId`, so concurrent callers converge on
|
|
772
|
+
* one run. (Use a per-run `token`, e.g. derived from `runId`, so the signal targets this entity.)
|
|
773
|
+
*/
|
|
774
|
+
async signalWithStart(workflow, input, runId, signal, opts) {
|
|
775
|
+
// `start` is overloaded per ref kind (class | string); a `WorkflowRef` union fits neither
|
|
776
|
+
// overload, so resolve to the string overload (the engine handles both at runtime).
|
|
777
|
+
await this.start(workflow, input, runId, opts); // idempotent: no-op if run exists
|
|
778
|
+
await this.signal(signal.token, signal.payload);
|
|
779
|
+
return { runId };
|
|
780
|
+
}
|
|
573
781
|
/**
|
|
574
782
|
* Report the result of a `ctx.task(name, …)` back to its run (async completion). The external
|
|
575
783
|
* worker that the task dispatched to calls this when done; the run resumes with `result`. Returns
|
|
@@ -603,6 +811,11 @@ class WorkflowEngine {
|
|
|
603
811
|
const run = await this.store.getRun(runId);
|
|
604
812
|
if (!run)
|
|
605
813
|
return null;
|
|
814
|
+
// Already finished — nothing to cancel (and don't clobber a completed/dead run). This also stops
|
|
815
|
+
// the child cascade below from looping on already-cancelled runs.
|
|
816
|
+
if (run.status === 'completed' || run.status === 'cancelled' || run.status === 'dead') {
|
|
817
|
+
return { runId, status: run.status, output: run.output, error: run.error };
|
|
818
|
+
}
|
|
606
819
|
// Compensating cancel: resume the run with a cancellation pending — replay re-registers the saga,
|
|
607
820
|
// and at the suspension point execute() runs the undo and marks it cancelled. Run that resume in
|
|
608
821
|
// the BACKGROUND so the caller (e.g. an HTTP request) never blocks on replaying the workflow +
|
|
@@ -620,11 +833,13 @@ class WorkflowEngine {
|
|
|
620
833
|
.then(() => this.notifyCancelled(runId))
|
|
621
834
|
.catch(() => undefined);
|
|
622
835
|
});
|
|
836
|
+
await this.cancelChildren(runId, opts);
|
|
623
837
|
return { runId, status: run.status };
|
|
624
838
|
}
|
|
625
839
|
const error = { message: 'cancelled' };
|
|
626
840
|
await this.store.updateRun(runId, { status: 'cancelled', error, updatedAt: new Date() });
|
|
627
841
|
this.emit({ type: 'run.failed', runId, workflow: run.workflow, error });
|
|
842
|
+
await this.cancelChildren(runId, opts);
|
|
628
843
|
// Notify local cancel listeners now (a worker on this pod), and broadcast so the instance/worker
|
|
629
844
|
// actually running this run learns of it and can abort cooperatively (the store already records
|
|
630
845
|
// `cancelled`, but a busy worker won't re-read it).
|
|
@@ -636,6 +851,25 @@ class WorkflowEngine {
|
|
|
636
851
|
}
|
|
637
852
|
return { runId, status: 'cancelled', error };
|
|
638
853
|
}
|
|
854
|
+
/**
|
|
855
|
+
* Cascade cancellation to a run's children — both awaited (`ctx.child`, found via its live
|
|
856
|
+
* `child:<id>` waiter) and fire-and-forget (`ctx.startChild`, found via its `spawn:<id>`
|
|
857
|
+
* checkpoint). Recursive, so a whole subtree is cancelled; the terminal guard in `cancel` stops it
|
|
858
|
+
* at finished / already-cancelled runs (no loops, no re-cancel).
|
|
859
|
+
*/
|
|
860
|
+
async cancelChildren(parentRunId, opts) {
|
|
861
|
+
const childIds = new Set();
|
|
862
|
+
for (const w of await this.store.listSignalWaiters('child:')) {
|
|
863
|
+
if (w.runId === parentRunId)
|
|
864
|
+
childIds.add(w.token.slice('child:'.length));
|
|
865
|
+
}
|
|
866
|
+
for (const cp of await this.store.listCheckpoints(parentRunId)) {
|
|
867
|
+
if (cp.name.startsWith('spawn:') && typeof cp.output === 'string')
|
|
868
|
+
childIds.add(cp.output);
|
|
869
|
+
}
|
|
870
|
+
for (const id of childIds)
|
|
871
|
+
await this.cancel(id, opts).catch(() => undefined);
|
|
872
|
+
}
|
|
639
873
|
/** Checkpoint a finished step and announce it — the two things that must always happen together. */
|
|
640
874
|
async completeStep(step) {
|
|
641
875
|
await this.store.saveCheckpoint({
|
|
@@ -887,6 +1121,9 @@ class WorkflowEngine {
|
|
|
887
1121
|
startChild: (workflow, input, id) => {
|
|
888
1122
|
queueMicrotask(() => void this.start(workflow, input, id).catch(() => undefined));
|
|
889
1123
|
},
|
|
1124
|
+
signalEntity: (name, key, op, arg, reply) => {
|
|
1125
|
+
queueMicrotask(() => void this.dispatchEntityOp(name, key, op, arg, reply).catch(() => undefined));
|
|
1126
|
+
},
|
|
890
1127
|
interceptStep: (invocation, body) => this.interceptStep(invocation, body),
|
|
891
1128
|
};
|
|
892
1129
|
}
|