UNPKG

@graphql-codegen/typescript-nest

Version:

GraphQL Code Generator plugin for generating NestJS compatible types

263 lines (262 loc) 13.1 kB
import autoBind from 'auto-bind'; import { GraphQLEnumType, } from 'graphql'; import { TsVisitor, TypeScriptOperationVariablesToObject, } from '@graphql-codegen/typescript'; import { DeclarationBlock, getConfigValue, indent } from '@graphql-codegen/visitor-plugin-common'; import { ARRAY_REGEX, FIX_DECORATOR_SIGNATURE, GRAPHQL_TYPES, MAYBE_REGEX, NEST_PREFIX, NEST_SCALARS, SCALAR_REGEX, SCALARS, } from './constants.js'; import { buildTypeString, escapeString, fixDecorator, formatDecoratorOptions, getNestNullableValue, } from './utils.js'; export class NestVisitor extends TsVisitor { constructor(schema, pluginConfig, additionalConfig = {}) { super(schema, pluginConfig, { avoidOptionals: getConfigValue(pluginConfig.avoidOptionals, false), maybeValue: getConfigValue(pluginConfig.maybeValue, 'T | null'), constEnums: getConfigValue(pluginConfig.constEnums, false), enumsAsTypes: getConfigValue(pluginConfig.enumsAsTypes, false), immutableTypes: getConfigValue(pluginConfig.immutableTypes, false), declarationKind: { type: 'class', interface: 'abstract class', arguments: 'class', input: 'class', scalar: 'type', }, decoratorName: { type: 'ObjectType', interface: 'InterfaceType', arguments: 'ArgsType', field: 'Field', input: 'InputType', ...pluginConfig.decoratorName, }, decorateTypes: getConfigValue(pluginConfig.decorateTypes, undefined), disableDescriptions: getConfigValue(pluginConfig.disableDescriptions, false), ...additionalConfig, }); autoBind(this); this.typescriptVisitor = new TsVisitor(schema, pluginConfig, additionalConfig); const enumNames = Object.values(schema.getTypeMap()) .map(type => (type instanceof GraphQLEnumType ? type.name : undefined)) .filter(t => t); this.setArgumentsTransformer(new TypeScriptOperationVariablesToObject(this.scalars, this.convertName, this.config.avoidOptionals, this.config.immutableTypes, null, enumNames, this.config.enumPrefix, this.config.enumValues, undefined, undefined, 'Maybe')); this.setDeclarationBlockConfig({ enumNameValueSeparator: ' =', }); } getBaseDecoratorOptions(node) { const decoratorOptions = {}; if (node.description) { // If we have a description, add it to the decorator options decoratorOptions.description = escapeString(typeof node.description === 'string' ? node.description : node.description.value); } return decoratorOptions; } getWrapperDefinitions() { return [...super.getWrapperDefinitions(), this.getFixDecoratorDefinition()]; } getFixDecoratorDefinition() { return `${this.getExportPrefix()}${FIX_DECORATOR_SIGNATURE}`; } getMaybeWrapper() { return 'Maybe'; } buildArgumentsBlock(node) { const fieldsWithArguments = node.fields.filter(field => field.arguments && field.arguments.length > 0) || []; return fieldsWithArguments .map(field => { const name = node.name.value + (this.config.addUnderscoreToArgsType ? '_' : '') + this.convertName(field, { useTypesPrefix: false, useTypesSuffix: false, }) + 'Args'; if (this.shouldBeDecorated(name)) { return this.getArgumentsObjectTypeDefinition(node, name, field); } return this.typescriptVisitor.getArgumentsObjectTypeDefinition(node, name, field); }) .join('\n\n'); } ObjectTypeDefinition(node, key, parent) { var _a; const nodeName = typeof node.name === 'string' ? node.name : node.name.value; const isGraphQLType = GRAPHQL_TYPES.includes(nodeName); if (!isGraphQLType && !this.shouldBeDecorated(nodeName)) { return this.typescriptVisitor.ObjectTypeDefinition(node, key, parent); } const typeDecorator = this.config.decoratorName.type; const originalNode = parent[key]; const decoratorOptions = this.getBaseDecoratorOptions(originalNode); let declarationBlock; if (isGraphQLType) { declarationBlock = this.typescriptVisitor.getObjectTypeDeclarationBlock(node, originalNode); } else { declarationBlock = this.getObjectTypeDeclarationBlock(node, originalNode); // Add decorator const interfaces = ((_a = originalNode.interfaces) === null || _a === void 0 ? void 0 : _a.map(i => this.convertName(i))) || []; if (interfaces.length > 1) { decoratorOptions.implements = `[${interfaces.join(', ')}]`; } else if (interfaces.length === 1) { decoratorOptions.implements = interfaces[0]; } declarationBlock = declarationBlock.withDecorator(`@${NEST_PREFIX}.${typeDecorator}(${formatDecoratorOptions(decoratorOptions)})`); } // Remove comment added by typescript plugin declarationBlock._comment = undefined; return [declarationBlock.string, this.buildArgumentsBlock(originalNode)] .filter(f => f) .join('\n\n'); } InputObjectTypeDefinition(node) { if (!this.shouldBeDecorated(typeof node.name === 'string' ? node.name : node.name.value)) { return this.typescriptVisitor.InputObjectTypeDefinition(node); } const typeDecorator = this.config.decoratorName.input; const decoratorOptions = this.getBaseDecoratorOptions(node); const declarationBlock = this.getInputObjectDeclarationBlock(node).withDecorator(`@${NEST_PREFIX}.${typeDecorator}(${formatDecoratorOptions(decoratorOptions)})`); return declarationBlock.string; } getArgumentsObjectDeclarationBlock(node, name, field) { return new DeclarationBlock(this._declarationBlockConfig) .export() .asKind(this._parsedConfig.declarationKind.arguments) .withName(this.convertName(name)) .withComment(node.description) .withBlock(field.arguments.map(argument => this.InputValueDefinition(argument)).join('\n')); } getArgumentsObjectTypeDefinition(node, name, field) { const typeDecorator = this.config.decoratorName.arguments; const declarationBlock = this.getArgumentsObjectDeclarationBlock(node, name, field).withDecorator(`@${NEST_PREFIX}.${typeDecorator}()`); return declarationBlock.string; } InterfaceTypeDefinition(node, key, parent) { if (!this.shouldBeDecorated(typeof node.name === 'string' ? node.name : node.name.value)) { return this.typescriptVisitor.InterfaceTypeDefinition(node, key, parent); } const interfaceDecorator = this.config.decoratorName.interface; const originalNode = parent[key]; const decoratorOptions = this.getBaseDecoratorOptions(originalNode); const declarationBlock = this.getInterfaceTypeDeclarationBlock(node, originalNode).withDecorator(`@${NEST_PREFIX}.${interfaceDecorator}(${formatDecoratorOptions(decoratorOptions)})`); return [declarationBlock.string, this.buildArgumentsBlock(originalNode)] .filter(f => f) .join('\n\n'); } shouldBeDecorated(name) { if (GRAPHQL_TYPES.includes(name)) { return false; } if (!this.config.decorateTypes) { return true; } return this.config.decorateTypes.includes(name); } parseType(type) { if (typeof type === 'string') { const isNullable = !!type.match(MAYBE_REGEX); const nonNullableType = type.replace(MAYBE_REGEX, '$1'); const isArray = !!nonNullableType.match(ARRAY_REGEX); const singularType = nonNullableType.replace(ARRAY_REGEX, '$1'); const isSingularTypeNullable = !!singularType.match(MAYBE_REGEX); const singularNonNullableType = singularType.replace(MAYBE_REGEX, '$1'); const isScalar = !!singularNonNullableType.match(SCALAR_REGEX); const resolvedType = singularNonNullableType.replace(SCALAR_REGEX, (_match, type) => { if (NEST_SCALARS.includes(type)) { return `${NEST_PREFIX}.${type}`; } if (global[type]) { return type; } if (this.scalars[type]) { return this.scalars[type]; } throw new Error(`Unknown scalar type ${type}`); }); return { type: resolvedType, isNullable, isArray, isScalar, isItemsNullable: isArray && isSingularTypeNullable, }; } else { const typeNode = type; if (typeNode.kind === 'NamedType') { return { type: typeNode.name.value, isNullable: true, isArray: false, isItemsNullable: false, isScalar: SCALARS.includes(typeNode.name.value), }; } if (typeNode.kind === 'NonNullType') { return { ...this.parseType(typeNode.type), isNullable: false, }; } if (typeNode.kind === 'ListType') { return { ...this.parseType(typeNode.type), isArray: true, isNullable: true, }; } } throw new Error(`Unknown type ${type}`); } FieldDefinition(node, key, parent, _path, ancestors) { var _a, _b; const parentName = (_b = (_a = ancestors === null || ancestors === void 0 ? void 0 : ancestors[ancestors.length - 1]) === null || _a === void 0 ? void 0 : _a.name) === null || _b === void 0 ? void 0 : _b.value; if (!this.shouldBeDecorated(parentName)) { return this.typescriptVisitor.FieldDefinition(node, key, parent); } const fieldDecorator = this.config.decoratorName.field; let typeString = node.type; const type = this.parseType(typeString); const decoratorOptions = this.getBaseDecoratorOptions(node); const nullableValue = getNestNullableValue(type); if (nullableValue) { decoratorOptions.nullable = nullableValue; } const decorator = '\n' + indent(`@${NEST_PREFIX}.${fieldDecorator}(type => ${type.isArray ? `[${type.type}]` : type.type}${formatDecoratorOptions(decoratorOptions, false)})`) + '\n'; typeString = fixDecorator(type, typeString); return (decorator + indent(`${this.config.immutableTypes ? 'readonly ' : ''}${node.name}${type.isNullable ? '?' : '!'}: ${typeString};`)); } InputValueDefinition(node, key, parent, path, ancestors) { const parentName = ancestors === null || ancestors === void 0 ? void 0 : ancestors[ancestors.length - 1].name.value; if (parent && !this.shouldBeDecorated(parentName)) { return this.typescriptVisitor.InputValueDefinition(node, key, parent, path, ancestors); } const fieldDecorator = this.config.decoratorName.field; const rawType = node.type; const type = this.parseType(rawType); const nestType = type.isScalar && NEST_SCALARS.includes(type.type) ? `${NEST_PREFIX}.${type.type}` : type.type; const decoratorOptions = this.getBaseDecoratorOptions(node); const nullableValue = getNestNullableValue(type); if (nullableValue) { decoratorOptions.nullable = nullableValue; } const decorator = '\n' + indent(`@${NEST_PREFIX}.${fieldDecorator}(type => ${type.isArray ? `[${nestType}]` : nestType}${formatDecoratorOptions(decoratorOptions, false)})`) + '\n'; const nameString = node.name.kind ? node.name.value : node.name; const typeString = rawType.kind ? buildTypeString(type) : fixDecorator(type, rawType); return (decorator + indent(`${this.config.immutableTypes ? 'readonly ' : ''}${nameString}${type.isNullable ? '?' : '!'}: ${typeString};`)); } EnumTypeDefinition(node) { if (!this.shouldBeDecorated(typeof node.name === 'string' ? node.name : node.name.value)) { return this.typescriptVisitor.EnumTypeDefinition(node); } return (super.EnumTypeDefinition(node) + `${NEST_PREFIX}.registerEnumType(${this.convertName(node)}, { name: '${this.convertName(node)}' });\n`); } }