1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.GraphQLAstExplorer = void 0;
|
4 | const tslib_1 = require("tslib");
|
5 | const common_1 = require("@nestjs/common");
|
6 | const lodash_1 = require("lodash");
|
7 | const graphql_constants_1 = require("./graphql.constants");
|
8 | let tsMorphLib;
|
9 | let 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 | };
|
323 | GraphQLAstExplorer = tslib_1.__decorate([
|
324 | (0, common_1.Injectable)()
|
325 | ], GraphQLAstExplorer);
|
326 | exports.GraphQLAstExplorer = GraphQLAstExplorer;
|