I'd like to have required fields and queried field...
# prisma-whats-new
l
I'd like to have required fields and queried fields returned by the bindings. Is there any way to do that? e.g. I'd like the
id
to always be returned, so ideally, I'd like to combine these into one...
Copy code
db.query.users({}, `{ id }`)
   db.query.users({}, info)
l
oh fragments... how I need to learn you more betterer. Thanks @nilan!
n
you're welcome! 🙂
reading your question again this might not be exactly what you had in mind 🤔 you talk about "returning the id", do you mean return it to the client? You can't do that easily.
fragments allow you to ensure that certain fields are available
a
Returning fields to the client that were not requested is against the spec, and the core principles of GraphQL in general. The query result should match the query, the client determines what fields he wants to query.
👍 2
Returning additional fields from a binding is fine, using fragments, if you need them in your resolvers
l
I was just about the write "humm, this may not be quite it." 🙂 Here's the overall problem. For directive permissions, you may want to protect, say
email: String @isOwner
on the
User
type. But if a query requests
users { email }
, the
id
is not available to the directive resolver, so there's no way to check if that's the owner of the email. In the User resolver, you can `db.query.users({},
{ id, email}
) ` but that's obviously error prone and inartful
a
@lawjolla That is the use case for using fragments like @nilan mentioned.
m
@agartha @nilan @lawjolla thank you for sharing this 🙂 I pick up so much on this channel 👍💚prisma
💚 1
l
I guess I'm not seeing the pattern where you can use a fragment to append a non requested field to the resolver.
Wait, I get it
The fragment can be called from the directive resolver
n
😄 I can't really put it into words to be honest.
a
n
With the fragment you can declare data dependencies for fields that you need in the resolvve function
l
Awesome, thanks! Let me do some head banging with the new information. I think I get it. Maybe 🙂
a
Fragments are 'inserted' into the query by the graphql engine.
So if you have:
Copy code
query Post {
   title
   mySpecialField
}
and the resolver for
mySpecialField
specifies a fragment
fragment MyFragment on Post { id }
the query engine will turn your query into:
Copy code
query Post {
  ...MyFragment
  title
  mySpecialField
}

fragment MyFragment on Post {
  id
}
Which is effectively:
Copy code
query Post {
  id
  title
  mySpecialField
}
😍 2
So that in your resolver function for
mySpecialField
, the `parent`/`root` argument (the first one) will have the value for
Post.id
as well that you need.
👍 1
l
That makes total sense. Thank you for laying it out like that!
n
Yup thanks for describing that process! So the example I pointed to actually shows more than that. It also uses
{ id }
as the first argument to the
resolve
function, which gives access to
id
. That's probably not sometihng @lawjolla needs in his specific case, but is generally a super useful pattern.
👍🏻 1
a
yw
@nilan if you are familiar with object destructuring, that syntax makes perfect sense. I personally like to keep my args always the same
(parent, args, context, info)
for clarity, and just use
parent.id
instead.
But that's personal preference I guess
l
After coming from Elixir, you'd hate my destructured code 🙂
n
Yea, I'm just saying that @lawjolla is interested in including
id
in
info
, not the actual
id
for his resolver logic. So in his case he doesn't need
{ id }
as a first arg
a
No, I actually think he needed the id value
At least, that's how I read it
l
I'm interested in getting
id
into the directive resolver.
👍🏻 2
I wasn't taking the right path by trying to manipulate
info
a
the
id
is not available to the directive resolver, so there's no way to check if that's the owner of the email.
Thanks for bringing that full circle @lawjolla
💯 2
n
😄 yup, thanks all!
fast parrot 1
m
@lawjolla could you post this on the forum? Would be useful for others 🙂
👍🏻 1
l
I updated the show and tell thread already with a link to this discussion. I'm going to try to update my example repo today (time allowing) and then explain what I did
🦜 2
💪🏻 4
l
How can we get the fragments to work generally though? For directive permissions, we want the
isOwner
directive to work arbitrarily for any type. If we have to declare the fragment before runtime, that's not going to work
Well, let me explore it a bit more I suppose. Are the directives resolvers declaring the fragment, or just the resolvers? If the fragment is declared on the resolver, will that fragment's information be passed to the directive resolver?
a
I don't know if
fragment
accepts a function, you should probably dive into the sources for that
@lastmjs I honestly don't know, haven't tested anything with directives
👍 1
I assume it does, but I don't even know why I assume that 🙂
Directive resolvers are resolved after the data is queried
l
Ah, good
a
So I assume you would get the data including the fields from the resolvers once you get to the directive resolver
But again: assumptions...
l
I'm also assuming that, but I'm also going to test it right now
a
Great, do let me know
l
I can't even get the fragments to work on at the resolver level. I think I'm missing something around the
fragmentReplacements
a
extractFragment....something
See the example nilan linked in the first reply
That should do it.
l
I'm sorry, is there anything obviously wrong here?
Copy code
const resolvers = {
    Query: {
        testUsers: {
            fragment: `fragment Test on User { id }`,
            resolve: async (parent, args, context, info) => {
                console.log('parent', parent);
                return await context.db.query.users({}, info);
            }
        }
    }
};
const fragmentReplacements = extractFragmentReplacements(resolvers);

const server = new GraphQLServer({
    typeDefs: `
        type User {
            id: ID!
            email: String!
        }

        type Query {
            testUsers: [User!]!
        }
    `,
    resolvers,
    context: (req) => {
        return {
            ...req,
            db: new Prisma({
                endpoint: '<http://127.0.0.1:4466/backend/dev>', // the endpoint of the Prisma DB service
                secret: 'mysecret123', // specified in database/prisma.yml //TODO obviously this should be controlled with environment variables
                debug: true, // log all GraphQL queries & mutations
                fragmentReplacements
            })
        };
    }
});

server.start(() => console.log('Server is running on <http://localhost:4000'>));
parent always logs as undefined when I do a testUsers query
a
testUsers is a root field
l
What's the problem with that?
If that is a problem, then perhaps directive permissions won't work, because we need to be able to retrieve other fields like the id for any query coming in, to send to the directive resolvers
Or maybe I just need to explicitly make a User resolver and apply the fragment there...
l
@lastmjs in my demo repo, I kept it all in the directive resolver by defining
@isOwner(type: String)
. Then made a wrapping function
const isRequestingUserAlsoOwner = ({ ctx, userId, type, typeId }) => ctx.db.exists[type]({ id: typeId, user: {id: userId}})
For every type that includes
@isOwner
and you don't control the client (to ensure id is queried), you'd have to make a fragment for the
id
field on the data resolver
💯 1
l
Okay, thank you. I finally got a basic example working and some clear direction to follow now
l
I don't think it's perfect though, and you're bringing up a lot of good not really edge cases
I have full control of the client, so I don't have a lot of these considerations. But that's just my own luck. These servers should be client agnostic
l
Yes, I think I'm going to have to do some manual AST traversal and add the extra resolvers with the fragments...we'll see how clean I can get it. Is there a better place to discuss this and keep everything together as we try to come to some conclusions that might be better incorporated into one of the many libraries?
l
Probably graphql-tools on Github. That'll be where the eventual changes are made. The Graphcool forum is good too
And so far these issues result in allowed clients being blocked information. If you find the opposite -- unallowed clients given blocked info -- please sound the alarm
l
Will do
Alright, finally at the end of the day I think I've got a general solution for arbitrarily adding fragments to your resolvers: https://www.graph.cool/forum/t/prisma-auth0-directive-permissions-example/2313/10?u=jordan.michael.last
👍🏻 2
fast parrot 1
It's working well during basic testing, tomorrow I'll be moving forward with implementing more of the permissions for my application, we'll see how it holds up
a
Will check it out tomorrow
👍 1
l
By the way, this discussion should probably move here: https://www.graph.cool/forum/t/prisma-auth0-directive-permissions-example/2313/13
A lot of these issues and their solutions have now been gathered into this blog post: https://medium.com/@lastmjs/advanced-graphql-directive-permissions-with-prisma-fdee6f846044