Fork me on GitHub
#pathom
<
2021-11-12
>
mauricio.szabo02:11:01

Hi, @wilkerlucio. Just to complement what we've been talking earlier in private: in my Duck-REPLed, I need some attributes available in all nodes/subnodes/etc all the times. The way I did this was by creating this code: https://gitlab.com/clj-editors/duck-repled/-/blob/master/src/duck_repled/editor_resolvers.cljc#L8-11. It's a resolver that have higher priority than everyone else, and it adds every information that somebody might need from env. Turns out, that's the worst idea ever that someone can do :rolling_on_the_floor_laughing:. It means that this node will be considered every time, for every query and it's slows everything down to a crawl. So I decided to, before each query, create a resolver and register it with pci/register. This works way better. Now, do you see a problem by doing this approach? Can it confuse plan caches? Is it a better way? WDYT?

wilkerlucio14:11:57

it wont confuse the cache because the cache key takes the hash of the indexes in consideration, so different indexes will end up with different cache keys

👍 1
mauricio.szabo02:11:14

BTW, I can't use Pathom-VIZ on this project: I keep getting errors like:

/home/mauricio/projects/duck-repled/.shadow-cljs/builds/tests/dev/out/cljs-runtime/cognitect/transit.cljs:418
        (WithMeta. (-with-meta ^not-native x nil) m)
        ^
TypeError: x.cljs$core$IWithMeta$_with_meta$arity$2 is not a function

wilkerlucio14:11:14

gotta see whats trying to add mat to some custom type you have that don't support it

wilkerlucio14:11:42

a dirty fix would be to support meta on your type, but that's more like pushing the problem under the carpet

mauricio.szabo16:11:54

The problem is that I have no idea what's that object, to be honest...

wilkerlucio16:11:43

you said you have a reify thing on your data, could that be it?

mauricio.szabo00:11:35

No, I converted it to defrecord and the problem is still there :(

lance.paine13:11:39

First, thanks for your amazing efforts here, this is really a super exciting project. I've been having great fun getting to grips with it. I'm using fulcro with a sparql backend for an exploratory POC project in my spare time. My data is therefore, inherently in a graph. I'm trying to figure out how that best maps to pathom and eql. I've got a couple of questions, this might look long, but I promise it's probably quite simple! :-) 1. what's the pattern for transitive, but normalized attributes? In my app, (I know, an odd choice for a learning exploration, but it's an already in RDF/Sparql open dataset! ) legal-entities (LEs) have addresses for two different reasons. legAddr (legal-address) and hqAddr(headQuarters). addresses have attributes like city, country, streetAddress. I have an query, and resolvers that's fetching the list of LEs, and some details for each LE. What I'm trying to wrap my head around, is if I want the query to ALSO grab the city of the legAddr for each LE, what does that look like? In the underlying data, legAddr and hqAddr are IRIs (so just ids), should the resolver be returning those uris as idents so the data remains normalized, and traversable? Something like {:legAddr [:address "address:addr1"]}? At the moment I can individually fetch the address details with a specific query.

example data looks something like
{
:legal-entities [{
    :id "le1" 
    :name "my company"
    :legAddr "address:addr1"
    :hqAddr "address:addr1"
  }
]
:addresses {
  "address:addr1" {
		:city "Auckland"
  }
}
So i want a query something like
;; for every le, get its name, and the city of the legal address
[{:legal-entities [:name {:legAddr [:city]}]}
[:legal-entities [
  {:id "le1" :name "my company" :city "Auckland"
]]
I've tried adding an alias resolver for :legAddr->:addressbut it doesn't seem to get triggered. I only see idents talked about in the docs in the context of mapping to graphql. What am I missing?

Björn Ebbinghaus15:11:05

(defresolver resolve-legal-entities []
  {::pc/outputs [{:legal-entities [:le/id]}]}
  ... )

(defresolver resolve-legal-entity [_ {id :le/id}]
  {::pc/inputs [:le/id]
   ::pc/outputs [:le/name :address/id]}
  ...)

(defresolver resolve-address [_ {id :address/id}]
  {::pc/inputs [:address/id]
   ::pc/outputs [:address/city]}  
This allows querying for the city directly:
{:legal-entities
  [:le/name
   :address/city]}
If you want this data nested. (For separate Fulcro components, for example) you can still use placeholders.
{:legal-entities
  [:le/name
   {:>/address [:address/city]}]}

lance.paine15:11:18

oh, am I overthinking this? I just tried something more like

(parser {} [{:address/legal-entities
               [:legal-entity/id
                :legal-entity/legalName
                :legal-entity/legAddr
                :address/city]}])
And get the flattened data i need, right now. But it doesn't keep the structure I'd want if i want the legalAddr/city vs the hqAddr/city

lance.paine15:11:17

@U4VT24ZM3 thanks! at a glance, I think you just answered my question as I was stumbling into an answer. It's a huge help 🙇

lance.paine15:11:44

I knew I'd seen this :>/ magic somewhere, but couldn't see it again!

Björn Ebbinghaus15:11:18

In the case of two (or-more) addresses, you have to decide what you need 🙂 You could just add them as joins.

(defresolver resolve-legal-entity [_ {id :le/id}]
  {::pc/inputs [:le/id]
   ::pc/outputs [:le/name 
                 {:le/legal-address [:address/id]}
                 {:le/hq-address [:address/id]}]}
  ...)

;; Query 
{:legal-entities
 [:le/name
  {:le/legal-address [:address/city]}
  {:le/hq-address [:address/city]}]}

Björn Ebbinghaus15:11:21

Of course, you can mix them depending on your use case. For example:

(defresolver resolve-legal-entity [_ {id :le/id}]
  {::pc/inputs [:le/id]
   ::pc/outputs [:le/name 
                 :address/id ;; legal-address is almost always used
                 {:le/hq-address [:address/id]}]} ;; hq-address is rarely used.
  ...)

lance.paine15:11:49

and i think you're also answering somewhat my question about returning the ident for the address.

lance.paine15:11:16

at the moment, I'm not declaring the resolver like

(defresolver resolve-legal-entity [_ {id :le/id}]
  {::pc/inputs [:le/id]
   ::pc/outputs [:le/name 
                 {:le/legal-address [:address/id]}
                
  ...)

but like 
(defresolver resolve-legal-entity [_ {id :le/id}]
  {::pc/inputs [:le/id]
   ::pc/outputs [:le/name 
                 :le/legal-address 
                 
  ...)

lance.paine15:11:38

and yet things are magically working. am i missing something in functionality, or performance?

lance.paine15:11:17

I just read today something like "tell the parser as much as you can and it will use it to try and do a better job"

Björn Ebbinghaus15:11:30

Yes, you want to tell the parser as much as possible. (Not just the parser, but also for documentation.) That the join works without you telling the parser before will depend on the implementation of the parser.

👍 1
lance.paine13:11:20

My second question: 2. What's the pattern for attributes that might exist on any id? In sparql land, any attribute can be attached to anything. So for instance, any IRI might have an rdfs:label attribute. Do I need to make a union query for all possible types in my system? Or alias every ident resolver to a label resolver? Or something else entirely?

Björn Ebbinghaus15:11:36

In this case, you can add a one-way alias:

(pc/alias-resolver :address/id :rdfs/id)

(defresolver resolve-label [_ {id :rdfs/id}]
  {::pc/input [:rdfs/id]
   ::pc/output [:rdfs/label]}
  ...)

;; Query:
{[:address/id 42]
 [:adress/city
  :rdfs/label]}

Björn Ebbinghaus16:11:14

The question you have to ask yourself is whether this is a real use case. When you know which model supports which attribute, I wouldn't generalize this and just write:

(defresolver resolve-address [_ {id :address/id}]
  {::pc/input [:address/id]
   ::pc/output [:address/city :address/label]}
  ...)

lance.paine13:11:57

Thanks @U4VT24ZM3, that's really helpful. Yes. it's possible I'm thinking about this from the perspective of the RDF/Sparql land. If you'll allow me, I'll ponder out loud here, to try and solidify my understanding. In the rdf land it's not uncommon to think of something deployable API like a 'label service', which given an entity ID (and a language) returns the appropriate label. This becomes less necessary with something like pathom in the mix. My end goal here is too look at how fulcro has done the likes of RAD backed by datomic, and make this datadriven from sparql. But I want to understand good manual practices, first. It seems the impedance mismatch here is that the coordinates for a datapoint in RDF are the entity (subject), and the predicate (attribute) -> object (in this example, the label), coloured by the open world philosophy (any one can say anything about anything). Whereas the pathom approach encodes an aspect of the type of the subject in the namespace of the attribute. This helps us by allowing the parser to hunt the graph, but has also closed the world, somewhat. By contrast the namespaces on predicates in rdf land are either some statement about who/what org has defined an ontology more so than what type it's expected to be applied on (for that, we've OWL and shacl).

Björn Ebbinghaus16:11:55

> Whereas the pathom approach encodes an aspect of the type of the subject in the namespace of the attribute. This helps us by allowing the parser to hunt the graph, but has also closed the world, somewhat. Attribute names are up to you. It is not like they are "types". You can mix and match everything. You only have to define "edges" between your attributes. And just because I said you should define as much as possible, doesn't mean you have to. 🙂 The Datomic RAD backend generates "static" resolvers based on a given schema. What may be interesting for you are "dynamic resolvers". Pathom 2 "technically" has support for them, but Pathom 3 will have "real" support. https://blog.wsscode.com/pathom-3-is-coming/#dynamic-resolvers-in-pathom-2 Maybe have a look at: https://github.com/wilkerlucio/pathom-datomic Which seems to be what you are trying to build for SPARQL.

Björn Ebbinghaus16:11:08

But considering dynamic resolvers, you should consult @wilkerlucio I know they exist and what they are for, but that's it. 🙂

lance.paine17:11:37

Thanks, I'd seen dynamic resolvers were better supported in pathom3, so hopeful for that. No matter whether I generate static resolvers, properly support dynamic resolvers, or other, the expressive power and succinctness is pretty cool 🙂

lance.paine17:11:06

i don't think I'd seen @wilkerlucio s pathom-datomic! 🙂

mauricio.szabo16:11:36

@wilkerlucio I'm having a hard time debugging an issue: I currently have the following resolver:

(connect/defresolver meta-for-clj-var
  [{:keys [var/fqn repl/clj repl/kind]}]
  {::pco/input [ :var/fqn  :repl/clj :repl/kind]
   ::pco/output [:var/meta]}

  (when (= :cljs kind)
    (eval-for-meta clj fqn 'user)))
It is never called in my graph. Even when I query for the inputs, they all resolve correctly, but the meta-for-clj-var itself never is called.

mauricio.szabo16:11:48

What I found is that if I add (pco/? :repl/clj) instead of asking for :repl/clj, it does resolve (and the input is present on the body). How do I debug this, to be able to even open an issue?

wilkerlucio16:11:11

you should try to reduce the case until you can find where exactly its failing to resolve, reporting with a simple and clear example pointing the issue is the best

mauricio.szabo18:11:06

Ok, it was 100% my fault, but I opened an issue even them because I think the behavior is strange: https://github.com/wilkerlucio/pathom3/issues/109 Long story short: the resolver for :repl/clj had a typo in the output. Even then, :repl/clj "leaked" through the graph, and in some places it was being accepted as a valid input

mauricio.szabo18:11:06

Ok, it was 100% my fault, but I opened an issue even them because I think the behavior is strange: https://github.com/wilkerlucio/pathom3/issues/109 Long story short: the resolver for :repl/clj had a typo in the output. Even then, :repl/clj "leaked" through the graph, and in some places it was being accepted as a valid input