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 | 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 | };
|
301 | exports.GraphQLAstExplorer = GraphQLAstExplorer;
|
302 | exports.GraphQLAstExplorer = GraphQLAstExplorer = tslib_1.__decorate([
|
303 | (0, common_1.Injectable)()
|
304 | ], GraphQLAstExplorer);
|