graphql#getNamedType TypeScript Examples

The following examples show how to use graphql#getNamedType. 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: resolve-additional-resolvers.ts    From graphql-mesh with MIT License 6 votes vote down vote up
function getTypeByPath(type: GraphQLType, path: string[]): GraphQLNamedType {
  if ('ofType' in type) {
    return getTypeByPath(getNamedType(type), path);
  }
  if (path.length === 0) {
    return getNamedType(type);
  }
  if (!('getFields' in type)) {
    throw new Error(`${type} cannot have a path ${path.join('.')}`);
  }
  const fieldMap = type.getFields();
  const currentFieldName = path[0];
  // Might be an index of an array
  if (!Number.isNaN(parseInt(currentFieldName))) {
    return getTypeByPath(type, path.slice(1));
  }
  const field = fieldMap[currentFieldName];
  if (!field?.type) {
    throw new Error(`${type}.${currentFieldName} is not a valid field.`);
  }
  return getTypeByPath(field.type, path.slice(1));
}
Example #2
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 #3
Source File: index.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
addTypeUsed(type: GraphQLType) {
    if (this.typesUsedSet.has(type)) return;

    if (
      isEnumType(type) ||
      isUnionType(type) ||
      isInputObjectType(type) ||
      isInterfaceType(type) ||
      isObjectType(type) ||
      (isScalarType(type) && !isSpecifiedScalarType(type))
    ) {
      this.typesUsedSet.add(type);
    }
    if (isInterfaceType(type) || isUnionType(type)) {
      for (const concreteType of this.schema.getPossibleTypes(type)) {
        this.addTypeUsed(getNamedType(concreteType));
      }
    }
    if (isInputObjectType(type)) {
      for (const field of Object.values(type.getFields())) {
        this.addTypeUsed(getNamedType(field.type));
      }
    }
    if (isObjectType(type)) {
      for (const fieldType of type.getInterfaces()) {
        this.addTypeUsed(getNamedType(fieldType));
      }
      for (const field of Object.values(type.getFields())) {
        this.addTypeUsed(getNamedType(field.type));
      }
    }
  }
Example #4
Source File: index.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
compileOperation(operationDefinition: OperationDefinitionNode): Operation {
    if (!operationDefinition.name) {
      throw new Error('Operations should be named');
    }

    const filePath = filePathForNode(operationDefinition);
    const operationName = operationDefinition.name.value;
    const operationType = operationDefinition.operation;

    const variables = (operationDefinition.variableDefinitions || []).map(node => {
      const name = node.variable.name.value;
      const type = typeFromAST(this.schema, node.type as NonNullTypeNode);
      this.addTypeUsed(getNamedType(type as GraphQLType));
      return { name, type: type as GraphQLNonNull<any> };
    });

    const source = print(operationDefinition);
    const rootType = getOperationRootType(this.schema, operationDefinition) as GraphQLObjectType;
    const selectionSet = this.compileSelectionSet(operationDefinition.selectionSet, rootType);

    this.addTypeUsed(getNamedType((selectionSet.selections[0] as Field).type)); // store result type

    return {
      filePath,
      operationName,
      operationType,
      variables,
      source,
      rootType,
      selectionSet,
    };
  }
Example #5
Source File: codeGeneration.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
/**
 * This exists only to properly generate types for union/interface typed fields that
 * do not have inline fragments. This currently can happen and the IR does give us
 * a set of fields per type condition unless fragments are used within the selection set.
 */
function getPossibleTypeNames(generator: CodeGenerator<LegacyCompilerContext>, property: Property) {
  const type = getNamedType(property.fieldType || property.type!);

  if (isUnionType(type) || isInterfaceType(type)) {
    return generator.context.schema.getPossibleTypes(type).map(type => type.name);
  }

  return [];
}
Example #6
Source File: helpers.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
propertiesForSelectionSet(selectionSet: SelectionSet, namespace?: string): (Field & Property)[] | undefined {
    const properties = collectAndMergeFields(selectionSet, true)
      .filter(field => field.name !== '__typename')
      .map(field => this.propertyFromField(field, namespace));

    // If we're not merging in fields from fragment spreads, there is no guarantee there will a generated
    // type for a composite field, so to avoid compiler errors we skip the initializer for now.
    if (
      selectionSet.selections.some(selection => selection.kind === 'FragmentSpread') &&
      properties.some(property => isCompositeType(getNamedType(property.type)))
    ) {
      return undefined;
    }

    return properties;
  }
Example #7
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 #8
Source File: codeGeneration.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
propertyAssignmentForField(field: { responseKey: string; propertyName: string; type: GraphQLType }) {
    const { responseKey, propertyName, type } = field;
    const valueExpression = isCompositeType(getNamedType(type))
      ? this.helpers.mapExpressionForType(type, identifier => `${identifier}.snapshot`, escapeIdentifierIfNeeded(propertyName))
      : escapeIdentifierIfNeeded(propertyName);
    return `"${responseKey}": ${valueExpression}`;
  }
Example #9
Source File: objectType.ts    From genql with MIT License 5 votes vote down vote up
objectType = (
    type: GraphQLObjectType | GraphQLInterfaceType | GraphQLInputObjectType,
    ctx: RenderContext,
) => {
    const typeObj: FieldMap<string> = Object.keys(type.getFields()).reduce<
        FieldMap<string>
    >((r, f) => {
        const field = type.getFields()[f]
        const namedType = getNamedType(field.type)
        const fieldObj: Field<string> = { type: namedType.name }
        r[f] = fieldObj

        const args: GraphQLArgument[] =
            (<GraphQLField<any, any>>field).args || []

        if (args.length > 0) {
            fieldObj.args = args.reduce<ArgMap<string>>((r, a) => {
                const concreteType = a.type.toString()
                const typename = getNamedType(a.type).name
                r[a.name] = [typename]
                if (typename !== concreteType) {
                    r[a.name]?.push(concreteType)
                }
                return r
            }, {})
        }

        return r
    }, {})

    if (isInterfaceType(type) && ctx.schema) {
        ctx.schema.getPossibleTypes(type).map((t) => {
            if (!isEmpty(typeObj)) {
                typeObj[`on_${t.name}`] = { type: t.name }
            }
        })
    }

    if (!isEmpty(typeObj)) {
        typeObj.__typename = { type: 'String' }
    }

    // const scalar = Object.keys(type.getFields())
    //   .map(f => type.getFields()[f])
    //   .filter(f => isScalarType(getNamedType(f.type)) || isEnumType(getNamedType(f.type)))
    //   .map(f => f.name)

    // if (scalar.length > 0) typeObj.scalar = scalar

    return typeObj
}
Example #10
Source File: objectType.ts    From genql with MIT License 5 votes vote down vote up
objectType = (
    type: GraphQLObjectType | GraphQLInterfaceType,
    ctx: RenderContext,
) => {
    let fields = type.getFields()

    if (ctx.config?.sortProperties) {
        fields = sortKeys(fields)
    }

    let fieldStrings = Object.keys(fields).map((fieldName) => {
        const field = fields[fieldName]

        const types: string[] = []
        const resolvedType = getNamedType(field.type)
        const resolvable = !(
            isEnumType(resolvedType) || isScalarType(resolvedType)
        )
        const argsPresent = field.args.length > 0
        const argsString = toArgsString(field)
        const argsOptional = !argsString.match(/[^?]:/)

        if (argsPresent) {
            if (resolvable) {
                types.push(`[${argsString},${requestTypeName(resolvedType)}]`)
            } else {
                types.push(`[${argsString}]`)
            }
        }

        if (!argsPresent || argsOptional) {
            if (resolvable) {
                types.push(`${requestTypeName(resolvedType)}`)
            } else {
                types.push('boolean | number')
            }
        }

        return `${fieldComment(field)}${field.name}?: ${types.join(' | ')}`
    })

    if (isInterfaceType(type) && ctx.schema) {
        let interfaceProperties = ctx.schema
            .getPossibleTypes(type)
            .map((t) => `on_${t.name}?: ${requestTypeName(t)}`)
        if (ctx.config?.sortProperties) {
            interfaceProperties = interfaceProperties.sort()
        }
        fieldStrings = fieldStrings.concat(interfaceProperties)
    }

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

    // add indentation
    fieldStrings = fieldStrings.map((x) =>
        x
            .split('\n')
            .filter(Boolean)
            .map((l) => INDENTATION + l)
            .join('\n'),
    )

    ctx.addCodeBlock(
        `${typeComment(type)}export interface ${requestTypeName(
            type,
        )}{\n${fieldStrings.join('\n')}\n}`,
    )
}
Example #11
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 #12
Source File: codeGeneration.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
// pickedPropertyDeclarations declares specific properties selected by execution schemas or fragment schemas.
export function pickedPropertyDeclarations(generator: CodeGenerator, properties: Property[], isOptional = false) {
  if (!properties) return;
  properties.forEach(property => {
    if (isAbstractType(getNamedType(property.type || property.fieldType!))) {
      const propertySets = getPossibleTypeNames(generator, property).map(type => {
        const inlineFragment =
          property.inlineFragments &&
          property.inlineFragments.find(inlineFragment => {
            return inlineFragment.typeCondition.toString() == type;
          });

        if (inlineFragment) {
          const fields = inlineFragment.fields.map(field => {
            if (field.fieldName === '__typename') {
              return {
                ...field,
                typeName: `"${inlineFragment.typeCondition}"`,
                type: { name: `"${inlineFragment.typeCondition}"` } as GraphQLType,
              };
            } else {
              return field;
            }
          });

          return propertiesFromFields(generator.context, fields);
        } else {
          const fields = property.fields!.map(field => {
            if (field.fieldName === '__typename') {
              return {
                ...field,
                typeName: `"${type}"`,
                type: { name: `"${type}"` } as GraphQLType,
              };
            } else {
              return field;
            }
          });

          return propertiesFromFields(generator.context, fields);
        }
      });

      pickedPropertySetsDeclaration(generator, property, propertySets);
    } else {
      if (
        (property.fields && property.fields.length > 0) ||
        (property.inlineFragments && property.inlineFragments.length > 0) ||
        (property.fragmentSpreads && property.fragmentSpreads.length > 0)
      ) {
        propertyDeclaration(generator, property, () => {
          const properties = propertiesFromFields(generator.context, property.fields!);
          pickedPropertyDeclarations(generator, properties, isOptional);
        });
      } else {
        propertyDeclaration(generator, { ...property, isOptional });
      }
    }
  });
}
Example #13
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 #14
Source File: codeGeneration.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
propertyDeclarationForField(field: Field & Property) {
    const { responseKey, propertyName, typeName, type, isOptional } = field;

    const unmodifiedFieldType = getNamedType(type);

    this.printNewlineIfNeeded();

    this.comment(field.description);
    this.deprecationAttributes(field.isDeprecated, field.deprecationReason);

    this.printOnNewline(`public var ${escapeIdentifierIfNeeded(propertyName)}: ${typeName}`);
    this.withinBlock(() => {
      if (isCompositeType(unmodifiedFieldType)) {
        const structName = escapeIdentifierIfNeeded(this.helpers.structNameForPropertyName(propertyName));

        if (isList(type)) {
          this.printOnNewline('get');
          this.withinBlock(() => {
            const snapshotTypeName = this.helpers.typeNameFromGraphQLType(type, 'Snapshot', false);
            let getter;
            if (isOptional) {
              getter = `return (snapshot["${responseKey}"] as? ${snapshotTypeName})`;
            } else {
              getter = `return (snapshot["${responseKey}"] as! ${snapshotTypeName})`;
            }
            getter += this.helpers.mapExpressionForType(type, identifier => `${structName}(snapshot: ${identifier})`);
            this.printOnNewline(getter);
          });
          this.printOnNewline('set');
          this.withinBlock(() => {
            let newValueExpression = this.helpers.mapExpressionForType(type, identifier => `${identifier}.snapshot`, 'newValue');
            this.printOnNewline(`snapshot.updateValue(${newValueExpression}, forKey: "${responseKey}")`);
          });
        } else {
          this.printOnNewline('get');
          this.withinBlock(() => {
            if (isOptional) {
              this.printOnNewline(`return (snapshot["${responseKey}"] as? Snapshot).flatMap { ${structName}(snapshot: $0) }`);
            } else {
              this.printOnNewline(`return ${structName}(snapshot: snapshot["${responseKey}"]! as! Snapshot)`);
            }
          });
          this.printOnNewline('set');
          this.withinBlock(() => {
            let newValueExpression;
            if (isOptional) {
              newValueExpression = 'newValue?.snapshot';
            } else {
              newValueExpression = 'newValue.snapshot';
            }
            this.printOnNewline(`snapshot.updateValue(${newValueExpression}, forKey: "${responseKey}")`);
          });
        }
      } else {
        this.printOnNewline('get');
        this.withinBlock(() => {
          if (isOptional) {
            this.printOnNewline(`return snapshot["${responseKey}"] as? ${typeName.slice(0, -1)}`);
          } else {
            this.printOnNewline(`return snapshot["${responseKey}"]! as! ${typeName}`);
          }
        });
        this.printOnNewline('set');
        this.withinBlock(() => {
          this.printOnNewline(`snapshot.updateValue(newValue, forKey: "${responseKey}")`);
        });
      }
    });
  }
Example #15
Source File: index.ts    From graphql-mesh with MIT License 4 votes vote down vote up
async getMeshSource(): Promise<MeshSource> {
    let fetch: ReturnType<typeof getCachedFetch>;
    if (this.config.customFetch) {
      fetch =
        typeof this.config.customFetch === 'string'
          ? await loadFromModuleExportExpression<ReturnType<typeof getCachedFetch>>(this.config.customFetch, {
              cwd: this.baseDir,
              importFn: this.importFn,
              defaultExportName: 'default',
            })
          : this.config.customFetch;
    } else {
      fetch = getCachedFetch(this.cache);
    }

    const { baseUrl: nonInterpolatedBaseUrl, operationHeaders } = this.config;
    const baseUrl = stringInterpolator.parse(nonInterpolatedBaseUrl, {
      env: process.env,
    });

    const schemaComposer = new SchemaComposer();
    schemaComposer.add(GraphQLBigInt);
    schemaComposer.add(GraphQLGUID);
    schemaComposer.add(GraphQLDateTime);
    schemaComposer.add(GraphQLJSON);
    schemaComposer.add(GraphQLByte);
    schemaComposer.add(GraphQLDate);
    schemaComposer.add(GraphQLISO8601Duration);

    const aliasNamespaceMap = new Map<string, string>();

    const metadataJson = await this.getCachedMetadataJson(fetch);
    const schemas = metadataJson.Edmx[0].DataServices[0].Schema;
    const multipleSchemas = schemas.length > 1;
    const namespaces = new Set<string>();

    const contextDataloaderName = Symbol(`${this.name}DataLoader`);

    function getNamespaceFromTypeRef(typeRef: string) {
      let namespace = '';
      namespaces?.forEach(el => {
        if (
          typeRef.startsWith(el) &&
          el.length > namespace.length && // It can be deeper namespace
          !typeRef.replace(el + '.', '').includes('.') // Typename cannot have `.`
        ) {
          namespace = el;
        }
      });
      return namespace;
    }

    function getTypeNameFromRef({
      typeRef,
      isInput,
      isRequired,
    }: {
      typeRef: string;
      isInput: boolean;
      isRequired: boolean;
    }) {
      const typeRefArr = typeRef.split('Collection(');
      const arrayDepth = typeRefArr.length;
      let actualTypeRef = typeRefArr.join('').split(')').join('');
      const typeNamespace = getNamespaceFromTypeRef(actualTypeRef);
      if (aliasNamespaceMap.has(typeNamespace)) {
        const alias = aliasNamespaceMap.get(typeNamespace);
        actualTypeRef = actualTypeRef.replace(typeNamespace, alias);
      }
      const actualTypeRefArr = actualTypeRef.split('.');
      const typeName = multipleSchemas
        ? pascalCase(actualTypeRefArr.join('_'))
        : actualTypeRefArr[actualTypeRefArr.length - 1];
      let realTypeName = typeName;
      if (SCALARS.has(actualTypeRef)) {
        realTypeName = SCALARS.get(actualTypeRef);
      } else if (schemaComposer.isEnumType(typeName)) {
        realTypeName = typeName;
      } else if (isInput) {
        realTypeName += 'Input';
      }
      const fakeEmptyArr = new Array(arrayDepth);
      realTypeName = fakeEmptyArr.join('[') + realTypeName + fakeEmptyArr.join(']');
      if (isRequired) {
        realTypeName += '!';
      }
      return realTypeName;
    }

    function getUrlString(url: URL) {
      return decodeURIComponent(url.toString()).split('+').join(' ');
    }

    function handleResponseText(responseText: string, urlString: string, info: GraphQLResolveInfo) {
      let responseJson: any;
      try {
        responseJson = JSON.parse(responseText);
      } catch (error) {
        const actualError = new Error(responseText);
        Object.assign(actualError, {
          extensions: {
            url: urlString,
          },
        });
        throw actualError;
      }
      if (responseJson.error) {
        const actualError = new Error(responseJson.error.message || responseJson.error) as any;
        actualError.extensions = responseJson.error;
        throw actualError;
      }
      const urlStringWithoutSearchParams = urlString.split('?')[0];
      if (isListType(info.returnType)) {
        const actualReturnType = getNamedType(info.returnType) as GraphQLObjectType;
        const entityTypeExtensions = actualReturnType.extensions as unknown as EntityTypeExtensions;
        if ('Message' in responseJson && !('value' in responseJson)) {
          const error = new Error(responseJson.Message);
          Object.assign(error, { extensions: responseJson });
          throw error;
        }
        const returnList: any[] = responseJson.value;
        return returnList.map(element => {
          if (!entityTypeExtensions?.entityInfo) {
            return element;
          }
          const urlOfElement = new URL(urlStringWithoutSearchParams);
          addIdentifierToUrl(
            urlOfElement,
            entityTypeExtensions.entityInfo.identifierFieldName,
            entityTypeExtensions.entityInfo.identifierFieldTypeRef,
            element
          );
          const identifierUrl = element['@odata.id'] || getUrlString(urlOfElement);
          const fieldMap = actualReturnType.getFields();
          for (const fieldName in element) {
            if (entityTypeExtensions.entityInfo.navigationFields.includes(fieldName)) {
              const field = element[fieldName];
              let fieldType = fieldMap[fieldName].type;
              if ('ofType' in fieldType) {
                fieldType = fieldType.ofType;
              }
              const { entityInfo: fieldEntityInfo } = (fieldType as any).extensions as EntityTypeExtensions;
              if (field instanceof Array) {
                for (const fieldElement of field) {
                  const urlOfField = new URL(urljoin(identifierUrl, fieldName));
                  addIdentifierToUrl(
                    urlOfField,
                    fieldEntityInfo.identifierFieldName,
                    fieldEntityInfo.identifierFieldTypeRef,
                    fieldElement
                  );
                  fieldElement['@odata.id'] = fieldElement['@odata.id'] || getUrlString(urlOfField);
                }
              } else {
                const urlOfField = new URL(urljoin(identifierUrl, fieldName));
                addIdentifierToUrl(
                  urlOfField,
                  fieldEntityInfo.identifierFieldName,
                  fieldEntityInfo.identifierFieldTypeRef,
                  field
                );
                field['@odata.id'] = field['@odata.id'] || getUrlString(urlOfField);
              }
            }
          }
          return {
            '@odata.id': identifierUrl,
            ...element,
          };
        });
      } else {
        const actualReturnType = info.returnType as GraphQLObjectType;
        const entityTypeExtensions = actualReturnType.extensions as unknown as EntityTypeExtensions;
        if (!entityTypeExtensions?.entityInfo) {
          return responseJson;
        }
        const identifierUrl = responseJson['@odata.id'] || urlStringWithoutSearchParams;
        const fieldMap = actualReturnType.getFields();
        for (const fieldName in responseJson) {
          if (entityTypeExtensions?.entityInfo.navigationFields.includes(fieldName)) {
            const field = responseJson[fieldName];
            let fieldType = fieldMap[fieldName].type;
            if ('ofType' in fieldType) {
              fieldType = fieldType.ofType;
            }
            const { entityInfo: fieldEntityInfo } = (fieldType as any).extensions as EntityTypeExtensions;
            if (field instanceof Array) {
              for (const fieldElement of field) {
                const urlOfField = new URL(urljoin(identifierUrl, fieldName));
                addIdentifierToUrl(
                  urlOfField,
                  fieldEntityInfo.identifierFieldName,
                  fieldEntityInfo.identifierFieldTypeRef,
                  fieldElement
                );
                fieldElement['@odata.id'] = fieldElement['@odata.id'] || getUrlString(urlOfField);
              }
            } else {
              const urlOfField = new URL(urljoin(identifierUrl, fieldName));
              addIdentifierToUrl(
                urlOfField,
                fieldEntityInfo.identifierFieldName,
                fieldEntityInfo.identifierFieldTypeRef,
                field
              );
              field['@odata.id'] = field['@odata.id'] || getUrlString(urlOfField);
            }
          }
        }
        return {
          '@odata.id': responseJson['@odata.id'] || urlStringWithoutSearchParams,
          ...responseJson,
        };
      }
    }

    schemaComposer.createEnumTC({
      name: 'InlineCount',
      values: {
        allpages: {
          value: 'allpages',
          description:
            'The OData MUST include a count of the number of entities in the collection identified by the URI (after applying any $filter System Query Options present on the URI)',
        },
        none: {
          value: 'none',
          description:
            'The OData service MUST NOT include a count in the response. This is equivalence to a URI that does not include a $inlinecount query string parameter.',
        },
      },
    });

    schemaComposer.createInputTC({
      name: 'QueryOptions',
      fields: queryOptionsFields,
    });

    const origHeadersFactory = getInterpolatedHeadersFactory(operationHeaders);
    const headersFactory = (resolverData: ResolverData, method: string) => {
      const headers = origHeadersFactory(resolverData);
      if (headers.accept == null) {
        headers.accept = 'application/json';
      }
      if (headers['content-type'] == null && method !== 'GET') {
        headers['content-type'] = 'application/json';
      }
      return headers;
    };
    const { args: commonArgs, contextVariables } = parseInterpolationStrings([
      ...Object.values(operationHeaders || {}),
      baseUrl,
    ]);

    function getTCByTypeNames(...typeNames: string[]) {
      for (const typeName of typeNames) {
        try {
          return schemaComposer.getAnyTC(typeName);
        } catch {}
      }
      return null;
    }

    function addIdentifierToUrl(url: URL, identifierFieldName: string, identifierFieldTypeRef: string, args: any) {
      url.href += `/${args[identifierFieldName]}/`;
    }

    function rebuildOpenInputObjects(input: any) {
      if (typeof input === 'object') {
        if ('rest' in input) {
          Object.assign(input, input.rest);
          delete input.rest;
        }
        for (const fieldName in input) {
          rebuildOpenInputObjects(input[fieldName]);
        }
      }
    }

    function handleBatchJsonResults(batchResponseJson: any, requests: Request[]) {
      if ('error' in batchResponseJson) {
        const error = new Error(batchResponseJson.error.message);
        Object.assign(error, {
          extensions: batchResponseJson.error,
        });
        throw error;
      }
      if (!('responses' in batchResponseJson)) {
        const error = new Error(
          batchResponseJson.ExceptionMessage ||
            batchResponseJson.Message ||
            `Batch Request didn't return a valid response.`
        );
        Object.assign(error, {
          extensions: batchResponseJson,
        });
        throw error;
      }
      return requests.map((_req, index) => {
        const responseObj = batchResponseJson.responses.find((res: any) => res.id === index.toString());
        return new Response(JSON.stringify(responseObj.body), {
          status: responseObj.status,
          headers: responseObj.headers,
        });
      });
    }

    const DATALOADER_FACTORIES = {
      multipart: (context: any) =>
        new DataLoader(async (requests: Request[]): Promise<Response[]> => {
          let requestBody = '';
          const requestBoundary = 'batch_' + Date.now();
          for (const requestIndex in requests) {
            requestBody += `--${requestBoundary}\n`;
            const request = requests[requestIndex];
            requestBody += `Content-Type: application/http\n`;
            requestBody += `Content-Transfer-Encoding:binary\n`;
            requestBody += `Content-ID: ${requestIndex}\n\n`;
            requestBody += `${request.method} ${request.url} HTTP/1.1\n`;
            request.headers?.forEach((value, key) => {
              requestBody += `${key}: ${value}\n`;
            });
            if (request.body) {
              const bodyAsStr = await request.text();
              requestBody += `Content-Length: ${bodyAsStr.length}`;
              requestBody += `\n`;
              requestBody += bodyAsStr;
            }
            requestBody += `\n`;
          }
          requestBody += `--${requestBoundary}--\n`;
          const batchHeaders = headersFactory(
            {
              context,
              env: process.env,
            },
            'POST'
          );
          batchHeaders['content-type'] = `multipart/mixed;boundary=${requestBoundary}`;
          const batchResponse = await fetch(urljoin(baseUrl, '$batch'), {
            method: 'POST',
            body: requestBody,
            headers: batchHeaders,
          });
          if (batchResponse.headers.get('content-type').includes('json')) {
            const batchResponseJson = await batchResponse.json();
            return handleBatchJsonResults(batchResponseJson, requests);
          }
          const batchResponseText = await batchResponse.text();
          const responseLines = batchResponseText.split('\n');
          const responseBoundary = responseLines[0];
          const actualResponse = responseLines.slice(1, responseLines.length - 2).join('\n');
          const responseTextArr = actualResponse.split(responseBoundary);
          return responseTextArr.map(responseTextWithContentHeader => {
            const responseText = responseTextWithContentHeader.split('\n').slice(4).join('\n');
            const { body, headers, statusCode, statusMessage } = parseResponse(responseText);
            return new Response(body, {
              headers,
              status: parseInt(statusCode),
              statusText: statusMessage,
            });
          });
        }),
      json: (context: any) =>
        new DataLoader(async (requests: Request[]): Promise<Response[]> => {
          const batchHeaders = headersFactory(
            {
              context,
              env: process.env,
            },
            'POST'
          );
          batchHeaders['content-type'] = 'application/json';
          const batchResponse = await fetch(urljoin(baseUrl, '$batch'), {
            method: 'POST',
            body: JSON.stringify({
              requests: await Promise.all(
                requests.map(async (request, index) => {
                  const id = index.toString();
                  const url = request.url.replace(baseUrl, '');
                  const method = request.method;
                  const headers: HeadersInit = {};
                  request.headers?.forEach((value, key) => {
                    headers[key] = value;
                  });
                  return {
                    id,
                    url,
                    method,
                    body: request.body && (await request.json()),
                    headers,
                  };
                })
              ),
            }),
            headers: batchHeaders,
          });
          const batchResponseJson = await batchResponse.json();
          return handleBatchJsonResults(batchResponseJson, requests);
        }),
      none: () =>
        new DataLoader(
          (requests: Request[]): Promise<Response[]> => Promise.all(requests.map(request => fetch(request)))
        ),
    };

    const dataLoaderFactory = memoize1(DATALOADER_FACTORIES[this.config.batch || 'none']);

    function buildName({ schemaNamespace, name }: { schemaNamespace: string; name: string }) {
      const alias = aliasNamespaceMap.get(schemaNamespace) || schemaNamespace;
      const ref = alias + '.' + name;
      return multipleSchemas ? pascalCase(ref.split('.').join('_')) : name;
    }

    schemas?.forEach((schemaObj: any) => {
      const schemaNamespace = schemaObj.attributes.Namespace;
      namespaces.add(schemaNamespace);
      const schemaAlias = schemaObj.attributes.Alias;
      if (schemaAlias) {
        aliasNamespaceMap.set(schemaNamespace, schemaAlias);
      }
    });

    schemas?.forEach((schemaObj: any) => {
      const schemaNamespace = schemaObj.attributes.Namespace;

      schemaObj.EnumType?.forEach((enumObj: any) => {
        const values: Record<string, EnumTypeComposerValueConfigDefinition> = {};
        enumObj.Member?.forEach((memberObj: any) => {
          const key = memberObj.attributes.Name;
          // This doesn't work.
          // const value = memberElement.getAttribute('Value')!;
          values[key] = {
            value: key,
            extensions: { memberObj },
          };
        });
        const enumTypeName = buildName({ schemaNamespace, name: enumObj.attributes.Name });
        schemaComposer.createEnumTC({
          name: enumTypeName,
          values,
          extensions: { enumObj },
        });
      });

      const allTypes = (schemaObj.EntityType || []).concat(schemaObj.ComplexType || []);
      const typesWithBaseType = allTypes.filter((typeObj: any) => typeObj.attributes.BaseType);

      allTypes?.forEach((typeObj: any) => {
        const entityTypeName = buildName({ schemaNamespace, name: typeObj.attributes.Name });
        const isOpenType = typeObj.attributes.OpenType === 'true';
        const isAbstract = typeObj.attributes.Abstract === 'true';
        const eventEmitter = new EventEmitter();
        eventEmitter.setMaxListeners(Infinity);
        this.eventEmitterSet.add(eventEmitter);
        const extensions: EntityTypeExtensions = {
          entityInfo: {
            actualFields: [],
            navigationFields: [],
            isOpenType,
          },
          typeObj,
          eventEmitter,
        };
        const inputType = schemaComposer.createInputTC({
          name: entityTypeName + 'Input',
          fields: {},
          extensions: () => extensions,
        });
        let abstractType: InterfaceTypeComposer;
        if (
          typesWithBaseType.some((typeObj: any) => typeObj.attributes.BaseType.includes(`.${entityTypeName}`)) ||
          isAbstract
        ) {
          abstractType = schemaComposer.createInterfaceTC({
            name: isAbstract ? entityTypeName : `I${entityTypeName}`,
            extensions,
            resolveType: (root: any) => {
              const typeRef = root['@odata.type']?.replace('#', '');
              if (typeRef) {
                const typeName = getTypeNameFromRef({
                  typeRef: root['@odata.type'].replace('#', ''),
                  isInput: false,
                  isRequired: false,
                });
                return typeName;
              }
              return isAbstract ? `T${entityTypeName}` : entityTypeName;
            },
          });
        }
        const outputType = schemaComposer.createObjectTC({
          name: isAbstract ? `T${entityTypeName}` : entityTypeName,
          extensions,
          interfaces: abstractType ? [abstractType] : [],
        });

        abstractType?.setInputTypeComposer(inputType);
        outputType.setInputTypeComposer(inputType);

        const propertyRefObj = typeObj.Key && typeObj.Key[0].PropertyRef[0];
        if (propertyRefObj) {
          extensions.entityInfo.identifierFieldName = propertyRefObj.attributes.Name;
        }

        typeObj.Property?.forEach((propertyObj: any) => {
          const propertyName = propertyObj.attributes.Name;
          extensions.entityInfo.actualFields.push(propertyName);
          const propertyTypeRef = propertyObj.attributes.Type;
          if (propertyName === extensions.entityInfo.identifierFieldName) {
            extensions.entityInfo.identifierFieldTypeRef = propertyTypeRef;
          }
          const isRequired = propertyObj.attributes.Nullable === 'false';
          inputType.addFields({
            [propertyName]: {
              type: getTypeNameFromRef({
                typeRef: propertyTypeRef,
                isInput: true,
                isRequired,
              }),
              extensions: { propertyObj },
            },
          });
          const field: ObjectTypeComposerFieldConfigDefinition<any, unknown> = {
            type: getTypeNameFromRef({
              typeRef: propertyTypeRef,
              isInput: false,
              isRequired,
            }),
            extensions: { propertyObj },
          };
          abstractType?.addFields({
            [propertyName]: field,
          });
          outputType.addFields({
            [propertyName]: field,
          });
        });
        typeObj.NavigationProperty?.forEach((navigationPropertyObj: any) => {
          const navigationPropertyName = navigationPropertyObj.attributes.Name;
          extensions.entityInfo.navigationFields.push(navigationPropertyName);
          const navigationPropertyTypeRef = navigationPropertyObj.attributes.Type;
          const isRequired = navigationPropertyObj.attributes.Nullable === 'false';
          const isList = navigationPropertyTypeRef.startsWith('Collection(');
          if (isList) {
            const singularField: ObjectTypeComposerFieldConfigDefinition<any, unknown> = {
              type: getTypeNameFromRef({
                typeRef: navigationPropertyTypeRef,
                isInput: false,
                isRequired,
              })
                .replace('[', '')
                .replace(']', ''),
              args: {
                ...commonArgs,
                id: {
                  type: 'ID',
                },
              },
              extensions: { navigationPropertyObj },
              resolve: async (root, args, context, info) => {
                if (navigationPropertyName in root) {
                  return root[navigationPropertyName];
                }
                const url = new URL(root['@odata.id']);
                url.href = urljoin(url.href, '/' + navigationPropertyName);
                const returnType = info.returnType as GraphQLObjectType;
                const { entityInfo } = returnType.extensions as unknown as EntityTypeExtensions;
                addIdentifierToUrl(url, entityInfo.identifierFieldName, entityInfo.identifierFieldTypeRef, args);
                const parsedInfoFragment = parseResolveInfo(info) as ResolveTree;
                const searchParams = this.prepareSearchParams(parsedInfoFragment, info.schema);
                searchParams?.forEach((value, key) => {
                  url.searchParams.set(key, value);
                });
                const urlString = getUrlString(url);
                const method = 'GET';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            };
            const pluralField: ObjectTypeComposerFieldConfigDefinition<any, unknown> = {
              type: getTypeNameFromRef({
                typeRef: navigationPropertyTypeRef,
                isInput: false,
                isRequired,
              }),
              args: {
                ...commonArgs,
                queryOptions: { type: 'QueryOptions' },
              },
              extensions: { navigationPropertyObj },
              resolve: async (root, args, context, info) => {
                if (navigationPropertyName in root) {
                  return root[navigationPropertyName];
                }
                const url = new URL(root['@odata.id']);
                url.href = urljoin(url.href, '/' + navigationPropertyName);
                const parsedInfoFragment = parseResolveInfo(info) as ResolveTree;
                const searchParams = this.prepareSearchParams(parsedInfoFragment, info.schema);
                searchParams?.forEach((value, key) => {
                  url.searchParams.set(key, value);
                });
                const urlString = getUrlString(url);
                const method = 'GET';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            };
            abstractType?.addFields({
              [navigationPropertyName]: pluralField,
              [`${navigationPropertyName}ById`]: singularField,
            });
            outputType.addFields({
              [navigationPropertyName]: pluralField,
              [`${navigationPropertyName}ById`]: singularField,
            });
          } else {
            const field: ObjectTypeComposerFieldConfigDefinition<any, unknown> = {
              type: getTypeNameFromRef({
                typeRef: navigationPropertyTypeRef,
                isInput: false,
                isRequired,
              }),
              args: {
                ...commonArgs,
              },
              extensions: { navigationPropertyObj },
              resolve: async (root, args, context, info) => {
                if (navigationPropertyName in root) {
                  return root[navigationPropertyName];
                }
                const url = new URL(root['@odata.id']);
                url.href = urljoin(url.href, '/' + navigationPropertyName);
                const parsedInfoFragment = parseResolveInfo(info) as ResolveTree;
                const searchParams = this.prepareSearchParams(parsedInfoFragment, info.schema);
                searchParams?.forEach((value, key) => {
                  url.searchParams.set(key, value);
                });
                const urlString = getUrlString(url);
                const method = 'GET';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            };
            abstractType?.addFields({
              [navigationPropertyName]: field,
            });
            outputType.addFields({
              [navigationPropertyName]: field,
            });
          }
        });
        if (isOpenType || outputType.getFieldNames().length === 0) {
          extensions.entityInfo.isOpenType = true;
          inputType.addFields({
            rest: {
              type: 'JSON',
            },
          });
          abstractType?.addFields({
            rest: {
              type: 'JSON',
              resolve: (root: any) => root,
            },
          });
          outputType.addFields({
            rest: {
              type: 'JSON',
              resolve: (root: any) => root,
            },
          });
        }
        const updateInputType = inputType.clone(`${entityTypeName}UpdateInput`);
        updateInputType.getFieldNames()?.forEach(fieldName => updateInputType.makeOptional(fieldName));
        // Types might be considered as unused implementations of interfaces so we must prevent that
        schemaComposer.addSchemaMustHaveType(outputType);
      });

      const handleUnboundFunctionObj = (unboundFunctionObj: any) => {
        const functionName = unboundFunctionObj.attributes.Name;
        const returnTypeRef = unboundFunctionObj.ReturnType[0].attributes.Type;
        const returnType = getTypeNameFromRef({
          typeRef: returnTypeRef,
          isInput: false,
          isRequired: false,
        });
        schemaComposer.Query.addFields({
          [functionName]: {
            type: returnType,
            args: {
              ...commonArgs,
            },
            resolve: async (root, args, context, info) => {
              const url = new URL(baseUrl);
              url.href = urljoin(url.href, '/' + functionName);
              url.href += `(${Object.entries(args)
                .filter(argEntry => argEntry[0] !== 'queryOptions')
                .map(argEntry => argEntry.join(' = '))
                .join(', ')})`;
              const parsedInfoFragment = parseResolveInfo(info) as ResolveTree;
              const searchParams = this.prepareSearchParams(parsedInfoFragment, info.schema);
              searchParams?.forEach((value, key) => {
                url.searchParams.set(key, value);
              });
              const urlString = getUrlString(url);
              const method = 'GET';
              const request = new Request(urlString, {
                method,
                headers: headersFactory(
                  {
                    root,
                    args,
                    context,
                    info,
                    env: process.env,
                  },
                  method
                ),
              });
              const response = await context[contextDataloaderName].load(request);
              const responseText = await response.text();
              return handleResponseText(responseText, urlString, info);
            },
          },
        });
        unboundFunctionObj.Parameter?.forEach((parameterObj: any) => {
          const parameterName = parameterObj.attributes.Name;
          const parameterTypeRef = parameterObj.attributes.Type;
          const isRequired = parameterObj.attributes.Nullable === 'false';
          const parameterType = getTypeNameFromRef({
            typeRef: parameterTypeRef,
            isInput: true,
            isRequired,
          });
          schemaComposer.Query.addFieldArgs(functionName, {
            [parameterName]: {
              type: parameterType,
            },
          });
        });
      };

      const handleBoundFunctionObj = (boundFunctionObj: any) => {
        const functionName = boundFunctionObj.attributes.Name;
        const functionRef = schemaNamespace + '.' + functionName;
        const returnTypeRef = boundFunctionObj.ReturnType[0].attributes.Type;
        const returnType = getTypeNameFromRef({
          typeRef: returnTypeRef,
          isInput: false,
          isRequired: false,
        });
        const args: ObjectTypeComposerArgumentConfigMapDefinition<any> = {
          ...commonArgs,
        };
        // eslint-disable-next-line prefer-const
        let entitySetPath = boundFunctionObj.attributes.EntitySetPath?.split('/')[0];
        let field: ObjectTypeComposerFieldConfigDefinition<any, any, any>;
        let boundEntityTypeName: string;
        boundFunctionObj.Parameter?.forEach((parameterObj: any) => {
          const parameterName = parameterObj.attributes.Name;
          const parameterTypeRef = parameterObj.attributes.Type;
          const isRequired = parameterObj.attributes.Nullable === 'false';
          const parameterTypeName = getTypeNameFromRef({
            typeRef: parameterTypeRef,
            isInput: true,
            isRequired,
          });
          // If entitySetPath is not available, take first parameter as entity
          // The first segment of the entity set path must match the binding parameter name
          // (see: http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#_Toc38530388)
          entitySetPath = (entitySetPath && entitySetPath.split('/')[0]) || parameterName;
          if (entitySetPath === parameterName) {
            boundEntityTypeName = getTypeNameFromRef({
              typeRef: parameterTypeRef,
              isInput: false,
              isRequired: false,
            })
              .replace('[', '')
              .replace(']', '');
            field = {
              type: returnType,
              args,
              resolve: async (root, args, context, info) => {
                const url = new URL(root['@odata.id']);
                url.href = urljoin(url.href, '/' + functionRef);
                const argsEntries = Object.entries(args);
                if (argsEntries.length) {
                  url.href += `(${argsEntries
                    .filter(argEntry => argEntry[0] !== 'queryOptions')
                    .map(([argName, value]) => [argName, typeof value === 'string' ? `'${value}'` : value])
                    .map(argEntry => argEntry.join('='))
                    .join(',')})`;
                }
                const parsedInfoFragment = parseResolveInfo(info) as ResolveTree;
                const searchParams = this.prepareSearchParams(parsedInfoFragment, info.schema);
                searchParams?.forEach((value, key) => {
                  url.searchParams.set(key, value);
                });
                const urlString = getUrlString(url);
                const method = 'GET';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            };
          }
          args[parameterName] = {
            type: parameterTypeName,
          };
        });
        const boundEntityType = schemaComposer.getAnyTC(boundEntityTypeName) as InterfaceTypeComposer;
        const boundEntityOtherType = getTCByTypeNames(
          'I' + boundEntityTypeName,
          'T' + boundEntityTypeName
        ) as InterfaceTypeComposer;
        boundEntityType.addFields({
          [functionName]: field,
        });
        boundEntityOtherType?.addFields({
          [functionName]: field,
        });
      };

      schemaObj.Function?.forEach((functionObj: any) => {
        if (functionObj.attributes?.IsBound === 'true') {
          handleBoundFunctionObj(functionObj);
        } else {
          handleUnboundFunctionObj(functionObj);
        }
      });

      const handleUnboundActionObj = (unboundActionObj: any) => {
        const actionName = unboundActionObj.attributes.Name;
        schemaComposer.Mutation.addFields({
          [actionName]: {
            type: 'JSON',
            args: {
              ...commonArgs,
            },
            resolve: async (root, args, context, info) => {
              const url = new URL(baseUrl);
              url.href = urljoin(url.href, '/' + actionName);
              const urlString = getUrlString(url);
              const method = 'POST';
              const request = new Request(urlString, {
                method,
                headers: headersFactory(
                  {
                    root,
                    args,
                    context,
                    info,
                    env: process.env,
                  },
                  method
                ),
                body: JSON.stringify(args),
              });
              const response = await context[contextDataloaderName].load(request);
              const responseText = await response.text();
              return handleResponseText(responseText, urlString, info);
            },
          },
        });

        unboundActionObj.Parameter?.forEach((parameterObj: any) => {
          const parameterName = parameterObj.attributes.Name;
          const parameterTypeRef = parameterObj.attributes.Type;
          const isRequired = parameterObj.attributes.Nullable === 'false';
          const parameterType = getTypeNameFromRef({
            typeRef: parameterTypeRef,
            isInput: true,
            isRequired,
          });
          schemaComposer.Mutation.addFieldArgs(actionName, {
            [parameterName]: {
              type: parameterType,
            },
          });
        });
      };

      const handleBoundActionObj = (boundActionObj: any) => {
        const actionName = boundActionObj.attributes.Name;
        const actionRef = schemaNamespace + '.' + actionName;
        const args: ObjectTypeComposerArgumentConfigMapDefinition<any> = {
          ...commonArgs,
        };
        let entitySetPath = boundActionObj.attributes.EntitySetPath;
        let boundField: ObjectTypeComposerFieldConfigDefinition<any, any, any>;
        let boundEntityTypeName: string;
        boundActionObj.Parameter?.forEach((parameterObj: any) => {
          const parameterName = parameterObj.attributes.Name;
          const parameterTypeRef = parameterObj.attributes.Type;
          const isRequired = parameterObj.attributes.Nullable === 'false';
          const parameterTypeName = getTypeNameFromRef({
            typeRef: parameterTypeRef,
            isInput: true,
            isRequired,
          });
          // If entitySetPath is not available, take first parameter as entity
          entitySetPath = entitySetPath || parameterName;
          if (entitySetPath === parameterName) {
            boundEntityTypeName = getTypeNameFromRef({
              typeRef: parameterTypeRef,
              isInput: false,
              isRequired: false,
            })
              .replace('[', '')
              .replace(']', ''); // Todo temp workaround
            boundField = {
              type: 'JSON',
              args,
              resolve: async (root, args, context, info) => {
                const url = new URL(root['@odata.id']);
                url.href = urljoin(url.href, '/' + actionRef);
                const urlString = getUrlString(url);
                const method = 'POST';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                  body: JSON.stringify(args),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            };
          }
          args[parameterName] = {
            type: parameterTypeName,
          };
        });
        const boundEntityType = schemaComposer.getAnyTC(boundEntityTypeName) as InterfaceTypeComposer;
        boundEntityType.addFields({
          [actionName]: boundField,
        });
        const otherType = getTCByTypeNames(
          `I${boundEntityTypeName}`,
          `T${boundEntityTypeName}`
        ) as InterfaceTypeComposer;
        otherType?.addFields({
          [actionName]: boundField,
        });
      };

      schemaObj.Action?.forEach((actionObj: any) => {
        if (actionObj.attributes?.IsBound === 'true') {
          handleBoundActionObj(actionObj);
        } else {
          handleUnboundActionObj(actionObj);
        }
      });

      // Rearrange fields for base types and implementations
      typesWithBaseType?.forEach((typeObj: any) => {
        const typeName = buildName({
          schemaNamespace,
          name: typeObj.attributes.Name,
        });
        const inputType = schemaComposer.getITC(typeName + 'Input') as InputTypeComposer;
        const abstractType = getTCByTypeNames('I' + typeName, typeName) as InterfaceTypeComposer;
        const outputType = getTCByTypeNames('T' + typeName, typeName) as ObjectTypeComposer;
        const baseTypeRef = typeObj.attributes.BaseType;
        const { entityInfo, eventEmitter } = outputType.getExtensions() as EntityTypeExtensions;
        const baseTypeName = getTypeNameFromRef({
          typeRef: baseTypeRef,
          isInput: false,
          isRequired: false,
        });
        const baseInputType = schemaComposer.getAnyTC(baseTypeName + 'Input') as InputTypeComposer;
        const baseAbstractType = getTCByTypeNames('I' + baseTypeName, baseTypeName) as InterfaceTypeComposer;
        const baseOutputType = getTCByTypeNames('T' + baseTypeName, baseTypeName) as ObjectTypeComposer;
        const { entityInfo: baseEntityInfo, eventEmitter: baseEventEmitter } =
          baseOutputType.getExtensions() as EntityTypeExtensions;
        const baseEventEmitterListener = () => {
          inputType.addFields(baseInputType.getFields());
          entityInfo.identifierFieldName = baseEntityInfo.identifierFieldName || entityInfo.identifierFieldName;
          entityInfo.identifierFieldTypeRef =
            baseEntityInfo.identifierFieldTypeRef || entityInfo.identifierFieldTypeRef;
          entityInfo.actualFields.unshift(...baseEntityInfo.actualFields);
          abstractType?.addFields(baseAbstractType?.getFields());
          outputType.addFields(baseOutputType.getFields());
          if (baseAbstractType instanceof InterfaceTypeComposer) {
            // abstractType.addInterface(baseAbstractType.getTypeName());
            outputType.addInterface(baseAbstractType.getTypeName());
          }
          eventEmitter.emit('onFieldChange');
        };
        baseEventEmitter.on('onFieldChange', baseEventEmitterListener);
        baseEventEmitterListener();
      });
    });

    schemas?.forEach((schemaObj: any) => {
      schemaObj.EntityContainer?.forEach((entityContainerObj: any) => {
        entityContainerObj.Singleton?.forEach((singletonObj: any) => {
          const singletonName = singletonObj.attributes.Name;
          const singletonTypeRef = singletonObj.attributes.Type;
          const singletonTypeName = getTypeNameFromRef({
            typeRef: singletonTypeRef,
            isInput: false,
            isRequired: false,
          });
          schemaComposer.Query.addFields({
            [singletonName]: {
              type: singletonTypeName,
              args: {
                ...commonArgs,
              },
              resolve: async (root, args, context, info) => {
                const url = new URL(baseUrl);
                url.href = urljoin(url.href, '/' + singletonName);
                const parsedInfoFragment = parseResolveInfo(info) as ResolveTree;
                const searchParams = this.prepareSearchParams(parsedInfoFragment, info.schema);
                searchParams?.forEach((value, key) => {
                  url.searchParams.set(key, value);
                });
                const urlString = getUrlString(url);
                const method = 'GET';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            },
          });
        });

        entityContainerObj?.EntitySet?.forEach((entitySetObj: any) => {
          const entitySetName = entitySetObj.attributes.Name;
          const entitySetTypeRef = entitySetObj.attributes.EntityType;
          const entityTypeName = getTypeNameFromRef({
            typeRef: entitySetTypeRef,
            isInput: false,
            isRequired: false,
          });
          const entityOutputTC = getTCByTypeNames('I' + entityTypeName, entityTypeName) as
            | InterfaceTypeComposer
            | ObjectTypeComposer;
          const { entityInfo } = entityOutputTC.getExtensions() as EntityTypeExtensions;
          const identifierFieldName = entityInfo.identifierFieldName;
          const identifierFieldTypeRef = entityInfo.identifierFieldTypeRef;
          const identifierFieldTypeName = entityOutputTC.getFieldTypeName(identifierFieldName);
          const typeName = entityOutputTC.getTypeName();
          const commonFields: Record<string, ObjectTypeComposerFieldConfigDefinition<any, any>> = {
            [entitySetName]: {
              type: `[${typeName}]`,
              args: {
                ...commonArgs,
                queryOptions: { type: 'QueryOptions' },
              },
              resolve: async (root, args, context, info) => {
                const url = new URL(baseUrl);
                url.href = urljoin(url.href, '/' + entitySetName);
                const parsedInfoFragment = parseResolveInfo(info) as ResolveTree;
                const searchParams = this.prepareSearchParams(parsedInfoFragment, info.schema);
                searchParams?.forEach((value, key) => {
                  url.searchParams.set(key, value);
                });
                const urlString = getUrlString(url);
                const method = 'GET';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            },
            [`${entitySetName}By${identifierFieldName}`]: {
              type: typeName,
              args: {
                ...commonArgs,
                [identifierFieldName]: {
                  type: identifierFieldTypeName,
                },
              },
              resolve: async (root, args, context, info) => {
                const url = new URL(baseUrl);
                url.href = urljoin(url.href, '/' + entitySetName);
                addIdentifierToUrl(url, identifierFieldName, identifierFieldTypeRef, args);
                const parsedInfoFragment = parseResolveInfo(info) as ResolveTree;
                const searchParams = this.prepareSearchParams(parsedInfoFragment, info.schema);
                searchParams?.forEach((value, key) => {
                  url.searchParams.set(key, value);
                });
                const urlString = getUrlString(url);
                const method = 'GET';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            },
          };
          schemaComposer.Query.addFields({
            ...commonFields,
            [`${entitySetName}Count`]: {
              type: 'Int',
              args: {
                ...commonArgs,
                queryOptions: { type: 'QueryOptions' },
              },
              resolve: async (root, args, context, info) => {
                const url = new URL(baseUrl);
                url.href = urljoin(url.href, `/${entitySetName}/$count`);
                const urlString = getUrlString(url);
                const method = 'GET';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return responseText;
              },
            },
          });
          schemaComposer.Mutation.addFields({
            ...commonFields,
            [`create${entitySetName}`]: {
              type: typeName,
              args: {
                ...commonArgs,
                input: {
                  type: entityTypeName + 'Input',
                },
              },
              resolve: async (root, args, context, info) => {
                const url = new URL(baseUrl);
                url.href = urljoin(url.href, '/' + entitySetName);
                const urlString = getUrlString(url);
                rebuildOpenInputObjects(args.input);
                const method = 'POST';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                  body: JSON.stringify(args.input),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            },
            [`delete${entitySetName}By${identifierFieldName}`]: {
              type: 'JSON',
              args: {
                ...commonArgs,
                [identifierFieldName]: {
                  type: identifierFieldTypeName,
                },
              },
              resolve: async (root, args, context, info) => {
                const url = new URL(baseUrl);
                url.href = urljoin(url.href, '/' + entitySetName);
                addIdentifierToUrl(url, identifierFieldName, identifierFieldTypeRef, args);
                const urlString = getUrlString(url);
                const method = 'DELETE';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            },
            [`update${entitySetName}By${identifierFieldName}`]: {
              type: typeName,
              args: {
                ...commonArgs,
                [identifierFieldName]: {
                  type: identifierFieldTypeName,
                },
                input: {
                  type: entityTypeName + 'UpdateInput',
                },
              },
              resolve: async (root, args, context, info) => {
                const url = new URL(baseUrl);
                url.href = urljoin(url.href, '/' + entitySetName);
                addIdentifierToUrl(url, identifierFieldName, identifierFieldTypeRef, args);
                const urlString = getUrlString(url);
                rebuildOpenInputObjects(args.input);
                const method = 'PATCH';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                  body: JSON.stringify(args.input),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            },
          });
        });
      });
    });

    // graphql-compose doesn't add @defer and @stream to the schema
    specifiedDirectives.forEach(directive => schemaComposer.addDirective(directive));

    const schema = schemaComposer.buildSchema();
    this.eventEmitterSet.forEach(ee => ee.removeAllListeners());
    this.eventEmitterSet.clear();

    const executor = createDefaultExecutor(schema);

    return {
      schema,
      executor: <TResult>(executionRequest: ExecutionRequest) => {
        const odataContext = {
          [contextDataloaderName]: dataLoaderFactory(executionRequest.context),
        };
        return executor({
          ...executionRequest,
          context: {
            ...executionRequest.context,
            ...odataContext,
          },
        }) as ExecutionResult<TResult>;
      },
      contextVariables,
      batch: true,
    };
  }
Example #16
Source File: codeGeneration.ts    From amplify-codegen with Apache License 2.0 4 votes vote down vote up
structDeclarationForVariant(
    {
      structName,
      adoptedProtocols = ['GraphQLSelectionSet'],
      variant,
      typeCase,
    }: {
      structName: string;
      adoptedProtocols?: string[];
      variant: Variant;
      typeCase?: TypeCase;
    },
    before?: Function,
    after?: Function
  ) {
    this.structDeclaration({ structName, adoptedProtocols }, () => {
      if (before) {
        before();
      }

      this.printNewlineIfNeeded();
      this.printOnNewline('public static let possibleTypes = [');
      this.print(join(variant.possibleTypes.map(type => `"${type.name}"`), ', '));
      this.print(']');

      this.printNewlineIfNeeded();
      this.printOnNewline('public static let selections: [GraphQLSelection] = ');
      if (typeCase) {
        this.typeCaseInitialization(typeCase);
      } else {
        this.selectionSetInitialization(variant);
      }

      this.printNewlineIfNeeded();

      this.propertyDeclaration({
        propertyName: 'snapshot',
        typeName: 'Snapshot',
      });

      this.printNewlineIfNeeded();
      this.printOnNewline('public init(snapshot: Snapshot)');
      this.withinBlock(() => {
        this.printOnNewline(`self.snapshot = snapshot`);
      });

      if (typeCase) {
        this.initializersForTypeCase(typeCase);
      } else {
        this.initializersForVariant(variant);
      }

      const fields = collectAndMergeFields(variant, this.context.options.mergeInFieldsFromFragmentSpreads).map(field =>
        this.helpers.propertyFromField(field as Field)
      );

      const fragmentSpreads = variant.fragmentSpreads.map(fragmentSpread => {
        const isConditional = variant.possibleTypes.some(type => !fragmentSpread.selectionSet.possibleTypes.includes(type));

        return this.helpers.propertyFromFragmentSpread(fragmentSpread, isConditional);
      });

      fields.forEach(this.propertyDeclarationForField, this);

      if (fragmentSpreads.length > 0) {
        this.printNewlineIfNeeded();
        this.printOnNewline(`public var fragments: Fragments`);
        this.withinBlock(() => {
          this.printOnNewline('get');
          this.withinBlock(() => {
            this.printOnNewline(`return Fragments(snapshot: snapshot)`);
          });
          this.printOnNewline('set');
          this.withinBlock(() => {
            this.printOnNewline(`snapshot += newValue.snapshot`);
          });
        });

        this.structDeclaration(
          {
            structName: 'Fragments',
          },
          () => {
            this.propertyDeclaration({
              propertyName: 'snapshot',
              typeName: 'Snapshot',
            });

            for (const fragmentSpread of fragmentSpreads) {
              const { propertyName, typeName, structName, isConditional } = fragmentSpread;

              this.printNewlineIfNeeded();
              this.printOnNewline(`public var ${escapeIdentifierIfNeeded(propertyName)}: ${typeName}`);
              this.withinBlock(() => {
                this.printOnNewline('get');
                this.withinBlock(() => {
                  if (isConditional) {
                    this.printOnNewline(`if !${structName}.possibleTypes.contains(snapshot["__typename"]! as! String) { return nil }`);
                  }
                  this.printOnNewline(`return ${structName}(snapshot: snapshot)`);
                });
                this.printOnNewline('set');
                this.withinBlock(() => {
                  if (isConditional) {
                    this.printOnNewline(`guard let newValue = newValue else { return }`);
                    this.printOnNewline(`snapshot += newValue.snapshot`);
                  } else {
                    this.printOnNewline(`snapshot += newValue.snapshot`);
                  }
                });
              });
            }
          }
        );
      }

      for (const field of fields) {
        if (isCompositeType(getNamedType(field.type)) && field.selectionSet) {
          this.structDeclarationForSelectionSet({
            structName: field.structName,
            selectionSet: field.selectionSet,
          });
        }
      }

      if (after) {
        after();
      }
    });
  }
Example #17
Source File: index.ts    From amplify-codegen with Apache License 2.0 4 votes vote down vote up
compileSelection(
    selectionNode: SelectionNode,
    parentType: GraphQLCompositeType,
    possibleTypes: GraphQLObjectType[],
    visitedFragments: Set<string>,
  ): Selection | null {
    switch (selectionNode.kind) {
      case Kind.FIELD: {
        const name = selectionNode.name.value;
        const alias = selectionNode.alias ? selectionNode.alias.value : undefined;

        const fieldDef = getFieldDef(this.schema, parentType, selectionNode);
        if (!fieldDef) {
          throw new GraphQLError(`Cannot query field "${name}" on type "${String(parentType)}"`, [selectionNode]);
        }

        const fieldType = fieldDef.type;
        const unmodifiedFieldType = getNamedType(fieldType);

        this.addTypeUsed(unmodifiedFieldType);

        const { description, isDeprecated, deprecationReason } = fieldDef;

        const responseKey = alias || name;

        const args =
          selectionNode.arguments && selectionNode.arguments.length > 0
            ? selectionNode.arguments.map(arg => {
                const name = arg.name.value;
                const argDef = fieldDef.args.find(argDef => argDef.name === arg.name.value);
                return {
                  name,
                  value: valueFromValueNode(arg.value),
                  type: (argDef && argDef.type) || undefined,
                };
              })
            : undefined;

        let field: Field = {
          kind: 'Field',
          responseKey,
          name,
          alias,
          args,
          type: fieldType,
          description: !isMetaFieldName(name) && description ? description : undefined,
          isDeprecated,
          deprecationReason: deprecationReason || undefined,
        };

        if (isCompositeType(unmodifiedFieldType)) {
          const selectionSetNode = selectionNode.selectionSet;
          if (!selectionSetNode) {
            throw new GraphQLError(`Composite field "${name}" on type "${String(parentType)}" requires selection set`, [selectionNode]);
          }

          field.selectionSet = this.compileSelectionSet(selectionNode.selectionSet as SelectionSetNode, unmodifiedFieldType);
        }
        return field;
      }
      case Kind.INLINE_FRAGMENT: {
        const typeNode = selectionNode.typeCondition;
        const type = typeNode ? (typeFromAST(this.schema, typeNode) as GraphQLCompositeType) : parentType;
        const possibleTypesForTypeCondition = this.possibleTypesForType(type).filter(type => possibleTypes.includes(type));
        return {
          kind: 'TypeCondition',
          type,
          selectionSet: this.compileSelectionSet(selectionNode.selectionSet, type, possibleTypesForTypeCondition),
        };
      }
      case Kind.FRAGMENT_SPREAD: {
        const fragmentName = selectionNode.name.value;
        if (visitedFragments.has(fragmentName)) return null;
        visitedFragments.add(fragmentName);

        const fragmentSpread: FragmentSpread = {
          kind: 'FragmentSpread',
          fragmentName,
          selectionSet: {
            possibleTypes,
            selections: [],
          },
        };
        this.unresolvedFragmentSpreads.push(fragmentSpread);
        return fragmentSpread;
      }
    }
  }
Example #18
Source File: resolve-additional-resolvers.ts    From graphql-mesh with MIT License 4 votes vote down vote up
function generateSelectionSetFactory(
  schema: GraphQLSchema,
  additionalResolver: YamlConfig.AdditionalStitchingBatchResolverObject | YamlConfig.AdditionalStitchingResolverObject
) {
  if (additionalResolver.sourceSelectionSet) {
    return () => parseSelectionSet(additionalResolver.sourceSelectionSet);
    // If result path provided without a selectionSet
  } else if (additionalResolver.result) {
    const resultPath = toPath(additionalResolver.result);
    let abstractResultTypeName: string;

    const sourceType = schema.getType(additionalResolver.sourceTypeName) as GraphQLObjectType;
    const sourceTypeFields = sourceType.getFields();
    const sourceField = sourceTypeFields[additionalResolver.sourceFieldName];
    const resultFieldType = getTypeByPath(sourceField.type, resultPath);

    if (isAbstractType(resultFieldType)) {
      if (additionalResolver.resultType) {
        abstractResultTypeName = additionalResolver.resultType;
      } else {
        const targetType = schema.getType(additionalResolver.targetTypeName) as GraphQLObjectType;
        const targetTypeFields = targetType.getFields();
        const targetField = targetTypeFields[additionalResolver.targetFieldName];
        const targetFieldType = getNamedType(targetField.type);
        abstractResultTypeName = targetFieldType?.name;
      }
      if (abstractResultTypeName !== resultFieldType.name) {
        const abstractResultType = schema.getType(abstractResultTypeName);
        if (
          (isInterfaceType(abstractResultType) || isObjectType(abstractResultType)) &&
          !schema.isSubType(resultFieldType, abstractResultType)
        ) {
          throw new Error(
            `${additionalResolver.sourceTypeName}.${additionalResolver.sourceFieldName}.${resultPath.join(
              '.'
            )} doesn't implement ${abstractResultTypeName}.}`
          );
        }
      }
    }

    return (subtree: SelectionSetNode) => {
      let finalSelectionSet = subtree;
      let isLastResult = true;
      const resultPathReversed = [...resultPath].reverse();
      for (const pathElem of resultPathReversed) {
        // Ensure the path elem is not array index
        if (Number.isNaN(parseInt(pathElem))) {
          if (isLastResult && abstractResultTypeName && abstractResultTypeName !== resultFieldType.name) {
            finalSelectionSet = {
              kind: Kind.SELECTION_SET,
              selections: [
                {
                  kind: Kind.INLINE_FRAGMENT,
                  typeCondition: {
                    kind: Kind.NAMED_TYPE,
                    name: {
                      kind: Kind.NAME,
                      value: abstractResultTypeName,
                    },
                  },
                  selectionSet: finalSelectionSet,
                },
              ],
            };
          }
          finalSelectionSet = {
            kind: Kind.SELECTION_SET,
            selections: [
              {
                // we create a wrapping AST Field
                kind: Kind.FIELD,
                name: {
                  kind: Kind.NAME,
                  value: pathElem,
                },
                // Inside the field selection
                selectionSet: finalSelectionSet,
              },
            ],
          };
          isLastResult = false;
        }
      }
      return finalSelectionSet;
    };
  }
  return undefined;
}
Example #19
Source File: get-mesh.ts    From graphql-mesh with MIT License 4 votes vote down vote up
export async function getMesh<TMeshContext = any>(options: GetMeshOptions): Promise<MeshInstance<TMeshContext>> {
  const rawSources: RawSourceOutput[] = [];
  const {
    pubsub = new PubSub(),
    cache,
    logger = new DefaultLogger('?️  Mesh'),
    additionalEnvelopPlugins = [],
    sources,
    merger,
    additionalResolvers,
    additionalTypeDefs,
    transforms,
  } = options;

  const getMeshLogger = logger.child('GetMesh');
  getMeshLogger.debug(`Getting subschemas from source handlers`);
  let failed = false;
  await Promise.allSettled(
    sources.map(async apiSource => {
      const apiName = apiSource.name;
      const sourceLogger = logger.child(apiName);
      sourceLogger.debug(`Generating the schema`);
      try {
        const source = await apiSource.handler.getMeshSource();
        sourceLogger.debug(`The schema has been generated successfully`);

        let apiSchema = source.schema;

        sourceLogger.debug(`Analyzing transforms`);

        let transforms: MeshTransform[];

        const { wrapTransforms, noWrapTransforms } = groupTransforms(apiSource.transforms);

        if (!wrapTransforms?.length && noWrapTransforms?.length) {
          sourceLogger.debug(`${noWrapTransforms.length} bare transforms found and applying`);
          apiSchema = applySchemaTransforms(apiSchema, source as SubschemaConfig, null, noWrapTransforms);
        } else {
          transforms = apiSource.transforms;
        }

        rawSources.push({
          name: apiName,
          schema: apiSchema,
          executor: source.executor,
          transforms,
          contextVariables: source.contextVariables || [],
          handler: apiSource.handler,
          batch: 'batch' in source ? source.batch : true,
          merge: apiSource.merge,
        });
      } catch (e: any) {
        sourceLogger.error(`Failed to generate the schema`, e);
        failed = true;
      }
    })
  );

  if (failed) {
    throw new Error(
      `Schemas couldn't be generated successfully. Check for the logs by running Mesh with DEBUG=1 environmental variable to get more verbose output.`
    );
  }

  getMeshLogger.debug(`Schemas have been generated by the source handlers`);

  getMeshLogger.debug(`Merging schemas using the defined merging strategy.`);
  const unifiedSchema = await merger.getUnifiedSchema({
    rawSources,
    typeDefs: additionalTypeDefs,
    resolvers: additionalResolvers,
    transforms,
  });

  getMeshLogger.debug(`Building Mesh Context`);
  const meshContext: Record<string, any> = {
    pubsub,
    cache,
    logger,
    [MESH_CONTEXT_SYMBOL]: true,
  };
  getMeshLogger.debug(`Attaching in-context SDK, pubsub and cache to the context`);
  const sourceMap = unifiedSchema.extensions.sourceMap as Map<RawSourceOutput, GraphQLSchema>;
  await Promise.all(
    rawSources.map(async rawSource => {
      const rawSourceLogger = logger.child(`${rawSource.name}`);

      const rawSourceContext: any = {
        rawSource,
        [MESH_API_CONTEXT_SYMBOL]: true,
      };
      // TODO: Somehow rawSource reference got lost in somewhere
      let rawSourceSubSchemaConfig: SubschemaConfig;
      const stitchingInfo = unifiedSchema.extensions.stitchingInfo as StitchingInfo;
      if (stitchingInfo) {
        for (const [subschemaConfig, subschema] of stitchingInfo.subschemaMap) {
          if ((subschemaConfig as any).name === rawSource.name) {
            rawSourceSubSchemaConfig = subschema;
            break;
          }
        }
      } else {
        rawSourceSubSchemaConfig = rawSource;
      }
      const transformedSchema = sourceMap.get(rawSource);
      const rootTypes: Record<OperationTypeNode, GraphQLObjectType> = {
        query: transformedSchema.getQueryType(),
        mutation: transformedSchema.getMutationType(),
        subscription: transformedSchema.getSubscriptionType(),
      };

      rawSourceLogger.debug(`Generating In Context SDK`);
      for (const operationType in rootTypes) {
        const rootType: GraphQLObjectType = rootTypes[operationType];
        if (rootType) {
          rawSourceContext[rootType.name] = {};
          const rootTypeFieldMap = rootType.getFields();
          for (const fieldName in rootTypeFieldMap) {
            const rootTypeField = rootTypeFieldMap[fieldName];
            const inContextSdkLogger = rawSourceLogger.child(`InContextSDK.${rootType.name}.${fieldName}`);
            const namedReturnType = getNamedType(rootTypeField.type);
            const shouldHaveSelectionSet = !isLeafType(namedReturnType);
            rawSourceContext[rootType.name][fieldName] = ({
              root,
              args,
              context,
              info = {
                fieldName,
                fieldNodes: [],
                returnType: namedReturnType,
                parentType: rootType,
                path: {
                  typename: rootType.name,
                  key: fieldName,
                  prev: undefined,
                },
                schema: transformedSchema,
                fragments: {},
                rootValue: root,
                operation: {
                  kind: Kind.OPERATION_DEFINITION,
                  operation: operationType as OperationTypeNode,
                  selectionSet: {
                    kind: Kind.SELECTION_SET,
                    selections: [],
                  },
                },
                variableValues: {},
                cacheControl: {
                  setCacheHint: () => {},
                  cacheHint: {},
                },
              },
              selectionSet,
              key,
              argsFromKeys,
              valuesFromResults,
            }: {
              root: any;
              args: any;
              context: any;
              info: GraphQLResolveInfo;
              selectionSet: SelectionSetParamOrFactory;
              key?: string;
              argsFromKeys?: (keys: string[]) => any;
              valuesFromResults?: (result: any, keys?: string[]) => any;
            }) => {
              inContextSdkLogger.debug(`Called with`, {
                args,
                key,
              });
              const commonDelegateOptions: IDelegateToSchemaOptions = {
                schema: rawSourceSubSchemaConfig,
                rootValue: root,
                operation: operationType as OperationTypeNode,
                fieldName,
                context,
                transformedSchema,
                info,
              };
              // If there isn't an extraction of a value
              if (typeof selectionSet !== 'function') {
                commonDelegateOptions.returnType = rootTypeField.type;
              }
              if (shouldHaveSelectionSet) {
                let selectionCount = 0;
                for (const fieldNode of info.fieldNodes) {
                  if (fieldNode.selectionSet != null) {
                    selectionCount += fieldNode.selectionSet.selections.length;
                  }
                }
                if (selectionCount === 0) {
                  if (!selectionSet) {
                    throw new Error(
                      `You have to provide 'selectionSet' for context.${rawSource.name}.${rootType.name}.${fieldName}`
                    );
                  }
                  commonDelegateOptions.info = {
                    ...info,
                    fieldNodes: [
                      {
                        ...info.fieldNodes[0],
                        selectionSet: {
                          kind: Kind.SELECTION_SET,
                          selections: [
                            {
                              kind: Kind.FIELD,
                              name: {
                                kind: Kind.NAME,
                                value: '__typename',
                              },
                            },
                          ],
                        },
                      },
                      ...info.fieldNodes.slice(1),
                    ],
                  };
                }
              }
              if (key && argsFromKeys) {
                const batchDelegationOptions = {
                  ...commonDelegateOptions,
                  key,
                  argsFromKeys,
                  valuesFromResults,
                } as unknown as BatchDelegateOptions;
                if (selectionSet) {
                  const selectionSetFactory = normalizeSelectionSetParamOrFactory(selectionSet);
                  const path = [fieldName];
                  const wrapQueryTransform = new WrapQuery(path, selectionSetFactory, identical);
                  batchDelegationOptions.transforms = [wrapQueryTransform as any];
                }
                return batchDelegateToSchema(batchDelegationOptions);
              } else {
                const regularDelegateOptions: IDelegateToSchemaOptions = {
                  ...commonDelegateOptions,
                  args,
                };
                if (selectionSet) {
                  const selectionSetFactory = normalizeSelectionSetParamOrFactory(selectionSet);
                  const path = [fieldName];
                  const wrapQueryTransform = new WrapQuery(path, selectionSetFactory, valuesFromResults || identical);
                  regularDelegateOptions.transforms = [wrapQueryTransform as any];
                }
                return delegateToSchema(regularDelegateOptions);
              }
            };
          }
        }
      }
      meshContext[rawSource.name] = rawSourceContext;
    })
  );

  const plugins: EnvelopPlugins = [
    useSchema(unifiedSchema),
    useExtendContext(() => meshContext),
    useErrorHandler(errors => {
      errors.forEach(error => logger.error(error.stack || error.message));
    }),
    {
      onParse({ setParseFn }) {
        setParseFn(parseWithCache);
      },
    },
    ...additionalEnvelopPlugins,
  ];

  const EMPTY_ROOT_VALUE: any = {};
  const EMPTY_CONTEXT_VALUE: any = {};
  const EMPTY_VARIABLES_VALUE: any = {};

  async function meshExecute<TVariables = any, TContext = any, TRootValue = any, TData = any>(
    documentOrSDL: GraphQLOperation<TData, TVariables>,
    variableValues: TVariables = EMPTY_VARIABLES_VALUE,
    contextValue: TContext = EMPTY_CONTEXT_VALUE,
    rootValue: TRootValue = EMPTY_ROOT_VALUE,
    operationName?: string
  ) {
    const getEnveloped = memoizedGetEnvelopedFactory(plugins);
    const { execute, contextFactory, parse } = getEnveloped(contextValue);

    return execute({
      document: typeof documentOrSDL === 'string' ? parse(documentOrSDL) : documentOrSDL,
      contextValue: await contextFactory(),
      rootValue,
      variableValues: variableValues as any,
      schema: unifiedSchema,
      operationName,
    });
  }

  async function meshSubscribe<TVariables = any, TContext = any, TRootValue = any, TData = any>(
    documentOrSDL: GraphQLOperation<TData, TVariables>,
    variableValues: TVariables = EMPTY_VARIABLES_VALUE,
    contextValue: TContext = EMPTY_CONTEXT_VALUE,
    rootValue: TRootValue = EMPTY_ROOT_VALUE,
    operationName?: string
  ) {
    const getEnveloped = memoizedGetEnvelopedFactory(plugins);
    const { subscribe, contextFactory, parse } = getEnveloped(contextValue);

    return subscribe({
      document: typeof documentOrSDL === 'string' ? parse(documentOrSDL) : documentOrSDL,
      contextValue: await contextFactory(),
      rootValue,
      variableValues: variableValues as any,
      schema: unifiedSchema,
      operationName,
    });
  }

  function sdkRequesterFactory(globalContext: any) {
    return async function meshSdkRequester(document: DocumentNode, variables: any, contextValue: any) {
      if (memoizedGetOperationType(document) === 'subscription') {
        const result = await meshSubscribe(document, variables, {
          ...globalContext,
          ...contextValue,
        });
        if (isAsyncIterable(result)) {
          return mapAsyncIterator(result, result => {
            if (result?.errors?.length) {
              return new AggregateError(result.errors);
            }
            return result?.data;
          });
        }
        if (result?.errors?.length) {
          return new AggregateError(result.errors);
        }
        return result?.data;
      } else {
        const result = await meshExecute(document, variables, {
          ...globalContext,
          ...contextValue,
        });
        if (result?.errors?.length) {
          return new AggregateError(result.errors);
        }
        return result?.data;
      }
    };
  }

  return {
    execute: meshExecute,
    subscribe: meshSubscribe,
    schema: unifiedSchema,
    rawSources,
    cache,
    pubsub,
    destroy: () => pubsub.publish('destroy', undefined),
    logger,
    meshContext: meshContext as TMeshContext,
    plugins,
    get getEnveloped() {
      return memoizedGetEnvelopedFactory(plugins);
    },
    sdkRequesterFactory,
  };
}
Example #20
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 #21
Source File: addExecutionLogicToComposer.ts    From graphql-mesh with MIT License 4 votes vote down vote up
export async function addExecutionLogicToComposer(
  schemaComposer: SchemaComposer,
  {
    fetch: globalFetch,
    logger,
    operations,
    operationHeaders,
    baseUrl,
    pubsub: globalPubsub,
    queryParams,
  }: AddExecutionLogicToComposerOptions
) {
  logger.debug(`Attaching execution logic to the schema`);
  for (const operationConfig of operations) {
    const { httpMethod, rootTypeName, fieldName } = getOperationMetadata(operationConfig);
    const operationLogger = logger.child(`${rootTypeName}.${fieldName}`);

    const interpolationStrings: string[] = [
      ...Object.values(operationHeaders || {}),
      ...Object.values(queryParams || {}),
      baseUrl,
    ];

    const rootTypeComposer = schemaComposer[rootTypeName];

    const field = rootTypeComposer.getField(fieldName);

    if (isPubSubOperationConfig(operationConfig)) {
      field.description = operationConfig.description || `PubSub Topic: ${operationConfig.pubsubTopic}`;
      field.subscribe = (root, args, context, info) => {
        const pubsub = context?.pubsub || globalPubsub;
        if (!pubsub) {
          return new GraphQLError(`You should have PubSub defined in either the config or the context!`);
        }
        const interpolationData = { root, args, context, info, env: process.env };
        const pubsubTopic = stringInterpolator.parse(operationConfig.pubsubTopic, interpolationData);
        operationLogger.debug(`=> Subscribing to pubSubTopic: ${pubsubTopic}`);
        return pubsub.asyncIterator(pubsubTopic);
      };
      field.resolve = root => {
        operationLogger.debug('Received ', root, ' from ', operationConfig.pubsubTopic);
        return root;
      };
      interpolationStrings.push(operationConfig.pubsubTopic);
    } else if (operationConfig.path) {
      if (process.env.DEBUG) {
        field.description = `
    ***Original Description***: ${operationConfig.description || '(none)'}
    ***Method***: ${operationConfig.method}
    ***Base URL***: ${baseUrl}
    ***Path***: ${operationConfig.path}
`;
      } else {
        field.description = operationConfig.description;
      }
      field.resolve = async (root, args, context) => {
        operationLogger.debug(`=> Resolving`);
        const interpolationData = { root, args, context, env: process.env };
        const interpolatedBaseUrl = stringInterpolator.parse(baseUrl, interpolationData);
        const interpolatedPath = stringInterpolator.parse(operationConfig.path, interpolationData);
        let fullPath = urlJoin(interpolatedBaseUrl, interpolatedPath);
        const headers = {
          ...operationHeaders,
          ...operationConfig?.headers,
        };
        for (const headerName in headers) {
          headers[headerName] = stringInterpolator.parse(headers[headerName], interpolationData);
        }
        const requestInit: RequestInit = {
          method: httpMethod,
          headers,
        };
        if (queryParams) {
          const interpolatedQueryParams: Record<string, any> = {};
          for (const queryParamName in queryParams) {
            interpolatedQueryParams[queryParamName] = stringInterpolator.parse(
              queryParams[queryParamName],
              interpolationData
            );
          }
          const queryParamsString = qsStringify(interpolatedQueryParams, { indices: false });
          fullPath += fullPath.includes('?') ? '&' : '?';
          fullPath += queryParamsString;
        }
        // Handle binary data
        if ('binary' in operationConfig) {
          const binaryUpload = await args.input;
          if (isFileUpload(binaryUpload)) {
            const readable = binaryUpload.createReadStream();
            const chunks: number[] = [];
            for await (const chunk of readable) {
              for (const byte of chunk) {
                chunks.push(byte);
              }
            }
            requestInit.body = new Uint8Array(chunks);

            const [, contentType] = Object.entries(headers).find(([key]) => key.toLowerCase() === 'content-type') || [];
            if (!contentType) {
              headers['content-type'] = binaryUpload.mimetype;
            }
          }
          requestInit.body = binaryUpload;
        } else {
          if (operationConfig.requestBaseBody != null) {
            args.input = args.input || {};
            for (const key in operationConfig.requestBaseBody) {
              const configValue = operationConfig.requestBaseBody[key];
              if (typeof configValue === 'string') {
                const value = stringInterpolator.parse(configValue, interpolationData);
                lodashSet(args.input, key, value);
              } else {
                args.input[key] = configValue;
              }
            }
          }
          // Resolve union input
          const input = (args.input = resolveDataByUnionInputType(
            cleanObject(args.input),
            field.args?.input?.type?.getType(),
            schemaComposer
          ));
          if (input != null) {
            switch (httpMethod) {
              case 'GET':
              case 'HEAD':
              case 'CONNECT':
              case 'OPTIONS':
              case 'TRACE': {
                fullPath += fullPath.includes('?') ? '&' : '?';
                fullPath += qsStringify(input, { indices: false });
                break;
              }
              case 'POST':
              case 'PUT':
              case 'PATCH':
              case 'DELETE': {
                const [, contentType] =
                  Object.entries(headers).find(([key]) => key.toLowerCase() === 'content-type') || [];
                if (contentType?.startsWith('application/x-www-form-urlencoded')) {
                  requestInit.body = qsStringify(input, { indices: false });
                } else {
                  requestInit.body = JSON.stringify(input);
                }
                break;
              }
              default:
                return createError(`Unknown HTTP Method: ${httpMethod}`, {
                  url: fullPath,
                  method: httpMethod,
                });
            }
          }
        }

        // Delete unused queryparams
        const [actualPath, queryString] = fullPath.split('?');
        if (queryString) {
          const queryParams = qsParse(queryString);
          const cleanedQueryParams = cleanObject(queryParams);
          fullPath = actualPath + '?' + qsStringify(cleanedQueryParams, { indices: false });
        }

        operationLogger.debug(`=> Fetching `, fullPath, `=>`, requestInit);
        const fetch: typeof globalFetch = context?.fetch || globalFetch;
        if (!fetch) {
          return createError(`You should have fetch defined in either the config or the context!`, {
            url: fullPath,
            method: httpMethod,
          });
        }
        const response = await fetch(fullPath, requestInit);
        // If return type is a file
        if (field.type.getTypeName() === 'File') {
          return response.blob();
        }
        const responseText = await response.text();
        operationLogger.debug(`=> Received`, {
          headers: response.headers,
          text: responseText,
        });
        let responseJson: any;
        try {
          responseJson = JSON.parse(responseText);
        } catch (error) {
          const returnNamedGraphQLType = getNamedType(field.type.getType());
          // The result might be defined as scalar
          if (isScalarType(returnNamedGraphQLType)) {
            operationLogger.debug(` => Return type is not a JSON so returning ${responseText}`);
            return responseText;
          } else if (response.status === 204) {
            responseJson = {};
          } else {
            return createError(`Unexpected response`, {
              url: fullPath,
              method: httpMethod,
              responseText,
              error,
            });
          }
        }

        if (!response.status.toString().startsWith('2')) {
          const returnNamedGraphQLType = getNamedType(field.type.getType());
          if (!isUnionType(returnNamedGraphQLType)) {
            return createError(`HTTP Error: ${response.status}`, {
              url: fullPath,
              method: httpMethod,
              ...(response.statusText ? { status: response.statusText } : {}),
              responseJson,
            });
          }
        }

        operationLogger.debug(`Returning `, responseJson);
        // Sometimes API returns an array but the return type is not an array
        const isListReturnType = isListTypeOrNonNullListType(field.type.getType());
        const isArrayResponse = Array.isArray(responseJson);
        if (isListReturnType && !isArrayResponse) {
          operationLogger.debug(`Response is not array but return type is list. Normalizing the response`);
          responseJson = [responseJson];
        }
        if (!isListReturnType && isArrayResponse) {
          operationLogger.debug(`Response is array but return type is not list. Normalizing the response`);
          responseJson = responseJson[0];
        }

        const addResponseMetadata = (obj: any) => {
          return {
            ...obj,
            $url: fullPath,
            $method: httpMethod,
            $request: {
              query: {
                ...obj,
                ...args,
                ...args.input,
              },
              path: {
                ...obj,
                ...args,
              },
              header: requestInit.headers,
            },
            $response: {
              url: fullPath,
              method: httpMethod,
              status: response.status,
              statusText: response.statusText,
              body: obj,
            },
          };
        };
        operationLogger.debug(`Adding response metadata to the response object`);
        return Array.isArray(responseJson)
          ? responseJson.map(obj => addResponseMetadata(obj))
          : addResponseMetadata(responseJson);
      };
      interpolationStrings.push(...Object.values(operationConfig.headers || {}));
      interpolationStrings.push(operationConfig.path);

      if ('links' in operationConfig) {
        for (const linkName in operationConfig.links) {
          const linkObj = operationConfig.links[linkName];
          const typeTC = schemaComposer.getOTC(field.type.getTypeName());
          typeTC.addFields({
            [linkName]: () => {
              const targetField = schemaComposer.Query.getField(linkObj.fieldName);
              return {
                ...targetField,
                args: {},
                description: linkObj.description || targetField.description,
                resolve: (root, args, context, info) =>
                  linkResolver(linkObj.args, targetField.resolve, root, args, context, info),
              };
            },
          });
        }
      } else if ('responseByStatusCode' in operationConfig) {
        const unionOrSingleTC = schemaComposer.getAnyTC(getNamedType(field.type.getType()));
        const types = 'getTypes' in unionOrSingleTC ? unionOrSingleTC.getTypes() : [unionOrSingleTC];
        const statusCodeOneOfIndexMap =
          (unionOrSingleTC.getExtension('statusCodeOneOfIndexMap') as Record<string, number>) || {};
        for (const statusCode in operationConfig.responseByStatusCode) {
          const responseConfig = operationConfig.responseByStatusCode[statusCode];
          for (const linkName in responseConfig.links) {
            const typeTCThunked = types[statusCodeOneOfIndexMap[statusCode] || 0];
            const typeTC = schemaComposer.getOTC(typeTCThunked.getTypeName());
            typeTC.addFields({
              [linkName]: () => {
                const linkObj = responseConfig.links[linkName];
                const targetField = schemaComposer.Query.getField(linkObj.fieldName);
                return {
                  ...targetField,
                  args: {},
                  description: linkObj.description || targetField.description,
                  resolve: (root, args, context, info) =>
                    linkResolver(linkObj.args, targetField.resolve, root, args, context, info),
                };
              },
            });
          }
        }
      }
    }
    const { args: globalArgs } = parseInterpolationStrings(interpolationStrings, operationConfig.argTypeMap);
    rootTypeComposer.addFieldArgs(fieldName, globalArgs);
  }

  logger.debug(`Building the executable schema.`);
  return schemaComposer;
}