@logicflow/engine
Version:
a process engine for javascript
235 lines • 9.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FlowModel = void 0;
const tslib_1 = require("tslib");
const Scheduler_1 = require("./Scheduler");
const utils_1 = require("./utils");
const constant_1 = require("./constant");
class FlowModel {
constructor({ nodeModelMap, recorder, context = {}, globalData = {}, startNodeType = 'StartNode', }) {
/**
* 当前流程模型中的所有节点,边会被转换成节点的 incoming 和 outgoing 属性
*/
this.nodeConfigMap = new Map();
/**
* 当前流程中开始节点组成的数组
*/
this.startNodes = [];
/**
* 用于存储全局数据,最终会传递给每个节点
*/
this.globalData = {};
// 流程包含的节点类型 ??? 在 load 代码中又初始化了一遍,为什么要传进来
this.nodeModelMap = nodeModelMap;
// 需要执行的队列
this.executeList = [];
// 执行中的任务
this.executingInstance = null;
// 外部传入的上下文,最终会传递给每个节点
this.context = context;
// 用于存储全局数据,可以在流程中共享
this.globalData = globalData;
// 开始节点类型,在执行流程时,会从这些节点开始执行
this.startNodeType = startNodeType;
this.isRunning = false;
this.scheduler = new Scheduler_1.Scheduler({
flowModel: this,
recorder,
});
this.scheduler.on(constant_1.EVENT_INSTANCE_COMPLETE, (result) => {
this.onExecuteFinished(result);
});
this.scheduler.on(constant_1.EVENT_INSTANCE_INTERRUPTED, (result) => {
this.onExecuteFinished(result);
});
this.scheduler.on(constant_1.EVENT_INSTANCE_ERROR, (result) => {
this.onExecuteFinished(result);
});
}
/**
* 解析LogicFlow图数据,将nodes和edges转换成节点格式。
* 例如:
* graphData: {
* nodes: [
* { id: 'node1', type: 'StartNode', properties: {} },
* { id: 'node2', type: 'TaskNode', properties: {} },
* ],
* edges: [
* { id: 'edge1', sourceNodeId: 'node1', targetNodeId: 'node2', properties: {} },
* ]
* }
* 转换成:
* nodeConfigMap: {
* node1: {
* id: 'node1',
* type: 'StartNode',
* properties: {},
* incoming: [],
* outgoing: [{ id: 'edge1', properties: {}, target: 'node2' }]
* },
* node2: {
* id: 'node2',
* type: 'TaskNode',
* properties: {},
* incoming: [{ id: 'edge1', properties: {}, source: 'node1' }],
* outgoing: [],
* }
* }
* 此格式方便后续执行时,根据节点id快速找到节点和执行初始化节点模型。
* 同时此方法还会找到所有的开始节点,方便后续执行时,从开始节点开始执行。
* @param graphData 流程图数据
*/
load(graphData) {
const { nodes = [], edges = [] } = graphData;
nodes.forEach((node) => {
if (this.nodeModelMap.has(node.type)) {
const nodeConfig = {
id: node.id,
type: node.type,
properties: node.properties,
incoming: [],
outgoing: [],
};
this.nodeConfigMap.set(node.id, nodeConfig);
if (node.type === this.startNodeType) {
this.startNodes.push(nodeConfig);
}
}
else {
console.warn(`未识别的节点类型:${node.type}`);
}
});
edges.forEach((edge) => {
const sourceNode = this.nodeConfigMap.get(edge.sourceNodeId);
const targetNode = this.nodeConfigMap.get(edge.targetNodeId);
if (sourceNode) {
sourceNode.outgoing.push({
id: edge.id,
properties: edge.properties,
target: edge.targetNodeId,
});
}
if (targetNode && targetNode.type !== this.startNodeType) {
targetNode.incoming.push({
id: edge.id,
properties: edge.properties,
source: edge.sourceNodeId,
});
}
});
}
/**
* 从待执行队列中取出需要执行的内容。
* 会依次判断是否有 actionId、nodeId、executionId。
* 若存在 actionId,那么表示恢复执行
* 若存在 nodeId,那么表示从指定节点开始执行
* 若都不存在,那么新建一个 executionId,从开始节点开始执行
* @private
*/
createExecution(execParam) {
var _a;
this.executeList.push(execParam);
// 如果有 actionId,则表示恢复执行
// TODO: 待测试,确认该流程
if (execParam.actionId && execParam.nodeId && execParam.executionId) {
this.scheduler.resume({
executionId: execParam.executionId,
actionId: execParam.actionId,
nodeId: execParam.nodeId,
data: execParam.data,
});
return;
}
// 否则,判断 executionId 是否存在,使用 executionId 或创建新的 execution,从开始节点开始执行
// const executionId = execParam?.executionId || createExecId()
const executionId = (0, utils_1.createExecId)();
execParam.executionId = executionId;
// 当指定了具体需要执行的节点时,执行下面方法
if (execParam === null || execParam === void 0 ? void 0 : execParam.nodeId) {
const nodeConfig = this.nodeConfigMap.get(execParam.nodeId);
if (!nodeConfig) {
(_a = execParam === null || execParam === void 0 ? void 0 : execParam.onError) === null || _a === void 0 ? void 0 : _a.call(execParam, new Error(`${(0, constant_1.getErrorMsg)(constant_1.ErrorCode.NONE_NODE_ID)}(${execParam.nodeId})`));
return;
}
// 当指定了开始节点,且该节点存在,则直接以这个节点开始执行
this.startNodes = [nodeConfig];
}
this.startNodes.forEach((startNode) => {
this.scheduler.addAction({
executionId,
nodeId: startNode.id,
});
});
// 所有的开始节点都执行
this.scheduler.run({
executionId,
});
}
/**
* 执行流程,每次执行都会生成一个唯一的 executionId,用于区分不同的执行。
* 同一次执行,这次执行内部的节点执行顺序为并行。内部并行是为了避免异步节点阻塞其他节点的执行
* 多次执行,多次执行之间为串行,这里选择串行的原因是避免多次执行之间的数据冲突。
* 例如:
* 一个流程存在两个开始节点,A 和 B,A 和 B 的下一个节点都是 C,C 的下两个节点是 D 和 E
* 外部分别触发了 A 和 B 的执行,那么 A 和 B 的执行是串行(即 A 执行完再执行 B),但是 D 和 E 的执行是并行的。
* 如果希望 A 和 B 的执行时并行的,就不能使用同一个流程模型执行,应该初始化两个。
* 下面直接使用调度器的队列
* @param params
*/
execute(params) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
this.createExecution(params);
});
}
resume(params) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
this.createExecution(params);
});
}
/**
* 创建节点实例,每个节点实例都会有一个唯一的 actionId
* 通过 executionId, nodeId, actionId 可以唯一确定一个节点的某一次执行
* @param nodeId
* @returns 节点实例
*/
// TODO: 确认下面这种场景,类型如何定义
createAction(nodeId) {
const nodeConfig = this.nodeConfigMap.get(nodeId);
if (nodeConfig) {
const NodeModel = this.nodeModelMap.get(nodeConfig.type);
if (!NodeModel) {
throw new Error('该 NodeModel 不存在,抛出异常');
}
return new NodeModel({
nodeConfig,
globalData: this.globalData,
context: this.context,
});
}
}
setStartNodeType(type) {
this.startNodeType = type;
}
updateGlobalData(data) {
// TODO: 数据的合并,是否考虑子项的合并(默认值的替换)
this.globalData = Object.assign(Object.assign({}, this.globalData), data);
return this.globalData;
}
/**
* 在执行完成后,通知外部此次之行完成
* 如果还存在待执行的任务,那么继续执行
* @param result
* @private
*/
onExecuteFinished(result) {
const index = this.executeList.findIndex((i) => i.executionId === result.executionId);
if (index > -1) {
const { callback } = this.executeList[index];
this.executeList.splice(index, 1);
callback === null || callback === void 0 ? void 0 : callback(result);
}
}
}
exports.FlowModel = FlowModel;
exports.default = FlowModel;
//# sourceMappingURL=FlowModel.js.map