@nocobase/plugin-workflow 2.1.0-beta.9 → 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
@@ -6,14 +6,29 @@
6
6
  * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
+ /// <reference types="node" />
9
10
  import { Transaction, Transactionable } from '@nocobase/database';
10
11
  import { Logger } from '@nocobase/logger';
11
12
  import type Plugin from './Plugin';
13
+ import { IJob } from './instructions';
12
14
  import type { ExecutionModel, FlowNodeModel, JobModel } from './types';
15
+ export type ProcessorRunOptions = {
16
+ rerun?: true;
17
+ signal?: AbortSignal;
18
+ };
19
+ export type ProcessorRerunOptions = {
20
+ nodeId?: string | number;
21
+ overwrite?: boolean;
22
+ };
13
23
  export interface ProcessorOptions extends Transactionable {
14
24
  plugin: Plugin;
15
25
  [key: string]: any;
16
26
  }
27
+ export type BackgroundAbortHandle = {
28
+ signal: AbortSignal;
29
+ dispose: () => void;
30
+ throwIfAborted: () => void;
31
+ };
17
32
  export default class Processor {
18
33
  execution: ExecutionModel;
19
34
  options: ProcessorOptions;
@@ -31,11 +46,7 @@ export default class Processor {
31
46
  /**
32
47
  * @experimental
33
48
  */
34
- transaction: Transaction;
35
- /**
36
- * @experimental
37
- */
38
- mainTransaction: Transaction;
49
+ transaction?: Transaction | null;
39
50
  /**
40
51
  * @experimental
41
52
  */
@@ -47,29 +58,70 @@ export default class Processor {
47
58
  private jobsMapByNodeKey;
48
59
  private jobResultsMapByNodeKey;
49
60
  private jobsToSave;
61
+ private rerunContext;
50
62
  /**
51
63
  * @experimental
52
64
  */
53
65
  lastSavedJob: JobModel | null;
66
+ abortController: AbortController;
67
+ timeoutGuard: NodeJS.Timeout | null;
68
+ private runningRegistered;
69
+ private abortReason;
70
+ private aborted;
54
71
  constructor(execution: ExecutionModel, options: ProcessorOptions);
72
+ get abortSignal(): AbortSignal;
73
+ setTimeoutGuard(ms: number): void;
74
+ abortExecution(reason?: string): void;
75
+ isTimeoutAborted(): boolean;
76
+ /**
77
+ * Create an independent abort handle for background work that outlives this processor's
78
+ * run loop (e.g. fire-and-forget instructions that resume the job later). It mirrors the
79
+ * current abort state and sets its own timer based on the execution's `expiresAt`, so the
80
+ * timeout still applies after the processor has exited its synchronous run.
81
+ *
82
+ * The caller must invoke `dispose()` once the background work settles to release the timer
83
+ * and the abort listener.
84
+ */
85
+ createBackgroundAbortHandle(): BackgroundAbortHandle;
86
+ /**
87
+ * Reload a job and return it only when it is still pending, otherwise `null`. Background
88
+ * work uses this before resuming so it never overwrites a job that another path (timeout
89
+ * abort, a competing resume) has already settled.
90
+ */
91
+ findPendingJob(jobId: number | string): Promise<JobModel | null>;
55
92
  private makeNodes;
56
93
  private makeJobs;
57
94
  prepare(): Promise<void>;
58
- start(): Promise<void>;
95
+ start(): Promise<any>;
59
96
  resume(job: JobModel): Promise<void>;
97
+ resolveRerun(options?: ProcessorRerunOptions): {
98
+ node: FlowNodeModel;
99
+ input: JobModel | {
100
+ result: any;
101
+ };
102
+ targetJob: JobModel;
103
+ };
104
+ rerun(options?: ProcessorRerunOptions): Promise<any>;
105
+ private getRerunNode;
106
+ private getRerunInput;
60
107
  private exec;
61
- run(node: any, input?: any): any;
62
- end(node: any, job: JobModel): Promise<any>;
108
+ run(node: FlowNodeModel, input?: JobModel | {
109
+ result: unknown;
110
+ }, options?: ProcessorRunOptions): Promise<any>;
111
+ end(node: FlowNodeModel, job: JobModel): Promise<any>;
63
112
  private recall;
64
113
  exit(s?: number | true): Promise<any>;
65
114
  /**
66
115
  * @experimental
67
116
  */
68
- saveJob(payload: JobModel | Record<string, any>): JobModel;
117
+ saveJob(payload: JobModel | IJob): JobModel;
69
118
  /**
70
119
  * @experimental
71
120
  */
72
121
  getBranches(node: FlowNodeModel): FlowNodeModel[];
122
+ private enterRunningState;
123
+ private shouldContinueExecution;
124
+ private leaveRunningState;
73
125
  /**
74
126
  * @experimental
75
127
  * find the first node in current branch
@@ -79,7 +131,7 @@ export default class Processor {
79
131
  * @experimental
80
132
  * find the node start current branch
81
133
  */
82
- findBranchParentNode(node: FlowNodeModel): FlowNodeModel | null;
134
+ findBranchParentNode(node?: FlowNodeModel): FlowNodeModel | null;
83
135
  /**
84
136
  * @experimental
85
137
  */
@@ -102,7 +154,7 @@ export default class Processor {
102
154
  [key: string]: any;
103
155
  };
104
156
  $system: {};
105
- $scopes: {};
157
+ $scopes: Record<string, any>;
106
158
  $env: {};
107
159
  };
108
160
  $context: any;
@@ -110,7 +162,7 @@ export default class Processor {
110
162
  [key: string]: any;
111
163
  };
112
164
  $system: {};
113
- $scopes: {};
165
+ $scopes: Record<string, any>;
114
166
  $env: {};
115
167
  };
116
168
  /**
@@ -45,6 +45,7 @@ var import_evaluators = require("@nocobase/evaluators");
45
45
  var import_utils = require("@nocobase/utils");
46
46
  var import_set = __toESM(require("lodash/set"));
47
47
  var import_constants = require("./constants");
48
+ var import_timeout_errors = require("./timeout-errors");
48
49
  class Processor {
49
50
  constructor(execution, options) {
50
51
  this.execution = execution;
@@ -66,11 +67,7 @@ class Processor {
66
67
  /**
67
68
  * @experimental
68
69
  */
69
- transaction;
70
- /**
71
- * @experimental
72
- */
73
- mainTransaction;
70
+ transaction = null;
74
71
  /**
75
72
  * @experimental
76
73
  */
@@ -82,10 +79,102 @@ class Processor {
82
79
  jobsMapByNodeKey = {};
83
80
  jobResultsMapByNodeKey = {};
84
81
  jobsToSave = /* @__PURE__ */ new Map();
82
+ rerunContext = null;
85
83
  /**
86
84
  * @experimental
87
85
  */
88
86
  lastSavedJob = null;
87
+ abortController = new AbortController();
88
+ timeoutGuard = null;
89
+ runningRegistered = false;
90
+ abortReason = null;
91
+ aborted = false;
92
+ get abortSignal() {
93
+ return this.abortController.signal;
94
+ }
95
+ setTimeoutGuard(ms) {
96
+ if (this.timeoutGuard) {
97
+ clearTimeout(this.timeoutGuard);
98
+ }
99
+ this.timeoutGuard = setTimeout(() => {
100
+ this.abortExecution(import_constants.EXECUTION_REASON.TIMEOUT);
101
+ }, ms);
102
+ }
103
+ abortExecution(reason) {
104
+ this.aborted = true;
105
+ this.abortReason = reason ?? null;
106
+ if (!this.abortController.signal.aborted) {
107
+ this.abortController.abort(
108
+ reason === import_constants.EXECUTION_REASON.TIMEOUT ? new import_timeout_errors.WorkflowTimeoutError("Workflow execution has been aborted") : new Error("Workflow execution has been aborted")
109
+ );
110
+ }
111
+ }
112
+ isTimeoutAborted() {
113
+ return this.abortSignal.aborted;
114
+ }
115
+ /**
116
+ * Create an independent abort handle for background work that outlives this processor's
117
+ * run loop (e.g. fire-and-forget instructions that resume the job later). It mirrors the
118
+ * current abort state and sets its own timer based on the execution's `expiresAt`, so the
119
+ * timeout still applies after the processor has exited its synchronous run.
120
+ *
121
+ * The caller must invoke `dispose()` once the background work settles to release the timer
122
+ * and the abort listener.
123
+ */
124
+ createBackgroundAbortHandle() {
125
+ const controller = new AbortController();
126
+ const sourceSignal = this.abortSignal;
127
+ let timeoutGuard = null;
128
+ let sourceListener = null;
129
+ const abort = (reason) => {
130
+ if (!controller.signal.aborted) {
131
+ controller.abort((0, import_timeout_errors.isWorkflowTimeoutError)(reason) ? reason : new import_timeout_errors.WorkflowTimeoutError());
132
+ }
133
+ };
134
+ if (sourceSignal.aborted) {
135
+ abort(sourceSignal.reason);
136
+ } else {
137
+ sourceListener = () => abort(sourceSignal.reason);
138
+ sourceSignal.addEventListener("abort", sourceListener, { once: true });
139
+ }
140
+ const remaining = this.execution.expiresAt ? this.execution.expiresAt.getTime() - Date.now() : null;
141
+ if (remaining != null) {
142
+ if (remaining <= 0) {
143
+ abort();
144
+ } else {
145
+ timeoutGuard = setTimeout(abort, remaining);
146
+ }
147
+ }
148
+ return {
149
+ signal: controller.signal,
150
+ dispose: () => {
151
+ if (timeoutGuard) {
152
+ clearTimeout(timeoutGuard);
153
+ timeoutGuard = null;
154
+ }
155
+ if (sourceListener) {
156
+ sourceSignal.removeEventListener("abort", sourceListener);
157
+ sourceListener = null;
158
+ }
159
+ },
160
+ throwIfAborted: () => {
161
+ if (controller.signal.aborted) {
162
+ throw controller.signal.reason ?? new import_timeout_errors.WorkflowTimeoutError();
163
+ }
164
+ }
165
+ };
166
+ }
167
+ /**
168
+ * Reload a job and return it only when it is still pending, otherwise `null`. Background
169
+ * work uses this before resuming so it never overwrites a job that another path (timeout
170
+ * abort, a competing resume) has already settled.
171
+ */
172
+ async findPendingJob(jobId) {
173
+ const job = await this.options.plugin.db.getRepository("jobs").findOne({
174
+ filterByTk: jobId
175
+ });
176
+ return (job == null ? void 0 : job.status) === import_constants.JOB_STATUS.PENDING ? job : null;
177
+ }
89
178
  // make dual linked nodes list then cache
90
179
  makeNodes(nodes = []) {
91
180
  this.nodes = nodes;
@@ -117,12 +206,13 @@ class Processor {
117
206
  execution,
118
207
  options: { plugin }
119
208
  } = this;
120
- this.mainTransaction = plugin.useDataSourceTransaction("main", this.transaction);
121
- const transaction = this.mainTransaction;
122
209
  if (!execution.workflow) {
123
- execution.workflow = plugin.enabledCache.get(execution.workflowId) || await execution.getWorkflow({ transaction });
210
+ execution.workflow = plugin.enabledCache.get(execution.workflowId) || await execution.getWorkflow();
211
+ }
212
+ if (!execution.workflow) {
213
+ throw new Error(`workflow (#${execution.workflowId}) not found for execution (#${execution.id})`);
124
214
  }
125
- const nodes = execution.workflow.nodes || await execution.workflow.getNodes({ transaction });
215
+ const nodes = execution.workflow.nodes || await execution.workflow.getNodes();
126
216
  execution.workflow.nodes = nodes;
127
217
  this.makeNodes(nodes);
128
218
  const JobDBModel = plugin.db.getModel("jobs");
@@ -132,52 +222,130 @@ class Processor {
132
222
  where: {
133
223
  executionId: execution.id
134
224
  },
135
- raw: true,
136
- transaction
225
+ raw: true
137
226
  });
138
227
  const jobs = await execution.getJobs({
139
228
  where: {
140
229
  id: jobIds.map((item) => item.id)
141
230
  },
142
- order: [["id", "ASC"]],
143
- transaction
231
+ order: [["id", "ASC"]]
144
232
  });
145
233
  execution.jobs = jobs;
146
234
  this.makeJobs(jobs);
147
235
  }
148
236
  async start() {
149
237
  const { execution } = this;
150
- if (execution.status) {
238
+ if (!await this.shouldContinueExecution()) {
151
239
  this.logger.warn(`execution was ended with status ${execution.status} before, could not be started again`, {
152
240
  workflowId: execution.workflowId
153
241
  });
154
242
  return;
155
243
  }
156
- await this.prepare();
157
- if (this.nodes.length) {
158
- const head = this.nodes.find((item) => !item.upstream);
159
- await this.run(head, { result: execution.context });
160
- } else {
161
- await this.exit(import_constants.JOB_STATUS.RESOLVED);
244
+ this.enterRunningState();
245
+ try {
246
+ await this.prepare();
247
+ if (this.nodes.length) {
248
+ const head = this.nodes.find((item) => !item.upstream);
249
+ if (!head) {
250
+ this.logger.warn(`head node not found for workflow (${execution.workflowId}), could not be started`, {
251
+ workflowId: execution.workflowId
252
+ });
253
+ return this.exit(import_constants.JOB_STATUS.ERROR);
254
+ }
255
+ await this.run(head);
256
+ } else {
257
+ await this.exit(import_constants.JOB_STATUS.RESOLVED);
258
+ }
259
+ } finally {
260
+ this.leaveRunningState();
162
261
  }
163
262
  }
164
263
  async resume(job) {
165
264
  const { execution } = this;
166
- if (execution.status) {
265
+ if (!await this.shouldContinueExecution()) {
167
266
  this.logger.warn(`execution was ended with status ${execution.status} before, could not be resumed`, {
168
267
  workflowId: execution.workflowId
169
268
  });
170
269
  return;
171
270
  }
172
- await this.prepare();
173
- const node = this.nodesMap.get(job.nodeId);
174
- await this.recall(node, job);
271
+ this.enterRunningState();
272
+ try {
273
+ await this.prepare();
274
+ const node = this.nodesMap.get(job.nodeId);
275
+ await this.recall(node, job);
276
+ } finally {
277
+ this.leaveRunningState();
278
+ }
279
+ }
280
+ resolveRerun(options = {}) {
281
+ const node = this.getRerunNode(options.nodeId);
282
+ const targetJob = this.jobsMapByNodeKey[node.key];
283
+ if (options.nodeId != null && !targetJob) {
284
+ throw new Error(`job of node (#${node.id}) not found in execution (#${this.execution.id})`);
285
+ }
286
+ if (options.nodeId == null && options.overwrite && !targetJob) {
287
+ throw new Error(`job of head node (#${node.id}) not found in execution (#${this.execution.id})`);
288
+ }
289
+ const input = this.getRerunInput(node);
290
+ return { node, input, targetJob };
291
+ }
292
+ async rerun(options = {}) {
293
+ const { execution } = this;
294
+ if (execution.status !== import_constants.EXECUTION_STATUS.STARTED) {
295
+ throw new Error(`execution (#${execution.id}) is not started`);
296
+ }
297
+ if (!await this.shouldContinueExecution()) {
298
+ this.logger.warn(`execution was ended with status ${execution.status} before, could not be rerun`, {
299
+ workflowId: execution.workflowId
300
+ });
301
+ return;
302
+ }
303
+ this.enterRunningState();
304
+ try {
305
+ await this.prepare();
306
+ const { node, input, targetJob } = this.resolveRerun(options);
307
+ this.rerunContext = {
308
+ overwrite: options.overwrite === true,
309
+ targetJob
310
+ };
311
+ return await this.run(node, input, { rerun: true });
312
+ } finally {
313
+ this.rerunContext = null;
314
+ this.leaveRunningState();
315
+ }
175
316
  }
176
- async exec(instruction, node, prevJob) {
317
+ getRerunNode(nodeId) {
318
+ if (nodeId != null) {
319
+ const node = this.nodesMap.get(nodeId) || this.nodes.find((item) => String(item.id) === String(nodeId));
320
+ if (!node) {
321
+ throw new Error(`node (#${nodeId}) not found in workflow (#${this.execution.workflowId})`);
322
+ }
323
+ return node;
324
+ }
325
+ const head = this.nodes.find((item) => !item.upstream);
326
+ if (!head) {
327
+ throw new Error(`head node not found in workflow (#${this.execution.workflowId})`);
328
+ }
329
+ return head;
330
+ }
331
+ getRerunInput(node) {
332
+ if (!node.upstream) {
333
+ return { result: this.execution.context };
334
+ }
335
+ const upstreamJob = this.jobsMapByNodeKey[node.upstream.key];
336
+ if (!upstreamJob) {
337
+ throw new Error(`upstream job of node (#${node.id}) not found in execution (#${this.execution.id})`);
338
+ }
339
+ return upstreamJob;
340
+ }
341
+ async exec(instruction, node, prevJob, options = {}) {
177
342
  let job;
343
+ if (!await this.shouldContinueExecution()) {
344
+ return this.exit();
345
+ }
178
346
  try {
179
347
  this.logger.debug(`config of node`, { data: node.config, workflowId: node.workflowId });
180
- job = await instruction(node, prevJob, this);
348
+ job = await instruction(node, prevJob, this, { ...options, signal: this.abortSignal });
181
349
  if (job === null) {
182
350
  return this.exit();
183
351
  }
@@ -185,18 +353,27 @@ class Processor {
185
353
  return this.exit(true);
186
354
  }
187
355
  } catch (err) {
188
- this.logger.error(
189
- `execution (${this.execution.id}) run instruction [${node.type}] for node (${node.id}) failed: `,
190
- { error: err, workflowId: node.workflowId }
191
- );
192
- job = {
193
- result: err instanceof Error ? {
194
- message: err.message,
195
- ...err
196
- } : err,
197
- status: import_constants.JOB_STATUS.ERROR
198
- };
199
- if (prevJob && prevJob.nodeId === node.id) {
356
+ if ((0, import_timeout_errors.isWorkflowTimeoutError)(err) || this.abortSignal.aborted && this.aborted) {
357
+ job = {
358
+ result: {
359
+ message: err.message
360
+ },
361
+ status: import_constants.JOB_STATUS.ABORTED
362
+ };
363
+ } else {
364
+ this.logger.error(
365
+ `execution (${this.execution.id}) run instruction [${node.type}] for node (${node.id}) failed: `,
366
+ { error: err, workflowId: node.workflowId }
367
+ );
368
+ job = {
369
+ result: err instanceof Error ? {
370
+ ...err,
371
+ message: err.message
372
+ } : err,
373
+ status: import_constants.JOB_STATUS.ERROR
374
+ };
375
+ }
376
+ if (prevJob instanceof import_database.Model && prevJob.nodeId === node.id) {
200
377
  prevJob.set(job);
201
378
  job = prevJob;
202
379
  }
@@ -213,13 +390,16 @@ class Processor {
213
390
  }
214
391
  );
215
392
  this.logger.debug(`result of node`, { data: savedJob.result });
393
+ if (this.execution.status === import_constants.EXECUTION_STATUS.ABORTED || this.isTimeoutAborted()) {
394
+ return this.exit(import_constants.JOB_STATUS.ABORTED);
395
+ }
216
396
  if (savedJob.status === import_constants.JOB_STATUS.RESOLVED && node.downstream) {
217
397
  this.logger.debug(`run next node (${node.downstreamId})`);
218
398
  return this.run(node.downstream, savedJob);
219
399
  }
220
400
  return this.end(node, savedJob);
221
401
  }
222
- async run(node, input) {
402
+ async run(node, input, options) {
223
403
  const { instructions } = this.options.plugin;
224
404
  const instruction = instructions.get(node.type);
225
405
  if (!instruction) {
@@ -231,7 +411,7 @@ class Processor {
231
411
  this.logger.info(`execution (${this.execution.id}) run instruction [${node.type}] for node (${node.id})`, {
232
412
  workflowId: node.workflowId
233
413
  });
234
- return this.exec(instruction.run.bind(instruction), node, input);
414
+ return this.exec(instruction.run.bind(instruction), node, input, options);
235
415
  }
236
416
  // parent node should take over the control
237
417
  async end(node, job) {
@@ -263,6 +443,7 @@ class Processor {
263
443
  return this.exec(instruction.resume.bind(instruction), node, job);
264
444
  }
265
445
  async exit(s) {
446
+ this.leaveRunningState();
266
447
  if (s === true) {
267
448
  return;
268
449
  }
@@ -289,7 +470,7 @@ class Processor {
289
470
  if (changes.length) {
290
471
  await this.options.plugin.db.sequelize.query(
291
472
  `UPDATE ${JobCollection.quotedTableName()} SET ${changes.map(([key]) => `${key} = ?`)} WHERE id='${job.id}'`,
292
- { replacements: changes.map(([, value]) => value), transaction: this.mainTransaction }
473
+ { replacements: changes.map(([, value]) => value) }
293
474
  );
294
475
  }
295
476
  }
@@ -299,7 +480,6 @@ class Processor {
299
480
  await JobsModel.bulkCreate(
300
481
  newJobs.map((job) => job.toJSON()),
301
482
  {
302
- transaction: this.mainTransaction,
303
483
  returning: false
304
484
  }
305
485
  );
@@ -311,10 +491,29 @@ class Processor {
311
491
  }
312
492
  if (typeof s === "number") {
313
493
  const status = this.constructor.StatusMap[s] ?? Math.sign(s);
314
- await this.execution.update({ status }, { transaction: this.mainTransaction });
494
+ const values = { status };
495
+ if (status === import_constants.EXECUTION_STATUS.ABORTED && this.abortReason) {
496
+ values.reason = this.abortReason;
497
+ }
498
+ const ExecutionModelClass = this.options.plugin.db.getModel("executions");
499
+ const [affected] = await ExecutionModelClass.update(values, {
500
+ where: {
501
+ id: this.execution.id,
502
+ status: import_constants.EXECUTION_STATUS.STARTED
503
+ },
504
+ individualHooks: true
505
+ });
506
+ if (affected) {
507
+ this.execution.set(values);
508
+ } else {
509
+ await this.execution.reload();
510
+ }
315
511
  }
316
- if (this.mainTransaction && this.mainTransaction !== this.transaction) {
317
- await this.mainTransaction.commit();
512
+ if (this.execution.status === import_constants.EXECUTION_STATUS.STARTED) {
513
+ this.options.plugin.timeoutManager.scheduleExecutionTimeout(this.execution);
514
+ } else {
515
+ this.options.plugin.timeoutManager.clear(this.execution.id);
516
+ this.options.plugin.timeoutManager.invalidateNextExpiresAtIfMatches(this.execution.expiresAt);
318
517
  }
319
518
  this.logger.info(`execution (${this.execution.id}) exiting with status ${this.execution.status}`, {
320
519
  workflowId: this.execution.workflowId
@@ -325,12 +524,21 @@ class Processor {
325
524
  * @experimental
326
525
  */
327
526
  saveJob(payload) {
527
+ var _a;
328
528
  const { database } = this.execution.constructor;
329
- const { model } = database.getCollection("jobs");
529
+ const model = database.getModel("jobs");
330
530
  let job;
331
531
  if (payload instanceof model) {
332
532
  job = payload;
333
533
  job.set("updatedAt", /* @__PURE__ */ new Date());
534
+ } else if (((_a = this.rerunContext) == null ? void 0 : _a.overwrite) && this.rerunContext.targetJob && this.rerunContext.targetJob.nodeId === payload.nodeId) {
535
+ job = this.rerunContext.targetJob;
536
+ job.set({
537
+ status: payload.status,
538
+ result: Object.prototype.hasOwnProperty.call(payload, "result") ? payload.result : null,
539
+ meta: Object.prototype.hasOwnProperty.call(payload, "meta") ? payload.meta : null,
540
+ updatedAt: /* @__PURE__ */ new Date()
541
+ });
334
542
  } else {
335
543
  job = model.build(
336
544
  {
@@ -345,7 +553,7 @@ class Processor {
345
553
  }
346
554
  );
347
555
  }
348
- this.jobsToSave.set(job.id, job);
556
+ this.jobsToSave.set(job.id.toString(), job);
349
557
  this.lastSavedJob = job;
350
558
  this.jobsMapByNodeKey[job.nodeKey] = job;
351
559
  this.jobResultsMapByNodeKey[job.nodeKey] = job.result;
@@ -360,6 +568,36 @@ class Processor {
360
568
  getBranches(node) {
361
569
  return this.nodes.filter((item) => item.upstream === node && item.branchIndex !== null).sort((a, b) => Number(a.branchIndex) - Number(b.branchIndex));
362
570
  }
571
+ enterRunningState() {
572
+ this.options.plugin.timeoutManager.clear(this.execution.id);
573
+ this.abortReason = null;
574
+ this.aborted = false;
575
+ this.options.plugin.registerRunningExecution(this.execution.id, (reason) => this.abortExecution(reason));
576
+ this.runningRegistered = true;
577
+ const remaining = this.execution.expiresAt ? this.execution.expiresAt.getTime() - Date.now() : null;
578
+ if (remaining == null) {
579
+ return;
580
+ }
581
+ if (remaining <= 0) {
582
+ this.abortExecution(import_constants.EXECUTION_REASON.TIMEOUT);
583
+ return;
584
+ }
585
+ this.setTimeoutGuard(remaining);
586
+ }
587
+ async shouldContinueExecution() {
588
+ return this.options.plugin.timeoutManager.shouldContinue(this.execution);
589
+ }
590
+ leaveRunningState() {
591
+ if (this.timeoutGuard) {
592
+ clearTimeout(this.timeoutGuard);
593
+ this.timeoutGuard = null;
594
+ }
595
+ if (!this.runningRegistered) {
596
+ return;
597
+ }
598
+ this.options.plugin.unregisterRunningExecution(this.execution.id);
599
+ this.runningRegistered = false;
600
+ }
363
601
  /**
364
602
  * @experimental
365
603
  * find the first node in current branch
@@ -423,7 +661,7 @@ class Processor {
423
661
  * @experimental
424
662
  */
425
663
  getScope(sourceNodeId, includeSelfScope = false) {
426
- const node = this.nodesMap.get(sourceNodeId);
664
+ const node = sourceNodeId ? this.nodesMap.get(sourceNodeId) : void 0;
427
665
  const systemFns = {};
428
666
  const scope = {
429
667
  execution: this.execution,
@@ -0,0 +1,18 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ type AbortHandler = {
10
+ abort(reason?: string): void;
11
+ };
12
+ export default class RunningExecutionRegistry {
13
+ private readonly executions;
14
+ register(executionId: number | string, handler: AbortHandler): void;
15
+ unregister(executionId: number | string): void;
16
+ abort(executionId: number | string, reason?: string): boolean;
17
+ }
18
+ export {};
@@ -0,0 +1,48 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+ var RunningExecutionRegistry_exports = {};
28
+ __export(RunningExecutionRegistry_exports, {
29
+ default: () => RunningExecutionRegistry
30
+ });
31
+ module.exports = __toCommonJS(RunningExecutionRegistry_exports);
32
+ class RunningExecutionRegistry {
33
+ executions = /* @__PURE__ */ new Map();
34
+ register(executionId, handler) {
35
+ this.executions.set(String(executionId), handler);
36
+ }
37
+ unregister(executionId) {
38
+ this.executions.delete(String(executionId));
39
+ }
40
+ abort(executionId, reason) {
41
+ const handler = this.executions.get(String(executionId));
42
+ if (!handler) {
43
+ return false;
44
+ }
45
+ handler.abort(reason);
46
+ return true;
47
+ }
48
+ }