1 | import { getNamedType, isEnumType, isInputObjectType, isInterfaceType, isObjectType, isScalarType, isSpecifiedScalarType, isUnionType, } from 'graphql';
|
2 | import { getImplementingTypes } from './get-implementing-types.js';
|
3 | import { MapperKind } from './Interfaces.js';
|
4 | import { mapSchema } from './mapSchema.js';
|
5 | import { getRootTypes } from './rootTypes.js';
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 | export function pruneSchema(schema, options = {}) {
|
12 | const { skipEmptyCompositeTypePruning, skipEmptyUnionPruning, skipPruning, skipUnimplementedInterfacesPruning, skipUnusedTypesPruning, } = options;
|
13 | let prunedTypes = [];
|
14 | let prunedSchema = schema;
|
15 | do {
|
16 | let visited = visitSchema(prunedSchema);
|
17 |
|
18 | if (skipPruning) {
|
19 | const revisit = [];
|
20 | for (const typeName in prunedSchema.getTypeMap()) {
|
21 | if (typeName.startsWith('__')) {
|
22 | continue;
|
23 | }
|
24 | const type = prunedSchema.getType(typeName);
|
25 |
|
26 | if (type && skipPruning(type)) {
|
27 | revisit.push(typeName);
|
28 | }
|
29 | }
|
30 | visited = visitQueue(revisit, prunedSchema, visited);
|
31 | }
|
32 | prunedTypes = [];
|
33 | prunedSchema = mapSchema(prunedSchema, {
|
34 | [MapperKind.TYPE]: type => {
|
35 | if (!visited.has(type.name) && !isSpecifiedScalarType(type)) {
|
36 | if (isUnionType(type) ||
|
37 | isInputObjectType(type) ||
|
38 | isInterfaceType(type) ||
|
39 | isObjectType(type) ||
|
40 | isScalarType(type)) {
|
41 |
|
42 | if (skipUnusedTypesPruning) {
|
43 | return type;
|
44 | }
|
45 |
|
46 | if (isUnionType(type) &&
|
47 | skipEmptyUnionPruning &&
|
48 | !Object.keys(type.getTypes()).length) {
|
49 | return type;
|
50 | }
|
51 | if (isInputObjectType(type) || isInterfaceType(type) || isObjectType(type)) {
|
52 |
|
53 | if (skipEmptyCompositeTypePruning && !Object.keys(type.getFields()).length) {
|
54 | return type;
|
55 | }
|
56 | }
|
57 |
|
58 | if (isInterfaceType(type) && skipUnimplementedInterfacesPruning) {
|
59 | return type;
|
60 | }
|
61 | }
|
62 | prunedTypes.push(type.name);
|
63 | visited.delete(type.name);
|
64 | return null;
|
65 | }
|
66 | return type;
|
67 | },
|
68 | });
|
69 | } while (prunedTypes.length);
|
70 | return prunedSchema;
|
71 | }
|
72 | function visitSchema(schema) {
|
73 | const queue = [];
|
74 |
|
75 | for (const type of getRootTypes(schema)) {
|
76 | queue.push(type.name);
|
77 | }
|
78 | return visitQueue(queue, schema);
|
79 | }
|
80 | function visitQueue(queue, schema, visited = new Set()) {
|
81 |
|
82 | const revisit = new Map();
|
83 |
|
84 | while (queue.length) {
|
85 | const typeName = queue.pop();
|
86 |
|
87 | if (visited.has(typeName) && revisit[typeName] !== true) {
|
88 | continue;
|
89 | }
|
90 | const type = schema.getType(typeName);
|
91 | if (type) {
|
92 |
|
93 | if (isUnionType(type)) {
|
94 | queue.push(...type.getTypes().map(type => type.name));
|
95 | }
|
96 |
|
97 | if (isInterfaceType(type) && revisit[typeName] === true) {
|
98 | queue.push(...getImplementingTypes(type.name, schema));
|
99 |
|
100 | revisit[typeName] = false;
|
101 | }
|
102 | if (isEnumType(type)) {
|
103 |
|
104 | queue.push(...type.getValues().flatMap(value => getDirectivesArgumentsTypeNames(schema, value)));
|
105 | }
|
106 |
|
107 | if ('getInterfaces' in type) {
|
108 |
|
109 | queue.push(...type.getInterfaces().map(iface => iface.name));
|
110 | }
|
111 |
|
112 | if ('getFields' in type) {
|
113 | const fields = type.getFields();
|
114 | const entries = Object.entries(fields);
|
115 | if (!entries.length) {
|
116 | continue;
|
117 | }
|
118 | for (const [, field] of entries) {
|
119 | if (isObjectType(type)) {
|
120 |
|
121 | queue.push(...field.args.flatMap(arg => {
|
122 | const typeNames = [getNamedType(arg.type).name];
|
123 | typeNames.push(...getDirectivesArgumentsTypeNames(schema, arg));
|
124 | return typeNames;
|
125 | }));
|
126 | }
|
127 | const namedType = getNamedType(field.type);
|
128 | queue.push(namedType.name);
|
129 | queue.push(...getDirectivesArgumentsTypeNames(schema, field));
|
130 |
|
131 | if (isInterfaceType(namedType) && !(namedType.name in revisit)) {
|
132 | revisit[namedType.name] = true;
|
133 | }
|
134 | }
|
135 | }
|
136 | queue.push(...getDirectivesArgumentsTypeNames(schema, type));
|
137 | visited.add(typeName);
|
138 | }
|
139 | }
|
140 | return visited;
|
141 | }
|
142 | function getDirectivesArgumentsTypeNames(schema, directableObj) {
|
143 | const argTypeNames = new Set();
|
144 | if (directableObj.astNode?.directives) {
|
145 | for (const directiveNode of directableObj.astNode.directives) {
|
146 | const directive = schema.getDirective(directiveNode.name.value);
|
147 | if (directive?.args) {
|
148 | for (const arg of directive.args) {
|
149 | const argType = getNamedType(arg.type);
|
150 | argTypeNames.add(argType.name);
|
151 | }
|
152 | }
|
153 | }
|
154 | }
|
155 | if (directableObj.extensions?.['directives']) {
|
156 | for (const directiveName in directableObj.extensions['directives']) {
|
157 | const directive = schema.getDirective(directiveName);
|
158 | if (directive?.args) {
|
159 | for (const arg of directive.args) {
|
160 | const argType = getNamedType(arg.type);
|
161 | argTypeNames.add(argType.name);
|
162 | }
|
163 | }
|
164 | }
|
165 | }
|
166 | return [...argTypeNames];
|
167 | }
|