{"id":19451233,"url":"https://github.com/thekashey/faste","last_synced_at":"2026-03-05T05:30:55.988Z","repository":{"id":57233204,"uuid":"134250677","full_name":"theKashey/faste","owner":"theKashey","description":"Table based 📦 Finite State Machine 🤖","archived":false,"fork":false,"pushed_at":"2023-03-18T06:03:27.000Z","size":5843,"stargazers_count":124,"open_issues_count":18,"forks_count":3,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-05-14T23:51:19.360Z","etag":null,"topics":["event-management","finite-state-machine","state-management"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/theKashey.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-05-21T09:54:42.000Z","updated_at":"2025-02-11T15:50:36.000Z","dependencies_parsed_at":"2024-06-19T05:30:40.099Z","dependency_job_id":"9775b3e9-cd4e-4990-8fe4-d365c84bc392","html_url":"https://github.com/theKashey/faste","commit_stats":{"total_commits":60,"total_committers":2,"mean_commits":30.0,"dds":"0.18333333333333335","last_synced_commit":"56d817c4134038239cb88ac127bdafb5046a5730"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/theKashey/faste","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theKashey%2Ffaste","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theKashey%2Ffaste/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theKashey%2Ffaste/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theKashey%2Ffaste/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/theKashey","download_url":"https://codeload.github.com/theKashey/faste/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theKashey%2Ffaste/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259398215,"owners_count":22851390,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["event-management","finite-state-machine","state-management"],"created_at":"2024-11-10T16:40:50.022Z","updated_at":"2025-09-20T11:27:58.927Z","avatar_url":"https://github.com/theKashey.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003ch1\u003e🤖 Faste 💡\u003c/h1\u003e\n  TypeScript centric Table Finite State Machine\n  \u003cbr/\u003e\n  \u003cimg src=\"./assets/table.png\" alt=\"faste\" height=\"233\" align=\"center\"\u003e\n  \u003cbr/\u003e\n  \u003cbr/\u003e\n  \u003ca href=\"https://circleci.com/gh/theKashey/faste/tree/master\"\u003e\n     \u003cimg src=\"https://img.shields.io/circleci/project/github/theKashey/faste/master.svg?style=flat-square)\" alt=\"Build status\"\u003e\n  \u003c/a\u003e\n\n  \u003ca href=\"https://codecov.io/github/thekashey/faste\"\u003e\n   \u003cimg src=\"https://img.shields.io/codecov/c/github/thekashey/faste.svg?style=flat-square\" /\u003e\n  \u003c/a\u003e\n\n  \u003ca href=\"https://www.npmjs.com/package/faste\"\u003e\n   \u003cimg src=\"https://img.shields.io/npm/v/faste.svg?style=flat-square\" /\u003e\n  \u003c/a\u003e\n\n  \u003ca href=\"https://greenkeeper.io/\"\u003e\n   \u003cimg alt=\"Greenkeeper badge\" src=\"https://badges.greenkeeper.io/theKashey/faste.svg\"/\u003e\n  \u003c/a\u003e\n\n  \u003cbr/\u003e\n  \u003cbr/\u003e\n  \u003cbr/\u003e  \n\u003c/div\u003e\n\n\u003e no dependencies, in around 2kb\n\n- 👨‍🔬 a bit less state-ish than [xstate](https://xstate.js.org/docs/)\n- 🤖 way more state-full than anything else in your code\n- 🧠 made for everything in between\n- 🖥 it does not have Visualizer, but it's VERY TypeScript centric\n\nState machine is a blackbox you can 1) `.start` 2) `.put` some events in and 3) _observe_ how its working.\n\nInternally it will be a machine working in different \"states\", which called **phases** here (water/ice) recieving\ndifferent events and doing something...\n\nCore concepts are:\n\n- phases: different modes your machine can be in\n- state: internal state machine sets and controls itself\n- attributes: external configuration of machine\n- messages: events it can receive from the outside or send to itself\n- signals: events it can send to the outer world\n- timers: in a very declarative form\n- hooks: callbacks which are executed when machine start or stop handling a given message\n- guards and traps: protectors from entering or leaving some states\n\n\u003e In react world `attributes` are `props`. In xstate world `state` is `context`\n\n# State machine\n\nState machine _starts_ in one phase, calls _hooks_ for all _messages_ for the current phase,\nthen _awaits_ for a _messages_ from hooks or external customer, then\ncould _trigger_ a new message, _emit_ signal to the outer world or _change_ the current phase.\n\nFaste is a black box - you can _put_ message inside, and wait for a _signal_ it will sent outside, meanwhile\nobserving a box _phase_. Black📦 == Component🎁.\n\n📖 Read an article about [FASTE, and when to use it](https://medium.com/@antonkorzunov/fasten-your-state-9fb9f9b44f30).\n\n# Example\n\n```js\nconst light = faste()\n  // define possible \"phases\" of a traffic light\n  .withPhases(['red', 'yellow', 'green'])\n  // define possible transitions from one phase to another\n  .withTransitions({\n    green: ['yellow'],\n    yellow: ['red'],\n    red: ['green'],\n  })\n  // define possible events for a machine\n  .withMessages(['switch'])\n  .on('switch', ['green'], ({ transitTo }) =\u003e transitTo('yellow'))\n  .on('switch', ['yellow'], ({ transitTo }) =\u003e transitTo('red'))\n  .on('switch', ['red'], ({ transitTo }) =\u003e transitTo('green'))\n  // ⚠️ the following line would throw an error at _compile time_\n  .on('switch', ['green'], ({ transitTo }) =\u003e transitTo('red')) // this transition is blocked\n\n  // block transition TO green if any pedestrian is on the road\n  .guard(['green'], () =\u003e noPedestriansOnTheRoad)\n  // block transition FROM red if any pedestrian is on the road\n  .trap(['red'], () =\u003e !noPedestriansOnTheRoad);\n// PS: noPedestriansOnTheRoad could be read from attr, passed from a higher state machine.\n```\n\n# API\n\n## Machine blueprint\n\n`faste(options)` - defines a new faste machine\nevery faste instance provide next _chainable_ commands\n\n- `on(eventName, [phases], callback)` - set a hook `callback` for `eventName` message in states `states`.\n- `hooks(hooks)` - set a hook when some message begins, or ends its presence.\n\n- `guard(phases, callback)` - add a transition guard, prevention transition to the phase\n- `trap(phases, callback)` - add a transition guard, prevention transition from the phase\n\nIn development mode, and for typed languages you could use next commands\n\n- `withState(state)` - set a initial state (use @init hook to derive state from props).\n- `withPhases(phases)` - limit phases to provided set.\n- `withTimers(timersConfuguration)` - configures timers\n- `withTransitions([phases]:[phases])` - limit phase transitions\n- `withMessages(messages)` - limit messages to provided set.\n- `withAttrs(attributes)` - limit attributes to provided set.\n- `withSignals(signals)` - limit signals to provided set.\n-\n- `withMessageArguments\u003cMessageConfiguration\u003e()` - enabled arguments for messages\n- `withSignalArguments\u003cSignalConfiguration\u003e()` - enabled arguments for signals\n\n- `create()` - creates a machine (copies existing, use it instead of `new`).\n\nAll methods returns a `faste` constructor itself.\n\n## Machine instance\n\nEach instance of Faste will have:\n\n- `attrs(attrs)` - set attributes.\n- `put` - put message in\n- `connect` - connects output to the destination\n- `observe` - observes phase changes\n\n- `phase` - returns the current phase\n- `instance` - returns the current internal state.\n\n- `destroy` - exits the current state, terminates all hooks, and stops machine.\n\n- `namedBy(string)` - sets name of the instance (for debug).\n\nFor all callbacks the first argument is `flow` instance, containing.\n\n- `attrs` - all the attrs, you cannot change them\n\n- `state` - internal state\n- `setState` - internal state change command\n\n- `phase` - current phase\n- `transitTo` - phase change command.\n\n- `startTimer(timerName)` - starts a Timer\n- `stopTimer(timerName)` - stops a Timer\n\n- `emit` - emits a message to the outer world\n\n### Magic events\n\n- `@init` - on initialization\n- `@enter` - on phase enter, last phase will be passed as a second arg.\n- `@leave` - on phase enter, new phase will be passed as a second arg.\n- `@change` - on state change, old state will be passed as a second arg.\n- `@miss` - on event without handler\n- `@error` - an error handler. If no errors handler will be found the real error will be thrown\n\n### Magic phases\n\n- `@current` - set the same phase as it was on the handler entry\n- `@busy` - set the _busy_ phase, when no other handler could be called\n\n### Hooks\n\nHook activates when message starts or ends it existence, ie when there is `on` callback defined for it.\n\n### Event bus\n\n- message handler could change phase, state and trigger a new message\n- hook could change state or trigger a new message, but not change phase\n- external consumer could only trigger a new message\n\n## InternalState\n\nEach `on` or `hook` handler will receive `internalState` as a first argument, with following shape\n\n```js\n attrs: { ...\n    AttributesYouSet\n}\n;  // attributes\nstate: { ..\n    CurrentState\n}\n;       // state\n\nsetState(newState);              // setState (as seen in React)\n\ntransitTo(phase);                // move to a new phase\n\nemit(message, ...args);          // emit Signal to the outer world (connected)\n\ntrigger(event, ...args);         // trigger own message handler (dispatch an internal action)\n```\n\n# Debug\n\nDebug mode is integrated into Faste.\n\n```js\nimport {setFasteDebug} from 'faste'\n\nsetFasteDebug(true);\nsetFasteDebug(true);\nsetFasteDebug((instance, message, args) =\u003e console.log(...));\n```\n\n# Examples\n\nTry online : https://codesandbox.io/s/n7kv9081xp\n\n### Using different handlers in different states\n\n```js\n// common code - invert the flag\nonClick = () =\u003e this.setState((state) =\u003e ({ enabled: !state.enabled }));\n\n// faste - use different flags for different states\nfaste()\n  .on('click', 'disabled', ({ transitTo }) =\u003e transitTo('enabled'))\n  .on('click', 'enabled', ({ transitTo }) =\u003e transitTo('disabled'));\n```\n\n### React to state change\n\n```js\n// common code - try to reverse engineer the change\ncomponentDidUpdate(oldProps)\n{\n    if (oldProps.enabled !== this.props.enabled) {\n        if (this.props.enabled) {\n            // I was enabled!\n        } else {\n            // I was disabled!\n        }\n    }\n}\n\n// faste - use \"magic\" methods\nfaste()\n    .on('@enter', ['disabled'], () =\u003e /* i was disabled */)\n    .on('@enter', ['enabled'], () =\u003e /* i was enabled */)\n    // or\n    .on('@leave', ['disabled'], () =\u003e /* i am no more disabled */)\n    .on('@leave', ['enabled'], () =\u003e /* i am no more enabled */)\n```\n\n### Connected states\n\nhttps://codesandbox.io/s/5zx8zl91ll\n\n```js\n// starts a timer when active\nconst SignalSource = faste()\n  .on('@enter', ['active'], ({ setState, attrs, emit }) =\u003e\n    setState({ interval: setInterval(() =\u003e emit('message'), attrs.duration) })\n  )\n  .on('@leave', ['active'], ({ state }) =\u003e clearInterval(state.interval));\n\n// responds to \"message\" by moving from tick to tock\n// emiting the current state outside\nconst TickState = faste()\n  // autoinit to \"tick\" mode\n  .on('@init', ({ transitTo }) =\u003e transitTo('tick'))\n  // message handlers\n  .on('message', ['tick'], ({ transitTo }) =\u003e transitTo('tock'))\n  .on('message', ['tock'], ({ transitTo }) =\u003e transitTo('tick'))\n  .on('@leave', ({ emit }, newPhase) =\u003e emit('currentState', newPhase));\n\n// just transfer message to attached node\nconst DisplayState = faste().on('currentState', ({ attrs }, message) =\u003e (attrs.node.innerHTML = message));\n\n// create machines\nconst signalSource = SignalSource.create().attrs({\n  duration: 1000,\n});\nconst tickState = TickState.create();\nconst displayState = DisplayState.create().attrs({\n  node: document.querySelector('.display'),\n});\n\n// direct connect signal source and ticker\nsignalSource.connect(tickState);\n\n// \"functionaly\" connect tickes and display\ntickState.connect((message, payload) =\u003e displayState.put(message, payload));\n\n// RUN! start signal in active mode\nsignalSource.start('active');\n```\n\n### Traffic light\n\n```js\nconst state = faste()\n  .withPhases(['red', 'yellow', 'green'])\n  .withMessages(['tick', 'next'])\n\n  .on('tick', ['green'], ({ transit }) =\u003e transit('yellow'))\n  .on('tick', ['yellow'], ({ transit }) =\u003e transit('red'))\n  .on('tick', ['red'], ({ transit }) =\u003e transit('green'))\n\n  // on 'next' trigger 'tick' for a better debugging.\n  // just rethrow event\n  .on('next', [], ({ trigger }) =\u003e trigger('tick'))\n\n  // on \"green\" - start timer\n  .on('@enter', ['green'], ({ setState, attrs, trigger }) =\u003e\n    setState({\n      interval: setInterval(() =\u003e trigger('next'), attrs.duration),\n    })\n  )\n  // on \"red\" - stop timer\n  .on('@leave', ['red'], ({ state }) =\u003e clearInterval(state.interval))\n\n  .check();\n\nstate.create().attrs({ duration: 1000 }).start('green');\n```\n\nTry online : https://codesandbox.io/s/n7kv9081xp\n\n### Draggable\n\n```js\nconst domHook =\n  (eventName) =\u003e\n  ({ attrs, trigger }) =\u003e {\n    const callback = (event) =\u003e trigger(eventName, event);\n    attrs.node.addEventListener(eventName, callback);\n    // \"hook\" could return anything, callback for example\n    return () =\u003e {\n      attrs.node.removeEventListener(eventName, hook);\n    };\n  };\n\nconst state = faste({})\n  .on('@enter', ['active'], ({ emit }) =\u003e emit('start'))\n  .on('@leave', ['active'], ({ emit }) =\u003e emit('end'))\n\n  .on('mousedown', ['idle'], ({ transitTo }) =\u003e transitTo('active'))\n  .on('mousemove', ['active'], (_, event) =\u003e emit('move', event))\n  .on('mouseup', ['active'], ({ transitTo }) =\u003e transitTo('idle'));\n\nhooks({\n  mousedown: domHook('mousedown'),\n  mousemove: domHook('mousemove'),\n  mouseup: domHook('mouseup'),\n})\n  .check()\n\n  .attr({ node: document.body })\n  .start('idle');\n```\n\n# Async\n\nMessage handler doesn't have to be sync. But managing async commands could be hard. But will not\n\n1. Accept command only in initial state, then transit to temporal state to prevent other commands to be executes.\n\n```js\nconst Login = faste().on('login', ['idle'], ({ transitTo }, { userName, password }) =\u003e {\n  transitTo('logging-in'); // just transit to \"other\" state\n  login(userName, password)\n    .then(() =\u003e transitTo('logged'))\n    .catch(() =\u003e transitTo('error'));\n});\n```\n\n2. Accept command only in initial state, then transit to execution state, and do the job on state enter\n\n```js\nconst Login = faste()\n    .on('login', ['idle'], ({transitTo}, data) =\u003e transitTo('logging', data)\n        .on('@enter', ['logging'], ({transitTo}, {userName, password}) =\u003e {\n            login(userName, password)\n                .then(() =\u003e transitTo('logged'))\n                .catch(() =\u003e transitTo('error'))\n        });\n```\n\n2. Always accept command, but be \"busy\" while doing stuff\n\n```js\nconst Login = faste().on('login', ({ transitTo }, { userName, password }) =\u003e {\n  transitTo('@busy'); // we are \"busy\"\n  return login(userName, password)\n    .then(() =\u003e transitTo('logged'))\n    .catch(() =\u003e transitTo('error'));\n});\n```\n\n\u003e handler returns Promise( could be async ) to indicate that ending in @busy state is not a mistake, and will not lead\n\u003e to deadlock.\n\nBy default `@busy` will queue messages, executing them after leaving busy phase.\nIf want to ignore them - instead of `@busy`, you might use `@locked` phase, which will ignore them.\n\nPS: You probably will never need those states.\n\n## Using timers to create timers\n\nPlain variant\n\n```tsx\nconst SignalSource = faste()\n  .on('@enter', ['active'], ({ setState, attrs, emit }) =\u003e\n    setState({ interval: setInterval(() =\u003e emit('message'), attrs.duration) })\n  )\n  .on('@leave', ['active'], ({ state }) =\u003e clearInterval(state.interval));\n```\n\nHook and timer based\n\n```tsx\nconst SignalSource = faste()\n  .on('tick', ['active'], ({ emit, startTimer }) =\u003e {\n    emit('message');\n  })\n  .hook({\n    tick: ({ attrs }) =\u003e {\n      const interval = setInterval(() =\u003e emit('message'), attrs.duration);\n      return () =\u003e clearInterval(interval);\n    },\n  });\n```\n\nHook and timer based\n\n```tsx\nconst SignalSource = faste()\n  .withTimers({\n    T0: 1000,\n  })\n  .on('on_T0', ({ emit, startTimer }) =\u003e {\n    emit('message');\n    startTimer('T0'); //restarts timers\n  })\n  .on('@enter', ['active'], ({ startTimer }) =\u003e startTimer('T0'));\n```\n\n# SDL and Block\n\nFaste was born from this. From Q.931(EDSS) state definition.\n\nHow it starts. What signals it accepts. What it does next.\n\n![U17](./assets/capeu17.gif)\n\nThat is quite simple diagram.\n\n## Thoughts\n\nThis is a Finite State Machine from\nSDL([Specification and Description Language](https://en.wikipedia.org/wiki/Specification_and_Description_Language))\nprospective.\nSDL defines state as a set of messages, it should react on, and the actions beneath.\n\nOnce `state` receives a `message` it executes an `action`, which could perform calculations and/or change the\ncurrent state.\n\n\u003e The goal is not to **change the state**, but - **execute a bound action**.\n\u003e From this prospective faste is closer to RxJX.\n\nUsually \"FSM\" are more focused on state transitions, often even omitting any operations on message receive.\nIn the Traffic Light example it could be useful, but in more real life examples - probably not.\n\nFaste is more about _when_ you will be able to do _what_. **What** you will do, **when** you receive event, and what you\nwill do next.\n\nKeeping in mind the best practices, like KISS and DRY, it is better to invert state-\u003emessage-\u003eaction connection,\nas long as actions are most complex part of it, and messages are usually reused across different states.\n\nAnd, make things more common we will call \"state\" as a \"phase\", and \"state\" will be for \"internal state\".\n\nThe key idea is not about transition between states, but transition between behaviors.\nKeep in mind - if some handler is not defined in some state, and you are sending a message - it will be **lost**.\n\n\u003e Written in TypeScript. To make things less flexible. Flow definitions as incomplete.\n\n# Prior art\n\nThis library combines ideas from [xstate](https://github.com/davidkpiano/xstate)\nand [redux-saga](https://github.com/redux-saga/redux-saga).\nThe original idea is based on [xflow](https://gist.github.com/theKashey/93f10d036961f4bd7dd728153bc4bea9) state machine,\ndeveloped for [CT Company](http://www.ctcom.com.tw)'s VoIP solutions back in 2005.\n\n# Licence\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthekashey%2Ffaste","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthekashey%2Ffaste","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthekashey%2Ffaste/lists"}