1    Introduction

1.1    Overview

std.lib.resource is part of the standard foundation library set. This page collects the public API reference for the namespace.

2    Walkthrough

2.1    Registering resource specs

Resources are described by specs and variants. res:spec-add registers a new spec, res:variant-add adds a variant, and res:spec-list and res:variant-list query the registry.

register and inspect a resource spec

(res:spec-add {:type :demo/counter
               :mode {:key :id
                      :allow #{:global :shared}
                      :default :shared}
               :instance {:create (fn [config] (atom (:start config 0)))
                          :setup identity
                          :teardown identity}})
=> any?

(res:spec-list)
=> (contains [:demo/counter])

(res:variant-add :demo/counter {:id :default})
=> any?

(res:variant-list :demo/counter)
=> (contains [:default])

2.2    Inspecting specs and modes

Retrieve the merged spec/variant with res:variant-get, and query the default mode with res:mode.

inspect a registered resource

(res:mode :demo/counter)
=> :shared

(:mode (res:variant-get :demo/counter))
=> (contains {:default :shared})

2.3    Resource keys and paths

res-key computes the key used to identify an active resource, and res-path builds the full path used to store it.

compute resource keys and paths

(res-key :shared :demo/counter :default {:id :main})
=> :main

(res-path :shared :demo/counter :default {:id :main})
=> '(:shared [:demo/counter :default] :main)

2.4    Starting and stopping resources

The res function (and friends such as res:start and res:stop) manage the lifecycle of active resources.

start and stop a resource

(let [counter (res :demo/counter {:id :main :start 10})]
  @counter
  => 10

  (res:exists? :demo/counter {:id :main})
  => true

  (res:stop :demo/counter {:id :main})
  => any?)

2.5    End-to-end: register, start, query, and tear down

A complete resource workflow: register a spec and variant, start an instance, verify it is active, then stop it and confirm it is gone.

lifecycle a counter resource

(res:spec-add {:type :demo/gauge
               :mode {:key :id
                      :allow #{:shared}
                      :default :shared}
               :instance {:create (fn [config] (atom (:value config 0)))
                          :setup identity
                          :teardown identity}})
(res:variant-add :demo/gauge {:id :default})

(let [g (res :demo/gauge {:id :primary :value 42})]
  @g
  => 42)

(res:active :demo/gauge)
=> (contains {:shared (contains {:demo/gauge (contains {:default (contains [:primary])})})})

(res:stop :demo/gauge {:id :primary})

(res:exists? :demo/gauge {:id :primary})
=> false

3    API



*active* ^

NONE
(defonce ^:dynamic *active*    (atom {:global {}
                                      :namespace {}
                                      :shared {}}))
link

*alias* ^

NONE
(defonce ^:dynamic *alias*     (atom {}))
link

*namespace* ^

NONE
(defonce ^:dynamic *namespace* nil)
link

*registry* ^

NONE
(defonce ^:dynamic *registry*  (atom {}))
link

+type+ ^

NONE
(def +type+)
link

res-access-get ^

[mode type variant key]
Added 3.0

access function for active resource

v 3.0
(defn res-access-get
  ([mode type variant key]
   (let [path (res-path mode type variant key)]
     (at/atom:get *active* path))))
link

res-access-set ^

[mode type variant key entry]
Added 3.0

access set function for active resource

v 3.0
(defn res-access-set
  ([mode type variant key entry]
   (let [path (res-path mode type variant key)]
     (-> (at/atom:set *active* path entry)
         (at/atom:set-changed)))))
link

res-api-fn ^

[res-call extra post default]
Added 3.0

helper function to create user friendly calls

v 3.0
(defn res-api-fn
  [res-call extra post default]
  (let [call-fn (res-call-fn res-call)]
    (fn [& all]
      (let [rall (reverse all)
            rall (if (and default (== (count all) 1))
                   (cons default rall)
                   rall)
            [rargs rstart] [(take extra rall) (drop extra rall)]]
        (cond-> (apply call-fn (conj (vec (reverse rstart)) (reverse rargs)))
          post (h/call post))))))
link

res-base ^

[mode type variant key config]
Added 3.0

gets or sets a reosurce

v 3.0
(defn res-base
  ([mode type variant key config]
   (let [spec (res:variant-get type variant)
         {:keys [allow]} (:mode spec)
         _     (or (allow mode)
                   (h/error "Mode not allowed" {:mode mode
                                                :options allow}))]
     (or (:instance (res-access-get mode type variant key))
         (res-start mode type variant key config)))))
link

res-call-fn ^

[f]
Added 3.0

helper function for res-apifn

v 3.0
(defn res-call-fn
  ([f]
   (fn res-call
     ([type args]
      (if-let [[type variant] (get @*alias* type)]
        (res-call type variant args)
        (res-call type :default args)))
     ([type input args]
      (let [[variant input] (res-input input)]
        (res-call type variant input args)))
     ([type variant input args]
      (let [mode (res:mode type variant)
            key  (res-key mode type variant input args)]
        (res-call mode type variant key args)))
     ([mode type variant key args]
      (apply f mode type variant key args)))))
link

res-input ^

[input]

NONE
(defn- res-input
  ([input]
   (cond (keyword? input) [input {}]
         (map? input)    [:default input]
         (symbol? input) [:default input]
         (string? input) [:default input]
         :else (h/error "Not valid" {:input input}))))
link

res-key ^

[mode type variant input & [args]]
Added 3.0

gets a resource key

v 3.0
(defn res-key
  ([mode type variant input & [args]]
   (let [input (or (last args) input)
         key (if (map? input)
               (let [spec (res:variant-get type variant)
                     {:keys [key]} (:mode spec)]
                 (key input))
               input)
         to-sym (fn [x]
                  (.getName (the-ns x)))]
     (case mode
       :global nil
       :namespace (to-sym (or key *namespace* *ns*))
       :shared key))))
link
(res-key :shared :hara/concurrent.atom.executor :default {:id :hello}) => :hello

res-path ^

[mode type variant input]
Added 3.0

gets the resource path

v 3.0
(defn res-path
  ([mode type variant input]
   (let [key (res-key mode type variant input)
         ext (cond-> [] key (conj key))]
     (concat (cons mode [[type variant]]) ext))))
link
(res-path :shared :hara/concurrent.atom.executor :default {:id :hello}) => '(:shared [:hara/concurrent.atom.executor :default] :hello)

res-restart ^

[mode type variant key]
Added 3.0

starts and stops current resource

v 3.0
(defn res-restart
  ([mode type variant key]
   (when-let [entry (res-access-get mode type variant key)]
     (res-stop mode type variant key)
     (res-start mode type variant key (:config entry)))))
link

res-setup ^

[type variant config]
Added 3.0

creates and starts a resource

v 3.0
(defn res-setup
  ([type variant config]
   (let [{:keys [instance hook] :as spec} (res:variant-get type variant)
         {:keys [create setup]} instance
         {:keys [pre-setup post-setup]} hook
         config (c/merge-nested (:config spec) config)]
     (cond-> (create config)
       pre-setup (doto pre-setup)
       :then setup
       post-setup (doto post-setup)))))
link

res-start ^

[mode type variant key config]
Added 3.0

start methods for resource

v 3.0
(defn res-start
  ([mode type variant key config]
   (if (res-access-get mode type variant key)
     (h/error "Already started" {:type type
                                 :variant variant
                                 :key key})
     (let [instance (res-setup type variant config)]
       (->> {:key key :config config :instance instance}
            (res-access-set mode type variant key))
       instance))))
link

res-stop ^

[mode type variant key]
Added 3.0

stop method for resource

v 3.0
(defn res-stop
  ([mode type variant key]
   (when-let [entry (res-access-get mode type variant key)]
     (at/atom:clear *active* (res-path mode type variant key))
     (res-teardown type variant (:instance entry)))))
link

res-teardown ^

[type variant instance]
Added 3.0

shutsdown a resource

v 3.0
(defn res-teardown
  ([type variant instance]
   (let [{:keys [hook] :as spec} (res:variant-get type variant)
         {:keys [teardown]} (:instance spec)
         {:keys [pre-teardown post-teardown]} hook]
     (h/suppress
      (cond-> instance
        pre-teardown (doto pre-teardown)
        :then teardown
        post-teardown (doto post-teardown))))))
link

res:active ^

[] [type] [type variant]
Added 3.0

gets all active resources

v 3.0
(defn res:active
  ([]
   (res:active nil))
  ([type]
   (res:active type nil))
  ([type variant]
   (let [keys-fn (apply comp keys (partial c/filter-vals not-empty)
                        (cond->   []
                          type    (conj (partial c/filter-keys (comp #{type} first)))
                          variant (conj (partial c/filter-keys (comp #{variant} first)))))
         filter-fn (partial c/filter-vals not-empty)]
     (h/-> @*active*
           (update :global keys-fn)
           (update :namespace (comp filter-fn (partial c/map-vals keys-fn)))
           (update :shared (comp filter-fn (partial c/map-vals keys-fn)))
           filter-fn))))
link
(res:active)

res:mode ^

[type] [type variant]
Added 3.0

gets the default mode of a resource

v 3.0
(defn res:mode
  ([type]
   (res:mode type :default))
  ([type variant]
   (let [spec (res:spec-get type)]
     (or (get-in spec [:variant variant :mode :default])
         (-> spec :mode :default)))))
link
(res:mode :hara/concurrent.atom.executor) => :global (res:mode :hara/concurrent.atom.executor :std.concurrent.print) => :global

res:spec-add ^

[{:keys [type], :as spec}]
Added 3.0

adds a new resource spec

v 3.0
(defn res:spec-add
  ([{:keys [type] :as spec}]
   (-> (at/atom:put *registry* [type] (c/merge-nested +type+ spec))
       (at/atom:put-changed))))
link
(resolve 'res:spec-add) => var?

res:spec-get ^

[type]
Added 3.0

retrieves a resource spec

v 3.0
(defn res:spec-get
  ([type]
   (or (at/atom:get *registry* [type])
       (h/error "No spec available" {:type type
                                     :options (res:spec-list)}))))
link
(res:spec-get :hara/concurrent.atom.executor) => map?

res:spec-list ^

[]
Added 3.0

lists all available specs

v 3.0
(defn res:spec-list
  ([] (keys @*registry*)))
link
(res:spec-list) => any?

res:spec-remove ^

[type]
Added 3.0

removes the resource spec

v 3.0
(defn res:spec-remove
  ([type]
   (at/atom:clear *registry* [type])))
link
(resolve 'res:spec-remove) => var?

res:variant-add ^

[type {:keys [id alias], :as spec}]
Added 3.0

adds a spec variant

v 3.0
(defn res:variant-add
  ([type {:keys [id alias] :as spec}]
   (if alias
     (at/atom:set *alias* [alias] [type id]))
   (-> (at/atom:set *registry* [type :variant id] spec)
       (at/atom:set-changed))))
link

res:variant-get ^

[type] [type id]
Added 3.0

gets a new variant

v 3.0
(defn res:variant-get
  ([type]
   (res:variant-get type :default))
  ([type id]
   (let [{:keys [variant] :as spec} (res:spec-get type)
         variant (or (get-in spec [:variant id])
                     (h/error "No variant available"
                              {:type type
                               :variant id
                               :options (keys variant)}))
         variant (c/merge-nested (dissoc spec :variant)
                                 variant)]
     variant)))
link

res:variant-list ^

[] [type]
Added 3.0

retrieves a list of variants to the spec

v 3.0
(defn res:variant-list
  ([] (c/map-vals (comp keys :variant) @*registry*))
  ([type] (-> (res:spec-get type) :variant keys)))
link
(res:variant-list :hara/concurrent.atom.executor) => coll?

res:variant-remove ^

[type id]
Added 3.0

removes a spec variant

v 3.0
(defn res:variant-remove
  ([type id]
   (let [{:keys [alias]} (at/atom:get *registry* [type :variant id])
         _  (if alias (at/atom:clear *alias* [alias]))]
     (at/atom:clear *registry* [type :variant id]))))
link

4    std.lib.resource: A Comprehensive Summary

The std.lib.resource namespace provides a powerful and extensible framework for managing application resources. It defines a registry for resource specifications (specs) and their variations (variants), and offers a lifecycle management system for creating, setting up, tearing down, and accessing resource instances. This module is crucial for ensuring consistent and controlled access to external dependencies, services, or any managed components within the foundation-base project.

Key Features and Concepts:

  1. Resource Registry and Specifications:n *namespace*: A dynamic var for the current namespace, used in resource key resolution.n *alias*: A global atom mapping resource aliases to their [type id] pairs.n *registry*: A global atom storing all registered resource specifications (specs).n *active*: A global atom tracking all active resource instances, categorized by mode (:global, :namespace, :shared).n +type+: A default resource specification template, defining structure for mode, config, instance (create/setup/teardown functions), and variant hooks.n res:spec-list []: Lists all registered resource specification types.n res:spec-add [spec]: Adds a new resource specification to the registry, merging with +type+.n res:spec-remove [type]: Removes a resource specification from the registry.n res:spec-get [type]: Retrieves a resource specification by its type.n res:variant-list [& [type]]: Lists all variants for a given resource type or for all types.n res:variant-add [type spec]: Adds a new variant to a resource specification, optionally creating an alias.n res:variant-remove [type id]: Removes a variant from a resource specification.n res:variant-get [type & [id]]: Retrieves a specific variant of a resource specification, merging it with the base spec.nn2. Resource Instance Management:n res:mode [type & [variant]]: Determines the default management mode (:global, :namespace, :shared) for a resource type and variant.n res:active [& [type variant]]: Lists all active resource instances, optionally filtered by type and variant.n res-setup [type variant config]: Creates and sets up a resource instance based on its type, variant, and config. It invokes create and setup functions defined in the spec, along with pre-setup and post-setup hooks.n res-teardown [type variant instance]: Tears down a resource instance, invoking the teardown function and pre-teardown/post-teardown hooks.nn3. Resource Access and Lifecycle:n res-input [input]: Normalizes various input formats (keyword, map, symbol, string) into a [type config] pair.n res-key [mode type variant input & [args]]: Generates a unique key for a resource instance based on its mode, type, variant, and input.n res-path [mode type variant input]: Constructs the path within the *active* atom to store/retrieve a resource instance.n res-access-get [mode type variant key]: Retrieves an active resource entry from *active*.n res-access-set [mode type variant key entry]: Stores an active resource entry in *active*.n res-start [mode type variant key config]: Starts a new resource instance and adds it to *active*.n res-stop [mode type variant key]: Stops and removes a resource instance from *active*.n res-restart [mode type variant key]: Stops and then restarts an existing resource instance.n res-base [mode type variant key config]: The core function for getting or starting a resource instance. It ensures the resource is started only once and respects the allowed modes.nn4. API Generation (User-Friendly Functions):n res-call-fn [f]: A helper function that creates a flexible dispatch function for resource operations, allowing various argument combinations to resolve to the full [mode type variant key args] signature.n res-api-fn [res-call extra post default]: A helper function to create user-friendly API calls (e.g., res:start, res:stop) that wrap res-call-fn.n res-api-tmpl: A template for generating resource API functions using std.lib.template/deftemplate.n Generated API Functions: The module uses h/template-entries with res-api-tmpl to generate a set of high-level functions:n res:exists?: Checks if a resource is active.n res:set: Sets an active resource instance.n res:stop: Stops a resource.n res:path: Gets the path for a resource.n res:start: Starts a resource.n res:restart: Restarts a resource.n * res: The main entry point for getting or starting a resource.

Overall Importance:

The std.lib.resource module is fundamental to the foundation-base project's ability to manage its complex dependencies and runtime components. Its key contributions include:

  • Centralized Resource Management: Provides a single, consistent mechanism for defining, registering, and managing all types of application resources.n Lifecycle Control: Ensures that resources are properly created, set up, used, and torn down, preventing leaks and ensuring system stability.n Extensibility: The protocol-driven design allows for easy integration of new resource types and custom lifecycle hooks.n Dynamic Configuration: Supports different resource variants and configurations, enabling flexible adaptation to various environments or use cases.n Modularity and Decoupling: Decouples resource usage from their concrete implementations, promoting a more modular and maintainable architecture.n* Namespace-Awareness: Supports namespace-specific resource instances, which is crucial for multi-tenant or modular applications.

By offering these comprehensive resource management capabilities, std.lib.resource significantly enhances the foundation-base project's ability to build and manage its sophisticated multi-language development ecosystem.