Node - GraphQL - Ad.user field type must be Output

2019-08-04 07:15发布

问题:

I seem to be having circular dependency problems with using UserType inside AdType.

This is my UserType file: UserType

This is my AdType file: AdType

If I try to use the code below I get the error "Ad.user field type must be Output Type but got: undefined" even tho I imported the UserType properly.

import { UserType } from '../User/user.graphql.model'
import { UserSchema } from '../User/user.mongoose.model'

const user =  {
  type: UserType,
  resolve(parentValue, args) {
    return UserSchema.findById(parentValue.user);
  }
};

//------------------------------
// Ad Type
//------------------------------
export const AdType = new GraphQLObjectType({
  name: 'Ad',
  fields: () => ({
    id,
    user,
    title,
    views,
    availability,
... more code

If I try to console log UserType inside AdType after importing it, it says undefined, but when I use it as:

//------------------------------
// Ad Type
//------------------------------
export const AdType = new GraphQLObjectType({
  name: 'Ad',
  fields: () => ({
    id,
    user: {
      type: UserType,
      resolve(parentValue, args) {
        return UserSchema.findById(parentValue.user);
      }
    },
    title,
... more code

it works as intended, it just doesn't allow me to pull out the code to separate constant. All other Types I import and use in the same manner work as expected, importing Ads into Users works as well, but importing User into Ads seems to break. It's basically the same code in both, just different info.

回答1:

I am already using fields: () => ( { } ) to lazily load fields to avoid problems with circular dependencies, so this problem is really banging against my head.

But you are not doing it correctly. Javascript does not have lazy evaluation. This means that the value of user is not determined when the function is called but at the point in time when the const variable definition is evaluated. At this point the variable UserType holds no value and thus is undefined. Your object definition needs to happen when the function is called. If it is still not clear I can offer to go into detail how your types are resolved.

Try defining the user type inline or make it a function:

const user = () => ({ type: UserType, /* ... */ })

export const AdType = new GraphQLObjectType({
  name: 'Ad',
  fields: () => ({
    id,
    user: user(),
    title,
    views,
    availability,

I am not sure why you pull your fields into seperate constants, your code does not seem that big that it improves readability but of course I can be wrong.

Okay, let's see how modules are resolved. To make this easier I use CJS because you most likely transpile the code down anyways and ES modules are just slowly coming to node.

// user.graphql.model.js
const adModule = require('ad.graphql.model.js');

// Node tries to resolve ad.graphql.model.js
const userModule = require('user.graphql.model.js');
// Ups, this one has been resolved already and required this as dependency.
// We have no other choice than to assign an empty object here
// userModule is {}
const user =  {
  type: userModule.UserType, // undefined
  resolve(parentValue, args) {
    return UserSchema.findById(parentValue.user);
  }
};
// finish this module and return to user.graphql.model.js
// adModule now contains the resolved module
const adModule = require('ad.graphql.model.js');
// finish module and replace {} with actual module content in ad.graphql.model.js
// userModule contains UserType
const userModule = require('user.graphql.model.js');

const ads = {
  type: new GraphQLList(asModule.AdType), // GraphQLObjectType
}

// Now your Schema does build/inits itself after the config has been specified
// (and all modules have been resolved)
// imagine user being a function now taht is called on Schema init
const user = () => ({
  type: userModule.UserType, // GraphQLObjectType
  /* ... */
})