@medusajs/workflow-engine-inmemory 3.0.0-snapshot-20250410112222 → 3.0.0-snapshot-20251104004624
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/loaders/utils.d.ts.map +1 -1
- package/dist/loaders/utils.js +1 -1
- package/dist/loaders/utils.js.map +1 -1
- package/dist/migrations/Migration20231228143900.d.ts +1 -1
- package/dist/migrations/Migration20231228143900.d.ts.map +1 -1
- package/dist/migrations/Migration20231228143900.js +1 -1
- package/dist/migrations/Migration20231228143900.js.map +1 -1
- package/dist/migrations/Migration20241206101446.d.ts +1 -1
- package/dist/migrations/Migration20241206101446.d.ts.map +1 -1
- package/dist/migrations/Migration20241206101446.js +1 -1
- package/dist/migrations/Migration20241206101446.js.map +1 -1
- package/dist/migrations/Migration20250128174331.d.ts +1 -1
- package/dist/migrations/Migration20250128174331.d.ts.map +1 -1
- package/dist/migrations/Migration20250128174331.js +1 -1
- package/dist/migrations/Migration20250128174331.js.map +1 -1
- package/dist/migrations/Migration20250505092459.d.ts +6 -0
- package/dist/migrations/Migration20250505092459.d.ts.map +1 -0
- package/dist/migrations/Migration20250505092459.js +40 -0
- package/dist/migrations/Migration20250505092459.js.map +1 -0
- package/dist/migrations/Migration20250819104213.d.ts +6 -0
- package/dist/migrations/Migration20250819104213.d.ts.map +1 -0
- package/dist/migrations/Migration20250819104213.js +14 -0
- package/dist/migrations/Migration20250819104213.js.map +1 -0
- package/dist/migrations/Migration20250819110924.d.ts +6 -0
- package/dist/migrations/Migration20250819110924.d.ts.map +1 -0
- package/dist/migrations/Migration20250819110924.js +16 -0
- package/dist/migrations/Migration20250819110924.js.map +1 -0
- package/dist/migrations/Migration20250908080305.d.ts +6 -0
- package/dist/migrations/Migration20250908080305.d.ts.map +1 -0
- package/dist/migrations/Migration20250908080305.js +20 -0
- package/dist/migrations/Migration20250908080305.js.map +1 -0
- package/dist/models/workflow-execution.d.ts +1 -0
- package/dist/models/workflow-execution.d.ts.map +1 -1
- package/dist/models/workflow-execution.js +22 -1
- package/dist/models/workflow-execution.js.map +1 -1
- package/dist/schema/index.d.ts +1 -1
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +1 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/services/workflow-orchestrator.d.ts +18 -3
- package/dist/services/workflow-orchestrator.d.ts.map +1 -1
- package/dist/services/workflow-orchestrator.js +176 -53
- package/dist/services/workflow-orchestrator.js.map +1 -1
- package/dist/services/workflows-module.d.ts +113 -8
- package/dist/services/workflows-module.d.ts.map +1 -1
- package/dist/services/workflows-module.js +114 -48
- package/dist/services/workflows-module.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/workflow-orchestrator-storage.d.ts +11 -3
- package/dist/utils/workflow-orchestrator-storage.d.ts.map +1 -1
- package/dist/utils/workflow-orchestrator-storage.js +336 -139
- package/dist/utils/workflow-orchestrator-storage.js.map +1 -1
- package/package.json +14 -28
|
@@ -7,45 +7,160 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
7
7
|
var _InMemoryDistributedTransactionStorage_instances, _InMemoryDistributedTransactionStorage_preventRaceConditionExecutionIfNecessary;
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.InMemoryDistributedTransactionStorage = void 0;
|
|
10
|
+
const core_1 = require("@medusajs/framework/mikro-orm/core");
|
|
10
11
|
const orchestration_1 = require("@medusajs/framework/orchestration");
|
|
11
12
|
const utils_1 = require("@medusajs/framework/utils");
|
|
12
13
|
const cron_parser_1 = require("cron-parser");
|
|
14
|
+
const THIRTY_MINUTES_IN_MS = 1000 * 60 * 30;
|
|
15
|
+
const doneStates = new Set([
|
|
16
|
+
utils_1.TransactionStepState.DONE,
|
|
17
|
+
utils_1.TransactionStepState.REVERTED,
|
|
18
|
+
utils_1.TransactionStepState.FAILED,
|
|
19
|
+
utils_1.TransactionStepState.SKIPPED,
|
|
20
|
+
utils_1.TransactionStepState.SKIPPED_FAILURE,
|
|
21
|
+
utils_1.TransactionStepState.TIMEOUT,
|
|
22
|
+
]);
|
|
23
|
+
const finishedStates = new Set([
|
|
24
|
+
utils_1.TransactionState.DONE,
|
|
25
|
+
utils_1.TransactionState.FAILED,
|
|
26
|
+
utils_1.TransactionState.REVERTED,
|
|
27
|
+
]);
|
|
28
|
+
const failedStates = new Set([
|
|
29
|
+
utils_1.TransactionState.FAILED,
|
|
30
|
+
utils_1.TransactionState.REVERTED,
|
|
31
|
+
]);
|
|
32
|
+
function calculateDelayFromExpression(expression) {
|
|
33
|
+
const nextTime = expression.next().getTime();
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
const delay = nextTime - now;
|
|
36
|
+
// If the calculated delay is negative or zero, get the next occurrence
|
|
37
|
+
if (delay <= 0) {
|
|
38
|
+
const nextNextTime = expression.next().getTime();
|
|
39
|
+
return Math.max(1, nextNextTime - now);
|
|
40
|
+
}
|
|
41
|
+
return delay;
|
|
42
|
+
}
|
|
13
43
|
function parseNextExecution(optionsOrExpression) {
|
|
14
44
|
if (typeof optionsOrExpression === "object") {
|
|
15
45
|
if ("cron" in optionsOrExpression) {
|
|
16
46
|
const expression = (0, cron_parser_1.parseExpression)(optionsOrExpression.cron);
|
|
17
|
-
return expression
|
|
47
|
+
return calculateDelayFromExpression(expression);
|
|
18
48
|
}
|
|
19
49
|
if ("interval" in optionsOrExpression) {
|
|
20
50
|
return optionsOrExpression.interval;
|
|
21
51
|
}
|
|
22
|
-
return optionsOrExpression
|
|
52
|
+
return calculateDelayFromExpression(optionsOrExpression);
|
|
23
53
|
}
|
|
24
54
|
const result = parseInt(`${optionsOrExpression}`);
|
|
25
55
|
if (isNaN(result)) {
|
|
26
56
|
const expression = (0, cron_parser_1.parseExpression)(`${optionsOrExpression}`);
|
|
27
|
-
return expression
|
|
57
|
+
return calculateDelayFromExpression(expression);
|
|
28
58
|
}
|
|
29
59
|
return result;
|
|
30
60
|
}
|
|
31
61
|
class InMemoryDistributedTransactionStorage {
|
|
32
62
|
constructor({ workflowExecutionService, logger, }) {
|
|
33
63
|
_InMemoryDistributedTransactionStorage_instances.add(this);
|
|
34
|
-
this.storage =
|
|
64
|
+
this.storage = {};
|
|
35
65
|
this.scheduled = new Map();
|
|
36
66
|
this.retries = new Map();
|
|
37
67
|
this.timeouts = new Map();
|
|
68
|
+
this.pendingTimers = new Set();
|
|
69
|
+
this.isLocked = new Map();
|
|
38
70
|
this.workflowExecutionService_ = workflowExecutionService;
|
|
39
71
|
this.logger_ = logger;
|
|
40
72
|
}
|
|
73
|
+
async onApplicationStart() {
|
|
74
|
+
this.clearTimeout_ = setInterval(async () => {
|
|
75
|
+
try {
|
|
76
|
+
await this.clearExpiredExecutions();
|
|
77
|
+
}
|
|
78
|
+
catch { }
|
|
79
|
+
}, THIRTY_MINUTES_IN_MS);
|
|
80
|
+
}
|
|
81
|
+
async onApplicationShutdown() {
|
|
82
|
+
clearInterval(this.clearTimeout_);
|
|
83
|
+
for (const timer of this.pendingTimers) {
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
}
|
|
86
|
+
this.pendingTimers.clear();
|
|
87
|
+
for (const timer of this.retries.values()) {
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
}
|
|
90
|
+
this.retries.clear();
|
|
91
|
+
for (const timer of this.timeouts.values()) {
|
|
92
|
+
clearTimeout(timer);
|
|
93
|
+
}
|
|
94
|
+
this.timeouts.clear();
|
|
95
|
+
// Clean up scheduled job timers
|
|
96
|
+
for (const job of this.scheduled.values()) {
|
|
97
|
+
clearTimeout(job.timer);
|
|
98
|
+
}
|
|
99
|
+
this.scheduled.clear();
|
|
100
|
+
}
|
|
41
101
|
setWorkflowOrchestratorService(workflowOrchestratorService) {
|
|
42
102
|
this.workflowOrchestratorService_ = workflowOrchestratorService;
|
|
43
103
|
}
|
|
104
|
+
createManagedTimer(callback, delay) {
|
|
105
|
+
const timer = setTimeout(async () => {
|
|
106
|
+
this.pendingTimers.delete(timer);
|
|
107
|
+
const res = callback();
|
|
108
|
+
if (res instanceof Promise) {
|
|
109
|
+
await res;
|
|
110
|
+
}
|
|
111
|
+
}, delay);
|
|
112
|
+
this.pendingTimers.add(timer);
|
|
113
|
+
return timer;
|
|
114
|
+
}
|
|
44
115
|
async saveToDb(data, retentionTime) {
|
|
116
|
+
const isNotStarted = data.flow.state === utils_1.TransactionState.NOT_STARTED;
|
|
117
|
+
const asyncVersion = data.flow._v;
|
|
118
|
+
const isFinished = finishedStates.has(data.flow.state);
|
|
119
|
+
const isWaitingToCompensate = data.flow.state === utils_1.TransactionState.WAITING_TO_COMPENSATE;
|
|
120
|
+
const isFlowInvoking = data.flow.state === utils_1.TransactionState.INVOKING;
|
|
121
|
+
const stepsArray = Object.values(data.flow.steps);
|
|
122
|
+
let currentStep;
|
|
123
|
+
let currentStepsIsAsync = false;
|
|
124
|
+
const targetStates = isFlowInvoking
|
|
125
|
+
? new Set([
|
|
126
|
+
utils_1.TransactionStepState.INVOKING,
|
|
127
|
+
utils_1.TransactionStepState.DONE,
|
|
128
|
+
utils_1.TransactionStepState.FAILED,
|
|
129
|
+
])
|
|
130
|
+
: new Set([utils_1.TransactionStepState.COMPENSATING]);
|
|
131
|
+
for (let i = stepsArray.length - 1; i >= 0; i--) {
|
|
132
|
+
const step = stepsArray[i];
|
|
133
|
+
if (step.id === "_root") {
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
const isTargetState = targetStates.has(step.invoke?.state);
|
|
137
|
+
if (isTargetState && !currentStep) {
|
|
138
|
+
currentStep = step;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (currentStep) {
|
|
143
|
+
for (const step of stepsArray) {
|
|
144
|
+
if (step.id === "_root") {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (step.depth === currentStep.depth &&
|
|
148
|
+
step?.definition?.async === true) {
|
|
149
|
+
currentStepsIsAsync = true;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (!(isNotStarted || isFinished || isWaitingToCompensate) &&
|
|
155
|
+
!currentStepsIsAsync &&
|
|
156
|
+
!asyncVersion) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
45
159
|
await this.workflowExecutionService_.upsert([
|
|
46
160
|
{
|
|
47
161
|
workflow_id: data.flow.modelId,
|
|
48
162
|
transaction_id: data.flow.transactionId,
|
|
163
|
+
run_id: data.flow.runId,
|
|
49
164
|
execution: data.flow,
|
|
50
165
|
context: {
|
|
51
166
|
data: data.context,
|
|
@@ -59,129 +174,213 @@ class InMemoryDistributedTransactionStorage {
|
|
|
59
174
|
async deleteFromDb(data) {
|
|
60
175
|
await this.workflowExecutionService_.delete([
|
|
61
176
|
{
|
|
62
|
-
|
|
63
|
-
transaction_id: data.flow.transactionId,
|
|
177
|
+
run_id: data.flow.runId,
|
|
64
178
|
},
|
|
65
179
|
]);
|
|
66
180
|
}
|
|
67
181
|
async get(key, options) {
|
|
68
|
-
const data = this.storage.get(key);
|
|
69
|
-
if (data) {
|
|
70
|
-
return data;
|
|
71
|
-
}
|
|
72
|
-
const { idempotent, store, retentionTime } = options ?? {};
|
|
73
|
-
if (!idempotent && !(store && (0, utils_1.isDefined)(retentionTime))) {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
182
|
const [_, workflowId, transactionId] = key.split(":");
|
|
77
183
|
const trx = await this.workflowExecutionService_
|
|
78
|
-
.
|
|
184
|
+
.list({
|
|
79
185
|
workflow_id: workflowId,
|
|
80
186
|
transaction_id: transactionId,
|
|
81
187
|
}, {
|
|
82
188
|
select: ["execution", "context"],
|
|
189
|
+
order: {
|
|
190
|
+
id: "desc",
|
|
191
|
+
},
|
|
192
|
+
take: 1,
|
|
83
193
|
})
|
|
194
|
+
.then((trx) => trx[0])
|
|
84
195
|
.catch(() => undefined);
|
|
85
196
|
if (trx) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
197
|
+
const { flow, errors } = this.storage[key]
|
|
198
|
+
? JSON.parse(JSON.stringify(this.storage[key]))
|
|
199
|
+
: {};
|
|
200
|
+
const { idempotent } = options ?? {};
|
|
201
|
+
const execution = trx.execution;
|
|
202
|
+
if (!idempotent) {
|
|
203
|
+
const isFailedOrReverted = failedStates.has(execution.state);
|
|
204
|
+
const isDone = execution.state === utils_1.TransactionState.DONE;
|
|
205
|
+
const isCancellingAndFailedOrReverted = options?.isCancelling && isFailedOrReverted;
|
|
206
|
+
const isNotCancellingAndDoneOrFailedOrReverted = !options?.isCancelling && (isDone || isFailedOrReverted);
|
|
207
|
+
if (isCancellingAndFailedOrReverted ||
|
|
208
|
+
isNotCancellingAndDoneOrFailedOrReverted) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return new orchestration_1.TransactionCheckpoint(flow ?? trx?.execution, trx?.context?.data, errors ?? trx?.context?.errors);
|
|
91
213
|
}
|
|
92
214
|
return;
|
|
93
215
|
}
|
|
94
|
-
async list() {
|
|
95
|
-
return Array.from(this.storage.values());
|
|
96
|
-
}
|
|
97
216
|
async save(key, data, ttl, options) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
* From that moment, this tuple can be later on archived or deleted after the retention time.
|
|
101
|
-
*/
|
|
102
|
-
const hasFinished = [
|
|
103
|
-
utils_1.TransactionState.DONE,
|
|
104
|
-
utils_1.TransactionState.FAILED,
|
|
105
|
-
utils_1.TransactionState.REVERTED,
|
|
106
|
-
].includes(data.flow.state);
|
|
107
|
-
const { retentionTime, idempotent } = options ?? {};
|
|
108
|
-
await __classPrivateFieldGet(this, _InMemoryDistributedTransactionStorage_instances, "m", _InMemoryDistributedTransactionStorage_preventRaceConditionExecutionIfNecessary).call(this, {
|
|
109
|
-
data,
|
|
110
|
-
key,
|
|
111
|
-
options,
|
|
112
|
-
});
|
|
113
|
-
Object.assign(data, {
|
|
114
|
-
retention_time: retentionTime,
|
|
115
|
-
});
|
|
116
|
-
this.storage.set(key, data);
|
|
117
|
-
if (hasFinished && !retentionTime && !idempotent) {
|
|
118
|
-
await this.deleteFromDb(data);
|
|
217
|
+
if (this.isLocked.has(key)) {
|
|
218
|
+
throw new Error("Transaction storage is locked");
|
|
119
219
|
}
|
|
120
|
-
|
|
121
|
-
|
|
220
|
+
this.isLocked.set(key, true);
|
|
221
|
+
try {
|
|
222
|
+
/**
|
|
223
|
+
* Store the retention time only if the transaction is done, failed or reverted.
|
|
224
|
+
* From that moment, this tuple can be later on archived or deleted after the retention time.
|
|
225
|
+
*/
|
|
226
|
+
const { retentionTime } = options ?? {};
|
|
227
|
+
const hasFinished = finishedStates.has(data.flow.state);
|
|
228
|
+
await __classPrivateFieldGet(this, _InMemoryDistributedTransactionStorage_instances, "m", _InMemoryDistributedTransactionStorage_preventRaceConditionExecutionIfNecessary).call(this, {
|
|
229
|
+
data,
|
|
230
|
+
key,
|
|
231
|
+
options,
|
|
232
|
+
});
|
|
233
|
+
// Only store retention time if it's provided
|
|
234
|
+
if (retentionTime) {
|
|
235
|
+
Object.assign(data, {
|
|
236
|
+
retention_time: retentionTime,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
// Store in memory
|
|
240
|
+
const isNotStarted = data.flow.state === utils_1.TransactionState.NOT_STARTED;
|
|
241
|
+
const isManualTransactionId = !data.flow.transactionId.startsWith("auto-");
|
|
242
|
+
if (isNotStarted && isManualTransactionId) {
|
|
243
|
+
const storedData = this.storage[key];
|
|
244
|
+
if (storedData) {
|
|
245
|
+
throw new orchestration_1.SkipExecutionError("Transaction already started for transactionId: " +
|
|
246
|
+
data.flow.transactionId);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (data.flow._v) {
|
|
250
|
+
const storedData = await this.get(key, {
|
|
251
|
+
isCancelling: !!data.flow.cancelledAt,
|
|
252
|
+
});
|
|
253
|
+
orchestration_1.TransactionCheckpoint.mergeCheckpoints(data, storedData);
|
|
254
|
+
}
|
|
255
|
+
const { flow, context, errors } = data;
|
|
256
|
+
this.storage[key] = {
|
|
257
|
+
flow: JSON.parse(JSON.stringify(flow)),
|
|
258
|
+
context: JSON.parse(JSON.stringify(context)),
|
|
259
|
+
errors: [...errors],
|
|
260
|
+
};
|
|
261
|
+
// Optimize DB operations - only perform when necessary
|
|
262
|
+
if (hasFinished) {
|
|
263
|
+
if (!retentionTime) {
|
|
264
|
+
if (!flow.metadata?.parentStepIdempotencyKey) {
|
|
265
|
+
await this.deleteFromDb(data);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
await this.saveToDb(data, retentionTime);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
await this.saveToDb(data, retentionTime);
|
|
273
|
+
}
|
|
274
|
+
delete this.storage[key];
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
await this.saveToDb(data, retentionTime);
|
|
278
|
+
}
|
|
279
|
+
return data;
|
|
122
280
|
}
|
|
123
|
-
|
|
124
|
-
this.
|
|
281
|
+
finally {
|
|
282
|
+
this.isLocked.delete(key);
|
|
125
283
|
}
|
|
126
284
|
}
|
|
127
285
|
async scheduleRetry(transaction, step, timestamp, interval) {
|
|
128
286
|
const { modelId: workflowId, transactionId } = transaction;
|
|
129
|
-
const
|
|
287
|
+
const key = `${workflowId}:${transactionId}:${step.id}`;
|
|
288
|
+
const existingTimer = this.retries.get(key);
|
|
289
|
+
if (existingTimer) {
|
|
290
|
+
clearTimeout(existingTimer);
|
|
291
|
+
this.pendingTimers.delete(existingTimer);
|
|
292
|
+
}
|
|
293
|
+
const timer = this.createManagedTimer(async () => {
|
|
294
|
+
this.retries.delete(key);
|
|
295
|
+
const context = transaction.getFlow().metadata ?? {};
|
|
130
296
|
await this.workflowOrchestratorService_.run(workflowId, {
|
|
131
297
|
transactionId,
|
|
132
298
|
logOnError: true,
|
|
133
299
|
throwOnError: false,
|
|
300
|
+
context: {
|
|
301
|
+
eventGroupId: context.eventGroupId,
|
|
302
|
+
parentStepIdempotencyKey: context.parentStepIdempotencyKey,
|
|
303
|
+
preventReleaseEvents: context.preventReleaseEvents,
|
|
304
|
+
},
|
|
134
305
|
});
|
|
135
306
|
}, interval * 1e3);
|
|
136
|
-
|
|
137
|
-
this.retries.set(key, inter);
|
|
307
|
+
this.retries.set(key, timer);
|
|
138
308
|
}
|
|
139
309
|
async clearRetry(transaction, step) {
|
|
140
310
|
const { modelId: workflowId, transactionId } = transaction;
|
|
141
311
|
const key = `${workflowId}:${transactionId}:${step.id}`;
|
|
142
|
-
const
|
|
143
|
-
if (
|
|
144
|
-
clearTimeout(
|
|
312
|
+
const timer = this.retries.get(key);
|
|
313
|
+
if (timer) {
|
|
314
|
+
clearTimeout(timer);
|
|
315
|
+
this.pendingTimers.delete(timer);
|
|
145
316
|
this.retries.delete(key);
|
|
146
317
|
}
|
|
147
318
|
}
|
|
148
319
|
async scheduleTransactionTimeout(transaction, timestamp, interval) {
|
|
149
320
|
const { modelId: workflowId, transactionId } = transaction;
|
|
150
|
-
const
|
|
321
|
+
const key = `${workflowId}:${transactionId}`;
|
|
322
|
+
const existingTimer = this.timeouts.get(key);
|
|
323
|
+
if (existingTimer) {
|
|
324
|
+
clearTimeout(existingTimer);
|
|
325
|
+
this.pendingTimers.delete(existingTimer);
|
|
326
|
+
}
|
|
327
|
+
const timer = this.createManagedTimer(async () => {
|
|
328
|
+
this.timeouts.delete(key);
|
|
329
|
+
const context = transaction.getFlow().metadata ?? {};
|
|
151
330
|
await this.workflowOrchestratorService_.run(workflowId, {
|
|
152
331
|
transactionId,
|
|
332
|
+
logOnError: true,
|
|
153
333
|
throwOnError: false,
|
|
334
|
+
context: {
|
|
335
|
+
eventGroupId: context.eventGroupId,
|
|
336
|
+
parentStepIdempotencyKey: context.parentStepIdempotencyKey,
|
|
337
|
+
preventReleaseEvents: context.preventReleaseEvents,
|
|
338
|
+
},
|
|
154
339
|
});
|
|
155
340
|
}, interval * 1e3);
|
|
156
|
-
|
|
157
|
-
this.timeouts.set(key, inter);
|
|
341
|
+
this.timeouts.set(key, timer);
|
|
158
342
|
}
|
|
159
343
|
async clearTransactionTimeout(transaction) {
|
|
160
344
|
const { modelId: workflowId, transactionId } = transaction;
|
|
161
345
|
const key = `${workflowId}:${transactionId}`;
|
|
162
|
-
const
|
|
163
|
-
if (
|
|
164
|
-
clearTimeout(
|
|
346
|
+
const timer = this.timeouts.get(key);
|
|
347
|
+
if (timer) {
|
|
348
|
+
clearTimeout(timer);
|
|
349
|
+
this.pendingTimers.delete(timer);
|
|
165
350
|
this.timeouts.delete(key);
|
|
166
351
|
}
|
|
167
352
|
}
|
|
168
353
|
async scheduleStepTimeout(transaction, step, timestamp, interval) {
|
|
169
354
|
const { modelId: workflowId, transactionId } = transaction;
|
|
170
|
-
const
|
|
355
|
+
const key = `${workflowId}:${transactionId}:${step.id}`;
|
|
356
|
+
const existingTimer = this.timeouts.get(key);
|
|
357
|
+
if (existingTimer) {
|
|
358
|
+
clearTimeout(existingTimer);
|
|
359
|
+
this.pendingTimers.delete(existingTimer);
|
|
360
|
+
}
|
|
361
|
+
const timer = this.createManagedTimer(async () => {
|
|
362
|
+
this.timeouts.delete(key);
|
|
363
|
+
const context = transaction.getFlow().metadata ?? {};
|
|
171
364
|
await this.workflowOrchestratorService_.run(workflowId, {
|
|
172
365
|
transactionId,
|
|
366
|
+
logOnError: true,
|
|
173
367
|
throwOnError: false,
|
|
368
|
+
context: {
|
|
369
|
+
eventGroupId: context.eventGroupId,
|
|
370
|
+
parentStepIdempotencyKey: context.parentStepIdempotencyKey,
|
|
371
|
+
preventReleaseEvents: context.preventReleaseEvents,
|
|
372
|
+
},
|
|
174
373
|
});
|
|
175
374
|
}, interval * 1e3);
|
|
176
|
-
|
|
177
|
-
this.timeouts.set(key, inter);
|
|
375
|
+
this.timeouts.set(key, timer);
|
|
178
376
|
}
|
|
179
377
|
async clearStepTimeout(transaction, step) {
|
|
180
378
|
const { modelId: workflowId, transactionId } = transaction;
|
|
181
379
|
const key = `${workflowId}:${transactionId}:${step.id}`;
|
|
182
|
-
const
|
|
183
|
-
if (
|
|
184
|
-
clearTimeout(
|
|
380
|
+
const timer = this.timeouts.get(key);
|
|
381
|
+
if (timer) {
|
|
382
|
+
clearTimeout(timer);
|
|
383
|
+
this.pendingTimers.delete(timer);
|
|
185
384
|
this.timeouts.delete(key);
|
|
186
385
|
}
|
|
187
386
|
}
|
|
@@ -189,11 +388,11 @@ class InMemoryDistributedTransactionStorage {
|
|
|
189
388
|
async schedule(jobDefinition, schedulerOptions) {
|
|
190
389
|
const jobId = typeof jobDefinition === "string" ? jobDefinition : jobDefinition.jobId;
|
|
191
390
|
// In order to ensure that the schedule configuration is always up to date, we first cancel an existing job, if there was one
|
|
192
|
-
// any only then we add the new one.
|
|
193
391
|
await this.remove(jobId);
|
|
194
392
|
let expression;
|
|
195
393
|
let nextExecution = parseNextExecution(schedulerOptions);
|
|
196
394
|
if ("cron" in schedulerOptions) {
|
|
395
|
+
// Cache the parsed expression to avoid repeated parsing
|
|
197
396
|
expression = (0, cron_parser_1.parseExpression)(schedulerOptions.cron);
|
|
198
397
|
}
|
|
199
398
|
else if ("interval" in schedulerOptions) {
|
|
@@ -205,6 +404,8 @@ class InMemoryDistributedTransactionStorage {
|
|
|
205
404
|
const timer = setTimeout(async () => {
|
|
206
405
|
this.jobHandler(jobId);
|
|
207
406
|
}, nextExecution);
|
|
407
|
+
// Set the timer's unref to prevent it from keeping the process alive
|
|
408
|
+
timer.unref();
|
|
208
409
|
this.scheduled.set(jobId, {
|
|
209
410
|
timer,
|
|
210
411
|
expression,
|
|
@@ -236,21 +437,22 @@ class InMemoryDistributedTransactionStorage {
|
|
|
236
437
|
return;
|
|
237
438
|
}
|
|
238
439
|
const nextExecution = parseNextExecution(job.expression);
|
|
239
|
-
const timer = setTimeout(async () => {
|
|
240
|
-
this.jobHandler(jobId);
|
|
241
|
-
}, nextExecution);
|
|
242
|
-
this.scheduled.set(jobId, {
|
|
243
|
-
timer,
|
|
244
|
-
expression: job.expression,
|
|
245
|
-
numberOfExecutions: (job.numberOfExecutions ?? 0) + 1,
|
|
246
|
-
config: job.config,
|
|
247
|
-
});
|
|
248
440
|
try {
|
|
249
|
-
// With running the job after setting a new timer we basically allow for concurrent runs, unless we add idempotency keys once they are supported.
|
|
250
441
|
await this.workflowOrchestratorService_.run(jobId, {
|
|
251
442
|
logOnError: true,
|
|
252
443
|
throwOnError: false,
|
|
253
444
|
});
|
|
445
|
+
const timer = this.createManagedTimer(() => {
|
|
446
|
+
this.jobHandler(jobId);
|
|
447
|
+
}, nextExecution);
|
|
448
|
+
// Prevent timer from keeping the process alive
|
|
449
|
+
timer.unref();
|
|
450
|
+
this.scheduled.set(jobId, {
|
|
451
|
+
timer,
|
|
452
|
+
expression: job.expression,
|
|
453
|
+
numberOfExecutions: (job.numberOfExecutions ?? 0) + 1,
|
|
454
|
+
config: job.config,
|
|
455
|
+
});
|
|
254
456
|
}
|
|
255
457
|
catch (e) {
|
|
256
458
|
if (e instanceof utils_1.MedusaError && e.type === utils_1.MedusaError.Types.NOT_FOUND) {
|
|
@@ -261,21 +463,65 @@ class InMemoryDistributedTransactionStorage {
|
|
|
261
463
|
throw e;
|
|
262
464
|
}
|
|
263
465
|
}
|
|
466
|
+
async clearExpiredExecutions() {
|
|
467
|
+
await this.workflowExecutionService_.delete({
|
|
468
|
+
retention_time: {
|
|
469
|
+
$ne: null,
|
|
470
|
+
},
|
|
471
|
+
updated_at: {
|
|
472
|
+
$lte: (0, core_1.raw)((_alias) => `CURRENT_TIMESTAMP - (INTERVAL '1 second' * "retention_time")`),
|
|
473
|
+
},
|
|
474
|
+
state: {
|
|
475
|
+
$in: [
|
|
476
|
+
utils_1.TransactionState.DONE,
|
|
477
|
+
utils_1.TransactionState.FAILED,
|
|
478
|
+
utils_1.TransactionState.REVERTED,
|
|
479
|
+
],
|
|
480
|
+
},
|
|
481
|
+
});
|
|
482
|
+
}
|
|
264
483
|
}
|
|
265
484
|
exports.InMemoryDistributedTransactionStorage = InMemoryDistributedTransactionStorage;
|
|
266
485
|
_InMemoryDistributedTransactionStorage_instances = new WeakSet(), _InMemoryDistributedTransactionStorage_preventRaceConditionExecutionIfNecessary = async function _InMemoryDistributedTransactionStorage_preventRaceConditionExecutionIfNecessary({ data, key, options, }) {
|
|
267
|
-
|
|
268
|
-
if (data.flow.state === utils_1.TransactionState.NOT_STARTED) {
|
|
269
|
-
isInitialCheckpoint = true;
|
|
270
|
-
}
|
|
486
|
+
const isInitialCheckpoint = [utils_1.TransactionState.NOT_STARTED].includes(data.flow.state);
|
|
271
487
|
/**
|
|
272
488
|
* In case many execution can succeed simultaneously, we need to ensure that the latest
|
|
273
489
|
* execution does continue if a previous execution is considered finished
|
|
274
490
|
*/
|
|
275
491
|
const currentFlow = data.flow;
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
if (
|
|
492
|
+
const rawData = this.storage[key];
|
|
493
|
+
let data_ = {};
|
|
494
|
+
if (rawData) {
|
|
495
|
+
data_ = rawData;
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
const getOptions = {
|
|
499
|
+
...options,
|
|
500
|
+
isCancelling: !!data.flow.cancelledAt,
|
|
501
|
+
};
|
|
502
|
+
data_ =
|
|
503
|
+
(await this.get(key, getOptions)) ??
|
|
504
|
+
{ flow: {} };
|
|
505
|
+
}
|
|
506
|
+
const { flow: latestUpdatedFlow } = data_;
|
|
507
|
+
if (options?.stepId) {
|
|
508
|
+
const stepId = options.stepId;
|
|
509
|
+
const currentStep = data.flow.steps[stepId];
|
|
510
|
+
const latestStep = latestUpdatedFlow.steps?.[stepId];
|
|
511
|
+
if (latestStep && currentStep) {
|
|
512
|
+
const isCompensating = data.flow.state === utils_1.TransactionState.COMPENSATING;
|
|
513
|
+
const latestState = isCompensating
|
|
514
|
+
? latestStep.compensate?.state
|
|
515
|
+
: latestStep.invoke?.state;
|
|
516
|
+
const shouldSkip = doneStates.has(latestState);
|
|
517
|
+
if (shouldSkip) {
|
|
518
|
+
throw new orchestration_1.SkipStepAlreadyFinishedError(`Step ${stepId} already finished by another execution`);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (!isInitialCheckpoint &&
|
|
523
|
+
!(0, utils_1.isPresent)(latestUpdatedFlow) &&
|
|
524
|
+
!data.flow.metadata?.parentStepIdempotencyKey) {
|
|
279
525
|
/**
|
|
280
526
|
* the initial checkpoint expect no other checkpoint to have been stored.
|
|
281
527
|
* In case it is not the initial one and another checkpoint is trying to
|
|
@@ -284,61 +530,12 @@ _InMemoryDistributedTransactionStorage_instances = new WeakSet(), _InMemoryDistr
|
|
|
284
530
|
*/
|
|
285
531
|
throw new orchestration_1.SkipExecutionError("Already finished by another execution");
|
|
286
532
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const latestUpdatedFlowLastInvokingStepIndex = !latestUpdatedFlow.steps
|
|
294
|
-
? 1 // There is no other execution, so the current execution is the latest
|
|
295
|
-
: Object.values(latestUpdatedFlow.steps ?? {}).findIndex((step) => {
|
|
296
|
-
return [
|
|
297
|
-
utils_1.TransactionStepState.INVOKING,
|
|
298
|
-
utils_1.TransactionStepState.NOT_STARTED,
|
|
299
|
-
].includes(step.invoke?.state);
|
|
300
|
-
});
|
|
301
|
-
const currentFlowLastCompensatingStepIndex = Object.values(currentFlow.steps)
|
|
302
|
-
.reverse()
|
|
303
|
-
.findIndex((step) => {
|
|
304
|
-
return [
|
|
305
|
-
utils_1.TransactionStepState.COMPENSATING,
|
|
306
|
-
utils_1.TransactionStepState.NOT_STARTED,
|
|
307
|
-
].includes(step.compensate?.state);
|
|
308
|
-
});
|
|
309
|
-
const latestUpdatedFlowLastCompensatingStepIndex = !latestUpdatedFlow.steps
|
|
310
|
-
? -1 // There is no other execution, so the current execution is the latest
|
|
311
|
-
: Object.values(latestUpdatedFlow.steps ?? {})
|
|
312
|
-
.reverse()
|
|
313
|
-
.findIndex((step) => {
|
|
314
|
-
return [
|
|
315
|
-
utils_1.TransactionStepState.COMPENSATING,
|
|
316
|
-
utils_1.TransactionStepState.NOT_STARTED,
|
|
317
|
-
].includes(step.compensate?.state);
|
|
318
|
-
});
|
|
319
|
-
const isLatestExecutionFinishedIndex = -1;
|
|
320
|
-
const invokeShouldBeSkipped = (latestUpdatedFlowLastInvokingStepIndex ===
|
|
321
|
-
isLatestExecutionFinishedIndex ||
|
|
322
|
-
currentFlowLastInvokingStepIndex <
|
|
323
|
-
latestUpdatedFlowLastInvokingStepIndex) &&
|
|
324
|
-
currentFlowLastInvokingStepIndex !== isLatestExecutionFinishedIndex;
|
|
325
|
-
const compensateShouldBeSkipped = currentFlowLastCompensatingStepIndex <
|
|
326
|
-
latestUpdatedFlowLastCompensatingStepIndex &&
|
|
327
|
-
currentFlowLastCompensatingStepIndex !== isLatestExecutionFinishedIndex &&
|
|
328
|
-
latestUpdatedFlowLastCompensatingStepIndex !==
|
|
329
|
-
isLatestExecutionFinishedIndex;
|
|
330
|
-
if ((data.flow.state !== utils_1.TransactionState.COMPENSATING &&
|
|
331
|
-
invokeShouldBeSkipped) ||
|
|
332
|
-
(data.flow.state === utils_1.TransactionState.COMPENSATING &&
|
|
333
|
-
compensateShouldBeSkipped) ||
|
|
334
|
-
(latestUpdatedFlow.state === utils_1.TransactionState.COMPENSATING &&
|
|
335
|
-
![utils_1.TransactionState.REVERTED, utils_1.TransactionState.FAILED].includes(currentFlow.state) &&
|
|
336
|
-
currentFlow.state !== latestUpdatedFlow.state) ||
|
|
337
|
-
(latestUpdatedFlow.state === utils_1.TransactionState.REVERTED &&
|
|
338
|
-
currentFlow.state !== utils_1.TransactionState.REVERTED) ||
|
|
339
|
-
(latestUpdatedFlow.state === utils_1.TransactionState.FAILED &&
|
|
340
|
-
currentFlow.state !== utils_1.TransactionState.FAILED)) {
|
|
341
|
-
throw new orchestration_1.SkipExecutionError("Already finished by another execution");
|
|
533
|
+
// Ensure that the latest execution was not cancelled, otherwise we skip the execution
|
|
534
|
+
const latestTransactionCancelledAt = latestUpdatedFlow.cancelledAt;
|
|
535
|
+
const currentTransactionCancelledAt = currentFlow.cancelledAt;
|
|
536
|
+
if (!!latestTransactionCancelledAt &&
|
|
537
|
+
currentTransactionCancelledAt == null) {
|
|
538
|
+
throw new orchestration_1.SkipCancelledExecutionError("Workflow execution has been cancelled during the execution");
|
|
342
539
|
}
|
|
343
540
|
};
|
|
344
541
|
//# sourceMappingURL=workflow-orchestrator-storage.js.map
|