Skip to content

Instantly share code, notes, and snippets.

@tmoerman
Last active November 9, 2022 09:40
Show Gist options
  • Save tmoerman/c4c863c00e5364932ad209f921d9e7d2 to your computer and use it in GitHub Desktop.
Save tmoerman/c4c863c00e5364932ad209f921d9e7d2 to your computer and use it in GitHub Desktop.
Illustration of Pathom eql processing split into :enter and :leave phases of an interceptor
(ns com.aa.nexus.eql.interceptor-spike
"See:
https://gist.github.com/tmoerman/c4c863c00e5364932ad209f921d9e7d2"
(:require
[clojure.string :as str]
[clojure.test :refer :all]
[com.wsscode.pathom3.connect.indexes :as pci]
[com.wsscode.pathom3.connect.operation :as pco]
[com.wsscode.pathom3.connect.runner :as pcr]
[com.wsscode.pathom3.interface.eql :as p.eql]
[com.wsscode.pathom3.format.eql :as pf.eql]
[com.wsscode.pathom3.plugin :as p.plugin]
[com.wsscode.pathom3.connect.built-in.plugins :as pbip]
[sieppari.core :as exe]
[edn-query-language.core :as eql]
[taoensso.timbre :as log]))
(def weather-db
{:brussels :overcast
:leuven :thunder
:genk :sunny})
(def population-db
{:brussels "1M"
:leuven "100K"
:genk "50K"})
(def mayor-db
{:brussels "Philippe Close"
:leuven "Mo Ridouani"
:genk "Wim Dries"})
(pco/defresolver city->weather [env {:keys [city] :as input}]
(log/info "resolver `city->weather`" city)
{:weather (weather-db city)})
(pco/defresolver city->population [env {:keys [city] :as input}]
(log/info "resolver `city->weather`" city)
{:population (population-db city)})
(pco/defresolver city->mayor [env {:keys [city] :as input}]
(log/info "resolver `city->mayor`" city)
{:mayor (mayor-db city)})
(pco/defmutation print-city! [env {:keys [city mayor] :as input}]
(log/info "mutation `print-city!` -> input" [city, mayor])
(log/warn (format "!! %s (%s) !!" (str/upper-case (name city)) mayor))
input)
(def eql-processor-enter
{::p.plugin/id `eql-processor/enter
::pcr/wrap-mutate
(fn [mutate]
(fn [{:keys [acc] :as env} ast]
;; capture the mutation result
(let [result (mutate env ast)]
(swap! acc conj result)
result)))
::p.eql/wrap-process-ast
(fn [process]
(fn [env ast]
;; strip the mutation join from the AST
(let [ast* (eql/transduce-children (map (fn [{:keys [type] :as node}]
(if (= type :call)
(dissoc node :children :query)
node))) ast)]
(process env ast*))))})
(def eql-processor-leave
{::p.plugin/id `eql-processor/leave
::pcr/wrap-mutate
(fn [mutate]
(fn [{:keys [acc] :as env} ast]
;; !! re-use the mutation result !!
(let [result (log/spy :info (peek @acc))]
(swap! acc pop)
result)))})
(def env
(-> (p.plugin/register [pbip/mutation-resolve-params])
(pci/register [city->weather
city->population
city->mayor
print-city!])))
(defn process-intercepted
"Simulation of :enter and :leave phase of an interceptor system."
[env tx]
(let [acc (atom nil)
enter-env (-> env
(assoc :acc acc)
(p.plugin/register eql-processor-enter))
enter-result (p.eql/process enter-env tx)
_ (log/info "Executing other interceptors")
_ (log/spy :info @acc)
_ (swap! acc reverse) ;; because we conj onto nil...
leave-env (-> env
(assoc :acc acc)
(p.plugin/register eql-processor-leave))
leave-result (p.eql/process leave-env tx)]
;enter-result
leave-result))
(def query [{[:city :leuven] [:city :weather]}])
(def mutation [`(print-city! [:city :leuven])])
(def mutation+join [{`(print-city! [:city :leuven]) [:city :weather]}])
(def mutation+joins [{`(print-city! [:city :leuven]) [:city :weather]}
{`(print-city! [:city :brussels]) [:city :population]}])
(comment
(process-intercepted env query)
(process-intercepted env mutation+join)
(process-intercepted env mutation+joins))
;; ---
;; TODO -> reimplement plugins, can we turn it into one plugin?
(def ix-plugin
{::p.plugin/id `ix-plugin
::pcr/wrap-mutate (fn [mutate]
(fn [{:ix/keys [acc phase] :as env} ast]
(condp = phase
:enter ;; capture the mutation result
(let [result (mutate env ast)]
(swap! acc conj result)
result)
:leave ;; re-use the mutation results
;; selection strategy = last
(last @acc))))
::p.eql/wrap-process-ast (fn [process]
(fn [{:ix/keys [phase] :as env} ast]
(condp = phase
:enter ;; strip the mutation join from the AST
(let [ast* (eql/transduce-children (map (fn [{:keys [type] :as node}]
(if (= type :call)
(dissoc node :children :query)
node))) ast)]
(process env ast*))
:leave ;; proceed as usual
(process env ast))))})
(def eql-processor-interceptor
{:enter (fn [ctx]
(let [acc (atom [])
env (-> ctx
(assoc :ix/acc acc)
(assoc :ix/phase :enter)
(p.plugin/register ix-plugin))
eql (get-in ctx [:request :body-params])]
;; results captured in acc
(log/spy :info (p.eql/process env eql))
(-> ctx
;; ensure the accumulator is carried to the :leave phase
(assoc :ix/acc acc))))
:leave (fn [{:ix/keys [acc] :as ctx}]
(let [env (-> ctx
(assoc :ix/phase :leave)
(p.plugin/register ix-plugin))
eql (get-in ctx [:request :body-params])
;; results are retrieved from acc
_ (p.eql/process env eql)
response {:status 200
:body @acc}]
(log/info :info response)
(-> ctx
(dissoc :ix/acc :ix/phase)
(assoc :response response))))
})
(defn ix-exe
[env eql]
(-> (exe/execute-context [eql-processor-interceptor]
(assoc-in env [:request :body-params] eql))
(select-keys [:request :response])))
(comment
(ix-exe env query)
(ix-exe env mutation)
(ix-exe env mutation+join)
(ix-exe env mutation+joins)
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment