UNPKG

@nestjs/graphql

Version:

Nest - modern, fast, powerful node.js web framework (@graphql)

328 lines (327 loc) 13.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.GraphQLAstExplorer = void 0; const tslib_1 = require("tslib"); const common_1 = require("@nestjs/common"); const lodash_1 = require("lodash"); const graphql_constants_1 = require("./graphql.constants"); let tsMorphLib; let GraphQLAstExplorer = class GraphQLAstExplorer { constructor() { this.root = ['Query', 'Mutation', 'Subscription']; } async explore(documentNode, outputPath, mode, options = {}) { if (!documentNode) { return; } tsMorphLib = await Promise.resolve().then(() => require('ts-morph')); const tsAstHelper = new tsMorphLib.Project({ manipulationSettings: { newLineKind: process.platform === 'win32' ? tsMorphLib.NewLineKind.CarriageReturnLineFeed : tsMorphLib.NewLineKind.LineFeed, }, }); const tsFile = tsAstHelper.createSourceFile(outputPath, '', { overwrite: true, }); let { definitions } = documentNode; definitions = (0, lodash_1.sortBy)(definitions, ['kind', 'name']); definitions.forEach((item) => this.lookupDefinition(item, tsFile, mode, options)); const header = options.additionalHeader ? `${graphql_constants_1.DEFINITIONS_FILE_HEADER}\n${options.additionalHeader}\n\n` : graphql_constants_1.DEFINITIONS_FILE_HEADER; tsFile.insertText(0, header); tsFile.addTypeAlias({ name: 'Nullable', isExported: false, type: 'T | null', typeParameters: [ { name: 'T', }, ], }); return tsFile; } lookupDefinition(item, tsFile, mode, options) { switch (item.kind) { case 'SchemaDefinition': return this.lookupRootSchemaDefinition(item.operationTypes, tsFile, mode); case 'ObjectTypeDefinition': case 'ObjectTypeExtension': case 'InputObjectTypeDefinition': case 'InputObjectTypeExtension': return this.addObjectTypeDefinition(item, tsFile, mode, options); case 'InterfaceTypeDefinition': case 'InterfaceTypeExtension': return this.addObjectTypeDefinition(item, tsFile, 'interface', options); case 'ScalarTypeDefinition': case 'ScalarTypeExtension': return this.addScalarDefinition(item, tsFile, options); case 'EnumTypeDefinition': case 'EnumTypeExtension': return this.addEnumDefinition(item, tsFile, options); case 'UnionTypeDefinition': case 'UnionTypeExtension': return this.addUnionDefinition(item, tsFile); } } lookupRootSchemaDefinition(operationTypes, tsFile, mode) { const structureKind = mode === 'class' ? tsMorphLib.StructureKind.Class : tsMorphLib.StructureKind.Interface; const rootInterface = this.addClassOrInterface(tsFile, mode, { name: 'ISchema', isExported: true, kind: structureKind, }); operationTypes.forEach((item) => { if (!item) { return; } const tempOperationName = item.operation; const typeName = (0, lodash_1.get)(item, 'type.name.value'); const interfaceName = typeName || tempOperationName; const interfaceRef = this.addClassOrInterface(tsFile, mode, { name: this.addSymbolIfRoot((0, lodash_1.upperFirst)(interfaceName)), isExported: true, kind: structureKind, }); rootInterface.addProperty({ name: interfaceName, type: interfaceRef.getName(), }); }); } addObjectTypeDefinition(item, tsFile, mode, options) { const parentName = (0, lodash_1.get)(item, 'name.value'); if (!parentName) { return; } let parentRef = this.getClassOrInterface(tsFile, mode, this.addSymbolIfRoot(parentName)); if (!parentRef) { const structureKind = mode === 'class' ? tsMorphLib.StructureKind.Class : tsMorphLib.StructureKind.Interface; const isRoot = this.root.indexOf(parentName) >= 0; parentRef = this.addClassOrInterface(tsFile, mode, { name: this.addSymbolIfRoot((0, lodash_1.upperFirst)(parentName)), isExported: true, isAbstract: isRoot && mode === 'class', kind: structureKind, }); } const interfaces = (0, lodash_1.get)(item, 'interfaces'); if (interfaces) { if (mode === 'class') { this.addImplementsInterfaces(interfaces, parentRef); } else { this.addExtendInterfaces(interfaces, parentRef); } } const isObjectType = item.kind === 'ObjectTypeDefinition'; if (isObjectType && options.emitTypenameField) { parentRef.addProperty({ name: '__typename', type: `'${parentRef.getName()}'`, hasQuestionToken: true, }); } (item.fields || []).forEach((element) => { this.lookupFieldDefiniton(element, parentRef, mode, options); }); } lookupFieldDefiniton(item, parentRef, mode, options) { switch (item.kind) { case 'FieldDefinition': case 'InputValueDefinition': return this.lookupField(item, parentRef, mode, options); } } lookupField(item, parentRef, mode, options) { const propertyName = (0, lodash_1.get)(item, 'name.value'); if (!propertyName) { return; } const federatedFields = ['_entities', '_service']; if (federatedFields.includes(propertyName)) { return; } const { name: type, required } = this.getFieldTypeDefinition(item.type, options); if (!this.isRoot(parentRef.getName())) { parentRef.addProperty({ name: propertyName, type, hasQuestionToken: !required, }); return; } if (options.skipResolverArgs) { parentRef.addProperty({ name: propertyName, type: this.addSymbolIfRoot(type), hasQuestionToken: !required, }); } else { parentRef.addMethod({ isAbstract: mode === 'class', name: propertyName, returnType: `${type} | Promise<${type}>`, parameters: this.getFunctionParameters(item.arguments, options), }); } } getFieldTypeDefinition(typeNode, options) { const { required, type } = this.getNestedType(typeNode); const isArray = type.kind === 'ListType'; if (isArray) { const { type: arrayType, required: arrayTypeRequired, } = this.getNestedType((0, lodash_1.get)(type, 'type')); const typeName = this.addSymbolIfRoot((0, lodash_1.get)(arrayType, 'name.value')); const name = arrayTypeRequired ? this.getType(typeName, options) : `Nullable<${this.getType(typeName, options)}>`; return { name: required ? name + '[]' : `Nullable<${name}[]>`, required, }; } const typeName = this.addSymbolIfRoot((0, lodash_1.get)(type, 'name.value')); return { name: required ? this.getType(typeName, options) : `Nullable<${this.getType(typeName, options)}>`, required, }; } getNestedType(type) { const isNonNullType = type.kind === 'NonNullType'; if (isNonNullType) { return { type: this.getNestedType((0, lodash_1.get)(type, 'type')).type, required: isNonNullType, }; } return { type, required: false }; } getType(typeName, options) { const defaults = this.getDefaultTypes(options); const isDefault = defaults[typeName]; return isDefault ? defaults[typeName] : typeName; } getDefaultTypes(options) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; return { String: (_b = (_a = options.defaultTypeMapping) === null || _a === void 0 ? void 0 : _a.String) !== null && _b !== void 0 ? _b : 'string', Int: (_d = (_c = options.defaultTypeMapping) === null || _c === void 0 ? void 0 : _c.Int) !== null && _d !== void 0 ? _d : 'number', Boolean: (_f = (_e = options.defaultTypeMapping) === null || _e === void 0 ? void 0 : _e.Boolean) !== null && _f !== void 0 ? _f : 'boolean', ID: (_h = (_g = options.defaultTypeMapping) === null || _g === void 0 ? void 0 : _g.ID) !== null && _h !== void 0 ? _h : 'string', Float: (_k = (_j = options.defaultTypeMapping) === null || _j === void 0 ? void 0 : _j.Float) !== null && _k !== void 0 ? _k : 'number', }; } getFunctionParameters(inputs, options) { if (!inputs) { return []; } return inputs.map((element) => { const { name, required } = this.getFieldTypeDefinition(element.type, options); return { name: (0, lodash_1.get)(element, 'name.value'), type: name, hasQuestionToken: !required, kind: tsMorphLib.StructureKind.Parameter, }; }); } addScalarDefinition(item, tsFile, options) { var _a, _b; const name = (0, lodash_1.get)(item, 'name.value'); if (!name || name === 'Date') { return; } const typeMapping = (_a = options.customScalarTypeMapping) === null || _a === void 0 ? void 0 : _a[name]; const mappedTypeName = typeof typeMapping === 'string' ? typeMapping : typeMapping === null || typeMapping === void 0 ? void 0 : typeMapping.name; tsFile.addTypeAlias({ name, type: (_b = mappedTypeName !== null && mappedTypeName !== void 0 ? mappedTypeName : options.defaultScalarType) !== null && _b !== void 0 ? _b : 'any', isExported: true, }); } addExtendInterfaces(interfaces, parentRef) { if (!interfaces) { return; } interfaces.forEach((element) => { const interfaceName = (0, lodash_1.get)(element, 'name.value'); if (interfaceName) { parentRef.addExtends(interfaceName); } }); } addImplementsInterfaces(interfaces, parentRef) { if (!interfaces) { return; } interfaces.forEach((element) => { const interfaceName = (0, lodash_1.get)(element, 'name.value'); if (interfaceName) { parentRef.addImplements(interfaceName); } }); } addEnumDefinition(item, tsFile, options) { const name = (0, lodash_1.get)(item, 'name.value'); if (!name) { return; } if (options.enumsAsTypes) { const values = item.values.map((value) => `"${(0, lodash_1.get)(value, 'name.value')}"`); return tsFile.addTypeAlias({ name, type: values.join(' | '), isExported: true, }); } const members = (0, lodash_1.map)(item.values, (value) => ({ name: (0, lodash_1.get)(value, 'name.value'), value: (0, lodash_1.get)(value, 'name.value'), })); tsFile.addEnum({ name, members, isExported: true, }); } addUnionDefinition(item, tsFile) { const name = (0, lodash_1.get)(item, 'name.value'); if (!name) { return; } const types = (0, lodash_1.map)(item.types, (value) => (0, lodash_1.get)(value, 'name.value')); tsFile.addTypeAlias({ name, type: types.join(' | '), isExported: true, }); } addSymbolIfRoot(name) { return this.root.indexOf(name) >= 0 ? `I${name}` : name; } isRoot(name) { return ['IQuery', 'IMutation', 'ISubscription'].indexOf(name) >= 0; } addClassOrInterface(tsFile, mode, options) { return mode === 'class' ? tsFile.addClass(options) : tsFile.addInterface(options); } getClassOrInterface(tsFile, mode, name) { return mode === 'class' ? tsFile.getClass(name) : tsFile.getInterface(name); } }; GraphQLAstExplorer = (0, tslib_1.__decorate)([ (0, common_1.Injectable)() ], GraphQLAstExplorer); exports.GraphQLAstExplorer = GraphQLAstExplorer;