@nocobase/plugin-workflow 2.0.57 → 2.0.58
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/lru-cache/package.json +1 -1
- package/dist/node_modules/nodejs-snowflake/package.json +1 -1
- package/dist/server/Dispatcher.d.ts +9 -3
- package/dist/server/Dispatcher.js +52 -26
- package/dist/server/Plugin.d.ts +2 -1
- package/dist/server/Plugin.js +5 -2
- package/dist/server/Processor.d.ts +19 -1
- package/dist/server/Processor.js +70 -4
- package/dist/server/actions/executions.d.ts +4 -3
- package/dist/server/actions/executions.js +86 -18
- package/dist/server/actions/jobs.js +20 -1
- package/dist/server/instructions/index.d.ts +6 -2
- 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.0.
|
|
15
|
-
"@nocobase/utils": "2.0.
|
|
14
|
+
"@nocobase/client": "2.0.58",
|
|
15
|
+
"@nocobase/utils": "2.0.58",
|
|
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.0.
|
|
24
|
-
"@nocobase/plugin-mobile": "2.0.
|
|
23
|
+
"@nocobase/flow-engine": "2.0.58",
|
|
24
|
+
"@nocobase/plugin-mobile": "2.0.58",
|
|
25
25
|
"sequelize": "6.35.2",
|
|
26
|
-
"@nocobase/server": "2.0.
|
|
27
|
-
"@nocobase/database": "2.0.
|
|
28
|
-
"@nocobase/data-source-manager": "2.0.
|
|
29
|
-
"@nocobase/logger": "2.0.
|
|
30
|
-
"@nocobase/evaluators": "2.0.
|
|
26
|
+
"@nocobase/server": "2.0.58",
|
|
27
|
+
"@nocobase/database": "2.0.58",
|
|
28
|
+
"@nocobase/data-source-manager": "2.0.58",
|
|
29
|
+
"@nocobase/logger": "2.0.58",
|
|
30
|
+
"@nocobase/evaluators": "2.0.58",
|
|
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.0.
|
|
35
|
+
"@nocobase/actions": "2.0.58",
|
|
36
36
|
"dayjs": "1.11.13",
|
|
37
|
-
"@nocobase/plugin-workflow-test": "2.0.
|
|
38
|
-
"@nocobase/test": "2.0.
|
|
37
|
+
"@nocobase/plugin-workflow-test": "2.0.58",
|
|
38
|
+
"@nocobase/test": "2.0.58"
|
|
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-05-
|
|
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-05-28T02:36:20.282Z"}
|
|
@@ -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":"2026-05-
|
|
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":"2026-05-28T02:36:19.962Z"}
|
|
@@ -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-05-
|
|
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-05-28T02:36:19.751Z"}
|
|
@@ -8,13 +8,17 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Transactionable } from 'sequelize';
|
|
10
10
|
import type { QueueEventOptions } from '@nocobase/server';
|
|
11
|
-
import Processor from './Processor';
|
|
11
|
+
import Processor, { ProcessorRerunOptions } from './Processor';
|
|
12
12
|
import type { ExecutionModel, JobModel, WorkflowModel } from './types';
|
|
13
13
|
import type PluginWorkflowServer from './Plugin';
|
|
14
14
|
type Pending = {
|
|
15
15
|
execution: ExecutionModel;
|
|
16
16
|
job?: JobModel;
|
|
17
17
|
loaded?: boolean;
|
|
18
|
+
rerun?: ProcessorRerunOptions;
|
|
19
|
+
};
|
|
20
|
+
type RunOptions = {
|
|
21
|
+
dispatch?: boolean;
|
|
18
22
|
};
|
|
19
23
|
export type EventOptions = {
|
|
20
24
|
eventKey?: string;
|
|
@@ -41,16 +45,18 @@ export default class Dispatcher {
|
|
|
41
45
|
getEventsCount(): number;
|
|
42
46
|
trigger(workflow: WorkflowModel, context: object, options?: EventOptions): void | Promise<Processor | null>;
|
|
43
47
|
private prepare;
|
|
44
|
-
resume(job:
|
|
48
|
+
resume(job: JobModel): Promise<void>;
|
|
45
49
|
start(execution: ExecutionModel): Promise<void>;
|
|
46
50
|
beforeStop(): Promise<void>;
|
|
47
51
|
dispatch(): void;
|
|
48
|
-
run(pending: Pending): Promise<void>;
|
|
52
|
+
run(pending: Pending, options?: RunOptions): Promise<void>;
|
|
49
53
|
private triggerSync;
|
|
50
54
|
private validateEvent;
|
|
51
55
|
private createExecution;
|
|
52
56
|
private acquirePendingExecution;
|
|
53
57
|
private acquireQueueingExecution;
|
|
58
|
+
private getExecutionLockKey;
|
|
59
|
+
private isLockAcquireError;
|
|
54
60
|
private process;
|
|
55
61
|
}
|
|
56
62
|
export {};
|
|
@@ -115,7 +115,7 @@ class Dispatcher {
|
|
|
115
115
|
logger.info(`preparing execution for event`);
|
|
116
116
|
try {
|
|
117
117
|
const execution = await this.createExecution(...event);
|
|
118
|
-
if (!
|
|
118
|
+
if (!execution.dispatched) {
|
|
119
119
|
if (this.plugin.serving() && !this.executing && !this.pending.length) {
|
|
120
120
|
logger.info(`local pending list is empty, adding execution (${execution.id}) to pending list`);
|
|
121
121
|
this.pending.push({ execution });
|
|
@@ -196,11 +196,12 @@ class Dispatcher {
|
|
|
196
196
|
this.executing = (async () => {
|
|
197
197
|
let next = null;
|
|
198
198
|
let execution = null;
|
|
199
|
+
let pending = null;
|
|
199
200
|
if (this.pending.length) {
|
|
200
|
-
|
|
201
|
+
pending = this.pending.shift();
|
|
201
202
|
execution = pending.loaded ? pending.execution : await this.acquirePendingExecution(pending.execution);
|
|
202
203
|
if (execution) {
|
|
203
|
-
next = [execution, pending.job];
|
|
204
|
+
next = [execution, pending.job, pending.rerun];
|
|
204
205
|
this.plugin.getLogger(next[0].workflowId).info(`pending execution (${next[0].id}) ready to process`);
|
|
205
206
|
}
|
|
206
207
|
} else {
|
|
@@ -216,7 +217,14 @@ class Dispatcher {
|
|
|
216
217
|
}
|
|
217
218
|
}
|
|
218
219
|
if (next) {
|
|
219
|
-
|
|
220
|
+
try {
|
|
221
|
+
await this.process(next[0], next[1], { rerun: next[2] });
|
|
222
|
+
} catch (error) {
|
|
223
|
+
this.plugin.getLogger(next[0].workflowId).error(`execution (${next[0].id}) process failed`, { error });
|
|
224
|
+
if (pending && this.isLockAcquireError(error)) {
|
|
225
|
+
this.pending.unshift({ ...pending, execution: next[0], loaded: true });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
220
228
|
}
|
|
221
229
|
setImmediate(() => {
|
|
222
230
|
this.executing = null;
|
|
@@ -227,9 +235,11 @@ class Dispatcher {
|
|
|
227
235
|
});
|
|
228
236
|
})();
|
|
229
237
|
}
|
|
230
|
-
async run(pending) {
|
|
238
|
+
async run(pending, options = {}) {
|
|
231
239
|
this.pending.push(pending);
|
|
232
|
-
|
|
240
|
+
if (options.dispatch !== false) {
|
|
241
|
+
this.dispatch();
|
|
242
|
+
}
|
|
233
243
|
}
|
|
234
244
|
async triggerSync(workflow, context, { deferred, ...options } = {}) {
|
|
235
245
|
let execution;
|
|
@@ -252,7 +262,7 @@ class Dispatcher {
|
|
|
252
262
|
if (!triggerValid) {
|
|
253
263
|
return false;
|
|
254
264
|
}
|
|
255
|
-
const { stack } = options;
|
|
265
|
+
const { stack = [] } = options;
|
|
256
266
|
let valid = true;
|
|
257
267
|
if ((stack == null ? void 0 : stack.length) > 0) {
|
|
258
268
|
const existed = await workflow.countExecutions({
|
|
@@ -282,7 +292,7 @@ class Dispatcher {
|
|
|
282
292
|
await transaction.commit();
|
|
283
293
|
}
|
|
284
294
|
(_a = options.onTriggerFail) == null ? void 0 : _a.call(options, workflow, context, options);
|
|
285
|
-
|
|
295
|
+
throw new Error("event is not valid");
|
|
286
296
|
}
|
|
287
297
|
let execution;
|
|
288
298
|
try {
|
|
@@ -391,26 +401,42 @@ class Dispatcher {
|
|
|
391
401
|
}
|
|
392
402
|
return fetched;
|
|
393
403
|
}
|
|
394
|
-
|
|
395
|
-
|
|
404
|
+
getExecutionLockKey(executionId) {
|
|
405
|
+
return `workflow:execution:${executionId}`;
|
|
406
|
+
}
|
|
407
|
+
isLockAcquireError(error) {
|
|
408
|
+
return error instanceof Error && error.constructor.name === "LockAcquireError";
|
|
409
|
+
}
|
|
410
|
+
async process(execution, job = null, options = {}) {
|
|
411
|
+
const { rerun, ...processorOptions } = options;
|
|
396
412
|
const logger = this.plugin.getLogger(execution.workflowId);
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
logger.info(`execution (${execution.id}) ${job ? "resuming" : "starting"}...`);
|
|
404
|
-
try {
|
|
405
|
-
await (job ? processor.resume(job) : processor.start());
|
|
406
|
-
logger.info(`execution (${execution.id}) finished with status: ${execution.status}`);
|
|
407
|
-
logger.debug(`execution (${execution.id}) details:`, { execution });
|
|
408
|
-
if (execution.status && ((_b = (_a = execution.workflow.options) == null ? void 0 : _a.deleteExecutionOnStatus) == null ? void 0 : _b.includes(execution.status))) {
|
|
409
|
-
await execution.destroy({ transaction: processor.mainTransaction });
|
|
413
|
+
const run = async () => {
|
|
414
|
+
var _a, _b, _c;
|
|
415
|
+
if (!execution.dispatched) {
|
|
416
|
+
const transaction = await this.plugin.useDataSourceTransaction("main", processorOptions.transaction);
|
|
417
|
+
await execution.update({ dispatched: true, status: import_constants.EXECUTION_STATUS.STARTED }, { transaction });
|
|
418
|
+
logger.info(`execution (${execution.id}) from pending list updated to started`);
|
|
410
419
|
}
|
|
411
|
-
|
|
412
|
-
logger.
|
|
420
|
+
const processor = this.plugin.createProcessor(execution, processorOptions);
|
|
421
|
+
logger.info(`execution (${execution.id}) ${rerun ? "rerunning" : job ? "resuming" : "starting"}...`);
|
|
422
|
+
try {
|
|
423
|
+
await (rerun ? processor.rerun(rerun) : job ? processor.resume(job) : processor.start());
|
|
424
|
+
logger.info(`execution (${execution.id}) finished with status: ${execution.status}`);
|
|
425
|
+
logger.debug(`execution (${execution.id}) details:`, { execution });
|
|
426
|
+
if (execution.status && ((_c = (_b = (_a = execution.workflow) == null ? void 0 : _a.options) == null ? void 0 : _b.deleteExecutionOnStatus) == null ? void 0 : _c.includes(execution.status))) {
|
|
427
|
+
await execution.destroy({ transaction: processor.mainTransaction });
|
|
428
|
+
}
|
|
429
|
+
} catch (err) {
|
|
430
|
+
logger.error(`execution (${execution.id}) error: ${err.message}`, err);
|
|
431
|
+
}
|
|
432
|
+
return processor;
|
|
433
|
+
};
|
|
434
|
+
const lock = await this.plugin.app.lockManager.tryAcquire(this.getExecutionLockKey(execution.id), 6e4);
|
|
435
|
+
try {
|
|
436
|
+
return await lock.runExclusive(run, 6e4);
|
|
437
|
+
} catch (error) {
|
|
438
|
+
logger.error(`execution (${execution.id}) could not acquire process lock`, { error });
|
|
439
|
+
throw error;
|
|
413
440
|
}
|
|
414
|
-
return processor;
|
|
415
441
|
}
|
|
416
442
|
}
|
package/dist/server/Plugin.d.ts
CHANGED
|
@@ -63,7 +63,8 @@ export default class PluginWorkflowServer extends Plugin {
|
|
|
63
63
|
load(): Promise<void>;
|
|
64
64
|
private toggle;
|
|
65
65
|
trigger(workflow: WorkflowModel, context: object, options?: EventOptions): void | Promise<Processor | null>;
|
|
66
|
-
run(pending: Parameters<Dispatcher['run']>[0]): Promise<void>;
|
|
66
|
+
run(pending: Parameters<Dispatcher['run']>[0], options?: Parameters<Dispatcher['run']>[1]): Promise<void>;
|
|
67
|
+
dispatch(): void;
|
|
67
68
|
resume(job: any): Promise<void>;
|
|
68
69
|
/**
|
|
69
70
|
* Start a deferred execution
|
package/dist/server/Plugin.js
CHANGED
|
@@ -406,8 +406,11 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
406
406
|
trigger(workflow, context, options = {}) {
|
|
407
407
|
return this.dispatcher.trigger(workflow, context, options);
|
|
408
408
|
}
|
|
409
|
-
async run(pending) {
|
|
410
|
-
return this.dispatcher.run(pending);
|
|
409
|
+
async run(pending, options) {
|
|
410
|
+
return this.dispatcher.run(pending, options);
|
|
411
|
+
}
|
|
412
|
+
dispatch() {
|
|
413
|
+
return this.dispatcher.dispatch();
|
|
411
414
|
}
|
|
412
415
|
async resume(job) {
|
|
413
416
|
return this.dispatcher.resume(job);
|
|
@@ -10,6 +10,13 @@ import { Transaction, Transactionable } from '@nocobase/database';
|
|
|
10
10
|
import { Logger } from '@nocobase/logger';
|
|
11
11
|
import type Plugin from './Plugin';
|
|
12
12
|
import type { ExecutionModel, FlowNodeModel, JobModel } from './types';
|
|
13
|
+
export type ProcessorRunOptions = {
|
|
14
|
+
rerun?: true;
|
|
15
|
+
};
|
|
16
|
+
export type ProcessorRerunOptions = {
|
|
17
|
+
nodeId?: string | number;
|
|
18
|
+
overwrite?: boolean;
|
|
19
|
+
};
|
|
13
20
|
export interface ProcessorOptions extends Transactionable {
|
|
14
21
|
plugin: Plugin;
|
|
15
22
|
[key: string]: any;
|
|
@@ -47,6 +54,7 @@ export default class Processor {
|
|
|
47
54
|
private jobsMapByNodeKey;
|
|
48
55
|
private jobResultsMapByNodeKey;
|
|
49
56
|
private jobsToSave;
|
|
57
|
+
private rerunContext;
|
|
50
58
|
/**
|
|
51
59
|
* @experimental
|
|
52
60
|
*/
|
|
@@ -57,8 +65,18 @@ export default class Processor {
|
|
|
57
65
|
prepare(): Promise<void>;
|
|
58
66
|
start(): Promise<void>;
|
|
59
67
|
resume(job: JobModel): Promise<void>;
|
|
68
|
+
resolveRerun(options?: ProcessorRerunOptions): {
|
|
69
|
+
node: FlowNodeModel;
|
|
70
|
+
input: JobModel | {
|
|
71
|
+
result: any;
|
|
72
|
+
};
|
|
73
|
+
targetJob: JobModel;
|
|
74
|
+
};
|
|
75
|
+
rerun(options?: ProcessorRerunOptions): Promise<any>;
|
|
76
|
+
private getRerunNode;
|
|
77
|
+
private getRerunInput;
|
|
60
78
|
private exec;
|
|
61
|
-
run(node: any, input?: any): any;
|
|
79
|
+
run(node: any, input?: any, options?: ProcessorRunOptions): any;
|
|
62
80
|
end(node: any, job: JobModel): Promise<any>;
|
|
63
81
|
private recall;
|
|
64
82
|
exit(s?: number | true): Promise<any>;
|
package/dist/server/Processor.js
CHANGED
|
@@ -82,6 +82,7 @@ class Processor {
|
|
|
82
82
|
jobsMapByNodeKey = {};
|
|
83
83
|
jobResultsMapByNodeKey = {};
|
|
84
84
|
jobsToSave = /* @__PURE__ */ new Map();
|
|
85
|
+
rerunContext = null;
|
|
85
86
|
/**
|
|
86
87
|
* @experimental
|
|
87
88
|
*/
|
|
@@ -122,6 +123,9 @@ class Processor {
|
|
|
122
123
|
if (!execution.workflow) {
|
|
123
124
|
execution.workflow = plugin.enabledCache.get(execution.workflowId) || await execution.getWorkflow({ transaction });
|
|
124
125
|
}
|
|
126
|
+
if (!execution.workflow) {
|
|
127
|
+
throw new Error(`workflow (#${execution.workflowId}) not found for execution (#${execution.id})`);
|
|
128
|
+
}
|
|
125
129
|
const nodes = execution.workflow.nodes || await execution.workflow.getNodes({ transaction });
|
|
126
130
|
execution.workflow.nodes = nodes;
|
|
127
131
|
this.makeNodes(nodes);
|
|
@@ -173,11 +177,64 @@ class Processor {
|
|
|
173
177
|
const node = this.nodesMap.get(job.nodeId);
|
|
174
178
|
await this.recall(node, job);
|
|
175
179
|
}
|
|
176
|
-
|
|
180
|
+
resolveRerun(options = {}) {
|
|
181
|
+
const node = this.getRerunNode(options.nodeId);
|
|
182
|
+
const targetJob = this.jobsMapByNodeKey[node.key];
|
|
183
|
+
if (options.nodeId != null && !targetJob) {
|
|
184
|
+
throw new Error(`job of node (#${node.id}) not found in execution (#${this.execution.id})`);
|
|
185
|
+
}
|
|
186
|
+
if (options.nodeId == null && options.overwrite && !targetJob) {
|
|
187
|
+
throw new Error(`job of head node (#${node.id}) not found in execution (#${this.execution.id})`);
|
|
188
|
+
}
|
|
189
|
+
const input = this.getRerunInput(node);
|
|
190
|
+
return { node, input, targetJob };
|
|
191
|
+
}
|
|
192
|
+
async rerun(options = {}) {
|
|
193
|
+
const { execution } = this;
|
|
194
|
+
if (execution.status !== import_constants.EXECUTION_STATUS.STARTED) {
|
|
195
|
+
throw new Error(`execution (#${execution.id}) is not started`);
|
|
196
|
+
}
|
|
197
|
+
await this.prepare();
|
|
198
|
+
const { node, input, targetJob } = this.resolveRerun(options);
|
|
199
|
+
this.rerunContext = {
|
|
200
|
+
overwrite: options.overwrite === true,
|
|
201
|
+
targetJob
|
|
202
|
+
};
|
|
203
|
+
try {
|
|
204
|
+
return await this.run(node, input, { rerun: true });
|
|
205
|
+
} finally {
|
|
206
|
+
this.rerunContext = null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
getRerunNode(nodeId) {
|
|
210
|
+
if (nodeId != null) {
|
|
211
|
+
const node = this.nodesMap.get(nodeId) || this.nodes.find((item) => String(item.id) === String(nodeId));
|
|
212
|
+
if (!node) {
|
|
213
|
+
throw new Error(`node (#${nodeId}) not found in workflow (#${this.execution.workflowId})`);
|
|
214
|
+
}
|
|
215
|
+
return node;
|
|
216
|
+
}
|
|
217
|
+
const head = this.nodes.find((item) => !item.upstream);
|
|
218
|
+
if (!head) {
|
|
219
|
+
throw new Error(`head node not found in workflow (#${this.execution.workflowId})`);
|
|
220
|
+
}
|
|
221
|
+
return head;
|
|
222
|
+
}
|
|
223
|
+
getRerunInput(node) {
|
|
224
|
+
if (!node.upstream) {
|
|
225
|
+
return { result: this.execution.context };
|
|
226
|
+
}
|
|
227
|
+
const upstreamJob = this.jobsMapByNodeKey[node.upstream.key];
|
|
228
|
+
if (!upstreamJob) {
|
|
229
|
+
throw new Error(`upstream job of node (#${node.id}) not found in execution (#${this.execution.id})`);
|
|
230
|
+
}
|
|
231
|
+
return upstreamJob;
|
|
232
|
+
}
|
|
233
|
+
async exec(instruction, node, prevJob, options) {
|
|
177
234
|
let job;
|
|
178
235
|
try {
|
|
179
236
|
this.logger.debug(`config of node`, { data: node.config, workflowId: node.workflowId });
|
|
180
|
-
job = await instruction(node, prevJob, this);
|
|
237
|
+
job = await instruction(node, prevJob, this, options);
|
|
181
238
|
if (job === null) {
|
|
182
239
|
return this.exit();
|
|
183
240
|
}
|
|
@@ -219,7 +276,7 @@ class Processor {
|
|
|
219
276
|
}
|
|
220
277
|
return this.end(node, savedJob);
|
|
221
278
|
}
|
|
222
|
-
async run(node, input) {
|
|
279
|
+
async run(node, input, options) {
|
|
223
280
|
const { instructions } = this.options.plugin;
|
|
224
281
|
const instruction = instructions.get(node.type);
|
|
225
282
|
if (!instruction) {
|
|
@@ -231,7 +288,7 @@ class Processor {
|
|
|
231
288
|
this.logger.info(`execution (${this.execution.id}) run instruction [${node.type}] for node (${node.id})`, {
|
|
232
289
|
workflowId: node.workflowId
|
|
233
290
|
});
|
|
234
|
-
return this.exec(instruction.run.bind(instruction), node, input);
|
|
291
|
+
return this.exec(instruction.run.bind(instruction), node, input, options);
|
|
235
292
|
}
|
|
236
293
|
// parent node should take over the control
|
|
237
294
|
async end(node, job) {
|
|
@@ -325,12 +382,21 @@ class Processor {
|
|
|
325
382
|
* @experimental
|
|
326
383
|
*/
|
|
327
384
|
saveJob(payload) {
|
|
385
|
+
var _a;
|
|
328
386
|
const { database } = this.execution.constructor;
|
|
329
387
|
const { model } = database.getCollection("jobs");
|
|
330
388
|
let job;
|
|
331
389
|
if (payload instanceof model) {
|
|
332
390
|
job = payload;
|
|
333
391
|
job.set("updatedAt", /* @__PURE__ */ new Date());
|
|
392
|
+
} else if (((_a = this.rerunContext) == null ? void 0 : _a.overwrite) && this.rerunContext.targetJob && this.rerunContext.targetJob.nodeId === payload.nodeId) {
|
|
393
|
+
job = this.rerunContext.targetJob;
|
|
394
|
+
job.set({
|
|
395
|
+
status: payload.status,
|
|
396
|
+
result: Object.prototype.hasOwnProperty.call(payload, "result") ? payload.result : null,
|
|
397
|
+
meta: Object.prototype.hasOwnProperty.call(payload, "meta") ? payload.meta : null,
|
|
398
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
399
|
+
});
|
|
334
400
|
} else {
|
|
335
401
|
job = model.build(
|
|
336
402
|
{
|
|
@@ -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,20 @@ 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
|
+
function getExecutionLockKey(executionId) {
|
|
49
|
+
return `workflow:execution:${executionId}`;
|
|
50
|
+
}
|
|
51
|
+
function isLockAcquireError(error) {
|
|
52
|
+
return error instanceof Error && error.constructor.name === "LockAcquireError";
|
|
53
|
+
}
|
|
46
54
|
async function destroy(context, next) {
|
|
47
55
|
context.action.mergeParams({
|
|
48
56
|
filter: {
|
|
@@ -67,30 +75,90 @@ async function cancel(context, next) {
|
|
|
67
75
|
if (execution.status) {
|
|
68
76
|
return context.throw(400);
|
|
69
77
|
}
|
|
70
|
-
|
|
71
|
-
await execution.
|
|
78
|
+
try {
|
|
79
|
+
const lock = await context.app.lockManager.tryAcquire(getExecutionLockKey(execution.id));
|
|
80
|
+
await lock.runExclusive(async () => {
|
|
81
|
+
await context.db.sequelize.transaction(async (transaction) => {
|
|
82
|
+
await execution.update(
|
|
83
|
+
{
|
|
84
|
+
status: import_constants.EXECUTION_STATUS.ABORTED
|
|
85
|
+
},
|
|
86
|
+
{ transaction }
|
|
87
|
+
);
|
|
88
|
+
const pendingJobs = execution.jobs.filter((job) => job.status === import_constants.JOB_STATUS.PENDING);
|
|
89
|
+
await JobRepo.update({
|
|
90
|
+
values: {
|
|
91
|
+
status: import_constants.JOB_STATUS.ABORTED
|
|
92
|
+
},
|
|
93
|
+
filter: {
|
|
94
|
+
id: pendingJobs.map((job) => job.id)
|
|
95
|
+
},
|
|
96
|
+
individualHooks: false,
|
|
97
|
+
transaction
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}, 6e4);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if (isLockAcquireError(error)) {
|
|
103
|
+
return context.throw(409, "Execution is being processed");
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
context.body = execution;
|
|
108
|
+
await next();
|
|
109
|
+
}
|
|
110
|
+
async function rerun(context, next) {
|
|
111
|
+
const workflowPlugin = context.app.pm.get(import_Plugin.default);
|
|
112
|
+
const { filterByTk, values = {} } = context.action.params;
|
|
113
|
+
const { nodeId, overwrite } = values;
|
|
114
|
+
const ExecutionRepo = context.db.getRepository("executions");
|
|
115
|
+
const execution = await ExecutionRepo.findOne({
|
|
116
|
+
filterByTk
|
|
117
|
+
});
|
|
118
|
+
if (!execution) {
|
|
119
|
+
return context.throw(404);
|
|
120
|
+
}
|
|
121
|
+
if (execution.status !== import_constants.EXECUTION_STATUS.STARTED) {
|
|
122
|
+
return context.throw(409, "Only started executions can be rerun");
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const lock = await context.app.lockManager.tryAcquire(getExecutionLockKey(execution.id));
|
|
126
|
+
await lock.runExclusive(async () => {
|
|
127
|
+
const processor = workflowPlugin.createProcessor(execution);
|
|
128
|
+
await processor.prepare();
|
|
129
|
+
processor.resolveRerun({
|
|
130
|
+
nodeId,
|
|
131
|
+
overwrite: overwrite === true
|
|
132
|
+
});
|
|
133
|
+
}, 6e4);
|
|
134
|
+
await workflowPlugin.run(
|
|
72
135
|
{
|
|
73
|
-
|
|
136
|
+
execution,
|
|
137
|
+
loaded: true,
|
|
138
|
+
rerun: {
|
|
139
|
+
nodeId,
|
|
140
|
+
overwrite: overwrite === true
|
|
141
|
+
}
|
|
74
142
|
},
|
|
75
|
-
{
|
|
143
|
+
{ dispatch: false }
|
|
76
144
|
);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
});
|
|
88
|
-
});
|
|
145
|
+
workflowPlugin.dispatch();
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (isLockAcquireError(error)) {
|
|
148
|
+
return context.throw(409, "Execution is being processed");
|
|
149
|
+
}
|
|
150
|
+
if (error instanceof Error) {
|
|
151
|
+
return context.throw(400, error.message);
|
|
152
|
+
}
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
89
155
|
context.body = execution;
|
|
156
|
+
context.status = 202;
|
|
90
157
|
await next();
|
|
91
158
|
}
|
|
92
159
|
// Annotate the CommonJS export names for ESM import in node:
|
|
93
160
|
0 && (module.exports = {
|
|
94
161
|
cancel,
|
|
95
|
-
destroy
|
|
162
|
+
destroy,
|
|
163
|
+
rerun
|
|
96
164
|
});
|
|
@@ -41,6 +41,12 @@ __export(jobs_exports, {
|
|
|
41
41
|
module.exports = __toCommonJS(jobs_exports);
|
|
42
42
|
var import_actions = require("@nocobase/actions");
|
|
43
43
|
var import_Plugin = __toESM(require("../Plugin"));
|
|
44
|
+
function getExecutionLockKey(executionId) {
|
|
45
|
+
return `workflow:execution:${executionId}`;
|
|
46
|
+
}
|
|
47
|
+
function isLockAcquireError(error) {
|
|
48
|
+
return error instanceof Error && error.constructor.name === "LockAcquireError";
|
|
49
|
+
}
|
|
44
50
|
async function resume(context, next) {
|
|
45
51
|
const repository = import_actions.utils.getRepositoryFromParams(context);
|
|
46
52
|
const workflowPlugin = context.app.pm.get(import_Plugin.default);
|
|
@@ -51,8 +57,21 @@ async function resume(context, next) {
|
|
|
51
57
|
if (!job) {
|
|
52
58
|
return context.throw(404, "Job not found");
|
|
53
59
|
}
|
|
60
|
+
if (!job.execution) {
|
|
61
|
+
job.execution = await job.getExecution();
|
|
62
|
+
}
|
|
54
63
|
workflowPlugin.getLogger(job.workflowId).warn(`Resuming job #${job.id}...`);
|
|
55
|
-
|
|
64
|
+
try {
|
|
65
|
+
const lock = await context.app.lockManager.tryAcquire(getExecutionLockKey(job.execution.id));
|
|
66
|
+
await lock.runExclusive(async () => {
|
|
67
|
+
await job.update(values);
|
|
68
|
+
}, 6e4);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (isLockAcquireError(error)) {
|
|
71
|
+
return context.throw(409, "Execution is being processed");
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
56
75
|
context.body = job;
|
|
57
76
|
context.status = 202;
|
|
58
77
|
await next();
|
|
@@ -24,7 +24,9 @@ export interface IJob {
|
|
|
24
24
|
* 3. `void` | Promise<void>: processor will do nothing, and terminate the current execution without any action.
|
|
25
25
|
*/
|
|
26
26
|
export type InstructionResult = IJob | Promise<IJob> | Promise<void> | Promise<null> | null | void;
|
|
27
|
-
export type Runner = (node: FlowNodeModel, input: any, processor: Processor
|
|
27
|
+
export type Runner = (node: FlowNodeModel, input: any, processor: Processor, options?: {
|
|
28
|
+
rerun?: true;
|
|
29
|
+
}) => InstructionResult;
|
|
28
30
|
export type InstructionInterface = {
|
|
29
31
|
run: Runner;
|
|
30
32
|
resume?: Runner;
|
|
@@ -37,6 +39,8 @@ export type InstructionInterface = {
|
|
|
37
39
|
export declare abstract class Instruction implements InstructionInterface {
|
|
38
40
|
workflow: Plugin;
|
|
39
41
|
constructor(workflow: Plugin);
|
|
40
|
-
abstract run(node: FlowNodeModel, input: any, processor: Processor
|
|
42
|
+
abstract run(node: FlowNodeModel, input: any, processor: Processor, options?: {
|
|
43
|
+
rerun?: true;
|
|
44
|
+
}): InstructionResult;
|
|
41
45
|
}
|
|
42
46
|
export default Instruction;
|
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.0.
|
|
9
|
+
"version": "2.0.58",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"main": "./dist/server/index.js",
|
|
12
12
|
"homepage": "https://docs.nocobase.com/handbook/workflow",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@nocobase/test": "2.x",
|
|
49
49
|
"@nocobase/utils": "2.x"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "158b99ec4f71555fc5125f61cfcf357763994da0",
|
|
52
52
|
"keywords": [
|
|
53
53
|
"Workflow"
|
|
54
54
|
]
|