UNPKG

6.96 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 (actualNamedTypeMap[actualName] != null) {
53 console.warn(`Duplicate schema type name ${actualName} found; keeping the existing one found in the schema`);
54 continue;
55 }
56 actualNamedTypeMap[actualName] = namedType;
57 // Note: we are deliberately leaving namedType in the schema by its
58 // original name (which might be different from actualName), so that
59 // references by that name can be healed.
60 }
61 // Now add back every named type by its actual name.
62 for (const typeName in actualNamedTypeMap) {
63 const namedType = actualNamedTypeMap[typeName];
64 originalTypeMap[typeName] = namedType;
65 }
66 // Directive declaration argument types can refer to named types.
67 for (const decl of directives) {
68 decl.args = decl.args.filter(arg => {
69 arg.type = healType(arg.type);
70 return arg.type !== null;
71 });
72 }
73 for (const typeName in originalTypeMap) {
74 const namedType = originalTypeMap[typeName];
75 // Heal all named types, except for dangling references, kept only to redirect.
76 if (!typeName.startsWith('__') && typeName in actualNamedTypeMap) {
77 if (namedType != null) {
78 healNamedType(namedType);
79 }
80 }
81 }
82 for (const typeName in originalTypeMap) {
83 if (!typeName.startsWith('__') && !(typeName in actualNamedTypeMap)) {
84 delete originalTypeMap[typeName];
85 }
86 }
87 function healNamedType(type) {
88 if ((0, graphql_1.isObjectType)(type)) {
89 healFields(type);
90 healInterfaces(type);
91 return;
92 }
93 else if ((0, graphql_1.isInterfaceType)(type)) {
94 healFields(type);
95 if ('getInterfaces' in type) {
96 healInterfaces(type);
97 }
98 return;
99 }
100 else if ((0, graphql_1.isUnionType)(type)) {
101 healUnderlyingTypes(type);
102 return;
103 }
104 else if ((0, graphql_1.isInputObjectType)(type)) {
105 healInputFields(type);
106 return;
107 }
108 else if ((0, graphql_1.isLeafType)(type)) {
109 return;
110 }
111 throw new Error(`Unexpected schema type: ${type}`);
112 }
113 function healFields(type) {
114 const fieldMap = type.getFields();
115 for (const [key, field] of Object.entries(fieldMap)) {
116 field.args
117 .map(arg => {
118 arg.type = healType(arg.type);
119 return arg.type === null ? null : arg;
120 })
121 .filter(Boolean);
122 field.type = healType(field.type);
123 if (field.type === null) {
124 delete fieldMap[key];
125 }
126 }
127 }
128 function healInterfaces(type) {
129 if ('getInterfaces' in type) {
130 const interfaces = type.getInterfaces();
131 interfaces.push(...interfaces
132 .splice(0)
133 .map(iface => healType(iface))
134 .filter(Boolean));
135 }
136 }
137 function healInputFields(type) {
138 const fieldMap = type.getFields();
139 for (const [key, field] of Object.entries(fieldMap)) {
140 field.type = healType(field.type);
141 if (field.type === null) {
142 delete fieldMap[key];
143 }
144 }
145 }
146 function healUnderlyingTypes(type) {
147 const types = type.getTypes();
148 types.push(...types
149 .splice(0)
150 .map(t => healType(t))
151 .filter(Boolean));
152 }
153 function healType(type) {
154 // Unwrap the two known wrapper types
155 if ((0, graphql_1.isListType)(type)) {
156 const healedType = healType(type.ofType);
157 return healedType != null ? new graphql_1.GraphQLList(healedType) : null;
158 }
159 else if ((0, graphql_1.isNonNullType)(type)) {
160 const healedType = healType(type.ofType);
161 return healedType != null ? new graphql_1.GraphQLNonNull(healedType) : null;
162 }
163 else if ((0, graphql_1.isNamedType)(type)) {
164 // If a type annotation on a field or an argument or a union member is
165 // any `GraphQLNamedType` with a `name`, then it must end up identical
166 // to `schema.getType(name)`, since `schema.getTypeMap()` is the source
167 // of truth for all named schema types.
168 // Note that new types can still be simply added by adding a field, as
169 // the official type will be undefined, not null.
170 const officialType = originalTypeMap[type.name];
171 if (officialType && type !== officialType) {
172 return officialType;
173 }
174 }
175 return type;
176 }
177}
178exports.healTypes = healTypes;