diff --git a/.changeset/tender-papayas-stare.md b/.changeset/tender-papayas-stare.md new file mode 100644 index 00000000000..7de8b4ca571 --- /dev/null +++ b/.changeset/tender-papayas-stare.md @@ -0,0 +1,20 @@ +--- +'@graphql-codegen/visitor-plugin-common': patch +--- + +Handle schema extension nodes correctly + +When a schema doesn't have an operation type defined but has `schema extension` definitions with directives like below, +schema extensions are not converted to schema definitions by GraphQL Tools. +So the visitor should handle schema extension nodes correctly. + +Follow-up to https://github.com/ardatan/graphql-tools/pull/7679 + +```graphql +extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"]) + +type Foo { + id: ID! @key + name: String +} +``` diff --git a/dev-test/codegen.ts b/dev-test/codegen.ts index 7c0b9838288..04584a36395 100644 --- a/dev-test/codegen.ts +++ b/dev-test/codegen.ts @@ -243,6 +243,19 @@ const config: CodegenConfig = { }, }, }, + './dev-test/test-federation/generated/types.ts': { + schema: './dev-test/test-federation/schema.gql', + plugins: ['typescript', 'typescript-resolvers'], + config: { + mapperTypeSuffix: 'Mapper', + enumsAsTypes: true, + useIndexSignature: true, + maybeValue: 'T | null | undefined', + scalars: { + CarKey: 'string', + }, + }, + }, }, }; diff --git a/dev-test/test-federation/generated/types.ts b/dev-test/test-federation/generated/types.ts new file mode 100644 index 00000000000..a73c3395a90 --- /dev/null +++ b/dev-test/test-federation/generated/types.ts @@ -0,0 +1,145 @@ +import { GraphQLResolveInfo, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql'; +export type Maybe = T | null | undefined; +export type InputMaybe = T | null | undefined; +export type Exact = { [K in keyof T]: T[K] }; +export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; +export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; +export type MakeEmpty = { [_ in K]?: never }; +export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: { input: string; output: string }; + String: { input: string; output: string }; + Boolean: { input: boolean; output: boolean }; + Int: { input: number; output: number }; + Float: { input: number; output: number }; + /** Represents a car */ + CarKey: { input: string; output: string }; +}; + +export type Car = { + __typename?: 'Car'; + carKey: Scalars['CarKey']['output']; + /** Extend Car with a simple "dummy" field */ + dummy?: Maybe; +}; + +export type WithIndex = TObject & Record; +export type ResolversObject = WithIndex; + +export type ResolverTypeWrapper = Promise | T; + +export type ResolverWithResolve = { + resolve: ResolverFn; +}; +export type Resolver< + TResult, + TParent = Record, + TContext = Record, + TArgs = Record +> = ResolverFn | ResolverWithResolve; + +export type ResolverFn = ( + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo +) => Promise | TResult; + +export type SubscriptionSubscribeFn = ( + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo +) => AsyncIterable | Promise>; + +export type SubscriptionResolveFn = ( + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo +) => TResult | Promise; + +export interface SubscriptionSubscriberObject { + subscribe: SubscriptionSubscribeFn<{ [key in TKey]: TResult }, TParent, TContext, TArgs>; + resolve?: SubscriptionResolveFn; +} + +export interface SubscriptionResolverObject { + subscribe: SubscriptionSubscribeFn; + resolve: SubscriptionResolveFn; +} + +export type SubscriptionObject = + | SubscriptionSubscriberObject + | SubscriptionResolverObject; + +export type SubscriptionResolver< + TResult, + TKey extends string, + TParent = Record, + TContext = Record, + TArgs = Record +> = + | ((...args: any[]) => SubscriptionObject) + | SubscriptionObject; + +export type TypeResolveFn, TContext = Record> = ( + parent: TParent, + context: TContext, + info: GraphQLResolveInfo +) => Maybe | Promise>; + +export type IsTypeOfResolverFn, TContext = Record> = ( + obj: T, + context: TContext, + info: GraphQLResolveInfo +) => boolean | Promise; + +export type NextResolverFn = () => Promise; + +export type DirectiveResolverFn< + TResult = Record, + TParent = Record, + TContext = Record, + TArgs = Record +> = ( + next: NextResolverFn, + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo +) => TResult | Promise; + +/** Mapping between all available schema types and the resolvers types */ +export type ResolversTypes = ResolversObject<{ + Boolean: ResolverTypeWrapper; + Car: ResolverTypeWrapper; + CarKey: ResolverTypeWrapper; + String: ResolverTypeWrapper; +}>; + +/** Mapping between all available schema types and the resolvers parents */ +export type ResolversParentTypes = ResolversObject<{ + Boolean: Scalars['Boolean']['output']; + Car: Car; + CarKey: Scalars['CarKey']['output']; + String: Scalars['String']['output']; +}>; + +export type CarResolvers< + ContextType = any, + ParentType extends ResolversParentTypes['Car'] = ResolversParentTypes['Car'] +> = ResolversObject<{ + carKey?: Resolver; + dummy?: Resolver, ParentType, ContextType>; +}>; + +export interface CarKeyScalarConfig extends GraphQLScalarTypeConfig { + name: 'CarKey'; +} + +export type Resolvers = ResolversObject<{ + Car?: CarResolvers; + CarKey?: GraphQLScalarType; +}>; diff --git a/dev-test/test-federation/schema.gql b/dev-test/test-federation/schema.gql new file mode 100644 index 00000000000..1b5925d74cc --- /dev/null +++ b/dev-test/test-federation/schema.gql @@ -0,0 +1,16 @@ +extend schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://specs.apollo.dev/federation/v2.6", import: ["@key", "@extends"]) + +type Car @extends @key(fields: "carKey") { + carKey: CarKey! + """ + Extend Car with a simple "dummy" field + """ + dummy: String +} + +""" +Represents a car +""" +scalar CarKey diff --git a/packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts b/packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts index 119db4119dc..bb8cef98b18 100644 --- a/packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts +++ b/packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts @@ -2067,6 +2067,10 @@ export class BaseResolversVisitor< return null; } + SchemaExtension() { + return null; + } + private getRelevantFieldsToOmit({ schemaType, shouldInclude, diff --git a/packages/plugins/other/visitor-plugin-common/src/base-types-visitor.ts b/packages/plugins/other/visitor-plugin-common/src/base-types-visitor.ts index a153e0283ec..a37350bf7a6 100644 --- a/packages/plugins/other/visitor-plugin-common/src/base-types-visitor.ts +++ b/packages/plugins/other/visitor-plugin-common/src/base-types-visitor.ts @@ -1046,6 +1046,10 @@ export class BaseTypesVisitor< return null; } + SchemaExtension() { + return null; + } + getNodeComment(node: FieldDefinitionNode | EnumValueDefinitionNode | InputValueDefinitionNode): string { let commentText = node.description?.value; const deprecationDirective = node.directives.find(v => v.name.value === 'deprecated'); diff --git a/yarn.lock b/yarn.lock index 825c8854b36..7288ed8fb6d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2998,9 +2998,9 @@ ws "^8.17.1" "@graphql-tools/utils@^10.0.0", "@graphql-tools/utils@^10.0.13", "@graphql-tools/utils@^10.10.0", "@graphql-tools/utils@^10.10.1", "@graphql-tools/utils@^10.3.2", "@graphql-tools/utils@^10.5.4", "@graphql-tools/utils@^10.8.6": - version "10.10.1" - resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-10.10.1.tgz#eeec3bdc5f5521b8b11c5148cdf0c8affb4f9aed" - integrity sha512-9iOZ7x6tuIpp/dviNmTCSH1cDDNLIcrj6T3WKH9lU4nRWx5Pr0e7Faj7T/HmP2Njrjik63dJWuDVRxfQSTOc4g== + version "10.10.2" + resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-10.10.2.tgz#2edeb1672dfb0183a23145453ea73c89d1a013c6" + integrity sha512-aVPIAsZ8PMomO2UODO+uG8YCwYOfPthHO2b8pXqixlXx01L0B01qGkrQ0KYJDI/gozNNFXiZ3TfoFMXSGnPiow== dependencies: "@graphql-typed-document-node/core" "^3.1.1" "@whatwg-node/promise-helpers" "^1.0.0" @@ -4890,16 +4890,16 @@ resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756" integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg== -"@types/unist@*", "@types/unist@^2", "@types/unist@^2.0.0", "@types/unist@^2.0.2": - version "2.0.11" - resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4" - integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== - -"@types/unist@^3.0.0": +"@types/unist@*", "@types/unist@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.0.tgz#988ae8af1e5239e89f9fbb1ade4c935f4eeedf9a" integrity sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w== +"@types/unist@^2", "@types/unist@^2.0.0", "@types/unist@^2.0.2": + version "2.0.11" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4" + integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== + "@types/ws@^8.0.0": version "8.5.4" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5"