@medusajs/orchestration 3.0.0-snapshot-20250410105645 → 3.0.0-snapshot-20251104004624

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 +584 -274
  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,51 +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
  }
466
- let hasExecutedSyncSteps = false;
625
+ const execution = [];
626
+ const executionAsync = [];
627
+ let i = 0;
467
628
  for (const step of nextSteps.next) {
468
- const curState = step.getStates();
469
- const type = step.isCompensating()
470
- ? types_1.TransactionHandlerType.COMPENSATE
471
- : types_1.TransactionHandlerType.INVOKE;
472
- step.lastAttempt = Date.now();
473
- step.attempts++;
474
- if (curState.state === utils_1.TransactionStepState.NOT_STARTED) {
475
- if (!step.startedAt) {
476
- step.startedAt = Date.now();
477
- }
478
- if (step.isCompensating()) {
479
- step.changeState(utils_1.TransactionStepState.COMPENSATING);
480
- if (step.definition.noCompensation) {
481
- step.changeState(utils_1.TransactionStepState.REVERTED);
482
- continue;
483
- }
484
- }
485
- else if (flow.state === types_1.TransactionState.INVOKING) {
486
- step.changeState(utils_1.TransactionStepState.INVOKING);
487
- }
629
+ const stepIndex = i++;
630
+ if (!stepsShouldContinueExecution[stepIndex]) {
631
+ continue;
488
632
  }
489
- step.changeStatus(types_1.TransactionStepStatus.WAITING);
490
- const payload = new distributed_transaction_1.TransactionPayload({
491
- model_id: flow.modelId,
492
- idempotency_key: TransactionOrchestrator.getKeyName(flow.modelId, flow.transactionId, step.definition.action, type),
493
- action: step.definition.action + "",
494
- action_type: type,
495
- attempt: step.attempts,
496
- timestamp: Date.now(),
497
- }, transaction.payload, transaction.getContext());
498
633
  if (step.hasTimeout() && !step.timedOutAt && step.attempts === 1) {
499
634
  await transaction.scheduleStepTimeout(step, step.definition.timeout);
500
635
  }
@@ -505,148 +640,262 @@ class TransactionOrchestrator extends events_1.EventEmitter {
505
640
  const isAsync = step.isCompensating()
506
641
  ? step.definition.compensateAsync
507
642
  : step.definition.async;
508
- const setStepFailure = async (error, { endRetry, response, } = {}) => {
509
- if ((0, utils_1.isDefined)(response) && step.saveResponse) {
510
- transaction.addResponse(step.definition.action, step.isCompensating()
511
- ? types_1.TransactionHandlerType.COMPENSATE
512
- : types_1.TransactionHandlerType.INVOKE, response);
513
- }
514
- const ret = await TransactionOrchestrator.setStepFailure(transaction, step, error, endRetry ? 0 : step.definition.maxRetries);
515
- if (isAsync && !ret.stopExecution) {
516
- await transaction.scheduleRetry(step, 0);
517
- }
518
- return ret;
519
- };
520
- const traceData = {
521
- action: step.definition.action + "",
522
- type,
523
- step_id: step.id,
524
- step_uuid: step.uuid + "",
525
- attempts: step.attempts,
526
- failures: step.failures,
527
- async: !!(type === "invoke"
528
- ? step.definition.async
529
- : step.definition.compensateAsync),
530
- idempotency_key: payload.metadata.idempotency_key,
531
- };
532
- const handlerArgs = [
533
- step.definition.action + "",
534
- type,
535
- payload,
536
- transaction,
537
- step,
538
- this,
539
- ];
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
+ }
540
651
  if (!isAsync) {
541
- hasExecutedSyncSteps = true;
542
- const stepHandler = async () => {
543
- return await transaction.handler(...handlerArgs);
544
- };
545
- let promise;
546
- if (TransactionOrchestrator.traceStep) {
547
- promise = TransactionOrchestrator.traceStep(stepHandler, traceData);
548
- }
549
- else {
550
- promise = stepHandler();
551
- }
552
- execution.push(promise
553
- .then(async (response) => {
554
- if (this.hasExpired({ transaction, step }, Date.now())) {
555
- await this.checkStepTimeout(transaction, step);
556
- await this.checkTransactionTimeout(transaction, nextSteps.next.includes(step) ? nextSteps.next : [step]);
557
- }
558
- const output = response?.__type ? response.output : response;
559
- if (errors_1.SkipStepResponse.isSkipStepResponse(output)) {
560
- await TransactionOrchestrator.skipStep(transaction, step);
561
- return;
562
- }
563
- await TransactionOrchestrator.setStepSuccess(transaction, step, response);
564
- })
565
- .catch(async (error) => {
566
- const response = error?.getStepResponse?.();
567
- if (this.hasExpired({ transaction, step }, Date.now())) {
568
- await this.checkStepTimeout(transaction, step);
569
- await this.checkTransactionTimeout(transaction, nextSteps.next.includes(step) ? nextSteps.next : [step]);
570
- }
571
- if (errors_1.PermanentStepFailureError.isPermanentStepFailureError(error)) {
572
- await setStepFailure(error, {
573
- endRetry: true,
574
- response,
575
- });
576
- return;
577
- }
578
- await setStepFailure(error, {
579
- response,
580
- });
581
- }));
652
+ execution.push(this.executeSyncStep(promise, transaction, step, nextSteps));
582
653
  }
583
654
  else {
584
- const stepHandler = async () => {
585
- return await transaction.handler(...handlerArgs);
586
- };
587
- execution.push(transaction.saveCheckpoint().then(() => {
588
- let promise;
589
- if (TransactionOrchestrator.traceStep) {
590
- promise = TransactionOrchestrator.traceStep(stepHandler, traceData);
591
- }
592
- else {
593
- promise = stepHandler();
594
- }
595
- promise
596
- .then(async (response) => {
597
- const output = response?.__type ? response.output : response;
598
- if (errors_1.SkipStepResponse.isSkipStepResponse(output)) {
599
- await TransactionOrchestrator.skipStep(transaction, step);
600
- }
601
- else {
602
- if (!step.definition.backgroundExecution ||
603
- step.definition.nested) {
604
- const eventName = types_1.DistributedTransactionEvent.STEP_AWAITING;
605
- transaction.emit(eventName, { step, transaction });
606
- return;
607
- }
608
- if (this.hasExpired({ transaction, step }, Date.now())) {
609
- await this.checkStepTimeout(transaction, step);
610
- await this.checkTransactionTimeout(transaction, nextSteps.next.includes(step) ? nextSteps.next : [step]);
611
- }
612
- await TransactionOrchestrator.setStepSuccess(transaction, step, response);
613
- }
614
- // check nested flow
615
- await transaction.scheduleRetry(step, 0);
616
- })
617
- .catch(async (error) => {
618
- const response = error?.getStepResponse?.();
619
- if (errors_1.PermanentStepFailureError.isPermanentStepFailureError(error)) {
620
- await setStepFailure(error, {
621
- endRetry: true,
622
- response,
623
- });
624
- return;
625
- }
626
- await setStepFailure(error, {
627
- response,
628
- });
629
- });
630
- }));
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));
631
657
  }
632
658
  }
633
- try {
634
- if (hasExecutedSyncSteps) {
635
- await transaction.saveCheckpoint();
659
+ await (0, utils_1.promiseAll)(execution);
660
+ if (!nextSteps.next.length || (hasAsyncSteps && !execution.length)) {
661
+ continueExecution = false;
662
+ }
663
+ if (hasAsyncSteps) {
664
+ await transaction.saveCheckpoint().catch((error) => {
665
+ if (TransactionOrchestrator.isExpectedError(error)) {
666
+ continueExecution = false;
667
+ }
668
+ throw error;
669
+ });
670
+ for (const exec of executionAsync) {
671
+ void exec();
636
672
  }
637
673
  }
638
- catch (error) {
639
- if (errors_1.SkipExecutionError.isSkipExecutionError(error)) {
640
- break;
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 };
641
706
  }
642
- else {
643
- throw error;
707
+ }
708
+ else if (flow.state === types_1.TransactionState.INVOKING) {
709
+ step.changeState(utils_1.TransactionStepState.INVOKING);
710
+ }
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;
644
829
  }
830
+ await this.handleStepExpiration(transaction, step, nextSteps);
831
+ await this.handleStepSuccess(transaction, step, response);
645
832
  }
646
- await (0, utils_1.promiseAll)(execution);
647
- if (nextSteps.next.length === 0) {
648
- continueExecution = false;
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;
649
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);
650
899
  }
651
900
  }
652
901
  /**
@@ -665,7 +914,9 @@ class TransactionOrchestrator extends events_1.EventEmitter {
665
914
  if (flow.state === types_1.TransactionState.NOT_STARTED) {
666
915
  flow.state = types_1.TransactionState.INVOKING;
667
916
  flow.startedAt = Date.now();
668
- await transaction.saveCheckpoint(flow.hasAsyncSteps ? 0 : TransactionOrchestrator.DEFAULT_TTL);
917
+ await transaction.saveCheckpoint({
918
+ ttl: flow.hasAsyncSteps ? 0 : TransactionOrchestrator.DEFAULT_TTL,
919
+ });
669
920
  if (transaction.hasTimeout()) {
670
921
  await transaction.scheduleTransactionTimeout(transaction.getTimeout());
671
922
  }
@@ -691,7 +942,7 @@ class TransactionOrchestrator extends events_1.EventEmitter {
691
942
  * Cancel and revert a transaction compensating all its executed steps. It can be an ongoing transaction or a completed one
692
943
  * @param transaction - The transaction to be reverted
693
944
  */
694
- async cancelTransaction(transaction) {
945
+ async cancelTransaction(transaction, options) {
695
946
  if (transaction.modelId !== this.id) {
696
947
  throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_ALLOWED, `TransactionModel "${transaction.modelId}" cannot be orchestrated by "${this.id}" model.`);
697
948
  }
@@ -699,7 +950,16 @@ class TransactionOrchestrator extends events_1.EventEmitter {
699
950
  if (flow.state === types_1.TransactionState.FAILED) {
700
951
  throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_ALLOWED, `Cannot revert a permanent failed transaction.`);
701
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
+ }
702
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
+ }
703
963
  await this.executeNext(transaction);
704
964
  }
705
965
  parseFlowOptions() {
@@ -716,7 +976,8 @@ class TransactionOrchestrator extends events_1.EventEmitter {
716
976
  if (hasStepTimeouts ||
717
977
  hasRetriesTimeout ||
718
978
  hasTransactionTimeout ||
719
- isIdempotent) {
979
+ isIdempotent ||
980
+ this.options.retentionTime) {
720
981
  this.options.store = true;
721
982
  }
722
983
  const parsedOptions = {
@@ -728,12 +989,13 @@ class TransactionOrchestrator extends events_1.EventEmitter {
728
989
  TransactionOrchestrator.workflowOptions[this.id] = parsedOptions;
729
990
  return [steps, features];
730
991
  }
731
- createTransactionFlow(transactionId, flowMetadata) {
992
+ createTransactionFlow(transactionId, flowMetadata, context) {
732
993
  const [steps, features] = TransactionOrchestrator.buildSteps(this.definition);
733
994
  const flow = {
734
995
  modelId: this.id,
735
996
  options: this.options,
736
997
  transactionId: transactionId,
998
+ runId: context?.runId ?? (0, ulid_1.ulid)(),
737
999
  metadata: flowMetadata,
738
1000
  hasAsyncSteps: features.hasAsyncSteps,
739
1001
  hasFailedSteps: false,
@@ -745,11 +1007,12 @@ class TransactionOrchestrator extends events_1.EventEmitter {
745
1007
  state: types_1.TransactionState.NOT_STARTED,
746
1008
  definition: this.definition,
747
1009
  steps,
1010
+ _v: 0, // Initialize version to 0
748
1011
  };
749
1012
  return flow;
750
1013
  }
751
- static async loadTransactionById(modelId, transactionId) {
752
- 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);
753
1016
  if (transaction !== null) {
754
1017
  const flow = transaction.flow;
755
1018
  const [steps] = TransactionOrchestrator.buildSteps(flow.definition, flow.steps);
@@ -821,6 +1084,7 @@ class TransactionOrchestrator extends events_1.EventEmitter {
821
1084
  failures: 0,
822
1085
  lastAttempt: null,
823
1086
  next: [],
1087
+ _v: 0, // Initialize step version to 0
824
1088
  });
825
1089
  }
826
1090
  if (Array.isArray(obj.next)) {
@@ -840,12 +1104,12 @@ class TransactionOrchestrator extends events_1.EventEmitter {
840
1104
  * @param payload - payload to be passed to all the transaction steps
841
1105
  * @param flowMetadata - flow metadata which can include event group id for example
842
1106
  */
843
- async beginTransaction(transactionId, handler, payload, flowMetadata) {
1107
+ async beginTransaction({ transactionId, handler, payload, flowMetadata, context, onLoad, }) {
844
1108
  const existingTransaction = await TransactionOrchestrator.loadTransactionById(this.id, transactionId);
845
1109
  let newTransaction = false;
846
1110
  let modelFlow;
847
1111
  if (!existingTransaction) {
848
- modelFlow = this.createTransactionFlow(transactionId, flowMetadata);
1112
+ modelFlow = this.createTransactionFlow(transactionId, flowMetadata, context);
849
1113
  newTransaction = true;
850
1114
  }
851
1115
  else {
@@ -853,7 +1117,12 @@ class TransactionOrchestrator extends events_1.EventEmitter {
853
1117
  }
854
1118
  const transaction = new distributed_transaction_1.DistributedTransaction(modelFlow, handler, payload, existingTransaction?.errors, existingTransaction?.context);
855
1119
  if (newTransaction && this.getOptions().store) {
856
- 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);
857
1126
  }
858
1127
  return transaction;
859
1128
  }
@@ -861,8 +1130,8 @@ class TransactionOrchestrator extends events_1.EventEmitter {
861
1130
  * @param transactionId - unique identifier of the transaction
862
1131
  * @param handler - function to handle action of the transaction
863
1132
  */
864
- async retrieveExistingTransaction(transactionId, handler) {
865
- 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 });
866
1135
  if (!existingTransaction) {
867
1136
  throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_FOUND, `Transaction ${transactionId} could not be found.`);
868
1137
  }
@@ -905,13 +1174,16 @@ class TransactionOrchestrator extends events_1.EventEmitter {
905
1174
  * @param handler - The handler function to execute the step
906
1175
  * @param transaction - The current transaction. If not provided it will be loaded based on the responseIdempotencyKey
907
1176
  */
908
- async skipStep(responseIdempotencyKey, handler, transaction) {
1177
+ async skipStep({ responseIdempotencyKey, handler, transaction, }) {
909
1178
  const [curTransaction, step] = await TransactionOrchestrator.getTransactionAndStepFromIdempotencyKey(responseIdempotencyKey, handler, transaction);
910
1179
  if (step.getStates().status === types_1.TransactionStepStatus.WAITING) {
911
1180
  this.emit(types_1.DistributedTransactionEvent.RESUME, {
912
1181
  transaction: curTransaction,
913
1182
  });
914
- await TransactionOrchestrator.skipStep(curTransaction, step);
1183
+ await TransactionOrchestrator.skipStep({
1184
+ transaction: curTransaction,
1185
+ step,
1186
+ });
915
1187
  await this.executeNext(curTransaction);
916
1188
  }
917
1189
  else {
@@ -919,19 +1191,48 @@ class TransactionOrchestrator extends events_1.EventEmitter {
919
1191
  }
920
1192
  return curTransaction;
921
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
+ }
922
1216
  /** Register a step success for a specific transaction and step
923
1217
  * @param responseIdempotencyKey - The idempotency key for the step
924
1218
  * @param handler - The handler function to execute the step
925
1219
  * @param transaction - The current transaction. If not provided it will be loaded based on the responseIdempotencyKey
926
1220
  * @param response - The response of the step
927
1221
  */
928
- async registerStepSuccess(responseIdempotencyKey, handler, transaction, response) {
1222
+ async registerStepSuccess({ responseIdempotencyKey, handler, transaction, response, onLoad, }) {
929
1223
  const [curTransaction, step] = await TransactionOrchestrator.getTransactionAndStepFromIdempotencyKey(responseIdempotencyKey, handler, transaction);
1224
+ if (onLoad) {
1225
+ await onLoad(curTransaction);
1226
+ }
930
1227
  if (step.getStates().status === types_1.TransactionStepStatus.WAITING) {
931
1228
  this.emit(types_1.DistributedTransactionEvent.RESUME, {
932
1229
  transaction: curTransaction,
933
1230
  });
934
- 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
+ }
935
1236
  await this.executeNext(curTransaction);
936
1237
  }
937
1238
  else {
@@ -947,13 +1248,22 @@ class TransactionOrchestrator extends events_1.EventEmitter {
947
1248
  * @param transaction - The current transaction
948
1249
  * @param response - The response of the step
949
1250
  */
950
- async registerStepFailure(responseIdempotencyKey, error, handler, transaction) {
1251
+ async registerStepFailure({ responseIdempotencyKey, error, handler, transaction, onLoad, forcePermanentFailure, }) {
951
1252
  const [curTransaction, step] = await TransactionOrchestrator.getTransactionAndStepFromIdempotencyKey(responseIdempotencyKey, handler, transaction);
1253
+ if (onLoad) {
1254
+ await onLoad(curTransaction);
1255
+ }
952
1256
  if (step.getStates().status === types_1.TransactionStepStatus.WAITING) {
953
1257
  this.emit(types_1.DistributedTransactionEvent.RESUME, {
954
1258
  transaction: curTransaction,
955
1259
  });
956
- 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
+ }
957
1267
  await this.executeNext(curTransaction);
958
1268
  }
959
1269
  else {