prune.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.pruneSchema = void 0;
  4. const graphql_1 = require("graphql");
  5. const mapSchema_js_1 = require("./mapSchema.js");
  6. const Interfaces_js_1 = require("./Interfaces.js");
  7. const rootTypes_js_1 = require("./rootTypes.js");
  8. const get_implementing_types_js_1 = require("./get-implementing-types.js");
  9. /**
  10. * Prunes the provided schema, removing unused and empty types
  11. * @param schema The schema to prune
  12. * @param options Additional options for removing unused types from the schema
  13. */
  14. function pruneSchema(schema, options = {}) {
  15. const { skipEmptyCompositeTypePruning, skipEmptyUnionPruning, skipPruning, skipUnimplementedInterfacesPruning, skipUnusedTypesPruning, } = options;
  16. let prunedTypes = []; // Pruned types during mapping
  17. let prunedSchema = schema;
  18. do {
  19. let visited = visitSchema(prunedSchema);
  20. // Custom pruning was defined, so we need to pre-emptively revisit the schema accounting for this
  21. if (skipPruning) {
  22. const revisit = [];
  23. for (const typeName in prunedSchema.getTypeMap()) {
  24. if (typeName.startsWith('__')) {
  25. continue;
  26. }
  27. const type = prunedSchema.getType(typeName);
  28. // if we want to skip pruning for this type, add it to the list of types to revisit
  29. if (type && skipPruning(type)) {
  30. revisit.push(typeName);
  31. }
  32. }
  33. visited = visitQueue(revisit, prunedSchema, visited); // visit again
  34. }
  35. prunedTypes = [];
  36. prunedSchema = (0, mapSchema_js_1.mapSchema)(prunedSchema, {
  37. [Interfaces_js_1.MapperKind.TYPE]: type => {
  38. if (!visited.has(type.name) && !(0, graphql_1.isSpecifiedScalarType)(type)) {
  39. if ((0, graphql_1.isUnionType)(type) ||
  40. (0, graphql_1.isInputObjectType)(type) ||
  41. (0, graphql_1.isInterfaceType)(type) ||
  42. (0, graphql_1.isObjectType)(type) ||
  43. (0, graphql_1.isScalarType)(type)) {
  44. // skipUnusedTypesPruning: skip pruning unused types
  45. if (skipUnusedTypesPruning) {
  46. return type;
  47. }
  48. // skipEmptyUnionPruning: skip pruning empty unions
  49. if ((0, graphql_1.isUnionType)(type) && skipEmptyUnionPruning && !Object.keys(type.getTypes()).length) {
  50. return type;
  51. }
  52. if ((0, graphql_1.isInputObjectType)(type) || (0, graphql_1.isInterfaceType)(type) || (0, graphql_1.isObjectType)(type)) {
  53. // skipEmptyCompositeTypePruning: skip pruning object types or interfaces with no fields
  54. if (skipEmptyCompositeTypePruning && !Object.keys(type.getFields()).length) {
  55. return type;
  56. }
  57. }
  58. // skipUnimplementedInterfacesPruning: skip pruning interfaces that are not implemented by any other types
  59. if ((0, graphql_1.isInterfaceType)(type) && skipUnimplementedInterfacesPruning) {
  60. return type;
  61. }
  62. }
  63. prunedTypes.push(type.name);
  64. visited.delete(type.name);
  65. return null;
  66. }
  67. return type;
  68. },
  69. });
  70. } while (prunedTypes.length); // Might have empty types and need to prune again
  71. return prunedSchema;
  72. }
  73. exports.pruneSchema = pruneSchema;
  74. function visitSchema(schema) {
  75. const queue = []; // queue of nodes to visit
  76. // Grab the root types and start there
  77. for (const type of (0, rootTypes_js_1.getRootTypes)(schema)) {
  78. queue.push(type.name);
  79. }
  80. return visitQueue(queue, schema);
  81. }
  82. function visitQueue(queue, schema, visited = new Set()) {
  83. // Interfaces encountered that are field return types need to be revisited to add their implementations
  84. const revisit = new Map();
  85. // Navigate all types starting with pre-queued types (root types)
  86. while (queue.length) {
  87. const typeName = queue.pop();
  88. // Skip types we already visited unless it is an interface type that needs revisiting
  89. if (visited.has(typeName) && revisit[typeName] !== true) {
  90. continue;
  91. }
  92. const type = schema.getType(typeName);
  93. if (type) {
  94. // Get types for union
  95. if ((0, graphql_1.isUnionType)(type)) {
  96. queue.push(...type.getTypes().map(type => type.name));
  97. }
  98. // If it is an interface and it is a returned type, grab all implementations so we can use proper __typename in fragments
  99. if ((0, graphql_1.isInterfaceType)(type) && revisit[typeName] === true) {
  100. queue.push(...(0, get_implementing_types_js_1.getImplementingTypes)(type.name, schema));
  101. // No need to revisit this interface again
  102. revisit[typeName] = false;
  103. }
  104. if ((0, graphql_1.isEnumType)(type)) {
  105. // Visit enum values directives argument types
  106. queue.push(...type.getValues().flatMap(value => {
  107. if (value.astNode) {
  108. return getDirectivesArgumentsTypeNames(schema, value.astNode);
  109. }
  110. return [];
  111. }));
  112. }
  113. // Visit interfaces this type is implementing if they haven't been visited yet
  114. if ('getInterfaces' in type) {
  115. // Only pushes to queue to visit but not return types
  116. queue.push(...type.getInterfaces().map(iface => iface.name));
  117. }
  118. // If the type has fields visit those field types
  119. if ('getFields' in type) {
  120. const fields = type.getFields();
  121. const entries = Object.entries(fields);
  122. if (!entries.length) {
  123. continue;
  124. }
  125. for (const [, field] of entries) {
  126. if ((0, graphql_1.isObjectType)(type)) {
  127. // Visit arg types and arg directives arguments types
  128. queue.push(...field.args.flatMap(arg => {
  129. const typeNames = [(0, graphql_1.getNamedType)(arg.type).name];
  130. if (arg.astNode) {
  131. typeNames.push(...getDirectivesArgumentsTypeNames(schema, arg.astNode));
  132. }
  133. return typeNames;
  134. }));
  135. }
  136. const namedType = (0, graphql_1.getNamedType)(field.type);
  137. queue.push(namedType.name);
  138. if (field.astNode) {
  139. queue.push(...getDirectivesArgumentsTypeNames(schema, field.astNode));
  140. }
  141. // Interfaces returned on fields need to be revisited to add their implementations
  142. if ((0, graphql_1.isInterfaceType)(namedType) && !(namedType.name in revisit)) {
  143. revisit[namedType.name] = true;
  144. }
  145. }
  146. }
  147. if (type.astNode) {
  148. queue.push(...getDirectivesArgumentsTypeNames(schema, type.astNode));
  149. }
  150. visited.add(typeName); // Mark as visited (and therefore it is used and should be kept)
  151. }
  152. }
  153. return visited;
  154. }
  155. function getDirectivesArgumentsTypeNames(schema, astNode) {
  156. var _a;
  157. return ((_a = astNode.directives) !== null && _a !== void 0 ? _a : []).flatMap(directive => { var _a, _b; return (_b = (_a = schema.getDirective(directive.name.value)) === null || _a === void 0 ? void 0 : _a.args.map(arg => (0, graphql_1.getNamedType)(arg.type).name)) !== null && _b !== void 0 ? _b : []; });
  158. }