Actions provide a data-oriented way to manipulate your system state in a controlled and highly observable way.
An action is a persistent collection of effects, and (optionally) a map of headers.
(require '[clj-arsenal.action :refer [act << dispatcher] :as action])
(act {:key ::my-action}
[:http (<< :http/post "api/example" :json {:foo 1 :bar 2})]
[:patch {:path [:foo] :change [:value (<< :http/response :get :foo)]}])
; -> #clj-arsenal.action/Action{:headers {:key ::my-action} :effects ...}
; it's just a record
When dispatched, an action's effects are executed in order;
with their injections (the <<
forms) within each effect
being resolved before execution.
A dispatcher is a function which takes an action, wraps it in a context map, and runs said context through a series of interceptors. A minimal dispatcher can be created like this:
(defn executor
[context [effect-kind & args :as effect]]
;; execute effect
context)
(defn injector
[context {:keys [kind args] :as injection}]
;; return injected value
)
(defn context-builder
[context-basis & args]
;; return a context map
context-basis)
(def dispatch!
(dispatcher [(action/errors) (action/effects executor injector)]
:context-builder context-builder))
(dispatch!
(act {:key ::my-action}
[:http (<< :http/post "api/example" :json {:foo 1 :bar 2})]
[:patch {:path [:foo] :change [:value (<< :http/response :get :foo)]}])
...extra-args)
Here's what happens when an action is dispatched:
context-builder
is called to create an initial dispatch context.
It's passed a minimal context-stub
map and any extra arguments passed
to dispatch!
. The context-stub
is a minimal context, which can be
modified as needed, or returned unmodified. The context-builder
is
optional, if omitted then the minimal context-stub
will be used as
the initial dispatch context.injector
, then executes the effect with executor
.
If either injector
or executor
return a chainable (i.e async) value, then
this too will be resolved before continuing the dispatch.The executor
returns a new (or the same) context map, or a chainable that
resolves to a context map.
The injector
returns the value to be injected, or a chainable that resolves
to this value.
Interceptors are maps with any of (all are optional):
::action/enter
- called with the context map when entering the interceptor.
Returns a new (or the same) context map, or a chainable that resolves to
a context map.::action/leave
- called with the context map when entering the interceptor.
Returns a new (or the same) context map, or a chainable that resolves to
a context map.::action/name
- the interceptor name, used to identify it in errors and such.The context-stub
, which is the default dispatch context, has the following:
::action/action
- the action being dispatched.::action/pending-effects
- a FIFO queue of pending effects.::action/pending-enter
- a FIFO queue of interceptors to enter.::action/pending-leave
- a vector of interceptors that have
been entered, and need to be left.These entries must be available in the context for dispatch to continue; however they can be manipulated by middleware.
When an error is thrown (in middleware, injector, executor, etc)
the error is added to ::action/errors
(in the context map), and
::action/pending-enter
is cleared. The errors map is a nested
map of the form interceptor-name -> interceptor-stage -> error
.
Where interceptor-stage
is the stage the interceptor was in
when the error was thrown (either ::action/enter
or ::action/leave
).
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close