Graphql, как спроектировать тип ввода, когда есть мутации "добавить" и "обновить"?

Вот мои требования:

  1. "добавить" мутацию, каждое поле (или называемое скаляром) входного типа BookInput должно иметь дополнительные модификаторы типа "!" для проверки ненулевого значения. Это означает, что когда я добавляю книгу, в аргументе должны быть поля title и author, например {title: "angular", author: "novaline"}.

  2. "обновить" мутацию, я хочу обновить часть полей книги, не хочу обновлять всю книгу (документ MongoDB, и я не хочу, чтобы интерфейсный интерфейс передавал серверу graphql аргумент о мутации всей большой книги для экономия полосы пропускания). Это означает, что аргумент книги может быть {title: "angular"} или {title: "angular", author: "novaline"}.

Вот мои определения типов:

const typeDefs = `
  input BookInput {
    title: String!
    author: String!
  }

  type Book {
    id: ID!
    title: String!
    author: String!
  }

  type Query {
    books: [Book!]!
  }

  type Mutation{
    add(book: BookInput!): Book
    update(id: String!, book: BookInput!): Book
  }
`;

На данный момент "добавить" мутацию работает нормально. Но мутация "update" не может пройти проверку на ненулевое значение, если я передаю аргумент {title: "angular"}.

Вот мутация, которая не проходит проверку на ненулевое значение, отсутствие поля «автор» для типа ввода BookInput.

mutation {
  update(id: "1", book: {title: "angular"}) {
    id
    title
    author
  }
}

Итак, graphql выдаст мне ошибку:

{
  "errors": [
    {
      "message": "Field BookInput.author of required type String! was not provided.",
      "locations": [
        {
          "line": 2,
          "column": 24
        }
      ]
    }
  ]
}

Как мне спроектировать входной тип BookInput? Не хочу определять addBookInput и updateBookInput. Это продублировано.

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Что такое Apollo Client и зачем он нужен?
Что такое Apollo Client и зачем он нужен?
Apollo Client - это полнофункциональный клиент GraphQL для JavaScript-приложений, который упрощает получение, управление и обновление данных в...
12
0
4 673
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

const typeDefs = `
  input AddBookInput {
    title: String!
    author: String!
  }

  input UpdateBookInput {
    # NOTE: all fields are optional for the update input 
    title: String
    author: String
  }

  type Book {
    id: ID!
    title: String!
    author: String!
  }

  type Query {
    books: [Book!]!
  }

  type Mutation{
    addBook(input: AddBookInput!): Book
    updateBook(id: String!, input: UpdateBookInput!): Book
  }
`;

Некоторым людям также нравится включать идентификатор обновления как часть ввода обновления:

const typeDefs = `
  input AddBookInput {
    title: String!
    author: String!
  }

  input UpdateBookInput {
    # NOTE: all fields, except the 'id' (the selector), are optional for the update input 
    id: String!
    title: String
    author: String
  }

  type Book {
    id: ID!
    title: String!
    author: String!
  }

  type Query {
    books: [Book!]!
  }

  type Mutation{
    addBook(input: AddBookInput!): Book
    updateBook(input: UpdateBookInput!): Book
  }
`;

Наконец, вы можете захотеть использовать тип «полезная нагрузка» для типа возвращаемого значения - для дополнительной гибкости (дает вам больше возможностей для маневра, чтобы изменить тип возвращаемого значения позже, не нарушая ваш API):

const typeDefs = `
  input AddBookInput {
    title: String!
    author: String!
  }

  input UpdateBookInput {
    # NOTE: all fields, except the 'id' (the selector), are optional for the update input 
    id: String!
    title: String
    author: String
  }

  type Book {
    id: ID!
    title: String!
    author: String!
  }

  type AddBookPayload {
    book: Book!
  }

  type UpdateBookPayload {
    book: Book!
  }

  type Query {
    books: [Book!]!
  }

  type Mutation{
    addBook(input: AddBookInput!): AddBookPayload!
    updateBook(input: UpdateBookInput!): UpdateBookPayload!
  }
`;

Надеюсь это поможет!

Вот мое решение: я пишу вспомогательную функцию для создания типа «создать» input и «обновить» тип input.

const { parse } = require('graphql');

/**
 * schema definition helper function - dynamic generate graphql input type
 *
 * @author https://github.com/mrdulin
 * @param {string} baseSchema
 * @param {object} options
 * @returns {string}
 */
function generateInputType(baseSchema, options) {
  const inputTypeNames = Object.keys(options);
  const schema = inputTypeNames
    .map(inputTypeName => {
      const { validator } = options[inputTypeName];
      const validatorSchema = Object.keys(validator)
        .map(field => `${field}: ${validator[field]}\n`)
        .join(' ');

      return `
      input ${inputTypeName} {
        ${baseSchema}
        ${validatorSchema}
      }
    `;
    })
    .join(' ')
    .replace(/^\s*$(?:\r\n?|\n)/gm, '');

  parse(schema);
  return schema;
}

schema.js:

${generateInputType(
  `
  campaignTemplateNme: String
`,
  {
    CreateCampaignTemplateInput: {
      validator: {
        channel: 'ChannelUnionInput!',
        campaignTemplateSharedLocationIds: '[ID]!',
        campaignTemplateEditableFields: '[String]!',
        organizationId: 'ID!',
      },
    },
    UpdateCampaignTemplateInput: {
      validator: {
        channel: 'ChannelUnionInput',
        campaignTemplateSharedLocationIds: '[ID]',
        campaignTemplateEditableFields: '[String]',
        organizationId: 'ID',
      },
    },
  },
)}

Другие вопросы по теме