Fork me on GitHub
#pathom
<
2020-05-03
>
kenny00:05:18

Is there a way to debug why pathom is calling a particular resolver?

dvingo02:05:35

i'm not sure of the answer but this may help:

kenny19:05:45

Ok so I got this working. Very cool! I see it calling a nested resolver multiple times but I don't understand why. Is there docs on how to read the graph shown by pathom-viz? The use case is debugging a batch query issue where is is calling an individual resolver multiple times when I think it should not.

Matthew00:05:08

Does anyone have a good reference set up for a pathom.core/parser for a node js environment (pathom mvn/version “2.2.31”)? I’ve switched the pathom.connect/reader2 to async-reader2 and I started getting async channels returned from my parser, but I’m wondering if there’s more to it. Any descriptions or links to a repo are appreciated!

kenny01:05:34

When writing derived/computed attribute resolver, does Pathom know to deeply merge the result?

wilkerlucio19:05:17

hello @matthew_lefebvre, did you changed the parser to async-parser?

wilkerlucio19:05:41

also, when switching to it, the parser itself will start returning a core async channel, reading on it will give you the full result

kenny19:05:45

Ok so I got this working. Very cool! I see it calling a nested resolver multiple times but I don't understand why. Is there docs on how to read the graph shown by pathom-viz? The use case is debugging a batch query issue where is is calling an individual resolver multiple times when I think it should not.

kenny19:05:33

Are the numbers here the index of the item in a list?

kenny20:05:46

Every single index that has a horizontal bar in this screenshot is an item without a :name attribute & Pathom attempting to call an individual item resolver to find it.

kenny20:05:06

I think my specific problem has to do with a resolver returning a list of items. Some of those items have a :name attribute and some don't. For the ones that don't, Pathom will call a resolver that works on the individual item (perhaps hoping it could resolve the :name). That resolver will also not return a :name because it simply does not exist on certain items. Is there a way to tell pathom that an attribute will not become available no matter how many other resolvers it calls?

kenny20:05:05

In general, how do people handle optional attributes? Do you not include them in the ::pc/output vector?

kenny20:05:46

Every single index that has a horizontal bar in this screenshot is an item without a :name attribute & Pathom attempting to call an individual item resolver to find it.

kenny20:05:57

Ah, I have found the bug/missing thing that I thought worked and does not. Here's a repro:

(pc/defresolver videos-resolver
  [_ _]
  {::pc/input  #{:user/id}
   ::pc/output [{:user/videos [:video/id :video/name :video/info]}]}
  {:user/videos [{:video/id   1
                  :video/name "v1"
                  :video/info {:a "a"}}]})

(pc/defresolver video-extra-info
  [_ _]
  {::pc/input  #{:video/id}
   ::pc/output [{:video/info [:b]}]}
  {:video/info {:b "b"}})

(parser
   {}
   [{[:user/id 1] [{:user/videos [:video/id
                                  {:video/info [:b]}]}]}])
=> {[:user/id 1] #:user{:videos [#:video{:id 1, :info {}}]}}
Even though my query is asking for {:video/info [:b]} and a resolver is capable of resolving that key, it is not included in the output. Is there a way around this?

kenny20:05:31

I could get hacky and do this:

(pc/defresolver video-extra-info
  [env _]
  {::pc/input  #{:video/id}
   ::pc/output [:video/duration
                {:video/info [:b]}]}
  (swap! (::p/entity env) assoc-in [:video/info :b] "b")
  {:video/duration 1
   :video/info     {:b "b"}})

(parser
 {}
 [{[:user/id 1] [{:user/videos [:video/id
                                :video/duration
                                {:video/info [:b]}]}]}])
(parser
 {}
 [{[:user/id 1] [{:user/videos [:video/id
                                :video/duration
                                {:video/info [:b]}]}]}])
As long as I have an additional key that would cause Pathom to hit the video-extra-info resolver, that works.

kenny20:05:23

To be clear, the problem is that Pathom is not letting me update a nested map that is already partially resolved.

dvingo20:05:59

i think the issue may be both resolvers have ::pc/output containing :video/info , this is working for me:

(pc/defresolver videos-resolver
  [_ _]
  {::pc/input  #{:user/id}
   ::pc/output [{:user/videos [:video/id :video/name]}]}
  {:user/videos [{:video/id 1 :video/name "v1"}]}) 

(pc/defresolver video-extra-info
  [_ _]
  {::pc/input  #{:video/id}
   ::pc/output [{:video/info [:b]}]}
  {:video/info {:b "b"}})

dvingo20:05:23

(myparser
    {}
    [{[:user/id 1] [{:user/videos [:video/id
                                   :video/name
                                   {:video/info [:b]}]}]}])
=> {[:user/id 1] #:user{:videos [#:video{:id 1, :name "v1", :info {:b "b"}}]}}

kenny21:05:54

@danvingo But I want it the way I wrote it -- a resolver will compute some new attributes on a nested entity.

dvingo22:05:18

(pc/defresolver task-resolver [env {:keys [task/id]}]
  {::pc/input  #{:task/id}
   ::pc/output [:task/description]}
  {:task/description "task Description"})

(pc/defresolver habit-resolver [env {:keys [habit/id]}]
  {::pc/input #{:habit/id}
   ::pc/output [:habit/description {:habit/task-id [:task/id]}]}

  {:habit/description "description" :habit/task-id {:task/id "task-id"}})

(myparser {} 
[{[:habit/id :habit] [:habit/id :habit/description {:habit/task-id [:task/description]}]}])
;; =>
{[:habit/id :habit] #:habit{:id :habit, :description "description", :task-id #:task{:description "task Description"}}}
I think this is what you're looking for - you have to wrap the property you want to join in a map. I just ran into this as well.

kenny22:05:01

I'd like to not have to wrap them though. It forces out new keys that have no purpose.

dvingo00:05:26

yep, it surprised me too, but not a big deal, i just add an alias:

(def habit-task-alias-res (pc/alias-resolver :habit/task-id :task/id))

dvingo00:05:40

and convert that prop to a map

dvingo21:05:55

i don't understand, video-extra-info is doing that

wilkerlucio21:05:01

@kenny the thign is that once a property is resolved, pathom wont try to resolve again, so you can't have a "merge" like you are thinking

kenny22:05:27

Oh 😞 Is there a technical reason for this?

wilkerlucio07:05:39

would put a new whole dimension of processing, it goes against some basic heuristics of how the library works, the same attribute appearing multiple times is expected to be just different paths for the same data, not an accumulator thing

wilkerlucio07:05:01

otherwise the engine would have to always process every path, that could get a out of control quick

wilkerlucio21:05:11

when it sees :video/info present, it wont try to get more of it

wilkerlucio21:05:40

but, if you had something more nested, it would work, like this:

wilkerlucio21:05:04

(pc/defresolver videos-resolver
  [_ _]
  {::pc/input  #{:user/id}
   ::pc/output [{:user/videos [:video/id :video/name]}]}
  {:user/videos [{:video/id 1 :video/name "v1"}]})

(pc/defresolver video-extra-info
  [_ _]
  {::pc/input  #{:video/id}
   ::pc/output [::video/info-b]}
  {:video/info-b "b"})

mruzekw21:05:11

Hi there, if I'm using Datomic, how do I make sure that my resolver is always getting the latest value of the DB? Would I need pass down the DB connection through the context and get the value in each resolver? Or is there another way?

eoliphant21:05:37

if you mean ‘latest’ in terms of perhaps during a mutation join, or something, you’d need to either pass in the conn and grab the db in your resolver, or write a plugin that say updates it in the env after a mutation.

eoliphant21:05:02

i played around with it a bit last year

mruzekw21:05:54

I mean, I'd want the latest value (`(d/db conn)`) at any time query or mutation

lilactown22:05:29

You probably want to get it once for the duration of a query

lilactown22:05:04

Otherwise you’re going to end up with potential inconsistencies

eoliphant22:05:07

as opposed to simply using the one that you pass in when you invoke the parser? such that if anything changes while the resolution is happening, you have the latest one? You’d still need either approach. my plugin experiment put the conn and the db in an atom, and refreshed the db after mutations. You could do the same to have it happen after anything. It gets a little weird because on the one hand, part of datomic’s value in many cases is having a ‘stable’ view of the database, which is really nice when you’re doing pathom/graphql/etc, but there are obvious cases, like mutation joins where you really need an update

mruzekw22:05:26

I guess where I'm confused is that if I just pass (d/db conn) to the parser like so:

(def parser
  (p/parser
    {::p/env     {::p/reader               [p/map-reader
                                            pc/reader2
                                            pc/open-ident-reader
                                            p/env-placeholder-reader]
                  ::p/placeholder-prefixes #{">"}
                  :db (d/db conn)}
     ::p/mutate  pc/mutate
     ::p/plugins [(pc/connect-plugin {::pc/register my-resolvers})
                  p/error-handler-plugin
                  p/trace-plugin]}))

mruzekw22:05:57

Sorry slack trouble

mruzekw22:05:06

I would only get one value of the db ever, right?

eoliphant22:05:12

ah you’d typically pass it in

eoliphant22:05:35

when you actually invoke the parser (parser {:db (d/db conn)} ….)

dvingo22:05:03

You can use the env-wrap-plugin:

(defn augment-env-request
  [env]
  (assoc env
    :db db
    :current-user (user/get-current-user env)
    :config config))
;; add to plugins vector:
(p/env-wrap-plugin augment-env-request)

4
mruzekw22:05:07

The parser is not a long-standing process, it's just a fn

4
eoliphant22:05:24

as opposed to when you create it. Or, you could do what you’re doing there, but pass in the conn. and have a plugin assoc in the db or something. but the simplest way to get going is pass it in when you invoke it

mruzekw22:05:26

Okay, I'm understanding better now

eoliphant22:05:28

yep, the value of values, higher order funcs, and all that jazz 🙂 It’s a function that’s closed over all your setup, and acts accordingly when you invoke it

mruzekw22:05:54

Cool, makes more sense now

mruzekw22:05:03

And I'd probably invoke that in my http handler

eoliphant22:05:35

its a wildly more complex example of something like

(def add2 (partial + 2))
(add2 2) ; => 4

mruzekw22:05:22

Gotcha, so p/parser is the higher order fn

eoliphant22:05:45

forgot about the env-wrap-plugin that @danvingo mentioned. Same principle. Easier way to dynamically muck with the env

mruzekw22:05:35

Gotcha, thanks

mruzekw22:05:09

And then for grabbing data after a db mutation?

eoliphant22:05:12

that particular case is a little tricker. I’ll have to find the code, but you can basically write your own plugin and say ‘refresh’ the db value after a mutation takes place. Mine was pretty straightforward, but you then get into stuff like ‘well was that one that i cared about’ etc. There may be ways (pathom packs a ton of metadata into the env), to further optimize it, like say determining if what i’m about to resolve, is linked to a mutation that happened. It can get a little weird because you can end up with other issues, in my more simplistic attempt you could end up calling a resolver with a new db value even though what was being resolved had nothing to do with the change that was made, and then you lose the ‘stable db’ view even when it’s not necessary

mruzekw22:05:38

I see. So would you recommend passing the connection and the latest db value (when the parser is first called) to the parser env?

mruzekw22:05:48

(Not assuming use of any plugins)

eoliphant22:05:08

yeah I’d start with that for sure, then see how your use cases play out

mruzekw22:05:36

Okay, cool. Thank you 🙂 I was a bit lost on how the env was actually constructed and where

eoliphant22:05:57

simplicity first 🙂 lol . np at all

mruzekw22:05:15

Are there any example projects on GH with Pathom usage? I know it's typically used in Fulcro

eoliphant22:05:48

yeah the default fulcro setup is probably the best way to get started as it shows how to wire in requests from the client, etc.

mruzekw22:05:00

Okay, I'll see if I can find that