@tachybase/module-workflow
Version:
A powerful BPM tool that provides foundational support for business automation, with the capability to extend unlimited triggers and nodes.
289 lines (288 loc) • 11.7 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var CustomActionTrigger_exports = {};
__export(CustomActionTrigger_exports, {
OmniTrigger: () => OmniTrigger
});
module.exports = __toCommonJS(CustomActionTrigger_exports);
var import_module_error_handler = __toESM(require("@tachybase/module-error-handler"));
var import_server = require("@tego/server");
var import_lodash = __toESM(require("lodash"));
var import_constants = require("../../constants");
var import_triggers = __toESM(require("../../triggers"));
var import_utils = require("../../utils");
class CustomActionInterceptionError extends Error {
constructor(message) {
super(message);
this.status = 400;
this.messages = [];
this.name = "CustomActionInterceptionError";
}
}
const _OmniTrigger = class _OmniTrigger extends import_triggers.default {
constructor(workflow) {
super(workflow);
this.triggerAction = async (ctx, next) => {
const {
params: { filterByTk, values, triggerWorkflows = "", filter, resourceName, actionName }
} = ctx.action;
if (actionName !== "trigger" || resourceName === "workflows") {
return next();
}
const { currentUser, currentRole } = ctx.state;
const { model: UserModel } = this.workflow.db.getCollection("users");
const userInfo = {
user: UserModel.build(currentUser).desensitize(),
roleName: currentRole
};
const dataSourceHeader = ctx.get("x-data-source");
const jointCollectionName = (0, import_server.joinCollectionName)(dataSourceHeader, resourceName);
const triggerWorkflowsMap = /* @__PURE__ */ new Map();
const triggerWorkflowsArray = [];
for (const trigger of triggerWorkflows.split(",")) {
const [key, path] = trigger.split("!");
triggerWorkflowsMap.set(key, path);
triggerWorkflowsArray.push(key);
}
const workflows = Array.from(this.workflow.enabledCache.values()).filter(
(item) => item.type === _OmniTrigger.TYPE && item.config.collection === jointCollectionName && triggerWorkflowsArray.includes(item.key)
).sort((a, b) => {
const aIndex = triggerWorkflowsArray.indexOf(a.key);
const bIndex = triggerWorkflowsArray.indexOf(b.key);
if (aIndex === -1 && bIndex === -1) {
return a.id - b.id;
}
if (aIndex === -1) {
return 1;
}
if (bIndex === -1) {
return -1;
}
return aIndex - bIndex;
});
const syncGroup = [];
const asyncGroup = [];
for (const workflow of workflows) {
const { appends = [] } = workflow.config;
const [dataSourceName, collectionName] = (0, import_server.parseCollectionName)(workflow.config.collection);
const dataPath = triggerWorkflowsMap.get(workflow.key);
const event = [workflow];
const { repository } = ctx.tego.dataSourceManager.dataSources.get(dataSourceName).collectionManager.getCollection(collectionName);
const formData = dataPath ? import_lodash.default.get(values, dataPath) : values;
let data = formData;
if (filterByTk != null) {
if ((0, import_lodash.isArray)(filterByTk)) {
data = await repository.find({ filterByTk, appends });
} else {
data = await repository.findOne({ filterByTk, appends });
}
if (!data) {
continue;
}
Object.assign(data, formData);
} else if (filter != null) {
data = await repository.find({ filter, appends });
if (!data) {
continue;
}
Object.assign(data, formData);
}
event.push({ data: (0, import_utils.toJSON)(data), ...userInfo });
(workflow.sync ? syncGroup : asyncGroup).push(event);
}
for (const event of syncGroup) {
const processor = await this.workflow.trigger(event[0], event[1], { httpContext: ctx });
if (!processor) {
return ctx.throw(500);
}
const { lastSavedJob, nodesMap } = processor;
const lastNode = nodesMap.get(lastSavedJob == null ? void 0 : lastSavedJob.nodeId);
if (processor.execution.status === import_constants.EXECUTION_STATUS.RESOLVED) {
if ((lastNode == null ? void 0 : lastNode.type) === "end") {
return;
}
continue;
}
if (processor.execution.status < import_constants.EXECUTION_STATUS.STARTED) {
if ((lastNode == null ? void 0 : lastNode.type) !== "end") {
return ctx.throw(
500,
ctx.t("Workflow on your action failed, please contact the administrator", { ns: "workflow" })
);
}
const err = new CustomActionInterceptionError("Request is intercepted by workflow");
err.status = 400;
err.messages = ctx.state.messages;
return ctx.throw(err.status, err);
}
return ctx.throw(500, "Workflow on your action hangs, please contact the administrator");
}
for (const event of asyncGroup) {
this.workflow.trigger(event[0], event[1]);
}
await next();
};
this.middleware = async (ctx, next) => {
const {
resourceName,
actionName,
params: { triggerWorkflows, beforeWorkflows }
} = ctx.action;
if (beforeWorkflows) {
await this.trigger(ctx, beforeWorkflows, "before");
}
if (resourceName === "workflows" && actionName === "trigger") {
return this.triggerAction(ctx, next);
}
await next();
if (!triggerWorkflows) {
return;
}
if (!["create", "update"].includes(actionName)) {
return;
}
return this.trigger(ctx, triggerWorkflows);
};
const errorHandlerPlugin = this.workflow.app.pm.get("error-handler") || this.workflow.app.pm.get(import_module_error_handler.default);
if (!(errorHandlerPlugin == null ? void 0 : errorHandlerPlugin.errorHandler)) {
throw new Error("Workflow omni trigger requires the error-handler plugin");
}
this.workflow.app.resourcer.registerActionHandler("trigger", this.triggerAction);
this.workflow.app.acl.allow("*", "trigger", "loggedIn");
errorHandlerPlugin.errorHandler.register(
(err) => err instanceof CustomActionInterceptionError,
async (err, ctx) => {
ctx.body = {
errors: err.messages
};
ctx.status = err.status;
}
);
workflow.app.resourcer.use(this.middleware, { tag: "workflowTrigger", after: "acl" });
}
async trigger(ctx, workflowList, order = "after") {
if (!workflowList) {
return;
}
const { values } = ctx.action.params;
const dataSourceHeader = ctx.get("x-data-source") || "main";
const { currentUser, currentRole } = ctx.state;
const { model: UserModel } = this.workflow.db.getCollection("users");
const userInfo = {
user: UserModel.build(currentUser).desensitize(),
roleName: currentRole
};
const triggers = workflowList.split(",").map((trigger) => trigger.split("!"));
const workflowRepo = this.workflow.db.getRepository("workflows");
const workflows = (await workflowRepo.find({
filter: {
key: triggers.map((trigger) => trigger[0]),
current: true,
type: "general-action",
enabled: true
}
})).filter((workflow) => Boolean(workflow.config.collection));
const syncGroup = [];
const asyncGroup = [];
for (const workflow of workflows) {
const { collection, appends = [] } = workflow.config;
const [dataSourceName, collectionName] = (0, import_server.parseCollectionName)(collection);
const trigger = triggers.find((trigger2) => trigger2[0] === workflow.key);
const event = [workflow];
if (ctx.action.resourceName !== "workflows") {
if (order === "before") {
event.push({ data: ctx.action.params, ...userInfo });
(workflow.sync ? syncGroup : asyncGroup).push(event);
continue;
}
if (!ctx.body) {
continue;
}
if (dataSourceName !== dataSourceHeader) {
continue;
}
const { body: data } = ctx;
for (const row of Array.isArray(data) ? data : [data]) {
let payload = row;
if (trigger[1]) {
const paths = trigger[1].split(".");
for (const field of paths) {
if (payload.get(field)) {
payload = payload.get(field);
} else {
const association = (0, import_server.modelAssociationByKey)(payload, field);
payload = await payload[association.accessors.get]();
}
}
}
const model = payload.constructor;
if (payload instanceof import_server.Model) {
if (collectionName !== model.collection.name) {
continue;
}
if (appends.length) {
payload = await model.collection.repository.findOne({
filterByTk: payload.get(model.primaryKeyAttribute),
appends
});
}
}
event.push({ data: (0, import_utils.toJSON)(payload), ...userInfo });
}
} else {
const { model, repository } = ctx.tego.dataSourceManager.dataSources.get(dataSourceName).collectionManager.getCollection(collectionName);
let data = trigger[1] ? (0, import_lodash.get)(values, trigger[1]) : values;
const pk = (0, import_lodash.get)(data, model.primaryKeyAttribute);
if (appends.length && pk != null) {
data = await repository.findOne({
filterByTk: pk,
appends
});
}
event.push({ data, ...userInfo });
}
(workflow.sync ? syncGroup : asyncGroup).push(event);
}
for (const event of syncGroup) {
await this.workflow.trigger(event[0], event[1], { httpContext: ctx });
}
for (const event of asyncGroup) {
this.workflow.trigger(event[0], event[1]);
}
}
on() {
}
off() {
}
};
_OmniTrigger.TYPE = "general-action";
let OmniTrigger = _OmniTrigger;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
OmniTrigger
});