Binding tries to query Prisma API instead calling application resolver

prisma

#1

I’ve encountered a strange problem. Chances are high that I messed something up. This is my Prisma database schema:

# import InvitationStatus, Role from "./generated/prisma.graphql"

type Invitation {
  sender: User! @relation(name: "InvitationSender")
  acceptor: User @relation(name: "InvitationAcceptor")
  status: InvitationStatus! @default(value: "Pending")
  token: String! @unique
  email: String!
}

type User {
  id: ID! @unique
  role: Role! @default(value: "Member")
  banned: Boolean! @default(value: "false")
  username: String! @unique
  emailAddresses: [EmailAddress!]! @relation(name: "EmailAddressOwner", onDelete: CASCADE)
  invitations: [Invitation!]! @relation(name: "InvitationSender", onDelete: CASCADE)
  invitation: Invitation @relation(name: "InvitationAcceptor")
  password: String!
  salt: String!
  profile: Profile
}

The structure differs slightly in my application schema:

type Invitation {
  "The user who has accepted the invitation. `null` if the invitation is `Pending`"
  acceptor: User
  status: InvitationStatus!
  "The address to which the invitation has been sent."
  email: String!
}

type Invitations {
  "Indicates how many invitations the user can still send."
  available: Int!
  "The history of sent invitations"
  history: [Invitation]
}

type User {
  id: ID!
  role: Role!
  banned: Boolean!
  activated: Boolean!
  email: String!
  username: String!
  "The invitations the user has sent + the count of invitations that can still be sent."
  invitations: Invitations!
}

type Mutation {
  register(input: RegistrationInput!): User!
}

When registering an user and querying for the invitations, the binding tries to query for invitations from the Prisma service instead executing the resolver in the application layer. This is how the call looks like in my register mutation:

const result = await context.db.mutation.updateInvitation(
    {
    where: {
        token: args.input.invitationToken
    },
    data: {
        status: "Accepted",
        acceptor: {
        create: {
            role: "Member",
            username: args.input.username,
            password,
            salt,
            emailAddresses: {
            create: {
                address: args.input.email,
                status: "Pending",
                verificationCode: createSalt(),
                primary: true
            }
            }
        }
        }
    }
    },
    `{acceptor(where:{username: "${args.input.username}"}) {id}}`
);

return context.db.query.user({ where: { id: result.acceptor.id } });

The query history against the Prisma service states that the binding tries to query invitations from there as well. Instead, it should execute the resolver in the application schema. This is the log output:

query ($_where: UserWhereUniqueInput!) {
  user(where: $_where) {
    id
    role
    banned
    username
    password
    salt
    invitations
  }
}

Any idea what is going on here? :slight_smile:


#2

Hey Andre - interesting edge case you have here.

I think that this is normal behaviour :slight_smile: When the user query is forwarded to prisma, it assumes the prisma schema.

What you might want to do is add a field resolver for user. So in addition to your user resolver, you add a sub-resolver (for want of a better word). This will transform what prisma gives you into your public facing definition for Invitations. I did it just last week with my own project.

const User = {
  invitations: async (parent, args, ctx: Context, info) => {
    //calculate available and history here
    const { invitations } = parent
    const available = invitations.length
    const history = [...invitations]
    return { available, invitations }
  }
}

Then add User to your resolvers

const resolvers = {
  Queries: {},
  Mutations: {},
  User
}

#3

Hi @meep – Thanks for your response. That is exactly what I did as a “workaround”. I agree with you that this might be the default behaviour. I added a Invitations resolver with two sub-resolvers: available and history:

const Invitations = {
  available: async (user: User, args, context: Context, info) => {},
  history: async (user: User, args, context: Context, info) => {}
};

I did that because of the possibility that the user only wants to query available or history, so that only the respective resolver will be executed. But yeah, funny, that we both faced a similar situation :slight_smile:


#4

Yep your method is probably a safer way of doing it.

My method would break if the user query sent to prisma does not include invitations.