graphql#isNonNullType TypeScript Examples

The following examples show how to use graphql#isNonNullType. 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-eslint with MIT License 6 votes vote down vote up
export function getBaseType(type: GraphQLOutputType): GraphQLNamedType {
  if (isNonNullType(type) || isListType(type)) {
    return getBaseType(type.ofType);
  }
  return type;
}
Example #2
Source File: resolveDataByUnionInputType.ts    From graphql-mesh with MIT License 6 votes vote down vote up
export function resolveDataByUnionInputType(data: any, type: GraphQLInputType, schemaComposer: SchemaComposer): any {
  if (data) {
    if (isListType(type)) {
      return asArray(data).map(elem => resolveDataByUnionInputType(elem, type.ofType, schemaComposer));
    }
    if (isNonNullType(type)) {
      return resolveDataByUnionInputType(data, type.ofType, schemaComposer);
    }
    if (isInputObjectType(type)) {
      const fieldMap = type.getFields();
      const isOneOf = schemaComposer.getAnyTC(type).getDirectiveByName('oneOf');
      data = asArray(data)[0];
      for (const propertyName in data) {
        const fieldName = sanitizeNameForGraphQL(propertyName);
        const field = fieldMap[fieldName];
        if (field) {
          if (isOneOf) {
            const resolvedData = resolveDataByUnionInputType(data[fieldName], field.type, schemaComposer);
            return resolvedData;
          }
          const realFieldName = (field.extensions?.propertyName as string) || fieldName;
          data[realFieldName] = resolveDataByUnionInputType(data[fieldName], field.type, schemaComposer);
        }
      }
    }
  }
  return data;
}
Example #3
Source File: isRequired.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
export default function isRequired(typeObj: GraphQLType): boolean {
  if (isNonNullType(typeObj) && isListType(typeObj.ofType)) {
    // See if it's a Non-null List of Non-null Types
    return isRequired(typeObj.ofType.ofType);
  }
  if (isListType(typeObj)) {
    // See if it's a Nullable List of Non-null Types
    return isNonNullType(typeObj.ofType);
  }
  return isNonNullType(typeObj);
}
Example #4
Source File: complextypes.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
export function isS3Field(field: GraphQLType): boolean {
  if (isObjectType(field) || isInputObjectType(field)) {
    const fields = field.getFields();
    const stringFields = Object.keys(fields).filter(f => {
      const fieldType = fields[f].type;
      const typeName = getNamedType(fieldType);
      return (typeName.name === 'String' && isNonNullType(fieldType));
    });
    const isS3FileField = S3_FIELD_NAMES.every(fieldName => stringFields.includes(fieldName));
    if (isS3FileField) {
      return true;
    }
  }
  return false;
}
Example #5
Source File: types.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
export function typeNameFromGraphQLType(
  context: LegacyCompilerContext,
  type: GraphQLType,
  bareTypeName?: string | null,
  nullable = true
): string {
  if (isNonNullType(type)) {
    return typeNameFromGraphQLType(context, type.ofType, bareTypeName, false);
  }

  let typeName;
  if (isListType(type)) {
    typeName = `Array< ${typeNameFromGraphQLType(context, type.ofType, bareTypeName, true)} >`;
  } else if (type instanceof GraphQLScalarType) {
    typeName =
      builtInScalarMap[type.name] ||
      appSyncScalars[type.name] ||
      (context.options.passthroughCustomScalars ? context.options.customScalarsPrefix + type.name : builtInScalarMap[GraphQLString.name]);
  } else {
    typeName = bareTypeName || type.name;
  }

  return nullable ? typeName + ' | null' : typeName;
}
Example #6
Source File: helpers.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
// Types

  typeNameFromGraphQLType(type: GraphQLType, unmodifiedTypeName?: string, isOptional?: boolean): string {
    if (isNonNullType(type)) {
      return this.typeNameFromGraphQLType(type.ofType, unmodifiedTypeName, false);
    } else if (isOptional === undefined) {
      isOptional = true;
    }

    let typeName;
    if (isListType(type)) {
      typeName = '[' + this.typeNameFromGraphQLType(type.ofType, unmodifiedTypeName) + ']';
    } else if (type instanceof GraphQLScalarType) {
      typeName = this.typeNameForScalarType(type);
    } else {
      typeName = unmodifiedTypeName || type.name;
    }

    return isOptional ? typeName + '?' : typeName;
  }
Example #7
Source File: helpers.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
fieldTypeEnum(type: GraphQLType, structName: string): string {
    if (isNonNullType(type)) {
      return `.nonNull(${this.fieldTypeEnum(type.ofType, structName)})`;
    } else if (isListType(type)) {
      return `.list(${this.fieldTypeEnum(type.ofType, structName)})`;
    } else if (type instanceof GraphQLScalarType) {
      return `.scalar(${this.typeNameForScalarType(type)}.self)`;
    } else if (type instanceof GraphQLEnumType) {
      return `.scalar(${type.name}.self)`;
    } else if (isCompositeType(type)) {
      return `.object(${structName}.selections)`;
    } else {
      throw new Error(`Unknown field type: ${type}`);
    }
  }
Example #8
Source File: helpers.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
// Properties

  propertyFromField(field: Field, namespace?: string): Field & Property & Struct {
    const { responseKey, isConditional } = field;

    const propertyName = isMetaFieldName(responseKey) ? responseKey : camelCase(responseKey);

    const structName = join([namespace, this.structNameForPropertyName(responseKey)], '.');

    let type = field.type;

    if (isConditional && isNonNullType(type)) {
      type = type.ofType;
    }

    const isOptional = !(type instanceof GraphQLNonNull);

    const unmodifiedType = getNamedType(field.type);

    const unmodifiedTypeName = isCompositeType(unmodifiedType) ? structName : unmodifiedType.name;

    const typeName = this.typeNameFromGraphQLType(type, unmodifiedTypeName);

    return Object.assign({}, field, {
      responseKey,
      propertyName,
      typeName,
      structName,
      isOptional,
    });
  }
Example #9
Source File: helpers.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
mapExpressionForType(type: GraphQLType, expression: (identifier: string) => string, identifier = ''): string {
    let isOptional;
    if (isNonNullType(type)) {
      isOptional = false;
      type = type.ofType;
    } else {
      isOptional = true;
    }

    if (isListType(type)) {
      if (isOptional) {
        return `${identifier}.flatMap { $0.map { ${this.mapExpressionForType(type.ofType, expression, '$0')} } }`;
      } else {
        return `${identifier}.map { ${this.mapExpressionForType(type.ofType, expression, '$0')} }`;
      }
    } else if (isOptional) {
      return `${identifier}.flatMap { ${expression('$0')} }`;
    } else {
      return expression(identifier);
    }
  }
Example #10
Source File: codeGeneration.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
function getObjectTypeName(type: GraphQLType): string {
  if (isListType(type)) {
    return getObjectTypeName(type.ofType);
  }
  if (isNonNullType(type)) {
    return getObjectTypeName(type.ofType);
  }
  if (isObjectType(type)) {
    return `"${type.name}"`;
  }
  if (isUnionType(type)) {
    return type
      .getTypes()
      .map(type => getObjectTypeName(type))
      .join(' | ');
  }
  return `"${type.name}"`;
}
Example #11
Source File: addExecutionLogicToComposer.ts    From graphql-mesh with MIT License 5 votes vote down vote up
isListTypeOrNonNullListType = memoize1(function isListTypeOrNonNullListType(type: GraphQLOutputType) {
  if (isNonNullType(type)) {
    return isListType(type.ofType);
  }
  return isListType(type);
})
Example #12
Source File: renderTyping.ts    From genql with MIT License 5 votes vote down vote up
render = (
  type: GraphQLOutputType | GraphQLInputType,
  nonNull: boolean,
  root: boolean,
  undefinableValues: boolean,
  undefinableFields: boolean,
  wrap: (x: string) => string = x => x
): string => {
    
  if (root) {
    if (undefinableFields) {
      if (isNonNullType(type)) {
        return `: ${render(type.ofType, true, false, undefinableValues, undefinableFields, wrap)}`
      } else {
        const rendered = render(type, true, false, undefinableValues, undefinableFields, wrap)
        return undefinableValues ? `?: ${rendered}` : `?: (${rendered} | null)`
      }
    } else {
      return `: ${render(type, false, false, undefinableValues, undefinableFields, wrap)}`
    }
  }

  if (isNamedType(type)) {
    let typeName = type.name

    // if is a scalar use the scalar interface to not expose reserved words
    if (isScalarType(type)) {
      typeName = `Scalars['${typeName}']`
    }

    const typing = wrap(typeName)

    if (undefinableValues) {
      return nonNull ? typing : `(${typing} | undefined)`
    } else {
      return nonNull ? typing : `(${typing} | null)`
    }
  }

  if (isListType(type)) {
    const typing = `${render(type.ofType, false, false, undefinableValues, undefinableFields, wrap)}[]`

    if (undefinableValues) {
      return nonNull ? typing : `(${typing} | undefined)`
    } else {
      return nonNull ? typing : `(${typing} | null)`
    }
  }

  return render((<GraphQLNonNull<any>>type).ofType, true, false, undefinableValues, undefinableFields, wrap)
}
Example #13
Source File: objectType.ts    From genql with MIT License 5 votes vote down vote up
objectType = (
    type: GraphQLObjectType | GraphQLInterfaceType,
    ctx: RenderContext,
    wrapper: 'Promise' | 'Observable',
) => {
    // console.log(Object.keys(type.getFields()))
    const fieldsMap: GraphQLFieldMap<any, any> = ctx.config?.sortProperties
        ? sortKeys(type.getFields())
        : type.getFields()

    const fieldStrings = Object.keys(fieldsMap).map((fieldName) => {
        const field = fieldsMap[fieldName]
        const resolvedType = getNamedType(field.type)
        // leaf type, obly has.get() method
        const stopChain =
            isListType(field.type) ||
            (isNonNullType(field.type) && isListType(field.type.ofType)) ||
            isUnionType(resolvedType)
        // non leaf type, has .get method
        const resolvable = !(
            isEnumType(resolvedType) || isScalarType(resolvedType)
        )
        const argsPresent = field.args.length > 0
        const argsOptional = !field.args.find((a) => isNonNullType(a.type))
        const argsString = toArgsString(field)

        const executeReturnType = renderTyping(field.type, true, false, false)
        const executeReturnTypeWithTypeMap = renderTyping(
            field.type,
            true,
            false,
            false,
            (x: string) => `FieldsSelection<${x}, R>`,
        )

        //     get: <R extends RepositoryRequest>(
        //         request: R,
        //         defaultValue?: Repository,
        //     ) => Promise<FieldsSelection<Repository, R>>
        // }

        const getFnType = `{get: <R extends ${requestTypeName(
            resolvedType,
        )}>(request: R, defaultValue?: ${executeReturnTypeWithTypeMap}) => ${wrapper}<${executeReturnTypeWithTypeMap}>}`
        const fieldType = resolvable
            ? stopChain
                ? getFnType
                : `${chainTypeName(resolvedType, wrapper)} & ${getFnType}`
            : `{get: (request?: boolean|number, defaultValue?: ${executeReturnType}) => ${wrapper}<${executeReturnType}>}`

        const result: string[] = []

        if (argsPresent) {
            result.push(
                `((args${
                    argsOptional ? '?' : ''
                }: ${argsString}) => ${fieldType})`,
            )
        }

        if (!argsPresent || argsOptional) {
            result.push(`(${fieldType})`)
        }

        return `${fieldComment(field)}${field.name}: ${result.join('&')}`
    })

    ctx.addImport(RUNTIME_LIB_NAME, false, 'FieldsSelection', true, true)

    if (wrapper === 'Observable') {
        ctx.addImport(RUNTIME_LIB_NAME, false, 'Observable', true, true)
    }

    ctx.addCodeBlock(
        `${typeComment(type)}export interface ${chainTypeName(
            type,
            wrapper,
        )}{\n    ${fieldStrings.join(',\n    ')}\n}`,
    )
}
Example #14
Source File: graphql.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
export function isList(type: GraphQLType): boolean {
  return isListType(type) || (isNonNullType(type) && isListType(type.ofType));
}
Example #15
Source File: codeGeneration.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
export function propertyFromField(
  context: LegacyCompilerContext,
  field: {
    name?: string;
    type: GraphQLType;
    fields?: any[];
    responseName?: string;
    description?: Maybe<string>;
    fragmentSpreads?: any;
    inlineFragments?: LegacyInlineFragment[];
    fieldName?: string;
  },
): Property {
  let { name: fieldName, type: fieldType, description, fragmentSpreads, inlineFragments } = field;
  fieldName = fieldName || field.responseName;

  const propertyName = fieldName;

  let property = { fieldName, fieldType, propertyName, description };

  const namedType = getNamedType(fieldType);

  let isNullable = true;
  if (isNonNullType(fieldType)) {
    isNullable = false;
  }

  if (isCompositeType(namedType)) {
    let typeName = namedType.toString();
    let isArray = false;
    let isArrayElementNullable = null;
    if (isListType(fieldType)) {
      isArray = true;
      isArrayElementNullable = !isNonNullType(fieldType.ofType);
    } else if (isNonNullType(fieldType) && isListType(fieldType.ofType)) {
      isArray = true;
      isArrayElementNullable = !isNonNullType(fieldType.ofType.ofType);
    } else if (!isNonNullType(fieldType)) {
      typeName = typeNameFromGraphQLType(context, fieldType, null, isNullable);
    }

    return {
      ...property,
      typeName,
      fields: field.fields,
      isComposite: true,
      fragmentSpreads,
      inlineFragments,
      fieldType,
      isArray,
      isNullable,
      isArrayElementNullable,
    };
  } else {
    if (field.fieldName === '__typename') {
      const typeName = typeNameFromGraphQLType(context, fieldType, null, false);
      return { ...property, typeName, isComposite: false, fieldType, isNullable: false };
    } else {
      const typeName = typeNameFromGraphQLType(context, fieldType, null, isNullable);
      return { ...property, typeName, isComposite: false, fieldType, isNullable };
    }
  }
}
Example #16
Source File: helpers.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
export function createTypeAnnotationFromGraphQLTypeFunction(compilerOptions: CompilerOptions): Function {
  return function typeAnnotationFromGraphQLType(
    type: GraphQLType,
    { nullable }: { nullable: boolean } = {
      nullable: true,
    }
  ): t.FlowTypeAnnotation {
    if (isNonNullType(type)) {
      return typeAnnotationFromGraphQLType(type.ofType, { nullable: false });
    }

    if (isListType(type)) {
      const typeAnnotation = t.arrayTypeAnnotation(typeAnnotationFromGraphQLType(type.ofType));

      if (nullable) {
        return t.nullableTypeAnnotation(typeAnnotation);
      } else {
        return typeAnnotation;
      }
    }

    let typeAnnotation;
    if (type instanceof GraphQLScalarType) {
      const builtIn = builtInScalarMap[type.name];
      if (builtIn) {
        typeAnnotation = builtIn;
      } else {
        if (compilerOptions.passthroughCustomScalars) {
          typeAnnotation = t.anyTypeAnnotation();
        } else {
          typeAnnotation = t.genericTypeAnnotation(t.identifier(type.name));
        }
      }
    } else {
      typeAnnotation = t.genericTypeAnnotation(t.identifier(type.name));
    }

    if (nullable) {
      return t.nullableTypeAnnotation(typeAnnotation);
    } else {
      return typeAnnotation;
    }
  };
}
Example #17
Source File: isRequiredList.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
export default function isRequired(typeObj: GraphQLType): boolean {
  return isNonNullType(typeObj) && isListType(typeObj.ofType);
}
Example #18
Source File: isList.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
export default function isList(typeObj: GraphQLType): boolean {
  if (isNonNullType(typeObj)) {
    return isList(typeObj.ofType);
  }
  return isListType(typeObj);
}
Example #19
Source File: codeGeneration.ts    From amplify-codegen with Apache License 2.0 4 votes vote down vote up
classDeclarationForOperation(operation: Operation) {
    const { operationName, operationType, variables, source, selectionSet } = operation;

    let className;
    let protocol;

    switch (operationType) {
      case 'query':
        className = `${this.helpers.operationClassName(operationName)}Query`;
        protocol = 'GraphQLQuery';
        break;
      case 'mutation':
        className = `${this.helpers.operationClassName(operationName)}Mutation`;
        protocol = 'GraphQLMutation';
        break;
      case 'subscription':
        className = `${this.helpers.operationClassName(operationName)}Subscription`;
        protocol = 'GraphQLSubscription';
        break;
      default:
        throw new GraphQLError(`Unsupported operation type "${operationType}"`);
    }

    this.classDeclaration(
      {
        className,
        modifiers: ['public', 'final'],
        adoptedProtocols: [protocol],
      },
      () => {
        if (source) {
          this.printOnNewline('public static let operationString =');
          this.withIndent(() => {
            this.multilineString(source);
          });
        }

        const fragmentsReferenced = collectFragmentsReferenced(operation.selectionSet, this.context.fragments);

        if (this.context.options.generateOperationIds) {
          const { operationId } = generateOperationId(operation, this.context.fragments, fragmentsReferenced);
          operation.operationId = operationId;
          this.printNewlineIfNeeded();
          this.printOnNewline(`public static let operationIdentifier: String? = "${operationId}"`);
        }

        if (fragmentsReferenced.size > 0) {
          this.printNewlineIfNeeded();
          this.printOnNewline('public static var requestString: String { return operationString');
          fragmentsReferenced.forEach(fragmentName => {
            this.print(`.appending(${this.helpers.structNameForFragmentName(fragmentName)}.fragmentString)`);
          });
          this.print(' }');
        }

        this.printNewlineIfNeeded();

        if (variables && variables.length > 0) {
          const properties = variables.map(({ name, type }) => {
            const typeName = this.helpers.typeNameFromGraphQLType(type);
            const isOptional = !(isNonNullType(type) || (isListType(type) && isNonNullType(type.ofType)));
            return { name, propertyName: name, type, typeName, isOptional };
          });

          this.propertyDeclarations(properties);

          this.printNewlineIfNeeded();
          this.initializerDeclarationForProperties(properties);

          this.printNewlineIfNeeded();
          this.printOnNewline(`public var variables: GraphQLMap?`);
          this.withinBlock(() => {
            this.printOnNewline(
              wrap(
                `return [`,
                join(properties.map(({ name, propertyName }) => `"${name}": ${escapeIdentifierIfNeeded(propertyName)}`), ', ') || ':',
                `]`
              )
            );
          });
        } else {
          this.initializerDeclarationForProperties([]);
        }

        this.structDeclarationForSelectionSet({
          structName: 'Data',
          selectionSet,
        });
      }
    );
  }
Example #20
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 #21
Source File: getComposerFromJSONSchema.ts    From graphql-mesh with MIT License 4 votes vote down vote up
export function getComposerFromJSONSchema(
  schema: JSONSchema,
  logger: Logger,
  generateInterfaceFromSharedFields = false
): Promise<TypeComposers> {
  const schemaComposer = new SchemaComposer();
  const ajv = new Ajv({
    strict: false,
  });
  addFormats(ajv);
  const formatScalarMap = getJSONSchemaStringFormatScalarMap(ajv);
  const futureTasks = new Set<VoidFunction>();
  return visitJSONSchema(schema, function mutateFn(subSchema, { path }): any {
    logger?.debug(`Processing ${path} for GraphQL Schema`);
    const getTypeComposer = (): any => {
      if (typeof subSchema === 'boolean') {
        const typeComposer = schemaComposer.getAnyTC(GraphQLJSON);
        return subSchema
          ? {
              input: typeComposer,
              output: typeComposer,
            }
          : undefined;
      }
      const validateWithJSONSchema = getValidateFnForSchemaPath(ajv, path, schema);

      if (!subSchema) {
        throw new Error(`Something is wrong with ${path}`);
      }

      if (subSchema.pattern) {
        const scalarType = new RegularExpression(
          getValidTypeName({
            schemaComposer,
            isInput: false,
            subSchema,
          }),
          new RegExp(subSchema.pattern),
          {
            description: subSchema.description,
          }
        );
        const typeComposer = schemaComposer.getAnyTC(scalarType);
        return {
          input: typeComposer,
          output: typeComposer,
        };
      }
      if (subSchema.const) {
        const tsTypeName = JSON.stringify(subSchema.const);
        const scalarTypeName = getValidTypeName({
          schemaComposer,
          isInput: false,
          subSchema,
        });
        const scalarType = new RegularExpression(scalarTypeName, new RegExp(subSchema.const), {
          description: subSchema.description || `A field whose value is ${tsTypeName}`,
          errorMessage: (_r, v: string) => `Expected ${tsTypeName} but got ${JSON.stringify(v)}`,
        });
        scalarType.extensions = {
          codegenScalarType: tsTypeName,
        };
        const typeComposer = schemaComposer.createScalarTC(scalarType);
        return {
          input: typeComposer,
          output: typeComposer,
        };
      }
      if (subSchema.enum && subSchema.type !== 'boolean') {
        const values: Record<string, EnumTypeComposerValueConfigDefinition> = {};
        for (const value of subSchema.enum) {
          let enumKey = sanitizeNameForGraphQL(value.toString());
          if (enumKey === 'false' || enumKey === 'true' || enumKey === 'null') {
            enumKey = enumKey.toUpperCase();
          }
          if (typeof enumKey === 'string' && enumKey.length === 0) {
            enumKey = '_';
          }
          values[enumKey] = {
            // Falsy values are ignored by GraphQL
            // eslint-disable-next-line no-unneeded-ternary
            value: value ? value : value?.toString(),
          };
        }
        const typeComposer = schemaComposer.createEnumTC({
          name: getValidTypeName({
            schemaComposer,
            isInput: false,
            subSchema,
          }),
          values,
          description: subSchema.description,
          extensions: {
            examples: subSchema.examples,
            default: subSchema.default,
          },
        });
        return {
          input: typeComposer,
          output: typeComposer,
        };
      }

      if (subSchema.oneOf && !subSchema.properties) {
        let statusCodeOneOfIndexMap: Record<string, number> | undefined;
        if (subSchema.$comment?.startsWith('statusCodeOneOfIndexMap:')) {
          const statusCodeOneOfIndexMapStr = subSchema.$comment.replace('statusCodeOneOfIndexMap:', '');
          statusCodeOneOfIndexMap = JSON.parse(statusCodeOneOfIndexMapStr);
        }
        const isPlural = (subSchema.oneOf as TypeComposers[]).some(({ output }) => 'ofType' in output);
        if (isPlural) {
          const { input, output } = getUnionTypeComposers({
            schemaComposer,
            ajv,
            typeComposersList: (subSchema.oneOf as any).map(({ input, output }: any) => ({
              input: input.ofType || input,
              output: output.ofType || output,
            })) as any[],
            subSchema,
            generateInterfaceFromSharedFields,
            statusCodeOneOfIndexMap,
          });
          return {
            input: input.getTypePlural(),
            output: output.getTypePlural(),
          };
        }
        return getUnionTypeComposers({
          schemaComposer,
          ajv,
          typeComposersList: subSchema.oneOf as any[],
          subSchema,
          generateInterfaceFromSharedFields,
          statusCodeOneOfIndexMap,
        });
      }

      if (subSchema.allOf && !subSchema.properties) {
        const inputFieldMap: InputTypeComposerFieldConfigMap = {};
        const fieldMap: ObjectTypeComposerFieldConfigMap<any, any> = {};
        let ableToUseGraphQLInputObjectType = true;
        for (const maybeTypeComposers of subSchema.allOf as any) {
          const { input: inputTypeComposer, output: outputTypeComposer } = maybeTypeComposers;

          if (inputTypeComposer instanceof ScalarTypeComposer) {
            ableToUseGraphQLInputObjectType = false;
          } else {
            const inputTypeElemFieldMap = inputTypeComposer.getFields();
            for (const fieldName in inputTypeElemFieldMap) {
              const field = inputTypeElemFieldMap[fieldName];
              inputFieldMap[fieldName] = field;
            }
          }

          if (outputTypeComposer instanceof ScalarTypeComposer) {
            fieldMap[outputTypeComposer.getTypeName()] = {
              type: outputTypeComposer,
              resolve: root => root,
            };
          } else if (outputTypeComposer instanceof UnionTypeComposer) {
            const outputTCElems = outputTypeComposer.getTypes() as ObjectTypeComposer[];
            for (const outputTCElem of outputTCElems) {
              const outputTypeElemFieldMap = outputTCElem.getFields();
              for (const fieldName in outputTypeElemFieldMap) {
                const field = outputTypeElemFieldMap[fieldName];
                fieldMap[fieldName] = field;
              }
            }
          } else {
            const typeElemFieldMap = outputTypeComposer.getFields();
            for (const fieldName in typeElemFieldMap) {
              const field = typeElemFieldMap[fieldName];
              fieldMap[fieldName] = field;
            }
          }
        }

        let inputTypeComposer;
        const outputTypeComposer = schemaComposer.createObjectTC({
          name: getValidTypeName({
            schemaComposer,
            isInput: false,
            subSchema,
          }),
          description: subSchema.description,
          fields: fieldMap,
          extensions: {
            validateWithJSONSchema,
            examples: subSchema.examples,
            default: subSchema.default,
          },
        });

        if (ableToUseGraphQLInputObjectType) {
          inputTypeComposer = schemaComposer.createInputTC({
            name: getValidTypeName({
              schemaComposer,
              isInput: true,
              subSchema,
            }),
            description: subSchema.description,
            fields: inputFieldMap,
            extensions: {
              examples: subSchema.examples,
              default: subSchema.default,
            },
          });
        } else {
          inputTypeComposer = isSomeInputTypeComposer(outputTypeComposer)
            ? outputTypeComposer
            : getGenericJSONScalar({
                schemaComposer,
                isInput: true,
                subSchema,
                validateWithJSONSchema,
              });
        }

        return {
          input: inputTypeComposer,
          output: outputTypeComposer,
        };
      }

      if (subSchema.anyOf && !subSchema.properties) {
        if (subSchema.title === 'Any') {
          const genericJSONScalar = getGenericJSONScalar({
            schemaComposer,
            isInput: false,
            subSchema,
            validateWithJSONSchema,
          });
          return {
            input: genericJSONScalar,
            output: genericJSONScalar,
          };
        }
        // It should not have `required` because it is `anyOf` not `allOf`
        const inputFieldMap: InputTypeComposerFieldConfigMap = {};
        const fieldMap: ObjectTypeComposerFieldConfigMap<any, any> = {};
        let ableToUseGraphQLInputObjectType = true;
        for (const typeComposers of subSchema.anyOf as any) {
          const { input: inputTypeComposer, output: outputTypeComposer } = typeComposers;
          if (inputTypeComposer instanceof ScalarTypeComposer) {
            ableToUseGraphQLInputObjectType = false;
          } else {
            const inputTypeElemFieldMap = inputTypeComposer.getFields();
            for (const fieldName in inputTypeElemFieldMap) {
              const field = inputTypeElemFieldMap[fieldName];
              inputFieldMap[fieldName] = isNonNullType(field.type.getType())
                ? {
                    ...field,
                    type: () => field.type.ofType,
                  }
                : field;
            }
          }

          if (outputTypeComposer instanceof ScalarTypeComposer) {
            const typeName = outputTypeComposer.getTypeName();
            fieldMap[typeName] = {
              type: outputTypeComposer,
              resolve: root => root,
            };
          } else {
            const typeElemFieldMap = outputTypeComposer.getFields();
            for (const fieldName in typeElemFieldMap) {
              const field = typeElemFieldMap[fieldName];
              fieldMap[fieldName] = {
                type: () => getNamedType(field.type.getType()),
                ...field,
              };
            }
          }
        }

        let inputTypeComposer;
        const outputTypeComposer = schemaComposer.createObjectTC({
          name: getValidTypeName({
            schemaComposer,
            isInput: false,
            subSchema,
          }),
          description: subSchema.description,
          fields: fieldMap,
          extensions: {
            validateWithJSONSchema,
            examples: subSchema.examples,
            default: subSchema.default,
          },
        });

        if (ableToUseGraphQLInputObjectType) {
          inputTypeComposer = schemaComposer.createInputTC({
            name: getValidTypeName({
              schemaComposer,
              isInput: true,
              subSchema,
            }),
            description: subSchema.description,
            fields: inputFieldMap,
            extensions: {
              examples: subSchema.examples,
              default: subSchema.default,
            },
          });
        } else {
          inputTypeComposer = isSomeInputTypeComposer(outputTypeComposer)
            ? outputTypeComposer
            : getGenericJSONScalar({
                schemaComposer,
                isInput: true,
                subSchema,
                validateWithJSONSchema,
              });
        }

        return {
          input: inputTypeComposer,
          output: outputTypeComposer,
        };
      }

      if (Array.isArray(subSchema.type)) {
        const validTypes = subSchema.type.filter((typeName: string) => typeName !== 'null');
        if (validTypes.length === 1) {
          subSchema.type = validTypes[0];
          // continue with the single type
        } else {
          const typeComposer = schemaComposer.getAnyTC(GraphQLVoid);
          return {
            input: typeComposer,
            output: typeComposer,
          };
        }
      }

      switch (subSchema.type as any) {
        case 'file': {
          const typeComposer = schemaComposer.getAnyTC(GraphQLFile);
          return {
            input: typeComposer,
            output: typeComposer,
            description: subSchema.description,
          };
        }
        case 'boolean': {
          const typeComposer = schemaComposer.getAnyTC(GraphQLBoolean);
          return {
            input: typeComposer,
            output: typeComposer,
            description: subSchema.description,
          };
        }
        case 'null': {
          const typeComposer = schemaComposer.getAnyTC(GraphQLVoid);
          return {
            input: typeComposer,
            output: typeComposer,
            description: subSchema.description,
          };
        }
        case 'integer': {
          if (subSchema.format === 'int64') {
            const typeComposer = schemaComposer.getAnyTC(GraphQLBigInt);
            return {
              input: typeComposer,
              output: typeComposer,
              description: subSchema.description,
            };
          }
          const typeComposer = schemaComposer.getAnyTC(GraphQLInt);
          return {
            input: typeComposer,
            output: typeComposer,
            description: subSchema.description,
          };
        }
        case 'number': {
          const typeComposer = schemaComposer.getAnyTC(GraphQLFloat);
          return {
            input: typeComposer,
            output: typeComposer,
            description: subSchema.description,
          };
        }
        case 'string': {
          if (subSchema.minLength || subSchema.maxLength) {
            const scalarType = getStringScalarWithMinMaxLength({
              schemaComposer,
              subSchema,
            });
            const typeComposer = schemaComposer.getAnyTC(scalarType);
            return {
              input: typeComposer,
              output: typeComposer,
              description: subSchema.description,
            };
          }
          switch (subSchema.format) {
            case 'date-time': {
              const typeComposer = schemaComposer.getAnyTC(GraphQLDateTime);
              return {
                input: typeComposer,
                output: typeComposer,
                description: subSchema.description,
              };
            }
            case 'time': {
              const typeComposer = schemaComposer.getAnyTC(GraphQLTime);
              return {
                input: typeComposer,
                output: typeComposer,
                description: subSchema.description,
              };
            }
            case 'email': {
              const typeComposer = schemaComposer.getAnyTC(GraphQLEmailAddress);
              return {
                input: typeComposer,
                output: typeComposer,
                description: subSchema.description,
              };
            }
            case 'ipv4': {
              const typeComposer = schemaComposer.getAnyTC(GraphQLIPv4);
              return {
                input: typeComposer,
                output: typeComposer,
                description: subSchema.description,
              };
            }
            case 'ipv6': {
              const typeComposer = schemaComposer.getAnyTC(GraphQLIPv6);
              return {
                input: typeComposer,
                output: typeComposer,
                description: subSchema.description,
              };
            }
            case 'uri': {
              const typeComposer = schemaComposer.getAnyTC(GraphQLURL);
              return {
                input: typeComposer,
                output: typeComposer,
                description: subSchema.description,
              };
            }
            default: {
              const formatScalar = formatScalarMap.get(subSchema.format) || GraphQLString;
              const typeComposer = schemaComposer.getAnyTC(formatScalar);
              return {
                input: typeComposer,
                output: typeComposer,
                description: subSchema.description,
              };
            }
          }
        }
        case 'array':
          if (
            typeof subSchema.items === 'object' &&
            !Array.isArray(subSchema.items) &&
            Object.keys(subSchema.items).length > 0
          ) {
            const typeComposers = subSchema.items;
            return {
              input: typeComposers.input.getTypePlural(),
              output: typeComposers.output.getTypePlural(),
              description: subSchema.description,
            };
          }
          if (subSchema.contains) {
            // Scalars cannot be in union type
            const typeComposer = getGenericJSONScalar({
              schemaComposer,
              isInput: false,
              subSchema,
              validateWithJSONSchema,
            }).getTypePlural();
            return {
              input: typeComposer,
              output: typeComposer,
            };
          }
          if (typeof subSchema.items === 'object' && Array.isArray(subSchema.items)) {
            const existingItems = [...(subSchema.items as any)];
            /* TODO
            if (subSchema.additionalItems) {
              existingItems.push(subSchema.additionalItems);
            }
            */
            const { input: inputTypeComposer, output: outputTypeComposer } = getUnionTypeComposers({
              schemaComposer,
              ajv,
              typeComposersList: existingItems,
              subSchema,
              generateInterfaceFromSharedFields,
            });
            return {
              input: inputTypeComposer.getTypePlural(),
              output: outputTypeComposer.getTypePlural(),
              description: subSchema.description,
            };
          }
          // If it doesn't have any clue
          {
            // const typeComposer = getGenericJSONScalar({
            //   schemaComposer,
            //   isInput: false,
            //   subSchema,
            //   validateWithJSONSchema,
            // }).getTypePlural();
            const typeComposer = schemaComposer.getAnyTC(GraphQLJSON).getTypePlural();
            return {
              input: typeComposer,
              output: typeComposer,
              description: subSchema.description,
            };
          }
        case 'object':
          const fieldMap: ObjectTypeComposerFieldConfigMapDefinition<any, any> = {};
          let inputFieldMap: Record<string, InputTypeComposerFieldConfigAsObjectDefinition & { type: any }> = {};
          if (subSchema.properties) {
            subSchema.type = 'object';
            for (const propertyName in subSchema.properties) {
              // TODO: needs to be fixed
              if (propertyName === 'additionalProperties') {
                continue;
              }
              const typeComposers = subSchema.properties[propertyName];
              const fieldName = sanitizeNameForGraphQL(propertyName);
              fieldMap[fieldName] = {
                type: () =>
                  subSchema.required?.includes(propertyName)
                    ? typeComposers.output.getTypeNonNull()
                    : typeComposers.output,
                // Make sure you get the right property
                resolve: root => {
                  const actualFieldObj = root[propertyName];
                  if (actualFieldObj != null) {
                    const isArray = Array.isArray(actualFieldObj);
                    const isListType = isListTC(typeComposers.output);
                    if (isListType && !isArray) {
                      return [actualFieldObj];
                    } else if (!isListTC(typeComposers.output) && isArray) {
                      return actualFieldObj[0];
                    }
                  }
                  return actualFieldObj;
                },
                description: typeComposers.description || typeComposers.output?.description,
              };
              inputFieldMap[fieldName] = {
                type: () =>
                  subSchema.required?.includes(propertyName)
                    ? typeComposers.input?.getTypeNonNull()
                    : typeComposers.input,
                // Let execution logic know what is the expected propertyName
                extensions: {
                  propertyName,
                },
                description: typeComposers.description || typeComposers.input?.description,
                defaultValue: typeComposers?.extensions?.default || typeComposers.input?.default,
              };
            }
          }

          if (subSchema.allOf) {
            for (const typeComposers of subSchema.allOf) {
              const outputTC: ObjectTypeComposer = (typeComposers as any).output;
              if (schemaComposer.isObjectType(outputTC)) {
                for (const outputFieldName of outputTC.getFieldNames()) {
                  if (!fieldMap[outputFieldName]) {
                    fieldMap[outputFieldName] = outputTC.getField(outputFieldName);
                  }
                }
              }
              const inputTC: InputTypeComposer = (typeComposers as any).input;
              if (schemaComposer.isInputObjectType(inputTC)) {
                for (const inputFieldName of inputTC.getFieldNames()) {
                  if (!inputFieldMap[inputFieldName]) {
                    inputFieldMap[inputFieldName] = inputTC.getField(inputFieldName);
                  }
                }
              }
            }
          }

          if (subSchema.additionalProperties) {
            if (
              typeof subSchema.additionalProperties === 'object' &&
              subSchema.additionalProperties.output instanceof ObjectTypeComposer
            ) {
              if (Object.keys(fieldMap).length === 0) {
                return subSchema.additionalProperties;
              } else {
                const outputTC: ObjectTypeComposer = (subSchema.additionalProperties as any).output;
                const outputTCFieldMap = outputTC.getFields();
                for (const fieldName in outputTCFieldMap) {
                  fieldMap[fieldName] = outputTCFieldMap[fieldName];
                }
                const inputTC: InputTypeComposer = (subSchema.additionalProperties as any).input;
                const inputTCFieldMap = inputTC.getFields();
                for (const fieldName in inputTCFieldMap) {
                  inputFieldMap[fieldName] = inputTCFieldMap[fieldName];
                }
              }
            } else {
              fieldMap.additionalProperties = {
                type: GraphQLJSON,
                resolve: (root: any) => root,
              };
              inputFieldMap = {};
            }
          }

          if (subSchema.title === '_schema') {
            futureTasks.forEach(futureTask => futureTask());
            return {
              output: schemaComposer,
            };
          }

          if (subSchema.title === 'Query') {
            const typeComposer = schemaComposer.Query;
            typeComposer.addFields(fieldMap);
            return {
              output: typeComposer,
            };
          }

          if (subSchema.title === 'Mutation') {
            const typeComposer = schemaComposer.Mutation;
            typeComposer.addFields(fieldMap);
            return {
              output: typeComposer,
            };
          }

          if (subSchema.title === 'Subscription') {
            const typeComposer = schemaComposer.Subscription;
            typeComposer.addFields(fieldMap);
            return {
              output: typeComposer,
            };
          }

          const getCorrectInputFieldType = (fieldName: string) => {
            const inputType: InputTypeComposer | ListComposer<InputTypeComposer> = inputFieldMap[fieldName].type();
            const actualInputType = isListTC(inputType) ? inputType.ofType : inputType;
            if (!actualInputType.getFields) {
              return actualInputType;
            }
            const fieldMap = actualInputType.getFields();
            for (const fieldName in fieldMap) {
              const fieldConfig = fieldMap[fieldName];
              if (fieldConfig.type.getTypeName().endsWith('!')) {
                return inputType.getTypeNonNull();
              }
            }
            return inputType;
          };

          if (subSchema.title === 'QueryInput') {
            const typeComposer = schemaComposer.Query;
            for (const fieldName in inputFieldMap) {
              futureTasks.add(() =>
                typeComposer.addFieldArgs(fieldName, {
                  input: {
                    type: () => getCorrectInputFieldType(fieldName),
                    description: inputFieldMap[fieldName].description,
                  },
                })
              );
            }
            return {
              output: typeComposer,
            };
          }

          if (subSchema.title === 'MutationInput') {
            const typeComposer = schemaComposer.Mutation;
            for (const fieldName in inputFieldMap) {
              futureTasks.add(() =>
                typeComposer.addFieldArgs(fieldName, {
                  input: {
                    type: () => getCorrectInputFieldType(fieldName),
                    description: inputFieldMap[fieldName].description,
                  },
                })
              );
            }
            return {
              output: typeComposer,
            };
          }

          if (subSchema.title === 'SubscriptionInput') {
            const typeComposer = schemaComposer.Subscription;
            for (const fieldName in inputFieldMap) {
              futureTasks.add(() =>
                typeComposer.addFieldArgs(fieldName, {
                  input: {
                    type: () => getCorrectInputFieldType(fieldName),
                    description: inputFieldMap[fieldName].description,
                  },
                })
              );
            }
            return {
              output: typeComposer,
            };
          }

          const output =
            Object.keys(fieldMap).length === 0
              ? getGenericJSONScalar({
                  schemaComposer,
                  isInput: false,
                  subSchema,
                  validateWithJSONSchema,
                })
              : schemaComposer.createObjectTC({
                  name: getValidTypeName({
                    schemaComposer,
                    isInput: false,
                    subSchema,
                  }),
                  description: subSchema.description,
                  fields: fieldMap,
                  extensions: {
                    validateWithJSONSchema,
                    examples: subSchema.examples,
                    default: subSchema.default,
                  },
                });

          const input =
            Object.keys(inputFieldMap).length === 0
              ? getGenericJSONScalar({
                  schemaComposer,
                  isInput: true,
                  subSchema,
                  validateWithJSONSchema,
                })
              : schemaComposer.createInputTC({
                  name: getValidTypeName({
                    schemaComposer,
                    isInput: true,
                    subSchema,
                  }),
                  description: subSchema.description,
                  fields: inputFieldMap,
                  extensions: {
                    examples: subSchema.examples,
                    default: subSchema.default,
                  },
                });

          return {
            input,
            output,
          };
      }
      logger.warn(`GraphQL Type cannot be created for this JSON Schema definition;`, {
        subSchema,
        path,
      });
      const typeComposer = schemaComposer.getAnyTC(GraphQLJSON);
      return {
        input: typeComposer,
        output: typeComposer,
      };
    };
    const result = getTypeComposer();
    return result;
  });
}
Example #22
Source File: types.ts    From tql with MIT License 4 votes vote down vote up
transform = (
  ast: DocumentNode,
  schema: GraphQLSchema
): ASTVisitor => {
  // @note needed to serialize inline enum values correctly at runtime
  const enumValues = new Set<string>();

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

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

    [Kind.ENUM_TYPE_DEFINITION]: (node) => {
      const typename = node.name.value;
      const values = node.values?.map((v) => v.name.value) ?? [];

      const printMember = (member: string): string => {
        return `${member} = "${member}"`;
      };

      return code`
        export enum ${typename} {
          ${values.map(printMember).join(",\n")}
        }
      `;
    },

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

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

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

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

      const printField = (field: GraphQLInputField) => {
        const isList = listType(field.type);
        const isNonNull = isNonNullType(field.type);
        const baseType = inputType(field.type);

        let tsType: string;

        if (baseType instanceof GraphQLScalarType) {
          tsType = toPrimitive(baseType);
        } else if (baseType instanceof GraphQLEnumType) {
          tsType = baseType.name;
        } else if (baseType instanceof GraphQLInputObjectType) {
          tsType = "I" + baseType.name;
        } else {
          throw new Error("Unable to render inputField!");
        }

        return [
          field.name,
          isNonNull ? ":" : "?:",
          " ",
          tsType,
          isList ? "[]" : "",
        ].join("");
      };

      return code`
        export interface I${typename} {
          ${fields.map(printField).join("\n")}
        }
      `;
    },

    [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());
      const interfaces = type.getInterfaces();

      // @note TypeScript only requires new fields to be defined on interface extendors
      const interfaceFields = interfaces.flatMap((i) =>
        Object.values(i.getFields()).map((field) => field.name)
      );
      const uncommonFields = fields.filter(
        (field) => !interfaceFields.includes(field.name)
      );

      // @todo extend any implemented interfaces
      // @todo only render fields unique to this type
      const extensions =
        interfaces.length > 0
          ? `extends ${interfaces.map((i) => "I" + i.name).join(", ")}`
          : "";

      return code`
        export interface I${typename} ${extensions} {
          readonly __typename: ${`"${typename}"`}
          ${uncommonFields.map(printField).join("\n")}
        }
      `;
    },

    [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`
        export interface I${typename} {
          readonly __typename: ${implementations
            .map((type) => `"${type}"`)
            .join(" | ")}
          ${fields.map(printField).join("\n")}
        }
      `;
    },

    [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`
        export type ${"I" + type.name} = ${implementations
        .map((type) => `I${type}`)
        .join(" | ")}
      `;
    },

    [Kind.SCHEMA_DEFINITION]: (_) => {
      return null;
    },
  };
}