All Projects → tolitius → mount-up

tolitius / mount-up

Licence: EPL-1.0 license
watching mount's ups and downs

Programming Languages

clojure
4091 projects

Projects that are alternatives of or similar to mount-up

stater
collection of Clojure/Script mount apps
Stars: ✭ 29 (+11.54%)
Mutual labels:  state-management, mount
redelay
Clojure library for first class lifecycle-managed state.
Stars: ✭ 44 (+69.23%)
Mutual labels:  state-management, mount
closeable-map
Application state management made simple: a Clojure map that implements java.io.Closeable.
Stars: ✭ 42 (+61.54%)
Mutual labels:  state-management, mount
Tinystate
A tiny, yet powerful state management library for Angular
Stars: ✭ 207 (+696.15%)
Mutual labels:  state-management
Controllerim
A state management library for React
Stars: ✭ 213 (+719.23%)
Mutual labels:  state-management
React Agent
Client and server-side state management library
Stars: ✭ 235 (+803.85%)
Mutual labels:  state-management
Xstate
State machines and statecharts for the modern web.
Stars: ✭ 18,300 (+70284.62%)
Mutual labels:  state-management
Eiffel
Redux-inspired Android architecture library leveraging Architecture Components and Kotlin Coroutines
Stars: ✭ 203 (+680.77%)
Mutual labels:  state-management
Pulse
✨ Pulse is a global state and logic framework for reactive Typescript & Javascript applications. Supporting frameworks like VueJS, React and React Native.
Stars: ✭ 243 (+834.62%)
Mutual labels:  state-management
Getx pattern
Design pattern designed to standardize your projects with GetX on Flutter.
Stars: ✭ 225 (+765.38%)
Mutual labels:  state-management
Use Machine
React Hook for using Statecharts powered by XState. use-machine.
Stars: ✭ 226 (+769.23%)
Mutual labels:  state-management
Hydux
A light-weight type-safe Elm-like alternative for Redux ecosystem, inspired by hyperapp and Elmish
Stars: ✭ 216 (+730.77%)
Mutual labels:  state-management
Redux Tiles
Composable way to create less verbose Redux code
Stars: ✭ 239 (+819.23%)
Mutual labels:  state-management
React Easy State
Simple React state management. Made with ❤️ and ES6 Proxies.
Stars: ✭ 2,459 (+9357.69%)
Mutual labels:  state-management
Final Form
🏁 Framework agnostic, high performance, subscription-based form state management
Stars: ✭ 2,787 (+10619.23%)
Mutual labels:  state-management
Reusable
Reusable is a library for state management using React hooks
Stars: ✭ 207 (+696.15%)
Mutual labels:  state-management
Vue Class Store
Universal Vue stores you write once and use anywhere
Stars: ✭ 243 (+834.62%)
Mutual labels:  state-management
React Organism
Dead simple React state management to bring pure components alive
Stars: ✭ 219 (+742.31%)
Mutual labels:  state-management
Tailor made
✄ Managing a Fashion designer's daily routine.
Stars: ✭ 219 (+742.31%)
Mutual labels:  state-management
Unstated Debug
Debug your Unstated containers with ease
Stars: ✭ 231 (+788.46%)
Mutual labels:  state-management

mount up

mount manages stateful components.

mount-up let's you know whenever any of these components are "managed".

Clojars Project

Ups and Downs

There are three types of events you can listen to:

Whenever any state / component is

  • started
(on-up [k f when])
  • stopped
(on-down [k f when])
  • started and/or stopped
(on-upndown [k f when])

where:

k: key / name of the listner
f: function / listener
when: when to apply f. possible values :before, :after or :wrap-in

Listener

As anything good in Clojure, listener is just a function.

This function will be passed a map with :name and :action keys.

:name will have a component's name
:action will have an action taked: i.e. :up or :down

Logging

mount-up comes with one such listener that logs whenever any of the states / components are started or stopped:

(defn log [{:keys [name action]}]
  (case action
    :up (log/info ">> starting.." name)
    :down (log/info "<< stopping.." name)))

Listening to Ups and Downs

Let's use the log function above as an example.

$ boot dev

Creating a server component, starting it and stopping it as usual:

boot.user=> (require '[mount.core :as mount :refer [defstate]])
nil
boot.user=> (defstate server :start 42 :stop -42)
#'boot.user/server

boot.user=> (mount/start)
{:started ["#'boot.user/server"]}

boot.user=> (mount/stop)
{:stopped ["#'boot.user/server"]}

On up

Now let's listen whenever this component is started and log :before it happens:

boot.user=> (require '[mount-up.core :as mu])
nil
boot.user=> (mu/on-up :info mu/log :before)
{:info #object[clojure.core$partial$fn__4761 0x703ef68c "clojure.core$partial$fn__4761@703ef68c"]}

boot.user=> (mount/start)
INFO  mount-up.core - >> starting.. #'boot.user/server
{:started ["#'boot.user/server"]}

boot.user=> (mount/stop)
{:stopped ["#'boot.user/server"]}

Removing all the listeners

We can also clear all the listeners by all-clear:

boot.user=> (mu/all-clear)
nil
boot.user=> (mount/start)
{:started ["#'boot.user/server"]}
boot.user=> (mount/stop)
{:stopped ["#'boot.user/server"]}

On up and down

boot.user=> (mu/on-upndown :info mu/log :before)
{:info #object[clojure.core$partial$fn__4761 0x75fda4b5 "clojure.core$partial$fn__4761@75fda4b5"]}

boot.user=> (mount/start)
INFO  mount-up.core - >> starting.. #'boot.user/server
{:started ["#'boot.user/server"]}

boot.user=> (mount/stop)
INFO  mount-up.core - << stopping.. #'boot.user/server
{:stopped ["#'boot.user/server"]}

mu/log function is just an example of course: any function(s) can be used as a listener.

Wrapping

Besides :before and :after, mount-up knows how to wrap ups and downs with a custom function via :wrap-in.

This is really useful in case you need to be in charge of calling start or stop for each individual state. For example to guard ups and downs of each state with a try/catch.

A "wrapper" function takes two arguments:

f: a function that is going to bring state up or down
state-name: a name of the state (i.e. "#'app/db")

Function f will be provided by mount and will just need to be invoked as (f) to start/stop the state. The rest is up to you.

Exception Handling

It is a lot simpler to demo than to explain.

mount-up comes with a generic try-catch function:

(defn try-catch [on-error]
  (fn [f state]
    (try (f)
         (catch Throwable t
           (on-error t state)))))

which returns a function that takes f and state (name) and wraps calling (f) in a try/catch. It takes an on-error function that will decide what will happen if starting or stopping state results in a Throwable.

Let's define a sample on-error function that will eat (ouch!) the exception and will log what happened:

boot.user=> (defn log-exception [ex _]
              (let [root (.getMessage (.getCause ex))]
                (log/error (str (.getMessage ex) " \"" root \"))))
#'boot.user/log-exception

Let's define three states, one of which throws an exception:

boot.user=> (defstate server :start 42 :stop -42)
#'boot.user/server
boot.user=> (defstate db :start (/ 1 0) :stop -42)
#'boot.user/db
boot.user=> (defstate pi :start 3.14 :stop 14.3)
#'boot.user/pi

Let's start these without wrapping anything:

boot.user=> (mount/start)
INFO  mount-up.core - >> starting.. #'boot.user/server
INFO  mount-up.core - >> starting.. #'boot.user/db

java.lang.ArithmeticException: Divide by zero
   java.lang.RuntimeException: could not start [#'boot.user/db] due to

As expected #'boot.user/db throws an exception and we have no control over it. Also notice that system failed (which in most cases is the right behavior), so #'boot.user/pi was not even attempted to start.

Let's plug in a sample try-catcher "on-up" and see what it does:

boot.user=> (mu/on-up :guard (mu/try-catch log-exception) :wrap-in)
{:guard
 #object[clojure.core$partial$fn__4761 0x7fbb46f2 "clojure.core$partial$fn__4761@7fbb46f2"],
 :info
 #object[clojure.core$partial$fn__4761 0x656ab49 "clojure.core$partial$fn__4761@656ab49"]}

(we still have the :info logger from the above section to help with a visual)

Notice the :wrap-in instead of :after or :before.

Let's stop and start it again:

boot.user=> (mount/stop)
{:stopped ["#'boot.user/server"]}
boot.user=> (mount/start)
INFO  mount-up.core - >> starting.. #'boot.user/server
INFO  mount-up.core - >> starting.. #'boot.user/db
ERROR boot.user - could not start [#'boot.user/db] due to "Divide by zero"
INFO  mount-up.core - >> starting.. #'boot.user/pi
{:started ["#'boot.user/server" "#'boot.user/pi"]}

this time we "controlled" the exception, reported the problem and decided the system may start without a database.

Let's check what all these state look like:

boot.user=> (require '[mount.tools.graph :as graph])
boot.user=> (graph/states-with-deps)
({:name "#'boot.user/server", :order 1, :status #{:started}, :deps #{}}
 {:name "#'boot.user/db", :order 2, :status #{:stopped}, :deps #{}}
 {:name "#'boot.user/pi", :order 3, :status #{:started}, :deps #{}})

again, a built in try-catch is just an example of a custom wrapper function.

License

Copyright © 2018 tolitius

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].