mount

repository·master·Indexed 22 days ago

https://github.com/tolitius/mount

A lightweight state management library for Clojure and ClojureScript that makes application state easily reloadable during development. It provides the `defstate` macro to define lifecycle-managed states with `:start` and `:stop` hooks, allowing developers to reset specific components or the entire application state within a REPL session without restarting.

Tokens
5.6K
Snippets
29
Records
34
Agent score
79%

What's inside @tolitius/mount

  1. How mount differs from Component

    master

    While the Component framework is a popular industry standard for managing lifecycle via inversion of control (treating components as objects), mount is designed as a lightweight library that leverages Clojure's native namespace and dependency system.

    Key conceptual differences:

    • Integration: Component is a framework requiring 'whole app buy-in' (everything must be a component), whereas mount is a library that allows you to add defstate incrementally to any existing application.
    • Mental Model: Component uses an Object-Oriented approach (objects and methods), while mount uses a functional/namespace approach (namespaces and vars).
    • Dependency Management: Component uses a dedicated dependency library to build a graph; mount relies on the Clojure compiler's :require and :use mechanisms to determine the order of states.
    • Complexity: mount reduces boilerplate by using a simple defstate macro instead of requiring records, protocols, and lifecycle implementations.
  2. Cleanup deleted states

    master

    Mount automatically detects when a state is renamed or deleted from a namespace (e.g., via ns-unmap in a REPL). When this happens:

    1. If the state had a :stop function, Mount invokes it to perform cleanup.
    2. Mount removes all internal knowledge of that state.

    This allows for seamless reloading in development environments.

  3. Compose application states using the Composer's Toolbox

    master

    You can compose complex application startup scenarios by chaining transformation functions. These functions do not start or stop states themselves; they transform the set of states that will eventually be passed to (mount/start).

    If called with two arguments, the first argument is treated as the 'universe' of states to work with. If called with one argument, it works with all states known to mount.

    Available Tools:

    • only: Returns only the states provided that exist in the application.
    • except: Returns all states except the ones provided.
    • swap: Takes a map where keys are states and values are their substitute values.
    • swap-states: Takes a map where keys are states and values are substitute states defined as {:start fn :stop fn}.
    • with-args: Takes a map of runtime arguments that can later be accessed via (mount/args).
    (-> (only #{#'foo/a #'foo/b #'foo/c #'bar/d #'baz/e})
        (except [#'foo/c #'bar/d])
        (with-args {:a 42})
        mount/start)
  4. Run the sample Clojure application

    master

    To explore mount using the built-in New York Stock Exchange (NYSE) sample application, clone the repository and use boot repl or lein repl to enter a REPL. Once in the REPL, switch to the (dev) namespace to access the application states.

    The sample app demonstrates four interconnected states:

    1. config: Loaded from files and refreshed on (reset).
    2. datomic connection: Created using the config state.
    3. nyse web app: A web server with Compojure routes.
    4. nrepl: Binds to host/port using config.
    $ boot repl
    
    user=> (dev)
    #object[clojure.lang.Namespace 0xcf1a0cc "dev"]
  5. Switch Mount to Clojure and ClojureScript mode

    master

    By default, mount operates in clj mode. To support both Clojure and ClojureScript with a consistent API, you can switch to cljc mode.

    In cljc mode, all states are "derefable", meaning you must use the @ operator to access their values. This mode is designed to make the API identical across both platforms.

    Important: You only need to call (mount/in-cljc-mode) once on the server (Clojure) side. You do not need to call it on the ClojureScript side.

    (mount/in-cljc-mode)
  6. Enable cljc mode for ClojureScript support

    master

    By default, Mount uses var references which works for Clojure but is unsuitable for ClojureScript (especially during :advanced compilation where var names are compressed). To support ClojureScript, enable cljc mode using (mount/in-cljc-mode).

    In cljc mode, states that are not explicitly started via (mount/start ...) or are not transitive dependencies (not :required at the time of start) will start lazily when they are first dereferenced.

    (mount/in-cljc-mode)
  7. Integrate mount with tools.namespace for hot reloading

    master

    You can combine mount with tools.namespace to make your entire application reloadable. By defining a reset function that stops the mount system and then refreshes the namespaces, you can reload code and state without restarting the REPL.

    (defn go []
      (start)
      :ready)
    
    (defn reset []
      (stop)
      (tn/refresh :after 'dev/go))
  8. Package applications by requiring state namespaces

    master

    Because Mount relies on the compiler to discover states, all namespaces containing defstate must be compiled before (mount/start) is called.

    When packaging a standalone JAR/WAR or using :init hooks (like in lein-ring or boot-http), ensure your application entry point namespace :requires the namespaces containing your states. You only need to require the top-level namespaces; others will be brought in transitively.

    (ns my.app
      (:require [a] 
                [b] 
                [c] 
                [mount.core :as mount]))
    
    (defn rock-n-roll []
      (mount/start))
  9. Package and run a standalone Uberjar

    master

    Once your application is configured with a mount/start entry point and the appropriate :uberjar settings in project.clj, you can package it using Leiningen:

    $ lein do clean, uberjar

    This produces a standalone JAR file in the target/ directory. You can run the application using the standard java -jar command:

    $ java -jar target/your-app-name-standalone.jar
  10. Reload application state in REPL

    master

    To reload the entire application state, use mount.core/stop followed by mount.core/start. This will walk through all defined defstate entities, calling their :stop functions in reverse order of their start order, and then calling their :start functions in the correct dependency order.

    (require '[mount.core :as mount])
    
    (mount/stop)
    (mount/start)
  11. Start, restart, or reset the application with (reset)

    master

    In a development REPL, use (reset) to start the application or reload all namespaces and restart the states. (reset) is a convenience function that performs both a (mount/stop) / (mount/start) cycle and a namespace refresh.

    Note that because states like database connections may have :stop hooks that perform cleanup (e.g., deleting a database), calling (reset) will return the application to its initial 'clean' state.

    dev=> (reset)
    
    :reloading (mount.tools.macro mount.core app.utils.logging app.conf app.db app.utils.datomic app.nyse app.www app.example dev)
    INFO  app.utils.logging - >> starting..  #'app.conf/config
    ... 
    :ready