Fork me on GitHub
#graphql
<
2017-10-24
>
eoliphant14:10:17

Hi, quick question. I’d asked about pagination approaches a while back, I’ve since done some reading (and coding) and ended up implementing the Relay Connection Spec for a few of my objects/queries. So where I had a :person query before that just returned a list of :Person’s I now have that guy typed as a :PersonConnection that has the edges, cursors and what have you. This works fine, but since there are tons of things that are lists, it seems very not DRY to have to create Edges and Connections for everything. Any suggestions on a clean way to do this. I’ve prototyped something that just generates them based on an extra metadata tag I added, works ok, but just curious about what other folks are doing. I found this JS example, which uses basically parameterized types for edges, etc. , but not sure how to achieve this with lacinia: https://medium.com/@mattmazzola/graphql-pagination-implementation-8604f77fb254

rickmoynihan10:10:03

GraphQL interfaces perhaps?

eoliphant14:10:02

Interfaces don’t seem to work as far as I can tell because your Edges and what have you need to be ‘typed’. Interfaces allow you to say “this object should have field a of type String” To create an Edge for instance the cursor field is always a String, but the node field is whatever the underlying type is. I think you need something like a parameterized type. which of course graphql doesn’t support. That article I posted gets around that because they’re calling functions in the schema def that give you the same thing

stijn19:10:43

@U380J7PAQ: we do this by walking the schema and updating every type that needs relay connections

stijn19:10:34

i'm writing a blog post on how to handle 'graphql generics' in lacinia, but it's not quite ready yet 🙂

eoliphant15:10:50

@U0539NJF7 yeah, that’s kinda what I was thinking I was going to have to do lol. I saw a Lacinia video from Conj where this (https://github.com/workco/umlaut) was referenced. Have been playing with it as well. It’s more a generic schema that you can use to generate lacinia edn, graphql, etc etc. I’ve forked it and have been hacking some enhancements that might make it easier. Then conversely, I read an article somewhere about not necessarily ‘genericizing’ Edges and connections and what have you as they should, if the situation warrants it, themselves have richer data on them. I just want a lazy man’s solution to this lol

rickmoynihan15:10:18

@U380J7PAQ: I thought you might’ve been able to get away with defining a UnionType on Cursor and Person… but as you say I guess it’s not really possible without post-processing the schema.

rickmoynihan15:10:29

hadn’t seen umlaut before though, so thanks for sharing will take a look

eoliphant15:10:17

yeah I messed around with unions too lol. Graphql is amazing, just seems like something as basic as paging, etc should be kinda built in

rickmoynihan08:10:08

I don’t know about amazing, but it is pretty good 🙂 The ideas all existed before it, just not in one place. It bugs me that it’s not called TreeQL, as it’s not really very graphy at all. Agreed on the pagination issue, though I think that would be better solved through more expressive types. I also really really wish that it had first class namespaces, as without them there’s no real way to build portable clients that can work across a variety of endpoints. They’ve essentially hacked around this for __schema (see for example my comments on the namespace issue: https://github.com/facebook/graphql/issues/163 )

rickmoynihan08:10:51

I think generic/reuasble/plugin pagination support is also a motivation for namespaces

hlship20:10:47

lacinia 0.22.0 and lacinia-pedestal 0.4.0 just hit clojars

hlship22:10:05

This is what I've come up with in terms of making it easy to wrap an existing resolver with a function that can modify or replace its value.

hlship22:10:25

This handles the following cases: - The resolver function returns a simple value - The resolver function returns a ResolverResult - The returned value has been decorated via with-error or with-context - The wrapped value is a simple value - The wrapped value is a ResolverResult

hlship22:10:17

So when the normal resolver returns a ResolverResult, the wrapper-fn is not invoked until a value is delivered to that result. And the wrapper-fn can then return a simple value or its own ResolverResult.

juanjo.lenero23:10:39

@hlship would you recommend using this wrap-resolver-result for the use case where I want to set cookie headers after a signin mutation?

hlship23:10:07

I just did some tests along the lines of: if we allow standard maps instead of ordered maps, is there a performance gain? And the answer: virtually none, so why complicate things.

juanjo.lenero23:10:26

if the mutation is succesful and the resolver returns a user map, I would like to set the session cookie.

hlship23:10:16

@juanjo.lenero Nope! That's something you can do, somehow, in your standard resolver function. This was intended for more cross-cutting concerns, including remapping keys from Clojure idiom to JSON idiom.

hlship23:10:54

@juanjo.lenero I'm honestly not sure of a good way to accomplish what you want that's also clean ... functional and not using a boolean inside an atom passed everywhere. Essentially, I would want to tackle this in two parts: resolver code that recognizes the scenario, and a Pedestal interceptor to do the actual work during the :leave event.

hlship23:10:53

So how could they communicate? The resolver could place something into the result map that would be visible to the interceptor. Unfortunately, options are currently limited.

hlship23:10:36

You could have in interceptor hard-coded to see something like: {:data {:my-mutation ...}} and that logic could trigger the creation of the session.

hlship23:10:02

A better solution would be if the resolver could put a key/value pair into the :extensions part of the result map.

hlship23:10:22

However, that is not currently supported by Lacinia. It would not be very difficult to accomplish.

hlship23:10:35

But would require a code change.

hlship23:10:44

Another option would be a special error that the resolver could place; the interceptor could check for the error and then remove it. That's kind of ugly, but something you could do today.

hlship23:10:25

The non-functional option is simply to add an (atom false) to the request, and have the resolver (reset! the-atom true).

hlship23:10:50

Again, the interceptor would put the atom into the request map during :enter, and check the atom on :leave.

juanjo.lenero23:10:46

Yep, I think the functional way dictates that the resolver function should be able to send something else back besides data, error, perhaps data,error,context. So ring middleware can then act on the returned context.

juanjo.lenero23:10:11

Which is exactly what you described here right?:

resolver code that recognizes the scenario, and a Pedestal interceptor to do the actual work during the :leave event.
So how could they communicate?  The resolver could place something into the result map that would be visible to the interceptor.

hlship23:10:29

Right. The resolver can return data directly, and errors indirectly (via resolve/with-error) BUT there isn't a mechanism to go any further.

hlship23:10:48

I think I might go with the ugly pass-and-atom solution until something more clever is available.

hlship23:10:43

I think this is a pretty valid use of the :extensions key. So adding a with-extension-data function would be useful and doable.

hlship23:10:50

That's not a commitment.

juanjo.lenero23:10:25

Didn’t know about the extensions key, but it does seem to fit.

juanjo.lenero23:10:29

I think I’ll go with the atom solution for the moment, and keep a lookout on the project, thanks.

eoliphant14:10:02
replied to a thread:Hi, quick question. I’d asked about pagination approaches a while back, I’ve since done some reading (and coding) and ended up implementing the Relay Connection Spec for a few of my objects/queries. So where I had a `:person` query before that just returned a list of `:Person`’s I now have that guy typed as a `:PersonConnection` that has the edges, cursors and what have you. This works fine, but since there are tons of things that are lists, it seems very not DRY to have to create Edges and Connections for everything. Any suggestions on a clean way to do this. I’ve prototyped something that just generates them based on an extra metadata tag I added, works ok, but just curious about what other folks are doing. I found this JS example, which uses basically parameterized types for edges, etc. , but not sure how to achieve this with lacinia: https://medium.com/@mattmazzola/graphql-pagination-implementation-8604f77fb254

Interfaces don’t seem to work as far as I can tell because your Edges and what have you need to be ‘typed’. Interfaces allow you to say “this object should have field a of type String” To create an Edge for instance the cursor field is always a String, but the node field is whatever the underlying type is. I think you need something like a parameterized type. which of course graphql doesn’t support. That article I posted gets around that because they’re calling functions in the schema def that give you the same thing