UNPKG

8.46 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.pruneSchema = void 0;
4const graphql_1 = require("graphql");
5const get_implementing_types_js_1 = require("./get-implementing-types.js");
6const Interfaces_js_1 = require("./Interfaces.js");
7const mapSchema_js_1 = require("./mapSchema.js");
8const rootTypes_js_1 = require("./rootTypes.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 */
14function 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) &&
50 skipEmptyUnionPruning &&
51 !Object.keys(type.getTypes()).length) {
52 return type;
53 }
54 if ((0, graphql_1.isInputObjectType)(type) || (0, graphql_1.isInterfaceType)(type) || (0, graphql_1.isObjectType)(type)) {
55 // skipEmptyCompositeTypePruning: skip pruning object types or interfaces with no fields
56 if (skipEmptyCompositeTypePruning && !Object.keys(type.getFields()).length) {
57 return type;
58 }
59 }
60 // skipUnimplementedInterfacesPruning: skip pruning interfaces that are not implemented by any other types
61 if ((0, graphql_1.isInterfaceType)(type) && skipUnimplementedInterfacesPruning) {
62 return type;
63 }
64 }
65 prunedTypes.push(type.name);
66 visited.delete(type.name);
67 return null;
68 }
69 return type;
70 },
71 });
72 } while (prunedTypes.length); // Might have empty types and need to prune again
73 return prunedSchema;
74}
75exports.pruneSchema = pruneSchema;
76function visitSchema(schema) {
77 const queue = []; // queue of nodes to visit
78 // Grab the root types and start there
79 for (const type of (0, rootTypes_js_1.getRootTypes)(schema)) {
80 queue.push(type.name);
81 }
82 return visitQueue(queue, schema);
83}
84function visitQueue(queue, schema, visited = new Set()) {
85 // Interfaces encountered that are field return types need to be revisited to add their implementations
86 const revisit = new Map();
87 // Navigate all types starting with pre-queued types (root types)
88 while (queue.length) {
89 const typeName = queue.pop();
90 // Skip types we already visited unless it is an interface type that needs revisiting
91 if (visited.has(typeName) && revisit[typeName] !== true) {
92 continue;
93 }
94 const type = schema.getType(typeName);
95 if (type) {
96 // Get types for union
97 if ((0, graphql_1.isUnionType)(type)) {
98 queue.push(...type.getTypes().map(type => type.name));
99 }
100 // If it is an interface and it is a returned type, grab all implementations so we can use proper __typename in fragments
101 if ((0, graphql_1.isInterfaceType)(type) && revisit[typeName] === true) {
102 queue.push(...(0, get_implementing_types_js_1.getImplementingTypes)(type.name, schema));
103 // No need to revisit this interface again
104 revisit[typeName] = false;
105 }
106 if ((0, graphql_1.isEnumType)(type)) {
107 // Visit enum values directives argument types
108 queue.push(...type.getValues().flatMap(value => getDirectivesArgumentsTypeNames(schema, value)));
109 }
110 // Visit interfaces this type is implementing if they haven't been visited yet
111 if ('getInterfaces' in type) {
112 // Only pushes to queue to visit but not return types
113 queue.push(...type.getInterfaces().map(iface => iface.name));
114 }
115 // If the type has fields visit those field types
116 if ('getFields' in type) {
117 const fields = type.getFields();
118 const entries = Object.entries(fields);
119 if (!entries.length) {
120 continue;
121 }
122 for (const [, field] of entries) {
123 if ((0, graphql_1.isObjectType)(type)) {
124 // Visit arg types and arg directives arguments types
125 queue.push(...field.args.flatMap(arg => {
126 const typeNames = [(0, graphql_1.getNamedType)(arg.type).name];
127 typeNames.push(...getDirectivesArgumentsTypeNames(schema, arg));
128 return typeNames;
129 }));
130 }
131 const namedType = (0, graphql_1.getNamedType)(field.type);
132 queue.push(namedType.name);
133 queue.push(...getDirectivesArgumentsTypeNames(schema, field));
134 // Interfaces returned on fields need to be revisited to add their implementations
135 if ((0, graphql_1.isInterfaceType)(namedType) && !(namedType.name in revisit)) {
136 revisit[namedType.name] = true;
137 }
138 }
139 }
140 queue.push(...getDirectivesArgumentsTypeNames(schema, type));
141 visited.add(typeName); // Mark as visited (and therefore it is used and should be kept)
142 }
143 }
144 return visited;
145}
146function getDirectivesArgumentsTypeNames(schema, directableObj) {
147 const argTypeNames = new Set();
148 if (directableObj.astNode?.directives) {
149 for (const directiveNode of directableObj.astNode.directives) {
150 const directive = schema.getDirective(directiveNode.name.value);
151 if (directive?.args) {
152 for (const arg of directive.args) {
153 const argType = (0, graphql_1.getNamedType)(arg.type);
154 argTypeNames.add(argType.name);
155 }
156 }
157 }
158 }
159 if (directableObj.extensions?.['directives']) {
160 for (const directiveName in directableObj.extensions['directives']) {
161 const directive = schema.getDirective(directiveName);
162 if (directive?.args) {
163 for (const arg of directive.args) {
164 const argType = (0, graphql_1.getNamedType)(arg.type);
165 argTypeNames.add(argType.name);
166 }
167 }
168 }
169 }
170 return [...argTypeNames];
171}