Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[typescript-resolvers] Add meta field to typescript-resolvers plugin's output #9961

Merged
merged 6 commits into from
May 15, 2024
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
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
Loading