@graphql-codegen/typescript-nest
Version:
GraphQL Code Generator plugin for generating NestJS compatible types
263 lines (262 loc) • 13.1 kB
JavaScript
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`);
}
}