如何在传递给解析器之前修改父节点?

How to modify parent node before it is passed to a resolver?

假设我们有这个 GraphQL 模式:

type Venue implements Node {
  country: Country!
  id: ID!
  name: String!
  nid: String!
  url: String!
}

此解析器支持:

// @flow

import type {
  VenueRecordType,
  ResolverType,
} from '../types';

const Venue: ResolverType<VenueRecordType> = {
  country: (node, parameters, context) => {
    return context.loaders.CountryByIdLoader.load(node.countryId);
  },
};

export default Venue;

我希望能够在解析器字段使用父节点/节点参数值之前对其进行修改。

据我阅读文档所知,实现此目的的唯一方法是实现和包装每个字段,例如

// @flow

import type {
  VenueRecordType,
  ResolverType,
} from '../types';

const createNodeDecorator = (fieldResolver) => {
  const updateNode = (node) => {
    // https://media0.giphy.com/media/12NUbkX6p4xOO4/giphy.gif
    return node;
  };

  return (parent, parameteres, context, info) => {
    return fieldResolver(updateNode(parent), parameteres, context, info);
  };
};

const Venue: ResolverType<VenueRecordType> = {
  country: createNodeDecorator((node, parameters, context) => {
    return context.loaders.CountryByIdLoader.load(node.countryId);
  }),
  id: createNodeDecorator((node) => {
    return node.id;
  }),
  name: createNodeDecorator((node) => {
    return node.name;
  }),
  nid: createNodeDecorator((node) => {
    return node.nid;
  }),
  url: createNodeDecorator((node) => {
    return node.url;
  }),
};

export default Venue;

有没有更好的方法?

理想情况下,我会在使用解析器之前调用一个 __load 挂钩,例如

const Venue: ResolverType<VenueRecordType> = {
  __load: (parent, parameteres, context, info, next) => {
    next(parent, parameteres, context, info);
  },
  country: (node, parameters, context) => {
    return context.loaders.CountryByIdLoader.load(node.countryId);
  },
};

但这(据我所知)并不存在。

如何在传递给解析器之前修改父节点?

实现此目的的一种方法是将修改节点的逻辑向上移动一个级别。例如,给定如下查询类型:

type Query {
  venues: [Venue!]!
}

我们可以在解析器中执行以下操作:

const resolvers: {
  Query: {
    venues: async (root, args, context) => {
      const venues = await context.loaders.VenueLoader.load()
      return venues.map(magic)
    }
  }
}

这行得通,但这意味着您必须在 returns 一个地点或地点列表的任何解析器中复制逻辑,这是乏味且容易出错的。如果您已经在使用加载器,我会把这个逻辑移到加载器本身中,然后就此结束。

但是,我们可以更进一步,也可以使用架构指令。例如,如果您想为不同的类型重用相同的逻辑,或者出于某些奇怪的原因,您只想修改某些字段的父级,这将很有帮助。下面是一个示例,可让您将指令应用于类型或单个字段:

class MagicDirective extends SchemaDirectiveVisitor {
  visitFieldDefinition(field) {
    const { resolve = defaultFieldResolver } = field
    field.resolve = function (source, args, context, info) {
      return resolve.apply(this, [magic(source), args, context, info])
    }
  }
  visitObject(object) {
    const fieldMap = object.getFields()
    for (const fieldName in fieldMap) {
      this.visitFieldDefinition(fieldMap[fieldName])
    }
  }
}

然后只需将指令作为 schemaDirectives 的一部分传递给您的 ApolloServer 配置,并将其包含在您的类型定义中:

directive @magic on FIELD_DEFINITION | OBJECT