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 | 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 | };
|
304 | GraphQLAstExplorer = tslib_1.__decorate([
|
305 | common_1.Injectable()
|
306 | ], GraphQLAstExplorer);
|
307 | exports.GraphQLAstExplorer = GraphQLAstExplorer;
|