@logicflow/engine 0.0.9 → 0.0.10-beta.0
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/README.md +5 -2
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/es/EventEmitter.d.ts +34 -4
- package/es/EventEmitter.js +70 -48
- package/es/EventEmitter.js.map +1 -0
- package/es/FlowModel.d.ts +75 -73
- package/es/FlowModel.js +130 -173
- package/es/FlowModel.js.map +1 -0
- package/es/Scheduler.d.ts +50 -34
- package/es/Scheduler.js +134 -209
- package/es/Scheduler.js.map +1 -0
- package/es/constant/{constant.js → index.js} +8 -7
- package/es/constant/index.js.map +1 -0
- package/es/constant/logCode.js +29 -0
- package/es/constant/logCode.js.map +1 -0
- package/es/expression/brewserVm.d.ts +2 -0
- package/es/expression/brewserVm.js +30 -0
- package/es/expression/brewserVm.js.map +1 -0
- package/es/expression/index.d.ts +1 -1
- package/es/expression/index.js +16 -59
- package/es/expression/index.js.map +1 -0
- package/es/expression/nodeVm.d.ts +4 -2
- package/es/expression/nodeVm.js +10 -50
- package/es/expression/nodeVm.js.map +1 -0
- package/es/index.d.ts +135 -38
- package/es/index.js +92 -144
- package/es/index.js.map +1 -0
- package/es/nodes/base.d.ts +108 -0
- package/es/nodes/base.js +149 -0
- package/es/nodes/base.js.map +1 -0
- package/es/nodes/index.d.ts +3 -0
- package/es/nodes/index.js +4 -0
- package/es/nodes/index.js.map +1 -0
- package/es/nodes/{StartNode.d.ts → start.d.ts} +3 -2
- package/es/nodes/start.js +11 -0
- package/es/nodes/start.js.map +1 -0
- package/es/nodes/{TaskNode.d.ts → task.d.ts} +3 -2
- package/es/nodes/task.js +11 -0
- package/es/nodes/task.js.map +1 -0
- package/es/platform/browser/browserVm.d.ts +4 -0
- package/es/platform/browser/browserVm.js +44 -0
- package/es/platform/browser/browserVm.js.map +1 -0
- package/es/platform/browser/index.d.ts +4 -0
- package/es/platform/browser/index.js +23 -0
- package/es/platform/browser/index.js.map +1 -0
- package/es/platform/index.d.ts +1 -0
- package/es/platform/index.js +2 -0
- package/es/platform/index.js.map +1 -0
- package/es/platform/node/index.d.ts +4 -0
- package/es/platform/node/index.js +23 -0
- package/es/platform/node/index.js.map +1 -0
- package/es/platform/node/nodeVm.d.ts +1 -0
- package/es/platform/node/nodeVm.js +9 -0
- package/es/platform/node/nodeVm.js.map +1 -0
- package/es/recorder/index.d.ts +36 -10
- package/es/recorder/index.js +82 -135
- package/es/recorder/index.js.map +1 -0
- package/es/utils/global.d.ts +5 -0
- package/es/utils/global.js +27 -0
- package/es/utils/global.js.map +1 -0
- package/es/utils/id.js +14 -0
- package/es/utils/id.js.map +1 -0
- package/es/utils/index.d.ts +4 -0
- package/es/utils/index.js +5 -0
- package/es/utils/index.js.map +1 -0
- package/es/{util → utils}/storage.js +17 -16
- package/es/utils/storage.js.map +1 -0
- package/lib/EventEmitter.d.ts +37 -0
- package/lib/EventEmitter.js +94 -0
- package/lib/EventEmitter.js.map +1 -0
- package/lib/FlowModel.d.ts +146 -0
- package/lib/FlowModel.js +236 -0
- package/lib/FlowModel.js.map +1 -0
- package/lib/Scheduler.d.ts +78 -0
- package/lib/Scheduler.js +179 -0
- package/lib/Scheduler.js.map +1 -0
- package/lib/constant/index.d.ts +16 -0
- package/{cjs/constant/constant.js → lib/constant/index.js} +4 -3
- package/lib/constant/index.js.map +1 -0
- package/lib/constant/logCode.d.ts +12 -0
- package/{cjs/constant/LogCode.js → lib/constant/logCode.js} +16 -13
- package/lib/constant/logCode.js.map +1 -0
- package/lib/expression/brewserVm.d.ts +2 -0
- package/lib/expression/brewserVm.js +33 -0
- package/lib/expression/brewserVm.js.map +1 -0
- package/lib/expression/index.d.ts +2 -0
- package/lib/expression/index.js +20 -0
- package/lib/expression/index.js.map +1 -0
- package/lib/expression/nodeVm.d.ts +4 -0
- package/lib/expression/nodeVm.js +13 -0
- package/lib/expression/nodeVm.js.map +1 -0
- package/lib/index.d.ts +157 -0
- package/lib/index.js +159 -0
- package/lib/index.js.map +1 -0
- package/lib/nodes/base.d.ts +108 -0
- package/lib/nodes/base.js +152 -0
- package/lib/nodes/base.js.map +1 -0
- package/lib/nodes/index.d.ts +3 -0
- package/lib/nodes/index.js +7 -0
- package/lib/nodes/index.js.map +1 -0
- package/lib/nodes/start.d.ts +6 -0
- package/lib/nodes/start.js +15 -0
- package/lib/nodes/start.js.map +1 -0
- package/lib/nodes/task.d.ts +6 -0
- package/lib/nodes/task.js +15 -0
- package/lib/nodes/task.js.map +1 -0
- package/lib/platform/browser/browserVm.d.ts +4 -0
- package/lib/platform/browser/browserVm.js +49 -0
- package/lib/platform/browser/browserVm.js.map +1 -0
- package/lib/platform/browser/index.d.ts +4 -0
- package/lib/platform/browser/index.js +28 -0
- package/lib/platform/browser/index.js.map +1 -0
- package/lib/platform/index.d.ts +1 -0
- package/lib/platform/index.js +5 -0
- package/lib/platform/index.js.map +1 -0
- package/lib/platform/node/index.d.ts +4 -0
- package/lib/platform/node/index.js +28 -0
- package/lib/platform/node/index.js.map +1 -0
- package/lib/platform/node/nodeVm.d.ts +1 -0
- package/lib/platform/node/nodeVm.js +13 -0
- package/lib/platform/node/nodeVm.js.map +1 -0
- package/lib/recorder/index.d.ts +46 -0
- package/lib/recorder/index.js +117 -0
- package/lib/recorder/index.js.map +1 -0
- package/lib/utils/global.d.ts +5 -0
- package/lib/utils/global.js +31 -0
- package/lib/utils/global.js.map +1 -0
- package/lib/utils/id.d.ts +3 -0
- package/lib/utils/id.js +20 -0
- package/lib/utils/id.js.map +1 -0
- package/lib/utils/index.d.ts +4 -0
- package/lib/utils/index.js +9 -0
- package/lib/utils/index.js.map +1 -0
- package/lib/utils/storage.d.ts +7 -0
- package/{cjs/util → lib/utils}/storage.js +18 -17
- package/lib/utils/storage.js.map +1 -0
- package/package.json +30 -71
- package/src/EventEmitter.ts +103 -0
- package/src/FlowModel.ts +325 -0
- package/src/Scheduler.ts +244 -0
- package/src/constant/index.ts +23 -0
- package/src/constant/logCode.ts +34 -0
- package/src/expression/brewserVm.ts +36 -0
- package/src/expression/index.ts +17 -0
- package/src/expression/nodeVm.ts +14 -0
- package/src/index.ts +300 -0
- package/src/nodes/base.ts +234 -0
- package/src/nodes/index.ts +3 -0
- package/src/nodes/start.ts +8 -0
- package/src/nodes/task.ts +8 -0
- package/src/platform/browser/browserVm.ts +52 -0
- package/src/platform/browser/index.ts +28 -0
- package/src/platform/index.ts +1 -0
- package/src/platform/node/index.ts +28 -0
- package/src/platform/node/nodeVm.ts +12 -0
- package/src/recorder/index.ts +137 -0
- package/src/typings.d.ts +0 -0
- package/src/utils/global.ts +41 -0
- package/src/utils/id.ts +16 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/storage.ts +55 -0
- package/cjs/EventEmitter.js +0 -70
- package/cjs/FlowModel.js +0 -277
- package/cjs/Scheduler.js +0 -252
- package/cjs/expression/browserVm.js +0 -81
- package/cjs/expression/index.js +0 -63
- package/cjs/expression/nodeVm.js +0 -53
- package/cjs/index.js +0 -210
- package/cjs/nodes/BaseNode.js +0 -252
- package/cjs/nodes/StartNode.js +0 -27
- package/cjs/nodes/TaskNode.js +0 -27
- package/cjs/recorder/index.js +0 -168
- package/cjs/util/ID.js +0 -16
- package/cjs/util/global.js +0 -32
- package/es/constant/LogCode.js +0 -28
- package/es/expression/browserVm.d.ts +0 -4
- package/es/expression/browserVm.js +0 -76
- package/es/nodes/BaseNode.d.ts +0 -110
- package/es/nodes/BaseNode.js +0 -250
- package/es/nodes/StartNode.js +0 -25
- package/es/nodes/TaskNode.js +0 -25
- package/es/util/ID.js +0 -13
- package/es/util/global.d.ts +0 -5
- package/es/util/global.js +0 -26
- package/lib/main.js +0 -1
- /package/es/constant/{constant.d.ts → index.d.ts} +0 -0
- /package/es/constant/{LogCode.d.ts → logCode.d.ts} +0 -0
- /package/es/{util/ID.d.ts → utils/id.d.ts} +0 -0
- /package/es/{util → utils}/storage.d.ts +0 -0
package/src/Scheduler.ts
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { Engine } from '.'
|
|
2
|
+
import Recorder from './recorder'
|
|
3
|
+
import FlowModel from './FlowModel'
|
|
4
|
+
import EventEmitter from './EventEmitter'
|
|
5
|
+
import { createActionId } from './utils'
|
|
6
|
+
import {
|
|
7
|
+
EVENT_INSTANCE_ERROR,
|
|
8
|
+
EVENT_INSTANCE_COMPLETE,
|
|
9
|
+
EVENT_INSTANCE_INTERRUPTED,
|
|
10
|
+
FlowStatus,
|
|
11
|
+
} from './constant'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 调度器
|
|
15
|
+
* 通过一个队列维护需要执行的节点,一个集合维护正在执行的节点
|
|
16
|
+
*/
|
|
17
|
+
export class Scheduler extends EventEmitter {
|
|
18
|
+
/**
|
|
19
|
+
* 当前需要执行的节点队列
|
|
20
|
+
*/
|
|
21
|
+
nodeQueueMap: Map<Engine.Key, Engine.NodeParam[]>
|
|
22
|
+
/**
|
|
23
|
+
* 当前正在执行的节点集合
|
|
24
|
+
* 在每个节点执行完成后,会从集合中删除
|
|
25
|
+
* 同时会判断次集合中是否还存在和此节点相同的 executionId,如果不存在,说明该流程已经执行完成
|
|
26
|
+
*/
|
|
27
|
+
actionRunningMap: Map<Engine.Key, Scheduler.ActionParamMap>
|
|
28
|
+
/**
|
|
29
|
+
* 流程模型,用于创建节点模型
|
|
30
|
+
*/
|
|
31
|
+
flowModel: FlowModel
|
|
32
|
+
/**
|
|
33
|
+
* 执行记录存储器
|
|
34
|
+
* 用于存储节点执行的结果
|
|
35
|
+
*/
|
|
36
|
+
recorder?: Recorder
|
|
37
|
+
|
|
38
|
+
constructor(config: Scheduler.ISchedulerProps) {
|
|
39
|
+
super()
|
|
40
|
+
this.nodeQueueMap = new Map()
|
|
41
|
+
this.actionRunningMap = new Map()
|
|
42
|
+
this.flowModel = config.flowModel
|
|
43
|
+
this.recorder = config.recorder
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 添加一个任务到队列中。
|
|
48
|
+
* 1. 由流程模型将所有的开始及诶带你添加到队列中
|
|
49
|
+
* 2. 当一个节点执行完成后,将后续的节点添加到队列中
|
|
50
|
+
* @param nodeParam
|
|
51
|
+
*/
|
|
52
|
+
public addAction(nodeParam: Engine.NodeParam) {
|
|
53
|
+
const { executionId } = nodeParam
|
|
54
|
+
if (!this.nodeQueueMap.has(executionId)) {
|
|
55
|
+
this.nodeQueueMap.set(executionId, [])
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const currentActionQueue: Engine.NodeParam[] | undefined =
|
|
59
|
+
this.nodeQueueMap.get(executionId)
|
|
60
|
+
if (currentActionQueue) {
|
|
61
|
+
currentActionQueue.push(nodeParam)
|
|
62
|
+
}
|
|
63
|
+
console.log('this.nodeQueueMap--->>>', this.nodeQueueMap)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private pushActionToRunningMap(actionParam: Scheduler.ActionParam) {
|
|
67
|
+
const { executionId, actionId } = actionParam
|
|
68
|
+
if (!this.actionRunningMap.has(executionId)) {
|
|
69
|
+
const runningMap: Scheduler.ActionParamMap = new Map()
|
|
70
|
+
this.actionRunningMap.set(executionId, runningMap)
|
|
71
|
+
}
|
|
72
|
+
if (actionId) {
|
|
73
|
+
this.actionRunningMap.get(executionId)?.set(actionId, actionParam)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private removeActionFromRunningMap(actionParam: Engine.ActionParam) {
|
|
78
|
+
const { executionId, actionId } = actionParam
|
|
79
|
+
if (!actionId) return
|
|
80
|
+
|
|
81
|
+
const runningMap = this.actionRunningMap.get(executionId)
|
|
82
|
+
if (!runningMap) return
|
|
83
|
+
|
|
84
|
+
runningMap.delete(actionId)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 为了防止多次添加导致
|
|
89
|
+
* @param actionParam
|
|
90
|
+
*/
|
|
91
|
+
private saveActionResult(actionParam: Engine.NextActionParam) {
|
|
92
|
+
this.recorder?.addActionRecord({
|
|
93
|
+
timestamp: Date.now(),
|
|
94
|
+
...actionParam,
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private hasRunningAction(executionId: Engine.Key): boolean {
|
|
99
|
+
const runningMap = this.actionRunningMap.get(executionId)
|
|
100
|
+
if (!runningMap) return false
|
|
101
|
+
if (runningMap.size === 0) {
|
|
102
|
+
this.actionRunningMap.delete(executionId)
|
|
103
|
+
return false
|
|
104
|
+
}
|
|
105
|
+
return true
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 调度器执行下一个任务
|
|
110
|
+
* 1. 提供给流程模型,用户开始执行第一个任务
|
|
111
|
+
* 2. 内部任务执行完成后,调用此方法继续执行下一个任务
|
|
112
|
+
* 3. 当判断没有可以继续执行的任务后,触发流程结束事件
|
|
113
|
+
* @param runParam
|
|
114
|
+
*/
|
|
115
|
+
public run(runParam: Scheduler.ActionParam) {
|
|
116
|
+
const nodeQueue: Engine.NodeParam[] | undefined = this.nodeQueueMap.get(
|
|
117
|
+
runParam.executionId,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
// 将同一个 executionId 当前待执行的节点一起执行
|
|
121
|
+
// 避免出现某一个节点执行时间过长,导致其他节点等待时间过长
|
|
122
|
+
while (nodeQueue?.length) {
|
|
123
|
+
const currentNode = nodeQueue.pop()
|
|
124
|
+
const actionId = createActionId()
|
|
125
|
+
const actionParam: Engine.ActionParam = {
|
|
126
|
+
...(currentNode as Engine.NodeParam),
|
|
127
|
+
actionId,
|
|
128
|
+
}
|
|
129
|
+
this.pushActionToRunningMap(actionParam)
|
|
130
|
+
this.exec(actionParam)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!this.hasRunningAction(runParam.executionId)) {
|
|
134
|
+
// 当一个流程在 nodeQueueMap 和 actionRunningMap 中都不存在执行的节点时,说明这个流程已经执行完成。
|
|
135
|
+
this.emit(EVENT_INSTANCE_COMPLETE, {
|
|
136
|
+
...runParam,
|
|
137
|
+
status: FlowStatus.COMPLETED,
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private next(data: Engine.NextActionParam) {
|
|
143
|
+
if (data.outgoing && data.outgoing.length > 0) {
|
|
144
|
+
data.outgoing.forEach((item) => {
|
|
145
|
+
if (item.result) {
|
|
146
|
+
this.addAction({
|
|
147
|
+
executionId: data.executionId,
|
|
148
|
+
nodeId: item.target,
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.saveActionResult(data)
|
|
155
|
+
this.removeActionFromRunningMap(data)
|
|
156
|
+
this.run(data)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 恢复某个任务的执行
|
|
161
|
+
* 可以自定义节点手动实现流程中断,然后通过此方法恢复流程的执行
|
|
162
|
+
* @param resumeParam
|
|
163
|
+
*/
|
|
164
|
+
public async resume(resumeParam: Engine.ResumeParam) {
|
|
165
|
+
const { executionId, actionId, nodeId } = resumeParam
|
|
166
|
+
this.pushActionToRunningMap({
|
|
167
|
+
executionId,
|
|
168
|
+
actionId,
|
|
169
|
+
nodeId,
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
const model = this.flowModel.createAction(nodeId)
|
|
173
|
+
await model?.resume({
|
|
174
|
+
...resumeParam,
|
|
175
|
+
next: this.next.bind(this),
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 中断时,触发事件
|
|
180
|
+
private interrupted(execResult: Engine.NextActionParam) {
|
|
181
|
+
this.emit(EVENT_INSTANCE_INTERRUPTED, execResult)
|
|
182
|
+
}
|
|
183
|
+
// 报错时,触发事件
|
|
184
|
+
private error(execResult: Engine.NextActionParam) {
|
|
185
|
+
this.emit(EVENT_INSTANCE_ERROR, execResult)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private async exec(actionParam: Engine.ActionParam) {
|
|
189
|
+
const { executionId, actionId, nodeId } = actionParam
|
|
190
|
+
const model = this.flowModel.createAction(nodeId)
|
|
191
|
+
const execResult = await model?.execute({
|
|
192
|
+
executionId,
|
|
193
|
+
actionId,
|
|
194
|
+
nodeId,
|
|
195
|
+
next: this.next.bind(this),
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
if (execResult) {
|
|
199
|
+
const { nodeType, properties, outgoing, status, detail } = execResult
|
|
200
|
+
const actionResult: Engine.NextActionParam = {
|
|
201
|
+
// actionParam
|
|
202
|
+
executionId,
|
|
203
|
+
actionId,
|
|
204
|
+
nodeId,
|
|
205
|
+
// execResult
|
|
206
|
+
nodeType,
|
|
207
|
+
properties,
|
|
208
|
+
outgoing,
|
|
209
|
+
status,
|
|
210
|
+
detail,
|
|
211
|
+
}
|
|
212
|
+
if (execResult?.status === FlowStatus.INTERRUPTED) {
|
|
213
|
+
this.interrupted(execResult)
|
|
214
|
+
this.saveActionResult(actionResult)
|
|
215
|
+
this.removeActionFromRunningMap(actionParam)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (execResult?.status === FlowStatus.ERROR) {
|
|
219
|
+
this.error(execResult)
|
|
220
|
+
this.saveActionResult(actionResult)
|
|
221
|
+
this.removeActionFromRunningMap(actionParam)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// TODO: 考虑停下所有的任务
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export namespace Scheduler {
|
|
229
|
+
export type ActionParam = {
|
|
230
|
+
executionId: Engine.Key
|
|
231
|
+
actionId?: Engine.Key
|
|
232
|
+
nodeId?: Engine.Key
|
|
233
|
+
data?: Record<string, unknown>
|
|
234
|
+
[key: string]: unknown
|
|
235
|
+
}
|
|
236
|
+
export type ActionParamMap = Map<Engine.Key, ActionParam>
|
|
237
|
+
|
|
238
|
+
export interface ISchedulerProps {
|
|
239
|
+
flowModel: FlowModel
|
|
240
|
+
recorder?: Recorder
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export default Scheduler
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// baseType
|
|
2
|
+
export const BASE_START_NODE = 'start'
|
|
3
|
+
|
|
4
|
+
// eventType
|
|
5
|
+
export const EVENT_INSTANCE_COMPLETE = 'instance:complete'
|
|
6
|
+
export const EVENT_INSTANCE_INTERRUPTED = 'instance:interrupted'
|
|
7
|
+
export const EVENT_INSTANCE_ERROR = 'instance:error'
|
|
8
|
+
|
|
9
|
+
// flowStatus
|
|
10
|
+
export enum FlowStatus {
|
|
11
|
+
COMPLETED = 'completed',
|
|
12
|
+
INTERRUPTED = 'interrupted',
|
|
13
|
+
RUNNING = 'running',
|
|
14
|
+
PENDING = 'pending',
|
|
15
|
+
ERROR = 'error',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// actionStatus
|
|
19
|
+
export enum ActionStatus {
|
|
20
|
+
SUCCESS = 'success',
|
|
21
|
+
ERROR = 'error',
|
|
22
|
+
INTERRUPTED = 'interrupted',
|
|
23
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export enum ErrorCode {
|
|
2
|
+
// 模型数据错误
|
|
3
|
+
NONE_START_NODE = 1000,
|
|
4
|
+
NONE_NODE_ID = 1001,
|
|
5
|
+
|
|
6
|
+
// 表达式错误
|
|
7
|
+
NO_DOCUMENT_BODY = 2001,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export enum WarningCode {
|
|
11
|
+
NONE_START_NODE_IN_DATA = 2000,
|
|
12
|
+
START_NODE_INCOMING = 2001,
|
|
13
|
+
|
|
14
|
+
// 表达式判断异常
|
|
15
|
+
EXPRESSION_EXEC_ERROR = 3000,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// TODO: 感觉这块可以用个国际化插件,这样配置不同语言加文件就可以了。
|
|
19
|
+
const errorMsgMapCn = {
|
|
20
|
+
[ErrorCode.NONE_START_NODE]: '未找到入度为0的节点',
|
|
21
|
+
[ErrorCode.NONE_NODE_ID]: '流程数据中存在没有此节点',
|
|
22
|
+
[ErrorCode.NO_DOCUMENT_BODY]: '找不到document.body, 请在DOM加载完成后再执行',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const warningMsgMapCn = {
|
|
26
|
+
[WarningCode.NONE_START_NODE_IN_DATA]: '初始化数据中未找到入度为0的节点',
|
|
27
|
+
[WarningCode.START_NODE_INCOMING]: '开始节点不允许被连入',
|
|
28
|
+
[WarningCode.EXPRESSION_EXEC_ERROR]: '表达式执行异常',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const getErrorMsg = (code: ErrorCode) =>
|
|
32
|
+
`error[${code}]: ${errorMsgMapCn[code]}`
|
|
33
|
+
export const getWarningMsg = (code: WarningCode) =>
|
|
34
|
+
`warning[${code}]: ${warningMsgMapCn[code]}`
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ErrorCode,
|
|
3
|
+
WarningCode,
|
|
4
|
+
getErrorMsg,
|
|
5
|
+
getWarningMsg,
|
|
6
|
+
} from '../constant/logCode'
|
|
7
|
+
|
|
8
|
+
const runInBrowserContext = async (code: string, globalData = {}) => {
|
|
9
|
+
const iframe = document.createElement('iframe')
|
|
10
|
+
iframe.style.display = 'none'
|
|
11
|
+
if (!document || !document.body) {
|
|
12
|
+
console.error(getErrorMsg(ErrorCode.NO_DOCUMENT_BODY))
|
|
13
|
+
}
|
|
14
|
+
document.body.appendChild(iframe)
|
|
15
|
+
|
|
16
|
+
const iframeWindow = iframe.contentWindow as any
|
|
17
|
+
const iframeEval = iframeWindow.eval
|
|
18
|
+
Object.keys(globalData).forEach((key: string) => {
|
|
19
|
+
iframeWindow[key] = globalData[key]
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
let res = null
|
|
23
|
+
try {
|
|
24
|
+
res = iframeEval.call(iframeWindow, code)
|
|
25
|
+
} catch (e) {
|
|
26
|
+
console.warn(getWarningMsg(WarningCode.EXPRESSION_EXEC_ERROR), {
|
|
27
|
+
code,
|
|
28
|
+
globalData,
|
|
29
|
+
e,
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
document.body.removeChild(iframe)
|
|
33
|
+
return res
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { runInBrowserContext }
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { runInNodeContext } from './nodeVm'
|
|
2
|
+
import { runInBrowserContext } from './brewserVm'
|
|
3
|
+
import { isInNodeJS, isInBrowser, globalScope } from '../utils'
|
|
4
|
+
|
|
5
|
+
const getExpressionResult = async (code: string, context: any) => {
|
|
6
|
+
if (isInNodeJS) {
|
|
7
|
+
const r = await runInNodeContext(code, context)
|
|
8
|
+
return r
|
|
9
|
+
}
|
|
10
|
+
if (isInBrowser) {
|
|
11
|
+
const r = await runInBrowserContext(code, context)
|
|
12
|
+
return r
|
|
13
|
+
}
|
|
14
|
+
return globalScope.eval(code)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { getExpressionResult }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import vm from 'node:vm'
|
|
2
|
+
// const vm = require('node:vm');
|
|
3
|
+
|
|
4
|
+
const runInNodeContext = async (
|
|
5
|
+
code: string,
|
|
6
|
+
globalData: Record<string, unknown> = {},
|
|
7
|
+
) => {
|
|
8
|
+
const context = vm.createContext(globalData)
|
|
9
|
+
vm.runInContext(code, context)
|
|
10
|
+
|
|
11
|
+
return context
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { runInNodeContext }
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
// import { LogicFlow } from '@logicflow/core';
|
|
2
|
+
import { BaseNode, StartNode, TaskNode } from './nodes'
|
|
3
|
+
import { FlowModel } from './FlowModel'
|
|
4
|
+
import { Recorder } from './recorder'
|
|
5
|
+
import { createEngineId } from './utils'
|
|
6
|
+
|
|
7
|
+
export class Engine {
|
|
8
|
+
readonly instanceId: string
|
|
9
|
+
graphData?: Engine.GraphConfigData
|
|
10
|
+
|
|
11
|
+
flowModel?: FlowModel
|
|
12
|
+
recorder?: Recorder
|
|
13
|
+
context?: Record<string, unknown>
|
|
14
|
+
nodeModelMap: Map<string, BaseNode.NodeConstructor>
|
|
15
|
+
|
|
16
|
+
constructor(options?: Engine.Options) {
|
|
17
|
+
this.nodeModelMap = new Map()
|
|
18
|
+
this.instanceId = createEngineId()
|
|
19
|
+
if (options?.debug) {
|
|
20
|
+
this.recorder = new Recorder({
|
|
21
|
+
instanceId: this.instanceId,
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
// 默认注册节点 register default nodes
|
|
25
|
+
this.register({
|
|
26
|
+
type: StartNode.nodeTypeName,
|
|
27
|
+
model: StartNode,
|
|
28
|
+
})
|
|
29
|
+
this.register({
|
|
30
|
+
type: TaskNode.nodeTypeName,
|
|
31
|
+
model: TaskNode,
|
|
32
|
+
})
|
|
33
|
+
this.context = options?.context || {}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 注册节点
|
|
38
|
+
* @param nodeConfig { type: 'custom-node', model: NodeClass }
|
|
39
|
+
*/
|
|
40
|
+
register(nodeConfig: Engine.NodeConfig) {
|
|
41
|
+
this.nodeModelMap.set(nodeConfig.type, nodeConfig.model)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 自定义执行记录的存储,默认浏览器使用 sessionStorage, nodejs 使用内存存储
|
|
46
|
+
* 注意:由于执行记录不全会主动删除,所以需要自行清理。
|
|
47
|
+
* nodejs 环境建议自定义为持久化存储。
|
|
48
|
+
* engine.setCustomRecorder({{
|
|
49
|
+
* async addActionRecord(task) {}
|
|
50
|
+
* async getTask(actionId) {}
|
|
51
|
+
* async getExecutionTasks(executionId) {}
|
|
52
|
+
* clear(instanceId) {}
|
|
53
|
+
* }}
|
|
54
|
+
* @param recorder
|
|
55
|
+
*/
|
|
56
|
+
setCustomRecorder(recorder: Recorder) {
|
|
57
|
+
this.recorder = recorder
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 加载流程图数据
|
|
62
|
+
*/
|
|
63
|
+
load({
|
|
64
|
+
graphData,
|
|
65
|
+
startNodeType = 'StartNode',
|
|
66
|
+
globalData = {},
|
|
67
|
+
}: Engine.LoadGraphParam): FlowModel {
|
|
68
|
+
this.graphData = graphData
|
|
69
|
+
const flowModel = new FlowModel({
|
|
70
|
+
nodeModelMap: this.nodeModelMap,
|
|
71
|
+
recorder: this.recorder,
|
|
72
|
+
context: this.context,
|
|
73
|
+
globalData,
|
|
74
|
+
startNodeType,
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
flowModel.load(graphData)
|
|
78
|
+
this.flowModel = flowModel
|
|
79
|
+
return flowModel
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 执行流程,允许多次调用
|
|
84
|
+
*/
|
|
85
|
+
async execute(
|
|
86
|
+
param?: Partial<Engine.ActionParam>,
|
|
87
|
+
): Promise<Engine.NextActionParam> {
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
let execParam = param
|
|
90
|
+
if (!param) {
|
|
91
|
+
execParam = {}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this.flowModel?.execute({
|
|
95
|
+
...execParam,
|
|
96
|
+
callback: (result) => {
|
|
97
|
+
resolve(result)
|
|
98
|
+
},
|
|
99
|
+
onError: (error) => {
|
|
100
|
+
reject(error)
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 中断流程恢复
|
|
108
|
+
* @param resumeParam
|
|
109
|
+
* @returns
|
|
110
|
+
*/
|
|
111
|
+
async resume(
|
|
112
|
+
resumeParam: Engine.ResumeParam,
|
|
113
|
+
): Promise<Engine.NextActionParam | undefined> {
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
this.flowModel?.resume({
|
|
116
|
+
...resumeParam,
|
|
117
|
+
callback: (result) => {
|
|
118
|
+
resolve(result)
|
|
119
|
+
},
|
|
120
|
+
onError: (error) => {
|
|
121
|
+
reject(error)
|
|
122
|
+
},
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async getExecutionList() {
|
|
128
|
+
return await this.recorder?.getExecutionList()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 获取执行任务记录
|
|
133
|
+
* @param executionId
|
|
134
|
+
* @returns
|
|
135
|
+
*/
|
|
136
|
+
async getExecutionRecord(
|
|
137
|
+
executionId: Engine.Key,
|
|
138
|
+
): Promise<Recorder.Info[] | null> {
|
|
139
|
+
const actions = await this.recorder?.getExecutionActions(executionId)
|
|
140
|
+
|
|
141
|
+
if (!actions) {
|
|
142
|
+
return null
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// DONE: 确认 records 的类型
|
|
146
|
+
const records: Promise<Recorder.Info>[] = []
|
|
147
|
+
for (let i = 0; i < actions?.length; i++) {
|
|
148
|
+
const action = actions[i]
|
|
149
|
+
if (this.recorder) {
|
|
150
|
+
records.push(this.recorder?.getActionRecord(action))
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return Promise.all(records)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
destroy() {
|
|
158
|
+
this.recorder?.clear()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
getGlobalData() {
|
|
162
|
+
return this.flowModel?.globalData
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
setGlobalData(data: Record<string, unknown>) {
|
|
166
|
+
if (this.flowModel) {
|
|
167
|
+
this.flowModel.globalData = data
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
updateGlobalData(data: Record<string, unknown>) {
|
|
172
|
+
if (this.flowModel) {
|
|
173
|
+
Object.assign(this.flowModel.globalData, data)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export namespace Engine {
|
|
179
|
+
export type Point = {
|
|
180
|
+
id?: string
|
|
181
|
+
x: number
|
|
182
|
+
y: number
|
|
183
|
+
[key: string]: unknown
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export type TextConfig = {
|
|
187
|
+
value: string
|
|
188
|
+
} & Point
|
|
189
|
+
|
|
190
|
+
export type NodeData = {
|
|
191
|
+
id: string
|
|
192
|
+
type: string
|
|
193
|
+
x?: number
|
|
194
|
+
y?: number
|
|
195
|
+
text?: TextConfig | string
|
|
196
|
+
zIndex?: number
|
|
197
|
+
properties?: Record<string, unknown>
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export type EdgeData = {
|
|
201
|
+
id: string
|
|
202
|
+
/**
|
|
203
|
+
* 边的类型,不传默认为lf.setDefaultEdgeType(type)传入的类型。
|
|
204
|
+
* LogicFlow内部默认为polyline
|
|
205
|
+
*/
|
|
206
|
+
type?: string
|
|
207
|
+
sourceNodeId: string
|
|
208
|
+
sourceAnchorId?: string
|
|
209
|
+
targetNodeId: string
|
|
210
|
+
targetAnchorId?: string
|
|
211
|
+
startPoint?: {
|
|
212
|
+
x: number
|
|
213
|
+
y: number
|
|
214
|
+
}
|
|
215
|
+
endPoint?: {
|
|
216
|
+
x: number
|
|
217
|
+
y: number
|
|
218
|
+
}
|
|
219
|
+
text?:
|
|
220
|
+
| {
|
|
221
|
+
x: number
|
|
222
|
+
y: number
|
|
223
|
+
value: string
|
|
224
|
+
}
|
|
225
|
+
| string
|
|
226
|
+
pointsList?: Point[]
|
|
227
|
+
zIndex?: number
|
|
228
|
+
properties?: Record<string, unknown>
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export type GraphConfigData = {
|
|
232
|
+
nodes: NodeData[]
|
|
233
|
+
edges: EdgeData[]
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export type LoadGraphParam = {
|
|
237
|
+
graphData: GraphConfigData
|
|
238
|
+
startNodeType?: string
|
|
239
|
+
globalData?: Record<string, unknown>
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export type Options = {
|
|
243
|
+
context?: Record<string, unknown>
|
|
244
|
+
debug?: boolean
|
|
245
|
+
}
|
|
246
|
+
export type Key = string | number
|
|
247
|
+
export type NodeConfig = {
|
|
248
|
+
type: string
|
|
249
|
+
model: any // TODO: NodeModel 可能有多个,类型该如何定义呢???
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export type NodeParam = {
|
|
253
|
+
executionId: Key
|
|
254
|
+
nodeId: Key
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export type CommonActionInfo = {
|
|
258
|
+
actionId: Key
|
|
259
|
+
} & NodeParam
|
|
260
|
+
|
|
261
|
+
export type ActionParam = CommonActionInfo
|
|
262
|
+
|
|
263
|
+
export type ResumeParam = {
|
|
264
|
+
data?: Record<string, unknown>
|
|
265
|
+
} & CommonActionInfo
|
|
266
|
+
|
|
267
|
+
export type ExecParam = {
|
|
268
|
+
next: (data: NextActionParam) => void
|
|
269
|
+
} & ActionParam
|
|
270
|
+
|
|
271
|
+
export type ExecResumeParam = {
|
|
272
|
+
next: (data: NextActionParam) => void
|
|
273
|
+
} & ResumeParam
|
|
274
|
+
|
|
275
|
+
export type ActionStatus = 'success' | 'error' | 'interrupted' | '' // ??? Question: '' 状态是什么状态
|
|
276
|
+
|
|
277
|
+
export type NextActionParam = {
|
|
278
|
+
executionId: Key
|
|
279
|
+
nodeId: Key
|
|
280
|
+
actionId: Key
|
|
281
|
+
nodeType: string
|
|
282
|
+
outgoing: BaseNode.OutgoingConfig[]
|
|
283
|
+
properties?: Record<string, unknown>
|
|
284
|
+
detail?: Record<string, unknown>
|
|
285
|
+
status?: ActionStatus
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export type ActionResult = NextActionParam
|
|
289
|
+
|
|
290
|
+
export type NodeExecResult = {
|
|
291
|
+
nodeType: string
|
|
292
|
+
properties?: Record<string, unknown>
|
|
293
|
+
} & CommonActionInfo &
|
|
294
|
+
ActionResult
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export * from './constant'
|
|
298
|
+
export { BaseNode, StartNode, TaskNode, Recorder }
|
|
299
|
+
|
|
300
|
+
export default Engine
|