@dudousxd/nestjs-durable-core 0.34.0 → 0.35.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 +102 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +102 -37
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -209,7 +209,15 @@ declare function workflowName(ref: WorkflowRef): string;
|
|
|
209
209
|
*/
|
|
210
210
|
type RunStatus =
|
|
211
211
|
/** Created + enqueued by `start`, not yet picked up — a worker will lease and execute it. */
|
|
212
|
-
'pending' | 'running' | 'suspended'
|
|
212
|
+
'pending' | 'running' | 'suspended'
|
|
213
|
+
/**
|
|
214
|
+
* Compensating cancel in progress: `cancel(runId, { compensate: true })` has been requested and the
|
|
215
|
+
* saga undo is running in the background. NON-TERMINAL — the run is still in-flight (counted by the
|
|
216
|
+
* admission gate, re-driven by recovery if the engine crashes mid-compensation), and flips to
|
|
217
|
+
* `cancelled` once compensation completes. A non-compensating cancel skips this and goes straight to
|
|
218
|
+
* `cancelled`.
|
|
219
|
+
*/
|
|
220
|
+
| 'cancelling' | 'completed' | 'failed' | 'cancelled'
|
|
213
221
|
/** Dead-letter: recovery gave up after `maxRecoveryAttempts` (a poison pill). Terminal; inspect it. */
|
|
214
222
|
| 'dead';
|
|
215
223
|
/** One execution of a workflow. The unit of durability and the unit shown in the dashboard. */
|
|
@@ -1751,6 +1759,13 @@ declare class WorkflowEngine {
|
|
|
1751
1759
|
/** Apply a turn's commands: persist recorded local steps, dispatch remote calls, schedule timers.
|
|
1752
1760
|
* Returns the earliest timer deadline to suspend on (or undefined — suspended on a result). */
|
|
1753
1761
|
private applyCommands;
|
|
1762
|
+
/**
|
|
1763
|
+
* Undo the registered saga compensations in reverse and settle the run `cancelled`. Shared by EVERY
|
|
1764
|
+
* exit path of a run with a compensating cancel in flight — whether the resumed replay returned,
|
|
1765
|
+
* suspended, hit continue-as-new, or threw — so a cancel is never lost just because the body didn't
|
|
1766
|
+
* stop at a suspension point. Clears the in-memory cancel flag.
|
|
1767
|
+
*/
|
|
1768
|
+
private compensateAndCancel;
|
|
1754
1769
|
/** The run body, lease held + renewed by {@link execute}. */
|
|
1755
1770
|
private runExecution;
|
|
1756
1771
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -209,7 +209,15 @@ declare function workflowName(ref: WorkflowRef): string;
|
|
|
209
209
|
*/
|
|
210
210
|
type RunStatus =
|
|
211
211
|
/** Created + enqueued by `start`, not yet picked up — a worker will lease and execute it. */
|
|
212
|
-
'pending' | 'running' | 'suspended'
|
|
212
|
+
'pending' | 'running' | 'suspended'
|
|
213
|
+
/**
|
|
214
|
+
* Compensating cancel in progress: `cancel(runId, { compensate: true })` has been requested and the
|
|
215
|
+
* saga undo is running in the background. NON-TERMINAL — the run is still in-flight (counted by the
|
|
216
|
+
* admission gate, re-driven by recovery if the engine crashes mid-compensation), and flips to
|
|
217
|
+
* `cancelled` once compensation completes. A non-compensating cancel skips this and goes straight to
|
|
218
|
+
* `cancelled`.
|
|
219
|
+
*/
|
|
220
|
+
| 'cancelling' | 'completed' | 'failed' | 'cancelled'
|
|
213
221
|
/** Dead-letter: recovery gave up after `maxRecoveryAttempts` (a poison pill). Terminal; inspect it. */
|
|
214
222
|
| 'dead';
|
|
215
223
|
/** One execution of a workflow. The unit of durability and the unit shown in the dashboard. */
|
|
@@ -1751,6 +1759,13 @@ declare class WorkflowEngine {
|
|
|
1751
1759
|
/** Apply a turn's commands: persist recorded local steps, dispatch remote calls, schedule timers.
|
|
1752
1760
|
* Returns the earliest timer deadline to suspend on (or undefined — suspended on a result). */
|
|
1753
1761
|
private applyCommands;
|
|
1762
|
+
/**
|
|
1763
|
+
* Undo the registered saga compensations in reverse and settle the run `cancelled`. Shared by EVERY
|
|
1764
|
+
* exit path of a run with a compensating cancel in flight — whether the resumed replay returned,
|
|
1765
|
+
* suspended, hit continue-as-new, or threw — so a cancel is never lost just because the body didn't
|
|
1766
|
+
* stop at a suspension point. Clears the in-memory cancel flag.
|
|
1767
|
+
*/
|
|
1768
|
+
private compensateAndCancel;
|
|
1754
1769
|
/** The run body, lease held + renewed by {@link execute}. */
|
|
1755
1770
|
private runExecution;
|
|
1756
1771
|
/**
|
package/dist/index.js
CHANGED
|
@@ -733,7 +733,8 @@ var SingletonGate = class {
|
|
|
733
733
|
statuses: [
|
|
734
734
|
"pending",
|
|
735
735
|
"running",
|
|
736
|
-
"suspended"
|
|
736
|
+
"suspended",
|
|
737
|
+
"cancelling"
|
|
737
738
|
]
|
|
738
739
|
});
|
|
739
740
|
if (queued.length >= cap) {
|
|
@@ -751,7 +752,8 @@ var SingletonGate = class {
|
|
|
751
752
|
workflow: run.workflow,
|
|
752
753
|
statuses: [
|
|
753
754
|
"running",
|
|
754
|
-
"suspended"
|
|
755
|
+
"suspended",
|
|
756
|
+
"cancelling"
|
|
755
757
|
]
|
|
756
758
|
})).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime() || a.id.localeCompare(b.id));
|
|
757
759
|
const idx = inflight.findIndex((r) => r.id === run.id);
|
|
@@ -1900,14 +1902,16 @@ var WorkflowEngine = class {
|
|
|
1900
1902
|
continue;
|
|
1901
1903
|
}
|
|
1902
1904
|
await this.store.releaseRunLock(run.id);
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1905
|
+
if (run.status !== "cancelling") {
|
|
1906
|
+
await this.store.updateRun(run.id, {
|
|
1907
|
+
status: "pending",
|
|
1908
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1907
1911
|
await this.runDispatcher.dispatch(run.id);
|
|
1908
1912
|
results.push({
|
|
1909
1913
|
runId: run.id,
|
|
1910
|
-
status: "pending"
|
|
1914
|
+
status: run.status === "cancelling" ? "cancelling" : "pending"
|
|
1911
1915
|
});
|
|
1912
1916
|
}
|
|
1913
1917
|
return results;
|
|
@@ -2026,7 +2030,7 @@ var WorkflowEngine = class {
|
|
|
2026
2030
|
* `start` when a call site needs the outcome — `await start(...); const r = await waitForRun(id)`.
|
|
2027
2031
|
*/
|
|
2028
2032
|
waitForRun(runId, opts) {
|
|
2029
|
-
const isSettled = /* @__PURE__ */ __name((s) => s !== "pending" && s !== "running", "isSettled");
|
|
2033
|
+
const isSettled = /* @__PURE__ */ __name((s) => s !== "pending" && s !== "running" && s !== "cancelling", "isSettled");
|
|
2030
2034
|
const toResult = /* @__PURE__ */ __name((run) => ({
|
|
2031
2035
|
runId,
|
|
2032
2036
|
status: run.status,
|
|
@@ -2190,8 +2194,19 @@ var WorkflowEngine = class {
|
|
|
2190
2194
|
error: run.error
|
|
2191
2195
|
};
|
|
2192
2196
|
}
|
|
2197
|
+
if (run.status === "cancelling") {
|
|
2198
|
+
return {
|
|
2199
|
+
runId,
|
|
2200
|
+
status: "cancelling"
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2193
2203
|
if (opts?.compensate && (run.status === "suspended" || run.status === "running")) {
|
|
2194
2204
|
this.cancelRequested.add(runId);
|
|
2205
|
+
await this.store.updateRun(runId, {
|
|
2206
|
+
status: "cancelling",
|
|
2207
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2208
|
+
});
|
|
2209
|
+
run.status = "cancelling";
|
|
2195
2210
|
if (this.controlPlane) {
|
|
2196
2211
|
void this.controlPlane.publishControl({
|
|
2197
2212
|
kind: "cancel",
|
|
@@ -2205,7 +2220,7 @@ var WorkflowEngine = class {
|
|
|
2205
2220
|
await this.cancelChildren(runId, opts);
|
|
2206
2221
|
return {
|
|
2207
2222
|
runId,
|
|
2208
|
-
status:
|
|
2223
|
+
status: "cancelling"
|
|
2209
2224
|
};
|
|
2210
2225
|
}
|
|
2211
2226
|
const error = {
|
|
@@ -2596,6 +2611,36 @@ var WorkflowEngine = class {
|
|
|
2596
2611
|
};
|
|
2597
2612
|
}
|
|
2598
2613
|
async runRemoteExecution(run, registered) {
|
|
2614
|
+
if (run.status === "cancelling") {
|
|
2615
|
+
const error = {
|
|
2616
|
+
message: "cancelled"
|
|
2617
|
+
};
|
|
2618
|
+
await this.store.updateRun(run.id, {
|
|
2619
|
+
status: "cancelled",
|
|
2620
|
+
error,
|
|
2621
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2622
|
+
});
|
|
2623
|
+
this.emit({
|
|
2624
|
+
type: "run.failed",
|
|
2625
|
+
runId: run.id,
|
|
2626
|
+
workflow: run.workflow,
|
|
2627
|
+
error
|
|
2628
|
+
});
|
|
2629
|
+
this.notifyCancelled(run.id);
|
|
2630
|
+
if (this.controlPlane) {
|
|
2631
|
+
void this.controlPlane.publishControl({
|
|
2632
|
+
kind: "cancel",
|
|
2633
|
+
runId: run.id,
|
|
2634
|
+
from: this.instanceId
|
|
2635
|
+
}).catch(() => void 0);
|
|
2636
|
+
}
|
|
2637
|
+
await this.store.releaseRunLock(run.id);
|
|
2638
|
+
return {
|
|
2639
|
+
runId: run.id,
|
|
2640
|
+
status: "cancelled",
|
|
2641
|
+
error
|
|
2642
|
+
};
|
|
2643
|
+
}
|
|
2599
2644
|
const remote = registered.remote;
|
|
2600
2645
|
if (run.status === "pending") {
|
|
2601
2646
|
await this.store.updateRun(run.id, {
|
|
@@ -2689,6 +2734,15 @@ var WorkflowEngine = class {
|
|
|
2689
2734
|
status: "cancelled"
|
|
2690
2735
|
};
|
|
2691
2736
|
}
|
|
2737
|
+
const fresh = await this.store.getRun(run.id);
|
|
2738
|
+
if (fresh && (fresh.status === "cancelled" || fresh.status === "completed" || fresh.status === "dead")) {
|
|
2739
|
+
return {
|
|
2740
|
+
runId: run.id,
|
|
2741
|
+
status: fresh.status,
|
|
2742
|
+
output: fresh.output,
|
|
2743
|
+
error: fresh.error
|
|
2744
|
+
};
|
|
2745
|
+
}
|
|
2692
2746
|
const wakeAt = await this.applyCommands(run, decision.commands);
|
|
2693
2747
|
return this.settleRun(run, {
|
|
2694
2748
|
kind: "suspended",
|
|
@@ -2856,8 +2910,41 @@ var WorkflowEngine = class {
|
|
|
2856
2910
|
}
|
|
2857
2911
|
return wakeAt;
|
|
2858
2912
|
}
|
|
2913
|
+
/**
|
|
2914
|
+
* Undo the registered saga compensations in reverse and settle the run `cancelled`. Shared by EVERY
|
|
2915
|
+
* exit path of a run with a compensating cancel in flight — whether the resumed replay returned,
|
|
2916
|
+
* suspended, hit continue-as-new, or threw — so a cancel is never lost just because the body didn't
|
|
2917
|
+
* stop at a suspension point. Clears the in-memory cancel flag.
|
|
2918
|
+
*/
|
|
2919
|
+
async compensateAndCancel(run, compensations) {
|
|
2920
|
+
this.cancelRequested.delete(run.id);
|
|
2921
|
+
for (let i = compensations.length - 1; i >= 0; i -= 1) {
|
|
2922
|
+
const comp = compensations[i];
|
|
2923
|
+
if (comp) await this.runCompensation(run, comp);
|
|
2924
|
+
}
|
|
2925
|
+
const error = {
|
|
2926
|
+
message: "cancelled"
|
|
2927
|
+
};
|
|
2928
|
+
await this.store.updateRun(run.id, {
|
|
2929
|
+
status: "cancelled",
|
|
2930
|
+
error,
|
|
2931
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2932
|
+
});
|
|
2933
|
+
this.emit({
|
|
2934
|
+
type: "run.failed",
|
|
2935
|
+
runId: run.id,
|
|
2936
|
+
workflow: run.workflow,
|
|
2937
|
+
error
|
|
2938
|
+
});
|
|
2939
|
+
return {
|
|
2940
|
+
runId: run.id,
|
|
2941
|
+
status: "cancelled",
|
|
2942
|
+
error
|
|
2943
|
+
};
|
|
2944
|
+
}
|
|
2859
2945
|
/** The run body, lease held + renewed by {@link execute}. */
|
|
2860
2946
|
async runExecution(run, fn, registered) {
|
|
2947
|
+
if (run.status === "cancelling") this.cancelRequested.add(run.id);
|
|
2861
2948
|
if (run.status === "pending") {
|
|
2862
2949
|
await this.store.updateRun(run.id, {
|
|
2863
2950
|
status: "running",
|
|
@@ -2870,7 +2957,7 @@ var WorkflowEngine = class {
|
|
|
2870
2957
|
workflow: run.workflow
|
|
2871
2958
|
});
|
|
2872
2959
|
}
|
|
2873
|
-
if (registered?.singleton && !await this.singletons.admit(run, registered.singleton)) {
|
|
2960
|
+
if (run.status !== "cancelling" && registered?.singleton && !await this.singletons.admit(run, registered.singleton)) {
|
|
2874
2961
|
const wakeAt = this.singletons.retryWakeAt();
|
|
2875
2962
|
await this.store.updateRun(run.id, {
|
|
2876
2963
|
status: "suspended",
|
|
@@ -2895,12 +2982,14 @@ var WorkflowEngine = class {
|
|
|
2895
2982
|
const ctx = createWorkflowCtx(this.ctxHostFor(replay), run.id, compensations, run.workflow);
|
|
2896
2983
|
try {
|
|
2897
2984
|
const output = await fn(ctx, run.input);
|
|
2985
|
+
if (this.cancelRequested.has(run.id)) return this.compensateAndCancel(run, compensations);
|
|
2898
2986
|
return this.settleRun(run, {
|
|
2899
2987
|
kind: "completed",
|
|
2900
2988
|
output
|
|
2901
2989
|
});
|
|
2902
2990
|
} catch (err) {
|
|
2903
2991
|
if (err instanceof ContinueAsNew) {
|
|
2992
|
+
if (this.cancelRequested.has(run.id)) return this.compensateAndCancel(run, compensations);
|
|
2904
2993
|
await this.store.updateRun(run.id, {
|
|
2905
2994
|
status: "completed",
|
|
2906
2995
|
output: void 0,
|
|
@@ -2924,37 +3013,13 @@ var WorkflowEngine = class {
|
|
|
2924
3013
|
};
|
|
2925
3014
|
}
|
|
2926
3015
|
if (err instanceof WorkflowSuspended) {
|
|
2927
|
-
if (this.cancelRequested.has(run.id))
|
|
2928
|
-
this.cancelRequested.delete(run.id);
|
|
2929
|
-
for (let i = compensations.length - 1; i >= 0; i -= 1) {
|
|
2930
|
-
const comp = compensations[i];
|
|
2931
|
-
if (comp) await this.runCompensation(run, comp);
|
|
2932
|
-
}
|
|
2933
|
-
const error2 = {
|
|
2934
|
-
message: "cancelled"
|
|
2935
|
-
};
|
|
2936
|
-
await this.store.updateRun(run.id, {
|
|
2937
|
-
status: "cancelled",
|
|
2938
|
-
error: error2,
|
|
2939
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
2940
|
-
});
|
|
2941
|
-
this.emit({
|
|
2942
|
-
type: "run.failed",
|
|
2943
|
-
runId: run.id,
|
|
2944
|
-
workflow: run.workflow,
|
|
2945
|
-
error: error2
|
|
2946
|
-
});
|
|
2947
|
-
return {
|
|
2948
|
-
runId: run.id,
|
|
2949
|
-
status: "cancelled",
|
|
2950
|
-
error: error2
|
|
2951
|
-
};
|
|
2952
|
-
}
|
|
3016
|
+
if (this.cancelRequested.has(run.id)) return this.compensateAndCancel(run, compensations);
|
|
2953
3017
|
return this.settleRun(run, {
|
|
2954
3018
|
kind: "suspended",
|
|
2955
3019
|
wakeAt: err.wakeAt
|
|
2956
3020
|
});
|
|
2957
3021
|
}
|
|
3022
|
+
if (this.cancelRequested.has(run.id)) return this.compensateAndCancel(run, compensations);
|
|
2958
3023
|
const error = {
|
|
2959
3024
|
message: err instanceof Error ? err.message : String(err),
|
|
2960
3025
|
stack: err instanceof Error ? err.stack : void 0
|
|
@@ -3908,7 +3973,7 @@ var InMemoryStateStore = class {
|
|
|
3908
3973
|
async listIncompleteRuns() {
|
|
3909
3974
|
return [
|
|
3910
3975
|
...this.runs.values()
|
|
3911
|
-
].filter((r) => r.status === "running").map((r) => ({
|
|
3976
|
+
].filter((r) => r.status === "running" || r.status === "cancelling").map((r) => ({
|
|
3912
3977
|
...r
|
|
3913
3978
|
}));
|
|
3914
3979
|
}
|