@nocobase/plugin-workflow 1.8.26 → 1.8.28
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/.env.example +6 -0
- package/dist/client/RemoveNodeContext.d.ts +11 -0
- package/dist/client/a88f009ee4ae9e85.js +10 -0
- package/dist/client/fa4bbdbaacf97d69.js +10 -0
- package/dist/client/index.js +1 -1
- package/dist/client/nodes/condition.d.ts +0 -3
- package/dist/externalVersion.js +11 -11
- package/dist/locale/zh-CN.json +14 -3
- package/dist/node_modules/cron-parser/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 +47 -1
- package/dist/server/Dispatcher.js +372 -1
- package/dist/server/Plugin.d.ts +4 -24
- package/dist/server/Plugin.js +16 -317
- package/dist/server/actions/nodes.js +86 -22
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +0 -2
- package/dist/server/triggers/CollectionTrigger.d.ts +1 -1
- package/dist/server/triggers/ScheduleTrigger/DateFieldScheduleTrigger.d.ts +5 -5
- package/dist/server/triggers/ScheduleTrigger/DateFieldScheduleTrigger.js +33 -13
- package/package.json +2 -2
- package/dist/client/3b0762a72796b5f8.js +0 -10
- package/dist/client/48fc0fadf459229d.js +0 -10
package/dist/externalVersion.js
CHANGED
|
@@ -11,8 +11,8 @@ module.exports = {
|
|
|
11
11
|
"react": "18.2.0",
|
|
12
12
|
"@formily/core": "2.3.0",
|
|
13
13
|
"@formily/react": "2.3.0",
|
|
14
|
-
"@nocobase/client": "1.8.
|
|
15
|
-
"@nocobase/utils": "1.8.
|
|
14
|
+
"@nocobase/client": "1.8.28",
|
|
15
|
+
"@nocobase/utils": "1.8.28",
|
|
16
16
|
"antd": "5.24.2",
|
|
17
17
|
"@ant-design/icons": "5.6.1",
|
|
18
18
|
"react-router-dom": "6.28.1",
|
|
@@ -20,17 +20,17 @@ module.exports = {
|
|
|
20
20
|
"lodash": "4.17.21",
|
|
21
21
|
"@dnd-kit/core": "6.1.0",
|
|
22
22
|
"@formily/shared": "2.3.2",
|
|
23
|
-
"@nocobase/plugin-mobile": "1.8.
|
|
23
|
+
"@nocobase/plugin-mobile": "1.8.28",
|
|
24
24
|
"sequelize": "6.35.2",
|
|
25
|
-
"@nocobase/database": "1.8.
|
|
26
|
-
"@nocobase/server": "1.8.
|
|
27
|
-
"@nocobase/data-source-manager": "1.8.
|
|
28
|
-
"@nocobase/logger": "1.8.
|
|
29
|
-
"@nocobase/evaluators": "1.8.
|
|
25
|
+
"@nocobase/database": "1.8.28",
|
|
26
|
+
"@nocobase/server": "1.8.28",
|
|
27
|
+
"@nocobase/data-source-manager": "1.8.28",
|
|
28
|
+
"@nocobase/logger": "1.8.28",
|
|
29
|
+
"@nocobase/evaluators": "1.8.28",
|
|
30
30
|
"@formily/antd-v5": "1.2.3",
|
|
31
31
|
"@formily/reactive": "2.3.0",
|
|
32
|
-
"@nocobase/actions": "1.8.
|
|
32
|
+
"@nocobase/actions": "1.8.28",
|
|
33
33
|
"dayjs": "1.11.13",
|
|
34
|
-
"@nocobase/plugin-workflow-test": "1.8.
|
|
35
|
-
"@nocobase/test": "1.8.
|
|
34
|
+
"@nocobase/plugin-workflow-test": "1.8.28",
|
|
35
|
+
"@nocobase/test": "1.8.28"
|
|
36
36
|
};
|
package/dist/locale/zh-CN.json
CHANGED
|
@@ -179,16 +179,19 @@
|
|
|
179
179
|
"Calculation result": "计算结果",
|
|
180
180
|
"True": "真",
|
|
181
181
|
"False": "假",
|
|
182
|
-
"
|
|
182
|
+
"Concat": "连接",
|
|
183
183
|
"Condition": "条件判断",
|
|
184
184
|
"Based on boolean result of the calculation to determine whether to \"continue\" or \"exit\" the process, or continue on different branches of \"yes\" and \"no\".":
|
|
185
185
|
"基于计算结果的真假来决定“继续”或“退出”流程,或者在“是”与“否”的分支上分别继续。",
|
|
186
186
|
"Mode": "模式",
|
|
187
|
+
"Yes": "是",
|
|
188
|
+
"No": "否",
|
|
187
189
|
"Continue when \"Yes\"": "“是”则继续",
|
|
188
190
|
"Branch into \"Yes\" and \"No\"": "“是”和“否”分别继续",
|
|
189
191
|
"Condition expression": "条件表达式",
|
|
190
|
-
"Inside of \"
|
|
191
|
-
"
|
|
192
|
+
"Inside of \"{{branchName}}\" branch": "“{{branchName}}”分支内",
|
|
193
|
+
"\"{{branchName}}\" branch": "“{{branchName}}”分支",
|
|
194
|
+
"Branch {{index}}": "分支 {{index}}",
|
|
192
195
|
"Create record": "新增数据",
|
|
193
196
|
"Add new record to a collection. You can use variables from upstream nodes to assign values to fields.":
|
|
194
197
|
"向一个数据表中添加新的数据。可以使用上游节点里的变量为字段赋值。",
|
|
@@ -235,7 +238,10 @@
|
|
|
235
238
|
"Succeeded": "成功",
|
|
236
239
|
"Test run": "测试执行",
|
|
237
240
|
"Test run will do the actual data manipulating or API calling, please use with caution.": "测试执行会进行实际的数据操作或 API 调用,请谨慎使用。",
|
|
241
|
+
"Replace variables": "替换变量",
|
|
238
242
|
"No variable": "无变量",
|
|
243
|
+
"Result": "结果",
|
|
244
|
+
"Log": "日志",
|
|
239
245
|
|
|
240
246
|
"Add node": "添加节点",
|
|
241
247
|
"Move all downstream nodes to": "将所有下游节点移至",
|
|
@@ -246,6 +252,11 @@
|
|
|
246
252
|
"New version enabled": "已启用新版本",
|
|
247
253
|
"Workflow is not exists": "工作流不存在",
|
|
248
254
|
|
|
255
|
+
"Delete node": "删除节点",
|
|
256
|
+
"Branch to keep": "保留分支",
|
|
257
|
+
"Delete all": "删除全部",
|
|
258
|
+
"Keep": "保留",
|
|
259
|
+
|
|
249
260
|
"Select users": "选择用户",
|
|
250
261
|
"Query users": "查询用户",
|
|
251
262
|
"Add": "添加",
|
|
@@ -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":"2025-
|
|
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":"2025-10-14T05:56:38.482Z"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"lru-cache","description":"A cache object that deletes the least-recently-used items.","version":"8.0.5","author":"Isaac Z. Schlueter <i@izs.me>","keywords":["mru","lru","cache"],"sideEffects":false,"scripts":{"build":"npm run prepare","preprepare":"rm -rf dist","prepare":"tsc -p tsconfig.json && tsc -p tsconfig-esm.json","postprepare":"bash fixup.sh","pretest":"npm run prepare","presnap":"npm run prepare","test":"c8 tap","snap":"c8 tap","preversion":"npm test","postversion":"npm publish","prepublishOnly":"git push origin --follow-tags","format":"prettier --write .","typedoc":"typedoc --tsconfig tsconfig-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"},"main":"./dist/cjs/index-cjs.js","module":"./dist/mjs/index.js","types":"./dist/mjs/index.d.ts","exports":{"./min":{"import":{"types":"./dist/mjs/index.d.ts","default":"./dist/mjs/index.min.js"},"require":{"types":"./dist/cjs/index.d.ts","default":"./dist/cjs/index.min.js"}},".":{"import":{"types":"./dist/mjs/index.d.ts","default":"./dist/mjs/index.js"},"require":{"types":"./dist/cjs/index.d.ts","default":"./dist/cjs/index-cjs.js"}}},"repository":"git://github.com/isaacs/node-lru-cache.git","devDependencies":{"@size-limit/preset-small-lib":"^7.0.8","@types/node":"^17.0.31","@types/tap":"^15.0.6","benchmark":"^2.1.4","c8":"^7.11.2","clock-mock":"^1.0.6","esbuild":"^0.17.11","eslint-config-prettier":"^8.5.0","marked":"^4.2.12","mkdirp":"^2.1.5","prettier":"^2.6.2","size-limit":"^7.0.8","tap":"^16.3.4","ts-node":"^10.7.0","tslib":"^2.4.0","typedoc":"^0.23.24","typescript":"^4.6.4"},"license":"ISC","files":["dist"],"engines":{"node":">=16.14"},"prettier":{"semi":false,"printWidth":70,"tabWidth":2,"useTabs":false,"singleQuote":true,"jsxSingleQuote":false,"bracketSameLine":true,"arrowParens":"avoid","endOfLine":"lf"},"tap":{"coverage":false,"node-arg":["--expose-gc","--no-warnings","--loader","ts-node/esm"],"ts":false},"size-limit":[{"path":"./dist/mjs/index.js"}],"_lastModified":"2025-
|
|
1
|
+
{"name":"lru-cache","description":"A cache object that deletes the least-recently-used items.","version":"8.0.5","author":"Isaac Z. Schlueter <i@izs.me>","keywords":["mru","lru","cache"],"sideEffects":false,"scripts":{"build":"npm run prepare","preprepare":"rm -rf dist","prepare":"tsc -p tsconfig.json && tsc -p tsconfig-esm.json","postprepare":"bash fixup.sh","pretest":"npm run prepare","presnap":"npm run prepare","test":"c8 tap","snap":"c8 tap","preversion":"npm test","postversion":"npm publish","prepublishOnly":"git push origin --follow-tags","format":"prettier --write .","typedoc":"typedoc --tsconfig tsconfig-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"},"main":"./dist/cjs/index-cjs.js","module":"./dist/mjs/index.js","types":"./dist/mjs/index.d.ts","exports":{"./min":{"import":{"types":"./dist/mjs/index.d.ts","default":"./dist/mjs/index.min.js"},"require":{"types":"./dist/cjs/index.d.ts","default":"./dist/cjs/index.min.js"}},".":{"import":{"types":"./dist/mjs/index.d.ts","default":"./dist/mjs/index.js"},"require":{"types":"./dist/cjs/index.d.ts","default":"./dist/cjs/index-cjs.js"}}},"repository":"git://github.com/isaacs/node-lru-cache.git","devDependencies":{"@size-limit/preset-small-lib":"^7.0.8","@types/node":"^17.0.31","@types/tap":"^15.0.6","benchmark":"^2.1.4","c8":"^7.11.2","clock-mock":"^1.0.6","esbuild":"^0.17.11","eslint-config-prettier":"^8.5.0","marked":"^4.2.12","mkdirp":"^2.1.5","prettier":"^2.6.2","size-limit":"^7.0.8","tap":"^16.3.4","ts-node":"^10.7.0","tslib":"^2.4.0","typedoc":"^0.23.24","typescript":"^4.6.4"},"license":"ISC","files":["dist"],"engines":{"node":">=16.14"},"prettier":{"semi":false,"printWidth":70,"tabWidth":2,"useTabs":false,"singleQuote":true,"jsxSingleQuote":false,"bracketSameLine":true,"arrowParens":"avoid","endOfLine":"lf"},"tap":{"coverage":false,"node-arg":["--expose-gc","--no-warnings","--loader","ts-node/esm"],"ts":false},"size-limit":[{"path":"./dist/mjs/index.js"}],"_lastModified":"2025-10-14T05:56:38.198Z"}
|
|
@@ -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":"2025-
|
|
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":"2025-10-14T05:56:37.939Z"}
|
|
@@ -6,6 +6,52 @@
|
|
|
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 { Transactionable } from 'sequelize';
|
|
10
|
+
import type { QueueEventOptions } from '@nocobase/server';
|
|
11
|
+
import Processor from './Processor';
|
|
12
|
+
import type { ExecutionModel, JobModel, WorkflowModel } from './types';
|
|
13
|
+
import type PluginWorkflowServer from './Plugin';
|
|
14
|
+
type Pending = {
|
|
15
|
+
execution: ExecutionModel;
|
|
16
|
+
job?: JobModel;
|
|
17
|
+
force?: boolean;
|
|
18
|
+
};
|
|
19
|
+
export type EventOptions = {
|
|
20
|
+
eventKey?: string;
|
|
21
|
+
context?: any;
|
|
22
|
+
deferred?: boolean;
|
|
23
|
+
manually?: boolean;
|
|
24
|
+
force?: boolean;
|
|
25
|
+
stack?: Array<number | string>;
|
|
26
|
+
onTriggerFail?: Function;
|
|
27
|
+
[key: string]: any;
|
|
28
|
+
} & Transactionable;
|
|
29
|
+
export declare const WORKER_JOB_WORKFLOW_PROCESS = "workflow:process";
|
|
9
30
|
export default class Dispatcher {
|
|
10
|
-
|
|
31
|
+
private readonly plugin;
|
|
32
|
+
private ready;
|
|
33
|
+
private executing;
|
|
34
|
+
private pending;
|
|
35
|
+
private events;
|
|
36
|
+
private eventsCount;
|
|
37
|
+
get idle(): boolean;
|
|
38
|
+
constructor(plugin: PluginWorkflowServer);
|
|
39
|
+
readonly onQueueExecution: QueueEventOptions['process'];
|
|
40
|
+
setReady(ready: boolean): void;
|
|
41
|
+
isReady(): boolean;
|
|
42
|
+
getEventsCount(): number;
|
|
43
|
+
trigger(workflow: WorkflowModel, context: object, options?: EventOptions): void | Promise<Processor | null>;
|
|
44
|
+
resume(job: any): Promise<void>;
|
|
45
|
+
start(execution: ExecutionModel): Promise<void>;
|
|
46
|
+
beforeStop(): Promise<void>;
|
|
47
|
+
dispatch(): Promise<void>;
|
|
48
|
+
run(pending: Pending): Promise<void>;
|
|
49
|
+
private triggerSync;
|
|
50
|
+
private validateEvent;
|
|
51
|
+
private createExecution;
|
|
52
|
+
private prepare;
|
|
53
|
+
private acquirePendingExecution;
|
|
54
|
+
private acquireQueueingExecution;
|
|
55
|
+
private process;
|
|
11
56
|
}
|
|
57
|
+
export {};
|
|
@@ -26,10 +26,381 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
26
26
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
27
|
var Dispatcher_exports = {};
|
|
28
28
|
__export(Dispatcher_exports, {
|
|
29
|
+
WORKER_JOB_WORKFLOW_PROCESS: () => WORKER_JOB_WORKFLOW_PROCESS,
|
|
29
30
|
default: () => Dispatcher
|
|
30
31
|
});
|
|
31
32
|
module.exports = __toCommonJS(Dispatcher_exports);
|
|
33
|
+
var import_crypto = require("crypto");
|
|
34
|
+
var import_sequelize = require("sequelize");
|
|
35
|
+
var import_database = require("@nocobase/database");
|
|
36
|
+
var import_constants = require("./constants");
|
|
37
|
+
const WORKER_JOB_WORKFLOW_PROCESS = "workflow:process";
|
|
32
38
|
class Dispatcher {
|
|
33
|
-
constructor() {
|
|
39
|
+
constructor(plugin) {
|
|
40
|
+
this.plugin = plugin;
|
|
41
|
+
this.prepare = this.prepare.bind(this);
|
|
42
|
+
}
|
|
43
|
+
ready = false;
|
|
44
|
+
executing = null;
|
|
45
|
+
pending = [];
|
|
46
|
+
events = [];
|
|
47
|
+
eventsCount = 0;
|
|
48
|
+
get idle() {
|
|
49
|
+
return !this.executing && !this.pending.length && !this.events.length;
|
|
50
|
+
}
|
|
51
|
+
onQueueExecution = async (event) => {
|
|
52
|
+
const ExecutionRepo = this.plugin.db.getRepository("executions");
|
|
53
|
+
const execution = await ExecutionRepo.findOne({
|
|
54
|
+
filterByTk: event.executionId
|
|
55
|
+
});
|
|
56
|
+
if (!execution || execution.status !== import_constants.EXECUTION_STATUS.QUEUEING) {
|
|
57
|
+
this.plugin.getLogger("dispatcher").info(`execution (${event.executionId}) from queue not found or not in queueing status, skip`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) received from queue, adding to pending list`);
|
|
61
|
+
this.run({ execution });
|
|
62
|
+
};
|
|
63
|
+
setReady(ready) {
|
|
64
|
+
this.ready = ready;
|
|
65
|
+
}
|
|
66
|
+
isReady() {
|
|
67
|
+
return this.ready;
|
|
68
|
+
}
|
|
69
|
+
getEventsCount() {
|
|
70
|
+
return this.eventsCount;
|
|
71
|
+
}
|
|
72
|
+
trigger(workflow, context, options = {}) {
|
|
73
|
+
const logger = this.plugin.getLogger(workflow.id);
|
|
74
|
+
if (!this.ready) {
|
|
75
|
+
logger.warn(`app is not ready, event of workflow ${workflow.id} will be ignored`);
|
|
76
|
+
logger.debug(`ignored event data:`, context);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (!options.force && !options.manually && !workflow.enabled) {
|
|
80
|
+
logger.warn(`workflow ${workflow.id} is not enabled, event will be ignored`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const duplicated = this.events.find(([w, c, { eventKey }]) => {
|
|
84
|
+
if (eventKey && options.eventKey) {
|
|
85
|
+
return eventKey === options.eventKey;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
if (duplicated) {
|
|
89
|
+
logger.warn(`event of workflow ${workflow.id} is duplicated (${options.eventKey}), event will be ignored`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (context == null) {
|
|
93
|
+
logger.warn(`workflow ${workflow.id} event data context is null, event will be ignored`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (options.manually || this.plugin.isWorkflowSync(workflow)) {
|
|
97
|
+
return this.triggerSync(workflow, context, options);
|
|
98
|
+
}
|
|
99
|
+
const { transaction, ...rest } = options;
|
|
100
|
+
this.events.push([workflow, context, rest]);
|
|
101
|
+
this.eventsCount = this.events.length;
|
|
102
|
+
logger.info(`new event triggered, now events: ${this.events.length}`);
|
|
103
|
+
logger.debug(`event data:`, { context });
|
|
104
|
+
if (this.events.length > 1) {
|
|
105
|
+
logger.info(`new event is pending to be prepared after previous preparation is finished`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
setImmediate(this.prepare);
|
|
109
|
+
}
|
|
110
|
+
async resume(job) {
|
|
111
|
+
let { execution } = job;
|
|
112
|
+
if (!execution) {
|
|
113
|
+
execution = await job.getExecution();
|
|
114
|
+
}
|
|
115
|
+
this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) resuming from job (${job.id}) added to pending list`);
|
|
116
|
+
this.run({ execution, job, force: true });
|
|
117
|
+
}
|
|
118
|
+
async start(execution) {
|
|
119
|
+
if (execution.status !== import_constants.EXECUTION_STATUS.STARTED) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
this.plugin.getLogger(execution.workflowId).info(`starting deferred execution (${execution.id})`);
|
|
123
|
+
this.run({ execution, force: true });
|
|
124
|
+
}
|
|
125
|
+
async beforeStop() {
|
|
126
|
+
this.ready = false;
|
|
127
|
+
if (this.events.length) {
|
|
128
|
+
await this.prepare();
|
|
129
|
+
}
|
|
130
|
+
if (this.executing) {
|
|
131
|
+
await this.executing;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
dispatch() {
|
|
135
|
+
if (!this.ready) {
|
|
136
|
+
this.plugin.getLogger("dispatcher").warn(`app is not ready, new dispatching will be ignored`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (!this.plugin.app.serving(WORKER_JOB_WORKFLOW_PROCESS)) {
|
|
140
|
+
this.plugin.getLogger("dispatcher").warn(`${WORKER_JOB_WORKFLOW_PROCESS} is not serving, new dispatching will be ignored`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (this.executing) {
|
|
144
|
+
this.plugin.getLogger("dispatcher").warn(`workflow executing is not finished, new dispatching will be ignored`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (this.events.length) {
|
|
148
|
+
return this.prepare();
|
|
149
|
+
}
|
|
150
|
+
this.executing = (async () => {
|
|
151
|
+
let next = null;
|
|
152
|
+
let execution = null;
|
|
153
|
+
if (this.pending.length) {
|
|
154
|
+
const pending = this.pending.shift();
|
|
155
|
+
execution = pending.force ? pending.execution : await this.acquirePendingExecution(pending.execution);
|
|
156
|
+
if (execution) {
|
|
157
|
+
next = [execution, pending.job];
|
|
158
|
+
this.plugin.getLogger(next[0].workflowId).info(`pending execution (${next[0].id}) ready to process`);
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
execution = await this.acquireQueueingExecution();
|
|
162
|
+
if (execution) {
|
|
163
|
+
next = [execution];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (next) {
|
|
167
|
+
await this.process(...next);
|
|
168
|
+
}
|
|
169
|
+
this.executing = null;
|
|
170
|
+
if (next || this.pending.length) {
|
|
171
|
+
this.plugin.getLogger("dispatcher").debug(`last process finished, will do another dispatch`);
|
|
172
|
+
this.dispatch();
|
|
173
|
+
}
|
|
174
|
+
})();
|
|
175
|
+
}
|
|
176
|
+
async run(pending) {
|
|
177
|
+
this.pending.push(pending);
|
|
178
|
+
this.dispatch();
|
|
179
|
+
}
|
|
180
|
+
async triggerSync(workflow, context, { deferred, ...options } = {}) {
|
|
181
|
+
let execution;
|
|
182
|
+
try {
|
|
183
|
+
execution = await this.createExecution(workflow, context, options);
|
|
184
|
+
} catch (err) {
|
|
185
|
+
this.plugin.getLogger(workflow.id).error(`creating execution failed: ${err.message}`, err);
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
return this.process(execution, null, options);
|
|
190
|
+
} catch (err) {
|
|
191
|
+
this.plugin.getLogger(execution.workflowId).error(`execution (${execution.id}) error: ${err.message}`, err);
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
async validateEvent(workflow, context, options) {
|
|
196
|
+
const trigger = this.plugin.triggers.get(workflow.type);
|
|
197
|
+
const triggerValid = await trigger.validateEvent(workflow, context, options);
|
|
198
|
+
if (!triggerValid) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
const { stack } = options;
|
|
202
|
+
let valid = true;
|
|
203
|
+
if ((stack == null ? void 0 : stack.length) > 0) {
|
|
204
|
+
const existed = await workflow.countExecutions({
|
|
205
|
+
where: {
|
|
206
|
+
id: stack
|
|
207
|
+
},
|
|
208
|
+
transaction: options.transaction
|
|
209
|
+
});
|
|
210
|
+
const limitCount = workflow.options.stackLimit || 1;
|
|
211
|
+
if (existed >= limitCount) {
|
|
212
|
+
this.plugin.getLogger(workflow.id).warn(
|
|
213
|
+
`workflow ${workflow.id} has already been triggered in stacks executions (${stack}), and max call coont is ${limitCount}, newly triggering will be skipped.`
|
|
214
|
+
);
|
|
215
|
+
valid = false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return valid;
|
|
219
|
+
}
|
|
220
|
+
async createExecution(workflow, context, options) {
|
|
221
|
+
var _a;
|
|
222
|
+
const { deferred } = options;
|
|
223
|
+
const transaction = await this.plugin.useDataSourceTransaction("main", options.transaction, true);
|
|
224
|
+
const sameTransaction = options.transaction === transaction;
|
|
225
|
+
const valid = await this.validateEvent(workflow, context, { ...options, transaction });
|
|
226
|
+
if (!valid) {
|
|
227
|
+
if (!sameTransaction) {
|
|
228
|
+
await transaction.commit();
|
|
229
|
+
}
|
|
230
|
+
(_a = options.onTriggerFail) == null ? void 0 : _a.call(options, workflow, context, options);
|
|
231
|
+
return Promise.reject(new Error("event is not valid"));
|
|
232
|
+
}
|
|
233
|
+
let execution;
|
|
234
|
+
try {
|
|
235
|
+
execution = await workflow.createExecution(
|
|
236
|
+
{
|
|
237
|
+
context,
|
|
238
|
+
key: workflow.key,
|
|
239
|
+
eventKey: options.eventKey ?? (0, import_crypto.randomUUID)(),
|
|
240
|
+
stack: options.stack,
|
|
241
|
+
status: deferred ? import_constants.EXECUTION_STATUS.STARTED : import_constants.EXECUTION_STATUS.QUEUEING
|
|
242
|
+
},
|
|
243
|
+
{ transaction }
|
|
244
|
+
);
|
|
245
|
+
} catch (err) {
|
|
246
|
+
if (!sameTransaction) {
|
|
247
|
+
await transaction.rollback();
|
|
248
|
+
}
|
|
249
|
+
throw err;
|
|
250
|
+
}
|
|
251
|
+
this.plugin.getLogger(workflow.id).info(`execution of workflow ${workflow.id} created as ${execution.id}`);
|
|
252
|
+
if (!workflow.stats) {
|
|
253
|
+
workflow.stats = await workflow.getStats({ transaction });
|
|
254
|
+
}
|
|
255
|
+
await workflow.stats.increment("executed", { transaction });
|
|
256
|
+
if (this.plugin.db.options.dialect !== "postgres") {
|
|
257
|
+
await workflow.stats.reload({ transaction });
|
|
258
|
+
}
|
|
259
|
+
if (!workflow.versionStats) {
|
|
260
|
+
workflow.versionStats = await workflow.getVersionStats({ transaction });
|
|
261
|
+
}
|
|
262
|
+
await workflow.versionStats.increment("executed", { transaction });
|
|
263
|
+
if (this.plugin.db.options.dialect !== "postgres") {
|
|
264
|
+
await workflow.versionStats.reload({ transaction });
|
|
265
|
+
}
|
|
266
|
+
if (!sameTransaction) {
|
|
267
|
+
await transaction.commit();
|
|
268
|
+
}
|
|
269
|
+
execution.workflow = workflow;
|
|
270
|
+
return execution;
|
|
271
|
+
}
|
|
272
|
+
prepare = async () => {
|
|
273
|
+
if (this.executing && this.plugin.db.options.dialect === "sqlite") {
|
|
274
|
+
await this.executing;
|
|
275
|
+
}
|
|
276
|
+
const event = this.events.shift();
|
|
277
|
+
this.eventsCount = this.events.length;
|
|
278
|
+
if (!event) {
|
|
279
|
+
this.plugin.getLogger("dispatcher").info(`events queue is empty, no need to prepare`);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const logger = this.plugin.getLogger(event[0].id);
|
|
283
|
+
logger.info(`preparing execution for event`);
|
|
284
|
+
try {
|
|
285
|
+
const execution = await this.createExecution(...event);
|
|
286
|
+
if ((execution == null ? void 0 : execution.status) === import_constants.EXECUTION_STATUS.QUEUEING) {
|
|
287
|
+
if (!this.executing && !this.pending.length) {
|
|
288
|
+
logger.info(`local pending list is empty, adding execution (${execution.id}) to pending list`);
|
|
289
|
+
this.pending.push({ execution });
|
|
290
|
+
} else {
|
|
291
|
+
logger.info(`local pending list is not empty, sending execution (${execution.id}) to queue`);
|
|
292
|
+
if (this.ready) {
|
|
293
|
+
this.plugin.app.backgroundJobManager.publish(`${this.plugin.name}.pendingExecution`, {
|
|
294
|
+
executionId: execution.id
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
} catch (error) {
|
|
300
|
+
logger.error(`failed to create execution:`, { error });
|
|
301
|
+
}
|
|
302
|
+
if (this.events.length) {
|
|
303
|
+
await this.prepare();
|
|
304
|
+
} else {
|
|
305
|
+
this.plugin.getLogger("dispatcher").info("no more events need to be prepared, dispatching...");
|
|
306
|
+
if (this.executing) {
|
|
307
|
+
await this.executing;
|
|
308
|
+
}
|
|
309
|
+
this.dispatch();
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
async acquirePendingExecution(execution) {
|
|
313
|
+
const logger = this.plugin.getLogger(execution.workflowId);
|
|
314
|
+
const isolationLevel = this.plugin.db.options.dialect === "sqlite" ? [][0] : import_sequelize.Transaction.ISOLATION_LEVELS.REPEATABLE_READ;
|
|
315
|
+
let fetched = execution;
|
|
316
|
+
try {
|
|
317
|
+
await this.plugin.db.sequelize.transaction({ isolationLevel }, async (transaction) => {
|
|
318
|
+
const ExecutionModelClass = this.plugin.db.getModel("executions");
|
|
319
|
+
const [affected] = await ExecutionModelClass.update(
|
|
320
|
+
{ status: import_constants.EXECUTION_STATUS.STARTED },
|
|
321
|
+
{
|
|
322
|
+
where: {
|
|
323
|
+
id: execution.id,
|
|
324
|
+
status: {
|
|
325
|
+
[import_database.Op.is]: import_constants.EXECUTION_STATUS.QUEUEING
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
transaction
|
|
329
|
+
}
|
|
330
|
+
);
|
|
331
|
+
if (!affected) {
|
|
332
|
+
fetched = null;
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
await execution.reload({ transaction });
|
|
336
|
+
});
|
|
337
|
+
} catch (error) {
|
|
338
|
+
logger.error(`acquiring pending execution failed: ${error.message}`, { error });
|
|
339
|
+
}
|
|
340
|
+
return fetched;
|
|
341
|
+
}
|
|
342
|
+
async acquireQueueingExecution() {
|
|
343
|
+
const isolationLevel = this.plugin.db.options.dialect === "sqlite" ? [][0] : import_sequelize.Transaction.ISOLATION_LEVELS.REPEATABLE_READ;
|
|
344
|
+
let fetched = null;
|
|
345
|
+
try {
|
|
346
|
+
await this.plugin.db.sequelize.transaction(
|
|
347
|
+
{
|
|
348
|
+
isolationLevel
|
|
349
|
+
},
|
|
350
|
+
async (transaction) => {
|
|
351
|
+
const execution = await this.plugin.db.getRepository("executions").findOne({
|
|
352
|
+
filter: {
|
|
353
|
+
status: {
|
|
354
|
+
[import_database.Op.is]: import_constants.EXECUTION_STATUS.QUEUEING
|
|
355
|
+
},
|
|
356
|
+
"workflow.enabled": true
|
|
357
|
+
},
|
|
358
|
+
sort: "id",
|
|
359
|
+
transaction
|
|
360
|
+
});
|
|
361
|
+
if (execution) {
|
|
362
|
+
this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) fetched from db`);
|
|
363
|
+
await execution.update(
|
|
364
|
+
{
|
|
365
|
+
status: import_constants.EXECUTION_STATUS.STARTED
|
|
366
|
+
},
|
|
367
|
+
{ transaction }
|
|
368
|
+
);
|
|
369
|
+
execution.workflow = this.plugin.enabledCache.get(execution.workflowId);
|
|
370
|
+
fetched = execution;
|
|
371
|
+
} else {
|
|
372
|
+
this.plugin.getLogger("dispatcher").debug(`no execution in db queued to process`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
);
|
|
376
|
+
} catch (error) {
|
|
377
|
+
this.plugin.getLogger("dispatcher").error(`fetching execution from db failed: ${error.message}`, { error });
|
|
378
|
+
}
|
|
379
|
+
return fetched;
|
|
380
|
+
}
|
|
381
|
+
async process(execution, job, options = {}) {
|
|
382
|
+
var _a, _b;
|
|
383
|
+
const logger = this.plugin.getLogger(execution.workflowId);
|
|
384
|
+
if (execution.status === import_constants.EXECUTION_STATUS.QUEUEING) {
|
|
385
|
+
const transaction = await this.plugin.useDataSourceTransaction("main", options.transaction);
|
|
386
|
+
await execution.update({ status: import_constants.EXECUTION_STATUS.STARTED }, { transaction });
|
|
387
|
+
logger.info(`execution (${execution.id}) from pending list updated to started`);
|
|
388
|
+
}
|
|
389
|
+
const processor = this.plugin.createProcessor(execution, options);
|
|
390
|
+
logger.info(`execution (${execution.id}) ${job ? "resuming" : "starting"}...`);
|
|
391
|
+
try {
|
|
392
|
+
await (job ? processor.resume(job) : processor.start());
|
|
393
|
+
logger.info(`execution (${execution.id}) finished with status: ${execution.status}`, { execution });
|
|
394
|
+
if (execution.status && ((_b = (_a = execution.workflow.options) == null ? void 0 : _a.deleteExecutionOnStatus) == null ? void 0 : _b.includes(execution.status))) {
|
|
395
|
+
await execution.destroy({ transaction: processor.mainTransaction });
|
|
396
|
+
}
|
|
397
|
+
} catch (err) {
|
|
398
|
+
logger.error(`execution (${execution.id}) error: ${err.message}`, err);
|
|
399
|
+
}
|
|
400
|
+
return processor;
|
|
34
401
|
}
|
|
35
402
|
}
|
|
403
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
404
|
+
0 && (module.exports = {
|
|
405
|
+
WORKER_JOB_WORKFLOW_PROCESS
|
|
406
|
+
});
|
package/dist/server/Plugin.d.ts
CHANGED
|
@@ -11,37 +11,23 @@ import { Transactionable } from 'sequelize';
|
|
|
11
11
|
import { Plugin } from '@nocobase/server';
|
|
12
12
|
import { Registry } from '@nocobase/utils';
|
|
13
13
|
import { Logger } from '@nocobase/logger';
|
|
14
|
+
import Dispatcher, { EventOptions } from './Dispatcher';
|
|
14
15
|
import Processor from './Processor';
|
|
15
16
|
import { CustomFunction } from './functions';
|
|
16
17
|
import Trigger from './triggers';
|
|
17
18
|
import { InstructionInterface } from './instructions';
|
|
18
|
-
import type { ExecutionModel,
|
|
19
|
+
import type { ExecutionModel, WorkflowModel } from './types';
|
|
19
20
|
type ID = number | string;
|
|
20
|
-
export type EventOptions = {
|
|
21
|
-
eventKey?: string;
|
|
22
|
-
context?: any;
|
|
23
|
-
deferred?: boolean;
|
|
24
|
-
manually?: boolean;
|
|
25
|
-
force?: boolean;
|
|
26
|
-
stack?: Array<ID>;
|
|
27
|
-
onTriggerFail?: Function;
|
|
28
|
-
[key: string]: any;
|
|
29
|
-
} & Transactionable;
|
|
30
21
|
export default class PluginWorkflowServer extends Plugin {
|
|
31
22
|
instructions: Registry<InstructionInterface>;
|
|
32
23
|
triggers: Registry<Trigger>;
|
|
33
24
|
functions: Registry<CustomFunction>;
|
|
34
25
|
enabledCache: Map<number, WorkflowModel>;
|
|
35
26
|
snowflake: Snowflake;
|
|
36
|
-
private
|
|
37
|
-
private executing;
|
|
38
|
-
private pending;
|
|
39
|
-
private events;
|
|
40
|
-
private eventsCount;
|
|
27
|
+
private dispatcher;
|
|
41
28
|
private loggerCache;
|
|
42
29
|
private meter;
|
|
43
30
|
private checker;
|
|
44
|
-
private onQueueExecution;
|
|
45
31
|
private onBeforeSave;
|
|
46
32
|
private onAfterCreate;
|
|
47
33
|
private onAfterUpdate;
|
|
@@ -74,20 +60,14 @@ export default class PluginWorkflowServer extends Plugin {
|
|
|
74
60
|
load(): Promise<void>;
|
|
75
61
|
private toggle;
|
|
76
62
|
trigger(workflow: WorkflowModel, context: object, options?: EventOptions): void | Promise<Processor | null>;
|
|
77
|
-
|
|
78
|
-
run(execution: ExecutionModel, job?: JobModel): Promise<void>;
|
|
63
|
+
run(pending: Parameters<Dispatcher['run']>[0]): Promise<void>;
|
|
79
64
|
resume(job: any): Promise<void>;
|
|
80
65
|
/**
|
|
81
66
|
* Start a deferred execution
|
|
82
67
|
* @experimental
|
|
83
68
|
*/
|
|
84
69
|
start(execution: ExecutionModel): Promise<void>;
|
|
85
|
-
private validateEvent;
|
|
86
|
-
private createExecution;
|
|
87
|
-
private prepare;
|
|
88
|
-
private dispatch;
|
|
89
70
|
createProcessor(execution: ExecutionModel, options?: {}): Processor;
|
|
90
|
-
private process;
|
|
91
71
|
execute(workflow: WorkflowModel, values: any, options?: EventOptions): Promise<void | Processor>;
|
|
92
72
|
/**
|
|
93
73
|
* @experimental
|