@graphql-codegen/visitor-plugin-common
Version:
1,007 lines • 55.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, federationMeta = {}) {
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),
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: {},
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._parsedSchemaMeta = {
types: {
interface: {},
union: {},
},
typesWithIsTypeOf: {},
};
this._collectedDirectiveResolvers = {};
this._usedMappers = {};
this._resolversTypes = {};
this._resolversParentTypes = {};
this._hasReferencedResolversUnionTypes = false;
this._hasReferencedResolversInterfaceTypes = false;
this._resolversUnionTypes = {};
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,
meta: federationMeta,
});
this._rootTypeNames = (0, utils_1.getRootTypeNames)(_schema);
this._variablesTransformer = new variables_to_object_js_1.OperationVariablesToObject(this.scalars, this.convertName, this.config.namespacedImportName);
// 1. Parse schema meta at the start once,
// so we can use it in subsequent generate functions
this.parseSchemaMeta();
// 2. Generate types for resolvers
this._resolversTypes = this.createResolversFields({
applyWrapper: type => this.applyResolverTypeWrapper(type),
clearWrapper: type => this.clearResolverTypeWrapper(type),
getTypeToUse: name => this.getTypeToUse(name),
currentType: 'ResolversTypes',
onNotMappedObjectType: ({ initialType }) => initialType,
});
this._resolversParentTypes = this.createResolversFields({
applyWrapper: type => type,
clearWrapper: type => type,
getTypeToUse: name => this.getParentTypeToUse(name),
currentType: 'ResolversParentTypes',
shouldInclude: namedType => !(0, graphql_1.isEnumType)(namedType),
onNotMappedObjectType: ({ typeName, initialType }) => {
let result = initialType;
const federationReferenceTypes = this._federation.printReferenceSelectionSets({
typeName,
baseFederationType: `${this.convertName('FederationTypes')}['${typeName}']`,
});
if (federationReferenceTypes) {
result += ` | ${federationReferenceTypes}`;
}
return result;
},
});
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, onNotMappedObjectType, }) {
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);
}
}
else {
internalType = onNotMappedObjectType({
typeName,
initialType: 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 unionTypes = Object.entries(this._parsedSchemaMeta.types.union).reduce((res, [typeName, { type: schemaType, unionMembers }]) => {
if ((0, graphql_1.isUnionType)(schemaType)) {
const { unionMember, excludeTypes } = this.config.resolversNonOptionalTypename;
res[typeName] = this.getAbstractMembersType({
typeName,
memberTypes: Object.values(unionMembers),
isTypenameNonOptional: unionMember && !excludeTypes?.includes(typeName),
});
}
return res;
}, {});
return unionTypes;
}
createResolversInterfaceTypes() {
if (!this._hasReferencedResolversInterfaceTypes) {
return {};
}
const interfaceTypes = Object.entries(this._parsedSchemaMeta.types.interface).reduce((res, [typeName, { type: schemaType, implementingTypes }]) => {
if ((0, graphql_1.isInterfaceType)(schemaType)) {
const { interfaceImplementingType, excludeTypes } = this.config.resolversNonOptionalTypename;
res[typeName] = this.getAbstractMembersType({
typeName,
memberTypes: Object.values(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;
}
buildFederationTypes() {
const federationMeta = this._federation.getMeta();
if (Object.keys(federationMeta).length === 0) {
return '';
}
const declarationKind = 'type';
return new utils_js_1.DeclarationBlock(this._declarationBlockConfig)
.export()
.asKind(declarationKind)
.withName(this.convertName('FederationTypes'))
.withComment('Mapping of federation types')
.withBlock(Object.keys(federationMeta)
.map(typeName => {
return (0, utils_js_1.indent)(`${typeName}: ${this.convertName(typeName)}${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 = {
hasResolveReference: federationMeta.hasResolveReference,
};
}
}
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 parentType = this.schema.getType(parentName);
const meta = {};
if (this._federation.skipField({ fieldNode: original, parentType })) {
return { value: null, meta };
}
const contextType = this.getContextType(parentName, node);
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.transformFieldParentType({
fieldNode: original,
parentType,
parentTypeSignature: this.getParentTypeForSignature(node),
federationTypeSignature: 'FederationType',
});
const { mappedTypeKey, resolverType } = (() => {
const baseType = (0, utils_js_1.getBaseTypeNode)(original.type);
const realType = baseType.name.value;
const typeToUse = this.getTypeToUse(realType);
/**
* Turns GraphQL type to TypeScript types (`mappedType`) e.g.
* - String! -> ResolversTypes['String']>
* - String -> Maybe<ResolversTypes['String']>
* - [String] -> Maybe<Array<Maybe<ResolversTypes['String']>>>
* - [String!]! -> Array<ResolversTypes['String']>
*/
const mappedType = this._variablesTransformer.wrapAstTypeWithModifiers(typeToUse, original.type);
const subscriptionType = this._schema.getSubscriptionType();
const isSubscriptionType = subscriptionType && subscriptionType.name === parentName;
if (isSubscriptionType) {
return {
mappedTypeKey: `${mappedType}, "${node.name}"`,
resolverType: 'SubscriptionResolver',
};
}
const directiveMappings = node.directives
?.map(directive => this._directiveResolverMappings[directive.name])
.filter(Boolean)
.reverse() ?? [];
return {
mappedTypeKey: mappedType,
resolverType: 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._federation.getMeta()[parentType.name].hasResolveReference) {
return { value: '', meta };
}
signature.type = 'ReferenceResolver';
signature.genericTypes = [mappedTypeKey, parentTypeSignature, contextType];
meta.federation = { isResolveReference: true };
}
return {
value: (0, utils_js_1.indent)(`${signature.name}${signature.modifier}: ${signature.type}<${signature.genericTypes.join(', ')}>${this.getPunctuation(declarationKind)}`),
meta,
};
};
}
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;
}
parseSchemaMeta() {
const allSchemaTypes = this._schema.getTypeMap();
const typeNames = this._federation.filterTypeNames(Object.keys(allSchemaTypes));
for (const typeName of typeNames) {
const schemaType = allSchemaTypes[typeName];
if ((0, graphql_1.isUnionType)(schemaType)) {
this._parsedSchemaMeta.types.union[schemaType.name] = {
type: schemaType,
unionMembers: {},
};
const unionMemberTypes = schemaType.getTypes();
for (const type of unionMemberTypes) {
this._parsedSchemaMeta.types.union[schemaType.name].unionMembers[type.name] = type;
this._parsedSchemaMeta.typesWithIsTypeOf[type.name] = true;
}
}
if ((0, graphql_1.isInterfaceType)(schemaType)) {
this._parsedSchemaMeta.types.interface[schemaType.name] = {
type: schemaType,
implementingTypes: {},
};
for (const graphqlType of Object.values(allSchemaTypes)) {
if (graphqlType instanceof graphql_1.GraphQLObjectType) {
const allInterfaces = graphqlType.getInterfaces();
if (allInterfaces.some(int => int.name === schemaType.name)) {
this._parsedSchemaMeta.types.interface[schemaType.name].implementingTypes[graphqlType.name] = graphqlType;
this._parsedSchemaMeta.typesWithIsTypeOf[graphqlType.name] = true;
}
}
}
}
}
}
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)).value;
});
if (!rootType && this._parsedSchemaMeta.typesWithIsTypeOf[typeName]) {
fieldsContent.push((0, utils_js_1.indent)(`${this.config.internalResolversPrefix}isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>${this.getPunctuation(declarationKind)}`));
}
const genericTypes = [
`ContextType = ${this.config.contextType.type}`,
this.transformParentGenericType(parentType),
];
this._federation.addFederationTypeGenericIfApplicable({
genericTypes,
federationTypesType: this.convertName('FederationTypes'),
typeName,
});
const block = new utils_js_1.DeclarationBlock(this._declarationBlockConfig)
.export()
.asKind(declarationKind)
.withName(name, `<${genericTypes.join(', ')}>`)
.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 typeName = node.name;
const implementingTypes = Object.keys(this._parsedSchemaMeta.types.interface[typeName].implementingTypes);
this._collectedResolvers[typeName] = {
typename: name + '<ContextType>',
baseGeneratedTypename: name,
};
const parentType = this.getParentTypeToUse(typeName);
const genericTypes = [
`ContextType = ${this.config.contextType.type}`,
this.transformParentGenericType(parentType),
];
this._federation.addFederationTypeGenericIfApplicable({
genericTypes,
federationTypesType: this.convertName('FederationTypes'),
typeName,
});
const possibleTypes = implementingTypes.map(name => `'${name}'`).join(' | ') || 'null';
// An Interface has __resolveType resolver, and no other fields.
const blockFields = [
(0, utils_js_1.indent)(`${this.config.internalResolversPrefix}resolveType${this.config.optionalResolveType ? '?' : ''}: TypeResolveFn<${possibleTypes}, ParentType, ContextType>${this.getPunctuation(declarationKind)}`),
];
// An Interface in Federation may have the additional __resolveReference resolver, if resolvable.
// So, we filter out the normal fields declared on the Interface and add the __resolveReference resolver.
const fields = node.fields.map(f => f(typeName, this.config.avoidOptionals.resolvers));
for (const field of fields) {
if (field.meta.federation?.isResolveReference) {
blockFields.push(field.value);
}
}
return new utils_js_1.DeclarationBlock(this._declarationBlockConfig)
.export()
.asKind(declarationKind)
.withName(name, `<${genericTypes.join(', ')}>`)
.withBlock(blockFields.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) {
isObj