Skip to content

Commit

Permalink
Named instances (#30)
Browse files Browse the repository at this point in the history
Making instance-name an optional parameter to all events/functions
  • Loading branch information
oliyh authored Nov 9, 2018
1 parent e83839c commit 78ac8b5
Show file tree
Hide file tree
Showing 5 changed files with 739 additions and 444 deletions.
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Features include:
* Works with Apollo-compatible servers like lacinia-pedestal
* Queues websocket messages until ready
* Websocket reconnects on disconnect
* Simultaneous connection to multiple GraphQL services

## Usage

Expand Down Expand Up @@ -99,6 +100,45 @@ Options can be passed to the init event, with the following possibilities:
}])
```

### Multiple instances

re-graph now supports multiple instances, allowing you to connect to multiple GraphQL services at the same time.
All function/event signatures now take an optional instance-name as the first argument to let you address them separately:

```clojure
(require [re-graph.core :as re-graph])

;; initialise re-graph for service A
(re-graph/init :service-a {:ws-url "wss://a.com/graphql-ws})
;; initialise re-graph for service B
(re-graph/init :service-b {:ws-url "wss://b.net/api/graphql-ws})

(defn on-a-thing [{:keys [data errors] :as payload}]
;; do things with data from service A
))

;; subscribe to service A, events will be sent to the on-a-thing callback
(re-graph/subscribe :service-a ;; the instance-name you want to talk to
:my-subscription-id ;; this id should uniquely identify this subscription for this service
"{ things { a } }"
on-a-thing)

(defn on-b-thing [{:keys [data errors] :as payload}]
;; do things with data from service B
))

;; subscribe to service B, events will be sent to the on-b-thing callback
(re-graph/subscribe :service-b ;; the instance-name you want to talk to
:my-subscription-id
"{ things { b } }"
on-b-thing)

;; stop the subscriptions
(re-graph/unsubscribe :service-a :my-subscription-id)
(re-graph/unsubscribe :service-b :my-subscription-id)
```

## Development

```clojure
Expand Down
6 changes: 3 additions & 3 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
["change" "version" "leiningen.release/bump-version"]
["vcs" "commit"]
["vcs" "push"]]
:dependencies [[re-frame "0.10.5"]
:dependencies [[re-frame "0.10.6"]
[cljs-http "0.1.45"]]
:plugins [[lein-cljsbuild "1.1.7"]
[lein-doo "0.1.8"]
[lein-doo "0.1.10"]
[lein-figwheel "0.5.14"]]
:profiles {:provided {:dependencies [[org.clojure/clojure "1.9.0"]
[org.clojure/clojurescript "1.10.238"]]}
[org.clojure/clojurescript "1.10.439"]]}
:dev {:source-paths ["dev"]
:resource-paths ["dev-resources"]
:exclusions [[org.clojure/tools.reader]]
Expand Down
149 changes: 84 additions & 65 deletions src/re_graph/core.cljs
Original file line number Diff line number Diff line change
@@ -1,144 +1,163 @@
(ns re-graph.core
(:require [re-frame.core :as re-frame]
[re-graph.internals :as internals]
[re-graph.internals :as internals :refer [interceptors default-instance-name]]
[re-frame.std-interceptors :as rfi]
[clojure.string :as string]))

(re-frame/reg-event-fx
::mutate
(fn [{:keys [db]} [_ query variables callback-event :as event]]
interceptors
(fn [{:keys [db dispatchable-event]} [query variables callback-event :as event]]
(let [query (str "mutation " (string/replace query #"^mutation\s?" ""))]
(cond
(get-in db [:re-graph :websocket :ready?])
(get-in db [:websocket :ready?])
(let [query-id (internals/generate-query-id)]
{:db (assoc-in db [:re-graph :subscriptions query-id] {:callback callback-event})
::internals/send-ws [(get-in db [:re-graph :websocket :connection])
{:db (assoc-in db [:subscriptions query-id] {:callback callback-event})
::internals/send-ws [(get-in db [:websocket :connection])
{:id query-id
:type "start"
:payload {:query query
:variables variables}}]})

(get-in db [:re-graph :websocket])
{:db (update-in db [:re-graph :websocket :queue] conj event)}
(:websocket db)
{:db (update-in db [:websocket :queue] conj dispatchable-event)}

:else
{::internals/send-http [(get-in db [:re-graph :http-url])
{:request (get-in db [:re-graph :http-parameters])
{::internals/send-http [(:http-url db)
{:request (:http-parameters db)
:payload {:query query
:variables variables}}
(fn [payload]
(re-frame/dispatch (conj callback-event payload)))]}))))

(defn mutate [query variables callback-fn]
(re-frame/dispatch [::mutate query variables [::internals/callback callback-fn]]))
(defn mutate
([query variables callback-fn] (query default-instance-name variables callback-fn))
([instance-name query variables callback-fn]
(re-frame/dispatch [::mutate query variables [::internals/callback callback-fn]])))

(re-frame/reg-event-fx
::query
(fn [{:keys [db]} [_ query variables callback-event :as event]]
interceptors
(fn [{:keys [db]} [query variables callback-event :as event]]
(let [query (str "query " (string/replace query #"^query\s?" ""))]
(cond
(get-in db [:re-graph :websocket :ready?])
(get-in db [:websocket :ready?])
(let [query-id (internals/generate-query-id)]
{:db (assoc-in db [:re-graph :subscriptions query-id] {:callback callback-event})
::internals/send-ws [(get-in db [:re-graph :websocket :connection])
{:db (assoc-in db [:subscriptions query-id] {:callback callback-event})
::internals/send-ws [(get-in db [:websocket :connection])
{:id query-id
:type "start"
:payload {:query query
:variables variables}}]})

(get-in db [:re-graph :websocket])
{:db (update-in db [:re-graph :websocket :queue] conj event)}
(get-in db [:websocket])
{:db (update-in db [:websocket :queue] conj event)}

:else
{::internals/send-http [(get-in db [:re-graph :http-url])
{:request (get-in db [:re-graph :http-parameters])
{::internals/send-http [(:http-url db)
{:request (:http-parameters db)
:payload {:query query
:variables variables}}
(fn [payload]
(re-frame/dispatch (conj callback-event payload)))]}))))

(defn query [query variables callback-fn]
(re-frame/dispatch [::query query variables [::internals/callback callback-fn]]))
(defn query
([query variables callback-fn] (query default-instance-name query variables callback-fn))
([instance-name query variables callback-fn]
(re-frame/dispatch [::query query variables [::internals/callback callback-fn]])))

(re-frame/reg-event-fx
::subscribe
(fn [{:keys [db]} [_ subscription-id query variables callback-event :as event]]
interceptors
(fn [{:keys [db instance-name dispatchable-event] :as cofx} [subscription-id query variables callback-event :as event]]
(cond
(get-in db [:re-graph :subscriptions (name subscription-id) :active?])
(get-in db [:subscriptions (name subscription-id) :active?])
{} ;; duplicate subscription

(get-in db [:re-graph :websocket :ready?])
{:db (assoc-in db [:re-graph :subscriptions (name subscription-id)] {:callback callback-event
:event event
:active? true})
::internals/send-ws [(get-in db [:re-graph :websocket :connection])
(get-in db [:websocket :ready?])
{:db (assoc-in db [:subscriptions (name subscription-id)] {:callback callback-event
:event dispatchable-event
:active? true})
::internals/send-ws [(get-in db [:websocket :connection])
{:id (name subscription-id)
:type "start"
:payload {:query (str "subscription " (string/replace query #"^subscription\s?" ""))
:variables variables}}]}

(get-in db [:re-graph :websocket])
{:db (update-in db [:re-graph :websocket :queue] conj event)}
(:websocket db)
{:db (update-in db [:websocket :queue] conj dispatchable-event)}

:else
(js/console.error "Websocket is not enabled, subscriptions are not possible. Please check your re-graph configuration"))))

(defn subscribe [subscription-id query variables callback-fn]
(re-frame/dispatch [::subscribe subscription-id query variables [::internals/callback callback-fn]]))
(defn subscribe
([subscription-id query variables callback-fn] (subscribe default-instance-name subscription-id query variables callback-fn))
([instance-name subscription-id query variables callback-fn]
(re-frame/dispatch [::subscribe instance-name subscription-id query variables [::internals/callback callback-fn]])))

(re-frame/reg-event-fx
::unsubscribe
(fn [{:keys [db]} [_ subscription-id]]
(if (get-in db [:re-graph :websocket :ready?])
{:db (update-in db [:re-graph :subscriptions] dissoc (name subscription-id))
::internals/send-ws [(get-in db [:re-graph :websocket :connection])
interceptors
(fn [{:keys [db instance-name]} [subscription-id :as event]]
(if (get-in db [:websocket :ready?])
{:db (update db :subscriptions dissoc (name subscription-id))
::internals/send-ws [(get-in db [:websocket :connection])
{:id (name subscription-id)
:type "stop"}]}

{:db (update-in db [:re-graph :websocket :queue] conj [::unsubscribe subscription-id])})))
{:db (update-in db [:websocket :queue] conj [::unsubscribe instance-name subscription-id])})))

(defn unsubscribe [subscription-id]
(re-frame/dispatch [::unsubscribe subscription-id]))
(defn unsubscribe
([subscription-id] (unsubscribe default-instance-name subscription-id))
([instance-name subscription-id]
(re-frame/dispatch [::unsubscribe instance-name subscription-id])))

(re-frame/reg-event-fx
::init
(fn [{:keys [db]} [_ {:keys [ws-url http-url http-parameters ws-reconnect-timeout resume-subscriptions? connection-init-payload]
(fn [{:keys [db]} [_ instance-name {:keys [ws-url http-url http-parameters ws-reconnect-timeout resume-subscriptions? connection-init-payload]
:or {ws-url (internals/default-ws-url)
http-parameters {}
http-url "/graphql"
ws-reconnect-timeout 5000
connection-init-payload {}
resume-subscriptions? true}}]]

(merge
{:db (assoc db :re-graph (merge
(when ws-url
{:websocket {:url ws-url
:ready? false
:connection-init-payload connection-init-payload
:queue []
:reconnect-timeout ws-reconnect-timeout
:resume-subscriptions? resume-subscriptions?}})
(when http-url
{:http-url http-url
:http-parameters http-parameters})))}
(when ws-url
{::internals/connect-ws [ws-url]}))))
(let [instance-name (or instance-name default-instance-name)]
(merge
{:db (assoc-in db [:re-graph instance-name]
(merge
(when ws-url
{:websocket {:url ws-url
:ready? false
:connection-init-payload connection-init-payload
:queue []
:reconnect-timeout ws-reconnect-timeout
:resume-subscriptions? resume-subscriptions?}})
(when http-url
{:http-url http-url
:http-parameters http-parameters})))}
(when ws-url
{::internals/connect-ws [instance-name ws-url]})))))

(re-frame/reg-event-fx
::destroy
(fn [{:keys [db]}]
(if-let [subscription-ids (not-empty (-> db :re-graph :subscriptions keys))]
interceptors
(fn [{:keys [db instance-name]}]
(if-let [subscription-ids (not-empty (-> db :subscriptions keys))]
{:dispatch-n (for [subscription-id subscription-ids]
[::unsubscribe subscription-id])
:dispatch [::destroy]}
[::unsubscribe instance-name subscription-id])
:dispatch [::destroy instance-name]}

(merge
{:db (dissoc db :re-graph)}
(when-let [ws (get-in db [:re-graph :websocket :connection])]
{:db nil}
(when-let [ws (get-in db [:websocket :connection])]
{::internals/disconnect-ws [ws]})))))

(defn init [& [args]]
(re-frame/dispatch [::init args]))
(defn init
([opts] (init default-instance-name opts))
([instance-name opts]
(re-frame/dispatch [::init instance-name opts])))

(defn destroy []
(re-frame/dispatch [::destroy]))
(defn destroy
([] (destroy default-instance-name))
([instance-name]
(re-frame/dispatch [::destroy instance-name])))
Loading

0 comments on commit 78ac8b5

Please sign in to comment.