Thank you both! ❤️
for Pathom 3 users, a planner fix just landed on master, related to partial unreachable paths on attributes with multiple paths, example user report: https://github.com/wilkerlucio/pathom/issues/192, fixed on Pathom 3 master 30f62a10ee78d678821a769ba15dece314d3dfc5
How one would deal with “writes” inside of resolvers, if at all? Context: In the process of resolving EQL query, data is pulled from a third-party API—I would like to both save it to db and return to client, what’s the cleanest way to do it with pathom3?
I suggest you don’t do it this way, semantically resolvers should be free of side effects (exceptions are logging and caching), so better to break the process, read it first, and them use the returned value to side effect something. but your problem seems more related to caching, which I see as a different dimension, as I understand what you like to cache that external call in some DB, if that’s the case, you can write a custom cache store and them use it on the resolvers you want to cache (using https://pathom3.wsscode.com/docs/cache#custom-cache-store-per-resolver)
one difficulty when trying to save to a generic store is that the cache key of pathom uses rich data (vector with maps), which isn’t usually a good key for an external storage, the simple solution is use the hash of the cache-key, there is some collision risk here, so need to evaluate on a case by case basis
I have a system that I’m doing caching like that, but I decided to go with a simpler cache keying mechanism, this only stores strings (as I expect to read HTML, JSON, XML…)
(defrecord MamuteStringCacheStore [base-path]
p.cache/CacheStore
(-cache-lookup-or-miss
[this cache-key f]
(let [path (str base-path "/" cache-key)
val (and (fs/exists? path) (slurp path))]
(if val
val
(let [val (f)]
(if-not (string? val) (throw (ex-info "MamuteStringCache can only cache strings." {:value val})))
(io/make-parents path)
(spit path val)
val)))))
(defn mamute-string-cache [base-path]
(->MamuteStringCacheStore base-path))
env looks like:
(def env
(-> {:wsscode/mamute-string (mamute/mamute-string-cache "/some/local/path")}
(pci/register
[...])
(p.plugin/register (pbip/attribute-errors-plugin))))
and a resolver getting geolocation data from Google Maps API:
(defn gcp-geolocation [address]
(slurp
(str
"<https://maps.googleapis.com/maps/api/geocode/json>"
"?address=" (url-encode address)
"&key=" (get-key))))
(defn address-cache-key [address]
(str "geocode/" (str/trim (str/replace address #"[^\w\s]+" "")) ".json"))
(pco/defresolver address-json [env {:keys [gcp.geo/address-string]}]
{:gcp.geo/address-json
(p.cache/cached :wsscode/mamute-string env (address-cache-key address-string)
#(gcp-geolocation address-string))})
note the usage of p.cache/cached
inside of the resolver, this way I don’t have to deal with the resolver standard cache key on this case
The db would be datomic so maybe I could store “rich data”, would have to look closer what it is. Thanks Wilker, will check out the docs on caching! Seems to be a good fit for data that has stable query -> result relationship ofc, maybe not for things that are changing e.g. “get latest news” etc. Cheers :beach_with_umbrella:
BTW meta-data on the resolver output map will not survive the process, right? it’s replaced by pathom’s own meta not merge, if I understand correctly?
I’m just thinking about some generic out-of band communication mechanism with the caller
the meta is preserved, but not from the resolver root return, its preserved from the process “initial data”, for example:
(meta (p.eql/process (pci/register [(pbir/constantly-resolver :x 10)])
^:with-meta {:foo "bar"}
[:x]))
in this case, you will see :with-meta
in the output, because it was in the initial data
in case of resolvers that return maps (or sequences of maps), those maps are also initial data, and their meta should be preserved (and pathom adds the run stats on top of it)
but meta on the resolver responses (the root map) isn’t, because that is just a bag of keys getting merged in the entity
I see, it seems I was thinking about that root map, I was just checking…
(defresolver foobar [{foo :foo}]
{::pco/output [:bar]}
(with-meta
{:bar {:bar-is :this-is-bar
:foo-is foo}}
{:hello-meta "meta"}))
(->> (p.eql/process (pci/register [foobar])
{:foo "hello"}
[:bar])
meta
:hello-meta)
;; => nil
Thanks!