UNPKG

@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
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 });