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

时间:2019-07-27 12:39:06

标签: graphql apollo apollo-server

假设我们具有以下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);
  },
};

但据我所知,这不存在。

如何在将父节点传递给解析器之前对其进行修改?

1 个答案:

答案 0 :(得分:1)

执行此操作的一种方法是将用于修改节点的逻辑上移。例如,给定查询类型,例如:

type Query {
  venues: [Venue!]!
}

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

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

这可行,但是这意味着您必须在返回Venue或Venues列表的任何解析器中复制逻辑,这很繁琐且容易出错。如果您已经在使用加载程序,则只需将该逻辑移动到加载程序本身内,并称之为一天。

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

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])
    }
  }
}

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

directive @magic on FIELD_DEFINITION | OBJECT