UNPKG

6.89 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.healTypes = exports.healSchema = void 0;
4const graphql_1 = require("graphql");
5// Update any references to named schema types that disagree with the named
6// types found in schema.getTypeMap().
7//
8// healSchema and its callers (visitSchema/visitSchemaDirectives) all modify the schema in place.
9// Therefore, private variables (such as the stored implementation map and the proper root types)
10// are not updated.
11//
12// If this causes issues, the schema could be more aggressively healed as follows:
13//
14// healSchema(schema);
15// const config = schema.toConfig()
16// const healedSchema = new GraphQLSchema({
17// ...config,
18// query: schema.getType('<desired new root query type name>'),
19// mutation: schema.getType('<desired new root mutation type name>'),
20// subscription: schema.getType('<desired new root subscription type name>'),
21// });
22//
23// One can then also -- if necessary -- assign the correct private variables to the initial schema
24// as follows:
25// Object.assign(schema, healedSchema);
26//
27// These steps are not taken automatically to preserve backwards compatibility with graphql-tools v4.
28// See https://github.com/ardatan/graphql-tools/issues/1462
29//
30// They were briefly taken in v5, but can now be phased out as they were only required when other
31// areas of the codebase were using healSchema and visitSchema more extensively.
32//
33function healSchema(schema) {
34 healTypes(schema.getTypeMap(), schema.getDirectives());
35 return schema;
36}
37exports.healSchema = healSchema;
38function healTypes(originalTypeMap, directives) {
39 const actualNamedTypeMap = Object.create(null);
40 // If any of the .name properties of the GraphQLNamedType objects in
41 // schema.getTypeMap() have changed, the keys of the type map need to
42 // be updated accordingly.
43 for (const typeName in originalTypeMap) {
44 const namedType = originalTypeMap[typeName];
45 if (namedType == null || typeName.startsWith('__')) {
46 continue;
47 }
48 const actualName = namedType.name;
49 if (actualName.startsWith('__')) {
50 continue;
51 }
52 if (actualName in actualNamedTypeMap) {
53 throw new Error(`Duplicate schema type name ${actualName}`);
54 }
55 actualNamedTypeMap[actualName] = namedType;
56 // Note: we are deliberately leaving namedType in the schema by its
57 // original name (which might be different from actualName), so that
58 // references by that name can be healed.
59 }
60 // Now add back every named type by its actual name.
61 for (const typeName in actualNamedTypeMap) {
62 const namedType = actualNamedTypeMap[typeName];
63 originalTypeMap[typeName] = namedType;
64 }
65 // Directive declaration argument types can refer to named types.
66 for (const decl of directives) {
67 decl.args = decl.args.filter(arg => {
68 arg.type = healType(arg.type);
69 return arg.type !== null;
70 });
71 }
72 for (const typeName in originalTypeMap) {
73 const namedType = originalTypeMap[typeName];
74 // Heal all named types, except for dangling references, kept only to redirect.
75 if (!typeName.startsWith('__') && typeName in actualNamedTypeMap) {
76 if (namedType != null) {
77 healNamedType(namedType);
78 }
79 }
80 }
81 for (const typeName in originalTypeMap) {
82 if (!typeName.startsWith('__') && !(typeName in actualNamedTypeMap)) {
83 delete originalTypeMap[typeName];
84 }
85 }
86 function healNamedType(type) {
87 if ((0, graphql_1.isObjectType)(type)) {
88 healFields(type);
89 healInterfaces(type);
90 return;
91 }
92 else if ((0, graphql_1.isInterfaceType)(type)) {
93 healFields(type);
94 if ('getInterfaces' in type) {
95 healInterfaces(type);
96 }
97 return;
98 }
99 else if ((0, graphql_1.isUnionType)(type)) {
100 healUnderlyingTypes(type);
101 return;
102 }
103 else if ((0, graphql_1.isInputObjectType)(type)) {
104 healInputFields(type);
105 return;
106 }
107 else if ((0, graphql_1.isLeafType)(type)) {
108 return;
109 }
110 throw new Error(`Unexpected schema type: ${type}`);
111 }
112 function healFields(type) {
113 const fieldMap = type.getFields();
114 for (const [key, field] of Object.entries(fieldMap)) {
115 field.args
116 .map(arg => {
117 arg.type = healType(arg.type);
118 return arg.type === null ? null : arg;
119 })
120 .filter(Boolean);
121 field.type = healType(field.type);
122 if (field.type === null) {
123 delete fieldMap[key];
124 }
125 }
126 }
127 function healInterfaces(type) {
128 if ('getInterfaces' in type) {
129 const interfaces = type.getInterfaces();
130 interfaces.push(...interfaces
131 .splice(0)
132 .map(iface => healType(iface))
133 .filter(Boolean));
134 }
135 }
136 function healInputFields(type) {
137 const fieldMap = type.getFields();
138 for (const [key, field] of Object.entries(fieldMap)) {
139 field.type = healType(field.type);
140 if (field.type === null) {
141 delete fieldMap[key];
142 }
143 }
144 }
145 function healUnderlyingTypes(type) {
146 const types = type.getTypes();
147 types.push(...types
148 .splice(0)
149 .map(t => healType(t))
150 .filter(Boolean));
151 }
152 function healType(type) {
153 // Unwrap the two known wrapper types
154 if ((0, graphql_1.isListType)(type)) {
155 const healedType = healType(type.ofType);
156 return healedType != null ? new graphql_1.GraphQLList(healedType) : null;
157 }
158 else if ((0, graphql_1.isNonNullType)(type)) {
159 const healedType = healType(type.ofType);
160 return healedType != null ? new graphql_1.GraphQLNonNull(healedType) : null;
161 }
162 else if ((0, graphql_1.isNamedType)(type)) {
163 // If a type annotation on a field or an argument or a union member is
164 // any `GraphQLNamedType` with a `name`, then it must end up identical
165 // to `schema.getType(name)`, since `schema.getTypeMap()` is the source
166 // of truth for all named schema types.
167 // Note that new types can still be simply added by adding a field, as
168 // the official type will be undefined, not null.
169 const officialType = originalTypeMap[type.name];
170 if (officialType && type !== officialType) {
171 return officialType;
172 }
173 }
174 return type;
175 }
176}
177exports.healTypes = healTypes;