Skip to content

Commit

Permalink
Move transformSchema into separate file (#365)
Browse files Browse the repository at this point in the history
  • Loading branch information
IvanGoncharov authored Sep 7, 2023
1 parent 9423d67 commit 6a1a604
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 185 deletions.
187 changes: 2 additions & 185 deletions src/introspection/introspection.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import {
assertNamedType,
getNamedType,
getNullableType,
GraphQLDirective,
GraphQLEnumType,
GraphQLFieldConfig,
GraphQLFieldConfigArgumentMap,
GraphQLFieldConfigMap,
GraphQLInputFieldConfig,
GraphQLInputFieldConfigMap,
GraphQLInputObjectType,
GraphQLInterfaceType,
GraphQLList,
Expand All @@ -17,23 +12,16 @@ import {
GraphQLObjectType,
GraphQLOutputType,
GraphQLSchema,
GraphQLType,
GraphQLUnionType,
isEnumType,
isInputObjectType,
isInterfaceType,
isIntrospectionType,
isListType,
isNonNullType,
isObjectType,
isScalarType,
isSpecifiedScalarType,
isUnionType,
lexicographicSortSchema,
} from 'graphql';

import { collectDirectlyReferencedTypes } from '../utils/collect-referenced-types';
import { unreachable } from '../utils/unreachable';
import { mapValues } from '../utils/mapValues';
import { transformSchema } from '../utils/transformSchema';

declare module 'graphql' {
interface GraphQLFieldExtensions<_TSource, _TContext, _TArgs> {
Expand Down Expand Up @@ -282,174 +270,3 @@ export function getSchema(

return transformSchema(schema, typeTransformers);
}

// FIXME: Contribute to graphql-js
export function transformSchema(
schema: GraphQLSchema,
transformType: ReadonlyArray<
(type: GraphQLNamedType) => GraphQLNamedType | null
>,
transformDirective: ReadonlyArray<
(directive: GraphQLDirective) => GraphQLDirective
> = [],
): GraphQLSchema {
const schemaConfig = schema.toConfig();

const typeMap = new Map<string, GraphQLNamedType>();
for (const oldType of schemaConfig.types) {
const newType = transformNamedType(oldType);
if (newType != null) {
typeMap.set(newType.name, newType);
}
}

const directives = [];
for (const oldDirective of schemaConfig.directives) {
let newDirective = oldDirective;
for (const fn of transformDirective) {
if (newDirective === null) {
continue;
}
newDirective = fn(newDirective);
}

if (newDirective == null) {
continue;
}
directives.push(replaceDirective(newDirective));
}

return new GraphQLSchema({
...schemaConfig,
types: Array.from(typeMap.values()),
directives,
query: replaceMaybeType(schemaConfig.query),
mutation: replaceMaybeType(schemaConfig.mutation),
subscription: replaceMaybeType(schemaConfig.subscription),
});

function replaceType<T extends GraphQLType>(type: T): T {
if (isListType(type)) {
// @ts-expect-error Type mismatch
return new GraphQLList(replaceType(type.ofType));
} else if (isNonNullType(type)) {
// @ts-expect-error Type mismatch
return new GraphQLNonNull(replaceType(type.ofType));
}
// @ts-expect-error Type mismatch
return replaceNamedType<GraphQLNamedType>(type);
}

function replaceMaybeType<T extends GraphQLNamedType>(
maybeType: T | null | undefined,
): T | null | undefined {
return maybeType && (replaceNamedType(maybeType) as T);
}

function replaceTypes<T extends GraphQLNamedType>(
array: ReadonlyArray<T>,
): Array<T> {
return array.map(replaceNamedType) as Array<T>;
}

function replaceNamedType(type: GraphQLNamedType): GraphQLNamedType {
return assertNamedType(typeMap.get(type.name));
}

function replaceDirective(directive: GraphQLDirective) {
const config = directive.toConfig();
return new GraphQLDirective({
...config,
args: transformArgs(config.args),
});
}

function transformArgs(args: GraphQLFieldConfigArgumentMap) {
return mapValues(args, (arg) => ({
...arg,
type: replaceType(arg.type),
}));
}

function transformFields(fieldsMap: GraphQLFieldConfigMap<unknown, unknown>) {
return mapValues(fieldsMap, (field) => ({
...field,
type: replaceType(field.type),
args: field.args && transformArgs(field.args),
}));
}

function transformInputFields(fieldsMap: GraphQLInputFieldConfigMap) {
return mapValues(fieldsMap, (field) => ({
...field,
type: replaceType(field.type),
}));
}

function transformNamedType(
oldType: GraphQLNamedType,
): GraphQLNamedType | null {
if (isIntrospectionType(oldType) || isSpecifiedScalarType(oldType)) {
return oldType;
}

let newType = oldType;
for (const fn of transformType) {
const resultType = fn(newType);
if (resultType === null) {
return null;
}
newType = resultType;
}

if (isScalarType(newType)) {
return newType;
}
if (isObjectType(newType)) {
const config = newType.toConfig();
return new GraphQLObjectType({
...config,
interfaces: () => replaceTypes(config.interfaces),
fields: () => transformFields(config.fields),
});
}
if (isInterfaceType(newType)) {
const config = newType.toConfig();
return new GraphQLInterfaceType({
...config,
interfaces: () => replaceTypes(config.interfaces),
fields: () => transformFields(config.fields),
});
}
if (isUnionType(newType)) {
const config = newType.toConfig();
return new GraphQLUnionType({
...config,
types: () => replaceTypes(config.types),
});
}
if (isEnumType(newType)) {
const config = newType.toConfig();
return new GraphQLEnumType({ ...config });
}
if (isInputObjectType(newType)) {
const config = newType.toConfig();
return new GraphQLInputObjectType({
...config,
fields: () => transformInputFields(config.fields),
});
}
unreachable(newType);
}
}

function mapValues<T, R>(
obj: { [key: string]: T },
mapper: (value: T, key: string) => R | null,
): { [key: string]: R } {
return Object.fromEntries(
Object.entries(obj)
.map(([key, value]) => [key, mapper(value, key)])
.filter(([, value]) => value != null),
);
}
10 changes: 10 additions & 0 deletions src/utils/mapValues.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function mapValues<T, R>(
obj: { [key: string]: T },
mapper: (value: T, key: string) => R | null,
): { [key: string]: R } {
return Object.fromEntries(
Object.entries(obj)
.map(([key, value]) => [key, mapper(value, key)])
.filter(([, value]) => value != null),
);
}
Loading

0 comments on commit 6a1a604

Please sign in to comment.