graphql#GraphQLUnionType TypeScript Examples

The following examples show how to use graphql#GraphQLUnionType. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Utils.ts    From graphql-ts-client with MIT License 6 votes vote down vote up
export function targetTypeOf(type: GraphQLType): GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType | undefined {
    if (type instanceof GraphQLNonNull) {
        return targetTypeOf(type.ofType);
    }
    if (type instanceof GraphQLList) {
        return targetTypeOf(type.ofType);
    }
    if (type instanceof GraphQLObjectType || type instanceof GraphQLInterfaceType || type instanceof GraphQLUnionType) {
        return type;
    }
    return undefined;
}
Example #2
Source File: unionType.ts    From genql with MIT License 6 votes vote down vote up
unionType = (type: GraphQLUnionType, _: RenderContext) => {
    const types = type.getTypes()
    const typeObj: FieldMap<string> = types.reduce<FieldMap<string>>((r, t) => {
        r[`on_${t.name}`] = { type: t.name }
        return r
    }, {})

    const commonInterfaces = uniq(flatten(types.map((x) => x.getInterfaces())))
    commonInterfaces.forEach((t) => {
        typeObj[`on_${t.name}`] = { type: t.name }
    })

    typeObj.__typename = { type: 'String' }

    return typeObj
}
Example #3
Source File: Generator.ts    From graphql-ts-client with MIT License 6 votes vote down vote up
protected createFetcheWriter(
        modelType: GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType,
        ctx: FetcherContext,
        stream: WriteStream,
        config: GeneratorConfig
    ): FetcherWriter {
        return new FetcherWriter(
            modelType,
            ctx,
            stream,
            config
        );
    }
Example #4
Source File: unionType.ts    From genql with MIT License 6 votes vote down vote up
unionType = (type: GraphQLUnionType, ctx: RenderContext) => {
    let types = type.getTypes()
    if (ctx.config?.sortProperties) {
        types = types.sort()
    }
    const fieldStrings = types.map((t) => `on_${t.name}?:${requestTypeName(t)}`)

    const commonInterfaces = uniq(flatten(types.map((x) => x.getInterfaces())))
    fieldStrings.push(
        ...commonInterfaces.map((type) => {
            return `on_${type.name}?: ${requestTypeName(type)}`
        }),
    )

    fieldStrings.push('__typename?: boolean | number')

    ctx.addCodeBlock(
        `${typeComment(type)}export interface ${requestTypeName(
            type,
        )}{\n${fieldStrings.map((x) => '    ' + x).join(',\n')}\n}`,
    )
}
Example #5
Source File: CommonTypesWriter.ts    From graphql-ts-client with MIT License 6 votes vote down vote up
private writeImplementionType() {

        const t = this.text.bind(this);

        t(IMPLEMENTANTION_TYPE_COMMENT);
        t("export type ImplementationType<T> = ");
        this.enter("BLANK", true);
        for (const [type, castTypes] of this.inheritanceInfo.downcastTypeMap) {
            t("T extends '");
            t(type.name)
            t("' ? ");
            this.enter("BLANK")
            if (!(type instanceof GraphQLUnionType)) {
                t("'");
                t(type.name);
                t("'");
            }
            for (const castType of castTypes) {
                this.separator(" | ");
                t("ImplementationType<'");
                t(castType.name);
                t("'>");
            }
            this.leave();
            t(" :\n");
        }
        t("T\n");
        this.leave();
        t(";");
    }
Example #6
Source File: types.ts    From tql with MIT License 6 votes vote down vote up
printField = (field: GraphQLField<any, any, any>): string => {
  const { args } = field;

  const isList = listType(field.type);
  const isNonNull = field.type instanceof GraphQLNonNull;
  const type = outputType(field.type);

  const printVariables = () => {
    return args.length > 0
      ? `(variables: { ${args.map(printVariable).join(", ")} })`
      : "";
  };

  if (type instanceof GraphQLScalarType) {
    return (
      `${args.length > 0 ? "" : "readonly"} ${field.name}${printVariables()}: ${
        isList ? `ReadonlyArray<${toPrimitive(type)}>` : `${toPrimitive(type)}`
      }` + (isNonNull ? "" : " | null")
    );
  } else if (type instanceof GraphQLEnumType) {
    return (
      `${args.length > 0 ? "" : "readonly"} ${field.name}${printVariables()}: ${
        isList ? `ReadonlyArray<${type.name}>` : `${type.name}`
      }` + (isNonNull ? "" : " | null")
    );
  } else if (
    type instanceof GraphQLInterfaceType ||
    type instanceof GraphQLUnionType ||
    type instanceof GraphQLObjectType
  ) {
    return (
      `${args.length > 0 ? "" : "readonly"} ${field.name}${printVariables()}: ${
        isList ? `ReadonlyArray<I${type.name}>` : `I${type.name}`
      }` + (isNonNull ? "" : " | null")
    );
  } else {
    throw new Error("Unable to print field.");
  }
}
Example #7
Source File: schema.ts    From squid with GNU General Public License v3.0 6 votes vote down vote up
function addUnion(model: Model, type: GraphQLUnionType): void {
    if (model[type.name]) return
    let variants: string[] = []

    model[type.name] = {
        kind: 'union',
        variants,
        description: type.description || undefined
    }

    type.getTypes().forEach(obj => {
        if (isEntityType(obj)) {
            throw new Error(`union ${type.name} has entity ${obj.name} as a variant. Entities in union types are not supported`)
        }
        addEntityOrJsonObjectOrInterface(model, obj)
        variants.push(obj.name)
    })
}
Example #8
Source File: serializeToJSON.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
function serializeUnionType(type: GraphQLUnionType) {
  const { name, description } = type;
  const types = type.getTypes();

  return {
    kind: 'UnionType',
    name,
    description,
    types: types.map(type => ({
      name: type.name,
      description: type.description,
    })),
  };
}
Example #9
Source File: GraphQLStateGenerator.d.ts    From graphql-ts-client with MIT License 5 votes vote down vote up
protected createFetcheWriter(modelType: GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType, ctx: FetcherContext, stream: WriteStream, config: GeneratorConfig): GraphQLStateFetcherWriter;
Example #10
Source File: visitor.ts    From proto2graphql with MIT License 5 votes vote down vote up
function visitOneOf(oneOf: protobuf.OneOf, context: Context) {
  const unionType = new GraphQLUnionType({
    name: fullTypeName(oneOf),
    types: () => oneOf.fieldsArray.map(field => visitFieldType(field, context))
  });
  setType(unionType, context);
  return unionType;
}
Example #11
Source File: FetcherWriter.d.ts    From graphql-ts-client with MIT License 5 votes vote down vote up
protected importedNamesForSuperType(superType: GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType): string[];
Example #12
Source File: schema.ts    From squid with GNU General Public License v3.0 4 votes vote down vote up
function addEntityOrJsonObjectOrInterface(model: Model, type: GraphQLObjectType | GraphQLInterfaceType): void {
    if (model[type.name]) return

    let kind: 'entity' | 'object' | 'interface' = isEntityType(type)
        ? 'entity'
        :  type instanceof GraphQLInterfaceType ? 'interface' : 'object'

    let properties: Record<string, Prop> = {}
    let interfaces: string[] = []
    let indexes: Index[] = type instanceof GraphQLObjectType ? checkEntityIndexes(type) : []
    let description = type.description || undefined

    switch(kind) {
        case 'entity':
            model[type.name] = {kind, properties, description, interfaces, indexes}
            break
        case 'object':
            model[type.name] = {kind, properties, description, interfaces}
            break
        case 'interface':
            model[type.name] = {kind, properties, description}
            break
        default:
            throw unexpectedCase(kind)
    }

    let fields = type.getFields()
    if (kind == 'entity') {
        if (fields.id == null) {
            properties.id = {
                type: {kind: 'scalar', name: 'ID'},
                nullable: false
            }
        } else {
            let correctIdType = fields.id.type instanceof GraphQLNonNull
                && fields.id.type.ofType instanceof GraphQLScalarType
                && fields.id.type.ofType.name === 'ID'
            if (!correctIdType) {
                throw unsupportedFieldTypeError(type.name + '.id')
            }
        }
    }

    for (let key in fields) {
        let f: GraphQLField<any, any> = fields[key]

        handleFulltextDirective(model, type, f)

        let propName = `${type.name}.${f.name}`
        let fieldType = f.type
        let nullable = true
        let description = f.description || undefined
        let derivedFrom = checkDerivedFrom(type, f)
        let index = checkFieldIndex(type, f)
        let unique = index?.unique || false

        if (index) {
            indexes.push(index)
        }

        if (fieldType instanceof GraphQLNonNull) {
            nullable = false
            fieldType = fieldType.ofType
        }

        let list = unwrapList(fieldType)
        fieldType = list.item

        if (fieldType instanceof GraphQLScalarType) {
            properties[key] = {
                type: wrapWithList(list.nulls, {
                    kind: 'scalar',
                    name: fieldType.name
                }),
                nullable,
                description
            }
        } else if (fieldType instanceof GraphQLEnumType) {
            addEnum(model, fieldType)
            properties[key] = {
                type: wrapWithList(list.nulls, {
                    kind: 'enum',
                    name: fieldType.name
                }),
                nullable,
                description
            }
        } else if (fieldType instanceof GraphQLUnionType) {
            addUnion(model, fieldType)
            properties[key] = {
                type: wrapWithList(list.nulls, {
                    kind: 'union',
                    name: fieldType.name
                }),
                nullable,
                description
            }
        } else if (fieldType instanceof GraphQLObjectType) {
            if (isEntityType(fieldType)) {
                switch(list.nulls.length) {
                    case 0:
                        if (derivedFrom) {
                            if (!nullable) {
                                throw new SchemaError(`Property ${propName} must be nullable`)
                            }
                            properties[key] = {
                                type: {
                                    kind: 'lookup',
                                    entity: fieldType.name,
                                    field: derivedFrom.field
                                },
                                nullable,
                                description
                            }
                        } else {
                            if (unique && nullable) {
                                throw new SchemaError(`Unique property ${propName} must be non-nullable`)
                            }
                            properties[key] = {
                                type: {
                                    kind: 'fk',
                                    foreignEntity: fieldType.name
                                },
                                nullable,
                                unique,
                                description
                            }
                        }
                        break
                    case 1:
                        if (derivedFrom == null) {
                            throw new SchemaError(`@derivedFrom directive is required on ${propName} declaration`)
                        }
                        properties[key] = {
                            type: {
                                kind: 'list-lookup',
                                entity: fieldType.name,
                                field: derivedFrom.field
                            },
                            nullable: false,
                            description
                        }
                        break
                    default:
                        throw unsupportedFieldTypeError(propName)
                }
            } else {
                addEntityOrJsonObjectOrInterface(model, fieldType)
                properties[key] = {
                    type: wrapWithList(list.nulls, {
                        kind: 'object',
                        name: fieldType.name
                    }),
                    nullable,
                    description
                }
            }
        } else {
            throw unsupportedFieldTypeError(propName)
        }
    }

    if (kind != 'interface') {
        type.getInterfaces().forEach(i => {
            addEntityOrJsonObjectOrInterface(model, i)
            interfaces.push(i.name)
        })
    }
}
Example #13
Source File: selectors.ts    From tql with MIT License 4 votes vote down vote up
transform = (
  ast: DocumentNode,
  schema: GraphQLSchema
): ASTVisitor => {
  // const Field = imp("Field@timkendall@tql");
  // const Argument = imp("Argument@timkendall@tql");
  // const Variable = imp("Variable@timkendall@tql");
  // const InlineFragment = imp("InlineFragment@timkendall@tql");

  return {
    [Kind.DIRECTIVE_DEFINITION]: () => null,

    [Kind.SCALAR_TYPE_DEFINITION]: () => null,

    [Kind.ENUM_TYPE_DEFINITION]: (node) => {
      return null;
    },

    [Kind.ENUM_VALUE_DEFINITION]: (node) => {
      return null;
    },

    [Kind.INPUT_OBJECT_TYPE_DEFINITION]: (def) => {
      return null;
    },

    [Kind.OBJECT_TYPE_DEFINITION]: (node) => {
      const typename = node.name.value;
      const type = schema.getType(typename);

      invariant(
        type instanceof GraphQLObjectType,
        `Type "${typename}" was not instance of expected class GraphQLObjectType.`
      );

      const fields = Object.values(type.getFields());

      return code`
        ${/* selector interface */ ""}
        interface I${type.name}Selector {
          readonly __typename: () => Field<"__typename">
          ${fields.map(printSignature).join("\n")}
        }

        ${/* selector object */ ""}
        const ${typename}Selector: I${typename}Selector = {
          __typename: () => field("__typename"),
          ${fields.map(printMethod).join("\n")}
        }

        ${/* select fn */ ""}
        export const ${toLower(
          typename
        )} = <T extends ReadonlyArray<Selection>>(select: (t: I${typename}Selector) => T) => new SelectionBuilder<ISchema, "${type}", T>(SCHEMA as any, "${typename}", select(${typename}Selector))
      `;
    },

    [Kind.INTERFACE_TYPE_DEFINITION]: (node) => {
      const typename = node.name.value;
      const type = schema.getType(typename);

      invariant(
        type instanceof GraphQLInterfaceType,
        `Type "${typename}" was not instance of expected class GraphQLInterfaceType.`
      );

      // @note Get all implementors of this union
      const implementations = schema
        .getPossibleTypes(type)
        .map((type) => type.name);

      const fields = Object.values(type.getFields());

      return code`
        ${/* selector interface */ ""}
        interface I${type.name}Selector {
          readonly __typename: () => Field<"__typename">
          
          ${fields.map(printSignature).join("\n")}

          readonly on: <T extends ReadonlyArray<Selection>, F extends ${implementations
            .map((name) => `"${name}"`)
            .join(" | ")}>(
            type: F,
            select: (t: ${printConditionalSelectorArg(
              implementations.map((name) => name)
            )}) => T
          ) => InlineFragment<NamedType<F>, SelectionSet<T>>
        }

        ${/* selector object */ ""}
        const ${typename}Selector: I${typename}Selector = {
          __typename: () => field("__typename"),
          
          ${fields.map(printMethod).join("\n")}

          on: (
            type,
            select,
          ) => {
            switch(type) {
              ${implementations
                .map(
                  (name) => `
                case "${name}": {
                  return inlineFragment(
                    namedType("${name}"),
                    selectionSet(select(${name}Selector as Parameters<typeof select>[0])),
                  )
                }
              `
                )
                .join("\n")}
              default:
                throw new TypeConditionError({ 
                  selectedType: type, 
                  abstractType: "${type.name}",
                })
            }
          },
        }

        ${/* select fn */ ""}
        export const ${toLower(
          typename
        )} = <T extends ReadonlyArray<Selection>>(select: (t: I${typename}Selector) => T) => new SelectionBuilder<ISchema, "${type}", T>(SCHEMA as any, "${typename}", select(${typename}Selector))
      `;
    },

    [Kind.UNION_TYPE_DEFINITION]: (node) => {
      const typename = node.name.value;
      const type = schema.getType(typename);

      invariant(
        type instanceof GraphQLUnionType,
        `Type "${typename}" was not instance of expected class GraphQLUnionType.`
      );

      // @note Get all implementors of this union
      const implementations = schema
        .getPossibleTypes(type)
        .map((type) => type.name);

      return code`
        ${/* selector interface */ ""}
        interface I${type.name}Selector {
          readonly __typename: () => Field<"__typename">

          readonly on: <T extends ReadonlyArray<Selection>, F extends ${implementations
            .map((name) => `"${name}"`)
            .join(" | ")}>(
            type: F,
            select: (t: ${printConditionalSelectorArg(
              implementations.map((name) => name)
            )}) => T
          ) => InlineFragment<NamedType<F>, SelectionSet<T>>
        }

        ${/* selector object */ ""}
        const ${typename}Selector: I${typename}Selector = {
          __typename: () => field("__typename"),
          
          on: (
            type,
            select,
          ) => {
            switch(type) {
              ${implementations
                .map(
                  (name) => `
                case "${name}": {
                  return inlineFragment(
                    namedType("${name}"),
                    selectionSet(select(${name}Selector as Parameters<typeof select>[0])),
                  )
                }
              `
                )
                .join("\n")}
              default:
                throw new TypeConditionError({ 
                  selectedType: type, 
                  abstractType: "${type.name}",
                })
            }
          },
        }

        ${/* select fn */ ""}
        export const ${toLower(
          typename
        )} = <T extends ReadonlyArray<Selection>>(select: (t: I${typename}Selector) => T) => new SelectionBuilder<ISchema, "${type}", T>(SCHEMA as any, "${typename}", select(${typename}Selector))
      `;
    },

    [Kind.SCHEMA_DEFINITION]: (node) => {
      return null;
    },
  };
}
Example #14
Source File: generateSchema.ts    From davinci with MIT License 4 votes vote down vote up
generateGQLSchema = ({
	type,
	parentType,
	key,
	schemas = {},
	isInput = false,
	operationType,
	resolverMetadata,
	partial,
	transformMetadata = _.identity
}: IGenerateGQLSchemaArgs) => {
	// grab meta infos
	// maybe it's a decorated class, let's try to get the fields metadata
	const parentFieldsMetadata: IFieldDecoratorMetadata[] = parentType?.prototype
		? getFieldsMetadata(parentType.prototype.constructor, isInput, operationType, resolverMetadata)
		: [];
	const meta = _fp.find({ key }, parentFieldsMetadata) || ({} as IFieldDecoratorMetadata);
	const metadata = transformMetadata(meta, { isInput, type, parentType, schemas });
	const isRequired = !partial && metadata.opts?.required;

	// it's a primitive type, simple case
	if ([String, Number, Boolean, Date, Object].includes(type)) {
		const gqlType = scalarDict[type.name.toLowerCase()];
		const schema = isRequired ? new GraphQLNonNull(gqlType) : gqlType;
		return { schema, schemas };
	}

	// it's an array => recursively call makeSchema on the first array element
	if (Array.isArray(type)) {
		const gqlSchema = generateGQLSchema({
			type: type[0],
			parentType,
			schemas,
			key,
			isInput,
			operationType,
			resolverMetadata,
			partial,
			transformMetadata
		});
		const gqlType = new GraphQLList(gqlSchema.schema);
		const schema = isRequired ? new GraphQLNonNull(gqlType) : gqlType;

		return { schema, schemas: _.merge(schemas, gqlSchema.schemas) };
	}

	// it's a complex type => create nested types
	if (typeof type === 'function' || typeof type === 'object') {
		const suffix = isInput
			? _.compact([partial && 'Partial', _.upperFirst(operationType || ''), 'Input']).join('')
			: '';
		const typeMetadata = Reflector.getMetadata('davinci:graphql:types', type) || {};
		const name = `${typeMetadata.name || type.name || key}${suffix}`;

		// existing type, let's return it
		if (schemas[name]) {
			return { schema: schemas[name], schemas };
		}

		if (type instanceof UnionType) {
			const types = Array.isArray(type.types)
				? type.types.map(
					t =>
						generateGQLSchema({
							type: t,
							parentType,
							schemas,
							key,
							isInput,
							operationType,
							resolverMetadata,
							partial,
							transformMetadata
						}).schema
				  )
				: type.types;

			const unionTypeConfig = {
				..._.omit(metadata.opts, ['type']),
				name,
				types,
				resolveType: type.resolveType
			};

			schemas[name] = isRequired
				? new GraphQLNonNull(new GraphQLUnionType(unionTypeConfig))
				: new GraphQLUnionType(unionTypeConfig);
		} else {
			const objTypeConfig: any = {
				...metadata.opts,
				name,

				// eslint-disable-next-line no-use-before-define
				fields: createObjectFields({
					parentType: type,
					schemas,
					isInput,
					operationType,
					resolverMetadata,
					partial,
					transformMetadata
				})
			};

			const ObjectType = isInput ? GraphQLInputObjectType : GraphQLObjectType;
			schemas[name] = isRequired
				? new GraphQLNonNull(new ObjectType(objTypeConfig))
				: new ObjectType(objTypeConfig);
		}

		return { schema: schemas[name], schemas };
	}

	return null;
}
Example #15
Source File: buildObjectType.ts    From payload with MIT License 4 votes vote down vote up
function buildObjectType(name: string, fields: Field[], parentName: string, baseFields: BaseFields = {}): GraphQLType {
  const recursiveBuildObjectType = buildObjectType.bind(this);

  const fieldToSchemaMap = {
    number: (field: NumberField) => ({ type: withNullableType(field, GraphQLFloat) }),
    text: (field: TextField) => ({ type: withNullableType(field, GraphQLString) }),
    email: (field: EmailField) => ({ type: withNullableType(field, EmailAddressResolver) }),
    textarea: (field: TextareaField) => ({ type: withNullableType(field, GraphQLString) }),
    code: (field: CodeField) => ({ type: withNullableType(field, GraphQLString) }),
    date: (field: DateField) => ({ type: withNullableType(field, DateTimeResolver) }),
    point: (field: PointField) => ({ type: withNullableType(field, new GraphQLList(GraphQLFloat)) }),
    richText: (field: RichTextField) => ({
      type: withNullableType(field, GraphQLJSON),
      async resolve(parent, args, context) {
        if (args.depth > 0) {
          await createRichTextRelationshipPromise({
            req: context.req,
            siblingDoc: parent,
            depth: args.depth,
            field,
            showHiddenFields: false,
          });
        }

        return parent[field.name];
      },
      args: {
        depth: {
          type: GraphQLInt,
        },
      },
    }),
    upload: (field: UploadField) => {
      const { relationTo, label } = field;

      const uploadName = combineParentName(parentName, label === false ? toWords(field.name, true) : label);

      // If the relationshipType is undefined at this point,
      // it can be assumed that this blockType can have a relationship
      // to itself. Therefore, we set the relationshipType equal to the blockType
      // that is currently being created.

      const type = this.collections[relationTo].graphQL.type || newlyCreatedBlockType;

      const uploadArgs = {} as LocaleInputType;

      if (this.config.localization) {
        uploadArgs.locale = {
          type: this.types.localeInputType,
        };

        uploadArgs.fallbackLocale = {
          type: this.types.fallbackLocaleInputType,
        };
      }

      const relatedCollectionSlug = field.relationTo;
      const relatedCollection = this.collections[relatedCollectionSlug];

      const { find } = this.operations.collections;

      const upload = {
        args: uploadArgs,
        type,
        extensions: { complexity: 20 },
        async resolve(parent, args, context) {
          const value = parent[field.name];
          const locale = args.locale || context.req.locale;
          const fallbackLocale = args.fallbackLocale || context.req.fallbackLocale;

          let id = value;

          if (id) {
            id = id.toString();

            const relatedDocumentQuery = {
              collection: relatedCollection,
              where: {
                ...(args.where || {}),
                _id: {
                  equals: id,
                },
              },
              res: context.res,
              req: {
                ...context.req,
                locale,
                fallbackLocale,
              },
              depth: 0,
              pagination: false,
            };

            const relatedDocument = await find(relatedDocumentQuery);

            if (relatedDocument.docs[0]) return relatedDocument.docs[0];

            return null;
          }

          return null;
        },
      };

      const whereFields = this.collections[relationTo].config.fields;

      upload.args.where = {
        type: this.buildWhereInputType(
          uploadName,
          whereFields,
          uploadName,
        ),
      };

      return upload;
    },
    radio: (field: RadioField) => ({
      type: withNullableType(
        field,
        new GraphQLEnumType({
          name: combineParentName(parentName, field.name),
          values: formatOptions(field),
        }),
      ),
    }),
    checkbox: (field: CheckboxField) => ({ type: withNullableType(field, GraphQLBoolean) }),
    select: (field: SelectField) => {
      const fullName = combineParentName(parentName, field.name);

      let type: GraphQLType = new GraphQLEnumType({
        name: fullName,
        values: formatOptions(field),
      });

      type = field.hasMany ? new GraphQLList(type) : type;
      type = withNullableType(field, type);

      return { type };
    },
    relationship: (field: RelationshipField) => {
      const { relationTo, label } = field;
      const isRelatedToManyCollections = Array.isArray(relationTo);
      const hasManyValues = field.hasMany;
      const relationshipName = combineParentName(parentName, label === false ? toWords(field.name, true) : label);

      let type;
      let relationToType = null;

      if (Array.isArray(relationTo)) {
        relationToType = new GraphQLEnumType({
          name: `${relationshipName}_RelationTo`,
          values: relationTo.reduce((relations, relation) => ({
            ...relations,
            [formatName(relation)]: {
              value: relation,
            },
          }), {}),
        });

        const types = relationTo.map((relation) => this.collections[relation].graphQL.type);

        type = new GraphQLObjectType({
          name: `${relationshipName}_Relationship`,
          fields: {
            relationTo: {
              type: relationToType,
            },
            value: {
              type: new GraphQLUnionType({
                name: relationshipName,
                types,
                async resolveType(data, { req: { payload } }) {
                  return payload.collections[data.collection].graphQL.type.name;
                },
              }),
            },
          },
        });
      } else {
        ({ type } = this.collections[relationTo as string].graphQL);
      }

      // If the relationshipType is undefined at this point,
      // it can be assumed that this blockType can have a relationship
      // to itself. Therefore, we set the relationshipType equal to the blockType
      // that is currently being created.

      type = type || newlyCreatedBlockType;

      const relationshipArgs: {
        locale?: unknown
        fallbackLocale?: unknown
        where?: unknown
        page?: unknown
        limit?: unknown
      } = {};

      if (this.config.localization) {
        relationshipArgs.locale = {
          type: this.types.localeInputType,
        };

        relationshipArgs.fallbackLocale = {
          type: this.types.fallbackLocaleInputType,
        };
      }

      const {
        collections,
        operations: {
          collections: {
            find,
          },
        },
      } = this;

      const relationship = {
        args: relationshipArgs,
        type: hasManyValues ? new GraphQLList(type) : type,
        extensions: { complexity: 10 },
        async resolve(parent, args, context) {
          const value = parent[field.name];
          const locale = args.locale || context.req.locale;
          const fallbackLocale = args.fallbackLocale || context.req.fallbackLocale;
          let relatedCollectionSlug = field.relationTo;

          if (hasManyValues) {
            const results = [];
            const resultPromises = [];

            const createPopulationPromise = async (relatedDoc, i) => {
              let id = relatedDoc;
              let collectionSlug = field.relationTo;

              if (isRelatedToManyCollections) {
                collectionSlug = relatedDoc.relationTo;
                id = relatedDoc.value;
              }

              const result = await find({
                collection: collections[collectionSlug as string],
                where: {
                  ...(args.where || {}),
                  _id: {
                    equals: id,
                  },
                },
                res: context.res,
                req: {
                  ...context.req,
                  locale,
                  fallbackLocale,
                },
                depth: 0,
                pagination: false,
              });

              if (result.docs.length === 1) {
                if (isRelatedToManyCollections) {
                  results[i] = {
                    relationTo: collectionSlug,
                    value: {
                      ...result.docs[0],
                      collection: collectionSlug,
                    },
                  };
                } else {
                  [results[i]] = result.docs;
                }
              }
            };

            if (value) {
              value.forEach((relatedDoc, i) => {
                resultPromises.push(createPopulationPromise(relatedDoc, i));
              });
            }

            await Promise.all(resultPromises);
            return results;
          }

          let id = value;
          if (isRelatedToManyCollections && value) {
            id = value.value;
            relatedCollectionSlug = value.relationTo;
          }

          if (id) {
            id = id.toString();

            const relatedDocumentQuery = {
              collection: collections[relatedCollectionSlug as string],
              where: {
                ...(args.where || {}),
                id: {
                  equals: id,
                },
              },
              ...context,
              depth: 0,
            };

            if (args.page) relatedDocumentQuery.paginate.page = args.page;
            if (args.limit) relatedDocumentQuery.paginate.limit = args.limit;

            const relatedDocument = await find(relatedDocumentQuery);

            if (relatedDocument.docs[0]) {
              if (isRelatedToManyCollections) {
                return {
                  relationTo: relatedCollectionSlug,
                  value: {
                    ...relatedDocument.docs[0],
                    collection: relatedCollectionSlug,
                  },
                };
              }

              return relatedDocument.docs[0];
            }

            return null;
          }

          return null;
        },
      };

      if (hasManyValues) {
        relationship.args.page = { type: GraphQLInt };
        relationship.args.limit = { type: GraphQLInt };
      }

      if (Array.isArray(relationTo)) {
        const relatedCollectionFields = relationTo.reduce((allFields, relation) => [
          ...allFields,
          ...collections[relation].config.fields,
        ], []);

        relationship.args.where = {
          type: this.buildWhereInputType(
            relationshipName,
            relatedCollectionFields,
            relationshipName,
          ),
        };
      } else {
        const whereFields = this.collections[relationTo].config.fields;

        relationship.args.where = {
          type: this.buildWhereInputType(
            relationshipName,
            whereFields,
            relationshipName,
          ),
        };
      }

      return relationship;
    },
    array: (field: ArrayField) => {
      const fullName = combineParentName(parentName, field.label === false ? toWords(field.name, true) : field.label);
      let type = recursiveBuildObjectType(fullName, field.fields, fullName);
      type = new GraphQLList(withNullableType(field, type));

      return { type };
    },
    group: (field: GroupField) => {
      const fullName = combineParentName(parentName, field.label === false ? toWords(field.name, true) : field.label);
      const type = recursiveBuildObjectType(fullName, field.fields, fullName);

      return { type };
    },
    blocks: (field: BlockField) => {
      const blockTypes = field.blocks.map((block) => {
        this.buildBlockType(block);
        return this.types.blockTypes[block.slug];
      });

      const fullName = combineParentName(parentName, field.label === false ? toWords(field.name, true) : field.label);

      const type = new GraphQLList(new GraphQLUnionType({
        name: fullName,
        types: blockTypes,
        resolveType: (data) => this.types.blockTypes[data.blockType].name,
      }));

      return { type };
    },
    row: (field) => field.fields.reduce((subFieldSchema, subField) => {
      const buildSchemaType = fieldToSchemaMap[subField.type];

      if (!fieldIsPresentationalOnly(subField) && buildSchemaType) {
        return {
          ...subFieldSchema,
          [formatName(subField.name)]: buildSchemaType(subField),
        };
      }

      return subFieldSchema;
    }, {}),
  };

  const objectSchema = {
    name,
    fields: () => fields.reduce((schema, field) => {
      if (!fieldIsPresentationalOnly(field) && !field.hidden) {
        const fieldSchema = fieldToSchemaMap[field.type];
        if (fieldSchema) {
          if (fieldAffectsData(field)) {
            return {
              ...schema,
              [formatName(field.name)]: fieldSchema(field),
            };
          }

          return {
            ...schema,
            ...fieldSchema(field),
          };
        }
      }

      return schema;
    }, baseFields),
  };

  const newlyCreatedBlockType = new GraphQLObjectType(objectSchema);

  return newlyCreatedBlockType;
}
Example #16
Source File: Generator.ts    From graphql-ts-client with MIT License 4 votes vote down vote up
async generate() {
        
        const schema = await this.loadSchema();
        validateConfigAndSchema(this.config, schema);
        await this.rmdirIfNecessary();
        await this.mkdirIfNecessary();

        const inheritanceInfo = new InheritanceInfo(schema);
        const fetcherTypes: Array<GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType> = [];
        const connections = new Map<GraphQLType, Connection>();
        const edgeTypes = new Set<GraphQLType>();
        const inputTypes: GraphQLInputObjectType[] = [];
        const enumTypes: GraphQLEnumType[] = [];
        const typeMap = schema.getTypeMap();
        for (const typeName in typeMap) {
            if (!typeName.startsWith("__")) {
                const type = typeMap[typeName]!;
                if (type instanceof GraphQLObjectType || type instanceof GraphQLInterfaceType) {
                    const tuple = connectionTypeTuple(type);
                    if (tuple !== undefined) {
                        connections.set(tuple[0], {
                            edgeType: tuple[1],
                            nodeType: tuple[2]
                        });
                        edgeTypes.add(tuple[1]);
                    }
                }
                if (type instanceof GraphQLObjectType || type instanceof GraphQLInterfaceType || type instanceof GraphQLUnionType) {
                    fetcherTypes.push(type);
                } else if (type instanceof GraphQLInputObjectType) {
                    inputTypes.push(type);
                } else if (type instanceof GraphQLEnumType) {
                    enumTypes.push(type);
                }
            }
        }

        const configuredIdFieldMap = this.config.idFieldMap ?? {};
        const entityTypes = new Set<GraphQLType>();
        const embeddedTypes = new Set<GraphQLType>();
        const idFieldMap = new Map<GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType, GraphQLField<any, any>>();
        const triggerableTypes = new Set<GraphQLType>();
        const typesWithParameterizedField = new Set<GraphQLObjectType | GraphQLInterfaceType>();
        for (const fetcherType of fetcherTypes) {
            if (connections.has(fetcherType) || edgeTypes.has(fetcherType)) {
                continue;
            }
            if (fetcherType instanceof GraphQLObjectType || fetcherType instanceof GraphQLInterfaceType) {
                const fieldMap = fetcherType.getFields();
                if (fetcherType.name === "Query") {
                    if (Object.keys(fieldMap).length !== 0) {
                        triggerableTypes.add(fetcherType);
                    }
                } else {
                    let idFieldName = configuredIdFieldMap[fetcherType.name];
                    if (idFieldName === undefined) {
                        let configuredUpcastType: GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType | undefined = undefined;
                        inheritanceInfo.visitUpcastTypesRecursively(fetcherType, upcastType => {
                            const newIdFieldName = configuredIdFieldMap[upcastType.name];
                            if (idFieldName === undefined) {
                                configuredUpcastType = upcastType;
                                idFieldName = newIdFieldName;
                            } else if (idFieldName !== newIdFieldName) {
                                throw new Error(
                                    `Conflict id property configuration: ${configuredUpcastType!.name}.${idFieldName} and ${fetcherType.name}.${newIdFieldName}`
                                );
                            }
                        });
                    }
                    const idField = fieldMap[idFieldName ?? "id"];
                    if (idField !== undefined && idField !== null) {
                        idFieldMap.set(fetcherType, idField);
                        entityTypes.add(fetcherType);
                        if (Object.keys(fieldMap).length !== 1) {
                            triggerableTypes.add(fetcherType);
                        }
                    } else {
                        embeddedTypes.add(fetcherType);
                    }
                }
                for (const fieldName in fieldMap) {
                    const args = fieldMap[fieldName].args;
                    if (args.length !== 0) {
                        typesWithParameterizedField.add(fetcherType);
                        break;
                    }
                }
            }
        }

        const ctx: FetcherContext = {
            schema,
            inheritanceInfo,
            fetcherTypes,
            entityTypes,
            embeddedTypes,
            connections,
            edgeTypes,
            triggerableTypes,
            idFieldMap,
            typesWithParameterizedField
        };

        const promises: Promise<any>[] = [];
        if (fetcherTypes.length !== 0) {
            await this.mkdirIfNecessary("fetchers");
            promises.push(this.generateFetcherTypes(ctx));
        }
        if (inputTypes.length !== 0) {
            await this.mkdirIfNecessary("inputs");
            promises.push(this.generateInputTypes(inputTypes));
        }
        if (enumTypes.length !== 0) {
            await this.mkdirIfNecessary("enums");
            promises.push(this.generateEnumTypes(enumTypes));
        }

        promises.push(this.generateCommonTypes(schema, inheritanceInfo));

        this.generateServices(ctx, promises);

        promises.push(this.writeIndex(schema));

        await Promise.all(promises);
    }
Example #17
Source File: getFields.test.ts    From amplify-codegen with Apache License 2.0 4 votes vote down vote up
describe('getField', () => {
  const nestedType = new GraphQLObjectType({
    name: 'NestedObject',
    fields: () => ({
      level: { type: GraphQLInt },
      subObj: { type: nestedType },
    }),
  });

  const schema = new GraphQLSchema({
    query: new GraphQLObjectType({
      name: 'Query',
      fields: {
        foo: { type: GraphQLInt },
        nested: { type: nestedType },
      },
    }),
  });

  it('should support simple scalar', () => {
    const queries = schema.getQueryType().getFields();
    expect(getFields(queries.foo, schema, 3, { useExternalFragmentForS3Object: false })).toEqual({
      name: 'foo',
      fields: [],
      fragments: [],
      hasBody: false,
    });
    expect(getFragment).not.toHaveBeenCalled();
  });

  it('it should recursively resolve fields up to max depth', () => {
    const queries = schema.getQueryType().getFields();
    expect(getFields(queries.nested, schema, 2, { useExternalFragmentForS3Object: false })).toEqual({
      name: 'nested',
      fields: [
        {
          name: 'level',
          fields: [],
          fragments: [],
          hasBody: false,
        },
        {
          name: 'subObj',
          fields: [
            {
              name: 'level',
              fields: [],
              fragments: [],
              hasBody: false,
            },
          ],
          fragments: [],
          hasBody: true,
        },
      ],
      fragments: [],
      hasBody: true,
    });
  });

  it('should not return anything for complex type when the depth is < 1', () => {
    const queries = schema.getQueryType().getFields();
    expect(getFields(queries.nested, schema, 0, { useExternalFragmentForS3Object: false })).toBeUndefined();
  });
  describe('When type is an Interface', () => {
    beforeEach(() => {
      jest.resetAllMocks();
    });
    const shapeInterfaceType = new GraphQLInterfaceType({
      name: 'Entity',
      fields: {
        name: { type: GraphQLString },
      },
    });
    const rectangleType = new GraphQLObjectType({
      name: 'Rectangle',
      fields: {
        name: { type: GraphQLString },
        length: { type: GraphQLInt },
        width: { type: GraphQLInt },
      },
      interfaces: () => [shapeInterfaceType],
    });

    const circleType = new GraphQLObjectType({
      name: 'Circle',
      fields: {
        name: { type: GraphQLString },
        radius: { type: GraphQLInt },
      },
      interfaces: () => [shapeInterfaceType],
    });

    const schema = new GraphQLSchema({
      query: new GraphQLObjectType({
        name: 'Query',
        fields: {
          shapeInterface: { type: shapeInterfaceType },
        },
      }),
      types: [circleType, rectangleType],
    });

    it('interface - should return fragments of all the implementations', () => {
      const maxDepth = 2;
      const getPossibleTypeSpy = jest.spyOn(schema, 'getPossibleTypes');
      getFields(schema.getQueryType().getFields().shapeInterface, schema, maxDepth, { useExternalFragmentForS3Object: false });
      expect(getPossibleTypeSpy).toHaveBeenCalled();
      expect(getFragment).toHaveBeenCalled();

      const commonField = {
        name: 'name',
        fragments: [],
        hasBody: false,
        fields: [],
      };

      expect(getFragment.mock.calls[0][0]).toEqual(circleType);
      expect(getFragment.mock.calls[0][1]).toEqual(schema);
      expect(getFragment.mock.calls[0][2]).toEqual(maxDepth);
      expect(getFragment.mock.calls[0][3]).toEqual([commonField]);

      expect(getFragment.mock.calls[1][0]).toEqual(rectangleType);
      expect(getFragment.mock.calls[1][1]).toEqual(schema);
      expect(getFragment.mock.calls[1][2]).toEqual(maxDepth);
      expect(getFragment.mock.calls[1][3]).toEqual([commonField]);
    });
  });
  describe('When type is an union', () => {
    beforeEach(() => {
      jest.resetAllMocks();
    });
    const rectangleType = new GraphQLObjectType({
      name: 'Rectangle',
      fields: {
        length: { type: GraphQLInt },
        width: { type: GraphQLInt },
      },
    });

    const circleType = new GraphQLObjectType({
      name: 'Circle',
      fields: {
        radius: { type: GraphQLInt },
      },
    });
    const shapeResultUnion = new GraphQLUnionType({
      name: 'ShapeResultUnion',
      types: [circleType, rectangleType],
    });

    const schema = new GraphQLSchema({
      query: new GraphQLObjectType({
        name: 'Query',
        fields: {
          shapeResult: { type: shapeResultUnion },
        },
      }),
    });

    it('union - should return fragments of all the types', () => {
      const maxDepth = 2;
      const getPossibleTypeSpy = jest.spyOn(schema, 'getPossibleTypes');
      getFields(schema.getQueryType().getFields().shapeResult, schema, maxDepth, { useExternalFragmentForS3Object: false });
      expect(getPossibleTypeSpy).toHaveBeenCalled();
      expect(getFragment).toHaveBeenCalled();

      const commonField = []; // unions don't have to have common field

      expect(getFragment.mock.calls[0][0]).toEqual(circleType);
      expect(getFragment.mock.calls[0][1]).toEqual(schema);
      expect(getFragment.mock.calls[0][2]).toEqual(maxDepth);
      expect(getFragment.mock.calls[0][3]).toEqual(commonField);

      expect(getFragment.mock.calls[1][0]).toEqual(rectangleType);
      expect(getFragment.mock.calls[1][1]).toEqual(schema);
      expect(getFragment.mock.calls[1][2]).toEqual(maxDepth);
      expect(getFragment.mock.calls[1][3]).toEqual(commonField);
    });
  });

  describe('aggregateItems should generate two additional levels', () => {
    beforeEach(() => {
      jest.resetAllMocks();
    });
    const aggregateScalarResult = new GraphQLObjectType({
      name: 'SearchableAggregateScalarResult',
      fields: {
        value: { type: GraphQLFloat },
      },
    });

    const aggregateBucketResultItem = new GraphQLObjectType({
      name: 'SearchableAggregateBucketResultItem',
      fields: {
        key: { type: GraphQLString },
        doc_count: { type: GraphQLInt },
      },
    });

    const aggregateBucketResult = new GraphQLObjectType({
      name: 'SearchableAggregateBucketResult',
      fields: {
        buckets: { type: aggregateBucketResultItem },
      },
    });
    
    const aggregateResult = new GraphQLUnionType({
      name: 'SearchableAggregateGenericResult',
      types: [aggregateScalarResult, aggregateBucketResult],
    });

    const aggregateItemsObject = new GraphQLObjectType({
      name: 'SearchableAggregateResult',
      fields: {
        name: { type: GraphQLString },
        result: { type: aggregateResult },
      },
    });

    const schema = new GraphQLSchema({
      query: new GraphQLObjectType({
        name: 'Query',
        fields: {
          aggregateItems: { type: aggregateItemsObject },
        },
      }),
    });

    it('aggregateItems property should traverse two additional levels to generate required fields with default depth 2', () => {
      const maxDepth = 2;
      const getPossibleTypeSpy = jest.spyOn(schema, 'getPossibleTypes');
      getFields(schema.getQueryType().getFields().aggregateItems, schema, maxDepth, { useExternalFragmentForS3Object: false });
      expect(getPossibleTypeSpy).toHaveBeenCalled();
      expect(getFragment).toHaveBeenCalled();

      const commonField = []; // unions don't have to have common field

      expect(getFragment.mock.calls[0][0]).toEqual(aggregateScalarResult);
      expect(getFragment.mock.calls[0][1]).toEqual(schema);
      expect(getFragment.mock.calls[0][2]).toEqual(maxDepth - 1);
      expect(getFragment.mock.calls[0][3]).toEqual(commonField);

      expect(getFragment.mock.calls[1][0]).toEqual(aggregateBucketResult);
      expect(getFragment.mock.calls[1][1]).toEqual(schema);
      expect(getFragment.mock.calls[1][2]).toEqual(maxDepth - 1);
      expect(getFragment.mock.calls[1][3]).toEqual(commonField);
    });
  });
});
Example #18
Source File: index.ts    From graphql-mesh with MIT License 4 votes vote down vote up
transformSchema(schema: GraphQLSchema, rawSource: SubschemaConfig) {
    const federationConfig: FederationConfig<any> = {};

    rawSource.merge = {};
    if (this.config?.types) {
      const queryType = schema.getQueryType();
      const queryTypeFields = queryType.getFields();
      for (const type of this.config.types) {
        rawSource.merge[type.name] = {};
        const fields: FederationFieldsConfig = {};
        if (type.config?.fields) {
          for (const field of type.config.fields) {
            fields[field.name] = field.config;
            rawSource.merge[type.name].fields = rawSource.merge[type.name].fields || {};
            rawSource.merge[type.name].fields[field.name] = rawSource.merge[type.name].fields[field.name] || {};
            if (field.config.requires) {
              rawSource.merge[type.name].fields[field.name].computed = true;
              rawSource.merge[type.name].fields[field.name].selectionSet = `{ ${field.config.requires} }`;
            }
          }
        }
        // If a field is a key field, it should be GraphQLID

        if (type.config?.keyFields) {
          rawSource.merge[type.name].selectionSet = `{ ${type.config.keyFields.join(' ')} }`;
          for (const fieldName of type.config.keyFields) {
            const objectType = schema.getType(type.name) as GraphQLObjectType;
            if (objectType) {
              const existingType = objectType.getFields()[fieldName].type;
              objectType.getFields()[fieldName].type = isNonNullType(existingType)
                ? new GraphQLNonNull(GraphQLID)
                : GraphQLID;
            }
          }
        }

        let resolveReference: MergedTypeResolver<any>;
        if (type.config?.resolveReference) {
          const resolveReferenceConfig = type.config.resolveReference;
          if (typeof resolveReferenceConfig === 'string') {
            const fn$ = loadFromModuleExportExpression<any>(resolveReferenceConfig, {
              cwd: this.baseDir,
              defaultExportName: 'default',
              importFn: this.importFn,
            });
            resolveReference = (...args: any[]) => fn$.then(fn => fn(...args));
          } else if (typeof resolveReferenceConfig === 'function') {
            resolveReference = resolveReferenceConfig;
          } else {
            const queryField = queryTypeFields[resolveReferenceConfig.queryFieldName];
            const keyArg = resolveReferenceConfig.keyArg || queryField.args[0].name;
            const keyField = type.config.keyFields[0];
            const isBatch = isListType(queryField.args.find(arg => arg.name === keyArg));
            resolveReference = async (root, context, info) => {
              const result = await context[this.apiName].Query[queryField.name]({
                root,
                ...(isBatch
                  ? {
                      key: root[keyField],
                      argsFromKeys: (keys: string[]) => ({
                        [keyArg]: keys,
                      }),
                    }
                  : {
                      args: {
                        [keyArg]: root[keyField],
                      },
                    }),
                context,
                info,
              });
              return {
                ...root,
                ...result,
              };
            };
          }
          rawSource.merge[type.name].resolve = resolveReference;
        }
        federationConfig[type.name] = {
          ...type.config,
          resolveReference,
          fields,
        };
      }
    }

    const entityTypes = Object.fromEntries(
      Object.entries(federationConfig)
        .filter(([, { keyFields }]) => keyFields?.length)
        .map(([objectName]) => {
          const type = schema.getType(objectName);
          if (!isObjectType(type)) {
            throw new Error(`Type "${objectName}" is not an object type and can't have a key directive`);
          }
          return [objectName, type];
        })
    );

    const hasEntities = !!Object.keys(entityTypes).length;

    const sdlWithFederationDirectives = addFederationAnnotations(printSchemaWithDirectives(schema), federationConfig);

    const schemaWithFederationQueryType = mapSchema(schema, {
      [MapperKind.QUERY]: type => {
        const config = type.toConfig();
        return new GraphQLObjectType({
          ...config,
          fields: {
            ...config.fields,
            ...(hasEntities && {
              _entities: entitiesField,
              _service: {
                ...serviceField,
                resolve: () => ({ sdl: sdlWithFederationDirectives }),
              },
            }),
          },
        });
      },
    });

    const schemaWithUnionType = mapSchema(schemaWithFederationQueryType, {
      [MapperKind.UNION_TYPE]: type => {
        if (type.name === EntityType.name) {
          return new GraphQLUnionType({
            ...EntityType.toConfig(),
            types: Object.values(entityTypes),
          });
        }
        return type;
      },
    });

    // Not using transformSchema since it will remove resolveReference
    Object.entries(federationConfig).forEach(([objectName, currentFederationConfig]) => {
      if (currentFederationConfig.resolveReference) {
        const type = schemaWithUnionType.getType(objectName);
        if (!isObjectType(type)) {
          throw new Error(`Type "${objectName}" is not an object type and can't have a resolveReference function`);
        }
        type.resolveObject = currentFederationConfig.resolveReference;
      }
    });

    return schemaWithUnionType;
  }
Example #19
Source File: generateSchema.test.ts    From davinci with MIT License 4 votes vote down vote up
describe('schema generation', () => {
	describe('#generateGQLSchema', () => {
		it('supports primitive types', () => {
			class Customer {
				@field()
				firstname: string;
				@field()
				age: number;
				@field()
				isActive: boolean;
				@field()
				date: Date
				@field()
				blob: Object

				@field({ type: String })
				something() {}
			}

			const { schema } = generateGQLSchema({ type: Customer });

			should(schema)
				.have.property('name')
				.equal('Customer');
			const fields = schema.getFields();

			should(Object.keys(fields)).be.deepEqual(['firstname', 'age', 'isActive', 'date', 'blob', 'something']);
			should(fields.firstname.type).be.equal(GraphQLString);
			should(fields.age.type).be.equal(GraphQLFloat);
			should(fields.isActive.type).be.equal(GraphQLBoolean);
			should(fields.date.type).be.equal(GraphQLDateTime);
			should(fields.blob.type).be.equal(GraphQLJSON);
			should(fields.something.type).be.equal(GraphQLString);
		});

		it('supports nested classes', () => {
			class CustomerBirth {
				@field()
				place: string;
			}

			class Customer {
				@field()
				birth: CustomerBirth;
			}

			const { schemas } = generateGQLSchema({ type: Customer });

			should(schemas)
				.have.property('Customer')
				.instanceOf(GraphQLObjectType);

			// @ts-ignore
			const { birth } = schemas.Customer.getFields();
			should(birth.type).be.instanceOf(GraphQLObjectType);
			// @ts-ignore
			const { place } = schemas.CustomerBirth.getFields();
			should(place.type).be.equal(GraphQLString);
		});

		it('should pass fields defined as function as resolve', () => {
			class Customer {
				@field({ type: String })
				something(parent, args, context, info) {
					return { parent, args, context, info };
				}

				@field({ type: [Number] })
				somethingArray(parent, args, context, info) {
					return { parent, args, context, info };
				}
			}

			const { schema } = generateGQLSchema({ type: Customer });

			should(schema).be.instanceOf(GraphQLObjectType);

			// @ts-ignore
			const { something, somethingArray } = schema.getFields();

			should(something.type).be.equal(GraphQLString);
			should(something.resolve).be.equal(Customer.prototype.something);
			should(somethingArray.type).be.instanceOf(GraphQLList);
			should(somethingArray.type.ofType).be.equal(GraphQLFloat);
			should(somethingArray.resolve).be.equal(Customer.prototype.somethingArray);
		});

		it('should create an external resolver', () => {
			class Book {}
			class Author {
				@field()
				title: string;
			}

			// @ts-ignore
			class AuthorController {
				@fieldResolver(Book, 'authors', [Author])
				getBookAuthors() {}
			}

			const { schema } = generateGQLSchema({ type: Book });

			should(schema).be.instanceOf(GraphQLObjectType);

			// @ts-ignore
			const { authors } = schema.getFields();

			should(authors.type).be.instanceOf(GraphQLList);
			should(authors.type.ofType).be.instanceOf(GraphQLObjectType);
			should(authors.resolve).be.type('function');
		});

		it('supports arrays', () => {
			class CustomerPhone {
				@field()
				number: string;
			}

			class Customer {
				@field({ type: [CustomerPhone] })
				phones: CustomerPhone[];
				@field({ type: [String] })
				tags: string[];
			}

			const schemas: any = generateGQLSchema({ type: Customer }).schemas;

			should(Object.keys(schemas.Customer.getFields())).be.deepEqual(['phones', 'tags']);
			should(Object.keys(schemas.CustomerPhone.getFields())).be.deepEqual(['number']);

			const { tags, phones } = schemas.Customer.getFields();

			should(tags.type)
				.be.instanceOf(GraphQLList)
				.have.property('ofType')
				.equal(GraphQLString);

			should(phones.type)
				.be.instanceOf(GraphQLList)
				.have.property('ofType')
				.equal(schemas.CustomerPhone);
		});

		it('supports union types', () => {
			class Cat {
				@field()
				breed: string;
			}

			class Human {
				@field()
				phone: string;
			}

			const resolveType = () => 'Human';
			const Animal = new UnionType('Animal', [Cat, Human], resolveType);

			const schemas: any = generateGQLSchema({ type: Animal }).schemas;

			should(Object.keys(schemas)).be.deepEqual(['Cat', 'Human', 'Animal']);
			const types = schemas.Animal.getTypes();
			should(types[0].name).be.equal('Cat')
			should(types[1].name).be.equal('Human')
			should(types).have.length(2);
			should(schemas.Animal).be.instanceOf(GraphQLUnionType);
		});

		it('if transformMetadata is supplied, it should transform the metadata', () => {
			class Customer {
				@field()
				firstname: string;
			}

			const transformMetadata = (metadata, { type: t, parentType }) => {
				const type = _fp.get('opts.type', metadata) || t;

				if (type === String && parentType.name !== 'Query') {
					class Query {
						@field()
						EQ: string;
					}
					return _fp.set('opts.type', Query, metadata);
				}

				return metadata;
			};

			const schemas: any = generateGQLSchema({ type: Customer, transformMetadata }).schemas;
			const fields = schemas.Customer.getFields();
			should(fields.firstname.type).be.instanceOf(GraphQLObjectType);
			should(fields.firstname.type.getFields()).have.property('EQ');
		});
	});
});