@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.
- package/dist/joiner/remote-joiner.d.ts +3 -2
- package/dist/joiner/remote-joiner.d.ts.map +1 -1
- package/dist/joiner/remote-joiner.js +310 -141
- package/dist/joiner/remote-joiner.js.map +1 -1
- package/dist/transaction/datastore/abstract-storage.d.ts +7 -5
- package/dist/transaction/datastore/abstract-storage.d.ts.map +1 -1
- package/dist/transaction/datastore/abstract-storage.js +3 -3
- package/dist/transaction/datastore/abstract-storage.js.map +1 -1
- package/dist/transaction/datastore/base-in-memory-storage.d.ts +2 -1
- package/dist/transaction/datastore/base-in-memory-storage.d.ts.map +1 -1
- package/dist/transaction/datastore/base-in-memory-storage.js +2 -0
- package/dist/transaction/datastore/base-in-memory-storage.js.map +1 -1
- package/dist/transaction/distributed-transaction.d.ts +23 -6
- package/dist/transaction/distributed-transaction.d.ts.map +1 -1
- package/dist/transaction/distributed-transaction.js +284 -54
- package/dist/transaction/distributed-transaction.js.map +1 -1
- package/dist/transaction/errors.d.ts +11 -0
- package/dist/transaction/errors.d.ts.map +1 -1
- package/dist/transaction/errors.js +34 -2
- package/dist/transaction/errors.js.map +1 -1
- package/dist/transaction/transaction-orchestrator.d.ts +99 -10
- package/dist/transaction/transaction-orchestrator.d.ts.map +1 -1
- package/dist/transaction/transaction-orchestrator.js +584 -274
- package/dist/transaction/transaction-orchestrator.js.map +1 -1
- package/dist/transaction/transaction-step.d.ts +2 -0
- package/dist/transaction/transaction-step.d.ts.map +1 -1
- package/dist/transaction/transaction-step.js +10 -3
- package/dist/transaction/transaction-step.js.map +1 -1
- package/dist/transaction/types.d.ts +26 -1
- package/dist/transaction/types.d.ts.map +1 -1
- package/dist/transaction/types.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/workflow/global-workflow.d.ts.map +1 -1
- package/dist/workflow/global-workflow.js +16 -4
- package/dist/workflow/global-workflow.js.map +1 -1
- package/dist/workflow/local-workflow.d.ts +3 -1
- package/dist/workflow/local-workflow.d.ts.map +1 -1
- package/dist/workflow/local-workflow.js +71 -9
- package/dist/workflow/local-workflow.js.map +1 -1
- 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
|
|
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) =>
|
|
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) =>
|
|
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 =
|
|
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:
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
408
|
-
const
|
|
409
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
436
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
461
|
-
|
|
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
|
-
|
|
464
|
-
|
|
620
|
+
throw error;
|
|
621
|
+
});
|
|
622
|
+
if (!continueExecution) {
|
|
623
|
+
break;
|
|
465
624
|
}
|
|
466
|
-
|
|
625
|
+
const execution = [];
|
|
626
|
+
const executionAsync = [];
|
|
627
|
+
let i = 0;
|
|
467
628
|
for (const step of nextSteps.next) {
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
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
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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
|
-
|
|
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
|
-
|
|
585
|
-
|
|
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
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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
|
-
|
|
639
|
-
|
|
640
|
-
|
|
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
|
-
|
|
643
|
-
|
|
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
|
-
|
|
647
|
-
|
|
648
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
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 {
|