typescript - Como eu implemento um resolvedor para um campo abstrato do GraphQL cujo tipo é determinado pelo seu campo irmão?
Problema
Estou usando o apollo-server para implementar um servidor GraphQL que fica em cima de uma API REST.
A API REST retorna uma entidade cujo campo de nível raiz determina a forma de outro campo de nível raiz. ou seja...
{
"entityType": "foo",
"entityMetadata": { /* based on value of `entityType` */ },
}
No esquema GraphQL, o campo cujo formato é determinado pelo valor de seu campo irmão é um tipo abstrato. Quero escrever um resolvedor do apollo-server para esse campo abstrato. Acho que devo usar __resolveType, mas estou tendo problemas para isso porque os argumentos que estou passando para o método __resolveType do campo abstrato não incluem nenhuma forma de eu referenciar o valor do campo irmão.
Então, o que eu faço?
Outro Exemplo
Usarei o seguinte esquema GraphQL para ilustrar meu problema.
type Character {
job: String!
weapon: Weapon
}
interface Weapon {
yearMade: Number
}
type LightSaber implements Weapon {
yearMade: Number
color: String
}
type Blaster implements Weapon {
yearMade: Number
ammo: Number
}
type Query {
searchCharacters: [Character]
}
Com este esquema, eu tenho um tipo Character com os campos job e weapon. O trabalho desse Character determina o tipo de arma que ele usará.
Então, se o Character tiver o job "jedi", sua arma deverá ser do tipo LightSaber com um campo exclusivo color. Se o Character tiver o job "stormtropper", sua arma será do tipo Blaster com um campo exclusivo ammo.
Quero buscar todos os personagens disponíveis com a consulta searchCharacters e retornar os dados do personagem e os dados exclusivos da arma dependendo do tipo da arma.
query {
searchCharacters {
type
weapon {
yearMade
... on LightSaber {
color
}
... on Blaster {
ammo
}
}
}
}
Isso funciona bem para todos os dados de caractere, mas encontrei um obstáculo quando tentei resolver o tipo da weapon. Até agora, eu tenho isto:
const Weapon = {
__resolveType: async ({ yearMade }, context, info): "LightSaber" | "Blaster" => {
// how do I resolve whether it's a LightSaber or Blaster?
},
};
Originalmente, eu estava sob a impressão de que o campo parent da função __resolveType() seria o objeto Character resolvido (ou seja, { type: 'jedi', weapon: { yearMade: 8, ...otherWeaponProperties }, ...otherProperties }), mas na verdade é apenas o objeto atual do campo (ou seja, { yearMade: 8, ...otherWeaponProperties }).
Se eu pudesse acessar o campo type do Character, eu seria capaz de determinar qual é o tipo de arma, mas não posso.
Soluções Potenciais
Eu vi duas soluções para este problema.
Solução #1
A primeira solução foi anexar o valor de job do Character ao objeto weapon (ou seja, { job: 'jedi', yearMade: 8, ...otherWeaponProperties }) e, dessa forma, a função __resolveType para o campo Weapon tem acesso ao valor de job em seu primeiro argumento.
const resolvers = {
Query: {
searchCharacters: async () => {
return api.getCharacters().map(apiCharacterData => {
return {
// --- start of important change ---
...apiCharacterData,
weapon: {
...apiCharacterData.weapon
job: apiCharacterData.job,
}
// --- end of important change ---
};
});
},
},
Weapon: {
// --- first arg is object `{ job, ...apiCharacterData.weapon }`
__resolveType: ({ job }) => {
return job === 'jedi' ? 'LightSaber' : 'Blaster';
}
},
}
Solução #2
A segunda solução foi alterar o esquema subjacente de modo que, em vez de um tipo generalizado Character, existam tipos Jedi e Stormtrooper que implementam uma interface Character, e esses tipos detalham explicitamente o tipo que teriam para o campo weapon.
interface BaseCharacter {
job: String
weapon: Weapon
}
type Jedi implements BaseCharacter {
job: String
weapon: LightSaber
}
type Stormtrooper implements BaseCharacter {
job: String
weapon: Blaster
}
union Character = Jedi | Stormtrooper
type Query {
searchCharacters: [Character]
}
query {
searchCharacters {
... on Character {
job
weapon {
yearMade
... on LightSaber {
color
}
... on Blaster {
ammo
}
}
}
}
}
E então eu realmente só preciso adicionar __resolveType ao tipo de união Character e não ao tipo Weapon.
const Character = {
__resolveType: ({ job }) => {
return job === 'jedi' ? 'LightSaber' : 'Blaster';
},
};
Socorro!
Entre as duas soluções potenciais, a solução #2 parece um passo na direção certa. O maior ganho da solução #2 em relação à solução #1 é que eu não preciso mapear os dados da API e deixo os resolvers fazerem o que os resolvers precisam fazer por entidade.
Mas a natureza aninhada da consulta e a inclusão do fragmento inline ... on BaseCharacter me fazem sentir que estou entendendo mal como escrever corretamente um esquema GraphQL que modele o que estou tentando alcançar.
Tenho quase certeza de que este não é um problema incomum, mas estou tendo dificuldade em descobrir como resolvê-lo. Toda ajuda é apreciada.