@8medusa/orchestration 2.7.0
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/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/joiner/helpers.d.ts +3 -0
- package/dist/joiner/helpers.d.ts.map +1 -0
- package/dist/joiner/helpers.js +69 -0
- package/dist/joiner/helpers.js.map +1 -0
- package/dist/joiner/index.d.ts +3 -0
- package/dist/joiner/index.d.ts.map +1 -0
- package/dist/joiner/index.js +19 -0
- package/dist/joiner/index.js.map +1 -0
- package/dist/joiner/remote-joiner.d.ts +42 -0
- package/dist/joiner/remote-joiner.d.ts.map +1 -0
- package/dist/joiner/remote-joiner.js +1108 -0
- package/dist/joiner/remote-joiner.js.map +1 -0
- package/dist/transaction/datastore/abstract-storage.d.ts +42 -0
- package/dist/transaction/datastore/abstract-storage.d.ts.map +1 -0
- package/dist/transaction/datastore/abstract-storage.js +52 -0
- package/dist/transaction/datastore/abstract-storage.js.map +1 -0
- package/dist/transaction/datastore/base-in-memory-storage.d.ts +11 -0
- package/dist/transaction/datastore/base-in-memory-storage.d.ts.map +1 -0
- package/dist/transaction/datastore/base-in-memory-storage.js +33 -0
- package/dist/transaction/datastore/base-in-memory-storage.js.map +1 -0
- package/dist/transaction/distributed-transaction.d.ts +99 -0
- package/dist/transaction/distributed-transaction.d.ts.map +1 -0
- package/dist/transaction/distributed-transaction.js +258 -0
- package/dist/transaction/distributed-transaction.js.map +1 -0
- package/dist/transaction/errors.d.ts +31 -0
- package/dist/transaction/errors.d.ts.map +1 -0
- package/dist/transaction/errors.js +89 -0
- package/dist/transaction/errors.js.map +1 -0
- package/dist/transaction/index.d.ts +8 -0
- package/dist/transaction/index.d.ts.map +1 -0
- package/dist/transaction/index.js +24 -0
- package/dist/transaction/index.js.map +1 -0
- package/dist/transaction/orchestrator-builder.d.ts +36 -0
- package/dist/transaction/orchestrator-builder.d.ts.map +1 -0
- package/dist/transaction/orchestrator-builder.js +300 -0
- package/dist/transaction/orchestrator-builder.js.map +1 -0
- package/dist/transaction/transaction-orchestrator.d.ts +181 -0
- package/dist/transaction/transaction-orchestrator.d.ts.map +1 -0
- package/dist/transaction/transaction-orchestrator.js +1061 -0
- package/dist/transaction/transaction-orchestrator.js.map +1 -0
- package/dist/transaction/transaction-step.d.ts +67 -0
- package/dist/transaction/transaction-step.d.ts.map +1 -0
- package/dist/transaction/transaction-step.js +146 -0
- package/dist/transaction/transaction-step.js.map +1 -0
- package/dist/transaction/types.d.ts +236 -0
- package/dist/transaction/types.d.ts.map +1 -0
- package/dist/transaction/types.js +23 -0
- package/dist/transaction/types.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/workflow/global-workflow.d.ts +14 -0
- package/dist/workflow/global-workflow.d.ts.map +1 -0
- package/dist/workflow/global-workflow.js +105 -0
- package/dist/workflow/global-workflow.js.map +1 -0
- package/dist/workflow/index.d.ts +5 -0
- package/dist/workflow/index.d.ts.map +1 -0
- package/dist/workflow/index.js +21 -0
- package/dist/workflow/index.js.map +1 -0
- package/dist/workflow/local-workflow.d.ts +46 -0
- package/dist/workflow/local-workflow.d.ts.map +1 -0
- package/dist/workflow/local-workflow.js +360 -0
- package/dist/workflow/local-workflow.js.map +1 -0
- package/dist/workflow/scheduler.d.ts +12 -0
- package/dist/workflow/scheduler.d.ts.map +1 -0
- package/dist/workflow/scheduler.js +35 -0
- package/dist/workflow/scheduler.js.map +1 -0
- package/dist/workflow/workflow-manager.d.ts +38 -0
- package/dist/workflow/workflow-manager.d.ts.map +1 -0
- package/dist/workflow/workflow-manager.js +124 -0
- package/dist/workflow/workflow-manager.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,1061 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TransactionOrchestrator = void 0;
|
|
4
|
+
const distributed_transaction_1 = require("./distributed-transaction");
|
|
5
|
+
const transaction_step_1 = require("./transaction-step");
|
|
6
|
+
const types_1 = require("./types");
|
|
7
|
+
const utils_1 = require("@8medusa/utils");
|
|
8
|
+
const events_1 = require("events");
|
|
9
|
+
const errors_1 = require("./errors");
|
|
10
|
+
/**
|
|
11
|
+
* @class TransactionOrchestrator is responsible for managing and executing distributed transactions.
|
|
12
|
+
* It is based on a single transaction definition, which is used to execute all the transaction steps
|
|
13
|
+
*/
|
|
14
|
+
class TransactionOrchestrator extends events_1.EventEmitter {
|
|
15
|
+
static getWorkflowOptions(modelId) {
|
|
16
|
+
return TransactionOrchestrator.workflowOptions[modelId];
|
|
17
|
+
}
|
|
18
|
+
constructor({ id, definition, options, isClone, }) {
|
|
19
|
+
super();
|
|
20
|
+
this.invokeSteps = [];
|
|
21
|
+
this.compensateSteps = [];
|
|
22
|
+
this.id = id;
|
|
23
|
+
this.definition = definition;
|
|
24
|
+
this.options = options;
|
|
25
|
+
if (!isClone) {
|
|
26
|
+
this.parseFlowOptions();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
static clone(orchestrator) {
|
|
30
|
+
return new TransactionOrchestrator({
|
|
31
|
+
id: orchestrator.id,
|
|
32
|
+
definition: orchestrator.definition,
|
|
33
|
+
options: orchestrator.options,
|
|
34
|
+
isClone: true,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
static getKeyName(...params) {
|
|
38
|
+
return params.join(this.SEPARATOR);
|
|
39
|
+
}
|
|
40
|
+
getPreviousStep(flow, step) {
|
|
41
|
+
const id = step.id.split(".");
|
|
42
|
+
id.pop();
|
|
43
|
+
const parentId = id.join(".");
|
|
44
|
+
return flow.steps[parentId];
|
|
45
|
+
}
|
|
46
|
+
getOptions() {
|
|
47
|
+
return this.options ?? {};
|
|
48
|
+
}
|
|
49
|
+
getInvokeSteps(flow) {
|
|
50
|
+
if (this.invokeSteps.length) {
|
|
51
|
+
return this.invokeSteps;
|
|
52
|
+
}
|
|
53
|
+
const steps = Object.keys(flow.steps);
|
|
54
|
+
steps.sort((a, b) => flow.steps[a].depth - flow.steps[b].depth);
|
|
55
|
+
this.invokeSteps = steps;
|
|
56
|
+
return steps;
|
|
57
|
+
}
|
|
58
|
+
getCompensationSteps(flow) {
|
|
59
|
+
if (this.compensateSteps.length) {
|
|
60
|
+
return this.compensateSteps;
|
|
61
|
+
}
|
|
62
|
+
const steps = Object.keys(flow.steps);
|
|
63
|
+
steps.sort((a, b) => (flow.steps[b].depth || 0) - (flow.steps[a].depth || 0));
|
|
64
|
+
this.compensateSteps = steps;
|
|
65
|
+
return steps;
|
|
66
|
+
}
|
|
67
|
+
canMoveForward(flow, previousStep) {
|
|
68
|
+
const states = [
|
|
69
|
+
utils_1.TransactionStepState.DONE,
|
|
70
|
+
utils_1.TransactionStepState.FAILED,
|
|
71
|
+
utils_1.TransactionStepState.TIMEOUT,
|
|
72
|
+
utils_1.TransactionStepState.SKIPPED,
|
|
73
|
+
utils_1.TransactionStepState.SKIPPED_FAILURE,
|
|
74
|
+
];
|
|
75
|
+
const siblings = this.getPreviousStep(flow, previousStep).next.map((sib) => flow.steps[sib]);
|
|
76
|
+
return (!!previousStep.definition.noWait ||
|
|
77
|
+
siblings.every((sib) => states.includes(sib.invoke.state)));
|
|
78
|
+
}
|
|
79
|
+
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
|
+
const siblings = step.next.map((sib) => flow.steps[sib]);
|
|
88
|
+
return (siblings.length === 0 ||
|
|
89
|
+
siblings.every((sib) => states.includes(sib.compensate.state)));
|
|
90
|
+
}
|
|
91
|
+
canContinue(flow, step) {
|
|
92
|
+
if (flow.state == types_1.TransactionState.COMPENSATING) {
|
|
93
|
+
return this.canMoveBackward(flow, step);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
const previous = this.getPreviousStep(flow, step);
|
|
97
|
+
if (previous.id === TransactionOrchestrator.ROOT_STEP) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
return this.canMoveForward(flow, previous);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
hasExpired({ transaction, step, }, dateNow) {
|
|
104
|
+
const hasStepTimedOut = step &&
|
|
105
|
+
step.hasTimeout() &&
|
|
106
|
+
!step.isCompensating() &&
|
|
107
|
+
dateNow > step.startedAt + step.getTimeout() * 1e3;
|
|
108
|
+
const hasTransactionTimedOut = transaction &&
|
|
109
|
+
transaction.hasTimeout() &&
|
|
110
|
+
transaction.getFlow().state !== types_1.TransactionState.COMPENSATING &&
|
|
111
|
+
dateNow >
|
|
112
|
+
transaction.getFlow().startedAt + transaction.getTimeout() * 1e3;
|
|
113
|
+
return !!hasStepTimedOut || !!hasTransactionTimedOut;
|
|
114
|
+
}
|
|
115
|
+
async checkTransactionTimeout(transaction, currentSteps) {
|
|
116
|
+
const flow = transaction.getFlow();
|
|
117
|
+
let hasTimedOut = false;
|
|
118
|
+
if (!flow.timedOutAt && this.hasExpired({ transaction }, Date.now())) {
|
|
119
|
+
flow.timedOutAt = Date.now();
|
|
120
|
+
void transaction.clearTransactionTimeout();
|
|
121
|
+
for (const step of currentSteps) {
|
|
122
|
+
await TransactionOrchestrator.setStepTimeout(transaction, step, new errors_1.TransactionTimeoutError());
|
|
123
|
+
}
|
|
124
|
+
this.emit(types_1.DistributedTransactionEvent.TIMEOUT, { transaction });
|
|
125
|
+
hasTimedOut = true;
|
|
126
|
+
}
|
|
127
|
+
return hasTimedOut;
|
|
128
|
+
}
|
|
129
|
+
async checkStepTimeout(transaction, step) {
|
|
130
|
+
let hasTimedOut = false;
|
|
131
|
+
if (!step.timedOutAt &&
|
|
132
|
+
step.canCancel() &&
|
|
133
|
+
this.hasExpired({ step }, Date.now())) {
|
|
134
|
+
step.timedOutAt = Date.now();
|
|
135
|
+
await TransactionOrchestrator.setStepTimeout(transaction, step, new errors_1.TransactionStepTimeoutError());
|
|
136
|
+
hasTimedOut = true;
|
|
137
|
+
this.emit(types_1.DistributedTransactionEvent.TIMEOUT, { transaction });
|
|
138
|
+
}
|
|
139
|
+
return hasTimedOut;
|
|
140
|
+
}
|
|
141
|
+
async checkAllSteps(transaction) {
|
|
142
|
+
const flow = transaction.getFlow();
|
|
143
|
+
const result = await this.computeCurrentTransactionState(transaction);
|
|
144
|
+
// Handle state transitions and emit events
|
|
145
|
+
if (flow.state === types_1.TransactionState.WAITING_TO_COMPENSATE &&
|
|
146
|
+
result.next.length === 0 &&
|
|
147
|
+
!flow.hasWaitingSteps) {
|
|
148
|
+
flow.state = types_1.TransactionState.COMPENSATING;
|
|
149
|
+
this.flagStepsToRevert(flow);
|
|
150
|
+
this.emit(types_1.DistributedTransactionEvent.COMPENSATE_BEGIN, { transaction });
|
|
151
|
+
return await this.checkAllSteps(transaction);
|
|
152
|
+
}
|
|
153
|
+
else if (result.completed === result.total) {
|
|
154
|
+
if (result.hasSkippedOnFailure) {
|
|
155
|
+
flow.hasSkippedOnFailureSteps = true;
|
|
156
|
+
}
|
|
157
|
+
if (result.hasSkipped) {
|
|
158
|
+
flow.hasSkippedSteps = true;
|
|
159
|
+
}
|
|
160
|
+
if (result.hasIgnoredFailure) {
|
|
161
|
+
flow.hasFailedSteps = true;
|
|
162
|
+
}
|
|
163
|
+
if (result.hasFailed) {
|
|
164
|
+
flow.state = types_1.TransactionState.FAILED;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
flow.state = result.hasReverted
|
|
168
|
+
? types_1.TransactionState.REVERTED
|
|
169
|
+
: types_1.TransactionState.DONE;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
current: result.current,
|
|
174
|
+
next: result.next,
|
|
175
|
+
total: result.total,
|
|
176
|
+
remaining: result.total - result.completed,
|
|
177
|
+
completed: result.completed,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
async computeCurrentTransactionState(transaction) {
|
|
181
|
+
let hasSkipped = false;
|
|
182
|
+
let hasSkippedOnFailure = false;
|
|
183
|
+
let hasIgnoredFailure = false;
|
|
184
|
+
let hasFailed = false;
|
|
185
|
+
let hasWaiting = false;
|
|
186
|
+
let hasReverted = false;
|
|
187
|
+
let completedSteps = 0;
|
|
188
|
+
const flow = transaction.getFlow();
|
|
189
|
+
const nextSteps = [];
|
|
190
|
+
const currentSteps = [];
|
|
191
|
+
const allSteps = flow.state === types_1.TransactionState.COMPENSATING
|
|
192
|
+
? this.getCompensationSteps(flow)
|
|
193
|
+
: this.getInvokeSteps(flow);
|
|
194
|
+
for (const step of allSteps) {
|
|
195
|
+
if (step === TransactionOrchestrator.ROOT_STEP ||
|
|
196
|
+
!this.canContinue(flow, flow.steps[step])) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const stepDef = flow.steps[step];
|
|
200
|
+
const curState = stepDef.getStates();
|
|
201
|
+
const hasTimedOut = await this.checkStepTimeout(transaction, stepDef);
|
|
202
|
+
if (hasTimedOut) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (curState.status === types_1.TransactionStepStatus.WAITING) {
|
|
206
|
+
currentSteps.push(stepDef);
|
|
207
|
+
hasWaiting = true;
|
|
208
|
+
if (stepDef.hasAwaitingRetry()) {
|
|
209
|
+
if (stepDef.canRetryAwaiting()) {
|
|
210
|
+
stepDef.retryRescheduledAt = null;
|
|
211
|
+
nextSteps.push(stepDef);
|
|
212
|
+
}
|
|
213
|
+
else if (!stepDef.retryRescheduledAt) {
|
|
214
|
+
stepDef.hasScheduledRetry = true;
|
|
215
|
+
stepDef.retryRescheduledAt = Date.now();
|
|
216
|
+
await transaction.scheduleRetry(stepDef, stepDef.definition.retryIntervalAwaiting);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
else if (curState.status === types_1.TransactionStepStatus.TEMPORARY_FAILURE) {
|
|
222
|
+
currentSteps.push(stepDef);
|
|
223
|
+
if (!stepDef.canRetry()) {
|
|
224
|
+
if (stepDef.hasRetryInterval() && !stepDef.retryRescheduledAt) {
|
|
225
|
+
stepDef.hasScheduledRetry = true;
|
|
226
|
+
stepDef.retryRescheduledAt = Date.now();
|
|
227
|
+
await transaction.scheduleRetry(stepDef, stepDef.definition.retryInterval);
|
|
228
|
+
}
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
stepDef.retryRescheduledAt = null;
|
|
232
|
+
}
|
|
233
|
+
if (stepDef.canInvoke(flow.state) || stepDef.canCompensate(flow.state)) {
|
|
234
|
+
nextSteps.push(stepDef);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
completedSteps++;
|
|
238
|
+
if (curState.state === utils_1.TransactionStepState.SKIPPED_FAILURE) {
|
|
239
|
+
hasSkippedOnFailure = true;
|
|
240
|
+
}
|
|
241
|
+
else if (curState.state === utils_1.TransactionStepState.SKIPPED) {
|
|
242
|
+
hasSkipped = true;
|
|
243
|
+
}
|
|
244
|
+
else if (curState.state === utils_1.TransactionStepState.REVERTED) {
|
|
245
|
+
hasReverted = true;
|
|
246
|
+
}
|
|
247
|
+
else if (curState.state === utils_1.TransactionStepState.FAILED) {
|
|
248
|
+
if (stepDef.definition.continueOnPermanentFailure) {
|
|
249
|
+
hasIgnoredFailure = true;
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
hasFailed = true;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
flow.hasWaitingSteps = hasWaiting;
|
|
258
|
+
flow.hasRevertedSteps = hasReverted;
|
|
259
|
+
return {
|
|
260
|
+
current: currentSteps,
|
|
261
|
+
next: nextSteps,
|
|
262
|
+
total: allSteps.length - 1,
|
|
263
|
+
completed: completedSteps,
|
|
264
|
+
hasSkipped,
|
|
265
|
+
hasSkippedOnFailure,
|
|
266
|
+
hasIgnoredFailure,
|
|
267
|
+
hasFailed,
|
|
268
|
+
hasWaiting,
|
|
269
|
+
hasReverted,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
flagStepsToRevert(flow) {
|
|
273
|
+
for (const step in flow.steps) {
|
|
274
|
+
if (step === TransactionOrchestrator.ROOT_STEP) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const stepDef = flow.steps[step];
|
|
278
|
+
const curState = stepDef.getStates();
|
|
279
|
+
if ([utils_1.TransactionStepState.DONE, utils_1.TransactionStepState.TIMEOUT].includes(curState.state) ||
|
|
280
|
+
curState.status === types_1.TransactionStepStatus.PERMANENT_FAILURE) {
|
|
281
|
+
stepDef.beginCompensation();
|
|
282
|
+
stepDef.changeState(utils_1.TransactionStepState.NOT_STARTED);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
static async setStepSuccess(transaction, step, response) {
|
|
287
|
+
const hasStepTimedOut = step.getStates().state === utils_1.TransactionStepState.TIMEOUT;
|
|
288
|
+
if (step.saveResponse) {
|
|
289
|
+
transaction.addResponse(step.definition.action, step.isCompensating()
|
|
290
|
+
? types_1.TransactionHandlerType.COMPENSATE
|
|
291
|
+
: types_1.TransactionHandlerType.INVOKE, response);
|
|
292
|
+
}
|
|
293
|
+
if (!hasStepTimedOut) {
|
|
294
|
+
step.changeStatus(types_1.TransactionStepStatus.OK);
|
|
295
|
+
}
|
|
296
|
+
if (step.isCompensating()) {
|
|
297
|
+
step.changeState(utils_1.TransactionStepState.REVERTED);
|
|
298
|
+
}
|
|
299
|
+
else if (!hasStepTimedOut) {
|
|
300
|
+
step.changeState(utils_1.TransactionStepState.DONE);
|
|
301
|
+
}
|
|
302
|
+
let shouldEmit = true;
|
|
303
|
+
try {
|
|
304
|
+
await transaction.saveCheckpoint();
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
shouldEmit = false;
|
|
308
|
+
}
|
|
309
|
+
const cleaningUp = [];
|
|
310
|
+
if (step.hasRetryScheduled()) {
|
|
311
|
+
cleaningUp.push(transaction.clearRetry(step));
|
|
312
|
+
}
|
|
313
|
+
if (step.hasTimeout()) {
|
|
314
|
+
cleaningUp.push(transaction.clearStepTimeout(step));
|
|
315
|
+
}
|
|
316
|
+
await (0, utils_1.promiseAll)(cleaningUp);
|
|
317
|
+
if (shouldEmit) {
|
|
318
|
+
const eventName = step.isCompensating()
|
|
319
|
+
? types_1.DistributedTransactionEvent.COMPENSATE_STEP_SUCCESS
|
|
320
|
+
: types_1.DistributedTransactionEvent.STEP_SUCCESS;
|
|
321
|
+
transaction.emit(eventName, { step, transaction });
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
stopExecution: !shouldEmit,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
static async skipStep({ transaction, step, }) {
|
|
328
|
+
const hasStepTimedOut = step.getStates().state === utils_1.TransactionStepState.TIMEOUT;
|
|
329
|
+
if (!hasStepTimedOut) {
|
|
330
|
+
step.changeStatus(types_1.TransactionStepStatus.OK);
|
|
331
|
+
step.changeState(utils_1.TransactionStepState.SKIPPED);
|
|
332
|
+
}
|
|
333
|
+
let shouldEmit = true;
|
|
334
|
+
try {
|
|
335
|
+
await transaction.saveCheckpoint();
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
if (errors_1.SkipExecutionError.isSkipExecutionError(error)) {
|
|
339
|
+
shouldEmit = false;
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
throw error;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
const cleaningUp = [];
|
|
346
|
+
if (step.hasRetryScheduled()) {
|
|
347
|
+
cleaningUp.push(transaction.clearRetry(step));
|
|
348
|
+
}
|
|
349
|
+
if (step.hasTimeout()) {
|
|
350
|
+
cleaningUp.push(transaction.clearStepTimeout(step));
|
|
351
|
+
}
|
|
352
|
+
await (0, utils_1.promiseAll)(cleaningUp);
|
|
353
|
+
if (shouldEmit) {
|
|
354
|
+
const eventName = types_1.DistributedTransactionEvent.STEP_SKIPPED;
|
|
355
|
+
transaction.emit(eventName, { step, transaction });
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
stopExecution: !shouldEmit,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
static async setStepTimeout(transaction, step, error) {
|
|
362
|
+
if ([
|
|
363
|
+
utils_1.TransactionStepState.TIMEOUT,
|
|
364
|
+
utils_1.TransactionStepState.DONE,
|
|
365
|
+
utils_1.TransactionStepState.REVERTED,
|
|
366
|
+
].includes(step.getStates().state)) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
step.changeState(utils_1.TransactionStepState.TIMEOUT);
|
|
370
|
+
if (error?.stack) {
|
|
371
|
+
const workflowId = transaction.modelId;
|
|
372
|
+
const stepAction = step.definition.action;
|
|
373
|
+
const sourcePath = transaction.getFlow().metadata?.sourcePath;
|
|
374
|
+
const sourceStack = sourcePath
|
|
375
|
+
? `\n⮑ \sat ${sourcePath}: [${workflowId} -> ${stepAction} (${types_1.TransactionHandlerType.INVOKE})]`
|
|
376
|
+
: `\n⮑ \sat [${workflowId} -> ${stepAction} (${types_1.TransactionHandlerType.INVOKE})]`;
|
|
377
|
+
error.stack += sourceStack;
|
|
378
|
+
}
|
|
379
|
+
transaction.addError(step.definition.action, types_1.TransactionHandlerType.INVOKE, error);
|
|
380
|
+
await TransactionOrchestrator.setStepFailure(transaction, step, undefined, 0, true, error);
|
|
381
|
+
await transaction.clearStepTimeout(step);
|
|
382
|
+
}
|
|
383
|
+
static async setStepFailure(transaction, step, error, maxRetries = TransactionOrchestrator.DEFAULT_RETRIES, isTimeout = false, timeoutError) {
|
|
384
|
+
if (errors_1.SkipExecutionError.isSkipExecutionError(error)) {
|
|
385
|
+
return {
|
|
386
|
+
stopExecution: false,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
step.failures++;
|
|
390
|
+
if ((0, utils_1.isErrorLike)(error)) {
|
|
391
|
+
error = (0, utils_1.serializeError)(error);
|
|
392
|
+
}
|
|
393
|
+
if (!isTimeout &&
|
|
394
|
+
step.getStates().status !== types_1.TransactionStepStatus.PERMANENT_FAILURE) {
|
|
395
|
+
step.changeStatus(types_1.TransactionStepStatus.TEMPORARY_FAILURE);
|
|
396
|
+
}
|
|
397
|
+
const flow = transaction.getFlow();
|
|
398
|
+
const cleaningUp = [];
|
|
399
|
+
const hasTimedOut = step.getStates().state === utils_1.TransactionStepState.TIMEOUT;
|
|
400
|
+
if (step.failures > maxRetries || hasTimedOut) {
|
|
401
|
+
if (!hasTimedOut) {
|
|
402
|
+
step.changeState(utils_1.TransactionStepState.FAILED);
|
|
403
|
+
}
|
|
404
|
+
step.changeStatus(types_1.TransactionStepStatus.PERMANENT_FAILURE);
|
|
405
|
+
if (!isTimeout) {
|
|
406
|
+
const handlerType = step.isCompensating()
|
|
407
|
+
? types_1.TransactionHandlerType.COMPENSATE
|
|
408
|
+
: types_1.TransactionHandlerType.INVOKE;
|
|
409
|
+
if (error?.stack) {
|
|
410
|
+
const workflowId = transaction.modelId;
|
|
411
|
+
const stepAction = step.definition.action;
|
|
412
|
+
const sourcePath = transaction.getFlow().metadata?.sourcePath;
|
|
413
|
+
const sourceStack = sourcePath
|
|
414
|
+
? `\n⮑ \sat ${sourcePath}: [${workflowId} -> ${stepAction} (${types_1.TransactionHandlerType.INVOKE})]`
|
|
415
|
+
: `\n⮑ \sat [${workflowId} -> ${stepAction} (${types_1.TransactionHandlerType.INVOKE})]`;
|
|
416
|
+
error.stack += sourceStack;
|
|
417
|
+
}
|
|
418
|
+
transaction.addError(step.definition.action, handlerType, error);
|
|
419
|
+
}
|
|
420
|
+
if (!step.isCompensating()) {
|
|
421
|
+
if (step.definition.continueOnPermanentFailure &&
|
|
422
|
+
!errors_1.TransactionTimeoutError.isTransactionTimeoutError(timeoutError)) {
|
|
423
|
+
for (const childStep of step.next) {
|
|
424
|
+
const child = flow.steps[childStep];
|
|
425
|
+
child.changeState(utils_1.TransactionStepState.SKIPPED_FAILURE);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
flow.state = types_1.TransactionState.WAITING_TO_COMPENSATE;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (step.hasTimeout()) {
|
|
433
|
+
cleaningUp.push(transaction.clearStepTimeout(step));
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
let shouldEmit = true;
|
|
437
|
+
try {
|
|
438
|
+
await transaction.saveCheckpoint();
|
|
439
|
+
}
|
|
440
|
+
catch (error) {
|
|
441
|
+
if (errors_1.SkipExecutionError.isSkipExecutionError(error)) {
|
|
442
|
+
shouldEmit = false;
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
throw error;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (step.hasRetryScheduled()) {
|
|
449
|
+
cleaningUp.push(transaction.clearRetry(step));
|
|
450
|
+
}
|
|
451
|
+
await (0, utils_1.promiseAll)(cleaningUp);
|
|
452
|
+
if (shouldEmit) {
|
|
453
|
+
const eventName = step.isCompensating()
|
|
454
|
+
? types_1.DistributedTransactionEvent.COMPENSATE_STEP_FAILURE
|
|
455
|
+
: types_1.DistributedTransactionEvent.STEP_FAILURE;
|
|
456
|
+
transaction.emit(eventName, { step, transaction });
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
stopExecution: !shouldEmit,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
async executeNext(transaction) {
|
|
463
|
+
let continueExecution = true;
|
|
464
|
+
while (continueExecution) {
|
|
465
|
+
if (transaction.hasFinished()) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const flow = transaction.getFlow();
|
|
469
|
+
const nextSteps = await this.checkAllSteps(transaction);
|
|
470
|
+
if (await this.checkTransactionTimeout(transaction, nextSteps.current)) {
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
if (nextSteps.remaining === 0) {
|
|
474
|
+
await this.finalizeTransaction(transaction);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const execution = [];
|
|
478
|
+
for (const step of nextSteps.next) {
|
|
479
|
+
const { stopStepExecution } = this.prepareStepForExecution(step, flow);
|
|
480
|
+
// Should stop the execution if next step cant be handled
|
|
481
|
+
if (!stopStepExecution) {
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
if (step.hasTimeout() && !step.timedOutAt && step.attempts === 1) {
|
|
485
|
+
await transaction.scheduleStepTimeout(step, step.definition.timeout);
|
|
486
|
+
}
|
|
487
|
+
transaction.emit(types_1.DistributedTransactionEvent.STEP_BEGIN, {
|
|
488
|
+
step,
|
|
489
|
+
transaction,
|
|
490
|
+
});
|
|
491
|
+
const isAsync = step.isCompensating()
|
|
492
|
+
? step.definition.compensateAsync
|
|
493
|
+
: step.definition.async;
|
|
494
|
+
// Compute current transaction state
|
|
495
|
+
await this.computeCurrentTransactionState(transaction);
|
|
496
|
+
// Save checkpoint before executing step
|
|
497
|
+
await transaction.saveCheckpoint().catch((error) => {
|
|
498
|
+
if (errors_1.SkipExecutionError.isSkipExecutionError(error)) {
|
|
499
|
+
continueExecution = false;
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
throw error;
|
|
503
|
+
});
|
|
504
|
+
if (!continueExecution) {
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
const promise = this.createStepExecutionPromise(transaction, step);
|
|
508
|
+
if (!isAsync) {
|
|
509
|
+
execution.push(this.executeSyncStep(promise, transaction, step, nextSteps));
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
// Execute async step in background and continue the execution of the transaction
|
|
513
|
+
this.executeAsyncStep(promise, transaction, step, nextSteps);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
await (0, utils_1.promiseAll)(execution);
|
|
517
|
+
if (nextSteps.next.length === 0) {
|
|
518
|
+
continueExecution = false;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Finalize the transaction when all steps are complete
|
|
524
|
+
*/
|
|
525
|
+
async finalizeTransaction(transaction) {
|
|
526
|
+
if (transaction.hasTimeout()) {
|
|
527
|
+
void transaction.clearTransactionTimeout();
|
|
528
|
+
}
|
|
529
|
+
await transaction.saveCheckpoint().catch((error) => {
|
|
530
|
+
if (!errors_1.SkipExecutionError.isSkipExecutionError(error)) {
|
|
531
|
+
throw error;
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
this.emit(types_1.DistributedTransactionEvent.FINISH, { transaction });
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Prepare a step for execution by setting state and incrementing attempts
|
|
538
|
+
*/
|
|
539
|
+
prepareStepForExecution(step, flow) {
|
|
540
|
+
const curState = step.getStates();
|
|
541
|
+
step.lastAttempt = Date.now();
|
|
542
|
+
step.attempts++;
|
|
543
|
+
if (curState.state === utils_1.TransactionStepState.NOT_STARTED) {
|
|
544
|
+
if (!step.startedAt) {
|
|
545
|
+
step.startedAt = Date.now();
|
|
546
|
+
}
|
|
547
|
+
if (step.isCompensating()) {
|
|
548
|
+
step.changeState(utils_1.TransactionStepState.COMPENSATING);
|
|
549
|
+
if (step.definition.noCompensation) {
|
|
550
|
+
step.changeState(utils_1.TransactionStepState.REVERTED);
|
|
551
|
+
return { stopStepExecution: false };
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
else if (flow.state === types_1.TransactionState.INVOKING) {
|
|
555
|
+
step.changeState(utils_1.TransactionStepState.INVOKING);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
step.changeStatus(types_1.TransactionStepStatus.WAITING);
|
|
559
|
+
return { stopStepExecution: true };
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Create the payload for a step execution
|
|
563
|
+
*/
|
|
564
|
+
createStepPayload(transaction, step, flow) {
|
|
565
|
+
const type = step.isCompensating()
|
|
566
|
+
? types_1.TransactionHandlerType.COMPENSATE
|
|
567
|
+
: types_1.TransactionHandlerType.INVOKE;
|
|
568
|
+
return new distributed_transaction_1.TransactionPayload({
|
|
569
|
+
model_id: flow.modelId,
|
|
570
|
+
idempotency_key: TransactionOrchestrator.getKeyName(flow.modelId, flow.transactionId, step.definition.action, type),
|
|
571
|
+
action: step.definition.action + "",
|
|
572
|
+
action_type: type,
|
|
573
|
+
attempt: step.attempts,
|
|
574
|
+
timestamp: Date.now(),
|
|
575
|
+
}, transaction.payload, transaction.getContext());
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Prepare handler arguments for step execution
|
|
579
|
+
*/
|
|
580
|
+
prepareHandlerArgs(transaction, step, flow, payload) {
|
|
581
|
+
const type = step.isCompensating()
|
|
582
|
+
? types_1.TransactionHandlerType.COMPENSATE
|
|
583
|
+
: types_1.TransactionHandlerType.INVOKE;
|
|
584
|
+
return [
|
|
585
|
+
step.definition.action + "",
|
|
586
|
+
type,
|
|
587
|
+
payload,
|
|
588
|
+
transaction,
|
|
589
|
+
step,
|
|
590
|
+
this,
|
|
591
|
+
];
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Create the step execution promise with optional tracing
|
|
595
|
+
*/
|
|
596
|
+
createStepExecutionPromise(transaction, step) {
|
|
597
|
+
const type = step.isCompensating()
|
|
598
|
+
? types_1.TransactionHandlerType.COMPENSATE
|
|
599
|
+
: types_1.TransactionHandlerType.INVOKE;
|
|
600
|
+
const handlerArgs = this.prepareHandlerArgs(transaction, step, transaction.getFlow(), this.createStepPayload(transaction, step, transaction.getFlow()));
|
|
601
|
+
const traceData = {
|
|
602
|
+
action: step.definition.action + "",
|
|
603
|
+
type,
|
|
604
|
+
step_id: step.id,
|
|
605
|
+
step_uuid: step.uuid + "",
|
|
606
|
+
attempts: step.attempts,
|
|
607
|
+
failures: step.failures,
|
|
608
|
+
async: !!(type === "invoke"
|
|
609
|
+
? step.definition.async
|
|
610
|
+
: step.definition.compensateAsync),
|
|
611
|
+
idempotency_key: handlerArgs[2].metadata.idempotency_key,
|
|
612
|
+
};
|
|
613
|
+
const stepHandler = async () => {
|
|
614
|
+
return await transaction.handler(...handlerArgs);
|
|
615
|
+
};
|
|
616
|
+
// Return the appropriate promise based on tracing configuration
|
|
617
|
+
if (TransactionOrchestrator.traceStep) {
|
|
618
|
+
return () => TransactionOrchestrator.traceStep(stepHandler, traceData);
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
return stepHandler;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Execute a synchronous step and handle its result
|
|
626
|
+
*/
|
|
627
|
+
executeSyncStep(promiseFn, transaction, step, nextSteps) {
|
|
628
|
+
return promiseFn()
|
|
629
|
+
.then(async (response) => {
|
|
630
|
+
await this.handleStepExpiration(transaction, step, nextSteps);
|
|
631
|
+
const output = response?.__type ? response.output : response;
|
|
632
|
+
if (errors_1.SkipStepResponse.isSkipStepResponse(output)) {
|
|
633
|
+
await TransactionOrchestrator.skipStep({
|
|
634
|
+
transaction,
|
|
635
|
+
step,
|
|
636
|
+
});
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
await this.handleStepSuccess(transaction, step, response);
|
|
640
|
+
})
|
|
641
|
+
.catch(async (error) => {
|
|
642
|
+
if (errors_1.SkipExecutionError.isSkipExecutionError(error)) {
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
const response = error?.getStepResponse?.();
|
|
646
|
+
await this.handleStepExpiration(transaction, step, nextSteps);
|
|
647
|
+
if (errors_1.PermanentStepFailureError.isPermanentStepFailureError(error)) {
|
|
648
|
+
await this.handleStepFailure(transaction, step, error, true, response);
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
await this.handleStepFailure(transaction, step, error, false, response);
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Execute an asynchronous step and handle its result
|
|
656
|
+
*/
|
|
657
|
+
executeAsyncStep(promiseFn, transaction, step, nextSteps) {
|
|
658
|
+
return promiseFn()
|
|
659
|
+
.then(async (response) => {
|
|
660
|
+
const output = response?.__type ? response.output : response;
|
|
661
|
+
if (errors_1.SkipStepResponse.isSkipStepResponse(output)) {
|
|
662
|
+
await TransactionOrchestrator.skipStep({
|
|
663
|
+
transaction,
|
|
664
|
+
step,
|
|
665
|
+
});
|
|
666
|
+
// Schedule to continue the execution of async steps because they are not awaited on purpose and can be handled by another machine
|
|
667
|
+
await transaction.scheduleRetry(step, 0);
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
if (!step.definition.backgroundExecution || step.definition.nested) {
|
|
672
|
+
const eventName = types_1.DistributedTransactionEvent.STEP_AWAITING;
|
|
673
|
+
transaction.emit(eventName, { step, transaction });
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
await this.handleStepExpiration(transaction, step, nextSteps);
|
|
677
|
+
await this.handleStepSuccess(transaction, step, response);
|
|
678
|
+
}
|
|
679
|
+
})
|
|
680
|
+
.catch(async (error) => {
|
|
681
|
+
if (errors_1.SkipExecutionError.isSkipExecutionError(error)) {
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
const response = error?.getStepResponse?.();
|
|
685
|
+
if (errors_1.PermanentStepFailureError.isPermanentStepFailureError(error)) {
|
|
686
|
+
await this.handleStepFailure(transaction, step, error, true, response);
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
await this.handleStepFailure(transaction, step, error, false, response);
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Check if step or transaction has expired and handle timeouts
|
|
694
|
+
*/
|
|
695
|
+
async handleStepExpiration(transaction, step, nextSteps) {
|
|
696
|
+
if (this.hasExpired({ transaction, step }, Date.now())) {
|
|
697
|
+
await this.checkStepTimeout(transaction, step);
|
|
698
|
+
await this.checkTransactionTimeout(transaction, nextSteps.next.includes(step) ? nextSteps.next : [step]);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Handle successful step completion
|
|
703
|
+
*/
|
|
704
|
+
async handleStepSuccess(transaction, step, response) {
|
|
705
|
+
const isAsync = step.isCompensating()
|
|
706
|
+
? step.definition.compensateAsync
|
|
707
|
+
: step.definition.async;
|
|
708
|
+
if ((0, utils_1.isDefined)(response) && step.saveResponse && !isAsync) {
|
|
709
|
+
transaction.addResponse(step.definition.action, step.isCompensating()
|
|
710
|
+
? types_1.TransactionHandlerType.COMPENSATE
|
|
711
|
+
: types_1.TransactionHandlerType.INVOKE, response);
|
|
712
|
+
}
|
|
713
|
+
const ret = await TransactionOrchestrator.setStepSuccess(transaction, step, response);
|
|
714
|
+
if (isAsync && !ret.stopExecution) {
|
|
715
|
+
// Schedule to continue the execution of async steps because they are not awaited on purpose and can be handled by another machine
|
|
716
|
+
await transaction.scheduleRetry(step, 0);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Handle step failure
|
|
721
|
+
*/
|
|
722
|
+
async handleStepFailure(transaction, step, error, isPermanent, response) {
|
|
723
|
+
if ((0, utils_1.isDefined)(response) && step.saveResponse) {
|
|
724
|
+
transaction.addResponse(step.definition.action, step.isCompensating()
|
|
725
|
+
? types_1.TransactionHandlerType.COMPENSATE
|
|
726
|
+
: types_1.TransactionHandlerType.INVOKE, response);
|
|
727
|
+
}
|
|
728
|
+
await TransactionOrchestrator.setStepFailure(transaction, step, error, isPermanent ? 0 : step.definition.maxRetries);
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Start a new transaction or resume a transaction that has been previously started
|
|
732
|
+
* @param transaction - The transaction to resume
|
|
733
|
+
*/
|
|
734
|
+
async resume(transaction) {
|
|
735
|
+
if (transaction.modelId !== this.id) {
|
|
736
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_ALLOWED, `TransactionModel "${transaction.modelId}" cannot be orchestrated by "${this.id}" model.`);
|
|
737
|
+
}
|
|
738
|
+
if (transaction.hasFinished()) {
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
const executeNext = async () => {
|
|
742
|
+
const flow = transaction.getFlow();
|
|
743
|
+
if (flow.state === types_1.TransactionState.NOT_STARTED) {
|
|
744
|
+
flow.state = types_1.TransactionState.INVOKING;
|
|
745
|
+
flow.startedAt = Date.now();
|
|
746
|
+
await transaction.saveCheckpoint(flow.hasAsyncSteps ? 0 : TransactionOrchestrator.DEFAULT_TTL);
|
|
747
|
+
if (transaction.hasTimeout()) {
|
|
748
|
+
await transaction.scheduleTransactionTimeout(transaction.getTimeout());
|
|
749
|
+
}
|
|
750
|
+
this.emit(types_1.DistributedTransactionEvent.BEGIN, { transaction });
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
this.emit(types_1.DistributedTransactionEvent.RESUME, { transaction });
|
|
754
|
+
}
|
|
755
|
+
return await this.executeNext(transaction);
|
|
756
|
+
};
|
|
757
|
+
if (TransactionOrchestrator.traceTransaction &&
|
|
758
|
+
!transaction.getFlow().hasAsyncSteps) {
|
|
759
|
+
await TransactionOrchestrator.traceTransaction(executeNext, {
|
|
760
|
+
model_id: transaction.modelId,
|
|
761
|
+
transaction_id: transaction.transactionId,
|
|
762
|
+
flow_metadata: transaction.getFlow().metadata,
|
|
763
|
+
});
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
await executeNext();
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Cancel and revert a transaction compensating all its executed steps. It can be an ongoing transaction or a completed one
|
|
770
|
+
* @param transaction - The transaction to be reverted
|
|
771
|
+
*/
|
|
772
|
+
async cancelTransaction(transaction) {
|
|
773
|
+
if (transaction.modelId !== this.id) {
|
|
774
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_ALLOWED, `TransactionModel "${transaction.modelId}" cannot be orchestrated by "${this.id}" model.`);
|
|
775
|
+
}
|
|
776
|
+
const flow = transaction.getFlow();
|
|
777
|
+
if (flow.state === types_1.TransactionState.FAILED) {
|
|
778
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_ALLOWED, `Cannot revert a permanent failed transaction.`);
|
|
779
|
+
}
|
|
780
|
+
flow.state = types_1.TransactionState.WAITING_TO_COMPENSATE;
|
|
781
|
+
await this.executeNext(transaction);
|
|
782
|
+
}
|
|
783
|
+
parseFlowOptions() {
|
|
784
|
+
const [steps, features] = TransactionOrchestrator.buildSteps(this.definition);
|
|
785
|
+
this.options ??= {};
|
|
786
|
+
const hasAsyncSteps = features.hasAsyncSteps;
|
|
787
|
+
const hasStepTimeouts = features.hasStepTimeouts;
|
|
788
|
+
const hasRetriesTimeout = features.hasRetriesTimeout;
|
|
789
|
+
const hasTransactionTimeout = !!this.options.timeout;
|
|
790
|
+
const isIdempotent = !!this.options.idempotent;
|
|
791
|
+
if (hasAsyncSteps) {
|
|
792
|
+
this.options.store = true;
|
|
793
|
+
}
|
|
794
|
+
if (hasStepTimeouts ||
|
|
795
|
+
hasRetriesTimeout ||
|
|
796
|
+
hasTransactionTimeout ||
|
|
797
|
+
isIdempotent) {
|
|
798
|
+
this.options.store = true;
|
|
799
|
+
}
|
|
800
|
+
const parsedOptions = {
|
|
801
|
+
...this.options,
|
|
802
|
+
hasAsyncSteps,
|
|
803
|
+
hasStepTimeouts,
|
|
804
|
+
hasRetriesTimeout,
|
|
805
|
+
};
|
|
806
|
+
TransactionOrchestrator.workflowOptions[this.id] = parsedOptions;
|
|
807
|
+
return [steps, features];
|
|
808
|
+
}
|
|
809
|
+
createTransactionFlow(transactionId, flowMetadata) {
|
|
810
|
+
const [steps, features] = TransactionOrchestrator.buildSteps(this.definition);
|
|
811
|
+
const flow = {
|
|
812
|
+
modelId: this.id,
|
|
813
|
+
options: this.options,
|
|
814
|
+
transactionId: transactionId,
|
|
815
|
+
metadata: flowMetadata,
|
|
816
|
+
hasAsyncSteps: features.hasAsyncSteps,
|
|
817
|
+
hasFailedSteps: false,
|
|
818
|
+
hasSkippedOnFailureSteps: false,
|
|
819
|
+
hasSkippedSteps: false,
|
|
820
|
+
hasWaitingSteps: false,
|
|
821
|
+
hasRevertedSteps: false,
|
|
822
|
+
timedOutAt: null,
|
|
823
|
+
state: types_1.TransactionState.NOT_STARTED,
|
|
824
|
+
definition: this.definition,
|
|
825
|
+
steps,
|
|
826
|
+
};
|
|
827
|
+
return flow;
|
|
828
|
+
}
|
|
829
|
+
static async loadTransactionById(modelId, transactionId) {
|
|
830
|
+
const transaction = await distributed_transaction_1.DistributedTransaction.loadTransaction(modelId, transactionId);
|
|
831
|
+
if (transaction !== null) {
|
|
832
|
+
const flow = transaction.flow;
|
|
833
|
+
const [steps] = TransactionOrchestrator.buildSteps(flow.definition, flow.steps);
|
|
834
|
+
transaction.flow.steps = steps;
|
|
835
|
+
return transaction;
|
|
836
|
+
}
|
|
837
|
+
return null;
|
|
838
|
+
}
|
|
839
|
+
static buildSteps(flow, existingSteps) {
|
|
840
|
+
const states = {
|
|
841
|
+
[TransactionOrchestrator.ROOT_STEP]: {
|
|
842
|
+
id: TransactionOrchestrator.ROOT_STEP,
|
|
843
|
+
next: [],
|
|
844
|
+
},
|
|
845
|
+
};
|
|
846
|
+
const actionNames = new Set();
|
|
847
|
+
const queue = [
|
|
848
|
+
{ obj: flow, level: [TransactionOrchestrator.ROOT_STEP] },
|
|
849
|
+
];
|
|
850
|
+
const features = {
|
|
851
|
+
hasAsyncSteps: false,
|
|
852
|
+
hasStepTimeouts: false,
|
|
853
|
+
hasRetriesTimeout: false,
|
|
854
|
+
hasNestedTransactions: false,
|
|
855
|
+
};
|
|
856
|
+
while (queue.length > 0) {
|
|
857
|
+
const { obj, level } = queue.shift();
|
|
858
|
+
if (obj.action) {
|
|
859
|
+
if (actionNames.has(obj.action)) {
|
|
860
|
+
throw new Error(`Step ${obj.action} is already defined in workflow.`);
|
|
861
|
+
}
|
|
862
|
+
actionNames.add(obj.action);
|
|
863
|
+
level.push(obj.action);
|
|
864
|
+
const id = level.join(".");
|
|
865
|
+
const parent = level.slice(0, level.length - 1).join(".");
|
|
866
|
+
if (!existingSteps || parent === TransactionOrchestrator.ROOT_STEP) {
|
|
867
|
+
states[parent].next?.push(id);
|
|
868
|
+
}
|
|
869
|
+
const definitionCopy = { ...obj };
|
|
870
|
+
delete definitionCopy.next;
|
|
871
|
+
if (definitionCopy.async) {
|
|
872
|
+
features.hasAsyncSteps = true;
|
|
873
|
+
}
|
|
874
|
+
if (definitionCopy.timeout) {
|
|
875
|
+
features.hasStepTimeouts = true;
|
|
876
|
+
}
|
|
877
|
+
if (definitionCopy.retryInterval ||
|
|
878
|
+
definitionCopy.retryIntervalAwaiting) {
|
|
879
|
+
features.hasRetriesTimeout = true;
|
|
880
|
+
}
|
|
881
|
+
if (definitionCopy.nested) {
|
|
882
|
+
features.hasNestedTransactions = true;
|
|
883
|
+
}
|
|
884
|
+
states[id] = Object.assign(new transaction_step_1.TransactionStep(), existingSteps?.[id] || {
|
|
885
|
+
id,
|
|
886
|
+
uuid: definitionCopy.uuid,
|
|
887
|
+
depth: level.length - 1,
|
|
888
|
+
definition: definitionCopy,
|
|
889
|
+
saveResponse: definitionCopy.saveResponse ?? true,
|
|
890
|
+
invoke: {
|
|
891
|
+
state: utils_1.TransactionStepState.NOT_STARTED,
|
|
892
|
+
status: types_1.TransactionStepStatus.IDLE,
|
|
893
|
+
},
|
|
894
|
+
compensate: {
|
|
895
|
+
state: utils_1.TransactionStepState.DORMANT,
|
|
896
|
+
status: types_1.TransactionStepStatus.IDLE,
|
|
897
|
+
},
|
|
898
|
+
attempts: 0,
|
|
899
|
+
failures: 0,
|
|
900
|
+
lastAttempt: null,
|
|
901
|
+
next: [],
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
if (Array.isArray(obj.next)) {
|
|
905
|
+
for (const next of obj.next) {
|
|
906
|
+
queue.push({ obj: next, level: [...level] });
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
else if ((0, utils_1.isObject)(obj.next)) {
|
|
910
|
+
queue.push({ obj: obj.next, level: [...level] });
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
return [states, features];
|
|
914
|
+
}
|
|
915
|
+
/** Create a new transaction
|
|
916
|
+
* @param transactionId - unique identifier of the transaction
|
|
917
|
+
* @param handler - function to handle action of the transaction
|
|
918
|
+
* @param payload - payload to be passed to all the transaction steps
|
|
919
|
+
* @param flowMetadata - flow metadata which can include event group id for example
|
|
920
|
+
*/
|
|
921
|
+
async beginTransaction({ transactionId, handler, payload, flowMetadata, onLoad, }) {
|
|
922
|
+
const existingTransaction = await TransactionOrchestrator.loadTransactionById(this.id, transactionId);
|
|
923
|
+
let newTransaction = false;
|
|
924
|
+
let modelFlow;
|
|
925
|
+
if (!existingTransaction) {
|
|
926
|
+
modelFlow = this.createTransactionFlow(transactionId, flowMetadata);
|
|
927
|
+
newTransaction = true;
|
|
928
|
+
}
|
|
929
|
+
else {
|
|
930
|
+
modelFlow = existingTransaction.flow;
|
|
931
|
+
}
|
|
932
|
+
const transaction = new distributed_transaction_1.DistributedTransaction(modelFlow, handler, payload, existingTransaction?.errors, existingTransaction?.context);
|
|
933
|
+
if (newTransaction && this.getOptions().store) {
|
|
934
|
+
await transaction.saveCheckpoint(modelFlow.hasAsyncSteps ? 0 : TransactionOrchestrator.DEFAULT_TTL);
|
|
935
|
+
}
|
|
936
|
+
if (onLoad) {
|
|
937
|
+
await onLoad(transaction);
|
|
938
|
+
}
|
|
939
|
+
return transaction;
|
|
940
|
+
}
|
|
941
|
+
/** Returns an existing transaction
|
|
942
|
+
* @param transactionId - unique identifier of the transaction
|
|
943
|
+
* @param handler - function to handle action of the transaction
|
|
944
|
+
*/
|
|
945
|
+
async retrieveExistingTransaction(transactionId, handler) {
|
|
946
|
+
const existingTransaction = await TransactionOrchestrator.loadTransactionById(this.id, transactionId);
|
|
947
|
+
if (!existingTransaction) {
|
|
948
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_FOUND, `Transaction ${transactionId} could not be found.`);
|
|
949
|
+
}
|
|
950
|
+
const transaction = new distributed_transaction_1.DistributedTransaction(existingTransaction.flow, handler, undefined, existingTransaction?.errors, existingTransaction?.context);
|
|
951
|
+
return transaction;
|
|
952
|
+
}
|
|
953
|
+
static getStepByAction(flow, action) {
|
|
954
|
+
for (const key in flow.steps) {
|
|
955
|
+
if (action === flow.steps[key]?.definition?.action) {
|
|
956
|
+
return flow.steps[key];
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return null;
|
|
960
|
+
}
|
|
961
|
+
static async getTransactionAndStepFromIdempotencyKey(responseIdempotencyKey, handler, transaction) {
|
|
962
|
+
const [modelId, transactionId, action, actionType] = responseIdempotencyKey.split(TransactionOrchestrator.SEPARATOR);
|
|
963
|
+
if (!transaction && !handler) {
|
|
964
|
+
throw new Error("If a transaction is not provided, the handler is required");
|
|
965
|
+
}
|
|
966
|
+
if (!transaction) {
|
|
967
|
+
const existingTransaction = await TransactionOrchestrator.loadTransactionById(modelId, transactionId);
|
|
968
|
+
if (existingTransaction === null) {
|
|
969
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_FOUND, `Transaction ${transactionId} could not be found.`);
|
|
970
|
+
}
|
|
971
|
+
transaction = new distributed_transaction_1.DistributedTransaction(existingTransaction.flow, handler, undefined, existingTransaction.errors, existingTransaction.context);
|
|
972
|
+
}
|
|
973
|
+
const step = TransactionOrchestrator.getStepByAction(transaction.getFlow(), action);
|
|
974
|
+
if (step === null) {
|
|
975
|
+
throw new Error("Action not found.");
|
|
976
|
+
}
|
|
977
|
+
else if (step.isCompensating()
|
|
978
|
+
? actionType !== types_1.TransactionHandlerType.COMPENSATE
|
|
979
|
+
: actionType !== types_1.TransactionHandlerType.INVOKE) {
|
|
980
|
+
throw new Error("Incorrect action type.");
|
|
981
|
+
}
|
|
982
|
+
return [transaction, step];
|
|
983
|
+
}
|
|
984
|
+
/** Skip the execution of a specific transaction and step
|
|
985
|
+
* @param responseIdempotencyKey - The idempotency key for the step
|
|
986
|
+
* @param handler - The handler function to execute the step
|
|
987
|
+
* @param transaction - The current transaction. If not provided it will be loaded based on the responseIdempotencyKey
|
|
988
|
+
*/
|
|
989
|
+
async skipStep({ responseIdempotencyKey, handler, transaction, }) {
|
|
990
|
+
const [curTransaction, step] = await TransactionOrchestrator.getTransactionAndStepFromIdempotencyKey(responseIdempotencyKey, handler, transaction);
|
|
991
|
+
if (step.getStates().status === types_1.TransactionStepStatus.WAITING) {
|
|
992
|
+
this.emit(types_1.DistributedTransactionEvent.RESUME, {
|
|
993
|
+
transaction: curTransaction,
|
|
994
|
+
});
|
|
995
|
+
await TransactionOrchestrator.skipStep({
|
|
996
|
+
transaction: curTransaction,
|
|
997
|
+
step,
|
|
998
|
+
});
|
|
999
|
+
await this.executeNext(curTransaction);
|
|
1000
|
+
}
|
|
1001
|
+
else {
|
|
1002
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_ALLOWED, `Cannot skip a step when status is ${step.getStates().status}`);
|
|
1003
|
+
}
|
|
1004
|
+
return curTransaction;
|
|
1005
|
+
}
|
|
1006
|
+
/** Register a step success for a specific transaction and step
|
|
1007
|
+
* @param responseIdempotencyKey - The idempotency key for the step
|
|
1008
|
+
* @param handler - The handler function to execute the step
|
|
1009
|
+
* @param transaction - The current transaction. If not provided it will be loaded based on the responseIdempotencyKey
|
|
1010
|
+
* @param response - The response of the step
|
|
1011
|
+
*/
|
|
1012
|
+
async registerStepSuccess({ responseIdempotencyKey, handler, transaction, response, onLoad, }) {
|
|
1013
|
+
const [curTransaction, step] = await TransactionOrchestrator.getTransactionAndStepFromIdempotencyKey(responseIdempotencyKey, handler, transaction);
|
|
1014
|
+
if (onLoad) {
|
|
1015
|
+
await onLoad(curTransaction);
|
|
1016
|
+
}
|
|
1017
|
+
if (step.getStates().status === types_1.TransactionStepStatus.WAITING) {
|
|
1018
|
+
this.emit(types_1.DistributedTransactionEvent.RESUME, {
|
|
1019
|
+
transaction: curTransaction,
|
|
1020
|
+
});
|
|
1021
|
+
await TransactionOrchestrator.setStepSuccess(curTransaction, step, response);
|
|
1022
|
+
await this.executeNext(curTransaction);
|
|
1023
|
+
}
|
|
1024
|
+
else {
|
|
1025
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_ALLOWED, `Cannot set step success when status is ${step.getStates().status}`);
|
|
1026
|
+
}
|
|
1027
|
+
return curTransaction;
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Register a step failure for a specific transaction and step
|
|
1031
|
+
* @param responseIdempotencyKey - The idempotency key for the step
|
|
1032
|
+
* @param error - The error that caused the failure
|
|
1033
|
+
* @param handler - The handler function to execute the step
|
|
1034
|
+
* @param transaction - The current transaction
|
|
1035
|
+
* @param response - The response of the step
|
|
1036
|
+
*/
|
|
1037
|
+
async registerStepFailure({ responseIdempotencyKey, error, handler, transaction, onLoad, }) {
|
|
1038
|
+
const [curTransaction, step] = await TransactionOrchestrator.getTransactionAndStepFromIdempotencyKey(responseIdempotencyKey, handler, transaction);
|
|
1039
|
+
if (onLoad) {
|
|
1040
|
+
await onLoad(curTransaction);
|
|
1041
|
+
}
|
|
1042
|
+
if (step.getStates().status === types_1.TransactionStepStatus.WAITING) {
|
|
1043
|
+
this.emit(types_1.DistributedTransactionEvent.RESUME, {
|
|
1044
|
+
transaction: curTransaction,
|
|
1045
|
+
});
|
|
1046
|
+
await TransactionOrchestrator.setStepFailure(curTransaction, step, error, 0);
|
|
1047
|
+
await this.executeNext(curTransaction);
|
|
1048
|
+
}
|
|
1049
|
+
else {
|
|
1050
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_ALLOWED, `Cannot set step failure when status is ${step.getStates().status}`);
|
|
1051
|
+
}
|
|
1052
|
+
return curTransaction;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
exports.TransactionOrchestrator = TransactionOrchestrator;
|
|
1056
|
+
TransactionOrchestrator.ROOT_STEP = "_root";
|
|
1057
|
+
TransactionOrchestrator.DEFAULT_TTL = 30;
|
|
1058
|
+
TransactionOrchestrator.DEFAULT_RETRIES = 0;
|
|
1059
|
+
TransactionOrchestrator.workflowOptions = {};
|
|
1060
|
+
TransactionOrchestrator.SEPARATOR = ":";
|
|
1061
|
+
//# sourceMappingURL=transaction-orchestrator.js.map
|