Created
November 13, 2017 22:58
-
-
Save pveglia/aa9d22573f147d6d6dce259fed23adc2 to your computer and use it in GitHub Desktop.
State machine in typescript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {State, StateMachine, Transition} from './state-machine'; | |
console.log('hey'); | |
const stateMachine = new StateMachine( | |
{ | |
'initial': { | |
onEnter: (input: any) => { | |
console.log('entering initial'); | |
input.state = 'initial'; | |
return input; | |
}, | |
onExit: (input: any): any => { | |
console.log('exiting initial'); | |
delete input.state; | |
}, | |
transitions: { | |
'next': { | |
action: () => { | |
console.log('action of next transition'); | |
}, | |
nextState: 'second' | |
} | |
} | |
}, | |
'second': { | |
transitions: { | |
'next': { | |
nextState: () => 'third' | |
}, | |
'back': { | |
nextState: 'initial' | |
} | |
} | |
}, | |
'third': { | |
transitions: { | |
'back': { | |
nextState: 'second' | |
} | |
} | |
} | |
}, | |
'initial', {}); | |
stateMachine.events.subscribe(event => { | |
console.log('event emitted', event); | |
}) | |
console.log('next state:', stateMachine.handle('next')); | |
stateMachine.handle('next'); | |
stateMachine.handle('back'); | |
stateMachine.handle('back'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Subject} from 'rxjs/Subject'; | |
type Transitioner = (context?: any) => any; | |
export enum EventType { | |
Event = 'event', | |
Transition = 'transition' | |
} | |
export interface State { | |
// name: string; | |
onEnter?: Transitioner, onExit?: Transitioner, | |
transitions: {[event_name: string]: Transition} | |
} | |
export interface Transition { | |
action?: Transitioner, nextState: (() => string)|string | |
} | |
export interface EventRecord { | |
type: EventType.Event; | |
value: string | |
} | |
export interface TransitionRecord { | |
type: EventType.Transition; | |
oldState?: string, newState: string, | |
context: any | |
} | |
export class StateMachine { | |
private eventEmitter = new Subject<EventRecord|TransitionRecord>(); | |
public events = this.eventEmitter.asObservable(); | |
private currentState: State; | |
private currentStateName: string; | |
private context: any; | |
constructor( | |
private configuration: {[state_name: string]: State}, | |
initialState: string, initialContext: any) { | |
this.currentState = this.configuration[initialState]; | |
this.currentStateName = initialState; | |
this.context = initialContext; | |
if (this.currentState) { | |
this.updateContext(this.currentState.onEnter); | |
} | |
} | |
private updateContext(fn?: Transitioner) { | |
if (fn) { | |
const result = fn(this.context); | |
if (result !== undefined) { | |
this.context = result; | |
} | |
} | |
} | |
handle(event: string): string { | |
this.eventEmitter.next({type: EventType.Event, value: event}); | |
if (this.currentState && this.currentState.transitions[event]) { | |
const transition = this.currentState.transitions[event]; | |
this.updateContext(this.currentState.onExit); | |
this.updateContext(transition.action); | |
const nextState = this.resolveNextState(transition.nextState); | |
if (this.configuration[nextState] === undefined) { | |
throw new Error(`State "${nextState}" is not configured`); | |
} | |
this.eventEmitter.next({ | |
type: EventType.Transition, | |
oldState: this.currentStateName, | |
newState: nextState, context: this.context | |
}); | |
this.currentState = this.configuration[nextState]; | |
this.currentStateName = nextState; | |
this.updateContext(this.currentState.onEnter); | |
return nextState; | |
} else { | |
throw new Error('Could not transition to new state'); | |
} | |
} | |
resolveNextState(nextState: string|((context: any) => string)): string { | |
if (typeof nextState === 'string') { | |
return nextState; | |
} | |
if (typeof nextState === 'function') { | |
return nextState(this.context); | |
} | |
throw new Error('Cannot resolve next state'); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment