Skip to content

Commit f485865

Browse files
committed
definition walker.
1 parent 8e757af commit f485865

File tree

10 files changed

+2445
-106
lines changed

10 files changed

+2445
-106
lines changed

.prettierrc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"printWidth": 140,
3+
"singleQuote": true,
4+
"trailingComma": "none",
5+
"semi": true,
6+
"useTabs": true,
7+
"arrowParens": "avoid"
8+
}

jest.config.cjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
testMatch: ['**/?(*.)+(spec|test).ts?(x)'],
3+
transform: {
4+
'^.+\\.(ts|js)x?$': 'ts-jest',
5+
}
6+
};

package.json

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "sequential-workflow-model",
33
"description": "Extendable data model of sequential workflow.",
4-
"version": "0.1.1",
4+
"version": "0.1.2",
55
"homepage": "https://nocode-js.com/",
66
"author": {
77
"name": "NoCode JS",
@@ -35,13 +35,21 @@
3535
},
3636
"scripts": {
3737
"clean": "rm -rf lib",
38-
"build": "yarn clean && rollup -c"
38+
"build": "yarn clean && rollup -c",
39+
"prettier": "prettier --check ./src",
40+
"prettier:fix": "prettier --write ./src",
41+
"test:single": "jest",
42+
"test": "jest --clearCache && jest --watchAll"
3943
},
4044
"devDependencies": {
4145
"typescript": "^4.9.5",
4246
"rollup-plugin-dts": "^5.2.0",
4347
"rollup-plugin-typescript2": "^0.34.1",
44-
"rollup": "^3.18.0"
48+
"rollup": "^3.18.0",
49+
"prettier": "^2.8.8",
50+
"@types/jest": "^29.5.1",
51+
"jest": "^29.5.0",
52+
"ts-jest": "^29.1.0"
4553
},
4654
"keywords": [
4755
"workflow",
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { BranchedStep, SequentialStep, Step } from '../model';
2+
import { StepChildren, StepChildrenResolver, StepChildrenType } from './definition-walker';
3+
4+
export const defaultResolvers: StepChildrenResolver[] = [sequentialResolver, branchedResolver];
5+
6+
function branchedResolver(step: Step): StepChildren | null {
7+
const branches = (step as BranchedStep).branches;
8+
if (branches) {
9+
return { type: StepChildrenType.branches, items: branches };
10+
}
11+
return null;
12+
}
13+
14+
function sequentialResolver(step: Step): StepChildren | null {
15+
const sequence = (step as SequentialStep).sequence;
16+
if (sequence) {
17+
return { type: StepChildrenType.sequence, items: sequence };
18+
}
19+
return null;
20+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { DefinitionWalker, StepChildrenType } from './definition-walker';
2+
import { BranchedStep, SequentialStep, Step } from '../model';
3+
4+
describe('DefinitionWalker', () => {
5+
function createIf(name: string, falseStep: Step): BranchedStep {
6+
return {
7+
componentType: 'switch',
8+
id: 'if' + name,
9+
type: 'if' + name,
10+
name,
11+
branches: {
12+
true: [],
13+
false: [falseStep]
14+
},
15+
properties: {}
16+
};
17+
}
18+
19+
function createTask(name: string): Step {
20+
return {
21+
componentType: 'task',
22+
id: 'task' + name,
23+
type: 'task' + name,
24+
name,
25+
properties: {}
26+
};
27+
}
28+
29+
const taskFoo = createTask('foo');
30+
const ifAlfa = createIf('beta', taskFoo);
31+
const ifBeta = createIf('alfa', ifAlfa);
32+
const loop = {
33+
componentType: 'container',
34+
id: 'loop',
35+
name: 'loop',
36+
type: 'loop',
37+
properties: {},
38+
sequence: [ifBeta]
39+
} as SequentialStep;
40+
const definition = {
41+
sequence: [
42+
createIf('q', createTask('p')),
43+
loop // loop > ifBeta > 'false' > ifAlfa > 'false' > taskFoo
44+
],
45+
properties: {}
46+
};
47+
48+
let walker: DefinitionWalker;
49+
50+
beforeAll(() => {
51+
walker = new DefinitionWalker();
52+
});
53+
54+
describe('getChildren', () => {
55+
it('returns null for task', () => {
56+
expect(walker.getChildren(taskFoo)).toBeNull();
57+
});
58+
59+
it('returns single sequence for container', () => {
60+
const result = walker.getChildren(loop);
61+
expect(result?.type).toEqual(StepChildrenType.sequence);
62+
expect(result?.items).toEqual(loop.sequence);
63+
});
64+
65+
it('returns branches for branched step', () => {
66+
const result = walker.getChildren(ifBeta);
67+
expect(result?.type).toEqual(StepChildrenType.branches);
68+
expect(result?.items).toEqual(ifBeta.branches);
69+
});
70+
});
71+
72+
describe('getParents', () => {
73+
it('returns task parents', () => {
74+
const parents = walker.getParents(definition, taskFoo);
75+
expect(parents.length).toEqual(6);
76+
expect(parents[0]).toEqual(loop);
77+
expect(parents[1]).toEqual(ifBeta);
78+
expect(parents[2]).toEqual('false');
79+
expect(parents[3]).toEqual(ifAlfa);
80+
expect(parents[4]).toEqual('false');
81+
expect(parents[5]).toEqual(taskFoo);
82+
});
83+
84+
it('returns alfa parents', () => {
85+
const parents = walker.getParents(definition, ifBeta);
86+
expect(parents.length).toEqual(2);
87+
expect(parents[0]).toEqual(loop);
88+
expect(parents[1]).toEqual(ifBeta);
89+
});
90+
91+
it('returns loop parents', () => {
92+
const parents = walker.getParents(definition, loop);
93+
expect(parents.length).toEqual(1);
94+
expect(parents[0]).toEqual(loop);
95+
});
96+
97+
it('returns no parents for root sequence', () => {
98+
const parents = walker.getParents(definition, definition.sequence);
99+
expect(parents.length).toEqual(0);
100+
});
101+
});
102+
103+
describe('findById', () => {
104+
it('returns null when stepId not exists', () => {
105+
const found = walker.findById(definition, 'invalidId');
106+
expect(found).toBeNull();
107+
});
108+
109+
it('returns task step', () => {
110+
const found = walker.findById(definition, taskFoo.id);
111+
expect(found).toEqual(taskFoo);
112+
});
113+
114+
it('returns container step', () => {
115+
const found = walker.findById(definition, loop.id);
116+
expect(found).toEqual(loop);
117+
});
118+
119+
it('returns switch step', () => {
120+
const found = walker.findById(definition, ifBeta.id);
121+
expect(found).toEqual(ifBeta);
122+
});
123+
});
124+
});
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { Branches, Definition, Sequence, Step } from '../model';
2+
import { defaultResolvers } from './default-resolvers';
3+
4+
export interface StepChildren {
5+
type: StepChildrenType;
6+
items: Sequence | Branches;
7+
}
8+
9+
export enum StepChildrenType {
10+
sequence = 1,
11+
branches = 2
12+
}
13+
14+
export type StepOrName = Step | string;
15+
16+
export interface StepWithParentSequence {
17+
step: Step;
18+
/**
19+
* Index of the step in the parent sequence.
20+
*/
21+
index: number;
22+
parentSequence: Sequence;
23+
}
24+
25+
type StepWithParentSequenceOrName = StepWithParentSequence | string;
26+
27+
export type StepChildrenResolver = (step: Step) => StepChildren | null;
28+
29+
export class DefinitionWalker {
30+
private readonly resolvers: StepChildrenResolver[];
31+
32+
public constructor(resolvers?: StepChildrenResolver[]) {
33+
this.resolvers = resolvers ? resolvers.concat(defaultResolvers) : defaultResolvers;
34+
}
35+
36+
public getChildren(step: Step): StepChildren | null {
37+
const count = this.resolvers.length;
38+
for (let i = 0; i < count; i++) {
39+
const result = this.resolvers[i](step);
40+
if (result) {
41+
return result;
42+
}
43+
}
44+
return null;
45+
}
46+
47+
public getParents(definition: Definition, needle: Sequence | Step): StepOrName[] {
48+
const result: StepWithParentSequenceOrName[] = [];
49+
const searchSequence = Array.isArray(needle) ? needle : null;
50+
const searchStepId = !searchSequence ? (needle as Step).id : null;
51+
52+
if (this.find(definition.sequence, searchSequence, searchStepId, result)) {
53+
result.reverse();
54+
return result.map(item => {
55+
return typeof item === 'string' ? item : item.step;
56+
});
57+
}
58+
59+
throw new Error(searchStepId ? `Cannot get parents of step: ${searchStepId}` : 'Cannot get parents of sequence');
60+
}
61+
62+
public findParentSequence(definition: Definition, stepId: string): StepWithParentSequence | null {
63+
const result: StepWithParentSequenceOrName[] = [];
64+
if (this.find(definition.sequence, null, stepId, result)) {
65+
return result[0] as StepWithParentSequence;
66+
}
67+
return null;
68+
}
69+
70+
public getParentSequence(definition: Definition, stepId: string): StepWithParentSequence {
71+
const result = this.findParentSequence(definition, stepId);
72+
if (!result) {
73+
throw new Error(`Cannot find step by id: ${stepId}`);
74+
}
75+
return result;
76+
}
77+
78+
public findById(definition: Definition, stepId: string): Step | null {
79+
const result = this.findParentSequence(definition, stepId);
80+
return result ? result.step : null;
81+
}
82+
83+
public getById(definition: Definition, stepId: string): Step {
84+
return this.getParentSequence(definition, stepId).step;
85+
}
86+
87+
private find(
88+
sequence: Sequence,
89+
needSequence: Sequence | null,
90+
needStepId: string | null,
91+
result: StepWithParentSequenceOrName[]
92+
): boolean {
93+
if (needSequence && sequence === needSequence) {
94+
return true;
95+
}
96+
const count = sequence.length;
97+
for (let index = 0; index < count; index++) {
98+
const step = sequence[index];
99+
if (needStepId && step.id === needStepId) {
100+
result.push({ step, index, parentSequence: sequence });
101+
return true;
102+
}
103+
104+
const children = this.getChildren(step);
105+
if (children) {
106+
switch (children.type) {
107+
case StepChildrenType.sequence:
108+
{
109+
const parentSequence = children.items as Sequence;
110+
if (this.find(parentSequence, needSequence, needStepId, result)) {
111+
result.push({ step, index, parentSequence });
112+
return true;
113+
}
114+
}
115+
break;
116+
117+
case StepChildrenType.branches:
118+
{
119+
const branches = children.items as Branches;
120+
const branchNames = Object.keys(branches);
121+
for (const branchName of branchNames) {
122+
const parentSequence = branches[branchName];
123+
if (this.find(parentSequence, needSequence, needStepId, result)) {
124+
result.push(branchName);
125+
result.push({ step, index, parentSequence });
126+
return true;
127+
}
128+
}
129+
}
130+
break;
131+
132+
default:
133+
throw new Error(`Step children type ${children.type} is not supported`);
134+
}
135+
}
136+
}
137+
return false;
138+
}
139+
}

src/definition-walker/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './definition-walker';

0 commit comments

Comments
 (0)