@nocobase/plugin-workflow 2.2.0-alpha.1 → 2.2.0-alpha.10

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 (264) hide show
  1. package/client-v2.d.ts +2 -0
  2. package/client-v2.js +1 -0
  3. package/dist/client/119.e7dbd037d52d1286.js +10 -0
  4. package/dist/client/142.fb4936d78fc80007.js +10 -0
  5. package/dist/client/159.cc9a0f2d92cab2c1.js +10 -0
  6. package/dist/client/276.69fb2aea13bc7c37.js +10 -0
  7. package/dist/client/346.66d060d37843d3d1.js +10 -0
  8. package/dist/client/382.d37fba14a6b91b0b.js +10 -0
  9. package/dist/client/394.a37310b78419ad7a.js +10 -0
  10. package/dist/client/438.6c6409d821d8c353.js +10 -0
  11. package/dist/client/509.88792f5bd054b2af.js +10 -0
  12. package/dist/client/54.d81b710f7b4c1cd5.js +10 -0
  13. package/dist/client/618.1aa6c21d0f2840cb.js +10 -0
  14. package/dist/client/635.ff6085589e23d39b.js +10 -0
  15. package/dist/client/67.56a3d15a55ab7d5c.js +10 -0
  16. package/dist/client/719.f011fde3ad54571e.js +10 -0
  17. package/dist/client/728.711fd4f1ed7d4e6a.js +10 -0
  18. package/dist/client/739.49407722fca8d236.js +10 -0
  19. package/dist/client/759.13aab01e6912ed8d.js +10 -0
  20. package/dist/client/772.1ebaef11a0ac8688.js +10 -0
  21. package/dist/client/782.47792ce10fc0d5fb.js +10 -0
  22. package/dist/client/828.6880ab21513b1968.js +10 -0
  23. package/dist/client/87.083467b89aaf3e53.js +10 -0
  24. package/dist/client/938.26493fc09ba1ccbc.js +10 -0
  25. package/dist/client/964.5ab736effd480a42.js +10 -0
  26. package/dist/client/972.dd7f074d5f12e171.js +10 -0
  27. package/dist/client/AddNodeContext.d.ts +1 -9
  28. package/dist/client/Branch.d.ts +1 -1
  29. package/dist/client/BranchContext.d.ts +2 -10
  30. package/dist/client/FlowContext.d.ts +10 -5
  31. package/dist/client/NodeClipboardContext.d.ts +12 -2
  32. package/dist/client/NodeDragContext.d.ts +14 -2
  33. package/dist/client/RemoveNodeContext.d.ts +16 -2
  34. package/dist/client/index.d.ts +1 -1
  35. package/dist/client/index.js +1 -1
  36. package/dist/client/nodeVariableUtils.d.ts +7 -6
  37. package/dist/client/nodes/calculation.d.ts +3 -75
  38. package/dist/client/nodes/condition.d.ts +7 -111
  39. package/dist/client/nodes/create.d.ts +3 -124
  40. package/dist/client/nodes/destroy.d.ts +2 -57
  41. package/dist/client/nodes/end.d.ts +2 -23
  42. package/dist/client/nodes/index.d.ts +2 -91
  43. package/dist/client/nodes/multi-conditions.d.ts +10 -48
  44. package/dist/client/nodes/output.d.ts +2 -44
  45. package/dist/client/nodes/query.d.ts +3 -255
  46. package/dist/client/nodes/update.d.ts +2 -117
  47. package/dist/client/style.d.ts +7 -26
  48. package/dist/client/triggers/collection.d.ts +3 -154
  49. package/dist/client/triggers/index.d.ts +2 -36
  50. package/dist/client/triggers/schedule/index.d.ts +8 -65
  51. package/dist/client/utils.d.ts +4 -5
  52. package/dist/client/variable.d.ts +7 -53
  53. package/dist/client-v2/119.81195bcd0d451279.js +10 -0
  54. package/dist/client-v2/142.a8c8df2a9abb43c5.js +10 -0
  55. package/dist/client-v2/159.cb221540c286cd32.js +10 -0
  56. package/dist/client-v2/276.7f746d899e221e2f.js +10 -0
  57. package/dist/client-v2/326.bdc99ad8e7590970.js +10 -0
  58. package/dist/client-v2/346.8c038b3c99d4fdc9.js +10 -0
  59. package/dist/client-v2/382.05f5482bb512b179.js +10 -0
  60. package/dist/client-v2/387.5e61d648d16dbf1b.js +10 -0
  61. package/dist/client-v2/394.515ed058e7f72797.js +10 -0
  62. package/dist/client-v2/438.b7df0416b66f8f64.js +10 -0
  63. package/dist/client-v2/513.617697f74783a611.js +10 -0
  64. package/dist/client-v2/54.d3daf06e53092726.js +10 -0
  65. package/dist/client-v2/635.14a6f21cf4f16345.js +10 -0
  66. package/dist/client-v2/677.7382807126d9621a.js +10 -0
  67. package/dist/client-v2/719.05cf070ef89a78f4.js +10 -0
  68. package/dist/client-v2/728.4e8322ff8fce87b9.js +10 -0
  69. package/dist/client-v2/739.5b60845103064f8d.js +10 -0
  70. package/dist/client-v2/759.7b162ad3adf450f1.js +10 -0
  71. package/dist/client-v2/772.8e5148d8c1518449.js +10 -0
  72. package/dist/client-v2/938.717e47af39485b29.js +10 -0
  73. package/dist/client-v2/ExecutionCanvas.d.ts +15 -0
  74. package/dist/client-v2/canvas/AddNodeContext.d.ts +35 -0
  75. package/dist/client-v2/canvas/AddNodeContext.shared.d.ts +33 -0
  76. package/dist/client-v2/canvas/AddNodeSlot.d.ts +20 -0
  77. package/dist/client-v2/canvas/Branch.d.ts +32 -0
  78. package/dist/client-v2/canvas/BranchContext.d.ts +29 -0
  79. package/dist/client-v2/canvas/BranchRenderContext.d.ts +14 -0
  80. package/dist/client-v2/canvas/CanvasContent.d.ts +20 -0
  81. package/dist/client-v2/canvas/DownstreamBranchIndex.d.ts +20 -0
  82. package/dist/client-v2/canvas/Instruction.d.ts +139 -0
  83. package/dist/{client/triggers/schedule/EndsByField.d.ts → client-v2/canvas/JobButton.d.ts} +3 -3
  84. package/dist/client-v2/canvas/Node.d.ts +38 -0
  85. package/dist/client-v2/canvas/NodeClipboardContext.d.ts +64 -0
  86. package/dist/client-v2/canvas/NodeConfigDrawer.d.ts +27 -0
  87. package/dist/client-v2/canvas/NodeDragContext.d.ts +83 -0
  88. package/dist/client-v2/canvas/RemoveNodeContext.d.ts +45 -0
  89. package/dist/client-v2/canvas/WorkflowTypedVariableInput.d.ts +16 -0
  90. package/dist/client-v2/canvas/WorkflowVariableInput.d.ts +47 -0
  91. package/dist/client-v2/canvas/WorkflowVariableJsonTextArea.d.ts +15 -0
  92. package/dist/client-v2/canvas/WorkflowVariableTag.d.ts +22 -0
  93. package/dist/client-v2/canvas/WorkflowVariableTextArea.d.ts +23 -0
  94. package/dist/client-v2/canvas/adaptVariableOptionToMetaTree.d.ts +48 -0
  95. package/dist/client-v2/canvas/addNodeController.d.ts +70 -0
  96. package/dist/client-v2/canvas/collectionFieldOptions.d.ts +57 -0
  97. package/dist/client-v2/canvas/contexts.d.ts +86 -0
  98. package/dist/client-v2/canvas/dropImpact.d.ts +28 -0
  99. package/dist/client-v2/canvas/instructionAvailability.d.ts +26 -0
  100. package/dist/client-v2/canvas/nodeRenderDispatch.d.ts +79 -0
  101. package/dist/client-v2/canvas/nodeTree.d.ts +21 -0
  102. package/dist/client-v2/canvas/nodeVariableUtils.d.ts +14 -0
  103. package/dist/client-v2/canvas/removeNodeUtils.d.ts +31 -0
  104. package/dist/client-v2/canvas/style.d.ts +35 -0
  105. package/dist/client-v2/canvas/textAreaInsertion.d.ts +14 -0
  106. package/dist/client-v2/canvas/useWorkflowInstruction.d.ts +23 -0
  107. package/dist/client-v2/canvas/useWorkflowVariableOptions.d.ts +40 -0
  108. package/dist/client-v2/canvas/workflowVariableConverters.d.ts +13 -0
  109. package/dist/client-v2/components/Calculation.d.ts +18 -0
  110. package/dist/client-v2/components/ExecutionStatusIcon.d.ts +18 -0
  111. package/dist/{client/triggers/schedule/RepeatField.d.ts → client-v2/components/ExecutionStatusTag.d.ts} +3 -4
  112. package/dist/client-v2/components/ExecutionViewHeader.d.ts +29 -0
  113. package/dist/client-v2/components/ExecutionsDropdown.d.ts +25 -0
  114. package/dist/client-v2/components/FilterDynamicComponent.d.ts +28 -0
  115. package/dist/client-v2/components/HideVariableContext.d.ts +11 -0
  116. package/dist/client-v2/components/JobResultModal.d.ts +11 -0
  117. package/dist/client-v2/components/RadioWithTooltip.d.ts +23 -0
  118. package/dist/client-v2/components/SyncModeSelect.d.ts +16 -0
  119. package/dist/{client/triggers/schedule/OnField.d.ts → client-v2/components/SyncModeTag.d.ts} +3 -3
  120. package/dist/client-v2/components/TestRunButton.d.ts +36 -0
  121. package/dist/client-v2/components/TimeoutInput.d.ts +15 -0
  122. package/dist/client-v2/components/WorkflowCanvasHeader.d.ts +17 -0
  123. package/dist/client-v2/components/WorkflowDetailsModal.d.ts +25 -0
  124. package/dist/client-v2/components/WorkflowEnabledSwitch.d.ts +16 -0
  125. package/dist/client-v2/components/WorkflowMenu.d.ts +17 -0
  126. package/dist/client-v2/components/WorkflowRevisionsDropdown.d.ts +15 -0
  127. package/dist/client-v2/components/WorkflowVariableWrapper.d.ts +25 -0
  128. package/dist/client-v2/components/collection/AppendsSelect.d.ts +15 -0
  129. package/dist/client-v2/components/collection/AssignedFieldsEditor.d.ts +24 -0
  130. package/dist/client-v2/components/collection/CollectionCascader.d.ts +15 -0
  131. package/dist/client-v2/components/collection/FieldsSelect.d.ts +16 -0
  132. package/dist/client-v2/components/collection/PaginationFields.d.ts +14 -0
  133. package/dist/client-v2/components/collection/SortFieldsInput.d.ts +19 -0
  134. package/dist/client-v2/components/collection/TriggerCollectionRecordSelect.d.ts +15 -0
  135. package/dist/client-v2/components/collection/index.d.ts +16 -0
  136. package/dist/client-v2/components/collection/utils.d.ts +42 -0
  137. package/dist/client-v2/components/formatResultForDisplay.d.ts +17 -0
  138. package/dist/client-v2/components/jobStatus.d.ts +27 -0
  139. package/dist/client-v2/components/renderEngineReference.d.ts +10 -0
  140. package/dist/client-v2/components/workflowCanvas.d.ts +41 -0
  141. package/dist/client-v2/constants.d.ts +19 -0
  142. package/dist/client-v2/hooks/useWorkflowRuntimePaths.d.ts +16 -0
  143. package/dist/client-v2/index.d.ts +48 -0
  144. package/dist/client-v2/index.js +10 -0
  145. package/dist/client-v2/locale.d.ts +41 -0
  146. package/dist/{client → client-v2}/models/NodeDetailsGridModel.d.ts +1 -1
  147. package/dist/{client → client-v2}/models/NodeDetailsModel.d.ts +1 -1
  148. package/dist/{client → client-v2}/models/NodeValueModel.d.ts +1 -1
  149. package/dist/{client → client-v2}/models/TaskCardCommonItemModel.d.ts +1 -1
  150. package/dist/client-v2/models/WorkflowTasksTopbarActionModel.d.ts +18 -0
  151. package/dist/{client → client-v2}/models/index.d.ts +2 -0
  152. package/dist/client-v2/models/triggerWorkflows.d.ts +43 -0
  153. package/dist/client-v2/nodes/calculation.d.ts +38 -0
  154. package/dist/client-v2/nodes/collectionNode.d.ts +29 -0
  155. package/dist/client-v2/nodes/components/calculation.d.ts +11 -0
  156. package/dist/client-v2/nodes/components/collection.d.ts +15 -0
  157. package/dist/client-v2/nodes/components/condition.d.ts +25 -0
  158. package/dist/client-v2/nodes/components/conditionShared.d.ts +20 -0
  159. package/dist/client-v2/nodes/components/create.d.ts +12 -0
  160. package/dist/client-v2/nodes/components/destroy.d.ts +12 -0
  161. package/dist/{client/triggers/schedule/ScheduleConfig.d.ts → client-v2/nodes/components/end.d.ts} +1 -1
  162. package/dist/client-v2/nodes/components/filter.d.ts +17 -0
  163. package/dist/client-v2/nodes/components/multi-conditions.d.ts +13 -0
  164. package/dist/client-v2/nodes/components/multiConditionsShared.d.ts +20 -0
  165. package/dist/client-v2/nodes/components/output.d.ts +10 -0
  166. package/dist/client-v2/nodes/components/query.d.ts +12 -0
  167. package/dist/client-v2/nodes/components/update.d.ts +12 -0
  168. package/dist/client-v2/nodes/condition.d.ts +46 -0
  169. package/dist/client-v2/nodes/create.d.ts +30 -0
  170. package/dist/client-v2/nodes/destroy.d.ts +24 -0
  171. package/dist/client-v2/nodes/end.d.ts +21 -0
  172. package/dist/client-v2/nodes/multi-conditions.d.ts +34 -0
  173. package/dist/client-v2/nodes/output.d.ts +29 -0
  174. package/dist/client-v2/nodes/query.d.ts +32 -0
  175. package/dist/client-v2/nodes/update.d.ts +25 -0
  176. package/dist/client-v2/pages/ExecutionHistoryDrawer.d.ts +13 -0
  177. package/dist/client-v2/pages/ExecutionViewPage.d.ts +10 -0
  178. package/dist/client-v2/pages/WorkflowCanvasPage.d.ts +10 -0
  179. package/dist/client-v2/pages/WorkflowCategoryTabs.d.ts +25 -0
  180. package/dist/client-v2/pages/WorkflowFormDrawer.d.ts +40 -0
  181. package/dist/client-v2/pages/WorkflowPane.d.ts +10 -0
  182. package/dist/client-v2/pages/WorkflowTasksPage.d.ts +10 -0
  183. package/dist/client-v2/plugin.d.ts +119 -0
  184. package/dist/client-v2/taskCenter.d.ts +105 -0
  185. package/dist/client-v2/triggers/ExecuteWorkflowButton.d.ts +35 -0
  186. package/dist/client-v2/triggers/TriggerConfig.d.ts +22 -0
  187. package/dist/client-v2/triggers/TriggerExecutionButton.d.ts +13 -0
  188. package/dist/client-v2/triggers/collection/CollectionConfig.d.ts +14 -0
  189. package/dist/client-v2/triggers/collection/TriggerCollectionConfig.d.ts +11 -0
  190. package/dist/client-v2/triggers/collection/constants.d.ts +28 -0
  191. package/dist/client-v2/triggers/collection/index.d.ts +40 -0
  192. package/dist/client-v2/triggers/index.d.ts +63 -0
  193. package/dist/client-v2/triggers/schedule/EndsByField.d.ts +17 -0
  194. package/dist/client-v2/triggers/schedule/OnField.d.ts +20 -0
  195. package/dist/client-v2/triggers/schedule/RepeatField.d.ts +23 -0
  196. package/dist/client-v2/triggers/schedule/ScheduleConfig.d.ts +17 -0
  197. package/dist/client-v2/triggers/schedule/ScheduleModes.d.ts +41 -0
  198. package/dist/{client → client-v2}/triggers/schedule/TriggerScheduleConfig.d.ts +1 -0
  199. package/dist/{client → client-v2}/triggers/schedule/constants.d.ts +8 -5
  200. package/dist/client-v2/triggers/schedule/index.d.ts +40 -0
  201. package/dist/client-v2/triggers/schedule/locale/Cron.zh-CN.d.ts +11 -0
  202. package/dist/client-v2/utils/contextSingleton.d.ts +9 -0
  203. package/dist/common/collections/workflows.d.ts +16 -0
  204. package/dist/common/collections/workflows.js +5 -0
  205. package/dist/common/defaultWorkflowFilter.d.ts +21 -0
  206. package/dist/common/defaultWorkflowFilter.js +38 -0
  207. package/dist/common/executionStatus.d.ts +44 -0
  208. package/dist/common/executionStatus.js +133 -0
  209. package/dist/externalVersion.js +16 -12
  210. package/dist/locale/de-DE.json +16 -1
  211. package/dist/locale/en-US.json +31 -2
  212. package/dist/locale/es-ES.json +16 -1
  213. package/dist/locale/fr-FR.json +16 -1
  214. package/dist/locale/hu-HU.json +16 -1
  215. package/dist/locale/id-ID.json +16 -1
  216. package/dist/locale/it-IT.json +16 -1
  217. package/dist/locale/ja-JP.json +16 -1
  218. package/dist/locale/ko-KR.json +16 -1
  219. package/dist/locale/nl-NL.json +16 -1
  220. package/dist/locale/pt-BR.json +16 -1
  221. package/dist/locale/ru-RU.json +16 -1
  222. package/dist/locale/tr-TR.json +16 -1
  223. package/dist/locale/uk-UA.json +16 -1
  224. package/dist/locale/vi-VN.json +16 -1
  225. package/dist/locale/zh-CN.json +30 -2
  226. package/dist/locale/zh-TW.json +22 -1
  227. package/dist/node_modules/cron-parser/package.json +1 -1
  228. package/dist/node_modules/joi/package.json +1 -1
  229. package/dist/node_modules/lru-cache/package.json +1 -1
  230. package/dist/node_modules/nodejs-snowflake/package.json +1 -1
  231. package/dist/server/Dispatcher.d.ts +27 -14
  232. package/dist/server/Dispatcher.js +208 -154
  233. package/dist/server/ExecutionTimeoutManager.js +4 -3
  234. package/dist/server/Plugin.d.ts +17 -8
  235. package/dist/server/Plugin.js +58 -13
  236. package/dist/server/Processor.d.ts +17 -7
  237. package/dist/server/Processor.js +112 -0
  238. package/dist/server/actions/executions.js +3 -6
  239. package/dist/server/actions/jobs.js +1 -2
  240. package/dist/server/actions/nodes.js +36 -1
  241. package/dist/server/actions/workflows.d.ts +1 -0
  242. package/dist/server/actions/workflows.js +17 -2
  243. package/dist/server/index.d.ts +1 -1
  244. package/dist/server/instructions/CreateInstruction.js +6 -2
  245. package/dist/server/instructions/DestroyInstruction.js +2 -1
  246. package/dist/server/instructions/QueryInstruction.js +2 -1
  247. package/dist/server/instructions/UpdateInstruction.js +2 -1
  248. package/dist/server/triggers/CollectionTrigger.d.ts +6 -1
  249. package/dist/server/triggers/CollectionTrigger.js +29 -1
  250. package/dist/server/triggers/ScheduleTrigger/StaticScheduleTrigger.d.ts +1 -1
  251. package/dist/server/triggers/index.d.ts +1 -1
  252. package/dist/server/types/Execution.d.ts +2 -0
  253. package/dist/server/types/Job.d.ts +5 -0
  254. package/dist/server/types/Workflow.d.ts +1 -0
  255. package/dist/server/utils.d.ts +2 -1
  256. package/dist/server/utils.js +46 -28
  257. package/dist/swagger/index.d.ts +1 -0
  258. package/dist/swagger/index.js +4 -2
  259. package/package.json +4 -2
  260. package/dist/client/618.19af7f84261c815d.js +0 -10
  261. package/dist/client/67.452743ce8ec30617.js +0 -10
  262. package/dist/client/964.ffbf5b47ed12bbdc.js +0 -10
  263. package/dist/client/triggers/schedule/ScheduleModes.d.ts +0 -170
  264. package/dist/client/triggers/schedule/locale/Cron.zh-CN.d.ts +0 -42
@@ -35,29 +35,52 @@ var import_constants = require("./constants");
35
35
  var import_Plugin = require("./Plugin");
36
36
  var import_utils = require("./utils");
37
37
  const EXECUTION_ACQUIRE_MAX_ATTEMPTS = 5;
38
+ const QUEUE_TASK_MAX_ATTEMPTS = 3;
39
+ const RECOVERY_BATCH_SIZE = 100;
40
+ const RECOVERY_LOCK_TTL = 3e5;
38
41
  class Dispatcher {
39
42
  constructor(plugin) {
40
43
  this.plugin = plugin;
41
44
  }
42
45
  ready = false;
43
46
  executing = null;
47
+ recovering = null;
44
48
  saving = null;
45
- pending = [];
46
49
  events = [];
47
50
  eventsCount = 0;
48
51
  get idle() {
49
- return this.ready && !this.executing && !this.saving && !this.pending.length && !this.events.length;
52
+ return this.ready && !this.executing && !this.saving && !this.events.length;
50
53
  }
51
- onQueueExecution = async (event) => {
52
- const ExecutionRepo = this.plugin.db.getRepository("executions");
53
- const execution = await ExecutionRepo.findOne({
54
- filterByTk: event.executionId
55
- });
56
- if (!execution || execution.dispatched) {
57
- return;
54
+ onQueueTask = async (event) => {
55
+ var _a;
56
+ const task = event;
57
+ let next = null;
58
+ const previous = this.executing;
59
+ const executing = (async () => {
60
+ await (previous == null ? void 0 : previous.catch(() => void 0));
61
+ this.plugin.getLogger("dispatcher").info(`workflow queue task for execution (${task.executionId}) received from queue`);
62
+ next = await this.resolveTask(task);
63
+ if (!next) {
64
+ return;
65
+ }
66
+ this.plugin.getLogger(next[0].workflowId).info(`queued execution (${next[0].id}) ready to process`);
67
+ await this.process(next[0], next[1], { rerun: next[2] });
68
+ })();
69
+ this.executing = executing;
70
+ try {
71
+ await executing;
72
+ } catch (error) {
73
+ const workflowId = ((_a = next == null ? void 0 : next[0]) == null ? void 0 : _a.workflowId) ?? task.executionId;
74
+ this.plugin.getLogger(workflowId).error(`workflow queue task failed`, { error });
75
+ throw error;
76
+ } finally {
77
+ if (this.executing === executing) {
78
+ this.executing = null;
79
+ }
80
+ if (this.events.length) {
81
+ this.saveEvent();
82
+ }
58
83
  }
59
- this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) received from queue, adding to pending list`);
60
- await this.run({ execution });
61
84
  };
62
85
  setReady(ready) {
63
86
  this.ready = ready;
@@ -70,7 +93,7 @@ class Dispatcher {
70
93
  if (!this.ready) {
71
94
  logger.warn(`app is not ready, event of workflow ${workflow.id} will be ignored`);
72
95
  logger.debug(`ignored event data:`, context);
73
- return;
96
+ return this.handleTriggerFail(workflow, context, options, new Error("app is not ready"));
74
97
  }
75
98
  if (!options.force && !options.manually && !workflow.enabled) {
76
99
  logger.warn(`workflow ${workflow.id} is not enabled, event will be ignored`);
@@ -87,7 +110,7 @@ class Dispatcher {
87
110
  }
88
111
  if (context == null) {
89
112
  logger.warn(`workflow ${workflow.id} event data context is null, event will be ignored`);
90
- return;
113
+ return this.handleTriggerFail(workflow, context, options, new Error("event context is null"));
91
114
  }
92
115
  if (options.manually || this.plugin.isWorkflowSync(workflow)) {
93
116
  return this.triggerSync(workflow, context, options);
@@ -117,20 +140,12 @@ class Dispatcher {
117
140
  try {
118
141
  const execution = await this.createExecution(...event);
119
142
  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
- }
143
+ try {
144
+ await this.enqueue({
145
+ executionId: execution.id
146
+ });
147
+ } catch (qErr) {
148
+ logger.error(`publishing execution (${execution.id}) to queue failed:`, { error: qErr });
134
149
  }
135
150
  }
136
151
  } catch (error) {
@@ -141,31 +156,17 @@ class Dispatcher {
141
156
  this.saving = null;
142
157
  if (this.events.length) {
143
158
  this.saveEvent();
144
- } else {
145
- this.dispatch();
146
159
  }
147
160
  }
148
161
  })();
149
162
  }
150
- async resume(job) {
151
- let { execution } = job;
152
- if (!execution) {
153
- execution = await job.getExecution();
154
- }
155
- this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) resuming from job (${job.id}) added to pending list`);
156
- await this.run({ execution, job });
157
- }
158
- async start(execution) {
159
- if (execution.status) {
160
- return;
161
- }
162
- this.plugin.getLogger(execution.workflowId).info(`starting deferred execution (${execution.id})`);
163
- await this.run({ execution });
164
- }
165
163
  async beforeStop() {
166
164
  this.ready = false;
167
165
  this.plugin.getLogger("dispatcher").info("app is stopping, draining local queues...");
168
- while (this.saving || this.executing || this.events.length || this.pending.length) {
166
+ if (this.recovering) {
167
+ await this.recovering;
168
+ }
169
+ while (this.saving || this.executing || this.events.length) {
169
170
  if (this.saving) {
170
171
  await this.saving;
171
172
  }
@@ -175,68 +176,131 @@ class Dispatcher {
175
176
  if (this.events.length && !this.saving) {
176
177
  this.saveEvent();
177
178
  }
178
- if (this.pending.length && !this.executing) {
179
- this.dispatch();
180
- }
181
179
  await new Promise((resolve) => setImmediate(resolve));
182
180
  }
183
181
  this.plugin.getLogger("dispatcher").info("local queues drained");
184
182
  }
185
- dispatch() {
186
- if (!this.ready && !this.pending.length && !this.events.length) {
183
+ async recover(options = {}) {
184
+ if (!this.ready) {
187
185
  return;
188
186
  }
189
- if (this.executing) {
190
- this.plugin.getLogger("dispatcher").warn(`workflow executing is not finished, new dispatching will be ignored`);
187
+ if (this.recovering) {
188
+ await this.recovering;
191
189
  return;
192
190
  }
193
- if (this.events.length) {
194
- this.saveEvent();
195
- return;
191
+ const recovering = this.recoverQueueTasks(options.gracePeriod ?? 0);
192
+ this.recovering = recovering;
193
+ try {
194
+ await recovering;
195
+ } finally {
196
+ if (this.recovering === recovering) {
197
+ this.recovering = null;
198
+ }
196
199
  }
197
- this.executing = (async () => {
198
- let next = null;
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
- });
204
- if (execution) {
205
- next = [execution, pending == null ? void 0 : pending.job, pending == null ? void 0 : pending.rerun];
206
- }
207
- if (pending && next) {
208
- this.plugin.getLogger(next[0].workflowId).info(`pending execution (${next[0].id}) ready to process`);
209
- }
210
- } else {
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`
200
+ }
201
+ async recoverQueueTasks(gracePeriod) {
202
+ const logger = this.plugin.getLogger("dispatcher");
203
+ try {
204
+ if (!this.plugin.serving()) {
205
+ logger.warn(
206
+ `${import_Plugin.WORKER_JOB_WORKFLOW_PROCESS} is not serving on this instance, workflow queue recovery will be ignored`
213
207
  );
208
+ return;
214
209
  }
215
- if (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
- }
210
+ let lock;
211
+ try {
212
+ lock = await this.plugin.app.lockManager.tryAcquire(`workflow:recover:${this.plugin.app.name}`);
213
+ } catch (error) {
214
+ if ((0, import_utils.isLockAcquireError)(error)) {
215
+ logger.debug(`workflow queue recovery is already running on another instance`);
216
+ return;
223
217
  }
218
+ throw error;
224
219
  }
225
- setImmediate(() => {
226
- this.executing = null;
227
- if (next || this.pending.length) {
228
- this.plugin.getLogger("dispatcher").debug(`last process finished, will do another dispatch`);
229
- this.dispatch();
220
+ await lock.runExclusive(async () => {
221
+ let executionCursor = null;
222
+ while (this.ready) {
223
+ const executions = await this.findQueueingExecutions(executionCursor, gracePeriod);
224
+ if (!executions.length) {
225
+ break;
226
+ }
227
+ for (const execution of executions) {
228
+ if (!this.ready) {
229
+ return;
230
+ }
231
+ await this.enqueue({ executionId: execution.id });
232
+ }
233
+ executionCursor = executions[executions.length - 1].id;
230
234
  }
231
- });
232
- })();
235
+ }, RECOVERY_LOCK_TTL);
236
+ } catch (error) {
237
+ logger.error(`workflow queue recovery failed`, { error });
238
+ }
233
239
  }
234
- async run(pending) {
235
- this.pending.push({
236
- ...pending,
237
- immediate: !this.executing && !this.pending.length && !this.saving && !this.events.length
240
+ async enqueue(task) {
241
+ await this.plugin.app.eventQueue.publish(this.plugin.channelPendingExecution, task, {
242
+ maxRetries: QUEUE_TASK_MAX_ATTEMPTS - 1
238
243
  });
239
- this.dispatch();
244
+ }
245
+ async loadJob(jobId) {
246
+ if (jobId == null) {
247
+ return null;
248
+ }
249
+ return this.plugin.db.getRepository("jobs").findOne({
250
+ filterByTk: jobId
251
+ });
252
+ }
253
+ async findQueueingExecutions(afterId, gracePeriod) {
254
+ const executions = await this.plugin.db.getRepository("executions").find({
255
+ filter: {
256
+ ...afterId == null ? {} : { id: { $gt: afterId } },
257
+ ...gracePeriod > 0 ? { createdAt: { $lt: new Date(Date.now() - gracePeriod) } } : {},
258
+ dispatched: false,
259
+ status: import_constants.EXECUTION_STATUS.QUEUEING,
260
+ startedAt: null,
261
+ "workflow.enabled": true
262
+ },
263
+ sort: "id",
264
+ limit: RECOVERY_BATCH_SIZE
265
+ });
266
+ for (const execution of executions) {
267
+ this.plugin.getLogger(execution.workflowId).info(`queueing execution (${execution.id}) found, publishing task`);
268
+ }
269
+ return executions;
270
+ }
271
+ async resolveTask(task) {
272
+ const executionInput = await this.plugin.db.getRepository("executions").findOne({
273
+ filterByTk: task.executionId
274
+ });
275
+ if (!executionInput) {
276
+ this.plugin.getLogger("dispatcher").warn(`execution (${task.executionId}) not found, queue task ignored`);
277
+ return null;
278
+ }
279
+ if (task.jobId == null && task.rerun == null && executionInput.status === import_constants.EXECUTION_STATUS.STARTED && executionInput.startedAt) {
280
+ this.plugin.getLogger(executionInput.workflowId).warn(`execution (${executionInput.id}) has already started, start task ignored`);
281
+ return null;
282
+ }
283
+ let job = null;
284
+ if (task.jobId != null) {
285
+ job = await this.loadJob(task.jobId);
286
+ if (!job) {
287
+ this.plugin.getLogger(executionInput.workflowId).warn(`job (${task.jobId}) not found, resume ignored`);
288
+ return null;
289
+ }
290
+ if (String(job.executionId) !== String(executionInput.id)) {
291
+ this.plugin.getLogger(executionInput.workflowId).warn(`job (${job.id}) does not belong to execution (${executionInput.id}), resume ignored`);
292
+ return null;
293
+ }
294
+ }
295
+ const execution = await this.prepare(executionInput);
296
+ if (!execution) {
297
+ return null;
298
+ }
299
+ if (job) {
300
+ job.execution = execution;
301
+ return [execution, job];
302
+ }
303
+ return [execution, void 0, task.rerun];
240
304
  }
241
305
  async triggerSync(workflow, context, { deferred, ...options } = {}) {
242
306
  let execution;
@@ -285,8 +349,15 @@ class Dispatcher {
285
349
  }
286
350
  return valid;
287
351
  }
288
- async createExecution(workflow, context, options) {
352
+ async handleTriggerFail(workflow, context, options, error) {
289
353
  var _a;
354
+ try {
355
+ await ((_a = options.onTriggerFail) == null ? void 0 : _a.call(options, workflow, context, options, error));
356
+ } catch (triggerFailError) {
357
+ this.plugin.getLogger(workflow.id).error(`trigger failure callback failed`, { error: triggerFailError });
358
+ }
359
+ }
360
+ async createExecution(workflow, context, options) {
290
361
  const { deferred } = options;
291
362
  let stack = options.stack;
292
363
  if (options.parentExecutionId && !stack) {
@@ -295,21 +366,34 @@ class Dispatcher {
295
366
  });
296
367
  stack = parentExecution ? [...parentExecution.stack ?? [], parentExecution.id] : [];
297
368
  }
298
- const valid = await this.validateEvent(workflow, context, { ...options, stack });
369
+ let valid;
370
+ try {
371
+ valid = await this.validateEvent(workflow, context, { ...options, stack });
372
+ } catch (error) {
373
+ await this.handleTriggerFail(workflow, context, options, error);
374
+ throw error;
375
+ }
299
376
  if (!valid) {
300
- (_a = options.onTriggerFail) == null ? void 0 : _a.call(options, workflow, context, options);
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
- });
377
+ const error = new Error("event is not valid");
378
+ await this.handleTriggerFail(workflow, context, options, error);
379
+ throw error;
380
+ }
381
+ let execution;
382
+ try {
383
+ execution = await workflow.createExecution({
384
+ context,
385
+ key: workflow.key,
386
+ eventKey: options.eventKey ?? (0, import_node_crypto.randomUUID)(),
387
+ stack,
388
+ parentExecutionId: options.parentExecutionId ?? null,
389
+ dispatched: deferred ?? false,
390
+ status: deferred ? import_constants.EXECUTION_STATUS.STARTED : import_constants.EXECUTION_STATUS.QUEUEING,
391
+ manually: options.manually
392
+ });
393
+ } catch (error) {
394
+ await this.handleTriggerFail(workflow, context, options, error);
395
+ throw error;
396
+ }
313
397
  this.plugin.getLogger(workflow.id).info(`execution of workflow ${workflow.id} created as ${execution.id}`);
314
398
  if (!workflow.stats) {
315
399
  workflow.stats = await workflow.getStats();
@@ -329,11 +413,10 @@ class Dispatcher {
329
413
  return execution;
330
414
  }
331
415
  async prepare(input, options = {}) {
332
- const logger = input ? this.plugin.getLogger(input.workflowId) : this.plugin.getLogger("dispatcher");
416
+ const logger = this.plugin.getLogger(input.workflowId);
333
417
  if (options.transaction) {
334
418
  try {
335
- const { execution } = await this.acquireExecution(input, options, options.transaction);
336
- return execution;
419
+ return await this.acquireExecution(input, options, options.transaction);
337
420
  } catch (error) {
338
421
  if (error instanceof Error) {
339
422
  logger.error(`entering execution failed: ${error.message}`, { error });
@@ -346,13 +429,12 @@ class Dispatcher {
346
429
  await this.acquireWithRetry(
347
430
  async () => {
348
431
  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
432
+ isolationLevel: this.plugin.db.options.dialect === "sqlite" ? void 0 : import_sequelize.Transaction.ISOLATION_LEVELS.READ_COMMITTED
350
433
  });
351
434
  try {
352
- const { execution, shouldRetry } = await this.acquireExecution(input, options, tx);
435
+ const execution = await this.acquireExecution(input, options, tx);
353
436
  await tx.commit();
354
437
  result = execution;
355
- return shouldRetry;
356
438
  } catch (error) {
357
439
  await tx.rollback();
358
440
  if (this.isConcurrentAcquireError(error)) {
@@ -362,13 +444,12 @@ class Dispatcher {
362
444
  logger.error(`entering execution failed: ${error.message}`, { error });
363
445
  }
364
446
  result = null;
365
- return false;
366
447
  }
367
448
  },
368
449
  {
369
450
  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`
451
+ conflictMessage: `acquiring queue task execution (${input.id}) conflicted with another worker, retrying`,
452
+ maxAttemptsMessage: `acquiring queue task execution (${input.id}) reached max retry attempts`
372
453
  }
373
454
  );
374
455
  } catch (error) {
@@ -380,53 +461,25 @@ class Dispatcher {
380
461
  return result;
381
462
  }
382
463
  async acquireExecution(input, options, transaction) {
383
- let execution = input;
384
- if (execution) {
385
- if (!options.immediate || execution.status !== import_constants.EXECUTION_STATUS.QUEUEING) {
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
398
- });
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
- }
464
+ if (!options.immediate || input.status !== import_constants.EXECUTION_STATUS.QUEUEING) {
465
+ await input.reload({ transaction });
404
466
  }
405
- if (!execution) {
406
- return { execution: null, shouldRetry: false };
407
- }
408
- const entered = await this.enter(execution, transaction);
409
- const shouldRetry = !input && !entered;
410
- return { execution: entered, shouldRetry };
467
+ return this.enter(input, transaction);
411
468
  }
412
469
  async acquireWithRetry(acquire, options) {
413
470
  for (let attempt = 1; attempt <= EXECUTION_ACQUIRE_MAX_ATTEMPTS; attempt++) {
414
- let shouldRetry = false;
415
471
  try {
416
- shouldRetry = await acquire();
472
+ await acquire();
473
+ break;
417
474
  } catch (error) {
418
475
  if (!this.isConcurrentAcquireError(error)) {
419
476
  throw error;
420
477
  }
421
- shouldRetry = true;
422
478
  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;
479
+ if (attempt >= EXECUTION_ACQUIRE_MAX_ATTEMPTS) {
480
+ options.logger.warn(options.maxAttemptsMessage);
481
+ break;
482
+ }
430
483
  }
431
484
  }
432
485
  }
@@ -492,7 +545,7 @@ class Dispatcher {
492
545
  var _a, _b, _c;
493
546
  if (!execution.dispatched) {
494
547
  await execution.update({ dispatched: true, status: import_constants.EXECUTION_STATUS.STARTED });
495
- logger.info(`execution (${execution.id}) from pending list updated to started`);
548
+ logger.info(`execution (${execution.id}) from workflow queue updated to started`);
496
549
  }
497
550
  this.plugin.timeoutManager.scheduleExecutionTimeout(execution);
498
551
  const processor = this.plugin.createProcessor(execution, processorOptions);
@@ -511,7 +564,8 @@ class Dispatcher {
511
564
  }
512
565
  return processor;
513
566
  };
514
- const lock = await this.plugin.app.lockManager.tryAcquire((0, import_utils.getExecutionLockKey)(execution.id), 6e4);
567
+ const lockManager = this.plugin.app.lockManager;
568
+ const lock = await lockManager.tryAcquire((0, import_utils.getExecutionLockKey)(execution.id), 6e4);
515
569
  try {
516
570
  return await lock.runExclusive(run, 6e4);
517
571
  } catch (error) {
@@ -81,10 +81,10 @@ class ExecutionTimeoutManager {
81
81
  await ((_a = this.scanning) == null ? void 0 : _a.catch(() => {
82
82
  }));
83
83
  }
84
- isExpired(execution, now = /* @__PURE__ */ new Date()) {
84
+ isExpired(execution, now = new Date(Date.now())) {
85
85
  return !!execution.expiresAt && execution.expiresAt.getTime() <= now.getTime();
86
86
  }
87
- getRemainingMs(execution, now = /* @__PURE__ */ new Date()) {
87
+ getRemainingMs(execution, now = new Date(Date.now())) {
88
88
  if (!execution.expiresAt) {
89
89
  return null;
90
90
  }
@@ -108,7 +108,8 @@ class ExecutionTimeoutManager {
108
108
  }
109
109
  async abortExecutionIfExpiredWithLock(executionOrId) {
110
110
  const executionId = typeof executionOrId === "object" ? executionOrId.id : executionOrId;
111
- const lock = await this.plugin.app.lockManager.tryAcquire((0, import_utils.getExecutionLockKey)(executionId), EXECUTION_LOCK_TIMEOUT);
111
+ const lockManager = this.plugin.app.lockManager;
112
+ const lock = await lockManager.tryAcquire((0, import_utils.getExecutionLockKey)(executionId), EXECUTION_LOCK_TIMEOUT);
112
113
  return lock.runExclusive(async () => {
113
114
  const execution = await this.plugin.db.getRepository("executions").findOne({
114
115
  filterByTk: executionId
@@ -11,14 +11,14 @@ import { Transactionable } from 'sequelize';
11
11
  import { Plugin } from '@nocobase/server';
12
12
  import { Registry } from '@nocobase/utils';
13
13
  import { Logger } from '@nocobase/logger';
14
- import Dispatcher, { EventOptions } from './Dispatcher';
15
- import Processor from './Processor';
14
+ import { EventOptions } from './Dispatcher';
15
+ import Processor, { ProcessorRerunOptions } from './Processor';
16
16
  import ExecutionTimeoutManager from './ExecutionTimeoutManager';
17
17
  import RunningExecutionRegistry from './RunningExecutionRegistry';
18
18
  import { CustomFunction } from './functions';
19
19
  import Trigger from './triggers';
20
20
  import { InstructionInterface } from './instructions';
21
- import type { ExecutionModel, WorkflowModel } from './types';
21
+ import type { ExecutionModel, JobModel, WorkflowModel } from './types';
22
22
  import type { Transaction } from '@nocobase/database';
23
23
  type ID = number | string;
24
24
  export declare const WORKER_JOB_WORKFLOW_PROCESS = "workflow:process";
@@ -74,16 +74,25 @@ export default class PluginWorkflowServer extends Plugin {
74
74
  */
75
75
  load(): Promise<void>;
76
76
  private toggle;
77
- trigger(workflow: WorkflowModel, context: object, options?: EventOptions): void | Promise<Processor | null>;
78
- run(pending: Parameters<Dispatcher['run']>[0]): Promise<void>;
79
- dispatch(): void;
80
- resume(job: any): Promise<void>;
77
+ trigger(workflow: WorkflowModel, context: object, options?: EventOptions): void | Promise<Processor | null | void>;
78
+ rerun(execution: ExecutionModel | ID, rerun?: ProcessorRerunOptions): Promise<void>;
79
+ recover(): Promise<void>;
80
+ /**
81
+ * Persist the current job state and publish a poke that evaluates that state.
82
+ *
83
+ * PENDING resume calls are valid for instructions such as delay and sequential approval.
84
+ * Callers that aggregate multiple user actions must only call this method when their
85
+ * atomic update changes the job from PENDING to a terminal status.
86
+ * Persistence and queue publication failures are logged and rethrown to awaiting callers.
87
+ */
88
+ resume(job: JobModel): Promise<void>;
81
89
  /**
82
90
  * Start a deferred execution
83
91
  * @experimental
84
92
  */
85
- start(execution: ExecutionModel): Promise<void>;
93
+ start(execution: ExecutionModel | ID): Promise<void>;
86
94
  createProcessor(execution: ExecutionModel, options?: {}): Processor;
95
+ private getModelId;
87
96
  execute(workflow: WorkflowModel, values: any, options?: EventOptions): Promise<void | Processor>;
88
97
  /**
89
98
  * @experimental