@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 CHANGED
@@ -1,55 +1,58 @@
1
- # jeeflow · Node.js / TypeScript
2
-
3
- [jeeflow](https://jeeflow-doc.mldong.com) 引擎规范的 **TypeScript 实现**。仅依赖 `node:*` 标准库,零外部依赖(引擎核心)。
4
-
5
- ```bash
6
- npm install jeeflow # 等发布到 npm
7
- ```
8
-
9
- ## 快速开始
10
-
11
- ```ts
12
- import { EngineImpl, MemoryRepository } from 'jeeflow'
13
-
14
- const repo = new MemoryRepository()
15
- const engine = new EngineImpl(repo)
16
-
17
- const def = {
18
- id: 1, name: 'leave', displayName: '请假审批',
19
- content: JSON.stringify({ nodes: [...], edges: [...] })
20
- }
21
- repo.addDefine(def)
22
-
23
- const inst = await engine.startProcessInstanceById(def.id, '张三')
24
- const tasks = await repo.findDoingTasks(inst.id)
25
- await engine.executeProcessTask(tasks[0].id, 'leader')
26
- ```
27
-
28
- ## 运行演示
29
-
30
- ```bash
31
- git clone https://github.com/mldong/jeeflow-node
32
- cd jeeflow-node
33
- npm install
34
- npm run demo # http://localhost:8082
35
- ```
36
-
37
- ## 目录
38
-
39
- | 路径 | 说明 |
40
- |------|------|
41
- | `src/engine.ts` | 引擎核心 |
42
- | `src/model.ts` | 域类型 |
43
- | `src/spi.ts` | SPI 接口 |
44
- | `src/memory.ts` | 内存仓储 |
45
- | `src/jdbc/` | JDBC 多库实现(shared 核心 + mysql/postgres 适配器) |
46
- | `demo/main.ts` | Express 演示 |
47
- | `__tests__/` | 9 项合规测试 |
48
-
49
- ## 节点支持
50
-
51
- 对齐 [SPEC.md](https://jeeflow-doc.mldong.com) v1.0——与 Java/Go 版一致。
52
-
53
- ## License
54
-
55
- Apache-2.0
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, _vars) {
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
- await this.repo.saveTask(inst.createTask(this.nextId(), node.id, node.text.value, actors[0], operator, form, now));
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. 固定指派 assignee(boot2 约定:"applicant" → 解析为流程发起人)
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 = assignee.split(',').map(s => s.trim()).filter(Boolean);
342
- return actors.map(a => (a === 'applicant' ? inst.operator : a));
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;
@@ -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
+ }