@nocobase/plugin-workflow 2.1.0-beta.43 → 2.1.0-beta.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/index.js +1 -1
- 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 +3 -0
- package/dist/server/Dispatcher.js +123 -85
- package/dist/server/ExecutionTimeoutManager.d.ts +1 -0
- package/dist/server/ExecutionTimeoutManager.js +16 -8
- package/dist/server/Plugin.d.ts +4 -3
- package/dist/server/Plugin.js +5 -8
- package/dist/server/Processor.d.ts +0 -4
- package/dist/server/Processor.js +8 -22
- package/dist/server/instructions/OutputInstruction.js +1 -1
- package/dist/server/triggers/CollectionTrigger.js +6 -0
- package/dist/server/utils.js +34 -30
- 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.0-beta.
|
|
15
|
-
"@nocobase/utils": "2.1.0-beta.
|
|
14
|
+
"@nocobase/client": "2.1.0-beta.45",
|
|
15
|
+
"@nocobase/utils": "2.1.0-beta.45",
|
|
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.0-beta.
|
|
24
|
-
"@nocobase/plugin-mobile": "2.1.0-beta.
|
|
23
|
+
"@nocobase/flow-engine": "2.1.0-beta.45",
|
|
24
|
+
"@nocobase/plugin-mobile": "2.1.0-beta.45",
|
|
25
25
|
"sequelize": "6.35.2",
|
|
26
|
-
"@nocobase/server": "2.1.0-beta.
|
|
27
|
-
"@nocobase/database": "2.1.0-beta.
|
|
28
|
-
"@nocobase/data-source-manager": "2.1.0-beta.
|
|
29
|
-
"@nocobase/logger": "2.1.0-beta.
|
|
30
|
-
"@nocobase/evaluators": "2.1.0-beta.
|
|
26
|
+
"@nocobase/server": "2.1.0-beta.45",
|
|
27
|
+
"@nocobase/database": "2.1.0-beta.45",
|
|
28
|
+
"@nocobase/data-source-manager": "2.1.0-beta.45",
|
|
29
|
+
"@nocobase/logger": "2.1.0-beta.45",
|
|
30
|
+
"@nocobase/evaluators": "2.1.0-beta.45",
|
|
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.0-beta.
|
|
35
|
+
"@nocobase/actions": "2.1.0-beta.45",
|
|
36
36
|
"dayjs": "1.11.13",
|
|
37
|
-
"@nocobase/plugin-workflow-test": "2.1.0-beta.
|
|
38
|
-
"@nocobase/test": "2.1.0-beta.
|
|
37
|
+
"@nocobase/plugin-workflow-test": "2.1.0-beta.45",
|
|
38
|
+
"@nocobase/test": "2.1.0-beta.45"
|
|
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-06-
|
|
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-06-08T04:33:04.401Z"}
|
|
@@ -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-06-
|
|
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-06-08T04:33:04.152Z"}
|
|
@@ -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-06-
|
|
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-06-08T04:33:03.437Z"}
|
|
@@ -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-06-
|
|
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-06-08T04:33:03.280Z"}
|
|
@@ -52,7 +52,10 @@ export default class Dispatcher {
|
|
|
52
52
|
private validateEvent;
|
|
53
53
|
private createExecution;
|
|
54
54
|
private prepare;
|
|
55
|
+
private acquireExecution;
|
|
56
|
+
private acquireWithRetry;
|
|
55
57
|
private enter;
|
|
58
|
+
private isConcurrentAcquireError;
|
|
56
59
|
private process;
|
|
57
60
|
}
|
|
58
61
|
export {};
|
|
@@ -34,6 +34,7 @@ var import_sequelize = require("sequelize");
|
|
|
34
34
|
var import_constants = require("./constants");
|
|
35
35
|
var import_Plugin = require("./Plugin");
|
|
36
36
|
var import_utils = require("./utils");
|
|
37
|
+
const EXECUTION_ACQUIRE_MAX_ATTEMPTS = 5;
|
|
37
38
|
class Dispatcher {
|
|
38
39
|
constructor(plugin) {
|
|
39
40
|
this.plugin = plugin;
|
|
@@ -248,9 +249,7 @@ class Dispatcher {
|
|
|
248
249
|
return null;
|
|
249
250
|
}
|
|
250
251
|
try {
|
|
251
|
-
const entered = await this.prepare(execution
|
|
252
|
-
transaction: this.plugin.useDataSourceTransaction("main", options.transaction)
|
|
253
|
-
});
|
|
252
|
+
const entered = await this.prepare(execution);
|
|
254
253
|
if (!entered) {
|
|
255
254
|
return null;
|
|
256
255
|
}
|
|
@@ -274,8 +273,7 @@ class Dispatcher {
|
|
|
274
273
|
const existed = await workflow.countExecutions({
|
|
275
274
|
where: {
|
|
276
275
|
id: stack
|
|
277
|
-
}
|
|
278
|
-
transaction: options.transaction
|
|
276
|
+
}
|
|
279
277
|
});
|
|
280
278
|
const limitCount = workflow.options.stackLimit || 1;
|
|
281
279
|
if (existed >= limitCount) {
|
|
@@ -290,115 +288,146 @@ class Dispatcher {
|
|
|
290
288
|
async createExecution(workflow, context, options) {
|
|
291
289
|
var _a;
|
|
292
290
|
const { deferred } = options;
|
|
293
|
-
const transaction = await this.plugin.useDataSourceTransaction("main", options.transaction, true);
|
|
294
|
-
const sameTransaction = options.transaction === transaction;
|
|
295
291
|
let stack = options.stack;
|
|
296
292
|
if (options.parentExecutionId && !stack) {
|
|
297
293
|
const parentExecution = await this.plugin.db.getRepository("executions").findOne({
|
|
298
|
-
filterByTk: options.parentExecutionId
|
|
299
|
-
transaction
|
|
294
|
+
filterByTk: options.parentExecutionId
|
|
300
295
|
});
|
|
301
296
|
stack = parentExecution ? [...parentExecution.stack ?? [], parentExecution.id] : [];
|
|
302
297
|
}
|
|
303
|
-
const valid = await this.validateEvent(workflow, context, { ...options, stack
|
|
298
|
+
const valid = await this.validateEvent(workflow, context, { ...options, stack });
|
|
304
299
|
if (!valid) {
|
|
305
|
-
if (!sameTransaction) {
|
|
306
|
-
await transaction.commit();
|
|
307
|
-
}
|
|
308
300
|
(_a = options.onTriggerFail) == null ? void 0 : _a.call(options, workflow, context, options);
|
|
309
301
|
throw new Error("event is not valid");
|
|
310
302
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
status: deferred ? import_constants.EXECUTION_STATUS.STARTED : import_constants.EXECUTION_STATUS.QUEUEING,
|
|
322
|
-
manually: options.manually
|
|
323
|
-
},
|
|
324
|
-
{ transaction }
|
|
325
|
-
);
|
|
326
|
-
} catch (err) {
|
|
327
|
-
if (!sameTransaction) {
|
|
328
|
-
await transaction.rollback();
|
|
329
|
-
}
|
|
330
|
-
throw err;
|
|
331
|
-
}
|
|
303
|
+
const execution = await workflow.createExecution({
|
|
304
|
+
context,
|
|
305
|
+
key: workflow.key,
|
|
306
|
+
eventKey: options.eventKey ?? (0, import_node_crypto.randomUUID)(),
|
|
307
|
+
stack,
|
|
308
|
+
parentExecutionId: options.parentExecutionId ?? null,
|
|
309
|
+
dispatched: deferred ?? false,
|
|
310
|
+
status: deferred ? import_constants.EXECUTION_STATUS.STARTED : import_constants.EXECUTION_STATUS.QUEUEING,
|
|
311
|
+
manually: options.manually
|
|
312
|
+
});
|
|
332
313
|
this.plugin.getLogger(workflow.id).info(`execution of workflow ${workflow.id} created as ${execution.id}`);
|
|
333
314
|
if (!workflow.stats) {
|
|
334
|
-
workflow.stats = await workflow.getStats(
|
|
315
|
+
workflow.stats = await workflow.getStats();
|
|
335
316
|
}
|
|
336
|
-
await workflow.stats.increment("executed"
|
|
317
|
+
await workflow.stats.increment("executed");
|
|
337
318
|
if (this.plugin.db.options.dialect !== "postgres") {
|
|
338
|
-
await workflow.stats.reload(
|
|
319
|
+
await workflow.stats.reload();
|
|
339
320
|
}
|
|
340
321
|
if (!workflow.versionStats) {
|
|
341
|
-
workflow.versionStats = await workflow.getVersionStats(
|
|
322
|
+
workflow.versionStats = await workflow.getVersionStats();
|
|
342
323
|
}
|
|
343
|
-
await workflow.versionStats.increment("executed"
|
|
324
|
+
await workflow.versionStats.increment("executed");
|
|
344
325
|
if (this.plugin.db.options.dialect !== "postgres") {
|
|
345
|
-
await workflow.versionStats.reload(
|
|
346
|
-
}
|
|
347
|
-
if (!sameTransaction) {
|
|
348
|
-
await transaction.commit();
|
|
326
|
+
await workflow.versionStats.reload();
|
|
349
327
|
}
|
|
350
328
|
execution.workflow = workflow;
|
|
351
329
|
return execution;
|
|
352
330
|
}
|
|
353
331
|
async prepare(input, options = {}) {
|
|
354
|
-
const transaction = options.transaction;
|
|
355
|
-
const ownTransaction = !transaction;
|
|
356
|
-
const tx = transaction || await this.plugin.db.sequelize.transaction({
|
|
357
|
-
isolationLevel: this.plugin.db.options.dialect === "sqlite" ? void 0 : import_sequelize.Transaction.ISOLATION_LEVELS.REPEATABLE_READ
|
|
358
|
-
});
|
|
359
332
|
const logger = input ? this.plugin.getLogger(input.workflowId) : this.plugin.getLogger("dispatcher");
|
|
333
|
+
if (options.transaction) {
|
|
334
|
+
try {
|
|
335
|
+
const { execution } = await this.acquireExecution(input, options, options.transaction);
|
|
336
|
+
return execution;
|
|
337
|
+
} catch (error) {
|
|
338
|
+
if (error instanceof Error) {
|
|
339
|
+
logger.error(`entering execution failed: ${error.message}`, { error });
|
|
340
|
+
}
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
let result = null;
|
|
360
345
|
try {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
346
|
+
await this.acquireWithRetry(
|
|
347
|
+
async () => {
|
|
348
|
+
const tx = await this.plugin.db.sequelize.transaction({
|
|
349
|
+
isolationLevel: this.plugin.db.options.dialect === "sqlite" ? void 0 : import_sequelize.Transaction.ISOLATION_LEVELS.REPEATABLE_READ
|
|
350
|
+
});
|
|
351
|
+
try {
|
|
352
|
+
const { execution, shouldRetry } = await this.acquireExecution(input, options, tx);
|
|
353
|
+
await tx.commit();
|
|
354
|
+
result = execution;
|
|
355
|
+
return shouldRetry;
|
|
356
|
+
} catch (error) {
|
|
357
|
+
await tx.rollback();
|
|
358
|
+
if (this.isConcurrentAcquireError(error)) {
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
if (error instanceof Error) {
|
|
362
|
+
logger.error(`entering execution failed: ${error.message}`, { error });
|
|
363
|
+
}
|
|
364
|
+
result = null;
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
logger,
|
|
370
|
+
conflictMessage: input ? `acquiring pending execution (${input.id}) conflicted with another worker, retrying` : `acquiring execution conflicted with another worker, retrying`,
|
|
371
|
+
maxAttemptsMessage: input ? `acquiring pending execution (${input.id}) reached max retry attempts` : `acquiring execution reached max retry attempts, will retry on next dispatch`
|
|
365
372
|
}
|
|
373
|
+
);
|
|
374
|
+
} catch (error) {
|
|
375
|
+
if (error instanceof Error) {
|
|
376
|
+
logger.error(`acquiring execution failed: ${error.message}`, { error });
|
|
377
|
+
}
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
382
|
+
async acquireExecution(input, options, transaction) {
|
|
383
|
+
let execution = input;
|
|
384
|
+
if (execution) {
|
|
385
|
+
if (!options.immediate || execution.status !== import_constants.EXECUTION_STATUS.QUEUEING) {
|
|
386
|
+
await execution.reload({ transaction });
|
|
387
|
+
}
|
|
388
|
+
} else {
|
|
389
|
+
execution = await this.plugin.db.getRepository("executions").findOne({
|
|
390
|
+
filter: {
|
|
391
|
+
dispatched: false,
|
|
392
|
+
"workflow.enabled": true
|
|
393
|
+
},
|
|
394
|
+
sort: "id",
|
|
395
|
+
transaction,
|
|
396
|
+
lock: transaction.LOCK.UPDATE,
|
|
397
|
+
skipLocked: true
|
|
398
|
+
});
|
|
399
|
+
if (execution) {
|
|
400
|
+
this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) fetched from db`);
|
|
366
401
|
} else {
|
|
367
|
-
|
|
368
|
-
filter: {
|
|
369
|
-
dispatched: false,
|
|
370
|
-
"workflow.enabled": true
|
|
371
|
-
},
|
|
372
|
-
sort: "id",
|
|
373
|
-
transaction: tx,
|
|
374
|
-
lock: tx.LOCK.UPDATE,
|
|
375
|
-
skipLocked: true
|
|
376
|
-
});
|
|
377
|
-
if (execution) {
|
|
378
|
-
this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) fetched from db`);
|
|
379
|
-
} else {
|
|
380
|
-
this.plugin.getLogger("dispatcher").debug(`no execution in db queued to process`);
|
|
381
|
-
}
|
|
402
|
+
this.plugin.getLogger("dispatcher").debug(`no execution in db queued to process`);
|
|
382
403
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
404
|
+
}
|
|
405
|
+
if (!execution) {
|
|
406
|
+
return { execution: null, shouldRetry: false };
|
|
407
|
+
}
|
|
408
|
+
const entered = await this.enter(execution, transaction);
|
|
409
|
+
const shouldRetry = !input && !entered;
|
|
410
|
+
return { execution: entered, shouldRetry };
|
|
411
|
+
}
|
|
412
|
+
async acquireWithRetry(acquire, options) {
|
|
413
|
+
for (let attempt = 1; attempt <= EXECUTION_ACQUIRE_MAX_ATTEMPTS; attempt++) {
|
|
414
|
+
let shouldRetry = false;
|
|
415
|
+
try {
|
|
416
|
+
shouldRetry = await acquire();
|
|
417
|
+
} catch (error) {
|
|
418
|
+
if (!this.isConcurrentAcquireError(error)) {
|
|
419
|
+
throw error;
|
|
386
420
|
}
|
|
387
|
-
|
|
421
|
+
shouldRetry = true;
|
|
422
|
+
options.logger.warn(options.conflictMessage, { error });
|
|
388
423
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
await tx.commit();
|
|
424
|
+
if (!shouldRetry) {
|
|
425
|
+
break;
|
|
392
426
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
await tx.rollback();
|
|
397
|
-
}
|
|
398
|
-
if (error instanceof Error) {
|
|
399
|
-
logger.error(`entering execution failed: ${error.message}`, { error });
|
|
427
|
+
if (attempt >= EXECUTION_ACQUIRE_MAX_ATTEMPTS) {
|
|
428
|
+
options.logger.warn(options.maxAttemptsMessage);
|
|
429
|
+
break;
|
|
400
430
|
}
|
|
401
|
-
return null;
|
|
402
431
|
}
|
|
403
432
|
}
|
|
404
433
|
async enter(execution, transaction) {
|
|
@@ -446,14 +475,23 @@ class Dispatcher {
|
|
|
446
475
|
execution.workflow = workflow;
|
|
447
476
|
return execution;
|
|
448
477
|
}
|
|
478
|
+
isConcurrentAcquireError(error) {
|
|
479
|
+
var _a, _b, _c, _d;
|
|
480
|
+
if (!(error instanceof Error)) {
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
const databaseError = error;
|
|
484
|
+
const code = ((_a = databaseError.parent) == null ? void 0 : _a.code) || ((_b = databaseError.original) == null ? void 0 : _b.code);
|
|
485
|
+
const errno = ((_c = databaseError.parent) == null ? void 0 : _c.errno) || ((_d = databaseError.original) == null ? void 0 : _d.errno);
|
|
486
|
+
return code === "40001" || code === "40P01" || code === "ER_LOCK_DEADLOCK" || errno === 1205 || errno === 1213;
|
|
487
|
+
}
|
|
449
488
|
async process(execution, job = null, options = {}) {
|
|
450
489
|
const { rerun, ...processorOptions } = options;
|
|
451
490
|
const logger = this.plugin.getLogger(execution.workflowId);
|
|
452
491
|
const run = async () => {
|
|
453
492
|
var _a, _b, _c;
|
|
454
493
|
if (!execution.dispatched) {
|
|
455
|
-
|
|
456
|
-
await execution.update({ dispatched: true, status: import_constants.EXECUTION_STATUS.STARTED }, { transaction });
|
|
494
|
+
await execution.update({ dispatched: true, status: import_constants.EXECUTION_STATUS.STARTED });
|
|
457
495
|
logger.info(`execution (${execution.id}) from pending list updated to started`);
|
|
458
496
|
}
|
|
459
497
|
this.plugin.timeoutManager.scheduleExecutionTimeout(execution);
|
|
@@ -464,7 +502,7 @@ class Dispatcher {
|
|
|
464
502
|
logger.info(`execution (${execution.id}) finished with status: ${execution.status}`);
|
|
465
503
|
logger.debug(`execution (${execution.id}) details:`, { execution });
|
|
466
504
|
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))) {
|
|
467
|
-
await execution.destroy(
|
|
505
|
+
await execution.destroy();
|
|
468
506
|
}
|
|
469
507
|
} catch (err) {
|
|
470
508
|
if (err instanceof Error) {
|
|
@@ -26,6 +26,7 @@ export default class ExecutionTimeoutManager {
|
|
|
26
26
|
getRemainingMs(execution: ExecutionModel, now?: Date): number;
|
|
27
27
|
abort(execution: ExecutionModel, options?: Transactionable): Promise<boolean>;
|
|
28
28
|
abortExecutionIfExpired(execution: ExecutionModel, options?: Transactionable): Promise<boolean>;
|
|
29
|
+
private abortExecutionIfExpiredWithLock;
|
|
29
30
|
clear(executionId: number | string): void;
|
|
30
31
|
shouldContinue(execution: ExecutionModel, options?: Transactionable): Promise<boolean>;
|
|
31
32
|
/**
|
|
@@ -36,6 +36,7 @@ const LOAD_BATCH_SIZE = 1e3;
|
|
|
36
36
|
const DEFAULT_SCAN_INTERVAL = 3e4;
|
|
37
37
|
const SCAN_JITTER = 5e3;
|
|
38
38
|
const MAX_TIMER_DELAY = 2147483647;
|
|
39
|
+
const EXECUTION_LOCK_TIMEOUT = 6e4;
|
|
39
40
|
class ExecutionTimeoutManager {
|
|
40
41
|
constructor(plugin) {
|
|
41
42
|
this.plugin = plugin;
|
|
@@ -105,6 +106,19 @@ class ExecutionTimeoutManager {
|
|
|
105
106
|
}
|
|
106
107
|
return this.abort(execution, options);
|
|
107
108
|
}
|
|
109
|
+
async abortExecutionIfExpiredWithLock(executionOrId) {
|
|
110
|
+
const executionId = typeof executionOrId === "object" ? executionOrId.id : executionOrId;
|
|
111
|
+
const lock = await this.plugin.app.lockManager.tryAcquire((0, import_utils.getExecutionLockKey)(executionId), EXECUTION_LOCK_TIMEOUT);
|
|
112
|
+
return lock.runExclusive(async () => {
|
|
113
|
+
const execution = await this.plugin.db.getRepository("executions").findOne({
|
|
114
|
+
filterByTk: executionId
|
|
115
|
+
});
|
|
116
|
+
if (!execution) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
return this.abortExecutionIfExpired(execution);
|
|
120
|
+
}, EXECUTION_LOCK_TIMEOUT);
|
|
121
|
+
}
|
|
108
122
|
clear(executionId) {
|
|
109
123
|
this.clearExecutionTimeout(executionId);
|
|
110
124
|
}
|
|
@@ -195,7 +209,7 @@ class ExecutionTimeoutManager {
|
|
|
195
209
|
limit: LOAD_BATCH_SIZE
|
|
196
210
|
});
|
|
197
211
|
for (const execution of executions) {
|
|
198
|
-
await this.
|
|
212
|
+
await this.abortExecutionIfExpiredWithLock(execution);
|
|
199
213
|
}
|
|
200
214
|
})();
|
|
201
215
|
try {
|
|
@@ -301,12 +315,6 @@ class ExecutionTimeoutManager {
|
|
|
301
315
|
if (this.plugin.abortRunningExecution(executionId, import_constants.EXECUTION_REASON.TIMEOUT)) {
|
|
302
316
|
return;
|
|
303
317
|
}
|
|
304
|
-
|
|
305
|
-
filterByTk: executionId
|
|
306
|
-
});
|
|
307
|
-
if (!execution) {
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
await this.abortExecutionIfExpired(execution);
|
|
318
|
+
await this.abortExecutionIfExpiredWithLock(executionId);
|
|
311
319
|
}
|
|
312
320
|
}
|
package/dist/server/Plugin.d.ts
CHANGED
|
@@ -89,10 +89,11 @@ export default class PluginWorkflowServer extends Plugin {
|
|
|
89
89
|
* @experimental
|
|
90
90
|
* @param {string} dataSourceName
|
|
91
91
|
* @param {Transaction} transaction
|
|
92
|
-
* @param {boolean} create
|
|
93
|
-
* @returns {
|
|
92
|
+
* @param {boolean} create Create a new transaction when the input transaction does not belong to this data source.
|
|
93
|
+
* @returns {Transaction}
|
|
94
94
|
*/
|
|
95
|
-
useDataSourceTransaction(dataSourceName
|
|
95
|
+
useDataSourceTransaction(dataSourceName?: string, transaction?: Transaction | null, create?: false): Transaction | undefined;
|
|
96
|
+
useDataSourceTransaction(dataSourceName: string | undefined, transaction: Transaction | null | undefined, create: true): Transaction | Promise<Transaction> | undefined;
|
|
96
97
|
/**
|
|
97
98
|
* @experimental
|
|
98
99
|
*/
|
package/dist/server/Plugin.js
CHANGED
|
@@ -477,15 +477,12 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
477
477
|
}
|
|
478
478
|
return trigger.execute(workflow, values, options);
|
|
479
479
|
}
|
|
480
|
-
/**
|
|
481
|
-
* @experimental
|
|
482
|
-
* @param {string} dataSourceName
|
|
483
|
-
* @param {Transaction} transaction
|
|
484
|
-
* @param {boolean} create
|
|
485
|
-
* @returns {Trasaction}
|
|
486
|
-
*/
|
|
487
480
|
useDataSourceTransaction(dataSourceName = "main", transaction, create = false) {
|
|
488
|
-
const
|
|
481
|
+
const dataSource = this.app.dataSourceManager.dataSources.get(dataSourceName);
|
|
482
|
+
if (!dataSource) {
|
|
483
|
+
throw new Error(`data source ${dataSourceName} is not found`);
|
|
484
|
+
}
|
|
485
|
+
const { db } = dataSource.collectionManager;
|
|
489
486
|
if (!db) {
|
|
490
487
|
return;
|
|
491
488
|
}
|
package/dist/server/Processor.js
CHANGED
|
@@ -68,10 +68,6 @@ class Processor {
|
|
|
68
68
|
* @experimental
|
|
69
69
|
*/
|
|
70
70
|
transaction = null;
|
|
71
|
-
/**
|
|
72
|
-
* @experimental
|
|
73
|
-
*/
|
|
74
|
-
mainTransaction = null;
|
|
75
71
|
/**
|
|
76
72
|
* @experimental
|
|
77
73
|
*/
|
|
@@ -210,15 +206,13 @@ class Processor {
|
|
|
210
206
|
execution,
|
|
211
207
|
options: { plugin }
|
|
212
208
|
} = this;
|
|
213
|
-
this.mainTransaction = plugin.useDataSourceTransaction("main", this.transaction);
|
|
214
|
-
const transaction = this.mainTransaction;
|
|
215
209
|
if (!execution.workflow) {
|
|
216
|
-
execution.workflow = plugin.enabledCache.get(execution.workflowId) || await execution.getWorkflow(
|
|
210
|
+
execution.workflow = plugin.enabledCache.get(execution.workflowId) || await execution.getWorkflow();
|
|
217
211
|
}
|
|
218
212
|
if (!execution.workflow) {
|
|
219
213
|
throw new Error(`workflow (#${execution.workflowId}) not found for execution (#${execution.id})`);
|
|
220
214
|
}
|
|
221
|
-
const nodes = execution.workflow.nodes || await execution.workflow.getNodes(
|
|
215
|
+
const nodes = execution.workflow.nodes || await execution.workflow.getNodes();
|
|
222
216
|
execution.workflow.nodes = nodes;
|
|
223
217
|
this.makeNodes(nodes);
|
|
224
218
|
const JobDBModel = plugin.db.getModel("jobs");
|
|
@@ -228,15 +222,13 @@ class Processor {
|
|
|
228
222
|
where: {
|
|
229
223
|
executionId: execution.id
|
|
230
224
|
},
|
|
231
|
-
raw: true
|
|
232
|
-
transaction
|
|
225
|
+
raw: true
|
|
233
226
|
});
|
|
234
227
|
const jobs = await execution.getJobs({
|
|
235
228
|
where: {
|
|
236
229
|
id: jobIds.map((item) => item.id)
|
|
237
230
|
},
|
|
238
|
-
order: [["id", "ASC"]]
|
|
239
|
-
transaction
|
|
231
|
+
order: [["id", "ASC"]]
|
|
240
232
|
});
|
|
241
233
|
execution.jobs = jobs;
|
|
242
234
|
this.makeJobs(jobs);
|
|
@@ -478,7 +470,7 @@ class Processor {
|
|
|
478
470
|
if (changes.length) {
|
|
479
471
|
await this.options.plugin.db.sequelize.query(
|
|
480
472
|
`UPDATE ${JobCollection.quotedTableName()} SET ${changes.map(([key]) => `${key} = ?`)} WHERE id='${job.id}'`,
|
|
481
|
-
{ replacements: changes.map(([, value]) => value)
|
|
473
|
+
{ replacements: changes.map(([, value]) => value) }
|
|
482
474
|
);
|
|
483
475
|
}
|
|
484
476
|
}
|
|
@@ -488,7 +480,6 @@ class Processor {
|
|
|
488
480
|
await JobsModel.bulkCreate(
|
|
489
481
|
newJobs.map((job) => job.toJSON()),
|
|
490
482
|
{
|
|
491
|
-
transaction: this.mainTransaction,
|
|
492
483
|
returning: false
|
|
493
484
|
}
|
|
494
485
|
);
|
|
@@ -510,18 +501,14 @@ class Processor {
|
|
|
510
501
|
id: this.execution.id,
|
|
511
502
|
status: import_constants.EXECUTION_STATUS.STARTED
|
|
512
503
|
},
|
|
513
|
-
individualHooks: true
|
|
514
|
-
transaction: this.mainTransaction
|
|
504
|
+
individualHooks: true
|
|
515
505
|
});
|
|
516
506
|
if (affected) {
|
|
517
507
|
this.execution.set(values);
|
|
518
508
|
} else {
|
|
519
|
-
await this.execution.reload(
|
|
509
|
+
await this.execution.reload();
|
|
520
510
|
}
|
|
521
511
|
}
|
|
522
|
-
if (this.mainTransaction && this.mainTransaction !== this.transaction) {
|
|
523
|
-
await this.mainTransaction.commit();
|
|
524
|
-
}
|
|
525
512
|
if (this.execution.status === import_constants.EXECUTION_STATUS.STARTED) {
|
|
526
513
|
this.options.plugin.timeoutManager.scheduleExecutionTimeout(this.execution);
|
|
527
514
|
} else {
|
|
@@ -598,8 +585,7 @@ class Processor {
|
|
|
598
585
|
this.setTimeoutGuard(remaining);
|
|
599
586
|
}
|
|
600
587
|
async shouldContinueExecution() {
|
|
601
|
-
|
|
602
|
-
return this.options.plugin.timeoutManager.shouldContinue(this.execution, { transaction });
|
|
588
|
+
return this.options.plugin.timeoutManager.shouldContinue(this.execution);
|
|
603
589
|
}
|
|
604
590
|
leaveRunningState() {
|
|
605
591
|
if (this.timeoutGuard) {
|
|
@@ -50,7 +50,7 @@ class ExecutionResultInstruction extends import__.Instruction {
|
|
|
50
50
|
const { value } = node.config;
|
|
51
51
|
const output = processor.getParsedValue(value, node.id);
|
|
52
52
|
try {
|
|
53
|
-
await processor.execution.update({ output }, { hooks: false
|
|
53
|
+
await processor.execution.update({ output }, { hooks: false });
|
|
54
54
|
} catch (e) {
|
|
55
55
|
return {
|
|
56
56
|
result: e.message,
|
|
@@ -124,6 +124,12 @@ class CollectionTrigger extends import__.default {
|
|
|
124
124
|
return;
|
|
125
125
|
}
|
|
126
126
|
if (workflow.sync) {
|
|
127
|
+
if (transaction && this.workflow.db.options.dialect === "sqlite") {
|
|
128
|
+
transaction.afterCommit(async () => {
|
|
129
|
+
await this.workflow.trigger(workflow, ctx, { stack });
|
|
130
|
+
});
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
127
133
|
await this.workflow.trigger(workflow, ctx, {
|
|
128
134
|
transaction,
|
|
129
135
|
stack
|