• Stars
    star
    119
  • Rank 297,930 (Top 6 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created over 6 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Table based 📦 Finite State Machine 🤖

🤖 Faste 💡

TypeScript centric Table Finite State Machine
faste

Build status Greenkeeper badge


no dependencies, in around 2kb

  • 👨‍🔬 a bit less state-ish than xstate
  • 🤖 way more state-full than anything else in your code
  • 🧠 made for everything in between
  • 🖥 it does not have Visualizer, but it's VERY TypeScript centric

State machine is a blackbox you can 1) .start 2) .put some events in and 3) observe how its working.

Internally it will be a machine working in different "states", which called phases here (water/ice) recieving different events and doing something...

Core concepts are:

  • phases: different modes your machine can be in
  • state: internal state machine sets and controls itself
  • attributes: external configuration of machine
  • messages: events it can receive from the outside or send to itself
  • signals: events it can send to the outer world
  • timers: in a very declarative form
  • hooks: callbacks which are executed when machine start or stop handling a given message
  • guards and traps: protectors from entering or leaving some states

In react world attributes are props. In xstate world state is context

State machine

State machine starts in one phase, calls hooks for all messages for the current phase, then awaits for a messages from hooks or external customer, then could trigger a new message, emit signal to the outer world or change the current phase.

Faste is a black box - you can put message inside, and wait for a signal it will sent outside, meanwhile observing a box phase. Black📦 == Component🎁.

📖 Read an article about FASTE, and when to use it.

Example

const light = faste()
  // define possible "phases" of a traffic light
  .withPhases(['red', 'yellow', 'green'])
  // define possible transitions from one phase to another
  .withTransitions({
    green: ['yellow'],
    yellow: ['red'],
    red: ['green'],
  })
  // define possible events for a machine
  .withMessages(['switch'])
  .on('switch', ['green'], ({ transitTo }) => transitTo('yellow'))
  .on('switch', ['yellow'], ({ transitTo }) => transitTo('red'))
  .on('switch', ['red'], ({ transitTo }) => transitTo('green'))
  // ⚠️ the following line would throw an error at _compile time_
  .on('switch', ['green'], ({ transitTo }) => transitTo('red')) // this transition is blocked

  // block transition TO green if any pedestrian is on the road
  .guard(['green'], () => noPedestriansOnTheRoad)
  // block transition FROM red if any pedestrian is on the road
  .trap(['red'], () => !noPedestriansOnTheRoad);
// PS: noPedestriansOnTheRoad could be read from attr, passed from a higher state machine.

API

Machine blueprint

faste(options) - defines a new faste machine every faste instance provide next chainable commands

  • on(eventName, [phases], callback) - set a hook callback for eventName message in states states.

  • hooks(hooks) - set a hook when some message begins, or ends its presence.

  • guard(phases, callback) - add a transition guard, prevention transition to the phase

  • trap(phases, callback) - add a transition guard, prevention transition from the phase

In development mode, and for typed languages you could use next commands

  • withState(state) - set a initial state (use @init hook to derive state from props).

  • withPhases(phases) - limit phases to provided set.

  • withTimers(timersConfuguration) - configures timers

  • withTransitions([phases]:[phases]) - limit phase transitions

  • withMessages(messages) - limit messages to provided set.

  • withAttrs(attributes) - limit attributes to provided set.

  • withSignals(signals) - limit signals to provided set.

  • withMessageArguments<MessageConfiguration>() - enabled arguments for messages

  • withSignalArguments<SignalConfiguration>() - enabled arguments for signals

  • create() - creates a machine (copies existing, use it instead of new).

All methods returns a faste constructor itself.

Machine instance

Each instance of Faste will have:

  • attrs(attrs) - set attributes.

  • put - put message in

  • connect - connects output to the destination

  • observe - observes phase changes

  • phase - returns the current phase

  • instance - returns the current internal state.

  • destroy - exits the current state, terminates all hooks, and stops machine.

  • namedBy(string) - sets name of the instance (for debug).

For all callbacks the first argument is flow instance, containing.

  • attrs - all the attrs, you cannot change them

  • state - internal state

  • setState - internal state change command

  • phase - current phase

  • transitTo - phase change command.

  • startTimer(timerName) - starts a Timer

  • stopTimer(timerName) - stops a Timer

  • emit - emits a message to the outer world

Magic events

  • @init - on initialization
  • @enter - on phase enter, last phase will be passed as a second arg.
  • @leave - on phase enter, new phase will be passed as a second arg.
  • @change - on state change, old state will be passed as a second arg.
  • @miss - on event without handler
  • @error - an error handler. If no errors handler will be found the real error will be thrown

Magic phases

  • @current - set the same phase as it was on the handler entry
  • @busy - set the busy phase, when no other handler could be called

Hooks

Hook activates when message starts or ends it existence, ie when there is on callback defined for it.

Event bus

  • message handler could change phase, state and trigger a new message
  • hook could change state or trigger a new message, but not change phase
  • external consumer could only trigger a new message

InternalState

Each on or hook handler will receive internalState as a first argument, with following shape

 attrs: { ...
    AttributesYouSet
}
;  // attributes
state: { ..
    CurrentState
}
;       // state

setState(newState);              // setState (as seen in React)

transitTo(phase);                // move to a new phase

emit(message, ...args);          // emit Signal to the outer world (connected)

trigger(event, ...args);         // trigger own message handler (dispatch an internal action)

Debug

Debug mode is integrated into Faste.

import {setFasteDebug} from 'faste'

setFasteDebug(true);
setFasteDebug(true);
setFasteDebug((instance, message, args) => console.log(...));

Examples

Try online : https://codesandbox.io/s/n7kv9081xp

Using different handlers in different states

// common code - invert the flag
onClick = () => this.setState((state) => ({ enabled: !state.enabled }));

// faste - use different flags for different states
faste()
  .on('click', 'disabled', ({ transitTo }) => transitTo('enabled'))
  .on('click', 'enabled', ({ transitTo }) => transitTo('disabled'));

React to state change

// common code - try to reverse engineer the change
componentDidUpdate(oldProps)
{
    if (oldProps.enabled !== this.props.enabled) {
        if (this.props.enabled) {
            // I was enabled!
        } else {
            // I was disabled!
        }
    }
}

// faste - use "magic" methods
faste()
    .on('@enter', ['disabled'], () => /* i was disabled */)
    .on('@enter', ['enabled'], () => /* i was enabled */)
    // or
    .on('@leave', ['disabled'], () => /* i am no more disabled */)
    .on('@leave', ['enabled'], () => /* i am no more enabled */)

Connected states

https://codesandbox.io/s/5zx8zl91ll

// starts a timer when active
const SignalSource = faste()
  .on('@enter', ['active'], ({ setState, attrs, emit }) =>
    setState({ interval: setInterval(() => emit('message'), attrs.duration) })
  )
  .on('@leave', ['active'], ({ state }) => clearInterval(state.interval));

// responds to "message" by moving from tick to tock
// emiting the current state outside
const TickState = faste()
  // autoinit to "tick" mode
  .on('@init', ({ transitTo }) => transitTo('tick'))
  // message handlers
  .on('message', ['tick'], ({ transitTo }) => transitTo('tock'))
  .on('message', ['tock'], ({ transitTo }) => transitTo('tick'))
  .on('@leave', ({ emit }, newPhase) => emit('currentState', newPhase));

// just transfer message to attached node
const DisplayState = faste().on('currentState', ({ attrs }, message) => (attrs.node.innerHTML = message));

// create machines
const signalSource = SignalSource.create().attrs({
  duration: 1000,
});
const tickState = TickState.create();
const displayState = DisplayState.create().attrs({
  node: document.querySelector('.display'),
});

// direct connect signal source and ticker
signalSource.connect(tickState);

// "functionaly" connect tickes and display
tickState.connect((message, payload) => displayState.put(message, payload));

// RUN! start signal in active mode
signalSource.start('active');

Traffic light

const state = faste()
  .withPhases(['red', 'yellow', 'green'])
  .withMessages(['tick', 'next'])

  .on('tick', ['green'], ({ transit }) => transit('yellow'))
  .on('tick', ['yellow'], ({ transit }) => transit('red'))
  .on('tick', ['red'], ({ transit }) => transit('green'))

  // on 'next' trigger 'tick' for a better debugging.
  // just rethrow event
  .on('next', [], ({ trigger }) => trigger('tick'))

  // on "green" - start timer
  .on('@enter', ['green'], ({ setState, attrs, trigger }) =>
    setState({
      interval: setInterval(() => trigger('next'), attrs.duration),
    })
  )
  // on "red" - stop timer
  .on('@leave', ['red'], ({ state }) => clearInterval(state.interval))

  .check();

state.create().attrs({ duration: 1000 }).start('green');

Try online : https://codesandbox.io/s/n7kv9081xp

Draggable

const domHook =
  (eventName) =>
  ({ attrs, trigger }) => {
    const callback = (event) => trigger(eventName, event);
    attrs.node.addEventListener(eventName, callback);
    // "hook" could return anything, callback for example
    return () => {
      attrs.node.removeEventListener(eventName, hook);
    };
  };

const state = faste({})
  .on('@enter', ['active'], ({ emit }) => emit('start'))
  .on('@leave', ['active'], ({ emit }) => emit('end'))

  .on('mousedown', ['idle'], ({ transitTo }) => transitTo('active'))
  .on('mousemove', ['active'], (_, event) => emit('move', event))
  .on('mouseup', ['active'], ({ transitTo }) => transitTo('idle'));

hooks({
  mousedown: domHook('mousedown'),
  mousemove: domHook('mousemove'),
  mouseup: domHook('mouseup'),
})
  .check()

  .attr({ node: document.body })
  .start('idle');

Async

Message handler doesn't have to be sync. But managing async commands could be hard. But will not

  1. Accept command only in initial state, then transit to temporal state to prevent other commands to be executes.
const Login = faste().on('login', ['idle'], ({ transitTo }, { userName, password }) => {
  transitTo('logging-in'); // just transit to "other" state
  login(userName, password)
    .then(() => transitTo('logged'))
    .catch(() => transitTo('error'));
});
  1. Accept command only in initial state, then transit to execution state, and do the job on state enter
const Login = faste()
    .on('login', ['idle'], ({transitTo}, data) => transitTo('logging', data)
        .on('@enter', ['logging'], ({transitTo}, {userName, password}) => {
            login(userName, password)
                .then(() => transitTo('logged'))
                .catch(() => transitTo('error'))
        });
  1. Always accept command, but be "busy" while doing stuff
const Login = faste().on('login', ({ transitTo }, { userName, password }) => {
  transitTo('@busy'); // we are "busy"
  return login(userName, password)
    .then(() => transitTo('logged'))
    .catch(() => transitTo('error'));
});

handler returns Promise( could be async ) to indicate that ending in @busy state is not a mistake, and will not lead to deadlock.

By default @busy will queue messages, executing them after leaving busy phase. If want to ignore them - instead of @busy, you might use @locked phase, which will ignore them.

PS: You probably will never need those states.

Using timers to create timers

Plain variant

const SignalSource = faste()
  .on('@enter', ['active'], ({ setState, attrs, emit }) =>
    setState({ interval: setInterval(() => emit('message'), attrs.duration) })
  )
  .on('@leave', ['active'], ({ state }) => clearInterval(state.interval));

Hook and timer based

const SignalSource = faste()
  .on('tick', ['active'], ({ emit, startTimer }) => {
    emit('message');
  })
  .hook({
    tick: ({ attrs }) => {
      const interval = setInterval(() => emit('message'), attrs.duration);
      return () => clearInterval(interval);
    },
  });

Hook and timer based

const SignalSource = faste()
  .withTimers({
    T0: 1000,
  })
  .on('on_T0', ({ emit, startTimer }) => {
    emit('message');
    startTimer('T0'); //restarts timers
  })
  .on('@enter', ['active'], ({ startTimer }) => startTimer('T0'));

SDL and Block

Faste was born from this. From Q.931(EDSS) state definition.

How it starts. What signals it accepts. What it does next.

U17

That is quite simple diagram.

Thoughts

This is a Finite State Machine from SDL(Specification and Description Language) prospective. SDL defines state as a set of messages, it should react on, and the actions beneath.

Once state receives a message it executes an action, which could perform calculations and/or change the current state.

The goal is not to change the state, but - execute a bound action. From this prospective faste is closer to RxJX.

Usually "FSM" are more focused on state transitions, often even omitting any operations on message receive. In the Traffic Light example it could be useful, but in more real life examples - probably not.

Faste is more about when you will be able to do what. What you will do, when you receive event, and what you will do next.

Keeping in mind the best practices, like KISS and DRY, it is better to invert state->message->action connection, as long as actions are most complex part of it, and messages are usually reused across different states.

And, make things more common we will call "state" as a "phase", and "state" will be for "internal state".

The key idea is not about transition between states, but transition between behaviors. Keep in mind - if some handler is not defined in some state, and you are sending a message - it will be lost.

Written in TypeScript. To make things less flexible. Flow definitions as incomplete.

Prior art

This library combines ideas from xstate and redux-saga. The original idea is based on xflow state machine, developed for CT Company's VoIP solutions back in 2005.

Licence

MIT

More Repositories

1

react-focus-lock

It is a trap! A lock for a Focus. 🔓
JavaScript
1,092
star
2

react-imported-component

✂️📦Bundler-independent solution for SSR-friendly code-splitting
TypeScript
651
star
3

react-remove-scroll

Removes and disables 📜in a "React" way
TypeScript
611
star
4

rewiremock

The right way to mock dependencies in Node.js or webpack environment.
JavaScript
442
star
5

memoize-state

The magic memoization for the State management. ✨🧠
JavaScript
324
star
6

react-focus-on

🎯 Solution for WAI ARIA compatible modal dialogs or full-screen tasks, you were looking for
TypeScript
271
star
7

use-callback-ref

🤙The same useRef, but it will callback
TypeScript
265
star
8

devolution

🦎 -> 🦖A de-evolution gun for your bundle!
JavaScript
189
star
9

react-prerendered-component

🤔Partial hydration and caching in a pre-suspense era
TypeScript
164
star
10

react-locky

"🔒-y" – Asgardian God of Event Scoping 📦, Scroll Locking 📜, Silence Casting 🙊
JavaScript
145
star
11

focus-lock

Gotcha! A11y util for scoping a focus.
TypeScript
139
star
12

vue-focus-lock

It is a trap! A lock for a Focus. A11y util for scoping a focus.
Vue
135
star
13

react-memoize

🧠 React memoization library we all deserve
JavaScript
129
star
14

react-shallow-context

☘️A speed optimization for a Context API
TypeScript
119
star
15

used-styles

📝All the critical styles you've used to render a page.
TypeScript
115
star
16

use-sidecar

Another way to code splitting
TypeScript
94
star
17

stylelint-semantic-groups

Opinionated rule ordering
TypeScript
92
star
18

beautiful-react-redux

Redux 🚀, Redux 🤘, Redux 🔥 - and the magic optimization
JavaScript
89
star
19

recondition

🤷‍♂️ Declarative render prop based Conditions
TypeScript
77
star
20

proxyequal

There is a bit more smart way to compare things, than a shallow equal.
JavaScript
72
star
21

kashe

A memoization library based on weakmaps. 🤯 Sometimes cache is kashe
TypeScript
63
star
22

react-remove-scroll-bar

Remove document scroll bar. Nothing more
TypeScript
59
star
23

react-scroll-locky

📜🔒 – React full-cream "anti-scroll" library, you were looking for
TypeScript
56
star
24

restate

A redux fractal state library 🕷
JavaScript
55
star
25

dom-focus-lock

It's a Trap! A11y util for scoping a focus.
JavaScript
52
star
26

react-event-injector

💉React way to addEventListener
TypeScript
45
star
27

React-stroller

🚶‍♂️Scroll as you Stroll - the most native custom scrollbars ever made
TypeScript
45
star
28

react-remock

Get any Component replaced. Anywhere. 🛠♻️
TypeScript
44
star
29

multiple-entry-points-example

a npm library with multiple entry points, all typed, all shipped in cjs/esm versions
TypeScript
43
star
30

react-reflexible

🐍 Responsible solution in a flexible form.
TypeScript
43
star
31

aria-hidden

🗣Cast aria-hidden to everything, except...
TypeScript
42
star
32

why-did-you-update-redux

Patch Redux to discover unnecessary re-renders
JavaScript
41
star
33

jsx-compress-loader

⚛️JSX optimisation loader to reduce size of React application
JavaScript
40
star
34

holistic-image

Wholesome image management
TypeScript
37
star
35

theKashey

This is me! 👨‍🔧And all the things I've done. 🧙🏻‍♂️
36
star
36

eslint-plugin-relations

Controls relationships between folders and packages 👩‍💻🤝👮🏻‍♂️
TypeScript
32
star
37

react-gearbox

⚙️📦 Gearbox - Renderless state provisioning and composition
TypeScript
30
star
38

styled-components-mixins

Use popular frameworks with Styled Components
JavaScript
30
star
39

react-on-time

Renderless composable ⏰timers and ⏱intervals
TypeScript
27
star
40

runtime-compress-loader

babel and typescript "runtime" helpers optimization loader
JavaScript
23
star
41

react-queue

⛓ Declarative task scheduler
TypeScript
23
star
42

plimited

👽Promise-based Resource Pool
TypeScript
15
star
43

flow-meter

http(s/2) chunked response meter
TypeScript
14
star
44

react-style-singleton

Simple stylesheet manager for good libraries
TypeScript
14
star
45

use-callback-state

The same `useState`, but it will callback - https://codesandbox.io/s/use-callback-state-fcxtb
TypeScript
14
star
46

require-control

Get the full control over the nodejs module system.
JavaScript
14
star
47

webpack-imported

📝stats-webpack-plugin and 💩webpack-flush-chunks had a baby!
TypeScript
13
star
48

react-rename

Create a renamed duplicate for any Component. For the Fun, and Debug ( sure 🤞)
TypeScript
11
star
49

ts-referent

🤖 project references made not by humans
TypeScript
9
star
50

partial-mock

A solution for ⬆️over and ⬇️under mocking
TypeScript
9
star
51

storybook-include

Folder based storybook configuration for monorepos
TypeScript
9
star
52

react-nyan-stroller

🐈🏳️‍🌈🏳️‍🌈🏳️‍🌈 Nyanyanyanyanyanyanya! (In form of custom scroll bar) https://codesandbox.io/s/j2l71rrxw
TypeScript
9
star
53

quadLoader

загрузчик по пирамиде
8
star
54

react-imported-library

✂Code-split any library using renderProps!
TypeScript
8
star
55

yarn-unlock-file

🧶🔓 keeping things up to date.
TypeScript
8
star
56

read-story-later

Defer storybook's story execution.
JavaScript
7
star
57

react-push-channel

🎈Context is to drill props down. React Push Channel to drill props up.
TypeScript
7
star
58

useReselect

hooks friendly reselect
TypeScript
6
star
59

address-complete

What are you looking for?
JavaScript
6
star
60

idea-exclude

Some your .ideas better be excluded
TypeScript
6
star
61

search-trie

Yet another O(n) trie. This time just and only for string search.
TypeScript
6
star
62

jest-typed-mock

Be safe!. And use the force of TS/Flow to make mocks better.
JavaScript
6
star
63

tileLoader

тайловый загрузчик для yandex2
5
star
64

react-loadable-library

Code-split any library using renderProps!
TypeScript
5
star
65

react-svg-atlas

Combine, isolate, skyrocket the SVG
JavaScript
4
star
66

the-country-names

How to explain the world? Split it into pieces and name the each one.
JavaScript
4
star
67

wipeWebpackCache

JavaScript
3
star
68

css-to-js-loader

Transforms your CSS to the `real` JS
JavaScript
3
star
69

package-self

Place yourself in the right place.
JavaScript
3
star
70

postcss-to-js

A way to create javascript code from css
JavaScript
3
star
71

react-side-channel

To the RenderProps and back.
TypeScript
3
star
72

react-dom-reflection

🔨React meets DOM-API
TypeScript
3
star
73

with-known-usage

👁ES5/ES6 compatible object key usage tracker
TypeScript
3
star
74

proxyquire-webpack-alias

Modification of proxyquire to work with webpack aliases. Proxies commonjs require/es6 import in order to allow overriding dependencies during testing.
JavaScript
3
star
75

restructor

Change your mind 🤯. Face naming standards 🤦‍♀️. Easily rename JavaScript modules 🧙🏻‍♂️.
JavaScript
3
star
76

scan-directory

Multithreaded nodejs directory scan
JavaScript
3
star
77

what-did-i-load

puppeteer based resource investigation
JavaScript
2
star
78

react-fiber-walker

Walks live React Fiber
TypeScript
2
star
79

function-double

Stand in for a function
JavaScript
2
star
80

vue-demi-focus-lock

Vue
2
star
81

derive-svg-components

Derive React components from SVG base
TypeScript
2
star
82

y2FastOverlay

Один из варианта получения скорости при работе с оверлеями напрямую
2
star
83

wipeNodeCache

JavaScript
2
star
84

compare-module-exports

JavaScript
2
star
85

flow-meter-ui

https://flow-meter-ui.vercel.app/
HTML
2
star
86

storybook-addon-poleaxe

a11y (poly) axe
TypeScript
2
star
87

4D

old school C++ stuff
C++
1
star
88

hoist-react-statics

A little helper for a better HOCs
JavaScript
1
star
89

rewiremock-simple-example

testing stuff
JavaScript
1
star
90

maps

различные околокартографические примеры
1
star
91

immutable-changes

Get will found the root change!
JavaScript
1
star
92

gulp-html-prefix

Simple gulp plugin to prefix classNames
JavaScript
1
star