@zola_do/workflow-engine 0.2.8 → 0.2.9
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 +86 -86
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,8 +30,8 @@ npm install @zola_do/nestjs-shared
|
|
|
30
30
|
### 1. Register Service
|
|
31
31
|
|
|
32
32
|
```typescript
|
|
33
|
-
import { Module } from
|
|
34
|
-
import { WorkflowEngineService } from
|
|
33
|
+
import { Module } from '@nestjs/common';
|
|
34
|
+
import { WorkflowEngineService } from '@zola_do/workflow-engine';
|
|
35
35
|
|
|
36
36
|
@Module({
|
|
37
37
|
providers: [WorkflowEngineService],
|
|
@@ -43,15 +43,15 @@ export class WorkflowModule {}
|
|
|
43
43
|
### 2. Convert Workflow
|
|
44
44
|
|
|
45
45
|
```typescript
|
|
46
|
-
import { Injectable } from
|
|
47
|
-
import { WorkflowEngineService } from
|
|
46
|
+
import { Injectable } from '@nestjs/common';
|
|
47
|
+
import { WorkflowEngineService } from '@zola_do/workflow-engine';
|
|
48
48
|
|
|
49
49
|
@Injectable()
|
|
50
50
|
export class WorkflowController {
|
|
51
51
|
constructor(private readonly workflowEngine: WorkflowEngineService) {}
|
|
52
52
|
|
|
53
|
-
@Post(
|
|
54
|
-
async compileWorkflow(@Param(
|
|
53
|
+
@Post('workflows/:id/compile')
|
|
54
|
+
async compileWorkflow(@Param('id') id: string) {
|
|
55
55
|
const uiJson = await this.workflowService.getUiJson(id);
|
|
56
56
|
|
|
57
57
|
const stateMachine = this.workflowEngine.convertToStateMachine(uiJson);
|
|
@@ -114,7 +114,7 @@ export class WorkflowController {
|
|
|
114
114
|
```typescript
|
|
115
115
|
interface WorkflowNode {
|
|
116
116
|
id: string;
|
|
117
|
-
type:
|
|
117
|
+
type: 'start' | 'end' | 'task' | 'condition' | 'parallel' | string;
|
|
118
118
|
data: {
|
|
119
119
|
label?: string;
|
|
120
120
|
[key: string]: any;
|
|
@@ -144,49 +144,49 @@ interface WorkflowEdge {
|
|
|
144
144
|
const uiJson = {
|
|
145
145
|
nodes: [
|
|
146
146
|
{
|
|
147
|
-
id:
|
|
148
|
-
type:
|
|
149
|
-
data: { label:
|
|
147
|
+
id: 'node-1',
|
|
148
|
+
type: 'start',
|
|
149
|
+
data: { label: 'Start' },
|
|
150
150
|
position: { x: 100, y: 100 },
|
|
151
151
|
},
|
|
152
152
|
{
|
|
153
|
-
id:
|
|
154
|
-
type:
|
|
155
|
-
data: { label:
|
|
153
|
+
id: 'node-2',
|
|
154
|
+
type: 'task',
|
|
155
|
+
data: { label: 'Review Application' },
|
|
156
156
|
position: { x: 300, y: 100 },
|
|
157
157
|
},
|
|
158
158
|
{
|
|
159
|
-
id:
|
|
160
|
-
type:
|
|
161
|
-
data: { label:
|
|
159
|
+
id: 'node-3',
|
|
160
|
+
type: 'condition',
|
|
161
|
+
data: { label: 'Approved?' },
|
|
162
162
|
position: { x: 500, y: 100 },
|
|
163
163
|
},
|
|
164
164
|
{
|
|
165
|
-
id:
|
|
166
|
-
type:
|
|
167
|
-
data: { label:
|
|
165
|
+
id: 'node-4',
|
|
166
|
+
type: 'task',
|
|
167
|
+
data: { label: 'Send Approval Email' },
|
|
168
168
|
position: { x: 700, y: 50 },
|
|
169
169
|
},
|
|
170
170
|
{
|
|
171
|
-
id:
|
|
172
|
-
type:
|
|
173
|
-
data: { label:
|
|
171
|
+
id: 'node-5',
|
|
172
|
+
type: 'task',
|
|
173
|
+
data: { label: 'Send Rejection Email' },
|
|
174
174
|
position: { x: 700, y: 150 },
|
|
175
175
|
},
|
|
176
176
|
{
|
|
177
|
-
id:
|
|
178
|
-
type:
|
|
179
|
-
data: { label:
|
|
177
|
+
id: 'node-6',
|
|
178
|
+
type: 'end',
|
|
179
|
+
data: { label: 'End' },
|
|
180
180
|
position: { x: 900, y: 100 },
|
|
181
181
|
},
|
|
182
182
|
],
|
|
183
183
|
edges: [
|
|
184
|
-
{ source:
|
|
185
|
-
{ source:
|
|
186
|
-
{ source:
|
|
187
|
-
{ source:
|
|
188
|
-
{ source:
|
|
189
|
-
{ source:
|
|
184
|
+
{ source: 'node-1', target: 'node-2' },
|
|
185
|
+
{ source: 'node-2', target: 'node-3' },
|
|
186
|
+
{ source: 'node-3', target: 'node-4', label: 'YES' },
|
|
187
|
+
{ source: 'node-3', target: 'node-5', label: 'NO' },
|
|
188
|
+
{ source: 'node-4', target: 'node-6' },
|
|
189
|
+
{ source: 'node-5', target: 'node-6' },
|
|
190
190
|
],
|
|
191
191
|
};
|
|
192
192
|
```
|
|
@@ -201,7 +201,7 @@ interface StateMachine {
|
|
|
201
201
|
initial: string; // Initial state name
|
|
202
202
|
states: {
|
|
203
203
|
[stateName: string]: {
|
|
204
|
-
type?:
|
|
204
|
+
type?: 'final' | 'atomic';
|
|
205
205
|
on?: {
|
|
206
206
|
[event: string]: string | StateMachine;
|
|
207
207
|
};
|
|
@@ -221,35 +221,35 @@ interface StateMachine {
|
|
|
221
221
|
|
|
222
222
|
```typescript
|
|
223
223
|
const stateMachine = {
|
|
224
|
-
id:
|
|
225
|
-
initial:
|
|
224
|
+
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
225
|
+
initial: 'Start',
|
|
226
226
|
states: {
|
|
227
227
|
Start: {
|
|
228
|
-
on: { NEXT:
|
|
229
|
-
meta: { type:
|
|
228
|
+
on: { NEXT: 'Review Application' },
|
|
229
|
+
meta: { type: 'start' },
|
|
230
230
|
},
|
|
231
|
-
|
|
232
|
-
on: { NEXT:
|
|
233
|
-
meta: { type:
|
|
231
|
+
'Review Application': {
|
|
232
|
+
on: { NEXT: 'Approved?' },
|
|
233
|
+
meta: { type: 'task' },
|
|
234
234
|
},
|
|
235
|
-
|
|
235
|
+
'Approved?': {
|
|
236
236
|
on: {
|
|
237
|
-
YES:
|
|
238
|
-
NO:
|
|
237
|
+
YES: 'Send Approval Email',
|
|
238
|
+
NO: 'Send Rejection Email',
|
|
239
239
|
},
|
|
240
|
-
meta: { type:
|
|
240
|
+
meta: { type: 'condition' },
|
|
241
241
|
},
|
|
242
|
-
|
|
243
|
-
on: { NEXT:
|
|
244
|
-
meta: { type:
|
|
242
|
+
'Send Approval Email': {
|
|
243
|
+
on: { NEXT: 'End' },
|
|
244
|
+
meta: { type: 'task' },
|
|
245
245
|
},
|
|
246
|
-
|
|
247
|
-
on: { NEXT:
|
|
248
|
-
meta: { type:
|
|
246
|
+
'Send Rejection Email': {
|
|
247
|
+
on: { NEXT: 'End' },
|
|
248
|
+
meta: { type: 'task' },
|
|
249
249
|
},
|
|
250
250
|
End: {
|
|
251
|
-
type:
|
|
252
|
-
meta: { type:
|
|
251
|
+
type: 'final',
|
|
252
|
+
meta: { type: 'end' },
|
|
253
253
|
},
|
|
254
254
|
},
|
|
255
255
|
};
|
|
@@ -319,20 +319,20 @@ const stateMachine = {
|
|
|
319
319
|
```typescript
|
|
320
320
|
const approvalWorkflow = {
|
|
321
321
|
nodes: [
|
|
322
|
-
{ id:
|
|
323
|
-
{ id:
|
|
324
|
-
{ id:
|
|
325
|
-
{ id:
|
|
326
|
-
{ id:
|
|
327
|
-
{ id:
|
|
322
|
+
{ id: '1', type: 'start', data: { label: 'Submit' } },
|
|
323
|
+
{ id: '2', type: 'task', data: { label: 'Manager Review' } },
|
|
324
|
+
{ id: '3', type: 'condition', data: { label: 'Approved?' } },
|
|
325
|
+
{ id: '4', type: 'task', data: { label: 'Send Approval' } },
|
|
326
|
+
{ id: '5', type: 'task', data: { label: 'Notify Rejection' } },
|
|
327
|
+
{ id: '6', type: 'end', data: { label: 'Done' } },
|
|
328
328
|
],
|
|
329
329
|
edges: [
|
|
330
|
-
{ source:
|
|
331
|
-
{ source:
|
|
332
|
-
{ source:
|
|
333
|
-
{ source:
|
|
334
|
-
{ source:
|
|
335
|
-
{ source:
|
|
330
|
+
{ source: '1', target: '2' },
|
|
331
|
+
{ source: '2', target: '3' },
|
|
332
|
+
{ source: '3', target: '4', label: 'YES' },
|
|
333
|
+
{ source: '3', target: '5', label: 'NO' },
|
|
334
|
+
{ source: '4', target: '6' },
|
|
335
|
+
{ source: '5', target: '6' },
|
|
336
336
|
],
|
|
337
337
|
};
|
|
338
338
|
|
|
@@ -344,20 +344,20 @@ const stateMachine = workflowEngine.convertToStateMachine(approvalWorkflow);
|
|
|
344
344
|
```typescript
|
|
345
345
|
const formWorkflow = {
|
|
346
346
|
nodes: [
|
|
347
|
-
{ id:
|
|
348
|
-
{ id:
|
|
349
|
-
{ id:
|
|
350
|
-
{ id:
|
|
351
|
-
{ id:
|
|
352
|
-
{ id:
|
|
347
|
+
{ id: 's1', type: 'start', data: { label: 'Step 1' } },
|
|
348
|
+
{ id: 's2', type: 'task', data: { label: 'Personal Info' } },
|
|
349
|
+
{ id: 's3', type: 'task', data: { label: 'Address' } },
|
|
350
|
+
{ id: 's4', type: 'task', data: { label: 'Payment' } },
|
|
351
|
+
{ id: 's5', type: 'condition', data: { label: 'Valid?' } },
|
|
352
|
+
{ id: 's6', type: 'end', data: { label: 'Complete' } },
|
|
353
353
|
],
|
|
354
354
|
edges: [
|
|
355
|
-
{ source:
|
|
356
|
-
{ source:
|
|
357
|
-
{ source:
|
|
358
|
-
{ source:
|
|
359
|
-
{ source:
|
|
360
|
-
{ source:
|
|
355
|
+
{ source: 's1', target: 's2' },
|
|
356
|
+
{ source: 's2', target: 's3' },
|
|
357
|
+
{ source: 's3', target: 's4' },
|
|
358
|
+
{ source: 's4', target: 's5' },
|
|
359
|
+
{ source: 's5', target: 's6', label: 'YES' },
|
|
360
|
+
{ source: 's5', target: 's2', label: 'NO' }, // Go back
|
|
361
361
|
],
|
|
362
362
|
};
|
|
363
363
|
```
|
|
@@ -367,32 +367,32 @@ const formWorkflow = {
|
|
|
367
367
|
The output is compatible with XState:
|
|
368
368
|
|
|
369
369
|
```typescript
|
|
370
|
-
import { createMachine } from
|
|
371
|
-
import { interpret } from
|
|
370
|
+
import { createMachine } from 'xstate';
|
|
371
|
+
import { interpret } from 'xstate';
|
|
372
372
|
|
|
373
373
|
const machine = createMachine(stateMachine);
|
|
374
374
|
|
|
375
375
|
const actor = interpret(machine).start();
|
|
376
376
|
|
|
377
|
-
actor.send({ type:
|
|
378
|
-
actor.send({ type:
|
|
377
|
+
actor.send({ type: 'NEXT' }); // Transition to next state
|
|
378
|
+
actor.send({ type: 'YES' }); // Transition on condition
|
|
379
379
|
```
|
|
380
380
|
|
|
381
381
|
### Full XState Example
|
|
382
382
|
|
|
383
383
|
```typescript
|
|
384
|
-
import { createMachine, interpret } from
|
|
384
|
+
import { createMachine, interpret } from 'xstate';
|
|
385
385
|
|
|
386
|
-
@Controller(
|
|
386
|
+
@Controller('workflows')
|
|
387
387
|
export class WorkflowExecutionController {
|
|
388
|
-
@Post(
|
|
389
|
-
async executeWorkflow(@Param(
|
|
388
|
+
@Post(':id/execute')
|
|
389
|
+
async executeWorkflow(@Param('id') id: string) {
|
|
390
390
|
const definition = await this.workflowService.getCompiledDefinition(id);
|
|
391
391
|
const machine = createMachine(definition);
|
|
392
392
|
|
|
393
393
|
const actor = interpret(machine)
|
|
394
394
|
.onTransition((state) => {
|
|
395
|
-
console.log(
|
|
395
|
+
console.log('Current state:', state.value);
|
|
396
396
|
})
|
|
397
397
|
.start();
|
|
398
398
|
|
|
@@ -405,11 +405,11 @@ export class WorkflowExecutionController {
|
|
|
405
405
|
private async executeSteps(actor: any, definition: any) {
|
|
406
406
|
const states = Object.keys(definition.states);
|
|
407
407
|
for (const state of states) {
|
|
408
|
-
if (definition.states[state].type ===
|
|
408
|
+
if (definition.states[state].type === 'final') {
|
|
409
409
|
break;
|
|
410
410
|
}
|
|
411
411
|
await this.performStateAction(state);
|
|
412
|
-
actor.send({ type:
|
|
412
|
+
actor.send({ type: 'NEXT' });
|
|
413
413
|
}
|
|
414
414
|
return { completed: true };
|
|
415
415
|
}
|