-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
[resolvers][federation] Add __resolveReference
to applicable Interface
entities, fix Interface types having non-meta resolver fields
#10221
base: federation-fixes
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: 65fa44f The changes in this PR will be included in the next version bump. This PR includes changesets to release 18 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
/** | ||
* @type boolean | ||
* @default false | ||
* @description Turning this flag to `true` will generate resolver signature that has only `resolveType` for interfaces, forcing developers to write inherited type resolvers in the type itself. | ||
*/ | ||
onlyResolveTypeForInterfaces?: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Related #5648
onlyResolveTypeForInterfaces
is only applicable for some rare use cases mentioned here. This option's implementation blocks __resolveReference
being created for Interfaces when the option is turned on.
I think it's simpler just to deprecate this option as we are moving to a new major version. If anyone needs it, we can put it back as a minor version
* @description If relevant internal resolvers are set to `true`, the resolver type will only be generated if the right conditions are met. | ||
* Enabling this allows a more correct type generation for the resolvers. | ||
* For example: | ||
* - `__isTypeOf` is generated for implementing types and union members | ||
* - `__resolveReference` is generated for federation types that have at least one resolvable `@key` directive | ||
*/ | ||
generateInternalResolversIfNeeded?: GenerateInternalResolversIfNeededConfig; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
generateInternalResolversIfNeeded
might be deprecated in this major version, it'll come in one of the next few PRs.
Since we are bumping a major version, it's worth generating these meta types only when required
* Adds `__resolveReference` in each ObjectType and InterfaceType involved in Federation. | ||
* @param schema | ||
*/ | ||
export function addFederationReferencesToSchema(schema: GraphQLSchema): GraphQLSchema { | ||
return mapSchema(schema, { | ||
export function addFederationReferencesToSchema(schema: GraphQLSchema): { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are transforming the schema to have __resolveReference
fields to prepare for the resolvers plugin. Because of this pattern, it's worth capturing meta here so, and use the meta instead of running checkTypeFederationDetails
in the plugin.
Because of this, checkTypeFederationDetails
is now only used in this file i.e. it's no longer exported and called in resolvers plugin -> better logic encapsulation
__resolveReference
to applicable Interfaces__resolveReference
to applicable Interface
entities
05b4751
to
f4ba147
Compare
* @param schema | ||
*/ | ||
export function addFederationReferencesToSchema(schema: GraphQLSchema): GraphQLSchema { | ||
return mapSchema(schema, { | ||
export function addFederationReferencesToSchema(schema: GraphQLSchema): { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We now collect Federation meta in this function because this is called before calling visitor.
This means we no longer have to collect meta as we find entities during visitor -> simpler, more predictable, and more performant
type FieldDefinitionPrintFn = ( | ||
parentName: string, | ||
avoidResolverOptionals: boolean | ||
) => { value: string | null; meta: { federation?: { isResolveReference: boolean } } }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, we don't know if a field is __resolveReference
or not until we have generated it. This is because we are injecting __resolveReference
as real fields in addFederationReferencesToSchema
. Therefore, we need to add meta
to have more info about the generated field
if (this.config.generateInternalResolversIfNeeded.__resolveReference) { | ||
const federationDetails = checkObjectTypeFederationDetails( | ||
parentType.astNode as ObjectTypeDefinitionNode, | ||
this._schema | ||
); | ||
|
||
if (!federationDetails || federationDetails.resolvableKeyDirectives.length === 0) { | ||
return ''; | ||
} | ||
signature.modifier = ''; // if a federation type has resolvable @key, then it should be required | ||
if (!this._federation.getMeta()[parentType.name].hasResolveReference) { | ||
return { value: '', meta }; | ||
} | ||
|
||
this._federation.setMeta(parentType.name, { hasResolveReference: true }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We no longer check and collect meta as we hit federation fields any more because we are doing it before running visitor. Relevant comment.
Therefore, we can check federation meta directly here
if (signature.genericTypes.length >= 3) { | ||
signature.genericTypes = signature.genericTypes.slice(0, 3); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bit odd to run .slice
to limit generics to 3, may as well be explicit here!
aa191e7
to
dd2b0bd
Compare
14fbb52
to
21db36f
Compare
it.skip('should handle interface types', async () => { | ||
const federatedSchema = /* GraphQL */ ` | ||
type Query { | ||
people: [Person] | ||
} | ||
|
||
extend interface Person @key(fields: "name { first last }") { | ||
name: Name! @external | ||
age: Int @requires(fields: "name") | ||
} | ||
|
||
extend type User implements Person @key(fields: "name { first last }") { | ||
name: Name! @external | ||
age: Int @requires(fields: "name { first last }") | ||
username: String | ||
} | ||
|
||
type Admin implements Person @key(fields: "name { first last }") { | ||
name: Name! @external | ||
age: Int @requires(fields: "name { first last }") | ||
permissions: [String!]! | ||
} | ||
|
||
extend type Name { | ||
first: String! @external | ||
last: String! @external | ||
} | ||
`; | ||
|
||
const content = await generate({ | ||
schema: federatedSchema, | ||
config: { | ||
federation: true, | ||
}, | ||
}); | ||
|
||
expect(content).toBeSimilarStringTo(` | ||
export type PersonResolvers<ContextType = any, ParentType extends ResolversParentTypes['Person'] = ResolversParentTypes['Person']> = { | ||
__resolveType: TypeResolveFn<'User' | 'Admin', ParentType, ContextType>; | ||
age?: Resolver<Maybe<ResolversTypes['Int']>, { __typename: 'User' | 'Admin' } & GraphQLRecursivePick<ParentType, {"name":{"first":true,"last":true}}>, ContextType>; | ||
}; | ||
`); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This Interface test is now covered in ts-resolvers.federation.interface.spec.ts
- Deprecate generateInternalResolversIfNeeded.__resolveReference - Fix tests - Deprecate onlyResolveTypeForInterfaces - Add changeset - Cleanup - Handle __resolveReference generation in Interface - Let FieldDefinition decide whether to generate __resolveReference by checking whether parent has resolvable key
21db36f
to
bbf0cf7
Compare
__resolveReference
to applicable Interface
entities__resolveReference
to applicable Interface
entities, fix Interface types having non-meta resolver fields
TODO: video walkthrough
Description
This PR ensures correct types for Interfaces:
1.
__resolveReference
are generated for Interface entitiesFederation Checklist: #10206
https://www.apollographql.com/docs/graphos/schema-design/federated-schemas/entities/interfaces#example-schemas
2. Interfaces do not have non-meta resolvers
Related PR: #5666
Breaking Changes
As part of this work, we remove unnecessary config that could generate wrong types for both Federation and normal use cases:
onlyResolveTypeForInterfaces
because majority of use cases cannot implement resolvers in Interfaces.generateInternalResolversIfNeeded.__resolveReference
because types do not have__resolveReference
if they are not Federation entities or are not resolvable. Users should not have to manually set this option. This option was put in to wait for this major version.Type of change
Please delete options that are not relevant.
How Has This Been Tested?