@nocobase/plugin-workflow 2.1.0-beta.8 → 2.2.0-alpha.1

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 (197) hide show
  1. package/dist/client/214.7e602cfe7a8251b8.js +10 -0
  2. package/dist/client/618.19af7f84261c815d.js +10 -0
  3. package/dist/client/67.452743ce8ec30617.js +10 -0
  4. package/dist/client/964.ffbf5b47ed12bbdc.js +10 -0
  5. package/dist/client/Branch.d.ts +7 -3
  6. package/dist/client/BranchContext.d.ts +18 -0
  7. package/dist/client/components/TimeoutInput.d.ts +11 -0
  8. package/dist/client/constants.d.ts +13 -0
  9. package/dist/client/flows/triggerWorkflows.d.ts +14 -1
  10. package/dist/client/hooks/{useWorkflowFilterActionProps.d.ts → useResourceFilterActionProps.d.ts} +1 -1
  11. package/dist/client/index.js +1 -1
  12. package/dist/client/nodes/create.d.ts +10 -0
  13. package/dist/client/nodes/destroy.d.ts +10 -0
  14. package/dist/client/nodes/index.d.ts +5 -0
  15. package/dist/client/nodes/query.d.ts +18 -2
  16. package/dist/client/nodes/update.d.ts +10 -0
  17. package/dist/client/schemas/collection.d.ts +8 -2
  18. package/dist/client/schemas/executions.d.ts +63 -2
  19. package/dist/client/triggers/collection.d.ts +14 -1
  20. package/dist/client/triggers/index.d.ts +4 -0
  21. package/dist/client/triggers/schedule/constants.d.ts +4 -0
  22. package/dist/client/triggers/schedule/index.d.ts +15 -0
  23. package/dist/client/utils.d.ts +17 -0
  24. package/dist/common/collections/executions.d.ts +44 -1
  25. package/dist/common/collections/executions.js +63 -1
  26. package/dist/common/collections/flow_nodes.d.ts +1 -0
  27. package/dist/common/collections/flow_nodes.js +1 -0
  28. package/dist/common/collections/jobs.d.ts +1 -0
  29. package/dist/common/collections/jobs.js +8 -0
  30. package/dist/common/collections/userWorkflowTasks.d.ts +1 -0
  31. package/dist/common/collections/userWorkflowTasks.js +1 -0
  32. package/dist/common/collections/workflowCategories.d.ts +1 -0
  33. package/dist/common/collections/workflowCategories.js +1 -0
  34. package/dist/common/collections/workflowCategoryRelations.d.ts +1 -0
  35. package/dist/common/collections/workflowCategoryRelations.js +1 -0
  36. package/dist/common/collections/workflowStats.d.ts +1 -0
  37. package/dist/common/collections/workflowStats.js +1 -0
  38. package/dist/common/collections/workflowTasks.js +1 -0
  39. package/dist/common/collections/workflowVersionStats.d.ts +1 -0
  40. package/dist/common/collections/workflowVersionStats.js +1 -0
  41. package/dist/common/collections/workflows.d.ts +66 -11
  42. package/dist/common/collections/workflows.js +35 -2
  43. package/dist/common/constants.d.ts +5 -0
  44. package/dist/common/constants.js +7 -0
  45. package/dist/externalVersion.js +15 -13
  46. package/dist/locale/de-DE.json +4 -0
  47. package/dist/locale/en-US.json +7 -0
  48. package/dist/locale/es-ES.json +4 -0
  49. package/dist/locale/fr-FR.json +4 -0
  50. package/dist/locale/hu-HU.json +7 -3
  51. package/dist/locale/id-ID.json +4 -0
  52. package/dist/locale/it-IT.json +4 -0
  53. package/dist/locale/ja-JP.json +5 -1
  54. package/dist/locale/ko-KR.json +4 -0
  55. package/dist/locale/nl-NL.json +7 -3
  56. package/dist/locale/pt-BR.json +4 -0
  57. package/dist/locale/ru-RU.json +4 -0
  58. package/dist/locale/tr-TR.json +4 -0
  59. package/dist/locale/uk-UA.json +7 -3
  60. package/dist/locale/vi-VN.json +7 -3
  61. package/dist/locale/zh-CN.json +10 -0
  62. package/dist/locale/zh-TW.json +7 -3
  63. package/dist/node_modules/cron-parser/lib/parser.js +1 -1
  64. package/dist/node_modules/cron-parser/package.json +1 -1
  65. package/dist/node_modules/joi/dist/joi-browser.min.js +1 -0
  66. package/dist/node_modules/joi/lib/annotate.js +175 -0
  67. package/dist/node_modules/joi/lib/base.js +1069 -0
  68. package/dist/node_modules/joi/lib/cache.js +143 -0
  69. package/dist/node_modules/joi/lib/common.js +216 -0
  70. package/dist/node_modules/joi/lib/compile.js +283 -0
  71. package/dist/node_modules/joi/lib/errors.js +271 -0
  72. package/dist/node_modules/joi/lib/extend.js +312 -0
  73. package/dist/node_modules/joi/lib/index.d.ts +2365 -0
  74. package/dist/node_modules/joi/lib/index.js +1 -0
  75. package/dist/node_modules/joi/lib/manifest.js +476 -0
  76. package/dist/node_modules/joi/lib/messages.js +178 -0
  77. package/dist/node_modules/joi/lib/modify.js +267 -0
  78. package/dist/node_modules/joi/lib/ref.js +414 -0
  79. package/dist/node_modules/joi/lib/schemas.js +302 -0
  80. package/dist/node_modules/joi/lib/state.js +166 -0
  81. package/dist/node_modules/joi/lib/template.js +463 -0
  82. package/dist/node_modules/joi/lib/trace.js +346 -0
  83. package/dist/node_modules/joi/lib/types/alternatives.js +364 -0
  84. package/dist/node_modules/joi/lib/types/any.js +174 -0
  85. package/dist/node_modules/joi/lib/types/array.js +809 -0
  86. package/dist/node_modules/joi/lib/types/binary.js +100 -0
  87. package/dist/node_modules/joi/lib/types/boolean.js +150 -0
  88. package/dist/node_modules/joi/lib/types/date.js +233 -0
  89. package/dist/node_modules/joi/lib/types/function.js +93 -0
  90. package/dist/node_modules/joi/lib/types/keys.js +1067 -0
  91. package/dist/node_modules/joi/lib/types/link.js +168 -0
  92. package/dist/node_modules/joi/lib/types/number.js +363 -0
  93. package/dist/node_modules/joi/lib/types/object.js +22 -0
  94. package/dist/node_modules/joi/lib/types/string.js +850 -0
  95. package/dist/node_modules/joi/lib/types/symbol.js +102 -0
  96. package/dist/node_modules/joi/lib/validator.js +750 -0
  97. package/dist/node_modules/joi/lib/values.js +263 -0
  98. package/dist/node_modules/joi/node_modules/@hapi/topo/lib/index.d.ts +60 -0
  99. package/dist/node_modules/joi/node_modules/@hapi/topo/lib/index.js +225 -0
  100. package/dist/node_modules/joi/node_modules/@hapi/topo/package.json +30 -0
  101. package/dist/node_modules/joi/package.json +1 -0
  102. package/dist/node_modules/lru-cache/dist/commonjs/diagnostics-channel.d.ts +5 -0
  103. package/dist/node_modules/lru-cache/dist/commonjs/diagnostics-channel.js +10 -0
  104. package/dist/node_modules/lru-cache/dist/commonjs/index.d.ts +1381 -0
  105. package/dist/node_modules/lru-cache/dist/commonjs/index.js +1692 -0
  106. package/dist/node_modules/lru-cache/dist/commonjs/index.min.js +1 -0
  107. package/dist/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.d.ts +5 -0
  108. package/dist/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.js +4 -0
  109. package/dist/node_modules/lru-cache/dist/esm/browser/index.d.ts +1381 -0
  110. package/dist/node_modules/lru-cache/dist/{mjs → esm/browser}/index.js +537 -179
  111. package/dist/node_modules/lru-cache/dist/esm/browser/index.min.js +2 -0
  112. package/dist/node_modules/lru-cache/dist/esm/diagnostics-channel.d.ts +5 -0
  113. package/dist/node_modules/lru-cache/dist/esm/diagnostics-channel.js +19 -0
  114. package/dist/node_modules/lru-cache/dist/esm/index.d.ts +1381 -0
  115. package/dist/node_modules/lru-cache/dist/{cjs → esm}/index.js +538 -184
  116. package/dist/node_modules/lru-cache/dist/esm/index.min.js +2 -0
  117. package/dist/node_modules/lru-cache/dist/esm/node/diagnostics-channel.d.ts +5 -0
  118. package/dist/node_modules/lru-cache/dist/esm/node/diagnostics-channel.js +7 -0
  119. package/dist/node_modules/lru-cache/dist/esm/node/index.d.ts +1381 -0
  120. package/dist/node_modules/lru-cache/dist/esm/node/index.js +1688 -0
  121. package/dist/node_modules/lru-cache/dist/esm/node/index.min.js +2 -0
  122. package/dist/node_modules/lru-cache/package.json +1 -1
  123. package/dist/node_modules/nodejs-snowflake/nodejs_snowflake.js +1 -1
  124. package/dist/node_modules/nodejs-snowflake/package.json +1 -1
  125. package/dist/server/Dispatcher.d.ts +12 -6
  126. package/dist/server/Dispatcher.js +309 -190
  127. package/dist/server/ExecutionTimeoutManager.d.ts +46 -0
  128. package/dist/server/ExecutionTimeoutManager.js +320 -0
  129. package/dist/server/Plugin.d.ts +17 -3
  130. package/dist/server/Plugin.js +54 -12
  131. package/dist/server/Processor.d.ts +64 -12
  132. package/dist/server/Processor.js +286 -48
  133. package/dist/server/RunningExecutionRegistry.d.ts +18 -0
  134. package/dist/server/RunningExecutionRegistry.js +48 -0
  135. package/dist/server/actions/executions.d.ts +4 -3
  136. package/dist/server/actions/executions.js +42 -21
  137. package/dist/server/actions/jobs.d.ts +2 -1
  138. package/dist/server/actions/jobs.js +28 -1
  139. package/dist/server/actions/nodes.d.ts +5 -0
  140. package/dist/server/actions/nodes.js +38 -5
  141. package/dist/server/actions/workflows.d.ts +6 -0
  142. package/dist/server/actions/workflows.js +38 -0
  143. package/dist/server/constants.d.ts +2 -0
  144. package/dist/server/constants.js +3 -0
  145. package/dist/server/index.d.ts +2 -0
  146. package/dist/server/index.js +2 -0
  147. package/dist/server/instructions/ConditionInstruction.d.ts +2 -0
  148. package/dist/server/instructions/ConditionInstruction.js +17 -0
  149. package/dist/server/instructions/CreateInstruction.d.ts +3 -0
  150. package/dist/server/instructions/CreateInstruction.js +25 -0
  151. package/dist/server/instructions/DestroyInstruction.d.ts +3 -0
  152. package/dist/server/instructions/DestroyInstruction.js +25 -0
  153. package/dist/server/instructions/EndInstruction.d.ts +2 -0
  154. package/dist/server/instructions/EndInstruction.js +4 -0
  155. package/dist/server/instructions/MultiConditionsInstruction.d.ts +2 -0
  156. package/dist/server/instructions/MultiConditionsInstruction.js +23 -0
  157. package/dist/server/instructions/OutputInstruction.d.ts +2 -0
  158. package/dist/server/instructions/OutputInstruction.js +15 -1
  159. package/dist/server/instructions/QueryInstruction.d.ts +3 -0
  160. package/dist/server/instructions/QueryInstruction.js +32 -7
  161. package/dist/server/instructions/UpdateInstruction.d.ts +3 -0
  162. package/dist/server/instructions/UpdateInstruction.js +27 -0
  163. package/dist/server/instructions/index.d.ts +24 -4
  164. package/dist/server/instructions/index.js +18 -0
  165. package/dist/server/migrations/20260423225800-fill-workflow-created-updated-by.d.ts +13 -0
  166. package/dist/server/migrations/20260423225800-fill-workflow-created-updated-by.js +57 -0
  167. package/dist/server/migrations/20260501120000-workflow-timeout.d.ts +13 -0
  168. package/dist/server/migrations/20260501120000-workflow-timeout.js +63 -0
  169. package/dist/server/timeout-errors.d.ts +13 -0
  170. package/dist/server/timeout-errors.js +47 -0
  171. package/dist/server/triggers/CollectionTrigger.d.ts +3 -0
  172. package/dist/server/triggers/CollectionTrigger.js +34 -0
  173. package/dist/server/triggers/ScheduleTrigger/index.d.ts +3 -0
  174. package/dist/server/triggers/ScheduleTrigger/index.js +18 -3
  175. package/dist/server/triggers/index.d.ts +3 -0
  176. package/dist/server/triggers/index.js +18 -0
  177. package/dist/server/types/Execution.d.ts +6 -0
  178. package/dist/server/types/Job.d.ts +3 -3
  179. package/dist/server/types/Workflow.d.ts +6 -1
  180. package/dist/server/utils.d.ts +27 -0
  181. package/dist/server/utils.js +142 -2
  182. package/dist/swagger/index.d.ts +66 -75
  183. package/dist/swagger/index.js +58 -67
  184. package/package.json +5 -4
  185. package/dist/client/0e458d99e9fc5e65.js +0 -10
  186. package/dist/client/27bd65abee87cafa.js +0 -10
  187. package/dist/client/478692c1637f2742.js +0 -10
  188. package/dist/client/f39e94207f92e352.js +0 -10
  189. package/dist/node_modules/lru-cache/LICENSE +0 -15
  190. package/dist/node_modules/lru-cache/dist/cjs/index-cjs.d.ts +0 -7
  191. package/dist/node_modules/lru-cache/dist/cjs/index-cjs.js +0 -1
  192. package/dist/node_modules/lru-cache/dist/cjs/index.d.ts +0 -807
  193. package/dist/node_modules/lru-cache/dist/cjs/index.min.js +0 -2
  194. package/dist/node_modules/lru-cache/dist/mjs/index.d.ts +0 -807
  195. package/dist/node_modules/lru-cache/dist/mjs/index.min.js +0 -2
  196. /package/dist/node_modules/lru-cache/dist/{cjs → commonjs}/package.json +0 -0
  197. /package/dist/node_modules/lru-cache/dist/{mjs → esm}/package.json +0 -0
@@ -29,22 +29,24 @@ __export(Dispatcher_exports, {
29
29
  default: () => Dispatcher
30
30
  });
31
31
  module.exports = __toCommonJS(Dispatcher_exports);
32
- var import_crypto = require("crypto");
32
+ var import_node_crypto = require("node:crypto");
33
33
  var import_sequelize = require("sequelize");
34
34
  var import_constants = require("./constants");
35
35
  var import_Plugin = require("./Plugin");
36
+ var import_utils = require("./utils");
37
+ const EXECUTION_ACQUIRE_MAX_ATTEMPTS = 5;
36
38
  class Dispatcher {
37
39
  constructor(plugin) {
38
40
  this.plugin = plugin;
39
- this.prepare = this.prepare.bind(this);
40
41
  }
41
42
  ready = false;
42
43
  executing = null;
44
+ saving = null;
43
45
  pending = [];
44
46
  events = [];
45
47
  eventsCount = 0;
46
48
  get idle() {
47
- return this.ready && !this.executing && !this.pending.length && !this.events.length;
49
+ return this.ready && !this.executing && !this.saving && !this.pending.length && !this.events.length;
48
50
  }
49
51
  onQueueExecution = async (event) => {
50
52
  const ExecutionRepo = this.plugin.db.getRepository("executions");
@@ -52,11 +54,10 @@ class Dispatcher {
52
54
  filterByTk: event.executionId
53
55
  });
54
56
  if (!execution || execution.dispatched) {
55
- this.plugin.getLogger("dispatcher").info(`execution (${event.executionId}) from queue not found or not in queueing status, skip`);
56
57
  return;
57
58
  }
58
59
  this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) received from queue, adding to pending list`);
59
- this.run({ execution });
60
+ await this.run({ execution });
60
61
  };
61
62
  setReady(ready) {
62
63
  this.ready = ready;
@@ -96,11 +97,55 @@ class Dispatcher {
96
97
  this.eventsCount = this.events.length;
97
98
  logger.info(`new event triggered, now events: ${this.events.length}`);
98
99
  logger.debug(`event data:`, { context });
99
- if (this.events.length > 1) {
100
- logger.info(`new event is pending to be prepared after previous preparation is finished`);
100
+ this.saveEvent();
101
+ }
102
+ saveEvent() {
103
+ if (this.saving) {
101
104
  return;
102
105
  }
103
- setImmediate(this.prepare);
106
+ this.saving = (async () => {
107
+ try {
108
+ while (this.events.length) {
109
+ if (this.executing && this.plugin.db.options.dialect === "sqlite") {
110
+ await this.executing;
111
+ }
112
+ const event = this.events.shift();
113
+ this.eventsCount = this.events.length;
114
+ if (!event) continue;
115
+ const logger = this.plugin.getLogger(event[0].id);
116
+ logger.info(`preparing execution for event`);
117
+ try {
118
+ const execution = await this.createExecution(...event);
119
+ if (!execution.dispatched) {
120
+ if (this.plugin.serving() && !this.executing && !this.pending.length) {
121
+ logger.info(`local pending list is empty, adding execution (${execution.id}) to pending list`);
122
+ this.pending.push({ execution });
123
+ } else {
124
+ logger.info(
125
+ `instance is not serving as worker or local pending list is not empty, sending execution (${execution.id}) to queue`
126
+ );
127
+ try {
128
+ await this.plugin.app.eventQueue.publish(this.plugin.channelPendingExecution, {
129
+ executionId: execution.id
130
+ });
131
+ } catch (qErr) {
132
+ logger.error(`publishing execution (${execution.id}) to queue failed:`, { error: qErr });
133
+ }
134
+ }
135
+ }
136
+ } catch (error) {
137
+ logger.error(`failed to create execution:`, { error });
138
+ }
139
+ }
140
+ } finally {
141
+ this.saving = null;
142
+ if (this.events.length) {
143
+ this.saveEvent();
144
+ } else {
145
+ this.dispatch();
146
+ }
147
+ }
148
+ })();
104
149
  }
105
150
  async resume(job) {
106
151
  let { execution } = job;
@@ -108,27 +153,37 @@ class Dispatcher {
108
153
  execution = await job.getExecution();
109
154
  }
110
155
  this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) resuming from job (${job.id}) added to pending list`);
111
- this.run({ execution, job, loaded: true });
156
+ await this.run({ execution, job });
112
157
  }
113
158
  async start(execution) {
114
159
  if (execution.status) {
115
160
  return;
116
161
  }
117
162
  this.plugin.getLogger(execution.workflowId).info(`starting deferred execution (${execution.id})`);
118
- this.run({ execution, loaded: true });
163
+ await this.run({ execution });
119
164
  }
120
165
  async beforeStop() {
121
166
  this.ready = false;
122
- if (this.events.length) {
123
- await this.prepare();
124
- }
125
- if (this.executing) {
126
- await this.executing;
167
+ this.plugin.getLogger("dispatcher").info("app is stopping, draining local queues...");
168
+ while (this.saving || this.executing || this.events.length || this.pending.length) {
169
+ if (this.saving) {
170
+ await this.saving;
171
+ }
172
+ if (this.executing) {
173
+ await this.executing;
174
+ }
175
+ if (this.events.length && !this.saving) {
176
+ this.saveEvent();
177
+ }
178
+ if (this.pending.length && !this.executing) {
179
+ this.dispatch();
180
+ }
181
+ await new Promise((resolve) => setImmediate(resolve));
127
182
  }
183
+ this.plugin.getLogger("dispatcher").info("local queues drained");
128
184
  }
129
185
  dispatch() {
130
- if (!this.ready) {
131
- this.plugin.getLogger("dispatcher").warn(`app is not ready, new dispatching will be ignored`);
186
+ if (!this.ready && !this.pending.length && !this.events.length) {
132
187
  return;
133
188
  }
134
189
  if (this.executing) {
@@ -136,30 +191,36 @@ class Dispatcher {
136
191
  return;
137
192
  }
138
193
  if (this.events.length) {
139
- return this.prepare();
194
+ this.saveEvent();
195
+ return;
140
196
  }
141
197
  this.executing = (async () => {
142
198
  let next = null;
143
- let execution = null;
144
- if (this.pending.length) {
145
- const pending = this.pending.shift();
146
- execution = pending.loaded ? pending.execution : await this.acquirePendingExecution(pending.execution);
199
+ const pending = this.pending.shift() ?? null;
200
+ if (pending || this.ready && this.plugin.serving()) {
201
+ const execution = await this.prepare((pending == null ? void 0 : pending.execution) ?? null, {
202
+ immediate: pending == null ? void 0 : pending.immediate
203
+ });
147
204
  if (execution) {
148
- next = [execution, pending.job];
205
+ next = [execution, pending == null ? void 0 : pending.job, pending == null ? void 0 : pending.rerun];
206
+ }
207
+ if (pending && next) {
149
208
  this.plugin.getLogger(next[0].workflowId).info(`pending execution (${next[0].id}) ready to process`);
150
209
  }
151
210
  } else {
152
- if (this.plugin.serving()) {
153
- execution = await this.acquireQueueingExecution();
154
- if (execution) {
155
- next = [execution];
156
- }
157
- } else {
158
- this.plugin.getLogger("dispatcher").warn(`${import_Plugin.WORKER_JOB_WORKFLOW_PROCESS} is not serving on this instance, new dispatching will be ignored`);
159
- }
211
+ this.plugin.getLogger("dispatcher").warn(
212
+ `${import_Plugin.WORKER_JOB_WORKFLOW_PROCESS} is not serving on this instance or app not ready, new dispatching will be ignored`
213
+ );
160
214
  }
161
215
  if (next) {
162
- await this.process(...next);
216
+ try {
217
+ await this.process(next[0], next[1], { rerun: next[2] });
218
+ } catch (error) {
219
+ this.plugin.getLogger(next[0].workflowId).error(`execution (${next[0].id}) process failed`, { error });
220
+ if (pending && (0, import_utils.isLockAcquireError)(error)) {
221
+ this.pending.unshift({ ...pending, execution: next[0], immediate: true });
222
+ }
223
+ }
163
224
  }
164
225
  setImmediate(() => {
165
226
  this.executing = null;
@@ -171,7 +232,10 @@ class Dispatcher {
171
232
  })();
172
233
  }
173
234
  async run(pending) {
174
- this.pending.push(pending);
235
+ this.pending.push({
236
+ ...pending,
237
+ immediate: !this.executing && !this.pending.length && !this.saving && !this.events.length
238
+ });
175
239
  this.dispatch();
176
240
  }
177
241
  async triggerSync(workflow, context, { deferred, ...options } = {}) {
@@ -179,13 +243,21 @@ class Dispatcher {
179
243
  try {
180
244
  execution = await this.createExecution(workflow, context, options);
181
245
  } catch (err) {
182
- this.plugin.getLogger(workflow.id).error(`creating execution failed: ${err.message}`, err);
246
+ if (err instanceof Error) {
247
+ this.plugin.getLogger(workflow.id).error(`creating execution failed: ${err.message}`, err);
248
+ }
183
249
  return null;
184
250
  }
185
251
  try {
186
- return this.process(execution, null, options);
252
+ const entered = await this.prepare(execution);
253
+ if (!entered) {
254
+ return null;
255
+ }
256
+ return this.process(entered, void 0, options);
187
257
  } catch (err) {
188
- this.plugin.getLogger(execution.workflowId).error(`execution (${execution.id}) error: ${err.message}`, err);
258
+ if (err instanceof Error) {
259
+ this.plugin.getLogger(execution.workflowId).error(`execution (${execution.id}) error: ${err.message}`, err);
260
+ }
189
261
  }
190
262
  return null;
191
263
  }
@@ -195,19 +267,18 @@ class Dispatcher {
195
267
  if (!triggerValid) {
196
268
  return false;
197
269
  }
198
- const { stack } = options;
270
+ const { stack = [] } = options;
199
271
  let valid = true;
200
- if ((stack == null ? void 0 : stack.length) > 0) {
272
+ if (stack == null ? void 0 : stack.length) {
201
273
  const existed = await workflow.countExecutions({
202
274
  where: {
203
275
  id: stack
204
- },
205
- transaction: options.transaction
276
+ }
206
277
  });
207
278
  const limitCount = workflow.options.stackLimit || 1;
208
279
  if (existed >= limitCount) {
209
280
  this.plugin.getLogger(workflow.id).warn(
210
- `workflow ${workflow.id} has already been triggered in stacks executions (${stack}), and max call coont is ${limitCount}, newly triggering will be skipped.`
281
+ `workflow ${workflow.id} has already been triggered in stacks executions (${stack}), and max call count is ${limitCount}, newly triggering will be skipped.`
211
282
  );
212
283
  valid = false;
213
284
  }
@@ -217,187 +288,235 @@ class Dispatcher {
217
288
  async createExecution(workflow, context, options) {
218
289
  var _a;
219
290
  const { deferred } = options;
220
- const transaction = await this.plugin.useDataSourceTransaction("main", options.transaction, true);
221
- const sameTransaction = options.transaction === transaction;
222
- const valid = await this.validateEvent(workflow, context, { ...options, transaction });
291
+ let stack = options.stack;
292
+ if (options.parentExecutionId && !stack) {
293
+ const parentExecution = await this.plugin.db.getRepository("executions").findOne({
294
+ filterByTk: options.parentExecutionId
295
+ });
296
+ stack = parentExecution ? [...parentExecution.stack ?? [], parentExecution.id] : [];
297
+ }
298
+ const valid = await this.validateEvent(workflow, context, { ...options, stack });
223
299
  if (!valid) {
224
- if (!sameTransaction) {
225
- await transaction.commit();
226
- }
227
300
  (_a = options.onTriggerFail) == null ? void 0 : _a.call(options, workflow, context, options);
228
- return Promise.reject(new Error("event is not valid"));
229
- }
230
- let execution;
231
- try {
232
- execution = await workflow.createExecution(
233
- {
234
- context,
235
- key: workflow.key,
236
- eventKey: options.eventKey ?? (0, import_crypto.randomUUID)(),
237
- stack: options.stack,
238
- dispatched: deferred ?? false,
239
- status: deferred ? import_constants.EXECUTION_STATUS.STARTED : import_constants.EXECUTION_STATUS.QUEUEING,
240
- manually: options.manually
241
- },
242
- { transaction }
243
- );
244
- } catch (err) {
245
- if (!sameTransaction) {
246
- await transaction.rollback();
247
- }
248
- throw err;
249
- }
301
+ throw new Error("event is not valid");
302
+ }
303
+ const execution = await workflow.createExecution({
304
+ context,
305
+ key: workflow.key,
306
+ eventKey: options.eventKey ?? (0, import_node_crypto.randomUUID)(),
307
+ stack,
308
+ parentExecutionId: options.parentExecutionId ?? null,
309
+ dispatched: deferred ?? false,
310
+ status: deferred ? import_constants.EXECUTION_STATUS.STARTED : import_constants.EXECUTION_STATUS.QUEUEING,
311
+ manually: options.manually
312
+ });
250
313
  this.plugin.getLogger(workflow.id).info(`execution of workflow ${workflow.id} created as ${execution.id}`);
251
314
  if (!workflow.stats) {
252
- workflow.stats = await workflow.getStats({ transaction });
315
+ workflow.stats = await workflow.getStats();
253
316
  }
254
- await workflow.stats.increment("executed", { transaction });
317
+ await workflow.stats.increment("executed");
255
318
  if (this.plugin.db.options.dialect !== "postgres") {
256
- await workflow.stats.reload({ transaction });
319
+ await workflow.stats.reload();
257
320
  }
258
321
  if (!workflow.versionStats) {
259
- workflow.versionStats = await workflow.getVersionStats({ transaction });
322
+ workflow.versionStats = await workflow.getVersionStats();
260
323
  }
261
- await workflow.versionStats.increment("executed", { transaction });
324
+ await workflow.versionStats.increment("executed");
262
325
  if (this.plugin.db.options.dialect !== "postgres") {
263
- await workflow.versionStats.reload({ transaction });
264
- }
265
- if (!sameTransaction) {
266
- await transaction.commit();
326
+ await workflow.versionStats.reload();
267
327
  }
268
328
  execution.workflow = workflow;
269
329
  return execution;
270
330
  }
271
- prepare = async () => {
272
- if (this.executing && this.plugin.db.options.dialect === "sqlite") {
273
- await this.executing;
274
- }
275
- const event = this.events.shift();
276
- this.eventsCount = this.events.length;
277
- if (!event) {
278
- this.plugin.getLogger("dispatcher").info(`events queue is empty, no need to prepare`);
279
- return;
331
+ async prepare(input, options = {}) {
332
+ const logger = input ? this.plugin.getLogger(input.workflowId) : this.plugin.getLogger("dispatcher");
333
+ if (options.transaction) {
334
+ try {
335
+ const { execution } = await this.acquireExecution(input, options, options.transaction);
336
+ return execution;
337
+ } catch (error) {
338
+ if (error instanceof Error) {
339
+ logger.error(`entering execution failed: ${error.message}`, { error });
340
+ }
341
+ return null;
342
+ }
280
343
  }
281
- const logger = this.plugin.getLogger(event[0].id);
282
- logger.info(`preparing execution for event`);
344
+ let result = null;
283
345
  try {
284
- const execution = await this.createExecution(...event);
285
- if (!(execution == null ? void 0 : execution.dispatched)) {
286
- if (this.plugin.serving() && !this.executing && !this.pending.length) {
287
- logger.info(`local pending list is empty, adding execution (${execution.id}) to pending list`);
288
- this.pending.push({ execution });
289
- } else {
290
- logger.info(
291
- `instance is not serving as worker or local pending list is not empty, sending execution (${execution.id}) to queue`
292
- );
346
+ await this.acquireWithRetry(
347
+ async () => {
348
+ const tx = await this.plugin.db.sequelize.transaction({
349
+ isolationLevel: this.plugin.db.options.dialect === "sqlite" ? void 0 : import_sequelize.Transaction.ISOLATION_LEVELS.REPEATABLE_READ
350
+ });
293
351
  try {
294
- await this.plugin.app.eventQueue.publish(this.plugin.channelPendingExecution, {
295
- executionId: execution.id
296
- });
297
- } catch (qErr) {
298
- logger.error(`publishing execution (${execution.id}) to queue failed:`, { error: qErr });
352
+ const { execution, shouldRetry } = await this.acquireExecution(input, options, tx);
353
+ await tx.commit();
354
+ result = execution;
355
+ return shouldRetry;
356
+ } catch (error) {
357
+ await tx.rollback();
358
+ if (this.isConcurrentAcquireError(error)) {
359
+ throw error;
360
+ }
361
+ if (error instanceof Error) {
362
+ logger.error(`entering execution failed: ${error.message}`, { error });
363
+ }
364
+ result = null;
365
+ return false;
299
366
  }
367
+ },
368
+ {
369
+ logger,
370
+ conflictMessage: input ? `acquiring pending execution (${input.id}) conflicted with another worker, retrying` : `acquiring execution conflicted with another worker, retrying`,
371
+ maxAttemptsMessage: input ? `acquiring pending execution (${input.id}) reached max retry attempts` : `acquiring execution reached max retry attempts, will retry on next dispatch`
300
372
  }
301
- }
373
+ );
302
374
  } catch (error) {
303
- logger.error(`failed to create execution:`, { error });
304
- }
305
- if (this.events.length) {
306
- await this.prepare();
307
- } else {
308
- this.plugin.getLogger("dispatcher").info("no more events need to be prepared, dispatching...");
309
- if (this.executing) {
310
- await this.executing;
375
+ if (error instanceof Error) {
376
+ logger.error(`acquiring execution failed: ${error.message}`, { error });
311
377
  }
312
- this.dispatch();
378
+ return null;
313
379
  }
314
- };
315
- async acquirePendingExecution(execution) {
316
- const logger = this.plugin.getLogger(execution.workflowId);
317
- const isolationLevel = this.plugin.db.options.dialect === "sqlite" ? [][0] : import_sequelize.Transaction.ISOLATION_LEVELS.REPEATABLE_READ;
318
- let fetched = execution;
319
- try {
320
- await this.plugin.db.sequelize.transaction({ isolationLevel }, async (transaction) => {
321
- const ExecutionModelClass = this.plugin.db.getModel("executions");
322
- const [affected] = await ExecutionModelClass.update(
323
- { dispatched: true, status: import_constants.EXECUTION_STATUS.STARTED },
324
- {
325
- where: {
326
- id: execution.id,
327
- dispatched: false
328
- },
329
- transaction
330
- }
331
- );
332
- if (!affected) {
333
- fetched = null;
334
- return;
335
- }
380
+ return result;
381
+ }
382
+ async acquireExecution(input, options, transaction) {
383
+ let execution = input;
384
+ if (execution) {
385
+ if (!options.immediate || execution.status !== import_constants.EXECUTION_STATUS.QUEUEING) {
336
386
  await execution.reload({ transaction });
387
+ }
388
+ } else {
389
+ execution = await this.plugin.db.getRepository("executions").findOne({
390
+ filter: {
391
+ dispatched: false,
392
+ "workflow.enabled": true
393
+ },
394
+ sort: "id",
395
+ transaction,
396
+ lock: transaction.LOCK.UPDATE,
397
+ skipLocked: true
337
398
  });
338
- } catch (error) {
339
- logger.error(`acquiring pending execution failed: ${error.message}`, { error });
399
+ if (execution) {
400
+ this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) fetched from db`);
401
+ } else {
402
+ this.plugin.getLogger("dispatcher").debug(`no execution in db queued to process`);
403
+ }
404
+ }
405
+ if (!execution) {
406
+ return { execution: null, shouldRetry: false };
340
407
  }
341
- return fetched;
408
+ const entered = await this.enter(execution, transaction);
409
+ const shouldRetry = !input && !entered;
410
+ return { execution: entered, shouldRetry };
342
411
  }
343
- async acquireQueueingExecution() {
344
- const isolationLevel = this.plugin.db.options.dialect === "sqlite" ? [][0] : import_sequelize.Transaction.ISOLATION_LEVELS.REPEATABLE_READ;
345
- let fetched = null;
346
- try {
347
- await this.plugin.db.sequelize.transaction(
348
- {
349
- isolationLevel
350
- },
351
- async (transaction) => {
352
- const execution = await this.plugin.db.getRepository("executions").findOne({
353
- filter: {
354
- dispatched: false,
355
- "workflow.enabled": true
356
- },
357
- sort: "id",
358
- transaction
359
- });
360
- if (execution) {
361
- this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) fetched from db`);
362
- await execution.update(
363
- {
364
- dispatched: true,
365
- status: import_constants.EXECUTION_STATUS.STARTED
366
- },
367
- { transaction }
368
- );
369
- execution.workflow = this.plugin.enabledCache.get(execution.workflowId);
370
- fetched = execution;
371
- } else {
372
- this.plugin.getLogger("dispatcher").debug(`no execution in db queued to process`);
373
- }
412
+ async acquireWithRetry(acquire, options) {
413
+ for (let attempt = 1; attempt <= EXECUTION_ACQUIRE_MAX_ATTEMPTS; attempt++) {
414
+ let shouldRetry = false;
415
+ try {
416
+ shouldRetry = await acquire();
417
+ } catch (error) {
418
+ if (!this.isConcurrentAcquireError(error)) {
419
+ throw error;
374
420
  }
375
- );
376
- } catch (error) {
377
- this.plugin.getLogger("dispatcher").error(`fetching execution from db failed: ${error.message}`, { error });
421
+ shouldRetry = true;
422
+ options.logger.warn(options.conflictMessage, { error });
423
+ }
424
+ if (!shouldRetry) {
425
+ break;
426
+ }
427
+ if (attempt >= EXECUTION_ACQUIRE_MAX_ATTEMPTS) {
428
+ options.logger.warn(options.maxAttemptsMessage);
429
+ break;
430
+ }
378
431
  }
379
- return fetched;
380
432
  }
381
- async process(execution, job, options = {}) {
382
- var _a, _b;
383
- const logger = this.plugin.getLogger(execution.workflowId);
433
+ async enter(execution, transaction) {
434
+ const workflow = execution.workflow || this.plugin.enabledCache.get(execution.workflowId) || await execution.getWorkflow({ transaction });
435
+ if (!workflow) {
436
+ this.plugin.getLogger(execution.workflowId).warn(`workflow (${execution.workflowId}) not found for execution`, {
437
+ workflowId: execution.workflowId,
438
+ executionId: execution.id
439
+ });
440
+ }
441
+ if (execution.status && execution.status !== import_constants.EXECUTION_STATUS.STARTED) {
442
+ return null;
443
+ }
444
+ if (execution.dispatched && execution.status === import_constants.EXECUTION_STATUS.STARTED && execution.startedAt) {
445
+ execution.workflow = workflow;
446
+ return execution;
447
+ }
448
+ const values = {
449
+ dispatched: true,
450
+ status: import_constants.EXECUTION_STATUS.STARTED
451
+ };
452
+ const where = {
453
+ id: execution.id,
454
+ status: execution.status ?? null
455
+ };
384
456
  if (!execution.dispatched) {
385
- const transaction = await this.plugin.useDataSourceTransaction("main", options.transaction);
386
- await execution.update({ dispatched: true, status: import_constants.EXECUTION_STATUS.STARTED }, { transaction });
387
- logger.info(`execution (${execution.id}) from pending list updated to started`);
457
+ where.dispatched = false;
458
+ }
459
+ if (!execution.startedAt) {
460
+ const startedAt = /* @__PURE__ */ new Date();
461
+ values.startedAt = startedAt;
462
+ execution.workflow = workflow;
463
+ values.expiresAt = this.plugin.timeoutManager.getExpiresAt(execution, startedAt);
464
+ where.startedAt = null;
465
+ }
466
+ const ExecutionModelClass = this.plugin.db.getModel("executions");
467
+ const [affected] = await ExecutionModelClass.update(values, {
468
+ where,
469
+ transaction
470
+ });
471
+ if (!affected) {
472
+ return null;
388
473
  }
389
- const processor = this.plugin.createProcessor(execution, options);
390
- logger.info(`execution (${execution.id}) ${job ? "resuming" : "starting"}...`);
391
- try {
392
- await (job ? processor.resume(job) : processor.start());
393
- logger.info(`execution (${execution.id}) finished with status: ${execution.status}`);
394
- logger.debug(`execution (${execution.id}) details:`, { execution });
395
- if (execution.status && ((_b = (_a = execution.workflow.options) == null ? void 0 : _a.deleteExecutionOnStatus) == null ? void 0 : _b.includes(execution.status))) {
396
- await execution.destroy({ transaction: processor.mainTransaction });
474
+ await execution.reload({ transaction });
475
+ execution.workflow = workflow;
476
+ return execution;
477
+ }
478
+ isConcurrentAcquireError(error) {
479
+ var _a, _b, _c, _d;
480
+ if (!(error instanceof Error)) {
481
+ return false;
482
+ }
483
+ const databaseError = error;
484
+ const code = ((_a = databaseError.parent) == null ? void 0 : _a.code) || ((_b = databaseError.original) == null ? void 0 : _b.code);
485
+ const errno = ((_c = databaseError.parent) == null ? void 0 : _c.errno) || ((_d = databaseError.original) == null ? void 0 : _d.errno);
486
+ return code === "40001" || code === "40P01" || code === "ER_LOCK_DEADLOCK" || errno === 1205 || errno === 1213;
487
+ }
488
+ async process(execution, job = null, options = {}) {
489
+ const { rerun, ...processorOptions } = options;
490
+ const logger = this.plugin.getLogger(execution.workflowId);
491
+ const run = async () => {
492
+ var _a, _b, _c;
493
+ if (!execution.dispatched) {
494
+ await execution.update({ dispatched: true, status: import_constants.EXECUTION_STATUS.STARTED });
495
+ logger.info(`execution (${execution.id}) from pending list updated to started`);
397
496
  }
398
- } catch (err) {
399
- logger.error(`execution (${execution.id}) error: ${err.message}`, err);
497
+ this.plugin.timeoutManager.scheduleExecutionTimeout(execution);
498
+ const processor = this.plugin.createProcessor(execution, processorOptions);
499
+ logger.info(`execution (${execution.id}) ${rerun ? "rerunning" : job ? "resuming" : "starting"}...`);
500
+ try {
501
+ await (rerun ? processor.rerun(rerun) : job ? processor.resume(job) : processor.start());
502
+ logger.info(`execution (${execution.id}) finished with status: ${execution.status}`);
503
+ logger.debug(`execution (${execution.id}) details:`, { execution });
504
+ if (execution.status && ((_c = (_b = (_a = execution.workflow) == null ? void 0 : _a.options) == null ? void 0 : _b.deleteExecutionOnStatus) == null ? void 0 : _c.includes(execution.status))) {
505
+ await execution.destroy();
506
+ }
507
+ } catch (err) {
508
+ if (err instanceof Error) {
509
+ logger.error(`execution (${execution.id}) error: ${err.message}`, err);
510
+ }
511
+ }
512
+ return processor;
513
+ };
514
+ const lock = await this.plugin.app.lockManager.tryAcquire((0, import_utils.getExecutionLockKey)(execution.id), 6e4);
515
+ try {
516
+ return await lock.runExclusive(run, 6e4);
517
+ } catch (error) {
518
+ logger.error(`execution (${execution.id}) could not acquire process lock`, { error });
519
+ throw error;
400
520
  }
401
- return processor;
402
521
  }
403
522
  }