@medusajs/orchestration 3.0.0-snapshot-20250410112222 → 3.0.0-snapshot-20251104011621
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +585 -271
- 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,50 +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
|
}
|
|
625
|
+
const execution = [];
|
|
626
|
+
const executionAsync = [];
|
|
627
|
+
let i = 0;
|
|
466
628
|
for (const step of nextSteps.next) {
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
: types_1.TransactionHandlerType.INVOKE;
|
|
471
|
-
step.lastAttempt = Date.now();
|
|
472
|
-
step.attempts++;
|
|
473
|
-
if (curState.state === utils_1.TransactionStepState.NOT_STARTED) {
|
|
474
|
-
if (!step.startedAt) {
|
|
475
|
-
step.startedAt = Date.now();
|
|
476
|
-
}
|
|
477
|
-
if (step.isCompensating()) {
|
|
478
|
-
step.changeState(utils_1.TransactionStepState.COMPENSATING);
|
|
479
|
-
if (step.definition.noCompensation) {
|
|
480
|
-
step.changeState(utils_1.TransactionStepState.REVERTED);
|
|
481
|
-
continue;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
else if (flow.state === types_1.TransactionState.INVOKING) {
|
|
485
|
-
step.changeState(utils_1.TransactionStepState.INVOKING);
|
|
486
|
-
}
|
|
629
|
+
const stepIndex = i++;
|
|
630
|
+
if (!stepsShouldContinueExecution[stepIndex]) {
|
|
631
|
+
continue;
|
|
487
632
|
}
|
|
488
|
-
step.changeStatus(types_1.TransactionStepStatus.WAITING);
|
|
489
|
-
const payload = new distributed_transaction_1.TransactionPayload({
|
|
490
|
-
model_id: flow.modelId,
|
|
491
|
-
idempotency_key: TransactionOrchestrator.getKeyName(flow.modelId, flow.transactionId, step.definition.action, type),
|
|
492
|
-
action: step.definition.action + "",
|
|
493
|
-
action_type: type,
|
|
494
|
-
attempt: step.attempts,
|
|
495
|
-
timestamp: Date.now(),
|
|
496
|
-
}, transaction.payload, transaction.getContext());
|
|
497
633
|
if (step.hasTimeout() && !step.timedOutAt && step.attempts === 1) {
|
|
498
634
|
await transaction.scheduleStepTimeout(step, step.definition.timeout);
|
|
499
635
|
}
|
|
@@ -504,146 +640,263 @@ class TransactionOrchestrator extends events_1.EventEmitter {
|
|
|
504
640
|
const isAsync = step.isCompensating()
|
|
505
641
|
? step.definition.compensateAsync
|
|
506
642
|
: step.definition.async;
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
-
|
|
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
|
-
|
|
583
|
-
|
|
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
|
-
|
|
632
|
-
|
|
659
|
+
await (0, utils_1.promiseAll)(execution);
|
|
660
|
+
if (!nextSteps.next.length || (hasAsyncSteps && !execution.length)) {
|
|
661
|
+
continueExecution = false;
|
|
633
662
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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
|
-
|
|
643
|
-
|
|
644
|
-
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Finalize the transaction when all steps are complete
|
|
678
|
+
*/
|
|
679
|
+
async finalizeTransaction(transaction) {
|
|
680
|
+
if (transaction.hasTimeout()) {
|
|
681
|
+
void transaction.clearTransactionTimeout();
|
|
682
|
+
}
|
|
683
|
+
await transaction.saveCheckpoint().catch((error) => {
|
|
684
|
+
if (!TransactionOrchestrator.isExpectedError(error)) {
|
|
685
|
+
throw error;
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
this.emit(types_1.DistributedTransactionEvent.FINISH, { transaction });
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Prepare a step for execution by setting state and incrementing attempts
|
|
692
|
+
*/
|
|
693
|
+
prepareStepForExecution(step, flow) {
|
|
694
|
+
const curState = step.getStates();
|
|
695
|
+
step.lastAttempt = Date.now();
|
|
696
|
+
step.attempts++;
|
|
697
|
+
if (curState.state === utils_1.TransactionStepState.NOT_STARTED) {
|
|
698
|
+
if (!step.startedAt) {
|
|
699
|
+
step.startedAt = Date.now();
|
|
700
|
+
}
|
|
701
|
+
if (step.isCompensating()) {
|
|
702
|
+
step.changeState(utils_1.TransactionStepState.COMPENSATING);
|
|
703
|
+
if (step.definition.noCompensation) {
|
|
704
|
+
step.changeState(utils_1.TransactionStepState.REVERTED);
|
|
705
|
+
return { shouldContinueExecution: false };
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
else if (flow.state === types_1.TransactionState.INVOKING) {
|
|
709
|
+
step.changeState(utils_1.TransactionStepState.INVOKING);
|
|
645
710
|
}
|
|
646
711
|
}
|
|
712
|
+
step.changeStatus(types_1.TransactionStepStatus.WAITING);
|
|
713
|
+
return { shouldContinueExecution: true };
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Create the payload for a step execution
|
|
717
|
+
*/
|
|
718
|
+
createStepPayload(transaction, step, flow, type) {
|
|
719
|
+
return new distributed_transaction_1.TransactionPayload({
|
|
720
|
+
model_id: flow.modelId,
|
|
721
|
+
idempotency_key: TransactionOrchestrator.getKeyName(flow.modelId, flow.transactionId, step.definition.action, type),
|
|
722
|
+
action: step.definition.action + "",
|
|
723
|
+
action_type: type,
|
|
724
|
+
attempt: step.attempts,
|
|
725
|
+
timestamp: Date.now(),
|
|
726
|
+
}, transaction.payload, transaction.getContext());
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Prepare handler arguments for step execution
|
|
730
|
+
*/
|
|
731
|
+
prepareHandlerArgs(transaction, step, payload, type) {
|
|
732
|
+
return [
|
|
733
|
+
step.definition.action + "",
|
|
734
|
+
type,
|
|
735
|
+
payload,
|
|
736
|
+
transaction,
|
|
737
|
+
step,
|
|
738
|
+
this,
|
|
739
|
+
];
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Create the step execution promise with optional tracing
|
|
743
|
+
*/
|
|
744
|
+
createStepExecutionPromise(transaction, step) {
|
|
745
|
+
const type = step.isCompensating()
|
|
746
|
+
? types_1.TransactionHandlerType.COMPENSATE
|
|
747
|
+
: types_1.TransactionHandlerType.INVOKE;
|
|
748
|
+
const flow = transaction.getFlow();
|
|
749
|
+
const payload = this.createStepPayload(transaction, step, flow, type);
|
|
750
|
+
const handlerArgs = this.prepareHandlerArgs(transaction, step, payload, type);
|
|
751
|
+
const traceData = {
|
|
752
|
+
action: step.definition.action + "",
|
|
753
|
+
type,
|
|
754
|
+
step_id: step.id,
|
|
755
|
+
step_uuid: step.uuid + "",
|
|
756
|
+
attempts: step.attempts,
|
|
757
|
+
failures: step.failures,
|
|
758
|
+
async: !!(type === "invoke"
|
|
759
|
+
? step.definition.async
|
|
760
|
+
: step.definition.compensateAsync),
|
|
761
|
+
idempotency_key: handlerArgs[2].metadata.idempotency_key,
|
|
762
|
+
};
|
|
763
|
+
const stepHandler = async () => {
|
|
764
|
+
return await transaction.handler(...handlerArgs);
|
|
765
|
+
};
|
|
766
|
+
// Return the appropriate promise based on tracing configuration
|
|
767
|
+
if (TransactionOrchestrator.traceStep) {
|
|
768
|
+
return () => TransactionOrchestrator.traceStep(stepHandler, traceData);
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
return stepHandler;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Execute a synchronous step and handle its result
|
|
776
|
+
*/
|
|
777
|
+
executeSyncStep(promiseFn, transaction, step, nextSteps) {
|
|
778
|
+
return promiseFn()
|
|
779
|
+
.then(async (response) => {
|
|
780
|
+
await this.handleStepExpiration(transaction, step, nextSteps);
|
|
781
|
+
const output = response?.__type || response?.output?.__type
|
|
782
|
+
? response.output
|
|
783
|
+
: response;
|
|
784
|
+
if (errors_1.SkipStepResponse.isSkipStepResponse(output)) {
|
|
785
|
+
await TransactionOrchestrator.skipStep({
|
|
786
|
+
transaction,
|
|
787
|
+
step,
|
|
788
|
+
});
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
await this.handleStepSuccess(transaction, step, response);
|
|
792
|
+
})
|
|
793
|
+
.catch(async (error) => {
|
|
794
|
+
if (TransactionOrchestrator.isExpectedError(error)) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
const response = error?.getStepResponse?.();
|
|
798
|
+
await this.handleStepExpiration(transaction, step, nextSteps);
|
|
799
|
+
if (errors_1.PermanentStepFailureError.isPermanentStepFailureError(error)) {
|
|
800
|
+
await this.handleStepFailure(transaction, step, error, true, response);
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
await this.handleStepFailure(transaction, step, error, false, response);
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Execute an asynchronous step and handle its result
|
|
808
|
+
*/
|
|
809
|
+
executeAsyncStep(promiseFn, transaction, step, nextSteps) {
|
|
810
|
+
return promiseFn()
|
|
811
|
+
.then(async (response) => {
|
|
812
|
+
const output = response?.__type || response?.output?.__type
|
|
813
|
+
? response.output
|
|
814
|
+
: response;
|
|
815
|
+
if (errors_1.SkipStepResponse.isSkipStepResponse(output)) {
|
|
816
|
+
await TransactionOrchestrator.skipStep({
|
|
817
|
+
transaction,
|
|
818
|
+
step,
|
|
819
|
+
});
|
|
820
|
+
// Schedule to continue the execution of async steps because they are not awaited on purpose and can be handled by another machine
|
|
821
|
+
await transaction.scheduleRetry(step, 0);
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
if (!step.definition.backgroundExecution || step.definition.nested) {
|
|
826
|
+
const eventName = types_1.DistributedTransactionEvent.STEP_AWAITING;
|
|
827
|
+
transaction.emit(eventName, { step, transaction });
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
await this.handleStepExpiration(transaction, step, nextSteps);
|
|
831
|
+
await this.handleStepSuccess(transaction, step, response);
|
|
832
|
+
}
|
|
833
|
+
})
|
|
834
|
+
.catch(async (error) => {
|
|
835
|
+
if (TransactionOrchestrator.isExpectedError(error)) {
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
const response = error?.getStepResponse?.();
|
|
839
|
+
if (errors_1.PermanentStepFailureError.isPermanentStepFailureError(error)) {
|
|
840
|
+
await this.handleStepFailure(transaction, step, error, true, response);
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
await this.handleStepFailure(transaction, step, error, false, response);
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Check if step or transaction has expired and handle timeouts
|
|
848
|
+
*/
|
|
849
|
+
async handleStepExpiration(transaction, step, nextSteps) {
|
|
850
|
+
if (this.hasExpired({ transaction, step }, Date.now())) {
|
|
851
|
+
await this.checkStepTimeout(transaction, step);
|
|
852
|
+
await this.checkTransactionTimeout(transaction, nextSteps.next.includes(step) ? nextSteps.next : [step]);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Handle successful step completion
|
|
857
|
+
*/
|
|
858
|
+
async handleStepSuccess(transaction, step, response) {
|
|
859
|
+
const isAsync = step.isCompensating()
|
|
860
|
+
? step.definition.compensateAsync
|
|
861
|
+
: step.definition.async;
|
|
862
|
+
if ((0, utils_1.isDefined)(response) && step.saveResponse && !isAsync) {
|
|
863
|
+
transaction.addResponse(step.definition.action, step.isCompensating()
|
|
864
|
+
? types_1.TransactionHandlerType.COMPENSATE
|
|
865
|
+
: types_1.TransactionHandlerType.INVOKE, response);
|
|
866
|
+
}
|
|
867
|
+
const ret = await TransactionOrchestrator.setStepSuccess(transaction, step, response);
|
|
868
|
+
if (ret.transactionIsCancelling) {
|
|
869
|
+
await this.cancelTransaction(transaction, {
|
|
870
|
+
preventExecuteNext: true,
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
if (isAsync && !ret.stopExecution) {
|
|
874
|
+
// Schedule to continue the execution of async steps because they are not awaited on purpose and can be handled by another machine
|
|
875
|
+
await transaction.scheduleRetry(step, 0);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Handle step failure
|
|
880
|
+
*/
|
|
881
|
+
async handleStepFailure(transaction, step, error, isPermanent, response) {
|
|
882
|
+
const isAsync = step.isCompensating()
|
|
883
|
+
? step.definition.compensateAsync
|
|
884
|
+
: step.definition.async;
|
|
885
|
+
if ((0, utils_1.isDefined)(response) && step.saveResponse) {
|
|
886
|
+
transaction.addResponse(step.definition.action, step.isCompensating()
|
|
887
|
+
? types_1.TransactionHandlerType.COMPENSATE
|
|
888
|
+
: types_1.TransactionHandlerType.INVOKE, response);
|
|
889
|
+
}
|
|
890
|
+
const ret = await TransactionOrchestrator.setStepFailure(transaction, step, error, isPermanent ? 0 : step.definition.maxRetries);
|
|
891
|
+
if (ret.transactionIsCancelling) {
|
|
892
|
+
await this.cancelTransaction(transaction, {
|
|
893
|
+
preventExecuteNext: true,
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
if (isAsync && !ret.stopExecution) {
|
|
897
|
+
// Schedule to continue the execution of async steps because they are not awaited on purpose and can be handled by another machine
|
|
898
|
+
await transaction.scheduleRetry(step, 0);
|
|
899
|
+
}
|
|
647
900
|
}
|
|
648
901
|
/**
|
|
649
902
|
* Start a new transaction or resume a transaction that has been previously started
|
|
@@ -661,7 +914,9 @@ class TransactionOrchestrator extends events_1.EventEmitter {
|
|
|
661
914
|
if (flow.state === types_1.TransactionState.NOT_STARTED) {
|
|
662
915
|
flow.state = types_1.TransactionState.INVOKING;
|
|
663
916
|
flow.startedAt = Date.now();
|
|
664
|
-
await transaction.saveCheckpoint(
|
|
917
|
+
await transaction.saveCheckpoint({
|
|
918
|
+
ttl: flow.hasAsyncSteps ? 0 : TransactionOrchestrator.DEFAULT_TTL,
|
|
919
|
+
});
|
|
665
920
|
if (transaction.hasTimeout()) {
|
|
666
921
|
await transaction.scheduleTransactionTimeout(transaction.getTimeout());
|
|
667
922
|
}
|
|
@@ -687,7 +942,7 @@ class TransactionOrchestrator extends events_1.EventEmitter {
|
|
|
687
942
|
* Cancel and revert a transaction compensating all its executed steps. It can be an ongoing transaction or a completed one
|
|
688
943
|
* @param transaction - The transaction to be reverted
|
|
689
944
|
*/
|
|
690
|
-
async cancelTransaction(transaction) {
|
|
945
|
+
async cancelTransaction(transaction, options) {
|
|
691
946
|
if (transaction.modelId !== this.id) {
|
|
692
947
|
throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_ALLOWED, `TransactionModel "${transaction.modelId}" cannot be orchestrated by "${this.id}" model.`);
|
|
693
948
|
}
|
|
@@ -695,7 +950,16 @@ class TransactionOrchestrator extends events_1.EventEmitter {
|
|
|
695
950
|
if (flow.state === types_1.TransactionState.FAILED) {
|
|
696
951
|
throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_ALLOWED, `Cannot revert a permanent failed transaction.`);
|
|
697
952
|
}
|
|
953
|
+
if (flow.state === types_1.TransactionState.COMPENSATING ||
|
|
954
|
+
flow.state === types_1.TransactionState.WAITING_TO_COMPENSATE) {
|
|
955
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_ALLOWED, `Cannot revert a transaction that is already compensating.`);
|
|
956
|
+
}
|
|
698
957
|
flow.state = types_1.TransactionState.WAITING_TO_COMPENSATE;
|
|
958
|
+
flow.cancelledAt = Date.now();
|
|
959
|
+
await transaction.saveCheckpoint();
|
|
960
|
+
if (options?.preventExecuteNext) {
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
699
963
|
await this.executeNext(transaction);
|
|
700
964
|
}
|
|
701
965
|
parseFlowOptions() {
|
|
@@ -712,7 +976,8 @@ class TransactionOrchestrator extends events_1.EventEmitter {
|
|
|
712
976
|
if (hasStepTimeouts ||
|
|
713
977
|
hasRetriesTimeout ||
|
|
714
978
|
hasTransactionTimeout ||
|
|
715
|
-
isIdempotent
|
|
979
|
+
isIdempotent ||
|
|
980
|
+
this.options.retentionTime) {
|
|
716
981
|
this.options.store = true;
|
|
717
982
|
}
|
|
718
983
|
const parsedOptions = {
|
|
@@ -724,12 +989,13 @@ class TransactionOrchestrator extends events_1.EventEmitter {
|
|
|
724
989
|
TransactionOrchestrator.workflowOptions[this.id] = parsedOptions;
|
|
725
990
|
return [steps, features];
|
|
726
991
|
}
|
|
727
|
-
createTransactionFlow(transactionId, flowMetadata) {
|
|
992
|
+
createTransactionFlow(transactionId, flowMetadata, context) {
|
|
728
993
|
const [steps, features] = TransactionOrchestrator.buildSteps(this.definition);
|
|
729
994
|
const flow = {
|
|
730
995
|
modelId: this.id,
|
|
731
996
|
options: this.options,
|
|
732
997
|
transactionId: transactionId,
|
|
998
|
+
runId: context?.runId ?? (0, ulid_1.ulid)(),
|
|
733
999
|
metadata: flowMetadata,
|
|
734
1000
|
hasAsyncSteps: features.hasAsyncSteps,
|
|
735
1001
|
hasFailedSteps: false,
|
|
@@ -741,11 +1007,12 @@ class TransactionOrchestrator extends events_1.EventEmitter {
|
|
|
741
1007
|
state: types_1.TransactionState.NOT_STARTED,
|
|
742
1008
|
definition: this.definition,
|
|
743
1009
|
steps,
|
|
1010
|
+
_v: 0, // Initialize version to 0
|
|
744
1011
|
};
|
|
745
1012
|
return flow;
|
|
746
1013
|
}
|
|
747
|
-
static async loadTransactionById(modelId, transactionId) {
|
|
748
|
-
const transaction = await distributed_transaction_1.DistributedTransaction.loadTransaction(modelId, transactionId);
|
|
1014
|
+
static async loadTransactionById(modelId, transactionId, options) {
|
|
1015
|
+
const transaction = await distributed_transaction_1.DistributedTransaction.loadTransaction(modelId, transactionId, options);
|
|
749
1016
|
if (transaction !== null) {
|
|
750
1017
|
const flow = transaction.flow;
|
|
751
1018
|
const [steps] = TransactionOrchestrator.buildSteps(flow.definition, flow.steps);
|
|
@@ -817,6 +1084,7 @@ class TransactionOrchestrator extends events_1.EventEmitter {
|
|
|
817
1084
|
failures: 0,
|
|
818
1085
|
lastAttempt: null,
|
|
819
1086
|
next: [],
|
|
1087
|
+
_v: 0, // Initialize step version to 0
|
|
820
1088
|
});
|
|
821
1089
|
}
|
|
822
1090
|
if (Array.isArray(obj.next)) {
|
|
@@ -836,12 +1104,12 @@ class TransactionOrchestrator extends events_1.EventEmitter {
|
|
|
836
1104
|
* @param payload - payload to be passed to all the transaction steps
|
|
837
1105
|
* @param flowMetadata - flow metadata which can include event group id for example
|
|
838
1106
|
*/
|
|
839
|
-
async beginTransaction(transactionId, handler, payload, flowMetadata) {
|
|
1107
|
+
async beginTransaction({ transactionId, handler, payload, flowMetadata, context, onLoad, }) {
|
|
840
1108
|
const existingTransaction = await TransactionOrchestrator.loadTransactionById(this.id, transactionId);
|
|
841
1109
|
let newTransaction = false;
|
|
842
1110
|
let modelFlow;
|
|
843
1111
|
if (!existingTransaction) {
|
|
844
|
-
modelFlow = this.createTransactionFlow(transactionId, flowMetadata);
|
|
1112
|
+
modelFlow = this.createTransactionFlow(transactionId, flowMetadata, context);
|
|
845
1113
|
newTransaction = true;
|
|
846
1114
|
}
|
|
847
1115
|
else {
|
|
@@ -849,7 +1117,12 @@ class TransactionOrchestrator extends events_1.EventEmitter {
|
|
|
849
1117
|
}
|
|
850
1118
|
const transaction = new distributed_transaction_1.DistributedTransaction(modelFlow, handler, payload, existingTransaction?.errors, existingTransaction?.context);
|
|
851
1119
|
if (newTransaction && this.getOptions().store) {
|
|
852
|
-
await transaction.saveCheckpoint(
|
|
1120
|
+
await transaction.saveCheckpoint({
|
|
1121
|
+
ttl: modelFlow.hasAsyncSteps ? 0 : TransactionOrchestrator.DEFAULT_TTL,
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
if (onLoad) {
|
|
1125
|
+
await onLoad(transaction);
|
|
853
1126
|
}
|
|
854
1127
|
return transaction;
|
|
855
1128
|
}
|
|
@@ -857,8 +1130,8 @@ class TransactionOrchestrator extends events_1.EventEmitter {
|
|
|
857
1130
|
* @param transactionId - unique identifier of the transaction
|
|
858
1131
|
* @param handler - function to handle action of the transaction
|
|
859
1132
|
*/
|
|
860
|
-
async retrieveExistingTransaction(transactionId, handler) {
|
|
861
|
-
const existingTransaction = await TransactionOrchestrator.loadTransactionById(this.id, transactionId);
|
|
1133
|
+
async retrieveExistingTransaction(transactionId, handler, options) {
|
|
1134
|
+
const existingTransaction = await TransactionOrchestrator.loadTransactionById(this.id, transactionId, { isCancelling: options?.isCancelling });
|
|
862
1135
|
if (!existingTransaction) {
|
|
863
1136
|
throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_FOUND, `Transaction ${transactionId} could not be found.`);
|
|
864
1137
|
}
|
|
@@ -901,13 +1174,16 @@ class TransactionOrchestrator extends events_1.EventEmitter {
|
|
|
901
1174
|
* @param handler - The handler function to execute the step
|
|
902
1175
|
* @param transaction - The current transaction. If not provided it will be loaded based on the responseIdempotencyKey
|
|
903
1176
|
*/
|
|
904
|
-
async skipStep(responseIdempotencyKey, handler, transaction) {
|
|
1177
|
+
async skipStep({ responseIdempotencyKey, handler, transaction, }) {
|
|
905
1178
|
const [curTransaction, step] = await TransactionOrchestrator.getTransactionAndStepFromIdempotencyKey(responseIdempotencyKey, handler, transaction);
|
|
906
1179
|
if (step.getStates().status === types_1.TransactionStepStatus.WAITING) {
|
|
907
1180
|
this.emit(types_1.DistributedTransactionEvent.RESUME, {
|
|
908
1181
|
transaction: curTransaction,
|
|
909
1182
|
});
|
|
910
|
-
await TransactionOrchestrator.skipStep(
|
|
1183
|
+
await TransactionOrchestrator.skipStep({
|
|
1184
|
+
transaction: curTransaction,
|
|
1185
|
+
step,
|
|
1186
|
+
});
|
|
911
1187
|
await this.executeNext(curTransaction);
|
|
912
1188
|
}
|
|
913
1189
|
else {
|
|
@@ -915,19 +1191,48 @@ class TransactionOrchestrator extends events_1.EventEmitter {
|
|
|
915
1191
|
}
|
|
916
1192
|
return curTransaction;
|
|
917
1193
|
}
|
|
1194
|
+
/**
|
|
1195
|
+
* Manually force a step to retry even if it is still in awaiting status
|
|
1196
|
+
* @param responseIdempotencyKey - The idempotency key for the step
|
|
1197
|
+
* @param handler - The handler function to execute the step
|
|
1198
|
+
* @param transaction - The current transaction. If not provided it will be loaded based on the responseIdempotencyKey
|
|
1199
|
+
*/
|
|
1200
|
+
async retryStep({ responseIdempotencyKey, handler, transaction, onLoad, }) {
|
|
1201
|
+
const [curTransaction, step] = await TransactionOrchestrator.getTransactionAndStepFromIdempotencyKey(responseIdempotencyKey, handler, transaction);
|
|
1202
|
+
if (onLoad) {
|
|
1203
|
+
await onLoad(curTransaction);
|
|
1204
|
+
}
|
|
1205
|
+
if (step.getStates().status === types_1.TransactionStepStatus.WAITING) {
|
|
1206
|
+
this.emit(types_1.DistributedTransactionEvent.RESUME, {
|
|
1207
|
+
transaction: curTransaction,
|
|
1208
|
+
});
|
|
1209
|
+
await TransactionOrchestrator.retryStep(curTransaction, step);
|
|
1210
|
+
}
|
|
1211
|
+
else {
|
|
1212
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_ALLOWED, `Cannot retry step when status is ${step.getStates().status}`);
|
|
1213
|
+
}
|
|
1214
|
+
return curTransaction;
|
|
1215
|
+
}
|
|
918
1216
|
/** Register a step success for a specific transaction and step
|
|
919
1217
|
* @param responseIdempotencyKey - The idempotency key for the step
|
|
920
1218
|
* @param handler - The handler function to execute the step
|
|
921
1219
|
* @param transaction - The current transaction. If not provided it will be loaded based on the responseIdempotencyKey
|
|
922
1220
|
* @param response - The response of the step
|
|
923
1221
|
*/
|
|
924
|
-
async registerStepSuccess(responseIdempotencyKey, handler, transaction, response) {
|
|
1222
|
+
async registerStepSuccess({ responseIdempotencyKey, handler, transaction, response, onLoad, }) {
|
|
925
1223
|
const [curTransaction, step] = await TransactionOrchestrator.getTransactionAndStepFromIdempotencyKey(responseIdempotencyKey, handler, transaction);
|
|
1224
|
+
if (onLoad) {
|
|
1225
|
+
await onLoad(curTransaction);
|
|
1226
|
+
}
|
|
926
1227
|
if (step.getStates().status === types_1.TransactionStepStatus.WAITING) {
|
|
927
1228
|
this.emit(types_1.DistributedTransactionEvent.RESUME, {
|
|
928
1229
|
transaction: curTransaction,
|
|
929
1230
|
});
|
|
930
|
-
await TransactionOrchestrator.setStepSuccess(curTransaction, step, response);
|
|
1231
|
+
const ret = await TransactionOrchestrator.setStepSuccess(curTransaction, step, response);
|
|
1232
|
+
if (ret.transactionIsCancelling) {
|
|
1233
|
+
await this.cancelTransaction(curTransaction);
|
|
1234
|
+
return curTransaction;
|
|
1235
|
+
}
|
|
931
1236
|
await this.executeNext(curTransaction);
|
|
932
1237
|
}
|
|
933
1238
|
else {
|
|
@@ -943,13 +1248,22 @@ class TransactionOrchestrator extends events_1.EventEmitter {
|
|
|
943
1248
|
* @param transaction - The current transaction
|
|
944
1249
|
* @param response - The response of the step
|
|
945
1250
|
*/
|
|
946
|
-
async registerStepFailure(responseIdempotencyKey, error, handler, transaction) {
|
|
1251
|
+
async registerStepFailure({ responseIdempotencyKey, error, handler, transaction, onLoad, forcePermanentFailure, }) {
|
|
947
1252
|
const [curTransaction, step] = await TransactionOrchestrator.getTransactionAndStepFromIdempotencyKey(responseIdempotencyKey, handler, transaction);
|
|
1253
|
+
if (onLoad) {
|
|
1254
|
+
await onLoad(curTransaction);
|
|
1255
|
+
}
|
|
948
1256
|
if (step.getStates().status === types_1.TransactionStepStatus.WAITING) {
|
|
949
1257
|
this.emit(types_1.DistributedTransactionEvent.RESUME, {
|
|
950
1258
|
transaction: curTransaction,
|
|
951
1259
|
});
|
|
952
|
-
await TransactionOrchestrator.setStepFailure(curTransaction, step, error,
|
|
1260
|
+
const ret = await TransactionOrchestrator.setStepFailure(curTransaction, step, error,
|
|
1261
|
+
// On permanent failure, the step should not consider any retries
|
|
1262
|
+
forcePermanentFailure ? 0 : step.definition.maxRetries);
|
|
1263
|
+
if (ret.transactionIsCancelling) {
|
|
1264
|
+
await this.cancelTransaction(curTransaction);
|
|
1265
|
+
return curTransaction;
|
|
1266
|
+
}
|
|
953
1267
|
await this.executeNext(curTransaction);
|
|
954
1268
|
}
|
|
955
1269
|
else {
|