@apollo/subgraph
Version:
Apollo Subgraph Utilities
170 lines (145 loc) • 4.67 kB
text/typescript
import {
GraphQLSchema,
GraphQLNamedType,
isIntrospectionType,
isObjectType,
GraphQLObjectType,
GraphQLType,
isListType,
GraphQLList,
isNonNullType,
GraphQLNonNull,
GraphQLFieldConfigMap,
GraphQLFieldConfigArgumentMap,
isInterfaceType,
GraphQLInterfaceType,
isUnionType,
GraphQLUnionType,
isInputObjectType,
GraphQLInputObjectType,
GraphQLInputFieldConfigMap,
GraphQLDirective
} from "graphql";
type TypeTransformer = (
type: GraphQLNamedType
) => GraphQLNamedType | null | undefined;
function mapValues<T, U = T>(
object: Record<string, T>,
callback: (value: T) => U
): Record<string, U> {
const result: Record<string, U> = Object.create(null);
for (const [key, value] of Object.entries(object)) {
result[key] = callback(value);
}
return result;
}
export function transformSchema(
schema: GraphQLSchema,
transformType: TypeTransformer
): GraphQLSchema {
const typeMap: { [typeName: string]: GraphQLNamedType } = Object.create(null);
for (const oldType of Object.values(schema.getTypeMap())) {
if (isIntrospectionType(oldType)) continue;
const result = transformType(oldType);
// Returning `null` removes the type.
if (result === null) continue;
// Returning `undefined` keeps the old type.
const newType = result || oldType;
typeMap[newType.name] = recreateNamedType(newType);
}
const schemaConfig = schema.toConfig();
return new GraphQLSchema({
...schemaConfig,
types: Object.values(typeMap),
query: replaceMaybeType(schemaConfig.query),
mutation: replaceMaybeType(schemaConfig.mutation),
subscription: replaceMaybeType(schemaConfig.subscription),
directives: replaceDirectives(schemaConfig.directives)
});
function recreateNamedType(type: GraphQLNamedType): GraphQLNamedType {
if (isObjectType(type)) {
const config = type.toConfig();
return new GraphQLObjectType({
...config,
interfaces: () => config.interfaces.map(replaceNamedType),
fields: () => replaceFields(config.fields)
});
} else if (isInterfaceType(type)) {
const config = type.toConfig();
return new GraphQLInterfaceType({
...config,
interfaces: () => config.interfaces.map(replaceNamedType),
fields: () => replaceFields(config.fields)
});
} else if (isUnionType(type)) {
const config = type.toConfig();
return new GraphQLUnionType({
...config,
types: () => config.types.map(replaceNamedType)
});
} else if (isInputObjectType(type)) {
const config = type.toConfig();
return new GraphQLInputObjectType({
...config,
fields: () => replaceInputFields(config.fields)
});
}
return type;
}
function replaceType<T extends GraphQLType>(
type: GraphQLList<T>
): GraphQLList<T>;
function replaceType<T extends GraphQLType>(
type: GraphQLNonNull<T>
): GraphQLNonNull<T>;
function replaceType<T extends GraphQLType>(type: T): T;
function replaceType(type: GraphQLType): GraphQLType {
if (isListType(type)) {
return new GraphQLList(replaceType(type.ofType));
} else if (isNonNullType(type)) {
return new GraphQLNonNull(replaceType(type.ofType));
}
return replaceNamedType(type);
}
function replaceNamedType<T extends GraphQLNamedType>(type: T): T {
const newType = typeMap[type.name] as T;
return newType ? newType : type;
}
function replaceMaybeType<T extends GraphQLNamedType>(
type: T | null | undefined
): T | undefined {
return type ? replaceNamedType(type) : undefined;
}
function replaceFields<TSource, TContext>(
fieldsMap: GraphQLFieldConfigMap<TSource, TContext>
): GraphQLFieldConfigMap<TSource, TContext> {
return mapValues(fieldsMap, field => ({
...field,
type: replaceType(field.type),
args: field.args ? replaceArgs(field.args) : undefined
}));
}
function replaceInputFields(
fieldsMap: GraphQLInputFieldConfigMap
): GraphQLInputFieldConfigMap {
return mapValues(fieldsMap, field => ({
...field,
type: replaceType(field.type)
}));
}
function replaceArgs(args: GraphQLFieldConfigArgumentMap): GraphQLFieldConfigArgumentMap {
return mapValues(args, (arg) => ({
...arg,
type: replaceType(arg.type),
}));
}
function replaceDirectives(directives: readonly GraphQLDirective[]): readonly GraphQLDirective[] {
return directives.map((directive) => {
const config = directive.toConfig();
return new GraphQLDirective({
...config,
args: replaceArgs(config.args),
});
});
}
}