{"id":19345918,"url":"https://github.com/tolitius/mount-up","last_synced_at":"2025-06-27T16:35:55.459Z","repository":{"id":62435401,"uuid":"77630491","full_name":"tolitius/mount-up","owner":"tolitius","description":"watching mount's ups and downs","archived":false,"fork":false,"pushed_at":"2020-05-05T22:00:41.000Z","size":12,"stargazers_count":27,"open_issues_count":4,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-12T19:19:55.784Z","etag":null,"topics":["clojure","mount","state-management"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tolitius.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}},"created_at":"2016-12-29T18:00:39.000Z","updated_at":"2024-11-26T12:34:50.000Z","dependencies_parsed_at":"2022-11-01T21:02:40.257Z","dependency_job_id":null,"html_url":"https://github.com/tolitius/mount-up","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/tolitius/mount-up","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolitius%2Fmount-up","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolitius%2Fmount-up/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolitius%2Fmount-up/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolitius%2Fmount-up/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tolitius","download_url":"https://codeload.github.com/tolitius/mount-up/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolitius%2Fmount-up/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262294103,"owners_count":23288879,"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":["clojure","mount","state-management"],"created_at":"2024-11-10T04:08:21.511Z","updated_at":"2025-06-27T16:35:55.433Z","avatar_url":"https://github.com/tolitius.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mount up\n\n[mount](https://github.com/tolitius/mount) manages stateful components.\n\nmount-up let's you know whenever any of these components are \"managed\".\n\n[![Clojars Project](http://clojars.org/tolitius/mount-up/latest-version.svg)](http://clojars.org/tolitius/mount-up)\n\n- [Ups and Downs](#ups-and-downs)\n- [Listener](#listener)\n  - [Logging](#logging)\n- [Listening to Ups and Downs](#listening-to-ups-and-downs)\n  - [On up](#on-up)\n  - [Removing all the listeners](#removing-all-the-listeners)\n  - [On up and down](#on-up-and-down)\n- [Wrapping](#wrapping)\n  - [Exception Handling](#exception-handling)\n- [License](#license)\n\n## Ups and Downs\n\nThere are three types of events you can listen to:\n\nWhenever any state / component is\n\n* started\n\n```clojure\n(on-up [k f when])\n```\n\n* stopped\n\n```clojure\n(on-down [k f when])\n```\n\n* started and/or stopped\n\n```clojure\n(on-upndown [k f when])\n```\n\nwhere:\n\n`k`: key / name of the listner\u003cbr/\u003e\n`f`: function / listener\u003cbr/\u003e\n`when`: when to apply `f`. possible values `:before`, `:after` or `:wrap-in`\n\n## Listener\n\nAs anything good in Clojure, listener is just a function.\n\nThis function will be passed a map with `:name` and `:action` keys.\n\n`:name` will have a component's name\u003cbr/\u003e\n`:action` will have an action taked: i.e. `:up` or `:down`\n\n### Logging\n\nmount-up comes with one such listener that logs whenever any of the states / components are started or stopped:\n\n```clojure\n(defn log [{:keys [name action]}]\n  (case action\n    :up (log/info \"\u003e\u003e starting..\" name)\n    :down (log/info \"\u003c\u003c stopping..\" name)))\n```\n\n## Listening to Ups and Downs\n\nLet's use the `log` function above as an example.\n\n```clojure\n$ boot dev\n```\n\nCreating a server component, starting it and stopping it as usual:\n\n```clojure\nboot.user=\u003e (require '[mount.core :as mount :refer [defstate]])\nnil\nboot.user=\u003e (defstate server :start 42 :stop -42)\n#'boot.user/server\n\nboot.user=\u003e (mount/start)\n{:started [\"#'boot.user/server\"]}\n\nboot.user=\u003e (mount/stop)\n{:stopped [\"#'boot.user/server\"]}\n```\n\n### On up\n\nNow let's listen whenever this component is started and log _:before_ it happens:\n\n```clojure\nboot.user=\u003e (require '[mount-up.core :as mu])\nnil\nboot.user=\u003e (mu/on-up :info mu/log :before)\n{:info #object[clojure.core$partial$fn__4761 0x703ef68c \"clojure.core$partial$fn__4761@703ef68c\"]}\n\nboot.user=\u003e (mount/start)\nINFO  mount-up.core - \u003e\u003e starting.. #'boot.user/server\n{:started [\"#'boot.user/server\"]}\n\nboot.user=\u003e (mount/stop)\n{:stopped [\"#'boot.user/server\"]}\n```\n\n### Removing all the listeners\n\nWe can also clear all the listeners by `all-clear`:\n\n```clojure\nboot.user=\u003e (mu/all-clear)\nnil\nboot.user=\u003e (mount/start)\n{:started [\"#'boot.user/server\"]}\nboot.user=\u003e (mount/stop)\n{:stopped [\"#'boot.user/server\"]}\n```\n\n### On up and down\n\n```clojure\nboot.user=\u003e (mu/on-upndown :info mu/log :before)\n{:info #object[clojure.core$partial$fn__4761 0x75fda4b5 \"clojure.core$partial$fn__4761@75fda4b5\"]}\n\nboot.user=\u003e (mount/start)\nINFO  mount-up.core - \u003e\u003e starting.. #'boot.user/server\n{:started [\"#'boot.user/server\"]}\n\nboot.user=\u003e (mount/stop)\nINFO  mount-up.core - \u003c\u003c stopping.. #'boot.user/server\n{:stopped [\"#'boot.user/server\"]}\n```\n\n`mu/log` function is just an example of course: any function(s) can be used as a listener.\n\n## Wrapping\n\nBesides `:before` and `:after`, mount-up knows how to _wrap_ ups and downs with a custom function via `:wrap-in`.\n\nThis is really useful in case you need to be in charge of calling start or stop for each individual state.\nFor example to guard ups and downs of each state with a `try/catch`.\n\nA \"wrapper\" function takes two arguments:\n\n`f`: a function that is going to bring state up or down\u003cbr/\u003e\n`state-name`: a name of the state (i.e. `\"#'app/db\"`)\u003cbr/\u003e\n\nFunction `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.\n\n### Exception Handling\n\nIt is a lot simpler to demo than to explain.\n\nmount-up comes with a generic `try-catch` function:\n\n```clojure\n(defn try-catch [on-error]\n  (fn [f state]\n    (try (f)\n         (catch Throwable t\n           (on-error t state)))))\n```\n\nwhich returns a function that takes `f` and `state` (name) and wraps calling `(f)` in a `try/catch`. It takes an `on-error` function\nthat will decide what will happen if starting or stopping state results in a `Throwable`.\n\nLet's define a sample `on-error` function that will eat (ouch!) the exception and will log what happened:\n\n```clojure\nboot.user=\u003e (defn log-exception [ex _]\n              (let [root (.getMessage (.getCause ex))]\n                (log/error (str (.getMessage ex) \" \\\"\" root \\\"))))\n#'boot.user/log-exception\n```\n\nLet's define three states, one of which throws an exception:\n\n```clojure\nboot.user=\u003e (defstate server :start 42 :stop -42)\n#'boot.user/server\nboot.user=\u003e (defstate db :start (/ 1 0) :stop -42)\n#'boot.user/db\nboot.user=\u003e (defstate pi :start 3.14 :stop 14.3)\n#'boot.user/pi\n```\n\nLet's start these without wrapping anything:\n\n```clojure\nboot.user=\u003e (mount/start)\nINFO  mount-up.core - \u003e\u003e starting.. #'boot.user/server\nINFO  mount-up.core - \u003e\u003e starting.. #'boot.user/db\n\njava.lang.ArithmeticException: Divide by zero\n   java.lang.RuntimeException: could not start [#'boot.user/db] due to\n```\n\nAs expected `#'boot.user/db` throws an exception and we have no control over it. Also notice that system failed\n(which in most cases is the right behavior), so `#'boot.user/pi` was not even attempted to start.\n\nLet's plug in a sample `try-catcher` \"on-up\" and see what it does:\n\n```clojure\nboot.user=\u003e (mu/on-up :guard (mu/try-catch log-exception) :wrap-in)\n{:guard\n #object[clojure.core$partial$fn__4761 0x7fbb46f2 \"clojure.core$partial$fn__4761@7fbb46f2\"],\n :info\n #object[clojure.core$partial$fn__4761 0x656ab49 \"clojure.core$partial$fn__4761@656ab49\"]}\n```\n\n(we still have the `:info` logger from the above section to help with a visual)\n\nNotice the `:wrap-in` instead of `:after` or `:before`.\n\nLet's stop and start it again:\n\n```clojure\nboot.user=\u003e (mount/stop)\n{:stopped [\"#'boot.user/server\"]}\n```\n\n```clojure\nboot.user=\u003e (mount/start)\nINFO  mount-up.core - \u003e\u003e starting.. #'boot.user/server\nINFO  mount-up.core - \u003e\u003e starting.. #'boot.user/db\nERROR boot.user - could not start [#'boot.user/db] due to \"Divide by zero\"\nINFO  mount-up.core - \u003e\u003e starting.. #'boot.user/pi\n{:started [\"#'boot.user/server\" \"#'boot.user/pi\"]}\n```\n\nthis time we \"controlled\" the exception, reported the problem and _decided_ the system may start without a database.\n\nLet's check what all these state look like:\n\n```clojure\nboot.user=\u003e (require '[mount.tools.graph :as graph])\n```\n```clojure\nboot.user=\u003e (graph/states-with-deps)\n({:name \"#'boot.user/server\", :order 1, :status #{:started}, :deps #{}}\n {:name \"#'boot.user/db\", :order 2, :status #{:stopped}, :deps #{}}\n {:name \"#'boot.user/pi\", :order 3, :status #{:started}, :deps #{}})\n```\n\nagain, a built in `try-catch` is just an example of a custom wrapper function.\n\n## License\n\nCopyright © 2018 tolitius\n\nDistributed under the Eclipse Public License either version 1.0 or (at\nyour option) any later version.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftolitius%2Fmount-up","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftolitius%2Fmount-up","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftolitius%2Fmount-up/lists"}