@mldong/jeeflow 1.0.0 → 1.2.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 +58 -55
- package/dist/engine.d.ts +3 -0
- package/dist/engine.js +66 -7
- package/dist/facade.d.ts +38 -0
- package/dist/facade.js +582 -0
- package/dist/jdbc/ext.d.ts +29 -0
- package/dist/jdbc/ext.js +258 -0
- package/dist/jdbc/shared.d.ts +7 -0
- package/dist/jdbc/shared.js +80 -3
- package/dist/memory-ext.d.ts +21 -0
- package/dist/memory-ext.js +83 -0
- package/dist/memory.d.ts +5 -0
- package/dist/memory.js +36 -0
- package/dist/model.d.ts +33 -0
- package/dist/spi.d.ts +21 -1
- package/package.json +45 -45
package/README.md
CHANGED
|
@@ -1,55 +1,58 @@
|
|
|
1
|
-
# jeeflow · Node.js / TypeScript
|
|
2
|
-
|
|
3
|
-
[jeeflow](https://jeeflow-doc.mldong.com) 引擎规范的 **TypeScript 实现**。仅依赖 `node:*` 标准库,零外部依赖(引擎核心)。
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
|
43
|
-
|
|
44
|
-
| `src/
|
|
45
|
-
| `src/
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
1
|
+
# jeeflow · Node.js / TypeScript
|
|
2
|
+
|
|
3
|
+
[jeeflow](https://jeeflow-doc.mldong.com) 引擎规范的 **TypeScript 实现**。仅依赖 `node:*` 标准库,零外部依赖(引擎核心)。
|
|
4
|
+
|
|
5
|
+
> **v1.1.0**:新增管理扩展(流程设计/历史/委托 + `ProcessExtRepository`)与统一门面
|
|
6
|
+
> `JeeflowFacade.flow(action, args)`;assignee 变量解析与 `flow.auto`/`flow.admin` 系统代执行对齐 boot2/boot3。
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @mldong/jeeflow # 等发布到 npm 后
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 快速开始
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { EngineImpl, MemoryRepository } from '@mldong/jeeflow'
|
|
16
|
+
|
|
17
|
+
const repo = new MemoryRepository()
|
|
18
|
+
const engine = new EngineImpl(repo)
|
|
19
|
+
|
|
20
|
+
const def = {
|
|
21
|
+
id: 1, name: 'leave', displayName: '请假审批',
|
|
22
|
+
content: JSON.stringify({ nodes: [...], edges: [...] })
|
|
23
|
+
}
|
|
24
|
+
repo.addDefine(def)
|
|
25
|
+
|
|
26
|
+
const inst = await engine.startProcessInstanceById(def.id, '张三')
|
|
27
|
+
const tasks = await repo.findDoingTasks(inst.id)
|
|
28
|
+
await engine.executeProcessTask(tasks[0].id, 'leader')
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 运行演示
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
git clone https://github.com/mldong/jeeflow-node
|
|
35
|
+
cd jeeflow-node
|
|
36
|
+
npm install
|
|
37
|
+
npm run demo # http://localhost:8082
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 目录
|
|
41
|
+
|
|
42
|
+
| 路径 | 说明 |
|
|
43
|
+
|------|------|
|
|
44
|
+
| `src/engine.ts` | 引擎核心 |
|
|
45
|
+
| `src/model.ts` | 域类型 |
|
|
46
|
+
| `src/spi.ts` | SPI 接口 |
|
|
47
|
+
| `src/memory.ts` | 内存仓储 |
|
|
48
|
+
| `src/jdbc/` | JDBC 多库实现(shared 核心 + mysql/postgres 适配器) |
|
|
49
|
+
| `demo/main.ts` | Express 演示 |
|
|
50
|
+
| `__tests__/` | 9 项合规测试 |
|
|
51
|
+
|
|
52
|
+
## 节点支持
|
|
53
|
+
|
|
54
|
+
对齐 [SPEC.md](https://jeeflow-doc.mldong.com) v1.0——与 Java/Go 版一致。
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
Apache-2.0
|
package/dist/engine.d.ts
CHANGED
|
@@ -10,6 +10,9 @@ export declare const KeyDeptID = "u_deptId";
|
|
|
10
10
|
export declare const KeyDeptName = "u_deptName";
|
|
11
11
|
export declare const KeyPostID = "u_postId";
|
|
12
12
|
export declare const KeyPostName = "u_postName";
|
|
13
|
+
export declare const KeyNextNodeOperator = "tf_nextNodeOperator";
|
|
14
|
+
export declare const KeyAutoExecute = "flow.auto";
|
|
15
|
+
export declare const KeyAdminID = "flow.admin";
|
|
13
16
|
export interface Engine {
|
|
14
17
|
startProcessInstanceById(defineId: number, operator: string, args?: Record<string, any>): Promise<ProcessInstance>;
|
|
15
18
|
executeProcessTask(taskId: number, operator: string, args?: Record<string, any>): Promise<ProcessInstance>;
|
package/dist/engine.js
CHANGED
|
@@ -8,6 +8,11 @@ export const KeyDeptID = 'u_deptId';
|
|
|
8
8
|
export const KeyDeptName = 'u_deptName';
|
|
9
9
|
export const KeyPostID = 'u_postId';
|
|
10
10
|
export const KeyPostName = 'u_postName';
|
|
11
|
+
// v1.0.1:下一节点处理人(对齐 boot3 tf_nextNodeOperator)
|
|
12
|
+
export const KeyNextNodeOperator = 'tf_nextNodeOperator';
|
|
13
|
+
// v1.0.1:系统代执行 / 超级管理员(对齐 boot3 FlowConst)
|
|
14
|
+
export const KeyAutoExecute = 'flow.auto';
|
|
15
|
+
export const KeyAdminID = 'flow.admin';
|
|
11
16
|
export class EngineImpl {
|
|
12
17
|
repo;
|
|
13
18
|
userProv;
|
|
@@ -74,6 +79,9 @@ export class EngineImpl {
|
|
|
74
79
|
// 聚合根:完成任务(子实体状态转换 + 实例变量合并)
|
|
75
80
|
inst.completeTask(task, operator, vars, now);
|
|
76
81
|
await this.repo.updateTask(task);
|
|
82
|
+
// v1.0.1:updateInstance 级联持久化依赖聚合内任务副本为最新状态,
|
|
83
|
+
// completeTask 改的是外部任务对象,需同步回聚合根
|
|
84
|
+
syncTaskToAggregate(inst, task);
|
|
77
85
|
await this.fireEvent({ type: EventType.TaskComplete, instanceId: inst.id, taskId: task.id, nodeId: task.taskName, operator });
|
|
78
86
|
const def = await this.repo.findDefineById(inst.defineId);
|
|
79
87
|
const flow = JSON.parse(typeof def.content === 'string' ? def.content : new TextDecoder().decode(def.content));
|
|
@@ -132,6 +140,8 @@ export class EngineImpl {
|
|
|
132
140
|
// 子实体:完成任务
|
|
133
141
|
task.finish(operator, task.variables, now);
|
|
134
142
|
await this.repo.updateTask(task);
|
|
143
|
+
// v1.0.1:同步回聚合根,避免 updateInstance 级联把任务写回旧状态
|
|
144
|
+
syncTaskToAggregate(inst, task);
|
|
135
145
|
// 聚合根:驳回
|
|
136
146
|
inst.reject(now);
|
|
137
147
|
await this.repo.updateInstance(inst);
|
|
@@ -286,8 +296,8 @@ export class EngineImpl {
|
|
|
286
296
|
}
|
|
287
297
|
}
|
|
288
298
|
}
|
|
289
|
-
async createTask(node, inst, operator,
|
|
290
|
-
const actors = await this.resolveActors(node, inst);
|
|
299
|
+
async createTask(node, inst, operator, vars) {
|
|
300
|
+
const actors = await this.resolveActors(node, inst, vars);
|
|
291
301
|
if (!actors.length)
|
|
292
302
|
return;
|
|
293
303
|
const performType = parseInt(String(node.properties?.performType ?? '0'));
|
|
@@ -316,9 +326,13 @@ export class EngineImpl {
|
|
|
316
326
|
return;
|
|
317
327
|
}
|
|
318
328
|
}
|
|
319
|
-
|
|
329
|
+
// 普通任务:一个任务承载全部参与者(对齐 boot3 createTask + addTaskActor,多参与者任一可办)
|
|
330
|
+
const nt = inst.createTask(this.nextId(), node.id, node.text.value, actors[0], operator, form, now);
|
|
331
|
+
if (actors.length > 1)
|
|
332
|
+
nt.actorIds = actors;
|
|
333
|
+
await this.repo.saveTask(nt);
|
|
320
334
|
}
|
|
321
|
-
async resolveActors(node, inst) {
|
|
335
|
+
async resolveActors(node, inst, vars) {
|
|
322
336
|
// 1a. Registry 按名称解析(推荐)
|
|
323
337
|
if (this.registry) {
|
|
324
338
|
const handlerName = node.properties?.assignmentHandler ?? '';
|
|
@@ -335,21 +349,56 @@ export class EngineImpl {
|
|
|
335
349
|
if (Array.isArray(result) && result.length > 0)
|
|
336
350
|
return result;
|
|
337
351
|
}
|
|
338
|
-
// 2.
|
|
352
|
+
// 2. 动态指定下一节点处理人优先(v1.0.1:对齐 boot3 tf_nextNodeOperator)
|
|
353
|
+
const nextOp = vars[KeyNextNodeOperator];
|
|
354
|
+
if (nextOp != null) {
|
|
355
|
+
if (typeof nextOp === 'string')
|
|
356
|
+
return nextOp.split(',').map(s => s.trim()).filter(Boolean);
|
|
357
|
+
if (Array.isArray(nextOp))
|
|
358
|
+
return nextOp.map(String);
|
|
359
|
+
return [String(nextOp)];
|
|
360
|
+
}
|
|
361
|
+
// 3. 固定指派 assignee——token 即变量 key,能替换就换,换不了就是字面量(v1.0.1 对齐 boot3 args.get(token, token))
|
|
339
362
|
const assignee = node.properties?.assignee;
|
|
340
363
|
if (assignee) {
|
|
341
|
-
const actors =
|
|
342
|
-
|
|
364
|
+
const actors = [];
|
|
365
|
+
for (const raw of assignee.split(',')) {
|
|
366
|
+
let token = raw.trim();
|
|
367
|
+
if (!token)
|
|
368
|
+
continue;
|
|
369
|
+
// mldong 契约特殊值:applicant → 流程发起人
|
|
370
|
+
if (token.includes('applicant'))
|
|
371
|
+
token = token.replace('applicant', inst.operator);
|
|
372
|
+
if (token in vars) {
|
|
373
|
+
const val = vars[token];
|
|
374
|
+
if (Array.isArray(val))
|
|
375
|
+
actors.push(...val.map(String));
|
|
376
|
+
else
|
|
377
|
+
actors.push(String(val));
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
actors.push(token);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return actors;
|
|
343
384
|
}
|
|
344
385
|
return [];
|
|
345
386
|
}
|
|
346
387
|
isAllowed(task, operator) {
|
|
388
|
+
// v1.0.1:系统代执行(flow.auto)/超级管理员(flow.admin)放行(对齐 boot3 isAllowed)
|
|
389
|
+
if (operator && (operator.toLowerCase() === KeyAutoExecute || operator.toLowerCase() === KeyAdminID)) {
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
347
392
|
// 子实体:actorIds 权限判断
|
|
348
393
|
return task.isAllowed(operator);
|
|
349
394
|
}
|
|
350
395
|
async addUserInfo(operator, vars) {
|
|
351
396
|
if (!this.userProv)
|
|
352
397
|
return;
|
|
398
|
+
// v1.0.1:系统代执行(flow.auto)/超级管理员(flow.admin)非真实用户,跳过注入(对齐 boot3)
|
|
399
|
+
if (operator && (operator.toLowerCase() === KeyAutoExecute || operator.toLowerCase() === KeyAdminID)) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
353
402
|
const u = await this.userProv.getUser(operator);
|
|
354
403
|
if (!u)
|
|
355
404
|
return;
|
|
@@ -389,6 +438,16 @@ function getCsState(vars, nodeId) {
|
|
|
389
438
|
const lc = parseInt(String(vars[`loopCounter_${nodeId}`] ?? '0'));
|
|
390
439
|
return [actors, lc];
|
|
391
440
|
}
|
|
441
|
+
// syncTaskToAggregate 把外部任务对象的最新状态同步回聚合根任务副本
|
|
442
|
+
// (v1.0.1:updateInstance 级联持久化依赖聚合内任务副本为最新状态)
|
|
443
|
+
function syncTaskToAggregate(inst, task) {
|
|
444
|
+
for (let i = 0; i < inst.tasks.length; i++) {
|
|
445
|
+
if (inst.tasks[i].id === task.id) {
|
|
446
|
+
inst.tasks[i] = task;
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
392
451
|
function isTruthy(v) {
|
|
393
452
|
if (typeof v === 'boolean')
|
|
394
453
|
return v;
|
package/dist/facade.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ProcessExtRepository, ProcessRepository } from './spi.js';
|
|
2
|
+
import type { EngineImpl } from './engine.js';
|
|
3
|
+
export type UserSearch = (query: Record<string, any>) => Promise<[Record<string, any>[], number]> | [Record<string, any>[], number];
|
|
4
|
+
export declare class JeeflowFacade {
|
|
5
|
+
private readonly engine;
|
|
6
|
+
private readonly repo;
|
|
7
|
+
private readonly extRepo?;
|
|
8
|
+
private userSearch?;
|
|
9
|
+
constructor(engine: EngineImpl, repo: ProcessRepository, extRepo?: ProcessExtRepository | undefined);
|
|
10
|
+
setUserSearch(fn: UserSearch): this;
|
|
11
|
+
flow(action: string, args?: Record<string, any>): Promise<Record<string, any>>;
|
|
12
|
+
private dispatch;
|
|
13
|
+
private startAndExecute;
|
|
14
|
+
private deploy;
|
|
15
|
+
private saveDeployedDefine;
|
|
16
|
+
private redeploy;
|
|
17
|
+
private withdraw;
|
|
18
|
+
private execute;
|
|
19
|
+
private designPage;
|
|
20
|
+
private designDetail;
|
|
21
|
+
private designSave;
|
|
22
|
+
private surrogatePage;
|
|
23
|
+
private surrogateSave;
|
|
24
|
+
private getLastByName;
|
|
25
|
+
private highLight;
|
|
26
|
+
private collectPath;
|
|
27
|
+
private approvalRecord;
|
|
28
|
+
private getAssigneeTextData;
|
|
29
|
+
private createCCInstance;
|
|
30
|
+
private updateCCStatus;
|
|
31
|
+
private taskDetail;
|
|
32
|
+
private jumpAbleTaskNameList;
|
|
33
|
+
private candidatePage;
|
|
34
|
+
private nextTaskCandidates;
|
|
35
|
+
private taskAddActor;
|
|
36
|
+
private taskLatest;
|
|
37
|
+
private ext;
|
|
38
|
+
}
|