@medusajs/workflow-engine-inmemory 3.0.0-snapshot-20250410105645 → 3.0.0-snapshot-20251104004624

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/loaders/utils.d.ts.map +1 -1
  2. package/dist/loaders/utils.js +1 -1
  3. package/dist/loaders/utils.js.map +1 -1
  4. package/dist/migrations/Migration20231228143900.d.ts +1 -1
  5. package/dist/migrations/Migration20231228143900.d.ts.map +1 -1
  6. package/dist/migrations/Migration20231228143900.js +1 -1
  7. package/dist/migrations/Migration20231228143900.js.map +1 -1
  8. package/dist/migrations/Migration20241206101446.d.ts +1 -1
  9. package/dist/migrations/Migration20241206101446.d.ts.map +1 -1
  10. package/dist/migrations/Migration20241206101446.js +1 -1
  11. package/dist/migrations/Migration20241206101446.js.map +1 -1
  12. package/dist/migrations/Migration20250128174331.d.ts +1 -1
  13. package/dist/migrations/Migration20250128174331.d.ts.map +1 -1
  14. package/dist/migrations/Migration20250128174331.js +1 -1
  15. package/dist/migrations/Migration20250128174331.js.map +1 -1
  16. package/dist/migrations/Migration20250505092459.d.ts +6 -0
  17. package/dist/migrations/Migration20250505092459.d.ts.map +1 -0
  18. package/dist/migrations/Migration20250505092459.js +40 -0
  19. package/dist/migrations/Migration20250505092459.js.map +1 -0
  20. package/dist/migrations/Migration20250819104213.d.ts +6 -0
  21. package/dist/migrations/Migration20250819104213.d.ts.map +1 -0
  22. package/dist/migrations/Migration20250819104213.js +14 -0
  23. package/dist/migrations/Migration20250819104213.js.map +1 -0
  24. package/dist/migrations/Migration20250819110924.d.ts +6 -0
  25. package/dist/migrations/Migration20250819110924.d.ts.map +1 -0
  26. package/dist/migrations/Migration20250819110924.js +16 -0
  27. package/dist/migrations/Migration20250819110924.js.map +1 -0
  28. package/dist/migrations/Migration20250908080305.d.ts +6 -0
  29. package/dist/migrations/Migration20250908080305.d.ts.map +1 -0
  30. package/dist/migrations/Migration20250908080305.js +20 -0
  31. package/dist/migrations/Migration20250908080305.js.map +1 -0
  32. package/dist/models/workflow-execution.d.ts +1 -0
  33. package/dist/models/workflow-execution.d.ts.map +1 -1
  34. package/dist/models/workflow-execution.js +22 -1
  35. package/dist/models/workflow-execution.js.map +1 -1
  36. package/dist/schema/index.d.ts +1 -1
  37. package/dist/schema/index.d.ts.map +1 -1
  38. package/dist/schema/index.js +1 -0
  39. package/dist/schema/index.js.map +1 -1
  40. package/dist/services/workflow-orchestrator.d.ts +18 -3
  41. package/dist/services/workflow-orchestrator.d.ts.map +1 -1
  42. package/dist/services/workflow-orchestrator.js +176 -53
  43. package/dist/services/workflow-orchestrator.js.map +1 -1
  44. package/dist/services/workflows-module.d.ts +113 -8
  45. package/dist/services/workflows-module.d.ts.map +1 -1
  46. package/dist/services/workflows-module.js +114 -48
  47. package/dist/services/workflows-module.js.map +1 -1
  48. package/dist/tsconfig.tsbuildinfo +1 -1
  49. package/dist/utils/workflow-orchestrator-storage.d.ts +11 -3
  50. package/dist/utils/workflow-orchestrator-storage.d.ts.map +1 -1
  51. package/dist/utils/workflow-orchestrator-storage.js +336 -139
  52. package/dist/utils/workflow-orchestrator-storage.js.map +1 -1
  53. 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.next().getTime() - Date.now();
47
+ return calculateDelayFromExpression(expression);
18
48
  }
19
49
  if ("interval" in optionsOrExpression) {
20
50
  return optionsOrExpression.interval;
21
51
  }
22
- return optionsOrExpression.next().getTime() - Date.now();
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.next().getTime() - Date.now();
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 = new Map();
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
- workflow_id: data.flow.modelId,
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
- .retrieve({
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
- return {
87
- flow: trx.execution,
88
- context: trx.context.data,
89
- errors: trx.context.errors,
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
- * Store the retention time only if the transaction is done, failed or reverted.
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
- else {
121
- await this.saveToDb(data, retentionTime);
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
- if (hasFinished) {
124
- this.storage.delete(key);
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 inter = setTimeout(async () => {
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
- const key = `${workflowId}:${transactionId}:${step.id}`;
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 inter = this.retries.get(key);
143
- if (inter) {
144
- clearTimeout(inter);
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 inter = setTimeout(async () => {
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
- const key = `${workflowId}:${transactionId}`;
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 inter = this.timeouts.get(key);
163
- if (inter) {
164
- clearTimeout(inter);
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 inter = setTimeout(async () => {
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
- const key = `${workflowId}:${transactionId}:${step.id}`;
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 inter = this.timeouts.get(key);
183
- if (inter) {
184
- clearTimeout(inter);
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
- let isInitialCheckpoint = false;
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 { flow: latestUpdatedFlow } = (await this.get(key, options)) ??
277
- { flow: {} };
278
- if (!isInitialCheckpoint && !(0, utils_1.isPresent)(latestUpdatedFlow)) {
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
- const currentFlowLastInvokingStepIndex = Object.values(currentFlow.steps).findIndex((step) => {
288
- return [
289
- utils_1.TransactionStepState.INVOKING,
290
- utils_1.TransactionStepState.NOT_STARTED,
291
- ].includes(step.invoke?.state);
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