{"id":14966943,"url":"https://github.com/vic/laminar_cycle","last_synced_at":"2026-03-05T08:02:47.797Z","repository":{"id":66386320,"uuid":"263857373","full_name":"vic/laminar_cycle","owner":"vic","description":"A cycle.js style user-computer model in Laminar","archived":false,"fork":false,"pushed_at":"2022-08-20T19:51:30.000Z","size":1402,"stargazers_count":24,"open_issues_count":6,"forks_count":3,"subscribers_count":1,"default_branch":"series/0.x","last_synced_at":"2025-10-18T20:03:18.851Z","etag":null,"topics":["cycle","cyclejs","hacktoberfest","laminar","reactiveui","scalajs"],"latest_commit_sha":null,"homepage":"https://vic.github.io/laminar_cycle/examples/cycle_counter/src/index.html","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/vic.png","metadata":{"files":{"readme":"README.md","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}},"created_at":"2020-05-14T08:24:42.000Z","updated_at":"2024-01-07T10:53:48.000Z","dependencies_parsed_at":"2023-03-18T22:34:49.949Z","dependency_job_id":null,"html_url":"https://github.com/vic/laminar_cycle","commit_stats":{"total_commits":134,"total_committers":2,"mean_commits":67.0,"dds":0.03731343283582089,"last_synced_commit":"5c52f42abd6cda9d39763616341dc913e6fc3175"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/vic/laminar_cycle","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic%2Flaminar_cycle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic%2Flaminar_cycle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic%2Flaminar_cycle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic%2Flaminar_cycle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vic","download_url":"https://codeload.github.com/vic/laminar_cycle/tar.gz/refs/heads/series/0.x","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic%2Flaminar_cycle/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30115662,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-05T03:40:26.266Z","status":"ssl_error","status_checked_at":"2026-03-05T03:39:15.902Z","response_time":93,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: 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":["cycle","cyclejs","hacktoberfest","laminar","reactiveui","scalajs"],"created_at":"2024-09-24T13:37:11.299Z","updated_at":"2026-03-05T08:02:47.759Z","avatar_url":"https://github.com/vic.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Laminar.cycle\n\n![Main workflow](https://github.com/vic/laminar_cycle/workflows/Main%20workflow/badge.svg?branch=master)\n[![Jipack](https://jitpack.io/v/vic/laminar_cycle.svg)](https://jitpack.io/#vic/laminar_cycle)\n\n![Everything is a Stream][everything-is-a-stream]\n\n[Cycle] style apps using [Laminar] on [ScalaJS]\n\n## Installation\n\n\u003e Artifact: `com.github.vic.laminar_cycle::cycle-core::VERSION`\n\nEach release artifacts are available from [JitPack][JitPack]\n\n## Intro\n\n[Laminar] is an awesome tool for creating [Functional Reactive][FRP] interfaces enterely based on [Streams][Airstream].\n\nThis repository offers a [tiny library][laminar-cycle-source] built on Laminar that can help you\nbuild applications using [Cycle's dialogue abstraction][cycle-dialogue].\n\n[cycle API][laminar-cycle-javadoc]\n\n### Senses and Actuators\n\n\u003cimg src=\"https://cycle.js.org/img/actuators-senses.svg\" width=\"400\"\u003e\n\n\nIn the [Cycle's dialogue abstraction][cycle-dialogue] pictured above, \nboth the _Human_ and the _Computer_ can be seen as entities interacting with each\nother by means of `Senses` to `Actuators`. \n\nThese *Input* and *Output* devices drive the interaction between actors.\n\nThis way, the Computer _reacts_ to user interactions (like clicks) by *producing* an updated interface,\nand the User _reacts_ to the interface on screen they *see* by *operating* on it (clicking again).\n\nIn [Cycle.js][Cycle], every component and the whole `Computer` can be seen like a function\nfrom input streams to output streams.\n\n```javascript\n// Javascript\nfunction computer(inputDevices) {\n  // define the behavior of `outputDevices` somehow\n  return outputDevices;\n}\n```\n\n### Laminar.cycle\n\nSince Laminar is already powerful enough to efficiently create and render stream-based reactive html elements,\nall we need now is a function to model the previously seen cycle dialogue abstraction.\n\n#### CycleIO\n\nThe type `CIO[I, O]` stands for `CycleIO` and models inputs of type `I` and outputs of type `O`.\n\u003e And yes, it's also a nod to the [ZIO] data type ;D -- [@vic]\n\nAs an example of _Inputs_ and _Outputs_, suppose you need to interact with\nsome external API by sending it `Request`s and receiving `Response`s from it.\n\nNote: once you finish reading this guide, you might want to look at the [SWAPIDriver][swapi-driver-source]\nexample to see how to implement a [Cycle driver][cycle-driver] in Laminar.\n\n```scala\nobject ExternalAPI {\n  sealed trait Request\n  sealed trait Response\n}\n```\n\nIn the following code snippet, we have a `computer` function that can take\nstimulus (`Response`) from the API but also might produce stimulus for it (`Request`).\n\n```scala\nimport cycle._\nimport com.raquo.laminar.api.L._\nimport ExternalAPI.{Request, Response}\n\ndef computer(api: CIO[Response, Request]) = {\n  // TODO: send requests and receive responses from API\n}\n```\n\nYou can think of the `CIO[Input, Output]` type as the following trait.\n\u003e If you want to know the truth, use the source, Luke.\n\u003e You will see it's actually defined as type alias in [Cycle.scala][laminar-cycle-source]\n\u003e -- [@vic][@vic]\n\n```scala\ntrait CIO[I, O] {\n  val in:  EventStream[I]\n  val out: WriteBus[O]\n}\n```\n\nit provides an incoming `Observable[I]` stream and an outgoing `Observer[O]` write bus.\n\nPretty much similar to [Airstream]'s own `EventBus[T]` but generic on both input and output types.\n\n\n#### Combining state and user interaction.\n\nSuppose we want to implement a counter UI that is able to track the current counter value\nand the number of times the user has interacted with the counter.\nA working example can be found at [Examples](#Examples)\n\n```scala\nobject Counter {\n\n  case class State(\n    value: Int,\n    numberOfInteractions: Int\n  )\n  \n  sealed trait Action // Type of the sensed stimulus produced by the user\n  case object Increment extends Action\n  case object Decrement extends Action\n\n}\n```\n\nHaving the above types, we could define our `computer` function like:\n\n```scala\nimport Counter._\n\ndef computer(states: EIO[State], actions: EIO[Action]): Mod[Element] = {\n  // Initialize the computer's internal state and keep it on a Signal[State]\n  // in order to always have a *current value*\n  val stateSignal: Signal[State] = states.startWith(State(0, 0))\n  \n  // Now, whenever we sense a user Action, we have to update our current state\n  val updatedState: EventStream[State] = \n     actions.withCurrentValueOf(stateSignal).map(Function.tupled(performAction))\n \n  updatedState --\u003e states\n}\n\ndef performAction(action: Action, state: State): State = action match {\n case Increment =\u003e\n   state.copy(state.value + 1, state.numberOfInteractions + 1)\n case Decrement =\u003e\n   state.copy(state.value - 1, state.numberOfInteractions + 1)\n}\n```\n\nLet's explore our previous example code.\n\n* The `EIO[E]` type is just an alias for `CIO[E, E]`. \n\n  EIO stands for Equal IO, meaning that both, input and output have the same type `E`.\n  It's equivalent to [Airstream]'s `EventBus[E]`\n\n* Our previous example does not render anything (we will get to producing views later).\n\n  Yet, it's a fully working example of how to create a cycle function that reads and writes\n  from both: `actions` and `states`.\n\n* The return type is `Mod[Element]`. \n\n  The `updatedState --\u003e states` produces Laminar modifier that manages the event subscriptions. \n  From now on we will be using the `type ModEl = Mod[Element]` alias included with this lib.\n  \n  Read more about modifiers and subscription ownership at [LaminarDocs].\n  All of this is part of Laminar's [memory safety and glitch free guarantees][LaminarSafety].\n\n\n#### Producing reactive views\n\nNow, we will refactor our `computer` function to actually render a user interface.\n\nFor brevity sake, we will add `???` for previously seen code.\n\n```scala\ndef computer(states: EIO[State], actions: EIO[Action]): Div = {\n  val stateSignal: Signal[State] = ???\n  val updatedState: EventStream[State] = ???\n \n  div(\n    counterView(stateSignal),\n    actionControls(actions),\n    updatedState --\u003e state\n  )\n}\n\ndef actionControls(actions: Observer[Action]): Mod[Div] = {\n  cycle.amend(\n    button(\n      cls := \"btn secondary\",\n      \"Increment\",\n      onClick.mapTo(Increment) --\u003e actions\n    ),\n    button(\n      cls := \"btn secondary\",\n      \"Decrement\",\n      onClick.mapTo(Decrement) --\u003e actions\n    ),\n    button(\n      cls := \"btn secondary\",\n      \"Reset\",\n      onClick.mapTo(Reset) --\u003e actions\n    )\n  )\n}\n\ndef counterView(state: Observable[State]): Div = {\n  div(\n    h2(\"Counter value: \", child.text \u003c-- state.map(_.value.toString)),\n    h2(\"Interactions: \", child.text \u003c-- state.map(_.interactions.toString))\n  )\n}\n```\n  \n## Drivers  \n\nA [Driver][cycle-driver] is the Cycle-way to interpret effects and\ninteract with the outside world.\n\n\nIn Laminar, Drivers are couple of Devices and Binders.\n\nDevices are things like `CIO[I, O]`, `EMO[T]`, etc, that provide\nread/write streams outside the Driver.\n\nBinders are Laminar's `Binder[Element]` that provide a way to start\nand interrupt dynamic subscriptions in order to keep Laminar's safe-memory\nand glitch-free guarantees.\n\nThe following is the full source code for the [DOM Fetch][fetch-driver-source]\nincluded in this library:\n\n```scala\nimport com.raquo.laminar.api.L._\nimport org.scalajs.dom.experimental._\n\nobject fetch {\n  final case class Request(input: RequestInfo, init: RequestInit = null)\n\n  // The Devices as seen from this driver's user perspective.\n  type FetchIO = CIO[(Request, Response), Request]\n\n  def driver: Driver[FetchIO] = {\n    val devices = PIO[Request, (Request, Response)]\n    val reqRes = devices.flatMap(req =\u003e\n      EventStream.fromFuture {\n        Fetch.fetch(req.input, req.init).toFuture\n      }.map(req -\u003e _)\n    )\n    Driver(devices, reqRes --\u003e devices)\n  }\n}\n```\n\nWhen used inside an element, Drivers provide their IO devices to the block\ngiven to them and the binders set up dynamic subscriptions to start/stop when the\nparent element is mounted/unmounted from UI.\n\n\n### Available Drivers\n\n\u003e All drivers artifact: `com.github.vic.laminar_cycle::cycle::VERSION`\n\nIndividual Drivers:\n\n###### [FetchDriver][fetch-driver-javadoc] ([source][fetch-driver-source])\n\n  \u003e Artifact: `com.github.vic.laminar_cycle::fetch-driver::VERSION`\n\n  A cycle driver around DOM's `Fetch API` for executing HTTP requests.\n  \n###### [StateDriver][state-driver-javadoc] ([source][state-driver-source])\n  \u003e Artifact: `com.github.vic.laminar_cycle::state-driver::VERSION`\n\n  This driver allows you to have State-layers. See the Onion example.\n  This way sub-components can have an inward view, just a layer of the outer \n  bigger state and their updates also get propagated outwards.\n  \n###### [History][history-driver-javadoc] ([source][history-driver-source])\n  \u003e Artifact: `com.github.vic.laminar_cycle::history-driver::VERSION`\n\n  The DOM History driver allows you to push/replace and get an stream\n  of current page state.\n\n###### [Router][router-driver-javadoc] ([source][router-driver-source])\n  \u003e Artifact: `com.github.vic.laminar_cycle::router-driver::VERSION`\n\n  Stream based router driver. This driver does not depend directly but\n  can be used with the History driver and anything that can encode/decode\n  an URL like, for example [urldsl].\n\n\n###### [Mount][mount-driver-javadoc] ([source][mount-driver-source])\n  \u003e Artifact: `com.github.vic.laminar_cycle::mount-driver::VERSION`\n\n  Convenience driver with event streams for mount and umount events.\n\n\n###### [TEA][tea-driver-javadoc] ([source][tea-driver-source])\n  \u003e Artifact: `com.github.vic.laminar_cycle::tea-driver::VERSION`\n\n  A simple driver that follows The Elm Architecture for updating\n  a current state with both: Pure and Effectful actions.\n\n\n###### [TopicDriver][topic-driver-javadoc] ([source][topic-driver-source])\n  \u003e Artifact: `com.github.vic.laminar_cycle::topic-driver::VERSION`\n\n  A Pub/Sub driver that allows any component in the system to communicate\n  with each other by registering to their topic of interest.\n  \n  You can use this to broadcast events across the whole application.\n  \n\n###### [ZIODriver][zio-driver-javadoc] ([source][zio-driver-source])\n  \u003e Artifact: `com.github.vic.laminar_cycle::zio-driver::VERSION`\n\nProvides conversions from `ZQueue` to Laminar's `EventStream` and `WriteBus`.\nAutomatically manages subscriptions between zio and Laminar streams.\n  \n\n## Examples\n\n###### [Counter] ([source][counter-source])\n\u003e `./ci example cycle_counter`\n\n  Runnable implementation of the counter example on README.md.\n  \n  Shows basics of using `cycle.InOut` types, handling user actions to update \n  the current state and update a view based on it.\n  \n###### [Onion State] ([source][onion-source])\n\u003e `./ci example onion_state`\n\n  Shows basic usage of the State driver in an Onion-layered app.\n  \n###### [ELM] ([source][elm-source])\n\u003e `./ci example elm_architecture`\n\n  Sample using the [The Elm Architecture][tea-driver-source] to implement a sampler.\n  \n###### [ZIO Clock]  ([source][zio-clock-source])\n\u003e `./ci example zio_clock`\n\n  Effectful ZIO application that renders a Queue of Clock's nanoSeconds inside a\n  Laminar view.\n  \n###### [SPA Router]  ([source][spa-router-source])\n\u003e `./ci example spa_router`\n\n\n  This example uses the History and Router drivers and [urldsl] to implement a mock\n  SPA (Single Page Application) social network.\n  \n  To start the SPA, clone this repo and run:\n  \n###### [SWAPIDriver] ([source][swapi-driver-source])\n\u003e `./ci example swapi_driver`\n\n  Search StartWars characters by name.\n  \n  Shows how a [Cycle driver][cycle-driver] looks like with Laminar.\n  \n  The SWAPIDriver makes http requests to a the [SWAPI] database via REST.\n  \n\n[JitPack]: https://jitpack.io/#vic/laminar_cycle\n[Cycle]: https://cycle.js.org/\n[Airstream]: https://github.com/raquo/Airstream\n[Laminar]: https://github.com/raquo/Laminar\n[LaminarSafety]: https://github.com/raquo/Laminar#safety\n[LaminarDocs]: https://github.com/raquo/Laminar/blob/master/docs/Documentation.md\n[ScalaJS]: https://www.scala-js.org/\n[FRP]: https://gist.github.com/staltz/868e7e9bc2a7b8c1f754\n[everything-is-a-stream]: https://camo.githubusercontent.com/e581baffb3db3e4f749350326af32de8d5ba4363/687474703a2f2f692e696d6775722e636f6d2f4149696d5138432e6a7067\n[senses-actuators]: https://cycle.js.org/img/actuators-senses.svg\n[cycle-dialogue]: https://cycle.js.org/dialogue.html\n[cycle-driver]: https://cycle.js.org/drivers.html\n[urldsl]: https://github.com/sherpal/url-dsl\n\n[Counter]: https://vic.github.io/laminar_cycle/examples/cycle_counter/src/index.html\n[counter-source]: examples/cycle_counter/src\n\n[Onion State]: https://vic.github.io/laminar_cycle/examples/onion_state/src/index.html\n[onion-source]: examples/onion_state/src\n\n[SPA Router]: https://vic.github.io/laminar_cycle/examples/spa_router/src/index.html\n[spa-router-source]: examples/spa_router/src\n\n[ZIO Clock]: https://vic.github.io/laminar_cycle/examples/zio_clock/src/index.html\n[zio-clock-source]: examples/zio_clock/src\n\n[ELM]: https://vic.github.io/laminar_cycle/examples/elm_architecture/src/index.html\n[elm-source]: examples/elm_architecture/src\n\n[SWAPI]: https://swapi.dev/\n[SWAPIDriver]: https://vic.github.io/laminar_cycle/examples/swapi_driver/src/index.html\n[swapi-driver-source]: examples/swapi_driver/src\n\n[laminar-cycle-javadoc]: https://vic.github.io/laminar_cycle/out/cycle/2.13.2/1.1.0/docJar/dest/javadoc/index.html\n[laminar-cycle-source]: cycle/src/Cycle.scala\n\n[fetch-driver-javadoc]: https://vic.github.io/laminar_cycle/out/drivers/fetch/2.13.2/1.1.0/docJar/dest/javadoc/index.html\n[fetch-driver-source]: drivers/fetch/src\n\n[state-driver-javadoc]: https://vic.github.io/laminar_cycle/out/drivers/state/2.13.2/1.1.0/docJar/dest/javadoc/index.html\n[state-driver-source]: drivers/state/src\n\n[mount-driver-javadoc]: https://vic.github.io/laminar_cycle/out/drivers/mount/2.13.2/1.1.0/docJar/dest/javadoc/index.html\n[mount-driver-source]: drivers/mount/src\n\n[history-driver-javadoc]: https://vic.github.io/laminar_cycle/out/drivers/history/2.13.2/1.1.0/docJar/dest/javadoc/index.html\n[history-driver-source]: drivers/history/src\n\n[router-driver-javadoc]: https://vic.github.io/laminar_cycle/out/drivers/router/2.13.2/1.1.0/docJar/dest/javadoc/index.html\n[router-driver-source]: drivers/router/src\n\n[tea-driver-javadoc]: https://vic.github.io/laminar_cycle/out/drivers/tea/2.13.2/1.1.0/docJar/dest/javadoc/index.html\n[tea-driver-source]: drivers/tea/src\n\n[topic-driver-javadoc]: https://vic.github.io/laminar_cycle/out/drivers/2.13.2/1.1.0/topic/docJar/dest/javadoc/index.html\n[topic-driver-source]: drivers/topic/src\n\n[combine-driver-javadoc]: https://vic.github.io/laminar_cycle/out/drivers/2.13.2/1.1.0/combine/docJar/dest/javadoc/index.html\n[combine-driver-source]: drivers/combine/src\n\n[zio-driver-javadoc]: https://vic.github.io/laminar_cycle/out/drivers/zio/2.13.2/1.1.0/docJar/dest/javadoc/index.html\n[zio-driver-source]: drivers/zio/src\n\n[ZIO]: https://zio.dev/\n[@vic]: https://twitter.com/oeiuwq\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvic%2Flaminar_cycle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvic%2Flaminar_cycle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvic%2Flaminar_cycle/lists"}