@dudousxd/nestjs-durable-core 0.49.2 → 0.49.4
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 +161 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +104 -2
- package/dist/index.d.ts +104 -2
- package/dist/index.js +157 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -57,17 +57,21 @@ __export(index_exports, {
|
|
|
57
57
|
attributeOperand: () => attributeOperand,
|
|
58
58
|
bindWorkflowClass: () => bindWorkflowClass,
|
|
59
59
|
breakpointToken: () => breakpointToken,
|
|
60
|
+
classifyWaiterToken: () => classifyWaiterToken,
|
|
60
61
|
collectMetrics: () => collectMetrics,
|
|
61
62
|
createStepLogger: () => createStepLogger,
|
|
62
63
|
currentWorkflowCtx: () => currentWorkflowCtx,
|
|
63
64
|
eventMatchOf: () => eventMatchOf,
|
|
64
65
|
eventMatches: () => eventMatches,
|
|
66
|
+
eventNameOf: () => eventNameOf,
|
|
65
67
|
eventPrefix: () => eventPrefix,
|
|
66
68
|
eventToken: () => eventToken,
|
|
69
|
+
indexWaitersByRun: () => indexWaitersByRun,
|
|
67
70
|
matchesAttributes: () => matchesAttributes,
|
|
68
71
|
normalizeAttributeRows: () => normalizeAttributeRows,
|
|
69
72
|
parseDuration: () => parseDuration,
|
|
70
73
|
prevCronFireMs: () => prevCronFireMs,
|
|
74
|
+
resolveRunWaiting: () => resolveRunWaiting,
|
|
71
75
|
runInWorkflowCtx: () => runInWorkflowCtx,
|
|
72
76
|
runSchedules: () => runSchedules,
|
|
73
77
|
runStepHandler: () => runStepHandler,
|
|
@@ -861,6 +865,14 @@ function eventToken(name, match, runId, seq) {
|
|
|
861
865
|
return `${eventPrefix(name)}${enc(JSON.stringify(match ?? {}))}:${runId}#${seq}`;
|
|
862
866
|
}
|
|
863
867
|
__name(eventToken, "eventToken");
|
|
868
|
+
function eventNameOf(token) {
|
|
869
|
+
try {
|
|
870
|
+
return dec(token.split(":")[1] ?? "");
|
|
871
|
+
} catch {
|
|
872
|
+
return token;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
__name(eventNameOf, "eventNameOf");
|
|
864
876
|
function eventMatchOf(token) {
|
|
865
877
|
try {
|
|
866
878
|
return JSON.parse(dec(token.split(":")[2] ?? ""));
|
|
@@ -1802,6 +1814,8 @@ var WorkflowEngine = class {
|
|
|
1802
1814
|
maxRecoveryAttempts;
|
|
1803
1815
|
remoteAdvanceSilenceMs;
|
|
1804
1816
|
reconcileMs;
|
|
1817
|
+
remoteRedispatchMs;
|
|
1818
|
+
remoteRedispatchMax;
|
|
1805
1819
|
webhookUrl;
|
|
1806
1820
|
traceparent;
|
|
1807
1821
|
context;
|
|
@@ -1873,6 +1887,8 @@ var WorkflowEngine = class {
|
|
|
1873
1887
|
this.maxRecoveryAttempts = deps.maxRecoveryAttempts;
|
|
1874
1888
|
this.remoteAdvanceSilenceMs = deps.remoteAdvanceSilenceMs;
|
|
1875
1889
|
this.reconcileMs = deps.reconcileMs === void 0 ? 3e5 : deps.reconcileMs || void 0;
|
|
1890
|
+
this.remoteRedispatchMs = deps.remoteRedispatchMs || void 0;
|
|
1891
|
+
this.remoteRedispatchMax = Math.max(1, deps.remoteRedispatchMax ?? 10);
|
|
1876
1892
|
this.webhookUrl = deps.webhookUrl;
|
|
1877
1893
|
this.traceparent = deps.traceparent;
|
|
1878
1894
|
this.context = deps.context;
|
|
@@ -2584,6 +2600,49 @@ var WorkflowEngine = class {
|
|
|
2584
2600
|
};
|
|
2585
2601
|
}
|
|
2586
2602
|
/**
|
|
2603
|
+
* Explicitly re-dispatch every remote step of `runId` stuck `pending` — the OPERATOR escape hatch for
|
|
2604
|
+
* a run whose dispatched step job was LOST (worker crashed with no result, or a transport that dropped
|
|
2605
|
+
* the job). No automatic recovery re-drives these: a reconcile re-drive re-suspends a still-pending
|
|
2606
|
+
* step by design (unless `remoteRedispatchMs` is set), `recoverIncomplete` only reclaims LEASED runs,
|
|
2607
|
+
* and `requeue` just replays back to the pending guard. This re-enqueues the same `stepId` so the
|
|
2608
|
+
* (idempotent) step re-runs and its result resumes the run. Safe to call on a healthy run — it re-runs
|
|
2609
|
+
* only in-flight `pending` remote steps, which are idempotent by the durable contract. Returns the
|
|
2610
|
+
* run's current status (with the count re-dispatched), or null if the run is unknown.
|
|
2611
|
+
*/
|
|
2612
|
+
async redispatchPending(runId) {
|
|
2613
|
+
const run = await this.store.getRun(runId);
|
|
2614
|
+
if (!run) return null;
|
|
2615
|
+
const pending = (await this.store.listCheckpoints(runId)).filter((cp) => cp.kind === "remote" && cp.status === "pending");
|
|
2616
|
+
let redispatched = 0;
|
|
2617
|
+
for (const cp of pending) {
|
|
2618
|
+
if (!cp.stepId) continue;
|
|
2619
|
+
const reAttempt = cp.attempts + 1;
|
|
2620
|
+
const reEnqueuedAt = /* @__PURE__ */ new Date();
|
|
2621
|
+
await this.store.saveCheckpoint({
|
|
2622
|
+
...cp,
|
|
2623
|
+
attempts: reAttempt,
|
|
2624
|
+
enqueuedAt: reEnqueuedAt,
|
|
2625
|
+
startedAt: reEnqueuedAt,
|
|
2626
|
+
finishedAt: reEnqueuedAt
|
|
2627
|
+
});
|
|
2628
|
+
await this.dispatchRemoteTask({
|
|
2629
|
+
runId,
|
|
2630
|
+
seq: cp.seq,
|
|
2631
|
+
name: cp.name,
|
|
2632
|
+
stepId: cp.stepId,
|
|
2633
|
+
group: cp.workerGroup ?? sanitizeQueueToken(cp.name),
|
|
2634
|
+
input: cp.input,
|
|
2635
|
+
attempt: reAttempt
|
|
2636
|
+
});
|
|
2637
|
+
redispatched += 1;
|
|
2638
|
+
}
|
|
2639
|
+
return {
|
|
2640
|
+
runId,
|
|
2641
|
+
status: run.status,
|
|
2642
|
+
redispatched
|
|
2643
|
+
};
|
|
2644
|
+
}
|
|
2645
|
+
/**
|
|
2587
2646
|
* **Fix-and-replay**: re-run a run (typically a `dead`/`failed` one) with a corrected `input`, as a
|
|
2588
2647
|
* fresh run with clean history. It's a NEW run — `newRunId` defaults to `<runId>~retry~<uuid>` — so
|
|
2589
2648
|
* the original stays inspectable. Returns the new run's id, or null if `runId` is unknown.
|
|
@@ -4071,7 +4130,47 @@ var WorkflowEngine = class {
|
|
|
4071
4130
|
if (existing?.status === "completed") return existing.output;
|
|
4072
4131
|
if (this.pool.size === 0) throw new Error("remote steps require a Transport");
|
|
4073
4132
|
if (step.timeoutMs) return this.callRemoteInMemory(runId, seq, step, input, transport);
|
|
4074
|
-
if (existing?.status === "pending")
|
|
4133
|
+
if (existing?.status === "pending") {
|
|
4134
|
+
if (this.remoteRedispatchMs == null) throw new WorkflowSuspended();
|
|
4135
|
+
if (existing.wakeAt == null) {
|
|
4136
|
+
const wakeAt = this.clock() + this.remoteRedispatchMs;
|
|
4137
|
+
await this.store.saveCheckpoint({
|
|
4138
|
+
...existing,
|
|
4139
|
+
wakeAt
|
|
4140
|
+
});
|
|
4141
|
+
throw new WorkflowSuspended(wakeAt);
|
|
4142
|
+
}
|
|
4143
|
+
if (this.clock() < existing.wakeAt) throw new WorkflowSuspended(existing.wakeAt);
|
|
4144
|
+
if (existing.attempts >= this.remoteRedispatchMax) {
|
|
4145
|
+
throw new RemoteStepError({
|
|
4146
|
+
message: `remote step "${step.name}" lost \u2014 no result after ${existing.attempts} re-dispatch(es)`,
|
|
4147
|
+
code: "remote_step_lost"
|
|
4148
|
+
});
|
|
4149
|
+
}
|
|
4150
|
+
const reAttempt = existing.attempts + 1;
|
|
4151
|
+
const nextDeadline = this.clock() + this.remoteRedispatchMs;
|
|
4152
|
+
const reEnqueuedAt = /* @__PURE__ */ new Date();
|
|
4153
|
+
await this.store.saveCheckpoint({
|
|
4154
|
+
...existing,
|
|
4155
|
+
attempts: reAttempt,
|
|
4156
|
+
wakeAt: nextDeadline,
|
|
4157
|
+
enqueuedAt: reEnqueuedAt,
|
|
4158
|
+
startedAt: reEnqueuedAt,
|
|
4159
|
+
finishedAt: reEnqueuedAt
|
|
4160
|
+
});
|
|
4161
|
+
await this.dispatchRemoteTask({
|
|
4162
|
+
runId,
|
|
4163
|
+
seq,
|
|
4164
|
+
name: step.name,
|
|
4165
|
+
stepId: existing.stepId ?? stepId(runId, seq),
|
|
4166
|
+
group: existing.workerGroup ?? tenantGroup(sanitizeQueueToken(step.name), step.partition),
|
|
4167
|
+
input: existing.input,
|
|
4168
|
+
priority: admission?.priority,
|
|
4169
|
+
attempt: reAttempt,
|
|
4170
|
+
transport
|
|
4171
|
+
});
|
|
4172
|
+
throw new WorkflowSuspended(nextDeadline);
|
|
4173
|
+
}
|
|
4075
4174
|
let attempt = 1;
|
|
4076
4175
|
if (existing?.status === "failed") {
|
|
4077
4176
|
const maxAttempts = Math.max(1, step.retries ?? 1);
|
|
@@ -4119,18 +4218,17 @@ var WorkflowEngine = class {
|
|
|
4119
4218
|
startedAt: enqueuedAt,
|
|
4120
4219
|
finishedAt: enqueuedAt
|
|
4121
4220
|
});
|
|
4122
|
-
await this.
|
|
4221
|
+
await this.dispatchRemoteTask({
|
|
4123
4222
|
runId,
|
|
4124
4223
|
seq,
|
|
4125
4224
|
name: step.name,
|
|
4126
4225
|
stepId: id,
|
|
4127
4226
|
group: tenantGroup(sanitizeQueueToken(step.name), step.partition),
|
|
4128
4227
|
input,
|
|
4129
|
-
traceparent: this.traceparent?.(),
|
|
4130
|
-
context: this.context?.(),
|
|
4131
4228
|
priority: admission?.priority,
|
|
4132
|
-
attempt
|
|
4133
|
-
|
|
4229
|
+
attempt,
|
|
4230
|
+
transport
|
|
4231
|
+
});
|
|
4134
4232
|
this.emit({
|
|
4135
4233
|
type: "step.started",
|
|
4136
4234
|
runId,
|
|
@@ -4140,6 +4238,23 @@ var WorkflowEngine = class {
|
|
|
4140
4238
|
});
|
|
4141
4239
|
throw new WorkflowSuspended();
|
|
4142
4240
|
}
|
|
4241
|
+
/** Enqueue a remote step task to its worker group via the transport pool. Shared by the initial
|
|
4242
|
+
* dispatch, the opt-in self-heal re-dispatch (see `remoteRedispatchMs`), and the explicit
|
|
4243
|
+
* {@link redispatchPending}. Carries the current trace/context so a re-dispatch is traceable. */
|
|
4244
|
+
dispatchRemoteTask(task) {
|
|
4245
|
+
return this.pool.dispatch({
|
|
4246
|
+
runId: task.runId,
|
|
4247
|
+
seq: task.seq,
|
|
4248
|
+
name: task.name,
|
|
4249
|
+
stepId: task.stepId,
|
|
4250
|
+
group: task.group,
|
|
4251
|
+
input: task.input,
|
|
4252
|
+
traceparent: this.traceparent?.(),
|
|
4253
|
+
context: this.context?.(),
|
|
4254
|
+
priority: task.priority,
|
|
4255
|
+
attempt: task.attempt
|
|
4256
|
+
}, task.transport);
|
|
4257
|
+
}
|
|
4143
4258
|
/**
|
|
4144
4259
|
* Complete a durable remote step from its worker result and resume the run — runs on whichever
|
|
4145
4260
|
* instance receives the result (the dispatching one may be gone), so the run is crash/scale-safe.
|
|
@@ -4376,6 +4491,42 @@ var TERMINAL_RUN_STATUSES = [
|
|
|
4376
4491
|
"dead"
|
|
4377
4492
|
];
|
|
4378
4493
|
|
|
4494
|
+
// src/run-waiting.ts
|
|
4495
|
+
var WEBHOOK_PREFIX = "wh:";
|
|
4496
|
+
var CHILD_PREFIX = "child:";
|
|
4497
|
+
var EVENT_PREFIX = "event:";
|
|
4498
|
+
function classifyWaiterToken(token) {
|
|
4499
|
+
if (token.startsWith(WEBHOOK_PREFIX)) return {
|
|
4500
|
+
on: "webhook",
|
|
4501
|
+
name: token
|
|
4502
|
+
};
|
|
4503
|
+
if (token.startsWith(CHILD_PREFIX)) return {
|
|
4504
|
+
on: "child",
|
|
4505
|
+
name: token.slice(CHILD_PREFIX.length)
|
|
4506
|
+
};
|
|
4507
|
+
if (token.startsWith(EVENT_PREFIX)) return {
|
|
4508
|
+
on: "signal",
|
|
4509
|
+
name: eventNameOf(token)
|
|
4510
|
+
};
|
|
4511
|
+
return {
|
|
4512
|
+
on: "signal",
|
|
4513
|
+
name: token
|
|
4514
|
+
};
|
|
4515
|
+
}
|
|
4516
|
+
__name(classifyWaiterToken, "classifyWaiterToken");
|
|
4517
|
+
function resolveRunWaiting(run, waiterByRun) {
|
|
4518
|
+
if (run.status !== "suspended") return void 0;
|
|
4519
|
+
const waiter = waiterByRun.get(run.id);
|
|
4520
|
+
return waiter ? classifyWaiterToken(waiter.token) : void 0;
|
|
4521
|
+
}
|
|
4522
|
+
__name(resolveRunWaiting, "resolveRunWaiting");
|
|
4523
|
+
function indexWaitersByRun(waiters) {
|
|
4524
|
+
const byRun = /* @__PURE__ */ new Map();
|
|
4525
|
+
for (const w of waiters) if (!byRun.has(w.runId)) byRun.set(w.runId, w);
|
|
4526
|
+
return byRun;
|
|
4527
|
+
}
|
|
4528
|
+
__name(indexWaitersByRun, "indexWaitersByRun");
|
|
4529
|
+
|
|
4379
4530
|
// src/codec-state-store.ts
|
|
4380
4531
|
var CodecStateStore = class {
|
|
4381
4532
|
static {
|
|
@@ -5131,17 +5282,21 @@ var InMemoryTransport = class {
|
|
|
5131
5282
|
attributeOperand,
|
|
5132
5283
|
bindWorkflowClass,
|
|
5133
5284
|
breakpointToken,
|
|
5285
|
+
classifyWaiterToken,
|
|
5134
5286
|
collectMetrics,
|
|
5135
5287
|
createStepLogger,
|
|
5136
5288
|
currentWorkflowCtx,
|
|
5137
5289
|
eventMatchOf,
|
|
5138
5290
|
eventMatches,
|
|
5291
|
+
eventNameOf,
|
|
5139
5292
|
eventPrefix,
|
|
5140
5293
|
eventToken,
|
|
5294
|
+
indexWaitersByRun,
|
|
5141
5295
|
matchesAttributes,
|
|
5142
5296
|
normalizeAttributeRows,
|
|
5143
5297
|
parseDuration,
|
|
5144
5298
|
prevCronFireMs,
|
|
5299
|
+
resolveRunWaiting,
|
|
5145
5300
|
runInWorkflowCtx,
|
|
5146
5301
|
runSchedules,
|
|
5147
5302
|
runStepHandler,
|