Search code examples
javascriptgraphqlkeystonejskeystonejs6

Keystone 6 custom schema - mutation not working - Maximum call stack size exceeded


I'm trying to extend a mutation in Keystone 6, but having a lot of trouble just getting the standard DB update to work in a custom mutation resolver; Using the standard Keystone boilerplate and added a new collection/list.

Following the examples here, I've matched custom-schema.ts with the generated schema.graphql

schema.graphql (simplified):

type Dog {
  id: ID!
  name: String
}

input DogWhereUniqueInput {
  id: ID
}

input DogUpdateInput {
  name: String
}

type Mutation {
  updateDog(
    where: DogWhereUniqueInput!
    data: DogUpdateInput!
  ): Dog
}

custom-schema.ts:

import { graphQLSchemaExtension } from '@keystone-6/core';
import { Context } from '.keystone/types';

export const extendGraphqlSchema = graphQLSchemaExtension<Context>({
  typeDefs: `
    type Mutation {
      """ update a dog """
      updateDog(
        where: DogWhereUniqueInput!
        data: DogUpdateInput!
      ): Dog
    }
  `,
  resolvers: {
    Mutation: {
      updateDog: async (root, { where, id }, context) => {
        try {
          const response = await context.db.Dog.updateOne({
            where: { id },
            data: { name: 'updated name'}
          });

          return response;
        } catch (updateError: any) {
          throw updateError;
        }
      }}
    }
  },
);

keystone.ts:

import { extendGraphqlSchema } from './custom-schema';

// ...

export default withAuth(
  config({
    db: {
      provider: 'sqlite',
      url: 'file:./keystone.db',
    },
    ui: {
      isAccessAllowed: (context) => !!context.session?.data,
    },
    lists,
    session,
    extendGraphqlSchema,
  })
);

When I trigger an update from the (boilerplate) UI, I get this error repeatedly from the catch error handler. Same happens in graphQL playground. Really struggling to understand what's happening and why the resolver is getting spammed and generating this error.

RangeError: Maximum call stack size exceeded
at isLeafType (.../poc/node_modules/graphql/type/definition.js:247:20)
at coerceInputValueImpl (.../poc/node_modules/graphql/utilities/coerceInputValue.js:122:34)

Why is this happening, how to fix? Am I missing something obvious?


Solution

  • That's because both context.db and context.query internally still use the GraphQL API for CRUD. And since your custom mutation updateDog also has the same name as the generated mutation from schema updateDog, both the mutations are repeatedly invoking each other and hence the error RangeError: Maximum call stack size exceeded.

    You can solve your problem in one of the two ways —

    1. Change the name of your custom mutation to something else. Eg. updateDogCustom

      or

    2. (Practice caution) Instead of context.db.Dog.updateOne, use the prisma client to skip keystone's data layer and CRUD the database directly. Be warned, this means if you have hooks, access control or validation logic in place they won't be invoked.

    export const extendGraphqlSchema = graphQLSchemaExtension<Context>({
      typeDefs: `
        type Mutation {
          """ update a dog """
          updateDog(
            where: DogWhereUniqueInput!
            data: DogUpdateInput!
          ): Dog
          """ update a dog custom """
          updateDogCustom(
            where: DogWhereUniqueInput!
            data: DogUpdateInput!
          ): Dog
        }
      `,
      resolvers: {
        Mutation: {
          updateDog: async (root, { where: { id }, data: { name } }, context) => {
            try {
              const response = await context.prisma.dog.update({
                where: { id },
                data: { name },
              });
    
              return response;
            } catch (updateError: any) {
              throw updateError;
            }
          },
          updateDogCustom: async (
            root,
            { where: { id }, data: { name } },
            context
          ) => {
            try {
              const response = await context.db.Dog.updateOne({
                where: { id },
                data: { name },
              });
    
              return response;
            } catch (updateError: any) {
              throw updateError;
            }
          },
        },
      },
    });
    

    Codesandbox here — https://codesandbox.io/s/winter-shadow-fz689e?file=/src/custom-schema.ts

    You can run the graphql playground right from codesandbox from /api/graphql path. Eg. https://fz689e.sse.codesandbox.io/api/graphql