@medusajs/orchestration 3.0.0-preview-20250410180148 → 3.0.0-preview-20251201152819

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 +312 -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 +599 -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 +31 -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,145 +640,262 @@ 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);
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;
645
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);
646
899
  }
647
900
  }
648
901
  /**
@@ -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() {
@@ -706,13 +970,12 @@ class TransactionOrchestrator extends events_1.EventEmitter {
706
970
  const hasRetriesTimeout = features.hasRetriesTimeout;
707
971
  const hasTransactionTimeout = !!this.options.timeout;
708
972
  const isIdempotent = !!this.options.idempotent;
709
- if (hasAsyncSteps) {
710
- this.options.store = true;
711
- }
712
973
  if (hasStepTimeouts ||
713
974
  hasRetriesTimeout ||
714
975
  hasTransactionTimeout ||
715
- isIdempotent) {
976
+ isIdempotent ||
977
+ this.options.retentionTime ||
978
+ hasAsyncSteps) {
716
979
  this.options.store = true;
717
980
  }
718
981
  const parsedOptions = {
@@ -724,12 +987,13 @@ class TransactionOrchestrator extends events_1.EventEmitter {
724
987
  TransactionOrchestrator.workflowOptions[this.id] = parsedOptions;
725
988
  return [steps, features];
726
989
  }
727
- createTransactionFlow(transactionId, flowMetadata) {
990
+ createTransactionFlow(transactionId, flowMetadata, context) {
728
991
  const [steps, features] = TransactionOrchestrator.buildSteps(this.definition);
729
992
  const flow = {
730
993
  modelId: this.id,
731
994
  options: this.options,
732
995
  transactionId: transactionId,
996
+ runId: context?.runId ?? (0, ulid_1.ulid)(),
733
997
  metadata: flowMetadata,
734
998
  hasAsyncSteps: features.hasAsyncSteps,
735
999
  hasFailedSteps: false,
@@ -741,11 +1005,12 @@ class TransactionOrchestrator extends events_1.EventEmitter {
741
1005
  state: types_1.TransactionState.NOT_STARTED,
742
1006
  definition: this.definition,
743
1007
  steps,
1008
+ _v: 0, // Initialize version to 0
744
1009
  };
745
1010
  return flow;
746
1011
  }
747
- static async loadTransactionById(modelId, transactionId) {
748
- const transaction = await distributed_transaction_1.DistributedTransaction.loadTransaction(modelId, transactionId);
1012
+ static async loadTransactionById(modelId, transactionId, options) {
1013
+ const transaction = await distributed_transaction_1.DistributedTransaction.loadTransaction(modelId, transactionId, options);
749
1014
  if (transaction !== null) {
750
1015
  const flow = transaction.flow;
751
1016
  const [steps] = TransactionOrchestrator.buildSteps(flow.definition, flow.steps);
@@ -786,6 +1051,9 @@ class TransactionOrchestrator extends events_1.EventEmitter {
786
1051
  }
787
1052
  const definitionCopy = { ...obj };
788
1053
  delete definitionCopy.next;
1054
+ const isAsync = !!definitionCopy.async;
1055
+ const hasRetryInterval = !!(definitionCopy.retryInterval || definitionCopy.retryIntervalAwaiting);
1056
+ const hasTimeout = !!definitionCopy.timeout;
789
1057
  if (definitionCopy.async) {
790
1058
  features.hasAsyncSteps = true;
791
1059
  }
@@ -799,6 +1067,16 @@ class TransactionOrchestrator extends events_1.EventEmitter {
799
1067
  if (definitionCopy.nested) {
800
1068
  features.hasNestedTransactions = true;
801
1069
  }
1070
+ /**
1071
+ * Force the checkpoint to save even for sync step when they have specific configurations.
1072
+ */
1073
+ definitionCopy.store = !!(definitionCopy.store ||
1074
+ isAsync ||
1075
+ hasRetryInterval ||
1076
+ hasTimeout);
1077
+ if (existingSteps?.[id]) {
1078
+ existingSteps[id].definition.store = definitionCopy.store;
1079
+ }
802
1080
  states[id] = Object.assign(new transaction_step_1.TransactionStep(), existingSteps?.[id] || {
803
1081
  id,
804
1082
  uuid: definitionCopy.uuid,
@@ -817,6 +1095,7 @@ class TransactionOrchestrator extends events_1.EventEmitter {
817
1095
  failures: 0,
818
1096
  lastAttempt: null,
819
1097
  next: [],
1098
+ _v: 0, // Initialize step version to 0
820
1099
  });
821
1100
  }
822
1101
  if (Array.isArray(obj.next)) {
@@ -836,12 +1115,12 @@ class TransactionOrchestrator extends events_1.EventEmitter {
836
1115
  * @param payload - payload to be passed to all the transaction steps
837
1116
  * @param flowMetadata - flow metadata which can include event group id for example
838
1117
  */
839
- async beginTransaction(transactionId, handler, payload, flowMetadata) {
1118
+ async beginTransaction({ transactionId, handler, payload, flowMetadata, context, onLoad, }) {
840
1119
  const existingTransaction = await TransactionOrchestrator.loadTransactionById(this.id, transactionId);
841
1120
  let newTransaction = false;
842
1121
  let modelFlow;
843
1122
  if (!existingTransaction) {
844
- modelFlow = this.createTransactionFlow(transactionId, flowMetadata);
1123
+ modelFlow = this.createTransactionFlow(transactionId, flowMetadata, context);
845
1124
  newTransaction = true;
846
1125
  }
847
1126
  else {
@@ -849,7 +1128,12 @@ class TransactionOrchestrator extends events_1.EventEmitter {
849
1128
  }
850
1129
  const transaction = new distributed_transaction_1.DistributedTransaction(modelFlow, handler, payload, existingTransaction?.errors, existingTransaction?.context);
851
1130
  if (newTransaction && this.getOptions().store) {
852
- await transaction.saveCheckpoint(modelFlow.hasAsyncSteps ? 0 : TransactionOrchestrator.DEFAULT_TTL);
1131
+ await transaction.saveCheckpoint({
1132
+ ttl: modelFlow.hasAsyncSteps ? 0 : TransactionOrchestrator.DEFAULT_TTL,
1133
+ });
1134
+ }
1135
+ if (onLoad) {
1136
+ await onLoad(transaction);
853
1137
  }
854
1138
  return transaction;
855
1139
  }
@@ -857,8 +1141,8 @@ class TransactionOrchestrator extends events_1.EventEmitter {
857
1141
  * @param transactionId - unique identifier of the transaction
858
1142
  * @param handler - function to handle action of the transaction
859
1143
  */
860
- async retrieveExistingTransaction(transactionId, handler) {
861
- const existingTransaction = await TransactionOrchestrator.loadTransactionById(this.id, transactionId);
1144
+ async retrieveExistingTransaction(transactionId, handler, options) {
1145
+ const existingTransaction = await TransactionOrchestrator.loadTransactionById(this.id, transactionId, { isCancelling: options?.isCancelling });
862
1146
  if (!existingTransaction) {
863
1147
  throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_FOUND, `Transaction ${transactionId} could not be found.`);
864
1148
  }
@@ -901,13 +1185,16 @@ class TransactionOrchestrator extends events_1.EventEmitter {
901
1185
  * @param handler - The handler function to execute the step
902
1186
  * @param transaction - The current transaction. If not provided it will be loaded based on the responseIdempotencyKey
903
1187
  */
904
- async skipStep(responseIdempotencyKey, handler, transaction) {
1188
+ async skipStep({ responseIdempotencyKey, handler, transaction, }) {
905
1189
  const [curTransaction, step] = await TransactionOrchestrator.getTransactionAndStepFromIdempotencyKey(responseIdempotencyKey, handler, transaction);
906
1190
  if (step.getStates().status === types_1.TransactionStepStatus.WAITING) {
907
1191
  this.emit(types_1.DistributedTransactionEvent.RESUME, {
908
1192
  transaction: curTransaction,
909
1193
  });
910
- await TransactionOrchestrator.skipStep(curTransaction, step);
1194
+ await TransactionOrchestrator.skipStep({
1195
+ transaction: curTransaction,
1196
+ step,
1197
+ });
911
1198
  await this.executeNext(curTransaction);
912
1199
  }
913
1200
  else {
@@ -915,19 +1202,48 @@ class TransactionOrchestrator extends events_1.EventEmitter {
915
1202
  }
916
1203
  return curTransaction;
917
1204
  }
1205
+ /**
1206
+ * Manually force a step to retry even if it is still in awaiting status
1207
+ * @param responseIdempotencyKey - The idempotency key for the step
1208
+ * @param handler - The handler function to execute the step
1209
+ * @param transaction - The current transaction. If not provided it will be loaded based on the responseIdempotencyKey
1210
+ */
1211
+ async retryStep({ responseIdempotencyKey, handler, transaction, onLoad, }) {
1212
+ const [curTransaction, step] = await TransactionOrchestrator.getTransactionAndStepFromIdempotencyKey(responseIdempotencyKey, handler, transaction);
1213
+ if (onLoad) {
1214
+ await onLoad(curTransaction);
1215
+ }
1216
+ if (step.getStates().status === types_1.TransactionStepStatus.WAITING) {
1217
+ this.emit(types_1.DistributedTransactionEvent.RESUME, {
1218
+ transaction: curTransaction,
1219
+ });
1220
+ await TransactionOrchestrator.retryStep(curTransaction, step);
1221
+ }
1222
+ else {
1223
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_ALLOWED, `Cannot retry step when status is ${step.getStates().status}`);
1224
+ }
1225
+ return curTransaction;
1226
+ }
918
1227
  /** Register a step success for a specific transaction and step
919
1228
  * @param responseIdempotencyKey - The idempotency key for the step
920
1229
  * @param handler - The handler function to execute the step
921
1230
  * @param transaction - The current transaction. If not provided it will be loaded based on the responseIdempotencyKey
922
1231
  * @param response - The response of the step
923
1232
  */
924
- async registerStepSuccess(responseIdempotencyKey, handler, transaction, response) {
1233
+ async registerStepSuccess({ responseIdempotencyKey, handler, transaction, response, onLoad, }) {
925
1234
  const [curTransaction, step] = await TransactionOrchestrator.getTransactionAndStepFromIdempotencyKey(responseIdempotencyKey, handler, transaction);
1235
+ if (onLoad) {
1236
+ await onLoad(curTransaction);
1237
+ }
926
1238
  if (step.getStates().status === types_1.TransactionStepStatus.WAITING) {
927
1239
  this.emit(types_1.DistributedTransactionEvent.RESUME, {
928
1240
  transaction: curTransaction,
929
1241
  });
930
- await TransactionOrchestrator.setStepSuccess(curTransaction, step, response);
1242
+ const ret = await TransactionOrchestrator.setStepSuccess(curTransaction, step, response);
1243
+ if (ret.transactionIsCancelling) {
1244
+ await this.cancelTransaction(curTransaction);
1245
+ return curTransaction;
1246
+ }
931
1247
  await this.executeNext(curTransaction);
932
1248
  }
933
1249
  else {
@@ -943,13 +1259,22 @@ class TransactionOrchestrator extends events_1.EventEmitter {
943
1259
  * @param transaction - The current transaction
944
1260
  * @param response - The response of the step
945
1261
  */
946
- async registerStepFailure(responseIdempotencyKey, error, handler, transaction) {
1262
+ async registerStepFailure({ responseIdempotencyKey, error, handler, transaction, onLoad, forcePermanentFailure, }) {
947
1263
  const [curTransaction, step] = await TransactionOrchestrator.getTransactionAndStepFromIdempotencyKey(responseIdempotencyKey, handler, transaction);
1264
+ if (onLoad) {
1265
+ await onLoad(curTransaction);
1266
+ }
948
1267
  if (step.getStates().status === types_1.TransactionStepStatus.WAITING) {
949
1268
  this.emit(types_1.DistributedTransactionEvent.RESUME, {
950
1269
  transaction: curTransaction,
951
1270
  });
952
- await TransactionOrchestrator.setStepFailure(curTransaction, step, error, 0);
1271
+ const ret = await TransactionOrchestrator.setStepFailure(curTransaction, step, error,
1272
+ // On permanent failure, the step should not consider any retries
1273
+ forcePermanentFailure ? 0 : step.definition.maxRetries);
1274
+ if (ret.transactionIsCancelling) {
1275
+ await this.cancelTransaction(curTransaction);
1276
+ return curTransaction;
1277
+ }
953
1278
  await this.executeNext(curTransaction);
954
1279
  }
955
1280
  else {