@mldong/jeeflow 1.0.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/LICENSE +201 -0
- package/README.md +55 -0
- package/dist/engine.d.ts +46 -0
- package/dist/engine.js +400 -0
- package/dist/extensions.d.ts +40 -0
- package/dist/extensions.js +8 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/jdbc/index.d.ts +3 -0
- package/dist/jdbc/index.js +7 -0
- package/dist/jdbc/mysql.d.ts +19 -0
- package/dist/jdbc/mysql.js +41 -0
- package/dist/jdbc/postgres.d.ts +19 -0
- package/dist/jdbc/postgres.js +45 -0
- package/dist/jdbc/shared.d.ts +59 -0
- package/dist/jdbc/shared.js +317 -0
- package/dist/memory.d.ts +29 -0
- package/dist/memory.js +127 -0
- package/dist/model.d.ts +137 -0
- package/dist/model.js +170 -0
- package/dist/registry.d.ts +18 -0
- package/dist/registry.js +17 -0
- package/dist/spi.d.ts +27 -0
- package/dist/spi.js +1 -0
- package/package.json +45 -0
package/dist/engine.js
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { TypeStart, TypeEnd, TypeTask, TypeDecision, TypeFork, TypeJoin, TypeCustom, ProcessInstance, TaskState, } from './model.js';
|
|
2
|
+
import { EventType } from './extensions.js';
|
|
3
|
+
export const KeySubmitType = 'submitType';
|
|
4
|
+
export const KeyBusinessNo = 'BUSINESS_NO';
|
|
5
|
+
export const KeyUserID = 'u_userId';
|
|
6
|
+
export const KeyRealName = 'u_realName';
|
|
7
|
+
export const KeyDeptID = 'u_deptId';
|
|
8
|
+
export const KeyDeptName = 'u_deptName';
|
|
9
|
+
export const KeyPostID = 'u_postId';
|
|
10
|
+
export const KeyPostName = 'u_postName';
|
|
11
|
+
export class EngineImpl {
|
|
12
|
+
repo;
|
|
13
|
+
userProv;
|
|
14
|
+
idGen;
|
|
15
|
+
exprEval;
|
|
16
|
+
ext;
|
|
17
|
+
registry;
|
|
18
|
+
constructor(repo, userProv, idGen, exprEval) {
|
|
19
|
+
this.repo = repo;
|
|
20
|
+
this.userProv = userProv;
|
|
21
|
+
this.idGen = idGen;
|
|
22
|
+
this.exprEval = exprEval;
|
|
23
|
+
}
|
|
24
|
+
setExtensions(ext) { this.ext = ext; }
|
|
25
|
+
setRegistry(reg) { this.registry = reg; }
|
|
26
|
+
async firePre(node, inst) {
|
|
27
|
+
if (!this.ext?.interceptors)
|
|
28
|
+
return true;
|
|
29
|
+
for (const ic of this.ext.interceptors.sort((a, b) => a.order - b.order))
|
|
30
|
+
if (!(await ic.preHandle(node, inst)))
|
|
31
|
+
return false;
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
async firePost(node, inst) {
|
|
35
|
+
if (!this.ext?.interceptors)
|
|
36
|
+
return;
|
|
37
|
+
for (const ic of this.ext.interceptors)
|
|
38
|
+
await ic.postHandle(node, inst);
|
|
39
|
+
}
|
|
40
|
+
async fireEvent(evt) {
|
|
41
|
+
if (!this.ext?.listeners)
|
|
42
|
+
return;
|
|
43
|
+
for (const l of this.ext.listeners)
|
|
44
|
+
await l(evt);
|
|
45
|
+
}
|
|
46
|
+
// ─── Start ─────────────────────────────────────────────────────────────────
|
|
47
|
+
async startProcessInstanceById(defineId, operator, args = {}) {
|
|
48
|
+
const def = await this.repo.findDefineById(defineId);
|
|
49
|
+
if (!def)
|
|
50
|
+
throw new Error(`define not found: ${defineId}`);
|
|
51
|
+
const content = typeof def.content === 'string' ? def.content : new TextDecoder().decode(def.content);
|
|
52
|
+
const flow = JSON.parse(content);
|
|
53
|
+
const vars = { ...args };
|
|
54
|
+
await this.addUserInfo(operator, vars);
|
|
55
|
+
const now = new Date();
|
|
56
|
+
// 聚合根工厂创建实例
|
|
57
|
+
const inst = ProcessInstance.create(this.nextId(), defineId, operator, vars, now);
|
|
58
|
+
await this.repo.saveInstance(inst);
|
|
59
|
+
await this.fireEvent({ type: EventType.ProcessStart, instanceId: inst.id, operator });
|
|
60
|
+
const startNode = findNodeByType(flow, TypeStart);
|
|
61
|
+
if (!startNode)
|
|
62
|
+
throw new Error('no start node');
|
|
63
|
+
for (const node of followEdges(flow, startNode.id)) {
|
|
64
|
+
await this.executeNode(flow, inst, node, operator, vars);
|
|
65
|
+
}
|
|
66
|
+
return (await this.repo.findInstanceById(inst.id));
|
|
67
|
+
}
|
|
68
|
+
// ─── Execute ───────────────────────────────────────────────────────────────
|
|
69
|
+
async executeProcessTask(taskId, operator, args = {}) {
|
|
70
|
+
const { task, inst } = await this.loadAndCheck(taskId, operator);
|
|
71
|
+
const vars = { ...inst.variables, ...task.variables, ...args };
|
|
72
|
+
await this.addUserInfo(operator, vars);
|
|
73
|
+
const now = new Date();
|
|
74
|
+
// 聚合根:完成任务(子实体状态转换 + 实例变量合并)
|
|
75
|
+
inst.completeTask(task, operator, vars, now);
|
|
76
|
+
await this.repo.updateTask(task);
|
|
77
|
+
await this.fireEvent({ type: EventType.TaskComplete, instanceId: inst.id, taskId: task.id, nodeId: task.taskName, operator });
|
|
78
|
+
const def = await this.repo.findDefineById(inst.defineId);
|
|
79
|
+
const flow = JSON.parse(typeof def.content === 'string' ? def.content : new TextDecoder().decode(def.content));
|
|
80
|
+
inst.variables = vars;
|
|
81
|
+
await this.repo.updateInstance(inst);
|
|
82
|
+
const curNode = findNode(flow, task.taskName);
|
|
83
|
+
if (curNode) {
|
|
84
|
+
const ct = curNode.properties?.countersignType;
|
|
85
|
+
if (ct === 'SEQUENTIAL') {
|
|
86
|
+
const doing = await this.repo.findDoingTasks(inst.id);
|
|
87
|
+
if (doing.length === 0) {
|
|
88
|
+
const [actors, lc] = getCsState(vars, curNode.id);
|
|
89
|
+
if (actors && lc + 1 < actors.length) {
|
|
90
|
+
// 聚合根:创建串行会签下一步任务
|
|
91
|
+
const nt = inst.createTask(this.nextId(), curNode.id, curNode.text.value, actors[lc + 1], operator, curNode.properties?.form ?? '', now);
|
|
92
|
+
nt.variables = {
|
|
93
|
+
[`nrOfInstances_${curNode.id}`]: actors.length,
|
|
94
|
+
[`loopCounter_${curNode.id}`]: lc + 1,
|
|
95
|
+
[`operatorList_${curNode.id}`]: actors,
|
|
96
|
+
};
|
|
97
|
+
await this.repo.saveTask(nt);
|
|
98
|
+
return (await this.repo.findInstanceById(inst.id));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
return (await this.repo.findInstanceById(inst.id));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (ct === 'PARALLEL' || ct?.startsWith('RATIO')) {
|
|
106
|
+
const doing = await this.repo.findDoingTasks(inst.id);
|
|
107
|
+
if (doing.length > 0)
|
|
108
|
+
return (await this.repo.findInstanceById(inst.id));
|
|
109
|
+
}
|
|
110
|
+
for (const node of followEdges(flow, curNode.id)) {
|
|
111
|
+
if (node.type === TypeEnd) {
|
|
112
|
+
// 聚合根:流程完成
|
|
113
|
+
inst.finish(new Date());
|
|
114
|
+
inst.variables = vars;
|
|
115
|
+
await this.repo.updateInstance(inst);
|
|
116
|
+
await this.fireEvent({ type: EventType.ProcessFinish, instanceId: inst.id, operator });
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
await this.executeNode(flow, inst, node, operator, vars);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return (await this.repo.findInstanceById(inst.id));
|
|
124
|
+
}
|
|
125
|
+
// ─── Reject ────────────────────────────────────────────────────────────────
|
|
126
|
+
async executeAndJumpToEnd(taskId, operator, args = {}) {
|
|
127
|
+
const { task, inst } = await this.loadAndCheck(taskId, operator);
|
|
128
|
+
const now = new Date();
|
|
129
|
+
// 聚合根:废弃所有进行中任务
|
|
130
|
+
for (const t of inst.abandonAllDoing(now))
|
|
131
|
+
await this.repo.updateTask(t);
|
|
132
|
+
// 子实体:完成任务
|
|
133
|
+
task.finish(operator, task.variables, now);
|
|
134
|
+
await this.repo.updateTask(task);
|
|
135
|
+
// 聚合根:驳回
|
|
136
|
+
inst.reject(now);
|
|
137
|
+
await this.repo.updateInstance(inst);
|
|
138
|
+
await this.fireEvent({ type: EventType.ProcessReject, instanceId: inst.id, taskId, operator });
|
|
139
|
+
return inst;
|
|
140
|
+
}
|
|
141
|
+
// ─── Jump ──────────────────────────────────────────────────────────────────
|
|
142
|
+
async executeAndJumpTask(taskId, operator, args = {}, targetTaskName) {
|
|
143
|
+
const { task, inst } = await this.loadAndCheck(taskId, operator);
|
|
144
|
+
const now = new Date();
|
|
145
|
+
// 聚合根:废弃所有进行中任务
|
|
146
|
+
for (const t of inst.abandonAllDoing(now))
|
|
147
|
+
await this.repo.updateTask(t);
|
|
148
|
+
// 子实体:完成任务
|
|
149
|
+
task.finish(operator, task.variables, now);
|
|
150
|
+
await this.repo.updateTask(task);
|
|
151
|
+
if (targetTaskName) {
|
|
152
|
+
const def = await this.repo.findDefineById(inst.defineId);
|
|
153
|
+
const flow = JSON.parse(typeof def.content === 'string' ? def.content : new TextDecoder().decode(def.content));
|
|
154
|
+
const target = findNode(flow, targetTaskName);
|
|
155
|
+
if (target)
|
|
156
|
+
await this.executeNode(flow, inst, target, operator, inst.variables);
|
|
157
|
+
}
|
|
158
|
+
return inst;
|
|
159
|
+
}
|
|
160
|
+
// ─── Jump To First Task(退回发起人,boot2 ROLLBACK_TO_OPERATOR=6)────────────
|
|
161
|
+
async executeAndJumpToFirstTaskNode(taskId, operator, args = {}) {
|
|
162
|
+
const { task, inst } = await this.loadAndCheck(taskId, operator);
|
|
163
|
+
const now = new Date();
|
|
164
|
+
// 聚合根:废弃所有进行中任务
|
|
165
|
+
for (const t of inst.abandonAllDoing(now))
|
|
166
|
+
await this.repo.updateTask(t);
|
|
167
|
+
// 子实体:完成任务
|
|
168
|
+
task.finish(operator, task.variables, now);
|
|
169
|
+
await this.repo.updateTask(task);
|
|
170
|
+
// 找到第一个任务节点,强制参与者为发起人,重新执行
|
|
171
|
+
const def = await this.repo.findDefineById(inst.defineId);
|
|
172
|
+
const flow = JSON.parse(typeof def.content === 'string' ? def.content : new TextDecoder().decode(def.content));
|
|
173
|
+
const startNode = findNodeByType(flow, TypeStart);
|
|
174
|
+
if (startNode) {
|
|
175
|
+
for (const node of followEdges(flow, startNode.id)) {
|
|
176
|
+
if (node.type === TypeTask || node.type === TypeCustom) {
|
|
177
|
+
node.properties = node.properties ?? {};
|
|
178
|
+
node.properties.assignee = inst.operator;
|
|
179
|
+
await this.executeNode(flow, inst, node, operator, inst.variables);
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return inst;
|
|
185
|
+
}
|
|
186
|
+
// ─── Helpers ───────────────────────────────────────────────────────────────
|
|
187
|
+
async loadAndCheck(taskId, operator) {
|
|
188
|
+
const task = await this.repo.findTaskById(taskId);
|
|
189
|
+
if (!task)
|
|
190
|
+
throw new Error(`task not found: ${taskId}`);
|
|
191
|
+
if (task.taskState !== TaskState.Doing)
|
|
192
|
+
throw new Error(`task not doing`);
|
|
193
|
+
if (!this.isAllowed(task, operator))
|
|
194
|
+
throw new Error(`operator ${operator} not allowed`);
|
|
195
|
+
const inst = await this.repo.findInstanceById(task.processInstanceId);
|
|
196
|
+
if (!inst)
|
|
197
|
+
throw new Error(`instance not found`);
|
|
198
|
+
return { task, inst };
|
|
199
|
+
}
|
|
200
|
+
async executeNode(flow, inst, node, operator, vars) {
|
|
201
|
+
if (!(await this.firePre(node, inst)))
|
|
202
|
+
return;
|
|
203
|
+
try {
|
|
204
|
+
switch (node.type) {
|
|
205
|
+
case TypeTask:
|
|
206
|
+
case TypeCustom:
|
|
207
|
+
return this.createTask(node, inst, operator, vars);
|
|
208
|
+
case TypeDecision:
|
|
209
|
+
return this.evaluateDecision(flow, inst, node, operator, vars);
|
|
210
|
+
case TypeFork:
|
|
211
|
+
for (const n of followEdges(flow, node.id))
|
|
212
|
+
await this.executeNode(flow, inst, n, operator, vars);
|
|
213
|
+
return;
|
|
214
|
+
case TypeJoin: {
|
|
215
|
+
const doing = await this.repo.findDoingTasks(inst.id);
|
|
216
|
+
if (doing.length === 0)
|
|
217
|
+
for (const n of followEdges(flow, node.id))
|
|
218
|
+
await this.executeNode(flow, inst, n, operator, vars);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
case TypeEnd:
|
|
222
|
+
inst.finish(new Date());
|
|
223
|
+
inst.variables = vars;
|
|
224
|
+
await this.repo.updateInstance(inst);
|
|
225
|
+
await this.fireEvent({ type: EventType.ProcessFinish, instanceId: inst.id, operator });
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
await this.firePost(node, inst);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
async evaluateDecision(flow, inst, node, operator, vars) {
|
|
234
|
+
// 自定义决策(Registry 优先)
|
|
235
|
+
if (this.registry) {
|
|
236
|
+
const handlerName = node.properties?.decisionHandler ?? '';
|
|
237
|
+
if (handlerName) {
|
|
238
|
+
const h = this.registry.resolveDecision(handlerName);
|
|
239
|
+
if (h) {
|
|
240
|
+
const branchId = await h.decide(node, inst, vars);
|
|
241
|
+
if (branchId) {
|
|
242
|
+
for (const edge of flow.edges) {
|
|
243
|
+
if (edge.id === branchId) {
|
|
244
|
+
const target = findNode(flow, edge.targetNodeId);
|
|
245
|
+
if (target)
|
|
246
|
+
return this.executeNode(flow, inst, target, operator, vars);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// 自定义决策(Extensions 兼容)
|
|
254
|
+
if (this.ext?.decisionHandler) {
|
|
255
|
+
const handlerName = node.properties?.decisionHandler ?? '';
|
|
256
|
+
const branchId = await this.ext.decisionHandler(handlerName, node, inst, vars);
|
|
257
|
+
if (branchId) {
|
|
258
|
+
for (const edge of flow.edges) {
|
|
259
|
+
if (edge.id === branchId) {
|
|
260
|
+
const target = findNode(flow, edge.targetNodeId);
|
|
261
|
+
if (target)
|
|
262
|
+
return this.executeNode(flow, inst, target, operator, vars);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// 表达式决策
|
|
268
|
+
for (const edge of flow.edges) {
|
|
269
|
+
if (edge.sourceNodeId !== node.id)
|
|
270
|
+
continue;
|
|
271
|
+
const expr = edge.properties?.expr;
|
|
272
|
+
if (!expr) {
|
|
273
|
+
const target = findNode(flow, edge.targetNodeId);
|
|
274
|
+
if (target)
|
|
275
|
+
return this.executeNode(flow, inst, target, operator, vars);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (this.exprEval) {
|
|
279
|
+
const result = await this.exprEval.eval(expr, vars);
|
|
280
|
+
if (isTruthy(result)) {
|
|
281
|
+
const target = findNode(flow, edge.targetNodeId);
|
|
282
|
+
if (target)
|
|
283
|
+
return this.executeNode(flow, inst, target, operator, vars);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
async createTask(node, inst, operator, _vars) {
|
|
290
|
+
const actors = await this.resolveActors(node, inst);
|
|
291
|
+
if (!actors.length)
|
|
292
|
+
return;
|
|
293
|
+
const performType = parseInt(String(node.properties?.performType ?? '0'));
|
|
294
|
+
const ct = node.properties?.countersignType;
|
|
295
|
+
const now = new Date();
|
|
296
|
+
const form = node.properties?.form ?? '';
|
|
297
|
+
if (performType === 1 && ct) {
|
|
298
|
+
switch (ct) {
|
|
299
|
+
case 'PARALLEL':
|
|
300
|
+
for (const actor of actors)
|
|
301
|
+
await this.repo.saveTask(inst.createTask(this.nextId(), node.id, node.text.value, actor, operator, form, now));
|
|
302
|
+
return;
|
|
303
|
+
case 'SEQUENTIAL': {
|
|
304
|
+
const nt = inst.createTask(this.nextId(), node.id, node.text.value, actors[0], operator, form, now);
|
|
305
|
+
nt.variables = {
|
|
306
|
+
[`nrOfInstances_${node.id}`]: actors.length,
|
|
307
|
+
[`loopCounter_${node.id}`]: 0,
|
|
308
|
+
[`operatorList_${node.id}`]: actors,
|
|
309
|
+
};
|
|
310
|
+
await this.repo.saveTask(nt);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
default:
|
|
314
|
+
for (const actor of actors)
|
|
315
|
+
await this.repo.saveTask(inst.createTask(this.nextId(), node.id, node.text.value, actor, operator, form, now));
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
await this.repo.saveTask(inst.createTask(this.nextId(), node.id, node.text.value, actors[0], operator, form, now));
|
|
320
|
+
}
|
|
321
|
+
async resolveActors(node, inst) {
|
|
322
|
+
// 1a. Registry 按名称解析(推荐)
|
|
323
|
+
if (this.registry) {
|
|
324
|
+
const handlerName = node.properties?.assignmentHandler ?? '';
|
|
325
|
+
if (handlerName) {
|
|
326
|
+
const h = this.registry.resolveAssignment(handlerName);
|
|
327
|
+
if (h)
|
|
328
|
+
return await h.assign(node, inst);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// 1b. Extensions 兼容
|
|
332
|
+
if (this.ext?.assignmentHandler) {
|
|
333
|
+
const handlerName = node.properties?.assignmentHandler ?? '';
|
|
334
|
+
const result = await this.ext.assignmentHandler(handlerName, node, inst);
|
|
335
|
+
if (Array.isArray(result) && result.length > 0)
|
|
336
|
+
return result;
|
|
337
|
+
}
|
|
338
|
+
// 2. 固定指派 assignee(boot2 约定:"applicant" → 解析为流程发起人)
|
|
339
|
+
const assignee = node.properties?.assignee;
|
|
340
|
+
if (assignee) {
|
|
341
|
+
const actors = assignee.split(',').map(s => s.trim()).filter(Boolean);
|
|
342
|
+
return actors.map(a => (a === 'applicant' ? inst.operator : a));
|
|
343
|
+
}
|
|
344
|
+
return [];
|
|
345
|
+
}
|
|
346
|
+
isAllowed(task, operator) {
|
|
347
|
+
// 子实体:actorIds 权限判断
|
|
348
|
+
return task.isAllowed(operator);
|
|
349
|
+
}
|
|
350
|
+
async addUserInfo(operator, vars) {
|
|
351
|
+
if (!this.userProv)
|
|
352
|
+
return;
|
|
353
|
+
const u = await this.userProv.getUser(operator);
|
|
354
|
+
if (!u)
|
|
355
|
+
return;
|
|
356
|
+
vars[KeyUserID] = u.userId;
|
|
357
|
+
if (u.realName)
|
|
358
|
+
vars[KeyRealName] = u.realName;
|
|
359
|
+
if (u.deptId)
|
|
360
|
+
vars[KeyDeptID] = u.deptId;
|
|
361
|
+
if (u.deptName)
|
|
362
|
+
vars[KeyDeptName] = u.deptName;
|
|
363
|
+
if (u.postId)
|
|
364
|
+
vars[KeyPostID] = u.postId;
|
|
365
|
+
if (u.postName)
|
|
366
|
+
vars[KeyPostName] = u.postName;
|
|
367
|
+
}
|
|
368
|
+
nextId() {
|
|
369
|
+
if (this.idGen)
|
|
370
|
+
return this.idGen.nextId();
|
|
371
|
+
return Date.now() * 1000 + Math.floor(Math.random() * 1000);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
// ─── Pure Functions ──────────────────────────────────────────────────────────
|
|
375
|
+
function findNode(flow, id) {
|
|
376
|
+
return flow.nodes.find(n => n.id === id);
|
|
377
|
+
}
|
|
378
|
+
function findNodeByType(flow, type) {
|
|
379
|
+
return flow.nodes.find(n => n.type === type);
|
|
380
|
+
}
|
|
381
|
+
function followEdges(flow, sourceId) {
|
|
382
|
+
return flow.edges
|
|
383
|
+
.filter(e => e.sourceNodeId === sourceId)
|
|
384
|
+
.map(e => findNode(flow, e.targetNodeId))
|
|
385
|
+
.filter(Boolean);
|
|
386
|
+
}
|
|
387
|
+
function getCsState(vars, nodeId) {
|
|
388
|
+
const actors = vars[`operatorList_${nodeId}`] ?? null;
|
|
389
|
+
const lc = parseInt(String(vars[`loopCounter_${nodeId}`] ?? '0'));
|
|
390
|
+
return [actors, lc];
|
|
391
|
+
}
|
|
392
|
+
function isTruthy(v) {
|
|
393
|
+
if (typeof v === 'boolean')
|
|
394
|
+
return v;
|
|
395
|
+
if (typeof v === 'string')
|
|
396
|
+
return v !== '' && v !== 'false';
|
|
397
|
+
if (typeof v === 'number')
|
|
398
|
+
return v !== 0;
|
|
399
|
+
return v != null;
|
|
400
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { FlowNode, ProcessInstance } from './model.js';
|
|
2
|
+
/** 流程拦截器——对标 Java FlowInterceptor */
|
|
3
|
+
export interface FlowInterceptor {
|
|
4
|
+
preHandle(node: FlowNode, inst: ProcessInstance): boolean | Promise<boolean>;
|
|
5
|
+
postHandle(node: FlowNode, inst: ProcessInstance): void | Promise<void>;
|
|
6
|
+
order: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* 动态参与者指派——对标 Java AssignmentHandler.assign
|
|
10
|
+
* @param handlerName 节点配置的 assignmentHandler 名(如 "com.xxx.MyHandler")
|
|
11
|
+
* @returns 参与者 ID 列表(空数组表示不处理)
|
|
12
|
+
*/
|
|
13
|
+
export type AssignmentHandler = (handlerName: string, node: FlowNode, inst: ProcessInstance) => string[] | Promise<string[]>;
|
|
14
|
+
/**
|
|
15
|
+
* 自定义决策处理器——对标 Java DecisionHandler
|
|
16
|
+
* @param handlerName 节点配置的 decisionHandler 名
|
|
17
|
+
* @returns 选中的分支边 ID(空字符串表示不处理)
|
|
18
|
+
*/
|
|
19
|
+
export type DecisionHandler = (handlerName: string, node: FlowNode, inst: ProcessInstance, vars: Record<string, any>) => string | Promise<string>;
|
|
20
|
+
export declare enum EventType {
|
|
21
|
+
ProcessStart = 0,
|
|
22
|
+
ProcessFinish = 1,
|
|
23
|
+
ProcessReject = 2,
|
|
24
|
+
TaskCreate = 3,
|
|
25
|
+
TaskComplete = 4
|
|
26
|
+
}
|
|
27
|
+
export interface ProcessEvent {
|
|
28
|
+
type: EventType;
|
|
29
|
+
instanceId: number;
|
|
30
|
+
taskId?: number;
|
|
31
|
+
nodeId?: string;
|
|
32
|
+
operator: string;
|
|
33
|
+
}
|
|
34
|
+
export type ProcessEventListener = (event: ProcessEvent) => void | Promise<void>;
|
|
35
|
+
export interface EngineExtensions {
|
|
36
|
+
interceptors?: FlowInterceptor[];
|
|
37
|
+
assignmentHandler?: AssignmentHandler;
|
|
38
|
+
decisionHandler?: DecisionHandler;
|
|
39
|
+
listeners?: ProcessEventListener[];
|
|
40
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export var EventType;
|
|
2
|
+
(function (EventType) {
|
|
3
|
+
EventType[EventType["ProcessStart"] = 0] = "ProcessStart";
|
|
4
|
+
EventType[EventType["ProcessFinish"] = 1] = "ProcessFinish";
|
|
5
|
+
EventType[EventType["ProcessReject"] = 2] = "ProcessReject";
|
|
6
|
+
EventType[EventType["TaskCreate"] = 3] = "TaskCreate";
|
|
7
|
+
EventType[EventType["TaskComplete"] = 4] = "TaskComplete";
|
|
8
|
+
})(EventType || (EventType = {}));
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { EngineImpl, type Engine } from './engine.js';
|
|
2
|
+
export { MemoryRepository } from './memory.js';
|
|
3
|
+
export { HandlerRegistry, type IAssignmentHandler, type IDecisionHandler } from './registry.js';
|
|
4
|
+
export * from './model.js';
|
|
5
|
+
export type { ProcessRepository, UserProvider, IDGenerator, ExpressionEvaluator } from './spi.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// JDBC 仓储(多数据库)——共享核心 + 数据库适配器
|
|
2
|
+
//
|
|
3
|
+
// - JdbcRepository / TsIDGenerator / SqlAdapter(共享核心,见 shared.ts)
|
|
4
|
+
// - MysqlAdapter(mysql2,`?` 原生)/ PostgresAdapter(pg,`$n`)
|
|
5
|
+
export { JdbcRepository, TsIDGenerator, convertPlaceholder, repeatPh, } from './shared.js';
|
|
6
|
+
export { MysqlAdapter, MysqlConnection } from './mysql.js';
|
|
7
|
+
export { PostgresAdapter, PostgresConnection } from './postgres.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Pool, PoolConnection } from 'mysql2/promise';
|
|
2
|
+
import type { SqlAdapter, SqlConnection } from './shared.js';
|
|
3
|
+
export declare class MysqlConnection implements SqlConnection {
|
|
4
|
+
readonly raw: PoolConnection;
|
|
5
|
+
constructor(raw: PoolConnection);
|
|
6
|
+
execute(sql: string, args: any[]): Promise<void>;
|
|
7
|
+
fetchOne(sql: string, args: any[]): Promise<any | null>;
|
|
8
|
+
fetchAll(sql: string, args: any[]): Promise<any[]>;
|
|
9
|
+
begin(): Promise<void>;
|
|
10
|
+
commit(): Promise<void>;
|
|
11
|
+
rollback(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export declare class MysqlAdapter implements SqlAdapter {
|
|
14
|
+
private readonly pool;
|
|
15
|
+
placeholder: "?";
|
|
16
|
+
constructor(pool: Pool);
|
|
17
|
+
acquire(): Promise<MysqlConnection>;
|
|
18
|
+
release(conn: SqlConnection): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// MySQL 适配器(mysql2)——连接池 + `?` 占位符(与核心一致,无需转换)。
|
|
2
|
+
export class MysqlConnection {
|
|
3
|
+
raw;
|
|
4
|
+
constructor(raw) {
|
|
5
|
+
this.raw = raw;
|
|
6
|
+
}
|
|
7
|
+
async execute(sql, args) {
|
|
8
|
+
await this.raw.execute(sql, args);
|
|
9
|
+
}
|
|
10
|
+
async fetchOne(sql, args) {
|
|
11
|
+
const [rows] = await this.raw.execute(sql, args);
|
|
12
|
+
return rows[0] ?? null;
|
|
13
|
+
}
|
|
14
|
+
async fetchAll(sql, args) {
|
|
15
|
+
const [rows] = await this.raw.execute(sql, args);
|
|
16
|
+
return rows;
|
|
17
|
+
}
|
|
18
|
+
async begin() {
|
|
19
|
+
await this.raw.beginTransaction();
|
|
20
|
+
}
|
|
21
|
+
async commit() {
|
|
22
|
+
await this.raw.commit();
|
|
23
|
+
}
|
|
24
|
+
async rollback() {
|
|
25
|
+
await this.raw.rollback();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export class MysqlAdapter {
|
|
29
|
+
pool;
|
|
30
|
+
placeholder = '?';
|
|
31
|
+
constructor(pool) {
|
|
32
|
+
this.pool = pool;
|
|
33
|
+
}
|
|
34
|
+
async acquire() {
|
|
35
|
+
return new MysqlConnection(await this.pool.getConnection());
|
|
36
|
+
}
|
|
37
|
+
async release(conn) {
|
|
38
|
+
;
|
|
39
|
+
conn.raw.release();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Pool, PoolClient } from 'pg';
|
|
2
|
+
import type { SqlAdapter, SqlConnection } from './shared.js';
|
|
3
|
+
export declare class PostgresConnection implements SqlConnection {
|
|
4
|
+
readonly raw: PoolClient;
|
|
5
|
+
constructor(raw: PoolClient);
|
|
6
|
+
execute(sql: string, args: any[]): Promise<void>;
|
|
7
|
+
fetchOne(sql: string, args: any[]): Promise<any | null>;
|
|
8
|
+
fetchAll(sql: string, args: any[]): Promise<any[]>;
|
|
9
|
+
begin(): Promise<void>;
|
|
10
|
+
commit(): Promise<void>;
|
|
11
|
+
rollback(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export declare class PostgresAdapter implements SqlAdapter {
|
|
14
|
+
private readonly pool;
|
|
15
|
+
placeholder: "$n";
|
|
16
|
+
constructor(pool: Pool);
|
|
17
|
+
acquire(): Promise<PostgresConnection>;
|
|
18
|
+
release(conn: SqlConnection): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// PostgreSQL 适配器(pg)——连接池 + `$n` 占位符。
|
|
2
|
+
//
|
|
3
|
+
// > 已在开发服务器实测(PostgreSQL 16,Docker mldong-pg)。
|
|
4
|
+
// > pg 无 beginTransaction API,直接执行 BEGIN/COMMIT/ROLLBACK;
|
|
5
|
+
// > 其余接口与 mysql.ts 对齐——核心 SQL 由 shared.convertPlaceholder 统一转换。
|
|
6
|
+
export class PostgresConnection {
|
|
7
|
+
raw;
|
|
8
|
+
constructor(raw) {
|
|
9
|
+
this.raw = raw;
|
|
10
|
+
}
|
|
11
|
+
async execute(sql, args) {
|
|
12
|
+
await this.raw.query(sql, args);
|
|
13
|
+
}
|
|
14
|
+
async fetchOne(sql, args) {
|
|
15
|
+
const r = await this.raw.query(sql, args);
|
|
16
|
+
return r.rows[0] ?? null;
|
|
17
|
+
}
|
|
18
|
+
async fetchAll(sql, args) {
|
|
19
|
+
const r = await this.raw.query(sql, args);
|
|
20
|
+
return r.rows;
|
|
21
|
+
}
|
|
22
|
+
async begin() {
|
|
23
|
+
await this.raw.query('BEGIN');
|
|
24
|
+
}
|
|
25
|
+
async commit() {
|
|
26
|
+
await this.raw.query('COMMIT');
|
|
27
|
+
}
|
|
28
|
+
async rollback() {
|
|
29
|
+
await this.raw.query('ROLLBACK');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export class PostgresAdapter {
|
|
33
|
+
pool;
|
|
34
|
+
placeholder = '$n';
|
|
35
|
+
constructor(pool) {
|
|
36
|
+
this.pool = pool;
|
|
37
|
+
}
|
|
38
|
+
async acquire() {
|
|
39
|
+
return new PostgresConnection(await this.pool.connect());
|
|
40
|
+
}
|
|
41
|
+
async release(conn) {
|
|
42
|
+
;
|
|
43
|
+
conn.raw.release();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ProcessInstance, ProcessTask, type ProcessDefine } from '../model.js';
|
|
2
|
+
import type { IDGenerator, ProcessRepository } from '../spi.js';
|
|
3
|
+
/** 默认 ID 生成器:时间戳毫秒 + 同毫秒递增序号(对齐 Java nextId 默认实现) */
|
|
4
|
+
export declare class TsIDGenerator implements IDGenerator {
|
|
5
|
+
private last;
|
|
6
|
+
private seq;
|
|
7
|
+
nextId(): number;
|
|
8
|
+
}
|
|
9
|
+
/** 适配器返回的连接包装——最小接口 */
|
|
10
|
+
export interface SqlConnection {
|
|
11
|
+
execute(sql: string, args: any[]): Promise<void>;
|
|
12
|
+
fetchOne(sql: string, args: any[]): Promise<any | null>;
|
|
13
|
+
fetchAll(sql: string, args: any[]): Promise<any[]>;
|
|
14
|
+
begin(): Promise<void>;
|
|
15
|
+
commit(): Promise<void>;
|
|
16
|
+
rollback(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
/** 数据库适配器——连接生命周期 + 占位符风格 */
|
|
19
|
+
export interface SqlAdapter {
|
|
20
|
+
placeholder: '?' | '$n';
|
|
21
|
+
acquire(): Promise<SqlConnection>;
|
|
22
|
+
release(conn: SqlConnection): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
/** 把核心 SQL 的统一 `?` 占位符转换为适配器风格 */
|
|
25
|
+
export declare function convertPlaceholder(sql: string, style: string): string;
|
|
26
|
+
/** 生成 n 个 `?` 占位符(用于 IN 列表) */
|
|
27
|
+
export declare function repeatPh(n: number): string;
|
|
28
|
+
export declare class JdbcRepository implements ProcessRepository {
|
|
29
|
+
private readonly adapter;
|
|
30
|
+
private readonly idGen;
|
|
31
|
+
constructor(adapter: SqlAdapter, idGen?: IDGenerator);
|
|
32
|
+
private sql;
|
|
33
|
+
withTx<T>(fn: () => Promise<T>): Promise<T>;
|
|
34
|
+
/** 返回当前连接:有事务绑定用事务连接,否则从适配器获取 */
|
|
35
|
+
private c;
|
|
36
|
+
/** 归还非事务连接(事务连接由 withTx 统一释放) */
|
|
37
|
+
private done;
|
|
38
|
+
findDefineById(id: number): Promise<ProcessDefine | null>;
|
|
39
|
+
private static INSTANCE_COLS;
|
|
40
|
+
findInstanceById(id: number): Promise<ProcessInstance | null>;
|
|
41
|
+
saveInstance(inst: ProcessInstance): Promise<void>;
|
|
42
|
+
updateInstance(inst: ProcessInstance): Promise<void>;
|
|
43
|
+
private static TASK_COLS;
|
|
44
|
+
findTaskById(taskId: number): Promise<ProcessTask | null>;
|
|
45
|
+
saveTask(task: ProcessTask): Promise<void>;
|
|
46
|
+
updateTask(task: ProcessTask): Promise<void>;
|
|
47
|
+
private findTasksByState;
|
|
48
|
+
findDoingTasks(instanceId: number, taskNames?: string[]): Promise<ProcessTask[]>;
|
|
49
|
+
findDoneTasks(instanceId: number, taskNames?: string[]): Promise<ProcessTask[]>;
|
|
50
|
+
findHistoryTasks(instanceId: number): Promise<ProcessTask[]>;
|
|
51
|
+
private mapTask;
|
|
52
|
+
private replaceTaskActors;
|
|
53
|
+
private insertTaskActors;
|
|
54
|
+
findTaskActors(taskId: number): Promise<string[]>;
|
|
55
|
+
addTaskActor(taskId: number, actors: string[]): Promise<void>;
|
|
56
|
+
removeTaskActor(taskId: number, actors: string[]): Promise<void>;
|
|
57
|
+
createCcInstance(instanceId: number, creator: string, ...actorIds: string[]): Promise<void>;
|
|
58
|
+
updateCcStatus(instanceId: number, actorId: string): Promise<void>;
|
|
59
|
+
}
|