Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/popular-colts-remember.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@graphql-codegen/visitor-plugin-common': minor
'@graphql-codegen/typescript-resolvers': minor
---

Add addInterfaceFieldResolverTypes option to support custom Interface resolver inheritance
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export interface ParsedResolversConfig extends ParsedConfig {
defaultMapper: ParsedMapper | null;
avoidOptionals: NormalizedAvoidOptionalsConfig;
addUnderscoreToArgsType: boolean;
addInterfaceFieldResolverTypes: boolean;
enumValues: ParsedEnumValuesMap;
resolverTypeWrapperSignature: string;
federation: boolean;
Expand Down Expand Up @@ -683,6 +684,40 @@ export interface RawResolversConfig extends RawConfig {
* This may not work for cases where provided default mapper types are also nested e.g. `defaultMapper: DeepPartial<{T}>` or `defaultMapper: Partial<{T}>`.
*/
avoidCheckingAbstractTypesRecursively?: boolean;
/**
* @description If true, add field resolver types to Interfaces.
* By default, GraphQL Interfaces do not trigger any field resolvers,
* meaning every implementing type must implement the same resolver for the shared fields.
*
* Some tools provide a way to change the default behaviour by making GraphQL Objects inherit
* missing resolvers from their Interface types. In these cases, it is fine to turn this option to true.
*
* For example, if you are using `@graphql-tools/schema#makeExecutableSchema` with `inheritResolversFromInterfaces: true`,
* you can make `addInterfaceFieldResolverTypes: true` as well
* https://the-guild.dev/graphql/tools/docs/generate-schema#makeexecutableschema
*
* @exampleMarkdown
* ```ts filename="codegen.ts"
* import type { CodegenConfig } from '@graphql-codegen/cli';
*
* const config: CodegenConfig = {
* // ...
* generates: {
* 'path/to/file': {
* plugins: ['typescript', 'typescript-resolver'],
* config: {
* addInterfaceFieldResolverTypes: true,
* },
* },
* },
* };
* export default config;
* ```
*
* @type boolean
* @default false
*/
addInterfaceFieldResolverTypes?: boolean;
/**
* @ignore
*/
Expand Down Expand Up @@ -769,6 +804,7 @@ export class BaseResolversVisitor<
mapOrStr: rawConfig.enumValues,
}),
addUnderscoreToArgsType: getConfigValue(rawConfig.addUnderscoreToArgsType, false),
addInterfaceFieldResolverTypes: getConfigValue(rawConfig.addInterfaceFieldResolverTypes, false),
contextType: parseMapper(rawConfig.contextType || 'any', 'ContextType'),
fieldContextTypes: getConfigValue(rawConfig.fieldContextTypes, []),
directiveContextTypes: getConfigValue(rawConfig.directiveContextTypes, []),
Expand Down Expand Up @@ -2015,7 +2051,7 @@ export class BaseResolversVisitor<
printContent(node, this.config.avoidOptionals.resolvers)
);
for (const field of fields) {
if (field.meta.federation?.isResolveReference) {
if (field.meta.federation?.isResolveReference || this.config.addInterfaceFieldResolverTypes) {
blockFields.push(field.value);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,50 @@ describe('TypeScript Resolvers Plugin + Apollo Federation - Interface', () => {
"
`);
});

it('generates normal Interface fields with addInterfaceFieldResolverTypes:true', async () => {
const federatedSchema = /* GraphQL */ `
type Query {
me: Person
}
interface Person @key(fields: "id") {
id: ID!
name: PersonName!
}
type User implements Person @key(fields: "id") {
id: ID!
name: PersonName!
}
type Admin implements Person @key(fields: "id") {
id: ID!
name: PersonName!
canImpersonate: Boolean!
}
type PersonName {
first: String!
last: String!
}
`;

const content = await generate({
schema: federatedSchema,
config: {
federation: true,
addInterfaceFieldResolverTypes: true,
},
});

expect(content).toBeSimilarStringTo(`
export type PersonResolvers<ContextType = any, ParentType extends ResolversParentTypes['Person'] = ResolversParentTypes['Person'], FederationReferenceType extends FederationReferenceTypes['Person'] = FederationReferenceTypes['Person']> = {
__resolveType: TypeResolveFn<'User' | 'Admin', ParentType, ContextType>;
__resolveReference?: ReferenceResolver<Maybe<ResolversTypes['Person']> | FederationReferenceType, FederationReferenceType, ContextType>;
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
name?: Resolver<ResolversTypes['PersonName'], ParentType, ContextType>;
};
`);
});
});
16 changes: 16 additions & 0 deletions packages/plugins/typescript/resolvers/tests/ts-resolvers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,22 @@ describe('TypeScript Resolvers Plugin', () => {
});

describe('Config', () => {
it('addInterfaceFieldResolverTypes - should allow to have only resolveType for interfaces', async () => {
const config = {
addInterfaceFieldResolverTypes: true,
};
const result = await plugin(resolversTestingSchema, [], config, { outputFile: '' });
const content = await resolversTestingValidate(result, config, resolversTestingSchema);

expect(content).toBeSimilarStringTo(`
export type WithChildrenResolvers<ContextType = any, ParentType extends ResolversParentTypes['WithChildren'] = ResolversParentTypes['WithChildren']> = {
__resolveType: TypeResolveFn<'AnotherNodeWithAll', ParentType, ContextType>;
unionChildren?: Resolver<Array<ResolversTypes['ChildUnion']>, ParentType, ContextType>;
nodes?: Resolver<Array<ResolversTypes['AnotherNode']>, ParentType, ContextType>;
};
`);
});

it('optionalInfoArgument - should allow to have optional info argument', async () => {
const config = {
noSchemaStitching: true,
Expand Down
Loading