@rytass/bpm-core-shared 0.4.0 → 0.5.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/eslint.config.mjs +19 -0
  3. package/jest.config.cts +10 -0
  4. package/package.json +2 -3
  5. package/project.json +49 -0
  6. package/src/lib/condition.ts +55 -0
  7. package/src/lib/form.ts +77 -0
  8. package/src/lib/identity.ts +25 -0
  9. package/src/lib/organization.ts +52 -0
  10. package/src/lib/shared.spec.ts +13 -0
  11. package/src/lib/status.ts +29 -0
  12. package/src/lib/workflow-command.spec.ts +278 -0
  13. package/src/lib/workflow-command.ts +848 -0
  14. package/src/lib/workflow-graph.ts +974 -0
  15. package/src/lib/workflow-toolset.spec.ts +230 -0
  16. package/src/lib/workflow-toolset.ts +1083 -0
  17. package/src/lib/workflow.ts +216 -0
  18. package/tsconfig.json +23 -0
  19. package/tsconfig.lib.json +16 -0
  20. package/tsconfig.spec.json +16 -0
  21. package/src/index.js +0 -13
  22. package/src/index.js.map +0 -1
  23. package/src/lib/condition.d.ts +0 -43
  24. package/src/lib/condition.js +0 -3
  25. package/src/lib/condition.js.map +0 -1
  26. package/src/lib/form.d.ts +0 -48
  27. package/src/lib/form.js +0 -3
  28. package/src/lib/form.js.map +0 -1
  29. package/src/lib/identity.d.ts +0 -24
  30. package/src/lib/identity.js +0 -3
  31. package/src/lib/identity.js.map +0 -1
  32. package/src/lib/organization.d.ts +0 -47
  33. package/src/lib/organization.js +0 -3
  34. package/src/lib/organization.js.map +0 -1
  35. package/src/lib/status.d.ts +0 -7
  36. package/src/lib/status.js +0 -3
  37. package/src/lib/status.js.map +0 -1
  38. package/src/lib/workflow-command.d.ts +0 -156
  39. package/src/lib/workflow-command.js +0 -292
  40. package/src/lib/workflow-command.js.map +0 -1
  41. package/src/lib/workflow-graph.d.ts +0 -96
  42. package/src/lib/workflow-graph.js +0 -605
  43. package/src/lib/workflow-graph.js.map +0 -1
  44. package/src/lib/workflow-toolset.d.ts +0 -105
  45. package/src/lib/workflow-toolset.js +0 -702
  46. package/src/lib/workflow-toolset.js.map +0 -1
  47. package/src/lib/workflow.d.ts +0 -160
  48. package/src/lib/workflow.js +0 -3
  49. package/src/lib/workflow.js.map +0 -1
  50. /package/src/{index.d.ts → index.ts} +0 -0
@@ -0,0 +1,230 @@
1
+ import { readFallbackWorkflowDefinition } from './workflow-graph';
2
+ import { WorkflowDesignerState } from './workflow-command';
3
+ import {
4
+ WORKFLOW_TOOLSET,
5
+ WorkflowDirectory,
6
+ WorkflowSnapshot,
7
+ executeWorkflowTool,
8
+ readWorkflowSnapshot,
9
+ } from './workflow-toolset';
10
+
11
+ function initialState(): WorkflowDesignerState {
12
+ return {
13
+ definition: readFallbackWorkflowDefinition(),
14
+ editingEdgeId: null,
15
+ formDefinitionVersionId: null,
16
+ formSchema: null,
17
+ initiatorPolicyCel: null,
18
+ selectedEdgeIds: [],
19
+ selectedNodeId: 'start',
20
+ };
21
+ }
22
+
23
+ const deterministicIds = {
24
+ createEdgeId: ((): ((source: string, target: string) => string) => {
25
+ let counter = 0;
26
+
27
+ return (source: string, target: string): string =>
28
+ `edge_${source}_${target}_${(counter += 1)}`;
29
+ })(),
30
+ };
31
+
32
+ describe('WORKFLOW_TOOLSET', () => {
33
+ it('every tool has a unique name and an object input schema', () => {
34
+ const names = WORKFLOW_TOOLSET.map((tool) => tool.name);
35
+
36
+ expect(new Set(names).size).toBe(names.length);
37
+
38
+ WORKFLOW_TOOLSET.forEach((tool) => {
39
+ expect(tool.inputSchema['type']).toBe('object');
40
+ expect(['mutation', 'macro', 'query']).toContain(tool.kind);
41
+ });
42
+ });
43
+ });
44
+
45
+ describe('executeWorkflowTool', () => {
46
+ it('returns an error for an unknown tool', async () => {
47
+ const result = await executeWorkflowTool(
48
+ initialState(),
49
+ 'no_such_tool',
50
+ {},
51
+ );
52
+
53
+ expect(result.ok).toBe(false);
54
+
55
+ if (!result.ok) {
56
+ expect(result.error).toContain('未知的工具');
57
+ }
58
+ });
59
+
60
+ it('runs a mutation tool and returns a fresh snapshot', async () => {
61
+ const result = await executeWorkflowTool(
62
+ initialState(),
63
+ 'add_node',
64
+ { nodeType: 'userTask' },
65
+ deterministicIds,
66
+ );
67
+
68
+ expect(result.ok).toBe(true);
69
+
70
+ if (result.ok && result.kind === 'mutation') {
71
+ expect(
72
+ result.snapshot.nodes.some((node) => node.type === 'userTask'),
73
+ ).toBe(true);
74
+ }
75
+ });
76
+
77
+ it('runs a macro tool (insert_approval_step)', async () => {
78
+ const result = await executeWorkflowTool(
79
+ initialState(),
80
+ 'insert_approval_step',
81
+ {
82
+ approverResolver: { memberIds: ['m-1'], type: 'DIRECT' },
83
+ label: '主管簽核',
84
+ },
85
+ deterministicIds,
86
+ );
87
+
88
+ expect(result.ok).toBe(true);
89
+
90
+ if (result.ok && result.kind === 'macro') {
91
+ const userTask = result.snapshot.nodes.find(
92
+ (node) => node.type === 'userTask',
93
+ );
94
+
95
+ expect(userTask?.label).toBe('主管簽核');
96
+ }
97
+ });
98
+
99
+ it('coerces a malformed approver resolver to the manager default instead of failing', async () => {
100
+ const added = await executeWorkflowTool(
101
+ initialState(),
102
+ 'add_node',
103
+ { nodeType: 'userTask' },
104
+ deterministicIds,
105
+ );
106
+
107
+ expect(added.ok).toBe(true);
108
+
109
+ if (!(added.ok && added.kind === 'mutation')) {
110
+ return;
111
+ }
112
+
113
+ const nodeId =
114
+ added.snapshot.nodes.find((node) => node.type === 'userTask')?.id ?? '';
115
+ const result = await executeWorkflowTool(
116
+ added.result.state,
117
+ 'set_user_task_approver',
118
+ { approverResolver: { type: 'DIRECT' }, nodeId },
119
+ deterministicIds,
120
+ );
121
+
122
+ expect(result.ok).toBe(true);
123
+
124
+ if (result.ok && result.kind === 'mutation') {
125
+ const userTask = result.snapshot.nodes.find((node) => node.id === nodeId);
126
+
127
+ expect(userTask?.summary).toContain('簽核人=ORG_MANAGER');
128
+ }
129
+ });
130
+
131
+ it('draws a draft approval step with no approver supplied (manager default)', async () => {
132
+ const result = await executeWorkflowTool(
133
+ initialState(),
134
+ 'insert_approval_step',
135
+ { label: '主管簽核' },
136
+ deterministicIds,
137
+ );
138
+
139
+ expect(result.ok).toBe(true);
140
+
141
+ if (result.ok && result.kind === 'macro') {
142
+ const userTask = result.snapshot.nodes.find(
143
+ (node) => node.type === 'userTask',
144
+ );
145
+
146
+ expect(userTask?.label).toBe('主管簽核');
147
+ expect(result.snapshot.issue).toBeNull();
148
+ }
149
+ });
150
+
151
+ it('answers the get_workflow_snapshot query without mutating', async () => {
152
+ const result = await executeWorkflowTool(
153
+ initialState(),
154
+ 'get_workflow_snapshot',
155
+ {},
156
+ );
157
+
158
+ expect(result.ok).toBe(true);
159
+
160
+ if (result.ok && result.kind === 'query') {
161
+ const snapshot = result.data as WorkflowSnapshot;
162
+
163
+ expect(snapshot.nodes.map((node) => node.type)).toEqual([
164
+ 'startEvent',
165
+ 'endEvent',
166
+ ]);
167
+ }
168
+ });
169
+
170
+ it('reports validation issues via validate_workflow', async () => {
171
+ const result = await executeWorkflowTool(
172
+ initialState(),
173
+ 'validate_workflow',
174
+ {},
175
+ );
176
+
177
+ expect(result.ok).toBe(true);
178
+
179
+ if (result.ok && result.kind === 'query') {
180
+ expect(result.data).toEqual({ issue: null });
181
+ }
182
+ });
183
+
184
+ it('search_members reports unavailable without an injected directory', async () => {
185
+ const result = await executeWorkflowTool(initialState(), 'search_members', {
186
+ query: '財務',
187
+ });
188
+
189
+ expect(result.ok).toBe(true);
190
+
191
+ if (result.ok && result.kind === 'query') {
192
+ expect(result.data).toEqual({ available: false, items: [] });
193
+ }
194
+ });
195
+
196
+ it('search_members returns members from the injected directory', async () => {
197
+ const directory: WorkflowDirectory = {
198
+ listOrgUnits: async () => [],
199
+ listPositions: async () => [],
200
+ searchMembers: async (query) => [
201
+ { email: 'amy@example.com', id: 'm-1', name: `${query}-王小明` },
202
+ ],
203
+ };
204
+ const result = await executeWorkflowTool(
205
+ initialState(),
206
+ 'search_members',
207
+ { query: '財務' },
208
+ { directory },
209
+ );
210
+
211
+ expect(result.ok).toBe(true);
212
+
213
+ if (result.ok && result.kind === 'query') {
214
+ expect(result.data).toEqual({
215
+ available: true,
216
+ items: [{ email: 'amy@example.com', id: 'm-1', name: '財務-王小明' }],
217
+ });
218
+ }
219
+ });
220
+ });
221
+
222
+ describe('readWorkflowSnapshot', () => {
223
+ it('summarises nodes and selection', () => {
224
+ const snapshot = readWorkflowSnapshot(initialState());
225
+
226
+ expect(snapshot.selectedNodeId).toBe('start');
227
+ expect(snapshot.issue).toBeNull();
228
+ expect(snapshot.nodes).toHaveLength(2);
229
+ });
230
+ });