@dudousxd/nestjs-durable-core 0.49.3 → 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 +109 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +49 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.js +109 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1814,6 +1814,8 @@ var WorkflowEngine = class {
|
|
|
1814
1814
|
maxRecoveryAttempts;
|
|
1815
1815
|
remoteAdvanceSilenceMs;
|
|
1816
1816
|
reconcileMs;
|
|
1817
|
+
remoteRedispatchMs;
|
|
1818
|
+
remoteRedispatchMax;
|
|
1817
1819
|
webhookUrl;
|
|
1818
1820
|
traceparent;
|
|
1819
1821
|
context;
|
|
@@ -1885,6 +1887,8 @@ var WorkflowEngine = class {
|
|
|
1885
1887
|
this.maxRecoveryAttempts = deps.maxRecoveryAttempts;
|
|
1886
1888
|
this.remoteAdvanceSilenceMs = deps.remoteAdvanceSilenceMs;
|
|
1887
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);
|
|
1888
1892
|
this.webhookUrl = deps.webhookUrl;
|
|
1889
1893
|
this.traceparent = deps.traceparent;
|
|
1890
1894
|
this.context = deps.context;
|
|
@@ -2596,6 +2600,49 @@ var WorkflowEngine = class {
|
|
|
2596
2600
|
};
|
|
2597
2601
|
}
|
|
2598
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
|
+
/**
|
|
2599
2646
|
* **Fix-and-replay**: re-run a run (typically a `dead`/`failed` one) with a corrected `input`, as a
|
|
2600
2647
|
* fresh run with clean history. It's a NEW run — `newRunId` defaults to `<runId>~retry~<uuid>` — so
|
|
2601
2648
|
* the original stays inspectable. Returns the new run's id, or null if `runId` is unknown.
|
|
@@ -4083,7 +4130,47 @@ var WorkflowEngine = class {
|
|
|
4083
4130
|
if (existing?.status === "completed") return existing.output;
|
|
4084
4131
|
if (this.pool.size === 0) throw new Error("remote steps require a Transport");
|
|
4085
4132
|
if (step.timeoutMs) return this.callRemoteInMemory(runId, seq, step, input, transport);
|
|
4086
|
-
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
|
+
}
|
|
4087
4174
|
let attempt = 1;
|
|
4088
4175
|
if (existing?.status === "failed") {
|
|
4089
4176
|
const maxAttempts = Math.max(1, step.retries ?? 1);
|
|
@@ -4131,18 +4218,17 @@ var WorkflowEngine = class {
|
|
|
4131
4218
|
startedAt: enqueuedAt,
|
|
4132
4219
|
finishedAt: enqueuedAt
|
|
4133
4220
|
});
|
|
4134
|
-
await this.
|
|
4221
|
+
await this.dispatchRemoteTask({
|
|
4135
4222
|
runId,
|
|
4136
4223
|
seq,
|
|
4137
4224
|
name: step.name,
|
|
4138
4225
|
stepId: id,
|
|
4139
4226
|
group: tenantGroup(sanitizeQueueToken(step.name), step.partition),
|
|
4140
4227
|
input,
|
|
4141
|
-
traceparent: this.traceparent?.(),
|
|
4142
|
-
context: this.context?.(),
|
|
4143
4228
|
priority: admission?.priority,
|
|
4144
|
-
attempt
|
|
4145
|
-
|
|
4229
|
+
attempt,
|
|
4230
|
+
transport
|
|
4231
|
+
});
|
|
4146
4232
|
this.emit({
|
|
4147
4233
|
type: "step.started",
|
|
4148
4234
|
runId,
|
|
@@ -4152,6 +4238,23 @@ var WorkflowEngine = class {
|
|
|
4152
4238
|
});
|
|
4153
4239
|
throw new WorkflowSuspended();
|
|
4154
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
|
+
}
|
|
4155
4258
|
/**
|
|
4156
4259
|
* Complete a durable remote step from its worker result and resume the run — runs on whichever
|
|
4157
4260
|
* instance receives the result (the dispatching one may be gone), so the run is crash/scale-safe.
|