Fork me on GitHub
#pathom
<
2023-02-02
>
Tom H.05:02:13

How can I write a resolver that puts a new key into a collection returned by another resolver? This is what I’m trying to get working:

(pco/defresolver new-key-resolver [{:keys [outside coll]}]
  {::pco/input [:outside {:coll [:inside]}]
   ::pco/output [{:coll [:new-key]}]}
  {:coll (map (fn [{:keys [inside]}]
                {:new-key (+ inside outside)})
           coll)})

Tom H.05:02:36

But when I try this:

(p.eql/process (pci/register [new-key-resolver])
    {:outside 2
     :coll [{:inside 1}
            {:inside 2}
            {:inside 3}]}
    [{:coll [:new-key]}])
I get
Execution error (ExceptionInfo) at com.wsscode.pathom3.connect.planner/verify-plan!* (planner.cljc:1688).
Pathom can't find a path for the following elements in the query: [:new-key] at path [:coll 0]

Tom H.05:02:09

But calling the resolver as a function works:

(new-key-resolver
    {:outside 2
     :coll [{:inside 2}
            {:inside 3}
            {:inside 4}]})

; => {:coll ({:new-key 4} {:new-key 5} {:new-key 6})}

caleb.macdonaldblack05:02:16

You need to feed :outside into the :coll the first time you get the coll

caleb.macdonaldblack05:02:47

for example a fetch-coll-by-id resolver. Would fetch the coll and insert the :outside key into it right there

Tom H.05:02:02

but :outside is dependent on :coll , sorry forgot to add that

Tom H.05:02:41

:outside in the particular case I’m working on is a minimum value taken from the items in :coll

Tom H.05:02:06

I suppose I could do that math in the resolver that returns :coll in the first place?

Tom H.05:02:31

but they’re currently decoupled, the :coll resolver just returns ids

caleb.macdonaldblack05:02:17

It’s a limitation in Pathom3. Nested resolvers can’t reach up or go backwards. You can only pass data down. This unfortunately ends up in coupling

caleb.macdonaldblack05:02:59

I’m working on something to address this though.

Tom H.05:02:14

I suppose I could make :coll2 that does the work

Tom H.05:02:41

and leave the decoupled ones alone

caleb.macdonaldblack05:02:59

It treats nested entities as references and tracks data with those references as a graph executes. Then you can do reverse lookups

💥 2
caleb.macdonaldblack05:02:54

(deftest product-with-order-test
  (testing "Product has a relationship with the order it belongs to"
    (let [env (ordering/create-env)]
      (is (= {:com.example.order/products
              [{:com.example.product/title "Green Apple"
                :com.example/order
                {:com.example.order.price/discount 0.1
                 :com.example/id                   "order-1"}}]}
             (p.eql/process
               env
               {:com.example/id                   "order-1"
                :com.example.order.price/discount 0.1
                :com.example.order/products
                [{:com.example/id               "green-apple"
                  :com.example.product/title    "Green Apple"
                  :com.example.product/quantity 4
                  :com.example.product/price    5.0}]}
               [{:com.example.order/products
                 [:com.example.product/title
                  {:com.example/order
                   [:com.example/id
                    :com.example.order.price/discount]}]}]))))))

caleb.macdonaldblack05:02:38

For example, this query starts at an order, nests into the products of the order and then nests further (with a reverse-lookup) to access discount

caleb.macdonaldblack05:02:11

And the resolvers to make that work look like this:

caleb.macdonaldblack05:02:37

(pco/defresolver reverse-lookup-for-products-is-an-order
  [_env input]
  {::pco/input  [{:com.example.order/_products [:com.example/id]}]
   ::pco/output [{:com.example/order [:com.example/id]}]}
  (let [{[{:com.example/keys [id]}] :com.example.order/_products} input]
    {:com.example/order {:com.example/id id}}))

caleb.macdonaldblack05:02:21

I have to use a plugin and generate some resolvers to make this work

Tom H.05:02:36

wow nice, how does it know what to use as the id attribute?

caleb.macdonaldblack05:02:25

All entities must have a com.example/id attribute in this example

caleb.macdonaldblack05:02:51

For example, the entity we pass in originally is this:

caleb.macdonaldblack05:02:02

{:com.example/id                   "order-1"
  :com.example.order.price/discount 0.1
  :com.example.order/products
  [{:com.example/id               "green-apple"
    :com.example.product/title    "Green Apple"
    :com.example.product/quantity 4
    :com.example.product/price    5.0}]}

caleb.macdonaldblack05:02:03

This is the magic plugin that makes it work:

caleb.macdonaldblack05:02:04

(p.plugin/defplugin transact-entity
  {::pcr/wrap-merge-attribute
   (fn [original]
     (fn [{:com.example.db/keys [schema] :as env} {:com.example/keys [id] :as out} k v]
       (let [env (update env :com.example/db
                   (fn [db]
                     (d/db-with (or db (d/empty-db schema))
                                [{:com.example/id id k v}])))]
         (original env out k v))))
   ::pcr/wrap-root-run-graph!
   (fn [original]
     (fn [{:com.example.db/keys [schema] :as env} ast-or-graph entity-tree*]
       (let [env (update env :com.example/db
                   (fn [db]
                     (d/db-with (or db (d/empty-db schema))
                                [@entity-tree*])))]
         (original env ast-or-graph entity-tree*))))})

caleb.macdonaldblack05:02:25

It manages state with datascript

clojure-spin 2
caleb.macdonaldblack05:02:51

It’s very experimental atm.

Jakub Holý (HolyJak)11:02:20

Hi! Do you have any tips for authorization of mutations? I was thinking about using a :wrap-mutation plugin. Ideally I would be able to attach some kind of “metadata” to each mutation (e.g. :allowd-roles #{..}) and retrieve that info in the plugin to allow/deny it…

2
caleb.macdonaldblack15:02:53

Take a look at https://github.com/souenzzo/eql-style-guide/issues/4 regarding auth in pathom. You can add your own custom attributes to the config map for resolvers and mutations and access them in a variety of ways. As far as implementation goes, you could: 1. Use the https://github.com/souenzzo/eql-style-guide/issues/4 feature which works for mutations too. 2. Use a plugin like ::pcr/wrap-mutate 3. Sanitise the EQL query before processing. 4. Filter the index and remove unauthorised resolvers before processing

caleb.macdonaldblack15:02:20

You also mention “metadata” which I just wanted to clarify would most likely be attributes within the resolver/mutation config map, along-side ::pco/input & ::pco/output

💯 2
Jakub Holý (HolyJak)16:02:30

Thanks a lot, Caleb!

Tommy22:02:00

this may also be relevant. The person working on this is around here, and its not finished AFAIK but good reference maybe