contra/graphql-helix

variables for a mutation query are not being passed to resolver

Sewdn opened this issue · 3 comments

Sewdn commented

Hi,

Maybe I'm missing something, but it seems to me that Input Type objects are not being passed to the implemeting mutation resolvers.

getGraphQLParameters is parsing the variables correctly (and the input is confirmed as being a valid inpt type for the specific mutation).

When the operation, query and variables are passed to the async method processRequest, the resolver is hit, but the mathod doesnt receive any variables...

Some example code:

...
const { operationName, query, variables } = getGraphQLParameters(request);

    console.log({ operationName, query, variables });

    const result = await processRequest({
      operationName,
      query,
      variables,
      request,
      schema,
    });

    if (result.type === 'RESPONSE') {
      result.headers.forEach(({ name, value }) => res.setHeader(name, value));
      res.status(result.status);
      res.json(result.payload);
    } else {
      res.status(400);
      res.json({ error: 'no support for multipart responsed or streams' });
    }
...
import { makeExecutableSchema } from '@graphql-tools/schema';

import typeDefs from './typedefs.generated';

import resolvers from './resolvers';

export const schema = makeExecutableSchema({
  typeDefs,
  resolvers,
});
import {
  Claim,
  Policy,
  Customer,
  ClaimInput,
  ClaimCreateResponse,
  // Resolvers,
} from '../types.generated';
import { Claims, Policies, Customers } from '../stores';
import { claims as claimsResolvers } from './claims';

const resolvers = {
  Query: {
    allClaims: () => Claims.findAll(),
    allPolicies: () => Policies.findAll(),
    allCustomers: () => Customers.findAll(),
  },
  Mutation: {
    createClaim: (claim: ClaimInput): Promise<ClaimCreateResponse> => Claims.create(claim),
  },

  ...claimsResolvers,

  Policy: {
    claims: ({ id }: Policy): Claim[] => Claims.findByPolicyId(id),
    customer: ({ customerId }: Policy): Customer | null => Customers.get(customerId),
  },
  Customer: {
    policies: ({ id }: Customer): Policy[] => Policies.findByCustomerId(id),
  },
};

export default resolvers;
import { find, filter } from 'lodash';
import { Claim, ClaimInput, ClaimCreateResponse } from '../types.generated';
import { validate } from '../validators';

import { claims as claimStubs } from './data';

const Claims = {
  get: (id: string): Claim | null => find(claimStubs, { id }) || null,
  findAll: (): Claim[] => claimStubs,
  findByPolicyId: (_id: string): Claim[] => filter(claimStubs, { policyId: _id }),
  create: async (claim: ClaimInput): Promise<ClaimCreateResponse> => {
    // validate input
    console.log(claim);
    if (validate('ClaimInput')(claim)) {
      return {
        claim: {
          ...claim,
          id: 'generatedId',
        },
      };
    } else {
      throw new Error('not valid');
    }
  },
};

export default Claims;

Hi @Sewdn. A GraphQL resolver takes four parameters: the parent value, an arguments object, a context object and a GraphQLResolveInfo object. Your resolver, however, is written like this:

createClaim: (claim: ClaimInput): Promise<ClaimCreateResponse> => Claims.create(claim),

Assuming claim is the name of an argument on the createClaim field, you should instead do:

createClaim: (args): Promise<ClaimCreateResponse> => Claims.create(args.claim),
Sewdn commented

Thanks for your swift response!
It was indeed the second parameter that I needed, to pass on the typed argument; (I had been testing with the first parameter).

Mutation: {
    createClaim: (_: any, args: MutationCreateClaimArgs): Promise<ClaimCreateResponse> => {
      return Claims.create(args.input);
    },
  },

What is the type of this first parameter? As it is the parent value for the field resolver of an entity, for a mutation this is somewhat misleading for a mutation resolver, since this would be the root entity, and thus an empty {} object of any type?

For root fields, regardless of whether they are on the Query or Mutation root type, the parent value will always be the root value. You can provide this value using the rootValueFactory option, in which case the value will be whatever the factory returns. If the option is omitted, rootValue is simply an empty object.