@graphql-codegen/visitor-plugin-common
Version:
1,018 lines (1,017 loc) • 51.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseResolversVisitor = void 0;
const tslib_1 = require("tslib");
const plugin_helpers_1 = require("@graphql-codegen/plugin-helpers");
const utils_1 = require("@graphql-tools/utils");
const auto_bind_1 = tslib_1.__importDefault(require("auto-bind"));
const graphql_1 = require("graphql");
const base_visitor_js_1 = require("./base-visitor.js");
const enum_values_js_1 = require("./enum-values.js");
const mappers_js_1 = require("./mappers.js");
const scalars_js_1 = require("./scalars.js");
const utils_js_1 = require("./utils.js");
const variables_to_object_js_1 = require("./variables-to-object.js");
const avoid_optionals_js_1 = require("./avoid-optionals.js");
class BaseResolversVisitor extends base_visitor_js_1.BaseVisitor {
constructor(rawConfig, additionalConfig, _schema, defaultScalars = scalars_js_1.DEFAULT_SCALARS) {
super(rawConfig, {
immutableTypes: (0, utils_js_1.getConfigValue)(rawConfig.immutableTypes, false),
optionalResolveType: (0, utils_js_1.getConfigValue)(rawConfig.optionalResolveType, false),
enumPrefix: (0, utils_js_1.getConfigValue)(rawConfig.enumPrefix, true),
enumSuffix: (0, utils_js_1.getConfigValue)(rawConfig.enumSuffix, true),
federation: (0, utils_js_1.getConfigValue)(rawConfig.federation, false),
resolverTypeWrapperSignature: (0, utils_js_1.getConfigValue)(rawConfig.resolverTypeWrapperSignature, 'Promise<T> | T'),
enumValues: (0, enum_values_js_1.parseEnumValues)({
schema: _schema,
mapOrStr: rawConfig.enumValues,
}),
addUnderscoreToArgsType: (0, utils_js_1.getConfigValue)(rawConfig.addUnderscoreToArgsType, false),
onlyResolveTypeForInterfaces: (0, utils_js_1.getConfigValue)(rawConfig.onlyResolveTypeForInterfaces, false),
contextType: (0, mappers_js_1.parseMapper)(rawConfig.contextType || 'any', 'ContextType'),
fieldContextTypes: (0, utils_js_1.getConfigValue)(rawConfig.fieldContextTypes, []),
directiveContextTypes: (0, utils_js_1.getConfigValue)(rawConfig.directiveContextTypes, []),
resolverTypeSuffix: (0, utils_js_1.getConfigValue)(rawConfig.resolverTypeSuffix, 'Resolvers'),
allResolversTypeName: (0, utils_js_1.getConfigValue)(rawConfig.allResolversTypeName, 'Resolvers'),
rootValueType: (0, mappers_js_1.parseMapper)(rawConfig.rootValueType || '{}', 'RootValueType'),
namespacedImportName: (0, utils_js_1.getConfigValue)(rawConfig.namespacedImportName, ''),
avoidOptionals: (0, avoid_optionals_js_1.normalizeAvoidOptionals)(rawConfig.avoidOptionals),
defaultMapper: rawConfig.defaultMapper
? (0, mappers_js_1.parseMapper)(rawConfig.defaultMapper || 'any', 'DefaultMapperType')
: null,
mappers: (0, mappers_js_1.transformMappers)(rawConfig.mappers || {}, rawConfig.mapperTypeSuffix),
scalars: (0, utils_js_1.buildScalarsFromConfig)(_schema, rawConfig, defaultScalars),
internalResolversPrefix: (0, utils_js_1.getConfigValue)(rawConfig.internalResolversPrefix, '__'),
generateInternalResolversIfNeeded: {
__resolveReference: rawConfig.generateInternalResolversIfNeeded?.__resolveReference ?? false,
},
resolversNonOptionalTypename: normalizeResolversNonOptionalTypename((0, utils_js_1.getConfigValue)(rawConfig.resolversNonOptionalTypename, false)),
avoidCheckingAbstractTypesRecursively: (0, utils_js_1.getConfigValue)(rawConfig.avoidCheckingAbstractTypesRecursively, 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._resolversUnionParentTypes = {};
this._resolversInterfaceTypes = {};
this._rootTypeNames = new Set();
this._globalDeclarations = new Set();
this._hasScalars = false;
this._checkedTypesWithNestedAbstractTypes = {};
this._shouldMapType = {};
(0, auto_bind_1.default)(this);
this._federation = new plugin_helpers_1.ApolloFederation({ enabled: this.config.federation, schema: this.schema });
this._rootTypeNames = (0, utils_1.getRootTypeNames)(_schema);
this._variablesTransformer = new variables_to_object_js_1.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 => !(0, graphql_1.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 ((0, graphql_1.isObjectType)(type) || (0, graphql_1.isInterfaceType)(type)) {
const fields = type.getFields();
return Object.keys(fields)
.filter(fieldName => {
const field = fields[fieldName];
const fieldType = (0, plugin_helpers_1.getBaseType)(field.type);
return !duringCheck.includes(fieldType.name);
})
.some(fieldName => {
const field = fields[fieldName];
const fieldType = (0, plugin_helpers_1.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 ((0, graphql_1.isEnumType)(schemaType) && this.config.enumValues[typeName]) {
const isExternalFile = !!this.config.enumValues[typeName].sourceFile;
prev[typeName] = isExternalFile
? this.convertName(this.config.enumValues[typeName].typeIdentifier, {
useTypesPrefix: false,
useTypesSuffix: false,
})
: this.config.enumValues[typeName].sourceIdentifier;
}
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 ((0, graphql_1.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 ((0, graphql_1.isUnionType)(schemaType)) {
this._hasReferencedResolversUnionTypes = true;
const type = this.convertName('ResolversUnionTypes');
const generic = this.convertName(currentType);
prev[typeName] = applyWrapper(`${type}<${generic}>['${typeName}']`);
}
else if ((0, graphql_1.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' && (0, graphql_1.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 ((0, graphql_1.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(utils_js_1.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 ((0, graphql_1.isUnionType)(schemaType)) {
const { unionMember, excludeTypes } = this.config.resolversNonOptionalTypename;
res[typeName] = this.getAbstractMembersType({
typeName,
memberTypes: schemaType.getTypes(),
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 ((0, graphql_1.isInterfaceType)(schemaType)) {
const allTypesMap = this._schema.getTypeMap();
const implementingTypes = [];
for (const graphqlType of Object.values(allTypesMap)) {
if (graphqlType instanceof graphql_1.GraphQLObjectType) {
const allInterfaces = graphqlType.getInterfaces();
if (allInterfaces.some(int => int.name === schemaType.name)) {
implementingTypes.push(graphqlType);
}
}
}
const { interfaceImplementingType, excludeTypes } = this.config.resolversNonOptionalTypename;
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]: (0, mappers_js_1.parseMapper)(`${sourceStr}#${contextTypeName}`) };
}
const [path, contextType] = items;
return { ...prev, [path]: (0, mappers_js_1.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]: (0, mappers_js_1.parseMapper)(`${sourceStr}#${contextTypeName}`) };
}
const [path, contextType] = items;
return { ...prev, [path]: (0, mappers_js_1.parseMapper)(contextType) };
}, {});
}
buildResolversTypes() {
const declarationKind = 'type';
return new utils_js_1.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 => (0, utils_js_1.indent)(`${typeName}: ${this._resolversTypes[typeName]}${this.getPunctuation(declarationKind)}`))
.join('\n')).string;
}
buildResolversParentTypes() {
const declarationKind = 'type';
return new utils_js_1.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 => (0, utils_js_1.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 utils_js_1.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]) => (0, utils_js_1.indent)(`${typeName}: ${value}${this.getPunctuation(declarationKind)}`))
.join('\n')).string;
}
buildResolversInterfaceTypes() {
if (Object.keys(this._resolversInterfaceTypes).length === 0) {
return '';
}
const declarationKind = 'type';
return new utils_js_1.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]) => (0, utils_js_1.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 = (0, utils_js_1.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 = (0, utils_js_1.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 => (0, mappers_js_1.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 utils_js_1.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 (0, utils_js_1.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 utils_js_1.DeclarationBlock(this._declarationBlockConfig)
.export()
.asKind(declarationKind)
.withName(name, contextType)
.withBlock(Object.keys(this._collectedDirectiveResolvers)
.map(schemaTypeName => {
const resolverType = this._collectedDirectiveResolvers[schemaTypeName];
return (0, utils_js_1.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 = (0, utils_js_1.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 = (0, plugin_helpers_1.checkObjectTypeFederationDetails)(parentType.astNode, this._schema);
if (!federationDetails || federationDetails.resolvableKeyDirectives.length === 0) {
return '';
}
}
this._federation.setMeta(parentType.name, { hasResolveReference: true });
signature.type = 'ReferenceResolver';
if (signature.genericTypes.length >= 3) {
signature.genericTypes = signature.genericTypes.slice(0, 3);
}
}
return (0, utils_js_1.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(utils_js_1.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) {
fieldsContent.push((0, utils_js_1.indent)(`${this.config.internalResolversPrefix}isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>${this.getPunctuation(declarationKind)}`));
}
const block = new utils_js_1.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 utils_js_1.DeclarationBlock(this._declarationBlockConfig)
.export()
.asKind(declarationKind)
.withName(name, `<ContextType = ${this.config.contextType.type}, ${this.transformParentGenericType(parentType)}>`)
.withBlock((0, utils_js_1.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 utils_js_1.DeclarationBlock({
...this._declarationBlockConfig,
blockTransformer(block) {
return block;
},
})
.export()
.asKind('interface')
.withName(this.convertName(node, {
suffix: 'ScalarConfig',
}), ` extends GraphQLScalarTypeConfig<${baseName}, any>`)
.withBlock((0, utils_js_1.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 utils_js_1.DeclarationBlock({
...this._declarationBlockConfig,
blockTransformer(block) {
return block;
},
})
.export()
.asKind('type')
.withName(directiveArgsTypeName)
.withContent(hasArguments
? `{\n${this._variablesTransformer.transform(sourceNode.arguments)}\n}`
: '{ }').string,
new utils_js_1.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 utils_js_1.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 graphql_1.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 utils_js_1.DeclarationBlock(this._declarationBlockConfig)
.export()
.asKind(declarationKind)
.withName(name, `<ContextType = ${this.config.contextType.type}, ${this.transformParentGenericType(parentType)}>`)
.withBlock([
(0, utils_js_1.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 = (0, plugin_helpers_1.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 = (0, plugin_helpers_1.getBaseType)(field.type);
const isUnion = (0, graphql_1.isUnionType)(baseType);
const isInterface = (0, graphql_1.isInterfaceType)(baseType);
const isObject = (0, graphql_1.isObjectType)(baseType);
let isObjectWithAbstractType = false;
if (isObject && !this.config.avoidCheckingAbstractTypesRecursively) {
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 && !(0, graphql_1.isNonNullType)(field.type);
return {
addOptionalSign,
fieldName,
replaceWithType: (0, utils_js_1.wrapTypeWithModifiers)(getTypeToUse(baseType.name), field.type, {
wrapOptional: this.applyMaybe,
wrapArray: this.wrapWithArray,
}),
};
})
.filter(a => a);
}
}
exports.BaseResolversVisitor = BaseResolversVisitor;
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 = (0, plugin_helpers_1.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 ((0, graphql_1.isInterfaceType)(fieldBaseType) || (0, graphql_1.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 ((0, graphql_1.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;
}