如何用graphql-tools将远程GraphQL Schema的Viewer字段移至Query类型?
解决远程GraphQL Schema中Viewer字段迁移到Query类型的方案
我之前刚好处理过类似的Schema转换需求,其实核心思路是先获取Viewer实例,再以它为父对象委托调用对应的字段,下面给你一步步拆解可行的实现方式:
核心思路
远程Schema的Viewer字段依赖Viewer类型作为父节点,而delegateToSchema默认是从根类型(Query/Mutation)开始委托,所以我们需要分两步走:
- 先通过远程Schema的根查询获取Viewer实例
- 将这个Viewer实例作为父对象,委托调用它身上的目标字段
具体实现步骤
1. 先获取并封装远程Schema
首先我们需要先拿到远程Schema的可执行实例,这里用@graphql-tools/wrap的工具来处理:
const { introspectSchema, makeRemoteExecutableSchema } = require('@graphql-tools/wrap'); const { HttpLink } = require('@apollo/client'); const fetch = require('cross-fetch'); async function getRemoteSchema() { // 替换成你的远程GraphQL API地址 const link = new HttpLink({ uri: 'https://your-remote-api.com/graphql', fetch }); const remoteSchema = await introspectSchema(link); return makeRemoteExecutableSchema({ schema: remoteSchema, link, }); }
2. 定义本地Schema扩展
我们需要在本地Query类型里新增要迁移的字段,和远程Viewer上的字段保持一致的参数和返回类型:
# 本地扩展Schema,把Viewer的字段加到Query上 extend type Query { user(id: ID!): User latestPosts(limit: Int = 10): [Post!]! # 其他你需要迁移的Viewer字段 }
3. 编写自定义解析器实现委托
这里我们用delegateToSchema工具,先获取Viewer实例,再以它为根对象委托调用目标字段:
const { delegateToSchema } = require('@graphql-tools/delegate'); const { mergeSchemas } = require('@graphql-tools/schema'); async function buildFinalSchema() { const remoteSchema = await getRemoteSchema(); const resolvers = { Query: { // 迁移Viewer.user字段到Query user: async (_, args, context, info) => { // 第一步:获取远程Viewer实例 const viewer = await delegateToSchema({ schema: remoteSchema, operation: 'query', fieldName: 'viewer', // 远程Query上获取Viewer的字段名 context, info, }); // 第二步:以Viewer为父对象,委托调用它的user字段 return delegateToSchema({ schema: remoteSchema, operation: 'query', fieldName: 'user', // Viewer上的目标字段名 args, context, info, rootValue: viewer, // 指定父对象为刚才获取的Viewer parentType: remoteSchema.getType('Viewer'), // 明确父类型是Viewer }); }, // 迁移Viewer.latestPosts字段到Query latestPosts: async (_, args, context, info) => { const viewer = await delegateToSchema({ schema: remoteSchema, operation: 'query', fieldName: 'viewer', context, info, }); return delegateToSchema({ schema: remoteSchema, operation: 'query', fieldName: 'latestPosts', args, context, info, rootValue: viewer, parentType: remoteSchema.getType('Viewer'), }); }, }, }; // 合并远程Schema和本地扩展Schema return mergeSchemas({ schemas: [remoteSchema, localExtensionSchema], // localExtensionSchema是你定义的扩展Schema resolvers, }); }
4. 封装通用工具函数简化代码
如果要迁移多个字段,重复写获取Viewer的逻辑会很冗余,我们可以封装一个通用函数:
async function delegateFromViewer(fieldName, args, context, info, remoteSchema) { const viewer = await delegateToSchema({ schema: remoteSchema, operation: 'query', fieldName: 'viewer', context, info, }); return delegateToSchema({ schema: remoteSchema, operation: 'query', fieldName, args, context, info, rootValue: viewer, parentType: remoteSchema.getType('Viewer'), }); } // 然后解析器就可以简化成: const resolvers = { Query: { user: (_, args, context, info) => delegateFromViewer('user', args, context, info, remoteSchema), latestPosts: (_, args, context, info) => delegateFromViewer('latestPosts', args, context, info, remoteSchema), }, };
关键注意点
- 确保远程Schema的Query类型确实存在
viewer字段(如果没有,需要确认远程API如何获取Viewer实例,比如是否需要通过认证上下文自动关联) - 字段的参数、返回类型要和远程Viewer上的字段完全匹配,避免类型校验错误
- 认证信息要正确通过
context传递给远程Schema,否则可能无法获取到合法的Viewer实例
内容的提问来源于stack exchange,提问作者Andrew Safigan
相关产品推荐
相关产品推荐

