@medusajs/orchestration 3.0.0-snapshot-20250410112222 → 3.0.0-snapshot-20251104011621

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.
Files changed (40) hide show
  1. package/dist/joiner/remote-joiner.d.ts +3 -2
  2. package/dist/joiner/remote-joiner.d.ts.map +1 -1
  3. package/dist/joiner/remote-joiner.js +310 -141
  4. package/dist/joiner/remote-joiner.js.map +1 -1
  5. package/dist/transaction/datastore/abstract-storage.d.ts +7 -5
  6. package/dist/transaction/datastore/abstract-storage.d.ts.map +1 -1
  7. package/dist/transaction/datastore/abstract-storage.js +3 -3
  8. package/dist/transaction/datastore/abstract-storage.js.map +1 -1
  9. package/dist/transaction/datastore/base-in-memory-storage.d.ts +2 -1
  10. package/dist/transaction/datastore/base-in-memory-storage.d.ts.map +1 -1
  11. package/dist/transaction/datastore/base-in-memory-storage.js +2 -0
  12. package/dist/transaction/datastore/base-in-memory-storage.js.map +1 -1
  13. package/dist/transaction/distributed-transaction.d.ts +23 -6
  14. package/dist/transaction/distributed-transaction.d.ts.map +1 -1
  15. package/dist/transaction/distributed-transaction.js +284 -54
  16. package/dist/transaction/distributed-transaction.js.map +1 -1
  17. package/dist/transaction/errors.d.ts +11 -0
  18. package/dist/transaction/errors.d.ts.map +1 -1
  19. package/dist/transaction/errors.js +34 -2
  20. package/dist/transaction/errors.js.map +1 -1
  21. package/dist/transaction/transaction-orchestrator.d.ts +99 -10
  22. package/dist/transaction/transaction-orchestrator.d.ts.map +1 -1
  23. package/dist/transaction/transaction-orchestrator.js +585 -271
  24. package/dist/transaction/transaction-orchestrator.js.map +1 -1
  25. package/dist/transaction/transaction-step.d.ts +2 -0
  26. package/dist/transaction/transaction-step.d.ts.map +1 -1
  27. package/dist/transaction/transaction-step.js +10 -3
  28. package/dist/transaction/transaction-step.js.map +1 -1
  29. package/dist/transaction/types.d.ts +26 -1
  30. package/dist/transaction/types.d.ts.map +1 -1
  31. package/dist/transaction/types.js.map +1 -1
  32. package/dist/tsconfig.tsbuildinfo +1 -1
  33. package/dist/workflow/global-workflow.d.ts.map +1 -1
  34. package/dist/workflow/global-workflow.js +16 -4
  35. package/dist/workflow/global-workflow.js.map +1 -1
  36. package/dist/workflow/local-workflow.d.ts +3 -1
  37. package/dist/workflow/local-workflow.d.ts.map +1 -1
  38. package/dist/workflow/local-workflow.js +71 -9
  39. package/dist/workflow/local-workflow.js.map +1 -1
  40. package/package.json +8 -26
@@ -1,12 +1,36 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TransactionOrchestrator = void 0;
4
+ const ulid_1 = require("ulid");
4
5
  const distributed_transaction_1 = require("./distributed-transaction");
5
6
  const transaction_step_1 = require("./transaction-step");
6
7
  const types_1 = require("./types");
7
8
  const utils_1 = require("@medusajs/utils");
8
9
  const events_1 = require("events");
9
10
  const errors_1 = require("./errors");
11
+ const canMoveForwardStates = new Set([
12
+ utils_1.TransactionStepState.DONE,
13
+ utils_1.TransactionStepState.FAILED,
14
+ utils_1.TransactionStepState.TIMEOUT,
15
+ utils_1.TransactionStepState.SKIPPED,
16
+ utils_1.TransactionStepState.SKIPPED_FAILURE,
17
+ ]);
18
+ const canMoveBackwardStates = new Set([
19
+ utils_1.TransactionStepState.DONE,
20
+ utils_1.TransactionStepState.REVERTED,
21
+ utils_1.TransactionStepState.FAILED,
22
+ utils_1.TransactionStepState.DORMANT,
23
+ utils_1.TransactionStepState.SKIPPED,
24
+ ]);
25
+ const flagStepsToRevertStates = new Set([
26
+ utils_1.TransactionStepState.DONE,
27
+ utils_1.TransactionStepState.TIMEOUT,
28
+ ]);
29
+ const setStepTimeoutSkipStates = new Set([
30
+ utils_1.TransactionStepState.TIMEOUT,
31
+ utils_1.TransactionStepState.DONE,
32
+ utils_1.TransactionStepState.REVERTED,
33
+ ]);
10
34
  /**
11
35
  * @class TransactionOrchestrator is responsible for managing and executing distributed transactions.
12
36
  * It is based on a single transaction definition, which is used to execute all the transaction steps
@@ -26,6 +50,11 @@ class TransactionOrchestrator extends events_1.EventEmitter {
26
50
  this.parseFlowOptions();
27
51
  }
28
52
  }
53
+ static isExpectedError(error) {
54
+ return (errors_1.SkipCancelledExecutionError.isSkipCancelledExecutionError(error) ||
55
+ errors_1.SkipExecutionError.isSkipExecutionError(error) ||
56
+ errors_1.SkipStepAlreadyFinishedError.isSkipStepAlreadyFinishedError(error));
57
+ }
29
58
  static clone(orchestrator) {
30
59
  return new TransactionOrchestrator({
31
60
  id: orchestrator.id,
@@ -37,7 +66,7 @@ class TransactionOrchestrator extends events_1.EventEmitter {
37
66
  static getKeyName(...params) {
38
67
  return params.join(this.SEPARATOR);
39
68
  }
40
- getPreviousStep(flow, step) {
69
+ static getPreviousStep(flow, step) {
41
70
  const id = step.id.split(".");
42
71
  id.pop();
43
72
  const parentId = id.join(".");
@@ -64,36 +93,26 @@ class TransactionOrchestrator extends events_1.EventEmitter {
64
93
  this.compensateSteps = steps;
65
94
  return steps;
66
95
  }
96
+ static countSiblings(flow, step) {
97
+ const previous = TransactionOrchestrator.getPreviousStep(flow, step);
98
+ return previous.next.length;
99
+ }
67
100
  canMoveForward(flow, previousStep) {
68
- const states = [
69
- utils_1.TransactionStepState.DONE,
70
- utils_1.TransactionStepState.FAILED,
71
- utils_1.TransactionStepState.TIMEOUT,
72
- utils_1.TransactionStepState.SKIPPED,
73
- utils_1.TransactionStepState.SKIPPED_FAILURE,
74
- ];
75
- const siblings = this.getPreviousStep(flow, previousStep).next.map((sib) => flow.steps[sib]);
101
+ const siblings = TransactionOrchestrator.getPreviousStep(flow, previousStep).next.map((sib) => flow.steps[sib]);
76
102
  return (!!previousStep.definition.noWait ||
77
- siblings.every((sib) => states.includes(sib.invoke.state)));
103
+ siblings.every((sib) => canMoveForwardStates.has(sib.invoke.state)));
78
104
  }
79
105
  canMoveBackward(flow, step) {
80
- const states = [
81
- utils_1.TransactionStepState.DONE,
82
- utils_1.TransactionStepState.REVERTED,
83
- utils_1.TransactionStepState.FAILED,
84
- utils_1.TransactionStepState.DORMANT,
85
- utils_1.TransactionStepState.SKIPPED,
86
- ];
87
106
  const siblings = step.next.map((sib) => flow.steps[sib]);
88
107
  return (siblings.length === 0 ||
89
- siblings.every((sib) => states.includes(sib.compensate.state)));
108
+ siblings.every((sib) => canMoveBackwardStates.has(sib.compensate.state)));
90
109
  }
91
110
  canContinue(flow, step) {
92
111
  if (flow.state == types_1.TransactionState.COMPENSATING) {
93
112
  return this.canMoveBackward(flow, step);
94
113
  }
95
114
  else {
96
- const previous = this.getPreviousStep(flow, step);
115
+ const previous = TransactionOrchestrator.getPreviousStep(flow, step);
97
116
  if (previous.id === TransactionOrchestrator.ROOT_STEP) {
98
117
  return true;
99
118
  }
@@ -139,6 +158,46 @@ class TransactionOrchestrator extends events_1.EventEmitter {
139
158
  return hasTimedOut;
140
159
  }
141
160
  async checkAllSteps(transaction) {
161
+ const flow = transaction.getFlow();
162
+ const result = await this.computeCurrentTransactionState(transaction);
163
+ // Handle state transitions and emit events
164
+ if (flow.state === types_1.TransactionState.WAITING_TO_COMPENSATE &&
165
+ result.next.length === 0 &&
166
+ !flow.hasWaitingSteps) {
167
+ flow.state = types_1.TransactionState.COMPENSATING;
168
+ this.flagStepsToRevert(flow);
169
+ this.emit(types_1.DistributedTransactionEvent.COMPENSATE_BEGIN, { transaction });
170
+ const result = await this.checkAllSteps(transaction);
171
+ return result;
172
+ }
173
+ else if (result.completed === result.total) {
174
+ if (result.hasSkippedOnFailure) {
175
+ flow.hasSkippedOnFailureSteps = true;
176
+ }
177
+ if (result.hasSkipped) {
178
+ flow.hasSkippedSteps = true;
179
+ }
180
+ if (result.hasIgnoredFailure) {
181
+ flow.hasFailedSteps = true;
182
+ }
183
+ if (result.hasFailed) {
184
+ flow.state = types_1.TransactionState.FAILED;
185
+ }
186
+ else {
187
+ flow.state = result.hasReverted
188
+ ? types_1.TransactionState.REVERTED
189
+ : types_1.TransactionState.DONE;
190
+ }
191
+ }
192
+ return {
193
+ current: result.current,
194
+ next: result.next,
195
+ total: result.total,
196
+ remaining: result.total - result.completed,
197
+ completed: result.completed,
198
+ };
199
+ }
200
+ async computeCurrentTransactionState(transaction) {
142
201
  let hasSkipped = false;
143
202
  let hasSkippedOnFailure = false;
144
203
  let hasIgnoredFailure = false;
@@ -177,9 +236,20 @@ class TransactionOrchestrator extends events_1.EventEmitter {
177
236
  await transaction.scheduleRetry(stepDef, stepDef.definition.retryIntervalAwaiting);
178
237
  }
179
238
  }
239
+ else if (stepDef.retryRescheduledAt) {
240
+ // The step is not configured for awaiting retry but is manually force to retry
241
+ stepDef.retryRescheduledAt = null;
242
+ nextSteps.push(stepDef);
243
+ }
180
244
  continue;
181
245
  }
182
246
  else if (curState.status === types_1.TransactionStepStatus.TEMPORARY_FAILURE) {
247
+ if (!stepDef.temporaryFailedAt &&
248
+ stepDef.definition.autoRetry === false) {
249
+ stepDef.temporaryFailedAt = Date.now();
250
+ continue;
251
+ }
252
+ stepDef.temporaryFailedAt = null;
183
253
  currentSteps.push(stepDef);
184
254
  if (!stepDef.canRetry()) {
185
255
  if (stepDef.hasRetryInterval() && !stepDef.retryRescheduledAt) {
@@ -206,7 +276,8 @@ class TransactionOrchestrator extends events_1.EventEmitter {
206
276
  hasReverted = true;
207
277
  }
208
278
  else if (curState.state === utils_1.TransactionStepState.FAILED) {
209
- if (stepDef.definition.continueOnPermanentFailure) {
279
+ if (stepDef.definition.continueOnPermanentFailure ||
280
+ stepDef.definition.skipOnPermanentFailure) {
210
281
  hasIgnoredFailure = true;
211
282
  }
212
283
  else {
@@ -217,40 +288,17 @@ class TransactionOrchestrator extends events_1.EventEmitter {
217
288
  }
218
289
  flow.hasWaitingSteps = hasWaiting;
219
290
  flow.hasRevertedSteps = hasReverted;
220
- const totalSteps = allSteps.length - 1;
221
- if (flow.state === types_1.TransactionState.WAITING_TO_COMPENSATE &&
222
- nextSteps.length === 0 &&
223
- !hasWaiting) {
224
- flow.state = types_1.TransactionState.COMPENSATING;
225
- this.flagStepsToRevert(flow);
226
- this.emit(types_1.DistributedTransactionEvent.COMPENSATE_BEGIN, { transaction });
227
- return await this.checkAllSteps(transaction);
228
- }
229
- else if (completedSteps === totalSteps) {
230
- if (hasSkippedOnFailure) {
231
- flow.hasSkippedOnFailureSteps = true;
232
- }
233
- if (hasSkipped) {
234
- flow.hasSkippedSteps = true;
235
- }
236
- if (hasIgnoredFailure) {
237
- flow.hasFailedSteps = true;
238
- }
239
- if (hasFailed) {
240
- flow.state = types_1.TransactionState.FAILED;
241
- }
242
- else {
243
- flow.state = hasReverted
244
- ? types_1.TransactionState.REVERTED
245
- : types_1.TransactionState.DONE;
246
- }
247
- }
248
291
  return {
249
292
  current: currentSteps,
250
293
  next: nextSteps,
251
- total: totalSteps,
252
- remaining: totalSteps - completedSteps,
294
+ total: allSteps.length - 1,
253
295
  completed: completedSteps,
296
+ hasSkipped,
297
+ hasSkippedOnFailure,
298
+ hasIgnoredFailure,
299
+ hasFailed,
300
+ hasWaiting,
301
+ hasReverted,
254
302
  };
255
303
  }
256
304
  flagStepsToRevert(flow) {
@@ -260,7 +308,11 @@ class TransactionOrchestrator extends events_1.EventEmitter {
260
308
  }
261
309
  const stepDef = flow.steps[step];
262
310
  const curState = stepDef.getStates();
263
- if ([utils_1.TransactionStepState.DONE, utils_1.TransactionStepState.TIMEOUT].includes(curState.state) ||
311
+ if (stepDef._v) {
312
+ flow._v = 0;
313
+ stepDef._v = 0;
314
+ }
315
+ if (flagStepsToRevertStates.has(curState.state) ||
264
316
  curState.status === types_1.TransactionStepStatus.PERMANENT_FAILURE) {
265
317
  stepDef.beginCompensation();
266
318
  stepDef.changeState(utils_1.TransactionStepState.NOT_STARTED);
@@ -284,11 +336,21 @@ class TransactionOrchestrator extends events_1.EventEmitter {
284
336
  step.changeState(utils_1.TransactionStepState.DONE);
285
337
  }
286
338
  let shouldEmit = true;
339
+ let transactionIsCancelling = false;
287
340
  try {
288
- await transaction.saveCheckpoint();
341
+ await transaction.saveCheckpoint({
342
+ _v: step._v,
343
+ parallelSteps: TransactionOrchestrator.countSiblings(transaction.getFlow(), step),
344
+ stepId: step.id,
345
+ });
289
346
  }
290
347
  catch (error) {
291
- shouldEmit = false;
348
+ if (!TransactionOrchestrator.isExpectedError(error)) {
349
+ throw error;
350
+ }
351
+ transactionIsCancelling =
352
+ errors_1.SkipCancelledExecutionError.isSkipCancelledExecutionError(error);
353
+ shouldEmit = !errors_1.SkipExecutionError.isSkipExecutionError(error);
292
354
  }
293
355
  const cleaningUp = [];
294
356
  if (step.hasRetryScheduled()) {
@@ -297,7 +359,9 @@ class TransactionOrchestrator extends events_1.EventEmitter {
297
359
  if (step.hasTimeout()) {
298
360
  cleaningUp.push(transaction.clearStepTimeout(step));
299
361
  }
300
- await (0, utils_1.promiseAll)(cleaningUp);
362
+ if (cleaningUp.length) {
363
+ await (0, utils_1.promiseAll)(cleaningUp);
364
+ }
301
365
  if (shouldEmit) {
302
366
  const eventName = step.isCompensating()
303
367
  ? types_1.DistributedTransactionEvent.COMPENSATE_STEP_SUCCESS
@@ -306,25 +370,53 @@ class TransactionOrchestrator extends events_1.EventEmitter {
306
370
  }
307
371
  return {
308
372
  stopExecution: !shouldEmit,
373
+ transactionIsCancelling,
309
374
  };
310
375
  }
311
- static async skipStep(transaction, step) {
376
+ static async retryStep(transaction, step) {
377
+ if (!step.retryRescheduledAt) {
378
+ step.hasScheduledRetry = true;
379
+ step.retryRescheduledAt = Date.now();
380
+ }
381
+ transaction.getFlow().hasWaitingSteps = true;
382
+ try {
383
+ await transaction.saveCheckpoint({
384
+ _v: step._v,
385
+ parallelSteps: TransactionOrchestrator.countSiblings(transaction.getFlow(), step),
386
+ stepId: step.id,
387
+ });
388
+ await transaction.scheduleRetry(step, 0);
389
+ }
390
+ catch (error) {
391
+ if (!TransactionOrchestrator.isExpectedError(error)) {
392
+ throw error;
393
+ }
394
+ }
395
+ }
396
+ static async skipStep({ transaction, step, }) {
312
397
  const hasStepTimedOut = step.getStates().state === utils_1.TransactionStepState.TIMEOUT;
313
398
  if (!hasStepTimedOut) {
314
399
  step.changeStatus(types_1.TransactionStepStatus.OK);
315
400
  step.changeState(utils_1.TransactionStepState.SKIPPED);
316
401
  }
317
402
  let shouldEmit = true;
403
+ let transactionIsCancelling = false;
318
404
  try {
319
- await transaction.saveCheckpoint();
405
+ await transaction.saveCheckpoint({
406
+ _v: step._v,
407
+ parallelSteps: TransactionOrchestrator.countSiblings(transaction.getFlow(), step),
408
+ stepId: step.id,
409
+ });
320
410
  }
321
411
  catch (error) {
412
+ if (!TransactionOrchestrator.isExpectedError(error)) {
413
+ throw error;
414
+ }
415
+ transactionIsCancelling =
416
+ errors_1.SkipCancelledExecutionError.isSkipCancelledExecutionError(error);
322
417
  if (errors_1.SkipExecutionError.isSkipExecutionError(error)) {
323
418
  shouldEmit = false;
324
419
  }
325
- else {
326
- throw error;
327
- }
328
420
  }
329
421
  const cleaningUp = [];
330
422
  if (step.hasRetryScheduled()) {
@@ -333,21 +425,20 @@ class TransactionOrchestrator extends events_1.EventEmitter {
333
425
  if (step.hasTimeout()) {
334
426
  cleaningUp.push(transaction.clearStepTimeout(step));
335
427
  }
336
- await (0, utils_1.promiseAll)(cleaningUp);
428
+ if (cleaningUp.length) {
429
+ await (0, utils_1.promiseAll)(cleaningUp);
430
+ }
337
431
  if (shouldEmit) {
338
432
  const eventName = types_1.DistributedTransactionEvent.STEP_SKIPPED;
339
433
  transaction.emit(eventName, { step, transaction });
340
434
  }
341
435
  return {
342
436
  stopExecution: !shouldEmit,
437
+ transactionIsCancelling,
343
438
  };
344
439
  }
345
440
  static async setStepTimeout(transaction, step, error) {
346
- if ([
347
- utils_1.TransactionStepState.TIMEOUT,
348
- utils_1.TransactionStepState.DONE,
349
- utils_1.TransactionStepState.REVERTED,
350
- ].includes(step.getStates().state)) {
441
+ if (setStepTimeoutSkipStates.has(step.getStates().state)) {
351
442
  return;
352
443
  }
353
444
  step.changeState(utils_1.TransactionStepState.TIMEOUT);
@@ -365,15 +456,30 @@ class TransactionOrchestrator extends events_1.EventEmitter {
365
456
  await transaction.clearStepTimeout(step);
366
457
  }
367
458
  static async setStepFailure(transaction, step, error, maxRetries = TransactionOrchestrator.DEFAULT_RETRIES, isTimeout = false, timeoutError) {
459
+ const result = {
460
+ stopExecution: false,
461
+ transactionIsCancelling: false,
462
+ };
368
463
  if (errors_1.SkipExecutionError.isSkipExecutionError(error)) {
369
- return {
370
- stopExecution: false,
371
- };
464
+ return result;
372
465
  }
373
466
  step.failures++;
374
467
  if ((0, utils_1.isErrorLike)(error)) {
375
468
  error = (0, utils_1.serializeError)(error);
376
469
  }
470
+ else {
471
+ try {
472
+ const serialized = JSON.stringify(error);
473
+ error = error?.message
474
+ ? JSON.parse(serialized)
475
+ : { message: serialized };
476
+ }
477
+ catch (e) {
478
+ error = {
479
+ message: "Unknown non-serializable error",
480
+ };
481
+ }
482
+ }
377
483
  if (!isTimeout &&
378
484
  step.getStates().status !== types_1.TransactionStepStatus.PERMANENT_FAILURE) {
379
485
  step.changeStatus(types_1.TransactionStepStatus.TEMPORARY_FAILURE);
@@ -390,23 +496,35 @@ class TransactionOrchestrator extends events_1.EventEmitter {
390
496
  const handlerType = step.isCompensating()
391
497
  ? types_1.TransactionHandlerType.COMPENSATE
392
498
  : types_1.TransactionHandlerType.INVOKE;
393
- if (error?.stack) {
394
- const workflowId = transaction.modelId;
395
- const stepAction = step.definition.action;
396
- const sourcePath = transaction.getFlow().metadata?.sourcePath;
397
- const sourceStack = sourcePath
398
- ? `\n⮑ \sat ${sourcePath}: [${workflowId} -> ${stepAction} (${types_1.TransactionHandlerType.INVOKE})]`
399
- : `\n⮑ \sat [${workflowId} -> ${stepAction} (${types_1.TransactionHandlerType.INVOKE})]`;
400
- error.stack += sourceStack;
401
- }
499
+ error.stack ??= "";
500
+ const workflowId = transaction.modelId;
501
+ const stepAction = step.definition.action;
502
+ const sourcePath = transaction.getFlow().metadata?.sourcePath;
503
+ const sourceStack = sourcePath
504
+ ? `\n⮑ \sat ${sourcePath}: [${workflowId} -> ${stepAction} (${types_1.TransactionHandlerType.INVOKE})]`
505
+ : `\n⮑ \sat [${workflowId} -> ${stepAction} (${types_1.TransactionHandlerType.INVOKE})]`;
506
+ error.stack += sourceStack;
402
507
  transaction.addError(step.definition.action, handlerType, error);
403
508
  }
404
509
  if (!step.isCompensating()) {
405
- if (step.definition.continueOnPermanentFailure &&
510
+ if ((step.definition.continueOnPermanentFailure ||
511
+ step.definition.skipOnPermanentFailure) &&
406
512
  !errors_1.TransactionTimeoutError.isTransactionTimeoutError(timeoutError)) {
407
- for (const childStep of step.next) {
408
- const child = flow.steps[childStep];
409
- child.changeState(utils_1.TransactionStepState.SKIPPED_FAILURE);
513
+ if (step.definition.skipOnPermanentFailure) {
514
+ const until = (0, utils_1.isString)(step.definition.skipOnPermanentFailure)
515
+ ? step.definition.skipOnPermanentFailure
516
+ : undefined;
517
+ let stepsToSkip = [...step.next];
518
+ while (stepsToSkip.length > 0) {
519
+ const currentStep = flow.steps[stepsToSkip.shift()];
520
+ if (until && currentStep.definition.action === until) {
521
+ break;
522
+ }
523
+ currentStep.changeState(utils_1.TransactionStepState.SKIPPED_FAILURE);
524
+ if (currentStep.next?.length > 0) {
525
+ stepsToSkip = stepsToSkip.concat(currentStep.next);
526
+ }
527
+ }
410
528
  }
411
529
  }
412
530
  else {
@@ -417,30 +535,49 @@ class TransactionOrchestrator extends events_1.EventEmitter {
417
535
  cleaningUp.push(transaction.clearStepTimeout(step));
418
536
  }
419
537
  }
420
- let shouldEmit = true;
538
+ else {
539
+ const isAsync = step.isCompensating()
540
+ ? step.definition.compensateAsync
541
+ : step.definition.async;
542
+ if (step.getStates().status === types_1.TransactionStepStatus.TEMPORARY_FAILURE &&
543
+ step.definition.autoRetry === false &&
544
+ isAsync) {
545
+ step.temporaryFailedAt = Date.now();
546
+ result.stopExecution = true;
547
+ }
548
+ }
421
549
  try {
422
- await transaction.saveCheckpoint();
550
+ await transaction.saveCheckpoint({
551
+ _v: step._v,
552
+ parallelSteps: TransactionOrchestrator.countSiblings(transaction.getFlow(), step),
553
+ stepId: step.id,
554
+ });
423
555
  }
424
556
  catch (error) {
425
- if (errors_1.SkipExecutionError.isSkipExecutionError(error)) {
426
- shouldEmit = false;
427
- }
428
- else {
557
+ if (!TransactionOrchestrator.isExpectedError(error)) {
429
558
  throw error;
430
559
  }
560
+ result.transactionIsCancelling =
561
+ errors_1.SkipCancelledExecutionError.isSkipCancelledExecutionError(error);
562
+ if (errors_1.SkipExecutionError.isSkipExecutionError(error)) {
563
+ result.stopExecution = true;
564
+ }
431
565
  }
432
566
  if (step.hasRetryScheduled()) {
433
567
  cleaningUp.push(transaction.clearRetry(step));
434
568
  }
435
- await (0, utils_1.promiseAll)(cleaningUp);
436
- if (shouldEmit) {
569
+ if (cleaningUp.length) {
570
+ await (0, utils_1.promiseAll)(cleaningUp);
571
+ }
572
+ if (!result.stopExecution) {
437
573
  const eventName = step.isCompensating()
438
574
  ? types_1.DistributedTransactionEvent.COMPENSATE_STEP_FAILURE
439
575
  : types_1.DistributedTransactionEvent.STEP_FAILURE;
440
576
  transaction.emit(eventName, { step, transaction });
441
577
  }
442
578
  return {
443
- stopExecution: !shouldEmit,
579
+ stopExecution: result.stopExecution,
580
+ transactionIsCancelling: result.transactionIsCancelling,
444
581
  };
445
582
  }
446
583
  async executeNext(transaction) {
@@ -450,50 +587,49 @@ class TransactionOrchestrator extends events_1.EventEmitter {
450
587
  return;
451
588
  }
452
589
  const flow = transaction.getFlow();
453
- const nextSteps = await this.checkAllSteps(transaction);
454
- const execution = [];
590
+ let nextSteps = await this.checkAllSteps(transaction);
455
591
  const hasTimedOut = await this.checkTransactionTimeout(transaction, nextSteps.current);
456
592
  if (hasTimedOut) {
457
593
  continue;
458
594
  }
459
595
  if (nextSteps.remaining === 0) {
460
- if (transaction.hasTimeout()) {
461
- void transaction.clearTransactionTimeout();
596
+ await this.finalizeTransaction(transaction);
597
+ return;
598
+ }
599
+ const stepsShouldContinueExecution = nextSteps.next.map((step) => {
600
+ const { shouldContinueExecution } = this.prepareStepForExecution(step, flow);
601
+ return shouldContinueExecution;
602
+ });
603
+ let asyncStepCount = 0;
604
+ for (const s of nextSteps.next) {
605
+ const stepIsAsync = s.isCompensating()
606
+ ? s.definition.compensateAsync
607
+ : s.definition.async;
608
+ if (stepIsAsync)
609
+ asyncStepCount++;
610
+ }
611
+ const hasMultipleAsyncSteps = asyncStepCount > 1;
612
+ const hasAsyncSteps = !!asyncStepCount;
613
+ // If there is any async step, we don't need to save the checkpoint here as it will be saved
614
+ // later down there
615
+ await transaction.saveCheckpoint().catch((error) => {
616
+ if (TransactionOrchestrator.isExpectedError(error)) {
617
+ continueExecution = false;
618
+ return;
462
619
  }
463
- await transaction.saveCheckpoint();
464
- this.emit(types_1.DistributedTransactionEvent.FINISH, { transaction });
620
+ throw error;
621
+ });
622
+ if (!continueExecution) {
623
+ break;
465
624
  }
625
+ const execution = [];
626
+ const executionAsync = [];
627
+ let i = 0;
466
628
  for (const step of nextSteps.next) {
467
- const curState = step.getStates();
468
- const type = step.isCompensating()
469
- ? types_1.TransactionHandlerType.COMPENSATE
470
- : types_1.TransactionHandlerType.INVOKE;
471
- step.lastAttempt = Date.now();
472
- step.attempts++;
473
- if (curState.state === utils_1.TransactionStepState.NOT_STARTED) {
474
- if (!step.startedAt) {
475
- step.startedAt = Date.now();
476
- }
477
- if (step.isCompensating()) {
478
- step.changeState(utils_1.TransactionStepState.COMPENSATING);
479
- if (step.definition.noCompensation) {
480
- step.changeState(utils_1.TransactionStepState.REVERTED);
481
- continue;
482
- }
483
- }
484
- else if (flow.state === types_1.TransactionState.INVOKING) {
485
- step.changeState(utils_1.TransactionStepState.INVOKING);
486
- }
629
+ const stepIndex = i++;
630
+ if (!stepsShouldContinueExecution[stepIndex]) {
631
+ continue;
487
632
  }
488
- step.changeStatus(types_1.TransactionStepStatus.WAITING);
489
- const payload = new distributed_transaction_1.TransactionPayload({
490
- model_id: flow.modelId,
491
- idempotency_key: TransactionOrchestrator.getKeyName(flow.modelId, flow.transactionId, step.definition.action, type),
492
- action: step.definition.action + "",
493
- action_type: type,
494
- attempt: step.attempts,
495
- timestamp: Date.now(),
496
- }, transaction.payload, transaction.getContext());
497
633
  if (step.hasTimeout() && !step.timedOutAt && step.attempts === 1) {
498
634
  await transaction.scheduleStepTimeout(step, step.definition.timeout);
499
635
  }
@@ -504,146 +640,263 @@ class TransactionOrchestrator extends events_1.EventEmitter {
504
640
  const isAsync = step.isCompensating()
505
641
  ? step.definition.compensateAsync
506
642
  : step.definition.async;
507
- const setStepFailure = async (error, { endRetry, response, } = {}) => {
508
- if ((0, utils_1.isDefined)(response) && step.saveResponse) {
509
- transaction.addResponse(step.definition.action, step.isCompensating()
510
- ? types_1.TransactionHandlerType.COMPENSATE
511
- : types_1.TransactionHandlerType.INVOKE, response);
512
- }
513
- const ret = await TransactionOrchestrator.setStepFailure(transaction, step, error, endRetry ? 0 : step.definition.maxRetries);
514
- if (isAsync && !ret.stopExecution) {
515
- await transaction.scheduleRetry(step, 0);
516
- }
517
- return ret;
518
- };
519
- const traceData = {
520
- action: step.definition.action + "",
521
- type,
522
- step_id: step.id,
523
- step_uuid: step.uuid + "",
524
- attempts: step.attempts,
525
- failures: step.failures,
526
- async: !!(type === "invoke"
527
- ? step.definition.async
528
- : step.definition.compensateAsync),
529
- idempotency_key: payload.metadata.idempotency_key,
530
- };
531
- const handlerArgs = [
532
- step.definition.action + "",
533
- type,
534
- payload,
535
- transaction,
536
- step,
537
- this,
538
- ];
643
+ // Compute current transaction state
644
+ await this.computeCurrentTransactionState(transaction);
645
+ const promise = this.createStepExecutionPromise(transaction, step);
646
+ const hasVersionControl = hasMultipleAsyncSteps || step.hasAwaitingRetry();
647
+ if (hasVersionControl && !step._v) {
648
+ transaction.getFlow()._v += 1;
649
+ step._v = transaction.getFlow()._v;
650
+ }
539
651
  if (!isAsync) {
540
- const stepHandler = async () => {
541
- return await transaction.handler(...handlerArgs);
542
- };
543
- let promise;
544
- if (TransactionOrchestrator.traceStep) {
545
- promise = TransactionOrchestrator.traceStep(stepHandler, traceData);
546
- }
547
- else {
548
- promise = stepHandler();
549
- }
550
- execution.push(promise
551
- .then(async (response) => {
552
- if (this.hasExpired({ transaction, step }, Date.now())) {
553
- await this.checkStepTimeout(transaction, step);
554
- await this.checkTransactionTimeout(transaction, nextSteps.next.includes(step) ? nextSteps.next : [step]);
555
- }
556
- const output = response?.__type ? response.output : response;
557
- if (errors_1.SkipStepResponse.isSkipStepResponse(output)) {
558
- await TransactionOrchestrator.skipStep(transaction, step);
559
- return;
560
- }
561
- await TransactionOrchestrator.setStepSuccess(transaction, step, response);
562
- })
563
- .catch(async (error) => {
564
- const response = error?.getStepResponse?.();
565
- if (this.hasExpired({ transaction, step }, Date.now())) {
566
- await this.checkStepTimeout(transaction, step);
567
- await this.checkTransactionTimeout(transaction, nextSteps.next.includes(step) ? nextSteps.next : [step]);
568
- }
569
- if (errors_1.PermanentStepFailureError.isPermanentStepFailureError(error)) {
570
- await setStepFailure(error, {
571
- endRetry: true,
572
- response,
573
- });
574
- return;
575
- }
576
- await setStepFailure(error, {
577
- response,
578
- });
579
- }));
652
+ execution.push(this.executeSyncStep(promise, transaction, step, nextSteps));
580
653
  }
581
654
  else {
582
- const stepHandler = async () => {
583
- return await transaction.handler(...handlerArgs);
584
- };
585
- execution.push(transaction.saveCheckpoint().then(() => {
586
- let promise;
587
- if (TransactionOrchestrator.traceStep) {
588
- promise = TransactionOrchestrator.traceStep(stepHandler, traceData);
589
- }
590
- else {
591
- promise = stepHandler();
592
- }
593
- promise
594
- .then(async (response) => {
595
- const output = response?.__type ? response.output : response;
596
- if (errors_1.SkipStepResponse.isSkipStepResponse(output)) {
597
- await TransactionOrchestrator.skipStep(transaction, step);
598
- }
599
- else {
600
- if (!step.definition.backgroundExecution ||
601
- step.definition.nested) {
602
- const eventName = types_1.DistributedTransactionEvent.STEP_AWAITING;
603
- transaction.emit(eventName, { step, transaction });
604
- return;
605
- }
606
- if (this.hasExpired({ transaction, step }, Date.now())) {
607
- await this.checkStepTimeout(transaction, step);
608
- await this.checkTransactionTimeout(transaction, nextSteps.next.includes(step) ? nextSteps.next : [step]);
609
- }
610
- await TransactionOrchestrator.setStepSuccess(transaction, step, response);
611
- }
612
- // check nested flow
613
- await transaction.scheduleRetry(step, 0);
614
- })
615
- .catch(async (error) => {
616
- const response = error?.getStepResponse?.();
617
- if (errors_1.PermanentStepFailureError.isPermanentStepFailureError(error)) {
618
- await setStepFailure(error, {
619
- endRetry: true,
620
- response,
621
- });
622
- return;
623
- }
624
- await setStepFailure(error, {
625
- response,
626
- });
627
- });
628
- }));
655
+ // Execute async step in background as part of the next event loop cycle and continue the execution of the transaction
656
+ executionAsync.push(() => this.executeAsyncStep(promise, transaction, step, nextSteps));
629
657
  }
630
658
  }
631
- try {
632
- await transaction.saveCheckpoint();
659
+ await (0, utils_1.promiseAll)(execution);
660
+ if (!nextSteps.next.length || (hasAsyncSteps && !execution.length)) {
661
+ continueExecution = false;
633
662
  }
634
- catch (error) {
635
- if (errors_1.SkipExecutionError.isSkipExecutionError(error)) {
636
- break;
637
- }
638
- else {
663
+ if (hasAsyncSteps) {
664
+ await transaction.saveCheckpoint().catch((error) => {
665
+ if (TransactionOrchestrator.isExpectedError(error)) {
666
+ continueExecution = false;
667
+ }
639
668
  throw error;
669
+ });
670
+ for (const exec of executionAsync) {
671
+ void exec();
640
672
  }
641
673
  }
642
- await (0, utils_1.promiseAll)(execution);
643
- if (nextSteps.next.length === 0) {
644
- continueExecution = false;
674
+ }
675
+ }
676
+ /**
677
+ * Finalize the transaction when all steps are complete
678
+ */
679
+ async finalizeTransaction(transaction) {
680
+ if (transaction.hasTimeout()) {
681
+ void transaction.clearTransactionTimeout();
682
+ }
683
+ await transaction.saveCheckpoint().catch((error) => {
684
+ if (!TransactionOrchestrator.isExpectedError(error)) {
685
+ throw error;
686
+ }
687
+ });
688
+ this.emit(types_1.DistributedTransactionEvent.FINISH, { transaction });
689
+ }
690
+ /**
691
+ * Prepare a step for execution by setting state and incrementing attempts
692
+ */
693
+ prepareStepForExecution(step, flow) {
694
+ const curState = step.getStates();
695
+ step.lastAttempt = Date.now();
696
+ step.attempts++;
697
+ if (curState.state === utils_1.TransactionStepState.NOT_STARTED) {
698
+ if (!step.startedAt) {
699
+ step.startedAt = Date.now();
700
+ }
701
+ if (step.isCompensating()) {
702
+ step.changeState(utils_1.TransactionStepState.COMPENSATING);
703
+ if (step.definition.noCompensation) {
704
+ step.changeState(utils_1.TransactionStepState.REVERTED);
705
+ return { shouldContinueExecution: false };
706
+ }
707
+ }
708
+ else if (flow.state === types_1.TransactionState.INVOKING) {
709
+ step.changeState(utils_1.TransactionStepState.INVOKING);
645
710
  }
646
711
  }
712
+ step.changeStatus(types_1.TransactionStepStatus.WAITING);
713
+ return { shouldContinueExecution: true };
714
+ }
715
+ /**
716
+ * Create the payload for a step execution
717
+ */
718
+ createStepPayload(transaction, step, flow, type) {
719
+ return new distributed_transaction_1.TransactionPayload({
720
+ model_id: flow.modelId,
721
+ idempotency_key: TransactionOrchestrator.getKeyName(flow.modelId, flow.transactionId, step.definition.action, type),
722
+ action: step.definition.action + "",
723
+ action_type: type,
724
+ attempt: step.attempts,
725
+ timestamp: Date.now(),
726
+ }, transaction.payload, transaction.getContext());
727
+ }
728
+ /**
729
+ * Prepare handler arguments for step execution
730
+ */
731
+ prepareHandlerArgs(transaction, step, payload, type) {
732
+ return [
733
+ step.definition.action + "",
734
+ type,
735
+ payload,
736
+ transaction,
737
+ step,
738
+ this,
739
+ ];
740
+ }
741
+ /**
742
+ * Create the step execution promise with optional tracing
743
+ */
744
+ createStepExecutionPromise(transaction, step) {
745
+ const type = step.isCompensating()
746
+ ? types_1.TransactionHandlerType.COMPENSATE
747
+ : types_1.TransactionHandlerType.INVOKE;
748
+ const flow = transaction.getFlow();
749
+ const payload = this.createStepPayload(transaction, step, flow, type);
750
+ const handlerArgs = this.prepareHandlerArgs(transaction, step, payload, type);
751
+ const traceData = {
752
+ action: step.definition.action + "",
753
+ type,
754
+ step_id: step.id,
755
+ step_uuid: step.uuid + "",
756
+ attempts: step.attempts,
757
+ failures: step.failures,
758
+ async: !!(type === "invoke"
759
+ ? step.definition.async
760
+ : step.definition.compensateAsync),
761
+ idempotency_key: handlerArgs[2].metadata.idempotency_key,
762
+ };
763
+ const stepHandler = async () => {
764
+ return await transaction.handler(...handlerArgs);
765
+ };
766
+ // Return the appropriate promise based on tracing configuration
767
+ if (TransactionOrchestrator.traceStep) {
768
+ return () => TransactionOrchestrator.traceStep(stepHandler, traceData);
769
+ }
770
+ else {
771
+ return stepHandler;
772
+ }
773
+ }
774
+ /**
775
+ * Execute a synchronous step and handle its result
776
+ */
777
+ executeSyncStep(promiseFn, transaction, step, nextSteps) {
778
+ return promiseFn()
779
+ .then(async (response) => {
780
+ await this.handleStepExpiration(transaction, step, nextSteps);
781
+ const output = response?.__type || response?.output?.__type
782
+ ? response.output
783
+ : response;
784
+ if (errors_1.SkipStepResponse.isSkipStepResponse(output)) {
785
+ await TransactionOrchestrator.skipStep({
786
+ transaction,
787
+ step,
788
+ });
789
+ return;
790
+ }
791
+ await this.handleStepSuccess(transaction, step, response);
792
+ })
793
+ .catch(async (error) => {
794
+ if (TransactionOrchestrator.isExpectedError(error)) {
795
+ return;
796
+ }
797
+ const response = error?.getStepResponse?.();
798
+ await this.handleStepExpiration(transaction, step, nextSteps);
799
+ if (errors_1.PermanentStepFailureError.isPermanentStepFailureError(error)) {
800
+ await this.handleStepFailure(transaction, step, error, true, response);
801
+ return;
802
+ }
803
+ await this.handleStepFailure(transaction, step, error, false, response);
804
+ });
805
+ }
806
+ /**
807
+ * Execute an asynchronous step and handle its result
808
+ */
809
+ executeAsyncStep(promiseFn, transaction, step, nextSteps) {
810
+ return promiseFn()
811
+ .then(async (response) => {
812
+ const output = response?.__type || response?.output?.__type
813
+ ? response.output
814
+ : response;
815
+ if (errors_1.SkipStepResponse.isSkipStepResponse(output)) {
816
+ await TransactionOrchestrator.skipStep({
817
+ transaction,
818
+ step,
819
+ });
820
+ // Schedule to continue the execution of async steps because they are not awaited on purpose and can be handled by another machine
821
+ await transaction.scheduleRetry(step, 0);
822
+ return;
823
+ }
824
+ else {
825
+ if (!step.definition.backgroundExecution || step.definition.nested) {
826
+ const eventName = types_1.DistributedTransactionEvent.STEP_AWAITING;
827
+ transaction.emit(eventName, { step, transaction });
828
+ return;
829
+ }
830
+ await this.handleStepExpiration(transaction, step, nextSteps);
831
+ await this.handleStepSuccess(transaction, step, response);
832
+ }
833
+ })
834
+ .catch(async (error) => {
835
+ if (TransactionOrchestrator.isExpectedError(error)) {
836
+ return;
837
+ }
838
+ const response = error?.getStepResponse?.();
839
+ if (errors_1.PermanentStepFailureError.isPermanentStepFailureError(error)) {
840
+ await this.handleStepFailure(transaction, step, error, true, response);
841
+ return;
842
+ }
843
+ await this.handleStepFailure(transaction, step, error, false, response);
844
+ });
845
+ }
846
+ /**
847
+ * Check if step or transaction has expired and handle timeouts
848
+ */
849
+ async handleStepExpiration(transaction, step, nextSteps) {
850
+ if (this.hasExpired({ transaction, step }, Date.now())) {
851
+ await this.checkStepTimeout(transaction, step);
852
+ await this.checkTransactionTimeout(transaction, nextSteps.next.includes(step) ? nextSteps.next : [step]);
853
+ }
854
+ }
855
+ /**
856
+ * Handle successful step completion
857
+ */
858
+ async handleStepSuccess(transaction, step, response) {
859
+ const isAsync = step.isCompensating()
860
+ ? step.definition.compensateAsync
861
+ : step.definition.async;
862
+ if ((0, utils_1.isDefined)(response) && step.saveResponse && !isAsync) {
863
+ transaction.addResponse(step.definition.action, step.isCompensating()
864
+ ? types_1.TransactionHandlerType.COMPENSATE
865
+ : types_1.TransactionHandlerType.INVOKE, response);
866
+ }
867
+ const ret = await TransactionOrchestrator.setStepSuccess(transaction, step, response);
868
+ if (ret.transactionIsCancelling) {
869
+ await this.cancelTransaction(transaction, {
870
+ preventExecuteNext: true,
871
+ });
872
+ }
873
+ if (isAsync && !ret.stopExecution) {
874
+ // Schedule to continue the execution of async steps because they are not awaited on purpose and can be handled by another machine
875
+ await transaction.scheduleRetry(step, 0);
876
+ }
877
+ }
878
+ /**
879
+ * Handle step failure
880
+ */
881
+ async handleStepFailure(transaction, step, error, isPermanent, response) {
882
+ const isAsync = step.isCompensating()
883
+ ? step.definition.compensateAsync
884
+ : step.definition.async;
885
+ if ((0, utils_1.isDefined)(response) && step.saveResponse) {
886
+ transaction.addResponse(step.definition.action, step.isCompensating()
887
+ ? types_1.TransactionHandlerType.COMPENSATE
888
+ : types_1.TransactionHandlerType.INVOKE, response);
889
+ }
890
+ const ret = await TransactionOrchestrator.setStepFailure(transaction, step, error, isPermanent ? 0 : step.definition.maxRetries);
891
+ if (ret.transactionIsCancelling) {
892
+ await this.cancelTransaction(transaction, {
893
+ preventExecuteNext: true,
894
+ });
895
+ }
896
+ if (isAsync && !ret.stopExecution) {
897
+ // Schedule to continue the execution of async steps because they are not awaited on purpose and can be handled by another machine
898
+ await transaction.scheduleRetry(step, 0);
899
+ }
647
900
  }
648
901
  /**
649
902
  * Start a new transaction or resume a transaction that has been previously started
@@ -661,7 +914,9 @@ class TransactionOrchestrator extends events_1.EventEmitter {
661
914
  if (flow.state === types_1.TransactionState.NOT_STARTED) {
662
915
  flow.state = types_1.TransactionState.INVOKING;
663
916
  flow.startedAt = Date.now();
664
- await transaction.saveCheckpoint(flow.hasAsyncSteps ? 0 : TransactionOrchestrator.DEFAULT_TTL);
917
+ await transaction.saveCheckpoint({
918
+ ttl: flow.hasAsyncSteps ? 0 : TransactionOrchestrator.DEFAULT_TTL,
919
+ });
665
920
  if (transaction.hasTimeout()) {
666
921
  await transaction.scheduleTransactionTimeout(transaction.getTimeout());
667
922
  }
@@ -687,7 +942,7 @@ class TransactionOrchestrator extends events_1.EventEmitter {
687
942
  * Cancel and revert a transaction compensating all its executed steps. It can be an ongoing transaction or a completed one
688
943
  * @param transaction - The transaction to be reverted
689
944
  */
690
- async cancelTransaction(transaction) {
945
+ async cancelTransaction(transaction, options) {
691
946
  if (transaction.modelId !== this.id) {
692
947
  throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_ALLOWED, `TransactionModel "${transaction.modelId}" cannot be orchestrated by "${this.id}" model.`);
693
948
  }
@@ -695,7 +950,16 @@ class TransactionOrchestrator extends events_1.EventEmitter {
695
950
  if (flow.state === types_1.TransactionState.FAILED) {
696
951
  throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_ALLOWED, `Cannot revert a permanent failed transaction.`);
697
952
  }
953
+ if (flow.state === types_1.TransactionState.COMPENSATING ||
954
+ flow.state === types_1.TransactionState.WAITING_TO_COMPENSATE) {
955
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_ALLOWED, `Cannot revert a transaction that is already compensating.`);
956
+ }
698
957
  flow.state = types_1.TransactionState.WAITING_TO_COMPENSATE;
958
+ flow.cancelledAt = Date.now();
959
+ await transaction.saveCheckpoint();
960
+ if (options?.preventExecuteNext) {
961
+ return;
962
+ }
699
963
  await this.executeNext(transaction);
700
964
  }
701
965
  parseFlowOptions() {
@@ -712,7 +976,8 @@ class TransactionOrchestrator extends events_1.EventEmitter {
712
976
  if (hasStepTimeouts ||
713
977
  hasRetriesTimeout ||
714
978
  hasTransactionTimeout ||
715
- isIdempotent) {
979
+ isIdempotent ||
980
+ this.options.retentionTime) {
716
981
  this.options.store = true;
717
982
  }
718
983
  const parsedOptions = {
@@ -724,12 +989,13 @@ class TransactionOrchestrator extends events_1.EventEmitter {
724
989
  TransactionOrchestrator.workflowOptions[this.id] = parsedOptions;
725
990
  return [steps, features];
726
991
  }
727
- createTransactionFlow(transactionId, flowMetadata) {
992
+ createTransactionFlow(transactionId, flowMetadata, context) {
728
993
  const [steps, features] = TransactionOrchestrator.buildSteps(this.definition);
729
994
  const flow = {
730
995
  modelId: this.id,
731
996
  options: this.options,
732
997
  transactionId: transactionId,
998
+ runId: context?.runId ?? (0, ulid_1.ulid)(),
733
999
  metadata: flowMetadata,
734
1000
  hasAsyncSteps: features.hasAsyncSteps,
735
1001
  hasFailedSteps: false,
@@ -741,11 +1007,12 @@ class TransactionOrchestrator extends events_1.EventEmitter {
741
1007
  state: types_1.TransactionState.NOT_STARTED,
742
1008
  definition: this.definition,
743
1009
  steps,
1010
+ _v: 0, // Initialize version to 0
744
1011
  };
745
1012
  return flow;
746
1013
  }
747
- static async loadTransactionById(modelId, transactionId) {
748
- const transaction = await distributed_transaction_1.DistributedTransaction.loadTransaction(modelId, transactionId);
1014
+ static async loadTransactionById(modelId, transactionId, options) {
1015
+ const transaction = await distributed_transaction_1.DistributedTransaction.loadTransaction(modelId, transactionId, options);
749
1016
  if (transaction !== null) {
750
1017
  const flow = transaction.flow;
751
1018
  const [steps] = TransactionOrchestrator.buildSteps(flow.definition, flow.steps);
@@ -817,6 +1084,7 @@ class TransactionOrchestrator extends events_1.EventEmitter {
817
1084
  failures: 0,
818
1085
  lastAttempt: null,
819
1086
  next: [],
1087
+ _v: 0, // Initialize step version to 0
820
1088
  });
821
1089
  }
822
1090
  if (Array.isArray(obj.next)) {
@@ -836,12 +1104,12 @@ class TransactionOrchestrator extends events_1.EventEmitter {
836
1104
  * @param payload - payload to be passed to all the transaction steps
837
1105
  * @param flowMetadata - flow metadata which can include event group id for example
838
1106
  */
839
- async beginTransaction(transactionId, handler, payload, flowMetadata) {
1107
+ async beginTransaction({ transactionId, handler, payload, flowMetadata, context, onLoad, }) {
840
1108
  const existingTransaction = await TransactionOrchestrator.loadTransactionById(this.id, transactionId);
841
1109
  let newTransaction = false;
842
1110
  let modelFlow;
843
1111
  if (!existingTransaction) {
844
- modelFlow = this.createTransactionFlow(transactionId, flowMetadata);
1112
+ modelFlow = this.createTransactionFlow(transactionId, flowMetadata, context);
845
1113
  newTransaction = true;
846
1114
  }
847
1115
  else {
@@ -849,7 +1117,12 @@ class TransactionOrchestrator extends events_1.EventEmitter {
849
1117
  }
850
1118
  const transaction = new distributed_transaction_1.DistributedTransaction(modelFlow, handler, payload, existingTransaction?.errors, existingTransaction?.context);
851
1119
  if (newTransaction && this.getOptions().store) {
852
- await transaction.saveCheckpoint(modelFlow.hasAsyncSteps ? 0 : TransactionOrchestrator.DEFAULT_TTL);
1120
+ await transaction.saveCheckpoint({
1121
+ ttl: modelFlow.hasAsyncSteps ? 0 : TransactionOrchestrator.DEFAULT_TTL,
1122
+ });
1123
+ }
1124
+ if (onLoad) {
1125
+ await onLoad(transaction);
853
1126
  }
854
1127
  return transaction;
855
1128
  }
@@ -857,8 +1130,8 @@ class TransactionOrchestrator extends events_1.EventEmitter {
857
1130
  * @param transactionId - unique identifier of the transaction
858
1131
  * @param handler - function to handle action of the transaction
859
1132
  */
860
- async retrieveExistingTransaction(transactionId, handler) {
861
- const existingTransaction = await TransactionOrchestrator.loadTransactionById(this.id, transactionId);
1133
+ async retrieveExistingTransaction(transactionId, handler, options) {
1134
+ const existingTransaction = await TransactionOrchestrator.loadTransactionById(this.id, transactionId, { isCancelling: options?.isCancelling });
862
1135
  if (!existingTransaction) {
863
1136
  throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_FOUND, `Transaction ${transactionId} could not be found.`);
864
1137
  }
@@ -901,13 +1174,16 @@ class TransactionOrchestrator extends events_1.EventEmitter {
901
1174
  * @param handler - The handler function to execute the step
902
1175
  * @param transaction - The current transaction. If not provided it will be loaded based on the responseIdempotencyKey
903
1176
  */
904
- async skipStep(responseIdempotencyKey, handler, transaction) {
1177
+ async skipStep({ responseIdempotencyKey, handler, transaction, }) {
905
1178
  const [curTransaction, step] = await TransactionOrchestrator.getTransactionAndStepFromIdempotencyKey(responseIdempotencyKey, handler, transaction);
906
1179
  if (step.getStates().status === types_1.TransactionStepStatus.WAITING) {
907
1180
  this.emit(types_1.DistributedTransactionEvent.RESUME, {
908
1181
  transaction: curTransaction,
909
1182
  });
910
- await TransactionOrchestrator.skipStep(curTransaction, step);
1183
+ await TransactionOrchestrator.skipStep({
1184
+ transaction: curTransaction,
1185
+ step,
1186
+ });
911
1187
  await this.executeNext(curTransaction);
912
1188
  }
913
1189
  else {
@@ -915,19 +1191,48 @@ class TransactionOrchestrator extends events_1.EventEmitter {
915
1191
  }
916
1192
  return curTransaction;
917
1193
  }
1194
+ /**
1195
+ * Manually force a step to retry even if it is still in awaiting status
1196
+ * @param responseIdempotencyKey - The idempotency key for the step
1197
+ * @param handler - The handler function to execute the step
1198
+ * @param transaction - The current transaction. If not provided it will be loaded based on the responseIdempotencyKey
1199
+ */
1200
+ async retryStep({ responseIdempotencyKey, handler, transaction, onLoad, }) {
1201
+ const [curTransaction, step] = await TransactionOrchestrator.getTransactionAndStepFromIdempotencyKey(responseIdempotencyKey, handler, transaction);
1202
+ if (onLoad) {
1203
+ await onLoad(curTransaction);
1204
+ }
1205
+ if (step.getStates().status === types_1.TransactionStepStatus.WAITING) {
1206
+ this.emit(types_1.DistributedTransactionEvent.RESUME, {
1207
+ transaction: curTransaction,
1208
+ });
1209
+ await TransactionOrchestrator.retryStep(curTransaction, step);
1210
+ }
1211
+ else {
1212
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_ALLOWED, `Cannot retry step when status is ${step.getStates().status}`);
1213
+ }
1214
+ return curTransaction;
1215
+ }
918
1216
  /** Register a step success for a specific transaction and step
919
1217
  * @param responseIdempotencyKey - The idempotency key for the step
920
1218
  * @param handler - The handler function to execute the step
921
1219
  * @param transaction - The current transaction. If not provided it will be loaded based on the responseIdempotencyKey
922
1220
  * @param response - The response of the step
923
1221
  */
924
- async registerStepSuccess(responseIdempotencyKey, handler, transaction, response) {
1222
+ async registerStepSuccess({ responseIdempotencyKey, handler, transaction, response, onLoad, }) {
925
1223
  const [curTransaction, step] = await TransactionOrchestrator.getTransactionAndStepFromIdempotencyKey(responseIdempotencyKey, handler, transaction);
1224
+ if (onLoad) {
1225
+ await onLoad(curTransaction);
1226
+ }
926
1227
  if (step.getStates().status === types_1.TransactionStepStatus.WAITING) {
927
1228
  this.emit(types_1.DistributedTransactionEvent.RESUME, {
928
1229
  transaction: curTransaction,
929
1230
  });
930
- await TransactionOrchestrator.setStepSuccess(curTransaction, step, response);
1231
+ const ret = await TransactionOrchestrator.setStepSuccess(curTransaction, step, response);
1232
+ if (ret.transactionIsCancelling) {
1233
+ await this.cancelTransaction(curTransaction);
1234
+ return curTransaction;
1235
+ }
931
1236
  await this.executeNext(curTransaction);
932
1237
  }
933
1238
  else {
@@ -943,13 +1248,22 @@ class TransactionOrchestrator extends events_1.EventEmitter {
943
1248
  * @param transaction - The current transaction
944
1249
  * @param response - The response of the step
945
1250
  */
946
- async registerStepFailure(responseIdempotencyKey, error, handler, transaction) {
1251
+ async registerStepFailure({ responseIdempotencyKey, error, handler, transaction, onLoad, forcePermanentFailure, }) {
947
1252
  const [curTransaction, step] = await TransactionOrchestrator.getTransactionAndStepFromIdempotencyKey(responseIdempotencyKey, handler, transaction);
1253
+ if (onLoad) {
1254
+ await onLoad(curTransaction);
1255
+ }
948
1256
  if (step.getStates().status === types_1.TransactionStepStatus.WAITING) {
949
1257
  this.emit(types_1.DistributedTransactionEvent.RESUME, {
950
1258
  transaction: curTransaction,
951
1259
  });
952
- await TransactionOrchestrator.setStepFailure(curTransaction, step, error, 0);
1260
+ const ret = await TransactionOrchestrator.setStepFailure(curTransaction, step, error,
1261
+ // On permanent failure, the step should not consider any retries
1262
+ forcePermanentFailure ? 0 : step.definition.maxRetries);
1263
+ if (ret.transactionIsCancelling) {
1264
+ await this.cancelTransaction(curTransaction);
1265
+ return curTransaction;
1266
+ }
953
1267
  await this.executeNext(curTransaction);
954
1268
  }
955
1269
  else {