{"id":37020766,"url":"https://github.com/open-jumpco/kfsm","last_synced_at":"2026-01-14T02:25:38.002Z","repository":{"id":44377781,"uuid":"192945337","full_name":"open-jumpco/kfsm","owner":"open-jumpco","description":"Finite State Machine in Kotlin","archived":false,"fork":false,"pushed_at":"2025-11-14T10:03:37.000Z","size":3639,"stargazers_count":94,"open_issues_count":11,"forks_count":4,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-11-14T12:13:04.718Z","etag":null,"topics":["finite-state-machine","fsm-library","kotlin","kotlin-dsl","kotlin-js","kotlin-mpp","kotlin-multiplatform","kotlin-native","open-source"],"latest_commit_sha":null,"homepage":null,"language":"Kotlin","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/open-jumpco.png","metadata":{"files":{"readme":"README.adoc","changelog":null,"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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2019-06-20T15:33:05.000Z","updated_at":"2025-11-14T10:03:41.000Z","dependencies_parsed_at":"2024-03-07T16:48:34.984Z","dependency_job_id":"cb595cc9-431e-40bc-82bf-5284031520a6","html_url":"https://github.com/open-jumpco/kfsm","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/open-jumpco/kfsm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/open-jumpco%2Fkfsm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/open-jumpco%2Fkfsm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/open-jumpco%2Fkfsm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/open-jumpco%2Fkfsm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/open-jumpco","download_url":"https://codeload.github.com/open-jumpco/kfsm/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/open-jumpco%2Fkfsm/sbom","scorecard":{"id":708370,"data":{"date":"2025-08-11","repo":{"name":"github.com/open-jumpco/kfsm","commit":"25c0f744ad21bfcedb51b8a4946b3d724489aec9"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.3,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/build.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Binary-Artifacts","score":9,"reason":"binaries present in source code","details":["Warn: binary detected: gradle/wrapper/gradle-wrapper.jar:1"],"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/open-jumpco/kfsm/build.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/build.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/open-jumpco/kfsm/build.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yml:43: update your workflow using https://app.stepsecurity.io/secureworkflow/open-jumpco/kfsm/build.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/build.yml:56: update your workflow using https://app.stepsecurity.io/secureworkflow/open-jumpco/kfsm/build.yml/main?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-22T07:25:15.202Z","repository_id":44377781,"created_at":"2025-08-22T07:25:15.202Z","updated_at":"2025-08-22T07:25:15.202Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28408711,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T01:52:23.358Z","status":"online","status_checked_at":"2026-01-14T02:00:06.678Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["finite-state-machine","fsm-library","kotlin","kotlin-dsl","kotlin-js","kotlin-mpp","kotlin-multiplatform","kotlin-native","open-source"],"created_at":"2026-01-14T02:25:37.358Z","updated_at":"2026-01-14T02:25:37.995Z","avatar_url":"https://github.com/open-jumpco.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"= Kotlin Finite-state machine\n\nThis work is licensed under link:https://www.apache.org/licenses/LICENSE-2.0.html[Apache License 2.0]\n\nThis is a small implementation of an FSM in Kotlin.\n\n== Resources\n* link:https://open.jumpco.io/projects/kfsm/index.html[Documentation]\n* link:https://open.jumpco.io/projects/kfsm/javadoc/index.html[API Docs]\n* link:https://github.com/open-jumpco/kfsm-viz[Visualization Support kfsm-io.jumpco.open.kfsm.viz]\n* link:https://github.com/open-jumpco/kfsm-samples[Sample Project]\n* link:https://github.com/open-jumpco/kfsm-web[Turnstile Sample for Browser]\n* link:https://github.com/open-jumpco/kfsm-kvision-web[Turnstile Sample with KVision for Browser]\n* link:https://github.com/open-jumpco/kfsm-android[Turnstile Sample for Android]\n* link:https://github.com/open-jumpco/kfsm-spring[Turnstile Sample for Spring MVC]\n* link:https://github.com/open-jumpco/kfsm-spring-rest[Turnstile Sample for Spring HATEOAS]\n* link:https://github.com/open-jumpco/kfsm-android-compose-traffic[Traffic Intersection for Android Compose]\n\n== Getting Started\n\nThe state machine implementation supports events triggering transitions from one state to another while performing an optional action as well as entry and exit actions.\n\n== Features\n* Event driven state machine.\n* External and internal transitions\n* State entry and exit actions.\n* Default state actions.\n* Default entry and exit actions.\n* Determine allowed events.\n* Multiple state maps with push / pop transitions\n* Automatic transitions\n* Externalisation of state.\n* Typed event parameters and return values.\n\n== Authors\n* http://github.com/corneil[corneil]\n\n== Star History\nimage::https://api.star-history.com/svg?repos=open-jumpco/kfsm\u0026type=Date[Star History link=\"https://star-history.com/#open-jumpco/kfsm\u0026Date\"]\n\n\n== Todo\n* [x] Multiple state maps\n* [x] Push / pop transitions\n* [x] Automatic transitions\n* [x] Externalisation of state\n* [x] Typed event parameters\n* [x] Typed event return values\n* [x] Simple Visualization\n* [x] Detail Visualization\n* [x] Gradle Plugin for Visualization\n* [x] Timeouts\n* [x] Corountines\n* [ ] Different type of contexts for Nested statemaps\n\n\n\n== Quick Tutorial\nThis is the classic turnstile FSM model from [SMC](http://smc.sourceforge.net/)\n\n=== Simple turnstile example\nAssume we and to manage the state on a simple lock.\nWe want to ensure that the `lock()` function is only called when the lock is not locked and we want `unlock()` to be called when locked.\n\nThen we use the DSL to declare a definition of a statemachine matching the diagram:\n\n==== State Diagram\n\nimage::src/docs/asciidoc/turnstile-fsm.png[Turnstile state diagram]\n\n==== State Table\n\n|===\n|Start State |Event |End State |Action\n\n|LOCKED\n|PASS\n|LOCKED\n|alarm\n\n|LOCKED\n|COIN\n|UNLOCKED\n|unlock\n\n|UNLOCKED\n|PASS\n|LOCKED\n|lock\n\n|UNLOCKED\n|COIN\n|UNLOCKED\n|returnCoin\n|===\n\n==== Context class\n[source,kotlin,numbered]\n----\nclass Turnstile(var locked: Boolean = true) {\n    fun unlock() {\n        assert(locked) { \"Cannot unlock when not locked\" }\n        println(\"Unlock\")\n        locked = false\n    }\n\n    fun lock() {\n        assert(!locked) { \"Cannot lock when locked\" }\n        println(\"Lock\")\n        locked = true\n    }\n\n    fun alarm() {\n        println(\"Alarm\")\n    }\n\n    fun returnCoin() {\n        println(\"Return coin\")\n    }\n    override fun toString(): String {\n        return \"Turnstile(locked=$locked)\"\n    }\n}\n----\n\n==== Enums for States and Events\nWe declare 2 enums, one for the possible states and one for the possible events.\n\n[source,kotlin,numbered]\n----\nenum class TurnstileStates {\n    LOCKED,\n    UNLOCKED\n}\n\nenum class TurnstileEvents {\n    COIN,\n    PASS\n}\n----\n\n==== Packaged definition and execution\n[source,kotlin,numbered]\n----\nclass TurnstileFSM(turnstile: Turnstile) {\n    private val fsm = definition.create(turnstile)\n\n    fun coin() = fsm.sendEvent(TurnstileEvents.COIN)\n    fun pass() = fsm.sendEvent(TurnstileEvents.PASS)\n    companion object {\n        private val definition = stateMachine(\n            TurnstileStates.values().toSet(),\n            TurnstileEvents::class,\n            Turnstile::class\n        ) {\n            initialState {\n            if (locked)\n                TurnstileStates.LOCKED\n            else\n                TurnstileStates.UNLOCKED\n            }\n            default {\n                onEntry { startState, targetState, _ -\u003e\n                    println(\"entering:$startState -\u003e $targetState for $this\")\n                }\n                // default transition will invoke alarm\n                action { state, event, _ -\u003e\n                    println(\"Default action for state($state) -\u003e event($event) for $this\")\n                    alarm()\n                }\n                onExit { startState, targetState, _ -\u003e\n                    println(\"exiting:$startState -\u003e $targetState for $this\")\n                }\n            }\n            // when current state is LOCKED\n            whenState(TurnstileStates.LOCKED) {\n                // external transition on COIN to UNLOCKED state\n                onEvent(TurnstileEvents.COIN to TurnstileStates.UNLOCKED) {\n                    unlock()\n                }\n            }\n            // when current state is UNLOCKED\n            whenState(TurnstileStates.UNLOCKED) {\n                // internal transition on COIN\n                onEvent(TurnstileEvents.COIN) {\n                    returnCoin()\n                }\n                // external transition on PASS to LOCKED state\n                onEvent(TurnstileEvents.PASS to TurnstileStates.LOCKED) {\n                    lock()\n                }\n            }\n        }.build()\n    }\n}\n----\n\nWith this definition we are saying:\nWhen the state is `LOCKED` and on a `COIN` event then transition to `UNLOCKED` and execute the lambda which is treated\nas a member of the context `{ unlock() }`\n\nWhen the state is `LOCKED` and on event `PASS` we perform the action `alarm()` without changing the end state.\n\n==== Usage\nThen we instantiate the FSM and provide a context to operate on:\n\n[source,kotlin,numbered]\n----\nval turnstile = Turnstile()\nval fsm = TurnstileFSM(turnstile)\n----\n\nNow we have a context that is independent of the FSM.\n\nSending events may invoke actions:\n[source,kotlin,numbered]\n----\n// State state is LOCKED\nfsm.coin()\n// Expect unlock action end state is UNLOCKED\nfsm.pass()\n// Expect lock() action and end state is LOCKED\nfsm.pass()\n// Expect alarm() action and end state is LOCKED\nfsm.coin()\n// Expect unlock() and end state is UNLOCKED\nfsm.coin()\n// Expect returnCoin() and end state is UNLOCKED\n----\n\nThis model means the FSM can be instantiated as needed if the context has values that represent the state. The idea is that the context will properly maintain it's internal state.\n\nThe FSM can derive the formal state from the value(s) of properties of the context.\n\nThe link:https://open.jumpco.io/projects/kfsm/index.html[Documentation] contains more detail on creating finite state machine implementations.\n\nThe documentation contains examples for:\n\n* link:https://open.jumpco.io/projects/kfsm/index.html#advanced-features[Turnstile providing for coin values.]\n* link:https://open.jumpco.io/projects/kfsm/index.html#secure-turnstile-example[Secure turnstile with card and override.]\n* link:https://open.jumpco.io/projects/kfsm/index.html#packet-reader-example[Packet Reader finite state machine.]\n* link:https://open.jumpco.io/projects/kfsm/index.html#immutable-context-example[ImmutableLock and FSM.]\n\n=== Repository\n\nUse this repository for SNAPSHOT builds. Releases are on Maven Central\n[source,groovy]\n----\nrepositories {\n    maven {\n        url 'https://oss.sonatype.org/content/groups/public'\n    }\n}\n----\n=== Dependencies\n\n==== KMP Projects\n\nThe dependency used in common modules.\n\n[source,groovy]\n----\ndependencies {\n    implementation 'io.jumpco.open:kfsm:1.9.0-RC1'\n}\n----\n\n==== JVM Projects\n\n[source,groovy]\n----\ndependencies {\n    implementation 'io.jumpco.open:kfsm-jvm:1.9.0-RC1'\n}\n----\n\n==== KotlinJS Projects\n\n[source,groovy]\n----\ndependencies {\n    implementation 'io.jumpco.open:kfsm-js:1.9.0-RC1'\n}\n----\n\n==== Kotlin/Native Projects using LinuxX64\n\n[source,groovy]\n----\ndependencies {\n    implementation 'io.jumpco.open:kfsm-linuxX64:1.9.0-RC1'\n}\n----\n\n==== Kotlin/Native Projects using MinGW64\n\n[source,groovy]\n----\ndependencies {\n    implementation 'io.jumpco.open:kfsm-mingwX64:1.9.0-RC1'\n}\n----\n\n==== Kotlin/Native Projects using macOS\n\n[source,groovy]\n----\ndependencies {\n    implementation 'io.jumpco.open:kfsm-macosX64:1.9.0-RC1'\n}\n----\n\n== Visualisation\n\nFor more information about visualization options use link:https://github.com/open-jumpco/kfsm-viz[kfsm-io.jumpco.open.kfsm.viz]\n\n\n=== Plantuml Examples\n\n==== Turnstile FSM\n\nlink:./src/commonTest/kotlin/io/jumpco/open/kfsm/example/TurnstileTypes.kt[TurnstileTypes.kt]\n\nimage::turnstile.png[]\n\n==== Paying Turnstile FSM\n\nlink:./src/commonTest/kotlin/io/jumpco/open/kfsm/example/PayingTurnstileTypes.kt[PayingTurnstileTypes.kt]\n\nimage::paying_turnstile.png[]\n\n==== Secure Turnstile FSM\n\nlink:./src/commonTest/kotlin/io/jumpco/open/kfsm/example/SecureTurnstile.kt[SecureTurnstile.kt]\n\nimage::secure_turnstile.png[]\n\n==== Packer Reader FSM\n\nlink:./src/jvmTest/kotlin/io/jumpco/open/kfsm/example/PacketReaderTests.kt[PacketReaderTests.kt]\n\nimage::packet_reader.png[]\n\n\n== Questions:\n* Should entry / exit actions receive state or event as arguments?\n* Should default actions receive state or event as arguments?\n* Is there a more elegant way to define States and Events using sealed classes?\n* Are any features missing from the implementation?\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopen-jumpco%2Fkfsm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopen-jumpco%2Fkfsm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopen-jumpco%2Fkfsm/lists"}