Fork me on GitHub
#graphql
<
2023-10-12
>
cch118:10:42

What is the preferred way of injecting differentiated context into Query resolvers versus Mutation resolvers versus Subscription resolvers? My first thought was to have a resolver for the root objects (e.g. :Query) but I can’t decipher the syntax that would allow that (naive attempts prevent the schema from even being compiled). Other options would be to inspect the operations to some extent before feeding the initial context into lacinia (yuck) or wrapping all my resolver fns (yuck). It’s clear from the docs that the root objects have resolvers, but it’s not clear how one would overrride/extend them.

favila18:10:01

Root objects have resolvers?

favila18:10:12

objects have resolvers?

cch118:10:29

Root objects do.

favila18:10:54

do you mean a resolver that determines the Query/Mutation/Subscription object?

favila18:10:05

or one that resolves the field of that object?

favila18:10:20

because the former AFAIK does not exist; the latter does

cch118:10:06

I want to “resolve” all Queries with a single fn before the individual resolvers on the named queries are executed.

favila18:10:38

right, but that’s some kind of object resolver. AFAIK only field resolvers exist

cch118:10:38

What I really want is to modify the context for all Queries separately from the context for all Mutations.

favila18:10:07

When I do this stuff, I have to do it in interceptor-land, before executing the query

cch118:10:14

Well, there is a :Query key in the compiled schema, and it has a :resolver key on it -and that is an internal lacinia fn.

cch118:10:44

Right -in interceptor land could work, but what happens when a single request has both a mutation and a query?

favila19:10:12

make a lacinia context with everything you need (maybe wrapped in a delay), and the root fields pick one?

cch119:10:24

That’s my current thinking, but it is not ideal. One of the goals is to make it hard for queries to use the wrong db value and for mutations to read outside the scope of a transaction (all this in Datomic, BTW).

cch119:10:05

For queries, I want to remove the connection from the context and only have a db value. For mutations I want to remove the db value and only provide a connection.

cch119:10:40

Since GraphQL is so hierarchical, I thought this would be easy. 😞

favila19:10:48

you can do this pretty easily with some helper using`l.resolve/with-context` and adjusting what’s available before calling a descendent resolver but you have to “wrap” every root field resolver.

favila19:10:30

there’s no field “edge” going into the root objects, so I’m not surprised if this isn’t possible

favila19:10:51

there’s no place to hang a resolver; there’d have to be something special

cch119:10:39

It seems like the place to hang the resolver is on the :Query object. In fact, there is already a resolver there (`wrap-resolver-to-ensure-resolver-result`)

cch119:10:55

But I see no way to wrap that resolver.

favila19:10:38

objects don’t have resolvers, so I don’t see how wrap-resolver-to-ensure-resolver-result is relevant

favila19:10:04

is there some magic field that resolves to the query/mutation/subscription objects?

cch119:10:14

My vocab may be inadequate, but it seems as though the root object have resolvers.

cch119:10:32

And I see them in the compiled schema.

cch119:10:49

(get-in <compiled-schema> [:Query :resolver])

favila19:10:38

I suspect it is never called

favila19:10:00

do any other object types have resolvers?

cch119:10:26

I see them for :Query, :Mutation and :Subscription.

favila19:10:41

my schema has nil for that

cch119:10:33

Weird,,, I can see all the keys on the compiled schema. And they include those three objects.

cch119:10:07

Try this: (keys (schema/compile {}))

favila19:10:41

You misunderstand me, I have the :Query key, but not :resolver on that

favila19:10:46

:Query is a type

favila19:10:50

not a field

favila19:10:09

(-> (l.schema/compile {})
    :Query
    keys)
=> (:category :type-name :description :fields :directives :compiled-directives :implements :tag :compiled-schema)

favila19:10:33

but fields have resolvers

cch119:10:52

As a root object, isn’t it the parent of all my queries?

favila19:10:16

yes, it is

favila19:10:29

but resolvers live on edges, and there’s no field edge to them

favila19:10:04

again, maybe there is some way, but it won’t be a resolver

favila19:10:56

I always get nil as the “parent value” for any field on a root object

favila19:10:11

I’m digging through the code to see where that is initialized

favila19:10:57

it seems to be in executor/execute-query

👀 1
favila19:10:07

I see a comment there

;; Outside of subscriptions, the ::root-value is nil.

favila19:10:59

root-value (_::resolved-value_ context) (line 388 in my version) is where it’s initialized

favila19:10:07

so you could set ::executor/root-value on your context in your interceptor, but that still doesn’t let you any closer to what you want vs using public interfaces

cch119:10:23

I’m trying to find where the multiple operations in a single request are “iterated” ….

favila19:10:01

can gql execute multiple operations in one request?

favila19:10:25

do you mean multiple fields of a root object ?

favila19:10:34

e.g. multiple Mutation fields?

cch119:10:06

Would you consider a query and a mutation in the same request as multiple operations?

favila19:10:18

yes, they have to be

favila19:10:24

because they share no object in common

cch119:10:30

Right. But two queries, no?

cch119:10:47

(I’m still learning the GraphQL vocabulary)

favila19:10:45

An operation is a (possibly named and parameterized) selection of fields from one of the root objects

cch119:10:15

OK. So it’s not possible to have multiple operations in the same request -that simplifies the problem quite a bit.

favila19:10:30

you can still select multiple fields

cch119:10:56

Right, but in terms of differentiated context, that means only one setup of context per “request”

favila19:10:57

and you can also send multiple operations, but only execute one

favila19:10:04

> To execute a request, the executor must have a parsed https://spec.graphql.org/draft/#Document and a selected operation name to run if the document defines multiple operations, otherwise the document is expected to only contain a single operation. The result of the request is determined by the result of executing this operation according to the “Executing Operations” section below.

favila19:10:18

This is gql minutia, I don’t think you’ll encounter this in the wild outside of extensions

cch119:10:02

Got it. I can imagine some scenarios where the late binding could be useful.

favila19:10:04

I know the apollo ecosystem has some extension that allows operation batching, but it’s basically http pipelining

cch119:10:30

I assumed that because the syntax of a Document can include multiple operations that it was possible execute multiple operations in a single request. Wrong!

favila19:10:20

circling back, there’s probably some way in interceptor-land to inspect the root context right before it is sent to execute-query, determine if it’s a mutation/query/subscription, alter the app-context appropriately, and then send it down

cch119:10:32

But I’m still going to have to hack something together to differentiate the contexts. Although now it’s feasible to do it once in an interceptor.

favila19:10:36

or, you can just wrap those top-level field resolvers

cch119:10:43

Yeah, we’re thinking the same thing.

favila19:10:41

honestly wrapping isn’t a bad idea in general because of how error handling works with promises. a catch-all-exceptions and with-warning/error if caught is a lifesaver

favila19:10:50

resolvers are expected never to throw

cch119:10:23

I’ll take a look at the parsed (but not executed) query and see if the operation type is easily discerned. If not, I’m going to wrappers.

cch119:10:45

As usual, you have been a big help. Thank you.

cch119:10:50

It turns out to be trivial to differentiate in an interceptor based on (:operation-type <parsed-query>).

gklijs05:10:02

What if you both have a mutation and a query on one request?