@dudousxd/nestjs-durable-core 0.32.1 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +155 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -8
- package/dist/index.d.ts +120 -8
- package/dist/index.js +152 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -29,6 +29,7 @@ __export(index_exports, {
|
|
|
29
29
|
Entities: () => Entities,
|
|
30
30
|
EventAccumulators: () => EventAccumulators,
|
|
31
31
|
FatalError: () => FatalError,
|
|
32
|
+
GatherError: () => GatherError,
|
|
32
33
|
InMemoryAdmissionBackend: () => InMemoryAdmissionBackend,
|
|
33
34
|
InMemoryStateStore: () => InMemoryStateStore,
|
|
34
35
|
InMemoryTransport: () => InMemoryTransport,
|
|
@@ -37,6 +38,7 @@ __export(index_exports, {
|
|
|
37
38
|
RemoteStepError: () => RemoteStepError,
|
|
38
39
|
RemoteStepTimeout: () => RemoteStepTimeout,
|
|
39
40
|
RemoteWorkflowExecutor: () => RemoteWorkflowExecutor,
|
|
41
|
+
RemoteWorkflowTimeout: () => RemoteWorkflowTimeout,
|
|
40
42
|
STATE_STORE: () => STATE_STORE,
|
|
41
43
|
STATE_STORE_CANONICAL: () => STATE_STORE_CANONICAL,
|
|
42
44
|
SignalTimeoutError: () => SignalTimeoutError,
|
|
@@ -51,6 +53,7 @@ __export(index_exports, {
|
|
|
51
53
|
attributeOperand: () => attributeOperand,
|
|
52
54
|
breakpointToken: () => breakpointToken,
|
|
53
55
|
collectMetrics: () => collectMetrics,
|
|
56
|
+
createStepLogger: () => createStepLogger,
|
|
54
57
|
eventMatchOf: () => eventMatchOf,
|
|
55
58
|
eventMatches: () => eventMatches,
|
|
56
59
|
eventPrefix: () => eventPrefix,
|
|
@@ -453,6 +456,7 @@ function stepCheckpoint(p) {
|
|
|
453
456
|
attempts: p.attempts,
|
|
454
457
|
workerGroup: p.workerGroup,
|
|
455
458
|
wakeAt: p.wakeAt,
|
|
459
|
+
parallelGroup: p.parallelGroup,
|
|
456
460
|
enqueuedAt: p.enqueuedAt,
|
|
457
461
|
startedAt: p.startedAt,
|
|
458
462
|
finishedAt: p.finishedAt
|
|
@@ -470,6 +474,7 @@ function instantCheckpoint(p) {
|
|
|
470
474
|
status: p.status ?? "completed",
|
|
471
475
|
output: p.output,
|
|
472
476
|
wakeAt: p.wakeAt,
|
|
477
|
+
parallelGroup: p.parallelGroup,
|
|
473
478
|
attempts: 1,
|
|
474
479
|
enqueuedAt: at,
|
|
475
480
|
startedAt: at,
|
|
@@ -531,6 +536,19 @@ var RemoteStepTimeout = class extends Error {
|
|
|
531
536
|
this.timeoutMs = timeoutMs;
|
|
532
537
|
}
|
|
533
538
|
};
|
|
539
|
+
var RemoteWorkflowTimeout = class extends Error {
|
|
540
|
+
static {
|
|
541
|
+
__name(this, "RemoteWorkflowTimeout");
|
|
542
|
+
}
|
|
543
|
+
taskId;
|
|
544
|
+
timeoutMs;
|
|
545
|
+
constructor(taskId, timeoutMs) {
|
|
546
|
+
super(`remote workflow task ${taskId} produced no decision within ${timeoutMs}ms \u2014 presumed dropped; re-driving via recovery`);
|
|
547
|
+
this.name = "RemoteWorkflowTimeout";
|
|
548
|
+
this.taskId = taskId;
|
|
549
|
+
this.timeoutMs = timeoutMs;
|
|
550
|
+
}
|
|
551
|
+
};
|
|
534
552
|
var NonDeterminismError = class extends Error {
|
|
535
553
|
static {
|
|
536
554
|
__name(this, "NonDeterminismError");
|
|
@@ -544,6 +562,19 @@ var NonDeterminismError = class extends Error {
|
|
|
544
562
|
this.seq = seq;
|
|
545
563
|
}
|
|
546
564
|
};
|
|
565
|
+
var GatherError = class extends Error {
|
|
566
|
+
static {
|
|
567
|
+
__name(this, "GatherError");
|
|
568
|
+
}
|
|
569
|
+
failures;
|
|
570
|
+
constructor(failures, total) {
|
|
571
|
+
const ids = failures.map((f) => f.id).join(", ");
|
|
572
|
+
const denom = total ?? failures.length;
|
|
573
|
+
super(`ctx.all: ${failures.length} of ${denom} child(ren) failed: ${ids}`);
|
|
574
|
+
this.name = "GatherError";
|
|
575
|
+
this.failures = failures;
|
|
576
|
+
}
|
|
577
|
+
};
|
|
547
578
|
var WorkflowSuspended = class extends Error {
|
|
548
579
|
static {
|
|
549
580
|
__name(this, "WorkflowSuspended");
|
|
@@ -1213,6 +1244,72 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1213
1244
|
}
|
|
1214
1245
|
throw new WorkflowSuspended();
|
|
1215
1246
|
}, "child");
|
|
1247
|
+
const all = /* @__PURE__ */ __name(async (workflow2, inputs, opts) => {
|
|
1248
|
+
if (inputs.length === 0) return [];
|
|
1249
|
+
const mode = opts?.mode ?? "waitAll";
|
|
1250
|
+
const name = workflowName(workflow2);
|
|
1251
|
+
const positions = inputs.map(() => pos.next());
|
|
1252
|
+
const group = `all:${positions[0]}`;
|
|
1253
|
+
const id = /* @__PURE__ */ __name((i) => `${runId}.all.${positions[0]}.${i}`, "id");
|
|
1254
|
+
const existing = await Promise.all(positions.map((seq) => readCheckpoint(seq)));
|
|
1255
|
+
if (mode === "failFast") {
|
|
1256
|
+
for (let i = 0; i < inputs.length; i += 1) {
|
|
1257
|
+
const cp = existing[i];
|
|
1258
|
+
const c = cp?.status === "completed" ? cp.output : null;
|
|
1259
|
+
if (c && c.ok === false) {
|
|
1260
|
+
throw new GatherError([
|
|
1261
|
+
{
|
|
1262
|
+
index: i,
|
|
1263
|
+
id: id(i),
|
|
1264
|
+
error: c.error ?? "unknown"
|
|
1265
|
+
}
|
|
1266
|
+
], inputs.length);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
let pending = false;
|
|
1271
|
+
for (let i = 0; i < inputs.length; i += 1) {
|
|
1272
|
+
const cp = existing[i];
|
|
1273
|
+
if (cp?.status === "completed") continue;
|
|
1274
|
+
pending = true;
|
|
1275
|
+
const seq = positions[i];
|
|
1276
|
+
const childId = id(i);
|
|
1277
|
+
await store.putSignalWaiter({
|
|
1278
|
+
token: `child:${childId}`,
|
|
1279
|
+
runId,
|
|
1280
|
+
seq
|
|
1281
|
+
});
|
|
1282
|
+
if (!await store.getRun(childId)) host.startChild(name, inputs[i], childId);
|
|
1283
|
+
if (!cp) {
|
|
1284
|
+
await writeCheckpoint(instantCheckpoint({
|
|
1285
|
+
runId,
|
|
1286
|
+
seq,
|
|
1287
|
+
name: `signal:child:${childId}`,
|
|
1288
|
+
kind: "signal",
|
|
1289
|
+
status: "running",
|
|
1290
|
+
parallelGroup: group
|
|
1291
|
+
}));
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
if (pending) throw new WorkflowSuspended();
|
|
1295
|
+
const outputs = [];
|
|
1296
|
+
const failures = [];
|
|
1297
|
+
for (let i = 0; i < inputs.length; i += 1) {
|
|
1298
|
+
const c = existing[i]?.output;
|
|
1299
|
+
if (c && typeof c === "object" && "ok" in c && c.ok === false) {
|
|
1300
|
+
failures.push({
|
|
1301
|
+
index: i,
|
|
1302
|
+
id: id(i),
|
|
1303
|
+
error: c.error ?? "unknown"
|
|
1304
|
+
});
|
|
1305
|
+
outputs.push(void 0);
|
|
1306
|
+
} else {
|
|
1307
|
+
outputs.push(c?.value);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
if (failures.length > 0) throw new GatherError(failures, inputs.length);
|
|
1311
|
+
return outputs;
|
|
1312
|
+
}, "all");
|
|
1216
1313
|
const startChild = /* @__PURE__ */ __name(async (workflow2, input, options) => {
|
|
1217
1314
|
const { childId, priority } = normalizeChildOptions(options);
|
|
1218
1315
|
const current = pos.next();
|
|
@@ -1347,6 +1444,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1347
1444
|
waitForEvent,
|
|
1348
1445
|
task,
|
|
1349
1446
|
child,
|
|
1447
|
+
all,
|
|
1350
1448
|
startChild,
|
|
1351
1449
|
breakpoint,
|
|
1352
1450
|
webhook,
|
|
@@ -1404,6 +1502,7 @@ var WorkflowEngine = class {
|
|
|
1404
1502
|
instanceId;
|
|
1405
1503
|
leaseMs;
|
|
1406
1504
|
maxRecoveryAttempts;
|
|
1505
|
+
remoteAdvanceSilenceMs;
|
|
1407
1506
|
webhookUrl;
|
|
1408
1507
|
traceparent;
|
|
1409
1508
|
context;
|
|
@@ -1428,7 +1527,9 @@ var WorkflowEngine = class {
|
|
|
1428
1527
|
accumulators;
|
|
1429
1528
|
/** In-flight remote steps awaiting a worker result, keyed by stepId. */
|
|
1430
1529
|
pending = /* @__PURE__ */ new Map();
|
|
1431
|
-
/**
|
|
1530
|
+
/** "Reset the liveness timer" callbacks keyed by the beat's target — a stepId (in-flight remote step)
|
|
1531
|
+
* or a runId (in-flight remote workflow turn) — invoked when a matching heartbeat arrives. The two id
|
|
1532
|
+
* shapes don't collide in practice: a stepId is `${runId}:${seq}`, a run key is the bare runId. */
|
|
1432
1533
|
heartbeatResets = /* @__PURE__ */ new Map();
|
|
1433
1534
|
listeners = /* @__PURE__ */ new Set();
|
|
1434
1535
|
/** Step interceptors (onion middleware around real local-step execution), first = outermost. */
|
|
@@ -1467,6 +1568,7 @@ var WorkflowEngine = class {
|
|
|
1467
1568
|
this.instanceId = deps.instanceId ?? globalThis.crypto.randomUUID();
|
|
1468
1569
|
this.leaseMs = deps.leaseMs ?? 3e4;
|
|
1469
1570
|
this.maxRecoveryAttempts = deps.maxRecoveryAttempts;
|
|
1571
|
+
this.remoteAdvanceSilenceMs = deps.remoteAdvanceSilenceMs;
|
|
1470
1572
|
this.webhookUrl = deps.webhookUrl;
|
|
1471
1573
|
this.traceparent = deps.traceparent;
|
|
1472
1574
|
this.context = deps.context;
|
|
@@ -1503,9 +1605,11 @@ var WorkflowEngine = class {
|
|
|
1503
1605
|
}
|
|
1504
1606
|
await this.completeRemoteResult(result);
|
|
1505
1607
|
},
|
|
1506
|
-
// A heartbeat for an in-flight long
|
|
1608
|
+
// A heartbeat resets the liveness window for whatever it targets: an in-flight long STEP (keyed
|
|
1609
|
+
// by stepId, see callRemote) or — when stepId is absent — the in-flight workflow TURN (keyed by
|
|
1610
|
+
// runId, see runRemoteExecution's heartbeat-rearmed advance).
|
|
1507
1611
|
async (beat) => {
|
|
1508
|
-
this.heartbeatResets.get(beat.stepId)?.();
|
|
1612
|
+
this.heartbeatResets.get(beat.stepId ?? beat.runId)?.();
|
|
1509
1613
|
},
|
|
1510
1614
|
// A remote workflow worker streams each local step's lifecycle (running → completed/failed) so
|
|
1511
1615
|
// it's checkpointed live, not all-at-once when the long turn ends.
|
|
@@ -2336,7 +2440,8 @@ var WorkflowEngine = class {
|
|
|
2336
2440
|
attempts: 1,
|
|
2337
2441
|
enqueuedAt: startedAt,
|
|
2338
2442
|
startedAt,
|
|
2339
|
-
finishedAt: startedAt
|
|
2443
|
+
finishedAt: startedAt,
|
|
2444
|
+
parallelGroup: event.parallelGroup
|
|
2340
2445
|
}));
|
|
2341
2446
|
this.emit({
|
|
2342
2447
|
type: "step.started",
|
|
@@ -2360,7 +2465,8 @@ var WorkflowEngine = class {
|
|
|
2360
2465
|
attempts: 1,
|
|
2361
2466
|
enqueuedAt: startedAt,
|
|
2362
2467
|
startedAt,
|
|
2363
|
-
finishedAt: event.finishedAt != null ? new Date(event.finishedAt) : /* @__PURE__ */ new Date()
|
|
2468
|
+
finishedAt: event.finishedAt != null ? new Date(event.finishedAt) : /* @__PURE__ */ new Date(),
|
|
2469
|
+
parallelGroup: event.parallelGroup
|
|
2364
2470
|
}));
|
|
2365
2471
|
this.emit({
|
|
2366
2472
|
type: failed ? "step.failed" : "step.completed",
|
|
@@ -2595,8 +2701,17 @@ var WorkflowEngine = class {
|
|
|
2595
2701
|
const history = await this.remoteHistory(run.id);
|
|
2596
2702
|
let decision;
|
|
2597
2703
|
try {
|
|
2598
|
-
|
|
2704
|
+
const silenceMs = this.remoteAdvanceSilenceMs;
|
|
2705
|
+
const advance = remote.executor.advance(run, history);
|
|
2706
|
+
decision = silenceMs != null ? await this.awaitWithLivenessDeadline(run.id, advance, silenceMs, () => new RemoteWorkflowTimeout(run.id, silenceMs)) : await advance;
|
|
2599
2707
|
} catch (err) {
|
|
2708
|
+
if (err instanceof RemoteWorkflowTimeout) {
|
|
2709
|
+
await this.store.releaseRunLock(run.id);
|
|
2710
|
+
return {
|
|
2711
|
+
runId: run.id,
|
|
2712
|
+
status: "running"
|
|
2713
|
+
};
|
|
2714
|
+
}
|
|
2600
2715
|
const error = {
|
|
2601
2716
|
message: err instanceof Error ? err.message : String(err),
|
|
2602
2717
|
stack: err instanceof Error ? err.stack : void 0
|
|
@@ -2716,7 +2831,11 @@ var WorkflowEngine = class {
|
|
|
2716
2831
|
attempts: 1,
|
|
2717
2832
|
enqueuedAt: startedAt,
|
|
2718
2833
|
startedAt,
|
|
2719
|
-
finishedAt
|
|
2834
|
+
finishedAt,
|
|
2835
|
+
// Carry the worker's `parallelGroup` (a Python `ctx.gather` tags every step in the fan with
|
|
2836
|
+
// the same group) onto the checkpoint, so the dashboard can render the steps as one parallel
|
|
2837
|
+
// group instead of a sequential list. Additive: undefined for ordinary sequential steps.
|
|
2838
|
+
parallelGroup: cmd.parallelGroup
|
|
2720
2839
|
}));
|
|
2721
2840
|
this.emit({
|
|
2722
2841
|
type: cmd.error ? "step.failed" : "step.completed",
|
|
@@ -3245,11 +3364,13 @@ var WorkflowEngine = class {
|
|
|
3245
3364
|
}
|
|
3246
3365
|
}
|
|
3247
3366
|
/**
|
|
3248
|
-
* Await
|
|
3249
|
-
* arrives within `timeoutMs`. Each heartbeat (delivered via `transport.onHeartbeat
|
|
3250
|
-
* window, so a worker that keeps beating stays alive past
|
|
3367
|
+
* Await `resultPromise`, but reject with `onTimeout()` if neither it settles nor a heartbeat for `id`
|
|
3368
|
+
* arrives within `timeoutMs`. Each heartbeat (delivered via `transport.onHeartbeat`, looked up in
|
|
3369
|
+
* `heartbeatResets` by `id`) REARMS the window, so a worker that keeps beating stays alive past
|
|
3370
|
+
* `timeoutMs`. Shared by the remote-STEP liveness (keyed by stepId) and the remote-WORKFLOW advance
|
|
3371
|
+
* liveness (keyed by runId); the caller supplies the timeout error and any extra cleanup via `onTimeout`.
|
|
3251
3372
|
*/
|
|
3252
|
-
|
|
3373
|
+
awaitWithLivenessDeadline(id, resultPromise, timeoutMs, onTimeout) {
|
|
3253
3374
|
return new Promise((resolve, reject) => {
|
|
3254
3375
|
let timer;
|
|
3255
3376
|
const cleanup = /* @__PURE__ */ __name(() => {
|
|
@@ -3259,8 +3380,7 @@ var WorkflowEngine = class {
|
|
|
3259
3380
|
const arm = /* @__PURE__ */ __name(() => {
|
|
3260
3381
|
timer = setTimeout(() => {
|
|
3261
3382
|
cleanup();
|
|
3262
|
-
|
|
3263
|
-
reject(new RemoteStepTimeout(id, timeoutMs));
|
|
3383
|
+
reject(onTimeout());
|
|
3264
3384
|
}, timeoutMs);
|
|
3265
3385
|
timer.unref?.();
|
|
3266
3386
|
}, "arm");
|
|
@@ -3278,6 +3398,14 @@ var WorkflowEngine = class {
|
|
|
3278
3398
|
});
|
|
3279
3399
|
});
|
|
3280
3400
|
}
|
|
3401
|
+
/** Remote-STEP liveness: await a worker result, timing out (with the step's pending-map cleanup) to
|
|
3402
|
+
* `RemoteStepTimeout` if it neither lands nor beats within `timeoutMs`. See {@link awaitWithLivenessDeadline}. */
|
|
3403
|
+
awaitWithHeartbeat(id, resultPromise, timeoutMs) {
|
|
3404
|
+
return this.awaitWithLivenessDeadline(id, resultPromise, timeoutMs, () => {
|
|
3405
|
+
this.pending.delete(id);
|
|
3406
|
+
return new RemoteStepTimeout(id, timeoutMs);
|
|
3407
|
+
});
|
|
3408
|
+
}
|
|
3281
3409
|
};
|
|
3282
3410
|
var RemoteStepError = class extends Error {
|
|
3283
3411
|
static {
|
|
@@ -3351,11 +3479,12 @@ var RemoteWorkflowExecutor = class {
|
|
|
3351
3479
|
};
|
|
3352
3480
|
const decision = new Promise((resolve, reject) => {
|
|
3353
3481
|
this.pending.set(taskId, resolve);
|
|
3354
|
-
|
|
3482
|
+
const { timeoutMs } = this.opts;
|
|
3483
|
+
if (timeoutMs) {
|
|
3355
3484
|
const timer = setTimeout(() => {
|
|
3356
3485
|
this.pending.delete(taskId);
|
|
3357
|
-
reject(new
|
|
3358
|
-
},
|
|
3486
|
+
reject(new RemoteWorkflowTimeout(taskId, timeoutMs));
|
|
3487
|
+
}, timeoutMs);
|
|
3359
3488
|
timer.unref?.();
|
|
3360
3489
|
}
|
|
3361
3490
|
});
|
|
@@ -4042,6 +4171,7 @@ var InMemoryTransport = class {
|
|
|
4042
4171
|
}
|
|
4043
4172
|
handlers = /* @__PURE__ */ new Map();
|
|
4044
4173
|
resultHandler;
|
|
4174
|
+
heartbeatHandler;
|
|
4045
4175
|
stepEventHandler;
|
|
4046
4176
|
controlHandlers = /* @__PURE__ */ new Set();
|
|
4047
4177
|
/** Register a fake worker handler for a step name. */
|
|
@@ -4056,7 +4186,12 @@ var InMemoryTransport = class {
|
|
|
4056
4186
|
onResult(handler) {
|
|
4057
4187
|
this.resultHandler = handler;
|
|
4058
4188
|
}
|
|
4059
|
-
onHeartbeat(
|
|
4189
|
+
onHeartbeat(handler) {
|
|
4190
|
+
this.heartbeatHandler = handler;
|
|
4191
|
+
}
|
|
4192
|
+
/** Test hook: deliver a liveness heartbeat to the engine, as a real broker would on its channel. */
|
|
4193
|
+
async emitHeartbeat(beat) {
|
|
4194
|
+
await this.heartbeatHandler?.(beat);
|
|
4060
4195
|
}
|
|
4061
4196
|
/** Workflow step lifecycle: deliver to the engine's handler (async, mirroring a real broker). */
|
|
4062
4197
|
async dispatchStepEvent(event) {
|
|
@@ -4083,6 +4218,7 @@ var InMemoryTransport = class {
|
|
|
4083
4218
|
Entities,
|
|
4084
4219
|
EventAccumulators,
|
|
4085
4220
|
FatalError,
|
|
4221
|
+
GatherError,
|
|
4086
4222
|
InMemoryAdmissionBackend,
|
|
4087
4223
|
InMemoryStateStore,
|
|
4088
4224
|
InMemoryTransport,
|
|
@@ -4091,6 +4227,7 @@ var InMemoryTransport = class {
|
|
|
4091
4227
|
RemoteStepError,
|
|
4092
4228
|
RemoteStepTimeout,
|
|
4093
4229
|
RemoteWorkflowExecutor,
|
|
4230
|
+
RemoteWorkflowTimeout,
|
|
4094
4231
|
STATE_STORE,
|
|
4095
4232
|
STATE_STORE_CANONICAL,
|
|
4096
4233
|
SignalTimeoutError,
|
|
@@ -4105,6 +4242,7 @@ var InMemoryTransport = class {
|
|
|
4105
4242
|
attributeOperand,
|
|
4106
4243
|
breakpointToken,
|
|
4107
4244
|
collectMetrics,
|
|
4245
|
+
createStepLogger,
|
|
4108
4246
|
eventMatchOf,
|
|
4109
4247
|
eventMatches,
|
|
4110
4248
|
eventPrefix,
|