@dudousxd/nestjs-durable-core 0.75.1 → 0.76.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 +77 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +77 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3861,6 +3861,12 @@ var WorkflowEngine = class {
|
|
|
3861
3861
|
updateValidators = /* @__PURE__ */ new Map();
|
|
3862
3862
|
/** Runs being cancelled WITH saga compensation — see `cancel({ compensate: true })`. */
|
|
3863
3863
|
cancelRequested = /* @__PURE__ */ new Set();
|
|
3864
|
+
/**
|
|
3865
|
+
* Seqs of the dispatched remote steps a turn suspended on, by run, while that turn executes on
|
|
3866
|
+
* THIS instance — so the turn can re-check them once its lease is released (see
|
|
3867
|
+
* {@link redriveSettledSteps}).
|
|
3868
|
+
*/
|
|
3869
|
+
awaitedRemoteSteps = /* @__PURE__ */ new Map();
|
|
3864
3870
|
/** Flow-control admission backend for remote steps (see {@link registerQueue}). */
|
|
3865
3871
|
admission;
|
|
3866
3872
|
/** Runs on THIS instance blocked on admission, by queue — woken early on a freed-slot signal. */
|
|
@@ -4335,7 +4341,18 @@ var WorkflowEngine = class {
|
|
|
4335
4341
|
createdAt: now,
|
|
4336
4342
|
updatedAt: now
|
|
4337
4343
|
};
|
|
4338
|
-
|
|
4344
|
+
try {
|
|
4345
|
+
await this.store.createRun(run);
|
|
4346
|
+
} catch (err) {
|
|
4347
|
+
const winner = await this.store.getRun(runId).catch(() => null);
|
|
4348
|
+
if (!winner) throw err;
|
|
4349
|
+
return {
|
|
4350
|
+
runId,
|
|
4351
|
+
status: winner.status,
|
|
4352
|
+
output: winner.output,
|
|
4353
|
+
error: winner.error
|
|
4354
|
+
};
|
|
4355
|
+
}
|
|
4339
4356
|
await this.runDispatcher.dispatch(runId);
|
|
4340
4357
|
if (this.controlPlane) {
|
|
4341
4358
|
void this.controlPlane.publishControl({
|
|
@@ -5264,6 +5281,24 @@ var WorkflowEngine = class {
|
|
|
5264
5281
|
* itself. Only re-drives for a group with no waiter left whose checkpoints are all settled, which
|
|
5265
5282
|
* the next turn is then guaranteed to see — so it cannot loop.
|
|
5266
5283
|
*/
|
|
5284
|
+
/**
|
|
5285
|
+
* Re-drive a run that just suspended on dispatched remote steps which, by now, have a result.
|
|
5286
|
+
*
|
|
5287
|
+
* Same race as {@link redriveSettledChildren}, for `ctx.step`: a result landing while the turn
|
|
5288
|
+
* that dispatched it still holds the lease has its wake dropped by {@link execute}, and the turn
|
|
5289
|
+
* then suspends with nothing left to wake it before the step's redispatch deadline. Called after
|
|
5290
|
+
* the lease is released, so any later result wakes the run itself; it only re-drives for a
|
|
5291
|
+
* checkpoint that is already settled, which the next turn replays past — so it cannot loop.
|
|
5292
|
+
*/
|
|
5293
|
+
async redriveSettledSteps(runId, seqs) {
|
|
5294
|
+
for (const seq of seqs) {
|
|
5295
|
+
const cp = await this.store.getCheckpoint(runId, seq);
|
|
5296
|
+
if (cp && (cp.status === "completed" || cp.status === "failed")) {
|
|
5297
|
+
await this.resumeImplicit(runId);
|
|
5298
|
+
return;
|
|
5299
|
+
}
|
|
5300
|
+
}
|
|
5301
|
+
}
|
|
5267
5302
|
async redriveSettledChildren(runId, fan) {
|
|
5268
5303
|
if (!this.store.hasSignalWaiter) return;
|
|
5269
5304
|
for (const wait of fan.waits) {
|
|
@@ -6718,6 +6753,9 @@ var WorkflowEngine = class {
|
|
|
6718
6753
|
const replay = /* @__PURE__ */ new Map();
|
|
6719
6754
|
for (const cp of snapshot) replay.set(cp.seq, cp);
|
|
6720
6755
|
let suspendedOnFan;
|
|
6756
|
+
let suspendedOnSteps = false;
|
|
6757
|
+
const awaitedSteps = /* @__PURE__ */ new Set();
|
|
6758
|
+
this.awaitedRemoteSteps.set(run.id, awaitedSteps);
|
|
6721
6759
|
const ctx = createWorkflowCtx(this.ctxHostFor(replay, run.namespace), run.id, compensations, run.workflow, registered?.searchAttributesSchema);
|
|
6722
6760
|
try {
|
|
6723
6761
|
const output = await runInWorkflowCtx(ctx, () => fn(ctx, run.input));
|
|
@@ -6774,6 +6812,7 @@ var WorkflowEngine = class {
|
|
|
6774
6812
|
wakeAt: err.wakeAt
|
|
6775
6813
|
});
|
|
6776
6814
|
suspendedOnFan = err.fan;
|
|
6815
|
+
suspendedOnSteps = suspended.status === "suspended";
|
|
6777
6816
|
return suspended;
|
|
6778
6817
|
}
|
|
6779
6818
|
if (this.cancelRequested.has(run.id)) {
|
|
@@ -6793,10 +6832,16 @@ var WorkflowEngine = class {
|
|
|
6793
6832
|
error: error2
|
|
6794
6833
|
});
|
|
6795
6834
|
} finally {
|
|
6835
|
+
if (this.awaitedRemoteSteps.get(run.id) === awaitedSteps) {
|
|
6836
|
+
this.awaitedRemoteSteps.delete(run.id);
|
|
6837
|
+
}
|
|
6796
6838
|
await this.store.releaseRunLock(run.id);
|
|
6797
6839
|
if (suspendedOnFan) {
|
|
6798
6840
|
void this.redriveSettledChildren(run.id, suspendedOnFan).catch(() => void 0);
|
|
6799
6841
|
}
|
|
6842
|
+
if (suspendedOnSteps && awaitedSteps.size > 0) {
|
|
6843
|
+
void this.redriveSettledSteps(run.id, awaitedSteps).catch(() => void 0);
|
|
6844
|
+
}
|
|
6800
6845
|
}
|
|
6801
6846
|
}
|
|
6802
6847
|
/**
|
|
@@ -7216,13 +7261,16 @@ var WorkflowEngine = class {
|
|
|
7216
7261
|
if (this.pool.size === 0) throw new Error("remote steps require a Transport");
|
|
7217
7262
|
if (step.timeoutMs) return this.callRemoteInMemory(runId, seq, step, input, transport, group);
|
|
7218
7263
|
if (existing?.status === "pending") {
|
|
7264
|
+
this.awaitedRemoteSteps.get(runId)?.add(seq);
|
|
7219
7265
|
if (this.remoteRedispatchMs == null) throw new WorkflowSuspended();
|
|
7220
7266
|
if (existing.wakeAt == null) {
|
|
7221
7267
|
const wakeAt = this.clock() + this.remoteRedispatchMs;
|
|
7222
|
-
await this.
|
|
7268
|
+
if (!await this.savePendingCheckpoint({
|
|
7223
7269
|
...existing,
|
|
7224
7270
|
wakeAt
|
|
7225
|
-
})
|
|
7271
|
+
})) {
|
|
7272
|
+
return this.callRemote(runId, seq, step, input, queue, transport, void 0, admission, routing);
|
|
7273
|
+
}
|
|
7226
7274
|
throw new WorkflowSuspended(wakeAt);
|
|
7227
7275
|
}
|
|
7228
7276
|
if (this.clock() < existing.wakeAt) throw new WorkflowSuspended(existing.wakeAt);
|
|
@@ -7235,7 +7283,7 @@ var WorkflowEngine = class {
|
|
|
7235
7283
|
const reAttempt = existing.attempts + 1;
|
|
7236
7284
|
const nextDeadline = this.clock() + this.remoteRedispatchMs;
|
|
7237
7285
|
const reEnqueuedAt = /* @__PURE__ */ new Date();
|
|
7238
|
-
await this.
|
|
7286
|
+
const claimed = await this.savePendingCheckpoint({
|
|
7239
7287
|
...existing,
|
|
7240
7288
|
attempts: reAttempt,
|
|
7241
7289
|
wakeAt: nextDeadline,
|
|
@@ -7249,6 +7297,9 @@ var WorkflowEngine = class {
|
|
|
7249
7297
|
lostStepEvent(this.clock(), reAttempt, "redispatched")
|
|
7250
7298
|
]
|
|
7251
7299
|
});
|
|
7300
|
+
if (!claimed) {
|
|
7301
|
+
return this.callRemote(runId, seq, step, input, queue, transport, void 0, admission, routing);
|
|
7302
|
+
}
|
|
7252
7303
|
await this.dispatchRemoteTask({
|
|
7253
7304
|
runId,
|
|
7254
7305
|
seq,
|
|
@@ -7336,8 +7387,15 @@ var WorkflowEngine = class {
|
|
|
7336
7387
|
name: step.name,
|
|
7337
7388
|
kind: "remote"
|
|
7338
7389
|
});
|
|
7390
|
+
this.awaitedRemoteSteps.get(runId)?.add(seq);
|
|
7339
7391
|
throw new WorkflowSuspended();
|
|
7340
7392
|
}
|
|
7393
|
+
/** {@link StateStore.saveCheckpointIfPending}, or a plain save on a store without it. */
|
|
7394
|
+
async savePendingCheckpoint(checkpoint) {
|
|
7395
|
+
if (this.store.saveCheckpointIfPending) return this.store.saveCheckpointIfPending(checkpoint);
|
|
7396
|
+
await this.store.saveCheckpoint(checkpoint);
|
|
7397
|
+
return true;
|
|
7398
|
+
}
|
|
7341
7399
|
/** Enqueue a remote step task to its worker group via the transport pool. Shared by the initial
|
|
7342
7400
|
* dispatch, the opt-in self-heal re-dispatch (see `remoteRedispatchMs`), and the explicit
|
|
7343
7401
|
* {@link redispatchPending}. Carries the current trace/context so a re-dispatch is traceable. */
|
|
@@ -7368,7 +7426,7 @@ var WorkflowEngine = class {
|
|
|
7368
7426
|
if (run && (run.status === "cancelled" || run.status === "completed")) return;
|
|
7369
7427
|
const finishedAt = /* @__PURE__ */ new Date();
|
|
7370
7428
|
const startedAt = result.startedAt ? new Date(result.startedAt) : cp.startedAt;
|
|
7371
|
-
await this.
|
|
7429
|
+
const claimed = await this.savePendingCheckpoint({
|
|
7372
7430
|
...cp,
|
|
7373
7431
|
status: result.status,
|
|
7374
7432
|
output: result.status === "completed" ? result.output : cp.output,
|
|
@@ -7377,6 +7435,7 @@ var WorkflowEngine = class {
|
|
|
7377
7435
|
startedAt,
|
|
7378
7436
|
finishedAt
|
|
7379
7437
|
});
|
|
7438
|
+
if (!claimed) return;
|
|
7380
7439
|
this.emit({
|
|
7381
7440
|
type: result.status === "completed" ? "step.completed" : "step.failed",
|
|
7382
7441
|
runId: result.runId,
|
|
@@ -7769,6 +7828,9 @@ var CodecStateStore = class {
|
|
|
7769
7828
|
/** Forwarded verbatim (deletes touch no payload). Bound only when the inner store has it, so the
|
|
7770
7829
|
* engine's per-run fallback still kicks in through the wrapper. */
|
|
7771
7830
|
deleteRuns;
|
|
7831
|
+
/** Encodes like {@link saveCheckpoint}. Bound only when the inner store has it, so the engine's
|
|
7832
|
+
* plain-save fallback still kicks in through the wrapper. */
|
|
7833
|
+
saveCheckpointIfPending;
|
|
7772
7834
|
/** Forwarded verbatim — retention selects by status/updatedAt/scope, never by payload. Bound only
|
|
7773
7835
|
* when the inner store prunes, so the retention poller still sees (and warns about) its absence. */
|
|
7774
7836
|
pruneTerminalRuns;
|
|
@@ -7796,6 +7858,8 @@ var CodecStateStore = class {
|
|
|
7796
7858
|
if (inner.tryAdmitSingleton) this.tryAdmitSingleton = inner.tryAdmitSingleton.bind(inner);
|
|
7797
7859
|
const deleteRuns = inner.deleteRuns;
|
|
7798
7860
|
if (deleteRuns) this.deleteRuns = (ids) => deleteRuns.call(inner, ids);
|
|
7861
|
+
const saveIfPending = inner.saveCheckpointIfPending;
|
|
7862
|
+
if (saveIfPending) this.saveCheckpointIfPending = (cp) => saveIfPending.call(inner, this.encCp(cp));
|
|
7799
7863
|
const prune = inner.pruneTerminalRuns;
|
|
7800
7864
|
if (prune) this.pruneTerminalRuns = (policy, now, limit) => prune.call(inner, policy, now, limit);
|
|
7801
7865
|
const count = inner.countRuns;
|
|
@@ -8359,6 +8423,14 @@ var InMemoryStateStore = class {
|
|
|
8359
8423
|
...checkpoint
|
|
8360
8424
|
});
|
|
8361
8425
|
}
|
|
8426
|
+
async saveCheckpointIfPending(checkpoint) {
|
|
8427
|
+
const key = this.key(checkpoint.runId, checkpoint.seq);
|
|
8428
|
+
if (this.checkpoints.get(key)?.status !== "pending") return false;
|
|
8429
|
+
this.checkpoints.set(key, {
|
|
8430
|
+
...checkpoint
|
|
8431
|
+
});
|
|
8432
|
+
return true;
|
|
8433
|
+
}
|
|
8362
8434
|
async listIncompleteRuns(namespace) {
|
|
8363
8435
|
return [
|
|
8364
8436
|
...this.runs.values()
|