@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 CHANGED
@@ -804,7 +804,8 @@ var SingletonGate = class {
804
804
  statuses: [
805
805
  "pending",
806
806
  "running",
807
- "suspended"
807
+ "suspended",
808
+ "cancelling"
808
809
  ]
809
810
  });
810
811
  if (queued.length >= cap) {
@@ -822,7 +823,8 @@ var SingletonGate = class {
822
823
  workflow: run.workflow,
823
824
  statuses: [
824
825
  "running",
825
- "suspended"
826
+ "suspended",
827
+ "cancelling"
826
828
  ]
827
829
  })).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime() || a.id.localeCompare(b.id));
828
830
  const idx = inflight.findIndex((r) => r.id === run.id);
@@ -1971,14 +1973,16 @@ var WorkflowEngine = class {
1971
1973
  continue;
1972
1974
  }
1973
1975
  await this.store.releaseRunLock(run.id);
1974
- await this.store.updateRun(run.id, {
1975
- status: "pending",
1976
- updatedAt: /* @__PURE__ */ new Date()
1977
- });
1976
+ if (run.status !== "cancelling") {
1977
+ await this.store.updateRun(run.id, {
1978
+ status: "pending",
1979
+ updatedAt: /* @__PURE__ */ new Date()
1980
+ });
1981
+ }
1978
1982
  await this.runDispatcher.dispatch(run.id);
1979
1983
  results.push({
1980
1984
  runId: run.id,
1981
- status: "pending"
1985
+ status: run.status === "cancelling" ? "cancelling" : "pending"
1982
1986
  });
1983
1987
  }
1984
1988
  return results;
@@ -2097,7 +2101,7 @@ var WorkflowEngine = class {
2097
2101
  * `start` when a call site needs the outcome — `await start(...); const r = await waitForRun(id)`.
2098
2102
  */
2099
2103
  waitForRun(runId, opts) {
2100
- const isSettled = /* @__PURE__ */ __name((s) => s !== "pending" && s !== "running", "isSettled");
2104
+ const isSettled = /* @__PURE__ */ __name((s) => s !== "pending" && s !== "running" && s !== "cancelling", "isSettled");
2101
2105
  const toResult = /* @__PURE__ */ __name((run) => ({
2102
2106
  runId,
2103
2107
  status: run.status,
@@ -2261,8 +2265,19 @@ var WorkflowEngine = class {
2261
2265
  error: run.error
2262
2266
  };
2263
2267
  }
2268
+ if (run.status === "cancelling") {
2269
+ return {
2270
+ runId,
2271
+ status: "cancelling"
2272
+ };
2273
+ }
2264
2274
  if (opts?.compensate && (run.status === "suspended" || run.status === "running")) {
2265
2275
  this.cancelRequested.add(runId);
2276
+ await this.store.updateRun(runId, {
2277
+ status: "cancelling",
2278
+ updatedAt: /* @__PURE__ */ new Date()
2279
+ });
2280
+ run.status = "cancelling";
2266
2281
  if (this.controlPlane) {
2267
2282
  void this.controlPlane.publishControl({
2268
2283
  kind: "cancel",
@@ -2276,7 +2291,7 @@ var WorkflowEngine = class {
2276
2291
  await this.cancelChildren(runId, opts);
2277
2292
  return {
2278
2293
  runId,
2279
- status: run.status
2294
+ status: "cancelling"
2280
2295
  };
2281
2296
  }
2282
2297
  const error = {
@@ -2667,6 +2682,36 @@ var WorkflowEngine = class {
2667
2682
  };
2668
2683
  }
2669
2684
  async runRemoteExecution(run, registered) {
2685
+ if (run.status === "cancelling") {
2686
+ const error = {
2687
+ message: "cancelled"
2688
+ };
2689
+ await this.store.updateRun(run.id, {
2690
+ status: "cancelled",
2691
+ error,
2692
+ updatedAt: /* @__PURE__ */ new Date()
2693
+ });
2694
+ this.emit({
2695
+ type: "run.failed",
2696
+ runId: run.id,
2697
+ workflow: run.workflow,
2698
+ error
2699
+ });
2700
+ this.notifyCancelled(run.id);
2701
+ if (this.controlPlane) {
2702
+ void this.controlPlane.publishControl({
2703
+ kind: "cancel",
2704
+ runId: run.id,
2705
+ from: this.instanceId
2706
+ }).catch(() => void 0);
2707
+ }
2708
+ await this.store.releaseRunLock(run.id);
2709
+ return {
2710
+ runId: run.id,
2711
+ status: "cancelled",
2712
+ error
2713
+ };
2714
+ }
2670
2715
  const remote = registered.remote;
2671
2716
  if (run.status === "pending") {
2672
2717
  await this.store.updateRun(run.id, {
@@ -2760,6 +2805,15 @@ var WorkflowEngine = class {
2760
2805
  status: "cancelled"
2761
2806
  };
2762
2807
  }
2808
+ const fresh = await this.store.getRun(run.id);
2809
+ if (fresh && (fresh.status === "cancelled" || fresh.status === "completed" || fresh.status === "dead")) {
2810
+ return {
2811
+ runId: run.id,
2812
+ status: fresh.status,
2813
+ output: fresh.output,
2814
+ error: fresh.error
2815
+ };
2816
+ }
2763
2817
  const wakeAt = await this.applyCommands(run, decision.commands);
2764
2818
  return this.settleRun(run, {
2765
2819
  kind: "suspended",
@@ -2927,8 +2981,41 @@ var WorkflowEngine = class {
2927
2981
  }
2928
2982
  return wakeAt;
2929
2983
  }
2984
+ /**
2985
+ * Undo the registered saga compensations in reverse and settle the run `cancelled`. Shared by EVERY
2986
+ * exit path of a run with a compensating cancel in flight — whether the resumed replay returned,
2987
+ * suspended, hit continue-as-new, or threw — so a cancel is never lost just because the body didn't
2988
+ * stop at a suspension point. Clears the in-memory cancel flag.
2989
+ */
2990
+ async compensateAndCancel(run, compensations) {
2991
+ this.cancelRequested.delete(run.id);
2992
+ for (let i = compensations.length - 1; i >= 0; i -= 1) {
2993
+ const comp = compensations[i];
2994
+ if (comp) await this.runCompensation(run, comp);
2995
+ }
2996
+ const error = {
2997
+ message: "cancelled"
2998
+ };
2999
+ await this.store.updateRun(run.id, {
3000
+ status: "cancelled",
3001
+ error,
3002
+ updatedAt: /* @__PURE__ */ new Date()
3003
+ });
3004
+ this.emit({
3005
+ type: "run.failed",
3006
+ runId: run.id,
3007
+ workflow: run.workflow,
3008
+ error
3009
+ });
3010
+ return {
3011
+ runId: run.id,
3012
+ status: "cancelled",
3013
+ error
3014
+ };
3015
+ }
2930
3016
  /** The run body, lease held + renewed by {@link execute}. */
2931
3017
  async runExecution(run, fn, registered) {
3018
+ if (run.status === "cancelling") this.cancelRequested.add(run.id);
2932
3019
  if (run.status === "pending") {
2933
3020
  await this.store.updateRun(run.id, {
2934
3021
  status: "running",
@@ -2941,7 +3028,7 @@ var WorkflowEngine = class {
2941
3028
  workflow: run.workflow
2942
3029
  });
2943
3030
  }
2944
- if (registered?.singleton && !await this.singletons.admit(run, registered.singleton)) {
3031
+ if (run.status !== "cancelling" && registered?.singleton && !await this.singletons.admit(run, registered.singleton)) {
2945
3032
  const wakeAt = this.singletons.retryWakeAt();
2946
3033
  await this.store.updateRun(run.id, {
2947
3034
  status: "suspended",
@@ -2966,12 +3053,14 @@ var WorkflowEngine = class {
2966
3053
  const ctx = createWorkflowCtx(this.ctxHostFor(replay), run.id, compensations, run.workflow);
2967
3054
  try {
2968
3055
  const output = await fn(ctx, run.input);
3056
+ if (this.cancelRequested.has(run.id)) return this.compensateAndCancel(run, compensations);
2969
3057
  return this.settleRun(run, {
2970
3058
  kind: "completed",
2971
3059
  output
2972
3060
  });
2973
3061
  } catch (err) {
2974
3062
  if (err instanceof ContinueAsNew) {
3063
+ if (this.cancelRequested.has(run.id)) return this.compensateAndCancel(run, compensations);
2975
3064
  await this.store.updateRun(run.id, {
2976
3065
  status: "completed",
2977
3066
  output: void 0,
@@ -2995,37 +3084,13 @@ var WorkflowEngine = class {
2995
3084
  };
2996
3085
  }
2997
3086
  if (err instanceof WorkflowSuspended) {
2998
- if (this.cancelRequested.has(run.id)) {
2999
- this.cancelRequested.delete(run.id);
3000
- for (let i = compensations.length - 1; i >= 0; i -= 1) {
3001
- const comp = compensations[i];
3002
- if (comp) await this.runCompensation(run, comp);
3003
- }
3004
- const error2 = {
3005
- message: "cancelled"
3006
- };
3007
- await this.store.updateRun(run.id, {
3008
- status: "cancelled",
3009
- error: error2,
3010
- updatedAt: /* @__PURE__ */ new Date()
3011
- });
3012
- this.emit({
3013
- type: "run.failed",
3014
- runId: run.id,
3015
- workflow: run.workflow,
3016
- error: error2
3017
- });
3018
- return {
3019
- runId: run.id,
3020
- status: "cancelled",
3021
- error: error2
3022
- };
3023
- }
3087
+ if (this.cancelRequested.has(run.id)) return this.compensateAndCancel(run, compensations);
3024
3088
  return this.settleRun(run, {
3025
3089
  kind: "suspended",
3026
3090
  wakeAt: err.wakeAt
3027
3091
  });
3028
3092
  }
3093
+ if (this.cancelRequested.has(run.id)) return this.compensateAndCancel(run, compensations);
3029
3094
  const error = {
3030
3095
  message: err instanceof Error ? err.message : String(err),
3031
3096
  stack: err instanceof Error ? err.stack : void 0
@@ -3979,7 +4044,7 @@ var InMemoryStateStore = class {
3979
4044
  async listIncompleteRuns() {
3980
4045
  return [
3981
4046
  ...this.runs.values()
3982
- ].filter((r) => r.status === "running").map((r) => ({
4047
+ ].filter((r) => r.status === "running" || r.status === "cancelling").map((r) => ({
3983
4048
  ...r
3984
4049
  }));
3985
4050
  }