Skip to content

Commit 92e647c

Browse files
[RFC]Add 'toConfig' method (#1331)
`toConfig` is intended to help with schema transformation by minimizing the amount of code you need to write and maintain, e.g. [recreateType](https://github.com/apollographql/graphql-tools/blob/4e68230c10a9fd1c6cb601869aa3a1ff8f9f0b90/src/stitching/schemaRecreation.ts#L33) from `graphql-tools`.
1 parent 5b17d41 commit 92e647c

File tree

5 files changed

+313
-263
lines changed

5 files changed

+313
-263
lines changed

src/type/definition.js

Lines changed: 147 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import instanceOf from '../jsutils/instanceOf';
1414
import inspect from '../jsutils/inspect';
1515
import invariant from '../jsutils/invariant';
1616
import keyMap from '../jsutils/keyMap';
17+
import keyValMap from '../jsutils/keyValMap';
1718
import mapValue from '../jsutils/mapValue';
1819
import type { ObjMap } from '../jsutils/ObjMap';
1920
import { Kind } from '../language/kinds';
@@ -511,6 +512,10 @@ function resolveThunk<+T>(thunk: Thunk<T>): T {
511512
return typeof thunk === 'function' ? thunk() : thunk;
512513
}
513514

515+
function undefineIfEmpty<T>(arr: ?$ReadOnlyArray<T>): ?$ReadOnlyArray<T> {
516+
return arr && arr.length > 0 ? arr : undefined;
517+
}
518+
514519
/**
515520
* Scalar Type Definition
516521
*
@@ -551,7 +556,7 @@ export class GraphQLScalarType {
551556
this.parseValue = config.parseValue || (value => value);
552557
this.parseLiteral = config.parseLiteral || valueFromASTUntyped;
553558
this.astNode = config.astNode;
554-
this.extensionASTNodes = config.extensionASTNodes;
559+
this.extensionASTNodes = undefineIfEmpty(config.extensionASTNodes);
555560
invariant(typeof config.name === 'string', 'Must provide name.');
556561
invariant(
557562
typeof config.serialize === 'function',
@@ -569,6 +574,23 @@ export class GraphQLScalarType {
569574
}
570575
}
571576

577+
toConfig(): {|
578+
...GraphQLScalarTypeConfig<*, *>,
579+
parseValue: GraphQLScalarValueParser<*>,
580+
parseLiteral: GraphQLScalarLiteralParser<*>,
581+
extensionASTNodes: $ReadOnlyArray<ScalarTypeExtensionNode>,
582+
|} {
583+
return {
584+
name: this.name,
585+
description: this.description,
586+
serialize: this.serialize,
587+
parseValue: this.parseValue,
588+
parseLiteral: this.parseLiteral,
589+
astNode: this.astNode,
590+
extensionASTNodes: this.extensionASTNodes || [],
591+
};
592+
}
593+
572594
toString(): string {
573595
return this.name;
574596
}
@@ -649,7 +671,7 @@ export class GraphQLObjectType {
649671
this.name = config.name;
650672
this.description = config.description;
651673
this.astNode = config.astNode;
652-
this.extensionASTNodes = config.extensionASTNodes;
674+
this.extensionASTNodes = undefineIfEmpty(config.extensionASTNodes);
653675
this.isTypeOf = config.isTypeOf;
654676
this._fields = defineFieldMap.bind(undefined, config);
655677
this._interfaces = defineInterfaces.bind(undefined, config);
@@ -675,6 +697,23 @@ export class GraphQLObjectType {
675697
return this._interfaces;
676698
}
677699

700+
toConfig(): {|
701+
...GraphQLObjectTypeConfig<*, *>,
702+
interfaces: Array<GraphQLInterfaceType>,
703+
fields: GraphQLFieldConfigMap<*, *>,
704+
extensionASTNodes: $ReadOnlyArray<ObjectTypeExtensionNode>,
705+
|} {
706+
return {
707+
name: this.name,
708+
description: this.description,
709+
isTypeOf: this.isTypeOf,
710+
interfaces: this.getInterfaces(),
711+
fields: fieldsToFieldsConfig(this.getFields()),
712+
astNode: this.astNode,
713+
extensionASTNodes: this.extensionASTNodes || [],
714+
};
715+
}
716+
678717
toString(): string {
679718
return this.name;
680719
}
@@ -752,6 +791,33 @@ function isPlainObj(obj) {
752791
return obj && typeof obj === 'object' && !Array.isArray(obj);
753792
}
754793

794+
function fieldsToFieldsConfig(fields) {
795+
return mapValue(fields, field => ({
796+
type: field.type,
797+
args: argsToArgsConfig(field.args),
798+
resolve: field.resolve,
799+
subscribe: field.subscribe,
800+
deprecationReason: field.deprecationReason,
801+
description: field.description,
802+
astNode: field.astNode,
803+
}));
804+
}
805+
806+
export function argsToArgsConfig(
807+
args: Array<GraphQLArgument>,
808+
): GraphQLFieldConfigArgumentMap {
809+
return keyValMap(
810+
args,
811+
arg => arg.name,
812+
arg => ({
813+
type: arg.type,
814+
defaultValue: arg.defaultValue,
815+
description: arg.description,
816+
astNode: arg.astNode,
817+
}),
818+
);
819+
}
820+
755821
export type GraphQLObjectTypeConfig<TSource, TContext> = {|
756822
name: string,
757823
interfaces?: Thunk<?Array<GraphQLInterfaceType>>,
@@ -893,7 +959,7 @@ export class GraphQLInterfaceType {
893959
this.name = config.name;
894960
this.description = config.description;
895961
this.astNode = config.astNode;
896-
this.extensionASTNodes = config.extensionASTNodes;
962+
this.extensionASTNodes = undefineIfEmpty(config.extensionASTNodes);
897963
this.resolveType = config.resolveType;
898964
this._fields = defineFieldMap.bind(undefined, config);
899965
invariant(typeof config.name === 'string', 'Must provide name.');
@@ -911,6 +977,21 @@ export class GraphQLInterfaceType {
911977
return this._fields;
912978
}
913979

980+
toConfig(): {|
981+
...GraphQLInterfaceTypeConfig<*, *>,
982+
fields: GraphQLFieldConfigMap<*, *>,
983+
extensionASTNodes: $ReadOnlyArray<InterfaceTypeExtensionNode>,
984+
|} {
985+
return {
986+
name: this.name,
987+
description: this.description,
988+
resolveType: this.resolveType,
989+
fields: fieldsToFieldsConfig(this.getFields()),
990+
astNode: this.astNode,
991+
extensionASTNodes: this.extensionASTNodes || [],
992+
};
993+
}
994+
914995
toString(): string {
915996
return this.name;
916997
}
@@ -970,7 +1051,7 @@ export class GraphQLUnionType {
9701051
this.name = config.name;
9711052
this.description = config.description;
9721053
this.astNode = config.astNode;
973-
this.extensionASTNodes = config.extensionASTNodes;
1054+
this.extensionASTNodes = undefineIfEmpty(config.extensionASTNodes);
9741055
this.resolveType = config.resolveType;
9751056
this._types = defineTypes.bind(undefined, config);
9761057
invariant(typeof config.name === 'string', 'Must provide name.');
@@ -988,6 +1069,21 @@ export class GraphQLUnionType {
9881069
return this._types;
9891070
}
9901071

1072+
toConfig(): {|
1073+
...GraphQLUnionTypeConfig<*, *>,
1074+
types: Array<GraphQLObjectType>,
1075+
extensionASTNodes: $ReadOnlyArray<UnionTypeExtensionNode>,
1076+
|} {
1077+
return {
1078+
name: this.name,
1079+
description: this.description,
1080+
resolveType: this.resolveType,
1081+
types: this.getTypes(),
1082+
astNode: this.astNode,
1083+
extensionASTNodes: this.extensionASTNodes || [],
1084+
};
1085+
}
1086+
9911087
toString(): string {
9921088
return this.name;
9931089
}
@@ -1058,7 +1154,7 @@ export class GraphQLEnumType /* <T> */ {
10581154
this.name = config.name;
10591155
this.description = config.description;
10601156
this.astNode = config.astNode;
1061-
this.extensionASTNodes = config.extensionASTNodes;
1157+
this.extensionASTNodes = undefineIfEmpty(config.extensionASTNodes);
10621158
this._values = defineEnumValues(this, config.values);
10631159
this._valueLookup = new Map(
10641160
this._values.map(enumValue => [enumValue.value, enumValue]),
@@ -1102,6 +1198,30 @@ export class GraphQLEnumType /* <T> */ {
11021198
}
11031199
}
11041200

1201+
toConfig(): {|
1202+
...GraphQLEnumTypeConfig,
1203+
extensionASTNodes: $ReadOnlyArray<EnumTypeExtensionNode>,
1204+
|} {
1205+
const values = keyValMap(
1206+
this.getValues(),
1207+
value => value.name,
1208+
value => ({
1209+
description: value.description,
1210+
value: value.value,
1211+
deprecationReason: value.deprecationReason,
1212+
astNode: value.astNode,
1213+
}),
1214+
);
1215+
1216+
return {
1217+
name: this.name,
1218+
description: this.description,
1219+
values,
1220+
astNode: this.astNode,
1221+
extensionASTNodes: this.extensionASTNodes || [],
1222+
};
1223+
}
1224+
11051225
toString(): string {
11061226
return this.name;
11071227
}
@@ -1199,7 +1319,7 @@ export class GraphQLInputObjectType {
11991319
this.name = config.name;
12001320
this.description = config.description;
12011321
this.astNode = config.astNode;
1202-
this.extensionASTNodes = config.extensionASTNodes;
1322+
this.extensionASTNodes = undefineIfEmpty(config.extensionASTNodes);
12031323
this._fields = defineInputFieldMap.bind(undefined, config);
12041324
invariant(typeof config.name === 'string', 'Must provide name.');
12051325
}
@@ -1211,6 +1331,27 @@ export class GraphQLInputObjectType {
12111331
return this._fields;
12121332
}
12131333

1334+
toConfig(): {|
1335+
...GraphQLInputObjectTypeConfig,
1336+
fields: GraphQLInputFieldConfigMap,
1337+
extensionASTNodes: $ReadOnlyArray<InputObjectTypeExtensionNode>,
1338+
|} {
1339+
const fields = mapValue(this.getFields(), field => ({
1340+
description: field.description,
1341+
type: field.type,
1342+
defaultValue: field.defaultValue,
1343+
astNode: field.astNode,
1344+
}));
1345+
1346+
return {
1347+
name: this.name,
1348+
description: this.description,
1349+
fields,
1350+
astNode: this.astNode,
1351+
extensionASTNodes: this.extensionASTNodes || [],
1352+
};
1353+
}
1354+
12141355
toString(): string {
12151356
return this.name;
12161357
}

src/type/directives.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
import objectEntries from '../polyfills/objectEntries';
11+
import { argsToArgsConfig } from './definition';
1112
import type {
1213
GraphQLFieldConfigArgumentMap,
1314
GraphQLArgument,
@@ -84,6 +85,19 @@ export class GraphQLDirective {
8485
toString(): string {
8586
return '@' + this.name;
8687
}
88+
89+
toConfig(): {|
90+
...GraphQLDirectiveConfig,
91+
args: GraphQLFieldConfigArgumentMap,
92+
|} {
93+
return {
94+
name: this.name,
95+
description: this.description,
96+
locations: this.locations,
97+
args: argsToArgsConfig(this.args),
98+
astNode: this.astNode,
99+
};
100+
}
87101
}
88102

89103
// Conditionally apply `[Symbol.toStringTag]` if `Symbol`s are supported

src/type/schema.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,27 @@ export class GraphQLSchema {
238238
getDirective(name: string): ?GraphQLDirective {
239239
return find(this.getDirectives(), directive => directive.name === name);
240240
}
241+
242+
toConfig(): {|
243+
...GraphQLSchemaConfig,
244+
types: Array<GraphQLNamedType>,
245+
directives: Array<GraphQLDirective>,
246+
extensionASTNodes: $ReadOnlyArray<SchemaExtensionNode>,
247+
assumeValid: boolean,
248+
allowedLegacyNames: $ReadOnlyArray<string>,
249+
|} {
250+
return {
251+
types: objectValues(this.getTypeMap()),
252+
directives: this.getDirectives().slice(),
253+
query: this.getQueryType(),
254+
mutation: this.getMutationType(),
255+
subscription: this.getSubscriptionType(),
256+
astNode: this.astNode,
257+
extensionASTNodes: this.extensionASTNodes || [],
258+
assumeValid: this.__validationErrors !== undefined,
259+
allowedLegacyNames: this.__allowedLegacyNames,
260+
};
261+
}
241262
}
242263

243264
// Conditionally apply `[Symbol.toStringTag]` if `Symbol`s are supported

0 commit comments

Comments
 (0)