UNPKG

@graphql-codegen/visitor-plugin-common

Version:
1,019 lines 50.3 kB
import { ApolloFederation, checkObjectTypeFederationDetails, getBaseType } from '@graphql-codegen/plugin-helpers';
import { getRootTypeNames } from '@graphql-tools/utils';
import autoBind from 'auto-bind';
import { GraphQLObjectType, isEnumType, isInterfaceType, isNonNullType, isObjectType, isUnionType, } from 'graphql';
import { BaseVisitor } from './base-visitor.js';
import { parseEnumValues } from './enum-values.js';
import { buildMapperImport, parseMapper, transformMappers } from './mappers.js';
import { DEFAULT_SCALARS } from './scalars.js';
import { buildScalarsFromConfig, DeclarationBlock, getBaseTypeNode, getConfigValue, indent, OMIT_TYPE, REQUIRE_FIELDS_TYPE, stripMapperTypeInterpolation, wrapTypeWithModifiers, } from './utils.js';
import { OperationVariablesToObject } from './variables-to-object.js';
import { normalizeAvoidOptionals } from './avoid-optionals.js';
export class BaseResolversVisitor extends BaseVisitor {
    constructor(rawConfig, additionalConfig, _schema, defaultScalars = DEFAULT_SCALARS) {
        super(rawConfig, {
            immutableTypes: getConfigValue(rawConfig.immutableTypes, false),
            optionalResolveType: getConfigValue(rawConfig.optionalResolveType, false),
            enumPrefix: getConfigValue(rawConfig.enumPrefix, true),
            enumSuffix: getConfigValue(rawConfig.enumSuffix, true),
            federation: getConfigValue(rawConfig.federation, false),
            resolverTypeWrapperSignature: getConfigValue(rawConfig.resolverTypeWrapperSignature, 'Promise<T> | T'),
            enumValues: parseEnumValues({
                schema: _schema,
                mapOrStr: rawConfig.enumValues,
            }),
            addUnderscoreToArgsType: getConfigValue(rawConfig.addUnderscoreToArgsType, false),
            onlyResolveTypeForInterfaces: getConfigValue(rawConfig.onlyResolveTypeForInterfaces, false),
            contextType: parseMapper(rawConfig.contextType || 'any', 'ContextType'),
            fieldContextTypes: getConfigValue(rawConfig.fieldContextTypes, []),
            directiveContextTypes: getConfigValue(rawConfig.directiveContextTypes, []),
            resolverTypeSuffix: getConfigValue(rawConfig.resolverTypeSuffix, 'Resolvers'),
            allResolversTypeName: getConfigValue(rawConfig.allResolversTypeName, 'Resolvers'),
            rootValueType: parseMapper(rawConfig.rootValueType || '{}', 'RootValueType'),
            namespacedImportName: getConfigValue(rawConfig.namespacedImportName, ''),
            avoidOptionals: normalizeAvoidOptionals(rawConfig.avoidOptionals),
            defaultMapper: rawConfig.defaultMapper
                ? parseMapper(rawConfig.defaultMapper || 'any', 'DefaultMapperType')
                : null,
            mappers: transformMappers(rawConfig.mappers || {}, rawConfig.mapperTypeSuffix),
            scalars: buildScalarsFromConfig(_schema, rawConfig, defaultScalars),
            internalResolversPrefix: getConfigValue(rawConfig.internalResolversPrefix, '__'),
            generateInternalResolversIfNeeded: {
                __resolveReference: rawConfig.generateInternalResolversIfNeeded?.__resolveReference ?? false,
                __isTypeOf: rawConfig.generateInternalResolversIfNeeded?.__isTypeOf ?? false,
            },
            resolversNonOptionalTypename: normalizeResolversNonOptionalTypename(getConfigValue(rawConfig.resolversNonOptionalTypename, false)),
            ...additionalConfig,
        });
        this._schema = _schema;
        this._declarationBlockConfig = {};
        this._collectedResolvers = {};
        this._collectedDirectiveResolvers = {};
        this._usedMappers = {};
        this._resolversTypes = {};
        this._resolversParentTypes = {};
        this._hasReferencedResolversUnionTypes = false;
        this._hasReferencedResolversInterfaceTypes = false;
        this._resolversUnionTypes = {};
        this._resolversInterfaceTypes = {};
        this._objectTypesWithIsTypeOf = {};
        this._rootTypeNames = new Set();
        this._globalDeclarations = new Set();
        this._hasScalars = false;
        this._checkedTypesWithNestedAbstractTypes = {};
        this._shouldMapType = {};
        autoBind(this);
        this._federation = new ApolloFederation({ enabled: this.config.federation, schema: this.schema });
        this._rootTypeNames = getRootTypeNames(_schema);
        this._variablesTransformer = new OperationVariablesToObject(this.scalars, this.convertName, this.config.namespacedImportName);
        this._resolversTypes = this.createResolversFields({
            applyWrapper: type => this.applyResolverTypeWrapper(type),
            clearWrapper: type => this.clearResolverTypeWrapper(type),
            getTypeToUse: name => this.getTypeToUse(name),
            currentType: 'ResolversTypes',
        });
        this._resolversParentTypes = this.createResolversFields({
            applyWrapper: type => type,
            clearWrapper: type => type,
            getTypeToUse: name => this.getParentTypeToUse(name),
            currentType: 'ResolversParentTypes',
            shouldInclude: namedType => !isEnumType(namedType),
        });
        this._resolversUnionTypes = this.createResolversUnionTypes();
        this._resolversInterfaceTypes = this.createResolversInterfaceTypes();
        this._fieldContextTypeMap = this.createFieldContextTypeMap();
        this._directiveContextTypesMap = this.createDirectivedContextType();
        this._directiveResolverMappings = rawConfig.directiveResolverMappings ?? {};
    }
    getResolverTypeWrapperSignature() {
        return `export type ResolverTypeWrapper<T> = ${this.config.resolverTypeWrapperSignature};`;
    }
    shouldMapType(type, duringCheck = []) {
        if (type.name.startsWith('__') || this.config.scalars[type.name]) {
            return false;
        }
        if (this.config.mappers[type.name]) {
            return true;
        }
        if (isObjectType(type) || isInterfaceType(type)) {
            const fields = type.getFields();
            return Object.keys(fields)
                .filter(fieldName => {
                const field = fields[fieldName];
                const fieldType = getBaseType(field.type);
                return !duringCheck.includes(fieldType.name);
            })
                .some(fieldName => {
                const field = fields[fieldName];
                const fieldType = getBaseType(field.type);
                if (this._shouldMapType[fieldType.name] !== undefined) {
                    return this._shouldMapType[fieldType.name];
                }
                if (this.config.mappers[type.name]) {
                    return true;
                }
                duringCheck.push(type.name);
                const innerResult = this.shouldMapType(fieldType, duringCheck);
                return innerResult;
            });
        }
        return false;
    }
    convertName(node, options, applyNamespacedImport = false) {
        const sourceType = super.convertName(node, options);
        return `${applyNamespacedImport && this.config.namespacedImportName ? this.config.namespacedImportName + '.' : ''}${sourceType}`;
    }
    // Kamil: this one is heeeeavvyyyy
    createResolversFields({ applyWrapper, clearWrapper, getTypeToUse, currentType, shouldInclude, }) {
        const allSchemaTypes = this._schema.getTypeMap();
        const typeNames = this._federation.filterTypeNames(Object.keys(allSchemaTypes));
        // avoid checking all types recursively if we have no `mappers` defined
        if (Object.keys(this.config.mappers).length > 0) {
            for (const typeName of typeNames) {
                if (this._shouldMapType[typeName] === undefined) {
                    const schemaType = allSchemaTypes[typeName];
                    this._shouldMapType[typeName] = this.shouldMapType(schemaType);
                }
            }
        }
        return typeNames.reduce((prev, typeName) => {
            const schemaType = allSchemaTypes[typeName];
            if (typeName.startsWith('__') || (shouldInclude && !shouldInclude(schemaType))) {
                return prev;
            }
            const isRootType = this._rootTypeNames.has(typeName);
            const isMapped = this.config.mappers[typeName];
            const isScalar = this.config.scalars[typeName];
            const hasDefaultMapper = !!this.config.defaultMapper?.type;
            if (isRootType) {
                prev[typeName] = applyWrapper(this.config.rootValueType.type);
                return prev;
            }
            if (isMapped && this.config.mappers[typeName].type && !hasPlaceholder(this.config.mappers[typeName].type)) {
                this.markMapperAsUsed(typeName);
                prev[typeName] = applyWrapper(this.config.mappers[typeName].type);
            }
            else if (isEnumType(schemaType) && this.config.enumValues[typeName]) {
                prev[typeName] =
                    this.config.enumValues[typeName].sourceIdentifier ||
                        this.convertName(this.config.enumValues[typeName].typeIdentifier);
            }
            else if (hasDefaultMapper && !hasPlaceholder(this.config.defaultMapper.type)) {
                prev[typeName] = applyWrapper(this.config.defaultMapper.type);
            }
            else if (isScalar) {
                prev[typeName] = applyWrapper(this._getScalar(typeName));
            }
            else if (isInterfaceType(schemaType)) {
                this._hasReferencedResolversInterfaceTypes = true;
                const type = this.convertName('ResolversInterfaceTypes');
                const generic = this.convertName(currentType);
                prev[typeName] = applyWrapper(`${type}<${generic}>['${typeName}']`);
                return prev;
            }
            else if (isUnionType(schemaType)) {
                this._hasReferencedResolversUnionTypes = true;
                const type = this.convertName('ResolversUnionTypes');
                const generic = this.convertName(currentType);
                prev[typeName] = applyWrapper(`${type}<${generic}>['${typeName}']`);
            }
            else if (isEnumType(schemaType)) {
                prev[typeName] = this.convertName(typeName, {
                    useTypesPrefix: this.config.enumPrefix,
                    useTypesSuffix: this.config.enumSuffix,
                }, true);
            }
            else {
                prev[typeName] = this.convertName(typeName, {}, true);
                if (prev[typeName] !== 'any' && isObjectType(schemaType)) {
                    const relevantFields = this.getRelevantFieldsToOmit({
                        schemaType,
                        getTypeToUse,
                        shouldInclude,
                    });
                    // If relevantFields, puts ResolverTypeWrapper on top of an entire type
                    let internalType = relevantFields.length > 0 ? this.replaceFieldsInType(prev[typeName], relevantFields) : prev[typeName];
                    if (isMapped) {
                        // replace the placeholder with the actual type
                        if (hasPlaceholder(internalType)) {
                            internalType = replacePlaceholder(internalType, typeName);
                        }
                        if (this.config.mappers[typeName].type && hasPlaceholder(this.config.mappers[typeName].type)) {
                            internalType = replacePlaceholder(this.config.mappers[typeName].type, internalType);
                        }
                    }
                    prev[typeName] = applyWrapper(internalType);
                }
            }
            if (!isMapped && hasDefaultMapper && hasPlaceholder(this.config.defaultMapper.type)) {
                const originalTypeName = isScalar ? this._getScalar(typeName) : prev[typeName];
                if (isUnionType(schemaType)) {
                    // Don't clear ResolverTypeWrapper from Unions
                    prev[typeName] = replacePlaceholder(this.config.defaultMapper.type, originalTypeName);
                }
                else {
                    const name = clearWrapper(originalTypeName);
                    const replaced = replacePlaceholder(this.config.defaultMapper.type, name);
                    prev[typeName] = applyWrapper(replaced);
                }
            }
            return prev;
        }, {});
    }
    replaceFieldsInType(typeName, relevantFields) {
        this._globalDeclarations.add(OMIT_TYPE);
        return `Omit<${typeName}, ${relevantFields.map(f => `'${f.fieldName}'`).join(' | ')}> & { ${relevantFields
            .map(f => `${f.fieldName}${f.addOptionalSign ? '?' : ''}: ${f.replaceWithType}`)
            .join(', ')} }`;
    }
    applyMaybe(str) {
        const namespacedImportPrefix = this.config.namespacedImportName ? this.config.namespacedImportName + '.' : '';
        return `${namespacedImportPrefix}Maybe<${str}>`;
    }
    applyResolverTypeWrapper(str) {
        return `ResolverTypeWrapper<${this.clearResolverTypeWrapper(str)}>`;
    }
    clearMaybe(str) {
        const namespacedImportPrefix = this.config.namespacedImportName ? this.config.namespacedImportName + '.' : '';
        if (str.startsWith(`${namespacedImportPrefix}Maybe<`)) {
            const maybeRe = new RegExp(`${namespacedImportPrefix.replace('.', '\\.')}Maybe<(.*?)>$`);
            return str.replace(maybeRe, '$1');
        }
        return str;
    }
    clearResolverTypeWrapper(str) {
        if (str.startsWith('ResolverTypeWrapper<')) {
            return str.replace(/ResolverTypeWrapper<(.*?)>$/, '$1');
        }
        return str;
    }
    wrapWithArray(t) {
        if (this.config.immutableTypes) {
            return `ReadonlyArray<${t}>`;
        }
        return `Array<${t}>`;
    }
    createResolversUnionTypes() {
        if (!this._hasReferencedResolversUnionTypes) {
            return {};
        }
        const allSchemaTypes = this._schema.getTypeMap();
        const typeNames = this._federation.filterTypeNames(Object.keys(allSchemaTypes));
        const unionTypes = typeNames.reduce((res, typeName) => {
            const schemaType = allSchemaTypes[typeName];
            if (isUnionType(schemaType)) {
                const { unionMember, excludeTypes } = this.config.resolversNonOptionalTypename;
                const memberTypes = schemaType.getTypes();
                for (const type of memberTypes) {
                    this._objectTypesWithIsTypeOf[type.name] = true;
                }
                res[typeName] = this.getAbstractMembersType({
                    typeName,
                    memberTypes,
                    isTypenameNonOptional: unionMember && !excludeTypes?.includes(typeName),
                });
            }
            return res;
        }, {});
        return unionTypes;
    }
    createResolversInterfaceTypes() {
        if (!this._hasReferencedResolversInterfaceTypes) {
            return {};
        }
        const allSchemaTypes = this._schema.getTypeMap();
        const typeNames = this._federation.filterTypeNames(Object.keys(allSchemaTypes));
        const interfaceTypes = typeNames.reduce((res, typeName) => {
            const schemaType = allSchemaTypes[typeName];
            if (isInterfaceType(schemaType)) {
                const allTypesMap = this._schema.getTypeMap();
                const implementingTypes = [];
                for (const graphqlType of Object.values(allTypesMap)) {
                    if (graphqlType instanceof GraphQLObjectType) {
                        const allInterfaces = graphqlType.getInterfaces();
                        if (allInterfaces.some(int => int.name === schemaType.name)) {
                            implementingTypes.push(graphqlType);
                        }
                    }
                }
                const { interfaceImplementingType, excludeTypes } = this.config.resolversNonOptionalTypename;
                for (const type of implementingTypes) {
                    this._objectTypesWithIsTypeOf[type.name] = true;
                }
                res[typeName] = this.getAbstractMembersType({
                    typeName,
                    memberTypes: implementingTypes,
                    isTypenameNonOptional: interfaceImplementingType && !excludeTypes?.includes(typeName),
                });
            }
            return res;
        }, {});
        return interfaceTypes;
    }
    /**
     * Function to generate the types of Abstract Type Members i.e. Union Members or Interface Implementing Types
     */
    getAbstractMembersType({ typeName, memberTypes, isTypenameNonOptional, }) {
        const result = memberTypes
            .map(type => {
            const isTypeMapped = this.config.mappers[type.name];
            // 1. If mapped without placehoder, just use it without doing extra checks
            if (isTypeMapped && !hasPlaceholder(isTypeMapped.type)) {
                return { typename: type.name, typeValue: isTypeMapped.type };
            }
            // 2. Work out value for type
            // 2a. By default, use the typescript type
            let typeValue = this.convertName(type.name, {}, true);
            // 2b. Find fields to Omit if needed.
            //  - If no field to Omit, "type with maybe Omit" is typescript type i.e. no Omit
            //  - If there are fields to Omit, keep track of these "type with maybe Omit" to replace in original unionMemberValue
            const fieldsToOmit = this.getRelevantFieldsToOmit({
                schemaType: type,
                getTypeToUse: baseType => `_RefType['${baseType}']`,
            });
            if (fieldsToOmit.length > 0) {
                typeValue = this.replaceFieldsInType(typeValue, fieldsToOmit);
            }
            // 2c. If type is mapped with placeholder, use the "type with maybe Omit" as {T}
            if (isTypeMapped && hasPlaceholder(isTypeMapped.type)) {
                return { typename: type.name, typeValue: replacePlaceholder(isTypeMapped.type, typeValue) };
            }
            // 2d. If has default mapper with placeholder, use the "type with maybe Omit" as {T}
            const hasDefaultMapper = !!this.config.defaultMapper?.type;
            const isScalar = this.config.scalars[typeName];
            if (hasDefaultMapper && hasPlaceholder(this.config.defaultMapper.type)) {
                const finalTypename = isScalar ? this._getScalar(typeName) : typeValue;
                return {
                    typename: type.name,
                    typeValue: replacePlaceholder(this.config.defaultMapper.type, finalTypename),
                };
            }
            return { typename: type.name, typeValue };
        })
            .map(({ typename, typeValue }) => {
            const nonOptionalTypenameModifier = isTypenameNonOptional ? ` & { __typename: '${typename}' }` : '';
            return `( ${typeValue}${nonOptionalTypenameModifier} )`; // Must wrap every type in explicit "( )" to separate them
        })
            .join(' | ') || 'never';
        return result;
    }
    createFieldContextTypeMap() {
        return this.config.fieldContextTypes.reduce((prev, fieldContextType) => {
            const isScoped = fieldContextType.includes('\\#');
            if (fieldContextType.includes('\\#')) {
                fieldContextType = fieldContextType.replace('\\#', '');
            }
            const items = fieldContextType.split('#');
            if (items.length === 3) {
                const [path, source, contextTypeName] = items;
                const sourceStr = isScoped ? `\\#${source}` : source;
                return { ...prev, [path]: parseMapper(`${sourceStr}#${contextTypeName}`) };
            }
            const [path, contextType] = items;
            return { ...prev, [path]: parseMapper(contextType) };
        }, {});
    }
    createDirectivedContextType() {
        return this.config.directiveContextTypes.reduce((prev, fieldContextType) => {
            const isScoped = fieldContextType.includes('\\#');
            if (fieldContextType.includes('\\#')) {
                fieldContextType = fieldContextType.replace('\\#', '');
            }
            const items = fieldContextType.split('#');
            if (items.length === 3) {
                const [path, source, contextTypeName] = items;
                const sourceStr = isScoped ? `\\#${source}` : source;
                return { ...prev, [path]: parseMapper(`${sourceStr}#${contextTypeName}`) };
            }
            const [path, contextType] = items;
            return { ...prev, [path]: parseMapper(contextType) };
        }, {});
    }
    buildResolversTypes() {
        const declarationKind = 'type';
        return new DeclarationBlock(this._declarationBlockConfig)
            .export()
            .asKind(declarationKind)
            .withName(this.convertName('ResolversTypes'))
            .withComment('Mapping between all available schema types and the resolvers types')
            .withBlock(Object.keys(this._resolversTypes)
            .map(typeName => indent(`${typeName}: ${this._resolversTypes[typeName]}${this.getPunctuation(declarationKind)}`))
            .join('\n')).string;
    }
    buildResolversParentTypes() {
        const declarationKind = 'type';
        return new DeclarationBlock(this._declarationBlockConfig)
            .export()
            .asKind(declarationKind)
            .withName(this.convertName('ResolversParentTypes'))
            .withComment('Mapping between all available schema types and the resolvers parents')
            .withBlock(Object.keys(this._resolversParentTypes)
            .map(typeName => indent(`${typeName}: ${this._resolversParentTypes[typeName]}${this.getPunctuation(declarationKind)}`))
            .join('\n')).string;
    }
    buildResolversUnionTypes() {
        if (Object.keys(this._resolversUnionTypes).length === 0) {
            return '';
        }
        const declarationKind = 'type';
        return new DeclarationBlock(this._declarationBlockConfig)
            .export()
            .asKind(declarationKind)
            .withName(this.convertName('ResolversUnionTypes'), `<_RefType extends Record<string, unknown>>`)
            .withComment('Mapping of union types')
            .withBlock(Object.entries(this._resolversUnionTypes)
            .map(([typeName, value]) => indent(`${typeName}: ${value}${this.getPunctuation(declarationKind)}`))
            .join('\n')).string;
    }
    buildResolversInterfaceTypes() {
        if (Object.keys(this._resolversInterfaceTypes).length === 0) {
            return '';
        }
        const declarationKind = 'type';
        return new DeclarationBlock(this._declarationBlockConfig)
            .export()
            .asKind(declarationKind)
            .withName(this.convertName('ResolversInterfaceTypes'), `<_RefType extends Record<string, unknown>>`)
            .withComment('Mapping of interface types')
            .withBlock(Object.entries(this._resolversInterfaceTypes)
            .map(([typeName, value]) => indent(`${typeName}: ${value}${this.getPunctuation(declarationKind)}`))
            .join('\n')).string;
    }
    get schema() {
        return this._schema;
    }
    get defaultMapperType() {
        return this.config.defaultMapper.type;
    }
    get unusedMappers() {
        return Object.keys(this.config.mappers).filter(name => !this._usedMappers[name]);
    }
    get globalDeclarations() {
        return Array.from(this._globalDeclarations);
    }
    isMapperImported(groupedMappers, identifier, source) {
        const exists = groupedMappers[source] ? !!groupedMappers[source].find(m => m.identifier === identifier) : false;
        const existsFromEnums = !!Object.keys(this.config.enumValues)
            .map(key => this.config.enumValues[key])
            .find(o => o.sourceFile === source && o.typeIdentifier === identifier);
        return exists || existsFromEnums;
    }
    get mappersImports() {
        const groupedMappers = {};
        const addMapper = (source, identifier, asDefault) => {
            if (!this.isMapperImported(groupedMappers, identifier, source)) {
                groupedMappers[source] ||= [];
                groupedMappers[source].push({ identifier, asDefault });
            }
        };
        for (const { mapper } of Object.keys(this.config.mappers)
            .map(gqlTypeName => ({ gqlType: gqlTypeName, mapper: this.config.mappers[gqlTypeName] }))
            .filter(({ mapper }) => mapper.isExternal)) {
            const externalMapper = mapper;
            const identifier = stripMapperTypeInterpolation(externalMapper.import);
            addMapper(externalMapper.source, identifier, externalMapper.default);
        }
        if (this.config.contextType.isExternal) {
            addMapper(this.config.contextType.source, this.config.contextType.import, this.config.contextType.default);
        }
        if (this.config.rootValueType.isExternal) {
            addMapper(this.config.rootValueType.source, this.config.rootValueType.import, this.config.rootValueType.default);
        }
        if (this.config.defaultMapper?.isExternal) {
            const identifier = stripMapperTypeInterpolation(this.config.defaultMapper.import);
            addMapper(this.config.defaultMapper.source, identifier, this.config.defaultMapper.default);
        }
        for (const parsedMapper of Object.values(this._fieldContextTypeMap)) {
            if (parsedMapper.isExternal) {
                addMapper(parsedMapper.source, parsedMapper.import, parsedMapper.default);
            }
        }
        for (const parsedMapper of Object.values(this._directiveContextTypesMap)) {
            if (parsedMapper.isExternal) {
                addMapper(parsedMapper.source, parsedMapper.import, parsedMapper.default);
            }
        }
        return Object.keys(groupedMappers)
            .map(source => buildMapperImport(source, groupedMappers[source], this.config.useTypeImports))
            .filter(Boolean);
    }
    setDeclarationBlockConfig(config) {
        this._declarationBlockConfig = config;
    }
    setVariablesTransformer(variablesTransfomer) {
        this._variablesTransformer = variablesTransfomer;
    }
    hasScalars() {
        return this._hasScalars;
    }
    hasFederation() {
        return Object.keys(this._federation.getMeta()).length > 0;
    }
    getRootResolver() {
        const name = this.convertName(this.config.allResolversTypeName);
        const declarationKind = 'type';
        const contextType = `<ContextType = ${this.config.contextType.type}>`;
        const userDefinedTypes = {};
        const content = [
            new DeclarationBlock(this._declarationBlockConfig)
                .export()
                .asKind(declarationKind)
                .withName(name, contextType)
                .withBlock(Object.keys(this._collectedResolvers)
                .map(schemaTypeName => {
                const resolverType = this._collectedResolvers[schemaTypeName];
                if (resolverType.baseGeneratedTypename) {
                    userDefinedTypes[schemaTypeName] = {
                        name: resolverType.baseGeneratedTypename,
                    };
                    const federationMeta = this._federation.getMeta()[schemaTypeName];
                    if (federationMeta) {
                        userDefinedTypes[schemaTypeName].federation = federationMeta;
                    }
                }
                return indent(this.formatRootResolver(schemaTypeName, resolverType.typename, declarationKind));
            })
                .join('\n')).string,
        ].join('\n');
        return {
            content,
            generatedResolverTypes: {
                resolversMap: { name },
                userDefined: userDefinedTypes,
            },
        };
    }
    formatRootResolver(schemaTypeName, resolverType, declarationKind) {
        return `${schemaTypeName}${this.config.avoidOptionals.resolvers ? '' : '?'}: ${resolverType}${this.getPunctuation(declarationKind)}`;
    }
    getAllDirectiveResolvers() {
        if (Object.keys(this._collectedDirectiveResolvers).length) {
            const declarationKind = 'type';
            const name = this.convertName('DirectiveResolvers');
            const contextType = `<ContextType = ${this.config.contextType.type}>`;
            return [
                new DeclarationBlock(this._declarationBlockConfig)
                    .export()
                    .asKind(declarationKind)
                    .withName(name, contextType)
                    .withBlock(Object.keys(this._collectedDirectiveResolvers)
                    .map(schemaTypeName => {
                    const resolverType = this._collectedDirectiveResolvers[schemaTypeName];
                    return indent(this.formatRootResolver(schemaTypeName, resolverType, declarationKind));
                })
                    .join('\n')).string,
            ].join('\n');
        }
        return '';
    }
    Name(node) {
        return node.value;
    }
    ListType(node) {
        const asString = node.type;
        return this.wrapWithArray(asString);
    }
    _getScalar(name) {
        return `${this.config.namespacedImportName ? this.config.namespacedImportName + '.' : ''}Scalars['${name}']['output']`;
    }
    NamedType(node) {
        const nameStr = node.name;
        if (this.config.scalars[nameStr]) {
            return this._getScalar(nameStr);
        }
        return this.convertName(node, null, true);
    }
    NonNullType(node) {
        const asString = node.type;
        return asString;
    }
    markMapperAsUsed(name) {
        this._usedMappers[name] = true;
    }
    getTypeToUse(name) {
        const resolversType = this.convertName('ResolversTypes');
        return `${resolversType}['${name}']`;
    }
    getParentTypeToUse(name) {
        const resolversType = this.convertName('ResolversParentTypes');
        return `${resolversType}['${name}']`;
    }
    getParentTypeForSignature(_node) {
        return 'ParentType';
    }
    transformParentGenericType(parentType) {
        return `ParentType extends ${parentType} = ${parentType}`;
    }
    FieldDefinition(node, key, parent) {
        const hasArguments = node.arguments && node.arguments.length > 0;
        const declarationKind = 'type';
        return (parentName, avoidResolverOptionals) => {
            const original = parent[key];
            const baseType = getBaseTypeNode(original.type);
            const realType = baseType.name.value;
            const parentType = this.schema.getType(parentName);
            if (this._federation.skipField({ fieldNode: original, parentType })) {
                return null;
            }
            const contextType = this.getContextType(parentName, node);
            const typeToUse = this.getTypeToUse(realType);
            const mappedType = this._variablesTransformer.wrapAstTypeWithModifiers(typeToUse, original.type);
            const subscriptionType = this._schema.getSubscriptionType();
            const isSubscriptionType = subscriptionType && subscriptionType.name === parentName;
            let argsType = hasArguments
                ? this.convertName(parentName +
                    (this.config.addUnderscoreToArgsType ? '_' : '') +
                    this.convertName(node.name, {
                        useTypesPrefix: false,
                        useTypesSuffix: false,
                    }) +
                    'Args', {
                    useTypesPrefix: true,
                }, true)
                : null;
            const avoidInputsOptionals = this.config.avoidOptionals.inputValue;
            if (argsType !== null) {
                const argsToForceRequire = original.arguments.filter(arg => !!arg.defaultValue || arg.type.kind === 'NonNullType');
                if (argsToForceRequire.length > 0) {
                    argsType = this.applyRequireFields(argsType, argsToForceRequire);
                }
                else if (original.arguments.length > 0 && avoidInputsOptionals !== true) {
                    argsType = this.applyOptionalFields(argsType, original.arguments);
                }
            }
            const parentTypeSignature = this._federation.transformParentType({
                fieldNode: original,
                parentType,
                parentTypeSignature: this.getParentTypeForSignature(node),
            });
            const mappedTypeKey = isSubscriptionType ? `${mappedType}, "${node.name}"` : mappedType;
            const directiveMappings = node.directives
                ?.map(directive => this._directiveResolverMappings[directive.name])
                .filter(Boolean)
                .reverse() ?? [];
            const resolverType = isSubscriptionType ? 'SubscriptionResolver' : directiveMappings[0] ?? 'Resolver';
            const signature = {
                name: node.name,
                modifier: avoidResolverOptionals ? '' : '?',
                type: resolverType,
                genericTypes: [mappedTypeKey, parentTypeSignature, contextType, argsType].filter(f => f),
            };
            if (this._federation.isResolveReferenceField(node)) {
                if (this.config.generateInternalResolversIfNeeded.__resolveReference) {
                    const federationDetails = checkObjectTypeFederationDetails(parentType.astNode, this._schema);
                    if (!federationDetails || federationDetails.resolvableKeyDirectives.length === 0) {
                        return '';
                    }
                    signature.modifier = ''; // if a federation type has resolvable @key, then it should be required
                }
                this._federation.setMeta(parentType.name, { hasResolveReference: true });
                signature.type = 'ReferenceResolver';
                if (signature.genericTypes.length >= 3) {
                    signature.genericTypes = signature.genericTypes.slice(0, 3);
                }
            }
            return indent(`${signature.name}${signature.modifier}: ${signature.type}<${signature.genericTypes.join(', ')}>${this.getPunctuation(declarationKind)}`);
        };
    }
    getFieldContextType(parentName, node) {
        if (this._fieldContextTypeMap[`${parentName}.${node.name}`]) {
            return this._fieldContextTypeMap[`${parentName}.${node.name}`].type;
        }
        return 'ContextType';
    }
    getContextType(parentName, node) {
        let contextType = this.getFieldContextType(parentName, node);
        for (const directive of node.directives) {
            const name = directive.name;
            const directiveMap = this._directiveContextTypesMap[name];
            if (directiveMap) {
                contextType = `${directiveMap.type}<${contextType}>`;
            }
        }
        return contextType;
    }
    applyRequireFields(argsType, fields) {
        this._globalDeclarations.add(REQUIRE_FIELDS_TYPE);
        return `RequireFields<${argsType}, ${fields.map(f => `'${f.name.value}'`).join(' | ')}>`;
    }
    applyOptionalFields(argsType, _fields) {
        return `Partial<${argsType}>`;
    }
    ObjectTypeDefinition(node) {
        const declarationKind = 'type';
        const name = this.convertName(node, {
            suffix: this.config.resolverTypeSuffix,
        });
        const typeName = node.name;
        const parentType = this.getParentTypeToUse(typeName);
        const rootType = (() => {
            if (this.schema.getQueryType()?.name === typeName) {
                return 'query';
            }
            if (this.schema.getMutationType()?.name === typeName) {
                return 'mutation';
            }
            if (this.schema.getSubscriptionType()?.name === typeName) {
                return 'subscription';
            }
            return false;
        })();
        const fieldsContent = node.fields.map(f => {
            return f(typeName, (rootType === 'query' && this.config.avoidOptionals.query) ||
                (rootType === 'mutation' && this.config.avoidOptionals.mutation) ||
                (rootType === 'subscription' && this.config.avoidOptionals.subscription) ||
                (rootType === false && this.config.avoidOptionals.resolvers));
        });
        if (!rootType &&
            (!this.config.generateInternalResolversIfNeeded.__isTypeOf ||
                (this.config.generateInternalResolversIfNeeded.__isTypeOf && this._objectTypesWithIsTypeOf[typeName]))) {
            fieldsContent.push(indent(`${this.config.internalResolversPrefix}isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>${this.getPunctuation(declarationKind)}`));
        }
        const block = new DeclarationBlock(this._declarationBlockConfig)
            .export()
            .asKind(declarationKind)
            .withName(name, `<ContextType = ${this.config.contextType.type}, ${this.transformParentGenericType(parentType)}>`)
            .withBlock(fieldsContent.join('\n'));
        this._collectedResolvers[node.name] = {
            typename: name + '<ContextType>',
            baseGeneratedTypename: name,
        };
        return block.string;
    }
    UnionTypeDefinition(node, key, parent) {
        const declarationKind = 'type';
        const name = this.convertName(node, {
            suffix: this.config.resolverTypeSuffix,
        });
        const originalNode = parent[key];
        const possibleTypes = originalNode.types
            .map(node => node.name.value)
            .map(f => `'${f}'`)
            .join(' | ');
        this._collectedResolvers[node.name] = {
            typename: name + '<ContextType>',
            baseGeneratedTypename: name,
        };
        const parentType = this.getParentTypeToUse(node.name);
        return new DeclarationBlock(this._declarationBlockConfig)
            .export()
            .asKind(declarationKind)
            .withName(name, `<ContextType = ${this.config.contextType.type}, ${this.transformParentGenericType(parentType)}>`)
            .withBlock(indent(`${this.config.internalResolversPrefix}resolveType${this.config.optionalResolveType ? '?' : ''}: TypeResolveFn<${possibleTypes}, ParentType, ContextType>${this.getPunctuation(declarationKind)}`)).string;
    }
    ScalarTypeDefinition(node) {
        const nameAsString = node.name;
        const baseName = this.getTypeToUse(nameAsString);
        if (this._federation.skipScalar(nameAsString)) {
            return null;
        }
        this._hasScalars = true;
        this._collectedResolvers[node.name] = {
            typename: 'GraphQLScalarType',
        };
        return new DeclarationBlock({
            ...this._declarationBlockConfig,
            blockTransformer(block) {
                return block;
            },
        })
            .export()
            .asKind('interface')
            .withName(this.convertName(node, {
            suffix: 'ScalarConfig',
        }), ` extends GraphQLScalarTypeConfig<${baseName}, any>`)
            .withBlock(indent(`name: '${node.name}'${this.getPunctuation('interface')}`)).string;
    }
    DirectiveDefinition(node, key, parent) {
        if (this._federation.skipDirective(node.name)) {
            return null;
        }
        const directiveName = this.convertName(node, {
            suffix: 'DirectiveResolver',
        });
        const sourceNode = parent[key];
        const hasArguments = sourceNode.arguments && sourceNode.arguments.length > 0;
        this._collectedDirectiveResolvers[node.name] = directiveName + '<any, any, ContextType>';
        const directiveArgsTypeName = this.convertName(node, {
            suffix: 'DirectiveArgs',
        });
        return [
            new DeclarationBlock({
                ...this._declarationBlockConfig,
                blockTransformer(block) {
                    return block;
                },
            })
                .export()
                .asKind('type')
                .withName(directiveArgsTypeName)
                .withContent(hasArguments
                ? `{\n${this._variablesTransformer.transform(sourceNode.arguments)}\n}`
                : '{ }').string,
            new DeclarationBlock({
                ...this._declarationBlockConfig,
                blockTransformer(block) {
                    return block;
                },
            })
                .export()
                .asKind('type')
                .withName(directiveName, `<Result, Parent, ContextType = ${this.config.contextType.type}, Args = ${directiveArgsTypeName}>`)
                .withContent(`DirectiveResolverFn<Result, Parent, ContextType, Args>`).string,
        ].join('\n');
    }
    buildEnumResolverContentBlock(_node, _mappedEnumType) {
        throw new Error(`buildEnumResolverContentBlock is not implemented!`);
    }
    buildEnumResolversExplicitMappedValues(_node, _valuesMapping) {
        throw new Error(`buildEnumResolversExplicitMappedValues is not implemented!`);
    }
    EnumTypeDefinition(node) {
        const rawTypeName = node.name;
        // If we have enumValues set, and it's point to an external enum - we need to allow internal values resolvers
        // In case we have enumValues set but as explicit values, no need to to do mapping since it's already
        // have type validation (the original enum has been modified by base types plugin).
        // If we have mapper for that type - we can skip
        if (!this.config.mappers[rawTypeName] && !this.config.enumValues[rawTypeName]) {
            return null;
        }
        const name = this.convertName(node, { suffix: this.config.resolverTypeSuffix });
        this._collectedResolvers[rawTypeName] = {
            typename: name,
            baseGeneratedTypename: name,
        };
        const hasExplicitValues = this.config.enumValues[rawTypeName]?.mappedValues;
        return new DeclarationBlock(this._declarationBlockConfig)
            .export()
            .asKind('type')
            .withName(name)
            .withContent(hasExplicitValues
            ? this.buildEnumResolversExplicitMappedValues(node, this.config.enumValues[rawTypeName].mappedValues)
            : this.buildEnumResolverContentBlock(node, this.getTypeToUse(rawTypeName))).string;
    }
    InterfaceTypeDefinition(node) {
        const name = this.convertName(node, {
            suffix: this.config.resolverTypeSuffix,
        });
        const declarationKind = 'type';
        const allTypesMap = this._schema.getTypeMap();
        const implementingTypes = [];
        const typeName = node.name;
        this._collectedResolvers[typeName] = {
            typename: name + '<ContextType>',
            baseGeneratedTypename: name,
        };
        for (const graphqlType of Object.values(allTypesMap)) {
            if (graphqlType instanceof GraphQLObjectType) {
                const allInterfaces = graphqlType.getInterfaces();
                if (allInterfaces.find(int => int.name === typeName)) {
                    implementingTypes.push(graphqlType.name);
                }
            }
        }
        const parentType = this.getParentTypeToUse(typeName);
        const possibleTypes = implementingTypes.map(name => `'${name}'`).join(' | ') || 'null';
        const fields = this.config.onlyResolveTypeForInterfaces ? [] : node.fields || [];
        return new DeclarationBlock(this._declarationBlockConfig)
            .export()
            .asKind(declarationKind)
            .withName(name, `<ContextType = ${this.config.contextType.type}, ${this.transformParentGenericType(parentType)}>`)
            .withBlock([
            indent(`${this.config.internalResolversPrefix}resolveType${this.config.optionalResolveType ? '?' : ''}: TypeResolveFn<${possibleTypes}, ParentType, ContextType>${this.getPunctuation(declarationKind)}`),
            ...fields.map(f => f(typeName, this.config.avoidOptionals.resolvers)),
        ].join('\n')).string;
    }
    SchemaDefinition() {
        return null;
    }
    getRelevantFieldsToOmit({ schemaType, shouldInclude, getTypeToUse, }) {
        const fields = schemaType.getFields();
        return this._federation
            .filterFieldNames(Object.keys(fields))
            .filter(fieldName => {
            const field = fields[fieldName];
            const baseType = getBaseType(field.type);
            // Filter out fields of types that are not included
            if (shouldInclude && !shouldInclude(baseType)) {
                return false;
            }
            return true;
        })
            .map(fieldName => {
            const field = fields[fieldName];
            const baseType = getBaseType(field.type);
            const isUnion = isUnionType(baseType);
            const isInterface = isInterfaceType(baseType);
            const isObject = isObjectType(baseType);
            let isObjectWithAbstractType = false;
            if (isObject) {
                isObjectWithAbstractType = checkIfObjectTypeHasAbstractTypesRecursively(baseType, {
                    isObjectWithAbstractType,
                    checkedTypesWithNestedAbstractTypes: this._checkedTypesWithNestedAbstractTypes,
                });
            }
            if (!this.config.mappers[baseType.name] &&
                !isUnion &&
                !isInterface &&
                !this._shouldMapType[baseType.name] &&
                !isObjectWithAbstractType) {
                return null;
            }
            const addOptionalSign = !this.config.avoidOptionals.resolvers && !isNonNullType(field.type);
            return {
                addOptionalSign,
                fieldName,
                replaceWithType: wrapTypeWithModifiers(getTypeToUse(baseType.name), field.type, {
                    wrapOptional: this.applyMaybe,
                    wrapArray: this.wrapWithArray,
                }),
            };
        })
            .filter(a => a);
    }
}
function replacePlaceholder(pattern, typename) {
    return pattern.replace(/\{T\}/g, typename);
}
function hasPlaceholder(pattern) {
    return pattern.includes('{T}');
}
function normalizeResolversNonOptionalTypename(input) {
    const defaultConfig = {
        unionMember: false,
    };
    if (typeof input === 'boolean') {
        return {
            unionMember: input,
            interfaceImplementingType: input,
        };
    }
    return {
        ...defaultConfig,
        ...input,
    };
}
function checkIfObjectTypeHasAbstractTypesRecursively(baseType, result) {
    if (result.checkedTypesWithNestedAbstractTypes[baseType.name] &&
        (result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus === 'yes' ||
            result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus === 'no')) {
        return result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus === 'yes';
    }
    result.checkedTypesWithNestedAbstractTypes[baseType.name] ||= { checkStatus: 'checking' };
    let atLeastOneFieldWithAbstractType = false;
    const fields = baseType.getFields();
    for (const field of Object.values(fields)) {
        const fieldBaseType = getBaseType(field.type);
        // If the field is self-referencing, skip it. Otherwise, it's an infinite loop
        if (baseType.name === fieldBaseType.name) {
            continue;
        }
        // If the current field has been checked, and it has nested abstract types,
        // mark the parent type as having nested abstract types
        if (result.checkedTypesWithNestedAbstractTypes[fieldBaseType.name]) {
            if (result.checkedTypesWithNestedAbstractTypes[fieldBaseType.name].checkStatus === 'yes') {
                atLeastOneFieldWithAbstractType = true;
                result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus = 'yes';
            }
            continue;
        }
        else {
            result.checkedTypesWithNestedAbstractTypes[fieldBaseType.name] = { checkStatus: 'checking' };
        }
        // If the field is an abstract type, then both the field type and parent type are abstract types
        if (isInterfaceType(fieldBaseType) || isUnionType(fieldBaseType)) {
            atLeastOneFieldWithAbstractType = true;
            result.checkedTypesWithNestedAbstractTypes[fieldBaseType.name].checkStatus = 'yes';
            result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus = 'yes';
            continue;
        }
        // If the field is an object, check it recursively to see if it has abstract types
        // If it does, both field type and parent type have abstract types
        if (isObjectType(fieldBaseType)) {
            // IMPORTANT: we are pointing the parent type to the field type here
            // to make sure when the field type is updated to either 'yes' or 'no', it becomes the parent's type as well
            if (result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus === 'checking') {
                result.checkedTypesWithNestedAbstractTypes[baseType.name] =
                    result.checkedTypesWithNestedAbstractTypes[fieldBaseType.name];
            }
            const foundAbstractType = checkIfObjectTypeHasAbstractTypesRecursively(fieldBaseType, result);
            if (foundAbstractType) {
                atLeastOneFieldWithAbstractType = true;
                result.checkedTypesWithNestedAbstractTypes[fieldBaseType.name].checkStatus = 'yes';
                result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus = 'yes';
            }
            continue;
        }
        // Otherwise, the current field type is not abstract type
        // This includes scalar types, enums, input types and objects without abstract types
        result.checkedTypesWithNestedAbstractTypes[fieldBaseType.name].checkStatus = 'no';
    }
    if (atLeastOneFieldWithAbstractType) {
        result.isObjectWithAbstractType = true;
    }
    else {
        result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus = 'no';
    }
    return atLeastOneFieldWithAbstractType;
}