GraphQL - возвращаемый вычисленный тип зависит от аргумента

Обзор (упрощенный):

На моем сервере NodeJS я реализовал следующую схему GraphQL:

type Item {
  name: String,
  value: Float
}


type Query {
  items(names: [String]!): [Item]
}

Затем клиентский запрос передает массив имен в качестве аргумента:

{
  items(names: ["total","active"] ) {
    name
    value
  }
}

Бэкэнд-API запрашивает базу данных mysql для полей total активных" (столбцы моей таблицы БД) и уменьшает ответ так:

[{"name":"total" , value:100} , {"name":"active" , value:50}]

Я бы хотел, чтобы мой API-интерфейс graphQL поддерживал "отношение" Item, I.E: Я хотел бы отправить следующий запрос:

{
  items(names: ["ratio"] ) {
    name
    value
  }
}

или

{
  items(names: ["total","active","ratio"] ) {
    name
    value
  }
}

И верните active/total как результат вычисления этого нового поля ([{"name":"ratio" , value:0.5}]). Каким будет общий способ обработки поля "по-разному?

Должен ли он быть новым типом в моей схеме или я должен реализовать логику в редукторе?

Ответ 1

Ответ на Joe (добавьте {"name":"ratio" , value:data.active/data.total} к результату после получения результата из базы данных), сделав это без внесения каких-либо изменений схемы.

Как альтернативный метод или более элегантный способ сделать это в GraphQL, имена полей могут быть указаны в самом типе, а не в качестве аргументов. И вычислить ratio, написав резольвер.

Итак, схема GraphQL будет:

Item {
  total: Int,
  active: Int,
  ratio: Float
}

type Query {
  items: [Item]
}

Клиент указывает поля:

{
  items {
    total 
    active 
    ratio
  }
}

И ratio можно рассчитать внутри резольвера.

Вот код:

const express = require('express');
const graphqlHTTP = require('express-graphql');
const { graphql } = require('graphql');
const { makeExecutableSchema } = require('graphql-tools');
const getFieldNames = require('graphql-list-fields');

const typeDefs = `
type Item {
  total: Int,
  active: Int,
  ratio: Float
}

type Query {
  items: [Item]
}
`;

const resolvers = {
  Query: {
    items(obj, args, context, info) {
      const fields = getFieldNames(info) // get the array of field names specified by the client
      return context.db.getItems(fields)
    }
  },
  Item: {
    ratio: (obj) => obj.active / obj.total // resolver for finding ratio
  }
};

const schema = makeExecutableSchema({ typeDefs, resolvers });

const db = {
  getItems: (fields) => // table.select(fields)
    [{total: 10, active: 5},{total: 5, active: 5},{total: 15, active: 5}] // dummy data
}
graphql(
  schema, 
  `query{
    items{
      total,
      active,
      ratio
    }
  }`, 
  {}, // rootValue
  { db } // context
).then(data => console.log(JSON.stringify(data)))

Ответ 2

Вы можете настроить свою функцию распознавания, чтобы использовать второй параметр - аргументы - чтобы увидеть, находится ли отношение "имя" в вашем массиве имен:

resolve: (root, { names }, context, fieldASTs) => {
    let arrayOfItems;
    // Contact DB, populate arrayOfItems with your total / active items

    // if 'ratio' is within your name array argument, calculate it:
    if (names.indexOf("ratio") > -1){
        // Calculate ratio
        arrayOfItems.push({ name: "ratio", value: calculatedRatio });
    }

    return(arrayOfItems);
}

Надеюсь, я правильно понял ваш вопрос.