Как я могу добавить / заменить / удалить тип из GraphQLSchema? - PullRequest
1 голос
/ 28 марта 2020

Я работаю над инструментом проверки схемы GraphQL. Я хотел бы обновить в памяти мой GraphQLSchema объект.

Например, чтобы заменить тип, который я пытался сделать:

const replaceType = (schema: GraphQLSchema, oldType: GraphQLNamedType, newType: GraphQLNamedType) => {
  const config = schema.toConfig();
  config.types = config.types.filter(t => t.name !== oldType.name);
  config.types.push(newType);
  return new GraphQLSchema(config);
}

Это, однако, не удается здесь с

Schema must contain uniquely named types but contains multiple types named "MyType".

at typeMapReducer (../../node_modules/graphql/type/schema.js:262:13)
at Array.reduce (<anonymous>)
at new GraphQLSchema (../../node_modules/graphql/type/schema.js:145:28)

Похоже, существуют существующие ссылки на старый тип, который я не обновляю,

1 Ответ

1 голос
/ 28 марта 2020

Если это полезно для кого-то другого, следующее обновление ссылок на типы работает

const replaceType = (schema: GraphQLSchema, oldType: GraphQLNamedType, newType: GraphQLNamedType) => {
  const config = schema.toConfig();
  config.types = config.types.filter(t => t.name !== oldType.name);
  config.types.push(newType);
  makeConfigConsistent(config);
  return new GraphQLSchema(config);
}


/**
 * As we add types that originally come from a different schema, we need to update all the references to maintain consistency
 * within the set of types we are including.
 *
 * Types from the original schema need to update their references to point to the new types,
 * and types from the new schema need to update their references to point to the original types that were not replaces.
 */
const makeConfigConsistent = (config: SchemaConfig) => {
  const typeMap: { [typeName: string]: GraphQLNamedType } = {};

  // Update references for root types
  config.query = null;
  config.mutation = null;
  config.subscription = null;
  config.types.forEach(type => {
    typeMap[type.name] = type;
    if (isObjectType(type)) {
      if (type.name === 'Query') {
        config.query = type;
      } else if (type.name === 'Mutation') {
        config.mutation = type;
      } else if (type.name === 'Subscription') {
        config.subscription = type;
      }
    }
  });

  // Update references to only point to the final set of types.
  const finalTypes = config.types;
  if (config.query) {
    finalTypes.push(config.query);
  }
  if (config.mutation) {
    finalTypes.push(config.mutation);
  }
  if (config.subscription) {
    finalTypes.push(config.subscription);
  }

  const updatedType = (type: any): any | undefined => {
    if (isNamedType(type)) {
      if (type === typeMap[type.name]) {
        return type;
      }
    }
    if (isListType(type)) {
      const subType = updatedType(type.ofType);
      if (!subType) {
        return undefined;
      }
      return new GraphQLList(subType);
    }
    if (isNonNullType(type)) {
      const subType = updatedType(type.ofType);
      if (!subType) {
        return undefined;
      }
      return new GraphQLNonNull(subType);
    }
    if (isScalarType(type)) {
      if (type === typeMap[type.name]) {
        return type;
      }
      if (['Int', 'String', 'Float', 'Boolean', 'ID'].includes(type.name)) {
        // This is a default scalar type (https://graphql.org/learn/schema/#scalar-types)
        return type;
      }
    }

    if (isNamedType(type)) {
      const result = typeMap[type.name];
      if (!result) {
        return undefined;
      }
      return result;
    }
    throw new Error(`Unhandled cases for ${type}`);
  };

  finalTypes.forEach(type => {
    if (isObjectType(type) || isInterfaceType(type)) {
      const anyType = type as any;
      anyType._fields = arraytoDict(
        Object.values(type.getFields())
          .filter(field => updatedType(field.type) !== undefined)
          .map(field => {
            field.type = updatedType(field.type);
            field.args = field.args
              .filter(arg => updatedType(arg.type) !== undefined)
              .map(arg => {
                arg.type = updatedType(arg.type);
                return arg;
              });
            return field;
          }),
        field => field.name,
      );
      if (isObjectType(type)) {
        anyType._interfaces = type.getInterfaces().map(int => updatedType(int));
      }
    } else if (isInputObjectType(type)) {
      const anyType = type as any;
      anyType._fields = arraytoDict(
        Object.values(type.getFields())
          .filter(field => updatedType(field.type) !== undefined)
          .map(field => {
            field.type = updatedType(field.type);
            return field;
          }),
        field => field.name,
      );
    } else if (isUnionType(type)) {
      const anyType = type as any;
      anyType._types = type
        .getTypes()
        .map(t => updatedType(t))
        .filter(t => t !== undefined);
    }
  });
};

function arraytoDict<T>(array: T[], getKey: (element: T) => string): { [key: string]: T } {
  const result: { [key: string]: T } = {};
  array.forEach(element => {
    result[getKey(element)] = element;
  });
  return result;
};
...