docs: function getType(graffitiModels, { name, description, fields }, path = [], rootType = null) {
    const root = path.length === 0;
    const graphQLType = { name, description };
    rootType = rootType || graphQLType;
  
    // These references have to be resolved when all type definitions are avaiable
    resolveReference[graphQLType.name] = resolveReference[graphQLType.name] || {};
    const graphQLTypeFields = reduce(fields, (graphQLFields,
        { name, description, type, subtype, reference, nonNull, hidden, hooks,
         fields: subfields, embeddedModel, enumValues }, key) => {
      name = name || key;
      const newPath = [...path, name];
  
      // Don't add hidden fields to the GraphQLObjectType
      if (hidden || name.startsWith('__')) {
        return graphQLFields;
      }
  
      const graphQLField = { name, description };
  
      if (type === 'Array') {
        if (subtype === 'Object') {
          const fields = subfields;
          const nestedObjectName = getTypeFieldName(graphQLType.name, name);
          graphQLField.type = new GraphQLList(
            getType(graffitiModels, { name: nestedObjectName, description, fields }, newPath, rootType));
        } else {
          graphQLField.type = new GraphQLList(stringToGraphQLType(subtype));
          if (reference) {
            resolveReference[rootType.name][name] = {
              name,
              type: reference,
              args: connectionArgs,
              resolve: addHooks((rootValue, args, context, info) => {
                args.id = rootValue[name].map((i) => i.toString());
                return connectionFromModel(graffitiModels[reference], args, context, info);
              }, hooks)
            };
          }
        }
      } else if (type === 'Object') {
        const fields = subfields;
        const nestedObjectName = getTypeFieldName(graphQLType.name, name);
        graphQLField.type = getType(graffitiModels, { name: nestedObjectName, description, fields }, newPath, rootType);
      } else if (type === 'Embedded') {
        const type = types.hasOwnProperty(name)
          ? types[name]
          : getType(graffitiModels, embeddedModel, ['embedded']);
        type.mongooseEmbedded = true;
        graphQLField.type = type;
      } else if (enumValues && type === 'String') {
        graphQLField.type = listToGraphQLEnumType(enumValues, getTypeFieldName(graphQLType.name, `${name}Enum`));
      } else {
        graphQLField.type = stringToGraphQLType(type);
      }
  
      if (reference && (graphQLField.type === GraphQLID || graphQLField.type === new GraphQLNonNull(GraphQLID))) {
        resolveReference[rootType.name][newPath.join('.')] = {
          name,
          type: reference,
          resolve: addHooks((rootValue, args, context, info) => {
            const resolver = getOneResolver(graffitiModels[reference]);
            return resolver(rootValue, { id: rootValue[name] ? rootValue[name].toString() : null }, context, info);
          }, hooks)
        };
      }
  
      if (nonNull && graphQLField.type) {
        graphQLField.type = new GraphQLNonNull(graphQLField.type);
      }
  
      if (!graphQLField.resolve) {
        graphQLField.resolve = addHooks((source) => source[name], hooks);
      }
  
      graphQLFields[name] = graphQLField;
      return graphQLFields;
    }, {});
  
    if (root) {
      // Implement the Node interface
      graphQLType.interfaces = [nodeInterface];
      graphQLTypeFields.id = globalIdField(name, (obj) => obj._id);
    }
  
    // Add fields to the GraphQL type
    graphQLType.fields = () => graphQLTypeFields;
  
    // Define type
    const GraphQLObjectTypeDefinition = new GraphQLObjectType(graphQLType);
  
    // Register type
    if (root) {
      addType(name, GraphQLObjectTypeDefinition);
    }
  
    return GraphQLObjectTypeDefinition;
  }