UNPKG

11.9 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.GraphQLAstExplorer = void 0;
4const tslib_1 = require("tslib");
5const common_1 = require("@nestjs/common");
6const lodash_1 = require("lodash");
7const graphql_constants_1 = require("./graphql.constants");
8let tsMorphLib;
9let GraphQLAstExplorer = class GraphQLAstExplorer {
10 constructor() {
11 this.root = ['Query', 'Mutation', 'Subscription'];
12 }
13 async explore(documentNode, outputPath, mode, options = {}) {
14 if (!documentNode) {
15 return;
16 }
17 tsMorphLib = await Promise.resolve().then(() => require('ts-morph'));
18 const tsAstHelper = new tsMorphLib.Project({
19 manipulationSettings: {
20 newLineKind: process.platform === 'win32'
21 ? tsMorphLib.NewLineKind.CarriageReturnLineFeed
22 : tsMorphLib.NewLineKind.LineFeed,
23 },
24 });
25 const tsFile = tsAstHelper.createSourceFile(outputPath, '', {
26 overwrite: true,
27 });
28 let { definitions } = documentNode;
29 definitions = (0, lodash_1.sortBy)(definitions, ['kind', 'name']);
30 const fileStructure = tsFile.getStructure();
31 const header = options.additionalHeader
32 ? `${graphql_constants_1.DEFINITIONS_FILE_HEADER}\n\n${options.additionalHeader}`
33 : graphql_constants_1.DEFINITIONS_FILE_HEADER;
34 fileStructure.statements = [header];
35 fileStructure.statements.push(...definitions
36 .map((item) => this.toDefinitionStructures(item, mode, options))
37 .filter(Boolean));
38 fileStructure.statements.push({
39 kind: tsMorphLib.StructureKind.TypeAlias,
40 name: 'Nullable',
41 isExported: false,
42 type: 'T | null',
43 typeParameters: [
44 {
45 name: 'T',
46 },
47 ],
48 });
49 tsFile.set(fileStructure);
50 return tsFile;
51 }
52 toDefinitionStructures(item, mode, options) {
53 switch (item.kind) {
54 case 'SchemaDefinition':
55 return this.toRootSchemaDefinitionStructure(item.operationTypes, mode);
56 case 'ObjectTypeDefinition':
57 case 'ObjectTypeExtension':
58 case 'InputObjectTypeDefinition':
59 case 'InputObjectTypeExtension':
60 return this.toObjectTypeDefinitionStructure(item, mode, options);
61 case 'InterfaceTypeDefinition':
62 case 'InterfaceTypeExtension':
63 return this.toObjectTypeDefinitionStructure(item, 'interface', options);
64 case 'ScalarTypeDefinition':
65 case 'ScalarTypeExtension':
66 return this.toScalarDefinitionStructure(item, options);
67 case 'EnumTypeDefinition':
68 case 'EnumTypeExtension':
69 return this.toEnumDefinitionStructure(item, options);
70 case 'UnionTypeDefinition':
71 case 'UnionTypeExtension':
72 return this.toUnionDefinitionStructure(item);
73 }
74 }
75 toRootSchemaDefinitionStructure(operationTypes, mode) {
76 const structureKind = mode === 'class'
77 ? tsMorphLib.StructureKind.Class
78 : tsMorphLib.StructureKind.Interface;
79 const properties = operationTypes
80 .filter(Boolean)
81 .map((item) => {
82 const tempOperationName = item.operation;
83 const typeName = (0, lodash_1.get)(item, 'type.name.value');
84 const interfaceName = typeName || tempOperationName;
85 return {
86 name: interfaceName,
87 type: this.addSymbolIfRoot((0, lodash_1.upperFirst)(interfaceName)),
88 };
89 })
90 .filter(Boolean);
91 return {
92 name: 'ISchema',
93 isExported: true,
94 kind: structureKind,
95 properties: properties,
96 };
97 }
98 toObjectTypeDefinitionStructure(item, mode, options) {
99 const parentName = (0, lodash_1.get)(item, 'name.value');
100 if (!parentName) {
101 return;
102 }
103 const structureKind = mode === 'class'
104 ? tsMorphLib.StructureKind.Class
105 : tsMorphLib.StructureKind.Interface;
106 const isRoot = this.root.indexOf(parentName) >= 0;
107 const parentStructure = {
108 name: this.addSymbolIfRoot((0, lodash_1.upperFirst)(parentName)),
109 isExported: true,
110 isAbstract: isRoot && mode === 'class',
111 kind: structureKind,
112 properties: [],
113 methods: [],
114 };
115 const interfaces = (0, lodash_1.get)(item, 'interfaces');
116 if (interfaces) {
117 if (mode === 'class') {
118 parentStructure.implements = interfaces
119 .map((element) => (0, lodash_1.get)(element, 'name.value'))
120 .filter(Boolean);
121 }
122 else {
123 parentStructure.extends = interfaces
124 .map((element) => (0, lodash_1.get)(element, 'name.value'))
125 .filter(Boolean);
126 }
127 }
128 const isObjectType = item.kind === 'ObjectTypeDefinition';
129 if (isObjectType && options.emitTypenameField) {
130 parentStructure.properties.push({
131 name: '__typename',
132 type: `'${parentStructure.name}'`,
133 hasQuestionToken: true,
134 });
135 }
136 if (!this.isRoot(parentStructure.name) || options.skipResolverArgs) {
137 const properties = (item.fields || [])
138 .map((element) => this.toPropertyDeclarationStructure(element, options))
139 .filter(Boolean);
140 parentStructure.properties.push(...properties);
141 }
142 else {
143 const methods = (item.fields || [])
144 .map((element) => this.toMethodDeclarationStructure(element, mode, options))
145 .filter(Boolean);
146 parentStructure.methods.push(...methods);
147 }
148 return parentStructure;
149 }
150 toPropertyDeclarationStructure(item, options) {
151 const propertyName = (0, lodash_1.get)(item, 'name.value');
152 if (!propertyName) {
153 return undefined;
154 }
155 const federatedFields = ['_entities', '_service'];
156 if (federatedFields.includes(propertyName)) {
157 return undefined;
158 }
159 const { name: type, required } = this.getFieldTypeDefinition(item.type, options);
160 return {
161 name: propertyName,
162 type: this.addSymbolIfRoot(type),
163 hasQuestionToken: !required || item.arguments?.length > 0,
164 };
165 }
166 toMethodDeclarationStructure(item, mode, options) {
167 const propertyName = (0, lodash_1.get)(item, 'name.value');
168 if (!propertyName) {
169 return;
170 }
171 const federatedFields = ['_entities', '_service'];
172 if (federatedFields.includes(propertyName)) {
173 return;
174 }
175 const { name: type } = this.getFieldTypeDefinition(item.type, options);
176 return {
177 isAbstract: mode === 'class',
178 name: propertyName,
179 returnType: `${type} | Promise<${type}>`,
180 parameters: this.getFunctionParameters(item.arguments, options),
181 };
182 }
183 getFieldTypeDefinition(typeNode, options) {
184 const stringifyType = (typeNode) => {
185 const { type, required } = this.unwrapTypeIfNonNull(typeNode);
186 const isArray = type.kind === 'ListType';
187 if (isArray) {
188 const arrayType = (0, lodash_1.get)(type, 'type');
189 return required
190 ? `${stringifyType(arrayType)}[]`
191 : `Nullable<${stringifyType(arrayType)}[]>`;
192 }
193 const typeName = this.addSymbolIfRoot((0, lodash_1.get)(type, 'name.value'));
194 return required
195 ? this.getType(typeName, options)
196 : `Nullable<${this.getType(typeName, options)}>`;
197 };
198 const { required } = this.unwrapTypeIfNonNull(typeNode);
199 return {
200 name: stringifyType(typeNode),
201 required,
202 };
203 }
204 unwrapTypeIfNonNull(type) {
205 const isNonNullType = type.kind === 'NonNullType';
206 if (isNonNullType) {
207 return {
208 type: this.unwrapTypeIfNonNull((0, lodash_1.get)(type, 'type')).type,
209 required: isNonNullType,
210 };
211 }
212 return { type, required: false };
213 }
214 getType(typeName, options) {
215 const defaults = this.getDefaultTypes(options);
216 const isDefault = defaults[typeName];
217 return isDefault ? defaults[typeName] : typeName;
218 }
219 getDefaultTypes(options) {
220 return {
221 String: options.defaultTypeMapping?.String ?? 'string',
222 Int: options.defaultTypeMapping?.Int ?? 'number',
223 Boolean: options.defaultTypeMapping?.Boolean ?? 'boolean',
224 ID: options.defaultTypeMapping?.ID ?? 'string',
225 Float: options.defaultTypeMapping?.Float ?? 'number',
226 };
227 }
228 getFunctionParameters(inputs, options) {
229 if (!inputs) {
230 return [];
231 }
232 return inputs.map((element) => {
233 const { name, required } = this.getFieldTypeDefinition(element.type, options);
234 return {
235 name: (0, lodash_1.get)(element, 'name.value'),
236 type: name,
237 hasQuestionToken: !required,
238 kind: tsMorphLib.StructureKind.Parameter,
239 };
240 });
241 }
242 toScalarDefinitionStructure(item, options) {
243 const name = (0, lodash_1.get)(item, 'name.value');
244 if (!name || name === 'Date') {
245 return undefined;
246 }
247 const typeMapping = options.customScalarTypeMapping?.[name];
248 const mappedTypeName = typeof typeMapping === 'string' ? typeMapping : typeMapping?.name;
249 return {
250 kind: tsMorphLib.StructureKind.TypeAlias,
251 name,
252 type: mappedTypeName ?? options.defaultScalarType ?? 'any',
253 isExported: true,
254 };
255 }
256 toEnumDefinitionStructure(item, options) {
257 const name = (0, lodash_1.get)(item, 'name.value');
258 if (!name) {
259 return undefined;
260 }
261 if (options.enumsAsTypes) {
262 const values = item.values.map((value) => `"${(0, lodash_1.get)(value, 'name.value')}"`);
263 return {
264 kind: tsMorphLib.StructureKind.TypeAlias,
265 name,
266 type: values.join(' | '),
267 isExported: true,
268 };
269 }
270 const members = (0, lodash_1.map)(item.values, (value) => ({
271 name: (0, lodash_1.get)(value, 'name.value'),
272 value: (0, lodash_1.get)(value, 'name.value'),
273 }));
274 return {
275 kind: tsMorphLib.StructureKind.Enum,
276 name,
277 members,
278 isExported: true,
279 };
280 }
281 toUnionDefinitionStructure(item) {
282 const name = (0, lodash_1.get)(item, 'name.value');
283 if (!name) {
284 return undefined;
285 }
286 const types = (0, lodash_1.map)(item.types, (value) => (0, lodash_1.get)(value, 'name.value'));
287 return {
288 kind: tsMorphLib.StructureKind.TypeAlias,
289 name,
290 type: types.join(' | '),
291 isExported: true,
292 };
293 }
294 addSymbolIfRoot(name) {
295 return this.root.indexOf(name) >= 0 ? `I${name}` : name;
296 }
297 isRoot(name) {
298 return ['IQuery', 'IMutation', 'ISubscription'].indexOf(name) >= 0;
299 }
300};
301exports.GraphQLAstExplorer = GraphQLAstExplorer;
302exports.GraphQLAstExplorer = GraphQLAstExplorer = tslib_1.__decorate([
303 (0, common_1.Injectable)()
304], GraphQLAstExplorer);