diff --git a/package-lock.json b/package-lock.json index 4db12e0..546fc00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -465,6 +465,19 @@ "integrity": "sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA==", "dev": true }, + "@types/lodash": { + "version": "4.14.144", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.144.tgz", + "integrity": "sha512-ogI4g9W5qIQQUhXAclq6zhqgqNUr7UlFaqDHbch7WLSLeeM/7d3CRaw7GLajxvyFvhJqw4Rpcz5bhoaYtIx6Tg==" + }, + "@types/lodash.clonedeep": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.6.tgz", + "integrity": "sha512-cE1jYr2dEg1wBImvXlNtp0xDoS79rfEdGozQVgliDZj1uERH4k+rmEMTudP9b4VQ8O6nRb5gPqft0QzEQGMQgA==", + "requires": { + "@types/lodash": "*" + } + }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", @@ -3279,6 +3292,11 @@ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", diff --git a/package.json b/package.json index dc381bb..855a787 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "it-fsm", - "version": "1.0.3", + "version": "1.0.4", "description": "Simple finite state machine for nodejs", "main": "./src/index.js", "types": "./src/index.d.ts", @@ -36,9 +36,13 @@ "homepage": "https://github.com/icetemple/npm-it-fsm#readme", "devDependencies": { "@types/jest": "^24.0.15", + "@types/lodash.clonedeep": "^4.5.6", "coveralls": "^3.0.5", "jest": "^24.8.0", "ts-jest": "^24.0.2", "typescript": "^3.5.3" + }, + "dependencies": { + "lodash.clonedeep": "^4.5.0" } } diff --git a/src/index.ts b/src/index.ts index a128275..b44d84f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,16 +1,16 @@ - +import cloneDeep from 'lodash.clonedeep'; export type Payload = Record export type StateType = string | number export type ActionConfigMap = Record -export type ActionEvent = (event: string, fromState: string, toState: string, +export type ActionEvent = (event: string, fromState: StateType, toState: StateType, payload: Payload) => Promise export interface IConfig { - [key: string]: ActionEvent | ActionConfigMap - onEnter: ActionEvent - onLeave: ActionEvent + [key: string]: undefined | ActionEvent | ActionConfigMap + onEnter?: ActionEvent + onLeave?: ActionEvent } export interface IActionConfig { @@ -23,54 +23,58 @@ export interface IActionConfig { export class StateMachine { [key: string]: any; - private _currentState: string; - private _onEnter: ActionEvent; - private _onLeave: ActionEvent; + private _currentState: StateType; + private _onEnter?: ActionEvent; + private _onLeave?: ActionEvent; private _eventsByState: Record any>> = {}; - private _statesByState: Record = {}; + private _statesByState: Record = {}; constructor(initial: StateType, config: IConfig) { - this._currentState = initial.toString(); + this._currentState = initial; - for (let fromState in config) { - if (['onEnter', 'onLeave'].includes(fromState)) { + for (let fromStateKey in config) { + if (['onEnter', 'onLeave'].includes(fromStateKey)) { this._onEnter = config.onEnter; continue } - this._statesByState[fromState] = []; + this._statesByState[fromStateKey] = []; - let actions = config[fromState] as ActionConfigMap; + let actions = config[fromStateKey] as ActionConfigMap; for (let actionName in actions) { let action = actions[actionName]; let actionConfig: IActionConfig = action.constructor === Object ? action as IActionConfig : { state: action as StateType }; - this._statesByState[fromState].push(actionConfig.state.toString()); - this._initChangeState(actionName, fromState, actionConfig.state.toString(), actionConfig); + this._statesByState[fromStateKey].push(actionConfig.state); + + let fromState: StateType = /^\d+$/.test(fromStateKey) ? parseInt(fromStateKey, 10) : fromStateKey; + this._initChangeState(actionName, fromState, actionConfig.state, actionConfig); } } } - private _initChangeState(eventName: string, fromState: string, toState: string, 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; - - this._eventsByState[fromState][eventName] = async (payload: Payload = {}) => { - console.log(this._currentState, typeof this._currentState, fromState, typeof fromState); - if (this._currentState !== fromState) { - return; + const _runEvent = async (method?: ActionEvent, payload: Payload = {}): Promise => { + if (method) { + await method(eventName, fromState, toState, payload); } + }; - this._onEnter && await this._onEnter(eventName, fromState, toState, payload); - onBeforeChange && await onBeforeChange(eventName, fromState, toState, payload); + this._eventsByState[fromState][eventName] = async (sourcePayload: Payload = {}) => { + const payload = cloneDeep(sourcePayload); + await _runEvent(this._onEnter, payload); + await _runEvent(onBeforeChange, payload); this._currentState = toState; - onChange && await onChange(eventName, fromState, toState, payload); - this._onLeave && await this._onLeave(eventName, fromState, toState, payload) + await _runEvent(onChange, payload); + await _runEvent(this._onLeave, payload); return this; }; @@ -81,13 +85,11 @@ export class StateMachine { && this._eventsByState[this._currentState][eventName]) { return this._eventsByState[this._currentState][eventName](payload); } - - console.log(this._eventsByState, this._currentState, eventName) } } } - public getCurrentState(): string { + public getCurrentState(): StateType { return this._currentState; } @@ -100,7 +102,7 @@ export class StateMachine { return Object.keys(events).includes(eventName); } - public canToState(stateName: string) { + public canToState(stateName: StateType) { const states = this._statesByState[this._currentState]; if (!states) { return false; diff --git a/tsconfig.json b/tsconfig.json index 9c30319..870495a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,6 +16,7 @@ "noImplicitThis": true, "noUnusedParameters": true, "strictNullChecks": true, + "esModuleInterop": true, "outDir": "./src" }, "exclude": [