Skip to content

Commit

Permalink
[typescript-resolvers] Add meta field to typescript-resolvers plugin'…
Browse files Browse the repository at this point in the history
…s output (#9961)

* Make typescript-resolvers generate meta of generated typenames

* Add changeset for @graphql-codegen/plugin-helpers

* Add changeset for typescript-resolvers meta field

* Use minor instead of patch for typescript-resolvers
  • Loading branch information
eddeee888 authored May 15, 2024
1 parent b49457b commit dfc5310
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 16 deletions.
5 changes: 5 additions & 0 deletions .changeset/curvy-lobsters-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-codegen/plugin-helpers': patch
---

Update plugin output type to allow option `meta` field
6 changes: 6 additions & 0 deletions .changeset/new-radios-flash.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
---

Update typescript-resolvers to report generated resolver types in the run to meta field in the output
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ export class BaseResolversVisitor<
> extends BaseVisitor<TRawConfig, TPluginConfig> {
protected _parsedConfig: TPluginConfig;
protected _declarationBlockConfig: DeclarationBlockConfig = {};
protected _collectedResolvers: { [key: string]: string } = {};
protected _collectedResolvers: { [key: string]: { typename: string; baseGeneratedTypename?: string } } = {};
protected _collectedDirectiveResolvers: { [key: string]: string } = {};
protected _variablesTransformer: OperationVariablesToObject;
protected _usedMappers: { [key: string]: boolean } = {};
Expand Down Expand Up @@ -1264,12 +1264,13 @@ export class BaseResolversVisitor<
return this._hasFederation;
}

public getRootResolver(): string {
public getRootResolver(): { content: string; generatedResolverTypes: Record<string, { name: string }> } {
const name = this.convertName(this.config.allResolversTypeName);
const declarationKind = 'type';
const contextType = `<ContextType = ${this.config.contextType.type}>`;

return [
const generatedResolverTypes: Record<string, { name: string }> = {};
const content = [
new DeclarationBlock(this._declarationBlockConfig)
.export()
.asKind(declarationKind)
Expand All @@ -1279,11 +1280,20 @@ export class BaseResolversVisitor<
.map(schemaTypeName => {
const resolverType = this._collectedResolvers[schemaTypeName];

return indent(this.formatRootResolver(schemaTypeName, resolverType, declarationKind));
if (resolverType.baseGeneratedTypename) {
generatedResolverTypes[schemaTypeName] = { name: resolverType.baseGeneratedTypename };
}

return indent(this.formatRootResolver(schemaTypeName, resolverType.typename, declarationKind));
})
.join('\n')
).string,
].join('\n');

return {
content,
generatedResolverTypes,
};
}

protected formatRootResolver(schemaTypeName: string, resolverType: string, declarationKind: DeclarationKind): string {
Expand Down Expand Up @@ -1536,7 +1546,10 @@ export class BaseResolversVisitor<
.withName(name, `<ContextType = ${this.config.contextType.type}, ${this.transformParentGenericType(parentType)}>`)
.withBlock(fieldsContent.join('\n'));

this._collectedResolvers[node.name as any] = name + '<ContextType>';
this._collectedResolvers[node.name as any] = {
typename: name + '<ContextType>',
baseGeneratedTypename: name,
};

return block.string;
}
Expand All @@ -1552,7 +1565,10 @@ export class BaseResolversVisitor<
.map(f => `'${f}'`)
.join(' | ');

this._collectedResolvers[node.name as any] = name + '<ContextType>';
this._collectedResolvers[node.name as any] = {
typename: name + '<ContextType>',
baseGeneratedTypename: name,
};
const parentType = this.getParentTypeToUse(node.name as any as string);

return new DeclarationBlock(this._declarationBlockConfig)
Expand All @@ -1577,7 +1593,9 @@ export class BaseResolversVisitor<
}

this._hasScalars = true;
this._collectedResolvers[node.name as any] = 'GraphQLScalarType';
this._collectedResolvers[node.name as any] = {
typename: 'GraphQLScalarType',
};

return new DeclarationBlock({
...this._declarationBlockConfig,
Expand Down Expand Up @@ -1667,7 +1685,10 @@ export class BaseResolversVisitor<
}

const name = this.convertName(node, { suffix: this.config.resolverTypeSuffix });
this._collectedResolvers[rawTypeName] = name;
this._collectedResolvers[rawTypeName] = {
typename: name,
baseGeneratedTypename: name,
};
const hasExplicitValues = this.config.enumValues[rawTypeName]?.mappedValues;

return new DeclarationBlock(this._declarationBlockConfig)
Expand All @@ -1689,7 +1710,10 @@ export class BaseResolversVisitor<
const allTypesMap = this._schema.getTypeMap();
const implementingTypes: string[] = [];

this._collectedResolvers[node.name as any] = name + '<ContextType>';
this._collectedResolvers[node.name as any] = {
typename: name + '<ContextType>',
baseGeneratedTypename: name,
};

for (const graphqlType of Object.values(allTypesMap)) {
if (graphqlType instanceof GraphQLObjectType) {
Expand Down
16 changes: 10 additions & 6 deletions packages/plugins/typescript/resolvers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ import { TypeScriptResolversVisitor } from './visitor.js';

const capitalize = (s: string): string => s.charAt(0).toUpperCase() + s.slice(1);

export const plugin: PluginFunction<TypeScriptResolversPluginConfig, Types.ComplexPluginOutput> = (
schema: GraphQLSchema,
documents: Types.DocumentFile[],
config: TypeScriptResolversPluginConfig
) => {
export const plugin: PluginFunction<
TypeScriptResolversPluginConfig,
Types.ComplexPluginOutput<{ generatedResolverTypes: Record<string, { name: string }> }>
> = (schema: GraphQLSchema, documents: Types.DocumentFile[], config: TypeScriptResolversPluginConfig) => {
const imports = [];
if (!config.customResolveInfo) {
imports.push('GraphQLResolveInfo');
Expand Down Expand Up @@ -280,6 +279,8 @@ export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs

prepend.push(...mappersImports, ...visitor.globalDeclarations);

const rootResolver = getRootResolver();

return {
prepend,
content: [
Expand All @@ -289,9 +290,12 @@ export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs
resolversTypeMapping,
resolversParentTypeMapping,
...visitorResult.definitions.filter(d => typeof d === 'string'),
getRootResolver(),
rootResolver.content,
getAllDirectiveResolvers(),
].join('\n'),
meta: {
generatedResolverTypes: rootResolver.generatedResolverTypes,
},
};
};

Expand Down
97 changes: 97 additions & 0 deletions packages/plugins/typescript/resolvers/tests/ts-resolvers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3039,4 +3039,101 @@ export type ResolverFn<TResult, TParent, TContext, TArgs> = (
};
`);
});

it('generates meta correctly', async () => {
const result = await plugin(
buildSchema(/* GraphQL */ `
type Query {
user(id: ID!): User
post(id: ID!): Post
}
type Mutation {
createUser(name: String!): CreateUserPayload!
}
interface Node {
id: ID!
}
type Post implements Node {
id: ID!
author: User
}
type User implements Node {
id: ID!
name: String
}
type CreateUserOk {
user: User!
}
type CreateUserError {
error: ErrorType!
}
union CreateUserPayload = CreateUserOk | CreateUserError
enum ErrorType {
FORBIDDEN_ERROR
INTERNAL_ERROR
}
`),
[],
{
namingConvention: 'change-case-all#snakeCase',
enumValues: {
ErrorType: {
FORBIDDEN_ERROR: '403',
INTERNAL_ERROR: '500',
},
},
},
{ outputFile: '' }
);

expect(result.content).toContain(`export type create_user_error_resolvers`);
expect(result.content).toContain(`export type create_user_ok_resolvers`);
expect(result.content).toContain(`export type create_user_payload_resolvers`);
expect(result.content).toContain(`export type error_type_resolvers`);
expect(result.content).toContain(`export type mutation_resolvers`);
expect(result.content).toContain(`export type node_resolvers`);
expect(result.content).toContain(`export type post_resolvers`);
expect(result.content).toContain(`export type query_resolvers`);
expect(result.content).toContain(`export type user_resolvers`);

expect(result.meta).toMatchInlineSnapshot(`
Object {
"generatedResolverTypes": Object {
"CreateUserError": Object {
"name": "create_user_error_resolvers",
},
"CreateUserOk": Object {
"name": "create_user_ok_resolvers",
},
"CreateUserPayload": Object {
"name": "create_user_payload_resolvers",
},
"ErrorType": Object {
"name": "error_type_resolvers",
},
"Mutation": Object {
"name": "mutation_resolvers",
},
"Node": Object {
"name": "node_resolvers",
},
"Post": Object {
"name": "post_resolvers",
},
"Query": Object {
"name": "query_resolvers",
},
"User": Object {
"name": "user_resolvers",
},
},
}
`);
});
});
7 changes: 6 additions & 1 deletion packages/utils/plugins-helpers/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,12 @@ export namespace Types {
noSilentErrors?: boolean;
}

export type ComplexPluginOutput = { content: string; prepend?: string[]; append?: string[] };
export type ComplexPluginOutput<M = Record<string, unknown>> = {
content: string;
prepend?: string[];
append?: string[];
meta?: M;
};
export type PluginOutput = string | ComplexPluginOutput;
export type HookFunction = (...args: any[]) => void | Promise<void>;
export type HookAlterFunction = (...args: any[]) => void | string | Promise<void | string>;
Expand Down

0 comments on commit dfc5310

Please sign in to comment.