pathom

:pathom: https://github.com/wilkerlucio/pathom/ & https://pathom3.wsscode.com & https://roamresearch.com/#/app/wsscode
2021-04-29T03:32:08.254700Z

Thank you both! ❤️

2🙏
wilkerlucio 2021-04-29T14:12:21.256700Z

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

7💪
prnc 2021-04-29T16:05:03.257900Z

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?

wilkerlucio 2021-04-29T16:09:58.258Z

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)

wilkerlucio 2021-04-29T16:11:12.258300Z

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

wilkerlucio 2021-04-29T16:20:02.258700Z

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))

wilkerlucio 2021-04-29T16:20:42.258900Z

env looks like:

(def env
  (-> {:wsscode/mamute-string (mamute/mamute-string-cache "/some/local/path")}
      (pci/register
        [...])
      (p.plugin/register (pbip/attribute-errors-plugin))))

wilkerlucio 2021-04-29T16:21:14.259100Z

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)
      "&amp;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))})

wilkerlucio 2021-04-29T16:21:42.259300Z

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

prnc 2021-04-29T16:37:17.259600Z

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:

prnc 2021-04-29T16:52:25.259800Z

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?

prnc 2021-04-29T16:53:39.260Z

I’m just thinking about some generic out-of band communication mechanism with the caller

wilkerlucio 2021-04-29T16:59:45.260300Z

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]))

wilkerlucio 2021-04-29T16:59:59.260500Z

in this case, you will see :with-meta in the output, because it was in the initial data

wilkerlucio 2021-04-29T17:00:29.260700Z

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)

wilkerlucio 2021-04-29T17:00:47.260900Z

but meta on the resolver responses (the root map) isn’t, because that is just a bag of keys getting merged in the entity

prnc 2021-04-29T17:04:07.261100Z

I see, it seems I was thinking about that root map, I was just checking…

prnc 2021-04-29T17:04:30.261300Z

(defresolver foobar [{foo :foo}]
    {::pco/output [:bar]}
    (with-meta
      {:bar {:bar-is :this-is-bar
                      :foo-is foo}}
      {:hello-meta "meta"}))

  (-&gt;&gt; (p.eql/process (pci/register [foobar])
                  {:foo "hello"}
                  [:bar])
       meta
       :hello-meta)
  ;; =&gt; nil

prnc 2021-04-29T17:04:52.261500Z

Thanks!

1👍