@nocobase/plugin-workflow 2.1.0-beta.9 → 2.2.0-beta.1
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/214.7e602cfe7a8251b8.js +10 -0
- package/dist/client/618.19af7f84261c815d.js +10 -0
- package/dist/client/67.452743ce8ec30617.js +10 -0
- package/dist/client/964.ffbf5b47ed12bbdc.js +10 -0
- package/dist/client/Branch.d.ts +7 -3
- package/dist/client/BranchContext.d.ts +18 -0
- package/dist/client/components/TimeoutInput.d.ts +11 -0
- package/dist/client/constants.d.ts +13 -0
- package/dist/client/flows/triggerWorkflows.d.ts +14 -1
- package/dist/client/hooks/{useWorkflowFilterActionProps.d.ts → useResourceFilterActionProps.d.ts} +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/nodes/create.d.ts +10 -0
- package/dist/client/nodes/destroy.d.ts +10 -0
- package/dist/client/nodes/index.d.ts +5 -0
- package/dist/client/nodes/query.d.ts +18 -2
- package/dist/client/nodes/update.d.ts +10 -0
- package/dist/client/schemas/collection.d.ts +8 -2
- package/dist/client/schemas/executions.d.ts +63 -2
- package/dist/client/triggers/collection.d.ts +14 -1
- package/dist/client/triggers/index.d.ts +4 -0
- package/dist/client/triggers/schedule/constants.d.ts +4 -0
- package/dist/client/triggers/schedule/index.d.ts +15 -0
- package/dist/client/utils.d.ts +17 -0
- package/dist/common/collections/executions.d.ts +44 -1
- package/dist/common/collections/executions.js +63 -1
- package/dist/common/collections/flow_nodes.d.ts +1 -0
- package/dist/common/collections/flow_nodes.js +1 -0
- package/dist/common/collections/jobs.d.ts +1 -0
- package/dist/common/collections/jobs.js +8 -0
- package/dist/common/collections/userWorkflowTasks.d.ts +1 -0
- package/dist/common/collections/userWorkflowTasks.js +1 -0
- package/dist/common/collections/workflowCategories.d.ts +1 -0
- package/dist/common/collections/workflowCategories.js +1 -0
- package/dist/common/collections/workflowCategoryRelations.d.ts +1 -0
- package/dist/common/collections/workflowCategoryRelations.js +1 -0
- package/dist/common/collections/workflowStats.d.ts +1 -0
- package/dist/common/collections/workflowStats.js +1 -0
- package/dist/common/collections/workflowTasks.js +1 -0
- package/dist/common/collections/workflowVersionStats.d.ts +1 -0
- package/dist/common/collections/workflowVersionStats.js +1 -0
- package/dist/common/collections/workflows.d.ts +66 -11
- package/dist/common/collections/workflows.js +35 -2
- package/dist/common/constants.d.ts +5 -0
- package/dist/common/constants.js +7 -0
- package/dist/externalVersion.js +15 -13
- package/dist/locale/de-DE.json +4 -0
- package/dist/locale/en-US.json +7 -0
- package/dist/locale/es-ES.json +4 -0
- package/dist/locale/fr-FR.json +4 -0
- package/dist/locale/hu-HU.json +7 -3
- package/dist/locale/id-ID.json +4 -0
- package/dist/locale/it-IT.json +4 -0
- package/dist/locale/ja-JP.json +5 -1
- package/dist/locale/ko-KR.json +4 -0
- package/dist/locale/nl-NL.json +7 -3
- package/dist/locale/pt-BR.json +4 -0
- package/dist/locale/ru-RU.json +4 -0
- package/dist/locale/tr-TR.json +4 -0
- package/dist/locale/uk-UA.json +7 -3
- package/dist/locale/vi-VN.json +7 -3
- package/dist/locale/zh-CN.json +10 -0
- package/dist/locale/zh-TW.json +7 -3
- package/dist/node_modules/cron-parser/lib/parser.js +1 -1
- package/dist/node_modules/cron-parser/package.json +1 -1
- package/dist/node_modules/joi/dist/joi-browser.min.js +1 -0
- package/dist/node_modules/joi/lib/annotate.js +175 -0
- package/dist/node_modules/joi/lib/base.js +1069 -0
- package/dist/node_modules/joi/lib/cache.js +143 -0
- package/dist/node_modules/joi/lib/common.js +216 -0
- package/dist/node_modules/joi/lib/compile.js +283 -0
- package/dist/node_modules/joi/lib/errors.js +271 -0
- package/dist/node_modules/joi/lib/extend.js +312 -0
- package/dist/node_modules/joi/lib/index.d.ts +2365 -0
- package/dist/node_modules/joi/lib/index.js +1 -0
- package/dist/node_modules/joi/lib/manifest.js +476 -0
- package/dist/node_modules/joi/lib/messages.js +178 -0
- package/dist/node_modules/joi/lib/modify.js +267 -0
- package/dist/node_modules/joi/lib/ref.js +414 -0
- package/dist/node_modules/joi/lib/schemas.js +302 -0
- package/dist/node_modules/joi/lib/state.js +166 -0
- package/dist/node_modules/joi/lib/template.js +463 -0
- package/dist/node_modules/joi/lib/trace.js +346 -0
- package/dist/node_modules/joi/lib/types/alternatives.js +364 -0
- package/dist/node_modules/joi/lib/types/any.js +174 -0
- package/dist/node_modules/joi/lib/types/array.js +809 -0
- package/dist/node_modules/joi/lib/types/binary.js +100 -0
- package/dist/node_modules/joi/lib/types/boolean.js +150 -0
- package/dist/node_modules/joi/lib/types/date.js +233 -0
- package/dist/node_modules/joi/lib/types/function.js +93 -0
- package/dist/node_modules/joi/lib/types/keys.js +1067 -0
- package/dist/node_modules/joi/lib/types/link.js +168 -0
- package/dist/node_modules/joi/lib/types/number.js +363 -0
- package/dist/node_modules/joi/lib/types/object.js +22 -0
- package/dist/node_modules/joi/lib/types/string.js +850 -0
- package/dist/node_modules/joi/lib/types/symbol.js +102 -0
- package/dist/node_modules/joi/lib/validator.js +750 -0
- package/dist/node_modules/joi/lib/values.js +263 -0
- package/dist/node_modules/joi/node_modules/@hapi/topo/lib/index.d.ts +60 -0
- package/dist/node_modules/joi/node_modules/@hapi/topo/lib/index.js +225 -0
- package/dist/node_modules/joi/node_modules/@hapi/topo/package.json +30 -0
- package/dist/node_modules/joi/package.json +1 -0
- package/dist/node_modules/lru-cache/dist/commonjs/diagnostics-channel.d.ts +5 -0
- package/dist/node_modules/lru-cache/dist/commonjs/diagnostics-channel.js +10 -0
- package/dist/node_modules/lru-cache/dist/commonjs/index.d.ts +1381 -0
- package/dist/node_modules/lru-cache/dist/commonjs/index.js +1692 -0
- package/dist/node_modules/lru-cache/dist/commonjs/index.min.js +1 -0
- package/dist/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.d.ts +5 -0
- package/dist/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.js +4 -0
- package/dist/node_modules/lru-cache/dist/esm/browser/index.d.ts +1381 -0
- package/dist/node_modules/lru-cache/dist/{mjs → esm/browser}/index.js +537 -179
- package/dist/node_modules/lru-cache/dist/esm/browser/index.min.js +2 -0
- package/dist/node_modules/lru-cache/dist/esm/diagnostics-channel.d.ts +5 -0
- package/dist/node_modules/lru-cache/dist/esm/diagnostics-channel.js +19 -0
- package/dist/node_modules/lru-cache/dist/esm/index.d.ts +1381 -0
- package/dist/node_modules/lru-cache/dist/{cjs → esm}/index.js +538 -184
- package/dist/node_modules/lru-cache/dist/esm/index.min.js +2 -0
- package/dist/node_modules/lru-cache/dist/esm/node/diagnostics-channel.d.ts +5 -0
- package/dist/node_modules/lru-cache/dist/esm/node/diagnostics-channel.js +7 -0
- package/dist/node_modules/lru-cache/dist/esm/node/index.d.ts +1381 -0
- package/dist/node_modules/lru-cache/dist/esm/node/index.js +1688 -0
- package/dist/node_modules/lru-cache/dist/esm/node/index.min.js +2 -0
- package/dist/node_modules/lru-cache/package.json +1 -1
- package/dist/node_modules/nodejs-snowflake/nodejs_snowflake.js +1 -1
- package/dist/node_modules/nodejs-snowflake/package.json +1 -1
- package/dist/server/Dispatcher.d.ts +12 -6
- package/dist/server/Dispatcher.js +309 -190
- package/dist/server/ExecutionTimeoutManager.d.ts +46 -0
- package/dist/server/ExecutionTimeoutManager.js +320 -0
- package/dist/server/Plugin.d.ts +17 -3
- package/dist/server/Plugin.js +54 -12
- package/dist/server/Processor.d.ts +64 -12
- package/dist/server/Processor.js +286 -48
- package/dist/server/RunningExecutionRegistry.d.ts +18 -0
- package/dist/server/RunningExecutionRegistry.js +48 -0
- package/dist/server/actions/executions.d.ts +4 -3
- package/dist/server/actions/executions.js +42 -21
- package/dist/server/actions/jobs.d.ts +2 -1
- package/dist/server/actions/jobs.js +28 -1
- package/dist/server/actions/nodes.d.ts +5 -0
- package/dist/server/actions/nodes.js +38 -5
- package/dist/server/actions/workflows.d.ts +6 -0
- package/dist/server/actions/workflows.js +38 -0
- package/dist/server/constants.d.ts +2 -0
- package/dist/server/constants.js +3 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +2 -0
- package/dist/server/instructions/ConditionInstruction.d.ts +2 -0
- package/dist/server/instructions/ConditionInstruction.js +17 -0
- package/dist/server/instructions/CreateInstruction.d.ts +3 -0
- package/dist/server/instructions/CreateInstruction.js +25 -0
- package/dist/server/instructions/DestroyInstruction.d.ts +3 -0
- package/dist/server/instructions/DestroyInstruction.js +25 -0
- package/dist/server/instructions/EndInstruction.d.ts +2 -0
- package/dist/server/instructions/EndInstruction.js +4 -0
- package/dist/server/instructions/MultiConditionsInstruction.d.ts +2 -0
- package/dist/server/instructions/MultiConditionsInstruction.js +23 -0
- package/dist/server/instructions/OutputInstruction.d.ts +2 -0
- package/dist/server/instructions/OutputInstruction.js +15 -1
- package/dist/server/instructions/QueryInstruction.d.ts +3 -0
- package/dist/server/instructions/QueryInstruction.js +32 -7
- package/dist/server/instructions/UpdateInstruction.d.ts +3 -0
- package/dist/server/instructions/UpdateInstruction.js +27 -0
- package/dist/server/instructions/index.d.ts +24 -4
- package/dist/server/instructions/index.js +18 -0
- package/dist/server/migrations/20260423225800-fill-workflow-created-updated-by.d.ts +13 -0
- package/dist/server/migrations/20260423225800-fill-workflow-created-updated-by.js +57 -0
- package/dist/server/migrations/20260501120000-workflow-timeout.d.ts +13 -0
- package/dist/server/migrations/20260501120000-workflow-timeout.js +63 -0
- package/dist/server/timeout-errors.d.ts +13 -0
- package/dist/server/timeout-errors.js +47 -0
- package/dist/server/triggers/CollectionTrigger.d.ts +3 -0
- package/dist/server/triggers/CollectionTrigger.js +34 -0
- package/dist/server/triggers/ScheduleTrigger/index.d.ts +3 -0
- package/dist/server/triggers/ScheduleTrigger/index.js +18 -3
- package/dist/server/triggers/index.d.ts +3 -0
- package/dist/server/triggers/index.js +18 -0
- package/dist/server/types/Execution.d.ts +6 -0
- package/dist/server/types/Job.d.ts +3 -3
- package/dist/server/types/Workflow.d.ts +6 -1
- package/dist/server/utils.d.ts +27 -0
- package/dist/server/utils.js +142 -2
- package/dist/swagger/index.d.ts +66 -75
- package/dist/swagger/index.js +58 -67
- package/package.json +5 -4
- package/dist/client/0e458d99e9fc5e65.js +0 -10
- package/dist/client/27bd65abee87cafa.js +0 -10
- package/dist/client/478692c1637f2742.js +0 -10
- package/dist/client/f39e94207f92e352.js +0 -10
- package/dist/node_modules/lru-cache/LICENSE +0 -15
- package/dist/node_modules/lru-cache/dist/cjs/index-cjs.d.ts +0 -7
- package/dist/node_modules/lru-cache/dist/cjs/index-cjs.js +0 -1
- package/dist/node_modules/lru-cache/dist/cjs/index.d.ts +0 -807
- package/dist/node_modules/lru-cache/dist/cjs/index.min.js +0 -2
- package/dist/node_modules/lru-cache/dist/mjs/index.d.ts +0 -807
- package/dist/node_modules/lru-cache/dist/mjs/index.min.js +0 -2
- /package/dist/node_modules/lru-cache/dist/{cjs → commonjs}/package.json +0 -0
- /package/dist/node_modules/lru-cache/dist/{mjs → esm}/package.json +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import { Transactionable } from '@nocobase/database';
|
|
10
|
+
import type PluginWorkflowServer from './Plugin';
|
|
11
|
+
import type { ExecutionModel } from './types';
|
|
12
|
+
export default class ExecutionTimeoutManager {
|
|
13
|
+
private readonly plugin;
|
|
14
|
+
private readonly timers;
|
|
15
|
+
private scanTimer;
|
|
16
|
+
private nextExpiresAtTimer;
|
|
17
|
+
private nextExpiresAt;
|
|
18
|
+
private scanning;
|
|
19
|
+
private stopped;
|
|
20
|
+
constructor(plugin: PluginWorkflowServer);
|
|
21
|
+
getTimeout(execution: ExecutionModel): number;
|
|
22
|
+
getExpiresAt(execution: ExecutionModel, startedAt: Date): Date;
|
|
23
|
+
load(): Promise<void>;
|
|
24
|
+
unload(): Promise<void>;
|
|
25
|
+
isExpired(execution: ExecutionModel, now?: Date): boolean;
|
|
26
|
+
getRemainingMs(execution: ExecutionModel, now?: Date): number;
|
|
27
|
+
abort(execution: ExecutionModel, options?: Transactionable): Promise<boolean>;
|
|
28
|
+
abortExecutionIfExpired(execution: ExecutionModel, options?: Transactionable): Promise<boolean>;
|
|
29
|
+
private abortExecutionIfExpiredWithLock;
|
|
30
|
+
clear(executionId: number | string): void;
|
|
31
|
+
shouldContinue(execution: ExecutionModel, options?: Transactionable): Promise<boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* Owner-only per-execution timer. Only call from code paths that have acquired
|
|
34
|
+
* local execution ownership, such as Dispatcher/Processor processing paths.
|
|
35
|
+
*/
|
|
36
|
+
scheduleExecutionTimeout(execution: ExecutionModel): void;
|
|
37
|
+
invalidateNextExpiresAtIfMatches(expiresAt?: Date | null): void;
|
|
38
|
+
private scanExpiredExecutions;
|
|
39
|
+
private scheduleScan;
|
|
40
|
+
private scheduleNextExpiresAtTimer;
|
|
41
|
+
private scheduleNextExpiresAtIfEarlier;
|
|
42
|
+
private handleNextExpiresAtTimeout;
|
|
43
|
+
private clearNextExpiresAtTimer;
|
|
44
|
+
private clearExecutionTimeout;
|
|
45
|
+
private handleExecutionTimeout;
|
|
46
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
|
+
var ExecutionTimeoutManager_exports = {};
|
|
28
|
+
__export(ExecutionTimeoutManager_exports, {
|
|
29
|
+
default: () => ExecutionTimeoutManager
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(ExecutionTimeoutManager_exports);
|
|
32
|
+
var import_database = require("@nocobase/database");
|
|
33
|
+
var import_constants = require("./constants");
|
|
34
|
+
var import_utils = require("./utils");
|
|
35
|
+
const LOAD_BATCH_SIZE = 1e3;
|
|
36
|
+
const DEFAULT_SCAN_INTERVAL = 3e4;
|
|
37
|
+
const SCAN_JITTER = 5e3;
|
|
38
|
+
const MAX_TIMER_DELAY = 2147483647;
|
|
39
|
+
const EXECUTION_LOCK_TIMEOUT = 6e4;
|
|
40
|
+
class ExecutionTimeoutManager {
|
|
41
|
+
constructor(plugin) {
|
|
42
|
+
this.plugin = plugin;
|
|
43
|
+
}
|
|
44
|
+
timers = /* @__PURE__ */ new Map();
|
|
45
|
+
scanTimer = null;
|
|
46
|
+
nextExpiresAtTimer = null;
|
|
47
|
+
nextExpiresAt = null;
|
|
48
|
+
scanning = null;
|
|
49
|
+
stopped = true;
|
|
50
|
+
getTimeout(execution) {
|
|
51
|
+
var _a, _b;
|
|
52
|
+
const timeout = Number(((_b = (_a = execution.workflow) == null ? void 0 : _a.options) == null ? void 0 : _b.timeout) ?? 0);
|
|
53
|
+
return Number.isFinite(timeout) && timeout > 0 ? timeout : 0;
|
|
54
|
+
}
|
|
55
|
+
getExpiresAt(execution, startedAt) {
|
|
56
|
+
const timeout = this.getTimeout(execution);
|
|
57
|
+
return timeout > 0 ? new Date(startedAt.getTime() + timeout) : null;
|
|
58
|
+
}
|
|
59
|
+
async load() {
|
|
60
|
+
this.stopped = false;
|
|
61
|
+
await this.scanExpiredExecutions();
|
|
62
|
+
await this.scheduleNextExpiresAtTimer();
|
|
63
|
+
this.scheduleScan();
|
|
64
|
+
}
|
|
65
|
+
async unload() {
|
|
66
|
+
var _a;
|
|
67
|
+
this.stopped = true;
|
|
68
|
+
if (this.scanTimer) {
|
|
69
|
+
clearTimeout(this.scanTimer);
|
|
70
|
+
this.scanTimer = null;
|
|
71
|
+
}
|
|
72
|
+
if (this.nextExpiresAtTimer) {
|
|
73
|
+
clearTimeout(this.nextExpiresAtTimer);
|
|
74
|
+
this.nextExpiresAtTimer = null;
|
|
75
|
+
this.nextExpiresAt = null;
|
|
76
|
+
}
|
|
77
|
+
for (const timer of this.timers.values()) {
|
|
78
|
+
clearTimeout(timer);
|
|
79
|
+
}
|
|
80
|
+
this.timers.clear();
|
|
81
|
+
await ((_a = this.scanning) == null ? void 0 : _a.catch(() => {
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
isExpired(execution, now = /* @__PURE__ */ new Date()) {
|
|
85
|
+
return !!execution.expiresAt && execution.expiresAt.getTime() <= now.getTime();
|
|
86
|
+
}
|
|
87
|
+
getRemainingMs(execution, now = /* @__PURE__ */ new Date()) {
|
|
88
|
+
if (!execution.expiresAt) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return execution.expiresAt.getTime() - now.getTime();
|
|
92
|
+
}
|
|
93
|
+
async abort(execution, options = {}) {
|
|
94
|
+
const aborted = await (0, import_utils.abortExecution)(this.plugin, execution, {
|
|
95
|
+
...options,
|
|
96
|
+
reason: import_constants.EXECUTION_REASON.TIMEOUT
|
|
97
|
+
});
|
|
98
|
+
if (aborted) {
|
|
99
|
+
this.clearExecutionTimeout(execution.id);
|
|
100
|
+
}
|
|
101
|
+
return aborted;
|
|
102
|
+
}
|
|
103
|
+
async abortExecutionIfExpired(execution, options = {}) {
|
|
104
|
+
if (execution.status !== import_constants.EXECUTION_STATUS.STARTED || !this.isExpired(execution)) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return this.abort(execution, options);
|
|
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
|
+
}
|
|
122
|
+
clear(executionId) {
|
|
123
|
+
this.clearExecutionTimeout(executionId);
|
|
124
|
+
}
|
|
125
|
+
async shouldContinue(execution, options = {}) {
|
|
126
|
+
if (execution.status !== import_constants.EXECUTION_STATUS.STARTED) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
if (!this.isExpired(execution)) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
await this.abortExecutionIfExpired(execution, options);
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Owner-only per-execution timer. Only call from code paths that have acquired
|
|
137
|
+
* local execution ownership, such as Dispatcher/Processor processing paths.
|
|
138
|
+
*/
|
|
139
|
+
scheduleExecutionTimeout(execution) {
|
|
140
|
+
const id = String(execution.id);
|
|
141
|
+
this.clearExecutionTimeout(id);
|
|
142
|
+
if (execution.status !== import_constants.EXECUTION_STATUS.STARTED || !execution.expiresAt) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
this.scheduleNextExpiresAtIfEarlier(execution.expiresAt);
|
|
146
|
+
const remaining = this.getRemainingMs(execution);
|
|
147
|
+
if (remaining == null) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (remaining <= 0) {
|
|
151
|
+
setImmediate(() => {
|
|
152
|
+
this.handleExecutionTimeout(id).catch((error) => {
|
|
153
|
+
this.plugin.getLogger(execution.workflowId).error(`execution (${execution.id}) timeout handling failed`, {
|
|
154
|
+
error
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
this.timers.set(
|
|
161
|
+
id,
|
|
162
|
+
setTimeout(
|
|
163
|
+
() => {
|
|
164
|
+
if (execution.expiresAt && execution.expiresAt.getTime() > Date.now()) {
|
|
165
|
+
this.scheduleExecutionTimeout(execution);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
this.handleExecutionTimeout(id).catch((error) => {
|
|
169
|
+
this.plugin.getLogger(execution.workflowId).error(`execution (${execution.id}) timeout handling failed`, {
|
|
170
|
+
error
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
Math.min(remaining, MAX_TIMER_DELAY)
|
|
175
|
+
)
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
invalidateNextExpiresAtIfMatches(expiresAt) {
|
|
179
|
+
if (!expiresAt || !this.nextExpiresAt || this.nextExpiresAt.getTime() !== expiresAt.getTime()) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
this.clearNextExpiresAtTimer();
|
|
183
|
+
this.scheduleNextExpiresAtTimer().catch((error) => {
|
|
184
|
+
this.plugin.getLogger("timeout").error(`workflow execution timeout one-shot refresh failed`, { error });
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
async scanExpiredExecutions() {
|
|
188
|
+
if (this.scanning) {
|
|
189
|
+
return this.scanning;
|
|
190
|
+
}
|
|
191
|
+
if (this.stopped) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
this.scanning = (async () => {
|
|
195
|
+
const ExecutionModelClass = this.plugin.db.getModel("executions");
|
|
196
|
+
const executions = await ExecutionModelClass.findAll({
|
|
197
|
+
attributes: ["id", "workflowId", "status", "expiresAt"],
|
|
198
|
+
where: {
|
|
199
|
+
status: import_constants.EXECUTION_STATUS.STARTED,
|
|
200
|
+
expiresAt: {
|
|
201
|
+
[import_database.Op.not]: null,
|
|
202
|
+
[import_database.Op.lt]: /* @__PURE__ */ new Date()
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
order: [
|
|
206
|
+
["expiresAt", "ASC"],
|
|
207
|
+
["id", "ASC"]
|
|
208
|
+
],
|
|
209
|
+
limit: LOAD_BATCH_SIZE
|
|
210
|
+
});
|
|
211
|
+
for (const execution of executions) {
|
|
212
|
+
await this.abortExecutionIfExpiredWithLock(execution);
|
|
213
|
+
}
|
|
214
|
+
})();
|
|
215
|
+
try {
|
|
216
|
+
await this.scanning;
|
|
217
|
+
} finally {
|
|
218
|
+
this.scanning = null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
scheduleScan() {
|
|
222
|
+
if (this.stopped) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (this.scanTimer) {
|
|
226
|
+
clearTimeout(this.scanTimer);
|
|
227
|
+
}
|
|
228
|
+
this.scanTimer = setTimeout(
|
|
229
|
+
async () => {
|
|
230
|
+
this.scanTimer = null;
|
|
231
|
+
if (this.stopped) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
await this.scanExpiredExecutions();
|
|
236
|
+
this.scheduleNextExpiresAtTimer();
|
|
237
|
+
} catch (error) {
|
|
238
|
+
this.plugin.getLogger("timeout").error(`workflow execution timeout scan failed`, { error });
|
|
239
|
+
} finally {
|
|
240
|
+
this.scheduleScan();
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
DEFAULT_SCAN_INTERVAL + Math.floor(Math.random() * SCAN_JITTER)
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
async scheduleNextExpiresAtTimer() {
|
|
247
|
+
if (this.stopped) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const ExecutionModelClass = this.plugin.db.getModel("executions");
|
|
251
|
+
const execution = await ExecutionModelClass.findOne({
|
|
252
|
+
attributes: ["id", "expiresAt"],
|
|
253
|
+
where: {
|
|
254
|
+
status: import_constants.EXECUTION_STATUS.STARTED,
|
|
255
|
+
expiresAt: {
|
|
256
|
+
[import_database.Op.not]: null
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
order: [
|
|
260
|
+
["expiresAt", "ASC"],
|
|
261
|
+
["id", "ASC"]
|
|
262
|
+
]
|
|
263
|
+
});
|
|
264
|
+
if (!(execution == null ? void 0 : execution.expiresAt)) {
|
|
265
|
+
this.clearNextExpiresAtTimer();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
this.scheduleNextExpiresAtIfEarlier(execution.expiresAt, true);
|
|
269
|
+
}
|
|
270
|
+
scheduleNextExpiresAtIfEarlier(expiresAt, force = false) {
|
|
271
|
+
if (this.stopped) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (!force && this.nextExpiresAt && this.nextExpiresAt.getTime() <= expiresAt.getTime()) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
this.clearNextExpiresAtTimer();
|
|
278
|
+
this.nextExpiresAt = expiresAt;
|
|
279
|
+
const remaining = expiresAt.getTime() - Date.now();
|
|
280
|
+
const delay = Math.max(0, Math.min(remaining, MAX_TIMER_DELAY));
|
|
281
|
+
this.nextExpiresAtTimer = setTimeout(() => {
|
|
282
|
+
this.handleNextExpiresAtTimeout(expiresAt).catch((error) => {
|
|
283
|
+
this.plugin.getLogger("timeout").error(`workflow execution timeout one-shot failed`, { error });
|
|
284
|
+
});
|
|
285
|
+
}, delay);
|
|
286
|
+
}
|
|
287
|
+
async handleNextExpiresAtTimeout(expiresAt) {
|
|
288
|
+
this.nextExpiresAtTimer = null;
|
|
289
|
+
this.nextExpiresAt = null;
|
|
290
|
+
if (expiresAt.getTime() > Date.now()) {
|
|
291
|
+
this.scheduleNextExpiresAtIfEarlier(expiresAt, true);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
await this.scanExpiredExecutions();
|
|
295
|
+
await this.scheduleNextExpiresAtTimer();
|
|
296
|
+
}
|
|
297
|
+
clearNextExpiresAtTimer() {
|
|
298
|
+
if (this.nextExpiresAtTimer) {
|
|
299
|
+
clearTimeout(this.nextExpiresAtTimer);
|
|
300
|
+
this.nextExpiresAtTimer = null;
|
|
301
|
+
}
|
|
302
|
+
this.nextExpiresAt = null;
|
|
303
|
+
}
|
|
304
|
+
clearExecutionTimeout(executionId) {
|
|
305
|
+
const id = String(executionId);
|
|
306
|
+
const timer = this.timers.get(id);
|
|
307
|
+
if (!timer) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
clearTimeout(timer);
|
|
311
|
+
this.timers.delete(id);
|
|
312
|
+
}
|
|
313
|
+
async handleExecutionTimeout(executionId) {
|
|
314
|
+
this.clearExecutionTimeout(executionId);
|
|
315
|
+
if (this.plugin.abortRunningExecution(executionId, import_constants.EXECUTION_REASON.TIMEOUT)) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
await this.abortExecutionIfExpiredWithLock(executionId);
|
|
319
|
+
}
|
|
320
|
+
}
|
package/dist/server/Plugin.d.ts
CHANGED
|
@@ -13,10 +13,13 @@ import { Registry } from '@nocobase/utils';
|
|
|
13
13
|
import { Logger } from '@nocobase/logger';
|
|
14
14
|
import Dispatcher, { EventOptions } from './Dispatcher';
|
|
15
15
|
import Processor from './Processor';
|
|
16
|
+
import ExecutionTimeoutManager from './ExecutionTimeoutManager';
|
|
17
|
+
import RunningExecutionRegistry from './RunningExecutionRegistry';
|
|
16
18
|
import { CustomFunction } from './functions';
|
|
17
19
|
import Trigger from './triggers';
|
|
18
20
|
import { InstructionInterface } from './instructions';
|
|
19
21
|
import type { ExecutionModel, WorkflowModel } from './types';
|
|
22
|
+
import type { Transaction } from '@nocobase/database';
|
|
20
23
|
type ID = number | string;
|
|
21
24
|
export declare const WORKER_JOB_WORKFLOW_PROCESS = "workflow:process";
|
|
22
25
|
export default class PluginWorkflowServer extends Plugin {
|
|
@@ -26,6 +29,8 @@ export default class PluginWorkflowServer extends Plugin {
|
|
|
26
29
|
enabledCache: Map<number, WorkflowModel>;
|
|
27
30
|
snowflake: Snowflake;
|
|
28
31
|
private dispatcher;
|
|
32
|
+
readonly timeoutManager: ExecutionTimeoutManager;
|
|
33
|
+
readonly runningExecutionRegistry: RunningExecutionRegistry;
|
|
29
34
|
get channelPendingExecution(): string;
|
|
30
35
|
private loggerCache;
|
|
31
36
|
private meter;
|
|
@@ -38,6 +43,12 @@ export default class PluginWorkflowServer extends Plugin {
|
|
|
38
43
|
private onBeforeStop;
|
|
39
44
|
handleSyncMessage(message: any): Promise<void>;
|
|
40
45
|
serving(): boolean;
|
|
46
|
+
abortExecutionIfExpired(execution: ExecutionModel, options?: {
|
|
47
|
+
transaction?: Transaction;
|
|
48
|
+
}): Promise<boolean>;
|
|
49
|
+
registerRunningExecution(executionId: number | string, abort: (reason?: string) => void): void;
|
|
50
|
+
unregisterRunningExecution(executionId: number | string): void;
|
|
51
|
+
abortRunningExecution(executionId: number | string, reason?: string): boolean;
|
|
41
52
|
/**
|
|
42
53
|
* @experimental
|
|
43
54
|
*/
|
|
@@ -56,6 +67,7 @@ export default class PluginWorkflowServer extends Plugin {
|
|
|
56
67
|
}): void;
|
|
57
68
|
private initTriggers;
|
|
58
69
|
private initInstructions;
|
|
70
|
+
private registerErrorHandlers;
|
|
59
71
|
beforeLoad(): Promise<void>;
|
|
60
72
|
/**
|
|
61
73
|
* @internal
|
|
@@ -64,6 +76,7 @@ export default class PluginWorkflowServer extends Plugin {
|
|
|
64
76
|
private toggle;
|
|
65
77
|
trigger(workflow: WorkflowModel, context: object, options?: EventOptions): void | Promise<Processor | null>;
|
|
66
78
|
run(pending: Parameters<Dispatcher['run']>[0]): Promise<void>;
|
|
79
|
+
dispatch(): void;
|
|
67
80
|
resume(job: any): Promise<void>;
|
|
68
81
|
/**
|
|
69
82
|
* Start a deferred execution
|
|
@@ -76,10 +89,11 @@ export default class PluginWorkflowServer extends Plugin {
|
|
|
76
89
|
* @experimental
|
|
77
90
|
* @param {string} dataSourceName
|
|
78
91
|
* @param {Transaction} transaction
|
|
79
|
-
* @param {boolean} create
|
|
80
|
-
* @returns {
|
|
92
|
+
* @param {boolean} create Create a new transaction when the input transaction does not belong to this data source.
|
|
93
|
+
* @returns {Transaction}
|
|
81
94
|
*/
|
|
82
|
-
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;
|
|
83
97
|
/**
|
|
84
98
|
* @experimental
|
|
85
99
|
*/
|
package/dist/server/Plugin.js
CHANGED
|
@@ -42,13 +42,17 @@ __export(Plugin_exports, {
|
|
|
42
42
|
module.exports = __toCommonJS(Plugin_exports);
|
|
43
43
|
var import_path = __toESM(require("path"));
|
|
44
44
|
var import_nodejs_snowflake = require("nodejs-snowflake");
|
|
45
|
-
var import_lru_cache =
|
|
45
|
+
var import_lru_cache = require("lru-cache");
|
|
46
46
|
var import_database = require("@nocobase/database");
|
|
47
47
|
var import_server = require("@nocobase/server");
|
|
48
48
|
var import_utils = require("@nocobase/utils");
|
|
49
49
|
var import_Dispatcher = __toESM(require("./Dispatcher"));
|
|
50
50
|
var import_Processor = __toESM(require("./Processor"));
|
|
51
|
+
var import_ExecutionTimeoutManager = __toESM(require("./ExecutionTimeoutManager"));
|
|
52
|
+
var import_RunningExecutionRegistry = __toESM(require("./RunningExecutionRegistry"));
|
|
51
53
|
var import_actions = __toESM(require("./actions"));
|
|
54
|
+
var import_nodes = require("./actions/nodes");
|
|
55
|
+
var import_workflows = require("./actions/workflows");
|
|
52
56
|
var import_functions = __toESM(require("./functions"));
|
|
53
57
|
var import_CollectionTrigger = __toESM(require("./triggers/CollectionTrigger"));
|
|
54
58
|
var import_ScheduleTrigger = __toESM(require("./triggers/ScheduleTrigger"));
|
|
@@ -70,6 +74,8 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
70
74
|
enabledCache = /* @__PURE__ */ new Map();
|
|
71
75
|
snowflake;
|
|
72
76
|
dispatcher = new import_Dispatcher.default(this);
|
|
77
|
+
timeoutManager = new import_ExecutionTimeoutManager.default(this);
|
|
78
|
+
runningExecutionRegistry = new import_RunningExecutionRegistry.default();
|
|
73
79
|
get channelPendingExecution() {
|
|
74
80
|
return `${this.name}.pendingExecution`;
|
|
75
81
|
}
|
|
@@ -164,9 +170,9 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
164
170
|
}
|
|
165
171
|
}
|
|
166
172
|
this.checker = setInterval(() => {
|
|
167
|
-
this.getLogger("dispatcher").debug(`(cycling) check for queueing executions`);
|
|
168
173
|
this.dispatcher.dispatch();
|
|
169
174
|
}, 3e5);
|
|
175
|
+
await this.timeoutManager.load();
|
|
170
176
|
this.app.on("workflow:dispatch", () => {
|
|
171
177
|
this.app.logger.info("workflow:dispatch");
|
|
172
178
|
this.dispatcher.dispatch();
|
|
@@ -179,6 +185,7 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
179
185
|
if (this.checker) {
|
|
180
186
|
clearInterval(this.checker);
|
|
181
187
|
}
|
|
188
|
+
await this.timeoutManager.unload();
|
|
182
189
|
await this.dispatcher.beforeStop();
|
|
183
190
|
this.app.logger.info(`stopping workflow plugin before app (${this.app.name}) shutdown...`);
|
|
184
191
|
for (const workflow of this.enabledCache.values()) {
|
|
@@ -212,6 +219,18 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
212
219
|
serving() {
|
|
213
220
|
return this.app.serving(WORKER_JOB_WORKFLOW_PROCESS);
|
|
214
221
|
}
|
|
222
|
+
async abortExecutionIfExpired(execution, options = {}) {
|
|
223
|
+
return this.timeoutManager.abortExecutionIfExpired(execution, options);
|
|
224
|
+
}
|
|
225
|
+
registerRunningExecution(executionId, abort) {
|
|
226
|
+
this.runningExecutionRegistry.register(executionId, { abort });
|
|
227
|
+
}
|
|
228
|
+
unregisterRunningExecution(executionId) {
|
|
229
|
+
this.runningExecutionRegistry.unregister(executionId);
|
|
230
|
+
}
|
|
231
|
+
abortRunningExecution(executionId, reason) {
|
|
232
|
+
return this.runningExecutionRegistry.abort(executionId, reason);
|
|
233
|
+
}
|
|
215
234
|
/**
|
|
216
235
|
* @experimental
|
|
217
236
|
*/
|
|
@@ -280,6 +299,29 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
280
299
|
this.registerInstruction(name, instruction);
|
|
281
300
|
}
|
|
282
301
|
}
|
|
302
|
+
registerErrorHandlers() {
|
|
303
|
+
const PluginErrorHandler = this.app.pm.get("error-handler");
|
|
304
|
+
if (PluginErrorHandler == null ? void 0 : PluginErrorHandler.errorHandler) {
|
|
305
|
+
PluginErrorHandler.errorHandler.register(
|
|
306
|
+
(err) => err instanceof import_nodes.NodeValidationError || err.name === "NodeValidationError",
|
|
307
|
+
(err, ctx) => {
|
|
308
|
+
ctx.status = err.status;
|
|
309
|
+
ctx.body = {
|
|
310
|
+
errors: Object.values(err.errors).map((message) => ({ message }))
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
);
|
|
314
|
+
PluginErrorHandler.errorHandler.register(
|
|
315
|
+
(err) => err instanceof import_workflows.WorkflowValidationError || err.name === "WorkflowValidationError",
|
|
316
|
+
(err, ctx) => {
|
|
317
|
+
ctx.status = err.status;
|
|
318
|
+
ctx.body = {
|
|
319
|
+
errors: Object.values(err.errors).map((message) => ({ message }))
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
283
325
|
async beforeLoad() {
|
|
284
326
|
this.db.registerRepositories({
|
|
285
327
|
WorkflowRepository: import_WorkflowRepository.default
|
|
@@ -302,10 +344,11 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
302
344
|
this.initTriggers(options.triggers);
|
|
303
345
|
this.initInstructions(options.instructions);
|
|
304
346
|
(0, import_functions.default)(this, options.functions);
|
|
347
|
+
this.registerErrorHandlers();
|
|
305
348
|
this.functions.register("instanceId", () => this.app.instanceId);
|
|
306
349
|
this.functions.register("epoch", () => 1605024e3);
|
|
307
350
|
this.functions.register("genSnowflakeId", () => this.app.snowflakeIdGenerator.generate());
|
|
308
|
-
this.loggerCache = new import_lru_cache.
|
|
351
|
+
this.loggerCache = new import_lru_cache.LRUCache({
|
|
309
352
|
max: 20,
|
|
310
353
|
updateAgeOnGet: true,
|
|
311
354
|
dispose(logger) {
|
|
@@ -350,7 +393,6 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
350
393
|
actions: ["workflows:list"]
|
|
351
394
|
});
|
|
352
395
|
this.app.acl.allow("userWorkflowTasks", "listMine", "loggedIn");
|
|
353
|
-
this.app.acl.allow("*", ["trigger"], "loggedIn");
|
|
354
396
|
db.on("workflows.beforeSave", this.onBeforeSave);
|
|
355
397
|
db.on("workflows.afterCreate", this.onAfterCreate);
|
|
356
398
|
db.on("workflows.afterUpdate", this.onAfterUpdate);
|
|
@@ -409,6 +451,9 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
409
451
|
async run(pending) {
|
|
410
452
|
return this.dispatcher.run(pending);
|
|
411
453
|
}
|
|
454
|
+
dispatch() {
|
|
455
|
+
return this.dispatcher.dispatch();
|
|
456
|
+
}
|
|
412
457
|
async resume(job) {
|
|
413
458
|
return this.dispatcher.resume(job);
|
|
414
459
|
}
|
|
@@ -432,15 +477,12 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
432
477
|
}
|
|
433
478
|
return trigger.execute(workflow, values, options);
|
|
434
479
|
}
|
|
435
|
-
/**
|
|
436
|
-
* @experimental
|
|
437
|
-
* @param {string} dataSourceName
|
|
438
|
-
* @param {Transaction} transaction
|
|
439
|
-
* @param {boolean} create
|
|
440
|
-
* @returns {Trasaction}
|
|
441
|
-
*/
|
|
442
480
|
useDataSourceTransaction(dataSourceName = "main", transaction, create = false) {
|
|
443
|
-
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;
|
|
444
486
|
if (!db) {
|
|
445
487
|
return;
|
|
446
488
|
}
|