heal.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.healTypes = exports.healSchema = void 0;
  4. const 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. //
  33. function healSchema(schema) {
  34. healTypes(schema.getTypeMap(), schema.getDirectives());
  35. return schema;
  36. }
  37. exports.healSchema = healSchema;
  38. function 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. }
  178. exports.healTypes = healTypes;