Fork me on GitHub
#pathom
<
2023-04-27
>
Tom H.03:04:57

When using the pathom3 boundary interface is there a way to tell it to only return attributes that were explicitly queried?

Tom H.04:04:30

I’ll try using com.wsscode.pathom3.format.eql/map-select , just looking to reduce what’s sent over the wire

wilkerlucio13:04:59

hi Tom, not sure what you mean, the boundary interface should only return attributes explicitly queried, example:

wilkerlucio13:04:08

(ns demos.boundary
  (:require [com.wsscode.pathom3.connect.indexes :as pci]
            [com.wsscode.pathom3.connect.operation :as pco]
            [com.wsscode.pathom3.interface.eql :as p.eql]))

(pco/defresolver user-data []
  {:user/id    123
   :user/name  "foo"
   :user/email ""})

(def env
  (-> {}
      (pci/register [user-data])))

(def request (p.eql/boundary-interface env))

(comment
  (request [:user/name]))
=> {:user/name "foo"}

wilkerlucio13:04:54

can you show what is the case you see where the output doesn't match the query?

Tom H.13:04:06

sure, so I tried to make a minimal repro using just the boundary interface as above and it’s working as expected so it must be something odd we’re doing we’re returning the result from the boundary interface as part of a pedestal handler like so:

(def handle-post
  (handler
    ::pathom
    {:summary "Pathom handler"}
    (fn [{:keys [body-params] :as request}]
      {:status 200
       :body ((p.eql/boundary-interface
                (-> (pci/register {::p.error/lenient-mode? true} registry)
                    (merge request)))
              body-params)})))

Tom H.13:04:17

I’ll try make a minimal repro with pedestal

wilkerlucio13:04:37

I also suggest you cache the boundary interface creation, re-building it on every request is suboptimal (you keep paying the price to register indexes and a few things, not huge deal, but something you can easly optimize)

👍 2
Tom H.13:04:02

btw we send body params like so:

{:pathom/eql [:a :b]
 :pathom/entity {:c 1}}

Tom H.13:04:06

re: caching the interface, would we still be able to inject things into the env on each request?

wilkerlucio13:04:36

yes you can, the boundary interface has an arity 2 option where you can use the first argument to extend env

❤️ 2
wilkerlucio13:04:59

the extension can be in 2 forms: • a map (that will merge with env) • a fn, that will receive the env and must return some updated env

wilkerlucio13:04:28

(check docstring of boundary-inteface for more details)

👍 2
Tom H.13:04:27

I need to head off but I’ll take a crack at making that repro tomorrow morning 🙂 We did fix our issue by wrapping the result of the boundary interface in map-select , using the same query, and it resulted in a massively smaller return value so something fishy going on..

wilkerlucio13:04:48

I would check if you have some plugins that might be doing something, because under the hood boundary interface uses the same eql/process / eql/process-ast, which do already apply map-select at the end

👍 2
Tom H.15:04:27

So it looks like the issue was that some of our resolvers actually return https://cljdoc.org/d/toucan/toucan/1.15.4/doc/toucan-db-functions#classes-of-fetched-objects rather than plain maps and map-select-ast returns them as-is (because of a coll/native-map? check). I forgot we needed to walk the return value from the boundary interface with (clojure.walk/prewalk #(if (record? %) (into {} %) %) to get map-select to work, that should have been a hint as to why it wasn’t being trimmed in the first place 😆

wilkerlucio15:04:08

cool, glad to hear, one idea, you can try to use ::p.f.eql/wrap-map-select-entry plugin entry and apply your record to map thing there, this way you avoid having to do an extra walk across the structure

❤️ 2
wilkerlucio15:04:02

oh, I just looked at the code, seems like unfortunally the native-map check happens before the plugin call, so maybe not

Tom H.15:04:09

ah I see what you mean, the p.plugin/run-with-plugins env ::wrap-map-select-entry part

wilkerlucio15:04:25

yup, it might still work, give it a try

wilkerlucio15:04:45

because it wraps the entry of each map, so unless your root map is a toucan, it may work

Tom H.15:04:05

yep root is a plain map, I’ll give it a go

Tom H.16:04:32

hmm doesn’t seem to be working, is this the correct way to get the value of each node?

(defn no-more-records-wrapper [mse]
  (fn [env source {k :key :as ast}]
    (if (and (contains? source k)
             (record? (get source k)))
      (let [v (get source k)]
        (tap> ['record!! v])
        (medley/map-entry k (into {} v)))
      (mse env source ast))))

(p.plugin/defplugin no-more-records-plugin
  {::p.f.eql/wrap-map-select-entry no-more-records-wrapper})

Tom H.16:04:41

doesn’t find any of the returned records

wilkerlucio16:04:40

gotcha, I was trying to repro your case, but got surprised that this worked fine:

wilkerlucio16:04:53

(ns demos.boundary
  (:require [clojure.walk :as walk]
            [com.wsscode.pathom3.connect.indexes :as pci]
            [com.wsscode.pathom3.connect.operation :as pco]
            [com.wsscode.pathom3.interface.eql :as p.eql]
            [com.wsscode.pathom3.plugin :as p.plugin]))

(defrecord SampleRec [])

(pco/defresolver user-data []
  {::pco/output [:user/id :user/name :user/email]}
  (-> (->SampleRec)
      (assoc
        :user/id 123
        :user/name "foo"
        :user/email "")))

(def env
  (-> {}
      (pci/register [user-data])))

(def request (p.eql/boundary-interface env))

(comment
  (request [:user/name]))
=> {:user/name "foo"}

😲 2
wilkerlucio16:04:59

anyway, I think this plugin should work:

wilkerlucio16:04:05

(p.plugin/defplugin no-more-records-plugin
  {:com.wsscode.pathom3.connect.runner/wrap-resolve
   (fn [resolve]
     (fn [env input]
       (let [res (resolve env input)]
         (walk/prewalk
           #(if (record? %)
              (do
                (println "WARN: some resolver returned a record")
                (into {} %)) %)
           res))))})

❤️ 2
wilkerlucio16:04:11

ideally you should avoid this on prod, and make sure you only give pathom native maps, so the warn can help during dev

💯 2
Tom H.16:04:11

That plugin works great 🙂 thank you

Tom H.17:04:11

very curious about the repro, it could be that we’re returning collections of records?

wilkerlucio17:04:40

could be, let me try

wilkerlucio17:04:50

also, a little update to make the plugin more informative:

wilkerlucio17:04:51

(p.plugin/defplugin no-more-records-plugin
  {:com.wsscode.pathom3.connect.runner/wrap-resolve
   (fn [resolve]
     (fn [env input]
       (let [res (resolve env input)]
         (walk/prewalk
           #(if (record? %)
              (let [node (-> env :com.wsscode.pathom3.connect.planner/node)]
                (println (str "WARN: " (::pco/op-name node) " resolver returned a record"))
                (into {} %))
              %)
           res))))})

🍻 2
wilkerlucio17:04:11

was able to repro with a collection:

🙌 2
wilkerlucio17:04:13

(ns demos.boundary
  (:require [clojure.walk :as walk]
            [com.wsscode.pathom3.connect.indexes :as pci]
            [com.wsscode.pathom3.connect.operation :as pco]
            [com.wsscode.pathom3.interface.eql :as p.eql]
            [com.wsscode.pathom3.plugin :as p.plugin]))

(defrecord SampleRec [])

(pco/defresolver user-data []
  {::pco/output
   [{:users
     [:user/id :user/name :user/email]}]}
  {:users
   [(-> (->SampleRec)
        (assoc
          :user/id 123
          :user/name "foo"
          :user/email ""))]})

(p.plugin/defplugin no-more-records-plugin
  {:com.wsscode.pathom3.connect.runner/wrap-resolve
   (fn [resolve]
     (fn [env input]
       (let [res (resolve env input)]
         (walk/prewalk
           #(if (record? %)
              (let [node (-> env :com.wsscode.pathom3.connect.planner/node)]
                (println (str "WARN: " (::pco/op-name node) " resolver returned a record"))
                (into {} %))
              %)
           res))))})

(def env
  (-> {}
      (pci/register [user-data])
      (p.plugin/register no-more-records-plugin)))

(def request (p.eql/boundary-interface env))

(comment
  (request [{:users [:user/name]}]))

Tom H.17:04:54

Ok cool, I was starting to feel crazy 😄