Compare commits
5 commits
8f3fd1337b
...
e4ef1a7c9f
Author | SHA1 | Date | |
---|---|---|---|
e4ef1a7c9f | |||
269291af04 | |||
9d14ed5e84 | |||
a91bc95bd5 | |||
b6d66fd906 |
4 changed files with 84 additions and 58 deletions
66
README.md
66
README.md
|
@ -3,50 +3,42 @@
|
|||
[](https://github.com/icetemple/it-fsm/actions/workflows/ci.yml)
|
||||
[](https://coveralls.io/github/icetemple/it-fsm?branch=master)
|
||||
|
||||
Simple finite state machine
|
||||
Simple full-featured finite state machine for your project
|
||||
|
||||
### Installation
|
||||
### Why it-fsm?
|
||||
|
||||
`npm install --save it-fsm`
|
||||
- 🚯 333 LOC - 0 dependencies
|
||||
- 🍒 Sophisticated object-oriented design
|
||||
|
||||
### Usage
|
||||
|
||||
### Getting started
|
||||
|
||||
```ts
|
||||
import { StateMachineBuilder } from "it-fsm";
|
||||
|
||||
enum ProjectStatus {
|
||||
Pending = "pending",
|
||||
Active = "active",
|
||||
Completed = "completed",
|
||||
Archived = "archive",
|
||||
}
|
||||
const [locked, unlocked] = ['locked', 'unlocked'] as const;
|
||||
|
||||
const smbProject = new StateMachineBuilder()
|
||||
.withStates(Object.values(ProjectStatus))
|
||||
const sm = new StateMachineBuilder()
|
||||
.withStates([locked, unlocked])
|
||||
.withTransitions([
|
||||
[ProjectStatus.Pending, [ProjectStatus.Active, ProjectStatus.Archived]],
|
||||
[ProjectStatus.Active, [ProjectStatus.Completed]],
|
||||
]);
|
||||
|
||||
async function main() {
|
||||
const project1 = { id: 1, status: ProjectStatus.Pending };
|
||||
const project2 = { id: 2, status: ProjectStatus.Completed };
|
||||
|
||||
// Build FSM with current project status
|
||||
const smForProject1 = smbProject.build(project1.status);
|
||||
const smForProject2 = smbProject.build(project2.status);
|
||||
|
||||
console.log(smForProject2.allowedTransitionStates()); // []
|
||||
|
||||
console.log(smForProject1.allowedTransitionStates()); // [active, archived]
|
||||
await smForProject1.changeState(ProjectStatus.Active);
|
||||
|
||||
console.log(smForProject1.allowedTransitionStates()); // [completed]
|
||||
await smForProject1.changeState(ProjectStatus.Completed);
|
||||
|
||||
console.log(smForProject1.allowedTransitionStates()); // []
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
[locked, { coin: unlocked }],
|
||||
[unlocked, { push: locked }],
|
||||
])
|
||||
.build(locked);
|
||||
```
|
||||
|
||||
or with deno
|
||||
|
||||
```ts
|
||||
import { StateMachineBuilder } from "https://raw.githubusercontent.com/icetemple/it-fsm/master/fsm.ts";
|
||||
|
||||
// ...
|
||||
```
|
||||
|
||||
You can find the full example in the examples folder.
|
||||
|
||||
### Install
|
||||
|
||||
If you want to use it in Node.js or the browser, you may need to install it as follows
|
||||
|
||||
`npm install --save it-fsm`
|
||||
|
|
30
examples/turnstile.ts
Normal file
30
examples/turnstile.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { StateMachineBuilder } from "../fsm.ts";
|
||||
|
||||
const [locked, unlocked] = ["locked", "unlocked"] as const;
|
||||
|
||||
const smbTurnstile = new StateMachineBuilder()
|
||||
.withStates([locked, unlocked])
|
||||
.withTransitions([
|
||||
[locked, { coin: unlocked }],
|
||||
[unlocked, { push: locked }],
|
||||
]);
|
||||
|
||||
async function main() {
|
||||
const sm = smbTurnstile.build(locked);
|
||||
|
||||
function logCurrentState() {
|
||||
console.log("current state", JSON.stringify(sm.currentState.name));
|
||||
}
|
||||
|
||||
logCurrentState();
|
||||
await sm.trigger("coin", {});
|
||||
logCurrentState();
|
||||
await sm.trigger("push", {});
|
||||
logCurrentState();
|
||||
await sm.trigger("push", {});
|
||||
logCurrentState();
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
main();
|
||||
}
|
44
fsm.ts
44
fsm.ts
|
@ -1,23 +1,23 @@
|
|||
type StateTransitions<Ctx, SN extends string> = WeakMap<
|
||||
export type StateTransitions<Ctx, SN extends string> = WeakMap<
|
||||
State<Ctx, SN>,
|
||||
WeakSet<State<Ctx, SN>>
|
||||
>;
|
||||
|
||||
type StateOrName<Ctx, SN extends string> =
|
||||
export type StateOrName<Ctx, SN extends string> =
|
||||
| State<Ctx, SN>
|
||||
| SN;
|
||||
|
||||
type SourceTransitions<SN extends string> = Array<[SN, Array<SN>]>;
|
||||
type SourceNamedTransitions<SN extends string> = Array<
|
||||
export type SourceTransitions<SN extends string> = Array<[SN, Array<SN>]>;
|
||||
export type SourceNamedTransitions<SN extends string> = Array<
|
||||
[SN, Record<string, SN>]
|
||||
>;
|
||||
type SourceActions<SN extends string> = Record<string, Array<[SN, SN]>>;
|
||||
export type SourceActions<SN extends string> = Record<string, Array<[SN, SN]>>;
|
||||
|
||||
export const _states = Symbol("states");
|
||||
const _transitions = Symbol("transitions");
|
||||
const _actions = Symbol("actions");
|
||||
const _prevState = Symbol("previous state");
|
||||
const _currState = Symbol("current state");
|
||||
export const _transitions = Symbol("transitions");
|
||||
export const _actions = Symbol("actions");
|
||||
export const _prevState = Symbol("previous state");
|
||||
export const _currState = Symbol("current state");
|
||||
|
||||
export class StateMachineBuilder<Ctx, SN extends string = string> {
|
||||
[_states]: Map<SN, Events<Ctx, SN>>;
|
||||
|
@ -123,12 +123,12 @@ export class StateMachineBuilder<Ctx, SN extends string = string> {
|
|||
}
|
||||
}
|
||||
|
||||
interface StateMachineOpts<Ctx, SN extends string> {
|
||||
export interface StateMachineOpts<Ctx, SN extends string> {
|
||||
transitions?: StateTransitions<Ctx, SN>;
|
||||
actions?: Actions<Ctx, SN>;
|
||||
}
|
||||
|
||||
type Actions<Ctx, SN extends string> = Map<
|
||||
export type Actions<Ctx, SN extends string> = Map<
|
||||
string,
|
||||
Array<[State<Ctx, SN>, State<Ctx, SN>]>
|
||||
>;
|
||||
|
@ -219,10 +219,10 @@ export class StateMachine<Ctx, SN extends string = string> {
|
|||
}
|
||||
}
|
||||
|
||||
const _stateName = Symbol("state name");
|
||||
const _stateEvents = Symbol("state events");
|
||||
export const _stateName = Symbol("state name");
|
||||
export const _stateEvents = Symbol("state events");
|
||||
|
||||
interface Events<Ctx, SN extends string> {
|
||||
export interface Events<Ctx, SN extends string> {
|
||||
beforeExit?(
|
||||
fromState: State<Ctx, SN>,
|
||||
toState: State<Ctx, SN>,
|
||||
|
@ -274,42 +274,46 @@ export class State<Ctx, SN extends string = string> {
|
|||
}
|
||||
}
|
||||
|
||||
function validNormalizedState<Ctx, SN extends string>(
|
||||
export function validNormalizedState<Ctx, SN extends string>(
|
||||
states: State<Ctx, SN>[],
|
||||
state: StateOrName<Ctx, SN>,
|
||||
) {
|
||||
return validState<Ctx, SN>(normalizeState(states, state));
|
||||
}
|
||||
|
||||
function normalizeState<Ctx, SN extends string>(
|
||||
export function normalizeState<Ctx, SN extends string>(
|
||||
states: State<Ctx, SN>[],
|
||||
state: StateOrName<Ctx, SN>,
|
||||
): State<Ctx, SN> | undefined {
|
||||
return isStr(state) ? stateFromName(states, state) : state;
|
||||
}
|
||||
|
||||
function validStateFromName<Ctx, SN extends string>(
|
||||
export function validStateFromName<Ctx, SN extends string>(
|
||||
states: State<Ctx, SN>[],
|
||||
name: SN,
|
||||
) {
|
||||
return validState<Ctx, SN>(stateFromName(states, name));
|
||||
}
|
||||
|
||||
function stateFromName<Ctx, SN extends string>(
|
||||
export function stateFromName<Ctx, SN extends string>(
|
||||
states: State<Ctx, SN>[],
|
||||
name: SN,
|
||||
) {
|
||||
return states.find((state) => state.name === name);
|
||||
}
|
||||
|
||||
function validState<Ctx, SN extends string>(val: unknown): State<Ctx, SN> {
|
||||
export function validState<Ctx, SN extends string>(
|
||||
val: unknown,
|
||||
): State<Ctx, SN> {
|
||||
if (!isState<Ctx, SN>(val)) {
|
||||
throw new TypeError("an instance of State class is expected");
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
function isState<Ctx, SN extends string>(val: unknown): val is State<Ctx, SN> {
|
||||
export function isState<Ctx, SN extends string>(
|
||||
val: unknown,
|
||||
): val is State<Ctx, SN> {
|
||||
return val instanceof State;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": false,
|
||||
"lib": ["es7"],
|
||||
"lib": ["es2017"],
|
||||
"declaration": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
|
|
Loading…
Add table
Reference in a new issue