graphql-js: Schema circular dependency issue

Keeping Entire Schema in One file is very ugly and hard to maintain. If we keep it in seperate files there is a Circular Dependency Issue with Interface.

Is there a solution to this?

CategoryEdge.node field type must be Output Type but got: undefined.

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 15 (6 by maintainers)

Commits related to this issue

Most upvoted comments

This is not an issue with GraphQL-JS, but it’s true that the solution to this is not well documented.

You can use a thunk for your fields like so:

import { OtherType } from './otherType';

const PersonType = new GraphQLObjectType({
  name: 'PersonType',
  fields: () => ({
    someField: { type: OtherType, resolve: () => 5 },
  })
});

I have a circular dependency in my Connection definitions

export default connectionDefinitions({
  name: 'Product',
  nodeType: ProductType,
  connectionFields: {
    count: {
      type: GraphQLInt,
    }
  }
});

ProductType is undefined in this case

how can I use a “thunk” to solve this?

Was there a updated solution to this? I tried doing all fields as thunk and @holmesal hacky fix without any luck. I created a Stack Overflow question on it

https://stackoverflow.com/questions/44527183/how-to-fix-circular-dependency-with-graphql-relay-connections

Edit: Solution was found! More info in the post but this is the fix for importing a circular type:

pageEdge: {
      type: require('./connections/PageConnection').default,
      args: connectionArgs,
      resolve: async (page, { ...args }, { loaders }) => {
        if (page.pageEdge) {
          const pageEdge = await loaders.pageLoader.loadMany(page.pageEdge);
          const connection = connectionFromArray(pageEdge, args);
          return connection;
        }
      },
    },

I highly recommend using separate files for your schema definitions. A good practice is one type per file with well named files.

As Jonas pointed out, whenever there is a circular dependency between types, you need to use a “thunk” (a function that returns a value, called later) for your fields

@joewoodhouse Sorry got confused, not it can’t 😦 I checked the source code and only fields, interfaces and types(for Union) could be a thunk.

Also, don’t use CommonJS. From this reference: https://gist.github.com/fbaiodias/77406c29ddf37fe46c3c

Some important notes … CommonJS modules export values, ES6 modules export immutable bindings. Use ES6 if possible to avoid problems.

Going back to the original issue…

So I have split up my schema into a number of seperate files, but now seem to have hit a circular dependency issue I can’t resolve.

Basically I want to keep each query and mutation in a separate file.

So at the top-level I might have a schema like:

# schema.js
import { updateProject } from './mutations';
import { projectType } from './types';

const projectField = {
    type: projectType,
    description: 'Look up a project by ID or slug',
    .... 
};

const schema = new GraphQLSchema({
    query: new GraphQLObjectType({
        name: 'RootQueryType',
        description: 'The query root of GraphQL Interface',
        fields: () => ({
            project: projectField
        })
    }),
    mutation: new GraphQLObjectType({
        name: 'RootMutationType',
        fields: () => ({
            updateProject
        })
    })
});

With a projectType definition in a separate file

export const projectType = new GraphQLObjectType({
    name: 'Project',
    description: 'A project',
    fields: () => {
      name: {
        type: GraphQLString,
        description: 'Project',
        resolve: () => 'test'
      },
    }
});

Now suppose that my definition of updateProject is as follows

import { GraphQLNonNull, GraphQLInt, GraphQLString } from 'graphql';
import { projectType } from '../types';
import * as DB from 'db';

export default {
    type: projectType,
    args: {
      ...
    },
    resolve: (project, { }, request, info) => {
       ...
    }
};

When this setup is run, it will fall over with:

Error: Attribute.field field type must be Output Type but got: undefined.

So what happens is:

  • schema.js imports updateProject mutation
  • updateProject imports projectType
  • As this is not run-time, projectType.fields thunk has not been run, hence the error above.

It feels like I need a thunk on the type field of my mutation? Does anyone have any suggestions?

If a mutation or query has a type of something that uses a thunk for it’s fields, then any field that uses it must also be inside a thunk? Seems impossible then to define it in a standalone file?

@sibelius AFAIK you can’t use a think on connectionDefinitions yet. I posted my current workaround in https://github.com/graphql/graphql-js/issues/612

@sibelius I don’t know the answer, but since your question is about graphql-relay-js you might have more luck getting it answered there.