Skip to content

Commit

Permalink
feat: improved readme for v.0.0.2
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Salaverry committed Aug 2, 2019
1 parent 7192c8f commit 817c7c3
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 99 deletions.
56 changes: 38 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,55 @@
[![Build Status](https://travis-ci.org/{{github-user-name}}/{{github-app-name}}.svg?branch=master)](https://travis-ci.org/{{github-user-name}}/{{github-app-name}}.svg?branch=master)
[![Coverage Status](https://coveralls.io/repos/github/{{github-user-name}}/{{github-app-name}}/badge.svg?branch=master)](https://coveralls.io/github/{{github-user-name}}/{{github-app-name}}?branch=master)
[![Build Status](https://travis-ci.org/barakplasma/finite-state-machine.svg?branch=master)](https://travis-ci.org/barakplasma/finite-state-machine.svg?branch=master)
[![Coverage Status](https://coveralls.io/repos/github/barakplasma/finite-state-machine/badge.svg?branch=master)](https://coveralls.io/github/barakplasma/finite-state-machine?branch=master)
[![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT)

# Using this module in other modules
# Finite State Machine
Tiny finite state machine library for fun and profit.
See the tests for more usage examples.

## Using this module in other modules

Here is a quick example of how this module can be used in other modules. The [TypeScript Module Resolution Logic](https://www.typescriptlang.org/docs/handbook/module-resolution.html) makes it quite easy. The file `src/index.ts` is a [barrel](https://basarat.gitbooks.io/typescript/content/docs/tips/barrel.html) that re-exports selected exports from other files. The _package.json_ file contains `main` attribute that points to the generated `lib/index.js` file and `typings` attribute that points to the generated `lib/index.d.ts` file.

> If you are planning to have code in multiple files (which is quite natural for a NodeJS module) that users can import, make sure you update `src/index.ts` file appropriately.
- To use the `FSM` class in a TypeScript file -

Now assuming you have published this amazing module to _npm_ with the name `my-amazing-lib`, and installed it in the module in which you need it -
```ts
import { FSM } from "@barakplasma/finite-state-machine";

- To use the `Greeter` class in a TypeScript file -
const anFSM = new FSM();

```ts
import { Greeter } from "my-amazing-lib";
anFSM.addState('on');
anFSM.addState('off');

anFSM.on({ inputName: 'toggle' }, () => {
return new Map([['on', 'off'], ['off', 'on']]);
});

const { dispatch } = anFSM;

dispatch({ inputName: 'toggle' });

const greeter = new Greeter("World!");
greeter.greet();
anFSM.getCurrentState() // 'off'
```

- To use the `Greeter` class in a JavaScript file -
- To use the `FSM` class in a JavaScript file -

```js
const Greeter = require('my-amazing-lib').Greeter;
const FSM = require('my-amazing-lib').FSM;

const anFSM = new FSM();

anFSM.addState('on');
anFSM.addState('off');

anFSM.on({ inputName: 'toggle' }, () => {
return new Map([['on', 'off'], ['off', 'on']]);
});

const { dispatch } = anFSM;

dispatch({ inputName: 'toggle' });

const greeter = new Greeter('World!');
greeter.greet();
anFSM.getCurrentState() // 'off'
```

## Setting travis and coveralls badges
1. Sign in to [travis](https://travis-ci.org/) and activate the build for your project.
2. Sign in to [coveralls](https://coveralls.io/) and activate the build for your project.
3. Replace {{github-user-name}}/{{github-app-name}} with your repo details like: "ospatil/generator-node-typescript".
# finite-state-machine
114 changes: 55 additions & 59 deletions __tests__/basics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,30 +34,25 @@ describe('Finite State Machine', () => {
// Action
anFSM.addState('on');
// we're missing the 'off' state
const shouldThrow = () => anFSM.on({inputName: 'toggle'}, () => {
return new Map([
['on','off'],
['off', 'on'],
])
})
const shouldThrow = () =>
anFSM.on({ inputName: 'toggle' }, () => {
return new Map([['on', 'off'], ['off', 'on']]);
});
// Assert
expect(shouldThrow).toThrow('unknown state in transition handler')
expect(shouldThrow).toThrow('unknown state in transition handler');
});
it('should allow transitioning between states', () => {
// Arrange
const anFSM = new FSM();
// Action
anFSM.addState('on');
anFSM.addState('off');
anFSM.on({inputName: 'toggle'}, () => {
return new Map([
['on','off'],
['off', 'on'],
])
})
anFSM.on({ inputName: 'toggle' }, () => {
return new Map([['on', 'off'], ['off', 'on']]);
});
const { dispatch } = anFSM;
// Action
dispatch({inputName: 'toggle'});
dispatch({ inputName: 'toggle' });
// Assert
expect(anFSM.getCurrentState()).toEqual('off');
});
Expand All @@ -67,33 +62,27 @@ describe('Finite State Machine', () => {
// Action
fuse.addState('closed');
fuse.addState('open');
fuse.on({inputName: 'powerSurge'}, (payload) => {
fuse.on({ inputName: 'powerSurge' }, payload => {
if (payload > 100) {
return new Map([
['closed', 'open'],
['open', 'open'],
]);
return new Map([['closed', 'open'], ['open', 'open']]);
}
return new Map([
['closed', 'closed'],
['open', 'open'],
])
})
return new Map([['closed', 'closed'], ['open', 'open']]);
});
const { dispatch } = fuse;
// Action
dispatch({inputName: 'powerSurge', payload: 99});
dispatch({ inputName: 'powerSurge', payload: 99 });
// Assert
expect(fuse.getCurrentState()).toEqual('closed');
// Action
dispatch({inputName: 'powerSurge', payload: 99});
dispatch({ inputName: 'powerSurge', payload: 99 });
// Assert
expect(fuse.getCurrentState()).toEqual('closed');
// Action
dispatch({inputName: 'powerSurge', payload: 101});
dispatch({ inputName: 'powerSurge', payload: 101 });
// Assert
expect(fuse.getCurrentState()).toEqual('open');
// Action
dispatch({inputName: 'powerSurge', payload: 99});
dispatch({ inputName: 'powerSurge', payload: 99 });
// Assert
expect(fuse.getCurrentState()).toEqual('open');
});
Expand All @@ -102,31 +91,31 @@ describe('Finite State Machine', () => {
const enum TrafficLightColors {
'red' = 'red',
'yellow' = 'yellow',
'green' = 'green'
'green' = 'green',
}
const TrafficLightFactory = () => {
const TrafficLight = new FSM();

TrafficLight.addState(TrafficLightColors.red);
TrafficLight.addState(TrafficLightColors.yellow);
TrafficLight.addState(TrafficLightColors.green);
TrafficLight.on({inputName: 'turnExpired'}, () => {
TrafficLight.on({ inputName: 'turnExpired' }, () => {
return new Map([
[TrafficLightColors.green, TrafficLightColors.yellow],
[TrafficLightColors.yellow, TrafficLightColors.red],
[TrafficLightColors.red, TrafficLightColors.green],
])
]);
});
return TrafficLight;
}
};

const NSTrafficLight = TrafficLightFactory();
const EWTrafficLight = TrafficLightFactory();
// should start the same
expect(NSTrafficLight.getCurrentState()).toEqual(TrafficLightColors.red);
expect(EWTrafficLight.getCurrentState()).toEqual(TrafficLightColors.red);
// changing one shouldn't change the other
NSTrafficLight.dispatch({inputName: 'turnExpired'});
NSTrafficLight.dispatch({ inputName: 'turnExpired' });
expect(NSTrafficLight.getCurrentState()).toEqual(TrafficLightColors.green);
expect(EWTrafficLight.getCurrentState()).toEqual(TrafficLightColors.red);
// let's connect one to the other using a third object
Expand All @@ -143,36 +132,43 @@ describe('Finite State Machine', () => {
this.mediatorFSM.addState(trafficStates.trafficMoving);
this.mediatorFSM.addState(trafficStates.trafficStopping);
this.mediatorFSM.addState(trafficStates.allTrafficStopped);
this.mediatorFSM.on({inputName: 'tick'}, (payload: TrafficLightColors[] | undefined) => {
if (payload) {
const isTrafficMoving = () => payload.find(tl => tl === TrafficLightColors.green) !== undefined;
this.mediatorFSM.on(
{ inputName: 'tick' },
(payload: TrafficLightColors[] | undefined) => {
if (payload) {
const isTrafficMoving = () =>
payload.find(tl => tl === TrafficLightColors.green) !==
undefined;

switch (true) {
case isTrafficMoving():
const movingTL = this.trafficLights.find(tl =>
tl.getCurrentState() === TrafficLightColors.green
)
if (movingTL) {
movingTL.dispatch({inputName: 'turnExpired'})
switch (true) {
case isTrafficMoving():
const movingTL = this.trafficLights.find(
tl => tl.getCurrentState() === TrafficLightColors.green
);
if (movingTL) {
movingTL.dispatch({ inputName: 'turnExpired' });
}
break;
default:
this.trafficLights.forEach(tl =>
tl.dispatch({ inputName: 'turnExpired' })
);
break;
}
break;
default:
this.trafficLights.forEach(tl => tl.dispatch({inputName: 'turnExpired'}))
break;
}
return new Map([
[trafficStates.allTrafficStopped, trafficStates.trafficMoving],
[trafficStates.trafficMoving, trafficStates.trafficStopping],
[trafficStates.trafficStopping, trafficStates.allTrafficStopped],
]);
}
}
return new Map([
[trafficStates.allTrafficStopped, trafficStates.trafficMoving],
[trafficStates.trafficMoving, trafficStates.trafficStopping],
[trafficStates.trafficStopping, trafficStates.allTrafficStopped],
])
})
);
}
turnExpired = () => {
const payload = this.trafficLights.map(tl => tl.getCurrentState());
this.mediatorFSM.dispatch({inputName: 'tick', payload});
}
};
this.mediatorFSM.dispatch({ inputName: 'tick', payload });
};
}
const TLM = new TrafficLightMediator([NSTrafficLight, EWTrafficLight]);
// Action start stopping traffic
TLM.turnExpired();
Expand All @@ -194,5 +190,5 @@ describe('Finite State Machine', () => {
// Assert end of fourth turn
expect(NSTrafficLight.getCurrentState()).toEqual(TrafficLightColors.green);
expect(EWTrafficLight.getCurrentState()).toEqual(TrafficLightColors.red);
})
});
});
25 changes: 15 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
{
"name": "finite-state-machine",
"version": "0.0.0",
"name": "@barakplasma/finite-state-machine",
"version": "0.0.1",
"description": "finite-state-machine",
"license": "MIT",
"repository": "",
"author": {
"name": "Michael Salaverry",
"url": "https://github.com/barakplasma"
"repository": {
"type": "git",
"url": "git+https://github.com/barakplasma/finite-state-machine.git"
},
"keywords": [
""
],
"author": "Michael Salaverry (https://github.com/barakplasma)",
"keywords": [],
"files": [
"lib"
],
Expand Down Expand Up @@ -47,5 +45,12 @@
},
"jest": {
"preset": "ts-jest"
}
},
"directories": {
"lib": "lib"
},
"bugs": {
"url": "https://github.com/barakplasma/finite-state-machine/issues"
},
"homepage": "https://github.com/barakplasma/finite-state-machine#readme"
}
28 changes: 16 additions & 12 deletions src/FSM.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
interface IInput {
inputName: string
payload?: any
inputName: string;
payload?: any;
}

interface TransitionHandler {
(payload?: any): Map<string, string>
};
(payload?: any): Map<string, string>;
}

export class FSM {
private currentState: string = '';
Expand All @@ -17,35 +17,39 @@ export class FSM {
this.currentState = state;
}
this.states.set(state, state);
}
};

private checkThatAllTransitionsAreValid = (transitionHandler: TransitionHandler) => {
private checkThatAllTransitionsAreValid = (
transitionHandler: TransitionHandler
) => {
const defaultTransitions = transitionHandler();
const possibleStates = [...defaultTransitions.keys()];
possibleStates.forEach(state => {
if (!this.states.has(state)) {
throw new Error('unknown state in transition handler')
throw new Error('unknown state in transition handler');
}
});
}
};

public on = (input: IInput, transitionHandler: TransitionHandler) => {
this.checkThatAllTransitionsAreValid(transitionHandler);
this.transitions.set(input.inputName, transitionHandler);
}
};

private missingTransitionHandlerError = (_?: any) => new Map();

public dispatch = (input: IInput) => {
const transitionHandler = this.transitions.get(input.inputName) || this.missingTransitionHandlerError;
const transitionHandler =
this.transitions.get(input.inputName) ||
this.missingTransitionHandlerError;
const newState = transitionHandler(input.payload).get(this.currentState);
if (!this.states.has(newState)) {
throw new Error('unknown new current state');
}
this.currentState = newState;
}
};

public getCurrentState = (): string => {
return this.currentState;
}
};
}

0 comments on commit 817c7c3

Please sign in to comment.