@nocobase/plugin-workflow 2.1.0-beta.37 → 2.1.0-beta.38
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.
- package/dist/client/618.19af7f84261c815d.js +10 -0
- package/dist/client/964.ffbf5b47ed12bbdc.js +10 -0
- package/dist/client/components/TimeoutInput.d.ts +11 -0
- package/dist/client/constants.d.ts +13 -0
- package/dist/client/index.js +1 -1
- package/dist/client/schemas/executions.d.ts +42 -0
- package/dist/common/collections/executions.d.ts +42 -0
- package/dist/common/collections/executions.js +50 -1
- package/dist/common/collections/workflows.d.ts +6 -2
- package/dist/common/collections/workflows.js +3 -1
- package/dist/common/constants.d.ts +5 -0
- package/dist/common/constants.js +7 -0
- package/dist/externalVersion.js +12 -12
- package/dist/locale/de-DE.json +4 -0
- package/dist/locale/en-US.json +7 -0
- package/dist/locale/es-ES.json +4 -0
- package/dist/locale/fr-FR.json +4 -0
- package/dist/locale/hu-HU.json +7 -3
- package/dist/locale/id-ID.json +4 -0
- package/dist/locale/it-IT.json +4 -0
- package/dist/locale/ja-JP.json +5 -1
- package/dist/locale/ko-KR.json +4 -0
- package/dist/locale/nl-NL.json +7 -3
- package/dist/locale/pt-BR.json +4 -0
- package/dist/locale/ru-RU.json +4 -0
- package/dist/locale/tr-TR.json +4 -0
- package/dist/locale/uk-UA.json +7 -3
- package/dist/locale/vi-VN.json +7 -3
- package/dist/locale/zh-CN.json +8 -0
- package/dist/locale/zh-TW.json +7 -3
- package/dist/node_modules/cron-parser/package.json +1 -1
- package/dist/node_modules/joi/package.json +1 -1
- package/dist/node_modules/lru-cache/package.json +1 -1
- package/dist/node_modules/nodejs-snowflake/package.json +1 -1
- package/dist/server/Dispatcher.d.ts +9 -7
- package/dist/server/Dispatcher.js +190 -122
- package/dist/server/ExecutionTimeoutManager.d.ts +45 -0
- package/dist/server/ExecutionTimeoutManager.js +312 -0
- package/dist/server/Plugin.d.ts +12 -0
- package/dist/server/Plugin.js +21 -1
- package/dist/server/Processor.d.ts +65 -9
- package/dist/server/Processor.js +285 -33
- package/dist/server/RunningExecutionRegistry.d.ts +18 -0
- package/dist/server/RunningExecutionRegistry.js +48 -0
- package/dist/server/actions/executions.d.ts +4 -3
- package/dist/server/actions/executions.js +42 -21
- package/dist/server/actions/jobs.d.ts +2 -1
- package/dist/server/actions/jobs.js +28 -1
- package/dist/server/constants.d.ts +2 -0
- package/dist/server/constants.js +3 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +2 -0
- package/dist/server/instructions/index.d.ts +10 -3
- package/dist/server/migrations/20260501120000-workflow-timeout.d.ts +13 -0
- package/dist/server/migrations/20260501120000-workflow-timeout.js +63 -0
- package/dist/server/timeout-errors.d.ts +13 -0
- package/dist/server/timeout-errors.js +47 -0
- package/dist/server/types/Execution.d.ts +6 -0
- package/dist/server/types/Job.d.ts +3 -3
- package/dist/server/types/Workflow.d.ts +6 -1
- package/dist/server/utils.d.ts +11 -1
- package/dist/server/utils.js +92 -5
- package/dist/swagger/index.d.ts +22 -0
- package/dist/swagger/index.js +22 -0
- package/package.json +2 -2
- package/dist/client/261.7722d7400942730e.js +0 -10
- package/dist/client/964.6251d37b35710747.js +0 -10
package/dist/server/Processor.js
CHANGED
|
@@ -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,11 @@ class Processor {
|
|
|
66
67
|
/**
|
|
67
68
|
* @experimental
|
|
68
69
|
*/
|
|
69
|
-
transaction;
|
|
70
|
+
transaction = null;
|
|
70
71
|
/**
|
|
71
72
|
* @experimental
|
|
72
73
|
*/
|
|
73
|
-
mainTransaction;
|
|
74
|
+
mainTransaction = null;
|
|
74
75
|
/**
|
|
75
76
|
* @experimental
|
|
76
77
|
*/
|
|
@@ -82,10 +83,102 @@ class Processor {
|
|
|
82
83
|
jobsMapByNodeKey = {};
|
|
83
84
|
jobResultsMapByNodeKey = {};
|
|
84
85
|
jobsToSave = /* @__PURE__ */ new Map();
|
|
86
|
+
rerunContext = null;
|
|
85
87
|
/**
|
|
86
88
|
* @experimental
|
|
87
89
|
*/
|
|
88
90
|
lastSavedJob = null;
|
|
91
|
+
abortController = new AbortController();
|
|
92
|
+
timeoutGuard = null;
|
|
93
|
+
runningRegistered = false;
|
|
94
|
+
abortReason = null;
|
|
95
|
+
aborted = false;
|
|
96
|
+
get abortSignal() {
|
|
97
|
+
return this.abortController.signal;
|
|
98
|
+
}
|
|
99
|
+
setTimeoutGuard(ms) {
|
|
100
|
+
if (this.timeoutGuard) {
|
|
101
|
+
clearTimeout(this.timeoutGuard);
|
|
102
|
+
}
|
|
103
|
+
this.timeoutGuard = setTimeout(() => {
|
|
104
|
+
this.abortExecution(import_constants.EXECUTION_REASON.TIMEOUT);
|
|
105
|
+
}, ms);
|
|
106
|
+
}
|
|
107
|
+
abortExecution(reason) {
|
|
108
|
+
this.aborted = true;
|
|
109
|
+
this.abortReason = reason ?? null;
|
|
110
|
+
if (!this.abortController.signal.aborted) {
|
|
111
|
+
this.abortController.abort(
|
|
112
|
+
reason === import_constants.EXECUTION_REASON.TIMEOUT ? new import_timeout_errors.WorkflowTimeoutError("Workflow execution has been aborted") : new Error("Workflow execution has been aborted")
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
isTimeoutAborted() {
|
|
117
|
+
return this.abortSignal.aborted;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Create an independent abort handle for background work that outlives this processor's
|
|
121
|
+
* run loop (e.g. fire-and-forget instructions that resume the job later). It mirrors the
|
|
122
|
+
* current abort state and sets its own timer based on the execution's `expiresAt`, so the
|
|
123
|
+
* timeout still applies after the processor has exited its synchronous run.
|
|
124
|
+
*
|
|
125
|
+
* The caller must invoke `dispose()` once the background work settles to release the timer
|
|
126
|
+
* and the abort listener.
|
|
127
|
+
*/
|
|
128
|
+
createBackgroundAbortHandle() {
|
|
129
|
+
const controller = new AbortController();
|
|
130
|
+
const sourceSignal = this.abortSignal;
|
|
131
|
+
let timeoutGuard = null;
|
|
132
|
+
let sourceListener = null;
|
|
133
|
+
const abort = (reason) => {
|
|
134
|
+
if (!controller.signal.aborted) {
|
|
135
|
+
controller.abort((0, import_timeout_errors.isWorkflowTimeoutError)(reason) ? reason : new import_timeout_errors.WorkflowTimeoutError());
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
if (sourceSignal.aborted) {
|
|
139
|
+
abort(sourceSignal.reason);
|
|
140
|
+
} else {
|
|
141
|
+
sourceListener = () => abort(sourceSignal.reason);
|
|
142
|
+
sourceSignal.addEventListener("abort", sourceListener, { once: true });
|
|
143
|
+
}
|
|
144
|
+
const remaining = this.execution.expiresAt ? this.execution.expiresAt.getTime() - Date.now() : null;
|
|
145
|
+
if (remaining != null) {
|
|
146
|
+
if (remaining <= 0) {
|
|
147
|
+
abort();
|
|
148
|
+
} else {
|
|
149
|
+
timeoutGuard = setTimeout(abort, remaining);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
signal: controller.signal,
|
|
154
|
+
dispose: () => {
|
|
155
|
+
if (timeoutGuard) {
|
|
156
|
+
clearTimeout(timeoutGuard);
|
|
157
|
+
timeoutGuard = null;
|
|
158
|
+
}
|
|
159
|
+
if (sourceListener) {
|
|
160
|
+
sourceSignal.removeEventListener("abort", sourceListener);
|
|
161
|
+
sourceListener = null;
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
throwIfAborted: () => {
|
|
165
|
+
if (controller.signal.aborted) {
|
|
166
|
+
throw controller.signal.reason ?? new import_timeout_errors.WorkflowTimeoutError();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Reload a job and return it only when it is still pending, otherwise `null`. Background
|
|
173
|
+
* work uses this before resuming so it never overwrites a job that another path (timeout
|
|
174
|
+
* abort, a competing resume) has already settled.
|
|
175
|
+
*/
|
|
176
|
+
async findPendingJob(jobId) {
|
|
177
|
+
const job = await this.options.plugin.db.getRepository("jobs").findOne({
|
|
178
|
+
filterByTk: jobId
|
|
179
|
+
});
|
|
180
|
+
return (job == null ? void 0 : job.status) === import_constants.JOB_STATUS.PENDING ? job : null;
|
|
181
|
+
}
|
|
89
182
|
// make dual linked nodes list then cache
|
|
90
183
|
makeNodes(nodes = []) {
|
|
91
184
|
this.nodes = nodes;
|
|
@@ -122,6 +215,9 @@ class Processor {
|
|
|
122
215
|
if (!execution.workflow) {
|
|
123
216
|
execution.workflow = plugin.enabledCache.get(execution.workflowId) || await execution.getWorkflow({ transaction });
|
|
124
217
|
}
|
|
218
|
+
if (!execution.workflow) {
|
|
219
|
+
throw new Error(`workflow (#${execution.workflowId}) not found for execution (#${execution.id})`);
|
|
220
|
+
}
|
|
125
221
|
const nodes = execution.workflow.nodes || await execution.workflow.getNodes({ transaction });
|
|
126
222
|
execution.workflow.nodes = nodes;
|
|
127
223
|
this.makeNodes(nodes);
|
|
@@ -147,37 +243,117 @@ class Processor {
|
|
|
147
243
|
}
|
|
148
244
|
async start() {
|
|
149
245
|
const { execution } = this;
|
|
150
|
-
if (
|
|
246
|
+
if (!await this.shouldContinueExecution()) {
|
|
151
247
|
this.logger.warn(`execution was ended with status ${execution.status} before, could not be started again`, {
|
|
152
248
|
workflowId: execution.workflowId
|
|
153
249
|
});
|
|
154
250
|
return;
|
|
155
251
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
252
|
+
this.enterRunningState();
|
|
253
|
+
try {
|
|
254
|
+
await this.prepare();
|
|
255
|
+
if (this.nodes.length) {
|
|
256
|
+
const head = this.nodes.find((item) => !item.upstream);
|
|
257
|
+
if (!head) {
|
|
258
|
+
this.logger.warn(`head node not found for workflow (${execution.workflowId}), could not be started`, {
|
|
259
|
+
workflowId: execution.workflowId
|
|
260
|
+
});
|
|
261
|
+
return this.exit(import_constants.JOB_STATUS.ERROR);
|
|
262
|
+
}
|
|
263
|
+
await this.run(head);
|
|
264
|
+
} else {
|
|
265
|
+
await this.exit(import_constants.JOB_STATUS.RESOLVED);
|
|
266
|
+
}
|
|
267
|
+
} finally {
|
|
268
|
+
this.leaveRunningState();
|
|
162
269
|
}
|
|
163
270
|
}
|
|
164
271
|
async resume(job) {
|
|
165
272
|
const { execution } = this;
|
|
166
|
-
if (
|
|
273
|
+
if (!await this.shouldContinueExecution()) {
|
|
167
274
|
this.logger.warn(`execution was ended with status ${execution.status} before, could not be resumed`, {
|
|
168
275
|
workflowId: execution.workflowId
|
|
169
276
|
});
|
|
170
277
|
return;
|
|
171
278
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
279
|
+
this.enterRunningState();
|
|
280
|
+
try {
|
|
281
|
+
await this.prepare();
|
|
282
|
+
const node = this.nodesMap.get(job.nodeId);
|
|
283
|
+
await this.recall(node, job);
|
|
284
|
+
} finally {
|
|
285
|
+
this.leaveRunningState();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
resolveRerun(options = {}) {
|
|
289
|
+
const node = this.getRerunNode(options.nodeId);
|
|
290
|
+
const targetJob = this.jobsMapByNodeKey[node.key];
|
|
291
|
+
if (options.nodeId != null && !targetJob) {
|
|
292
|
+
throw new Error(`job of node (#${node.id}) not found in execution (#${this.execution.id})`);
|
|
293
|
+
}
|
|
294
|
+
if (options.nodeId == null && options.overwrite && !targetJob) {
|
|
295
|
+
throw new Error(`job of head node (#${node.id}) not found in execution (#${this.execution.id})`);
|
|
296
|
+
}
|
|
297
|
+
const input = this.getRerunInput(node);
|
|
298
|
+
return { node, input, targetJob };
|
|
299
|
+
}
|
|
300
|
+
async rerun(options = {}) {
|
|
301
|
+
const { execution } = this;
|
|
302
|
+
if (execution.status !== import_constants.EXECUTION_STATUS.STARTED) {
|
|
303
|
+
throw new Error(`execution (#${execution.id}) is not started`);
|
|
304
|
+
}
|
|
305
|
+
if (!await this.shouldContinueExecution()) {
|
|
306
|
+
this.logger.warn(`execution was ended with status ${execution.status} before, could not be rerun`, {
|
|
307
|
+
workflowId: execution.workflowId
|
|
308
|
+
});
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
this.enterRunningState();
|
|
312
|
+
try {
|
|
313
|
+
await this.prepare();
|
|
314
|
+
const { node, input, targetJob } = this.resolveRerun(options);
|
|
315
|
+
this.rerunContext = {
|
|
316
|
+
overwrite: options.overwrite === true,
|
|
317
|
+
targetJob
|
|
318
|
+
};
|
|
319
|
+
return await this.run(node, input, { rerun: true });
|
|
320
|
+
} finally {
|
|
321
|
+
this.rerunContext = null;
|
|
322
|
+
this.leaveRunningState();
|
|
323
|
+
}
|
|
175
324
|
}
|
|
176
|
-
|
|
325
|
+
getRerunNode(nodeId) {
|
|
326
|
+
if (nodeId != null) {
|
|
327
|
+
const node = this.nodesMap.get(nodeId) || this.nodes.find((item) => String(item.id) === String(nodeId));
|
|
328
|
+
if (!node) {
|
|
329
|
+
throw new Error(`node (#${nodeId}) not found in workflow (#${this.execution.workflowId})`);
|
|
330
|
+
}
|
|
331
|
+
return node;
|
|
332
|
+
}
|
|
333
|
+
const head = this.nodes.find((item) => !item.upstream);
|
|
334
|
+
if (!head) {
|
|
335
|
+
throw new Error(`head node not found in workflow (#${this.execution.workflowId})`);
|
|
336
|
+
}
|
|
337
|
+
return head;
|
|
338
|
+
}
|
|
339
|
+
getRerunInput(node) {
|
|
340
|
+
if (!node.upstream) {
|
|
341
|
+
return { result: this.execution.context };
|
|
342
|
+
}
|
|
343
|
+
const upstreamJob = this.jobsMapByNodeKey[node.upstream.key];
|
|
344
|
+
if (!upstreamJob) {
|
|
345
|
+
throw new Error(`upstream job of node (#${node.id}) not found in execution (#${this.execution.id})`);
|
|
346
|
+
}
|
|
347
|
+
return upstreamJob;
|
|
348
|
+
}
|
|
349
|
+
async exec(instruction, node, prevJob, options = {}) {
|
|
177
350
|
let job;
|
|
351
|
+
if (!await this.shouldContinueExecution()) {
|
|
352
|
+
return this.exit();
|
|
353
|
+
}
|
|
178
354
|
try {
|
|
179
355
|
this.logger.debug(`config of node`, { data: node.config, workflowId: node.workflowId });
|
|
180
|
-
job = await instruction(node, prevJob, this);
|
|
356
|
+
job = await instruction(node, prevJob, this, { ...options, signal: this.abortSignal });
|
|
181
357
|
if (job === null) {
|
|
182
358
|
return this.exit();
|
|
183
359
|
}
|
|
@@ -185,18 +361,27 @@ class Processor {
|
|
|
185
361
|
return this.exit(true);
|
|
186
362
|
}
|
|
187
363
|
} catch (err) {
|
|
188
|
-
this.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
364
|
+
if ((0, import_timeout_errors.isWorkflowTimeoutError)(err) || this.abortSignal.aborted && this.aborted) {
|
|
365
|
+
job = {
|
|
366
|
+
result: {
|
|
367
|
+
message: err.message
|
|
368
|
+
},
|
|
369
|
+
status: import_constants.JOB_STATUS.ABORTED
|
|
370
|
+
};
|
|
371
|
+
} else {
|
|
372
|
+
this.logger.error(
|
|
373
|
+
`execution (${this.execution.id}) run instruction [${node.type}] for node (${node.id}) failed: `,
|
|
374
|
+
{ error: err, workflowId: node.workflowId }
|
|
375
|
+
);
|
|
376
|
+
job = {
|
|
377
|
+
result: err instanceof Error ? {
|
|
378
|
+
...err,
|
|
379
|
+
message: err.message
|
|
380
|
+
} : err,
|
|
381
|
+
status: import_constants.JOB_STATUS.ERROR
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
if (prevJob instanceof import_database.Model && prevJob.nodeId === node.id) {
|
|
200
385
|
prevJob.set(job);
|
|
201
386
|
job = prevJob;
|
|
202
387
|
}
|
|
@@ -213,13 +398,16 @@ class Processor {
|
|
|
213
398
|
}
|
|
214
399
|
);
|
|
215
400
|
this.logger.debug(`result of node`, { data: savedJob.result });
|
|
401
|
+
if (this.execution.status === import_constants.EXECUTION_STATUS.ABORTED || this.isTimeoutAborted()) {
|
|
402
|
+
return this.exit(import_constants.JOB_STATUS.ABORTED);
|
|
403
|
+
}
|
|
216
404
|
if (savedJob.status === import_constants.JOB_STATUS.RESOLVED && node.downstream) {
|
|
217
405
|
this.logger.debug(`run next node (${node.downstreamId})`);
|
|
218
406
|
return this.run(node.downstream, savedJob);
|
|
219
407
|
}
|
|
220
408
|
return this.end(node, savedJob);
|
|
221
409
|
}
|
|
222
|
-
async run(node, input) {
|
|
410
|
+
async run(node, input, options) {
|
|
223
411
|
const { instructions } = this.options.plugin;
|
|
224
412
|
const instruction = instructions.get(node.type);
|
|
225
413
|
if (!instruction) {
|
|
@@ -231,7 +419,7 @@ class Processor {
|
|
|
231
419
|
this.logger.info(`execution (${this.execution.id}) run instruction [${node.type}] for node (${node.id})`, {
|
|
232
420
|
workflowId: node.workflowId
|
|
233
421
|
});
|
|
234
|
-
return this.exec(instruction.run.bind(instruction), node, input);
|
|
422
|
+
return this.exec(instruction.run.bind(instruction), node, input, options);
|
|
235
423
|
}
|
|
236
424
|
// parent node should take over the control
|
|
237
425
|
async end(node, job) {
|
|
@@ -263,6 +451,7 @@ class Processor {
|
|
|
263
451
|
return this.exec(instruction.resume.bind(instruction), node, job);
|
|
264
452
|
}
|
|
265
453
|
async exit(s) {
|
|
454
|
+
this.leaveRunningState();
|
|
266
455
|
if (s === true) {
|
|
267
456
|
return;
|
|
268
457
|
}
|
|
@@ -311,11 +500,34 @@ class Processor {
|
|
|
311
500
|
}
|
|
312
501
|
if (typeof s === "number") {
|
|
313
502
|
const status = this.constructor.StatusMap[s] ?? Math.sign(s);
|
|
314
|
-
|
|
503
|
+
const values = { status };
|
|
504
|
+
if (status === import_constants.EXECUTION_STATUS.ABORTED && this.abortReason) {
|
|
505
|
+
values.reason = this.abortReason;
|
|
506
|
+
}
|
|
507
|
+
const ExecutionModelClass = this.options.plugin.db.getModel("executions");
|
|
508
|
+
const [affected] = await ExecutionModelClass.update(values, {
|
|
509
|
+
where: {
|
|
510
|
+
id: this.execution.id,
|
|
511
|
+
status: import_constants.EXECUTION_STATUS.STARTED
|
|
512
|
+
},
|
|
513
|
+
individualHooks: true,
|
|
514
|
+
transaction: this.mainTransaction
|
|
515
|
+
});
|
|
516
|
+
if (affected) {
|
|
517
|
+
this.execution.set(values);
|
|
518
|
+
} else {
|
|
519
|
+
await this.execution.reload({ transaction: this.mainTransaction });
|
|
520
|
+
}
|
|
315
521
|
}
|
|
316
522
|
if (this.mainTransaction && this.mainTransaction !== this.transaction) {
|
|
317
523
|
await this.mainTransaction.commit();
|
|
318
524
|
}
|
|
525
|
+
if (this.execution.status === import_constants.EXECUTION_STATUS.STARTED) {
|
|
526
|
+
this.options.plugin.timeoutManager.scheduleExecutionTimeout(this.execution);
|
|
527
|
+
} else {
|
|
528
|
+
this.options.plugin.timeoutManager.clear(this.execution.id);
|
|
529
|
+
this.options.plugin.timeoutManager.invalidateNextExpiresAtIfMatches(this.execution.expiresAt);
|
|
530
|
+
}
|
|
319
531
|
this.logger.info(`execution (${this.execution.id}) exiting with status ${this.execution.status}`, {
|
|
320
532
|
workflowId: this.execution.workflowId
|
|
321
533
|
});
|
|
@@ -325,12 +537,21 @@ class Processor {
|
|
|
325
537
|
* @experimental
|
|
326
538
|
*/
|
|
327
539
|
saveJob(payload) {
|
|
540
|
+
var _a;
|
|
328
541
|
const { database } = this.execution.constructor;
|
|
329
|
-
const
|
|
542
|
+
const model = database.getModel("jobs");
|
|
330
543
|
let job;
|
|
331
544
|
if (payload instanceof model) {
|
|
332
545
|
job = payload;
|
|
333
546
|
job.set("updatedAt", /* @__PURE__ */ new Date());
|
|
547
|
+
} else if (((_a = this.rerunContext) == null ? void 0 : _a.overwrite) && this.rerunContext.targetJob && this.rerunContext.targetJob.nodeId === payload.nodeId) {
|
|
548
|
+
job = this.rerunContext.targetJob;
|
|
549
|
+
job.set({
|
|
550
|
+
status: payload.status,
|
|
551
|
+
result: Object.prototype.hasOwnProperty.call(payload, "result") ? payload.result : null,
|
|
552
|
+
meta: Object.prototype.hasOwnProperty.call(payload, "meta") ? payload.meta : null,
|
|
553
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
554
|
+
});
|
|
334
555
|
} else {
|
|
335
556
|
job = model.build(
|
|
336
557
|
{
|
|
@@ -345,7 +566,7 @@ class Processor {
|
|
|
345
566
|
}
|
|
346
567
|
);
|
|
347
568
|
}
|
|
348
|
-
this.jobsToSave.set(job.id, job);
|
|
569
|
+
this.jobsToSave.set(job.id.toString(), job);
|
|
349
570
|
this.lastSavedJob = job;
|
|
350
571
|
this.jobsMapByNodeKey[job.nodeKey] = job;
|
|
351
572
|
this.jobResultsMapByNodeKey[job.nodeKey] = job.result;
|
|
@@ -360,6 +581,37 @@ class Processor {
|
|
|
360
581
|
getBranches(node) {
|
|
361
582
|
return this.nodes.filter((item) => item.upstream === node && item.branchIndex !== null).sort((a, b) => Number(a.branchIndex) - Number(b.branchIndex));
|
|
362
583
|
}
|
|
584
|
+
enterRunningState() {
|
|
585
|
+
this.options.plugin.timeoutManager.clear(this.execution.id);
|
|
586
|
+
this.abortReason = null;
|
|
587
|
+
this.aborted = false;
|
|
588
|
+
this.options.plugin.registerRunningExecution(this.execution.id, (reason) => this.abortExecution(reason));
|
|
589
|
+
this.runningRegistered = true;
|
|
590
|
+
const remaining = this.execution.expiresAt ? this.execution.expiresAt.getTime() - Date.now() : null;
|
|
591
|
+
if (remaining == null) {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
if (remaining <= 0) {
|
|
595
|
+
this.abortExecution(import_constants.EXECUTION_REASON.TIMEOUT);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
this.setTimeoutGuard(remaining);
|
|
599
|
+
}
|
|
600
|
+
async shouldContinueExecution() {
|
|
601
|
+
const transaction = this.mainTransaction ?? this.transaction;
|
|
602
|
+
return this.options.plugin.timeoutManager.shouldContinue(this.execution, { transaction });
|
|
603
|
+
}
|
|
604
|
+
leaveRunningState() {
|
|
605
|
+
if (this.timeoutGuard) {
|
|
606
|
+
clearTimeout(this.timeoutGuard);
|
|
607
|
+
this.timeoutGuard = null;
|
|
608
|
+
}
|
|
609
|
+
if (!this.runningRegistered) {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
this.options.plugin.unregisterRunningExecution(this.execution.id);
|
|
613
|
+
this.runningRegistered = false;
|
|
614
|
+
}
|
|
363
615
|
/**
|
|
364
616
|
* @experimental
|
|
365
617
|
* find the first node in current branch
|
|
@@ -423,7 +675,7 @@ class Processor {
|
|
|
423
675
|
* @experimental
|
|
424
676
|
*/
|
|
425
677
|
getScope(sourceNodeId, includeSelfScope = false) {
|
|
426
|
-
const node = this.nodesMap.get(sourceNodeId);
|
|
678
|
+
const node = sourceNodeId ? this.nodesMap.get(sourceNodeId) : void 0;
|
|
427
679
|
const systemFns = {};
|
|
428
680
|
const scope = {
|
|
429
681
|
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
|
+
}
|
|
@@ -6,6 +6,7 @@
|
|
|
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
|
-
import { Context } from '@nocobase/actions';
|
|
10
|
-
export declare function destroy(context: Context, next:
|
|
11
|
-
export declare function cancel(context: Context, next:
|
|
9
|
+
import { Context, Next } from '@nocobase/actions';
|
|
10
|
+
export declare function destroy(context: Context, next: Next): Promise<void>;
|
|
11
|
+
export declare function cancel(context: Context, next: Next): Promise<never>;
|
|
12
|
+
export declare function rerun(context: Context, next: Next): Promise<never>;
|
|
@@ -37,12 +37,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
37
37
|
var executions_exports = {};
|
|
38
38
|
__export(executions_exports, {
|
|
39
39
|
cancel: () => cancel,
|
|
40
|
-
destroy: () => destroy
|
|
40
|
+
destroy: () => destroy,
|
|
41
|
+
rerun: () => rerun
|
|
41
42
|
});
|
|
42
43
|
module.exports = __toCommonJS(executions_exports);
|
|
43
44
|
var import_actions = __toESM(require("@nocobase/actions"));
|
|
44
45
|
var import_database = require("@nocobase/database");
|
|
46
|
+
var import_Plugin = __toESM(require("../Plugin"));
|
|
45
47
|
var import_constants = require("../constants");
|
|
48
|
+
var import_utils = require("../utils");
|
|
46
49
|
async function destroy(context, next) {
|
|
47
50
|
context.action.mergeParams({
|
|
48
51
|
filter: {
|
|
@@ -55,8 +58,8 @@ async function destroy(context, next) {
|
|
|
55
58
|
}
|
|
56
59
|
async function cancel(context, next) {
|
|
57
60
|
const { filterByTk } = context.action.params;
|
|
61
|
+
const workflowPlugin = context.app.pm.get(import_Plugin.default);
|
|
58
62
|
const ExecutionRepo = context.db.getRepository("executions");
|
|
59
|
-
const JobRepo = context.db.getRepository("jobs");
|
|
60
63
|
const execution = await ExecutionRepo.findOne({
|
|
61
64
|
filterByTk,
|
|
62
65
|
appends: ["jobs"]
|
|
@@ -67,30 +70,48 @@ async function cancel(context, next) {
|
|
|
67
70
|
if (execution.status) {
|
|
68
71
|
return context.throw(400);
|
|
69
72
|
}
|
|
70
|
-
|
|
71
|
-
await execution.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
73
|
+
try {
|
|
74
|
+
const lock = await context.app.lockManager.tryAcquire((0, import_utils.getExecutionLockKey)(execution.id));
|
|
75
|
+
await lock.runExclusive(async () => {
|
|
76
|
+
await (0, import_utils.abortExecution)(workflowPlugin, execution, { reason: import_constants.EXECUTION_REASON.MANUAL_CANCEL });
|
|
77
|
+
}, 6e4);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if ((0, import_utils.isLockAcquireError)(error)) {
|
|
80
|
+
return context.throw(409, "Execution is being processed");
|
|
81
|
+
}
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
context.body = execution;
|
|
85
|
+
await next();
|
|
86
|
+
}
|
|
87
|
+
async function rerun(context, next) {
|
|
88
|
+
const workflowPlugin = context.app.pm.get(import_Plugin.default);
|
|
89
|
+
const { filterByTk, values = {} } = context.action.params;
|
|
90
|
+
const { nodeId, overwrite } = values;
|
|
91
|
+
const ExecutionRepo = context.db.getRepository("executions");
|
|
92
|
+
const execution = await ExecutionRepo.findOne({
|
|
93
|
+
filterByTk
|
|
94
|
+
});
|
|
95
|
+
if (!execution) {
|
|
96
|
+
return context.throw(404);
|
|
97
|
+
}
|
|
98
|
+
if (execution.status !== import_constants.EXECUTION_STATUS.STARTED) {
|
|
99
|
+
return context.throw(400, "Only started executions can be rerun");
|
|
100
|
+
}
|
|
101
|
+
await workflowPlugin.run({
|
|
102
|
+
execution,
|
|
103
|
+
rerun: {
|
|
104
|
+
nodeId,
|
|
105
|
+
overwrite: overwrite === true
|
|
106
|
+
}
|
|
88
107
|
});
|
|
89
108
|
context.body = execution;
|
|
109
|
+
context.status = 202;
|
|
90
110
|
await next();
|
|
91
111
|
}
|
|
92
112
|
// Annotate the CommonJS export names for ESM import in node:
|
|
93
113
|
0 && (module.exports = {
|
|
94
114
|
cancel,
|
|
95
|
-
destroy
|
|
115
|
+
destroy,
|
|
116
|
+
rerun
|
|
96
117
|
});
|
|
@@ -6,4 +6,5 @@
|
|
|
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
|
-
|
|
9
|
+
import { Context, Next } from '@nocobase/actions';
|
|
10
|
+
export declare function resume(context: Context, next: Next): Promise<never>;
|
|
@@ -40,7 +40,10 @@ __export(jobs_exports, {
|
|
|
40
40
|
});
|
|
41
41
|
module.exports = __toCommonJS(jobs_exports);
|
|
42
42
|
var import_actions = require("@nocobase/actions");
|
|
43
|
+
var import_constants = require("../constants");
|
|
43
44
|
var import_Plugin = __toESM(require("../Plugin"));
|
|
45
|
+
var import_constants2 = require("../../common/constants");
|
|
46
|
+
var import_utils = require("../utils");
|
|
44
47
|
async function resume(context, next) {
|
|
45
48
|
const repository = import_actions.utils.getRepositoryFromParams(context);
|
|
46
49
|
const workflowPlugin = context.app.pm.get(import_Plugin.default);
|
|
@@ -51,11 +54,35 @@ async function resume(context, next) {
|
|
|
51
54
|
if (!job) {
|
|
52
55
|
return context.throw(404, "Job not found");
|
|
53
56
|
}
|
|
57
|
+
if (!job.execution) {
|
|
58
|
+
job.execution = await job.getExecution();
|
|
59
|
+
}
|
|
54
60
|
workflowPlugin.getLogger(job.workflowId).warn(`Resuming job #${job.id}...`);
|
|
55
|
-
|
|
61
|
+
const execution = job.execution;
|
|
62
|
+
if (!execution) {
|
|
63
|
+
return context.throw(400, "Execution is not running");
|
|
64
|
+
}
|
|
65
|
+
if (await workflowPlugin.abortExecutionIfExpired(execution)) {
|
|
66
|
+
return context.throw(400, context.t("Execution timed out", { ns: import_constants2.NAMESPACE }));
|
|
67
|
+
}
|
|
68
|
+
if (execution.status !== import_constants.EXECUTION_STATUS.STARTED) {
|
|
69
|
+
return context.throw(400, "Execution is not running");
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const lock = await context.app.lockManager.tryAcquire((0, import_utils.getExecutionLockKey)(job.execution.id));
|
|
73
|
+
await lock.runExclusive(async () => {
|
|
74
|
+
await job.update(values);
|
|
75
|
+
}, 6e4);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if ((0, import_utils.isLockAcquireError)(error)) {
|
|
78
|
+
return context.throw(409, "Execution is being processed");
|
|
79
|
+
}
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
56
82
|
context.body = job;
|
|
57
83
|
context.status = 202;
|
|
58
84
|
await next();
|
|
85
|
+
job.execution = execution;
|
|
59
86
|
workflowPlugin.resume(job);
|
|
60
87
|
}
|
|
61
88
|
// Annotate the CommonJS export names for ESM import in node:
|