Fork me on GitHub
#pathom
<
2021-02-03
>
souenzzo16:02:44

In pathom3, there is some function that given a an env and a query it returns which inputs are required to resolve this function? for example: - env: (pci/register [(single-attr-resolver :a :b inc)]) - query: [:b] - returns: [:a]

wilkerlucio17:02:34

that's the planner

wilkerlucio17:02:48

it wont tell you directly that, but you can create a plan and infer this data from it

✔️ 3
jmayaalv17:02:44

@wilkerlucio i am testing the batch resolvers on pathom3 and found a problem. If the “parent” resolver of the batch resolver returns a seq you get a 1. Unhandled java.lang.ClassCastException this is a way to reproduce it:

(def db {:contracts {"contract-1" {::contract/id   1
                               ::contract/code "contract-1"
                               ::contract/name "the name"}}
         :positions {1 '({::position/units  10
                         ::instrument/fund {::fund/id 1}}
                        {::position/units  20
                         ::instrument/fund {::fund/id 2}})}
         :funds     {1 {::fund/code "fund 1"}
                     2 {::fund/code "fund 2"}}})

(pco/defresolver contract-resolver [{::contract/keys [code]}]
  {::pco/output [::contract/id ::contract/code ::contract/name]}
  (get-in db [:contracts code]))

(pco/defresolver position-resolver [{::contract/keys [id]}]
  {::pco/output [{::contract/positions [::position/units {::instrument/fund [::fund/id]}]} ]}
  {::contract/positions (get-in db [:positions id])})

(pco/defresolver fund-resolver [input]
  {::pco/input [::fund/id]
   ::pco/output [::fund/code]
   ::pco/batch? true}
  (clojure.pprint/pprint
   {:in input
    :res (map #(get-in db [:funds (::fund/id %)])
         input)})

  (map #(get-in db [:funds (::fund/id %)])
       input))

(p.eql/process
  (pci/register [contract-resolver position-resolver fund-resolver])
  [{[::contract/code "contract-1"] [::contract/name {::contract/positions [::position/units {::instrument/fund [::fund/code]}]}]}])
everything works perfectly if db is changed to
(def db {:contracts {"contract-1" {::contract/id   1
                               ::contract/code "contract-1"
                               ::contract/name "the name"}}
          ;;notice that positions are now a vec
         :positions {1 [{::position/units  10
                         ::instrument/fund {::fund/id 1}}
                        {::position/units  20
                         ::instrument/fund {::fund/id 2}}]}
         :funds     {1 {::fund/code "fund 1"}
                     2 {::fund/code "fund 2"}}})

wilkerlucio17:02:36

@jmayaalv yes, that's a limitation, I think the right approach at this time is to give a better error message

wilkerlucio17:02:12

the issue is that batch needs to be able to navigate, it would be possible to make lists work, but at some processing cost (because I can't update-in a list, but I can in a vector)

wilkerlucio17:02:30

can you open an issue for it?

jmayaalv17:02:46

got it, yes will do. Thanks a lot!

markaddleman18:02:35

I'm struggling to surface resolver exceptions in Pathom3. What attr should I provide to pcrs/get-attribute-error to get the internal error exception?

(pco/defresolver throws [{:a/keys [input]}]
  (throw (Exception. "Internal error"))
  {:a/output (str "echoing " input)})

(-> (p.eql/process (-> [throws] (pci/register))
                   [{[:a/input "hello"] [:a/output]}])
    (meta)
    ::pcr/run-stats
    (psm/smart-run-stats)
    (pcrs/get-attribute-error :a/output))   

markaddleman19:02:50

Under the latest Pathom3 commit, get-attribute-error itself returns

{:com.wsscode.pathom3.connect.runner.stats/node-error-type :com.wsscode.pathom3.connect.runner.stats/node-error-type-unreachable
    :com.wsscode.pathom3.connect.runner/node-error #reveal/error{:via [{:type clojure.lang.ExceptionInfo
                                                                        :message "Can't find a path for :a/output"
                                                                        :data {:com.wsscode.pathom3.attribute/attribute :a/output}
                                                                        :at [com.wsscode.pathom3.connect.runner.stats$attribute_error__30270 invokeStatic "stats.cljc" 73]}]
                                                                 :trace [[com.wsscode.pathom3.connect.runner.stats$attribute_error__30270 invokeStatic "stats.cljc" 73]
                                                                         [com.wsscode.pathom3.connect.runner.stats$attribute_error__30270 invoke "stats.cljc" 51]...}

wilkerlucio19:02:08

hello @markaddleman, the issue is that you are looking for the error in the wrong map

wilkerlucio19:02:21

errors and stats are per entity (each map in the response gets its own)

wilkerlucio19:02:38

this should work:

(-> (p.eql/process (-> [throws] (pci/register))
      [{[:a/input "hello"] [:a/output]}])
    (get [:a/input "hello"])
    (meta)
    ::pcr/run-stats
    (psm/smart-run-stats)
    (pcrs/get-attribute-error :a/output))

markaddleman19:02:07

One more thing: Is there a convenient & performant way of checking if any attributes have errors?

wilkerlucio19:02:32

yes, in the run stats, there is a key that says all the nodes with errors

markaddleman19:02:38

My desired exception handling policy is to throw an exception if any resolver throws so I'm writing my own query function to handle this

wilkerlucio20:02:42

one thing to consider is that its possible to have a failed node, but still a fully successful response, this can happen if there are multiple paths for something (a OR node), some path fails, other path succeed

wilkerlucio20:02:51

in this case, you will have an error, but still a complete response

markaddleman20:02:35

Understood. In my case, there is no OR but I'll keep this is mind

markaddleman19:02:32

perfect! Thank you

markaddleman23:02:31

Wondering what you think about resolver input maps being smart-maps? My app gets a client query, processed by pathom as EQL and many times it is convenient to pass the input around to different functions that might need to compute things for which resolvers already exist.

wilkerlucio01:02:48

that can kill efficiency in a sense, ideally you can just write more resolvers and always be as specific as possible about your requirements, this is the way to maximize the utility of how pathom can optimize your code paths to fulfill data needs

wilkerlucio01:02:22

because if you give them as smart maps, you delay the point in which you know what you need

wilkerlucio01:02:36

so each time you read, pathom has to go over the planning and running process again

wilkerlucio01:02:12

while if you do in a single go, pathom can plan once, and with more information makes it more efficient (because it can optimize repeated paths and stuff like that)

markaddleman01:02:15

"each time you read" - This means get operations on the smart map?

wilkerlucio01:02:52

doing a single query is much likely going to be faster than lazy loading one attribute at a time

wilkerlucio01:02:36

I had that situation with Pathom Viz, I was using smart maps to compute trace data

markaddleman01:02:21

Fair enough. Although, in my case, I think the only performance difference is Pathom's overhead to compute the plan. No matter what, I'm going to have to compute the result I care about. The only issue is whether the computation occurs as part of a smart-map get or as a direct function call

wilkerlucio01:02:26

I was feeling some perf hit, and switch over to a pre-defined query in EQL, that got around 8x faster (because there was a lot of collection items, in those cases the usage of smart maps to do one attr a time adds up)

wilkerlucio01:02:09

what I'm telling you is mostly about my perf expectations, but I like to encourage you to play around with a mixed approach, and Pathom totally supports that

wilkerlucio01:02:29

you can write a plugin to make this happen

markaddleman01:02:54

Oh, that's a good idea. I had planned on each resolver wrapping input in a smart map but a plugin is much more convenient

wilkerlucio01:02:54

using the wrap-resolve hook, there, you can wrap the resolver call by transforming the input in a smart map, them you get what you asked for

markaddleman01:02:53

This whole conversation brings to mind something I've been wondering about: Given smart-maps, what is the right balance between function calls on maps vs get operations & resolvers

wilkerlucio01:02:58

and I'm curious how this goes, would to learn more about what happens after some time doing that 🙂

wilkerlucio01:02:21

function calls on maps?

markaddleman01:02:35

I mean a function that takes a map as input

wilkerlucio01:02:09

that's the design part, depends how much you want to delegate to pathom

wilkerlucio01:02:19

or what "entry points" you wanna give to the system

wilkerlucio01:02:38

I think a very open question, varies a lot

💯 3
eoliphant19:02:44

i’ve been struggling with that as well lol.

markaddleman20:02:14

Here is my latest thought: Because Cursive does a much better job of navigating among function defs and function calls, my sanity prefers function calls over smart-map resolvers and keys. It's just much easier to reverse engineer what's going on. Perhaps when I become more familiar with Pathom Viz and other debugging tools, I'll change my mind.

markaddleman20:02:49

That's not to say I don't think smart-maps & resolvers have their place... They definitely do. It's just that when I don't have a compelling reason one way or the other, I'll stick to fns