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