{"id":13681937,"url":"https://github.com/weavejester/integrant","last_synced_at":"2025-05-13T18:07:03.097Z","repository":{"id":37549695,"uuid":"75573542","full_name":"weavejester/integrant","owner":"weavejester","description":"Micro-framework for data-driven architecture","archived":false,"fork":false,"pushed_at":"2025-02-12T16:49:51.000Z","size":353,"stargazers_count":1277,"open_issues_count":17,"forks_count":62,"subscribers_count":41,"default_branch":"master","last_synced_at":"2025-05-03T00:05:39.507Z","etag":null,"topics":["clojure","data-driven","micro-framework"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/weavejester.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","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,"zenodo":null}},"created_at":"2016-12-05T00:18:16.000Z","updated_at":"2025-04-27T22:03:55.000Z","dependencies_parsed_at":"2023-11-30T17:55:26.450Z","dependency_job_id":"d6309a1b-6f6d-45a4-bd49-dc9dfbaa3766","html_url":"https://github.com/weavejester/integrant","commit_stats":{"total_commits":250,"total_committers":16,"mean_commits":15.625,"dds":0.08799999999999997,"last_synced_commit":"58f605a834835f6d01bcb83709eed64dbf5f830c"},"previous_names":[],"tags_count":39,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/weavejester%2Fintegrant","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/weavejester%2Fintegrant/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/weavejester%2Fintegrant/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/weavejester%2Fintegrant/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/weavejester","download_url":"https://codeload.github.com/weavejester/integrant/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254000848,"owners_count":21997441,"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","data-driven","micro-framework"],"created_at":"2024-08-02T13:01:38.039Z","updated_at":"2025-05-13T18:07:03.072Z","avatar_url":"https://github.com/weavejester.png","language":"Clojure","funding_links":[],"categories":["Clojure","Dependency injection"],"sub_categories":[],"readme":"# Integrant [![Build Status](https://github.com/weavejester/integrant/actions/workflows/test.yml/badge.svg)](https://github.com/weavejester/integrant/actions/workflows/test.yml)\n\n\u003e integrant /ˈɪntɪɡr(ə)nt/\n\u003e\n\u003e (of parts) making up or contributing to a whole; constituent.\n\nIntegrant is a Clojure (and ClojureScript) micro-framework for\nbuilding applications with data-driven architecture. It can be thought\nof as an alternative to [Component][] or [Mount][], and was inspired\nby [Arachne][] and through work on [Duct][].\n\n[component]: https://github.com/stuartsierra/component\n[mount]: https://github.com/tolitius/mount\n[arachne]: http://arachne-framework.org/\n[duct]: https://github.com/duct-framework/duct\n\n## Rationale\n\nIntegrant was built as a reaction to fix some perceived weaknesses\nwith Component.\n\nIn Component, systems are created programmatically. Constructor\nfunctions are used to build records, which are then assembled into\nsystems.\n\nIn Integrant, systems are created from a configuration data structure,\ntypically loaded from an [edn][] resource. The architecture of the\napplication is defined through data, rather than code.\n\nIn Component, only records or maps may have dependencies. Anything\nelse you might want to have dependencies, like a function, needs to be\nwrapped in a record.\n\nIn Integrant, anything can be dependent on anything else. The\ndependencies are resolved from the configuration before it's\ninitialized into a system.\n\n[edn]: https://github.com/edn-format/edn\n\n## Installation\n\nAdd the following dependency to your deps.edn file:\n\n    integrant/integrant {:mvn/version \"0.13.1\"}\n\nOr this to your Leiningen dependencies:\n\n    [integrant \"0.13.1\"]\n\n## Presentations\n\n* [Enter Integrant](https://skillsmatter.com/skillscasts/9820-enter-integrant)\n\n## Usage\n\n### Configurations\n\nIntegrant starts with a configuration map. Each top-level key in the\nmap represents a configuration that can be \"initialized\" into a\nconcrete implementation. Configurations can reference other keys via\nthe `ref` (or `refset`) function.\n\nFor example:\n\n```clojure\n(require '[integrant.core :as ig])\n\n(def config\n  {:adapter/jetty {:port 8080, :handler (ig/ref :handler/greet)}\n   :handler/greet {:name \"Alice\"}})\n```\n\nAlternatively, you can specify your configuration as pure edn:\n\n```edn\n{:adapter/jetty {:port 8080, :handler #ig/ref :handler/greet}\n :handler/greet {:name \"Alice\"}}\n```\n\nAnd load it with Integrant's version of `read-string`:\n\n```clojure\n(def config\n  (ig/read-string (slurp \"config.edn\")))\n```\n\n### Initializing and halting\n\nOnce you have a configuration, Integrant needs to be told how to\nimplement it. The `init-key` multimethod takes two arguments, a key\nand its corresponding value, and tells Integrant how to initialize it:\n\n```clojure\n(require '[ring.adapter.jetty :as jetty]\n         '[ring.util.response :as resp])\n\n(defmethod ig/init-key :adapter/jetty [_ {:keys [handler] :as opts}]\n  (jetty/run-jetty handler (-\u003e opts (dissoc :handler) (assoc :join? false))))\n\n(defmethod ig/init-key :handler/greet [_ {:keys [name]}]\n  (fn [_] (resp/response (str \"Hello \" name))))\n```\n\nKeys are initialized recursively, with the values in the map being\nreplaced by the return value from `init-key`.\n\nIn the configuration we defined before, `:handler/greet` will be\ninitialized first, and its value replaced with a handler function.\nWhen `:adapter/jetty` references `:handler/greet`, it will receive the\ninitialized handler function, rather than the raw configuration.\n\nThe `halt-key!` multimethod tells Integrant how to stop and clean up\nafter a key. Like `init-key`, it takes two arguments, a key and its\ncorresponding initialized value.\n\n```clojure\n(defmethod ig/halt-key! :adapter/jetty [_ server]\n  (.stop server))\n```\n\nNote that we don't need to define a `halt-key!` for `:handler/greet`.\n\nOnce the multimethods have been defined, we can use the `init` and\n`halt!` functions to handle entire configurations. The `init` function\nwill start keys in dependency order, and resolve references as it\ngoes:\n\n```clojure\n(def system\n  (ig/init config))\n```\n\nWhen a system needs to be shut down, `halt!` is used:\n\n```clojure\n(ig/halt! system)\n```\n\nLike Component, `halt!` shuts down the system in reverse dependency\norder. Unlike Component, `halt!` is entirely side-effectful. The\nreturn value should be ignored, and the system structure discarded.\n\nIt's also important that `halt-key!` is **idempotent**. We should be\nable to run it multiple times on the same key without issue.\n\nIntegrant marks functions that are entirely side-effectful with an\nending `!`. You should ignore the return value of any function ending\nin a `!`.\n\nBoth `init` and `halt!` can take a second argument of a collection of\nkeys. If this is supplied, the functions will only initiate or halt\nthe supplied keys (and any referenced keys). For example:\n\n```clojure\n(def system\n  (ig/init config [:adapter/jetty]))\n```\n\n#### Initializer functions\n\nSometimes all that is necessary is `init-key`, particularly if what is\nbeing initiated is all in memory, and we can rely on the garbage\ncollector to clean up afterwards.\n\nFor this purpose, `init-key` will try to find a function with the\n**same namespace and name** as the keyword, if no more specific method\nis set. For example:\n\n```clojure\n(def config\n  {::sugared-greet {:name \"Alice\"}})\n\n(defn sugared-greet [{:keys [name]}]\n  (println \"Hi\" name))\n\n(ig/init config)\n```\n\nThe `sugared-greet` function is equivalent to the `init-key` method:\n\n```clojure\n(defmethod ig/init-key ::sugared-greet [_ {:keys [name]}]\n  (println \"Hi\" name))\n```\n\nNote that the function needs to be in a loaded namespace for `init-key`\nto find it. The `integrant.core/load-namespaces` function can be used on\na configuration to load namespaces matching the keys.\n\n\n### Suspending and resuming\n\nDuring development, we often want to rebuild a system, but not to\nclose open connections or terminate running threads. For this purpose\nIntegrant has the `suspend!` and `resume` functions.\n\nThe `suspend!` function acts like `halt!`:\n\n```clojure\n(ig/suspend! system)\n```\n\nBy default this functions the same as `halt!`, but we can customize\nthe behavior with the `suspend-key!` multimethod to keep open\nconnections and resources that `halt-key!` would close.\n\nLike `halt-key!`, `suspend-key!` should be both side-effectful and\nidempotent.\n\nThe `resume` function acts like `init` but takes an additional\nargument specifying a suspended system:\n\n```clojure\n(def new-system\n  (ig/resume config system))\n```\n\nBy default the system argument is ignored and `resume` functions the\nsame as `init`, but as with `suspend!` we can customize the behavior\nwith the `resume-key` multimethod. If we implement this method, we can\nreuse open resources from the suspended system.\n\nTo illustrate this, let's reimplement the Jetty adapter with the\ncapability to suspend and resume:\n\n```clojure\n(defmethod ig/init-key :adapter/jetty [_ opts]\n  (let [handler (atom (delay (:handler opts)))\n        options (-\u003e opts (dissoc :handler) (assoc :join? false))]\n    {:handler handler\n     :server  (jetty/run-jetty (fn [req] (@@handler req)) options)}))\n\n(defmethod ig/halt-key! :adapter/jetty [_ {:keys [server]}]\n  (.stop server))\n\n(defmethod ig/suspend-key! :adapter/jetty [_ {:keys [handler]}]\n  (reset! handler (promise)))\n\n(defmethod ig/resume-key :adapter/jetty [key opts old-opts old-impl]\n  (if (= (dissoc opts :handler) (dissoc old-opts :handler))\n    (do (deliver @(:handler old-impl) (:handler opts))\n        old-impl)\n    (do (ig/halt-key! key old-impl)\n        (ig/init-key key opts))))\n```\n\nThis example may require some explanation. Instead of passing the\nhandler directly to the web server, we put it in an `atom`, so that we\ncan change the handler without restarting the server.\n\nWe further encase the handler in a `delay`. This allows us to replace\nit with a `promise` when we suspend the server. Because a promise will\nblock until a value is delivered, once suspended the server will\naccept requests but wait around until it's resumed.\n\nOnce we decide to resume the server, we first check to see if the\noptions have changed. If they have, we don't take any chances; better\nto halt and re-init from scratch. If the server options haven't\nchanged, then deliver the new handler to the promise which unblocks\nthe server.\n\nNote that we only need to go to this additional effort if retaining\nopen resources is useful during development, otherwise we can rely on\nthe default `init` and `halt!` behavior. In production, it's always\nbetter to terminate and restart.\n\nLike `init` and `halt!`, `resume` and `suspend!` can be supplied with\na collection of keys to narrow down the parts of the configuration\nthat are suspended or resumed.\n\n### Resolving\n\nIt's sometimes useful to hide information when resolving a\nreference. In our previous example, we changed the initiation from:\n\n```clojure\n(defmethod ig/init-key :adapter/jetty [_ {:keys [handler] :as opts}]\n  (jetty/run-jetty handler (-\u003e opts (dissoc :handler) (assoc :join? false))))\n```\n\nTo:\n\n```clojure\n(defmethod ig/init-key :adapter/jetty [_ opts]\n  (let [handler (atom (delay (:handler opts)))\n        options (-\u003e opts (dissoc :handler) (assoc :join? false))]\n    {:handler handler\n     :server  (jetty/run-jetty (fn [req] (@@handler req)) options)}))\n```\n\nThis changed the return value from a Jetty server object to a map, so\nthat `suspend!` and `resume` would be able to temporarily block the\nhandler. However, this also changes the return type! Ideally, we'd want\nto pass the handler atom to `suspend-key!` and `resume-key`, without\naffecting how references are resolved in the configuration.\n\nTo solve this, we can use `resolve-key`:\n\n```clojure\n(defmethod ig/resolve-key :adapter/jetty [_ {:keys [server]}]\n  server)\n```\n\nBefore a reference is resolved, `resolve-key` is applied. This allows\nus to cut out information that is only relevant behind the scenes. In\nthis case, we replace the map with the container Jetty server object.\n\n### Expanding\n\nBefore being initiated, keys can be *expanded*. Expansions can be\nthought of as the equivalent of macros in normal Clojure code.\n\nAn expansion is defined via the `expand-key` method. When `expand` is\ncalled on a configuration map, `expand-key` is called on every key/value\nthat implements that method, and then the results are deep-merged into a\nnew configuration map.\n\nExpansions are used to abstract groups of keys. For example:\n\n```clojure\n(defmethod ig/expand-key :module/greet [_ {:keys [name]}]\n  {:adapter/jetty {:port 8080, :handler (ig/ref :handler/greet)}\n   :handler/greet {:name name}})\n```\n\nThis would be used in a configuration in the following way:\n\n```edn\n{:module/greet {:name \"Alice\"}}\n```\n\nAnd would expand to:\n\n```edn\n{:adapter/jetty {:port 8080, :handler #ig/ref :handler/greet}\n :handler/greet {:name \"Alice\"}}\n```\n\nThis allows for commonly used configurations to be factored out into\nreusable modules, while still allowing the expansion to be inspected and\nmodified. For example:\n\n```edn\n{:module/greet {:name \"Alice\"}\n :adapter/jetty {:port 3000}}\n```\n\nThe port set via `:adapter/jetty` will override the port set when\n`:module/greet` is expanded:\n\n```edn\n{:adapter/jetty {:port 3000, :handler #ig/ref :handler/greet}\n :handler/greet {:name \"Alice\"}}\n```\n\nThis is because values you set via the configuration are considered to\nbe higher priority than those generated via expansions. This can also\nbe used to resolve conflicts in expansions. For example, suppose we have\nan expansion:\n\n```clojure\n(defmethod ig/expand-key :module/web-server [_ _]\n  {:adapter/jetty {:port 80}})\n```\n\nIf we used this together with our `:module/greet` expansion, the port\nnumber would conflict. `:module/web-server` would want it to be `80`,\nwhile `:module/greet` would want it to be `8080`. Integrant can't\nresolve this on its own (it will raise an exception), so we need to\nspecify which value it should use:\n\n```edn\n{:module/greet {:name \"Alice\"}\n :module/web-server {}\n :adapter/jetty {:port 80}}\n```\n\nTo make use of expansions, the `integrant.core/expand` function needs to\nbe called before the configuration is initiated:\n\n```clojure\n(-\u003e config ig/expand ig/init)\n```\n\n#### Replacing prep\n\nExpansions supplant the now deprecated `prep` and `prep-key`. If you had a\nmethod that looked like:\n\n```clojure\n(defmethod ig/prep-key ::example [_ v]\n  (assoc v :example \"example prep\"))\n```\n\nThis should be turned into an `expand-key` method like this:\n\n```clojure\n(defmethod ig/expand-key ::example [k v]\n  {k (assoc v :example \"example prep\")})\n```\n\n### Profiles\n\nSometimes Integrant operates in multiple environments that require the\nconfiguration to be slightly changed. This is where profiles can be\nuseful.\n\nFor example, suppose we wanted the web server to run on port 8080 in the\ndevelopment environment, and port 80 in the production environment:\n\n```edn\n{:adapter/jetty {:port #ig/profile {:dev 8080, :prod 80}}}\n```\n\nTo choose a profile to use, the `integrant.core/deprofile` function is\nused:\n\n```clojure\n(ig/deprofile config [:dev])\n```\n\nThis function takes the configuration and an ordered collection of\nprofile keys. The keys are tried in sequence until one fits. If there's\na profile that no supplied key fits, an exception is raised.\n\nIn the above case, it will return a configuration map:\n\n```edn\n{:adapter/jetty {:port 8080}}\n```\n\n#### Profiles and expansions\n\nIt's sometimes useful for expansions to be given profiles. For example:\n\n```clojure\n(defmethod ig/expand-key :module/greet [_ {:keys [name]}]\n  (ig/profile\n   :dev  {:adapter.jetty {:port 8080, :handler (ig/ref :handler/debug)}\n          :handler/debug {:name name}}\n   :prod {:adapter/jetty {:port 80, :handler (ig/ref :handler/greet)}\n          :handler/greet {:name name}})\n```\n\nThe expansion generated depends on the profile used. To use this, the\ndeprofile step needs to be carried out after the expansion, but before\nthe expansions are merged. Fortunately, the `expand` function allows for\na inner processing function to be supplied:\n\n```clojure\n(ig/expand config #(ig/deprofile % [:dev]))\n```\n\nThis ensures the deprofile step is applied before the expansions are\nmerged. This can also be written more concisely as:\n\n```clojure\n(ig/expand config (ig/deprofile [:dev]))\n```\n\n## Vars\n\nA var is a placeholder for a value that will be added later to the\nconfiguration:\n\n```edn\n{:adapter/jetty {:port #ig/var port}}\n```\n\nThis can be set by supplying a map of values to the `bind` function:\n\n```clojure\n(ig/bind config {'port 8080})\n```\n\nIf there are any unbound vars when `init` is called, an exception will\nbe thrown.\n\n### Derived keywords\n\nKeywords have an inherited hierarchy. Integrant takes advantage of\nthis by allowing keywords to refer to their descendants. For example:\n\n```clojure\n(derive :adapter/jetty :adapter/ring)\n```\n\nThis sets up a hierarchical relationship, where the specific\n`:adapter/jetty` keyword is derived from the more generic\n`:adapter/ring`.\n\nWe can now use `:adapter/ring` in place of `:adapter/jetty`:\n\n```clojure\n(ig/init config [:adapter/ring])\n```\n\nWe can also use it as a reference, but only if the reference is\nunambiguous, and only refers to one key in the configuration.\n\n### Composite keys\n\nSometimes it's useful to have two keys of the same type in your\nconfiguration. For example, you may want to run two Ring adapters on\ndifferent ports.\n\nOne way would be to create two new keywords, derived from a common\nparent:\n\n```clojure\n(derive :example/web-1 :adapter/jetty)\n(derive :example/web-2 :adapter/jetty)\n```\n\nYou could then write a configuration like:\n\n```edn\n{:example/web-1 {:port 8080, :handler #ig/ref :handler/greet}\n :example/web-2 {:port 8081, :handler #ig/ref :handler/greet}\n :handler/greet {:name \"Alice\"}}\n```\n\nHowever, you could also make use of composite keys. If your\nconfiguration contains a key that is a vector of keywords, Integrant\ntreats it as being derived from all the keywords inside it.\n\nSo you could also write:\n\n```edn\n{[:adapter/jetty :example/web-1] {:port 8080, :handler #ig/ref :handler/greet}\n [:adapter/jetty :example/web-2] {:port 8081, :handler #ig/ref :handler/greet}\n :handler/greet {:name \"Alice\"}}\n```\n\nThis syntax sugar allows you to avoid adding extra `derive`\ninstructions to your source code.\n\n### Composite references\n\nComposite references complement composite keys. A normal reference\nmatches any key derived from the value of the reference. A composite\nreference matches any key derived from every value in a vector.\n\nFor example:\n\n```edn\n{[:group/a :adapter/jetty] {:port 8080, :handler #ig/ref [:group/a :handler/greet]}\n [:group/a :handler/greet] {:name \"Alice\"}\n [:group/b :adapter/jetty] {:port 8081, :handler #ig/ref [:group/b :handler/greet]}\n [:group/b :handler/greet] {:name \"Bob\"}}\n```\n\nOne use of composite references is to provide a way of grouping keys\nin a configuration.\n\n### Refs vs refsets\n\nAn Integrant ref is used to reference another key in the\nconfiguration. The ref will be replaced with the initialized value of\nthe key. The ref does not need to refer to an exact key - the parent of\na derived key may be specified, so long as the ref is unambiguous.\n\nFor example suppose we have a configuration:\n\n```edn\n{:handler/greet    {:name #ig/ref :const/name}\n :const.name/alice {:name \"Alice\"}\n :const.name/bob   {:name \"Bob\"}}\n```\n\nAnd some definitions:\n\n```clojure\n(defmethod ig/init-key :const/name [_ {:keys [name]}]\n  name)\n\n(derive :const.name/alice :const/name)\n(derive :const.name/bob   :const/name)\n```\n\nIn this case `#ig/ref :const/name` is ambiguous - it could refer to\neither `:const.name/alice` or `:const.name/bob`. To fix this we could\nmake the reference more specific:\n\n```edn\n{:handler/greet    {:name #ig/ref :const.name/alice}\n :const.name/alice {:name \"Alice\"}\n :const.name/bob   {:name \"Bob\"}}\n```\n\nBut suppose we want to greet not just one person, but several. In this\ncase we can use a refset:\n\n```edn\n{:handler/greet-all {:names #ig/refset :const/name}\n :const.name/alice  {:name \"Alice\"}\n :const.name/bob    {:name \"Bob\"}}\n```\n\nWhen initialized, a refset will produce a set of all matching\nvalues.\n\n```clojure\n(defmethod ig/init-key :handler/greet-all [_ {:keys [names]}]\n  (fn [_] (resp/response (str \"Hello \" (clojure.string/join \", \" names))))\n```\n\n### Asserting\n\nIt's often useful to add assertions to ensure that the system has been\ninitiated correctly. This can be done via `assert-key`:\n\n```clojure\n(defmethod ig/assert-key :adapter/jetty [_ {:keys [port]}]\n  (assert (nat-int? port) \":port should be a valid port number\"))\n```\n\nIf we try to `init` an invalid configuration, then an `AssertionError`\nis thrown explaining the error:\n\n```\nuser=\u003e (ig/init {:adapter/jetty {:port \"3000\"}})\nAssertionError Assert failed: :port should be a valid port number\n(nat-int? port)  user/eval3088/fn--3090 (form-init14815382800832764134.clj:2\n```\n\nThis error is wrapped in an `clojure.lang.ExceptionInfo` that contains\nadditional information:\n\n```\nuser=\u003e (ex-data *e)\n{:reason :integrant.core/build-failed-spec\n :system {}\n :key    :adapter/jetty\n :value  {:port \"3000\"}}\n```\n\n### Loading namespaces\n\nIt can be hard to remember to load all the namespaces that contain the\nrelevant multimethods. If you name your keys carefully, Integrant can\nhelp via the `load-namespaces` function.\n\nIf a key has a namespace, `load-namespaces` will attempt to load\nit. It will also try concatenating the name of the key onto the end of\nits namespace, and loading that as well.\n\nFor example:\n\n```clojure\n(load-namespaces {:foo.component/bar {:message \"hello\"}})\n```\n\nThis will attempt to load the namespace `foo.component` and also\n`foo.component.bar`. A list of all successfully loaded namespaces will\nbe returned from the function. Missing namespaces are ignored.\n\n### Annotations\n\nNamespaced keywords may be annotated with metadata using the\n`integrant.core/annotate` function:\n\n```clojure\n(ig/annotate :adapter/jetty\n  {:doc \"A Ring adapter for the Jetty webserver.\"})\n```\n\nThis metadata can be recalled with `integrant.core/describe`:\n\n```clojure\n(ig/describe :adapter/jetty)\n;; =\u003e {:doc \"A Ring adapter for the Jetty webserver.\"}\n```\n\n### Loading hierarchies and annotations\n\nLoading a Clojure namespace can be slow and potentially side-effectful,\nand it's not possible to know ahead of time whether a namespace adds\nkeyword annotations or hierarchy information.\n\nTo solve this issue, Integrant provides ways of loading keyword\nhierarchies and annotations from edn files on the classpath:\n\n```clojure\n(ig/load-hierarchy)\n(ig/load-annotations)\n```\n\nThese functions will search the classpath for files named\n`integrant/hierarchy.edn` and `integrant/annotations.edn` respectively.\nIncluding these files in a library will allow Integrant to quickly\ndiscover information about the keywords used in the library.\n\n## Reloaded workflow\n\nSee [Integrant-REPL](https://github.com/weavejester/integrant-repl) to\nuse Integrant systems at the REPL, in line with Stuart Sierra's [reloaded\nworkflow](http://thinkrelevance.com/blog/2013/06/04/clojure-workflow-reloaded).\n\n## Further Documentation\n\n* [API docs](https://weavejester.github.io/integrant/integrant.core.html)\n\n## License\n\nCopyright © 2024 James Reeves\n\nReleased under the MIT license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fweavejester%2Fintegrant","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fweavejester%2Fintegrant","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fweavejester%2Fintegrant/lists"}