pathom 2024-04-30

How should resolvers express output keys that may not always be present in the returned value? Broadly, I think options are: 1. Don’t put them in the resolver output at all, resolvers that depend on them should pco/? those keys as input. (I don’t have a good understanding of the consequences of optional input for query planning. It also feels like it causes pco/? to proliferate just for the sake of nil-punning resolver function code.) 2. Make sure the keys are always present, even if have to use a sentinel value like nil. Use pco/? on input less often. (This feels like it frustrates key-speccing by making everything s/maybe. I’m also not sure if it’s legal to mention an output-key you don’t always provide.) 3. Something else? Maybe case-by-case? The concrete scenario I’m encountering is a resolver that returns the result of a (static) pull expression. The result is nested. Other resolvers have (sometimes deeply) nested inputs and many expect to be able to produce a non-nil value based on the absence of any value in the path (typical get-in style nil-punning). Datomic never includes nil values in its pull expressions, so I am unsure which approach to take.

caleb.macdonaldblack 2024-04-30T19:04:57.319519Z

when the resolver can’t return the value, use ::pco/unknown-value as the value instead.

caleb.macdonaldblack 2024-04-30T19:05:41.278739Z

if the query requires the value, it will look elsewhere, or fail (which is what you want probably)

in principle, all output items are potentially optional, its better to say it can be there and not return than the other way around. because if you dont provide the info, Pathom wont know that path is possible, this is especially important for nested requiremnets, because Pathom 3 will look ahead to see if a potentional nested requirement is reacahble, and if not it will discard that path

👍 2
caleb.macdonaldblack 2024-04-30T19:06:56.530199Z

@wilkerlucio ah righto. So {} is just as good as {:prop ::pco/unknown-value} yeah?

@caleb.macdonaldblack yup, the unknown value is useful when you have a conditional statement and wanna have the option to "dont provide the value", so you use the unknown value in such cases

btw, a good time to point out there is a little helper plugin to even warn you if you return something from a resolver that wasn't declared, because that usually means a misconfiguration: https://github.com/wilkerlucio/pathom3/blob/main/src/main/com/wsscode/pathom3/connect/built_in/plugins.cljc#L123-L147

@caleb.macdonaldblack to give a concrete example, this is a common scenario where I use unknown-value:

(pco/defresolver some-resolver [{:keys [input-thing]}]
  {:output-thing
   (if input-thing
     (str "modified" input-thing)
     ::pco/unknown-value)})

Ah, it’s more ergonomic than a when-some. Lets you return the map entry structurally, but as if it wasn’t returned

caleb.macdonaldblack 2024-04-30T19:11:02.309359Z

Makes sense. Not to be confused with:

(pco/defresolver some-resolver [{:keys [input-thing]}]
  {:output-thing
   (when input-thing
     (str "modified" input-thing))})
Which resolves nil for :output-thing,

👍 1

this way I can leverage the output inference, at the same time I can say that value is unknown, which is effectively the same as not returning that key (pathom will completly drop it). good to remember that for Pathom, a nil is different than a not defined value, a nil is considered a resolved value (Pathom wont try to find a way to get it anymore) while not having a value means an unknown, and if possible, Pathom will try to find a way to get it

I’m not sure in my particular case whether returning nil would be better. In a certain sense my resolver is probably terminal: if a pull doesn’t find it, it isn’t findable; otoh it makes resolvers less composable, because I couldn’t add one later that computes it another way.

it depends if you wanna short-circuit that attribute processing or not

caleb.macdonaldblack 2024-04-30T19:17:39.978549Z

returning nil is probably an anti-pattern

if you return nil is like saying: "I know this attribute is nil, stop here", while the other way is more like: "I can't respond for this attribute, if there is another way path to it, go try it"

One thing that really gets me though is the ergonomics of nil-pun style path access. E.g. a “get-in with not-found value” needs pco/? wrapping every element of the path. Is there any downside to that? (I could easily make a resolver-constructor utility that does this)

@caleb.macdonaldblack not really, in the biggest Pathom project I know we do use it quite a lot, because there are many paths for many things, but in a lot of cases we do like to short-circuit it because we know in some cases its better to just stop, which makes the process faster

@favila sorry, I dont get the last question, can you give an example?

caleb.macdonaldblack 2024-04-30T19:19:40.649929Z

@wilkerlucio fair enough.

Say a resolver produces a deeply-nested value, e.g. from the result of a datomic pull. Then you want another resolver to produce the equivalent of {:denormalized-derived-value (or (get-in [:a :b :c ...]) ::missing)} The :input of this resolver would be [{(pco/? :a) [{(pco/? :b) [...]}]] which feels awkward.

will pathom exhaust every possibility of getting an input before calling a resolver where that input is optional?

anyway with datomic data expecially pco/? is essentially everywhere; only if your domain-model has some extra constraint can you remove it.

if its optional, then altough awkward its the right thing to do here. Pathom does the optional processing in a different "lane" then the main one, so it wont fail if those are unavailable. if you need it a lot like that, you can write some helpers to traverse the query and add the optionals, since its all data its quite trivial to implement

you can write a helper like: (make-all-optional query)

👍 1

Ok, thank you very much. I’m still not sure which approach to take here, but I understand the tradeoffs better!

It’s especially good to know that providing widest-possible output expression is desirable all the time.

🙏 1