typescript - ¿Cómo implemento un resolutor para un campo abstracto de GraphQL cuyo tipo está determinado por su campo hermano?
Problema
Estoy usando apollo-server para implementar un servidor GraphQL que se encuentra encima de una API REST.
La API REST devuelve una entidad cuyo campo de nivel raíz determina la forma de otro campo de nivel raíz. es decir...
{
"entityType": "foo",
"entityMetadata": { /* based on value of `entityType` */ },
}
En el esquema de GraphQL, el campo cuya forma está determinada por el valor de su campo hermano, es un tipo abstracto. Quiero escribir un resolvedor de apollo-server para ese campo abstracto. Creo que debería usar __resolveType, pero estoy teniendo problemas para hacerlo porque los argumentos que estoy pasando al método __resolveType del campo abstracto no incluyen ninguna forma de que pueda referenciar el valor del campo hermano.
Entonces, ¿qué hago?
Otro ejemplo
Usaré el siguiente esquema de GraphQL para ilustrar mi 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]
}
Con este esquema, tengo un tipo Character con campos job y weapon. El trabajo de ese Character determina el tipo de arma que usarán.
Entonces, si el Character tiene como job "jedi", su arma debería ser del tipo LightSaber con un campo exclusivo color. Si el Character tiene como job "stormtropper", su arma será del tipo Blaster con un campo exclusivo ammo.
Quiero buscar todos los personajes disponibles con la consulta searchCharacters y devolver sus datos de personaje y los datos exclusivos de su arma dependiendo del tipo de arma.
query {
searchCharacters {
type
weapon {
yearMade
... on LightSaber {
color
}
... on Blaster {
ammo
}
}
}
}
Esto funciona bien para todos los datos de caracteres, pero me encontré con un obstáculo cuando intento resolver el tipo de arma. Hasta ahora, tengo esto:
const Weapon = {
__resolveType: async ({ yearMade }, context, info): "LightSaber" | "Blaster" => {
// how do I resolve whether it's a LightSaber or Blaster?
},
};
Originalmente, tenía la impresión de que el campo parent de la función __resolveType() sería el objeto Character resuelto (es decir, { type: 'jedi', weapon: { yearMade: 8, ...otherWeaponProperties }, ...otherProperties }), pero en realidad es solo el objeto actual del campo (es decir, { yearMade: 8, ...otherWeaponProperties }).
Si pudiera acceder al campo type del Character, podría determinar cuál es el tipo de arma, pero no puedo.
Soluciones potenciales
He visto dos soluciones a este problema.
Solución #1
La primera solución fue añadir el valor de job del Character al objeto weapon (es decir, { job: 'jedi', yearMade: 8, ...otrasPropiedadesDelArma }) y de esa manera la función __resolveType para el campo Weapon tiene acceso al valor de job en su primer 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';
}
},
}
Solución #2
La segunda solución fue cambiar el esquema subyacente de modo que, en lugar de un tipo generalizado Character, existan tipos Jedi y Stormtrooper que implementen una interfaz Character, y estos tipos detallan explícitamente el tipo que tendrían para el 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
}
}
}
}
}
Y luego realmente solo necesito agregar __resolveType al tipo de unión Character y no al tipo Weapon.
const Character = {
__resolveType: ({ job }) => {
return job === 'jedi' ? 'LightSaber' : 'Blaster';
},
};
¡Ayuda!
Entre las dos posibles soluciones, la solución #2 parece un paso en la dirección correcta. La mayor ventaja de la solución #2 sobre la solución #1 es que no tengo que mapear los datos de la API y dejo que los resolutores hagan lo que los resolutores necesitan hacer por cada entidad.
Pero la naturaleza anidada de la consulta y la inclusión del fragmento en línea ... on BaseCharacter me hace sentir que estoy entendiendo mal cómo escribir correctamente un esquema de GraphQL que modele lo que estoy tratando de lograr.
Estoy bastante seguro de que este no es un problema poco común, pero estoy teniendo dificultades para averiguar cómo solucionarlo. Se agradece toda ayuda.