parent
f19d608388
commit
fe2ffc9182
2 changed files with 45 additions and 21 deletions
45
src/index.ts
45
src/index.ts
|
@ -8,15 +8,15 @@ export type ActionEvent = (event: string, fromState: StateType, toState: StateTy
|
||||||
|
|
||||||
|
|
||||||
export interface IConfig {
|
export interface IConfig {
|
||||||
[key: string]: undefined | ActionEvent | ActionConfigMap
|
[key: string]: undefined | ActionEvent | ActionConfigMap;
|
||||||
onEnter?: ActionEvent
|
onEnter?: ActionEvent;
|
||||||
onLeave?: ActionEvent
|
onLeave?: ActionEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IActionConfig {
|
export interface IActionConfig {
|
||||||
state: StateType
|
state: StateType;
|
||||||
onBeforeChange?: ActionEvent
|
onBeforeChange?: ActionEvent;
|
||||||
onChange?: ActionEvent
|
onChange?: ActionEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,8 +26,8 @@ export class StateMachine {
|
||||||
private _currentState: StateType;
|
private _currentState: StateType;
|
||||||
private _onEnter?: ActionEvent;
|
private _onEnter?: ActionEvent;
|
||||||
private _onLeave?: ActionEvent;
|
private _onLeave?: ActionEvent;
|
||||||
private _eventsByState: Record<string, Record<string, (payload: Payload) => any>> = {};
|
private _eventsByState = new Map<StateType, Record<string, (payload: Payload) => any>>();
|
||||||
private _statesByState: Record<string, StateType[]> = {};
|
private _statesByState = new Map<StateType, StateType[]>();
|
||||||
|
|
||||||
constructor(initial: StateType, config: IConfig) {
|
constructor(initial: StateType, config: IConfig) {
|
||||||
this._currentState = initial;
|
this._currentState = initial;
|
||||||
|
@ -38,7 +38,11 @@ export class StateMachine {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
this._statesByState[fromStateKey] = [];
|
const fromState: StateType = /^\d+$/.test(fromStateKey) ?
|
||||||
|
parseInt(fromStateKey, 10)
|
||||||
|
: fromStateKey;
|
||||||
|
|
||||||
|
const statesOfState: StateType[] = [];
|
||||||
|
|
||||||
let actions = config[fromStateKey] as ActionConfigMap;
|
let actions = config[fromStateKey] as ActionConfigMap;
|
||||||
for (let actionName in actions) {
|
for (let actionName in actions) {
|
||||||
|
@ -47,9 +51,9 @@ export class StateMachine {
|
||||||
action as IActionConfig
|
action as IActionConfig
|
||||||
: { state: action as StateType };
|
: { state: action as StateType };
|
||||||
|
|
||||||
this._statesByState[fromStateKey].push(actionConfig.state);
|
statesOfState.push(actionConfig.state);
|
||||||
|
this._statesByState.set(fromState, statesOfState);
|
||||||
|
|
||||||
let fromState: StateType = /^\d+$/.test(fromStateKey) ? parseInt(fromStateKey, 10) : fromStateKey;
|
|
||||||
this._initChangeState(actionName, fromState, actionConfig.state, actionConfig);
|
this._initChangeState(actionName, fromState, actionConfig.state, actionConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,10 +61,6 @@ export class StateMachine {
|
||||||
|
|
||||||
|
|
||||||
private _initChangeState(eventName: string, fromState: StateType, toState: StateType, actionConfig: IActionConfig): void {
|
private _initChangeState(eventName: string, fromState: StateType, toState: StateType, actionConfig: IActionConfig): void {
|
||||||
if (!this._eventsByState[fromState]) {
|
|
||||||
this._eventsByState[fromState] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const { onBeforeChange, onChange } = actionConfig;
|
const { onBeforeChange, onChange } = actionConfig;
|
||||||
const _runEvent = async (method?: ActionEvent, payload: Payload = {}): Promise<void> => {
|
const _runEvent = async (method?: ActionEvent, payload: Payload = {}): Promise<void> => {
|
||||||
if (method) {
|
if (method) {
|
||||||
|
@ -68,7 +68,8 @@ export class StateMachine {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this._eventsByState[fromState][eventName] = async (sourcePayload: Payload = {}) => {
|
const events = this._eventsByState.get(fromState) ?? {};
|
||||||
|
events[eventName] = async (sourcePayload: Payload = {}) => {
|
||||||
const payload = cloneDeep(sourcePayload);
|
const payload = cloneDeep(sourcePayload);
|
||||||
await _runEvent(this._onEnter, payload);
|
await _runEvent(this._onEnter, payload);
|
||||||
await _runEvent(onBeforeChange, payload);
|
await _runEvent(onBeforeChange, payload);
|
||||||
|
@ -79,11 +80,13 @@ export class StateMachine {
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this._eventsByState.set(fromState, events);
|
||||||
|
|
||||||
if (!this[eventName]) {
|
if (!this[eventName]) {
|
||||||
this[eventName] = async (payload: Payload = {}) => {
|
this[eventName] = async (payload: Payload = {}) => {
|
||||||
if (this._eventsByState[this._currentState]
|
const events = this._eventsByState.get(this._currentState);
|
||||||
&& this._eventsByState[this._currentState][eventName]) {
|
if (events && events[eventName]) {
|
||||||
return this._eventsByState[this._currentState][eventName](payload);
|
return events[eventName](payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,10 +105,10 @@ export class StateMachine {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAvailableStates(): StateType[] {
|
public getAvailableStates(): StateType[] {
|
||||||
return this._statesByState[this._currentState] || []
|
return this._statesByState.get(this._currentState) ?? []
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAvailableActions(): string[] {
|
public getAvailableActions(): string[] {
|
||||||
return Object.keys(this._eventsByState[this._currentState] || {});
|
return Object.keys(this._eventsByState.get(this._currentState) ?? {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,6 +129,27 @@ describe('StateMachine', () => {
|
||||||
objectStrFSM._currentState = StrStatus.PENDING;
|
objectStrFSM._currentState = StrStatus.PENDING;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('::new', () => {
|
||||||
|
it('should init fsm model successfully', () => {
|
||||||
|
|
||||||
|
const simpleIntFSM = new StateMachine(IntStatus.PENDING, {
|
||||||
|
[IntStatus.PENDING]: {
|
||||||
|
active: IntStatus.ACTIVE,
|
||||||
|
delete: IntStatus.DELETED,
|
||||||
|
},
|
||||||
|
[IntStatus.ACTIVE]: {
|
||||||
|
toDraft: IntStatus.PENDING,
|
||||||
|
archive: IntStatus.ARCHIVED,
|
||||||
|
doNothing: IntStatus.ACTIVE,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(simpleIntFSM).toBeDefined();
|
||||||
|
expect(simpleIntFSM.getCurrentState()).toBe(IntStatus.PENDING);
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('.getCurrentState', () => {
|
describe('.getCurrentState', () => {
|
||||||
describe('<IntStatus>', () => {
|
describe('<IntStatus>', () => {
|
||||||
it('should return initial int state for simple fsm model', () => {
|
it('should return initial int state for simple fsm model', () => {
|
||||||
|
|
Loading…
Reference in a new issue