graphql#GraphQLFieldConfig TypeScript Examples

The following examples show how to use graphql#GraphQLFieldConfig. 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: schema-resolver.ts    From graphql-mesh with MIT License 6 votes vote down vote up
createSoapServiceField(service: SoapService, rootType: RootType): GraphQLFieldConfig<any, any> {
    const fieldsThunk = (): GraphQLFieldConfigMap<any, any> => {
      const fields: GraphQLFieldConfigMap<any, any> = {};

      service.ports().forEach((port: SoapPort) => {
        if (this.options.includePorts) {
          fields[port.name()] = this.createSoapPortField(service, port, rootType);
        } else {
          port.operations().forEach((operation: SoapOperation) => {
            const fieldConfig = this.createSoapOperationField(operation, rootType);
            if (fieldConfig) {
              fields[operation.name()] = fieldConfig;
            }
          });
        }
      });

      return fields;
    };

    const returnType = new GraphQLObjectType({
      name: service.name() + 'Service' + (rootType === 'query' ? 'Query' : ''),
      description: `Service ${service.name()}`,
      fields: fieldsThunk,
    });

    return {
      type: returnType,
      description: `Service ${service.name()}`,
      resolve: () => {
        return {};
      },
    };
  }
Example #2
Source File: schema-resolver.ts    From graphql-mesh with MIT License 6 votes vote down vote up
createSoapPortField(service: SoapService, port: SoapPort, rootType: RootType): GraphQLFieldConfig<any, any> {
    const fieldsThunk = (): GraphQLFieldConfigMap<any, any> => {
      const fields: GraphQLFieldConfigMap<any, any> = {};

      port.operations().forEach((operation: SoapOperation) => {
        const fieldConfig = this.createSoapOperationField(operation, rootType);
        if (fieldConfig) {
          fields[operation.name()] = fieldConfig;
        }
      });

      return fields;
    };

    const returnType = new GraphQLObjectType({
      name: port.name() + 'Port' + (rootType === 'query' ? 'Query' : ''),
      description: `Port ${port.name()}, service ${service.name()}`,
      fields: fieldsThunk,
    });

    return {
      type: returnType,
      description: `Port ${port.name()}, service ${service.name()}`,
      resolve: () => {
        return {};
      },
    };
  }
Example #3
Source File: schema-resolver.ts    From graphql-mesh with MIT License 6 votes vote down vote up
getFieldConfig(operation: SoapOperation): GraphQLFieldConfig<any, any> {
    const args: GraphQLFieldConfigArgumentMap = this.createSoapOperationFieldArgs(operation);
    const returnType: GraphQLOutputType = this.resolveSoapOperationReturnType(operation);
    const resolver: GraphQLFieldResolver<any, any, any> = this.createSoapOperationFieldResolver(operation);
    return {
      type: returnType,
      description: `Operation ${operation.name()}, port ${operation.port().name()}, service ${operation
        .service()
        .name()}`,
      args,
      resolve: resolver,
    };
  }
Example #4
Source File: schema-resolver.ts    From graphql-mesh with MIT License 6 votes vote down vote up
createSoapOperationField(operation: SoapOperation, rootType: RootType): GraphQLFieldConfig<any, any> {
    if (this.options.selectQueryOrMutationField?.length) {
      const selectionConfig = this.options.selectQueryOrMutationField.find(
        configElem =>
          configElem.service === operation.service().name() &&
          configElem.port === operation.port().name() &&
          configElem.operation === operation.name()
      );
      if (selectionConfig != null) {
        if (selectionConfig.type === rootType) {
          return this.getFieldConfig(operation);
        } else {
          return undefined;
        }
      }
    }

    if (this.options.selectQueryOperationsAuto) {
      if (
        operation.name().toLowerCase().startsWith('get') ||
        operation.name().toLowerCase().startsWith('find') ||
        operation.name().toLowerCase().startsWith('list') ||
        operation.name().toLowerCase().startsWith('query') ||
        operation.name().toLowerCase().startsWith('search')
      ) {
        if (rootType === 'query') {
          return this.getFieldConfig(operation);
        }
      } else {
        if (rootType === 'mutation') {
          return this.getFieldConfig(operation);
        }
      }
    } else if (rootType === 'mutation') {
      return this.getFieldConfig(operation);
    }
    return undefined;
  }
Example #5
Source File: barePrefix.ts    From graphql-mesh with MIT License 6 votes vote down vote up
transformSchema(schema: GraphQLSchema) {
    return mapSchema(schema, {
      [MapperKind.TYPE]: (type: GraphQLNamedType) => {
        if (this.includeTypes && !isSpecifiedScalarType(type)) {
          const currentName = type.name;
          if (!this.ignoreList.includes(currentName)) {
            return renameType(type, this.prefix + currentName);
          }
        }
        return undefined;
      },
      [MapperKind.ROOT_OBJECT]() {
        return undefined;
      },
      ...(this.includeRootOperations && {
        [MapperKind.COMPOSITE_FIELD]: (
          fieldConfig: GraphQLFieldConfig<any, any>,
          fieldName: string,
          typeName: string
        ) => {
          return !rootOperations.has(typeName) || // check we're in a root Type
            this.ignoreList.includes(typeName) || // check if type is to be ignored
            this.ignoreList.includes(`${typeName}.${fieldName}`) // check if field in type is to be ignored
            ? undefined // do not perform any change
            : [`${this.prefix}${fieldName}`, fieldConfig]; // apply prefix
        },
      }),
    });
  }
Example #6
Source File: bareRename.ts    From graphql-mesh with MIT License 6 votes vote down vote up
transformSchema(schema: GraphQLSchema) {
    return mapSchema(schema, {
      ...(this.typesMap.size && { [MapperKind.TYPE]: type => this.renameType(type) }),
      ...(this.typesMap.size && { [MapperKind.ROOT_OBJECT]: type => this.renameType(type) }),
      ...((this.fieldsMap.size || this.argsMap.size) && {
        [MapperKind.COMPOSITE_FIELD]: (
          fieldConfig: GraphQLFieldConfig<any, any>,
          fieldName: string,
          typeName: string
        ) => {
          const typeRules = this.fieldsMap.get(typeName);
          const fieldRules = this.argsMap.get(`${typeName}.${fieldName}`);
          const newFieldName = typeRules && this.matchInMap(typeRules, fieldName);
          const argsMap =
            fieldRules &&
            Array.from(fieldRules.entries()).reduce((acc, [orName, newName]) => ({ ...acc, [newName]: orName }), {});
          if (!newFieldName && !fieldRules) return undefined;

          // Rename rules for type might have been emptied by matchInMap, in which case we can cleanup
          if (!typeRules?.size) this.fieldsMap.delete(typeName);

          if (fieldRules && fieldConfig.args) {
            fieldConfig.args = Object.entries(fieldConfig.args).reduce(
              (args, [argName, argConfig]) => ({
                ...args,
                [this.matchInMap(fieldRules, argName) || argName]: argConfig,
              }),
              {}
            );
          }

          // Wrap resolve fn to handle mapping renamed field name and/or renamed arguments
          fieldConfig.resolve = defaultResolverComposer(fieldConfig.resolve, fieldName, argsMap);

          return [newFieldName || fieldName, fieldConfig];
        },
      }),
    });
  }
Example #7
Source File: index.ts    From graphql-mesh with MIT License 5 votes vote down vote up
transformSchema(schema: GraphQLSchema) {
    const additionalTypeDefs =
      this.typeDefs &&
      loadTypedefsSync(this.typeDefs, {
        cwd: this.baseDir,
        loaders: [new CodeFileLoader(), new GraphQLFileLoader()],
      });
    const baseSchema = additionalTypeDefs ? extendSchema(schema, additionalTypeDefs[0].document) : schema;

    const transformedSchema = mapSchema(baseSchema, {
      [MapperKind.COMPOSITE_FIELD]: (
        fieldConfig: GraphQLFieldConfig<any, any>,
        currentFieldName: string,
        typeName: string
      ) => {
        const fieldKey = `${typeName}.${currentFieldName}`;
        const newFieldConfig = this.replacementsMap.get(fieldKey);
        if (!newFieldConfig) {
          return undefined;
        }

        const fieldName = newFieldConfig.name || currentFieldName;
        const targetFieldName = newFieldConfig.field;
        const targetFieldConfig = selectObjectFields(
          baseSchema,
          newFieldConfig.type,
          fieldName => fieldName === targetFieldName
        )[targetFieldName];

        if (newFieldConfig.scope === 'config') {
          const targetResolver = targetFieldConfig.resolve;
          targetFieldConfig.resolve = newFieldConfig.composer(targetResolver);

          // replace the entire field config
          return [fieldName, targetFieldConfig];
        }

        // override field type with the target type requested
        fieldConfig.type = targetFieldConfig.type;

        // If renaming fields that don't have a custom resolver, we need to map response to original field name
        if (newFieldConfig.name && !fieldConfig.resolve) fieldConfig.resolve = source => source[currentFieldName];

        if (newFieldConfig.scope === 'hoistValue') {
          // implement value hoisting by wrapping a default composer that hoists the value from resolver result
          fieldConfig.resolve = defaultHoistFieldComposer(fieldConfig.resolve || defaultFieldResolver, targetFieldName);
        }

        // wrap user-defined composer to current field resolver or, if not preset, defaultFieldResolver
        fieldConfig.resolve = newFieldConfig.composer(fieldConfig.resolve || defaultFieldResolver);

        // avoid re-iterating over replacements that have already been applied
        this.replacementsMap.delete(fieldKey);

        return [fieldName, fieldConfig];
      },
    });

    return transformedSchema;
  }
Example #8
Source File: index.ts    From graphql-mesh with MIT License 4 votes vote down vote up
/**
 * Creates a GraphQL interface from the given OpenAPI Specification 3.0.x
 */
async function translateOpenAPIToGraphQL<TSource, TContext, TArgs>(
  oass: Oas3[],
  {
    strict,
    report,

    // Schema options
    operationIdFieldNames,
    fillEmptyResponses,
    addLimitArgument,
    idFormats,
    selectQueryOrMutationField,
    genericPayloadArgName,
    simpleNames,
    singularNames,
    includeHttpDetails,

    // Resolver options
    headers,
    qs,
    requestOptions,
    connectOptions,
    baseUrl,
    customResolvers,
    fetch,
    resolverMiddleware,
    pubsub,

    // Authentication options
    viewer,
    tokenJSONpath,
    sendOAuthTokenInQuery,

    // Logging options
    provideErrorExtensions,
    equivalentToMessages,
    logger,
  }: InternalOptions<TSource, TContext, TArgs>
): Promise<{ schema: GraphQLSchema; report: Report }> {
  const options = {
    strict,
    report,

    // Schema options
    operationIdFieldNames,
    fillEmptyResponses,
    addLimitArgument,
    idFormats,
    selectQueryOrMutationField,
    genericPayloadArgName,
    simpleNames,
    singularNames,
    includeHttpDetails,

    // Resolver options
    headers,
    qs,
    requestOptions,
    connectOptions,
    baseUrl,
    customResolvers,
    fetch,
    resolverMiddleware,
    pubsub,

    // Authentication options
    viewer,
    tokenJSONpath,
    sendOAuthTokenInQuery,

    // Logging options
    provideErrorExtensions,
    equivalentToMessages,

    logger,
  };

  const translationLogger = options.logger.child('translation');
  translationLogger.debug(`Options:`, options);

  /**
   * Extract information from the OASs and put it inside a data structure that
   * is easier for OpenAPI-to-GraphQL to use
   */
  const data: PreprocessingData<TSource, TContext, TArgs> = preprocessOas(oass, options);

  preliminaryChecks(options, data, translationLogger);

  // Query, Mutation, and Subscription fields
  let queryFields: { [fieldName: string]: GraphQLFieldConfig<any, any> } = {};
  let mutationFields: { [fieldName: string]: GraphQLFieldConfig<any, any> } = {};
  let subscriptionFields: {
    [fieldName: string]: GraphQLFieldConfig<any, any>;
  } = {};

  // Authenticated Query, Mutation, and Subscription fields
  let authQueryFields: {
    [fieldName: string]: {
      [securityRequirement: string]: GraphQLFieldConfig<any, any>;
    };
  } = {};
  let authMutationFields: {
    [fieldName: string]: {
      [securityRequirement: string]: GraphQLFieldConfig<any, any>;
    };
  } = {};
  let authSubscriptionFields: {
    [fieldName: string]: {
      [securityRequirement: string]: GraphQLFieldConfig<any, any>;
    };
  } = {};

  // Add Query and Mutation fields
  Object.entries(data.operations).forEach(([operationId, operation]) => {
    translationLogger.debug(`Process operation '${operation.operationString}'...`);

    const field = getFieldForOperation(
      operation,
      options.baseUrl,
      data,
      requestOptions,
      connectOptions,
      includeHttpDetails,
      pubsub,
      logger
    );

    const saneOperationId = Oas3Tools.sanitize(operationId, Oas3Tools.CaseStyle.camelCase);

    // Check if the operation should be added as a Query or Mutation
    if (operation.operationType === GraphQLOperationType.Query) {
      let fieldName = !singularNames
        ? Oas3Tools.uncapitalize(operation.responseDefinition.graphQLTypeName)
        : Oas3Tools.sanitize(Oas3Tools.inferResourceNameFromPath(operation.path), Oas3Tools.CaseStyle.camelCase);

      if (operation.inViewer) {
        for (const securityRequirement of operation.securityRequirements) {
          if (typeof authQueryFields[securityRequirement] !== 'object') {
            authQueryFields[securityRequirement] = {};
          }
          // Avoid overwriting fields that return the same data:
          if (
            fieldName in authQueryFields[securityRequirement] ||
            /**
             * If the option is set operationIdFieldNames, the fieldName is
             * forced to be the operationId
             */
            operationIdFieldNames
          ) {
            fieldName = Oas3Tools.storeSaneName(saneOperationId, operationId, data.saneMap, options.logger);
          }

          if (fieldName in authQueryFields[securityRequirement]) {
            handleWarning({
              mitigationType: MitigationTypes.DUPLICATE_FIELD_NAME,
              message:
                `Multiple operations have the same name ` +
                `'${fieldName}' and security requirement ` +
                `'${securityRequirement}'. GraphQL field names must be ` +
                `unique so only one can be added to the authentication ` +
                `viewer. Operation '${operation.operationString}' will be ignored.`,
              data,
              logger: translationLogger,
            });
          } else {
            authQueryFields[securityRequirement][fieldName] = field;
          }
        }
      } else {
        // Avoid overwriting fields that return the same data:
        if (
          fieldName in queryFields ||
          /**
           * If the option is set operationIdFieldNames, the fieldName is
           * forced to be the operationId
           */
          operationIdFieldNames
        ) {
          fieldName = Oas3Tools.storeSaneName(saneOperationId, operationId, data.saneMap, options.logger);
        }

        if (fieldName in queryFields) {
          handleWarning({
            mitigationType: MitigationTypes.DUPLICATE_FIELD_NAME,
            message:
              `Multiple operations have the same name ` +
              `'${fieldName}'. GraphQL field names must be ` +
              `unique so only one can be added to the Query object. ` +
              `Operation '${operation.operationString}' will be ignored.`,
            data,
            logger: translationLogger,
          });
        } else {
          queryFields[fieldName] = field;
        }
      }
    } else {
      let saneFieldName;

      if (!singularNames) {
        /**
         * Use operationId to avoid problems differentiating operations with the
         * same path but differnet methods
         */
        saneFieldName = Oas3Tools.storeSaneName(saneOperationId, operationId, data.saneMap, options.logger);
      } else {
        const fieldName = `${operation.method}${Oas3Tools.inferResourceNameFromPath(operation.path)}`;

        saneFieldName = Oas3Tools.storeSaneName(
          Oas3Tools.sanitize(fieldName, Oas3Tools.CaseStyle.camelCase),
          fieldName,
          data.saneMap,
          options.logger
        );
      }

      if (operation.inViewer) {
        for (const securityRequirement of operation.securityRequirements) {
          if (typeof authMutationFields[securityRequirement] !== 'object') {
            authMutationFields[securityRequirement] = {};
          }

          if (saneFieldName in authMutationFields[securityRequirement]) {
            handleWarning({
              mitigationType: MitigationTypes.DUPLICATE_FIELD_NAME,
              message:
                `Multiple operations have the same name ` +
                `'${saneFieldName}' and security requirement ` +
                `'${securityRequirement}'. GraphQL field names must be ` +
                `unique so only one can be added to the authentication ` +
                `viewer. Operation '${operation.operationString}' will be ignored.`,
              data,
              logger: translationLogger,
            });
          } else {
            authMutationFields[securityRequirement][saneFieldName] = field;
          }
        }
      } else {
        if (saneFieldName in mutationFields) {
          handleWarning({
            mitigationType: MitigationTypes.DUPLICATE_FIELD_NAME,
            message:
              `Multiple operations have the same name ` +
              `'${saneFieldName}'. GraphQL field names must be ` +
              `unique so only one can be added to the Mutation object. ` +
              `Operation '${operation.operationString}' will be ignored.`,
            data,
            logger: translationLogger,
          });
        } else {
          mutationFields[saneFieldName] = field;
        }
      }
    }
  });

  // Add Subscription fields
  Object.entries(data.callbackOperations).forEach(([operationId, operation]) => {
    translationLogger.debug(`Process operation '${operationId}'...`);

    const field = getFieldForOperation(
      operation,
      options.baseUrl,
      data,
      requestOptions,
      connectOptions,
      includeHttpDetails,
      pubsub,
      logger
    );

    const saneOperationId = Oas3Tools.sanitize(operationId, Oas3Tools.CaseStyle.camelCase);

    const saneFieldName = Oas3Tools.storeSaneName(saneOperationId, operationId, data.saneMap, options.logger);
    if (operation.inViewer) {
      for (const securityRequirement of operation.securityRequirements) {
        if (typeof authSubscriptionFields[securityRequirement] !== 'object') {
          authSubscriptionFields[securityRequirement] = {};
        }

        if (saneFieldName in authSubscriptionFields[securityRequirement]) {
          handleWarning({
            mitigationType: MitigationTypes.DUPLICATE_FIELD_NAME,
            message:
              `Multiple operations have the same name ` +
              `'${saneFieldName}' and security requirement ` +
              `'${securityRequirement}'. GraphQL field names must be ` +
              `unique so only one can be added to the authentication ` +
              `viewer. Operation '${operation.operationString}' will be ignored.`,
            data,
            logger: translationLogger,
          });
        } else {
          authSubscriptionFields[securityRequirement][saneFieldName] = field;
        }
      }
    } else {
      if (saneFieldName in subscriptionFields) {
        handleWarning({
          mitigationType: MitigationTypes.DUPLICATE_FIELD_NAME,
          message:
            `Multiple operations have the same name ` +
            `'${saneFieldName}'. GraphQL field names must be ` +
            `unique so only one can be added to the Mutation object. ` +
            `Operation '${operation.operationString}' will be ignored.`,
          data,
          logger: translationLogger,
        });
      } else {
        subscriptionFields[saneFieldName] = field;
      }
    }
  });

  // Sorting fields
  queryFields = sortObject(queryFields);
  mutationFields = sortObject(mutationFields);
  subscriptionFields = sortObject(subscriptionFields);
  authQueryFields = sortObject(authQueryFields);
  Object.keys(authQueryFields).forEach(key => {
    authQueryFields[key] = sortObject(authQueryFields[key]);
  });
  authMutationFields = sortObject(authMutationFields);
  Object.keys(authMutationFields).forEach(key => {
    authMutationFields[key] = sortObject(authMutationFields[key]);
  });
  authSubscriptionFields = sortObject(authSubscriptionFields);
  Object.keys(authSubscriptionFields).forEach(key => {
    authSubscriptionFields[key] = sortObject(authSubscriptionFields[key]);
  });

  // Count created Query, Mutation, and Subscription fields
  options.report.numQueriesCreated =
    Object.keys(queryFields).length +
    Object.keys(authQueryFields).reduce((sum, key) => {
      return (sum as any) + Object.keys(authQueryFields[key]).length;
    }, 0);

  options.report.numMutationsCreated =
    Object.keys(mutationFields).length +
    Object.keys(authMutationFields).reduce((sum, key) => {
      return (sum as any) + Object.keys(authMutationFields[key]).length;
    }, 0);

  options.report.numSubscriptionsCreated =
    Object.keys(subscriptionFields).length +
    Object.keys(authSubscriptionFields).reduce((sum, key) => {
      return (sum as any) + Object.keys(authSubscriptionFields[key]).length;
    }, 0);

  /**
   * Organize authenticated Query, Mutation, and Subscriptions fields into
   * viewer objects.
   */
  if (Object.keys(authQueryFields).length > 0) {
    Object.assign(
      queryFields,
      createAndLoadViewer(authQueryFields, GraphQLOperationType.Query, data, includeHttpDetails, options.logger)
    );
  }

  if (Object.keys(authMutationFields).length > 0) {
    Object.assign(
      mutationFields,
      createAndLoadViewer(authMutationFields, GraphQLOperationType.Mutation, data, includeHttpDetails, options.logger)
    );
  }

  if (Object.keys(authSubscriptionFields).length > 0) {
    Object.assign(
      subscriptionFields,
      createAndLoadViewer(
        authSubscriptionFields,
        GraphQLOperationType.Subscription,
        data,
        includeHttpDetails,
        options.logger
      )
    );
  }

  // Build up the schema
  const schemaConfig: GraphQLSchemaConfig = {
    query:
      Object.keys(queryFields).length > 0
        ? new GraphQLObjectType({
            name: 'Query',
            fields: queryFields,
          })
        : GraphQLTools.getEmptyObjectType('Query'), // A GraphQL schema must contain a Query object type
    mutation:
      Object.keys(mutationFields).length > 0
        ? new GraphQLObjectType({
            name: 'Mutation',
            fields: mutationFields,
          })
        : null,
    subscription:
      Object.keys(subscriptionFields).length > 0
        ? new GraphQLObjectType({
            name: 'Subscription',
            fields: subscriptionFields,
          })
        : null,
  };

  /**
   * Fill in yet undefined object types to avoid GraphQLSchema from breaking.
   *
   * The reason: once creating the schema, the 'fields' thunks will resolve and
   * if a field references an undefined object type, GraphQL will throw.
   */
  Object.entries(data.operations).forEach(([, operation]) => {
    if (typeof operation.responseDefinition.graphQLType === 'undefined') {
      operation.responseDefinition.graphQLType = GraphQLTools.getEmptyObjectType(
        operation.responseDefinition.graphQLTypeName
      );
    }
  });

  const schema = new GraphQLSchema(schemaConfig);

  return { schema, report: options.report };
}
Example #9
Source File: index.ts    From graphql-mesh with MIT License 4 votes vote down vote up
/**
 * Creates the field object for the given operation.
 */
function getFieldForOperation<TSource, TContext, TArgs>(
  operation: Operation,
  baseUrl: string,
  data: PreprocessingData<TSource, TContext, TArgs>,
  requestOptions: RequestOptions<TSource, TContext, TArgs>,
  connectOptions: ConnectOptions,
  includeHttpDetails: boolean,
  pubsub: MeshPubSub,
  logger: Logger
): GraphQLFieldConfig<TSource, TContext | SubscriptionContext, TArgs> {
  // Create GraphQL Type for response:
  const type = getGraphQLType({
    def: operation.responseDefinition,
    data,
    operation,
    includeHttpDetails,
    logger,
  }) as GraphQLOutputType;

  const payloadSchemaName = operation.payloadDefinition ? operation.payloadDefinition.graphQLInputObjectTypeName : null;

  const args: Args = getArgs({
    /**
     * Even though these arguments seems redundent because of the operation
     * argument, the function cannot be refactored because it is also used to
     * create arguments for links. The operation argument is really used to pass
     * data to other functions.
     */
    requestPayloadDef: operation.payloadDefinition,
    parameters: operation.parameters,
    operation,
    data,
    includeHttpDetails,
    logger,
  });

  // Get resolver and subscribe function for Subscription fields
  if (operation.operationType === GraphQLOperationType.Subscription) {
    const responseSchemaName = operation.responseDefinition ? operation.responseDefinition.graphQLTypeName : null;

    const resolve = getPublishResolver({
      operation,
      responseName: responseSchemaName,
      data,
      logger,
    });

    const subscribe = getSubscribe({
      operation,
      payloadName: payloadSchemaName,
      data,
      baseUrl,
      connectOptions,
      pubsub,
      logger,
    });

    return {
      type,
      resolve,
      subscribe,
      args,
      description: operation.description,
    };

    // Get resolver for Query and Mutation fields
  } else {
    const resolve = data.options.resolverMiddleware(
      () => ({
        operation,
        payloadName: payloadSchemaName,
        data,
        baseUrl,
        requestOptions,
        logger,
      }),
      getResolver
    );

    return {
      type,
      resolve,
      args,
      description: operation.description,
    };
  }
}