Fork me on GitHub
#pathom
<
2023-01-23
>
Ben Grabow22:01:55

Is there an easy way to ask Pathom "Here's an entity. Resolve all the attributes that are resolvable from that entity."? I have hacked together some naive code to do this using the io-index but I wonder if there is a robust, built-in way to do it.

wilkerlucio22:01:18

the reachable-paths will take an env and some entity, and will tell you all reachable paths

wilkerlucio22:01:30

this is what Pathom Viz uses under the hood to provide completions

Ben Grabow22:01:35

It looks like I get the results in shape descriptor format, and I can pull the top-level attributes from the keys of this map. Is there a way to branch out to all nested attributes too? Or do I write the recursion myself?

wilkerlucio22:01:34

I can't remember on the top of my head if that also does deep diving on nested, I guess no, but if that's case you can just repeat the process with the attributes at that level

Ben Grabow22:01:38

Hmm, I realize now the to-many resolver I have doesn't declare the nested attributes in the output spec, so I don't think I can make the jump automatically at planning time. I'll have to run the query first then iterate over the results to query into the nested attrs. reachable-paths does a ton of the heavy lifting for me though. Thanks!

👍 2
Ben Grabow22:01:20

Still pretty hacky, but this "works" for simple cases:

(defn recursive-resolve
  [env entity]
  (let [entity (p.eql/process
                 env
                 entity
                 (vec (keys (pci/reachable-paths env entity))))]
    (->> (for [[k v] entity]
           [k (cond
                (instance? PersistentArrayMap v) (recursive-resolve env v)
                (vector? v) (map #(recursive-resolve env %) v)
                :else v)])
         (into {}))))
At first instead of (instance? PersistentArrayMap v) I tried (map? v) but ran into trouble because my data includes some clojure.data.xml.node.Element objects which "are" IPersistentMaps but don't work as Pathom entities (nor should they).

Ben Grabow22:01:56

This approach does not forward parent attributes to the child query context, although that may be useful for some cases.

wilkerlucio22:01:19

I suggest you replace (vec (keys ... with (pfsd/shape-descriptor->query ...

💡 2
Ben Grabow22:01:20

This doesn't seem to work smoothly since my input entity has some data in it:

(pfsd/shape-descriptor->query {:my-provided-entity {:body "hello world"}
                               :derived-from-my-entity {}})
Execution error (UnsupportedOperationException) at com.wsscode.pathom3.format.shape-descriptor/shape-descriptor->query$fn (shape_descriptor.cljc:138).
nth not supported on this type: Character

wilkerlucio22:01:02

maybe you removed the reachable-paths by accident? since that should return a valid shape descriptor

wilkerlucio22:01:36

as: (pfsd/shape-descriptor->query (pci/reachable-paths env entity))

Ben Grabow22:01:22

Eliding the real data, my situation looks something like this:

(pci/reachable-paths env {:my-provided-attr {:body "hello world"}})
=>
{:my-provided-attr {:body "hello world"}
 :derived-from-my-attr {}}
The {:body "hello world"} is treated as a black box from Pathom's perspective. It should be an opaque piece of data, and :body is not a resolved attribute.

Ben Grabow22:01:02

Edited entity -> attr to be more clear

wilkerlucio22:01:05

humm, or maybe something I missed, let me try it here

wilkerlucio22:01:53

ah, this is what you can do: (pci/reachable-paths env (pfsd/data->shape-descriptor entity))

wilkerlucio22:01:25

which is the right way to use reachable-paths, it expects a shape descriptor as input, but I only remembered it now looking it its sources, hehe:

(>defn reachable-paths
  "Discover which paths are available, given an index and a data context.

  Also includes the attributes from available-data."
  [{::keys [index-io] :as env} available-data]
  [(s/keys) ::pfsd/shape-descriptor
   => ::pfsd/shape-descriptor]

Ben Grabow22:01:33

(pfsd/data->shape-descriptor {:my-provided-entity {:body "hello world"}})
=>
{:my-provided-entity {:body {}}}

Ben Grabow22:01:39

Is there a way I can tell this to only parse keys that are in the index? :body is not a pathom attribute, so I think I don't want it mentioned in the query. (It seems to work fine since the parent attr is provided instead of resolved.)

wilkerlucio22:01:14

its fine to mention it in the query because its already present in the data, so Pathom will just get it from the entity, no attempt look it up in the index

wilkerlucio22:01:28

you could make that filtering, but its fine to keep them there

Ben Grabow22:01:34

I think pfsd/data->shape-descriptor-shallow gets me what I want in this particular case but I'm not sure about downsides in the general case.

wilkerlucio22:01:53

if you don't care about the nested parts, then it should be fine

Ben Grabow22:01:11

The recursive query handles it 🙂

wilkerlucio22:01:35

yup, makes sense

Ben Grabow22:01:00

v2:

(defn recursive-resolve
  [env entity]
  (let [entity (p.eql/process
                 env
                 entity
                 (pfsd/shape-descriptor->query 
                   (pci/reachable-paths env 
                     (pfsd/data->shape-descriptor-shallow entity))))]
    (->> (for [[k v] entity]
           [k (cond
                (instance? PersistentArrayMap v) (recursive-resolve env v)
                (vector? v) (map #(recursive-resolve env %) v)
                :else v)])
         (into {}))))

wilkerlucio22:01:00

another suggestion, use (coll/native-map? v) instead of (instance? PersistentArrayMap v)

Ben Grabow22:01:44

(defn recursive-resolve
  [env entity]
  (let [entity (p.eql/process
                 env
                 entity
                 (pfsd/shape-descriptor->query
                   (pci/reachable-paths env
                     (pfsd/data->shape-descriptor-shallow entity))))]
    (->> (for [[k v] entity]
           [k (cond
                (coll/native-map? v) (recursive-resolve env v)
                (vector? v) (map #(recursive-resolve env %) v)
                :else v)])
         (into {}))))

wilkerlucio22:01:34

also, since you are reading form vector, mapv instead of map, to keep it as a vector

Ben Grabow22:01:32

yeah lots of rough edges here (including consuming stack!) that would need to be cleaned up before shipping to prod

wilkerlucio22:01:07

one thing though, there is big drawback in mapping over the entities yourself, that is you lose the batch capatibility, since each entity gets its own request

wilkerlucio22:01:36

also, if you are not using a persistent cache for the planner (you should do it in general), it means its planning again for each instance, also not very efficient

Ben Grabow22:01:51

Luckily I am doing ad-hoc analytical work today so we get to be sloppy about performance 😆

wilkerlucio22:01:54

the caching is easy and will help a lot, you just need to make something like this:

(def env
  (-> {:com.wsscode.pathom3.connect.planner/plan-cache* (atom {})}
      (pci/register ...)))

Ben Grabow23:01:32

Planning and query resolution has been so dang fast every time I've used Pathom that it's never seemed like a priority to me. I have a little over 100 attributes and 100 resolvers in my index. At the REPL queries come back apparently instantly unless my resolvers are doing IO, and in production my typical query takes about 15 ms to plan (which is dwarfed by time spent fetching input data). One day I hope to have a graph big enough and complex enough that I will need the excellent performance optimizations you've made, but I don't need them today!

Ben Grabow23:01:14

I am glad to learn about the plan-cache though, and I will reach for it when I need it!

wilkerlucio23:01:18

great to hear 🙂

wilkerlucio23:01:52

yeah, take your time, when you do so you will see a drop to 1ms after the first run for planning 🙂

👍 2
Joel05:02:07

@UANMXF34G trying to use your function above, maybe i just don’t understand how to invoke it.

Ben Grabow14:02:45

@UH13Y2FSA can you show what you have tried?

Joel15:02:05

Error printing return value (IllegalArgumentException) at clojure.lang.RT/seqFrom (RT.java:553). Don't know how to create ISeq from: java.lang.Double (recursive-resolve env {:some/id {:body "hi"}}) I’ve used env with p.eql/process and it works. I didn’t follow what :body is.