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.

457 lines (456 loc) 14.1 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; 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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var nodes_exports = {}; __export(nodes_exports, { create: () => create, destroy: () => destroy, moveDown: () => moveDown, moveUp: () => moveUp, syncRemoteCode: () => syncRemoteCode, update: () => update }); module.exports = __toCommonJS(nodes_exports); var import_server = require("@tego/server"); var import_get_remote_code_fetcher = require("../utils/get-remote-code-fetcher"); async function create(ctx, next) { const { db } = ctx; const repository = import_server.utils.getRepositoryFromParams(ctx); const { whitelist, blacklist, updateAssociationValues, values, associatedIndex: workflowId } = ctx.action.params; ctx.body = await db.sequelize.transaction(async (transaction) => { const workflow = await repository.getSourceModel(transaction); if (workflow.executed) { ctx.throw(400, "Node could not be created in executed workflow"); } const instance = await repository.create({ values, whitelist, blacklist, updateAssociationValues, context: ctx, transaction }); if (!instance.upstreamId) { const previousHead = await repository.findOne({ filter: { id: { $ne: instance.id }, upstreamId: null }, transaction }); if (previousHead) { await previousHead.setUpstream(instance, { transaction }); await instance.setDownstream(previousHead, { transaction }); instance.set("downstream", previousHead); } return instance; } const upstream = await instance.getUpstream({ transaction }); if (instance.branchIndex == null) { const downstream = await upstream.getDownstream({ transaction }); if (downstream) { await downstream.setUpstream(instance, { transaction }); await instance.setDownstream(downstream, { transaction }); instance.set("downstream", downstream); } await upstream.update( { downstreamId: instance.id }, { transaction } ); upstream.set("downstream", instance); } else { const [downstream] = await upstream.getBranches({ where: { id: { [import_server.Op.ne]: instance.id }, branchIndex: instance.branchIndex }, transaction }); if (downstream) { await downstream.update( { upstreamId: instance.id, branchIndex: null }, { transaction } ); await instance.setDownstream(downstream, { transaction }); instance.set("downstream", downstream); } } instance.set("upstream", upstream); return instance; }); await next(); } function searchBranchNodes(nodes, from) { const branchHeads = nodes.filter((item) => item.upstreamId === from.id && item.branchIndex != null); return branchHeads.reduce( (flatten, head) => flatten.concat(searchBranchDownstreams(nodes, head)), [] ); } function searchBranchDownstreams(nodes, from) { let result = []; for (let search = from; search; search = search.downstream) { result = [...result, search, ...searchBranchNodes(nodes, search)]; } return result; } async function destroy(ctx, next) { const { db } = ctx; const repository = import_server.utils.getRepositoryFromParams(ctx); const { filterByTk } = ctx.action.params; const fields = ["id", "upstreamId", "downstreamId", "branchIndex"]; const instance = await repository.findOne({ filterByTk, fields: [...fields, "workflowId"], appends: ["upstream", "downstream", "workflow"] }); if (instance.workflow.executed) { ctx.throw(400, "Nodes in executed workflow could not be deleted"); } await db.sequelize.transaction(async (transaction) => { const { upstream, downstream } = instance.get(); if (upstream && upstream.downstreamId === instance.id) { await upstream.update( { downstreamId: instance.downstreamId }, { transaction } ); } if (downstream) { await downstream.update( { upstreamId: instance.upstreamId, branchIndex: instance.branchIndex }, { transaction } ); } const nodes = await repository.find({ filter: { workflowId: instance.workflowId }, fields, transaction }); const nodesMap = /* @__PURE__ */ new Map(); nodes.forEach((item) => { nodesMap.set(item.id, item); }); nodes.forEach((item) => { if (item.upstreamId) { item.upstream = nodesMap.get(item.upstreamId); } if (item.downstreamId) { item.downstream = nodesMap.get(item.downstreamId); } }); const branchNodes = searchBranchNodes(nodes, nodesMap.get(instance.id)); await repository.destroy({ filterByTk: [instance.id, ...branchNodes.map((item) => item.id)], transaction }); }); ctx.body = instance; await next(); } async function update(ctx, next) { const { db } = ctx; const repository = import_server.utils.getRepositoryFromParams(ctx); const { filterByTk, values, whitelist, blacklist, filter, updateAssociationValues } = ctx.action.params; ctx.body = await db.sequelize.transaction(async (transaction) => { const { workflow } = await repository.findOne({ filterByTk, appends: ["workflow.executed"], transaction }); if (workflow.executed) { ctx.throw(400, "Nodes in executed workflow could not be reconfigured"); } return repository.update({ filterByTk, values, whitelist, blacklist, filter, updateAssociationValues, context: ctx, transaction }); }); await next(); } async function moveUp(ctx, next) { const { db } = ctx; const repository = import_server.utils.getRepositoryFromParams(ctx); const { filterByTk } = ctx.action.params; const fields = ["id", "upstreamId", "downstreamId", "branchIndex", "key"]; const instance = await repository.findOne({ filterByTk, fields: [...fields, "workflowId"], appends: ["upstream", "downstream", "workflow"] }); if (instance.workflow.executed) { ctx.throw(400, "Nodes in executed workflow could not be deleted"); } await db.sequelize.transaction(async (transaction) => { const { upstream, downstream } = instance.get(); if (!upstream) { ctx.throw(400, "First node could not be moved up"); } const upUpStreamId = upstream.upstreamId; const upStreamId = upstream.id; if (upUpStreamId) { await repository.update({ filterByTk: upUpStreamId, values: { downstreamId: instance.id }, transaction }); } await upstream.update( { downstreamId: instance.downstreamId, upstreamId: instance.id }, { transaction } ); await instance.update( { downstreamId: instance.upstreamId, upstreamId: upUpStreamId }, { transaction } ); if (downstream) { await downstream.update( { upstreamId: upStreamId }, { transaction } ); } }); ctx.body = instance; await next(); } async function moveDown(ctx, next) { const { db } = ctx; const repository = import_server.utils.getRepositoryFromParams(ctx); const { filterByTk } = ctx.action.params; const fields = ["id", "upstreamId", "downstreamId", "branchIndex", "key"]; const instance = await repository.findOne({ filterByTk, fields: [...fields, "workflowId"], appends: ["upstream", "downstream", "workflow"] }); if (instance.workflow.executed) { ctx.throw(400, "Nodes in executed workflow could not be deleted"); } await db.sequelize.transaction(async (transaction) => { const { upstream, downstream } = instance.get(); const downDownstreamId = downstream.downstreamId; if (!downstream) { ctx.throw(400, "Last node could not be moved up"); } if (upstream) { await upstream.update( { downstreamId: instance.downstreamId }, { transaction } ); } await downstream.update( { upstreamId: instance.upstreamId, downstreamId: instance.id }, { transaction } ); await instance.update( { downstreamId: downDownstreamId, upstreamId: downstream.id }, { transaction } ); if (downDownstreamId) { await repository.update({ filterByTk: downDownstreamId, values: { upstreamId: instance.id }, transaction }); } }); ctx.body = instance; await next(); } async function syncRemoteCode(ctx, next) { var _a; const params = ctx.action.params.values || ctx.action.params || {}; const { codeUrl, codeType, codeBranch = "main", codeAuthType, codeAuthToken, codeAuthUsername, nodeId } = params; if (!codeUrl || !codeType) { ctx.throw(400, "codeUrl and codeType are required"); } try { const remoteCodeFetcher = (0, import_get_remote_code_fetcher.getRemoteCodeFetcher)(ctx.app); if (codeType === "git" && !remoteCodeFetcher) { ctx.throw(500, "WorkflowRemoteCodeFetcher service is required for Git type."); return; } ctx.logger.info( `Syncing remote code (force refresh): ${codeUrl} (type: ${codeType}, branch: ${codeBranch || "main"})` ); if (!remoteCodeFetcher) { if (codeType === "cdn") { const http = require("node:http"); const https = require("node:https"); const { URL } = require("node:url"); const urlObj = new URL(codeUrl); const client = urlObj.protocol === "https:" ? https : http; const code = await new Promise((resolve, reject) => { const headers = { "User-Agent": "TegoWorkflow/1.0" }; if (codeAuthType === "token" && codeAuthToken) { headers["Authorization"] = `Bearer ${codeAuthToken}`; } else if (codeAuthType === "basic" && codeAuthUsername && codeAuthToken) { const credentials = Buffer.from(`${codeAuthUsername}:${codeAuthToken}`).toString("base64"); headers["Authorization"] = `Basic ${credentials}`; } const request = client.get( { hostname: urlObj.hostname, port: urlObj.port || (urlObj.protocol === "https:" ? 443 : 80), path: urlObj.pathname + urlObj.search, headers, timeout: 1e4 }, (res) => { if (res.statusCode !== 200) { reject(new Error(`Failed to fetch: HTTP ${res.statusCode}`)); return; } let data = ""; res.on("data", (chunk) => { data += chunk; }); res.on("end", () => { resolve(data); }); } ); request.on("error", reject); request.on("timeout", () => { request.destroy(); reject(new Error("Request timeout")); }); }); ctx.body = { code }; } else { ctx.throw(500, `Unsupported code type: ${codeType}. RemoteCodeFetcher service is required.`); return; } } else { const code = await remoteCodeFetcher.fetchCode( codeUrl, codeType, codeBranch || "main", // 使用配置的分支,默认为 'main' void 0, // codePath - 使用默认值 codeAuthType, codeAuthToken, codeAuthUsername ); ctx.body = { code }; } if (nodeId && ((_a = ctx.body) == null ? void 0 : _a.code)) { try { const repository = ctx.db.getRepository("flow_nodes"); const node = await repository.findOne({ filterByTk: nodeId }); if (node) { const currentConfig = node.get("config") || {}; const config = JSON.parse(JSON.stringify(currentConfig)); const syncTime = (/* @__PURE__ */ new Date()).toISOString(); config.lastSyncTime = syncTime; config.codeCache = { content: ctx.body.code, timestamp: Date.now() }; await repository.update({ filterByTk: nodeId, values: { config } }); } } catch (error) { ctx.logger.warn("Failed to update lastSyncTime and codeCache", { error: error instanceof Error ? error.message : String(error), nodeId }); } } await next(); } catch (error) { ctx.logger.error("Failed to sync remote code", { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : void 0, params }); ctx.throw(500, `Failed to fetch remote code: ${error instanceof Error ? error.message : String(error)}`); } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { create, destroy, moveDown, moveUp, syncRemoteCode, update });