@nocobase/plugin-workflow 2.1.0-alpha.4 → 2.1.0-alpha.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/LICENSE +201 -661
- package/README.md +79 -10
- 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 +62 -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 +43 -1
- package/dist/common/collections/executions.js +62 -1
- package/dist/common/collections/jobs.js +7 -0
- package/dist/common/collections/workflows.d.ts +65 -11
- package/dist/common/collections/workflows.js +34 -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 +12 -1
- 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 +9 -6
- package/dist/server/Dispatcher.js +241 -160
- package/dist/server/ExecutionTimeoutManager.d.ts +45 -0
- package/dist/server/ExecutionTimeoutManager.js +312 -0
- package/dist/server/Plugin.d.ts +13 -0
- package/dist/server/Plugin.js +49 -4
- package/dist/server/Processor.d.ts +65 -9
- package/dist/server/Processor.js +285 -33
- 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 +14 -0
- 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 +28 -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 +138 -2
- package/dist/swagger/index.d.ts +849 -106
- package/dist/swagger/index.js +969 -208
- package/package.json +6 -5
- package/dist/client/27bd65abee87cafa.js +0 -10
- package/dist/client/478692c1637f2742.js +0 -10
- package/dist/client/c1347b9d21f864d9.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
|
@@ -29,22 +29,23 @@ __export(Dispatcher_exports, {
|
|
|
29
29
|
default: () => Dispatcher
|
|
30
30
|
});
|
|
31
31
|
module.exports = __toCommonJS(Dispatcher_exports);
|
|
32
|
-
var
|
|
32
|
+
var import_node_crypto = require("node:crypto");
|
|
33
33
|
var import_sequelize = require("sequelize");
|
|
34
34
|
var import_constants = require("./constants");
|
|
35
35
|
var import_Plugin = require("./Plugin");
|
|
36
|
+
var import_utils = require("./utils");
|
|
36
37
|
class Dispatcher {
|
|
37
38
|
constructor(plugin) {
|
|
38
39
|
this.plugin = plugin;
|
|
39
|
-
this.prepare = this.prepare.bind(this);
|
|
40
40
|
}
|
|
41
41
|
ready = false;
|
|
42
42
|
executing = null;
|
|
43
|
+
saving = null;
|
|
43
44
|
pending = [];
|
|
44
45
|
events = [];
|
|
45
46
|
eventsCount = 0;
|
|
46
47
|
get idle() {
|
|
47
|
-
return this.ready && !this.executing && !this.pending.length && !this.events.length;
|
|
48
|
+
return this.ready && !this.executing && !this.saving && !this.pending.length && !this.events.length;
|
|
48
49
|
}
|
|
49
50
|
onQueueExecution = async (event) => {
|
|
50
51
|
const ExecutionRepo = this.plugin.db.getRepository("executions");
|
|
@@ -52,11 +53,10 @@ class Dispatcher {
|
|
|
52
53
|
filterByTk: event.executionId
|
|
53
54
|
});
|
|
54
55
|
if (!execution || execution.dispatched) {
|
|
55
|
-
this.plugin.getLogger("dispatcher").info(`execution (${event.executionId}) from queue not found or not in queueing status, skip`);
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
58
|
this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) received from queue, adding to pending list`);
|
|
59
|
-
this.run({ execution });
|
|
59
|
+
await this.run({ execution });
|
|
60
60
|
};
|
|
61
61
|
setReady(ready) {
|
|
62
62
|
this.ready = ready;
|
|
@@ -96,11 +96,55 @@ class Dispatcher {
|
|
|
96
96
|
this.eventsCount = this.events.length;
|
|
97
97
|
logger.info(`new event triggered, now events: ${this.events.length}`);
|
|
98
98
|
logger.debug(`event data:`, { context });
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
this.saveEvent();
|
|
100
|
+
}
|
|
101
|
+
saveEvent() {
|
|
102
|
+
if (this.saving) {
|
|
101
103
|
return;
|
|
102
104
|
}
|
|
103
|
-
|
|
105
|
+
this.saving = (async () => {
|
|
106
|
+
try {
|
|
107
|
+
while (this.events.length) {
|
|
108
|
+
if (this.executing && this.plugin.db.options.dialect === "sqlite") {
|
|
109
|
+
await this.executing;
|
|
110
|
+
}
|
|
111
|
+
const event = this.events.shift();
|
|
112
|
+
this.eventsCount = this.events.length;
|
|
113
|
+
if (!event) continue;
|
|
114
|
+
const logger = this.plugin.getLogger(event[0].id);
|
|
115
|
+
logger.info(`preparing execution for event`);
|
|
116
|
+
try {
|
|
117
|
+
const execution = await this.createExecution(...event);
|
|
118
|
+
if (!execution.dispatched) {
|
|
119
|
+
if (this.plugin.serving() && !this.executing && !this.pending.length) {
|
|
120
|
+
logger.info(`local pending list is empty, adding execution (${execution.id}) to pending list`);
|
|
121
|
+
this.pending.push({ execution });
|
|
122
|
+
} else {
|
|
123
|
+
logger.info(
|
|
124
|
+
`instance is not serving as worker or local pending list is not empty, sending execution (${execution.id}) to queue`
|
|
125
|
+
);
|
|
126
|
+
try {
|
|
127
|
+
await this.plugin.app.eventQueue.publish(this.plugin.channelPendingExecution, {
|
|
128
|
+
executionId: execution.id
|
|
129
|
+
});
|
|
130
|
+
} catch (qErr) {
|
|
131
|
+
logger.error(`publishing execution (${execution.id}) to queue failed:`, { error: qErr });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
} catch (error) {
|
|
136
|
+
logger.error(`failed to create execution:`, { error });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} finally {
|
|
140
|
+
this.saving = null;
|
|
141
|
+
if (this.events.length) {
|
|
142
|
+
this.saveEvent();
|
|
143
|
+
} else {
|
|
144
|
+
this.dispatch();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
})();
|
|
104
148
|
}
|
|
105
149
|
async resume(job) {
|
|
106
150
|
let { execution } = job;
|
|
@@ -108,27 +152,37 @@ class Dispatcher {
|
|
|
108
152
|
execution = await job.getExecution();
|
|
109
153
|
}
|
|
110
154
|
this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) resuming from job (${job.id}) added to pending list`);
|
|
111
|
-
this.run({ execution, job
|
|
155
|
+
await this.run({ execution, job });
|
|
112
156
|
}
|
|
113
157
|
async start(execution) {
|
|
114
158
|
if (execution.status) {
|
|
115
159
|
return;
|
|
116
160
|
}
|
|
117
161
|
this.plugin.getLogger(execution.workflowId).info(`starting deferred execution (${execution.id})`);
|
|
118
|
-
this.run({ execution
|
|
162
|
+
await this.run({ execution });
|
|
119
163
|
}
|
|
120
164
|
async beforeStop() {
|
|
121
165
|
this.ready = false;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
166
|
+
this.plugin.getLogger("dispatcher").info("app is stopping, draining local queues...");
|
|
167
|
+
while (this.saving || this.executing || this.events.length || this.pending.length) {
|
|
168
|
+
if (this.saving) {
|
|
169
|
+
await this.saving;
|
|
170
|
+
}
|
|
171
|
+
if (this.executing) {
|
|
172
|
+
await this.executing;
|
|
173
|
+
}
|
|
174
|
+
if (this.events.length && !this.saving) {
|
|
175
|
+
this.saveEvent();
|
|
176
|
+
}
|
|
177
|
+
if (this.pending.length && !this.executing) {
|
|
178
|
+
this.dispatch();
|
|
179
|
+
}
|
|
180
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
127
181
|
}
|
|
182
|
+
this.plugin.getLogger("dispatcher").info("local queues drained");
|
|
128
183
|
}
|
|
129
184
|
dispatch() {
|
|
130
|
-
if (!this.ready) {
|
|
131
|
-
this.plugin.getLogger("dispatcher").warn(`app is not ready, new dispatching will be ignored`);
|
|
185
|
+
if (!this.ready && !this.pending.length && !this.events.length) {
|
|
132
186
|
return;
|
|
133
187
|
}
|
|
134
188
|
if (this.executing) {
|
|
@@ -136,30 +190,36 @@ class Dispatcher {
|
|
|
136
190
|
return;
|
|
137
191
|
}
|
|
138
192
|
if (this.events.length) {
|
|
139
|
-
|
|
193
|
+
this.saveEvent();
|
|
194
|
+
return;
|
|
140
195
|
}
|
|
141
196
|
this.executing = (async () => {
|
|
142
197
|
let next = null;
|
|
143
|
-
|
|
144
|
-
if (this.
|
|
145
|
-
const
|
|
146
|
-
|
|
198
|
+
const pending = this.pending.shift() ?? null;
|
|
199
|
+
if (pending || this.ready && this.plugin.serving()) {
|
|
200
|
+
const execution = await this.prepare((pending == null ? void 0 : pending.execution) ?? null, {
|
|
201
|
+
immediate: pending == null ? void 0 : pending.immediate
|
|
202
|
+
});
|
|
147
203
|
if (execution) {
|
|
148
|
-
next = [execution, pending.job];
|
|
204
|
+
next = [execution, pending == null ? void 0 : pending.job, pending == null ? void 0 : pending.rerun];
|
|
205
|
+
}
|
|
206
|
+
if (pending && next) {
|
|
149
207
|
this.plugin.getLogger(next[0].workflowId).info(`pending execution (${next[0].id}) ready to process`);
|
|
150
208
|
}
|
|
151
209
|
} else {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
next = [execution];
|
|
156
|
-
}
|
|
157
|
-
} else {
|
|
158
|
-
this.plugin.getLogger("dispatcher").warn(`${import_Plugin.WORKER_JOB_WORKFLOW_PROCESS} is not serving on this instance, new dispatching will be ignored`);
|
|
159
|
-
}
|
|
210
|
+
this.plugin.getLogger("dispatcher").warn(
|
|
211
|
+
`${import_Plugin.WORKER_JOB_WORKFLOW_PROCESS} is not serving on this instance or app not ready, new dispatching will be ignored`
|
|
212
|
+
);
|
|
160
213
|
}
|
|
161
214
|
if (next) {
|
|
162
|
-
|
|
215
|
+
try {
|
|
216
|
+
await this.process(next[0], next[1], { rerun: next[2] });
|
|
217
|
+
} catch (error) {
|
|
218
|
+
this.plugin.getLogger(next[0].workflowId).error(`execution (${next[0].id}) process failed`, { error });
|
|
219
|
+
if (pending && (0, import_utils.isLockAcquireError)(error)) {
|
|
220
|
+
this.pending.unshift({ ...pending, execution: next[0], immediate: true });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
163
223
|
}
|
|
164
224
|
setImmediate(() => {
|
|
165
225
|
this.executing = null;
|
|
@@ -171,7 +231,10 @@ class Dispatcher {
|
|
|
171
231
|
})();
|
|
172
232
|
}
|
|
173
233
|
async run(pending) {
|
|
174
|
-
this.pending.push(
|
|
234
|
+
this.pending.push({
|
|
235
|
+
...pending,
|
|
236
|
+
immediate: !this.executing && !this.pending.length && !this.saving && !this.events.length
|
|
237
|
+
});
|
|
175
238
|
this.dispatch();
|
|
176
239
|
}
|
|
177
240
|
async triggerSync(workflow, context, { deferred, ...options } = {}) {
|
|
@@ -179,13 +242,23 @@ class Dispatcher {
|
|
|
179
242
|
try {
|
|
180
243
|
execution = await this.createExecution(workflow, context, options);
|
|
181
244
|
} catch (err) {
|
|
182
|
-
|
|
245
|
+
if (err instanceof Error) {
|
|
246
|
+
this.plugin.getLogger(workflow.id).error(`creating execution failed: ${err.message}`, err);
|
|
247
|
+
}
|
|
183
248
|
return null;
|
|
184
249
|
}
|
|
185
250
|
try {
|
|
186
|
-
|
|
251
|
+
const entered = await this.prepare(execution, {
|
|
252
|
+
transaction: this.plugin.useDataSourceTransaction("main", options.transaction)
|
|
253
|
+
});
|
|
254
|
+
if (!entered) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
return this.process(entered, void 0, options);
|
|
187
258
|
} catch (err) {
|
|
188
|
-
|
|
259
|
+
if (err instanceof Error) {
|
|
260
|
+
this.plugin.getLogger(execution.workflowId).error(`execution (${execution.id}) error: ${err.message}`, err);
|
|
261
|
+
}
|
|
189
262
|
}
|
|
190
263
|
return null;
|
|
191
264
|
}
|
|
@@ -195,9 +268,9 @@ class Dispatcher {
|
|
|
195
268
|
if (!triggerValid) {
|
|
196
269
|
return false;
|
|
197
270
|
}
|
|
198
|
-
const { stack } = options;
|
|
271
|
+
const { stack = [] } = options;
|
|
199
272
|
let valid = true;
|
|
200
|
-
if (
|
|
273
|
+
if (stack == null ? void 0 : stack.length) {
|
|
201
274
|
const existed = await workflow.countExecutions({
|
|
202
275
|
where: {
|
|
203
276
|
id: stack
|
|
@@ -207,7 +280,7 @@ class Dispatcher {
|
|
|
207
280
|
const limitCount = workflow.options.stackLimit || 1;
|
|
208
281
|
if (existed >= limitCount) {
|
|
209
282
|
this.plugin.getLogger(workflow.id).warn(
|
|
210
|
-
`workflow ${workflow.id} has already been triggered in stacks executions (${stack}), and max call
|
|
283
|
+
`workflow ${workflow.id} has already been triggered in stacks executions (${stack}), and max call count is ${limitCount}, newly triggering will be skipped.`
|
|
211
284
|
);
|
|
212
285
|
valid = false;
|
|
213
286
|
}
|
|
@@ -219,13 +292,21 @@ class Dispatcher {
|
|
|
219
292
|
const { deferred } = options;
|
|
220
293
|
const transaction = await this.plugin.useDataSourceTransaction("main", options.transaction, true);
|
|
221
294
|
const sameTransaction = options.transaction === transaction;
|
|
222
|
-
|
|
295
|
+
let stack = options.stack;
|
|
296
|
+
if (options.parentExecutionId && !stack) {
|
|
297
|
+
const parentExecution = await this.plugin.db.getRepository("executions").findOne({
|
|
298
|
+
filterByTk: options.parentExecutionId,
|
|
299
|
+
transaction
|
|
300
|
+
});
|
|
301
|
+
stack = parentExecution ? [...parentExecution.stack ?? [], parentExecution.id] : [];
|
|
302
|
+
}
|
|
303
|
+
const valid = await this.validateEvent(workflow, context, { ...options, stack, transaction });
|
|
223
304
|
if (!valid) {
|
|
224
305
|
if (!sameTransaction) {
|
|
225
306
|
await transaction.commit();
|
|
226
307
|
}
|
|
227
308
|
(_a = options.onTriggerFail) == null ? void 0 : _a.call(options, workflow, context, options);
|
|
228
|
-
|
|
309
|
+
throw new Error("event is not valid");
|
|
229
310
|
}
|
|
230
311
|
let execution;
|
|
231
312
|
try {
|
|
@@ -233,8 +314,9 @@ class Dispatcher {
|
|
|
233
314
|
{
|
|
234
315
|
context,
|
|
235
316
|
key: workflow.key,
|
|
236
|
-
eventKey: options.eventKey ?? (0,
|
|
237
|
-
stack
|
|
317
|
+
eventKey: options.eventKey ?? (0, import_node_crypto.randomUUID)(),
|
|
318
|
+
stack,
|
|
319
|
+
parentExecutionId: options.parentExecutionId ?? null,
|
|
238
320
|
dispatched: deferred ?? false,
|
|
239
321
|
status: deferred ? import_constants.EXECUTION_STATUS.STARTED : import_constants.EXECUTION_STATUS.QUEUEING,
|
|
240
322
|
manually: options.manually
|
|
@@ -268,136 +350,135 @@ class Dispatcher {
|
|
|
268
350
|
execution.workflow = workflow;
|
|
269
351
|
return execution;
|
|
270
352
|
}
|
|
271
|
-
prepare =
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
this.plugin.getLogger("dispatcher").info(`events queue is empty, no need to prepare`);
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
const logger = this.plugin.getLogger(event[0].id);
|
|
282
|
-
logger.info(`preparing execution for event`);
|
|
353
|
+
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
|
+
const logger = input ? this.plugin.getLogger(input.workflowId) : this.plugin.getLogger("dispatcher");
|
|
283
360
|
try {
|
|
284
|
-
|
|
285
|
-
if (
|
|
286
|
-
if (
|
|
287
|
-
|
|
288
|
-
|
|
361
|
+
let execution = input;
|
|
362
|
+
if (execution) {
|
|
363
|
+
if (!options.immediate || execution.status !== import_constants.EXECUTION_STATUS.QUEUEING) {
|
|
364
|
+
await execution.reload({ transaction: tx });
|
|
365
|
+
}
|
|
366
|
+
} else {
|
|
367
|
+
execution = await this.plugin.db.getRepository("executions").findOne({
|
|
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`);
|
|
289
379
|
} else {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
});
|
|
297
|
-
} catch (qErr) {
|
|
298
|
-
logger.error(`publishing execution (${execution.id}) to queue failed:`, { error: qErr });
|
|
299
|
-
}
|
|
380
|
+
this.plugin.getLogger("dispatcher").debug(`no execution in db queued to process`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (!execution) {
|
|
384
|
+
if (ownTransaction) {
|
|
385
|
+
await tx.commit();
|
|
300
386
|
}
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
const entered = await this.enter(execution, tx);
|
|
390
|
+
if (ownTransaction) {
|
|
391
|
+
await tx.commit();
|
|
301
392
|
}
|
|
393
|
+
return entered;
|
|
302
394
|
} catch (error) {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if (this.events.length) {
|
|
306
|
-
await this.prepare();
|
|
307
|
-
} else {
|
|
308
|
-
this.plugin.getLogger("dispatcher").info("no more events need to be prepared, dispatching...");
|
|
309
|
-
if (this.executing) {
|
|
310
|
-
await this.executing;
|
|
395
|
+
if (ownTransaction) {
|
|
396
|
+
await tx.rollback();
|
|
311
397
|
}
|
|
312
|
-
|
|
398
|
+
if (error instanceof Error) {
|
|
399
|
+
logger.error(`entering execution failed: ${error.message}`, { error });
|
|
400
|
+
}
|
|
401
|
+
return null;
|
|
313
402
|
}
|
|
314
|
-
}
|
|
315
|
-
async
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const ExecutionModelClass = this.plugin.db.getModel("executions");
|
|
322
|
-
const [affected] = await ExecutionModelClass.update(
|
|
323
|
-
{ dispatched: true, status: import_constants.EXECUTION_STATUS.STARTED },
|
|
324
|
-
{
|
|
325
|
-
where: {
|
|
326
|
-
id: execution.id,
|
|
327
|
-
dispatched: false
|
|
328
|
-
},
|
|
329
|
-
transaction
|
|
330
|
-
}
|
|
331
|
-
);
|
|
332
|
-
if (!affected) {
|
|
333
|
-
fetched = null;
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
await execution.reload({ transaction });
|
|
403
|
+
}
|
|
404
|
+
async enter(execution, transaction) {
|
|
405
|
+
const workflow = execution.workflow || this.plugin.enabledCache.get(execution.workflowId) || await execution.getWorkflow({ transaction });
|
|
406
|
+
if (!workflow) {
|
|
407
|
+
this.plugin.getLogger(execution.workflowId).warn(`workflow (${execution.workflowId}) not found for execution`, {
|
|
408
|
+
workflowId: execution.workflowId,
|
|
409
|
+
executionId: execution.id
|
|
337
410
|
});
|
|
338
|
-
} catch (error) {
|
|
339
|
-
logger.error(`acquiring pending execution failed: ${error.message}`, { error });
|
|
340
411
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
async acquireQueueingExecution() {
|
|
344
|
-
const isolationLevel = this.plugin.db.options.dialect === "sqlite" ? [][0] : import_sequelize.Transaction.ISOLATION_LEVELS.REPEATABLE_READ;
|
|
345
|
-
let fetched = null;
|
|
346
|
-
try {
|
|
347
|
-
await this.plugin.db.sequelize.transaction(
|
|
348
|
-
{
|
|
349
|
-
isolationLevel
|
|
350
|
-
},
|
|
351
|
-
async (transaction) => {
|
|
352
|
-
const execution = await this.plugin.db.getRepository("executions").findOne({
|
|
353
|
-
filter: {
|
|
354
|
-
dispatched: false,
|
|
355
|
-
"workflow.enabled": true
|
|
356
|
-
},
|
|
357
|
-
sort: "id",
|
|
358
|
-
transaction
|
|
359
|
-
});
|
|
360
|
-
if (execution) {
|
|
361
|
-
this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) fetched from db`);
|
|
362
|
-
await execution.update(
|
|
363
|
-
{
|
|
364
|
-
dispatched: true,
|
|
365
|
-
status: import_constants.EXECUTION_STATUS.STARTED
|
|
366
|
-
},
|
|
367
|
-
{ transaction }
|
|
368
|
-
);
|
|
369
|
-
execution.workflow = this.plugin.enabledCache.get(execution.workflowId);
|
|
370
|
-
fetched = execution;
|
|
371
|
-
} else {
|
|
372
|
-
this.plugin.getLogger("dispatcher").debug(`no execution in db queued to process`);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
);
|
|
376
|
-
} catch (error) {
|
|
377
|
-
this.plugin.getLogger("dispatcher").error(`fetching execution from db failed: ${error.message}`, { error });
|
|
412
|
+
if (execution.status && execution.status !== import_constants.EXECUTION_STATUS.STARTED) {
|
|
413
|
+
return null;
|
|
378
414
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
const
|
|
415
|
+
if (execution.dispatched && execution.status === import_constants.EXECUTION_STATUS.STARTED && execution.startedAt) {
|
|
416
|
+
execution.workflow = workflow;
|
|
417
|
+
return execution;
|
|
418
|
+
}
|
|
419
|
+
const values = {
|
|
420
|
+
dispatched: true,
|
|
421
|
+
status: import_constants.EXECUTION_STATUS.STARTED
|
|
422
|
+
};
|
|
423
|
+
const where = {
|
|
424
|
+
id: execution.id,
|
|
425
|
+
status: execution.status ?? null
|
|
426
|
+
};
|
|
384
427
|
if (!execution.dispatched) {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
428
|
+
where.dispatched = false;
|
|
429
|
+
}
|
|
430
|
+
if (!execution.startedAt) {
|
|
431
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
432
|
+
values.startedAt = startedAt;
|
|
433
|
+
execution.workflow = workflow;
|
|
434
|
+
values.expiresAt = this.plugin.timeoutManager.getExpiresAt(execution, startedAt);
|
|
435
|
+
where.startedAt = null;
|
|
436
|
+
}
|
|
437
|
+
const ExecutionModelClass = this.plugin.db.getModel("executions");
|
|
438
|
+
const [affected] = await ExecutionModelClass.update(values, {
|
|
439
|
+
where,
|
|
440
|
+
transaction
|
|
441
|
+
});
|
|
442
|
+
if (!affected) {
|
|
443
|
+
return null;
|
|
388
444
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
445
|
+
await execution.reload({ transaction });
|
|
446
|
+
execution.workflow = workflow;
|
|
447
|
+
return execution;
|
|
448
|
+
}
|
|
449
|
+
async process(execution, job = null, options = {}) {
|
|
450
|
+
const { rerun, ...processorOptions } = options;
|
|
451
|
+
const logger = this.plugin.getLogger(execution.workflowId);
|
|
452
|
+
const run = async () => {
|
|
453
|
+
var _a, _b, _c;
|
|
454
|
+
if (!execution.dispatched) {
|
|
455
|
+
const transaction = await this.plugin.useDataSourceTransaction("main", processorOptions.transaction);
|
|
456
|
+
await execution.update({ dispatched: true, status: import_constants.EXECUTION_STATUS.STARTED }, { transaction });
|
|
457
|
+
logger.info(`execution (${execution.id}) from pending list updated to started`);
|
|
397
458
|
}
|
|
398
|
-
|
|
399
|
-
|
|
459
|
+
this.plugin.timeoutManager.scheduleExecutionTimeout(execution);
|
|
460
|
+
const processor = this.plugin.createProcessor(execution, processorOptions);
|
|
461
|
+
logger.info(`execution (${execution.id}) ${rerun ? "rerunning" : job ? "resuming" : "starting"}...`);
|
|
462
|
+
try {
|
|
463
|
+
await (rerun ? processor.rerun(rerun) : job ? processor.resume(job) : processor.start());
|
|
464
|
+
logger.info(`execution (${execution.id}) finished with status: ${execution.status}`);
|
|
465
|
+
logger.debug(`execution (${execution.id}) details:`, { execution });
|
|
466
|
+
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({ transaction: processor.mainTransaction });
|
|
468
|
+
}
|
|
469
|
+
} catch (err) {
|
|
470
|
+
if (err instanceof Error) {
|
|
471
|
+
logger.error(`execution (${execution.id}) error: ${err.message}`, err);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return processor;
|
|
475
|
+
};
|
|
476
|
+
const lock = await this.plugin.app.lockManager.tryAcquire((0, import_utils.getExecutionLockKey)(execution.id), 6e4);
|
|
477
|
+
try {
|
|
478
|
+
return await lock.runExclusive(run, 6e4);
|
|
479
|
+
} catch (error) {
|
|
480
|
+
logger.error(`execution (${execution.id}) could not acquire process lock`, { error });
|
|
481
|
+
throw error;
|
|
400
482
|
}
|
|
401
|
-
return processor;
|
|
402
483
|
}
|
|
403
484
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
clear(executionId: number | string): void;
|
|
30
|
+
shouldContinue(execution: ExecutionModel, options?: Transactionable): Promise<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* Owner-only per-execution timer. Only call from code paths that have acquired
|
|
33
|
+
* local execution ownership, such as Dispatcher/Processor processing paths.
|
|
34
|
+
*/
|
|
35
|
+
scheduleExecutionTimeout(execution: ExecutionModel): void;
|
|
36
|
+
invalidateNextExpiresAtIfMatches(expiresAt?: Date | null): void;
|
|
37
|
+
private scanExpiredExecutions;
|
|
38
|
+
private scheduleScan;
|
|
39
|
+
private scheduleNextExpiresAtTimer;
|
|
40
|
+
private scheduleNextExpiresAtIfEarlier;
|
|
41
|
+
private handleNextExpiresAtTimeout;
|
|
42
|
+
private clearNextExpiresAtTimer;
|
|
43
|
+
private clearExecutionTimeout;
|
|
44
|
+
private handleExecutionTimeout;
|
|
45
|
+
}
|