UNPKG

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