什么是正确的 return 类型的 GraphQL 解析函数?
What is a correct return type of a GraphQL resolve function?
我遇到了一个我自己无法解决的问题。让我们一步一步来指出问题。
- 我有一个突变
bookAppointment
,它 return 是一个 Appointment
对象
- GraphQL 模式表示该对象应该 return 4 个属性:
id
、date
、specialist
、client
.
- 为了遵循 GraphQL 风格,
specialist
和 client
属性应该是字段级解析器
- 要获取此对象,我需要将
specialistId
传递给专家字段级解析器,并将 clientId
传递给客户端字段级解析器。
- 此时问题出现了
client
、specialist
的字段级解析器期望根突变 returns 字段,如 clientId
和 specialistId
。但是由该语法生成的 GraphQL 语法和类型不包含此道具(有意义)。
- 如何“扩展”解析器的 return 类型及其
interface BookAppointmentPayload
让我和 TypeScript 开心?
这是我的 GraphQL 架构
type Client {
id: ID!
name: String!
}
type Specialist {
id: ID!
name: String!
}
type Appointment {
id: ID!
date: Date!
client: Client!
specialist: Specialist!
}
input BookAppointmentInput {
date: Date!
userId: ID!
specialistId: ID!
}
type BookAppointmentPayload {
appointment: Appointment!
}
type Mutation {
bookAppointment(input: BookAppointmentInput!): BookAppointmentPayload!
}
这是 GraphQL 模式的 TypeScript 表示
interface Client {
id: string
name: string
}
interface Specialist {
id: string
name: string
}
interface Appointment {
id: string
date: Date
client: Client
specialist: Specialist
}
interface BookAppointmentPayload {
appointment: Appointment
}
这里我定义了我的解析器对象
const resolvers = {
...
Mutation: {
bookAppointment: (parent, args, context, info): BookAppointmentPayload => {
return {
appointment: {
id: '1',
date: new Date(),
clientId: '1', // This prop doesn't exist in the TypeScript interface of Appointment, but is required for the field-level resolver of a `client` prop
specialistId: '1' // This prop doesn't exist int he TypeScript interface of Appointment, but is required for the field-level resolver of a `specialist` prop
}
}
}
},
Appointment: {
client: (parent, args, context, info) => {
// I need a clientId (e.g. args.clientId) to fetch the client object from the database
return {
id: '1',
name: 'Jhon'
}
},
specialist: (parent, args, context, info) => {
// I need a specialistId (e.g. args.specialistId) to fetch the specialist object from the database
return {
id: '1',
name: 'Jane'
}
}
}
}
我想到的解决方案:
- 创建一个表示解析器“实际”return类型的接口
...
interface Apppointment {
id: string
date: Date
clientId: string // instead of `client: Client`
specialistId: string // instead of `specialist: Specialist`
}
interface BookAppointmentPayload {
appointment: Appointment
}
...
但这并不反映 GraphQL 类型。此外,graphql-generator
等工具生成的类型包含应包含在响应中的实际对象,而不是字段级解析器将使用的字段。 (我错了吗?)
我想知道你是如何解决这个问题的?
我已经对这个问题进行了大量调查,并得出以下结论。
Create an interface which represent "actual" return type of the resolver
大多数时候 return 类型的解析器函数(在 JavaScript 中)与 GraphQL SDL
中声明的类型不匹配
例如,
# GraphQL SDL
type Appointment {
id: String!
client: User!
specialist: Specialist!
}
type BookAppointmentInput { ... }
type BookAppointmentPayload {
appointment: Appointment!
}
type Mutation {
bookAppointment: (input: BookAppointmentInput!): BookAppointmentPayload!
}
interface AppointmentDatabaseEntity {
id: string
clientId: string // In GraphQL-world this prop is an object, but not in JS. Use this prop in field-level resolver to fetch entire object
specialistId: string // In GraphQL-world this prop is an object, but not in JS. Use this prop in field-level resolver to fetch entire object
}
interface BookAppointmentPayload {
appointment: AppointmentDatabaseEntity // The return type SHOULDN'T be equal to the GraphQL type (Appointment)
}
const resolvers = {
Mutatiuon: {
bookAppointment: (parent, args, context, info) => {
const appointment = { id: '1', specialistId: '1', clientId: '1' }
return {
id: appointment.id,
specialistId: appointment.specialistId, // Pass this prop to the child resolvers to fetch entire object
clientId: appointment.clientId // Pass this prop to the child resolvers to fetch entire object
}
}
},
Appointment: {
client: (parent: AppointmentDatabaseEntity, args, context, info) => {
const client = database.getClient(parent.clientId) // Fetching entire object by the property from the parent object
return {
id: client.id,
name: client.name,
email: client.email
}
},
specialist: (parent: AppointmentDatabaseEntity, args, context, info) => {
const specialist = database.getSpecialist(parent.specialistId) // Fetching entire object by the property from the parent object
return {
id: specialist.id,
name: specialist.name,
email: specialist.email
}
}
}
}
But this doesn't reflect the GraphQL type
据我了解还可以
Also tools like graphql-generator generates the type with actual objects that should be included in the response, not the fields that are going to be used by field-level resolvers. (Am I wrong?)
是的。我错了。 graphql-generator
有一个配置文件,可用于将默认生成的类型替换为您希望解析器 return 生成的类型。此选项称为 mappers
。
plugins
config:
mappers:
User: ./my-models#UserDbObject # User is GraphQL object, which will be replaced with UserDbObject
Book: ./my-modelsBook # Same rule goes here
我不想详细介绍如何配置和使用它,但您可以查看帮助我理解它的链接
- Documentation(查看映射器章节)
- 很好的解释
杰米·巴顿 (YouTube)
如果您不同意我的结论,或者您对如何处理它有更好的理解,请随时发表评论
我遇到了一个我自己无法解决的问题。让我们一步一步来指出问题。
- 我有一个突变
bookAppointment
,它 return 是一个Appointment
对象 - GraphQL 模式表示该对象应该 return 4 个属性:
id
、date
、specialist
、client
. - 为了遵循 GraphQL 风格,
specialist
和client
属性应该是字段级解析器 - 要获取此对象,我需要将
specialistId
传递给专家字段级解析器,并将clientId
传递给客户端字段级解析器。 - 此时问题出现了
client
、specialist
的字段级解析器期望根突变 returns 字段,如clientId
和specialistId
。但是由该语法生成的 GraphQL 语法和类型不包含此道具(有意义)。- 如何“扩展”解析器的 return 类型及其
interface BookAppointmentPayload
让我和 TypeScript 开心?
这是我的 GraphQL 架构
type Client {
id: ID!
name: String!
}
type Specialist {
id: ID!
name: String!
}
type Appointment {
id: ID!
date: Date!
client: Client!
specialist: Specialist!
}
input BookAppointmentInput {
date: Date!
userId: ID!
specialistId: ID!
}
type BookAppointmentPayload {
appointment: Appointment!
}
type Mutation {
bookAppointment(input: BookAppointmentInput!): BookAppointmentPayload!
}
这是 GraphQL 模式的 TypeScript 表示
interface Client {
id: string
name: string
}
interface Specialist {
id: string
name: string
}
interface Appointment {
id: string
date: Date
client: Client
specialist: Specialist
}
interface BookAppointmentPayload {
appointment: Appointment
}
这里我定义了我的解析器对象
const resolvers = {
...
Mutation: {
bookAppointment: (parent, args, context, info): BookAppointmentPayload => {
return {
appointment: {
id: '1',
date: new Date(),
clientId: '1', // This prop doesn't exist in the TypeScript interface of Appointment, but is required for the field-level resolver of a `client` prop
specialistId: '1' // This prop doesn't exist int he TypeScript interface of Appointment, but is required for the field-level resolver of a `specialist` prop
}
}
}
},
Appointment: {
client: (parent, args, context, info) => {
// I need a clientId (e.g. args.clientId) to fetch the client object from the database
return {
id: '1',
name: 'Jhon'
}
},
specialist: (parent, args, context, info) => {
// I need a specialistId (e.g. args.specialistId) to fetch the specialist object from the database
return {
id: '1',
name: 'Jane'
}
}
}
}
我想到的解决方案:
- 创建一个表示解析器“实际”return类型的接口
...
interface Apppointment {
id: string
date: Date
clientId: string // instead of `client: Client`
specialistId: string // instead of `specialist: Specialist`
}
interface BookAppointmentPayload {
appointment: Appointment
}
...
但这并不反映 GraphQL 类型。此外,graphql-generator
等工具生成的类型包含应包含在响应中的实际对象,而不是字段级解析器将使用的字段。 (我错了吗?)
我想知道你是如何解决这个问题的?
我已经对这个问题进行了大量调查,并得出以下结论。
Create an interface which represent "actual" return type of the resolver
大多数时候 return 类型的解析器函数(在 JavaScript 中)与 GraphQL SDL
中声明的类型不匹配例如,
# GraphQL SDL
type Appointment {
id: String!
client: User!
specialist: Specialist!
}
type BookAppointmentInput { ... }
type BookAppointmentPayload {
appointment: Appointment!
}
type Mutation {
bookAppointment: (input: BookAppointmentInput!): BookAppointmentPayload!
}
interface AppointmentDatabaseEntity {
id: string
clientId: string // In GraphQL-world this prop is an object, but not in JS. Use this prop in field-level resolver to fetch entire object
specialistId: string // In GraphQL-world this prop is an object, but not in JS. Use this prop in field-level resolver to fetch entire object
}
interface BookAppointmentPayload {
appointment: AppointmentDatabaseEntity // The return type SHOULDN'T be equal to the GraphQL type (Appointment)
}
const resolvers = {
Mutatiuon: {
bookAppointment: (parent, args, context, info) => {
const appointment = { id: '1', specialistId: '1', clientId: '1' }
return {
id: appointment.id,
specialistId: appointment.specialistId, // Pass this prop to the child resolvers to fetch entire object
clientId: appointment.clientId // Pass this prop to the child resolvers to fetch entire object
}
}
},
Appointment: {
client: (parent: AppointmentDatabaseEntity, args, context, info) => {
const client = database.getClient(parent.clientId) // Fetching entire object by the property from the parent object
return {
id: client.id,
name: client.name,
email: client.email
}
},
specialist: (parent: AppointmentDatabaseEntity, args, context, info) => {
const specialist = database.getSpecialist(parent.specialistId) // Fetching entire object by the property from the parent object
return {
id: specialist.id,
name: specialist.name,
email: specialist.email
}
}
}
}
But this doesn't reflect the GraphQL type
据我了解还可以
Also tools like graphql-generator generates the type with actual objects that should be included in the response, not the fields that are going to be used by field-level resolvers. (Am I wrong?)
是的。我错了。 graphql-generator
有一个配置文件,可用于将默认生成的类型替换为您希望解析器 return 生成的类型。此选项称为 mappers
。
plugins
config:
mappers:
User: ./my-models#UserDbObject # User is GraphQL object, which will be replaced with UserDbObject
Book: ./my-modelsBook # Same rule goes here
我不想详细介绍如何配置和使用它,但您可以查看帮助我理解它的链接
- Documentation(查看映射器章节)
- 很好的解释 杰米·巴顿 (YouTube)
如果您不同意我的结论,或者您对如何处理它有更好的理解,请随时发表评论