@nocobase/plugin-workflow 2.2.0-beta.9 → 3.0.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 (267) 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.8bd46a04ca080a4e.js +10 -0
  11. package/dist/client/509.3a4ac27f10833c75.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.5eb8767a709f27d9.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.d03ecfc4ac66e0f8.js +10 -0
  23. package/dist/client/87.b62b1123d4d560fb.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/BranchContext.d.ts +2 -10
  29. package/dist/client/FlowContext.d.ts +10 -5
  30. package/dist/client/NodeClipboardContext.d.ts +12 -2
  31. package/dist/client/NodeDragContext.d.ts +14 -2
  32. package/dist/client/RemoveNodeContext.d.ts +16 -2
  33. package/dist/client/WorkflowTasks.d.ts +26 -3
  34. package/dist/client/index.d.ts +2 -2
  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 -27
  48. package/dist/client/triggers/collection.d.ts +3 -181
  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.e002f8eb34aa77e3.js +10 -0
  61. package/dist/client-v2/394.515ed058e7f72797.js +10 -0
  62. package/dist/client-v2/438.1459f1a7cd19ea4f.js +10 -0
  63. package/dist/client-v2/513.18e257b3025d7692.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.7007f4a6a4640165.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/legacySettingsRedirect.d.ts +15 -0
  146. package/dist/client-v2/locale.d.ts +41 -0
  147. package/dist/{client → client-v2}/models/NodeDetailsGridModel.d.ts +1 -1
  148. package/dist/{client → client-v2}/models/NodeDetailsModel.d.ts +1 -1
  149. package/dist/{client → client-v2}/models/NodeValueModel.d.ts +1 -1
  150. package/dist/{client → client-v2}/models/TaskCardCommonItemModel.d.ts +1 -1
  151. package/dist/client-v2/models/WorkflowTasksTopbarActionModel.d.ts +18 -0
  152. package/dist/{client → client-v2}/models/index.d.ts +2 -0
  153. package/dist/client-v2/models/triggerWorkflows.d.ts +43 -0
  154. package/dist/client-v2/nodes/calculation.d.ts +38 -0
  155. package/dist/client-v2/nodes/collectionNode.d.ts +29 -0
  156. package/dist/client-v2/nodes/components/calculation.d.ts +11 -0
  157. package/dist/client-v2/nodes/components/collection.d.ts +15 -0
  158. package/dist/client-v2/nodes/components/condition.d.ts +25 -0
  159. package/dist/client-v2/nodes/components/conditionShared.d.ts +20 -0
  160. package/dist/client-v2/nodes/components/create.d.ts +12 -0
  161. package/dist/client-v2/nodes/components/destroy.d.ts +12 -0
  162. package/dist/{client/triggers/schedule/ScheduleConfig.d.ts → client-v2/nodes/components/end.d.ts} +1 -1
  163. package/dist/client-v2/nodes/components/filter.d.ts +17 -0
  164. package/dist/client-v2/nodes/components/multi-conditions.d.ts +13 -0
  165. package/dist/client-v2/nodes/components/multiConditionsShared.d.ts +20 -0
  166. package/dist/client-v2/nodes/components/output.d.ts +10 -0
  167. package/dist/client-v2/nodes/components/query.d.ts +12 -0
  168. package/dist/client-v2/nodes/components/update.d.ts +12 -0
  169. package/dist/client-v2/nodes/condition.d.ts +46 -0
  170. package/dist/client-v2/nodes/create.d.ts +30 -0
  171. package/dist/client-v2/nodes/destroy.d.ts +24 -0
  172. package/dist/client-v2/nodes/end.d.ts +21 -0
  173. package/dist/client-v2/nodes/multi-conditions.d.ts +34 -0
  174. package/dist/client-v2/nodes/output.d.ts +29 -0
  175. package/dist/client-v2/nodes/query.d.ts +32 -0
  176. package/dist/client-v2/nodes/update.d.ts +25 -0
  177. package/dist/client-v2/pages/ExecutionHistoryDrawer.d.ts +13 -0
  178. package/dist/client-v2/pages/ExecutionViewPage.d.ts +10 -0
  179. package/dist/client-v2/pages/WorkflowCanvasPage.d.ts +10 -0
  180. package/dist/client-v2/pages/WorkflowCategoryTabs.d.ts +25 -0
  181. package/dist/client-v2/pages/WorkflowFormDrawer.d.ts +40 -0
  182. package/dist/client-v2/pages/WorkflowPane.d.ts +10 -0
  183. package/dist/client-v2/pages/WorkflowTasksPage.d.ts +10 -0
  184. package/dist/client-v2/plugin.d.ts +119 -0
  185. package/dist/client-v2/taskCenter.d.ts +105 -0
  186. package/dist/client-v2/triggers/ExecuteWorkflowButton.d.ts +35 -0
  187. package/dist/client-v2/triggers/TriggerConfig.d.ts +22 -0
  188. package/dist/client-v2/triggers/TriggerExecutionButton.d.ts +13 -0
  189. package/dist/client-v2/triggers/collection/CollectionConfig.d.ts +14 -0
  190. package/dist/client-v2/triggers/collection/TriggerCollectionConfig.d.ts +11 -0
  191. package/dist/client-v2/triggers/collection/constants.d.ts +28 -0
  192. package/dist/client-v2/triggers/collection/index.d.ts +40 -0
  193. package/dist/client-v2/triggers/index.d.ts +63 -0
  194. package/dist/client-v2/triggers/schedule/EndsByField.d.ts +17 -0
  195. package/dist/client-v2/triggers/schedule/OnField.d.ts +20 -0
  196. package/dist/client-v2/triggers/schedule/RepeatField.d.ts +23 -0
  197. package/dist/client-v2/triggers/schedule/ScheduleConfig.d.ts +17 -0
  198. package/dist/client-v2/triggers/schedule/ScheduleModes.d.ts +41 -0
  199. package/dist/{client → client-v2}/triggers/schedule/TriggerScheduleConfig.d.ts +1 -0
  200. package/dist/{client → client-v2}/triggers/schedule/constants.d.ts +8 -5
  201. package/dist/client-v2/triggers/schedule/index.d.ts +40 -0
  202. package/dist/client-v2/triggers/schedule/locale/Cron.zh-CN.d.ts +11 -0
  203. package/dist/client-v2/utils/contextSingleton.d.ts +9 -0
  204. package/dist/common/collections/userWorkflowTaskStats.d.ts +54 -0
  205. package/dist/common/collections/userWorkflowTaskStats.js +87 -0
  206. package/dist/common/collections/workflows.d.ts +16 -0
  207. package/dist/common/collections/workflows.js +5 -0
  208. package/dist/common/defaultWorkflowFilter.d.ts +21 -0
  209. package/dist/common/defaultWorkflowFilter.js +38 -0
  210. package/dist/common/executionStatus.d.ts +44 -0
  211. package/dist/common/executionStatus.js +133 -0
  212. package/dist/externalVersion.js +16 -13
  213. package/dist/locale/de-DE.json +15 -1
  214. package/dist/locale/en-US.json +30 -1
  215. package/dist/locale/es-ES.json +15 -1
  216. package/dist/locale/fr-FR.json +15 -1
  217. package/dist/locale/hu-HU.json +15 -1
  218. package/dist/locale/id-ID.json +15 -1
  219. package/dist/locale/it-IT.json +15 -1
  220. package/dist/locale/ja-JP.json +15 -1
  221. package/dist/locale/ko-KR.json +15 -1
  222. package/dist/locale/nl-NL.json +15 -1
  223. package/dist/locale/pt-BR.json +15 -1
  224. package/dist/locale/ru-RU.json +15 -1
  225. package/dist/locale/tr-TR.json +15 -1
  226. package/dist/locale/uk-UA.json +15 -1
  227. package/dist/locale/vi-VN.json +15 -1
  228. package/dist/locale/zh-CN.json +29 -1
  229. package/dist/locale/zh-TW.json +21 -1
  230. package/dist/node_modules/cron-parser/package.json +1 -1
  231. package/dist/node_modules/joi/package.json +1 -1
  232. package/dist/node_modules/lru-cache/package.json +1 -1
  233. package/dist/node_modules/nodejs-snowflake/package.json +1 -1
  234. package/dist/server/Dispatcher.d.ts +27 -15
  235. package/dist/server/Dispatcher.js +199 -161
  236. package/dist/server/ExecutionTimeoutManager.js +2 -1
  237. package/dist/server/Plugin.d.ts +58 -12
  238. package/dist/server/Plugin.js +355 -15
  239. package/dist/server/Processor.d.ts +1 -7
  240. package/dist/server/Processor.js +11 -7
  241. package/dist/server/actions/executions.js +3 -6
  242. package/dist/server/actions/index.js +3 -1
  243. package/dist/server/actions/jobs.js +1 -2
  244. package/dist/server/actions/userWorkflowTaskStats.d.ts +10 -0
  245. package/dist/server/actions/userWorkflowTaskStats.js +112 -0
  246. package/dist/server/actions/workflows.d.ts +1 -0
  247. package/dist/server/actions/workflows.js +34 -2
  248. package/dist/server/collections/userWorkflowTaskStats.d.ts +11 -0
  249. package/dist/server/collections/userWorkflowTaskStats.js +43 -0
  250. package/dist/server/index.d.ts +1 -0
  251. package/dist/server/triggers/CollectionTrigger.d.ts +1 -1
  252. package/dist/server/triggers/CollectionTrigger.js +3 -0
  253. package/dist/server/triggers/ScheduleTrigger/StaticScheduleTrigger.d.ts +1 -1
  254. package/dist/server/triggers/index.d.ts +1 -1
  255. package/dist/server/types/Execution.d.ts +2 -0
  256. package/dist/server/types/Job.d.ts +5 -0
  257. package/dist/server/types/Workflow.d.ts +1 -0
  258. package/dist/server/utils.d.ts +2 -1
  259. package/dist/server/utils.js +19 -1
  260. package/dist/swagger/index.d.ts +69 -1
  261. package/dist/swagger/index.js +65 -3
  262. package/package.json +4 -2
  263. package/dist/client/618.c91be3f6e4d73501.js +0 -10
  264. package/dist/client/67.61e70b8b777f8638.js +0 -10
  265. package/dist/client/964.5e11e1820fece37b.js +0 -10
  266. package/dist/client/triggers/schedule/ScheduleModes.d.ts +0 -170
  267. package/dist/client/triggers/schedule/locale/Cron.zh-CN.d.ts +0 -42
@@ -35,30 +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 PENDING_DISPATCH_MAX_ATTEMPTS = 3;
38
+ const QUEUE_TASK_MAX_ATTEMPTS = 3;
39
+ const RECOVERY_BATCH_SIZE = 100;
40
+ const RECOVERY_LOCK_TTL = 3e5;
39
41
  class Dispatcher {
40
42
  constructor(plugin) {
41
43
  this.plugin = plugin;
42
44
  }
43
45
  ready = false;
44
46
  executing = null;
47
+ recovering = null;
45
48
  saving = null;
46
- pending = [];
47
49
  events = [];
48
50
  eventsCount = 0;
49
51
  get idle() {
50
- return this.ready && !this.executing && !this.saving && !this.pending.length && !this.events.length;
52
+ return this.ready && !this.executing && !this.saving && !this.events.length;
51
53
  }
52
- onQueueExecution = async (event) => {
53
- const ExecutionRepo = this.plugin.db.getRepository("executions");
54
- const execution = await ExecutionRepo.findOne({
55
- filterByTk: event.executionId
56
- });
57
- if (!execution || execution.dispatched || execution.status !== import_constants.EXECUTION_STATUS.QUEUEING) {
58
- 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
+ }
59
83
  }
60
- this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) received from queue, adding to pending list`);
61
- await this.run({ execution });
62
84
  };
63
85
  setReady(ready) {
64
86
  this.ready = ready;
@@ -71,7 +93,7 @@ class Dispatcher {
71
93
  if (!this.ready) {
72
94
  logger.warn(`app is not ready, event of workflow ${workflow.id} will be ignored`);
73
95
  logger.debug(`ignored event data:`, context);
74
- return;
96
+ return this.handleTriggerFail(workflow, context, options, new Error("app is not ready"));
75
97
  }
76
98
  if (!options.force && !options.manually && !workflow.enabled) {
77
99
  logger.warn(`workflow ${workflow.id} is not enabled, event will be ignored`);
@@ -88,7 +110,7 @@ class Dispatcher {
88
110
  }
89
111
  if (context == null) {
90
112
  logger.warn(`workflow ${workflow.id} event data context is null, event will be ignored`);
91
- return;
113
+ return this.handleTriggerFail(workflow, context, options, new Error("event context is null"));
92
114
  }
93
115
  if (options.manually || this.plugin.isWorkflowSync(workflow)) {
94
116
  return this.triggerSync(workflow, context, options);
@@ -118,20 +140,12 @@ class Dispatcher {
118
140
  try {
119
141
  const execution = await this.createExecution(...event);
120
142
  if (!execution.dispatched) {
121
- if (this.plugin.serving() && !this.executing && !this.pending.length) {
122
- logger.info(`local pending list is empty, adding execution (${execution.id}) to pending list`);
123
- this.pending.push({ execution });
124
- } else {
125
- logger.info(
126
- `instance is not serving as worker or local pending list is not empty, sending execution (${execution.id}) to queue`
127
- );
128
- try {
129
- await this.plugin.app.eventQueue.publish(this.plugin.channelPendingExecution, {
130
- executionId: execution.id
131
- });
132
- } catch (qErr) {
133
- logger.error(`publishing execution (${execution.id}) to queue failed:`, { error: qErr });
134
- }
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 });
135
149
  }
136
150
  }
137
151
  } catch (error) {
@@ -142,31 +156,17 @@ class Dispatcher {
142
156
  this.saving = null;
143
157
  if (this.events.length) {
144
158
  this.saveEvent();
145
- } else {
146
- this.dispatch();
147
159
  }
148
160
  }
149
161
  })();
150
162
  }
151
- async resume(job) {
152
- let { execution } = job;
153
- if (!execution) {
154
- execution = await job.getExecution();
155
- }
156
- this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) resuming from job (${job.id}) added to pending list`);
157
- await this.run({ execution, job });
158
- }
159
- async start(execution) {
160
- if (execution.status) {
161
- return;
162
- }
163
- this.plugin.getLogger(execution.workflowId).info(`starting deferred execution (${execution.id})`);
164
- await this.run({ execution });
165
- }
166
163
  async beforeStop() {
167
164
  this.ready = false;
168
165
  this.plugin.getLogger("dispatcher").info("app is stopping, draining local queues...");
169
- 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) {
170
170
  if (this.saving) {
171
171
  await this.saving;
172
172
  }
@@ -176,85 +176,131 @@ class Dispatcher {
176
176
  if (this.events.length && !this.saving) {
177
177
  this.saveEvent();
178
178
  }
179
- if (this.pending.length && !this.executing) {
180
- this.dispatch();
181
- }
182
179
  await new Promise((resolve) => setImmediate(resolve));
183
180
  }
184
181
  this.plugin.getLogger("dispatcher").info("local queues drained");
185
182
  }
186
- dispatch() {
187
- if (!this.ready && !this.pending.length && !this.events.length) {
183
+ async recover(options = {}) {
184
+ if (!this.ready) {
188
185
  return;
189
186
  }
190
- if (this.executing) {
191
- this.plugin.getLogger("dispatcher").warn(`workflow executing is not finished, new dispatching will be ignored`);
187
+ if (this.recovering) {
188
+ await this.recovering;
192
189
  return;
193
190
  }
194
- if (this.events.length) {
195
- this.saveEvent();
196
- 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
+ }
197
199
  }
198
- this.executing = (async () => {
199
- let next = null;
200
- let pending = null;
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`
207
+ );
208
+ return;
209
+ }
210
+ let lock;
201
211
  try {
202
- pending = this.pending.shift() ?? null;
203
- if (pending || this.ready && this.plugin.serving()) {
204
- const execution = await this.prepare((pending == null ? void 0 : pending.execution) ?? null, {
205
- immediate: pending == null ? void 0 : pending.immediate
206
- });
207
- if (execution) {
208
- next = [execution, pending == null ? void 0 : pending.job, pending == null ? void 0 : pending.rerun];
209
- }
210
- if (pending && next) {
211
- this.plugin.getLogger(next[0].workflowId).info(`pending execution (${next[0].id}) ready to process`);
212
- }
213
- } else {
214
- this.plugin.getLogger("dispatcher").warn(
215
- `${import_Plugin.WORKER_JOB_WORKFLOW_PROCESS} is not serving on this instance or app not ready, new dispatching will be ignored`
216
- );
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;
217
217
  }
218
- if (next) {
219
- try {
220
- await this.process(next[0], next[1], { rerun: next[2] });
221
- } catch (error) {
222
- this.plugin.getLogger(next[0].workflowId).error(`execution (${next[0].id}) process failed`, { error });
223
- if (pending && (0, import_utils.isLockAcquireError)(error)) {
224
- this.pending.unshift({ ...pending, execution: next[0], immediate: true });
225
- }
218
+ throw error;
219
+ }
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
226
  }
227
- }
228
- } catch (error) {
229
- this.plugin.getLogger("dispatcher").error(`workflow dispatch failed`, { error });
230
- if (pending) {
231
- const dispatchAttempts = (pending.dispatchAttempts ?? 0) + 1;
232
- if (dispatchAttempts < PENDING_DISPATCH_MAX_ATTEMPTS) {
233
- this.pending.push({ ...pending, dispatchAttempts });
234
- } else {
235
- this.plugin.getLogger(pending.execution.workflowId).error(`pending execution (${pending.execution.id}) dispatch failed, local retry limit reached`, {
236
- error,
237
- dispatchAttempts
238
- });
227
+ for (const execution of executions) {
228
+ if (!this.ready) {
229
+ return;
230
+ }
231
+ await this.enqueue({ executionId: execution.id });
239
232
  }
233
+ executionCursor = executions[executions.length - 1].id;
240
234
  }
241
- } finally {
242
- setImmediate(() => {
243
- this.executing = null;
244
- if (next || this.pending.length) {
245
- this.plugin.getLogger("dispatcher").debug(`last process finished, will do another dispatch`);
246
- this.dispatch();
247
- }
248
- });
249
- }
250
- })();
235
+ }, RECOVERY_LOCK_TTL);
236
+ } catch (error) {
237
+ logger.error(`workflow queue recovery failed`, { error });
238
+ }
251
239
  }
252
- async run(pending) {
253
- this.pending.push({
254
- ...pending,
255
- 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
256
243
  });
257
- 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];
258
304
  }
259
305
  async triggerSync(workflow, context, { deferred, ...options } = {}) {
260
306
  let execution;
@@ -303,8 +349,15 @@ class Dispatcher {
303
349
  }
304
350
  return valid;
305
351
  }
306
- async createExecution(workflow, context, options) {
352
+ async handleTriggerFail(workflow, context, options, error) {
307
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) {
308
361
  const { deferred } = options;
309
362
  let stack = options.stack;
310
363
  if (options.parentExecutionId && !stack) {
@@ -313,21 +366,34 @@ class Dispatcher {
313
366
  });
314
367
  stack = parentExecution ? [...parentExecution.stack ?? [], parentExecution.id] : [];
315
368
  }
316
- 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
+ }
317
376
  if (!valid) {
318
- (_a = options.onTriggerFail) == null ? void 0 : _a.call(options, workflow, context, options);
319
- throw new Error("event is not valid");
320
- }
321
- const execution = await workflow.createExecution({
322
- context,
323
- key: workflow.key,
324
- eventKey: options.eventKey ?? (0, import_node_crypto.randomUUID)(),
325
- stack,
326
- parentExecutionId: options.parentExecutionId ?? null,
327
- dispatched: deferred ?? false,
328
- status: deferred ? import_constants.EXECUTION_STATUS.STARTED : import_constants.EXECUTION_STATUS.QUEUEING,
329
- manually: options.manually
330
- });
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
+ }
331
397
  this.plugin.getLogger(workflow.id).info(`execution of workflow ${workflow.id} created as ${execution.id}`);
332
398
  if (!workflow.stats) {
333
399
  workflow.stats = await workflow.getStats();
@@ -347,7 +413,7 @@ class Dispatcher {
347
413
  return execution;
348
414
  }
349
415
  async prepare(input, options = {}) {
350
- const logger = input ? this.plugin.getLogger(input.workflowId) : this.plugin.getLogger("dispatcher");
416
+ const logger = this.plugin.getLogger(input.workflowId);
351
417
  if (options.transaction) {
352
418
  try {
353
419
  return await this.acquireExecution(input, options, options.transaction);
@@ -382,8 +448,8 @@ class Dispatcher {
382
448
  },
383
449
  {
384
450
  logger,
385
- conflictMessage: input ? `acquiring pending execution (${input.id}) conflicted with another worker, retrying` : `acquiring execution conflicted with another worker, retrying`,
386
- 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`
387
453
  }
388
454
  );
389
455
  } catch (error) {
@@ -395,39 +461,10 @@ class Dispatcher {
395
461
  return result;
396
462
  }
397
463
  async acquireExecution(input, options, transaction) {
398
- let execution = input;
399
- if (execution) {
400
- if (!options.immediate || execution.status !== import_constants.EXECUTION_STATUS.QUEUEING) {
401
- await execution.reload({ transaction });
402
- }
403
- } else {
404
- const workflowIds = [...this.plugin.enabledCache.keys()];
405
- if (!workflowIds.length) {
406
- this.plugin.getLogger("dispatcher").debug(`no enabled workflow to process`);
407
- return null;
408
- }
409
- execution = await this.plugin.db.getRepository("executions").findOne({
410
- filter: {
411
- dispatched: false,
412
- status: import_constants.EXECUTION_STATUS.QUEUEING,
413
- startedAt: null,
414
- workflowId: workflowIds
415
- },
416
- sort: "id",
417
- transaction,
418
- lock: transaction.LOCK.UPDATE,
419
- skipLocked: true
420
- });
421
- if (execution) {
422
- this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) fetched from db`);
423
- } else {
424
- this.plugin.getLogger("dispatcher").debug(`no execution in db queued to process`);
425
- }
426
- }
427
- if (!execution) {
428
- return null;
464
+ if (!options.immediate || input.status !== import_constants.EXECUTION_STATUS.QUEUEING) {
465
+ await input.reload({ transaction });
429
466
  }
430
- return this.enter(execution, transaction);
467
+ return this.enter(input, transaction);
431
468
  }
432
469
  async acquireWithRetry(acquire, options) {
433
470
  for (let attempt = 1; attempt <= EXECUTION_ACQUIRE_MAX_ATTEMPTS; attempt++) {
@@ -508,7 +545,7 @@ class Dispatcher {
508
545
  var _a, _b, _c;
509
546
  if (!execution.dispatched) {
510
547
  await execution.update({ dispatched: true, status: import_constants.EXECUTION_STATUS.STARTED });
511
- logger.info(`execution (${execution.id}) from pending list updated to started`);
548
+ logger.info(`execution (${execution.id}) from workflow queue updated to started`);
512
549
  }
513
550
  this.plugin.timeoutManager.scheduleExecutionTimeout(execution);
514
551
  const processor = this.plugin.createProcessor(execution, processorOptions);
@@ -527,7 +564,8 @@ class Dispatcher {
527
564
  }
528
565
  return processor;
529
566
  };
530
- 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);
531
569
  try {
532
570
  return await lock.runExclusive(run, 6e4);
533
571
  } catch (error) {
@@ -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,16 +11,39 @@ 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
+ type TaskStats = {
25
+ pending: number;
26
+ all: number;
27
+ };
28
+ export type TaskStatsRow = {
29
+ userId: number;
30
+ workflowKey: string;
31
+ type: string;
32
+ pending: number;
33
+ all: number;
34
+ };
35
+ export type RepairTaskStatsOptions = {
36
+ userIds?: number[];
37
+ workflowKeys?: string[];
38
+ types?: string[];
39
+ transaction?: Transaction;
40
+ silent?: boolean;
41
+ };
42
+ export type TaskStatsProvider = {
43
+ collectTaskStats: (options: RepairTaskStatsOptions, context: {
44
+ plugin: PluginWorkflowServer;
45
+ }) => Promise<TaskStatsRow[]>;
46
+ };
24
47
  export declare const WORKER_JOB_WORKFLOW_PROCESS = "workflow:process";
25
48
  export default class PluginWorkflowServer extends Plugin {
26
49
  instructions: Registry<InstructionInterface>;
@@ -35,6 +58,7 @@ export default class PluginWorkflowServer extends Plugin {
35
58
  private loggerCache;
36
59
  private meter;
37
60
  private checker;
61
+ private taskStatsProviders;
38
62
  private onBeforeSave;
39
63
  private onAfterCreate;
40
64
  private onAfterUpdate;
@@ -74,16 +98,25 @@ export default class PluginWorkflowServer extends Plugin {
74
98
  */
75
99
  load(): Promise<void>;
76
100
  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>;
101
+ trigger(workflow: WorkflowModel, context: object, options?: EventOptions): void | Promise<Processor | null | void>;
102
+ rerun(execution: ExecutionModel | ID, rerun?: ProcessorRerunOptions): Promise<void>;
103
+ recover(): Promise<void>;
104
+ /**
105
+ * Persist the current job state and publish a poke that evaluates that state.
106
+ *
107
+ * PENDING resume calls are valid for instructions such as delay and sequential approval.
108
+ * Callers that aggregate multiple user actions must only call this method when their
109
+ * atomic update changes the job from PENDING to a terminal status.
110
+ * Persistence and queue publication failures are logged and rethrown to awaiting callers.
111
+ */
112
+ resume(job: JobModel): Promise<void>;
81
113
  /**
82
114
  * Start a deferred execution
83
115
  * @experimental
84
116
  */
85
- start(execution: ExecutionModel): Promise<void>;
117
+ start(execution: ExecutionModel | ID): Promise<void>;
86
118
  createProcessor(execution: ExecutionModel, options?: {}): Processor;
119
+ private getModelId;
87
120
  execute(workflow: WorkflowModel, values: any, options?: EventOptions): Promise<void | Processor>;
88
121
  /**
89
122
  * @experimental
@@ -94,12 +127,25 @@ export default class PluginWorkflowServer extends Plugin {
94
127
  */
95
128
  useDataSourceTransaction(dataSourceName?: string, transaction?: Transaction | null, create?: false): Transaction | undefined;
96
129
  useDataSourceTransaction(dataSourceName: string | undefined, transaction: Transaction | null | undefined, create: true): Transaction | Promise<Transaction> | undefined;
130
+ registerTaskStatsProvider(type: string, provider: TaskStatsProvider): void;
131
+ getWorkflowIdsByKey(workflowKey: string, transaction?: Transaction): Promise<any>;
132
+ private buildTaskStatsFilter;
133
+ private normalizeTaskStats;
134
+ private upsertTaskStatsRow;
135
+ refreshUserWorkflowTaskTypeStats(userId: number, type: string, { transaction }?: Transactionable): Promise<any>;
136
+ refreshUserWorkflowTaskWorkflowStats(userId: number, workflowKey: string, { transaction }?: Transactionable): Promise<any>;
137
+ private sendTaskWorkflowStatsUpdated;
138
+ updateTaskStatsByWorkflow(input: {
139
+ userId: number;
140
+ workflowKey: string;
141
+ type: string;
142
+ stats: TaskStats;
143
+ }, { transaction }?: Transactionable): Promise<void>;
144
+ repairTaskStats(options?: RepairTaskStatsOptions): Promise<void>;
97
145
  /**
98
146
  * @experimental
147
+ * @deprecated Use updateTaskStatsByWorkflow() when workflowKey is available.
99
148
  */
100
- updateTasksStats(userId: number, type: string, stats: {
101
- pending: number;
102
- all: number;
103
- }, { transaction }: Transactionable): Promise<void>;
149
+ updateTasksStats(userId: number, type: string, stats: TaskStats, { transaction }: Transactionable): Promise<void>;
104
150
  }
105
151
  export {};