2016-03-01 20:18:42 +02:00
|
|
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
;;
|
|
|
|
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
|
|
|
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
|
|
|
|
2015-06-18 19:35:50 +02:00
|
|
|
(ns uxbox.rstore
|
|
|
|
"Reactive storage management architecture helpers."
|
2015-12-14 14:17:18 +02:00
|
|
|
(:require [beicon.core :as rx]))
|
2015-06-18 19:35:50 +02:00
|
|
|
|
|
|
|
;; An abstraction for implement a simple state
|
|
|
|
;; transition. The `-apply-update` function receives
|
|
|
|
;; the state and shoudl return the transformed state.
|
|
|
|
|
|
|
|
(defprotocol UpdateEvent
|
|
|
|
(-apply-update [event state]))
|
|
|
|
|
|
|
|
;; An abstraction for perform some async stuff such
|
|
|
|
;; as communicate with api rest or other resources
|
|
|
|
;; that implies asynchronous access.
|
|
|
|
;; The `-apply-watch` receives the state and should
|
|
|
|
;; return a reactive stream of events (that can be
|
|
|
|
;; of `UpdateEvent`, `WatchEvent` or `EffectEvent`.
|
|
|
|
|
|
|
|
(defprotocol WatchEvent
|
2016-03-19 12:48:39 +02:00
|
|
|
(-apply-watch [event state s]))
|
2015-06-18 19:35:50 +02:00
|
|
|
|
|
|
|
;; An abstraction for perform just side effects. It
|
|
|
|
;; receives state and its return value is completly
|
|
|
|
;; ignored.
|
|
|
|
|
|
|
|
(defprotocol EffectEvent
|
|
|
|
(-apply-effect [event state]))
|
|
|
|
|
|
|
|
(defn update?
|
|
|
|
"Return `true` when `e` satisfies
|
|
|
|
the UpdateEvent protocol."
|
|
|
|
[e]
|
|
|
|
(satisfies? UpdateEvent e))
|
|
|
|
|
|
|
|
(defn watch?
|
|
|
|
"Return `true` when `e` satisfies
|
|
|
|
the WatchEvent protocol."
|
|
|
|
[e]
|
|
|
|
(satisfies? WatchEvent e))
|
|
|
|
|
|
|
|
(defn effect?
|
|
|
|
"Return `true` when `e` satisfies
|
|
|
|
the EffectEvent protocol."
|
|
|
|
[e]
|
|
|
|
(satisfies? EffectEvent e))
|
|
|
|
|
2016-02-24 17:19:40 +02:00
|
|
|
(extend-protocol UpdateEvent
|
|
|
|
function
|
|
|
|
(-apply-update [func state]
|
|
|
|
(func state)))
|
|
|
|
|
2016-03-19 13:50:05 +02:00
|
|
|
(defonce ^:private bus (rx/bus))
|
|
|
|
(defonce stream (rx/map identity bus))
|
2015-06-18 19:35:50 +02:00
|
|
|
|
|
|
|
(defn emit!
|
|
|
|
"Emits an event or a collection of them.
|
|
|
|
The order of events does not matters."
|
|
|
|
([event]
|
|
|
|
(rx/push! bus event))
|
|
|
|
([event & events]
|
2016-03-19 13:50:05 +02:00
|
|
|
(run! emit! (cons event events))))
|
|
|
|
|
|
|
|
(defrecord SwapState [f]
|
|
|
|
UpdateEvent
|
|
|
|
(-apply-update [_ state]
|
|
|
|
(f state)))
|
2015-06-18 19:35:50 +02:00
|
|
|
|
2016-03-15 20:52:29 +02:00
|
|
|
(defn swap
|
2015-06-18 19:35:50 +02:00
|
|
|
"A helper for just apply some function to state
|
|
|
|
without a need to declare additional event."
|
|
|
|
[f]
|
2016-03-19 13:50:05 +02:00
|
|
|
(->SwapState f))
|
2015-06-18 19:35:50 +02:00
|
|
|
|
2016-03-15 20:52:29 +02:00
|
|
|
(defn reset
|
2015-06-18 19:35:50 +02:00
|
|
|
"A event that resets the internal state with
|
|
|
|
the provided value."
|
2016-03-19 13:50:05 +02:00
|
|
|
[v]
|
|
|
|
(->SwapState (fn [_] v)))
|
2015-06-18 19:35:50 +02:00
|
|
|
|
2016-03-15 20:52:29 +02:00
|
|
|
(enable-console-print!)
|
|
|
|
|
2016-03-19 14:53:51 +02:00
|
|
|
(defn- on-error
|
|
|
|
"A default error handler."
|
|
|
|
[e]
|
|
|
|
(println "Unexpected error: " e)
|
2016-03-31 21:46:59 +03:00
|
|
|
(js/console.error e.stack)
|
2016-03-19 15:02:46 +02:00
|
|
|
(rx/throw e))
|
2016-03-19 14:53:51 +02:00
|
|
|
|
2015-06-18 19:35:50 +02:00
|
|
|
(defn init
|
|
|
|
"Initializes the stream event loop and
|
|
|
|
return a stream with model changes."
|
|
|
|
[state]
|
2016-03-19 13:50:05 +02:00
|
|
|
(let [watch-s (rx/filter watch? stream)
|
2016-03-19 12:48:39 +02:00
|
|
|
effect-s (rx/filter effect? stream)
|
|
|
|
update-s (rx/filter update? stream)
|
2015-06-18 19:35:50 +02:00
|
|
|
state-s (->> update-s
|
|
|
|
(rx/scan #(-apply-update %2 %1) state)
|
2016-03-19 14:53:51 +02:00
|
|
|
(rx/catch on-error)
|
2016-03-19 15:02:46 +02:00
|
|
|
(rx/retry 1024)
|
2015-06-18 19:35:50 +02:00
|
|
|
(rx/share))]
|
|
|
|
|
|
|
|
;; Process event sources: combine with the latest model and the result will be
|
|
|
|
;; pushed to the event-stream bus
|
2016-02-24 17:20:58 +02:00
|
|
|
(as-> watch-s $
|
|
|
|
(rx/with-latest-from vector state-s $)
|
2016-03-19 12:48:39 +02:00
|
|
|
(rx/flat-map (fn [[event model]] (-apply-watch event model stream)) $)
|
2016-03-19 14:53:51 +02:00
|
|
|
(rx/catch on-error $)
|
2016-03-19 15:02:46 +02:00
|
|
|
(rx/retry 1024 $)
|
2015-06-18 19:35:50 +02:00
|
|
|
(rx/on-value $ emit!))
|
|
|
|
|
2016-02-24 17:20:58 +02:00
|
|
|
;; Process effects: combine with the latest model to process the new effect
|
|
|
|
(as-> effect-s $
|
|
|
|
(rx/with-latest-from vector state-s $)
|
|
|
|
(rx/subscribe $ (fn [[event model]] (-apply-effect event model))))
|
|
|
|
|
2015-06-18 19:35:50 +02:00
|
|
|
;; Initialize the stream machinary with initial state.
|
2016-03-15 20:52:29 +02:00
|
|
|
(emit! (swap #(merge % state)))
|
2015-06-18 19:35:50 +02:00
|
|
|
state-s))
|