{"id":13547390,"url":"https://github.com/suzaku-io/diode","last_synced_at":"2026-01-11T17:00:13.255Z","repository":{"id":2471761,"uuid":"46449525","full_name":"suzaku-io/diode","owner":"suzaku-io","description":"Scala library for managing immutable application model","archived":false,"fork":false,"pushed_at":"2024-12-07T16:04:29.000Z","size":5117,"stargazers_count":642,"open_issues_count":14,"forks_count":88,"subscribers_count":18,"default_branch":"master","last_synced_at":"2025-04-02T19:44:25.736Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/suzaku-io.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":null,"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":"2015-11-18T21:46:06.000Z","updated_at":"2024-12-07T16:04:33.000Z","dependencies_parsed_at":"2024-06-19T02:50:25.033Z","dependency_job_id":"6269d60f-3b4e-4bd4-9fe7-eecd647a55c3","html_url":"https://github.com/suzaku-io/diode","commit_stats":{"total_commits":335,"total_committers":45,"mean_commits":7.444444444444445,"dds":0.6985074626865672,"last_synced_commit":"147f6fd10b591cac356068553fe94e410fb1d735"},"previous_names":["ochrons/diode"],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/suzaku-io/diode","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suzaku-io%2Fdiode","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suzaku-io%2Fdiode/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suzaku-io%2Fdiode/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suzaku-io%2Fdiode/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/suzaku-io","download_url":"https://codeload.github.com/suzaku-io/diode/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suzaku-io%2Fdiode/sbom","scorecard":{"id":701449,"data":{"date":"2025-08-11","repo":{"name":"github.com/suzaku-io/diode","commit":"9aea00f1cf4382795e525608aecc575579ec86b5"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.7,"checks":[{"name":"Code-Review","score":10,"reason":"all changesets reviewed","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":"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":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/release-drafter.yml:1","Warn: no topLevel permission defined: .github/workflows/release.yml:1","Warn: no topLevel permission defined: .github/workflows/scala.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":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"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: third-party GitHubAction not pinned by hash: .github/workflows/release-drafter.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/suzaku-io/diode/release-drafter.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:10: update your workflow using https://app.stepsecurity.io/secureworkflow/suzaku-io/diode/release.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/suzaku-io/diode/release.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/suzaku-io/diode/release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/suzaku-io/diode/release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/suzaku-io/diode/release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/scala.yml:10: update your workflow using https://app.stepsecurity.io/secureworkflow/suzaku-io/diode/scala.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/scala.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/suzaku-io/diode/scala.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/scala.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/suzaku-io/diode/scala.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/scala.yml:26: update your workflow using https://app.stepsecurity.io/secureworkflow/suzaku-io/diode/scala.yml/master?enable=pin","Info:   0 out of   6 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   4 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":"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":"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":"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":"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":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENCE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENCE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"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":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/release.yml:7"],"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":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"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"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-22T05:18:19.610Z","repository_id":2471761,"created_at":"2025-08-22T05:18:19.611Z","updated_at":"2025-08-22T05:18:19.611Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28314253,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-11T14:58:17.114Z","status":"ssl_error","status_checked_at":"2026-01-11T14:55:53.580Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":[],"created_at":"2024-08-01T12:00:54.936Z","updated_at":"2026-01-11T17:00:13.084Z","avatar_url":"https://github.com/suzaku-io.png","language":"Scala","funding_links":[],"categories":["Scala"],"sub_categories":[],"readme":"# ![Diode](./doc/images/diode-logo-big.png)\n\n\u003e /ˈdʌɪəʊd/\n\u003e\n\u003e a semiconductor device with two terminals, typically allowing the flow of current in one direction only\n\n[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/suzaku-io/diode)\n[![Build Status](https://travis-ci.org/suzaku-io/diode.svg?branch=master)](https://travis-ci.org/suzaku-io/diode)\n[![Scala.js](https://www.scala-js.org/assets/badges/scalajs-0.6.17.svg)](https://www.scala-js.org)\n\nDiode is a Scala/Scala.js library for managing immutable application state with unidirectional data flow. It is heavily\ninfluenced and inspired by [Flux](https://facebook.github.io/flux/) and\n[Elm](https://github.com/evancz/elm-architecture-tutorial/) architectures, and libraries like\n[Om](https://github.com/omcljs/om) and [Redux](https://github.com/reactjs/redux). Diode helps you build applications that\nare easy to reason about, have predictable behaviour, are easy to test and debug and are type safe. It's a compact, high\nperforming and extensible library with no external dependencies.\n\n## Documentation\n\nFull documentation [available here](https://diode.suzaku.io).\n\n## Getting Started\n\nAdd following dependency declaration to your Scala project.\n\n```scala\n\"io.suzaku\" %% \"diode\" % \"1.1.8\"\n```\n\nIn a Scala.js project the dependency looks like this.\n\n```scala\n\"io.suzaku\" %%% \"diode\" % \"1.1.8\"\n```\n\nStarting with version \"1.1.6\" diode has dropped support for Scala 2.11. If you need 2.11 support use version \"1.1.5\"\n\n\u003cimg align=\"right\" src=\"doc/images/architecture.png\"\u003e\n\n### The Model\n\nCore of an application using Diode is the immutable application _model_, typically represented as a hierarchy of case\nclasses, storing the application's state. In our super simple example app the model contains a single value, the\n`counter`.\n\n```scala\ncase class RootModel(counter: Int)\n```\n\nSince having a static counter is not very interesting, we'll want to have some way to change it. In Diode all changes to\nthe application model happen through _actions_. Let's define a couple of useful actions:\n\n```scala\ncase class Increase(amount: Int) extends Action\ncase class Decrease(amount: Int) extends Action\ncase object Reset extends Action\n```\n\nActions in Diode can be anything (that extends `diode.Action` trait or provides an `ActionType[A]` type class instance), but typically\ncase classes work best due to their pattern matching capability.\n\n### The Circuit\n\nApplication state is stored in a `Circuit[M]` which also takes care of all of the action handling. In your application\nyou'd typically define a singleton object extending the Circuit. Your only responsibilities are to define the\n`initialModel` and `actionHandler` methods, everything else is taken care by Diode's Circuit.\n\n```scala\nobject AppCircuit extends Circuit[RootModel] {\n  def initialModel = RootModel(0)\n  override val actionHandler: HandlerFunction =\n    (model, action) =\u003e action match {\n      case Increase(a) =\u003e Some(ModelUpdate(model.copy(counter = model.counter + a)))\n      case Decrease(a) =\u003e Some(ModelUpdate(model.copy(counter = model.counter - a)))\n      case Reset =\u003e Some(ModelUpdate(model.copy(counter = 0)))\n      case _ =\u003e None\n    }\n}\n```\n\nThe `actionHandler` is a function receiving the current model and action and returning a result of type\n`Option[ActionResult[M]]`. Each action must return a new copy of the (root)model so we use the convenient case\nclass `copy` function. Note that we are _not updating_ the `model` variable here, just returning a copy. The actual\nupdate is performed by the Circuit.\n\nBecause we want to keep the code clean and DRY, let's use the `ActionHandler` helper class to define our actions\ninstead.\n\n```scala\nval counterHandler = new ActionHandler(zoomTo(_.counter)) {\n  override def handle = {\n    case Increase(a) =\u003e updated(value + a)\n    case Decrease(a) =\u003e updated(value - a)\n    case Reset =\u003e updated(0)\n  }\n}\nval actionHandler = composeHandlers(counterHandler)\n```\n\nNote how in the `ActionHandler`'s constructor we _zoom_ into the model, defining a reader/writer pair to access the\npart of the model these actions are interested in. This defines a _scope_ for our handler, both preventing it from\ninadvertently accessing other parts of our application model and also simplifying access to the part we _are_ interested\nin (provided through the `value` function). The class also provides helper functions like `updated`, making it trivial\nto perform model updates in our code.\n\nFinally we convert the `ActionHandler` into a handler function with `composeHandlers`. As its name implies, you can\nsupply several handlers that are composed together to form a single handler function taking care of all your actions.\n\n### The View\n\nTo show our model to the user and to allow some interaction with it, we need a view. In this example we'll be building\nit using Scalatags, but typically you would be using some framework like React instead (for which diode-react provides\nconvenient integration).\n\n```scala\nclass CounterView(counter: ModelRO[Int], dispatch: Dispatcher) {\n  def render = {\n    div(\n      h3(\"Counter\"),\n      p(\"Value = \", b(counter.value)),\n      button(onclick := { () =\u003e dispatch(Increase(2)) }, \"Increase\"),\n      button(onclick := { () =\u003e dispatch(Decrease(1)) }, \"Decrease\"),\n      button(onclick := { () =\u003e dispatch(Reset) }, \"Reset\")\n    )\n  }\n}\n```\n\nThe view receives a _model reader_ to access the part of the model we are interested in and a `Dispatcher` to dispatch\nactions. Because we use a model reader instead of a direct counter value, we don't need to create a new instance of the\nview when the counter changes. The reader always returns an up-to-date value. If we had a more complex model, the reader\nwould also allow us to zoom further into the model, to provide subviews only a partial model.\n\nFor interaction we define three buttons for increasing, decreasing and resetting the counter. Clicking one of the\nbuttons dispatches an appropriate action, to be handled by the `counterHandler` we defined before.\n\nIn our main class we create an instance of the `CounterView`, granting it read-only access to the `counter` value in our\nmodel.\n\n```scala\nval counter = new CounterView(AppCircuit.zoom(_.counter), AppCircuit)\n```\n\n### The Listener\n\nOur application now has a model, it can change the model through actions and view the model in HTML. What is still\nmissing is informing the view about changes to the model so that it knows when to redraw itself. For this the Circuit\nprovides a subscription model, informing registered listeners whenever the model is updated.\n\n```scala\nval root = document.getElementById(\"root\")\nAppCircuit.subscribe(() =\u003e render(root))\nAppCircuit(Reset)\n\ndef render(root: Element) = {\n  val e = div(\n    h1(\"Diode example\"),\n    counter.render\n  ).render\n  root.innerHTML = \"\"\n  root.appendChild(e)\n}\n```\n\nAfter subscribing to model updates we start the circuit by dispatching a `Reset` action. This will cause the model to\nupdate and the `render` to be called. Further updates due to user clicking the buttons are handled similarly.\n\n# Examples\n\nThere are several example projects in the [examples](examples) directory.\n\nFirst one is a [simple](examples/simple) application showing the basic use of Diode.\n\nThe second example features a recursive [tree view](examples/treeview) component with a couple of edit actions. Both\nexamples use \"plain HTML\" without any framework like React.\n\nThe third example is the traditional [TodoMVC](examples/todomvc) implemented with React on Diode. This example also\ndemonstrates the use of Diode Dev Tools.\n\nThe [RAF example](examples/raf) demonstrates the use of _action processors_ to batch and render synchronized animations\nwith `requestAnimationFrame`.\n\nA more complete application example is the [Scala.js SPA tutorial](https://github.com/ochrons/scalajs-spa-tutorial),\ndemonstrating the use of React integration features and async operations.\n\n# Change history\n\nSee separate [changes document](CHANGES.md)\n\n# Contributors\n\nDiode was created and is maintained by [Otto Chrons](https://github.com/ochrons) - otto@chrons.me - Twitter:\n[@ochrons](https://twitter.com/ochrons).\n\nContributors: @PerWiklander, @DylanArnold, @netzwerg, @alonsodomin, @glmars, @vpavkin, @bpatters, \n@cquiroz, @teozkr, @mla310, @mprevel, @an-tex, @torstenrudolf, @devkat, @russwyte\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuzaku-io%2Fdiode","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsuzaku-io%2Fdiode","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuzaku-io%2Fdiode/lists"}