@nocobase/plugin-workflow 2.1.30 → 2.1.31
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/externalVersion.js +12 -12
- 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 +24 -13
- package/dist/server/Dispatcher.js +160 -143
- package/dist/server/Plugin.d.ts +16 -7
- package/dist/server/Plugin.js +58 -13
- package/dist/server/Processor.d.ts +1 -7
- package/dist/server/actions/executions.js +3 -6
- package/dist/server/actions/jobs.js +1 -2
- package/dist/server/types/Execution.d.ts +2 -0
- package/dist/server/types/Job.d.ts +5 -0
- package/dist/server/utils.d.ts +1 -0
- package/dist/server/utils.js +5 -0
- package/package.json +2 -2
package/dist/externalVersion.js
CHANGED
|
@@ -11,8 +11,8 @@ module.exports = {
|
|
|
11
11
|
"react": "18.2.0",
|
|
12
12
|
"@formily/core": "2.3.7",
|
|
13
13
|
"@formily/react": "2.3.7",
|
|
14
|
-
"@nocobase/client": "2.1.
|
|
15
|
-
"@nocobase/utils": "2.1.
|
|
14
|
+
"@nocobase/client": "2.1.31",
|
|
15
|
+
"@nocobase/utils": "2.1.31",
|
|
16
16
|
"antd": "5.24.2",
|
|
17
17
|
"@ant-design/icons": "5.6.1",
|
|
18
18
|
"react-router-dom": "6.30.1",
|
|
@@ -20,20 +20,20 @@ module.exports = {
|
|
|
20
20
|
"lodash": "4.18.1",
|
|
21
21
|
"@dnd-kit/core": "6.1.0",
|
|
22
22
|
"@formily/shared": "2.3.7",
|
|
23
|
-
"@nocobase/flow-engine": "2.1.
|
|
24
|
-
"@nocobase/plugin-mobile": "2.1.
|
|
23
|
+
"@nocobase/flow-engine": "2.1.31",
|
|
24
|
+
"@nocobase/plugin-mobile": "2.1.31",
|
|
25
25
|
"sequelize": "6.35.2",
|
|
26
|
-
"@nocobase/server": "2.1.
|
|
27
|
-
"@nocobase/database": "2.1.
|
|
28
|
-
"@nocobase/data-source-manager": "2.1.
|
|
29
|
-
"@nocobase/logger": "2.1.
|
|
30
|
-
"@nocobase/evaluators": "2.1.
|
|
26
|
+
"@nocobase/server": "2.1.31",
|
|
27
|
+
"@nocobase/database": "2.1.31",
|
|
28
|
+
"@nocobase/data-source-manager": "2.1.31",
|
|
29
|
+
"@nocobase/logger": "2.1.31",
|
|
30
|
+
"@nocobase/evaluators": "2.1.31",
|
|
31
31
|
"@formily/antd-v5": "1.2.3",
|
|
32
32
|
"@formily/reactive": "2.3.7",
|
|
33
33
|
"@emotion/css": "11.13.0",
|
|
34
34
|
"@formily/json-schema": "2.3.7",
|
|
35
|
-
"@nocobase/actions": "2.1.
|
|
35
|
+
"@nocobase/actions": "2.1.31",
|
|
36
36
|
"dayjs": "1.11.13",
|
|
37
|
-
"@nocobase/plugin-workflow-test": "2.1.
|
|
38
|
-
"@nocobase/test": "2.1.
|
|
37
|
+
"@nocobase/plugin-workflow-test": "2.1.31",
|
|
38
|
+
"@nocobase/test": "2.1.31"
|
|
39
39
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"cron-parser","version":"4.4.0","description":"Node.js library for parsing crontab instructions","main":"lib/parser.js","types":"index.d.ts","typesVersions":{"<4.1":{"*":["types/ts3/*"]}},"directories":{"test":"test"},"scripts":{"test:tsd":"tsd","test:unit":"TZ=UTC tap ./test/*.js","test:cover":"TZ=UTC tap --coverage-report=html ./test/*.js","lint":"eslint .","lint:fix":"eslint --fix .","test":"npm run lint && npm run test:unit && npm run test:tsd"},"repository":{"type":"git","url":"https://github.com/harrisiirak/cron-parser.git"},"keywords":["cron","crontab","parser"],"author":"Harri Siirak","contributors":["Nicholas Clawson","Daniel Prentis <daniel@salsitasoft.com>","Renault John Lecoultre","Richard Astbury <richard.astbury@gmail.com>","Meaglin Wasabi <Meaglin.wasabi@gmail.com>","Mike Kusold <hello@mikekusold.com>","Alex Kit <alex.kit@atmajs.com>","Santiago Gimeno <santiago.gimeno@gmail.com>","Daniel <darc.tec@gmail.com>","Christian Steininger <christian.steininger.cs@gmail.com>","Mykola Piskovyi <m.piskovyi@gmail.com>","Brian Vaughn <brian.david.vaughn@gmail.com>","Nicholas Clawson <nickclaw@gmail.com>","Yasuhiroki <yasuhiroki.duck@gmail.com>","Nicholas Clawson <nickclaw@gmail.com>","Brendan Warkentin <faazshift@gmail.com>","Charlie Fish <fishcharlie.code@gmail.com>","Ian Graves <ian+diskimage@iangrav.es>","Andy Thompson <me@andytson.com>","Regev Brody <regevbr@gmail.com>"],"license":"MIT","dependencies":{"luxon":"^1.28.0"},"devDependencies":{"eslint":"^8.2.0","sinon":"^10.0.0","tap":"^16.0.1","tsd":"^0.19.0"},"engines":{"node":">=0.8"},"browser":{"fs":false},"tap":{"check-coverage":false},"tsd":{"directory":"test","compilerOptions":{"lib":["es2017","dom"]}},"_lastModified":"2026-07-
|
|
1
|
+
{"name":"cron-parser","version":"4.4.0","description":"Node.js library for parsing crontab instructions","main":"lib/parser.js","types":"index.d.ts","typesVersions":{"<4.1":{"*":["types/ts3/*"]}},"directories":{"test":"test"},"scripts":{"test:tsd":"tsd","test:unit":"TZ=UTC tap ./test/*.js","test:cover":"TZ=UTC tap --coverage-report=html ./test/*.js","lint":"eslint .","lint:fix":"eslint --fix .","test":"npm run lint && npm run test:unit && npm run test:tsd"},"repository":{"type":"git","url":"https://github.com/harrisiirak/cron-parser.git"},"keywords":["cron","crontab","parser"],"author":"Harri Siirak","contributors":["Nicholas Clawson","Daniel Prentis <daniel@salsitasoft.com>","Renault John Lecoultre","Richard Astbury <richard.astbury@gmail.com>","Meaglin Wasabi <Meaglin.wasabi@gmail.com>","Mike Kusold <hello@mikekusold.com>","Alex Kit <alex.kit@atmajs.com>","Santiago Gimeno <santiago.gimeno@gmail.com>","Daniel <darc.tec@gmail.com>","Christian Steininger <christian.steininger.cs@gmail.com>","Mykola Piskovyi <m.piskovyi@gmail.com>","Brian Vaughn <brian.david.vaughn@gmail.com>","Nicholas Clawson <nickclaw@gmail.com>","Yasuhiroki <yasuhiroki.duck@gmail.com>","Nicholas Clawson <nickclaw@gmail.com>","Brendan Warkentin <faazshift@gmail.com>","Charlie Fish <fishcharlie.code@gmail.com>","Ian Graves <ian+diskimage@iangrav.es>","Andy Thompson <me@andytson.com>","Regev Brody <regevbr@gmail.com>"],"license":"MIT","dependencies":{"luxon":"^1.28.0"},"devDependencies":{"eslint":"^8.2.0","sinon":"^10.0.0","tap":"^16.0.1","tsd":"^0.19.0"},"engines":{"node":">=0.8"},"browser":{"fs":false},"tap":{"check-coverage":false},"tsd":{"directory":"test","compilerOptions":{"lib":["es2017","dom"]}},"_lastModified":"2026-07-27T15:40:38.691Z"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"joi","description":"Object schema validation","version":"17.13.3","repository":"git://github.com/hapijs/joi","main":"lib/index.js","types":"lib/index.d.ts","browser":"dist/joi-browser.min.js","files":["lib/**/*","dist/*"],"keywords":["schema","validation"],"dependencies":{"@hapi/hoek":"^9.3.0","@hapi/topo":"^5.1.0","@sideway/address":"^4.1.5","@sideway/formula":"^3.0.1","@sideway/pinpoint":"^2.0.0"},"devDependencies":{"@hapi/bourne":"2.x.x","@hapi/code":"8.x.x","@hapi/joi-legacy-test":"npm:@hapi/joi@15.x.x","@hapi/lab":"^25.1.3","@types/node":"^14.18.63","typescript":"4.3.x"},"scripts":{"prepublishOnly":"cd browser && npm install && npm run build","test":"lab -t 100 -a @hapi/code -L -Y","test-cov-html":"lab -r html -o coverage.html -a @hapi/code"},"license":"BSD-3-Clause","_lastModified":"2026-07-
|
|
1
|
+
{"name":"joi","description":"Object schema validation","version":"17.13.3","repository":"git://github.com/hapijs/joi","main":"lib/index.js","types":"lib/index.d.ts","browser":"dist/joi-browser.min.js","files":["lib/**/*","dist/*"],"keywords":["schema","validation"],"dependencies":{"@hapi/hoek":"^9.3.0","@hapi/topo":"^5.1.0","@sideway/address":"^4.1.5","@sideway/formula":"^3.0.1","@sideway/pinpoint":"^2.0.0"},"devDependencies":{"@hapi/bourne":"2.x.x","@hapi/code":"8.x.x","@hapi/joi-legacy-test":"npm:@hapi/joi@15.x.x","@hapi/lab":"^25.1.3","@types/node":"^14.18.63","typescript":"4.3.x"},"scripts":{"prepublishOnly":"cd browser && npm install && npm run build","test":"lab -t 100 -a @hapi/code -L -Y","test-cov-html":"lab -r html -o coverage.html -a @hapi/code"},"license":"BSD-3-Clause","_lastModified":"2026-07-27T15:40:38.384Z"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"lru-cache","description":"A cache object that deletes the least-recently-used items.","version":"11.3.5","author":"Isaac Z. Schlueter <i@izs.me>","keywords":["mru","lru","cache"],"sideEffects":false,"scripts":{"build":"npm run prepare","prepare":"tshy && bash scripts/build.sh","pretest":"npm run prepare","presnap":"npm run prepare","test":"tap","snap":"tap","preversion":"npm test","postversion":"npm publish","prepublishOnly":"git push origin --follow-tags","format":"prettier --write .","typedoc":"typedoc --tsconfig ./.tshy/esm.json ./src/*.ts","benchmark-results-typedoc":"bash scripts/benchmark-results-typedoc.sh","prebenchmark":"npm run prepare","benchmark":"make -C benchmark","preprofile":"npm run prepare","profile":"make -C benchmark profile","lint":"oxlint --fix src test","postsnap":"npm run lint","postlint":"npm run format"},"main":"./dist/commonjs/index.min.js","types":"./dist/commonjs/index.d.ts","tshy":{"esmDialects":["node","browser"],"exports":{"./raw":"./src/index.ts",".":{"import":{"node":{"types":"./dist/esm/node/index.d.ts","default":"./dist/esm/node/index.min.js"},"browser":{"types":"./dist/esm/browser/index.d.ts","default":"./dist/esm/browser/index.min.js"},"types":"./dist/esm/index.d.ts","default":"./dist/esm/index.min.js"},"require":{"types":"./dist/commonjs/index.d.ts","default":"./dist/commonjs/index.min.js"}}},"selfLink":false},"repository":{"type":"git","url":"git+ssh://git@github.com/isaacs/node-lru-cache.git"},"devDependencies":{"benchmark":"^2.1.4","esbuild":"^0.25.9","marked":"^4.2.12","mkdirp":"^3.0.1","oxlint":"^1.58.0","oxlint-tsgolint":"^0.19.0","prettier":"^3.8.1","tap":"^21.6.3","tshy":"^4.1.1","typedoc":"^0.28.18"},"license":"BlueOak-1.0.0","files":["dist"],"engines":{"node":"20 || >=22"},"exports":{"./raw":{"import":{"node":{"types":"./dist/esm/node/index.d.ts","default":"./dist/esm/node/index.js"},"browser":{"types":"./dist/esm/browser/index.d.ts","default":"./dist/esm/browser/index.js"},"types":"./dist/esm/index.d.ts","default":"./dist/esm/index.js"},"require":{"types":"./dist/commonjs/index.d.ts","default":"./dist/commonjs/index.js"}},".":{"import":{"node":{"types":"./dist/esm/node/index.d.ts","default":"./dist/esm/node/index.min.js"},"browser":{"types":"./dist/esm/browser/index.d.ts","default":"./dist/esm/browser/index.min.js"},"types":"./dist/esm/index.d.ts","default":"./dist/esm/index.min.js"},"require":{"types":"./dist/commonjs/index.d.ts","default":"./dist/commonjs/index.min.js"}}},"type":"module","module":"./dist/esm/index.min.js","_lastModified":"2026-07-
|
|
1
|
+
{"name":"lru-cache","description":"A cache object that deletes the least-recently-used items.","version":"11.3.5","author":"Isaac Z. Schlueter <i@izs.me>","keywords":["mru","lru","cache"],"sideEffects":false,"scripts":{"build":"npm run prepare","prepare":"tshy && bash scripts/build.sh","pretest":"npm run prepare","presnap":"npm run prepare","test":"tap","snap":"tap","preversion":"npm test","postversion":"npm publish","prepublishOnly":"git push origin --follow-tags","format":"prettier --write .","typedoc":"typedoc --tsconfig ./.tshy/esm.json ./src/*.ts","benchmark-results-typedoc":"bash scripts/benchmark-results-typedoc.sh","prebenchmark":"npm run prepare","benchmark":"make -C benchmark","preprofile":"npm run prepare","profile":"make -C benchmark profile","lint":"oxlint --fix src test","postsnap":"npm run lint","postlint":"npm run format"},"main":"./dist/commonjs/index.min.js","types":"./dist/commonjs/index.d.ts","tshy":{"esmDialects":["node","browser"],"exports":{"./raw":"./src/index.ts",".":{"import":{"node":{"types":"./dist/esm/node/index.d.ts","default":"./dist/esm/node/index.min.js"},"browser":{"types":"./dist/esm/browser/index.d.ts","default":"./dist/esm/browser/index.min.js"},"types":"./dist/esm/index.d.ts","default":"./dist/esm/index.min.js"},"require":{"types":"./dist/commonjs/index.d.ts","default":"./dist/commonjs/index.min.js"}}},"selfLink":false},"repository":{"type":"git","url":"git+ssh://git@github.com/isaacs/node-lru-cache.git"},"devDependencies":{"benchmark":"^2.1.4","esbuild":"^0.25.9","marked":"^4.2.12","mkdirp":"^3.0.1","oxlint":"^1.58.0","oxlint-tsgolint":"^0.19.0","prettier":"^3.8.1","tap":"^21.6.3","tshy":"^4.1.1","typedoc":"^0.28.18"},"license":"BlueOak-1.0.0","files":["dist"],"engines":{"node":"20 || >=22"},"exports":{"./raw":{"import":{"node":{"types":"./dist/esm/node/index.d.ts","default":"./dist/esm/node/index.js"},"browser":{"types":"./dist/esm/browser/index.d.ts","default":"./dist/esm/browser/index.js"},"types":"./dist/esm/index.d.ts","default":"./dist/esm/index.js"},"require":{"types":"./dist/commonjs/index.d.ts","default":"./dist/commonjs/index.js"}},".":{"import":{"node":{"types":"./dist/esm/node/index.d.ts","default":"./dist/esm/node/index.min.js"},"browser":{"types":"./dist/esm/browser/index.d.ts","default":"./dist/esm/browser/index.min.js"},"types":"./dist/esm/index.d.ts","default":"./dist/esm/index.min.js"},"require":{"types":"./dist/commonjs/index.d.ts","default":"./dist/commonjs/index.min.js"}}},"type":"module","module":"./dist/esm/index.min.js","_lastModified":"2026-07-27T15:40:37.569Z"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"nodejs-snowflake","collaborators":["Utkarsh Srivastava <utkarsh@sagacious.dev>"],"description":"Generate time sortable 64 bits unique ids for distributed systems (inspired from twitter snowflake)","version":"2.0.1","license":"Apache 2.0","repository":{"type":"git","url":"https://github.com/utkarsh-pro/nodejs-snowflake.git"},"files":["nodejs_snowflake_bg.wasm","nodejs_snowflake.js","nodejs_snowflake.d.ts"],"main":"nodejs_snowflake.js","types":"nodejs_snowflake.d.ts","_lastModified":"2026-07-
|
|
1
|
+
{"name":"nodejs-snowflake","collaborators":["Utkarsh Srivastava <utkarsh@sagacious.dev>"],"description":"Generate time sortable 64 bits unique ids for distributed systems (inspired from twitter snowflake)","version":"2.0.1","license":"Apache 2.0","repository":{"type":"git","url":"https://github.com/utkarsh-pro/nodejs-snowflake.git"},"files":["nodejs_snowflake_bg.wasm","nodejs_snowflake.js","nodejs_snowflake.d.ts"],"main":"nodejs_snowflake.js","types":"nodejs_snowflake.d.ts","_lastModified":"2026-07-27T15:40:37.383Z"}
|
|
@@ -9,14 +9,21 @@
|
|
|
9
9
|
import { Transactionable } from 'sequelize';
|
|
10
10
|
import type { QueueEventOptions } from '@nocobase/server';
|
|
11
11
|
import Processor, { ProcessorRerunOptions } from './Processor';
|
|
12
|
-
import type {
|
|
12
|
+
import type { WorkflowModel } from './types';
|
|
13
13
|
import type PluginWorkflowServer from './Plugin';
|
|
14
|
-
type
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
rerun?:
|
|
19
|
-
|
|
14
|
+
type ID = number | string;
|
|
15
|
+
type WorkflowQueueTask = {
|
|
16
|
+
executionId: ID;
|
|
17
|
+
jobId?: never;
|
|
18
|
+
rerun?: never;
|
|
19
|
+
} | {
|
|
20
|
+
executionId: ID;
|
|
21
|
+
jobId: ID;
|
|
22
|
+
rerun?: never;
|
|
23
|
+
} | {
|
|
24
|
+
executionId: ID;
|
|
25
|
+
jobId?: never;
|
|
26
|
+
rerun: ProcessorRerunOptions;
|
|
20
27
|
};
|
|
21
28
|
export type EventOptions = {
|
|
22
29
|
eventKey?: string;
|
|
@@ -33,22 +40,26 @@ export default class Dispatcher {
|
|
|
33
40
|
private readonly plugin;
|
|
34
41
|
private ready;
|
|
35
42
|
private executing;
|
|
43
|
+
private recovering;
|
|
36
44
|
private saving;
|
|
37
|
-
private pending;
|
|
38
45
|
private events;
|
|
39
46
|
private eventsCount;
|
|
40
47
|
get idle(): boolean;
|
|
41
48
|
constructor(plugin: PluginWorkflowServer);
|
|
42
|
-
readonly
|
|
49
|
+
readonly onQueueTask: QueueEventOptions['process'];
|
|
43
50
|
setReady(ready: boolean): void;
|
|
44
51
|
getEventsCount(): number;
|
|
45
52
|
trigger(workflow: WorkflowModel, context: object, options?: EventOptions): void | Promise<Processor | null | void>;
|
|
46
53
|
private saveEvent;
|
|
47
|
-
resume(job: JobModel): Promise<void>;
|
|
48
|
-
start(execution: ExecutionModel): Promise<void>;
|
|
49
54
|
beforeStop(): Promise<void>;
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
recover(options?: {
|
|
56
|
+
gracePeriod?: number;
|
|
57
|
+
}): Promise<void>;
|
|
58
|
+
private recoverQueueTasks;
|
|
59
|
+
enqueue(task: WorkflowQueueTask): Promise<void>;
|
|
60
|
+
private loadJob;
|
|
61
|
+
private findQueueingExecutions;
|
|
62
|
+
private resolveTask;
|
|
52
63
|
private triggerSync;
|
|
53
64
|
private validateEvent;
|
|
54
65
|
private handleTriggerFail;
|
|
@@ -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
|
|
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.
|
|
52
|
+
return this.ready && !this.executing && !this.saving && !this.events.length;
|
|
51
53
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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;
|
|
@@ -118,20 +140,12 @@ class Dispatcher {
|
|
|
118
140
|
try {
|
|
119
141
|
const execution = await this.createExecution(...event);
|
|
120
142
|
if (!execution.dispatched) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
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
|
-
|
|
187
|
-
if (!this.ready
|
|
183
|
+
async recover(options = {}) {
|
|
184
|
+
if (!this.ready) {
|
|
188
185
|
return;
|
|
189
186
|
}
|
|
190
|
-
if (this.
|
|
191
|
-
this.
|
|
187
|
+
if (this.recovering) {
|
|
188
|
+
await this.recovering;
|
|
192
189
|
return;
|
|
193
190
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
})();
|
|
235
|
+
}, RECOVERY_LOCK_TTL);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
logger.error(`workflow queue recovery failed`, { error });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
async enqueue(task) {
|
|
241
|
+
await this.plugin.app.eventQueue.publish(this.plugin.channelPendingExecution, task, {
|
|
242
|
+
maxRetries: QUEUE_TASK_MAX_ATTEMPTS - 1
|
|
243
|
+
});
|
|
251
244
|
}
|
|
252
|
-
async
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
245
|
+
async loadJob(jobId) {
|
|
246
|
+
if (jobId == null) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
return this.plugin.db.getRepository("jobs").findOne({
|
|
250
|
+
filterByTk: jobId
|
|
256
251
|
});
|
|
257
|
-
|
|
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;
|
|
@@ -367,7 +413,7 @@ class Dispatcher {
|
|
|
367
413
|
return execution;
|
|
368
414
|
}
|
|
369
415
|
async prepare(input, options = {}) {
|
|
370
|
-
const logger =
|
|
416
|
+
const logger = this.plugin.getLogger(input.workflowId);
|
|
371
417
|
if (options.transaction) {
|
|
372
418
|
try {
|
|
373
419
|
return await this.acquireExecution(input, options, options.transaction);
|
|
@@ -402,8 +448,8 @@ class Dispatcher {
|
|
|
402
448
|
},
|
|
403
449
|
{
|
|
404
450
|
logger,
|
|
405
|
-
conflictMessage:
|
|
406
|
-
maxAttemptsMessage:
|
|
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`
|
|
407
453
|
}
|
|
408
454
|
);
|
|
409
455
|
} catch (error) {
|
|
@@ -415,39 +461,10 @@ class Dispatcher {
|
|
|
415
461
|
return result;
|
|
416
462
|
}
|
|
417
463
|
async acquireExecution(input, options, transaction) {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
if (!options.immediate || execution.status !== import_constants.EXECUTION_STATUS.QUEUEING) {
|
|
421
|
-
await execution.reload({ transaction });
|
|
422
|
-
}
|
|
423
|
-
} else {
|
|
424
|
-
const workflowIds = [...this.plugin.enabledCache.keys()];
|
|
425
|
-
if (!workflowIds.length) {
|
|
426
|
-
this.plugin.getLogger("dispatcher").debug(`no enabled workflow to process`);
|
|
427
|
-
return null;
|
|
428
|
-
}
|
|
429
|
-
execution = await this.plugin.db.getRepository("executions").findOne({
|
|
430
|
-
filter: {
|
|
431
|
-
dispatched: false,
|
|
432
|
-
status: import_constants.EXECUTION_STATUS.QUEUEING,
|
|
433
|
-
startedAt: null,
|
|
434
|
-
workflowId: workflowIds
|
|
435
|
-
},
|
|
436
|
-
sort: "id",
|
|
437
|
-
transaction,
|
|
438
|
-
lock: transaction.LOCK.UPDATE,
|
|
439
|
-
skipLocked: true
|
|
440
|
-
});
|
|
441
|
-
if (execution) {
|
|
442
|
-
this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) fetched from db`);
|
|
443
|
-
} else {
|
|
444
|
-
this.plugin.getLogger("dispatcher").debug(`no execution in db queued to process`);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
if (!execution) {
|
|
448
|
-
return null;
|
|
464
|
+
if (!options.immediate || input.status !== import_constants.EXECUTION_STATUS.QUEUEING) {
|
|
465
|
+
await input.reload({ transaction });
|
|
449
466
|
}
|
|
450
|
-
return this.enter(
|
|
467
|
+
return this.enter(input, transaction);
|
|
451
468
|
}
|
|
452
469
|
async acquireWithRetry(acquire, options) {
|
|
453
470
|
for (let attempt = 1; attempt <= EXECUTION_ACQUIRE_MAX_ATTEMPTS; attempt++) {
|
|
@@ -528,7 +545,7 @@ class Dispatcher {
|
|
|
528
545
|
var _a, _b, _c;
|
|
529
546
|
if (!execution.dispatched) {
|
|
530
547
|
await execution.update({ dispatched: true, status: import_constants.EXECUTION_STATUS.STARTED });
|
|
531
|
-
logger.info(`execution (${execution.id}) from
|
|
548
|
+
logger.info(`execution (${execution.id}) from workflow queue updated to started`);
|
|
532
549
|
}
|
|
533
550
|
this.plugin.timeoutManager.scheduleExecutionTimeout(execution);
|
|
534
551
|
const processor = this.plugin.createProcessor(execution, processorOptions);
|
package/dist/server/Plugin.d.ts
CHANGED
|
@@ -11,14 +11,14 @@ import { Transactionable } from 'sequelize';
|
|
|
11
11
|
import { Plugin } from '@nocobase/server';
|
|
12
12
|
import { Registry } from '@nocobase/utils';
|
|
13
13
|
import { Logger } from '@nocobase/logger';
|
|
14
|
-
import
|
|
15
|
-
import Processor from './Processor';
|
|
14
|
+
import { EventOptions } from './Dispatcher';
|
|
15
|
+
import Processor, { ProcessorRerunOptions } from './Processor';
|
|
16
16
|
import ExecutionTimeoutManager from './ExecutionTimeoutManager';
|
|
17
17
|
import RunningExecutionRegistry from './RunningExecutionRegistry';
|
|
18
18
|
import { CustomFunction } from './functions';
|
|
19
19
|
import Trigger from './triggers';
|
|
20
20
|
import { InstructionInterface } from './instructions';
|
|
21
|
-
import type { ExecutionModel, WorkflowModel } from './types';
|
|
21
|
+
import type { ExecutionModel, JobModel, WorkflowModel } from './types';
|
|
22
22
|
import type { Transaction } from '@nocobase/database';
|
|
23
23
|
type ID = number | string;
|
|
24
24
|
export declare const WORKER_JOB_WORKFLOW_PROCESS = "workflow:process";
|
|
@@ -75,15 +75,24 @@ export default class PluginWorkflowServer extends Plugin {
|
|
|
75
75
|
load(): Promise<void>;
|
|
76
76
|
private toggle;
|
|
77
77
|
trigger(workflow: WorkflowModel, context: object, options?: EventOptions): void | Promise<Processor | null | void>;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
rerun(execution: ExecutionModel | ID, rerun?: ProcessorRerunOptions): Promise<void>;
|
|
79
|
+
recover(): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Persist the current job state and publish a poke that evaluates that state.
|
|
82
|
+
*
|
|
83
|
+
* PENDING resume calls are valid for instructions such as delay and sequential approval.
|
|
84
|
+
* Callers that aggregate multiple user actions must only call this method when their
|
|
85
|
+
* atomic update changes the job from PENDING to a terminal status.
|
|
86
|
+
* Persistence and queue publication failures are logged and rethrown to awaiting callers.
|
|
87
|
+
*/
|
|
88
|
+
resume(job: JobModel): Promise<void>;
|
|
81
89
|
/**
|
|
82
90
|
* Start a deferred execution
|
|
83
91
|
* @experimental
|
|
84
92
|
*/
|
|
85
|
-
start(execution: ExecutionModel): Promise<void>;
|
|
93
|
+
start(execution: ExecutionModel | ID): Promise<void>;
|
|
86
94
|
createProcessor(execution: ExecutionModel, options?: {}): Processor;
|
|
95
|
+
private getModelId;
|
|
87
96
|
execute(workflow: WorkflowModel, values: any, options?: EventOptions): Promise<void | Processor>;
|
|
88
97
|
/**
|
|
89
98
|
* @experimental
|
package/dist/server/Plugin.js
CHANGED
|
@@ -53,6 +53,7 @@ var import_RunningExecutionRegistry = __toESM(require("./RunningExecutionRegistr
|
|
|
53
53
|
var import_actions = __toESM(require("./actions"));
|
|
54
54
|
var import_nodes = require("./actions/nodes");
|
|
55
55
|
var import_workflows = require("./actions/workflows");
|
|
56
|
+
var import_constants = require("./constants");
|
|
56
57
|
var import_functions = __toESM(require("./functions"));
|
|
57
58
|
var import_CollectionTrigger = __toESM(require("./triggers/CollectionTrigger"));
|
|
58
59
|
var import_ScheduleTrigger = __toESM(require("./triggers/ScheduleTrigger"));
|
|
@@ -170,16 +171,16 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
170
171
|
}
|
|
171
172
|
}
|
|
172
173
|
this.checker = setInterval(() => {
|
|
173
|
-
this.dispatcher.
|
|
174
|
+
this.dispatcher.recover({ gracePeriod: 6e4 });
|
|
174
175
|
}, 3e5);
|
|
175
176
|
await this.timeoutManager.load();
|
|
176
|
-
this.app.on("workflow:
|
|
177
|
-
this.app.logger.info("workflow:
|
|
178
|
-
this.dispatcher.
|
|
177
|
+
this.app.on("workflow:recover", () => {
|
|
178
|
+
this.app.logger.info("workflow:recover");
|
|
179
|
+
this.dispatcher.recover();
|
|
179
180
|
});
|
|
180
181
|
this.dispatcher.setReady(true);
|
|
181
182
|
this.getLogger("dispatcher").info("(starting) check for queueing executions");
|
|
182
|
-
this.dispatcher.
|
|
183
|
+
this.dispatcher.recover();
|
|
183
184
|
};
|
|
184
185
|
onBeforeStop = async () => {
|
|
185
186
|
if (this.checker) {
|
|
@@ -191,7 +192,6 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
191
192
|
for (const workflow of this.enabledCache.values()) {
|
|
192
193
|
this.toggle(workflow, false, { silent: true });
|
|
193
194
|
}
|
|
194
|
-
this.app.eventQueue.unsubscribe(this.channelPendingExecution);
|
|
195
195
|
this.loggerCache.clear();
|
|
196
196
|
};
|
|
197
197
|
async handleSyncMessage(message) {
|
|
@@ -401,7 +401,7 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
401
401
|
this.app.on("beforeStop", this.onBeforeStop);
|
|
402
402
|
this.app.eventQueue.subscribe(this.channelPendingExecution, {
|
|
403
403
|
idle: () => this.serving() && this.dispatcher.idle,
|
|
404
|
-
process: this.dispatcher.
|
|
404
|
+
process: this.dispatcher.onQueueTask
|
|
405
405
|
});
|
|
406
406
|
}
|
|
407
407
|
toggle(workflow, enable, { silent, transaction } = {}) {
|
|
@@ -448,25 +448,70 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
448
448
|
trigger(workflow, context, options = {}) {
|
|
449
449
|
return this.dispatcher.trigger(workflow, context, options);
|
|
450
450
|
}
|
|
451
|
-
async
|
|
452
|
-
return this.dispatcher.
|
|
451
|
+
async rerun(execution, rerun) {
|
|
452
|
+
return this.dispatcher.enqueue({ executionId: this.getModelId(execution), rerun: rerun ?? {} });
|
|
453
453
|
}
|
|
454
|
-
|
|
455
|
-
return this.dispatcher.
|
|
454
|
+
recover() {
|
|
455
|
+
return this.dispatcher.recover();
|
|
456
456
|
}
|
|
457
|
+
/**
|
|
458
|
+
* Persist the current job state and publish a poke that evaluates that state.
|
|
459
|
+
*
|
|
460
|
+
* PENDING resume calls are valid for instructions such as delay and sequential approval.
|
|
461
|
+
* Callers that aggregate multiple user actions must only call this method when their
|
|
462
|
+
* atomic update changes the job from PENDING to a terminal status.
|
|
463
|
+
* Persistence and queue publication failures are logged and rethrown to awaiting callers.
|
|
464
|
+
*/
|
|
457
465
|
async resume(job) {
|
|
458
|
-
|
|
466
|
+
var _a;
|
|
467
|
+
const executionId = job.executionId ?? ((_a = job.execution) == null ? void 0 : _a.id);
|
|
468
|
+
if (executionId == null) {
|
|
469
|
+
this.getLogger("dispatcher").warn(`execution id of job (${job.id}) not found, resume ignored`);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
try {
|
|
473
|
+
if (job.changed()) {
|
|
474
|
+
await job.save();
|
|
475
|
+
}
|
|
476
|
+
} catch (error) {
|
|
477
|
+
this.getLogger("dispatcher").error(
|
|
478
|
+
`persisting job (${job.id}) before resuming execution (${executionId}) failed`,
|
|
479
|
+
{ error, executionId, jobId: job.id }
|
|
480
|
+
);
|
|
481
|
+
throw error;
|
|
482
|
+
}
|
|
483
|
+
try {
|
|
484
|
+
await this.dispatcher.enqueue({ executionId, jobId: job.id });
|
|
485
|
+
this.getLogger("dispatcher").info(`execution (${executionId}) resuming from job (${job.id}) published to queue`);
|
|
486
|
+
} catch (error) {
|
|
487
|
+
this.getLogger("dispatcher").error(
|
|
488
|
+
`publishing resume task for execution (${executionId}) from job (${job.id}) failed`,
|
|
489
|
+
{ error, executionId, jobId: job.id }
|
|
490
|
+
);
|
|
491
|
+
throw error;
|
|
492
|
+
}
|
|
459
493
|
}
|
|
460
494
|
/**
|
|
461
495
|
* Start a deferred execution
|
|
462
496
|
* @experimental
|
|
463
497
|
*/
|
|
464
498
|
async start(execution) {
|
|
465
|
-
|
|
499
|
+
if (typeof execution !== "string" && typeof execution !== "number") {
|
|
500
|
+
if (execution.status !== import_constants.EXECUTION_STATUS.QUEUEING && execution.status !== import_constants.EXECUTION_STATUS.STARTED && execution.status != null) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (execution.status === import_constants.EXECUTION_STATUS.STARTED && execution.startedAt) {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return this.dispatcher.enqueue({ executionId: this.getModelId(execution) });
|
|
466
508
|
}
|
|
467
509
|
createProcessor(execution, options = {}) {
|
|
468
510
|
return new import_Processor.default(execution, { ...options, plugin: this });
|
|
469
511
|
}
|
|
512
|
+
getModelId(input) {
|
|
513
|
+
return typeof input === "string" || typeof input === "number" ? input : input.id;
|
|
514
|
+
}
|
|
470
515
|
async execute(workflow, values, options = {}) {
|
|
471
516
|
const trigger = this.triggers.get(workflow.type);
|
|
472
517
|
if (!trigger) {
|
|
@@ -94,13 +94,7 @@ export default class Processor {
|
|
|
94
94
|
prepare(): Promise<void>;
|
|
95
95
|
start(): Promise<any>;
|
|
96
96
|
resume(job: JobModel): Promise<void>;
|
|
97
|
-
resolveRerun
|
|
98
|
-
node: FlowNodeModel;
|
|
99
|
-
input: JobModel | {
|
|
100
|
-
result: any;
|
|
101
|
-
};
|
|
102
|
-
targetJob: JobModel;
|
|
103
|
-
};
|
|
97
|
+
private resolveRerun;
|
|
104
98
|
rerun(options?: ProcessorRerunOptions): Promise<any>;
|
|
105
99
|
private getRerunNode;
|
|
106
100
|
private getRerunInput;
|
|
@@ -98,12 +98,9 @@ async function rerun(context, next) {
|
|
|
98
98
|
if (execution.status !== import_constants.EXECUTION_STATUS.STARTED) {
|
|
99
99
|
return context.throw(400, "Only started executions can be rerun");
|
|
100
100
|
}
|
|
101
|
-
await workflowPlugin.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
nodeId,
|
|
105
|
-
overwrite: overwrite === true
|
|
106
|
-
}
|
|
101
|
+
await workflowPlugin.rerun(execution, {
|
|
102
|
+
nodeId,
|
|
103
|
+
overwrite: overwrite === true
|
|
107
104
|
});
|
|
108
105
|
context.body = execution;
|
|
109
106
|
context.status = 202;
|
|
@@ -82,8 +82,7 @@ async function resume(context, next) {
|
|
|
82
82
|
context.body = job;
|
|
83
83
|
context.status = 202;
|
|
84
84
|
await next();
|
|
85
|
-
job
|
|
86
|
-
workflowPlugin.resume(job);
|
|
85
|
+
await workflowPlugin.resume(job);
|
|
87
86
|
}
|
|
88
87
|
// Annotate the CommonJS export names for ESM import in node:
|
|
89
88
|
0 && (module.exports = {
|
|
@@ -12,11 +12,13 @@ import WorkflowModel from './Workflow';
|
|
|
12
12
|
export default class ExecutionModel extends Model {
|
|
13
13
|
static readonly database: Database;
|
|
14
14
|
id: number;
|
|
15
|
+
workflowId: number;
|
|
15
16
|
title: string;
|
|
16
17
|
context: any;
|
|
17
18
|
status: number;
|
|
18
19
|
reason?: string | null;
|
|
19
20
|
dispatched: boolean;
|
|
21
|
+
manually?: boolean | null;
|
|
20
22
|
parentExecutionId?: number | null;
|
|
21
23
|
stack?: Array<number | string>;
|
|
22
24
|
startedAt?: Date | null;
|
|
@@ -8,8 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { BelongsToGetAssociationMixin, Model } from '@nocobase/database';
|
|
10
10
|
import FlowNodeModel from './FlowNode';
|
|
11
|
+
import ExecutionModel from './Execution';
|
|
11
12
|
export default class JobModel extends Model {
|
|
12
13
|
id: number;
|
|
14
|
+
executionId: number;
|
|
15
|
+
workflowId?: number;
|
|
13
16
|
status: number;
|
|
14
17
|
result?: any;
|
|
15
18
|
meta?: any;
|
|
@@ -20,4 +23,6 @@ export default class JobModel extends Model {
|
|
|
20
23
|
nodeId: number;
|
|
21
24
|
node?: FlowNodeModel;
|
|
22
25
|
getNode: BelongsToGetAssociationMixin<FlowNodeModel>;
|
|
26
|
+
execution?: ExecutionModel;
|
|
27
|
+
getExecution: BelongsToGetAssociationMixin<ExecutionModel>;
|
|
23
28
|
}
|
package/dist/server/utils.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ type AbortOptions = Transactionable & {
|
|
|
16
16
|
reason?: (typeof EXECUTION_REASON)[keyof typeof EXECUTION_REASON];
|
|
17
17
|
};
|
|
18
18
|
export declare function getExecutionLockKey(executionId: number | string): string;
|
|
19
|
+
export declare function getJobLockKey(jobId: number | string): string;
|
|
19
20
|
export declare function isLockAcquireError(error: unknown): boolean;
|
|
20
21
|
export declare function validateCollectionField(collection: string, dataSourceManager: DataSourceManager): Record<string, string> | null;
|
|
21
22
|
export declare function getExecutionStatusName(status: number | null | undefined): string;
|
package/dist/server/utils.js
CHANGED
|
@@ -29,6 +29,7 @@ __export(utils_exports, {
|
|
|
29
29
|
abortExecution: () => abortExecution,
|
|
30
30
|
getExecutionLockKey: () => getExecutionLockKey,
|
|
31
31
|
getExecutionStatusName: () => getExecutionStatusName,
|
|
32
|
+
getJobLockKey: () => getJobLockKey,
|
|
32
33
|
getWorkflowExecutionLogMeta: () => getWorkflowExecutionLogMeta,
|
|
33
34
|
isLockAcquireError: () => isLockAcquireError,
|
|
34
35
|
toJSON: () => toJSON,
|
|
@@ -41,6 +42,9 @@ var import_constants = require("./constants");
|
|
|
41
42
|
function getExecutionLockKey(executionId) {
|
|
42
43
|
return `workflow:execution:${executionId}`;
|
|
43
44
|
}
|
|
45
|
+
function getJobLockKey(jobId) {
|
|
46
|
+
return `workflow:job:${jobId}`;
|
|
47
|
+
}
|
|
44
48
|
function isLockAcquireError(error) {
|
|
45
49
|
return error instanceof Error && error.constructor.name === "LockAcquireError";
|
|
46
50
|
}
|
|
@@ -197,6 +201,7 @@ function toJSON(data) {
|
|
|
197
201
|
abortExecution,
|
|
198
202
|
getExecutionLockKey,
|
|
199
203
|
getExecutionStatusName,
|
|
204
|
+
getJobLockKey,
|
|
200
205
|
getWorkflowExecutionLogMeta,
|
|
201
206
|
isLockAcquireError,
|
|
202
207
|
toJSON,
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"description": "A powerful BPM tool that provides foundational support for business automation, with the capability to extend unlimited triggers and nodes.",
|
|
7
7
|
"description.zh-CN": "一个强大的 BPM 工具,为业务自动化提供基础支持,并且可任意扩展更多的触发器和节点。",
|
|
8
8
|
"description.ru-RU": "Мощный инструмент BPM, обеспечивающий базовую поддержку автоматизации бизнес-процессов с возможностью неограниченного расширения триггеров и узлов.",
|
|
9
|
-
"version": "2.1.
|
|
9
|
+
"version": "2.1.31",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"main": "./dist/server/index.js",
|
|
12
12
|
"homepage": "https://docs.nocobase.com/handbook/workflow",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@nocobase/test": "2.x",
|
|
50
50
|
"@nocobase/utils": "2.x"
|
|
51
51
|
},
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "b5d46b76adb72b4fa736dd0e6c89d8a0bc83bfe8",
|
|
53
53
|
"keywords": [
|
|
54
54
|
"Workflow"
|
|
55
55
|
]
|