UNPKG

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