and to start the good year, another processing feature: now Pathom 3 supports optional inputs! https://pathom3.wsscode.com/docs/resolvers/#optional-inputs
@wilkerlucio in situations like
;; resolver 1
{:input [:a :b]
:ouput [:c]}
;; resolver 2
{:input [:a]
:ouput [:c]}
;; entity
{:a 1 :b 2}
Will resolver 1
*always* be called?
If possible, answer both in pathom2
and pathom3
šquick answer: no
in pathom2 that depends on the resolver weight, whatever Pathom things is the faster path, it will take it
in pathom3 there still no prioritization yet (altough that's the next thing I'll be working on), so in Pathom 3 its kind random, but will always be same, for now
@wilkerlucio will there be an alternative for :com.wsscode.pathom.core/not-found
on pathom3? starting to migrate a green field app to pathom3 and we are really happy with the simplicity š
@jmayaalv the alternative is check if the key is in the response, in case of not-found, the attribute is gonna be absent, I guess that can solve for most cases, if not, troubleshooting will come as an extension of the new "run stats" thing, hard to explain in brief, but there you can find most data needed to figure any situation, you can take a look at that by checking the meta on the responses (for EQL interface)
important thing to take note: each response map has its on run stats (each is a different run context), so if you have a response like {:foo {:bar "baz"}}
, there are 2 run stats, one for the outermost map, and one for the inner, in cases of collections, each entry has its own stats
i will check the plugins out, although absent of value should be good enough for us. Thank you !
Is there any way for a resolver to set a parameter on one of its outputs?
what you mean set a parameter on its outputs?
just trying to come up with a proper example š
so, assuming I have the following resolver:
(pc/defresolver output-resolver
[env {value :foo/intermediate}]
{::pc/input #{:foo/intermediate}
::pc/output [:foo/output]}
{:foo/output [value (-> env :ast :params :some-param)]})
I can use the query
[{[:foo/intermediate 123]
[(:foo/output {:some-param "value"})]}]
and get {:foo/output [123 "value"]}
back
(not guaranteed to be correct, I'm just typing this into a text editor)
I want to create an intermediate resolver, like
(pc/defresolver intermediate-resolver
[_ {value :foo/input}]
{::pc/input #{:foo/input}
::pc/output [:foo/intermediate]}
{:foo/intermediate value})
And within this resolver I want to determine a value for :some-param
and pass it on to output-resolver
so a query of `[{[:foo/input 123]
[:foo/output]}]` could return {:foo/output [123 "value-set-by-intermediate-resolver"]}
no, you can't change things like this sideways, if you need information to flow, add new attributes to the system, make the output of the resolver respond with more keys, and use those keys in the input forward, makes sense?
It does I guess, I primarily just wanted to find out whether it was possible. So for example, if there's a resolver that supports pagination via a parameter, and some other resolver wants to call it with pagination, then pagination will need to be made an input. Is that correct?
for context, this came up while experimenting with foreign-resolvers, and the second resolver is actually implemented in a different service
Has anyone here used Pathom as a Dependency Injection framework like @souenzzo did here? https://github.com/souenzzo/souenzzo.github.io/blob/master/conduit/src/br/com/souenzzo/conduit/ssr.clj#L270
Any thoughts about using it? (pathom2 examples could be interesting too š )
I'm looking for a db component + http component service just to example š
I'll do some comparison between Integrant and this approach
I was looking for some tips about this
@d.ian.b dependency injection in Pathom is accomplished by env, you can add things to env at call time (when calling the parser), or in any point you want, so you can pull those in any part of the system, makes sense?
ok, foreign is still a very exploratory area, but I would try to avoid going too deep on this idea, since from the Pathom perpective that is hard to happen, one way to think of it is that params are always a "local" thing, while inputs are things that flow across
The concept of parameters is interesting in that it's orthogonal to outputs. However, if they cannot be built on with other resolvers, that could make it risky to depend on them
one acceptable thing is for a resolver to accept some data as input OR params, this way you can have both options
the just released optional inputs
on Pathom 3 can help with that, so you can have soft dependencies on specific attributes
but about foreign, its still experimental area, more work on it will come in Pathom 3, and feedback is very appreciated, if you have some cases you like to share, I'll love to hear about them
another alternative to sideways communication is to have some mutable state in the env, you can add a new atom there, modify from one resolver and read in the other, this is a tricky option and I suggest a lot of care if you try this path, but its there
Hey @wilkerlucio I kinda did some cursory poking around and couldnāt find anything analogous to async parsing in pathom2 so that I can use core.async with pathom3. Any pointers?
its on the backlog, but not available in Pathom 3 yet
but I hope an initial version of it should land in the next few weeks
I will try doing some comparison between Integrant / Pathom then š
imo dependency injection is quite easy in Clojure because of immutable data structure. You can use Pathom, Prismatic Plumbing, Integrant or even Datascript. The (harder) related problem is starting/stopping components and that's what Integrant (and Duct) does
why not start / stopping components via pathom?
Thank you for the insight. Fortunately in this case the foreign service is under control so we can change it. Will be a bit of extra work but I certainly wouldn't want to use the lib in a non-intended way. I'm sure we'll get back to you wrt foreign resolvers š
you can, and for that I suggest you can use the https://pathom3.wsscode.com/docs/cache#custom-cache-store-per-resolver to share a cache on the resolvers that are for building up initialization
this way you can make them persistent, at the same time they will delay initialization until something in the system needs them
@d.ian.b if you want I can review the code with you once you have it
altough, there is no stop š
and if I want to use pathom2 for this?
again, altough you can, I think something like "component" still a better option, because on Pathom you can't stop in reverse order, so not as good to this kind of initialization
will be merged soon
(in this value2 is always tried first, default priority is 0
, higher numbers go first)
Does anyone know where an example is in the documentation (or anywhere, really) about how to construct a query for a resolver with two items in ::pc/input
?
e.g. ::pc/input #{:item-1 :item-2}
https://blog.wsscode.com/pathom/#_multiple_inputs indicates that it can be done, as the āinput to a resolver is a setā, but I canāt find any examples anywhere.
@kendall.buchanan the example you sent is correct, are you having troubles to make it work?
I just donāt know what a query looks like that resolves to that resolver.
[{[[:item-1 "hello"] [:item-2 "world"]] [:what-im-fetching]}]
This doesnāt appear to work.
The best I can come up with is to combine them into a single ::pc/input #{:item-1+2}
with a query like [{[:item-1+2 {:item-1 "hello" :item-2 "world"] [:what-im-fetching]}]
Good to hear from you @wilkerlucio, by the wayāwe had dinner together a couple years ago at Clojure/conj š. One of our devs has jumped all in with Pathom; Iām just getting my feet wet.
@kendall.buchanan you can find an example here: https://blog.wsscode.com/pathom/v2/pathom/2.2.0/connect/resolvers.html#_multiple_inputs
but other than a query itself, you could start from one data point, then a resolver realizes those 2 attributes, which then calls the next one
glad to hear about you too, great times at Conj, can't wait for us to be able to do another one of those again š
Okay, Iām wondering if Iām genuinely not understandingā¦
`[{([:customer/id 123] {:pathom/context {:customer/first-name āFooā :customer/last-name āBarā}}) [:customer/full-name]}]`
hello everyone, another feature out š! Now you can express different priorities for resolvers, so in cases that pathom need to choose, you can have a say on it: https://pathom3.wsscode.com/docs/resolvers#prioritization
lets go by steps š
the [:customer/id 123] will set the :customer/id
value, and the :pathom-context
(when sent as a param to the ident) merges that data in as well
::pc/input #{:customer/id :customer/first-name :customer/last-name}
you probably don't need those 3, you could use any ident there
Pathom is about "shape matching", so imagine that if you have enough to trigger a resolver, pathom will do it
Sureā¦
But some data requires more than one input.
Hereās a more concrete exampleā¦
I have a user with multiple logins across multiple apps.
Trying to fetch a record of the most recent login.
I can build a chain of resolvers that will eventually get me that information, from a single user IDā¦
But that inevitably passes through many more layers than is necessary.
Assuming I can pass a user ID, and an app ID.
(This is not my exact situation, but it simplifies it.)
here is a full example:
(ns com.wsscode.demos.multiple-inputs
(:require [com.wsscode.pathom.core :as p]
[com.wsscode.pathom.connect :as pc]))
(def users-db
{1 {:user/first-name "Sam"
:user/last-name "Rock"}})
(pc/defresolver user-by-id [env {:keys [user/id]}]
{::pc/input #{:user/id}
::pc/output [:user/first-name :user/last-name]}
(get users-db id))
(pc/defresolver full-name [env {:user/keys [first-name last-name]}]
{::pc/input #{:user/first-name :user/last-name}
::pc/output [:user/full-name]}
{:user/full-name (str first-name " " last-name)})
(def registry
[user-by-id full-name])
(def parser
(p/parser
{::p/env {::p/reader [p/map-reader
pc/reader2
pc/open-ident-reader
p/env-placeholder-reader]
::p/placeholder-prefixes #{">"}}
::p/mutate pc/mutate
::p/plugins [(pc/connect-plugin {::pc/register registry})
p/error-handler-plugin
p/trace-plugin]}))
(comment
; just use the ident, pull name from resolver
(parser {} '[{[:user/id 1] [:user/full-name]}])
; provide data, note we use an id that dont even exist
(parser {} '[{([:user/id 3] {:pathom/context {:user/first-name "Foo" :user/last-name "Bar"}})
[:user/full-name]}])
; to show the ident doesn't matter, changing it for anything else
(parser {} '[{([:whatever-we-want "bla"] {:pathom/context {:user/first-name "Foo" :user/last-name "Bar"}})
[:user/full-name]}]))
the :user/full-name
does what you said, depending on multiple attributes, and getting then from the fake db load
But what if this person has multiple names he goes by. You know what I mean?
In this example, the first and last name are derived automatically.
not sure if I get what you mean
what you mean multiple names?
(def users-db
{1 {:public-name {:user/first-name "Sam"
:user/last-name "Rock"}
:secret-name {:user/first-name "Ram"
:user/last-name "Sock"}}})
in the core its about attribute relations, and how one attribute relates to another, so in this case we are expressing what a full name means
that example you just sent is a different thing
Thatās what Iām trying to get at: how do I get to my data assuming there is no derivation for the second piece of data.
its not just multiple inputs, but nested inputs, when you need to depend on some deeper shape, you would have to make a decision of which to pick in the end somehow
That the query is fundamentally incomplete without two data points.
I woulnd't say incomplete, it just depends on your system
that example I show on full name is a common one, and in most systems the user will only have one field for it
then its about modeling the semantics of your system
the nested thing you sent is possible in Pathom 3: https://pathom3.wsscode.com/docs/resolvers#nested-inputs
but I feel like this is not what you thinking about, I fell like you have some misconception we didn't figure here yet
I see where youāre going about nested resolversā¦
Or subqueries, I suppose.
But, why is a subquery necessary if I already have my data?
If I know X and Y, why can I not write a resolver that reacts to the existence of X and Y?
sometimes you dont
as the aggrgation example I show in the docs
you can
I still dont understand what you are trying to do
Take the example you posted hereā¦
what is the shape you have? what do you want in the end?
Assume Iām looking up a name, based on an ID.
(Not caring for :customer/full-name, just looking up a name.)
ok
But the user has multiple names, of different types.
lets give names, you want :user/name
, you have :user/id
Noā¦ not quiteā¦
what you mean multiple names? are from multiple sources?
can you give an example?
Yeah, lemme write out some dataā¦
its helps to think about the output shape you want, so we can build the chain from it, and in the end you have to give names to every attribute you want to return, thinking like that often helps me figure it out
You know what, I think youāre right: itās about the semantics of the keys.
Rightā¦
Iām imagining my resolver working with this:
(def users-db
{1 {:public-name {:user/first-name "Sam"
:user/last-name "Rock"}
:secret-name {:user/first-name "Ram"
:user/last-name "Sock"}}})
When, I think what youāre saying isā¦
(def users-db
{1 {:user.public/first-name "Sam"
:user.secret/first-name "Ram"}})
yup
In other wordsā¦
in Pathom its good to keep things as flat as possible
It pushes the burden of shaping the data further down into the system.
gives more leverage
(In my real world example.)
But how would that work when user.x
is unknown/
?
:user.x/first-name
Or infinite options.
you can't do that, Pathom must know about the attributes of the system, infinite attributes is not a thing
Iāll have to think on that.
because Pathom is based on index processing, it has to know ahead of time about all relationships
I'm curious where you land, IME this has never been a limitation so far, maybe you find one, hehe
The situation I have is thisā¦
We have an infinite number of users (with ids)ā¦
And they play āgamesā, I suppose you could call them.
And the number of games they play is infinite.
But the number of times they play the game is also infinite.
So, Iām trying to fetch a record of their most recent session, given a user ID, and a game ID.
Simple SQL query.
WHERE clause takes two params.
But I canāt understand how this translates to Pathom.
Takes two joins in SQL across three tables. Itās a junction, essentially.
And I donāt see how Pathom handles a junctionāwhere two indexes are required to resolve it.
(Thank you for all this attention, by the wayāsurely you have important things to be doing.)
it really helps if you start naming the attributes that participates in this query, I dont see any unbounded attributes in what you said, from the messages I can extract: :user/id
:game/id
, :session/id
(maybe), I mean, Pathom is a connector, you can make the resolver do anything
what you described looks like a resolver with a query
the session is another table?
The āclientā has the :user/id
and :game/id
, and is seeking a :session/id
.
what you want to get select * from game_session where userid = 10 AND gameid= 20
, like this?
and then, from the session, what data you expect out (name in attributes)
Yeah, more or less.
?
:session/id
.
That is whatās needed.
(pc/defresolver user-session [env {user-id :user/id game-id :game/id}]
{::pc/input #{:user/id :game/id}
::pc/output [:session/id]}
(let [res (fake-sql (str "select session_id from sessions where user_id=" user-id " and game_id = " game-id))]
{:session/id (:session_id res)}))
something like this
you probably want a different resolver to generate those combinations
something like:
Yeah. My original question was how to construct the EQL for that: ::pc/input #{:user/id :game/id}
(pc/defresolver games-from-user [env {:keys [user/id]}]
{::pc/input #{:user/id}
::pc/output [{:user/games [:user/id :game/id]}]}
; assume this returns a list of game ids
(let [res (find-games-from-user env id)]
{:user/games (mapv #(hash-map :user/id id :game/id %) res)}))
now you can run: [{[:user/id 1] [{:user/games [:session/id]}]}]
its really up to you to model, its just different, and takes some practice to adjust
Yeah. IMO, this is constraint that I donāt understand.
Two constraints it imposesā¦
Prevents the resolver writer from tailoring more performant resolvers.
I suggest you take a read on this page: https://pathom3.wsscode.com/docs/planner
And limits the type granularity of responses for the client.
this explains how pathom 3 plans for the graph, pathom 2 is bit different, but the main idea is the same, when it sees something the user wants, it traverses the indexes for that attribute, to figure how to reach it
But if I have a SQL query that can fetch one item faster than N, your example suggests it canāt be used.
you can create as many paths as you want, so you can make a direct one if you want to
and we are limiting our chat here around "static resolvers"
to get as efficient as what you saying, there are the dynamic resolvers (still getting mature), but their ability is to have dynamic inputs and outputs, that responds depending on the user requirement (and dependencies requirements)
this is done to call GraphQL for example, where it would be very inneficient if each attribute made its own call
In other wordsā¦
::pc/input #{:user/id :game/id}
so the dynamic resolvers have a different thing, they can optimize laterally
There is no way to satisfy this, correct?
what you mean you can't satisfy?
In a single trip to the resolver index.
you can provide the data for it, or provide resolvers to reach it
you can provide the data for it
have you seen what the indexes look like?
What does the query look like?
I sent you at the two final examples:
(comment
; just use the ident, pull name from resolver
(parser {} '[{[:user/id 1] [:user/full-name]}])
; provide data, note we use an id that dont even exist
(parser {} '[{([:user/id 3] {:pathom/context {:user/first-name "Foo" :user/last-name "Bar"}})
[:user/full-name]}])
; to show the ident doesn't matter, changing it for anything else
(parser {} '[{([:whatever-we-want "bla"] {:pathom/context {:user/first-name "Foo" :user/last-name "Bar"}})
[:user/full-name]}]))
using :pathom/context
param is a way
you can also provide though the env
, setting ::p/entity (atom {:data "here"}
And either key can be in the tuple?
yeah, the key there doesn't matter
this is more a legacy from Fulcro integration, in this case (for Fulcro) the ident part can tell which local table it should use, without matter what the rest of the context is
this an example providing data via env:
(parser {::p/entity (atom {:user/first-name "Foo" :user/last-name "Bar"})}
'[:user/full-name])
([:user/id 3] {:pathom/context {:game/id 1}}])
is the same as ([:game/id 1] {:pathom/context {:user/id 3}}])
yeah
Okay. Iām assuming that was never desirable, right?
it only shapes your output shape (which will match the ident)
Justā¦ legacy, like you say.
its was a later addition to support extra inputs at query level
K, Iāll give all your feedback some thought for how I construct our actual resolvers. Thank you again. Big help. Love the project.
If I had any feedback, it seems to me that multiple params in the queries ought to be natively supported, but perhaps I just have more adjusting to do.
in Pathom 3 you can use placeholders for a simpler version of it
You feel like itās ready for real use?
not yet, but its quite easy to implement that feature in Pathom 2, I think I'll do that today, because you are not the first person asking, hehe
Oh good, hehe. Thank you !
you can see what it looks like here: https://pathom3.wsscode.com/docs/placeholders#provide-data
Pathom 3 is getting there, I think its good for experiments and small usages, but its lacking tooling, so when things start to get complicated, the tooling can be quite missed
but in terms of core features, it has a lot done already