Skip to content

Commit 253d37c

Browse files
authored
0.6.0. (#12)
1 parent aef8df8 commit 253d37c

17 files changed

+597
-48
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## 0.6.0
2+
3+
This version introduces the [parallel activity](https://nocode-js.com/docs/sequential-workflow-machine/activities/parallel-activity). The parallel activity allows to execute in the same time many activities.
4+
5+
**Breaking Changes**
6+
7+
* The `getStatePath` method in the `WorkflowMachineSnapshot` class is deleted. Please use the `tryGetStatePath` method or the `getStatePaths` method instead.
8+
19
## 0.5.2
210

311
This version adds a possibility to stop a workflow machine. To stop a workflow machine, you should call the `tryStop()` method of the `WorkflowMachineInterpreter` class.

machine/package.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "sequential-workflow-machine",
33
"description": "Powerful sequential workflow machine for frontend and backend applications.",
4-
"version": "0.5.2",
4+
"version": "0.6.0",
55
"type": "module",
66
"main": "./lib/esm/index.js",
77
"types": "./lib/index.d.ts",
@@ -44,11 +44,11 @@
4444
},
4545
"peerDependencies": {
4646
"sequential-workflow-model": "^0.2.0",
47-
"xstate": "^4.38.2"
47+
"xstate": "^4.38.3"
4848
},
4949
"dependencies": {
5050
"sequential-workflow-model": "^0.2.0",
51-
"xstate": "^4.38.2"
51+
"xstate": "^4.38.3"
5252
},
5353
"devDependencies": {
5454
"@types/jest": "^29.4.0",
@@ -72,4 +72,4 @@
7272
"nocode",
7373
"lowcode"
7474
]
75-
}
75+
}

machine/src/activities/fork-activity/fork-activity-node-builder.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export class ForkActivityNodeBuilder<TStep extends BranchedStep, TGlobalState, T
3636

3737
const states: Record<string, ActivityNodeConfig<TGlobalState>> = {};
3838
for (const branchName of branchNames) {
39-
const branchNodeId = getBranchNodeId(branchName);
39+
const branchNodeId = getBranchNodeId(step.id, branchName);
4040
states[branchNodeId] = this.sequenceNodeBuilder.build(buildingContext, step.branches[branchName], nextNodeTarget);
4141
}
4242

@@ -82,7 +82,7 @@ export class ForkActivityNodeBuilder<TStep extends BranchedStep, TGlobalState, T
8282
},
8383
...branchNames.map(branchName => {
8484
return {
85-
target: getBranchNodeId(branchName),
85+
target: getBranchNodeId(step.id, branchName),
8686
cond: (context: MachineContext<TGlobalState>) => {
8787
const activityState = activityStateProvider.get(context, nodeId);
8888
return activityState.targetBranchName === branchName;

machine/src/activities/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ export * from './container-activity';
44
export * from './fork-activity';
55
export * from './interruption-activity';
66
export * from './loop-activity';
7+
export * from './parallel-activity';
78
export * from './results';

machine/src/activities/loop-activity/loop-activity.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ describe('LoopActivity', () => {
102102

103103
interpreter.onChange(() => {
104104
const snapshot = interpreter.getSnapshot();
105-
expect(snapshot.getStatePath()).toMatchObject(expectedRun[index].path);
105+
expect(snapshot.tryGetStatePath()).toMatchObject(expectedRun[index].path);
106106
expect(snapshot.tryGetCurrentStepId()).toBe(expectedRun[index].id);
107107
expect(snapshot.isFailed()).toBe(false);
108108
expect(snapshot.isFinished()).toBe(index === 8);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './types';
2+
export * from './parallel-activity';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import {
2+
ActivityNodeBuilder,
3+
BuildingContext,
4+
MachineContext,
5+
ActivityNodeConfig,
6+
STATE_FAILED_TARGET,
7+
STATE_INTERRUPTED_TARGET
8+
} from '../../types';
9+
import { ActivityStateProvider } from '../../core/activity-context-provider';
10+
import { catchUnhandledError } from '../../core/catch-unhandled-error';
11+
import { SequenceNodeBuilder } from '../../core/sequence-node-builder';
12+
import { getBranchNodeId, getStepNodeId } from '../../core/safe-node-id';
13+
import { BranchedStep } from 'sequential-workflow-model';
14+
import { ParallelActivityConfig, ParallelActivityState } from './types';
15+
import { isInterruptResult, isSkipResult } from '../results';
16+
17+
export class ParallelActivityNodeBuilder<TStep extends BranchedStep, TGlobalState, TActivityState extends object>
18+
implements ActivityNodeBuilder<TGlobalState>
19+
{
20+
public constructor(
21+
private readonly sequenceNodeBuilder: SequenceNodeBuilder<TGlobalState>,
22+
private readonly config: ParallelActivityConfig<TStep, TGlobalState, TActivityState>
23+
) {}
24+
25+
public build(step: TStep, nextNodeTarget: string, buildingContext: BuildingContext): ActivityNodeConfig<TGlobalState> {
26+
const activityStateProvider = new ActivityStateProvider<TStep, TGlobalState, ParallelActivityState<TActivityState>>(
27+
step,
28+
(s, g) => ({ activityState: this.config.init(s, g) })
29+
);
30+
31+
const nodeId = getStepNodeId(step.id);
32+
const branchNames = Object.keys(step.branches);
33+
const states: Record<string, ActivityNodeConfig<TGlobalState>> = {};
34+
35+
for (const branchName of branchNames) {
36+
const branchNodeId = getBranchNodeId(step.id, branchName);
37+
const leaveNodeId = branchNodeId + '_LEAVE';
38+
const leaveNodeTarget = '#' + leaveNodeId;
39+
40+
states[branchNodeId] = {
41+
initial: 'TRY_LEAVE',
42+
states: {
43+
TRY_LEAVE: {
44+
invoke: {
45+
src: catchUnhandledError(async () => {
46+
// TODO: emit on enter?
47+
}),
48+
onDone: [
49+
{
50+
target: 'SEQUENCE',
51+
cond: (context: MachineContext<TGlobalState>) => {
52+
const activityState = activityStateProvider.get(context, nodeId);
53+
return activityState.branchNames?.includes(branchName) ?? false;
54+
}
55+
},
56+
{
57+
target: 'LEAVE'
58+
}
59+
],
60+
onError: STATE_FAILED_TARGET
61+
}
62+
},
63+
SEQUENCE: this.sequenceNodeBuilder.build(buildingContext, step.branches[branchName], leaveNodeTarget),
64+
LEAVE: {
65+
id: leaveNodeId,
66+
type: 'final'
67+
}
68+
}
69+
};
70+
}
71+
72+
const CONDITION: ActivityNodeConfig<TGlobalState> = {
73+
invoke: {
74+
src: catchUnhandledError(async (context: MachineContext<TGlobalState>) => {
75+
const internalState = activityStateProvider.get(context, nodeId);
76+
const result = await this.config.handler(step, context.globalState, internalState.activityState);
77+
78+
if (isInterruptResult(result)) {
79+
context.interrupted = nodeId;
80+
return;
81+
}
82+
if (isSkipResult(result)) {
83+
internalState.branchNames = [];
84+
return;
85+
}
86+
if (Array.isArray(result)) {
87+
internalState.branchNames = result.map(r => r.branchName);
88+
return;
89+
}
90+
throw new Error('Not supported result for parallel activity');
91+
}),
92+
onDone: [
93+
{
94+
target: STATE_INTERRUPTED_TARGET,
95+
cond: (context: MachineContext<TGlobalState>) => Boolean(context.interrupted)
96+
},
97+
{
98+
target: 'PARALLEL'
99+
}
100+
],
101+
onError: STATE_FAILED_TARGET
102+
}
103+
};
104+
105+
return {
106+
id: nodeId,
107+
initial: 'CONDITION',
108+
states: {
109+
CONDITION,
110+
PARALLEL: {
111+
type: 'parallel',
112+
states,
113+
onDone: 'JOIN'
114+
},
115+
JOIN: {
116+
invoke: {
117+
src: catchUnhandledError(async (context: MachineContext<TGlobalState>) => {
118+
const internalState = activityStateProvider.get(context, nodeId);
119+
internalState.branchNames = undefined;
120+
}),
121+
onDone: nextNodeTarget,
122+
onError: STATE_FAILED_TARGET
123+
}
124+
}
125+
}
126+
};
127+
}
128+
}

0 commit comments

Comments
 (0)