-
The latest version of typescript resolvers has improved the type safety of resolvers for fields that are federated entities (thank Eddy!): #10297 In practice, what this means is that the parent argument in resolvers is a union of the federated entity or the type of the parent (or its mapper). E.g. schema: type User @key(fields: "id") {
id: ID!
firstName: String!
lastName: String!
fullName: String
} The challenge now is, despite defining a reference resolver, the resolvers aren't aware that these fields are defined. It's using the federated entity type, rather than the defined Resolvers: const resolvers: Resolvers = {
User: {
__resolverReference: (user) => { // expects a return type of User
return {
id: user.id,
firstName: "Bob",
lastName: "Smith",
};
},
fullName: (user) => {
return user.firstName + user.lastName; // property firstName does not exist
},
},
}; My question is what's the best way to handle this? For now what we've done is define a basic type guard: if ("firstName" in user) {
...
} But this is introducing extra runtime code (okay, a tiny amount), to perform a check that will always be true. Is there a better way? Alternatively, could the resolver types be structured in such a way that the resolvers include the true type if a reference resolver has been defined? i.e. type ResolverWithReference = {
__resolveReference: never;
fullName: (parent: FederatedUser) => string;
};
type ResolverWithoutReference = {
__resolveReference: (entity: FederatedUser) => User;
fullName: (parent: User) => string;
};
// I know the types here are actually vastly more complicated, but I hope it captures my thinking
type Resolver = ResolverWithReference | ResolverWithoutReference; |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
Hi @RMHonor, You can set mappers to explicitly declare what you'd like to return in For example: // codegen.ts
const config: CodegenConfig = {
// ... other config
'src/types.generated.ts': {
plugins: ['typescript', 'typescript-resolvers'],
config: {
mappers: {
User: './mappers#UserMapper'
}
}
}
} And in the export type UserMapper = {
id: string,
firstName: string,
lastName: string,
} This forces all resolvers (including Note that in your example of type ResolverWithReference = {
__resolveReference: never;
fullName: (parent: FederatedUser | User) => string; // Must include `| User` because another resolver in the same subgraph may return it
}; The scenarios are explained in this video: https://youtu.be/9mbWNVyIGm4?si=khZYIFjm_knsoqnL&t=45 This is why explicit Our recommended approach is to use on Server Preset to help detect when resolvers (normal or Here are some common use cases of mappers in Federation (This is mentioned in the migration guide): // src/mappers.ts
// If you are only resolving fields through `__resolveReference`, then explicitly set the reference type:
import { FederationReferenceTypes } from "./types.generated";
export type UserMapper = FederationReferenceTypes["User"]
// If your current subgraph returns a different interface to `__resolveReference`, include both in the mapper, and handle both cases in subsequent resolvers
// this is the `if ("firstName" in user)` approach you mentioned
import { UserTypeFromDatabase } from "./your-database-type";
import { FederationReferenceTypes } from "./types.generated";
export type UserMapper =
| UserTypeFromDatabase
| FederationReferenceTypes["User"]
// If you want to standardise the mapper interface (which means you may need to resolve for `UserTypeFromDatabase` in `__resolveReference`):
import { UserTypeFromDatabase } from "./your-database-type";
export type UserMapper = UserTypeFromDatabase |
Beta Was this translation helpful? Give feedback.
Hi @RMHonor,
You can set mappers to explicitly declare what you'd like to return in
User
"nodes".For example:
And in the
mappers.ts
file:This forces all resolvers (including
__resolveReference
) that returnUser
type in the schema to returnUserMapper
instead.Note that in your example of
ResolverWithReference
andResolverWithoutReference
,User.fullName
resolver's parent…