diff --git a/src/re_frame/alpha.cljc b/src/re_frame/alpha.cljc
deleted file mode 100644
index b5bc239c..00000000
--- a/src/re_frame/alpha.cljc
+++ /dev/null
@@ -1,1398 +0,0 @@
-(ns re-frame.alpha
- (:require
- [re-frame.events :as events]
- [re-frame.subs :as subs]
- [re-frame.subs.alpha :as subs.alpha]
- [re-frame.interop :as interop]
- [re-frame.db :as db]
- [re-frame.fx :as fx]
- [re-frame.cofx :as cofx]
- [re-frame.router :as router]
- [re-frame.settings :as settings]
- [re-frame.loggers :as loggers]
- [re-frame.registrar :as registrar]
- [re-frame.register.alpha :as register.alpha]
- [re-frame.interceptor :as interceptor]
- [re-frame.std-interceptors :as std-interceptors :refer [db-handler->interceptor
- fx-handler->interceptor
- ctx-handler->interceptor]]
- [re-frame.flow.alpha :as flow]
- [re-frame.utils :as utils]
- [clojure.set :as set]))
-
-(defn reg
- "Register a handler.
-
- `kind`: what kind of handler to register. Possible vals:
-
- ** :sub-lifecycle **
-
- UNDOCUMENTED. See https://github.com/day8/re-frame/issues/680.
-
- ** :legacy-sub **
-
- `(reg :sub query-id signal computation-fn)`
-
- Register a `signal-fn` and a `computation-fn` for a given `query-id`.
- `computation-fn` is a function of `[signals query-vector]`.
- See `:sub` for an explanation of all the arguments.
-
- Compatibility:
-
- Call `sub` to invoke the `computation-fn`. If passed a query-map,
- `sub` converts it to a query-vector.
-
- If a `:re-frame/query-v` key is present, `sub` uses it
- for the query-vector. Otherwise it uses the `:re-frame/q`
- key to build a 1-item vector. `sub` includes the original
- query-map in the metadata with a `:re-frame/query-m` key.
-
- For instance,
- `#!clj {::rf/q ::items}`
- converts to:
- `#!clj ^{::rf/query-m {::rf/q ::items}} [::items]`
-
- #!clj
- {::rf/q ::items
- ::rf/lifecycle :reactive
- ::rf/query-v [::items 1 2 3]}
-
-
- converts to:
-
- #!clj
- ^{::rf/query-m {::rf/q ::items
- ::rf/lifecycle :reactive}}
- [::items 1 2 3]
-
- ** :sub **
-
- `(reg :sub query-id signal computation-fn)`
-
- Register a `signal-fn` and a `computation-fn` for a given `query-id`.
- `computation-fn` is a function of `[signals query-map]`.
- Call `sub` to invoke the handler. If passed a query-vector,
- re-frame converts it to a query map, merging in the value of
- `:re-frame/query-m` from the metadata, and including
- the original query-vector with a `:re-frame/query-v` key.
-
- For instance,
- `#!clj ^{:rf/query-m {:rf/lifecycle :reactive}} [::items 1 2 3]`
- converts to:
-
- #!clj
- {::rf/q ::items
- ::rf/lifecycle :reactive
- ::rf/query-v [::items 1 2 3]}
-
- For convenience, re-frame also merges in the `:re-frame/lifecycle`
- key from the metadata. For instance,
- `#!clj ^{:rf/lifecycle :reactive} [::items 1 2 3]`
- converts to:
-
- #!clj
- {:rf/q ::items
- :rf/lifecycle :reactive
- :rf/query-v [::items 1 2 3]}
-
- A call to `(reg :sub query-id signal-fn computation-fn)`
- associates a `query-id` WITH two functions.
-
- The two functions provide 'a mechanism' for creating a node
- in the Signal Graph. When a node of type `query-id` is needed,
- the two functions can be used to create it.
-
- - `query-id` - typically a namespaced keyword (later used in subscribe).
- - `signal-fn` - optional. Returns the input data-flows required by this kind of node.
- - `computation-fn` - computes the value (output) of the node (from the input data flows).
-
- Later, during app execution, a call to `(sub {:re-frame/q :sub-id :color :blue})`
- will trigger the need for a new `:sub-id` Signal Graph node (matching the
- query `{:re-frame/q :sub-id :color :blue}`). And, to create that node the two functions
- associated with `:sub-id` will be looked up and used.
-
- Just to be clear: calling `reg :sub` does not immediately create a node.
- It only registers 'a mechanism' (the two functions) by which nodes
- can be created later, when a node is bought into existence by the
- use of `sub`.
-
- The `computation-fn` is always the last argument supplied and has three ways to be called.
- Two of these ways are syntactic sugar to provide easier access to functional abstractions around your data.
-
- 1. A function that will accept two parameters, the `input-values` and `query`. This is the
- standard way to provide a `computation-function`
-
- #!clj
- (reg-sub
- :query-id
- (fn [input-values query]
- (:foo input-values)))
-
- 2. A single sugary tuple of `:->`and a 1-arity `computation-function`:
-
- #!clj
- (reg-sub
- :query-id
- :-> computation-fn)
-
- This sugary variation allows you to pass a function that will expect only one parameter,
- namely the `input-values`, and entirely omit the `query`. A typical `computation-function`
- expects two parameters which can cause unfortunate results when attempting to use
- clojure standard library functions, or other functions, in a functional manner.
-
- For example, a significant number of subscriptions exist only to get a value
- from the `input-values`. As shown below, this subscription will simply retrieve
- the value associated with the `:foo` key in our db:
-
- #!clj
- (reg-sub
- :query-id
- (fn [db _] ;; :<---- trivial boilerplate we might want to skip over
- (:foo db)))
-
- This is slightly more boilerplate than we might like to do,
- as we can use a keyword directly as a function, and we might like to do this:
-
- #!clj
- (reg-sub
- :query-id
- :foo) ;; :<---- This could be dangerous. If `:foo` is not in db, we get the `query-vector` instead of `nil`.
-
- By using `:->` our function would not contain the `query-vector`, and any
- missing keys would be represented as such:
-
- #!clj
- (reg-sub
- :query-id
- :-> :foo)
-
- This form allows us to ignore the `query` if our `computation-fn`
- has no need for it, and be safe from any accidents. Any 1-arity function can be provided,
- and for more complicated use cases, `partial`, `comp`, and anonymous functions can still be used.
-
- 3. A single sugary tuple of `:=>` and a multi-arity `computation-function`
-
- #!clj
- (reg-sub
- :query-id
- :=> computation-fn)
-
- A vector `query` can be broken into two components `[query-id & optional-values]`, and
- some subscriptions require the `optional-values` for extra work within the subscription.
- To use them in variation #1, we need to destructure our `computation-fn` parameters
- in order to use them.
-
- #!clj
- (reg-sub
- :query-id
- (fn [db [_ foo]]
- [db foo]))
-
- Again we are writing boilerplate just to reach our values, and we might prefer to
- have direct access through a parameter vector like `[input-values optional-values]`
- instead, so we might be able to use a multi-arity function directly as our `computation-fn`.
- A rewrite of the above sub using this sugary syntax would look like this:
-
- #!clj
- (reg-sub
- :query-id
- :=> vector) ;; :<---- Could also be `(fn [db foo] [db foo])`
-
- If handling a map query, `:=>` simply uses the entire map.
-
- The `computation function` is expected to take two arguments:
-
- - `input-values` - the values which flow into this node (how is it wired into the graph?)
- - `query` - the value passed to `sub`
-
- and it returns a computed value (which becomes the output of the node)
-
- When `computation-fn` is called, the 2nd argument, `query`, will be that
- value passed to `sub`. So, if the call was `(subscribe {:re-frame/q :sub-id :color :blue})`,
- then the `query` supplied to the computation function will be `{:re-frame/q :sub-id :color :blue}`.
-
- The argument(s) supplied to `reg-sub` between `query-id` and the `computation-function`
- can vary in 3 ways, but whatever is there defines the `input signals` part
- of `the mechanism`, specifying what input values \"flow into\" the
- `computation function` (as the 1st argument) when it is called.
-
- So, `reg-sub` can be called in one of three ways, because there are three ways
- to define the input signals part. But note, the 2nd way, in which a
- `signals function` is explicitly supplied, is the most canonical and
- instructive. The other two are really just sugary variations.
-
- **First variation** - no input signal function given:
-
- #!clj
- (reg-sub
- :query-id
- a-computation-fn) ;; has signature: (fn [db query] ... ret-value)
-
- In the absence of an explicit `signals function`, the node's input signal defaults to `app-db`
- and, as a result, the value within `app-db` (a map) is
- given as the 1st argument when `a-computation-fn` is called.
-
-
- **Second variation** - a signal function is explicitly supplied:
-
- #!clj
- (reg-sub
- :query-id
- signal-fn ;; <-- here
- computation-fn)
-
- This is the most canonical and instructive of the three variations.
-
- When a node is created from the template, the `signal function` will be called and it
- is expected to return the input signal(s) as either a singleton, if there is only
- one, or a sequence if there are many, or a map with the signals as the values.
-
- The current values of the returned signals will be supplied as the 1st argument to
- the `a-computation-fn` when it is called - and subject to what this `signal-fn` returns,
- this value will be either a singleton, sequence or map of them (paralleling
- the structure returned by the `signal function`).
-
- This example `signal function` returns a 2-vector of input signals.
-
- #!clj
- (fn [query]
- [(sub [:a-sub])
- (sub [:b-sub])])
-
- The associated computation function must be written
- to expect a 2-vector of values for its first argument:
-
- #!clj
- (fn [[a b] query] ;; 1st argument is a seq of two values
- ....)
-
- If, on the other hand, the signal function was simpler and returned a singleton, like this:
-
- #!clj
- (fn [query]
- (sub [:a-sub])) ;; <-- returning a singleton
-
- then the associated computation function must be written to expect a single value
- as the 1st argument:
-
- #!clj
- (fn [a query] ;; 1st argument is a single value
- ...)
-
- Further Note: variation #1 above, in which an `signal-fn` was not supplied, like this:
-
- #!clj
- (reg-sub
- :query-id
- a-computation-fn) ;; has signature: (fn [db query] ... ret-value)
-
- is the equivalent of using this
- 2nd variation and explicitly supplying a `signal-fn` which returns `app-db`:
-
- #!clj
- (reg-sub
- :query-id
- (fn [_ _] re-frame/app-db) ;; <-- explicit signal-fn
- a-computation-fn) ;; has signature: (fn [db query-vec] ... ret-value)
-
- **Third variation** - syntax Sugar
-
- #!clj
- (reg-sub
- :a-b-sub
- :<- [:a-sub]
- :<- [:b-sub]
- (fn [[a b] query] ;; 1st argument is a seq of two values
- {:a a :b b}))
-
- This 3rd variation is just syntactic sugar for the 2nd. Instead of providing an
- `signals-fn` you provide one or more pairs of `:<-` and a subscription vector.
-
- If you supply only one pair a singleton will be supplied to the computation function,
- as if you had supplied a `signal-fn` returning only a single value:
-
- #!clj
- (reg-sub
- :a-sub
- :<- [:a-sub]
- (fn [a query] ;; only one pair, so 1st argument is a single value
- ...))
-
- Syntactic sugar for both the `signal-fn` and `computation-fn` can be used together
- and the direction of arrows shows the flow of data and functions. The example from
- directly above is reproduced here:
-
- #!clj
- (reg-sub
- :a-b-sub
- :<- [:a-sub]
- :<- [:b-sub]
- :-> (partial zipmap [:a :b]))
-
- For further understanding, read the tutorials, and look at the detailed comments in
- /examples/todomvc/src/subs.cljs.
-
- See also: `sub`
- "
- {:api-docs/heading "Registration"}
- [kind & args]
- (apply register.alpha/reg kind args))
-
-;; -- dispatch ----------------------------------------------------------------
-
-(defn dispatch
- "Queue `event` for processing (handling).
-
- `event` is a vector and the first element is typically a keyword
- which identifies the kind of event.
-
- The event will be added to a FIFO processing queue, so event
- handling does not happen immediately. It will happen 'very soon'
- but not now. And if the queue already contains events, they
- will be processed first.
-
- Usage:
-
- #!clj
- (dispatch [:order \"pizza\" {:supreme 2 :meatlovers 1 :veg 1}])
- "
- {:api-docs/hide true}
- [event]
- (router/dispatch event))
-
-(defn dispatch-sync
- "Synchronously (immediately) process `event`. It does **not** queue
- the event for handling later as `dispatch` does.
-
- `event` is a vector and the first element is typically a keyword
- which identifies the kind of event.
-
- It is an error to use `dispatch-sync` within an event handler because
- you can't immediately process an new event when one is already
- part way through being processed.
-
- Generally, avoid using this function, and instead, use `dispatch`.
- Only use it in the narrow set of cases where any delay in
- processing is a problem:
-
- 1. the `:on-change` handler of a text field where we are expecting fast typing
- 2. when initialising your app - see 'main' in examples/todomvc/src/core.cljs
- 3. in a unit test where immediate, synchronous processing is useful
-
- Usage:
-
- #!clj
- (dispatch-sync [:sing :falsetto \"piano accordion\"])
- "
- {:api-docs/hide true}
- [event]
- (router/dispatch-sync event))
-
-;; -- Events ------------------------------------------------------------------
-
-(defn reg-event-db
- "Register the given event `handler` (function) for the given `id`. Optionally, provide
- an `interceptors` chain:
-
- - `id` is typically a namespaced keyword (but can be anything)
- - `handler` is a function: (db event) -> db
- - `interceptors` is a collection of interceptors. Will be flattened and nils removed.
-
- Example Usage:
-
- #!clj
- (reg-event-db
- :token
- (fn [db event]
- (assoc db :some-key (get event 2))) ;; return updated db
-
- Or perhaps:
-
- #!clj
- (reg-event-db
- :namespaced/id ;; <-- namespaced keywords are often used
- [one two three] ;; <-- a seq of interceptors
- (fn [db [_ arg1 arg2]] ;; <-- event vector is destructured
- (-> db
- (dissoc arg1)
- (update :key + arg2)))) ;; return updated db
- "
- {:api-docs/hide true}
- ([id handler]
- (reg-event-db id nil handler))
- ([id interceptors handler]
- (events/register id [cofx/inject-db
- fx/do-fx
- flow/interceptor
- flow/do-fx
- std-interceptors/inject-global-interceptors
- interceptors
- (db-handler->interceptor handler)])))
-
-(defn reg-event-fx
- "Register the given event `handler` (function) for the given `id`. Optionally, provide
- an `interceptors` chain:
-
- - `id` is typically a namespaced keyword (but can be anything)
- - `handler` is a function: (coeffects-map event-vector) -> effects-map
- - `interceptors` is a collection of interceptors. Will be flattened and nils removed.
-
-
- Example Usage:
-
- #!clj
- (reg-event-fx
- :event-id
- (fn [cofx event]
- {:db (assoc (:db cofx) :some-key (get event 2))})) ;; return a map of effects
-
-
- Or perhaps:
-
- #!clj
- (reg-event-fx
- :namespaced/id ;; <-- namespaced keywords are often used
- [one two three] ;; <-- a seq of interceptors
- (fn [{:keys [db] :as cofx} [_ arg1 arg2]] ;; destructure both arguments
- {:db (assoc db :some-key arg1) ;; return a map of effects
- :fx [[:dispatch [:some-event arg2]]]}))
- "
- {:api-docs/hide true}
- ([id handler]
- (reg-event-fx id nil handler))
- ([id interceptors handler]
- (events/register id [cofx/inject-db
- fx/do-fx
- flow/interceptor
- flow/do-fx
- std-interceptors/inject-global-interceptors
- flow/interceptor
- interceptors
- (fx-handler->interceptor handler)])))
-
-(defn reg-event-ctx
- "Register the given event `handler` (function) for the given `id`. Optionally, provide
- an `interceptors` chain:
-
- - `id` is typically a namespaced keyword (but can be anything)
- - `handler` is a function: context-map -> context-map
-
- You can explore what is provided in `context` [here](https://day8.github.io/re-frame/Interceptors/#what-is-context).
-
- Example Usage:
-
- #!clj
- (reg-event-ctx
- :event-id
- (fn [{:keys [coeffects] :as context}]
- (let [initial {:db (:db coeffects)
- :event (:event coeffects)
- :fx []}
- result (-> initial
- function1
- function2
- function3)
- effects (select-keys result [:db :fx])]
- (assoc context :effects effects))))
- "
- {:api-docs/hide true}
- ([id handler]
- (reg-event-ctx id nil handler))
- ([id interceptors handler]
- (events/register id [cofx/inject-db
- fx/do-fx
- flow/interceptor
- flow/do-fx
- std-interceptors/inject-global-interceptors
- interceptors
- (ctx-handler->interceptor handler)])))
-
-(defn clear-event
- "Unregisters event handlers (presumably registered previously via the use of `reg-event-db` or `reg-event-fx`).
-
- When called with no args, it will unregister all currently registered event handlers.
-
- When given one arg, assumed to be the `id` of a previously registered
- event handler, it will unregister the associated handler. Will produce a warning to
- console if it finds no matching registration."
- {:api-docs/hide true}
- ([]
- (registrar/clear-handlers events/kind))
- ([id]
- (registrar/clear-handlers events/kind id)))
-
-;; -- error handler -----------------------------------------------------------
-
-(defn reg-event-error-handler
- "Register the given event error `handler` (function) that will catch unhandled exceptions
- thrown in the interceptors/handler chain.
-
- Only one `handler` can be registered. Registering a new `handler` clears the existing `handler`.
-
- This `handler` function has the signature:
-
- `(handler [original-error re-frame-error])`
-
- - `original-error`: A plaform-native Error object.
- Represents the original error thrown by user code.
-
- This is the error you see when no `handler` is registered.
- To preserve this behavior, simply call `(throw original-error)`
- at the end of your handler function.
-
- - `re-frame-error`: A clojure ExceptionInfo object.
- Includes the stacktrace of re-frame's internal functions,
- and extra data about the current interceptor context.
- Call `(ex-data re-frame-error)` to get this info.
- It includes:
-
- - `:interceptor`: the `:id` of the throwing interceptor.
- - `:direction`: `:before` or `:after`.
- - `:event-v`: the re-frame event which invoked this interceptor."
- {:api-docs/hide true}
- [handler]
- (registrar/register-handler :error :event-handler handler))
-
-(reg-event-error-handler interceptor/default-error-handler)
-
-;; -- subscriptions -----------------------------------------------------------
-
-(defn sub
- "Given a `query`, returns a Reagent `reaction` which will, over
- time, reactively deliver a stream of values. So, in FRP-ish terms,
- it returns a `Signal`.
-
- To obtain the current value from the Signal, it must be dereferenced:
-
- #!clj
- (let [signal (sub {:re-frame/q ::items})
- value (deref signal)] ;; could be written as @signal
- ...)
-
- which is typically written tersely as simple:
-
- #!clj
- (let [items @(sub {:re-frame/q ::items})]
- ...)
-
-
- `query` is a map containing:
- `:re-frame/q`: Required. Names the query. Typically a namespaced keyword.
- `:re-frame/lifecycle`: Optional. See docs for `reg-sub-lifecycle`.
-
- The entire `query` is passed to the subscription handler. This means you can use
- additional keys to parameterise the query it performs.
-
- **Example Usage**:
-
- #!clj
- (require '[re-frame :as-alias rf])
- (sub {::rf/q ::items
- ::rf/lifecycle ::rf/reactive
- :color \"blue\"
- :size :small})
-
- Note: for any given call to `sub there must have been a previous call
- to `reg`, registering the query handler (functions) associated with
- `query-id`.
-
- **De-duplication**
-
- Two, or more, concurrent subscriptions for the same query will
- source reactive updates from the one executing handler.
-
- See also: `reg-sub`
-
- **Flows**
-
- Flows have their own lifecycle, and you don't need to provide an `::rf/lifecycle` key.
- To subscribe to a flow, simply call:
-
- #!clj
- (sub :flow {:id :your-flow-id})
-
- **Legacy support**
-
- `dyn-v` is not supported.
- "
- {:api-docs/heading "Subscription"}
- ([q]
- (subs.alpha/sub q))
- ([id q]
- (subs.alpha/sub id q)))
-
-(defn clear-sub ;; think unreg-sub
- "Unregisters subscription handlers (presumably registered previously via the use of `reg-sub`).
-
- When called with no args, it will unregister all currently registered subscription handlers.
-
- When given one arg, assumed to be the `id` of a previously registered
- subscription handler, it will unregister the associated handler. Will produce a warning to
- console if it finds no matching registration.
-
- NOTE: Depending on the usecase, it may be necessary to call `clear-subscription-cache!` afterwards"
- {:api-docs/hide true}
- ([]
- (registrar/clear-handlers subs/kind))
- ([query-id]
- (registrar/clear-handlers subs/kind query-id)))
-
-(defn reg-sub-raw
- "This is a low level, advanced function. You should probably be
- using `reg-sub` instead.
-
- Some explanation is available in the docs at
- http://day8.github.io/re-frame/flow-mechanics/"
- {:api-docs/hide true}
- [query-id handler-fn]
- (registrar/register-handler subs/kind query-id handler-fn))
-
-;; XXX
-(defn clear-subscription-cache!
- "Removes all subscriptions from the cache.
-
- This function can be used at development time or test time. Useful when hot reloading
- namespaces containing subscription handlers. Also call it after a React/render exception,
- because React components won't have been cleaned up properly. And this, in turn, means
- the subscriptions within those components won't have been cleaned up correctly. So this
- forces the issue.
- "
- {:api-docs/hide true}
- []
- (subs/clear-subscription-cache!))
-
-;; -- effects -----------------------------------------------------------------
-
-(defn reg-fx
- "Register the given effect `handler` for the given `id`:
-
- - `id` is keyword, often namespaced.
- - `handler` is a side-effecting function which takes a single argument and whose return
- value is ignored.
-
- To use, first, associate `:effect2` with a handler:
-
- #!clj
- (reg-fx
- :effect2
- (fn [value]
- ... do something side-effect-y))
-
- Then, later, if an event handler were to return this effects map:
-
- #!clj
- {:effect2 [1 2]}
-
- then the `handler` `fn` we registered previously, using `reg-fx`, will be
- called with an argument of `[1 2]`.
- "
- {:api-docs/hide true}
- [id handler]
- (fx/reg-fx id handler))
-
-(defn clear-fx ;; think unreg-fx
- "Unregisters effect handlers (presumably registered previously via the use of `reg-fx`).
-
- When called with no args, it will unregister all currently registered effect handlers.
-
- When given one arg, assumed to be the `id` of a previously registered
- effect handler, it will unregister the associated handler. Will produce a warning to
- console if it finds no matching registration.
- "
- {:api-docs/hide true}
- ([]
- (registrar/clear-handlers fx/kind))
- ([id]
- (registrar/clear-handlers fx/kind id)))
-
-;; -- coeffects ---------------------------------------------------------------
-
-(defn reg-cofx
- "Register the given coeffect `handler` for the given `id`, for later use
- within `inject-cofx`:
-
- - `id` is keyword, often namespaced.
- - `handler` is a function which takes either one or two arguments, the first of which is
- always `coeffects` and which returns an updated `coeffects`.
-
- See also: `inject-cofx`
- "
- {:api-docs/hide true}
- [id handler]
- (cofx/reg-cofx id handler))
-
-(defn inject-cofx
- "Given an `id`, and an optional, arbitrary `value`, returns an interceptor
- whose `:before` adds to the `:coeffects` (map) by calling a pre-registered
- 'coeffect handler' identified by the `id`.
-
- The previous association of a `coeffect handler` with an `id` will have
- happened via a call to `re-frame.core/reg-cofx` - generally on program startup.
-
- Within the created interceptor, this 'looked up' `coeffect handler` will
- be called (within the `:before`) with two arguments:
-
- - the current value of `:coeffects`
- - optionally, the originally supplied arbitrary `value`
-
- This `coeffect handler` is expected to modify and return its first, `coeffects` argument.
-
- **Example of `inject-cofx` and `reg-cofx` working together**
-
-
- First - Early in app startup, you register a `coeffect handler` for `:datetime`:
-
- #!clj
- (re-frame.core/reg-cofx
- :datetime ;; usage (inject-cofx :datetime)
- (fn coeffect-handler
- [coeffect]
- (assoc coeffect :now (js/Date.)))) ;; modify and return first arg
-
- Second - Later, add an interceptor to an -fx event handler, using `inject-cofx`:
-
- #!clj
- (re-frame.core/reg-event-fx ;; when registering an event handler
- :event-id
- [ ... (inject-cofx :datetime) ... ] ;; <-- create an injecting interceptor
- (fn event-handler
- [coeffect event]
- ;;... in here can access (:now coeffect) to obtain current datetime ...
- )))
-
- **Background**
-
- `coeffects` are the input resources required by an event handler
- to perform its job. The two most obvious ones are `db` and `event`.
- But sometimes an event handler might need other resources.
-
- Perhaps an event handler needs a random number or a GUID or the current
- datetime. Perhaps it needs access to a DataScript database connection.
-
- If an event handler directly accesses these resources, it stops being
- pure and, consequently, it becomes harder to test, etc. So we don't
- want that.
-
- Instead, the interceptor created by this function is a way to 'inject'
- 'necessary resources' into the `:coeffects` (map) subsequently given
- to the event handler at call time.
-
- See also `reg-cofx`
- "
- {:api-docs/hide true}
- ([id]
- (cofx/inject-cofx id))
- ([id value]
- (cofx/inject-cofx id value)))
-
-(defn clear-cofx ;; think unreg-cofx
- "Unregisters coeffect handlers (presumably registered previously via the use of `reg-cofx`).
-
- When called with no args, it will unregister all currently registered coeffect handlers.
-
- When given one arg, assumed to be the `id` of a previously registered
- coeffect handler, it will unregister the associated handler. Will produce a warning to
- console if it finds no matching registration."
- {:api-docs/hide true}
- ([]
- (registrar/clear-handlers cofx/kind))
- ([id]
- (registrar/clear-handlers cofx/kind id)))
-
-;; -- interceptors ------------------------------------------------------------
-
-(def ^{:api-docs/hide true} debug
- "An interceptor which logs/instruments an event handler's actions to
- `re-frame/console` at the `:log` level.
-
- Output includes:
-
- 1. the event vector
- 2. a `clojure.data/diff` of db, before vs after, which shows
- the changes caused by the event handler. To understand the output,
- you should understand:
- https://clojuredocs.org/clojure.data/diff.
-
- You'd typically include this interceptor after (to the right of) any
- `path` interceptor.
-
- Warning: calling `clojure.data/diff` on large, complex data structures
- can be slow. So, you won't want this interceptor present in production
- code. So, you should condition it out like this:
-
- #!clj
- (re-frame.core/reg-event-db
- :evt-id
- [(when ^boolean goog.DEBUG re-frame.core/debug)] ;; <-- conditional
- (fn [db v]
- ...))
-
- To make this code fragment work, you'll also have to set `goog.DEBUG` to
- `false` in your production builds. For an example, look in `project.clj` of /examples/todomvc.
- "
- std-interceptors/debug)
-
-(defn path
- "Returns an interceptor which acts somewhat like `clojure.core/update-in`, in the sense that
- the event handler is given a specific part of `app-db` to change, not all of `app-db`.
-
- The interceptor has both a `:before` and `:after` functions. The `:before` replaces
- the `:db` key within coeffects with a sub-path within `app-db`. The `:after` reverses the process,
- and it grafts the handler's return value back into db, at the right path.
-
- Examples:
-
- #!clj
- (path :some :path)
- (path [:some :path])
- (path [:some :path] :to :here)
- (path [:some :path] [:to] :here)
-
- Example Use:
-
- #!clj
- (reg-event-db
- :event-id
- (path [:a :b]) ;; <-- used here, in interceptor chain
- (fn [b v] ;; 1st arg is not db. Is the value from path [:a :b] within db
- ... new-b)) ;; returns a new value for that path (not the entire db)
-
- Notes:
-
- 1. `path` may appear more than once in an interceptor chain. Progressive narrowing.
- 2. if `:effects` contains no `:db` effect, can't graft a value back in.
- "
- {:api-docs/hide true}
- [& args]
- (apply std-interceptors/path args))
-
-(defn enrich
- "Returns an interceptor which will run the given function `f` in the `:after`
- position.
-
- `f` is called with two arguments: `db` and `event`, and is expected to
- return a modified `db`.
-
- Unlike the `after` interceptor which is only about side effects, `enrich`
- expects `f` to process and alter the given `db` coeffect in some useful way,
- contributing to the derived data, flowing vibe.
-
- If `f` returns `nil`, the `db` value passed to `f` will be returned instead.
-
- #### Example Use:
-
- Imagine that todomvc needed to do duplicate detection - if any two todos had
- the same text, then highlight their background, and report them via a warning
- at the bottom of the panel.
-
- Almost any user action (edit text, add new todo, remove a todo) requires a
- complete reassessment of duplication errors and warnings. E.g. that edit
- just made might have introduced a new duplicate, or removed one. Same with
- any todo removal. So we need to re-calculate warnings after any CRUD events
- associated with the todos list.
-
- Unless we are careful, we might end up coding subtly different checks
- for each kind of CRUD operation. The duplicates check made after
- 'delete todo' event might be subtly different to that done after an
- editing operation. Nice and efficient, but fiddly. A bug generator
- approach.
-
- So, instead, we create an `f` which recalculates ALL warnings from scratch
- every time there is ANY change. It will inspect all the todos, and
- reset ALL FLAGS every time (overwriting what was there previously)
- and fully recalculate the list of duplicates (displayed at the bottom?).
-
- https://twitter.com/nathanmarz/status/879722740776939520
-
- By applying `f` in an `:enrich` interceptor, after every CRUD event,
- we keep the handlers simple and yet we ensure this important step
- (of getting warnings right) is not missed on any change.
-
- We can test `f` easily - it is a pure function - independently of
- any CRUD operation.
-
- This brings huge simplicity at the expense of some re-computation
- each time. This may be a very satisfactory trade-off in many cases.
-
- #### Returning nil
-
- In some cases, it's useful to apply a change to specific situations that can
- be determined at runtime instead of when defining the handler with an
- `:enrich` interceptor. Instead of forcing you to return the `db` from every
- non-applicable branch, you can return `nil` to use the given `db` value:
-
- #!clj
- (def set-last-update
- (core/enrich
- (fn [{db :db} [_ {user :user}]]
- (when (active-user? user) ;; <- Only perform an update if user is active
- ...))))
- "
- {:api-docs/hide true}
- [f]
- (std-interceptors/enrich f))
-
-(def ^{:api-docs/hide true} unwrap
- "> New in v1.2.0
-
- An interceptor which decreases the amount of destructuring necessary in an
- event handler where the event is structured as a 2-vector of
- [event-id payload-map].
-
- It promotes the `payload-map` part to be the event ultimately given to the
- event handler. Should you want the full original event, it can be found in
- `coeffects` under the key `:original-event`.
-
- If a dispatch looked like this:
-
- #!clj
- (dispatch [:event-id {:x 1 :y 2 :z 3}])
-
- Your event handlers can look like this:
-
- #!clj
- (reg-event-fx
- :event-id
- [... unwrap ...] ;; <-- added to the interceptors
- (fn [{:keys [db]} {:keys [x y z]}] ;; <-- instead of [_ {:keys [x y z]}]
- ...)
- "
- std-interceptors/unwrap)
-
-(def ^{:api-docs/hide true} trim-v
- "An interceptor which removes the first element of the event vector,
- before it is supplied to the event handler, allowing you to write more
- aesthetically pleasing event handlers. No leading underscore on the event-v!
-
- Should you want the full original event, it can be found in `coeffects` under
- the key `:original-event`.
-
- Your event handlers will look like this:
-
- #!clj
- (reg-event-db
- :event-id
- [... trim-v ...] ;; <-- added to the interceptors
- (fn [db [x y z]] ;; <-- instead of [_ x y z]
- ...)
- "
- std-interceptors/trim-v)
-
-(defn after
- "Returns an interceptor which runs the given function `f` in the `:after`
- position, presumably for side effects.
-
- `f` is called with two arguments: the `:effects` value for `:db`
- (or the `:coeffect` value of `:db` if no `:db` effect is returned) and the event.
- Its return value is ignored, so `f` can only side-effect.
-
- An example of use can be seen in the re-frame github repo in `/examples/todomvc/events.cljs`:
-
- - `f` runs schema validation (reporting any errors found).
- - `f` writes to localstorage."
- {:api-docs/hide true}
- [f]
- (std-interceptors/after f))
-
-(defn on-changes
- "Returns an interceptor which will observe N paths within `db`, and if any of them
- test not `identical?` to their previous value (as a result of a event handler
- being run), then it will run `f` to compute a new value, which is then assoc-ed
- into the given `out-path` within `db`.
-
- Example Usage:
-
- #!clj
- (defn my-f
- [a-val b-val]
- ... some computation on a and b in here)
-
- ;; use it
- (def my-interceptor (on-changes my-f [:c] [:a] [:b]))
-
- (reg-event-db
- :event-id
- [... my-interceptor ...] ;; <-- ultimately used here
- (fn [db v]
- ...))
-
-
- If you put this interceptor on handlers which might change paths `:a` or `:b`,
- it will:
-
- - call `f` each time the value at path `[:a]` or `[:b]` changes
- - call `f` with the values extracted from `[:a]` `[:b]`
- - assoc the return value from `f` into the path `[:c]`
- "
- {:api-docs/hide true}
- [f out-path & in-paths]
- (apply std-interceptors/on-changes f out-path in-paths))
-
-(defn reg-global-interceptor
- "Registers the given `interceptor` as a global interceptor. Global interceptors are
- included in the processing chain of every event.
-
- When you register an event handler, you have the option of supplying an
- interceptor chain. Any global interceptors you register are effectively
- prepending to this chain.
-
- Global interceptors are run in the order that they are registered.
-
- Global interceptors are unique by :id. If a global interceptor with the same :id
- key as `interceptor` is already registered, `interceptor` will take its place in the
- global interceptor chain. This facilitates hot-reloading.
-
- Note: members of re-frame.std-interceptors do not have unique ids. To register
- more than one, consider:
-
- (reg-global-interceptor (-> (re-frame.std-interceptors/on-changes + [:a] [:b])
- (assoc :id :my-unique-id)))"
- {:api-docs/hide true}
- [interceptor]
- (settings/reg-global-interceptor interceptor))
-
-(defn clear-global-interceptor
- "Unregisters global interceptors (presumably registered previously via the use of `reg-global-interceptor`).
-
- When called with no args, it will unregister all currently registered global interceptors.
-
- When given one arg, assumed to be the `id` of a previously registered
- global interceptors, it will unregister the associated interceptor. Will produce a warning to
- console if it finds no matching registration."
- {:api-docs/hide true}
- ([]
- (settings/clear-global-interceptors))
- ([id]
- (settings/clear-global-interceptors id)))
-
-(defn ->interceptor
- "A utility function for creating interceptors.
-
- Accepts three optional, named arguments:
-
- - `:id` - an id for the interceptor (decorative only)
- - `:before` - the interceptor's before function
- - `:after` - the interceptor's after function
-
- Example use:
-
- #!clj
- (def my-interceptor
- (->interceptor
- :id :my-interceptor
- :before (fn [context]
- ... modifies and returns `context`)
- :after (fn [context]
- ... modifies and returns `context`)))
-
- Notes:
-
- - `:before` functions modify and return their `context` argument. Sometimes they
- only side effect, in which case, they'll perform the side effect and return
- `context` unchanged.
- - `:before` functions often modify the `:coeffects` map within `context` and,
- if they do, then they should use the utility functions `get-coeffect` and
- `assoc-coeffect`.
- - `:after` functions modify and return their `context` argument. Sometimes they
- only side effect, in which case, they'll perform the side effect and return
- `context` unchanged.
- - `:after` functions often modify the `:effects` map within `context` and,
- if they do, then they should use the utility functions `get-effect`
- and `assoc-effect`"
- {:api-docs/hide true}
- [& {:as m :keys [id before after]}]
- (utils/apply-kw interceptor/->interceptor m))
-
-(defn get-coeffect
- "A utility function, typically used when writing an interceptor's `:before` function.
-
- When called with one argument, it returns the `:coeffects` map from within that `context`.
-
- When called with two or three arguments, behaves like `clojure.core/get` and
- returns the value mapped to `key` in the `:coeffects` map within `context`, `not-found` or
- `nil` if `key` is not present."
- {:api-docs/hide true}
- ([context]
- (interceptor/get-coeffect context))
- ([context key]
- (interceptor/get-coeffect context key))
- ([context key not-found]
- (interceptor/get-coeffect context key not-found)))
-
-(defn assoc-coeffect
- "A utility function, typically used when writing an interceptor's `:before` function.
-
- Adds or updates a key/value pair in the `:coeffects` map within `context`. "
- {:api-docs/hide true}
- [context key value]
- (interceptor/assoc-coeffect context key value))
-
-(defn get-effect
- "A utility function, used when writing interceptors, typically within an `:after` function.
-
- When called with one argument, returns the `:effects` map from the `context`.
-
- When called with two or three arguments, behaves like `clojure.core/get` and
- returns the value mapped to `key` in the effects map, `not-found` or
- `nil` if `key` is not present."
- {:api-docs/hide true}
- ([context]
- (interceptor/get-effect context))
- ([context key]
- (interceptor/get-effect context key))
- ([context key not-found]
- (interceptor/get-effect context key not-found)))
-
-(defn assoc-effect
- "A utility function, typically used when writing an interceptor's `:after` function.
-
- Adds or updates a key/value pair in the `:effects` map within `context`. "
- {:api-docs/hide true}
- [context key value]
- (interceptor/assoc-effect context key value))
-
-(defn enqueue
- "A utility function, used when writing an interceptor's `:before` function.
-
- Adds the given collection of `interceptors` to those already in `context's`
- execution `:queue`. It returns the updated `context`.
-
- So, it provides a way for one interceptor to add more interceptors to the
- currently executing interceptor chain.
- "
- {:api-docs/hide true}
- [context interceptors]
- (interceptor/enqueue context interceptors))
-
-;; -- logging ----------------------------------------------------------------
-
-(defn set-loggers!
- "re-frame outputs warnings and errors via the API function `console`
- which, by default, delegates to `js/console`'s default implementation for
- `log`, `error`, `warn`, `debug`, `group` and `groupEnd`. But, using this function,
- you can override that behaviour with your own functions.
-
- The argument `new-loggers` should be a map containing a subset of they keys
- for the standard `loggers`, namely `:log` `:error` `:warn` `:debug` `:group`
- or `:groupEnd`.
-
- Example Usage:
-
- #!clj
- (defn my-logger ;; my alternative logging function
- [& args]
- (post-it-somewhere (apply str args)))
-
- ;; now install my alternative loggers
- (re-frame.core/set-loggers! {:warn my-logger :log my-logger})
- "
- {:api-docs/hide true}
- [new-loggers]
- (loggers/set-loggers! new-loggers))
-
-(defn console
- "A utility logging function which is used internally within re-frame to produce
- warnings and other output. It can also be used by libraries which
- extend re-frame, such as effect handlers.
-
- By default, it will output the given `args` to `js/console` at the given log `level`.
- However, an application using re-frame can redirect `console` output via `set-loggers!`.
-
- `level` can be one of `:log`, `:error`, `:warn`, `:debug`, `:group` or `:groupEnd`.
-
- Example usage:
-
- #!clj
- (console :error \"Sure enough it happened:\" a-var \"and\" another)
- (console :warn \"Possible breach of containment wall at:\" dt)
- "
- {:api-docs/hide true}
- [level & args]
- (apply loggers/console level args))
-
-;; -- unit testing ------------------------------------------------------------
-
-(defn make-restore-fn
- "This is a utility function, typically used in testing.
-
- It checkpoints the current state of re-frame and returns a function which, when
- later called, will restore re-frame to the checkpointed state.
-
- The checkpoint includes `app-db`, all registered handlers and all subscriptions.
- "
- {:api-docs/hide true}
- []
- (let [handlers @registrar/kind->id->handler
- app-db @db/app-db
- subs-cache @subs/query->reaction]
- (fn []
- ;; call `dispose!` on all current subscriptions which
- ;; didn't originally exist.
- (let [original-subs (set (vals subs-cache))
- current-subs (set (vals @subs/query->reaction))]
- (doseq [sub (set/difference current-subs original-subs)]
- (interop/dispose! sub)))
-
- ;; Reset the atoms
- ;; We don't need to reset subs/query->reaction, as
- ;; disposing of the subs removes them from the cache anyway
- (reset! registrar/kind->id->handler handlers)
- (reset! db/app-db app-db)
- nil)))
-
-(defn purge-event-queue
- "Removes all events currently queued for processing"
- {:api-docs/hide true}
- []
- (router/purge re-frame.router/event-queue))
-
-;; -- Event Processing Callbacks ---------------------------------------------
-
-(defn add-post-event-callback
- "Registers the given function `f` to be called after each event is processed.
-
- `f` will be called with two arguments:
-
- - `event`: a vector. The event just processed.
- - `queue`: a PersistentQueue, possibly empty, of events yet to be processed.
-
- This facility is useful in advanced cases like:
-
- - you are implementing a complex bootstrap pipeline
- - you want to create your own handling infrastructure, with perhaps multiple
- handlers for the one event, etc. Hook in here.
- - libraries providing 'isomorphic javascript' rendering on Nodejs or Nashorn.
-
- `id` is typically a keyword. If it supplied when an `f` is added, it can be
- subsequently be used to identify it for removal. See `remove-post-event-callback`.
- "
- {:api-docs/hide true}
- ([f]
- (add-post-event-callback f f)) ;; use f as its own identifier
- ([id f]
- (router/add-post-event-callback re-frame.router/event-queue id f)))
-
-(defn remove-post-event-callback
- "Unregisters a post event callback function, identified by `id`.
-
- Such a function must have been previously registered via `add-post-event-callback`"
- {:api-docs/hide true}
- [id]
- (router/remove-post-event-callback re-frame.router/event-queue id))
-
-;; -- Flows ------------------------------------------------------------------
-
-(defn reg-flow
- "Registers a `flow`.
-
- A full tutorial can be found at https://day8.github.io/re-frame/Flows
-
- Re-frame uses the flow registry to execute a dataflow graph.
-
- On every event, re-frame runs each registered `flow`.
- It resolves the flow's inputs, determines if the flow is live, and if so,
- evaluates the output function, putting the result in `app-db` at the path.
-
- A `flow` is a map, specifying one dataflow node. It has keys:
-
- - `:id`: uniquely identifies the node.
- - When a `flow` is already registered with the same `:id`, replaces it.
- - You can provide an `id` argument to `reg-flow`, instead of including `:id`.
- - `:inputs`: a map of `keyword->input`. An input can be one of two types:
- - vector: expresses a path in `app-db`.
- - map: expresses the output of another flow, identified by a
- `::re-frame.flow.alpha/flow<-` key.
- Call the `re-frame.alpha/flow<-` function to construct this map.
- - `:output`: a function of the `keyword->resolved-input` map, returning the output value of the node.
- - A resolved vector input is the value in `app-db` at that path.
- - A resolved `flow<-` input is the value in `app-db` at the path of the named flow.
- - Re-frame topologically sorts the flows, to make sure any input flows always run first.
- - Re-frame throws an error at registration time if any flow inputs form a cycle.
- - `:path`: specifies the `app-db` location where the `:output` value is stored.
- - `:live-inputs`: a map of `keyword->live-input` for the `:live?` function.
- - A `live-input` works the same way an `input`.
- - `:live?`: a predicate function of the `keyword->resolved-live-input` map, returning the current lifecycle state of the node.
- - `:cleanup`: a function of `app-db` and the `:path`.
- - Returns a new `app-db`.
- - Runs the first time `:live?` returns `false`
- - Runs when the flow is cleared (see `re-frame.alpha/clear-flow`).
-
- `:id` is the only required key. All others have a default value:
-
- - `:path`: `[id]`
- - `:inputs`: `{}`
- - `:output`: `(constantly true)`
- - `:live?`: `(constantly true)`
- - `:live-inputs`: `{}`
- - `:cleanup`: `re-frame.utils/dissoc-in`"
- {:api-docs/heading "Flows"}
- ([flow] (flow/reg-flow flow))
- ([id flow] (flow/reg-flow id flow)))
-
-(defn clear-flow
- "Arguments: `[id]`
- Deregisters a flow, identified by `id`.
-
- Later, re-frame will update `app-db` with the flow's `:cleanup` function.
-
- If `clear-flow` is invoked by the `:clear-flow` effect, this cleanup happens in the `:after` phase of the same event returning `:clear-flow`.
-
- If you call `clear-flow` directly, cleanup will happen on the next event."
- {:api-docs/heading "Flows"}
- ([] (flow/clear-flow))
- ([id] (flow/clear-flow id)))
-
-(defn get-flow
- "Returns the value within `db` at the `:path` given by the registered flow
- with an `:id` key equal to `id`, if it exists. Otherwise, returns nil."
- {:api-docs/heading "Flows"}
- [db id] (flow/resolve-input db (flow/flow<- id)))
-
-(defn flow<-
- "Creates an input from a flow id."
- {:api-docs/heading "Flows"}
- [id]
- (flow/flow<- id))
-
-(reg :sub :flow
- (fn [db input]
- (flow/resolve-input db input)))
-
-(reg :sub :live?
- (fn [db input]
- (flow/resolve-input db input)))
-
-(reg-fx :reg-flow flow/reg-flow)
-
-(reg-fx :clear-flow flow/clear-flow)
-
-;; -- Deprecation ------------------------------------------------------------
-
-(def ^{:api-docs/heading "Legacy Compatibility"} subscribe
- "Equivalent to `sub` (except with flows, which have their own lifecycle and are not cached).
-
- Call `(subscribe [:flow {:id :your-flow-id}])` to subscribe to a flow."
- sub)
-
-(defn reg-sub
- "Equivalent to `reg` `:legacy-sub`."
- {:api-docs/heading "Legacy Compatibility"}
- [& args]
- (apply reg :legacy-sub args))
-
-;; Assisting the v0.7.x -> v0.8.x transition.
-(defn register-handler
- "Deprecated. Use `reg-event-db` instead."
- {:deprecated "0.8.0"
- :api-docs/hide true}
- [& args]
- (console :warn "re-frame: \"register-handler\" has been renamed \"reg-event-db\" (look for registration of " (str (first args)) ")")
- (apply reg-event-db args))
-
-(defn register-sub
- "Deprecated. Use `reg-sub-raw` instead."
- {:deprecated "0.8.0"
- :api-docs/hide true}
- [& args]
- (console :warn "re-frame: \"register-sub\" is used to register the event " (str (first args)) " but it is a deprecated part of the API. Please use \"reg-sub-raw\" instead.")
- (apply reg-sub-raw args))
diff --git a/src/re_frame/cofx.cljc b/src/re_frame/cofx.cljc
index 49d0eb60..6754b7e6 100644
--- a/src/re_frame/cofx.cljc
+++ b/src/re_frame/cofx.cljc
@@ -1,8 +1,7 @@
(ns re-frame.cofx
(:require
- [re-frame.db :refer [app-db]]
[re-frame.interceptor :refer [->interceptor]]
- [re-frame.registrar :refer [get-handler register-handler]]
+ [re-frame.registrar :refer [get-handler register-handler *current-frame*]]
[re-frame.loggers :refer [console]]))
;; -- Registration ------------------------------------------------------------
@@ -10,42 +9,38 @@
(def kind :cofx)
(assert (re-frame.registrar/kinds kind))
-(defn reg-cofx
- [id handler]
- (register-handler kind id handler))
-
;; -- Interceptor -------------------------------------------------------------
(defn inject-cofx
- ([id]
+ ([registry id]
(->interceptor
:id :coeffects
:before (fn coeffects-before
[context]
- (if-let [handler (get-handler kind id)]
- (update context :coeffects handler)
+ (if-let [handler (get-handler registry kind id)]
+ (binding [*current-frame* (:frame context)]
+ (update context :coeffects handler))
(console :error "No cofx handler registered for" id)))))
- ([id value]
+ ([registry id value]
(->interceptor
:id :coeffects
:before (fn coeffects-before
[context]
- (if-let [handler (get-handler kind id)]
- (update context :coeffects handler value)
+ (if-let [handler (get-handler registry kind id)]
+ (binding [*current-frame* (:frame context)]
+ (update context :coeffects handler value))
(console :error "No cofx handler registered for" id))))))
;; -- Builtin CoEffects Handlers ---------------------------------------------
-;; :db
-;;
-;; Adds to coeffects the value in `app-db`, under the key `:db`
-(reg-cofx
- :db
- (fn db-coeffects-handler
- [coeffects]
- (assoc coeffects :db @app-db)))
-
-;; Because this interceptor is used so much, we reify it
-(def inject-db (inject-cofx :db))
-
-
+(defn register-built-in!
+ [{:keys [registry]}]
+ (let [reg-cofx (partial register-handler registry kind)]
+ ;; :db
+ ;;
+ ;; Adds to coeffects the value in `app-db`, under the key `:db`
+ (reg-cofx
+ :db
+ (fn db-coeffects-handler
+ [coeffects]
+ (assoc coeffects :db @(:app-db *current-frame*))))))
diff --git a/src/re_frame/context.clj b/src/re_frame/context.clj
new file mode 100644
index 00000000..8e6939bf
--- /dev/null
+++ b/src/re_frame/context.clj
@@ -0,0 +1,42 @@
+(ns re-frame.context
+ (:refer-clojure :exclude [bound-fn])
+ (:require [cljs.env]
+ [cljs.analyzer]))
+
+(defmacro defc
+ "For definining Reagent components that honor the contextual frame. Like defn
+ but sets a :context-type metadata on the function, which Reagent will pick up
+ on, so that the correct React context is set for this component."
+ [name & fntail]
+ (let [[doc fntail] (if (string? (first fntail))
+ [(first fntail) (rest fntail)]
+ [nil fntail])]
+ `(def ~(with-meta name (merge {:doc doc} (:meta &form)))
+ ^{:context-type frame-context}
+ (fn ~@fntail))))
+
+(defmacro bind-frame [frame & body]
+ `(binding [~'re-frame.registrar/*current-frame* ~frame]
+ (assert (satisfies? ~'re-frame.frame/IFrame ~frame) "given frame is not of type `re-frame.frame/IFrame`")
+ ~@body))
+
+(defmacro import-with-frame
+ ([var-sym]
+ `(import-with-frame ~(symbol (name var-sym)) ~var-sym))
+ ([name var-sym]
+ `(defn ~name
+ ;; Attempt at propagating the doc string / arglists, for some reason CIDER
+ ;; is not picking this up though.
+ ~(select-keys (:meta (cljs.analyzer/resolve-var cljs.env/*compiler* var-sym))
+ [:doc :arglists])
+ [& args#]
+ (apply ~var-sym (current-frame) args#))))
+
+(defmacro bound-fn [& args]
+ (let [[name argv & body] (if (symbol? (first args))
+ args
+ (into [nil] args))]
+ `(let [frame# (~'re-frame.context/current-frame)]
+ (fn ~@(when name name) ~argv
+ (binding [~'re-frame.registrar/*current-frame* frame#]
+ ~@body)))))
diff --git a/src/re_frame/context.cljs b/src/re_frame/context.cljs
new file mode 100644
index 00000000..a09bae4b
--- /dev/null
+++ b/src/re_frame/context.cljs
@@ -0,0 +1,110 @@
+(ns re-frame.context
+ (:require ["react" :as react]
+ [goog.object :as gobj]
+ [re-frame.core :as r]
+ [re-frame.frame :as frame]
+ [re-frame.registrar :as registrar]
+ [re-frame.subs :as subs]
+ [reagent.core])
+ (:require-macros [re-frame.context :refer [defc import-with-frame]]))
+
+(def frame-context (.createContext react r/default-frame))
+
+(defn set-default-frame [frame]
+ (gobj/set frame-context "_currentValue" frame)
+ (gobj/set frame-context "_currentValue2" frame))
+
+(defn current-context
+ "Gets the react Context for the current component, to be used in lifecycle
+ hooks (e.g. render). Assumes that Component.contextType has been set."
+ []
+ (when-let [cmp (reagent.core/current-component)]
+ ;; When used without setting the right contextType we will get #js {} back
+ (when (not (object? (.-context cmp)))
+ (.-context cmp))))
+
+(defn current-frame
+ "Get the current frame provided by the context, falling back to the default
+ frame. Assumes that Component.contextType = frame-context."
+ []
+ (or registrar/*current-frame*
+ (current-context)
+ (gobj/get frame-context "_currentValue")))
+
+(defn bound-frame []
+ (or registrar/*current-frame*
+ (current-context)
+ (throw (js/Error. "No frame bound"))))
+
+(defn provide-frame
+ "Component that acts as a provider for the frame, so to run an isolated version
+ of your app, use.
+
+ [provide-frame (frame/make-frame)
+ [app]]"
+ [frame & children]
+ (reagent.core/create-element
+ (.-Provider frame-context)
+ #js {:value frame
+ :children (reagent.core/as-element (into [:<>] children))}))
+
+(defc provide-app-db
+ "Component that acts as a provider for the app-db, it takes the registry from
+ the current frame, but uses the given atom for the app-db"
+ [app-db & children]
+ `[~provide-frame ~(frame/make-frame {:registry (:registry (current-frame))
+ :app-db app-db})
+ ~@children])
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Complete copy of the top-level re-frame API. If you are using the context
+;; approach then import re-frame.context instead of re-frame.core and things
+;; should generally Just Workâ˘
+
+(import-with-frame subscribe re-frame.frame/subscribe)
+(import-with-frame dispatch re-frame.frame/dispatch)
+(import-with-frame dispatch-sync re-frame.frame/dispatch-sync)
+(import-with-frame clear-sub re-frame.frame/clear-sub)
+(import-with-frame reg-fx re-frame.frame/reg-fx)
+(import-with-frame reg-cofx re-frame.frame/reg-cofx)
+(import-with-frame inject-cofx re-frame.frame/inject-cofx)
+(import-with-frame clear-cofx re-frame.frame/clear-cofx)
+(import-with-frame reg-event-db re-frame.frame/reg-event-db)
+(import-with-frame reg-event-fx re-frame.frame/reg-event-fx)
+(import-with-frame reg-event-ctx re-frame.frame/reg-event-ctx)
+(import-with-frame clear-event re-frame.frame/clear-event)
+
+;; A few special cases which we can't import directly
+
+(defn reg-sub-raw [query-id handler-fn]
+ (frame/reg-sub-raw
+ (current-frame)
+ query-id
+ (fn [frame query-v]
+ (handler-fn (:app-db frame) query-v))))
+
+(defn reg-sub [query-id & args]
+ (apply frame/reg-sub (current-frame) query-id args))
+
+(defn clear-subscriptions-cache! [& args]
+ (apply subs/-clear (:subs-cache (current-frame)) args))
+
+
+(defn context-fns
+ "Returns subscribe/dispatch/dispatch-sync functions that are bound to the current frame. Use like this
+
+ (defc my-component []
+ (reagent/with-let [{:keys [subscribe dispatch]} (re-frame/context-fns)]
+ ,,,
+ )) "
+ ([] (context-fns (current-frame)))
+ ([frame]
+ {:subscribe (partial re-frame.frame/subscribe frame)
+ :dispatch (partial re-frame.frame/dispatch frame)
+ :dispatch-sync (partial re-frame.frame/dispatch-sync frame)}))
+
+(defn bind-fn [f]
+ (let [frame (current-frame)]
+ (fn [& args]
+ (binding [registrar/*current-frame* frame]
+ (apply f args)))))
diff --git a/src/re_frame/core.cljc b/src/re_frame/core.cljc
index 8ae500e4..1acbcb3e 100644
--- a/src/re_frame/core.cljc
+++ b/src/re_frame/core.cljc
@@ -1,21 +1,13 @@
(ns re-frame.core
(:require
- [re-frame.events :as events]
- [re-frame.subs :as subs]
- [re-frame.interop :as interop]
- [re-frame.db :as db]
- [re-frame.fx :as fx]
- [re-frame.cofx :as cofx]
+ [re-frame.frame :as frame]
+ [re-frame.db :refer [default-frame]]
[re-frame.router :as router]
[re-frame.settings :as settings]
[re-frame.loggers :as loggers]
- [re-frame.registrar :as registrar]
[re-frame.interceptor :as interceptor]
- [re-frame.std-interceptors :as std-interceptors :refer [db-handler->interceptor
- fx-handler->interceptor
- ctx-handler->interceptor]]
- [re-frame.utils :as utils]
- [clojure.set :as set]))
+ [re-frame.std-interceptors :as std-interceptors]
+ [re-frame.utils :as utils]))
;; -- dispatch ----------------------------------------------------------------
@@ -37,7 +29,7 @@
"
{:api-docs/heading "Dispatching Events"}
[event]
- (router/dispatch event))
+ (frame/dispatch default-frame event))
(defn dispatch-sync
"Synchronously (immediately) process `event`. It does **not** queue
@@ -65,7 +57,7 @@
"
{:api-docs/heading "Dispatching Events"}
[event]
- (router/dispatch-sync event))
+ (frame/dispatch-sync default-frame event))
;; -- Events ------------------------------------------------------------------
@@ -98,9 +90,9 @@
"
{:api-docs/heading "Event Handlers"}
([id handler]
- (reg-event-db id nil handler))
+ (frame/reg-event-db default-frame id handler))
([id interceptors handler]
- (events/register id [cofx/inject-db fx/do-fx std-interceptors/inject-global-interceptors interceptors (db-handler->interceptor handler)])))
+ (frame/reg-event-db default-frame id interceptors handler)))
(defn reg-event-fx
"Register the given event `handler` (function) for the given `id`. Optionally, provide
@@ -132,9 +124,9 @@
"
{:api-docs/heading "Event Handlers"}
([id handler]
- (reg-event-fx id nil handler))
+ (frame/reg-event-fx default-frame id handler))
([id interceptors handler]
- (events/register id [cofx/inject-db fx/do-fx std-interceptors/inject-global-interceptors interceptors (fx-handler->interceptor handler)])))
+ (frame/reg-event-fx default-frame id interceptors handler)))
(defn reg-event-ctx
"Register the given event `handler` (function) for the given `id`. Optionally, provide
@@ -163,9 +155,9 @@
"
{:api-docs/heading "Event Handlers"}
([id handler]
- (reg-event-ctx id nil handler))
+ (frame/reg-event-ctx default-frame id nil handler))
([id interceptors handler]
- (events/register id [cofx/inject-db fx/do-fx std-interceptors/inject-global-interceptors interceptors (ctx-handler->interceptor handler)])))
+ (frame/reg-event-ctx default-frame id interceptors handler)))
(defn clear-event
"Unregisters event handlers (presumably registered previously via the use of `reg-event-db` or `reg-event-fx`).
@@ -177,9 +169,9 @@
console if it finds no matching registration."
{:api-docs/heading "Event Handlers"}
([]
- (registrar/clear-handlers events/kind))
+ (frame/clear-event default-frame))
([id]
- (registrar/clear-handlers events/kind id)))
+ (frame/clear-event default-frame id)))
;; -- subscriptions -----------------------------------------------------------
@@ -432,7 +424,7 @@
"
{:api-docs/heading "Subscriptions"}
[query-id & args]
- (apply subs/reg-sub query-id args))
+ (apply frame/reg-sub default-frame query-id args))
(defn subscribe
"Given a `query` vector, returns a Reagent `reaction` which will, over
@@ -496,9 +488,9 @@
"
{:api-docs/heading "Subscriptions"}
([query]
- (subs/subscribe query))
+ (frame/subscribe default-frame query))
([query dynv]
- (subs/subscribe query dynv)))
+ (frame/subscribe default-frame query dynv)))
(defn clear-sub ;; think unreg-sub
"Unregisters subscription handlers (presumably registered previously via the use of `reg-sub`).
@@ -512,9 +504,9 @@
NOTE: Depending on the usecase, it may be necessary to call `clear-subscription-cache!` afterwards"
{:api-docs/heading "Subscriptions"}
([]
- (registrar/clear-handlers subs/kind))
+ (frame/clear-sub default-frame))
([query-id]
- (registrar/clear-handlers subs/kind query-id)))
+ (frame/clear-sub default-frame query-id)))
(defn reg-sub-raw
"This is a low level, advanced function. You should probably be
@@ -524,7 +516,7 @@
http://day8.github.io/re-frame/flow-mechanics/"
{:api-docs/heading "Subscriptions"}
[query-id handler-fn]
- (registrar/register-handler subs/kind query-id handler-fn))
+ (frame/reg-sub-raw default-frame query-id handler-fn))
;; XXX
(defn clear-subscription-cache!
@@ -538,7 +530,7 @@
"
{:api-docs/heading "Subscriptions"}
[]
- (subs/clear-subscription-cache!))
+ (frame/clear-subscriptions-cache default-frame))
;; -- effects -----------------------------------------------------------------
@@ -567,7 +559,7 @@
"
{:api-docs/heading "Effect Handlers"}
[id handler]
- (fx/reg-fx id handler))
+ (frame/reg-fx default-frame id handler))
(defn clear-fx ;; think unreg-fx
"Unregisters effect handlers (presumably registered previously via the use of `reg-fx`).
@@ -580,9 +572,9 @@
"
{:api-docs/heading "Effect Handlers"}
([]
- (registrar/clear-handlers fx/kind))
+ (frame/clear-fx default-frame))
([id]
- (registrar/clear-handlers fx/kind id)))
+ (frame/clear-fx default-frame id)))
;; -- coeffects ---------------------------------------------------------------
@@ -598,7 +590,7 @@
"
{:api-docs/heading "Coeffects"}
[id handler]
- (cofx/reg-cofx id handler))
+ (frame/reg-cofx default-frame id handler))
(defn inject-cofx
"Given an `id`, and an optional, arbitrary `value`, returns an interceptor
@@ -660,9 +652,9 @@
"
{:api-docs/heading "Coeffects"}
([id]
- (cofx/inject-cofx id))
+ (frame/inject-cofx default-frame id))
([id value]
- (cofx/inject-cofx id value)))
+ (frame/inject-cofx default-frame id value)))
(defn clear-cofx ;; think unreg-cofx
"Unregisters coeffect handlers (presumably registered previously via the use of `reg-cofx`).
@@ -674,9 +666,9 @@
console if it finds no matching registration."
{:api-docs/heading "Coeffects"}
([]
- (registrar/clear-handlers cofx/kind))
+ (frame/clear-cofx default-frame))
([id]
- (registrar/clear-handlers cofx/kind id)))
+ (frame/clear-cofx default-frame id)))
;; -- error handler ----------------------------------------------------------
@@ -705,7 +697,7 @@
- `:direction`: `:before` or `:after`.
- `:event-v`: the re-frame event which invoked this interceptor."
[handler]
- (registrar/register-handler :error :event-handler handler))
+ (frame/event-error-handler default-frame handler))
(reg-event-error-handler interceptor/default-error-handler)
@@ -1132,29 +1124,13 @@
"
{:api-docs/heading "Miscellaneous"}
[]
- (let [handlers @registrar/kind->id->handler
- app-db @db/app-db
- subs-cache @subs/query->reaction]
- (fn []
- ;; call `dispose!` on all current subscriptions which
- ;; didn't originally exist.
- (let [original-subs (set (vals subs-cache))
- current-subs (set (vals @subs/query->reaction))]
- (doseq [sub (set/difference current-subs original-subs)]
- (interop/dispose! sub)))
-
- ;; Reset the atoms
- ;; We don't need to reset subs/query->reaction, as
- ;; disposing of the subs removes them from the cache anyway
- (reset! registrar/kind->id->handler handlers)
- (reset! db/app-db app-db)
- nil)))
+ (frame/make-restore-fn default-frame))
(defn purge-event-queue
"Removes all events currently queued for processing"
{:api-docs/heading "Miscellaneous"}
[]
- (router/purge re-frame.router/event-queue))
+ (router/purge (:event-queue default-frame)))
;; -- Event Processing Callbacks ---------------------------------------------
@@ -1180,7 +1156,7 @@
([f]
(add-post-event-callback f f)) ;; use f as its own identifier
([id f]
- (router/add-post-event-callback re-frame.router/event-queue id f)))
+ (router/add-post-event-callback (:event-queue default-frame) id f)))
(defn remove-post-event-callback
"Unregisters a post event callback function, identified by `id`.
@@ -1188,7 +1164,7 @@
Such a function must have been previously registered via `add-post-event-callback`"
{:api-docs/heading "Miscellaneous"}
[id]
- (router/remove-post-event-callback re-frame.router/event-queue id))
+ (router/remove-post-event-callback (:event-queue default-frame) id))
;; -- Deprecation ------------------------------------------------------------
;; Assisting the v0.7.x -> v0.8.x transition.
diff --git a/src/re_frame/db.cljc b/src/re_frame/db.cljc
index 5ee49bd5..1c2d33e3 100644
--- a/src/re_frame/db.cljc
+++ b/src/re_frame/db.cljc
@@ -1,10 +1,18 @@
(ns re-frame.db
- (:require [re-frame.interop :refer [ratom]]))
+ (:require [re-frame.interop :refer [ratom]]
+ [re-frame.frame :as frame]
+ [re-frame.fx :as fx]
+ [re-frame.cofx :as cofx]))
+
+
+(def default-frame (frame/make-frame))
+
+(fx/register-built-in! default-frame)
+(cofx/register-built-in! default-frame)
;; -- Application State --------------------------------------------------------------------------
;;
;; Should not be accessed directly by application code.
;; Read access goes through subscriptions.
;; Updates via event handlers.
-(def app-db (ratom {}))
-
+(def app-db (:app-db default-frame))
diff --git a/src/re_frame/events.cljc b/src/re_frame/events.cljc
index 95bf8208..b03fedc5 100644
--- a/src/re_frame/events.cljc
+++ b/src/re_frame/events.cljc
@@ -1,6 +1,5 @@
(ns re-frame.events
- (:require [re-frame.db :refer [app-db]]
- [re-frame.utils :refer [first-in-vector]]
+ (:require [re-frame.utils :refer [first-in-vector]]
[re-frame.interop :refer [empty-queue debug-enabled?]]
[re-frame.registrar :refer [get-handler register-handler]]
[re-frame.loggers :refer [console]]
@@ -39,8 +38,8 @@
Typically, an `event handler` will be at the end of the chain (wrapped
in an interceptor)."
- [id interceptors]
- (register-handler kind id (flatten-and-remove-nils id interceptors)))
+ [registry id interceptors]
+ (register-handler registry kind id (flatten-and-remove-nils id interceptors)))
;; -- handle event --------------------------------------------------------------------------------
@@ -48,9 +47,9 @@
(defn handle
"Given an event vector `event-v`, look up the associated interceptor chain, and execute it."
- [event-v]
+ [{:keys [registry app-db] :as frame} event-v]
(let [event-id (first-in-vector event-v)]
- (if-let [interceptors (get-handler kind event-id true)]
+ (if-let [interceptors (get-handler registry kind event-id true)]
(if *handling*
(console :error "re-frame: while handling" *handling* ", dispatch-sync was called for" event-v ". You can't call dispatch-sync within an event handler.")
(binding [*handling* event-v]
@@ -58,7 +57,5 @@
:op-type kind
:tags {:event event-v}}
(trace/merge-trace! {:tags {:app-db-before @app-db}})
- (interceptor/execute event-v interceptors)
+ (interceptor/execute frame event-v interceptors)
(trace/merge-trace! {:tags {:app-db-after @app-db}})))))))
-
-
diff --git a/src/re_frame/flow/alpha.cljc b/src/re_frame/flow/alpha.cljc
deleted file mode 100644
index 2f704fdd..00000000
--- a/src/re_frame/flow/alpha.cljc
+++ /dev/null
@@ -1,172 +0,0 @@
-(ns re-frame.flow.alpha
- (:require
- #?(:cljs [re-frame.db :as db])
- [re-frame.utils :as u]
- [re-frame.registrar :refer [get-handler]]
- [re-frame.loggers :refer [console]]
- [re-frame.interceptor :refer [->interceptor get-effect get-coeffect assoc-effect]]
- [re-frame.interop :as interop]
- #?(:cljs [reagent.core :as r])))
-
-(def db-path? vector?)
-
-(def flow? map?)
-
-(def flow<-? (comp some? ::flow<-))
-
-(def flows (interop/ratom {}))
-
-(defn lookup [id] (get @flows id))
-
-(defn input-ids [{:keys [inputs live-inputs]}]
- (vec (distinct (into []
- (comp (remove db-path?)
- (map #(or (::flow<- %) %)))
- (concat (vals inputs) (vals live-inputs))))))
-
-(defn topsort [flows]
- (->> flows
- (u/map-vals input-ids)
- u/remove-orphans
- u/topsort-kahn
- reverse
- (map flows)))
-
-(defn default [id]
- {:id id
- :path [id]
- :inputs {}
- :output (constantly true)
- :live? (constantly true)
- :live-inputs {}
- :cleanup u/deep-dissoc})
-
-(defn stale-in-flows [flows {:keys [inputs]}]
- (reduce-kv (fn [m k {:keys [path]}]
- (cond-> m
- (contains? (set (vals inputs)) path) (assoc k path)))
- {}
- flows))
-
-(defn stale-out-flows [flows {:keys [path]}]
- (reduce-kv (fn [m k {:keys [inputs]}]
- (let [bad-inputs (into {} (filter (comp #{path} val)) inputs)]
- (cond-> m (seq bad-inputs) (assoc k bad-inputs))))
- {}
- flows))
-
-(defn validate-inputs [{:keys [inputs]}]
- (doseq [[_ input] inputs
- :when (not ((some-fn db-path? flow<-?) input))]
- (throw (#?(:clj Exception. :cljs js/Error.) "bad input"))))
-
-(defn warn-stale-dependencies [flows new-flow]
- (let [ins (stale-in-flows flows new-flow)
- outs (stale-out-flows flows new-flow)
- warn-ins (fn [[id path]]
- ["- Input" (str path)
- "matches the output path of" (str id) ".\n"
- " For an explicit dependency, change it to (re-frame/flow<-"
- (str id ").") "\n"])
- warn-outs (fn [[id inputs]]
- (mapcat (fn [[input-id _]]
- ["- Output" (str (:path new-flow))
- "matches the input" (str input-id)
- "of the flow" (str id ".\n")
- " For an explicit dependency, change that input to"
- "(re-frame/flow<-" (str (:id new-flow) ").") "\n"])
- inputs))
- warnings (concat (mapcat warn-ins ins) (mapcat warn-outs outs))]
- (when (seq warnings)
- (apply console :warn "Warning: You called `reg-flow` with the flow" (str (:id new-flow))
- "but this created stale dependencies.\n"
- "Your flows may not evaluate in the correct order.\n"
- warnings))))
-
-(defn reg-flow
- ([k m]
- (reg-flow (assoc m :id k)))
- ([m]
- (validate-inputs m)
- (warn-stale-dependencies @flows m)
- (swap! flows assoc
- (:id m) (with-meta (merge (default (:id m)) m)
- (merge
- {::new? true}
- #?(:cljs
- {::ref (r/reaction (get-in @db/app-db (:path m)))}))))))
-
-(defn clear-flow
- ([]
- (swap! flows vary-meta update ::cleared into @flows)
- (swap! flows empty))
- ([id]
- (when-let [flow (lookup id)]
- (swap! flows dissoc id)
- (swap! flows vary-meta update ::cleared assoc (:id flow) flow))))
-
-(defn flow<- [id] {::flow<- id})
-
-(def flow-fx-ids #{:reg-flow :clear-flow})
-
-(defn do-effect [[k v]] ((get-handler :fx k false) v))
-
-(def remove-fx (partial remove flow-fx-ids))
-
-(def do-fx
- (->interceptor
- {:id :do-flow-fx
- :after (fn [{{:keys [fx] :as effects} :effects
- :as ctx}]
- (let [flow-fx (concat (select-keys effects flow-fx-ids)
- (filterv (comp flow-fx-ids first) fx))]
- (doall (map do-effect flow-fx))
- (-> ctx
- (update-in [:effects :fx] remove-fx)
- (update :effects (comp (partial into {}) remove-fx)))))}))
-
-(defn resolve-input [db input]
- (if (vector? input)
- (get-in db input)
- (some->> input ::flow<- lookup :path (resolve-input db))))
-
-(defn resolve-inputs [db inputs]
- (if (empty? inputs) db (u/map-vals (partial resolve-input db) inputs)))
-
-(defn run [ctx {:as flow
- :keys [path cleanup live? inputs live-inputs output id]
- ::keys [cleared?]}]
- (let [{::keys [new?]} (meta flow)
- old-db (get-coeffect ctx :db)
- db (or (get-effect ctx :db) old-db)
-
- id->old-in (resolve-inputs old-db inputs)
- id->in (resolve-inputs db inputs)
- dirty? (not= id->in id->old-in)
-
- id->old-live-in (resolve-inputs old-db live-inputs)
- id->live-in (resolve-inputs db live-inputs)
- bardo [(cond new? :new (live? id->old-live-in) :live :else :dead)
- (cond cleared? :cleared (live? id->live-in) :live :else :dead)]
-
- new-db (case bardo
- [:live :live] (cond-> db dirty? (assoc-in path (output id->in)))
- [:live :dead] (cleanup db path)
- [:dead :live] (assoc-in db path (output id->in))
- [:new :live] (do (swap! flows update id vary-meta dissoc ::new?)
- (assoc-in db path (output id->in)))
- [:live :cleared] (cleanup db path)
- nil)]
- (cond-> ctx new-db (assoc-effect :db new-db))))
-
-(defn with-cleared [m]
- (into m (map (fn [[k v]] [[::cleared k (gensym)] (assoc v ::cleared? true)])
- (::cleared (meta m)))))
-
-(def interceptor
- (->interceptor
- {:id :flow
- :after (fn [ctx]
- (let [all-flows (with-cleared @flows)]
- (swap! flows vary-meta dissoc ::cleared)
- (reduce run ctx ((memoize topsort) all-flows))))}))
diff --git a/src/re_frame/frame.cljc b/src/re_frame/frame.cljc
new file mode 100644
index 00000000..66ad31a7
--- /dev/null
+++ b/src/re_frame/frame.cljc
@@ -0,0 +1,236 @@
+(ns re-frame.frame
+ (:require [re-frame.cofx :as cofx]
+ [re-frame.events :as events]
+ [re-frame.fx :as fx]
+ [re-frame.interop :as interop]
+ [re-frame.registrar :as reg]
+ [re-frame.router :as router]
+ [re-frame.std-interceptors :as stdi]
+ [re-frame.subs :as subs]))
+
+(defprotocol IFrame
+ ;; dispatch ----
+ (dispatch [this event-v])
+ (dispatch-sync [this event-v])
+
+ ;; subs ----
+ (reg-sub-raw [this query-id handler-fn])
+ (reg-sub
+ ;; variadic args are not supported in protocols
+ [this query-id arg1]
+ [this query-id arg1 arg2]
+ [this query-id arg1 arg2 arg3]
+ [this query-id arg1 arg2 arg3 arg4]
+ [this query-id arg1 arg2 arg3 arg4 arg5]
+ [this query-id arg1 arg2 arg3 arg4 arg5 arg6]
+ [this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7]
+ [this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8]
+ [this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9]
+ [this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10]
+ [this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11]
+ [this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12]
+ [this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13]
+ [this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14]
+ [this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14 arg15]
+ [this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14 arg15 arg16])
+
+ (subscribe
+ [this query-v]
+ [this query-v dynv])
+ (clear-sub
+ [this]
+ [this query-id])
+ (clear-subscriptions-cache [this])
+
+ ;; fx ----
+ (reg-fx [this fx-id handler-fn])
+ (clear-fx
+ [this]
+ [this fx-id])
+
+ ;; cofx ----
+ (reg-cofx [this cofx-id handler-fn])
+ (inject-cofx
+ [this cofx-id]
+ [this cofx-id value])
+ (clear-cofx
+ [this]
+ [this cofx-id])
+
+ ;; events ----
+ (clear-event
+ [this]
+ [this event-id])
+ (reg-event-db
+ [this id db-handler]
+ [this id interceptors db-handler])
+ (reg-event-fx
+ [this id fx-handler]
+ [this id interceptors fx-handler])
+ (reg-event-ctx
+ [this id handler]
+ [this id interceptors handler])
+
+ ;; errors ----
+ (event-error-handler
+ [this handler]))
+
+;; connect all the pieces of state ----
+(defrecord Frame [registry event-queue app-db subs-cache default-interceptors]
+ IFrame
+ ;; dispatch ----
+ (dispatch [this event-v]
+ (router/dispatch event-queue event-v))
+ (dispatch-sync [this event-v]
+ (router/dispatch-sync this event-v))
+
+ ;; subs ----
+ (reg-sub-raw [this query-id handler-fn]
+ (reg/register-handler registry subs/kind query-id handler-fn))
+ (reg-sub [this query-id arg1]
+ (subs/reg-sub this query-id arg1))
+ (reg-sub [this query-id arg1 arg2]
+ (subs/reg-sub this query-id arg1 arg2))
+ (reg-sub [this query-id arg1 arg2 arg3]
+ (subs/reg-sub this query-id arg1 arg2 arg3))
+ (reg-sub [this query-id arg1 arg2 arg3 arg4]
+ (subs/reg-sub this query-id arg1 arg2 arg3 arg4))
+ (reg-sub [this query-id arg1 arg2 arg3 arg4 arg5]
+ (subs/reg-sub this query-id arg1 arg2 arg3 arg4 arg5))
+ (reg-sub [this query-id arg1 arg2 arg3 arg4 arg5 arg6]
+ (subs/reg-sub this query-id arg1 arg2 arg3 arg4 arg5 arg6))
+ (reg-sub [this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7]
+ (subs/reg-sub this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7))
+ (reg-sub [this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8]
+ (subs/reg-sub this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8))
+ (reg-sub [this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9]
+ (subs/reg-sub this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9))
+ (reg-sub [this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10]
+ (subs/reg-sub this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10))
+ (reg-sub [this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11]
+ (subs/reg-sub this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11))
+ (reg-sub [this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12]
+ (subs/reg-sub this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12))
+ (reg-sub [this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13]
+ (subs/reg-sub this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13))
+ (reg-sub [this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14]
+ (subs/reg-sub this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14))
+ (reg-sub [this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14 arg15]
+ (subs/reg-sub this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14 arg15))
+ (reg-sub [this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14 arg15 arg16]
+ (subs/reg-sub this query-id arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14 arg15 arg16))
+ (subscribe [this query-v]
+ (subs/subscribe this query-v))
+ (subscribe [this query-v dynv]
+ (subs/subscribe this query-v dynv))
+ (clear-sub [this]
+ (reg/clear-handlers registry subs/kind))
+ (clear-sub [this query-id]
+ (reg/clear-handlers registry subs/kind query-id))
+ (clear-subscriptions-cache [this]
+ (subs/clear-subscription-cache! subs-cache))
+
+ ;; fx ----
+ (reg-fx [this fx-id handler-fn]
+ (reg/register-handler registry fx/kind fx-id handler-fn))
+ (clear-fx [this]
+ (reg/clear-handlers registry fx/kind))
+ (clear-fx [this fx-id]
+ (reg/clear-handlers registry fx/kind fx-id))
+
+ ;; cofx ----
+ (reg-cofx [this cofx-id handler-fn]
+ (reg/register-handler registry cofx/kind cofx-id handler-fn))
+ (inject-cofx [this cofx-id]
+ (cofx/inject-cofx registry cofx-id))
+ (inject-cofx [this cofx-id value]
+ (cofx/inject-cofx registry cofx-id value))
+ (clear-cofx [this]
+ (reg/clear-handlers registry cofx/kind))
+ (clear-cofx [this cofx-id]
+ (reg/clear-handlers registry cofx/kind cofx-id))
+
+ ;; events ----
+ (clear-event [this]
+ (reg/clear-handlers registry events/kind))
+ (clear-event [this id]
+ (reg/clear-handlers registry events/kind id))
+
+ (reg-event-db [this id db-handler]
+ (reg-event-db this id nil db-handler))
+ (reg-event-db [this id interceptors db-handler]
+ (events/register
+ registry
+ id
+ [default-interceptors interceptors (stdi/db-handler->interceptor db-handler)]))
+ (reg-event-fx [this id fx-handler]
+ (reg-event-fx this id nil fx-handler))
+ (reg-event-fx [this id interceptors fx-handler]
+ (events/register
+ registry
+ id
+ [default-interceptors interceptors (stdi/fx-handler->interceptor fx-handler)]))
+ (reg-event-ctx [this id handler]
+ (reg-event-ctx this id nil handler))
+ (reg-event-ctx [this id interceptors handler]
+ (events/register
+ registry
+ id
+ [default-interceptors interceptors (stdi/ctx-handler->interceptor handler)]))
+
+ (event-error-handler [this handler]
+ (reg/register-handler registry :error :event-handler handler)))
+
+(def frame-id (atom 0))
+
+(defn make-frame
+ "Creates a new frame, which bundles the registry (subscriptions, event-handlers,
+ fx, cofx), app-db, subscription cache, default interceptors, and event queue.
+
+ :registry, :app-db, and :interceptors can be provided through an options map."
+ [& [{:keys [registry app-db interceptors] :as extra-keys}]]
+ (let [registry (or registry (reg/make-registry))
+ app-db (or app-db (interop/ratom {}))
+ default-interceptors [(cofx/inject-cofx registry :db)
+ (fx/do-fx registry)
+ stdi/inject-global-interceptors]
+ frame (map->Frame
+ (merge {:frame-id (swap! frame-id inc)
+ :registry registry
+ :app-db app-db
+ :subs-cache (subs/->SubscriptionCache (atom {}))
+ :default-interceptors (if interceptors
+ (if (:replace (meta interceptors))
+ interceptors
+ (into default-interceptors interceptors))
+ default-interceptors)
+ :event-queue (router/->EventQueue :idle interop/empty-queue {} nil)}
+ (dissoc extra-keys :registry :app-db :interceptors)))]
+ ;; When events / fx fire, they get their frame from the event-queue
+ (router/set-frame (:event-queue frame) frame)
+ frame))
+
+(defn make-restore-fn
+ "Checkpoints the state of re-frame and returns a function which, when
+ later called, will restore re-frame to that checkpointed state.
+
+ Checkpoint includes app-db, all registered handlers and all subscriptions."
+ ([frame]
+ (let [handlers (-> frame :registry :kind->id->handler deref)
+ app-db (-> frame :app-db deref)
+ subs-cache (-> frame :subs-cache deref)]
+ (fn []
+ ;; call `dispose!` on all current subscriptions which
+ ;; didn't originally exist.
+ (let [original-subs (-> subs-cache vals set)
+ current-subs (-> frame :subs-cache deref vals)]
+ (doseq [sub current-subs
+ :when (not (contains? original-subs sub))]
+ (interop/dispose! sub)))
+
+ ;; Reset the atoms
+ ;; We don't need to reset subs-cache, as disposing of the subs
+ ;; removes them from the cache anyway
+ (reset! (-> frame :registry :kind->id->handler) handlers)
+ (reset! (-> frame :app-db) app-db)
+ nil))))
diff --git a/src/re_frame/fx.cljc b/src/re_frame/fx.cljc
index 17cc27ae..d8ee6b76 100644
--- a/src/re_frame/fx.cljc
+++ b/src/re_frame/fx.cljc
@@ -1,11 +1,10 @@
(ns re-frame.fx
(:require
[re-frame.router :as router]
- [re-frame.db :refer [app-db]]
[re-frame.interceptor :refer [->interceptor]]
[re-frame.interop :refer [set-timeout!]]
[re-frame.events :as events]
- [re-frame.registrar :refer [get-handler clear-handlers register-handler]]
+ [re-frame.registrar :refer [get-handler clear-handlers register-handler *current-frame*]]
[re-frame.loggers :refer [console]]
[re-frame.trace :as trace :include-macros true]))
@@ -14,13 +13,9 @@
(def kind :fx)
(assert (re-frame.registrar/kinds kind))
-(defn reg-fx
- [id handler]
- (register-handler kind id handler))
-
;; -- Interceptor -------------------------------------------------------------
-(def do-fx
+(defn do-fx
"An interceptor whose `:after` actions the contents of `:effects`. As a result,
this interceptor is Domino 3.
@@ -42,20 +37,24 @@
You cannot rely on the ordering in which effects are executed, other than that
`:db` is guaranteed to be executed first."
+ [registry]
(->interceptor
:id :do-fx
:after (fn do-fx-after
[context]
+ {:pre [(:frame context)]}
(trace/with-trace
{:op-type :event/do-fx}
(let [effects (:effects context)
effects-without-db (dissoc effects :db)]
;; :db effect is guaranteed to be handled before all other effects.
(when-let [new-db (:db effects)]
- ((get-handler kind :db false) new-db))
+ (binding [*current-frame* (:frame context)]
+ ((get-handler registry kind :db false) new-db)))
(doseq [[effect-key effect-value] effects-without-db]
- (if-let [effect-fn (get-handler kind effect-key false)]
- (effect-fn effect-value)
+ (if-let [effect-fn (get-handler registry kind effect-key false)]
+ (binding [*current-frame* (:frame context)]
+ (effect-fn effect-value))
(console :warn
"re-frame: no handler registered for effect:"
effect-key
@@ -64,126 +63,130 @@
(str "You may be trying to return a coeffect map from an event-fx handler. "
"See https://day8.github.io/re-frame/use-cofx-as-fx/"))))))))))
-;; -- Builtin Effect Handlers ------------------------------------------------
-
-;; :dispatch-later
-;;
-;; `dispatch` one or more events after given delays. Expects a collection
-;; of maps with two keys: :`ms` and `:dispatch`
-;;
-;; usage:
-;;
-;; {:dispatch-later [{:ms 200 :dispatch [:event-id "param"]} ;; in 200ms do this: (dispatch [:event-id "param"])
-;; {:ms 100 :dispatch [:also :this :in :100ms]}]}
-;;
-;; Note: nil entries in the collection are ignored which means events can be added
-;; conditionally:
-;; {:dispatch-later [ (when (> 3 5) {:ms 200 :dispatch [:conditioned-out]})
-;; {:ms 100 :dispatch [:another-one]}]}
-;;
-(defn dispatch-later
- [{:keys [ms dispatch] :as effect}]
- (if (or (empty? dispatch) (not (number? ms)))
- (console :error "re-frame: ignoring bad :dispatch-later value:" effect)
- (set-timeout! #(router/dispatch dispatch) ms)))
-
-(reg-fx
- :dispatch-later
- (fn [value]
- (if (map? value)
- (dispatch-later value)
- (doseq [effect (remove nil? value)]
- (dispatch-later effect)))))
-
-;; :fx
-;;
-;; Handle one or more effects. Expects a collection of vectors (tuples) of the
-;; form [effect-key effect-value]. `nil` entries in the collection are ignored
-;; so effects can be added conditionally.
-;;
-;; usage:
-;;
-;; {:fx [[:dispatch [:event-id "param"]]
-;; nil
-;; [:http-xhrio {:method :post
-;; ...}]]}
-;;
-
-(reg-fx
- :fx
- (fn [seq-of-effects]
- (if-not (sequential? seq-of-effects)
- (console :warn "re-frame: \":fx\" effect expects a seq, but was given " (type seq-of-effects))
- (doseq [[effect-key effect-value] (remove nil? seq-of-effects)]
- (when (= :db effect-key)
- (console :warn "re-frame: \":fx\" effect should not contain a :db effect"))
- (if-let [effect-fn (get-handler kind effect-key false)]
- (effect-fn effect-value)
- (console :warn "re-frame: in \":fx\" effect found " effect-key " which has no associated handler. Ignoring."))))))
-
-;; :dispatch
-;;
-;; `dispatch` one event. Expects a single vector.
-;;
-;; usage:
-;; {:dispatch [:event-id "param"] }
-
-(reg-fx
- :dispatch
- (fn [value]
- (if-not (vector? value)
- (console :error "re-frame: ignoring bad :dispatch value. Expected a vector, but got:" value)
- (router/dispatch value))))
-
-;; :dispatch-n
-;;
-;; `dispatch` more than one event. Expects a list or vector of events. Something for which
-;; sequential? returns true.
-;;
-;; usage:
-;; {:dispatch-n (list [:do :all] [:three :of] [:these])}
-;;
-;; Note: nil events are ignored which means events can be added
-;; conditionally:
-;; {:dispatch-n (list (when (> 3 5) [:conditioned-out])
-;; [:another-one])}
-;;
-(reg-fx
- :dispatch-n
- (fn [value]
- (if-not (sequential? value)
- (console :error "re-frame: ignoring bad :dispatch-n value. Expected a collection, but got:" value)
- (doseq [event (remove nil? value)] (router/dispatch event)))))
-
-;; :deregister-event-handler
-;;
-;; removes a previously registered event handler. Expects either a single id (
-;; typically a namespaced keyword), or a seq of ids.
-;;
-;; usage:
-;; {:deregister-event-handler :my-id)}
-;; or:
-;; {:deregister-event-handler [:one-id :another-id]}
-;;
-(reg-fx
- :deregister-event-handler
- (fn [value]
- (let [clear-event (partial clear-handlers events/kind)]
- (if (sequential? value)
- (doseq [event value] (clear-event event))
- (clear-event value)))))
-
-;; :db
-;;
-;; reset! app-db with a new value. `value` is expected to be a map.
-;;
-;; usage:
-;; {:db {:key1 value1 key2 value2}}
-;;
-(reg-fx
- :db
- (fn [value]
- (if-not (identical? @app-db value)
- (reset! app-db value)
- (trace/with-trace {:op-type :reagent/quiescent}))))
-
+(defn register-built-in!
+ [{:keys [registry]}]
+ (let [reg-fx (partial register-handler registry kind)]
+
+ ;; -- Builtin Effect Handlers ------------------------------------------------
+
+ ;; :dispatch-later
+ ;;
+ ;; `dispatch` one or more events after given delays. Expects a collection
+ ;; of maps with two keys: :`ms` and `:dispatch`
+ ;;
+ ;; usage:
+ ;;
+ ;; {:dispatch-later [{:ms 200 :dispatch [:event-id "param"]} ;; in 200ms do this: (dispatch [:event-id "param"])
+ ;; {:ms 100 :dispatch [:also :this :in :100ms]}]}
+ ;;
+ ;; Note: nil entries in the collection are ignored which means events can be added
+ ;; conditionally:
+ ;; {:dispatch-later [ (when (> 3 5) {:ms 200 :dispatch [:conditioned-out]})
+ ;; {:ms 100 :dispatch [:another-one]}]}
+ ;;
+ (letfn [(dispatch-later
+ [{:keys [ms dispatch] :as effect} {:keys [event-queue]}]
+ (if (or (empty? dispatch) (not (number? ms)))
+ (console :error "re-frame: ignoring bad :dispatch-later value:" effect)
+ (set-timeout! #(router/dispatch event-queue dispatch) ms)))]
+
+ (reg-fx
+ :dispatch-later
+ (fn [value]
+ (if (map? value)
+ (dispatch-later value *current-frame*)
+ (doseq [effect (remove nil? value)]
+ (dispatch-later effect *current-frame*))))))
+
+ ;; :fx
+ ;;
+ ;; Handle one or more effects. Expects a collection of vectors (tuples) of the
+ ;; form [effect-key effect-value]. `nil` entries in the collection are ignored
+ ;; so effects can be added conditionally.
+ ;;
+ ;; usage:
+ ;;
+ ;; {:fx [[:dispatch [:event-id "param"]]
+ ;; nil
+ ;; [:http-xhrio {:method :post
+ ;; ...}]]}
+ ;;
+
+ (reg-fx
+ :fx
+ (fn [seq-of-effects]
+ (if-not (sequential? seq-of-effects)
+ (console :warn "re-frame: \":fx\" effect expects a seq, but was given " (type seq-of-effects))
+ (doseq [[effect-key effect-value] (remove nil? seq-of-effects)]
+ (when (= :db effect-key)
+ (console :warn "re-frame: \":fx\" effect should not contain a :db effect"))
+ (if-let [effect-fn (get-handler (:registry *current-frame*) kind effect-key false)]
+ (effect-fn effect-value *current-frame*)
+ (console :warn "re-frame: in \":fx\" effect found " effect-key " which has no associated handler. Ignoring."))))))
+
+ ;; :dispatch
+ ;;
+ ;; `dispatch` one event. Expects a single vector.
+ ;;
+ ;; usage:
+ ;; {:dispatch [:event-id "param"] }
+
+ (reg-fx
+ :dispatch
+ (fn [value]
+ (if-not (vector? value)
+ (console :error "re-frame: ignoring bad :dispatch value. Expected a vector, but got:" value)
+ (router/dispatch (:event-queue *current-frame*) value))))
+
+ ;; :dispatch-n
+ ;;
+ ;; `dispatch` more than one event. Expects a list or vector of events. Something for which
+ ;; sequential? returns true.
+ ;;
+ ;; usage:
+ ;; {:dispatch-n (list [:do :all] [:three :of] [:these])}
+ ;;
+ ;; Note: nil events are ignored which means events can be added
+ ;; conditionally:
+ ;; {:dispatch-n (list (when (> 3 5) [:conditioned-out])
+ ;; [:another-one])}
+ ;;
+ (reg-fx
+ :dispatch-n
+ (fn [value]
+ (if-not (sequential? value)
+ (console :error "re-frame: ignoring bad :dispatch-n value. Expected a collection, but got:" value)
+ (doseq [event (remove nil? value)] (router/dispatch (:event-queue *current-frame*) event)))))
+
+ ;; :deregister-event-handler
+ ;;
+ ;; removes a previously registered event handler. Expects either a single id (
+ ;; typically a namespaced keyword), or a seq of ids.
+ ;;
+ ;; usage:
+ ;; {:deregister-event-handler :my-id)}
+ ;; or:
+ ;; {:deregister-event-handler [:one-id :another-id]}
+ ;;
+ (reg-fx
+ :deregister-event-handler
+ (fn [value]
+ (let [clear-event (partial clear-handlers (:registry *current-frame*) events/kind)]
+ (if (sequential? value)
+ (doseq [event value] (clear-event event))
+ (clear-event value)))))
+
+ ;; :db
+ ;;
+ ;; reset! app-db with a new value. `value` is expected to be a map.
+ ;;
+ ;; usage:
+ ;; {:db {:key1 value1 key2 value2}}
+ ;;
+ (reg-fx
+ :db
+ (fn [value]
+ (let [{:keys [app-db]} *current-frame*]
+ (if-not (identical? @app-db value)
+ (reset! app-db value)
+ (trace/with-trace {:op-type :reagent/quiescent})))))))
diff --git a/src/re_frame/interceptor.cljc b/src/re_frame/interceptor.cljc
index f3853631..efd5ef18 100644
--- a/src/re_frame/interceptor.cljc
+++ b/src/re_frame/interceptor.cljc
@@ -130,15 +130,15 @@
(defn- context
"Create a fresh context"
- ([event interceptors]
- (-> {}
+ ([frame event interceptors]
+ (-> {:frame frame}
(assoc-coeffect :event event)
;; Some interceptors, like `trim-v` and `unwrap`, alter event so capture
;; the original for use cases such as tracing.
(assoc-coeffect :original-event event)
(enqueue interceptors)))
- ([event interceptors db] ;; only used in tests, probably a hack, remove ? XXX
- (-> (context event interceptors)
+ ([frame event interceptors db] ;; only used in tests, probably a hack, remove ? XXX
+ (-> (context frame event interceptors)
(assoc-coeffect :db db))))
(defn- change-direction
@@ -228,9 +228,9 @@
of interceptors yet to be processed, and a `:stack` of interceptors
already done. In advanced cases, these values can be modified by the
functions through which the context is threaded."
- [event-v interceptors]
- (let [ctx (context event-v interceptors)
- error-handler (registrar/get-handler :error :event-handler)]
+ [frame event-v interceptors]
+ (let [ctx (context frame event-v interceptors)
+ error-handler (registrar/get-handler (:registry frame) :error :event-handler)]
(trace/merge-trace!
{:tags {:interceptors interceptors}})
(if-not error-handler
diff --git a/src/re_frame/query/alpha.cljc b/src/re_frame/query/alpha.cljc
deleted file mode 100644
index ad67ef93..00000000
--- a/src/re_frame/query/alpha.cljc
+++ /dev/null
@@ -1,61 +0,0 @@
-(ns re-frame.query.alpha
- (:require
- [re-frame :as-alias rf]
- [re-frame.db :refer [app-db]]
- [re-frame.interop :refer [reagent-id]]
- [re-frame.loggers :refer [console]]
- [re-frame.register.alpha :refer [lifecycle->method]]
- [re-frame.registrar :refer [get-handler]]
- [re-frame.trace :as trace :include-macros true]))
-
-(declare lifecycle)
-
-(defn legacy-lifecycle [v]
- (when (vector? v)
- (or (lifecycle (meta v))
- :default)))
-
-(defn legacy-query-id [q]
- (when (vector? q) (first q)))
-
-(def id (some-fn legacy-query-id ::rf/q))
-
-(def flow-lifecycle (comp #{:flow} id))
-
-(def lifecycle (some-fn flow-lifecycle
- legacy-lifecycle
- ::rf/lifecycle
- (constantly :default)))
-
-(defn method [q] (@lifecycle->method (lifecycle q)))
-
-(defn clear-all-methods! [] (reset! lifecycle->method {}))
-
-(def cache (atom {}))
-
-(defn cached [q] (if-some [r (get-in @cache [(lifecycle q) q])]
- (do (trace/merge-trace! {:tags {:cached? true
- :reaction (reagent-id r)}})
- r)
- (trace/merge-trace! {:tags {:cached? false}})))
-
-(defn cache! [q r] (swap! cache assoc-in [(lifecycle q) q] r) r)
-
-(defn clear!
- ([] (reset! cache {}))
- ([q] (clear! q (lifecycle q)))
- ([q strat] (swap! cache update strat dissoc q)))
-
-(defn handle [q]
- (let [handler (get-handler :sub (id q))]
- (if-not (nil? handler)
- (handler app-db q)
- (do (trace/merge-trace! {:error true})
- (console :error
- "re-frame: no subscription handler registered for: "
- (id q)
- ". Returning a nil subscription.")))))
-
-(defn query? [q]
- (some? (and (id q)
- (lifecycle q))))
diff --git a/src/re_frame/register/alpha.cljc b/src/re_frame/register/alpha.cljc
deleted file mode 100644
index 4ffb72d2..00000000
--- a/src/re_frame/register/alpha.cljc
+++ /dev/null
@@ -1,5 +0,0 @@
-(ns re-frame.register.alpha)
-
-(defmulti reg (fn [kind & _] kind))
-
-(def lifecycle->method (atom {}))
diff --git a/src/re_frame/registrar.cljc b/src/re_frame/registrar.cljc
index c23cee9d..bb56856a 100644
--- a/src/re_frame/registrar.cljc
+++ b/src/re_frame/registrar.cljc
@@ -6,48 +6,64 @@
[re-frame.loggers :refer [console]]
[re-frame.settings :as settings]))
+(def ^:dynamic *current-frame*)
+
+(defprotocol IRegistry
+ (get-handler
+ [this kind]
+ [this kind id]
+ [this kind id required?])
+
+ (register-handler
+ [this kind id handler-fn])
+
+ (clear-handlers
+ [this]
+ [this kind]
+ [this kind id]))
+
;; kinds of handlers
(def kinds #{:event :fx :cofx :sub :error})
-;; This atom contains a register of all handlers.
-;; Contains a two layer map, keyed first by `kind` (of handler), and then `id` of handler.
-;; Leaf nodes are handlers.
-(def kind->id->handler (atom {}))
-
-(defn get-handler
-
- ([kind]
- (get @kind->id->handler kind))
-
- ([kind id]
- (-> (get @kind->id->handler kind)
- (get id)))
-
- ([kind id required?]
- (let [handler (get-handler kind id)]
- (when debug-enabled? ;; This is in a separate `when` so Closure DCE can run ...
- (when (and required? (nil? handler)) ;; ...otherwise you'd need to type-hint the `and` with a ^boolean for DCE.
- (console :error "re-frame: no" (str kind) "handler registered for:" id)))
- handler)))
-
-(defn register-handler
- [kind id handler-fn]
- (when debug-enabled? ;; This is in a separate when so Closure DCE can run
- (when (and (not (settings/loaded?)) (get-handler kind id false))
- (console :warn "re-frame: overwriting" (str kind) "handler for:" id))) ;; allow it, but warn. Happens on figwheel reloads.
- (swap! kind->id->handler assoc-in [kind id] handler-fn)
- handler-fn) ;; note: returns the just registered handler
-
-(defn clear-handlers
- ([] ;; clear all kinds
- (reset! kind->id->handler {}))
-
- ([kind] ;; clear all handlers for this kind
- (assert (kinds kind))
- (swap! kind->id->handler dissoc kind))
-
- ([kind id] ;; clear a single handler for a kind
- (assert (kinds kind))
- (if (get-handler kind id)
- (swap! kind->id->handler update-in [kind] dissoc id)
- (console :warn "re-frame: can't clear" (str kind) "handler for" (str id ". Handler not found.")))))
+(defrecord Registry [kind->id->handler]
+ IRegistry
+
+ (get-handler [this kind]
+ (get @kind->id->handler kind))
+
+ (get-handler [this kind id]
+ (-> (get @kind->id->handler kind)
+ (get id)))
+
+ (get-handler [this kind id required?]
+ (let [handler (get-handler this kind id)]
+ (when debug-enabled? ;; This is in a separate `when` so Closure DCE can run ...
+ (when (and required? (nil? handler)) ;; ...otherwise you'd need to type-hint the `and` with a ^boolean for DCE.
+ (console :error "re-frame: no" (str kind) "handler registered for:" id)))
+ handler))
+
+ (register-handler [this kind id handler-fn]
+ (when debug-enabled? ;; This is in a separate when so Closure DCE can run
+ (when (and (not (settings/loaded?)) (get-handler this kind id false))
+ (console :warn "re-frame: overwriting" (str kind) "handler for:" id))) ;; allow it, but warn. Happens on figwheel reloads.
+ (swap! kind->id->handler assoc-in [kind id] handler-fn)
+ handler-fn) ;; note: returns the just registered handler
+
+ (clear-handlers [this] ;; clear all kinds
+ (reset! kind->id->handler {}))
+
+ (clear-handlers [this kind] ;; clear all handlers for this kind
+ (assert (kinds kind))
+ (swap! kind->id->handler dissoc kind))
+
+ (clear-handlers [this kind id] ;; clear a single handler for a kind
+ (assert (kinds kind))
+ (if (get-handler this kind id)
+ (swap! kind->id->handler update-in [kind] dissoc id)
+ (console :warn "re-frame: can't clear" (str kind) "handler for" (str id ". Handler not found.")))))
+
+(defn make-registry []
+ ;; This atom contains a register of all handlers.
+ ;; Contains a map keyed first by `kind` (of handler), and then `id`.
+ ;; Leaf nodes are handlers.
+ (->Registry (atom {})))
diff --git a/src/re_frame/router.cljc b/src/re_frame/router.cljc
index 9caddc60..6c880781 100644
--- a/src/re_frame/router.cljc
+++ b/src/re_frame/router.cljc
@@ -73,6 +73,7 @@
(add-post-event-callback [this id callback-fn])
(remove-post-event-callback [this id])
(purge [this])
+ (set-frame [this frame])
;; -- Implementation via a Finite State Machine
(-fsm-trigger [this trigger arg])
@@ -90,6 +91,7 @@
;; Concrete implementation of IEventQueue
(deftype EventQueue [#?(:cljs ^:mutable fsm-state :clj ^:volatile-mutable fsm-state)
#?(:cljs ^:mutable queue :clj ^:volatile-mutable queue)
+ #?(:cljs ^:mutable frame :clj ^:volatile-mutable frame)
#?(:cljs ^:mutable post-event-callback-fns :clj ^:volatile-mutable post-event-callback-fns)]
IEventQueue
@@ -114,6 +116,9 @@
(purge [_]
(set! queue empty-queue))
+ (set-frame [_ value]
+ (set! frame value))
+
;; -- FSM Implementation ---------------------------------------------------
(-fsm-trigger
@@ -173,7 +178,7 @@
[this]
(let [event-v (peek queue)]
(try
- (handle event-v)
+ (handle frame event-v)
(set! queue (pop queue))
(-call-post-event-callbacks this event-v)
(catch #?(:cljs :default :clj Exception) ex
@@ -214,27 +219,21 @@
(-process-1st-event-in-queue this) ;; do the event which paused processing
(-run-queue this))) ;; do the rest of the queued events
-;; ---------------------------------------------------------------------------
-;; Event Queue
-;; When "dispatch" is called, the event is added into this event queue. Later,
-;; the queue will "run" and the event will be "handled" by the registered function.
-;;
-(def event-queue (->EventQueue :idle empty-queue {}))
-
;; ---------------------------------------------------------------------------
;; Dispatching
;;
(defn dispatch
- [event]
+ [event-queue event]
+ {:pre [event-queue]}
(if (nil? event)
(throw (ex-info "re-frame: you called \"dispatch\" without an event vector." {}))
(push event-queue event))
nil) ;; Ensure nil return. See https://github.com/day8/re-frame/wiki/Beware-Returning-False
(defn dispatch-sync
- [event-v]
- (handle event-v)
- (-call-post-event-callbacks event-queue event-v) ;; slightly ugly hack. Run the registered post event callbacks.
+ [frame event-v]
+ (handle frame event-v)
+ (-call-post-event-callbacks (:event-queue frame) event-v) ;; slightly ugly hack. Run the registered post event callbacks.
(trace/with-trace {:op-type :sync})
nil) ;; Ensure nil return. See https://github.com/day8/re-frame/wiki/Beware-Returning-False
diff --git a/src/re_frame/std_interceptors.cljc b/src/re_frame/std_interceptors.cljc
index 4563425d..930d6e8c 100644
--- a/src/re_frame/std_interceptors.cljc
+++ b/src/re_frame/std_interceptors.cljc
@@ -4,7 +4,6 @@
[re-frame.interceptor :refer [->interceptor get-effect get-coeffect assoc-coeffect assoc-effect update-coeffect]]
[re-frame.loggers :refer [console]]
[re-frame.settings :as settings]
- [re-frame.db :refer [app-db]]
[clojure.data :as data]
[re-frame.cofx :as cofx]
[re-frame.utils :as utils]
diff --git a/src/re_frame/subs.cljc b/src/re_frame/subs.cljc
index 667788e8..6a277df9 100644
--- a/src/re_frame/subs.cljc
+++ b/src/re_frame/subs.cljc
@@ -1,10 +1,9 @@
(ns re-frame.subs
(:require
- [re-frame.db :refer [app-db]]
[re-frame.interop :refer [add-on-dispose! debug-enabled? make-reaction ratom? deref? dispose! reagent-id reactive?]]
[re-frame.loggers :refer [console]]
[re-frame.utils :refer [first-in-vector]]
- [re-frame.registrar :refer [get-handler clear-handlers register-handler]]
+ [re-frame.registrar :refer [get-handler clear-handlers register-handler *current-frame*]]
[re-frame.trace :as trace :include-macros true]))
(def kind :sub)
@@ -15,51 +14,67 @@
;; De-duplicate subscriptions. If two or more equal subscriptions
;; are concurrently active, we want only one handler running.
;; Two subscriptions are "equal" if their query vectors test "=".
-(def query->reaction (atom {}))
+(defprotocol ICache
+ ;; Using -prefixed methods here because for some reason when removing the dash I get
+ ;;
+ ;; java.lang.ClassFormatError: Duplicate method name&signature in class file re_frame/subs/SubscriptionCache
+ ;;
+ ;; as far as I understand that would happen when you try to implement two identically
+ ;; named functions in one defrecord call but I don't see how I'm doing that here
+ (-clear [this])
+ (-cache-and-return [this query-v dynv r])
+ (-cache-lookup
+ [this query-v]
+ [this query-v dyn-v]))
+
+
+(defrecord SubscriptionCache [state]
+ #?(:cljs IDeref :clj clojure.lang.IDeref)
+ #?(:cljs (-deref [this] (-> this :state deref))
+ :clj (deref [this] (-> this :state deref)))
+ ICache
+ (-clear [this]
+ (doseq [[k rxn] @state]
+ (dispose! rxn))
+ (if (not-empty @state)
+ (console :warn "re-frame: The subscription cache isn't empty after being cleared")))
+ (-cache-and-return [this query-v dynv r]
+ (let [cache-key [query-v dynv]]
+ ;; when this reaction is no longer being used, remove it from the cache
+ (add-on-dispose! r #(trace/with-trace {:operation (first-in-vector query-v)
+ :op-type :sub/dispose
+ :tags {:query-v query-v
+ :reaction (reagent-id r)}}
+ (swap! state
+ (fn [query-cache]
+ (if (and (contains? query-cache cache-key) (identical? r (get query-cache cache-key)))
+ (dissoc query-cache cache-key)
+ query-cache)))))
+ ;; cache this reaction, so it can be used to deduplicate other, later "=" subscriptions
+ (swap! state (fn [query-cache]
+ (when debug-enabled?
+ (when (contains? query-cache cache-key)
+ (console :warn "re-frame: Adding a new subscription to the cache while there is an existing subscription in the cache" cache-key)))
+ (assoc query-cache cache-key r)))
+ (trace/merge-trace! {:tags {:reaction (reagent-id r)}})
+ r)) ;; return the actual reaction
+ (-cache-lookup [this query-v]
+ (-cache-lookup this query-v []))
+ (-cache-lookup [this query-v dyn-v]
+ (get @state [query-v dyn-v])))
(defn clear-subscription-cache!
"calls `on-dispose` for each cached item,
which will cause the value to be removed from the cache"
- []
- (doseq [[k rxn] @query->reaction]
- (dispose! rxn))
- (if (not-empty @query->reaction)
- (console :warn "re-frame: The subscription cache isn't empty after being cleared")))
+ [subs-cache]
+ (-clear subs-cache))
+
(defn clear-all-handlers!
"Unregisters all existing subscription handlers"
- []
- (clear-handlers kind)
- (clear-subscription-cache!))
-
-(defn cache-and-return
- "cache the reaction r"
- [query-v dynv r]
- (let [cache-key [query-v dynv]]
- ;; when this reaction is no longer being used, remove it from the cache
- (add-on-dispose! r #(trace/with-trace {:operation (first-in-vector query-v)
- :op-type :sub/dispose
- :tags {:query-v query-v
- :reaction (reagent-id r)}}
- (swap! query->reaction
- (fn [query-cache]
- (if (and (contains? query-cache cache-key) (identical? r (get query-cache cache-key)))
- (dissoc query-cache cache-key)
- query-cache)))))
- ;; cache this reaction, so it can be used to deduplicate other, later "=" subscriptions
- (swap! query->reaction (fn [query-cache]
- (when debug-enabled?
- (when (contains? query-cache cache-key)
- (console :warn "re-frame: Adding a new subscription to the cache while there is an existing subscription in the cache" cache-key)))
- (assoc query-cache cache-key r)))
- (trace/merge-trace! {:tags {:reaction (reagent-id r)}})
- r)) ;; return the actual reaction
-
-(defn cache-lookup
- ([query-v]
- (cache-lookup query-v []))
- ([query-v dyn-v]
- (get @query->reaction [query-v dyn-v])))
+ [{:keys [registry subs-cache]}]
+ (clear-handlers registry kind)
+ (-clear subs-cache))
;; -- subscribe ---------------------------------------------------------------
@@ -71,38 +86,38 @@
"https://day8.github.io/re-frame/FAQs/UseASubscriptionInAnEventHandler/")))
(defn subscribe
- ([query]
+ ([{:keys [registry app-db subs-cache frame-id] :as frame} query]
(warn-when-not-reactive)
(trace/with-trace {:operation (first-in-vector query)
:op-type :sub/create
:tags {:query-v query}}
- (if-let [cached (cache-lookup query)]
+ (if-let [cached (-cache-lookup subs-cache query)]
(do
(trace/merge-trace! {:tags {:cached? true
:reaction (reagent-id cached)}})
cached)
(let [query-id (first-in-vector query)
- handler-fn (get-handler kind query-id)]
+ handler-fn (get-handler registry kind query-id)]
(trace/merge-trace! {:tags {:cached? false}})
(if (nil? handler-fn)
(do (trace/merge-trace! {:error true})
(console :error (str "re-frame: no subscription handler registered for: " query-id ". Returning a nil subscription.")))
- (cache-and-return query [] (handler-fn app-db query)))))))
+ (-cache-and-return subs-cache query [] (handler-fn frame query)))))))
- ([query dynv]
+ ([{:keys [registry app-db subs-cache frame-id] :as frame} query dynv]
(warn-when-not-reactive)
(trace/with-trace {:operation (first-in-vector query)
:op-type :sub/create
:tags {:query-v query
:dyn-v dynv}}
- (if-let [cached (cache-lookup query dynv)]
+ (if-let [cached (-cache-lookup subs-cache query dynv)]
(do
(trace/merge-trace! {:tags {:cached? true
:reaction (reagent-id cached)}})
cached)
(let [query-id (first-in-vector query)
- handler-fn (get-handler kind query-id)]
+ handler-fn (get-handler registry kind query-id)]
(trace/merge-trace! {:tags {:cached? false}})
(when debug-enabled?
(when-let [not-reactive (not-empty (remove ratom? dynv))]
@@ -111,11 +126,11 @@
(do (trace/merge-trace! {:error true})
(console :error (str "re-frame: no subscription handler registered for: " query-id ". Returning a nil subscription.")))
(let [dyn-vals (make-reaction (fn [] (mapv deref dynv)))
- sub (make-reaction (fn [] (handler-fn app-db query @dyn-vals)))]
+ sub (make-reaction (fn [] (handler-fn frame query @dyn-vals)))]
;; handler-fn returns a reaction which is then wrapped in the sub reaction
;; need to double deref it to get to the actual value.
;(console :log "Subscription created: " v dynv)
- (cache-and-return query dynv (make-reaction (fn [] @@sub))))))))))
+ (-cache-and-return subs-cache query dynv (make-reaction (fn [] @@sub))))))))))
;; -- reg-sub -----------------------------------------------------------------
@@ -155,7 +170,7 @@
(trace/merge-trace! {:tags {:input-signals (doall (to-seq (map-signals reagent-id signals)))}})
dereffed-signals))
-(defn sugar [query-id sub-fn query? & args]
+(defn sugar [{:keys [app-db] :as frame} query-id sub-fn query? & args]
(let [error-header (str "re-frame: reg-sub for " query-id ", ")
[op f :as comp-f] (take-last 2 args)
[input-args ;; may be empty, or one signal fn, or pairs of :<- / vector
@@ -191,15 +206,16 @@
1 (let [f (first input-args)]
(when-not (fn? f)
(console :error error-header "2nd argument expected to be an inputs function, got:" f))
- f)
+ #(binding [*current-frame* frame]
+ (apply f %&)))
;; one sugar pair
2 (let [[marker vec] input-args]
(when-not (= :<- marker)
(console :error error-header "expected :<-, got:" marker))
(fn inp-fn
- ([_] (sub-fn vec))
- ([_ _] (sub-fn vec))))
+ ([_] (sub-fn frame vec))
+ ([_ _] (sub-fn frame vec))))
;; multiple sugar pairs
(let [pairs (partition 2 input-args)
@@ -208,19 +224,21 @@
(when-not (and (every? #{:<-} markers) (every? query? vecs))
(console :error error-header "expected pairs of :<- and vectors, got:" pairs))
(fn inp-fn
- ([_] (map sub-fn vecs))
- ([_ _] (map sub-fn vecs)))))]
+ ([_] (map (partial sub-fn frame) vecs))
+ ([_ _] (map (partial sub-fn frame) vecs)))))]
[inputs-fn computation-fn]))
(defn reg-sub
- [query-id & args]
- (let [[inputs-fn computation-fn] (apply sugar query-id subscribe vector? args)]
+ [{:keys [registry]} query-id & args]
+ (do
(register-handler
+ registry
kind
query-id
(fn subs-handler-fn
- ([db query-vec]
- (let [subscriptions (inputs-fn query-vec nil)
+ ([frame query-vec]
+ (let [[inputs-fn computation-fn] (apply sugar frame query-id subscribe vector? args)
+ subscriptions (inputs-fn query-vec nil)
reaction-id (atom nil)
reaction (make-reaction
(fn []
@@ -228,14 +246,16 @@
:op-type :sub/run
:tags {:query-v query-vec
:reaction @reaction-id}}
- (let [subscription (computation-fn (deref-input-signals subscriptions query-id) query-vec)]
- (trace/merge-trace! {:tags {:value subscription}})
- subscription))))]
+ (binding [*current-frame* frame]
+ (let [subscription (computation-fn (deref-input-signals subscriptions query-id) query-vec)]
+ (trace/merge-trace! {:tags {:value subscription}})
+ subscription)))))]
(reset! reaction-id (reagent-id reaction))
reaction))
- ([db query-vec dyn-vec]
- (let [subscriptions (inputs-fn query-vec dyn-vec)
+ ([frame query-vec dyn-vec]
+ (let [[inputs-fn computation-fn] (apply sugar frame query-id subscribe vector? args)
+ subscriptions (inputs-fn query-vec dyn-vec)
reaction-id (atom nil)
reaction (make-reaction
(fn []
@@ -244,9 +264,10 @@
:tags {:query-v query-vec
:dyn-v dyn-vec
:reaction @reaction-id}}
- (let [subscription (computation-fn (deref-input-signals subscriptions query-id) query-vec dyn-vec)]
- (trace/merge-trace! {:tags {:value subscription}})
- subscription))))]
+ (binding [*current-frame* frame]
+ (let [subscription (computation-fn (deref-input-signals subscriptions query-id) query-vec dyn-vec)]
+ (trace/merge-trace! {:tags {:value subscription}})
+ subscription)))))]
(reset! reaction-id (reagent-id reaction))
reaction))))))
diff --git a/src/re_frame/subs/alpha.cljc b/src/re_frame/subs/alpha.cljc
deleted file mode 100644
index eb19baff..00000000
--- a/src/re_frame/subs/alpha.cljc
+++ /dev/null
@@ -1,111 +0,0 @@
-(ns re-frame.subs.alpha
- (:require
- [re-frame.subs :refer [deref-input-signals sugar warn-when-not-reactive]]
- [re-frame.registrar :refer [register-handler]]
- [re-frame.register.alpha :refer [reg lifecycle->method]]
- [re-frame.interop :refer [add-on-dispose! make-reaction reactive? reagent-id ratom]]
- [re-frame.query.alpha :as q]
- [re-frame :as-alias rf]
- [re-frame.trace :as trace :include-macros true]
- [re-frame.flow.alpha :as flow]))
-
-(defmethod reg :sub-lifecycle [_ k f]
- (swap! lifecycle->method assoc
- k
- (fn [q]
- (trace/with-trace {:operation (q/id q)
- :op-type :sub/create
- :tags {:query q}}
- (f q)))))
-
-(defn sub
- ([q]
- (if (keyword? q)
- (sub q {})
- (let [md (q/method q)]
- (cond (map? q) (md q)
- (vector? q) (md {::rf/q (q/id q)
- ::rf/lifecycle (q/lifecycle q)
- ::rf/query-v q})))))
- ([id q]
- (sub (assoc q ::rf/q id))))
-
-(defmethod reg :sub [kind id & args]
- (let [[inputs-fn computation-fn] (apply sugar id sub q/query? args)]
- (register-handler
- kind
- id
- (fn subs-handler-fn [_ q]
- (let [subscriptions (inputs-fn q nil)
- rid (atom nil)
- r (make-reaction
- #(trace/with-trace {:operation (q/id q)
- :op-type :sub/run
- :tags {:query q
- :reaction @rid}}
- (let [subscription (computation-fn
- (deref-input-signals subscriptions id)
- q)]
- (trace/merge-trace! {:tags {:value subscription}})
- subscription)))]
- (reset! rid (reagent-id r))
- r)))))
-
-(defmethod reg :legacy-sub [_ id & args]
- (let [[inputs-fn computation-fn] (apply sugar id sub q/query? args)]
- (register-handler
- :sub
- id
- (fn subs-handler-fn [_ q]
- (let [subscriptions (inputs-fn q nil)
- rid (atom nil)
- r (make-reaction
- #(trace/with-trace {:operation (q/id q)
- :op-type :sub/run
- :tags {:query q
- :reaction @rid}}
- (let [q (if (map? q)
- (-> (or (::rf/query-v q) [(q/id q)])
- (vary-meta assoc ::rf/lifecycle (q/lifecycle q)))
- q)
- subscription (computation-fn
- (deref-input-signals subscriptions id)
- q)]
- (trace/merge-trace! {:tags {:value subscription}})
- subscription)))]
- (reset! rid (reagent-id r))
- r)))))
-
-(defn sub-reactive [q]
- (warn-when-not-reactive)
- (or (q/cached q)
- (let [md (q/lifecycle q)
- r (q/handle q)]
- (add-on-dispose! r #(q/clear! q md))
- (q/cache! q r))))
-
-(reg :sub-lifecycle :reactive sub-reactive)
-
-(defn sub-safe [q]
- (if (reactive?)
- (sub-reactive q)
- (or (q/cached q)
- (q/handle q))))
-
-(reg :sub-lifecycle :safe sub-safe)
-(reg :sub-lifecycle :default sub-safe)
-
-(defn sub-forever [q]
- (or (q/cached q)
- (q/cache! q (q/handle q))))
-
-(reg :sub-lifecycle :forever sub-forever)
-
-(def nil-ref (ratom nil))
-
-(defn sub-flow [q]
- (or (some-> (:id (or (second (::rf/query-v q)) q))
- flow/lookup meta :re-frame.flow.alpha/ref)
- nil-ref))
-
-(reg :sub-lifecycle :flow sub-flow)
diff --git a/test/re_frame/flow/alpha_test.cljc b/test/re_frame/flow/alpha_test.cljc
deleted file mode 100644
index 6beb83cd..00000000
--- a/test/re_frame/flow/alpha_test.cljc
+++ /dev/null
@@ -1,129 +0,0 @@
-(ns re-frame.flow.alpha-test
- (:require
- [cljs.test :refer [is deftest use-fixtures testing]]
- [re-frame.alpha :as rf]
- [re-frame.flow.alpha :as f]
- [re-frame.db :refer [app-db]]))
-
-(use-fixtures :each (fn [f] (f) (reset! f/flows {})))
-
-(deftest abstractions
- (is (f/db-path? []))
- (is (f/flow? {}))
- (is (f/flow<-? {::f/flow<- :a}))
- (is (f/flow<-? (f/flow<- :a))))
-
-(deftest helpers
- (let [c {:id :c
- :inputs {:db-path [:some :path]
- :flow-a (rf/flow<- :a)}
- :live-inputs {:db-path [:some :path]
- :flow-b (rf/flow<- :b)}
- :path [:x :y :z]}]
- (rf/reg-flow c)
- (rf/reg-flow {:id :a})
- (rf/reg-flow {:id :b})
- (testing "registration"
- (is (= :a (:id (f/lookup :a)))))
- (testing "topological sort"
- (is (= #{:a :b} (set (f/input-ids c))))
- (is (= :c (last (map :id (f/topsort @f/flows))))))
-
- (testing "stale inputs & outputs"
- (is (= {:flow-a [:a]} (f/stale-in-flows {:flow-a {:path [:a]}}
- {:inputs {:a [:a]}})))
-
- (is (= {:flow-a {:b [:b]}} (f/stale-out-flows {:flow-a {:inputs {:b [:b]}}}
- {:path [:b]}))))))
-
-(deftest registry
- (testing "reg-flow"
- (rf/reg-flow {:id :a})
- (is (some? (f/lookup :a)))
- (rf/reg-flow :b {})
- (is (some? (f/lookup :b))))
- (testing "clear-flow"
- (rf/clear-flow :a)
- (is (nil? (f/lookup :a)))
- (rf/reg-flow {:id :c})
- (rf/clear-flow)
- (is (nil? (f/lookup :b)))
- (is (nil? (f/lookup :c)))))
-
-(deftest get-flow
- (rf/reg-flow {:id :x :path [:a :b]})
- (let [db {:a {:b :y}}]
- (is (= :y (rf/get-flow db :x)))))
-
-(deftest run-flow
- (rf/reg-event-db :go-live (fn [db _] (assoc db :l? true)))
- (rf/reg-event-db :go-dead (fn [db _] (dissoc db :l?)))
- (rf/reg-flow {:id :live-db
- :live? :l?})
- (rf/reg-flow {:id :live-path
- :live? :l?
- :live-inputs {:l? [:l?]}})
- (rf/reg-flow {:id :live-input
- :live? :l?
- :live-inputs {:l? (rf/flow<- :live-path)}})
- (testing "basic flow"
- (rf/reg-event-fx :basic-event (fn [_ _] {}))
- (rf/reg-flow {:id :basic-flow})
- (is (not (rf/get-flow @app-db :basic-flow)))
- (rf/dispatch-sync [:basic-event])
- (is (rf/get-flow @app-db :basic-flow)))
- (testing "live flow"
- (is (nil? (rf/get-flow @app-db :live-db)))
- (is (nil? (rf/get-flow @app-db :live-path)))
- (is (nil? (rf/get-flow @app-db :live-input)))
- (rf/dispatch-sync [:go-live])
- (is (rf/get-flow @app-db :live-db))
- (is (rf/get-flow @app-db :live-path))
- (is (rf/get-flow @app-db :live-input))
- (rf/dispatch-sync [:go-dead])
- (is (nil? (rf/get-flow @app-db :live-db)))
- (is (nil? (rf/get-flow @app-db :live-path)))
- (is (nil? (rf/get-flow @app-db :live-input))))
- (testing "flow effects"
- (let [flow-a {:id :fx-flow
- :output (fn [_] :a)}
- flow-b {:id :fx-flow
- :output (constantly :b)}]
- (rf/reg-event-fx :reg-a (fn [_ _] {:fx [[:reg-flow flow-a]]}))
- (rf/reg-event-fx :reg-b (fn [_ _] {:fx [[:reg-flow flow-b]]}))
- (rf/reg-event-fx :clear-fx (fn [_ _] {:fx [[:clear-flow :fx-flow]]}))
- (is (nil? (rf/get-flow @app-db :fx-flow)))
- (rf/dispatch-sync [:reg-a])
- (is (= :a (rf/get-flow @app-db :fx-flow)))
- (rf/dispatch-sync [:reg-b])
- (is (= :b (rf/get-flow @app-db :fx-flow)))
- (rf/dispatch-sync [:clear-fx])
- (is (nil? (rf/get-flow @app-db :fx-flow)))))
- (testing "flow run count"
- (let [ct (atom 0)
- live-ct (atom 0)
- ct-flow {:id :ct-flow
- :live-inputs {:l? [:l?]}
- :live? (fn [{:keys [l?]}] (swap! live-ct inc) l?)
- :output (fn [_] (swap! ct inc))}]
- (rf/reg-flow ct-flow)
- (rf/dispatch-sync [:go-live])
- (is (= 1 @ct))
- (is (= 1 @live-ct))
- (rf/dispatch-sync [:go-dead])
- (is (= 1 @ct))
- (is (= 3 @live-ct))
- (rf/dispatch-sync [:go-live])
- (is (= 2 @ct))
- (is (= 5 @live-ct))
- (rf/clear-flow :ct-flow)
- (rf/dispatch-sync [:go-live])
- (is (= 2 @ct))
- (is (= 6 @live-ct))
- (rf/dispatch-sync [:go-live])
- (is (= 2 @ct))
- (is (= 6 @live-ct))
- (rf/reg-flow ct-flow)
- (rf/dispatch-sync [:go-live])
- (is (= 3 @ct))
- (is (= 7 @live-ct)))))
diff --git a/test/re_frame/interceptor_test.cljs b/test/re_frame/interceptor_test.cljs
index 93582ced..9e8bb606 100644
--- a/test/re_frame/interceptor_test.cljs
+++ b/test/re_frame/interceptor_test.cljs
@@ -1,6 +1,8 @@
(ns re-frame.interceptor-test
(:require [cljs.test :refer-macros [is deftest testing use-fixtures]]
+ [re-frame.frame :as frame]
[reagent.ratom :refer [atom]]
+ [re-frame.frame]
[re-frame.core :refer [reg-global-interceptor clear-global-interceptor]]
[re-frame.interceptor :refer [context get-coeffect assoc-effect assoc-coeffect get-effect
update-effect update-coeffect ->interceptor]]
@@ -12,6 +14,13 @@
(enable-console-print!)
+(def ^:dynamic *frame*)
+
+(defn set-frame
+ [f]
+ (binding [*frame* (frame/make-frame)]
+ (f)))
+
(defn global-interceptor-fixture
[f]
(reg-global-interceptor {:id :interceptor-test
@@ -24,15 +33,15 @@
(defn error-handler-fixture
[f]
- (let [original-handler (registrar/get-handler :error :event-handler)]
+ (let [original-handler (registrar/get-handler (:registry *frame*) :error :event-handler)]
(f)
- (registrar/register-handler :error :event-handler original-handler)))
+ (registrar/register-handler (:registry *frame*) :error :event-handler original-handler)))
-(use-fixtures :once global-interceptor-fixture)
+(use-fixtures :once set-frame global-interceptor-fixture)
(use-fixtures :each error-handler-fixture)
(deftest test-trim-v
- (let [ctx (context [:event-id :b :c] [])
+ (let [ctx (context *frame* [:event-id :b :c] [])
ctx-trimmed ((:before trim-v) ctx)
ctx-untrimmed ((:after trim-v) ctx-trimmed)]
(is (= (get-coeffect ctx-trimmed :event)
@@ -45,7 +54,7 @@
(let [db {:showing true :another 1}
p1 (path [:showing])] ;; a simple one level path
- (let [b4 (-> (context [] [] db)
+ (let [b4 (-> (context *frame* [] [] db)
((:before p1))) ;; before
a (-> b4
(assoc-effect :db false)
@@ -60,7 +69,7 @@
(let [db {:1 {:2 :target}}
p (path [:1 :2])] ;; a two level path
- (let [b4 (-> (context [] [] db)
+ (let [b4 (-> (context *frame* [] [] db)
((:before p)))] ;; before
(is (= (get-coeffect b4 :db)) ;; test before
@@ -82,7 +91,7 @@
(deftest path-with-no-db-returned
(let [path-interceptor (path :a)]
- (-> (context [] [path-interceptor] {:a 1})
+ (-> (context *frame* [] [path-interceptor] {:a 1})
(interceptor/invoke-interceptors :before)
interceptor/change-direction
(interceptor/invoke-interceptors :after)
@@ -91,7 +100,7 @@
(is))))
(deftest test-inject-global-interceptors
- (let [forward (-> (context [] [inject-global-interceptors] {:a 1})
+ (let [forward (-> (context *frame* [] [inject-global-interceptors] {:a 1})
(interceptor/invoke-interceptors :before))
_ (is (= {:direction :before} (get-coeffect forward :global)))
reverse (-> forward
@@ -110,7 +119,7 @@
:new-db-val)
i1 (db-handler->interceptor handler)
- db (-> (context event [] :original-db-val)
+ db (-> (context *frame* event [] :original-db-val)
((:before i1)) ;; calls handler - causing :db in :effects to change
(get-effect :db))]
(is (= db :new-db-val))))
@@ -129,7 +138,7 @@
effect)
i1 (fx-handler->interceptor handler)
- e (-> (context event [] (:db coeffect))
+ e (-> (context *frame* event [] (:db coeffect))
((:before i1)) ;; call the handler
(get-effect))]
(is (= e {:db 5 :dispatch [:a]}))))
@@ -148,18 +157,18 @@
orig-db {:a 0 :b 2}]
(is (= {:a 0 :b 2}
- (-> (context [] [] orig-db)
+ (-> (context *frame* [] [] orig-db)
((:before no-change-handler-i)) ;; no change to :a and :b
((:after change-i))
(get-effect :db))))
(is (= {:a 10 :b 2 :c 12}
- (-> (context [] [] orig-db)
+ (-> (context *frame* [] [] orig-db)
((:before change-handler-i)) ;; cause change to :a
((:after change-i))
(get-effect :db))))
(is (= ::not-found
- (-> (context [] [] orig-db)
+ (-> (context *frame* [] [] orig-db)
((:before no-db-handler-i)) ;; no db effect in context
((:after change-i))
(get-effect :db ::not-found))))))
@@ -167,18 +176,18 @@
(deftest test-after
(testing "when no db effect is returned"
(let [after-db-val (atom nil)]
- (-> (context [:a :b]
- [(after (fn [db] (reset! after-db-val db)))]
- {:a 1})
+ (-> (context *frame* [:a :b]
+ [(after (fn [db] (reset! after-db-val db)))]
+ {:a 1})
(interceptor/invoke-interceptors :before)
interceptor/change-direction
(interceptor/invoke-interceptors :after))
(is (= @after-db-val {:a 1}))))
(testing "when a false db effect is returned"
(let [after-db-val (atom :not-reset)]
- (-> (context [:a :b]
- [(after (fn [db] (reset! after-db-val db)))]
- {:a 2})
+ (-> (context *frame* [:a :b]
+ [(after (fn [db] (reset! after-db-val db)))]
+ {:a 2})
(assoc-effect :db nil)
(interceptor/invoke-interceptors :before)
interceptor/change-direction
@@ -186,9 +195,9 @@
(is (= @after-db-val nil))))
(testing "when a nil db effect is returned"
(let [after-db-val (atom :not-reset)]
- (-> (context [:a :b]
- [(after (fn [db] (reset! after-db-val db)))]
- {:a 3})
+ (-> (context *frame* [:a :b]
+ [(after (fn [db] (reset! after-db-val db)))]
+ {:a 3})
(assoc-effect :db false)
(interceptor/invoke-interceptors :before)
interceptor/change-direction
@@ -198,7 +207,7 @@
(deftest test-enrich
(testing "when no db effect is returned"
(let [db {:a 1}
- ctx (context [] [] db)
+ ctx (context *frame* [] [] db)
enrich-interceptor (enrich (fn [db _] db))]
(is (= ::not-found (get-effect ctx :db ::not-found)))
(is (get-effect ((:after enrich-interceptor) ctx) :db))
@@ -206,7 +215,7 @@
(testing "uses db returned by f"
(let [update-fn (fn [db _] (update db :a inc))
updater (db-handler->interceptor update-fn)
- ctx (context [] [updater] {:a 1})
+ ctx (context *frame* [] [updater] {:a 1})
enrich-interceptor (enrich update-fn)
result (-> ctx
((:before updater))
@@ -214,7 +223,7 @@
(is (= {:a 3} (get-effect result :db)))))
(testing "uses given db if f returns nil"
(let [updater (db-handler->interceptor (fn [db _] (update db :a inc)))
- ctx (context [] [updater] {:a 1})
+ ctx (context *frame* [] [updater] {:a 1})
enrich-interceptor (enrich (fn [_ _] nil))
result (-> ctx
((:before updater))
@@ -274,7 +283,7 @@
(testing "throws via exception->ex-info"
;; actual handler doesn't matter here, we just need a registered handler so invoke-exception
- (registrar/register-handler :error :event-handler identity)
+ (registrar/register-handler (:registry *frame*) :error :event-handler identity)
(try
(let [exception (ex-info "Oopsie" {:foo :bar})
interceptor {:id :throws
@@ -291,7 +300,7 @@
:interceptor :throws}
(ex-data e))))))
(finally
- (registrar/clear-handlers :error))))))
+ (registrar/clear-handlers (:registry *frame*) :error))))))
(deftest test-exceptions
(let [error-atom (atom nil)
@@ -303,20 +312,20 @@
interceptors [throws-before]]
(testing "an exception in an interceptor, without error handler"
- (registrar/register-handler :error :event-handler nil)
- (is (nil? (registrar/get-handler :error :event-handler)))
+ (registrar/register-handler (:registry *frame*) :error :event-handler nil)
+ (is (nil? (registrar/get-handler (:registry *frame*) :error :event-handler)))
(try
- (interceptor/execute [:_] interceptors)
+ (interceptor/execute *frame* [:_] interceptors)
(is false "interceptor should have thrown")
(catch :default e
(is (= "Thrown from interceptor" (ex-message e)))))
(is (nil? @error-atom)))
(testing "an exception in an interceptor, with error handler"
- (registrar/register-handler :error :event-handler error-handler)
+ (registrar/register-handler (:registry *frame*) :error :event-handler error-handler)
(try
- (is (registrar/get-handler :error))
- (interceptor/execute [:_] interceptors)
+ (is (registrar/get-handler (:registry *frame*) :error))
+ (interceptor/execute *frame* [:_] interceptors)
(let [[original-error re-frame-error] @error-atom
{:keys [direction event-v interceptor]} (ex-data re-frame-error)]
(is (= "Interceptor Exception: Thrown from interceptor"
@@ -329,4 +338,4 @@
(is (= [:_] event-v))
(is (= :throws-before interceptor)))
(finally
- (registrar/clear-handlers :error))))))
+ (registrar/clear-handlers (:registry *frame*) :error))))))
diff --git a/test/re_frame/restore_test.cljs b/test/re_frame/restore_test.cljs
index b867acc9..789ac4ec 100644
--- a/test/re_frame/restore_test.cljs
+++ b/test/re_frame/restore_test.cljs
@@ -1,22 +1,39 @@
(ns re-frame.restore-test
(:require [cljs.test :refer-macros [is deftest async use-fixtures testing]]
- [re-frame.core :refer [make-restore-fn reg-sub subscribe]]
+ [re-frame.frame :as frame :refer [make-restore-fn reg-sub subscribe]]
[re-frame.subs :as subs]))
;; TODO: future tests in this area could check DB state and registrations are being correctly restored.
-(use-fixtures :each {:before subs/clear-all-handlers!})
+(def ^:dynamic *frame*)
+
+(defn set-frame
+ [f]
+ (binding [*frame* (frame/make-frame)]
+ (f)))
+
+(use-fixtures :once set-frame)
+
+
+(defn clear-all-handlers!
+ [f]
+ (subs/clear-all-handlers! *frame*)
+ (f))
+
+(use-fixtures :each clear-all-handlers!)
(defn one? [x] (= 1 x))
(defn two? [x] (= 2 x))
(defn register-test-subs []
(reg-sub
+ *frame*
:test-sub
(fn [db ev]
(:test-sub db)))
(reg-sub
+ *frame*
:test-sub2
(fn [db ev]
(:test-sub2 db))))
@@ -24,51 +41,51 @@
(deftest make-restore-fn-test
(testing "no existing subs, then making one subscription"
(register-test-subs)
- (let [original-subs @subs/query->reaction
- restore-fn (make-restore-fn)]
+ (let [original-subs @(:subs-cache *frame*)
+ restore-fn (make-restore-fn *frame*)]
(is (zero? (count original-subs)))
- @(subscribe [:test-sub])
- (is (one? (count @subs/query->reaction)))
- (is (contains? @subs/query->reaction [[:test-sub] []]))
+ @(subscribe *frame* [:test-sub])
+ (is (one? (count @(:subs-cache *frame*))))
+ (is (contains? @(:subs-cache *frame*) [[:test-sub] []]))
(restore-fn)
- (is (zero? (count @subs/query->reaction))))))
+ (is (zero? (count @(:subs-cache *frame*)))))))
(deftest make-restore-fn-test2
(testing "existing subs, making more subscriptions"
(register-test-subs)
- @(subscribe [:test-sub])
- (let [original-subs @subs/query->reaction
- restore-fn (make-restore-fn)]
+ @(subscribe *frame* [:test-sub])
+ (let [original-subs @(:subs-cache *frame*)
+ restore-fn (make-restore-fn *frame*)]
(is (one? (count original-subs)))
- @(subscribe [:test-sub2])
- (is (contains? @subs/query->reaction [[:test-sub2] []]))
- (is (two? (count @subs/query->reaction)))
+ @(subscribe *frame* [:test-sub2])
+ (is (contains? @(:subs-cache *frame*) [[:test-sub2] []]))
+ (is (two? (count @(:subs-cache *frame*))))
(restore-fn)
- (is (not (contains? @subs/query->reaction [[:test-sub2] []])))
- (is (one? (count @subs/query->reaction))))))
+ (is (not (contains? @(:subs-cache *frame*) [[:test-sub2] []])))
+ (is (one? (count @(:subs-cache *frame*)))))))
(deftest make-restore-fn-test3
(testing "existing subs, making more subscriptions with different params on same subscriptions"
(register-test-subs)
- @(subscribe [:test-sub])
- (let [original-subs @subs/query->reaction
- restore-fn (make-restore-fn)]
+ @(subscribe *frame* [:test-sub])
+ (let [original-subs @(:subs-cache *frame*)
+ restore-fn (make-restore-fn *frame*)]
(is (one? (count original-subs)))
- @(subscribe [:test-sub :extra :params])
- (is (two? (count @subs/query->reaction)))
+ @(subscribe *frame* [:test-sub :extra :params])
+ (is (two? (count @(:subs-cache *frame*))))
(restore-fn)
- (is (one? (count @subs/query->reaction))))))
+ (is (one? (count @(:subs-cache *frame*)))))))
(deftest nested-restores
(testing "running nested restores"
(register-test-subs)
- (let [restore-fn-1 (make-restore-fn)
- _ @(subscribe [:test-sub])
- _ (is (one? (count @subs/query->reaction)))
- restore-fn-2 (make-restore-fn)]
- @(subscribe [:test-sub2])
- (is (two? (count @subs/query->reaction)))
+ (let [restore-fn-1 (make-restore-fn *frame*)
+ _ @(subscribe *frame* [:test-sub])
+ _ (is (one? (count @(:subs-cache *frame*))))
+ restore-fn-2 (make-restore-fn *frame*)]
+ @(subscribe *frame* [:test-sub2])
+ (is (two? (count @(:subs-cache *frame*))))
(restore-fn-2)
- (is (one? (count @subs/query->reaction)))
+ (is (one? (count @(:subs-cache *frame*))))
(restore-fn-1)
- (is (zero? (count @subs/query->reaction))))))
+ (is (zero? (count @(:subs-cache *frame*)))))))
diff --git a/test/re_frame/router_test.clj b/test/re_frame/router_test.clj
index 2f5808cb..6537fc04 100644
--- a/test/re_frame/router_test.clj
+++ b/test/re_frame/router_test.clj
@@ -1,37 +1,53 @@
(ns re-frame.router-test
(:require [clojure.test :refer :all]
- [re-frame.core :as rf]
+ [re-frame.frame :as frame]
[re-frame.db :as db]))
+(def ^:dynamic *frame*)
+
+(defn set-frame
+ [f]
+ (binding [*frame* (frame/make-frame)]
+ (f)))
+
+(use-fixtures :once set-frame)
+
(defn fixture-re-frame
[f]
- (let [restore-re-frame (re-frame.core/make-restore-fn)]
+ (let [restore-re-frame (frame/make-restore-fn *frame*)]
(f)
(restore-re-frame)))
(use-fixtures :each fixture-re-frame)
-(rf/reg-event-db
- ::test
- (fn [db [_ i]]
- (update db ::test (fnil conj []) i)))
+(defn register
+ []
+ (frame/reg-event-db
+ *frame*
+ ::test
+ (fn [db [_ i]]
+ (update db ::test (fnil conj []) i)))
+
+ (frame/reg-fx
+ *frame*
+ ::promise
+ (fn [{:keys [p val]}]
+ (deliver p val)))
-(rf/reg-fx
- ::promise
- (fn [{:keys [p val]}]
- (deliver p val)))
+ (frame/reg-event-fx
+ *frame*
+ ::sentinel
+ (fn [cofx [_ p val]]
+ {::promise {:p p :val val}})))
-(rf/reg-event-fx
- ::sentinel
- (fn [cofx [_ p val]]
- {::promise {:p p :val val}}))
+(use-fixtures :once register)
(deftest dispatching-race-condition-469-test
;; Checks for day8/re-frame#469
(let [p (promise)]
(is (nil? (dotimes [i 1000]
- (rf/dispatch [::test i]))))
- (is (nil? (rf/dispatch [::sentinel p ::done])))
+ (frame/dispatch *frame* [::test i]))))
+ (is (nil? (frame/dispatch *frame* [::sentinel p ::done])))
(let [val (deref p 1000 ::timed-out)]
(is (= ::done val)))
(is (= (::test @db/app-db)
diff --git a/test/re_frame/subs/alpha_test.cljs b/test/re_frame/subs/alpha_test.cljs
deleted file mode 100644
index 0aee992f..00000000
--- a/test/re_frame/subs/alpha_test.cljs
+++ /dev/null
@@ -1,148 +0,0 @@
-(ns re-frame.subs.alpha-test
- (:require [cljs.test :as test :refer-macros [is deftest testing]]
- [reagent.ratom :as r :refer-macros [reaction]]
- [re-frame.db :as db]
- [re-frame.interop :refer [reactive?]]
- [re-frame :as-alias rf]
- [re-frame.alpha :refer [reg sub]]
- [re-frame.query.alpha :as q]
- [re-frame.register.alpha :as reg]
- [re-frame.core :refer [reg-sub-raw]]
- [re-frame.subs :refer [clear-all-handlers!]]))
-
-(test/use-fixtures :each {:before #(do (clear-all-handlers!)
- (q/clear!))})
-
-(def queries
- {::map
- {::rf/q ::map}
- ::map-safe
- {::rf/q ::map-safe
- ::rf/lifecycle :safe}
- ::map-reactive
- {::rf/q ::map-reactive
- ::rf/lifecycle :reactive}
- ::map-forever
- {::rf/q ::map-forever
- ::rf/lifecycle :forever}
- ::query-v
- {::rf/q ::query-v
- ::rf/query-v [::query-v 1 2 3]}
- ::vec
- [::vec 1 2 3]
- ::vec-reactive
- ^{::rf/lifecycle :reactive}
- [::vec-reactive 1 2 3]})
-
-(defn report [_db q]
- {:query q
- :lifecycle (q/lifecycle q)
- :query-id (q/id q)
- :method (q/method q)})
-
-(deftest test-sub-method-registration
- (testing "Method registration"
- (reg :sub-lifecycle :test q/handle)
- (is (fn? (get @reg/lifecycle->method :test)))))
-
-(deftest test-query-api
- (testing "Query ID"
- (doseq [[qid q] queries]
- (is (= qid (q/id q)))))
- (testing "Method lookup"
- (doseq [[_ q] queries]
- (is (fn? (q/method q)))))
-
- (testing "Lifecycle"
- (doseq [k [::map ::query-v ::vec]
- :let [q (queries k)]]
- (is (= :default (q/lifecycle q))))
- (doseq [k [::map-reactive ::vec-reactive]
- :let [q (queries k)]]
- (is (= :reactive (q/lifecycle q))))))
-
-(deftest test-subscription
- (testing "Subscription"
- (doseq [[qid q] queries
- :let [_ (reg :sub qid report)
- result @(sub q)]]
- (is (map? (:query result)))
- (is (= (:lifecycle result) (q/lifecycle q)))
- (is (= (:query-id result) qid))
- (is (= (:method result) (q/method q))))))
-
-(deftest test-legacy-subscription
- (testing "Legacy Subscription"
- (doseq [[qid q] queries
- :let [_ (reg :legacy-sub qid report)
- result @(sub q)]]
- (is (vector? (:query result)))
- (is (= (:lifecycle result) (q/lifecycle q)))
- (is (= (:query-id result) qid))
- (is (= (:method result) (q/method q))))))
-
-(deftest test-caching
- (testing "Caching a subscription value"
- (let [test-query {:test-sub "cache"}]
- (q/cache! test-query 123)
- (is (= (q/cached test-query) 123))))
-
- (testing "Clearing cache"
- (q/clear!)
- (is (= @q/cache {}))))
-
-(def side-effect-atom (atom 0))
-
-(defn safe-test [q]
- (q/clear!)
- (reset! side-effect-atom 0)
- (let [test-sub (sub q)]
- (reset! db/app-db {:test true})
- (is (= {:test true} @test-sub))
- (is (= @side-effect-atom 1))
- ;; no caching is done
- (sub q)
- (is (= @side-effect-atom 2))
- (with-redefs [reactive? (constantly true)]
- (sub q)
- (is (= @side-effect-atom 3))
- ;; now the sub is cached
- (sub q)
- (is (= @side-effect-atom 3)))
- ;; cached sub is now available outside reactive context
- (sub q)
- (is (= @side-effect-atom 3))))
-
-(deftest test-subscription-lifecycles
- (reg-sub-raw
- :side-effecting-handler
- (fn side-effect [db _]
- (swap! side-effect-atom inc)
- (reaction @db)))
-
- (testing "Default subscription lifecycle"
- (safe-test {::rf/q :side-effecting-handler}))
-
- (testing "Safe subscription lifecycle"
- (safe-test {::rf/q :side-effecting-handler
- ::rf/lifecycle :safe}))
-
- (q/clear!)
- (reset! side-effect-atom 0)
-
- (testing "Reactive subscription lifecycle"
- (let [q {::rf/q :side-effecting-handler
- ::rf/lifecycle :reactive}]
- (reset! db/app-db {:test true})
- (is (= {:test true} @(sub q)))
- (is (= @side-effect-atom 1))
- ;; sub is cached, even outside a reactive context
- (sub q)
- (is (= @side-effect-atom 1))
- (with-redefs [reactive? (constantly true)]
- (sub q)
- (is (= @side-effect-atom 1))
- (sub q)
- (is (= @side-effect-atom 1)))
- (sub q)
- (is (= @side-effect-atom 1)))))
diff --git a/test/re_frame/subs_test.clj b/test/re_frame/subs_test.clj
index 304267aa..c95b0783 100644
--- a/test/re_frame/subs_test.clj
+++ b/test/re_frame/subs_test.clj
@@ -1,35 +1,46 @@
(ns re-frame.subs-test
(:require [clojure.test :refer :all]
- [re-frame.subs :as subs]
- [re-frame.db :as db]))
+ [re-frame.frame :as frame]))
+
+(def ^:dynamic *frame*)
+
+(defn set-frame
+ [f]
+ (binding [*frame* (frame/make-frame)]
+ (f)))
+
+(use-fixtures :once set-frame)
(defn fixture-re-frame
[f]
- (let [restore-re-frame (re-frame.core/make-restore-fn)]
+ (let [restore-re-frame (frame/make-restore-fn *frame*)]
(f)
(restore-re-frame)))
(use-fixtures :each fixture-re-frame)
(deftest test-reg-sub-clj-repl
- (subs/reg-sub
+ (frame/reg-sub
+ *frame*
:a-sub
(fn [db _] (:a db)))
- (subs/reg-sub
+ (frame/reg-sub
+ *frame*
:b-sub
(fn [db _] (:b db)))
- (subs/reg-sub
+ (frame/reg-sub
+ *frame*
:a-b-sub
(fn [_ _]
- [(subs/subscribe [:a-sub])
- (subs/subscribe [:b-sub])])
+ [(frame/subscribe *frame* [:a-sub])
+ (frame/subscribe *frame* [:b-sub])])
(fn [[a b] _]
{:a a :b b}))
- (let [test-sub (subs/subscribe [:a-b-sub])]
- (reset! db/app-db {:a 1 :b 2})
+ (let [test-sub (frame/subscribe *frame* [:a-b-sub])]
+ (reset! (:app-db *frame*) {:a 1 :b 2})
(is (= {:a 1 :b 2} @test-sub))
- (swap! db/app-db assoc :b 3)
+ (swap! (:app-db *frame*) assoc :b 3)
(is (= {:a 1 :b 3} @test-sub))))
diff --git a/test/re_frame/subs_test.cljs b/test/re_frame/subs_test.cljs
index 8a423a8f..2040d7b5 100644
--- a/test/re_frame/subs_test.cljs
+++ b/test/re_frame/subs_test.cljs
@@ -5,68 +5,78 @@
[re-frame.db :as db]
[re-frame.core :as re-frame]))
-(test/use-fixtures :each {:before (fn [] (subs/clear-all-handlers!))})
+(defn reg-sub-raw-compat
+ [query-id handler-fn]
+ (re-frame/reg-sub-raw
+ query-id
+ (fn
+ ([frame query-v]
+ (handler-fn (:app-db frame) query-v))
+ ([frame query-v dyn-v]
+ (handler-fn (:app-db frame) query-v dyn-v)))))
+
+(test/use-fixtures :each {:before (fn [] (subs/clear-all-handlers! db/default-frame))})
;=====test basic subscriptions ======
(deftest test-reg-sub
- (re-frame/reg-sub-raw
+ (reg-sub-raw-compat
:test-sub
(fn [db [_]] (reaction (deref db))))
- (let [test-sub (subs/subscribe [:test-sub])]
+ (let [test-sub (re-frame/subscribe [:test-sub])]
(is (= @db/app-db @test-sub))
(reset! db/app-db 1)
(is (= 1 @test-sub))))
(deftest test-chained-subs
- (re-frame/reg-sub-raw
+ (reg-sub-raw-compat
:a-sub
(fn [db [_]] (reaction (:a @db))))
- (re-frame/reg-sub-raw
+ (reg-sub-raw-compat
:b-sub
(fn [db [_]] (reaction (:b @db))))
- (re-frame/reg-sub-raw
+ (reg-sub-raw-compat
:a-b-sub
(fn [db [_]]
- (let [a (subs/subscribe [:a-sub])
- b (subs/subscribe [:b-sub])]
+ (let [a (re-frame/subscribe [:a-sub])
+ b (re-frame/subscribe [:b-sub])]
(reaction {:a @a :b @b}))))
- (let [test-sub (subs/subscribe [:a-b-sub])]
+ (let [test-sub (re-frame/subscribe [:a-b-sub])]
(reset! db/app-db {:a 1 :b 2})
(is (= {:a 1 :b 2} @test-sub))
(swap! db/app-db assoc :b 3)
(is (= {:a 1 :b 3} @test-sub))))
(deftest test-sub-parameters
- (re-frame/reg-sub-raw
+ (reg-sub-raw-compat
:test-sub
(fn [db [_ b]] (reaction [(:a @db) b])))
- (let [test-sub (subs/subscribe [:test-sub :c])]
+ (let [test-sub (re-frame/subscribe [:test-sub :c])]
(reset! db/app-db {:a 1 :b 2})
(is (= [1 :c] @test-sub))))
(deftest test-sub-chained-parameters
- (re-frame/reg-sub-raw
+ (reg-sub-raw-compat
:a-sub
(fn [db [_ a]] (reaction [(:a @db) a])))
- (re-frame/reg-sub-raw
+ (reg-sub-raw-compat
:b-sub
(fn [db [_ b]] (reaction [(:b @db) b])))
- (re-frame/reg-sub-raw
+ (reg-sub-raw-compat
:a-b-sub
(fn [db [_ c]]
- (let [a (subs/subscribe [:a-sub c])
- b (subs/subscribe [:b-sub c])]
+ (let [a (re-frame/subscribe [:a-sub c])
+ b (re-frame/subscribe [:b-sub c])]
(reaction {:a @a :b @b}))))
- (let [test-sub (subs/subscribe [:a-b-sub :c])]
+ (let [test-sub (re-frame/subscribe [:a-b-sub :c])]
(reset! db/app-db {:a 1 :b 2})
(is (= {:a [1 :c], :b [2 :c]} @test-sub))))
@@ -79,22 +89,22 @@
(deftest test-cached-subscriptions
(reset! side-effect-atom 0)
- (re-frame/reg-sub-raw
+ (reg-sub-raw-compat
:side-effecting-handler
(fn side-effect
[db [_] [_]]
(swap! side-effect-atom inc)
(reaction @db)))
- (let [test-sub (subs/subscribe [:side-effecting-handler])]
+ (let [test-sub (re-frame/subscribe [:side-effecting-handler])]
(reset! db/app-db :test)
(is (= :test @test-sub))
(is (= @side-effect-atom 1))
- (subs/subscribe [:side-effecting-handler]) ;; this should be handled by cache
+ (re-frame/subscribe [:side-effecting-handler]) ;; this should be handled by cache
(is (= @side-effect-atom 1))
- (subs/subscribe [:side-effecting-handler :a]) ;; should fire again because of the param
+ (re-frame/subscribe [:side-effecting-handler :a]) ;; should fire again because of the param
(is (= @side-effect-atom 2))
- (subs/subscribe [:side-effecting-handler :a]) ;; this should be handled by cache
+ (re-frame/subscribe [:side-effecting-handler :a]) ;; this should be handled by cache
(is (= @side-effect-atom 2))))
;============== test clear-subscription-cache! ================
@@ -105,198 +115,198 @@
(fn clear-subs-cache [db _] 1))
(testing "cold cache"
- (is (nil? (subs/cache-lookup [:clear-subscription-cache!]))))
+ (is (nil? (subs/-cache-lookup (:subs-cache db/default-frame) [:clear-subscription-cache!]))))
(testing "cache miss"
- (is (= 1 @(subs/subscribe [:clear-subscription-cache!])))
- (is (some? (subs/cache-lookup [:clear-subscription-cache!]))))
+ (is (= 1 @(re-frame/subscribe [:clear-subscription-cache!])))
+ (is (some? (subs/-cache-lookup (:subs-cache db/default-frame) [:clear-subscription-cache!]))))
(testing "clearing"
- (subs/clear-subscription-cache!)
- (is (nil? (subs/cache-lookup [:clear-subscription-cache!])))))
+ (re-frame/clear-subscription-cache!)
+ (is (nil? (subs/-cache-lookup (:subs-cache db/default-frame) [:clear-subscription-cache!])))))
;============== test register-pure macros ================
(deftest test-reg-sub-macro
- (subs/reg-sub
+ (re-frame/reg-sub
:test-sub
(fn [db [_]] db))
- (let [test-sub (subs/subscribe [:test-sub])]
+ (let [test-sub (re-frame/subscribe [:test-sub])]
(is (= @db/app-db @test-sub))
(reset! db/app-db 1)
(is (= 1 @test-sub))))
(deftest test-reg-sub-macro-singleton
- (subs/reg-sub
+ (re-frame/reg-sub
:a-sub
(fn [db [_]] (:a db)))
- (subs/reg-sub
+ (re-frame/reg-sub
:a-b-sub
(fn [_ _ _]
- (subs/subscribe [:a-sub]))
+ (re-frame/subscribe [:a-sub]))
(fn [a [_]]
{:a a}))
- (let [test-sub (subs/subscribe [:a-b-sub])]
+ (let [test-sub (re-frame/subscribe [:a-b-sub])]
(reset! db/app-db {:a 1 :b 2})
(is (= {:a 1} @test-sub))
(swap! db/app-db assoc :b 3)
(is (= {:a 1} @test-sub))))
(deftest test-reg-sub-macro-vector
- (subs/reg-sub
+ (re-frame/reg-sub
:a-sub
(fn [db [_]] (:a db)))
- (subs/reg-sub
+ (re-frame/reg-sub
:b-sub
(fn [db [_]] (:b db)))
- (subs/reg-sub
+ (re-frame/reg-sub
:a-b-sub
(fn [_ _ _]
- [(subs/subscribe [:a-sub])
- (subs/subscribe [:b-sub])])
+ [(re-frame/subscribe [:a-sub])
+ (re-frame/subscribe [:b-sub])])
(fn [[a b] [_]]
{:a a :b b}))
- (let [test-sub (subs/subscribe [:a-b-sub])]
+ (let [test-sub (re-frame/subscribe [:a-b-sub])]
(reset! db/app-db {:a 1 :b 2})
(is (= {:a 1 :b 2} @test-sub))
(swap! db/app-db assoc :b 3)
(is (= {:a 1 :b 3} @test-sub))))
(deftest test-reg-sub-macro-map
- (subs/reg-sub
+ (re-frame/reg-sub
:a-sub
(fn [db [_]] (:a db)))
- (subs/reg-sub
+ (re-frame/reg-sub
:b-sub
(fn [db [_]] (:b db)))
- (subs/reg-sub
+ (re-frame/reg-sub
:a-b-sub
(fn [_ _ _]
- {:a (subs/subscribe [:a-sub])
- :b (subs/subscribe [:b-sub])})
+ {:a (re-frame/subscribe [:a-sub])
+ :b (re-frame/subscribe [:b-sub])})
(fn [{:keys [a b]} [_]]
{:a a :b b}))
- (let [test-sub (subs/subscribe [:a-b-sub])]
+ (let [test-sub (re-frame/subscribe [:a-b-sub])]
(reset! db/app-db {:a 1 :b 2})
(is (= {:a 1 :b 2} @test-sub))
(swap! db/app-db assoc :b 3)
(is (= {:a 1 :b 3} @test-sub))))
(deftest test-sub-macro-parameters
- (subs/reg-sub
+ (re-frame/reg-sub
:test-sub
(fn [db [_ b]] [(:a db) b]))
- (let [test-sub (subs/subscribe [:test-sub :c])]
+ (let [test-sub (re-frame/subscribe [:test-sub :c])]
(reset! db/app-db {:a 1 :b 2})
(is (= [1 :c] @test-sub))))
(deftest test-sub-macros-chained-parameters
- (subs/reg-sub
+ (re-frame/reg-sub
:a-sub
(fn [db [_ a]] [(:a db) a]))
- (subs/reg-sub
+ (re-frame/reg-sub
:b-sub
(fn [db [_ b]] [(:b db) b]))
- (subs/reg-sub
+ (re-frame/reg-sub
:a-b-sub
(fn [[_ c] _]
- [(subs/subscribe [:a-sub c])
- (subs/subscribe [:b-sub c])])
+ [(re-frame/subscribe [:a-sub c])
+ (re-frame/subscribe [:b-sub c])])
(fn [[a b] [_ c]] {:a a :b b}))
- (let [test-sub (subs/subscribe [:a-b-sub :c])]
+ (let [test-sub (re-frame/subscribe [:a-b-sub :c])]
(reset! db/app-db {:a 1 :b 2})
(is (= {:a [1 :c] :b [2 :c]} @test-sub))))
(deftest test-sub-macros-<-
"test the syntactical sugar"
- (subs/reg-sub
+ (re-frame/reg-sub
:a-sub
(fn [db [_]] (:a db)))
- (subs/reg-sub
+ (re-frame/reg-sub
:a-b-sub
:<- [:a-sub]
(fn [a [_]] {:a a}))
- (let [test-sub (subs/subscribe [:a-b-sub])]
+ (let [test-sub (re-frame/subscribe [:a-b-sub])]
(reset! db/app-db {:a 1 :b 2})
(is (= {:a 1} @test-sub))))
(deftest test-sub-macros-chained-parameters-<-
"test the syntactical sugar"
- (subs/reg-sub
+ (re-frame/reg-sub
:a-sub
(fn [db [_]] (:a db)))
- (subs/reg-sub
+ (re-frame/reg-sub
:b-sub
(fn [db [_]] (:b db)))
- (subs/reg-sub
+ (re-frame/reg-sub
:a-b-sub
:<- [:a-sub]
:<- [:b-sub]
(fn [[a b] [_ c]] {:a a :b b}))
- (let [test-sub (subs/subscribe [:a-b-sub :c])]
+ (let [test-sub (re-frame/subscribe [:a-b-sub :c])]
(reset! db/app-db {:a 1 :b 2})
(is (= {:a 1 :b 2} @test-sub))))
(deftest test-sub-macros-->
"test the syntactical sugar for input signal"
- (subs/reg-sub
+ (re-frame/reg-sub
:a-sub
:-> :a)
- (subs/reg-sub
+ (re-frame/reg-sub
:b-sub
:-> :b)
- (subs/reg-sub
+ (re-frame/reg-sub
:c-sub
:-> :c)
- (subs/reg-sub
+ (re-frame/reg-sub
:d-sub
:-> :d)
- (subs/reg-sub
+ (re-frame/reg-sub
:d-first-sub
:<- [:d-sub]
:-> first)
;; variant of :d-first-sub without an input parameter
- (subs/reg-sub
+ (re-frame/reg-sub
:e-first-sub
:-> (comp first :e))
;; test for equality
- (subs/reg-sub
+ (re-frame/reg-sub
:c-foo?-sub
:<- [:c-sub]
:-> #{:foo})
- (subs/reg-sub
+ (re-frame/reg-sub
:a-b-sub
:<- [:a-sub]
:<- [:b-sub]
:-> (partial zipmap [:a :b]))
- (let [test-sub (subs/subscribe [:a-b-sub])
- test-sub-c (subs/subscribe [:c-foo?-sub])
- test-sub-d (subs/subscribe [:d-first-sub])
- test-sub-e (subs/subscribe [:e-first-sub])]
+ (let [test-sub (re-frame/subscribe [:a-b-sub])
+ test-sub-c (re-frame/subscribe [:c-foo?-sub])
+ test-sub-d (re-frame/subscribe [:d-first-sub])
+ test-sub-e (re-frame/subscribe [:e-first-sub])]
(is (= nil @test-sub-c))
(reset! db/app-db {:a 1 :b 2 :c :foo :d [1 2] :e [3 4]})
(is (= {:a 1 :b 2} @test-sub))
@@ -306,55 +316,55 @@
(deftest test-sub-macros-=>
"test the syntactical sugar for input signals and query vector arguments"
- (subs/reg-sub
+ (re-frame/reg-sub
:a-sub
:-> :a)
- (subs/reg-sub
+ (re-frame/reg-sub
:b-sub
:-> :b)
- (subs/reg-sub
+ (re-frame/reg-sub
:test-a-sub
:<- [:a-sub]
:=> vector)
;; test for equality of input signal and query parameter
- (subs/reg-sub
+ (re-frame/reg-sub
:test-b-sub
:<- [:b-sub]
:=> =)
- (let [test-a-sub (subs/subscribe [:test-a-sub :c])
- test-b-sub (subs/subscribe [:test-b-sub 2])]
+ (let [test-a-sub (re-frame/subscribe [:test-a-sub :c])
+ test-b-sub (re-frame/subscribe [:test-b-sub 2])]
(reset! db/app-db {:a 1 :b 2})
(is (= [1 :c] @test-a-sub))
(is (= true @test-b-sub))))
(deftest test-registering-subs-doesnt-create-subscription
(let [sub-called? (atom false)]
- (with-redefs [subs/subscribe (fn [& args] (reset! sub-called? true))]
- (subs/reg-sub
+ (with-redefs [re-frame/subscribe (fn [& args] (reset! sub-called? true))]
+ (re-frame/reg-sub
:a-sub
(fn [db [_]] (:a db)))
- (subs/reg-sub
+ (re-frame/reg-sub
:b-sub
(fn [db [_]] (:b db)))
- (subs/reg-sub
+ (re-frame/reg-sub
:fn-sub
(fn [[_ c] _]
- [(subs/subscribe [:a-sub c])
- (subs/subscribe [:b-sub c])])
+ [(re-frame/subscribe [:a-sub c])
+ (re-frame/subscribe [:b-sub c])])
(fn [db [_]] (:b db)))
- (subs/reg-sub
+ (re-frame/reg-sub
:a-sugar-sub
:<- [:a-sub]
(fn [[a] [_ c]] {:a a}))
- (subs/reg-sub
+ (re-frame/reg-sub
:a-b-sub
:<- [:a-sub]
:<- [:b-sub]
@@ -365,14 +375,14 @@
;; Dynamic subscriptions
(deftest test-dynamic-subscriptions
- (subs/reg-sub
+ (re-frame/reg-sub
:dyn-sub
(fn [db ev dynv]
(first dynv)))
(testing "happy case"
- (is (= 1 @(subs/subscribe [:dyn-sub] [(r/atom 1)]))))
+ (is (= 1 @(re-frame/subscribe [:dyn-sub] [(r/atom 1)]))))
(testing "subscription that doesn't exist"
- (is (nil? (subs/subscribe [:non-existent] [(r/atom 1)]))))
+ (is (nil? (re-frame/subscribe [:non-existent] [(r/atom 1)]))))
(testing "Passing a non-reactive value to a dynamic subscription"
- (is (thrown-with-msg? js/Error #"No protocol method IDeref" @(subs/subscribe [:dyn-sub] [1])))))
+ (is (thrown-with-msg? js/Error #"No protocol method IDeref" @(re-frame/subscribe [:dyn-sub] [1])))))