Fork me on GitHub
#pathom
<
2023-03-10
>
Joel19:03:59

Since a mutation stores the result by the function symbol, then one can’t invoke the same mutation 2x and expect to use the results?

wilkerlucio19:03:51

you can, and it will run once for each call, but there is a problem in the part of the getting the results for each of them: https://github.com/wilkerlucio/pathom3/issues/157

Joel19:03:12

@U066U8JQJ I was already wishing I didn’t have to change the name of my function to handle different attributes. If I could use a map to express that when parameter x is a then output b, that would avoid boilerplate for me. It would be really nice if I could use defprotocol, but that doesn’t help pathom to know what’s going on… but maybe the defrecord/map/deftype could implement an alias-output function?

wilkerlucio19:03:14

this can't be something in the mutation config, because if it is, it would apply to all mutations of that same name, which keeps the same problem

wilkerlucio19:03:43

here is a decision matrix on the possible ways I'm thinking to handle this issue: https://docs.google.com/spreadsheets/d/1M7DZyPJHjyWFVdAVv9JRJcRwEnHPRgRvcjxGZSFB6UE/edit#gid=0

wilkerlucio19:03:48

love to get more input/ideas on it

Joel19:03:19

The alias one is along the lines of what I was thinking.

Joel19:03:26

I presume what that means is that the parameter list might have ['function-call {:alias-output ::my/output …} and then the response wouldn’t be stored to /function-call but rather to ::my/output instead? @U066U8JQJ

wilkerlucio20:03:22

yes, something like that

Joel20:03:39

So, could this mechanism be extended to use a protocol?

Mark Wardle07:03:28

Can a placeholder work here? Nest the call? For those calling a single mutation, get the answer. If you want to make multiple calls, then you must provide your own structure?

Mark Wardle07:03:09

Client then explicitly chooses what to name the result of that instance of the same call?

wilkerlucio23:03:54

@UH13Y2FSA what you mean by extended to use a protocol? what kind of extension you wish you could make there?

wilkerlucio23:03:58

@U013CFKNP2R a placeholder is an interesting idea, and actually works already:

(pco/defmutation foo [env {:keys [x]}]
  {:bar x})

(def env
  (-> {}
      (pci/register foo)))

(comment
  (p.eql/process env
    [{:>/a [`(foo {:x 4})]}
     {:>/b [`(foo {:x 10})]}]))
=>
{:>/a {com.wsscode.pathom3.demos.mutation-outside-root/foo {:bar 4}},
 :>/b {com.wsscode.pathom3.demos.mutation-outside-root/foo {:bar 10}}}

👍 2
Joel23:03:44

Just a way to be able to change the values going to the mutator w/o having to vary the name of the function. I haven’t tried, but I suspect I could generate functions/ops and then hide that from the user by having a convenience function generate the pathom mutation call though. What does this notation mean? :>/a

wilkerlucio23:03:55

@UH13Y2FSA if you write an extension wrapping the mutation calls you can probably do something like that, what kind of thing you are trying to accomplish by centralizing the mutations under a single name?

Joel23:03:38

We have calls storing data in a key/value store. So, say for a given “object” each property will need a different function name it sounds like. e.g. put-object-property-a put-object-property-b put-object-property-c, etc… Rather the signature was more like (save-object {:property :a :value "x"})

wilkerlucio00:03:53

you need some special threatment depending on the property you are storing? or is just a generic, open thing where the user should be able to store any property it asks for?

wilkerlucio00:03:13

I mean, I'm just thinking the border, after that you could have an allow list of properties the user might affect, and reject any property that isn't there

Joel01:03:08

The properties are limited, there’s a known list, but that doesn’t help distinguish the responses (output).

Joel02:03:31

the placeholders are interesting. I wanted to resolve my mutation parameter(s), but the problem was it conflicted with a resolver: phone# resolver: (person id) => phone# mutation: (person id, new phone#) => stored phone number So it was resolving the existing phone number and then storing that, rather than the one from the request. So, instead if the request resolves to a place: (:>/new {::phonenumber "value"}then i could have the mutation resolve to that one? But then I still couldn’t use '(save-person {::person-id "xyz" :attribute ::phonenumber, :value "5551212"}) as that would overlap response data to save-person. I’d need a (save-person-phone-number, and it just seems like those names could easily bifurcate. Besides it seems much more consistent to keep working with the attribute keywords rather than getting a weird mutation naming scheme to accommodate.

Mark Wardle10:03:33

If I understand your requirement @UH13Y2FSA I would separate your resolvers from their implementation. So keep your resolvers simple - and use placeholders to make multiple mutations in one request/response cycle. I try to keep my resolvers as simple as possible - delegating to functions. They could use other ways to implement polymorphism such as multimethods. Have a single save resolver and implement the choice of how to save based on the contents of the data passed in to the resolver.

Joel15:03:53

@U013CFKNP2R Multimethod makes sense for parameters/input, but as far as pathom long-term design seems awkward to be forced to use placeholders for the output.

Mark Wardle15:03:12

I use placeholders only for when client side I need structure - or when my queries are parameterised and I need to make more than one call to the same resolver with different parameters. I use a mutation save-form that takes arbitrary data and does whatever is necessary - I don’t have different mutations with names defining the parameters they take.

wilkerlucio19:03:34

I also smell this situation is similar to when someone wants to output some attribute under a different name, in GraphQL there is some specific syntax for it (like:

query GetEntries {
  entries {
    id
    updatedAt: updated_at
  }
}
)

wilkerlucio19:03:42

EQL doesn't have a similar feature in the syntax, but we can accomplish the same using some special parameter

wilkerlucio19:03:16

I remember, that via plugins you can already implement something like that for attribute reads, but I don't remember if its also possible to make something for mutations, later I'll check it and get back to you

👀 2
Mark Wardle20:03:16

My impression is that @joel is over complicating this. Can you share more information about how you might call your mutation and what results you expect back? I understand you might want to call more than one mutation in a single request, but what is the output and does it vary considerably or have a core set of result props and some that vary based on what was passed in? When things get tricky, it is also helpful to take Pathom out of the equation - write down the inputs and outputs through functions and then re-introduce Pathom to provide your API atop those functions.

wilkerlucio23:03:18

sorry the delay folks, here is how to make a plugin to rename outputs, and it does work with mutations too (sort of):

(ns com.wsscode.pathom3.demos.alias-output
  (:require [clojure.set :as set]
            [com.wsscode.misc.coll :as coll]
            [com.wsscode.pathom3.connect.indexes :as pci]
            [com.wsscode.pathom3.connect.operation :as pco]
            [com.wsscode.pathom3.interface.eql :as p.eql]
            [com.wsscode.pathom3.plugin :as p.plugin]))

(p.plugin/defplugin alias-param
  {:com.wsscode.pathom3.format.eql/wrap-map-select-entry
   (fn [select-entry]
     (fn [env source ast]
       (let [entry (select-entry env source ast)]
         (if-let [new-name (get-in ast [:params :rename-as])]
           (coll/make-map-entry new-name (val entry))
           entry))))})

(pco/defresolver some-data []
  {:foo "bar"})

(pco/defmutation some-call [{:keys [x]}]
  {:call (str x " result")})

(def env
  (-> {}
      (pci/register [some-data some-call])
      (p.plugin/register alias-param)))

(comment
  (meta (set/rename-keys (with-meta {:a 1} {:some "meta"}) {:a :b}))

  (p.eql/process env [`(:foo {:rename-as :xfoo})
                      `(some-call {:x 1 :rename-as ~'whatever})
                      `(some-call {:x 2 :rename-as ~'whatever2})])
  (p.eql/process env [:foo]))

👀 2
wilkerlucio23:03:43

@UH13Y2FSA if you run and check the output, you will see that this still doesn't fix your case:

=> {:xfoo "bar", whatever {:call "2 result"}, whatever2 {:call "2 result"}}

wilkerlucio23:03:27

altough we have 2 names in the output, they have the same result (whatever is the last one), that's because the entry point I'm using is the one that happens after the process already has finished, so the information was already lost at that point

wilkerlucio23:03:54

altough I have interest in the problem of multiple mutations, I think there are some other ways to handle your case

wilkerlucio23:03:13

if you plan to have multiple calls happening frequently, one idea is to make your mutation accept lists of things to do, this way you will have full control over the output, and the mutation will also have more visibility, which means you can optimize things if they make sense (for instance, if your input has multiple entries to change the same row, you can get those together and issue a single request to the database to change it, if the db supports such thing)

wilkerlucio23:03:43

examples of input/output like Mark asked would be nice for us to help you design such API

Joel00:03:53

In my case, I’d tend to have a thread for each property, eg, phonenumber/email/someotherattribute for a person being saved separately. Since pathom already handles that, it would be natural to have each as a separate entry. However, if I’m to use the returned info, then I suppose it makes more sense for me to lump them, and handle the thread results in my code, but then return everything into one attribute. '(save-person {::person-id "xyz" ::phonenumber "5551212" ::email "", ::someotherattribute "value" ...}) => thread saves, wait for returns, return result as one. It sounds like placeholder would serve better though. I’m not stuck, I was just throwing in my 2cents.

Mark Wardle16:03:11

I’ve been reflecting on your issue - if you have multiple threads making independent saves then why do you need multiple calls in one call to Pathom? I think I am misunderstanding your requirements but rereading this thread, it seems that there are two issues for you. The first is making multiple calls to Pathom with the same resolver or mutation. The answer is to use placeholders. The second is to have a single mutation that can return different things depending on what attributes were passed in? In a mutation, that is definitely possible. Remember the attributes to return are the choice of the client - your resolvers can return a simple identifier and so long as everything else is resolvable from that, it works. You use a mutation join to choose the attributes you want from a mutation and so if your client wants to update a single attribute and receive only a limited set of attributes in return, that’s possible.

Mark Wardle16:03:52

I’ve had it happen to me so often with libraries and frameworks that I feel as if I am fighting the system until I realise there’s an idiomatic way of achieving what I thought I wanted, usually far simpler that I had envisaged. It might be better to disregard what you’ve tried so far and just give examples of exactly what you need- both from the perspective of the client and that of the service.